From f5b89add6a04b935b9ae8dda0f694eb826ef6d9a Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Wed, 30 Aug 2023 15:30:42 +0200 Subject: [PATCH] feat: change default script to PushPubKey (#5653) Description --- Change the default script to `PushPubKey` to ensure the spending of outputs from standard transactions is only performed with the use of specific private keys. Motivation and Context --- This change to the default helps enforce that UTXOS can't be spent from a software wallet, when a hardware wallet is in use. How Has This Been Tested? --- Manually, locally, but pretty tests still require updating. What process can a PR reviewer use to test or verify this change? --- Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --------- Co-authored-by: SW van Heerden --- .../comms_interface/inbound_handlers.rs | 2 +- .../unconfirmed_pool/unconfirmed_pool.rs | 6 +- .../src/transactions/key_manager/inner.rs | 27 +++++++ .../src/transactions/key_manager/interface.rs | 6 ++ .../src/transactions/key_manager/wrapper.rs | 12 +++ .../core/src/transactions/test_helpers.rs | 12 ++- .../transaction_input.rs | 3 +- .../transaction_protocol/sender.rs | 2 +- .../transaction_protocol/single_receiver.rs | 4 +- .../transaction_initializer.rs | 6 +- .../aggregate_body_internal_validator.rs | 10 +-- base_layer/core/src/validation/test.rs | 14 ++-- .../chain_storage_tests/chain_backend.rs | 2 +- .../core/tests/tests/block_validation.rs | 4 +- base_layer/core/tests/tests/mempool.rs | 17 +---- .../core/tests/tests/node_comms_interface.rs | 4 +- .../recovery/standard_outputs_recoverer.rs | 73 ++++++++++++++---- .../src/output_manager_service/service.rs | 74 ++++++++----------- .../protocols/transaction_send_protocol.rs | 4 +- .../wallet/src/transaction_service/service.rs | 3 +- .../transaction_service/storage/sqlite_db.rs | 6 +- .../output_manager_service_tests/service.rs | 28 +++---- base_layer/wallet/tests/support/utils.rs | 4 +- .../transaction_service_tests/service.rs | 8 +- .../transaction_service_tests/storage.rs | 4 +- infrastructure/tari_script/src/script.rs | 53 ++++++++++++- 26 files changed, 253 insertions(+), 135 deletions(-) diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index 0a84a991cf..b647b4d720 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -744,7 +744,7 @@ where B: BlockchainBackend + 'static ); if let Err(e) = self .connectivity - .ban_peer(source_peer.clone(), format!("Peer sen invalid API response")) + .ban_peer(source_peer.clone(), "Peer sent invalid API response".to_string()) .await { error!(target: LOG_TARGET, "Failed to ban peer: {}", e); diff --git a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs index 0fdd5928e1..abf5519f75 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -798,7 +798,7 @@ impl UnconfirmedPool { #[cfg(test)] mod test { use tari_common::configuration::Network; - use tari_script::{inputs, script}; + use tari_script::{ExecutionStack, TariScript}; use super::*; use crate::{ @@ -924,8 +924,8 @@ mod test { .with_lock_height(0) .with_fee_per_gram(5.into()) .with_change_data( - script!(Nop), - inputs!(change.script_key_pk), + TariScript::default(), + ExecutionStack::default(), change.script_key_id.clone(), change.spend_key_id.clone(), Covenant::default(), diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index 1319399e33..d7eb01d0dc 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -217,6 +217,33 @@ where TBackend: KeyManagerBackend + 'static Ok((spend_key_id, spend_public_key, script_key_id, script_public_key)) } + /// Calculates a script key id from the spend key id, if a public key is provided, it will only return a result of + /// the public keys match + pub async fn find_script_key_id_from_spend_key_id( + &self, + spend_key_id: &TariKeyId, + public_script_key: Option<&PublicKey>, + ) -> Result, KeyManagerServiceError> { + let index = match spend_key_id { + KeyId::Managed { index, .. } => *index, + KeyId::Imported { .. } => return Ok(None), + KeyId::Zero => return Ok(None), + }; + let script_key_id = KeyId::Managed { + branch: TransactionKeyManagerBranch::ScriptKey.get_branch_key(), + index, + }; + + if let Some(key) = public_script_key { + let script_public_key = self.get_public_key_at_key_id(&script_key_id).await?; + if *key == script_public_key { + return Ok(Some(script_key_id)); + } + return Ok(None); + } + Ok(Some(script_key_id)) + } + /// Search the specified branch key manager key chain to find the index of the specified key. pub async fn find_key_index(&self, branch: &str, key: &PublicKey) -> Result { let km = self diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index 95f97d19f4..65ac0f49ba 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -101,6 +101,12 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { &self, ) -> Result<(TariKeyId, PublicKey, TariKeyId, PublicKey), KeyManagerServiceError>; + async fn find_script_key_id_from_spend_key_id( + &self, + spend_key_id: &TariKeyId, + public_script_key: Option<&PublicKey>, + ) -> Result, KeyManagerServiceError>; + async fn get_diffie_hellman_shared_secret( &self, secret_key_id: &TariKeyId, diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index f4c09d118c..b0a6487684 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -206,6 +206,18 @@ where TBackend: KeyManagerBackend + 'static .await } + async fn find_script_key_id_from_spend_key_id( + &self, + spend_key_id: &TariKeyId, + public_script_key: Option<&PublicKey>, + ) -> Result, KeyManagerServiceError> { + self.transaction_key_manager_inner + .read() + .await + .find_script_key_id_from_spend_key_id(spend_key_id, public_script_key) + .await + } + async fn get_diffie_hellman_shared_secret( &self, secret_key_id: &TariKeyId, diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index cd74d2b413..df0709005e 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -615,8 +615,8 @@ pub async fn create_transaction_with( .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()) .with_change_data( - script!(Nop), - inputs!(change.script_key_pk), + TariScript::default(), + ExecutionStack::default(), change.script_key_id, change.spend_key_id, Covenant::default(), @@ -658,12 +658,16 @@ pub async fn create_stx_protocol( .clone(); let mut stx_builder = SenderTransactionProtocol::builder(constants, key_manager.clone()); let change = TestParams::new(key_manager).await; + let script_public_key = key_manager + .get_public_key_at_key_id(&change.script_key_id) + .await + .unwrap(); stx_builder .with_lock_height(schema.lock_height) .with_fee_per_gram(schema.fee) .with_change_data( - script!(Nop), - inputs!(change.script_key_pk), + script!(PushPubKey(Box::new(script_public_key))), + ExecutionStack::default(), change.script_key_id, change.spend_key_id, Covenant::default(), diff --git a/base_layer/core/src/transactions/transaction_components/transaction_input.rs b/base_layer/core/src/transactions/transaction_components/transaction_input.rs index 383b953e61..25b864e1a1 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_input.rs @@ -507,11 +507,12 @@ impl Display for TransactionInput { .. } => write!( fmt, - "({}, {}) [{:?}], Script: ({}), Offset_Pubkey: ({}), Input Hash: {}", + "({}, {}) [{:?}], Script: ({}), Input_data : ({}), Offset_Pubkey: ({}), Input Hash: {}", commitment.to_hex(), self.output_hash().to_hex(), features, script, + self.input_data.to_hex(), sender_offset_public_key.to_hex(), self.canonical_hash().to_hex(), ), diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index c6a6debd2e..8df23ac0a8 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -1057,7 +1057,7 @@ mod test { .with_lock_height(0) .with_fee_per_gram(MicroMinotari(2)) .with_change_data( - script!(Nop), + TariScript::default(), inputs!(change.script_key_pk), change.script_key_id.clone(), change.spend_key_id.clone(), diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index 81a991733e..f4941d8ba0 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -143,7 +143,7 @@ mod test { use tari_common_types::types::PublicKey; use tari_crypto::{keys::PublicKey as PublicKeyTrait, signatures::CommitmentAndPublicKeySignature}; use tari_key_manager::key_manager_service::KeyManagerInterface; - use tari_script::{script, ExecutionStack, TariScript}; + use tari_script::{script, ExecutionStack}; use crate::{ covenants::Covenant, @@ -255,7 +255,7 @@ mod test { let m = TransactionMetadata::new(MicroMinotari(100), 0); let test_params = TestParams::new(&key_manager).await; let test_params2 = TestParams::new(&key_manager).await; - let script = TariScript::default(); + let script = script!(Nop); let sender_offset_public_key = key_manager .get_public_key_at_key_id(&test_params.sender_offset_key_id) .await diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index 9a91603f35..2a94b88290 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -581,7 +581,7 @@ where KM: TransactionKeyManagerInterface #[cfg(test)] mod test { - use tari_script::{inputs, script, TariScript}; + use tari_script::{inputs, script}; use crate::{ covenants::Covenant, @@ -696,7 +696,7 @@ mod test { ); let output = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &p, MicroMinotari(5000) - expected_fee, @@ -791,7 +791,7 @@ mod test { let p = TestParams::new(&key_manager).await; let output = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &p, MicroMinotari(500), diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs index dc84c029ac..c48bc7d6f9 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs @@ -427,7 +427,7 @@ mod test { use rand::seq::SliceRandom; use tari_common::configuration::Network; use tari_common_types::types::RANGE_PROOF_AGGREGATION_FACTOR; - use tari_script::TariScript; + use tari_script::script; use super::*; use crate::{ @@ -504,7 +504,7 @@ mod test { 100.into(), &key_manager, &OutputFeatures::create_burn_output(), - &TariScript::default(), + &script!(Nop), &Covenant::default(), 0.into(), ) @@ -513,7 +513,7 @@ mod test { 101.into(), &key_manager, &OutputFeatures::create_burn_output(), - &TariScript::default(), + &script!(Nop), &Covenant::default(), 0.into(), ) @@ -522,7 +522,7 @@ mod test { 102.into(), &key_manager, &OutputFeatures::create_burn_output(), - &TariScript::default(), + &script!(Nop), &Covenant::default(), 0.into(), ) @@ -567,7 +567,7 @@ mod test { 100.into(), &key_manager, &OutputFeatures::create_burn_output(), - &TariScript::default(), + &script!(Nop), &Covenant::default(), 0.into(), ) diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index ffb7c37c8f..292ff19e87 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -25,7 +25,7 @@ use std::{cmp, sync::Arc}; use tari_common::configuration::Network; use tari_common_types::types::Commitment; use tari_crypto::commitment::HomomorphicCommitment; -use tari_script::script; +use tari_script::TariScript; use tari_test_utils::unpack_enum; use crate::{ @@ -182,7 +182,7 @@ async fn chain_balance_validation() { faucet_value, &key_manager, &OutputFeatures::default(), - &script!(Nop), + &TariScript::default(), &Covenant::default(), MicroMinotari::zero(), ) @@ -240,7 +240,7 @@ async fn chain_balance_validation() { coinbase_value, &key_manager, &OutputFeatures::create_coinbase(1, None), - &script!(Nop), + &TariScript::default(), &Covenant::default(), MicroMinotari::zero(), ) @@ -302,7 +302,7 @@ async fn chain_balance_validation() { v, &key_manager, &OutputFeatures::create_coinbase(1, None), - &script!(Nop), + &TariScript::default(), &Covenant::default(), MicroMinotari::zero(), ) @@ -367,7 +367,7 @@ async fn chain_balance_validation_burned() { faucet_value, &key_manager, &OutputFeatures::default(), - &script!(Nop), + &TariScript::default(), &Covenant::default(), MicroMinotari::zero(), ) @@ -425,7 +425,7 @@ async fn chain_balance_validation_burned() { coinbase_value, &key_manager, &OutputFeatures::create_coinbase(1, None), - &script!(Nop), + &TariScript::default(), &Covenant::default(), MicroMinotari::zero(), ) @@ -451,7 +451,7 @@ async fn chain_balance_validation_burned() { 100.into(), &key_manager, &OutputFeatures::create_burn_output(), - &script!(Nop), + &TariScript::default(), &Covenant::default(), MicroMinotari::zero(), ) diff --git a/base_layer/core/tests/chain_storage_tests/chain_backend.rs b/base_layer/core/tests/chain_storage_tests/chain_backend.rs index 2b715a274c..3c07f67221 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_backend.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_backend.rs @@ -126,7 +126,7 @@ fn test_utxo_order() { let mut utxos = Vec::with_capacity(2000); let version = TransactionOutputVersion::V0; let features = OutputFeatures::default(); - let script = TariScript::default(); + let script = script!(Nop); let proof = RangeProof::default(); let sig = ComAndPubSignature::default(); let covenant = Covenant::default(); diff --git a/base_layer/core/tests/tests/block_validation.rs b/base_layer/core/tests/tests/block_validation.rs index e274c79c6a..e1248551e5 100644 --- a/base_layer/core/tests/tests/block_validation.rs +++ b/base_layer/core/tests/tests/block_validation.rs @@ -310,7 +310,7 @@ async fn test_orphan_validator() { let key_manager = create_test_core_key_manager_with_memory_db(); let network = Network::Igor; let consensus_constants = ConsensusConstantsBuilder::new(network) - .with_max_block_transaction_weight(321) + .with_max_block_transaction_weight(325) .build(); let (genesis, outputs) = create_genesis_block_with_utxos(&[T, T, T], &consensus_constants, &key_manager).await; let network = Network::LocalNet; @@ -885,7 +885,7 @@ async fn test_block_sync_body_validator() { matches!( err, ValidationError::BlockTooLarge { actual_weight, max_weight } if - actual_weight == 449 && max_weight == 400 + actual_weight == 455 && max_weight == 400 ), "{}", err diff --git a/base_layer/core/tests/tests/mempool.rs b/base_layer/core/tests/tests/mempool.rs index 2ea124fa06..ac1ad9052e 100644 --- a/base_layer/core/tests/tests/mempool.rs +++ b/base_layer/core/tests/tests/mempool.rs @@ -152,7 +152,6 @@ async fn test_insert_and_process_published_block() { mempool.insert(tx3.clone()).await.unwrap(); mempool.insert(tx5.clone()).await.unwrap(); mempool.process_published_block(blocks[1].to_arc_block()).await.unwrap(); - assert_eq!( mempool .has_tx_with_excess_sig(orphan.body.kernels()[0].excess_sig.clone()) @@ -197,18 +196,10 @@ async fn test_insert_and_process_published_block() { let stats = mempool.stats().await.unwrap(); assert_eq!(stats.unconfirmed_txs, 1); assert_eq!(stats.reorg_txs, 0); - let expected_weight = consensus_manager - .consensus_constants(0) - .transaction_weight_params() - .calculate( - 1, - 1, - 2, - TestParams::new(&key_manager) - .await - .get_size_for_default_features_and_scripts(2) - .expect("Failed to get size for default features and scripts"), - ); + let expected_weight = tx2 + .body + .calculate_weight(consensus_manager.consensus_constants(0).transaction_weight_params()) + .unwrap(); assert_eq!(stats.unconfirmed_weight, expected_weight); // Spend tx2, so it goes in Reorg pool diff --git a/base_layer/core/tests/tests/node_comms_interface.rs b/base_layer/core/tests/tests/node_comms_interface.rs index 2d600de867..691494e35e 100644 --- a/base_layer/core/tests/tests/node_comms_interface.rs +++ b/base_layer/core/tests/tests/node_comms_interface.rs @@ -49,7 +49,7 @@ use tari_core::{ validation::{mocks::MockValidator, transaction::TransactionChainLinkedValidator}, }; use tari_key_manager::key_manager_service::KeyManagerInterface; -use tari_script::{inputs, script, TariScript}; +use tari_script::{inputs, script}; use tari_service_framework::reply_channel; use tokio::sync::{broadcast, mpsc}; @@ -195,7 +195,7 @@ async fn inbound_fetch_utxos() { MicroMinotari(10_000), &key_manager, &Default::default(), - &TariScript::default(), + &script!(Nop), &Covenant::default(), MicroMinotari::zero(), ) diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 081868e2ab..623a8ba948 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -29,7 +29,7 @@ use tari_core::transactions::{ tari_amount::MicroMinotari, transaction_components::{TransactionError, TransactionOutput, WalletOutput}, }; -use tari_script::{inputs, script, Opcode}; +use tari_script::{inputs, script, ExecutionStack, Opcode, TariScript}; use tari_utilities::hex::Hex; use crate::output_manager_service::{ @@ -37,7 +37,7 @@ use crate::output_manager_service::{ handle::RecoveredOutput, storage::{ database::{OutputManagerBackend, OutputManagerDatabase}, - models::DbWalletOutput, + models::{DbWalletOutput, KnownOneSidedPaymentScript}, OutputSource, }, }; @@ -70,9 +70,13 @@ where let known_scripts = self.db.get_all_known_one_sided_payment_scripts()?; let mut rewound_outputs: Vec = Vec::new(); + let push_pub_key_script = script!(PushPubKey(Box::default())); for output in outputs { let known_script_index = known_scripts.iter().position(|s| s.script == output.script); - if output.script != script!(Nop) && known_script_index.is_none() { + if output.script != script!(Nop) && + known_script_index.is_none() && + !output.script.pattern_match(&push_pub_key_script) + { continue; } @@ -80,19 +84,14 @@ where Some(recovered) => recovered, None => continue, }; - - let (input_data, script_key) = if let Some(index) = known_script_index { - ( - known_scripts[index].input.clone(), - known_scripts[index].script_key_id.clone(), - ) - } else { - let (key, public_key) = self - .master_key_manager - .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) - .await?; - (inputs!(public_key), key) + let (input_data, script_key) = match self + .find_script_key(&output.script, &spending_key, known_script_index, &known_scripts) + .await? + { + Some((input_data, script_key)) => (input_data, script_key), + None => continue, }; + let uo = WalletOutput::new_with_rangeproof( output.version, committed_value, @@ -169,6 +168,50 @@ where Ok(rewound_outputs_with_tx_id) } + async fn find_script_key( + &self, + script: &TariScript, + spending_key: &TariKeyId, + known_script_index: Option, + known_scripts: &[KnownOneSidedPaymentScript], + ) -> Result, OutputManagerError> { + let (input_data, script_key) = if script == &script!(Nop) { + // This is a nop, so we can just create a new key an create the input stack. + let (key, public_key) = self + .master_key_manager + .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) + .await?; + (inputs!(public_key), key) + } else { + // This is a known script so lets fill in the details + if let Some(index) = known_script_index { + ( + known_scripts[index].input.clone(), + known_scripts[index].script_key_id.clone(), + ) + } else { + // this is push public key script, so lets see if we know the public key + if let Some(Opcode::PushPubKey(public_key)) = script.opcode(0) { + let result = self + .master_key_manager + .find_script_key_id_from_spend_key_id(spending_key, Some(public_key)) + .await?; + if let Some(script_key_id) = result { + (ExecutionStack::default(), script_key_id) + } else { + // The spending key is recoverable but we dont know how to calculate the script key + return Ok(None); + } + } else { + // this should not happen as the script should have been either nop, known or a pushpubkey + // script, but somehow opcode 0 is not pushPubKey + return Ok(None); + } + } + }; + Ok(Some((input_data, script_key))) + } + async fn attempt_output_recovery( &self, output: &TransactionOutput, diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index dc443e3f79..4949b07c24 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -59,7 +59,7 @@ use tari_core::{ SenderTransactionProtocol, }, }; -use tari_script::{inputs, script, Opcode, TariScript}; +use tari_script::{inputs, script, ExecutionStack, Opcode, TariScript}; use tari_service_framework::reply_channel; use tari_shutdown::ShutdownSignal; use tari_utilities::{hex::Hex, ByteArray}; @@ -661,10 +661,10 @@ where value: MicroMinotari, features: OutputFeatures, ) -> Result { - let (spending_key_id, _, script_key_id, script_public_key) = + let (spending_key_id, _spending_key_id, script_key_id, _script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; - let input_data = inputs!(script_public_key); - let script = script!(Nop); + let input_data = ExecutionStack::default(); + let script = TariScript::default(); Ok(WalletOutputBuilder::new(value, spending_key_id) .with_features(features) @@ -747,7 +747,7 @@ where spending_key_id.clone(), single_round_sender_data.features.clone(), script, - inputs!(script_public_key), + ExecutionStack::default(), script_key_id, single_round_sender_data.sender_offset_public_key.clone(), // Note: The signature at this time is only partially built @@ -802,7 +802,7 @@ where num_kernels, num_outputs ); - // We assume that default OutputFeatures and Nop TariScript is used + // We assume that default OutputFeatures and PushPubKey TariScript is used let features_and_scripts_byte_size = self .resources .consensus_constants @@ -811,7 +811,7 @@ where OutputFeatures::default() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + - script![Nop] + TariScript::default() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + Covenant::new() @@ -842,7 +842,7 @@ where output_features_estimate .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + - script![Nop] + TariScript::default() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + Covenant::new() @@ -945,8 +945,8 @@ where let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; builder.with_change_data( - script!(Nop), - inputs!(change_script_public_key), + script!(PushPubKey(Box::new(change_script_public_key.clone()))), + ExecutionStack::default(), change_script_key_id, change_spending_key_id, Covenant::default(), @@ -1113,8 +1113,8 @@ where let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; builder.with_change_data( - script!(Nop), - inputs!(change_script_public_key), + script!(PushPubKey(Box::new(change_script_public_key))), + ExecutionStack::default(), change_script_key_id, change_spending_key_id, Covenant::default(), @@ -1186,7 +1186,6 @@ where fee_per_gram: MicroMinotari, lock_height: Option, ) -> Result<(MicroMinotari, Transaction), OutputManagerError> { - let script = script!(Nop); let covenant = Covenant::default(); let features_and_scripts_byte_size = self @@ -1197,7 +1196,7 @@ where output_features .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + - script + TariScript::default() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + covenant @@ -1231,7 +1230,7 @@ where builder.with_input(kmo.wallet_output.clone()).await?; } - let (output, sender_offset_key_id) = self.output_to_self(output_features, amount, covenant, script).await?; + let (output, sender_offset_key_id) = self.output_to_self(output_features, amount, covenant).await?; builder .with_output(output.wallet_output.clone(), sender_offset_key_id.clone()) @@ -1243,8 +1242,8 @@ where let (change_spending_key_id, _spend_public_key, change_script_key_id, change_script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; builder.with_change_data( - script!(Nop), - inputs!(change_script_public_key), + script!(PushPubKey(Box::new(change_script_public_key.clone()))), + ExecutionStack::default(), change_script_key_id.clone(), change_spending_key_id, Covenant::default(), @@ -1374,7 +1373,7 @@ where Covenant::new() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + - script![Nop] + TariScript::default() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))?, ); @@ -1466,7 +1465,7 @@ where .consensus_constants .transaction_weight_params() .round_up_features_and_scripts_size( - script!(Nop) + TariScript::default() .get_serialized_size() .map_err(|e| OutputManagerError::ConversionError(e.to_string()))? + OutputFeatures::default() @@ -1681,12 +1680,7 @@ where }; let (output, sender_offset_key_id) = self - .output_to_self( - OutputFeatures::default(), - amount_per_split, - Covenant::default(), - script!(Nop), - ) + .output_to_self(OutputFeatures::default(), amount_per_split, Covenant::default()) .await?; tx_builder @@ -1841,12 +1835,7 @@ where for _ in 0..number_of_splits { let (output, sender_offset_key_id) = self - .output_to_self( - OutputFeatures::default(), - amount_per_split, - Covenant::default(), - script!(Nop), - ) + .output_to_self(OutputFeatures::default(), amount_per_split, Covenant::default()) .await?; tx_builder @@ -1864,8 +1853,8 @@ where let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; tx_builder.with_change_data( - script!(Nop), - inputs!(change_script_public_key), + script!(PushPubKey(Box::new(change_script_public_key))), + ExecutionStack::default(), change_script_key_id, change_spending_key_id, Covenant::default(), @@ -1939,10 +1928,10 @@ where output_features: OutputFeatures, amount: MicroMinotari, covenant: Covenant, - script: TariScript, ) -> Result<(DbWalletOutput, TariKeyId), OutputManagerError> { let (spending_key_id, _, script_key_id, script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let script = script!(PushPubKey(Box::new(script_public_key.clone()))); let encrypted_data = self .resources @@ -1982,7 +1971,7 @@ where spending_key_id, output_features, script, - inputs!(script_public_key), + ExecutionStack::default(), script_key_id, sender_offset_public_key, metadata_signature, @@ -2066,12 +2055,7 @@ where } let (output, sender_offset_key_id) = self - .output_to_self( - OutputFeatures::default(), - accumulated_amount, - Covenant::default(), - script!(Nop), - ) + .output_to_self(OutputFeatures::default(), accumulated_amount, Covenant::default()) .await?; tx_builder @@ -2201,8 +2185,8 @@ where let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; builder.with_change_data( - script!(Nop), - inputs!(change_script_public_key), + script!(PushPubKey(Box::new(change_script_public_key.clone()))), + ExecutionStack::default(), change_script_key_id, change_spending_key_id, Covenant::default(), @@ -2282,8 +2266,8 @@ where let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = self.resources.key_manager.get_next_spend_and_script_key_ids().await?; builder.with_change_data( - script!(Nop), - inputs!(change_script_public_key), + script!(PushPubKey(Box::new(change_script_public_key.clone()))), + ExecutionStack::default(), change_script_key_id, change_spending_key_id, Covenant::default(), diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index 7e5bef87ce..24f5b332ba 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -50,7 +50,7 @@ use tari_core::{ }, }; use tari_p2p::tari_message::TariMessageType; -use tari_script::script; +use tari_script::TariScript; use tokio::{ sync::{mpsc::Receiver, oneshot}, time::sleep, @@ -224,7 +224,7 @@ where self.fee_per_gram, self.tx_meta.clone(), self.message.clone(), - script!(Nop), + TariScript::default(), Covenant::default(), MicroMinotari::zero(), ) diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 9e710be811..71e61edfe7 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -1523,6 +1523,7 @@ where .cloned() .map(OutputFeatures::create_burn_confidential_output) .unwrap_or_else(OutputFeatures::create_burn_output); + // Prepare sender part of the transaction let tx_meta = TransactionMetadata::new_with_features(0.into(), 0, KernelFeatures::create_burn()); let mut stp = self @@ -1536,7 +1537,7 @@ where fee_per_gram, tx_meta, message.clone(), - TariScript::default(), + script!(Nop), Covenant::default(), MicroMinotari::zero(), ) diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 7a6e8870c4..66d639bdde 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -2269,7 +2269,7 @@ mod test { SenderTransactionProtocol, }; use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}; - use tari_script::{inputs, script, TariScript}; + use tari_script::{inputs, script}; use tari_test_utils::random::string; use tempfile::tempdir; @@ -2330,7 +2330,7 @@ mod test { let mut builder = SenderTransactionProtocol::builder(constants, key_manager.clone()); let test_params = TestParams::new(&key_manager).await; let input = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &test_params, MicroMinotari::from(100_000), @@ -2434,7 +2434,7 @@ mod test { ); let output = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &test_params, MicroMinotari::from(100_000), diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index a7ef952308..aff4db5e30 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -95,7 +95,7 @@ use crate::support::{ fn default_features_and_scripts_size_byte_size() -> std::io::Result { Ok(TransactionWeight::latest().round_up_features_and_scripts_size( - OutputFeatures::default().get_serialized_size()? + script![Nop].get_serialized_size()?, + OutputFeatures::default().get_serialized_size()? + TariScript::default().get_serialized_size()?, )) } @@ -379,7 +379,7 @@ async fn fee_estimate() { ) .await .unwrap(); - assert_eq!(fee, MicroMinotari::from(365)); + assert_eq!(fee, MicroMinotari::from(375)); } #[allow(clippy::identity_op)] @@ -477,14 +477,14 @@ async fn test_utxo_selection_no_chain_metadata() { .fee_estimate(spendable_amount, UtxoSelectionCriteria::default(), fee_per_gram, 1, 2) .await .unwrap(); - assert_eq!(fee, MicroMinotari::from(252)); + assert_eq!(fee, MicroMinotari::from(256)); let broke_amount = spendable_amount + MicroMinotari::from(2000); let fee = oms .fee_estimate(broke_amount, UtxoSelectionCriteria::default(), fee_per_gram, 1, 2) .await .unwrap(); - assert_eq!(fee, MicroMinotari::from(252)); + assert_eq!(fee, MicroMinotari::from(256)); // coin split uses the "Largest" selection strategy let (_, tx, utxos_total_value) = oms.create_coin_split(vec![], amount, 5, fee_per_gram).await.unwrap(); @@ -585,7 +585,7 @@ async fn test_utxo_selection_with_chain_metadata() { .fee_estimate(spendable_amount, UtxoSelectionCriteria::default(), fee_per_gram, 1, 2) .await .unwrap(); - assert_eq!(fee, MicroMinotari::from(252)); + assert_eq!(fee, MicroMinotari::from(256)); // test coin split is maturity aware let (_, tx, utxos_total_value) = oms.create_coin_split(vec![], amount, 5, fee_per_gram).await.unwrap(); @@ -830,7 +830,7 @@ async fn send_no_change() { fee_per_gram, TransactionMetadata::default(), "".to_string(), - script!(Nop), + TariScript::default(), Covenant::default(), MicroMinotari::zero(), ) @@ -862,7 +862,7 @@ async fn send_not_enough_for_change() { oms.output_manager_handle .add_output( create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &TestParams::new(&key_manager).await, value1, @@ -878,7 +878,7 @@ async fn send_not_enough_for_change() { oms.output_manager_handle .add_output( create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &TestParams::new(&key_manager).await, value2, @@ -1203,7 +1203,7 @@ async fn coin_split_no_change() { let backend = OutputManagerSqliteDatabase::new(connection.clone()); let mut oms = setup_output_manager_service(backend, true).await; - let fee_per_gram = MicroMinotari::from(4); + let fee_per_gram = MicroMinotari::from(5); let split_count = 15; let constants = create_consensus_constants(0); let fee_calc = Fee::new(*constants.transaction_weight_params()); @@ -1439,7 +1439,7 @@ async fn test_txo_validation() { MicroMinotari::from(10), TransactionMetadata::default(), "".to_string(), - script!(Nop), + TariScript::default(), Covenant::default(), MicroMinotari::zero(), ) @@ -1509,7 +1509,7 @@ async fn test_txo_validation() { balance.pending_incoming_balance, MicroMinotari::from(output1_value) - MicroMinotari::from(900_000) - - MicroMinotari::from(1280) + //Output4 = output 1 -900_000 and 1280 for fees + MicroMinotari::from(1320) + //Output4 = output 1 -900_000 and 1320 for fees MicroMinotari::from(8_000_000) ); @@ -1659,7 +1659,7 @@ async fn test_txo_validation() { balance.available_balance, MicroMinotari::from(output2_value) + MicroMinotari::from(output3_value) + MicroMinotari::from(output1_value) - MicroMinotari::from(900_000) - - MicroMinotari::from(1280) + //spent 900_000 and 1280 for fees + MicroMinotari::from(1320) + //spent 900_000 and 1320 for fees MicroMinotari::from(8_000_000) + //output 5 MicroMinotari::from(16_000_000) // output 6 ); @@ -1804,7 +1804,7 @@ async fn test_txo_validation() { assert_eq!(balance.pending_outgoing_balance, MicroMinotari::from(output1_value)); assert_eq!( balance.pending_incoming_balance, - MicroMinotari::from(output1_value) - MicroMinotari::from(901_280) + MicroMinotari::from(output1_value) - MicroMinotari::from(901_320) ); assert_eq!(MicroMinotari::from(0), balance.time_locked_balance.unwrap()); @@ -1866,7 +1866,7 @@ async fn test_txo_validation() { assert_eq!( balance.available_balance, MicroMinotari::from(output2_value) + MicroMinotari::from(output3_value) + MicroMinotari::from(output1_value) - - MicroMinotari::from(901_280) + MicroMinotari::from(901_320) ); assert_eq!(balance.pending_outgoing_balance, MicroMinotari::from(0)); assert_eq!(balance.pending_incoming_balance, MicroMinotari::from(0)); diff --git a/base_layer/wallet/tests/support/utils.rs b/base_layer/wallet/tests/support/utils.rs index 44d58dfd4b..b91be5482c 100644 --- a/base_layer/wallet/tests/support/utils.rs +++ b/base_layer/wallet/tests/support/utils.rs @@ -38,7 +38,7 @@ use tari_core::{ }, }; use tari_key_manager::key_manager_service::KeyManagerInterface; -use tari_script::{inputs, script}; +use tari_script::{inputs, script, TariScript}; pub async fn make_input( _rng: &mut R, @@ -47,7 +47,7 @@ pub async fn make_input( key_manager: &TestKeyManager, ) -> WalletOutput { let test_params = TestParams::new(key_manager).await; - create_wallet_output_with_data(script!(Nop), features.clone(), &test_params, val, key_manager) + create_wallet_output_with_data(TariScript::default(), features.clone(), &test_params, val, key_manager) .await .unwrap() } diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 0efaadc3ed..a9b60d1c61 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -155,7 +155,7 @@ use tari_key_manager::{ key_manager_service::{storage::sqlite_db::KeyManagerSqliteDatabase, KeyId, KeyManagerInterface}, }; use tari_p2p::{comms_connector::pubsub_connector, domain_message::DomainMessage, Network}; -use tari_script::{inputs, one_sided_payment_script, script, ExecutionStack, TariScript}; +use tari_script::{inputs, one_sided_payment_script, script, ExecutionStack}; use tari_service_framework::{reply_channel, RegisterHandle, StackBuilder}; use tari_shutdown::{Shutdown, ShutdownSignal}; use tari_test_utils::{comms_and_services::get_next_memory_address, random}; @@ -2490,7 +2490,7 @@ async fn test_transaction_cancellation() { let key_manager = create_test_core_key_manager_with_memory_db(); let input = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &TestParams::new(&key_manager).await, MicroMinotari::from(100_000), @@ -2577,7 +2577,7 @@ async fn test_transaction_cancellation() { // Lets cancel the last one using a Comms stack message let input = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &TestParams::new(&key_manager.clone()).await, MicroMinotari::from(100_000), @@ -5156,7 +5156,7 @@ async fn test_transaction_timeout_cancellation() { // First we will check the Send Transction message let key_manager = create_test_core_key_manager_with_memory_db(); let input = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &TestParams::new(&key_manager).await, MicroMinotari::from(100_000), diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 55056bd60a..fb4044c888 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -68,7 +68,7 @@ use tari_core::{ }; use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}; use tari_key_manager::key_manager_service::KeyManagerInterface; -use tari_script::{inputs, script, TariScript}; +use tari_script::{inputs, script}; use tari_test_utils::random; use tempfile::tempdir; @@ -76,7 +76,7 @@ pub async fn test_db_backend(backend: T) { let mut db = TransactionDatabase::new(backend); let key_manager = create_test_core_key_manager_with_memory_db(); let input = create_wallet_output_with_data( - TariScript::default(), + script!(Nop), OutputFeatures::default(), &TestParams::new(&key_manager).await, MicroMinotari::from(100_000), diff --git a/infrastructure/tari_script/src/script.rs b/infrastructure/tari_script/src/script.rs index f79a428f83..39312949ca 100644 --- a/infrastructure/tari_script/src/script.rs +++ b/infrastructure/tari_script/src/script.rs @@ -91,6 +91,34 @@ impl TariScript { TariScript { script } } + /// This pattern matches two scripts ensure they have the same instructions in the opcodes, but not the same values + /// inside example: + /// Script A = {PushPubKey(AA)}, Script B = {PushPubKey(BB)} will pattern match, but doing Script A == Script B will + /// not match Script A = {PushPubKey(AA)}, Script B = {PushPubKey(AA)} will pattern match, doing Script A == + /// Script B will also match Script A = {PushPubKey(AA)}, Script B = {PushHash(BB)} will not pattern match, and + /// doing Script A == Script B will not match + pub fn pattern_match(&self, script: &TariScript) -> bool { + for (i, opcode) in self.script.iter().enumerate() { + if let Some(code) = script.opcode(i) { + if std::mem::discriminant(opcode) != std::mem::discriminant(code) { + return false; + } + } else { + return false; + } + } + // We need to ensure they are the same length + script.opcode(self.script.len()).is_none() + } + + /// Retrieve the opcode at the index, returns None if the index does not exist + pub fn opcode(&self, i: usize) -> Option<&Opcode> { + if i >= self.script.len() { + return None; + } + Some(&self.script[i]) + } + /// Executes the script using a default context. If successful, returns the final stack item. pub fn execute(&self, inputs: &ExecutionStack) -> Result { self.execute_with_context(inputs, &ScriptContext::default()) @@ -625,10 +653,10 @@ impl Hex for TariScript { } } -/// The default Tari script is to push a single zero onto the stack; which will execute successfully with zero inputs. +/// The default Tari script is to push a sender pubkey onto the stack impl Default for TariScript { fn default() -> Self { - script!(PushZero) + script!(PushPubKey(Box::default())) } } @@ -696,6 +724,27 @@ mod test { ScriptContext::new(height, &HashValue::default(), &PedersenCommitment::default()) } + #[test] + fn pattern_match() { + let script_a = script!(Or(1)); + let script_b = script!(Or(1)); + assert_eq!(script_a, script_b); + assert!(script_a.pattern_match(&script_b)); + + let script_b = script!(Or(2)); + assert_ne!(script_a, script_b); + assert!(script_a.pattern_match(&script_b)); + + let script_b = script!(Or(2) Or(2)); + assert_ne!(script_a, script_b); + assert!(!script_a.pattern_match(&script_b)); + + let script_a = script!(Or(2) Or(1)); + let script_b = script!(Or(3) Or(5)); + assert_ne!(script_a, script_b); + assert!(script_a.pattern_match(&script_b)); + } + #[test] fn op_or() { let script = script!(Or(1));