diff --git a/chainstate/src/detail/ban_score.rs b/chainstate/src/detail/ban_score.rs index 6c974bc8cc..78b2b9a975 100644 --- a/chainstate/src/detail/ban_score.rs +++ b/chainstate/src/detail/ban_score.rs @@ -209,6 +209,7 @@ impl BanScore for tx_verifier::error::TimelockContextError { fn ban_score(&self) -> u32 { match self { Self::TimelockedAccount => 0, + Self::MissingUtxoSource => 0, Self::HeaderLoad(e, _) => e.ban_score(), } } diff --git a/chainstate/src/detail/error_classification.rs b/chainstate/src/detail/error_classification.rs index 103b3ee6b7..8a49e117b8 100644 --- a/chainstate/src/detail/error_classification.rs +++ b/chainstate/src/detail/error_classification.rs @@ -404,6 +404,7 @@ impl BlockProcessingErrorClassification for tx_verifier::error::TimelockContextE fn classify(&self) -> BlockProcessingErrorClass { match self { Self::TimelockedAccount => BlockProcessingErrorClass::General, + Self::MissingUtxoSource => BlockProcessingErrorClass::General, Self::HeaderLoad(e, _) => e.classify(), } } diff --git a/chainstate/tx-verifier/src/transaction_verifier/input_check/mod.rs b/chainstate/tx-verifier/src/transaction_verifier/input_check/mod.rs index 4880ff1f7e..a34b4e65ba 100644 --- a/chainstate/tx-verifier/src/transaction_verifier/input_check/mod.rs +++ b/chainstate/tx-verifier/src/transaction_verifier/input_check/mod.rs @@ -20,7 +20,8 @@ use common::{ chain::{ block::timestamp::BlockTimestamp, signature::{inputsig::InputWitness, DestinationSigError, Transactable}, - ChainConfig, GenBlock, TxInput, TxOutput, + tokens::TokenId, + ChainConfig, DelegationId, Destination, GenBlock, PoolId, TxInput, TxOutput, }, primitives::{BlockHeight, Id}, }; @@ -33,6 +34,8 @@ use crate::TransactionVerifierStorageRef; use super::TransactionSourceForConnect; +pub mod signature_only_check; + pub type HashlockError = mintscript::checker::HashlockError; pub type TimelockError = mintscript::checker::TimelockError; pub type ScriptError = @@ -61,6 +64,22 @@ impl From } } +impl From> + for InputCheckErrorPayload +{ + fn from( + value: mintscript::script::ScriptError, + ) -> Self { + let err = match value { + mintscript::script::ScriptError::Signature(e) => ScriptError::Signature(e), + mintscript::script::ScriptError::Timelock(_e) => unreachable!(), + mintscript::script::ScriptError::Hashlock(e) => ScriptError::Hashlock(e.into()), + mintscript::script::ScriptError::Threshold(e) => ScriptError::Threshold(e), + }; + Self::Verification(err) + } +} + #[derive(PartialEq, Eq, Clone, thiserror::Error, Debug)] #[error("Error verifying input #{input_num}: {error}")] pub struct InputCheckError { @@ -84,6 +103,9 @@ pub enum TimelockContextError { #[error("Timelocks on accounts not supported")] TimelockedAccount, + #[error("Utxo source is missing")] + MissingUtxoSource, + #[error("Loading ancestor header at height {1} failed: {0}")] HeaderLoad(chainstate_types::GetAncestorError, BlockHeight), } @@ -113,7 +135,11 @@ impl<'a> PerInputData<'a> { let err = InputCheckErrorPayload::MissingUtxo(outpoint.clone()); InputCheckError::new(input_num, err) })?; - InputInfo::Utxo { outpoint, utxo } + InputInfo::Utxo { + outpoint, + utxo_source: Some(utxo.source().clone()), + utxo: utxo.take_output(), + } } TxInput::Account(outpoint) => InputInfo::Account { outpoint }, TxInput::AccountCommand(_, command) => InputInfo::AccountCommand { command }, @@ -177,20 +203,45 @@ where TV: tokens_accounting::TokensAccountingView, OV: orders_accounting::OrdersAccountingView, { - type PoSAccounting = AV; - type Tokens = TV; - type Orders = OV; - - fn pos_accounting(&self) -> &Self::PoSAccounting { - &self.pos_accounting - } - - fn tokens(&self) -> &Self::Tokens { - &self.tokens_accounting + fn get_pool_decommission_destination( + &self, + pool_id: &PoolId, + ) -> Result, pos_accounting::Error> { + Ok(self + .pos_accounting + .get_pool_data(*pool_id)? + .map(|pool| pool.decommission_destination().clone())) + } + + fn get_delegation_spend_destination( + &self, + delegation_id: &DelegationId, + ) -> Result, pos_accounting::Error> { + Ok(self + .pos_accounting + .get_delegation_data(*delegation_id)? + .map(|delegation| delegation.spend_destination().clone())) + } + + fn get_tokens_authority( + &self, + token_id: &TokenId, + ) -> Result, tokens_accounting::Error> { + Ok( + self.tokens_accounting.get_token_data(token_id)?.map(|token| match token { + tokens_accounting::TokenData::FungibleToken(data) => data.authority().clone(), + }), + ) } - fn orders(&self) -> &Self::Orders { - &self.orders_accounting + fn get_orders_conclude_destination( + &self, + order_id: &common::chain::OrderId, + ) -> Result, orders_accounting::Error> { + Ok(self + .orders_accounting + .get_order_data(order_id)? + .map(|data| data.conclude_key().clone())) } } @@ -341,7 +392,11 @@ impl TimelockContext for InputVerifyContextTim fn source_height(&self) -> Result { match self.info() { - InputInfo::Utxo { outpoint: _, utxo } => match utxo.source() { + InputInfo::Utxo { + outpoint: _, + utxo: _, + utxo_source, + } => match utxo_source.as_ref().ok_or(TimelockContextError::MissingUtxoSource)? { utxo::UtxoSource::Blockchain(height) => Ok(*height), utxo::UtxoSource::Mempool => Ok(self.ctx.spending_height), }, @@ -353,7 +408,11 @@ impl TimelockContext for InputVerifyContextTim fn source_time(&self) -> Result { match self.info() { - InputInfo::Utxo { outpoint: _, utxo } => match utxo.source() { + InputInfo::Utxo { + outpoint: _, + utxo: _, + utxo_source, + } => match utxo_source.as_ref().ok_or(TimelockContextError::MissingUtxoSource)? { utxo::UtxoSource::Blockchain(height) => { let block_index_getter = |db_tx: &S, _: &ChainConfig, id: &Id| { db_tx.get_gen_block_index(id) diff --git a/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs b/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs new file mode 100644 index 0000000000..537c6b6d5a --- /dev/null +++ b/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs @@ -0,0 +1,178 @@ +// Copyright (c) 2024 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::convert::Infallible; + +use common::chain::{ + partially_signed_transaction::PartiallySignedTransaction, + signature::{inputsig::InputWitness, DestinationSigError, Transactable}, + tokens::TokenId, + ChainConfig, DelegationId, Destination, PoolId, SignedTransaction, TxInput, TxOutput, +}; +use mintscript::{ + script::ScriptError, translate::InputInfoProvider, InputInfo, SignatureContext, TranslateInput, +}; +use utils::ensure; + +use super::{InputCheckError, InputCheckErrorPayload, PerInputData}; + +struct InputVerifyContextSignature<'a, T> { + chain_config: &'a ChainConfig, + tx: &'a T, + outpoint_destination: &'a Destination, + inputs_utxos: &'a [Option<&'a TxOutput>], + input_num: usize, + input_data: PerInputData<'a>, +} + +impl SignatureContext for InputVerifyContextSignature<'_, T> { + type Tx = T; + + fn chain_config(&self) -> &ChainConfig { + self.chain_config + } + + fn transaction(&self) -> &Self::Tx { + self.tx + } + + fn input_utxos(&self) -> &[Option<&TxOutput>] { + self.inputs_utxos + } + + fn input_num(&self) -> usize { + self.input_num + } +} + +impl mintscript::translate::SignatureInfoProvider + for InputVerifyContextSignature<'_, T> +{ + fn get_pool_decommission_destination( + &self, + _pool_id: &PoolId, + ) -> Result, pos_accounting::Error> { + Ok(Some(self.outpoint_destination.clone())) + } + + fn get_delegation_spend_destination( + &self, + _delegation_id: &DelegationId, + ) -> Result, pos_accounting::Error> { + Ok(Some(self.outpoint_destination.clone())) + } + + fn get_tokens_authority( + &self, + _token_id: &TokenId, + ) -> Result, tokens_accounting::Error> { + Ok(Some(self.outpoint_destination.clone())) + } + + fn get_orders_conclude_destination( + &self, + _order_id: &common::chain::OrderId, + ) -> Result, orders_accounting::Error> { + Ok(Some(self.outpoint_destination.clone())) + } +} + +impl InputInfoProvider for InputVerifyContextSignature<'_, T> { + fn input_info(&self) -> &InputInfo { + self.input_data.input_info() + } + + fn witness(&self) -> &InputWitness { + self.input_data.witness() + } +} + +// Prevent BlockRewardTransactable from being used here +pub trait SignatureOnlyVerifiable {} +impl SignatureOnlyVerifiable for SignedTransaction {} +impl SignatureOnlyVerifiable for PartiallySignedTransaction {} + +pub fn verify_tx_signature( + chain_config: &ChainConfig, + outpoint_destination: &Destination, + tx: &T, + inputs_utxos: &[Option<&TxOutput>], + input_num: usize, +) -> Result<(), InputCheckError> { + let map_sig_err = |e: DestinationSigError| { + InputCheckError::new( + input_num, + ScriptError::::Signature(e), + ) + }; + + let inputs = tx + .inputs() + .ok_or(DestinationSigError::SignatureVerificationWithoutInputs) + .map_err(map_sig_err)?; + let input = inputs + .get(input_num) + .ok_or(DestinationSigError::InvalidInputIndex( + input_num, + inputs.len(), + )) + .map_err(map_sig_err)?; + + ensure!( + inputs.len() == inputs_utxos.len(), + map_sig_err(DestinationSigError::InvalidUtxoCountVsInputs( + inputs_utxos.len(), + inputs.len() + )) + ); + + let input_info = match input { + TxInput::Utxo(outpoint) => { + let utxo = inputs_utxos[input_num] + .ok_or(InputCheckError::new( + input_num, + InputCheckErrorPayload::MissingUtxo(outpoint.clone()), + ))? + .clone(); + InputInfo::Utxo { + outpoint, + utxo, + utxo_source: None, + } + } + TxInput::Account(outpoint) => InputInfo::Account { outpoint }, + TxInput::AccountCommand(_, command) => InputInfo::AccountCommand { command }, + }; + let input_witness = tx.signatures()[input_num] + .clone() + .ok_or(DestinationSigError::SignatureNotFound) + .map_err(map_sig_err)?; + + let input_data = PerInputData::new(input_info, input_witness); + let context = InputVerifyContextSignature { + chain_config, + tx, + outpoint_destination, + inputs_utxos, + input_num, + input_data, + }; + let script = mintscript::translate::SignatureOnlyTx::translate_input(&context) + .map_err(|e| InputCheckError::new(input_num, e))?; + let mut checker = mintscript::ScriptChecker::signature_only(context); + script.verify(&mut checker).map_err(|e| InputCheckError::new(input_num, e))?; + + Ok(()) +} diff --git a/common/src/chain/transaction/mod.rs b/common/src/chain/transaction/mod.rs index 4d979bb51e..d78edb6814 100644 --- a/common/src/chain/transaction/mod.rs +++ b/common/src/chain/transaction/mod.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use partially_signed_transaction::PartiallySignedTransaction; use thiserror::Error; use serialization::{DirectDecode, DirectEncode}; @@ -33,6 +34,7 @@ pub use account_nonce::*; pub mod utxo_outpoint; pub use utxo_outpoint::*; +pub mod partially_signed_transaction; pub mod signed_transaction; pub mod output; @@ -100,6 +102,8 @@ impl Eq for WithId {} pub enum TransactionCreationError { #[error("The number of signatures does not match the number of inputs")] InvalidWitnessCount, + #[error("Failed to convert partially signed tx to signed")] + FailedToConvertPartiallySignedTx(PartiallySignedTransaction), } impl Transaction { diff --git a/common/src/chain/transaction/partially_signed_transaction.rs b/common/src/chain/transaction/partially_signed_transaction.rs new file mode 100644 index 0000000000..39aa84caf5 --- /dev/null +++ b/common/src/chain/transaction/partially_signed_transaction.rs @@ -0,0 +1,140 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + signature::{inputsig::InputWitness, Signable, Transactable}, + Destination, Transaction, TxOutput, +}; +use crate::chain::{SignedTransaction, TransactionCreationError, TxInput}; +use serialization::{Decode, Encode}; +use utils::ensure; + +#[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)] +pub struct PartiallySignedTransaction { + tx: Transaction, + witnesses: Vec>, + + input_utxos: Vec>, + destinations: Vec>, +} + +impl PartiallySignedTransaction { + pub fn new( + tx: Transaction, + witnesses: Vec>, + input_utxos: Vec>, + destinations: Vec>, + ) -> Result { + ensure!( + tx.inputs().len() == witnesses.len(), + TransactionCreationError::InvalidWitnessCount + ); + + ensure!( + input_utxos.len() == witnesses.len(), + TransactionCreationError::InvalidWitnessCount + ); + + ensure!( + input_utxos.len() == destinations.len(), + TransactionCreationError::InvalidWitnessCount + ); + + Ok(Self { + tx, + witnesses, + input_utxos, + destinations, + }) + } + + pub fn with_witnesses(mut self, witnesses: Vec>) -> Self { + self.witnesses = witnesses; + self + } + + pub fn tx(&self) -> &Transaction { + &self.tx + } + + pub fn take_tx(self) -> Transaction { + self.tx + } + + pub fn input_utxos(&self) -> &[Option] { + self.input_utxos.as_ref() + } + + pub fn destinations(&self) -> &[Option] { + self.destinations.as_ref() + } + + pub fn witnesses(&self) -> &[Option] { + self.witnesses.as_ref() + } + + pub fn count_inputs(&self) -> usize { + self.tx.inputs().len() + } + + pub fn all_signatures_available(&self) -> bool { + self.witnesses + .iter() + .enumerate() + .zip(&self.destinations) + .all(|((_, w), d)| match (w, d) { + (Some(InputWitness::NoSignature(_)), None) => true, + (Some(InputWitness::NoSignature(_)), Some(_)) => false, + (Some(InputWitness::Standard(_)), None) => false, + (Some(InputWitness::Standard(_)), Some(_)) => true, + (None, _) => false, + }) + } + + pub fn into_signed_tx(self) -> Result { + if self.all_signatures_available() { + let witnesses = self.witnesses.into_iter().map(|w| w.expect("cannot fail")).collect(); + Ok(SignedTransaction::new(self.tx, witnesses)?) + } else { + Err(TransactionCreationError::FailedToConvertPartiallySignedTx( + self, + )) + } + } +} + +impl Signable for PartiallySignedTransaction { + fn inputs(&self) -> Option<&[TxInput]> { + Some(self.tx.inputs()) + } + + fn outputs(&self) -> Option<&[TxOutput]> { + Some(self.tx.outputs()) + } + + fn version_byte(&self) -> Option { + Some(self.tx.version_byte()) + } + + fn flags(&self) -> Option { + Some(self.tx.flags()) + } +} + +impl Transactable for PartiallySignedTransaction { + fn signatures(&self) -> Vec> { + self.witnesses.clone() + } +} diff --git a/mempool/src/error/ban_score.rs b/mempool/src/error/ban_score.rs index d8ba99d4a9..a9e3e1d7a8 100644 --- a/mempool/src/error/ban_score.rs +++ b/mempool/src/error/ban_score.rs @@ -235,6 +235,7 @@ impl MempoolBanScore for tx_verifier::error::TimelockContextError { fn mempool_ban_score(&self) -> u32 { match self { Self::TimelockedAccount => 0, + Self::MissingUtxoSource => 0, Self::HeaderLoad(e, _) => e.ban_score(), } } diff --git a/mintscript/src/checker/mod.rs b/mintscript/src/checker/mod.rs index 7f395d74c4..e004509f49 100644 --- a/mintscript/src/checker/mod.rs +++ b/mintscript/src/checker/mod.rs @@ -24,6 +24,7 @@ use hashlock::{HashlockChecker, NoOpHashlockChecker, StandardHashlockChecker}; pub use signature::{ NoOpSignatureChecker, SignatureChecker, SignatureContext, StandardSignatureChecker, }; +use timelock::NoOpTimelockChecker; pub use timelock::{StandardTimelockChecker, TimelockChecker, TimelockContext, TimelockError}; /// Script signature and timelock checker. @@ -42,6 +43,10 @@ pub struct ScriptChecker { pub type TimelockOnlyScriptChecker = ScriptChecker; +/// Script checker only verifying signtures. +pub type SignatureOnlyScriptChecker = + ScriptChecker; + /// Full script checker with all checks active. pub type FullScriptChecker = ScriptChecker; @@ -58,6 +63,18 @@ impl TimelockOnlyScriptChecker { } } +impl SignatureOnlyScriptChecker { + /// Create a script checker that only checks signatures. + pub fn signature_only(context: C) -> Self { + Self::custom( + context, + StandardSignatureChecker, + NoOpTimelockChecker, + NoOpHashlockChecker, + ) + } +} + impl FullScriptChecker { /// Create a full script checker verifying everything. pub fn full(context: C) -> Self { diff --git a/mintscript/src/checker/timelock.rs b/mintscript/src/checker/timelock.rs index 5d17be3050..0e6978b42e 100644 --- a/mintscript/src/checker/timelock.rs +++ b/mintscript/src/checker/timelock.rs @@ -91,3 +91,13 @@ impl TimelockChecker for StandardTimelockChecker { } } } + +pub struct NoOpTimelockChecker; + +impl TimelockChecker for NoOpTimelockChecker { + type Error = std::convert::Infallible; + + fn check_timelock(&mut self, _ctx: &mut C, _lock: &OutputTimeLock) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/mintscript/src/tests/translate/mocks.rs b/mintscript/src/tests/translate/mocks.rs index b42c81c7a7..10902059d1 100644 --- a/mintscript/src/tests/translate/mocks.rs +++ b/mintscript/src/tests/translate/mocks.rs @@ -62,86 +62,36 @@ impl crate::translate::InputInfoProvider for MockSigInfoProvider<'_> { } impl crate::translate::SignatureInfoProvider for MockSigInfoProvider<'_> { - type PoSAccounting = Self; - type Tokens = Self; - type Orders = Self; - - fn pos_accounting(&self) -> &Self::PoSAccounting { - self - } - - fn tokens(&self) -> &Self::Tokens { - self - } - - fn orders(&self) -> &Self::Orders { - self - } -} - -impl pos_accounting::PoSAccountingView for MockSigInfoProvider<'_> { - type Error = pos_accounting::Error; - - fn pool_exists(&self, id: PoolId) -> Result { - Ok(self.pools.contains_key(&id)) - } - - fn get_pool_balance(&self, _id: PoolId) -> Result { - unreachable!("not used in these tests") - } - - fn get_pool_data(&self, id: PoolId) -> Result, Self::Error> { - Ok(self.pools.get(&id).cloned()) - } - - fn get_pool_delegations_shares( + fn get_pool_decommission_destination( &self, - _id: PoolId, - ) -> Result>, Self::Error> { - unreachable!("not used in these tests") + pool_id: &PoolId, + ) -> Result, pos_accounting::Error> { + Ok(self.pools.get(pool_id).map(|pool| pool.decommission_destination().clone())) } - fn get_delegation_balance(&self, _id: DelegationId) -> Result { - unreachable!("not used in these tests") - } - - fn get_delegation_data(&self, id: DelegationId) -> Result, Self::Error> { - Ok(self.delegations.get(&id).cloned()) - } - - fn get_pool_delegation_share( + fn get_delegation_spend_destination( &self, - _pid: PoolId, - _did: DelegationId, - ) -> Result { - unreachable!("not used in these tests") - } -} - -impl tokens_accounting::TokensAccountingView for MockSigInfoProvider<'_> { - type Error = tokens_accounting::Error; - - fn get_token_data(&self, id: &TokenId) -> Result, Self::Error> { - Ok(self.tokens.get(id).cloned()) + delegation_id: &DelegationId, + ) -> Result, pos_accounting::Error> { + Ok(self + .delegations + .get(delegation_id) + .map(|delegation| delegation.spend_destination().clone())) } - fn get_circulating_supply(&self, _id: &TokenId) -> Result { - unreachable!("not used in these tests") - } -} - -impl orders_accounting::OrdersAccountingView for MockSigInfoProvider<'_> { - type Error = orders_accounting::Error; - - fn get_order_data(&self, id: &OrderId) -> Result, Self::Error> { - Ok(self.orders.get(id).cloned()) - } - - fn get_ask_balance(&self, _id: &OrderId) -> Result { - unreachable!() + fn get_tokens_authority( + &self, + token_id: &TokenId, + ) -> Result, tokens_accounting::Error> { + Ok(self.tokens.get(token_id).map(|token| match token { + TokenData::FungibleToken(data) => data.authority().clone(), + })) } - fn get_give_balance(&self, _id: &OrderId) -> Result { - unreachable!() + fn get_orders_conclude_destination( + &self, + order_id: &OrderId, + ) -> Result, orders_accounting::Error> { + Ok(self.orders.get(order_id).map(|data| data.conclude_key().clone())) } } diff --git a/mintscript/src/tests/translate/mod.rs b/mintscript/src/tests/translate/mod.rs index d8b70c093f..bb0111f2e9 100644 --- a/mintscript/src/tests/translate/mod.rs +++ b/mintscript/src/tests/translate/mod.rs @@ -56,7 +56,8 @@ impl TestInputInfo { match self { Self::Utxo { outpoint, utxo } => InputInfo::Utxo { outpoint, - utxo: utxo.clone(), + utxo: utxo.output().clone(), + utxo_source: Some(utxo.source().clone()), }, Self::Account { outpoint } => InputInfo::Account { outpoint }, Self::AccountCommand { command } => InputInfo::AccountCommand { command }, @@ -290,6 +291,11 @@ impl TranslationMode<'_> for TimelockOnly { type Mode = Self; } +impl TranslationMode<'_> for SignatureOnlyTx { + const NAME: &'static str = "sigonly"; + type Mode = Self; +} + fn mode_name<'a, T: TranslationMode<'a>>(_: &T) -> &'static str { T::NAME } @@ -361,7 +367,7 @@ fn mode_name<'a, T: TranslationMode<'a>>(_: &T) -> &'static str { #[case("fillorder_01", fill_order(fake_id(0x77)), nosig())] #[case("fillorder_00", fill_order(order0().0), stdsig(0x45))] fn translate_snap( - #[values(TxnMode, RewardMode, TimelockOnly)] mode: impl for<'a> TranslationMode<'a>, + #[values(TxnMode, RewardMode, TimelockOnly, SignatureOnlyTx)] mode: impl for<'a> TranslationMode<'a>, #[case] name: &str, #[case] test_input_info: TestInputInfo, #[case] witness: InputWitness, diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_00.txt new file mode 100644 index 0000000000..58cba20280 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_00.txt @@ -0,0 +1 @@ +signature(0x020002819f7f36a2790938e5f45ac07053110b8e985fbf7cff8a60a403e95b2a2c24fc, 0x0101085454) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_01.txt new file mode 100644 index 0000000000..3177f51198 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_01.txt @@ -0,0 +1 @@ +ERROR: Delegation f5f5…f5f5 does not exist \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_02.txt new file mode 100644 index 0000000000..582e6d4d7b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.acctspend_02.txt @@ -0,0 +1 @@ +signature(0x020002819f7f36a2790938e5f45ac07053110b8e985fbf7cff8a60a403e95b2a2c24fc, 0x0000) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.anyonecantake_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.anyonecantake_00.txt new file mode 100644 index 0000000000..776077671b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.anyonecantake_00.txt @@ -0,0 +1 @@ +ERROR: Attempt to spend an unspendable output \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.anyonecantake_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.anyonecantake_01.txt new file mode 100644 index 0000000000..776077671b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.anyonecantake_01.txt @@ -0,0 +1 @@ +ERROR: Attempt to spend an unspendable output \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.burn_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.burn_00.txt new file mode 100644 index 0000000000..776077671b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.burn_00.txt @@ -0,0 +1 @@ +ERROR: Attempt to spend an unspendable output \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.burn_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.burn_01.txt new file mode 100644 index 0000000000..776077671b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.burn_01.txt @@ -0,0 +1 @@ +ERROR: Attempt to spend an unspendable output \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_00.txt new file mode 100644 index 0000000000..ecd4b9ce80 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_00.txt @@ -0,0 +1 @@ +signature(0x02000236d8c927b785e27385737e82cdde2e06dc510ab8545d6eab0ca05c36040a437c, 0x0000) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_01.txt new file mode 100644 index 0000000000..44099c2e1b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_01.txt @@ -0,0 +1 @@ +ERROR: Order with id 8888…8888 does not exist \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_02.txt new file mode 100644 index 0000000000..ef0b468b80 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_02.txt @@ -0,0 +1 @@ +signature(0x02000236d8c927b785e27385737e82cdde2e06dc510ab8545d6eab0ca05c36040a437c, 0x0101084444) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_03.txt b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_03.txt new file mode 100644 index 0000000000..976babc21a --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.concludeorder_03.txt @@ -0,0 +1 @@ +signature(0x02000236d8c927b785e27385737e82cdde2e06dc510ab8545d6eab0ca05c36040a437c, 0x0101084545) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.delegate_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.delegate_00.txt new file mode 100644 index 0000000000..776077671b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.delegate_00.txt @@ -0,0 +1 @@ +ERROR: Attempt to spend an unspendable output \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.delegate_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.delegate_01.txt new file mode 100644 index 0000000000..776077671b --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.delegate_01.txt @@ -0,0 +1 @@ +ERROR: Attempt to spend an unspendable output \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_00.txt new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_00.txt @@ -0,0 +1 @@ +true diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_01.txt new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_01.txt @@ -0,0 +1 @@ +true diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_02.txt new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.fillorder_02.txt @@ -0,0 +1 @@ +true diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.htlc_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_00.txt new file mode 100644 index 0000000000..1c10b0cdc0 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_00.txt @@ -0,0 +1 @@ +signature(0x020003574c6b846c9a4c555ea75d771d5a40564b9ef37419682da12573e1d8ac27d71e, 0x0101085454) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.htlc_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_01.txt new file mode 100644 index 0000000000..eedc64c997 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_01.txt @@ -0,0 +1 @@ +signature(0x020002a3fe239606e407ea161143e42c7c3ef0059573466950a910b28289df247df7a3, 0x0101085858) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.htlc_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_02.txt new file mode 100644 index 0000000000..6cb31ca1bf --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_02.txt @@ -0,0 +1 @@ +signature(0x0200039315c9da756f584d5a7fff618d230bf13115a43d63e7c7d464bb513ab6be7bbc, 0x0101085353) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.htlc_03.txt b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_03.txt new file mode 100644 index 0000000000..28143e8292 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_03.txt @@ -0,0 +1 @@ +signature(0x041c9bb73a209c49363022813e7197ac80c761d80b, 0x0101085454) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.htlc_04.txt b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_04.txt new file mode 100644 index 0000000000..73a76ec766 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.htlc_04.txt @@ -0,0 +1 @@ +signature(0x04d55789fd7dd4b58f8bdb889a0d31cac70e67df92, 0x0101085555) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.mint_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.mint_00.txt new file mode 100644 index 0000000000..8c13683b9a --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.mint_00.txt @@ -0,0 +1 @@ +ERROR: Token with id a1a1…a1a1 does not exist \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.mint_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.mint_01.txt new file mode 100644 index 0000000000..a9637cc964 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.mint_01.txt @@ -0,0 +1 @@ +signature(0x020003745607a08b12634e402eec525ddaaaaab73cc3951cd232cb88ad934f4be717f6, 0x0101085757) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.mint_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.mint_02.txt new file mode 100644 index 0000000000..7d85876d3d --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.mint_02.txt @@ -0,0 +1 @@ +signature(0x020003745607a08b12634e402eec525ddaaaaab73cc3951cd232cb88ad934f4be717f6, 0x0000) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.newpool_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.newpool_00.txt new file mode 100644 index 0000000000..6cb31ca1bf --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.newpool_00.txt @@ -0,0 +1 @@ +signature(0x0200039315c9da756f584d5a7fff618d230bf13115a43d63e7c7d464bb513ab6be7bbc, 0x0101085353) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_00.txt new file mode 100644 index 0000000000..c93e6dc95e --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_00.txt @@ -0,0 +1 @@ +ERROR: Stake pool e0e0…e0e0 does not exist \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_01.txt new file mode 100644 index 0000000000..5162fe2be0 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_01.txt @@ -0,0 +1 @@ +ERROR: Stake pool e1e1…e1e1 does not exist \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_02.txt new file mode 100644 index 0000000000..3362bb16d2 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_02.txt @@ -0,0 +1 @@ +ERROR: Stake pool e2e2…e2e2 does not exist \ No newline at end of file diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_03.txt b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_03.txt new file mode 100644 index 0000000000..772e72bd26 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_03.txt @@ -0,0 +1 @@ +signature(0x0200024efcfcb197750301c44ffc5a8b176159a2c5de0b9945c5998245054efea6ac89, 0x0101086464) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_04.txt b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_04.txt new file mode 100644 index 0000000000..4d81c84ae8 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.prodblock_04.txt @@ -0,0 +1 @@ +signature(0x0200024efcfcb197750301c44ffc5a8b176159a2c5de0b9945c5998245054efea6ac89, 0x0101086565) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfer_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_00.txt new file mode 100644 index 0000000000..1b2f3ec6e7 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_00.txt @@ -0,0 +1 @@ +signature(0x020003e843fa18427b5e71eb6b94eaffcbf52ddc8dc6e843d259f31d7d5566ddc1b6c2, 0x0000) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfer_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_01.txt new file mode 100644 index 0000000000..0cd13f2ebd --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_01.txt @@ -0,0 +1 @@ +signature(0x020002a3fe239606e407ea161143e42c7c3ef0059573466950a910b28289df247df7a3, 0x0101085151) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfer_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_02.txt new file mode 100644 index 0000000000..3d29c7fbb9 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_02.txt @@ -0,0 +1 @@ +signature(0x011212121212121212121212121212121212121212, 0x0101085252) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfer_03.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_03.txt new file mode 100644 index 0000000000..48791373a0 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfer_03.txt @@ -0,0 +1 @@ +signature(0x011212121212121212121212121212121212121212, 0x0000) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_00.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_00.txt new file mode 100644 index 0000000000..b62b47af04 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_00.txt @@ -0,0 +1 @@ +signature(0x020003e843fa18427b5e71eb6b94eaffcbf52ddc8dc6e843d259f31d7d5566ddc1b6c2, 0x0101085d5d) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_01.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_01.txt new file mode 100644 index 0000000000..999853ba87 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_01.txt @@ -0,0 +1 @@ +signature(0x020002a3fe239606e407ea161143e42c7c3ef0059573466950a910b28289df247df7a3, 0x0101085959) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_02.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_02.txt new file mode 100644 index 0000000000..2f32a22553 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_02.txt @@ -0,0 +1 @@ +signature(0x02000253f0022f209dfa5c224294e4aaf337dc062ec9f689fcc04b4f2196a71fad3758, 0x0101085a5a) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_03.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_03.txt new file mode 100644 index 0000000000..c6074c8c17 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_03.txt @@ -0,0 +1 @@ +signature(0x0200039315c9da756f584d5a7fff618d230bf13115a43d63e7c7d464bb513ab6be7bbc, 0x0101085b5b) diff --git a/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_04.txt b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_04.txt new file mode 100644 index 0000000000..9705bf0078 --- /dev/null +++ b/mintscript/src/tests/translate/snap.translate.sigonly.transfertl_04.txt @@ -0,0 +1 @@ +signature(0x020002ebcadc73233ea7fc2c8e2e5bcafc7dd4b46444a60d9b5bc9a965d2c6d8a44ebb, 0x0000) diff --git a/mintscript/src/translate.rs b/mintscript/src/translate.rs index ca590096be..5a72ee9109 100644 --- a/mintscript/src/translate.rs +++ b/mintscript/src/translate.rs @@ -26,10 +26,7 @@ use common::chain::{ AccountCommand, AccountOutPoint, AccountSpending, DelegationId, Destination, OrderId, PoolId, SignedTransaction, TxOutput, UtxoOutPoint, }; -use orders_accounting::OrdersAccountingView; -use pos_accounting::PoSAccountingView; -use tokens_accounting::TokensAccountingView; -use utxo::Utxo; +use utxo::UtxoSource; use crate::{script::HashChallenge, WitnessScript}; @@ -74,7 +71,8 @@ pub enum TranslationError { pub enum InputInfo<'a> { Utxo { outpoint: &'a UtxoOutPoint, - utxo: Utxo, + utxo: TxOutput, + utxo_source: Option, }, Account { outpoint: &'a AccountOutPoint, @@ -87,7 +85,11 @@ pub enum InputInfo<'a> { impl InputInfo<'_> { pub fn as_utxo_output(&self) -> Option<&TxOutput> { match self { - InputInfo::Utxo { outpoint: _, utxo } => Some(utxo.output()), + InputInfo::Utxo { + outpoint: _, + utxo, + utxo_source: _, + } => Some(utxo), InputInfo::Account { .. } | InputInfo::AccountCommand { .. } => None, } } @@ -100,13 +102,25 @@ pub trait InputInfoProvider { /// Provides information necessary to translate an input to a script. pub trait SignatureInfoProvider: InputInfoProvider { - type PoSAccounting: PoSAccountingView; - type Tokens: TokensAccountingView; - type Orders: OrdersAccountingView; + fn get_pool_decommission_destination( + &self, + pool_id: &PoolId, + ) -> Result, pos_accounting::Error>; - fn pos_accounting(&self) -> &Self::PoSAccounting; - fn tokens(&self) -> &Self::Tokens; - fn orders(&self) -> &Self::Orders; + fn get_delegation_spend_destination( + &self, + delegation_id: &DelegationId, + ) -> Result, pos_accounting::Error>; + + fn get_tokens_authority( + &self, + token_id: &TokenId, + ) -> Result, tokens_accounting::Error>; + + fn get_orders_conclude_destination( + &self, + order_id: &OrderId, + ) -> Result, orders_accounting::Error>; } pub trait TranslateInput { @@ -116,12 +130,15 @@ pub trait TranslateInput { impl TranslateInput for SignedTransaction { fn translate_input(ctx: &C) -> Result { - let pos_accounting = ctx.pos_accounting(); let checksig = |dest: &Destination| WitnessScript::signature(dest.clone(), ctx.witness().clone()); match ctx.input_info() { - InputInfo::Utxo { outpoint: _, utxo } => match utxo.output() { + InputInfo::Utxo { + outpoint: _, + utxo, + utxo_source: _, + } => match utxo { TxOutput::Transfer(_val, dest) => Ok(checksig(dest)), TxOutput::LockThenTransfer(_val, dest, timelock) => { Ok(WitnessScript::satisfied_conjunction([ @@ -132,11 +149,11 @@ impl TranslateInput for SignedTransaction { TxOutput::CreateStakePool(_pool_id, pool_data) => { Ok(checksig(pool_data.decommission_key())) } - TxOutput::ProduceBlockFromStake(_dest, pool_id) => { - let pool_data = pos_accounting - .get_pool_data(*pool_id)? + TxOutput::ProduceBlockFromStake(_, pool_id) => { + let dest = ctx + .get_pool_decommission_destination(pool_id)? .ok_or(TranslationError::PoolNotFound(*pool_id))?; - Ok(checksig(pool_data.decommission_destination())) + Ok(checksig(&dest)) } TxOutput::Htlc(_, htlc) => { let script = match ctx.witness() { @@ -193,10 +210,10 @@ impl TranslateInput for SignedTransaction { }, InputInfo::Account { outpoint } => match outpoint.account() { AccountSpending::DelegationBalance(delegation_id, _amount) => { - let delegation = pos_accounting - .get_delegation_data(*delegation_id)? + let dest = ctx + .get_delegation_spend_destination(delegation_id)? .ok_or(TranslationError::DelegationNotFound(*delegation_id))?; - Ok(checksig(delegation.spend_destination())) + Ok(checksig(&dest)) } }, InputInfo::AccountCommand { command } => match command { @@ -206,21 +223,16 @@ impl TranslateInput for SignedTransaction { | AccountCommand::FreezeToken(token_id, _) | AccountCommand::UnfreezeToken(token_id) | AccountCommand::ChangeTokenAuthority(token_id, _) => { - let token_data = ctx - .tokens() - .get_token_data(token_id)? + let dest = ctx + .get_tokens_authority(token_id)? .ok_or(TranslationError::TokenNotFound(*token_id))?; - let dest = match &token_data { - tokens_accounting::TokenData::FungibleToken(data) => data.authority(), - }; - Ok(checksig(dest)) + Ok(checksig(&dest)) } AccountCommand::ConcludeOrder(order_id) => { - let order_data = ctx - .orders() - .get_order_data(order_id)? + let dest = ctx + .get_orders_conclude_destination(order_id)? .ok_or(TranslationError::OrderNotFound(*order_id))?; - Ok(checksig(order_data.conclude_key())) + Ok(checksig(&dest)) } AccountCommand::FillOrder(_, _, _) => Ok(WitnessScript::TRUE), }, @@ -233,8 +245,12 @@ impl TranslateInput for BlockRewardTransactable<'_> let checksig = |dest: &Destination| WitnessScript::signature(dest.clone(), ctx.witness().clone()); match ctx.input_info() { - InputInfo::Utxo { outpoint: _, utxo } => { - match utxo.output() { + InputInfo::Utxo { + outpoint: _, + utxo, + utxo_source: _, + } => { + match utxo { TxOutput::Transfer(_, _) | TxOutput::LockThenTransfer(_, _, _) | TxOutput::IssueNft(_, _, _) @@ -272,7 +288,11 @@ pub struct TimelockOnly; impl TranslateInput for TimelockOnly { fn translate_input(ctx: &C) -> Result { match ctx.input_info() { - InputInfo::Utxo { outpoint: _, utxo } => match utxo.output() { + InputInfo::Utxo { + outpoint: _, + utxo, + utxo_source: _, + } => match utxo { TxOutput::LockThenTransfer(_val, _dest, timelock) => { Ok(WitnessScript::timelock(*timelock)) } @@ -321,3 +341,114 @@ impl TranslateInput for TimelockOnly { } } } + +pub struct SignatureOnlyTx; + +impl TranslateInput for SignatureOnlyTx { + fn translate_input(ctx: &C) -> Result { + let checksig = + |dest: &Destination| WitnessScript::signature(dest.clone(), ctx.witness().clone()); + + match ctx.input_info() { + InputInfo::Utxo { + outpoint: _, + utxo, + utxo_source: _, + } => match utxo { + TxOutput::Transfer(_val, dest) | TxOutput::LockThenTransfer(_val, dest, _) => { + Ok(checksig(dest)) + } + TxOutput::CreateStakePool(_pool_id, pool_data) => { + Ok(checksig(pool_data.decommission_key())) + } + TxOutput::ProduceBlockFromStake(_dest, pool_id) => { + let dest = ctx + .get_pool_decommission_destination(pool_id)? + .ok_or(TranslationError::PoolNotFound(*pool_id))?; + Ok(checksig(&dest)) + } + TxOutput::Htlc(_, htlc) => { + let script = match ctx.witness() { + InputWitness::NoSignature(_) => { + return Err(TranslationError::SignatureError( + DestinationSigError::SignatureNotFound, + )) + } + InputWitness::Standard(sig) => { + let htlc_spend = AuthorizedHashedTimelockContractSpend::from_data( + sig.raw_signature(), + )?; + match htlc_spend { + AuthorizedHashedTimelockContractSpend::Secret(_, raw_signature) => { + WitnessScript::signature( + htlc.spend_key.clone(), + InputWitness::Standard(StandardInputSignature::new( + sig.sighash_type(), + raw_signature, + )), + ) + } + AuthorizedHashedTimelockContractSpend::Multisig(raw_signature) => { + WitnessScript::signature( + htlc.refund_key.clone(), + InputWitness::Standard(StandardInputSignature::new( + sig.sighash_type(), + raw_signature, + )), + ) + } + } + } + }; + Ok(script) + } + TxOutput::IssueNft(_id, _issuance, dest) => Ok(checksig(dest)), + TxOutput::DelegateStaking(_amount, _deleg_id) => Err(TranslationError::Unspendable), + TxOutput::CreateDelegationId(_dest, _pool_id) => Err(TranslationError::Unspendable), + TxOutput::IssueFungibleToken(_issuance) => Err(TranslationError::Unspendable), + TxOutput::Burn(_val) => Err(TranslationError::Unspendable), + TxOutput::DataDeposit(_data) => Err(TranslationError::Unspendable), + TxOutput::AnyoneCanTake(_) => Err(TranslationError::Unspendable), + }, + InputInfo::Account { outpoint } => match outpoint.account() { + AccountSpending::DelegationBalance(delegation_id, _amount) => { + let dest = ctx + .get_delegation_spend_destination(delegation_id)? + .ok_or(TranslationError::DelegationNotFound(*delegation_id))?; + Ok(checksig(&dest)) + } + }, + InputInfo::AccountCommand { command } => match command { + AccountCommand::MintTokens(token_id, _) + | AccountCommand::UnmintTokens(token_id) + | AccountCommand::LockTokenSupply(token_id) + | AccountCommand::FreezeToken(token_id, _) + | AccountCommand::UnfreezeToken(token_id) + | AccountCommand::ChangeTokenAuthority(token_id, _) => { + let dest = ctx + .get_tokens_authority(token_id)? + .ok_or(TranslationError::TokenNotFound(*token_id))?; + Ok(checksig(&dest)) + } + AccountCommand::ConcludeOrder(order_id) => { + let dest = ctx + .get_orders_conclude_destination(order_id)? + .ok_or(TranslationError::OrderNotFound(*order_id))?; + Ok(checksig(&dest)) + } + AccountCommand::FillOrder(_, _, _) => Ok(WitnessScript::TRUE), + }, + } + } +} + +pub struct SignatureOnlyReward; + +impl TranslateInput for SignatureOnlyReward { + fn translate_input(_ctx: &C) -> Result { + // Not used anywhere. + // But it's important to outline that if needed the reward implementation must be different + // because staking/decommissioning destinations are not the same. + unimplemented!() + } +} diff --git a/wallet/src/account/mod.rs b/wallet/src/account/mod.rs index ebead6cb95..8e20d23a36 100644 --- a/wallet/src/account/mod.rs +++ b/wallet/src/account/mod.rs @@ -21,8 +21,8 @@ mod utxo_selector; use common::address::pubkeyhash::PublicKeyHash; use common::chain::block::timestamp::BlockTimestamp; use common::chain::classic_multisig::ClassicMultisigChallenge; -use common::chain::signature::sighash::signature_hash; -use common::chain::{AccountCommand, AccountOutPoint, AccountSpending, TransactionCreationError}; +use common::chain::partially_signed_transaction::PartiallySignedTransaction; +use common::chain::{AccountCommand, AccountOutPoint, AccountSpending}; use common::primitives::id::WithId; use common::primitives::{Idable, H256}; use common::size_estimation::{ @@ -50,7 +50,6 @@ use crate::wallet_events::{WalletEvents, WalletEventsNoOp}; use crate::{get_tx_output_destination, SendRequest, WalletError, WalletResult}; use common::address::{Address, RpcAddress}; use common::chain::output_value::OutputValue; -use common::chain::signature::inputsig::InputWitness; use common::chain::tokens::{ make_token_id, IsTokenUnfreezable, NftIssuance, NftIssuanceV0, RPCFungibleTokenInfo, TokenId, }; @@ -64,7 +63,6 @@ use crypto::key::hdkd::u31::U31; use crypto::key::{PrivateKey, PublicKey}; use crypto::vrf::VRFPublicKey; use itertools::{izip, Itertools}; -use serialization::{Decode, Encode}; use std::cmp::Reverse; use std::collections::btree_map::Entry; use std::collections::{BTreeMap, BTreeSet}; @@ -108,107 +106,6 @@ impl TransactionToSign { } } -#[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)] -pub struct PartiallySignedTransaction { - tx: Transaction, - witnesses: Vec>, - - input_utxos: Vec>, - destinations: Vec>, -} - -impl PartiallySignedTransaction { - pub fn new( - tx: Transaction, - witnesses: Vec>, - input_utxos: Vec>, - destinations: Vec>, - ) -> WalletResult { - ensure!( - tx.inputs().len() == witnesses.len(), - TransactionCreationError::InvalidWitnessCount - ); - - ensure!( - input_utxos.len() == witnesses.len(), - TransactionCreationError::InvalidWitnessCount - ); - - ensure!( - input_utxos.len() == destinations.len(), - TransactionCreationError::InvalidWitnessCount - ); - - Ok(Self { - tx, - witnesses, - input_utxos, - destinations, - }) - } - - pub fn new_witnesses(mut self, witnesses: Vec>) -> Self { - self.witnesses = witnesses; - self - } - - pub fn tx(&self) -> &Transaction { - &self.tx - } - - pub fn take_tx(self) -> Transaction { - self.tx - } - - pub fn input_utxos(&self) -> &[Option] { - self.input_utxos.as_ref() - } - - pub fn destinations(&self) -> &[Option] { - self.destinations.as_ref() - } - - pub fn witnesses(&self) -> &[Option] { - self.witnesses.as_ref() - } - - pub fn count_inputs(&self) -> usize { - self.tx.inputs().len() - } - - pub fn count_completed_signatures(&self) -> usize { - self.witnesses.iter().filter(|w| w.is_some()).count() - } - - pub fn is_fully_signed(&self, chain_config: &ChainConfig) -> bool { - let inputs_utxos_refs: Vec<_> = self.input_utxos.iter().map(|out| out.as_ref()).collect(); - self.witnesses - .iter() - .enumerate() - .zip(&self.destinations) - .all(|((input_num, w), d)| match (w, d) { - (Some(InputWitness::NoSignature(_)), None) => true, - (Some(InputWitness::NoSignature(_)), Some(_)) => false, - (Some(InputWitness::Standard(_)), None) => false, - (Some(InputWitness::Standard(sig)), Some(dest)) => { - signature_hash(sig.sighash_type(), &self.tx, &inputs_utxos_refs, input_num) - .and_then(|sighash| sig.verify_signature(chain_config, dest, &sighash)) - .is_ok() - } - (None, _) => false, - }) - } - - pub fn into_signed_tx(self, chain_config: &ChainConfig) -> WalletResult { - if self.is_fully_signed(chain_config) { - let witnesses = self.witnesses.into_iter().map(|w| w.expect("cannot fail")).collect(); - Ok(SignedTransaction::new(self.tx, witnesses)?) - } else { - Err(WalletError::FailedToConvertPartiallySignedTx(self)) - } - } -} - pub struct Account { chain_config: Arc, key_chain: AccountKeyChainImpl, @@ -1358,7 +1255,9 @@ impl Account { .unzip(); let num_inputs = tx.inputs().len(); - PartiallySignedTransaction::new(tx, vec![None; num_inputs], input_utxos, destinations) + let ptx = + PartiallySignedTransaction::new(tx, vec![None; num_inputs], input_utxos, destinations)?; + Ok(ptx) } pub fn find_unspent_utxo_with_destination( diff --git a/wallet/src/send_request/mod.rs b/wallet/src/send_request/mod.rs index 2ec6174ab9..7e8d2bdeb8 100644 --- a/wallet/src/send_request/mod.rs +++ b/wallet/src/send_request/mod.rs @@ -18,6 +18,7 @@ use std::mem::take; use common::address::Address; use common::chain::output_value::OutputValue; +use common::chain::partially_signed_transaction::PartiallySignedTransaction; use common::chain::stakelock::StakePoolData; use common::chain::timelock::OutputTimeLock::ForBlockCount; use common::chain::tokens::{Metadata, TokenId, TokenIssuance}; @@ -30,7 +31,7 @@ use crypto::vrf::VRFPublicKey; use utils::ensure; use crate::account::currency_grouper::Currency; -use crate::account::{PartiallySignedTransaction, PoolData}; +use crate::account::PoolData; use crate::{WalletError, WalletResult}; /// The `SendRequest` struct provides the necessary information to the wallet @@ -283,7 +284,9 @@ impl SendRequest { let tx = Transaction::new(self.flags, self.inputs, self.outputs)?; let destinations = self.destinations.into_iter().map(Some).collect(); - PartiallySignedTransaction::new(tx, vec![None; num_inputs], self.utxos, destinations) + let ptx = + PartiallySignedTransaction::new(tx, vec![None; num_inputs], self.utxos, destinations)?; + Ok(ptx) } } diff --git a/wallet/src/signer/mod.rs b/wallet/src/signer/mod.rs index 439a3397ef..8d8e36344a 100644 --- a/wallet/src/signer/mod.rs +++ b/wallet/src/signer/mod.rs @@ -14,6 +14,7 @@ // limitations under the License. use common::chain::{ + partially_signed_transaction::PartiallySignedTransaction, signature::{ inputsig::arbitrary_message::{ArbitraryMessageSignature, SignArbitraryMessageError}, DestinationSigError, @@ -23,10 +24,7 @@ use common::chain::{ use crypto::key::hdkd::derivable::DerivationError; use wallet_types::signature_status::SignatureStatus; -use crate::{ - account::PartiallySignedTransaction, - key_chain::{AccountKeyChains, KeyChainError}, -}; +use crate::key_chain::{AccountKeyChains, KeyChainError}; pub mod software_signer; diff --git a/wallet/src/signer/software_signer/mod.rs b/wallet/src/signer/software_signer/mod.rs index 6d81a74d6f..09e827e066 100644 --- a/wallet/src/signer/software_signer/mod.rs +++ b/wallet/src/signer/software_signer/mod.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use common::chain::{ + partially_signed_transaction::PartiallySignedTransaction, signature::{ inputsig::{ arbitrary_message::ArbitraryMessageSignature, @@ -42,10 +43,7 @@ use serialization::Encode; use wallet_storage::WalletStorageReadUnlocked; use wallet_types::signature_status::SignatureStatus; -use crate::{ - account::PartiallySignedTransaction, - key_chain::{make_account_path, AccountKeyChains, FoundPubKey, MasterKeyChain}, -}; +use crate::key_chain::{make_account_path, AccountKeyChains, FoundPubKey, MasterKeyChain}; use super::{Signer, SignerError, SignerResult}; @@ -247,13 +245,17 @@ impl<'a, T: WalletStorageReadUnlocked> Signer for SoftwareSigner<'a, T> { )), InputWitness::Standard(sig) => match destination { Some(destination) => { - let sighash = - signature_hash(sig.sighash_type(), ptx.tx(), &inputs_utxo_refs, i)?; + let sig_verified = + tx_verifier::input_check::signature_only_check::verify_tx_signature( + &self.chain_config, + destination, + &ptx, + &inputs_utxo_refs, + i, + ) + .is_ok(); - if sig - .verify_signature(&self.chain_config, destination, &sighash) - .is_ok() - { + if sig_verified { Ok(( Some(w.clone()), SignatureStatus::FullySigned, @@ -304,7 +306,7 @@ impl<'a, T: WalletStorageReadUnlocked> Signer for SoftwareSigner<'a, T> { .into_iter() .multiunzip(); - Ok((ptx.new_witnesses(witnesses), prev_statuses, new_statuses)) + Ok((ptx.with_witnesses(witnesses), prev_statuses, new_statuses)) } fn sign_challenge( diff --git a/wallet/src/signer/software_signer/tests.rs b/wallet/src/signer/software_signer/tests.rs index d278690715..b406b5cc94 100644 --- a/wallet/src/signer/software_signer/tests.rs +++ b/wallet/src/signer/software_signer/tests.rs @@ -130,9 +130,9 @@ fn sign_transaction(#[case] seed: Seed) { let signer = SoftwareSigner::new(&db_tx, config.clone(), DEFAULT_ACCOUNT_INDEX); let (ptx, _, _) = signer.sign_tx(ptx, account.key_chain()).unwrap(); - assert!(ptx.is_fully_signed(&config)); + assert!(ptx.all_signatures_available()); - let sig_tx = ptx.into_signed_tx(&config).unwrap(); + let sig_tx = ptx.into_signed_tx().unwrap(); let utxos_ref = utxos.iter().map(Some).collect::>(); diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index 29d56d47ff..f06d6906ed 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -20,8 +20,8 @@ use std::sync::Arc; use crate::account::transaction_list::TransactionList; use crate::account::TxInfo; use crate::account::{ - currency_grouper::Currency, CurrentFeeRate, DelegationData, PartiallySignedTransaction, - PoolData, TransactionToSign, UnconfirmedTokenInfo, UtxoSelectorError, + currency_grouper::Currency, CurrentFeeRate, DelegationData, PoolData, TransactionToSign, + UnconfirmedTokenInfo, UtxoSelectorError, }; use crate::key_chain::{ make_account_path, make_path_to_vrf_key, KeyChainError, MasterKeyChain, LOOKAHEAD_SIZE, @@ -39,6 +39,7 @@ use common::address::pubkeyhash::PublicKeyHash; use common::address::{Address, AddressError, RpcAddress}; use common::chain::block::timestamp::BlockTimestamp; use common::chain::classic_multisig::ClassicMultisigChallenge; +use common::chain::partially_signed_transaction::PartiallySignedTransaction; use common::chain::signature::inputsig::arbitrary_message::{ ArbitraryMessageSignature, SignArbitraryMessageError, }; @@ -967,11 +968,34 @@ impl Wallet { let ptx = request.into_partially_signed_tx()?; let signer = SoftwareSigner::new(db_tx, Arc::new(chain_config.clone()), account_index); - let tx = signer - .sign_tx(ptx, account.key_chain()) - .map(|(ptx, _, _)| ptx)? - .into_signed_tx(chain_config) - .map_err(error_mapper)?; + let ptx = signer.sign_tx(ptx, account.key_chain()).map(|(ptx, _, _)| ptx)?; + + let inputs_utxo_refs: Vec<_> = ptx.input_utxos().iter().map(|u| u.as_ref()).collect(); + let is_fully_signed = ptx.destinations().iter().enumerate().zip(ptx.witnesses()).all( + |((i, destination), witness)| match (witness, destination) { + (None | Some(_), None) | (None, Some(_)) => false, + (Some(_), Some(destination)) => { + tx_verifier::input_check::signature_only_check::verify_tx_signature( + chain_config, + destination, + &ptx, + &inputs_utxo_refs, + i, + ) + .is_ok() + } + }, + ); + + if !is_fully_signed { + return Err(error_mapper(WalletError::FailedToConvertPartiallySignedTx( + ptx, + ))); + } + + let tx = ptx + .into_signed_tx() + .map_err(|e| error_mapper(WalletError::TransactionCreation(e)))?; check_transaction(chain_config, block_height.next_height(), &tx)?; Ok(tx) @@ -1707,7 +1731,7 @@ impl Wallet { let signer = SoftwareSigner::new(db_tx, Arc::new(chain_config.clone()), account_index); let ptx = signer.sign_tx(ptx, account.key_chain()).map(|(ptx, _, _)| ptx)?; - if ptx.is_fully_signed(chain_config) { + if ptx.all_signatures_available() { return Err(WalletError::FullySignedTransactionInDecommissionReq); } Ok(ptx) diff --git a/wallet/src/wallet/tests.rs b/wallet/src/wallet/tests.rs index c1ef54966e..6341e7637f 100644 --- a/wallet/src/wallet/tests.rs +++ b/wallet/src/wallet/tests.rs @@ -4008,10 +4008,10 @@ fn decommission_pool_request_wrong_account(#[case] seed: Seed) { FeeRate::from_amount_per_kb(Amount::from_atoms(0)), ) .unwrap(); - assert!(!decommission_partial_tx.is_fully_signed(&chain_config)); + assert!(!decommission_partial_tx.all_signatures_available()); matches!( - decommission_partial_tx.into_signed_tx(&chain_config).unwrap_err(), - WalletError::FailedToConvertPartiallySignedTx(_) + decommission_partial_tx.into_signed_tx().unwrap_err(), + TransactionCreationError::FailedToConvertPartiallySignedTx(_) ); } @@ -4067,7 +4067,7 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { .sign_raw_transaction(acc_0_index, TransactionToSign::Tx(tx)) .unwrap() .0 - .into_signed_tx(&chain_config) + .into_signed_tx() .unwrap(); let _ = create_block( @@ -4103,7 +4103,7 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { .unwrap() .0; // the tx is still not fully signed - assert!(!sign_from_acc0_res.is_fully_signed(&chain_config)); + assert!(!sign_from_acc0_res.all_signatures_available()); let signed_tx = wallet .sign_raw_transaction( @@ -4112,7 +4112,7 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { ) .unwrap() .0 - .into_signed_tx(&chain_config) + .into_signed_tx() .unwrap(); let _ = create_block(&chain_config, &mut wallet, vec![signed_tx], Amount::ZERO, 2); @@ -4198,7 +4198,7 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) { ) .unwrap() .0; - assert!(partially_signed_transaction.is_fully_signed(&chain_config)); + assert!(partially_signed_transaction.all_signatures_available()); // sign it with the hot wallet should leave the signatures in place even if it can't find the // destinations for the inputs @@ -4209,9 +4209,9 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) { ) .unwrap() .0; - assert!(partially_signed_transaction.is_fully_signed(&chain_config)); + assert!(partially_signed_transaction.all_signatures_available()); - let signed_tx = partially_signed_transaction.into_signed_tx(&chain_config).unwrap(); + let signed_tx = partially_signed_transaction.into_signed_tx().unwrap(); let _ = create_block( &chain_config, @@ -4372,14 +4372,14 @@ fn sign_send_request_cold_wallet(#[case] seed: Seed) { .unwrap() .0; // the tx is not fully signed - assert!(!tx.is_fully_signed(&chain_config)); + assert!(!tx.all_signatures_available()); // sign the tx with cold wallet let signed_tx = cold_wallet .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(send_req)) .unwrap() .0 - .into_signed_tx(&chain_config) + .into_signed_tx() .unwrap(); let (_, block2) = create_block( @@ -4600,25 +4600,25 @@ fn test_add_standalone_multisig(#[case] seed: Seed) { .unwrap(); // sign it with wallet1 - let ptx = wallet1 + let (ptx, _, statuses) = wallet1 .sign_raw_transaction( DEFAULT_ACCOUNT_INDEX, TransactionToSign::Tx(spend_multisig_tx), ) - .unwrap() - .0; + .unwrap(); // check it is still not fully signed - assert!(!ptx.is_fully_signed(&chain_config)); + assert!(ptx.all_signatures_available()); + assert!(!statuses.iter().all(|s| *s == SignatureStatus::FullySigned)); // try to sign it with wallet1 again - let ptx = wallet1 + let (ptx, _, statuses) = wallet1 .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(ptx)) - .unwrap() - .0; + .unwrap(); // check it is still not fully signed - assert!(!ptx.is_fully_signed(&chain_config)); + assert!(ptx.all_signatures_available()); + assert!(!statuses.iter().all(|s| *s == SignatureStatus::FullySigned)); // try to sign it with wallet2 but wallet2 does not have the multisig added as standalone let ptx = wallet2 @@ -4630,11 +4630,11 @@ fn test_add_standalone_multisig(#[case] seed: Seed) { wallet2.add_standalone_multisig(DEFAULT_ACCOUNT_INDEX, challenge, None).unwrap(); // now we can sign it - let ptx = wallet2 + let (ptx, _, statuses) = wallet2 .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(ptx)) - .unwrap() - .0; + .unwrap(); // now it is fully signed - assert!(ptx.is_fully_signed(&chain_config)); + assert!(ptx.all_signatures_available()); + assert!(statuses.iter().all(|s| *s == SignatureStatus::FullySigned)); } diff --git a/wallet/wallet-cli-commands/src/command_handler/mod.rs b/wallet/wallet-cli-commands/src/command_handler/mod.rs index 591c53b412..930d7dd164 100644 --- a/wallet/wallet-cli-commands/src/command_handler/mod.rs +++ b/wallet/wallet-cli-commands/src/command_handler/mod.rs @@ -20,8 +20,9 @@ use std::{fmt::Write, str::FromStr}; use common::{ address::Address, chain::{ - config::checkpoints_data::print_block_heights_ids_as_checkpoints_data, ChainConfig, - Destination, SignedTransaction, TxOutput, UtxoOutPoint, + config::checkpoints_data::print_block_heights_ids_as_checkpoints_data, + partially_signed_transaction::PartiallySignedTransaction, ChainConfig, Destination, + SignedTransaction, TxOutput, UtxoOutPoint, }, primitives::H256, text_summary::TextSummary, @@ -32,7 +33,7 @@ use mempool::tx_options::TxOptionsOverrides; use node_comm::node_traits::NodeInterface; use serialization::{hex::HexEncode, hex_encoded::HexEncoded}; use utils::qrcode::{QrCode, QrCodeError}; -use wallet::{account::PartiallySignedTransaction, version::get_version}; +use wallet::version::get_version; use wallet_rpc_client::wallet_rpc_traits::{PartialOrSignedTx, WalletInterface}; use wallet_rpc_lib::types::{ Balances, ComposedTransaction, ControllerConfig, MnemonicInfo, NewTransaction, NftMetadata, diff --git a/wallet/wallet-controller/src/lib.rs b/wallet/wallet-controller/src/lib.rs index 0926f49ff6..df29b87b76 100644 --- a/wallet/wallet-controller/src/lib.rs +++ b/wallet/wallet-controller/src/lib.rs @@ -25,6 +25,9 @@ const NORMAL_DELAY: Duration = Duration::from_secs(1); const ERROR_DELAY: Duration = Duration::from_secs(10); use blockprod::BlockProductionError; +use chainstate::tx_verifier::{ + self, error::ScriptError, input_check::signature_only_check::SignatureOnlyVerifiable, +}; use futures::{ never::Never, stream::{FuturesOrdered, FuturesUnordered}, @@ -52,11 +55,8 @@ use common::{ address::AddressError, chain::{ block::timestamp::BlockTimestamp, - signature::{ - inputsig::{standard_signature::StandardInputSignature, InputWitness}, - sighash::signature_hash, - DestinationSigError, - }, + partially_signed_transaction::PartiallySignedTransaction, + signature::{inputsig::InputWitness, DestinationSigError, Transactable}, tokens::{RPCTokenInfo, TokenId}, Block, ChainConfig, Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxInput, TxOutput, UtxoOutPoint, @@ -80,7 +80,7 @@ use randomness::{make_pseudo_rng, make_true_rng, Rng}; use wallet::{ account::{ currency_grouper::{self, Currency}, - PartiallySignedTransaction, TransactionToSign, + TransactionToSign, }, get_tx_output_destination, wallet::WalletPoolsFilter, @@ -107,7 +107,7 @@ pub enum ControllerError { #[error("Wallet file {0} error: {1}")] WalletFileError(PathBuf, String), #[error("Wallet error: {0}")] - WalletError(wallet::wallet::WalletError), + WalletError(#[from] wallet::wallet::WalletError), #[error("Encoding error: {0}")] AddressEncodingError(#[from] AddressError), #[error("No staking pool found")] @@ -782,13 +782,9 @@ impl Controll (InputWitness::NoSignature(_), None) => SignatureStatus::FullySigned, (InputWitness::NoSignature(_), Some(_)) => SignatureStatus::NotSigned, (InputWitness::Standard(_), None) => SignatureStatus::InvalidSignature, - (InputWitness::Standard(sig), Some(dest)) => self.verify_tx_signature( - sig, - stx.transaction(), - &inputs_utxos_refs, - input_num, - &dest, - ), + (InputWitness::Standard(_), Some(dest)) => { + self.verify_tx_signature(stx, &inputs_utxos_refs, input_num, &dest) + } }) .collect(); Ok((fees, signature_statuses)) @@ -810,8 +806,8 @@ impl Controll (Some(InputWitness::NoSignature(_)), None) => SignatureStatus::FullySigned, (Some(InputWitness::NoSignature(_)), Some(_)) => SignatureStatus::InvalidSignature, (Some(InputWitness::Standard(_)), None) => SignatureStatus::UnknownSignature, - (Some(InputWitness::Standard(sig)), Some(dest)) => { - self.verify_tx_signature(sig, ptx.tx(), &inputs_utxos_refs, input_num, dest) + (Some(InputWitness::Standard(_)), Some(dest)) => { + self.verify_tx_signature(&ptx, &inputs_utxos_refs, input_num, dest) } (None, _) => SignatureStatus::NotSigned, }) @@ -861,30 +857,43 @@ impl Controll fn verify_tx_signature( &self, - sig: &StandardInputSignature, - tx: &Transaction, + tx: &(impl Transactable + SignatureOnlyVerifiable), inputs_utxos_refs: &[Option<&TxOutput>], input_num: usize, dest: &Destination, ) -> SignatureStatus { - signature_hash(sig.sighash_type(), tx, inputs_utxos_refs, input_num).map_or( - SignatureStatus::InvalidSignature, - |sighash| { - let valid = sig.verify_signature(&self.chain_config, dest, &sighash); - - match valid { - Err(DestinationSigError::IncompleteClassicalMultisigSignature( - required_signatures, - num_signatures, - )) => SignatureStatus::PartialMultisig { - required_signatures, - num_signatures, - }, - Err(_) => SignatureStatus::InvalidSignature, - Ok(_) => SignatureStatus::FullySigned, + let valid = tx_verifier::input_check::signature_only_check::verify_tx_signature( + &self.chain_config, + dest, + tx, + inputs_utxos_refs, + input_num, + ); + + match valid { + Ok(_) => SignatureStatus::FullySigned, + Err(e) => match e.error() { + tx_verifier::error::InputCheckErrorPayload::MissingUtxo(_) + | tx_verifier::error::InputCheckErrorPayload::UtxoView(_) + | tx_verifier::error::InputCheckErrorPayload::Translation(_) => { + SignatureStatus::InvalidSignature } + + tx_verifier::error::InputCheckErrorPayload::Verification( + ScriptError::Signature( + DestinationSigError::IncompleteClassicalMultisigSignature( + required_signatures, + num_signatures, + ), + ), + ) => SignatureStatus::PartialMultisig { + required_signatures: *required_signatures, + num_signatures: *num_signatures, + }, + + _ => SignatureStatus::InvalidSignature, }, - ) + } } pub async fn compose_transaction( @@ -921,7 +930,7 @@ impl Controll input_utxos.into_iter().map(Option::Some).collect(), destinations.into_iter().map(Option::Some).collect(), ) - .map_err(ControllerError::WalletError)?; + .map_err(WalletError::TransactionCreation)?; TransactionToSign::Partial(tx) }; diff --git a/wallet/wallet-controller/src/synced_controller.rs b/wallet/wallet-controller/src/synced_controller.rs index 987882915f..9b8d2c1cab 100644 --- a/wallet/wallet-controller/src/synced_controller.rs +++ b/wallet/wallet-controller/src/synced_controller.rs @@ -19,6 +19,7 @@ use common::{ address::{pubkeyhash::PublicKeyHash, Address}, chain::{ classic_multisig::ClassicMultisigChallenge, + partially_signed_transaction::PartiallySignedTransaction, signature::inputsig::arbitrary_message::ArbitraryMessageSignature, tokens::{ IsTokenFreezable, IsTokenUnfreezable, Metadata, RPCTokenInfo, TokenId, TokenIssuance, @@ -41,10 +42,7 @@ use logging::log; use mempool::FeeRate; use node_comm::node_traits::NodeInterface; use wallet::{ - account::{ - currency_grouper::Currency, PartiallySignedTransaction, TransactionToSign, - UnconfirmedTokenInfo, - }, + account::{currency_grouper::Currency, TransactionToSign, UnconfirmedTokenInfo}, get_tx_output_destination, send_request::{ make_address_output, make_address_output_token, make_create_delegation_output, diff --git a/wallet/wallet-controller/src/types/transaction.rs b/wallet/wallet-controller/src/types/transaction.rs index 325019792d..a1aab80247 100644 --- a/wallet/wallet-controller/src/types/transaction.rs +++ b/wallet/wallet-controller/src/types/transaction.rs @@ -13,9 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::chain::{SignedTransaction, Transaction}; +use common::chain::{ + partially_signed_transaction::PartiallySignedTransaction, SignedTransaction, Transaction, +}; use serialization::hex_encoded::HexEncoded; -use wallet::account::PartiallySignedTransaction; use wallet_types::signature_status::SignatureStatus; use super::Balances; diff --git a/wallet/wallet-rpc-client/src/handles_client/mod.rs b/wallet/wallet-rpc-client/src/handles_client/mod.rs index 5411c03ed7..011220d159 100644 --- a/wallet/wallet-rpc-client/src/handles_client/mod.rs +++ b/wallet/wallet-rpc-client/src/handles_client/mod.rs @@ -19,8 +19,9 @@ use chainstate::ChainInfo; use common::{ address::{dehexify::dehexify_all_addresses, AddressError}, chain::{ - block::timestamp::BlockTimestamp, tokens::IsTokenUnfreezable, Block, GenBlock, - SignedTransaction, Transaction, TxOutput, UtxoOutPoint, + block::timestamp::BlockTimestamp, partially_signed_transaction::PartiallySignedTransaction, + tokens::IsTokenUnfreezable, Block, GenBlock, SignedTransaction, Transaction, TxOutput, + UtxoOutPoint, }, primitives::{BlockHeight, DecimalAmount, Id, Idable, H256}, }; @@ -30,10 +31,7 @@ use p2p_types::{bannable_address::BannableAddress, socket_address::SocketAddress use rpc::types::RpcHexString; use serialization::{hex::HexEncode, hex_encoded::HexEncoded, json_encoded::JsonEncoded}; use utils_networking::IpOrSocketAddress; -use wallet::{ - account::{PartiallySignedTransaction, TxInfo}, - version::get_version, -}; +use wallet::{account::TxInfo, version::get_version}; use wallet_controller::{ types::{CreatedBlockInfo, SeedWithPassPhrase, WalletInfo}, ConnectedPeer, ControllerConfig, UtxoState, UtxoType, @@ -49,7 +47,10 @@ use wallet_rpc_lib::{ }, RpcError, WalletRpc, }; -use wallet_types::{seed_phrase::StoreSeedPhrase, utxo_types::UtxoTypes, with_locked::WithLocked}; +use wallet_types::{ + seed_phrase::StoreSeedPhrase, signature_status::SignatureStatus, utxo_types::UtxoTypes, + with_locked::WithLocked, +}; use crate::wallet_rpc_traits::{PartialOrSignedTx, SignRawTransactionResult, WalletInterface}; @@ -1190,11 +1191,11 @@ impl WalletInterface .sign_raw_transaction(account_index, RpcHexString::from_str(&raw_tx)?, config) .await .map(|(ptx, prev_signatures, cur_signatures)| { - let tx = if ptx.is_fully_signed(self.wallet_rpc.chain_config()) { - PartialOrSignedTx::Signed( - ptx.into_signed_tx(self.wallet_rpc.chain_config()) - .expect("already checked"), - ) + let is_fully_signed = ptx.all_signatures_available() + && cur_signatures.iter().all(|s| *s == SignatureStatus::FullySigned); + + let tx = if is_fully_signed { + PartialOrSignedTx::Signed(ptx.into_signed_tx().expect("already checked")) } else { PartialOrSignedTx::Partial(ptx) }; diff --git a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs index 8bbd34eb81..49bfe6ca53 100644 --- a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs +++ b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs @@ -22,8 +22,8 @@ use super::{ClientWalletRpc, WalletRpcError}; use chainstate::ChainInfo; use common::{ chain::{ - block::timestamp::BlockTimestamp, Block, GenBlock, SignedTransaction, Transaction, - TxOutput, UtxoOutPoint, + block::timestamp::BlockTimestamp, partially_signed_transaction::PartiallySignedTransaction, + Block, GenBlock, SignedTransaction, Transaction, TxOutput, UtxoOutPoint, }, primitives::{BlockHeight, DecimalAmount, Id}, }; @@ -32,7 +32,7 @@ use p2p_types::{bannable_address::BannableAddress, socket_address::SocketAddress use serialization::hex_encoded::HexEncoded; use serialization::DecodeAll; use utils_networking::IpOrSocketAddress; -use wallet::account::{PartiallySignedTransaction, TxInfo}; +use wallet::account::TxInfo; use wallet_controller::{ types::{Balances, CreatedBlockInfo, SeedWithPassPhrase, WalletInfo}, ConnectedPeer, ControllerConfig, UtxoState, UtxoType, diff --git a/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs b/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs index 388d546da6..b835c83dbf 100644 --- a/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs +++ b/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs @@ -18,8 +18,8 @@ use std::{collections::BTreeMap, num::NonZeroUsize, path::PathBuf}; use chainstate::ChainInfo; use common::{ chain::{ - block::timestamp::BlockTimestamp, Block, GenBlock, SignedTransaction, Transaction, - TxOutput, UtxoOutPoint, + block::timestamp::BlockTimestamp, partially_signed_transaction::PartiallySignedTransaction, + Block, GenBlock, SignedTransaction, Transaction, TxOutput, UtxoOutPoint, }, primitives::{BlockHeight, DecimalAmount, Id}, }; @@ -27,7 +27,7 @@ use crypto::key::{hdkd::u31::U31, PrivateKey}; use p2p_types::{bannable_address::BannableAddress, socket_address::SocketAddress, PeerId}; use serialization::hex_encoded::HexEncoded; use utils_networking::IpOrSocketAddress; -use wallet::account::{PartiallySignedTransaction, TxInfo}; +use wallet::account::TxInfo; use wallet_controller::{ types::{CreatedBlockInfo, SeedWithPassPhrase, WalletInfo}, ConnectedPeer, ControllerConfig, UtxoState, UtxoType, diff --git a/wallet/wallet-rpc-lib/src/rpc/interface.rs b/wallet/wallet-rpc-lib/src/rpc/interface.rs index e76b715ed8..1b174a5054 100644 --- a/wallet/wallet-rpc-lib/src/rpc/interface.rs +++ b/wallet/wallet-rpc-lib/src/rpc/interface.rs @@ -18,15 +18,16 @@ use std::{collections::BTreeMap, num::NonZeroUsize}; use common::{ address::RpcAddress, chain::{ - block::timestamp::BlockTimestamp, tokens::TokenId, Block, DelegationId, Destination, - GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, + block::timestamp::BlockTimestamp, tokens::TokenId, + transaction::partially_signed_transaction::PartiallySignedTransaction, Block, DelegationId, + Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, }, primitives::{BlockHeight, Id}, }; use crypto::key::PrivateKey; use p2p_types::{bannable_address::BannableAddress, socket_address::SocketAddress}; use rpc::types::RpcHexString; -use wallet::account::{PartiallySignedTransaction, TxInfo}; +use wallet::account::TxInfo; use wallet_controller::{ types::{BlockInfo, CreatedBlockInfo, SeedWithPassPhrase, WalletInfo}, ConnectedPeer, diff --git a/wallet/wallet-rpc-lib/src/rpc/mod.rs b/wallet/wallet-rpc-lib/src/rpc/mod.rs index 2f90181bd7..3bdf133779 100644 --- a/wallet/wallet-rpc-lib/src/rpc/mod.rs +++ b/wallet/wallet-rpc-lib/src/rpc/mod.rs @@ -35,10 +35,7 @@ use serialization::{hex_encoded::HexEncoded, Decode, DecodeAll}; use utils::{ensure, shallow_clone::ShallowClone}; use utils_networking::IpOrSocketAddress; use wallet::{ - account::{ - transaction_list::TransactionList, PartiallySignedTransaction, PoolData, TransactionToSign, - TxInfo, - }, + account::{transaction_list::TransactionList, PoolData, TransactionToSign, TxInfo}, WalletError, }; @@ -47,6 +44,7 @@ use common::{ chain::{ block::timestamp::BlockTimestamp, classic_multisig::ClassicMultisigChallenge, + partially_signed_transaction::PartiallySignedTransaction, signature::inputsig::arbitrary_message::{ produce_message_challenge, ArbitraryMessageSignature, }, diff --git a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs index abef07302a..8a5d0d839b 100644 --- a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs +++ b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs @@ -19,6 +19,7 @@ use common::{ address::dehexify::dehexify_all_addresses, chain::{ block::timestamp::BlockTimestamp, + partially_signed_transaction::PartiallySignedTransaction, tokens::{IsTokenUnfreezable, TokenId}, Block, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, @@ -29,15 +30,14 @@ use crypto::key::PrivateKey; use p2p_types::{bannable_address::BannableAddress, socket_address::SocketAddress, PeerId}; use serialization::{hex::HexEncode, json_encoded::JsonEncoded}; use utils_networking::IpOrSocketAddress; -use wallet::{ - account::{PartiallySignedTransaction, TxInfo}, - version::get_version, -}; +use wallet::{account::TxInfo, version::get_version}; use wallet_controller::{ types::{BlockInfo, CreatedBlockInfo, SeedWithPassPhrase, WalletInfo}, ConnectedPeer, ControllerConfig, NodeInterface, UtxoState, UtxoStates, UtxoType, UtxoTypes, }; -use wallet_types::{seed_phrase::StoreSeedPhrase, with_locked::WithLocked}; +use wallet_types::{ + seed_phrase::StoreSeedPhrase, signature_status::SignatureStatus, with_locked::WithLocked, +}; use crate::{ rpc::{ColdWalletRpcServer, WalletEventsRpcServer, WalletRpc, WalletRpcServer}, @@ -229,9 +229,10 @@ impl ColdWalletRpcServ rpc::handle_result( self.sign_raw_transaction(account_arg.index::()?, raw_tx, config).await.map( |(tx, prev_signatures, cur_signatures)| { - let is_complete = tx.is_fully_signed(&self.chain_config); + let is_complete = tx.all_signatures_available() + && cur_signatures.iter().all(|s| *s == SignatureStatus::FullySigned); let hex = if is_complete { - let tx = tx.into_signed_tx(&self.chain_config).expect("already checked"); + let tx = tx.into_signed_tx().expect("already checked"); tx.hex_encode() } else { tx.hex_encode()