diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 00c3d2eeca..4667c3637a 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -221,6 +221,8 @@ async fn finalise_aggregate_utxo( script_signatures: Vec, wallet_script_secret_key: PrivateKey, ) -> Result { + trace!(target: LOG_TARGET, "finalise_aggregate_utxo: start"); + let mut meta_sig = Signature::default(); for sig in &meta_signatures { meta_sig = &meta_sig + sig; @@ -229,6 +231,7 @@ async fn finalise_aggregate_utxo( for sig in &script_signatures { script_sig = &script_sig + sig; } + trace!(target: LOG_TARGET, "finalise_aggregate_utxo: aggregated signatures"); wallet_transaction_service .finalize_aggregate_utxo(tx_id, meta_sig, script_sig, wallet_script_secret_key) @@ -807,7 +810,7 @@ pub async fn command_runner( break; }, }; - let signature = match key_manager_service + let verification_signature = match key_manager_service .sign_script_message(&key_id, PrivateKey::from(index).as_bytes()) .await { @@ -821,7 +824,7 @@ pub async fn command_runner( outputs_for_leader.push(PreMineCreateStep1ForLeader { index, script_public_key, - verification_signature: signature, + verification_signature, }); } if error { @@ -1186,6 +1189,14 @@ pub async fn command_runner( println!(); }, PreMineSpendSessionInfo(args) => { + match key_manager_service.get_wallet_type().await { + WalletType::Ledger(_) => {}, + _ => { + eprintln!("\nError: Wallet type must be 'Ledger' to spend pre-mine outputs!\n"); + break; + }, + } + let embedded_output = match get_embedded_pre_mine_outputs(vec![args.output_index]) { Ok(outputs) => outputs[0].clone(), Err(e) => { @@ -1240,6 +1251,14 @@ pub async fn command_runner( println!(); }, PreMineSpendPartyDetails(args) => { + match key_manager_service.get_wallet_type().await { + WalletType::Ledger(_) => {}, + _ => { + eprintln!("\nError: Wallet type must be 'Ledger' to spend pre-mine outputs!\n"); + break; + }, + } + if args.alias.is_empty() || args.alias.contains(" ") { eprintln!("\nError: Alias cannot contain spaces!\n"); break; @@ -1313,15 +1332,13 @@ pub async fn command_runner( break; }, }; - let script_input_signature = key_manager_service - .sign_script_message(&wallet_spend_key.key_id, commitment.as_bytes()) + .sign_script_message(&pre_mine_script_key_id, commitment.as_bytes()) .await?; let out_dir = out_dir(&session_info.session_id, Context::Spend)?; let step_2_outputs_for_leader = PreMineSpendStep2OutputsForLeader { script_input_signature, - wallet_public_spend_key: wallet_spend_key.pub_key, public_script_nonce_key: script_nonce_key.pub_key, public_sender_offset_key: sender_offset_key.pub_key, public_sender_offset_nonce_key: sender_offset_nonce.pub_key, @@ -1355,6 +1372,14 @@ pub async fn command_runner( println!(); }, PreMineSpendEncumberAggregateUtxo(args) => { + match key_manager_service.get_wallet_type().await { + WalletType::Ledger(_) => {}, + _ => { + eprintln!("\nError: Wallet type must be 'Ledger' to spend pre-mine outputs!\n"); + break; + }, + } + // Read session info let session_info = read_verify_session_info::(&args.session_id)?; @@ -1441,6 +1466,14 @@ pub async fn command_runner( } }, PreMineSpendInputOutputSigs(args) => { + match key_manager_service.get_wallet_type().await { + WalletType::Ledger(_) => {}, + _ => { + eprintln!("\nError: Wallet type must be 'Ledger' to spend pre-mine outputs!\n"); + break; + }, + } + // Read session info let session_info = read_verify_session_info::(&args.session_id)?; // Read leader input @@ -1484,7 +1517,7 @@ pub async fn command_runner( // Metadata signature let script_offset = key_manager_service - .get_script_offset(&vec![party_info.wallet_spend_key_id], &vec![party_info + .get_script_offset(&vec![party_info.pre_mine_script_key_id], &vec![party_info .sender_offset_key_id .clone()]) .await?; @@ -1545,6 +1578,14 @@ pub async fn command_runner( } }, PreMineSpendAggregateTransaction(args) => { + match key_manager_service.get_wallet_type().await { + WalletType::Ledger(_) => {}, + _ => { + eprintln!("\nError: Wallet type must be 'Ledger' to spend pre-mine outputs!\n"); + break; + }, + } + // Read session info let session_info = read_verify_session_info::(&args.session_id)?; diff --git a/applications/minotari_console_wallet/src/automation/mod.rs b/applications/minotari_console_wallet/src/automation/mod.rs index 27ab569548..59caf40619 100644 --- a/applications/minotari_console_wallet/src/automation/mod.rs +++ b/applications/minotari_console_wallet/src/automation/mod.rs @@ -79,7 +79,6 @@ struct PreMineSpendStep2OutputsForSelf { #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct PreMineSpendStep2OutputsForLeader { script_input_signature: CheckSigSchnorrSignature, - wallet_public_spend_key: PublicKey, public_script_nonce_key: PublicKey, public_sender_offset_key: PublicKey, public_sender_offset_nonce_key: PublicKey, diff --git a/applications/minotari_ledger_wallet/comms/src/accessor_methods.rs b/applications/minotari_ledger_wallet/comms/src/accessor_methods.rs index abf94f2d33..1774436eda 100644 --- a/applications/minotari_ledger_wallet/comms/src/accessor_methods.rs +++ b/applications/minotari_ledger_wallet/comms/src/accessor_methods.rs @@ -48,7 +48,10 @@ pub fn verify_ledger_application() -> Result<(), LedgerDeviceError> { if let Ok(mut verified) = VERIFIED.try_lock() { if verified.is_none() { match verify() { - Ok(_) => *verified = Some(Ok(())), + Ok(_) => { + debug!(target: LOG_TARGET, "Ledger application 'Minotari Wallet' running and verified"); + *verified = Some(Ok(())) + }, Err(e) => return Err(e), } } diff --git a/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs b/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs index 0bae2d5c6a..ba01110554 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs @@ -21,10 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use derivative::Derivative; -use tari_common_types::{ - key_branches::TransactionKeyManagerBranch, - types::{ComAndPubSignature, PublicKey}, -}; +use tari_common_types::types::{ComAndPubSignature, PublicKey}; use tari_script::{ExecutionStack, TariScript}; use crate::{ @@ -229,9 +226,7 @@ impl WalletOutputBuilder { let aggregate_sender_offset_public_key = aggregated_sender_offset_public_key_shares + &sender_offset_public_key_self; - let ephemeral_pubkey_self = key_manager - .get_next_key(TransactionKeyManagerBranch::MetadataEphemeralNonce.get_branch_key()) - .await?; + let ephemeral_pubkey_self = key_manager.get_random_key().await?; let aggregate_ephemeral_pubkey = aggregated_ephemeral_public_key_shares + &ephemeral_pubkey_self.pub_key; let receiver_partial_metadata_signature = key_manager @@ -314,6 +309,7 @@ impl WalletOutputBuilder { #[cfg(test)] mod test { + use tari_common_types::key_branches::TransactionKeyManagerBranch; use tari_key_manager::key_manager_service::KeyManagerInterface; use super::*; diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 9b1fd16eae..f64cfef97c 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -68,6 +68,7 @@ use tari_core::{ }, }; use tari_crypto::{keys::SecretKey, ristretto::pedersen::PedersenCommitment}; +use tari_key_manager::key_manager_service::{KeyAndId, KeyId}; use tari_script::{ inputs, push_pubkey_script, @@ -1165,6 +1166,32 @@ where Ok((tx_id, stp.into_transaction()?)) } + async fn pre_mine_script_key_from_payment_id( + &self, + payment_id: PaymentId, + tx_id: TxId, + ) -> Result, OutputManagerError> { + if let PaymentId::U64(index) = payment_id { + let script_key_id = KeyId::Managed { + branch: TransactionKeyManagerBranch::PreMine.get_branch_key(), + index, + }; + Ok(KeyAndId:: { + pub_key: self + .resources + .key_manager + .get_public_key_at_key_id(&script_key_id) + .await?, + key_id: script_key_id, + }) + } else { + Err(OutputManagerError::ServiceError(format!( + "Invalid payment id (TxId: {}): expected 'PaymentId::U64(_)', received {:?}", + tx_id, payment_id + ))) + } + } + /// Create a partial transaction in order to prepare output #[allow(clippy::too_many_lines)] #[allow(clippy::mutable_key_type)] @@ -1195,6 +1222,7 @@ where ), OutputManagerError, > { + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: start"); // Fetch the output from the blockchain let output = self .fetch_unspent_outputs_from_node(vec![output_hash]) @@ -1212,63 +1240,62 @@ where tx_id ))); } + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: fetched outputs"); // Retrieve the list of n public keys from the script - let public_keys = if let Some(Opcode::CheckMultiSigVerifyAggregatePubKey(_n, _m, keys, _msg)) = - output.script.as_slice().get(3) - { - keys.clone() - } else { - return Err(OutputManagerError::ServiceError(format!( - "Invalid script (TxId: {})", - tx_id - ))); - }; + let (multi_sig_public_keys, threshold) = get_multi_sig_script_components(&output.script, tx_id)?; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: retrieved public keys from script"); // Create a deterministic encryption key from the sum of the public keys - let sum_public_keys = public_keys + let sum_public_keys = multi_sig_public_keys .iter() .fold(tari_common_types::types::PublicKey::default(), |acc, x| acc + x); let encryption_private_key = public_key_to_output_encryption_key(&sum_public_keys)?; let mut aggregated_script_public_key_shares = PublicKey::default(); + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created deterministic encryption key"); // Decrypt the output secrets and create a new input as WalletOutput (unblinded) - let input = if let Ok((amount, spending_key, payment_id)) = + let input = if let Ok((amount, commitment_mask, payment_id)) = EncryptedData::decrypt_data(&encryption_private_key, &output.commitment, &output.encrypted_data) { - if output.verify_mask(&self.resources.factories.range_proof, &spending_key, amount.as_u64())? { + if output.verify_mask(&self.resources.factories.range_proof, &commitment_mask, amount.as_u64())? { + let script_key = self + .pre_mine_script_key_from_payment_id(payment_id.clone(), tx_id) + .await?; let mut script_signatures = Vec::new(); // lets add our own signature to the list let self_signature = self .resources .key_manager - .sign_script_message( - &self.resources.key_manager.get_spend_key().await?.key_id, - output.commitment.as_bytes(), - ) + .sign_script_message(&script_key.key_id, output.commitment.as_bytes()) .await?; - script_input_shares.insert( - self.resources.key_manager.get_spend_key().await?.pub_key, - self_signature, - ); + script_input_shares.insert(script_key.pub_key.clone(), self_signature); - // the order here is important, we need to add the signatures in the same order as public keys where + // the order here is important, we need to add the signatures in the same order as public keys were // added to the script originally - for key in public_keys { + for key in multi_sig_public_keys { if let Some(signature) = script_input_shares.get(&key) { script_signatures.push(StackItem::Signature(signature.clone())); - // our own key should not be added yet, it will be added with the script signing - if key != self.resources.key_manager.get_spend_key().await?.pub_key { + // our own key should not be aggregated yet, it will be added with the script signing + if key != script_key.pub_key { aggregated_script_public_key_shares = aggregated_script_public_key_shares + key; } } } - let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; + if script_signatures.len() != usize::from(threshold) { + return Err(OutputManagerError::ServiceError(format!( + "Invalid number of signatures (TxId: {}), expected {}, received {}", + tx_id, + threshold, + script_signatures.len() + ))); + } + let commitment_mask_key_id = self.resources.key_manager.import_key(commitment_mask).await?; WalletOutput::new_with_rangeproof( output.version, amount, - spending_key_id, + commitment_mask_key_id, output.features, output.script, ExecutionStack::new(script_signatures), - self.resources.key_manager.get_spend_key().await?.key_id, // Only of the master wallet + script_key.key_id.clone(), // Only of the master wallet output.sender_offset_public_key, output.metadata_signature, 0, @@ -1290,6 +1317,8 @@ where tx_id ))); }; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: decrypt secrets, created unblinded input"); + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: {:?}", input.input_data); // The entire input will be spent to a single recipient with no change let output_features = OutputFeatures { @@ -1310,6 +1339,7 @@ where let fee = self.get_fee_calc(); let fee = fee.calculate(fee_per_gram, 1, 1, 1, metadata_byte_size); let amount = input.value - fee; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created script"); // Create sender transaction protocol builder with recipient data and no change let mut builder = SenderTransactionProtocol::builder( @@ -1342,6 +1372,14 @@ where .build() .await .map_err(|e| OutputManagerError::BuildError(e.message))?; + stp.change_recipient_sender_offset_private_key( + self.resources + .key_manager + .get_next_key(TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key()) + .await? + .key_id, + )?; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created sender transaction protocol"); // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, // but the returned value is not used @@ -1382,6 +1420,7 @@ where key_sum = key_sum + &PublicKey::from_vec(&shared_secret_self.as_bytes().to_vec())?; CommsDHKE::from_canonical_bytes(key_sum.as_bytes())? }; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created dh shared secret"); let spending_key = shared_secret_to_output_spending_key(&shared_secret)?; let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; @@ -1404,10 +1443,11 @@ where .await .map_err(|e| service_error_with_id(tx_id, e.to_string(), true))?, ); - let aggregated_metadata_ephemeral_public_key_shares = metadata_ephemeral_public_key_shares .iter() .fold(PublicKey::default(), |acc, x| acc + x); + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: prepared inputs for partial metadata signature"); + // Create the output with a partially signed metadata signature let output = WalletOutputBuilder::new(amount, spending_key_id) .with_features( @@ -1442,6 +1482,7 @@ where .map_err(|e|service_error_with_id(tx_id, e.to_string(), true))?; let total_metadata_ephemeral_public_key = aggregated_metadata_ephemeral_public_key_shares + output.metadata_signature.ephemeral_pubkey(); + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created output with partial metadata signature"); // Finalize the partial transaction - it will not be valid at this stage as the metadata and script // signatures are not yet complete. @@ -1458,6 +1499,7 @@ where .await .map_err(|e| service_error_with_id(tx_id, e.to_string(), true))?; info!(target: LOG_TARGET, "Finalized partial one-side transaction TxId: {}", tx_id); + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: finalized partial transaction"); let aggregated_script_signature_public_nonces = script_signature_public_nonces .iter() @@ -1471,6 +1513,7 @@ where &self.resources.key_manager, ) .await?; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: updated script input signature"); let total_script_nonce = aggregated_script_signature_public_nonces + updated_input.script_signature.ephemeral_pubkey(); @@ -1478,6 +1521,7 @@ where let mut tx_body = tx.body; tx_body.update_script_signature(updated_input.commitment()?, updated_input.script_signature.clone())?; tx.body = tx_body; + trace!(target: LOG_TARGET, "encumber_aggregate_utxo: updated script signature"); let fee = stp.get_fee_amount()?; @@ -2842,6 +2886,20 @@ where } } +fn get_multi_sig_script_components( + script: &TariScript, + tx_id: TxId, +) -> Result<(Vec, u8), OutputManagerError> { + if let Some(Opcode::CheckMultiSigVerifyAggregatePubKey(m, _n, keys, _msg)) = script.as_slice().get(3) { + Ok((keys.clone(), *m)) + } else { + Err(OutputManagerError::ServiceError(format!( + "Invalid script (TxId: {})", + tx_id + ))) + } +} + fn service_error_with_id(tx_id: TxId, err: String, log_error: bool) -> OutputManagerError { let err_str = format!("TxId: {} ({})", tx_id, err); if log_error { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 751b670e26..931751e9c5 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -102,15 +102,6 @@ pub enum TransactionServiceRequest { message: String, claim_public_key: Option, }, - CreateNMUtxo { - amount: MicroMinotari, - fee_per_gram: MicroMinotari, - n: u8, - m: u8, - public_keys: Vec, - message: [u8; 32], - maturity: u64, - }, EncumberAggregateUtxo { fee_per_gram: MicroMinotari, output_hash: HashOutput, @@ -219,18 +210,6 @@ impl fmt::Display for TransactionServiceRequest { amount, destination, message ), Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), - Self::CreateNMUtxo { - amount, - fee_per_gram: _, - n, - m, - public_keys: _, - message: _, - maturity: _, - } => f.write_str(&format!( - "Creating a new n-of-m aggregate uxto with: amount = {}, n = {}, m = {}", - amount, n, m - )), Self::EncumberAggregateUtxo { fee_per_gram, output_hash, @@ -709,34 +688,6 @@ impl TransactionServiceHandle { } } - pub async fn create_aggregate_signature_utxo( - &mut self, - amount: MicroMinotari, - fee_per_gram: MicroMinotari, - n: u8, - m: u8, - public_keys: Vec, - message: [u8; 32], - maturity: u64, - ) -> Result<(TxId, FixedHash), TransactionServiceError> { - match self - .handle - .call(TransactionServiceRequest::CreateNMUtxo { - amount, - fee_per_gram, - n, - m, - public_keys, - message, - maturity, - }) - .await?? - { - TransactionServiceResponse::TransactionSentWithOutputHash(tx_id, output_hash) => Ok((tx_id, output_hash)), - _ => Err(TransactionServiceError::UnexpectedApiResponse), - } - } - #[allow(clippy::mutable_key_type)] pub async fn encumber_aggregate_utxo( &mut self, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 8455e9723f..dda738baff 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -39,7 +39,7 @@ use tari_common_types::{ key_branches::TransactionKeyManagerBranch, tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, - types::{CommitmentFactory, FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, + types::{CommitmentFactory, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_comms_dht::outbound::OutboundMessageRequester; @@ -48,21 +48,19 @@ use tari_core::{ covenants::Covenant, mempool::FeePerGramStat, one_sided::{ - public_key_to_output_encryption_key, shared_secret_to_output_encryption_key, shared_secret_to_output_spending_key, stealth_address_script_spending_key, }, proto::{base_node as base_node_proto, base_node::FetchMatchingUtxos}, transactions::{ - key_manager::{TariKeyId, TransactionKeyManagerInterface}, + key_manager::TransactionKeyManagerInterface, tari_amount::MicroMinotari, transaction_components::{ encrypted_data::PaymentId, CodeTemplateRegistration, KernelFeatures, OutputFeatures, - RangeProofType, Transaction, TransactionOutput, WalletOutputBuilder, @@ -84,15 +82,7 @@ use tari_crypto::{ }; use tari_key_manager::key_manager_service::KeyId; use tari_p2p::domain_message::DomainMessage; -use tari_script::{ - push_pubkey_script, - script, - slice_to_boxed_message, - CheckSigSchnorrSignature, - ExecutionStack, - ScriptContext, - TariScript, -}; +use tari_script::{push_pubkey_script, script, CheckSigSchnorrSignature, ExecutionStack, ScriptContext, TariScript}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; use tokio::{ @@ -702,29 +692,6 @@ where tx_id, proof: Box::new(proof), }), - TransactionServiceRequest::CreateNMUtxo { - amount, - fee_per_gram, - n, - m, - public_keys, - message, - maturity, - } => self - .create_aggregate_signature_utxo( - amount, - fee_per_gram, - n, - m, - public_keys, - message, - maturity, - transaction_broadcast_join_handles, - ) - .await - .map(|(tx_id, output_hash)| { - TransactionServiceResponse::TransactionSentWithOutputHash(tx_id, output_hash) - }), TransactionServiceRequest::EncumberAggregateUtxo { fee_per_gram, output_hash, @@ -1180,218 +1147,6 @@ where Ok(()) } - /// Creates a utxo with aggregate public key out of m-of-n public keys - #[allow(clippy::too_many_lines)] - pub async fn create_aggregate_signature_utxo( - &mut self, - amount: MicroMinotari, - fee_per_gram: MicroMinotari, - n: u8, - m: u8, - public_keys: Vec, - message: [u8; 32], - maturity: u64, - transaction_broadcast_join_handles: &mut FuturesUnordered< - JoinHandle>>, - >, - ) -> Result<(TxId, FixedHash), TransactionServiceError> { - let tx_id = TxId::new_random(); - - let msg = slice_to_boxed_message(message.as_bytes()); - let script = script!(CheckMultiSigVerifyAggregatePubKey(n, m, public_keys.clone(), msg)); - - // Empty covenant - let covenant = Covenant::default(); - - // Default range proof - let minimum_value_promise = amount; - - // Prepare sender part of transaction - let mut stp = self - .resources - .output_manager_service - .prepare_transaction_to_send( - tx_id, - amount, - UtxoSelectionCriteria::default(), - OutputFeatures { - range_proof_type: RangeProofType::RevealedValue, - maturity, - ..Default::default() - }, - fee_per_gram, - TransactionMetadata::default(), - "".to_string(), - script.clone(), - covenant.clone(), - minimum_value_promise, - ) - .await?; - let sender_message = TransactionSenderMessage::new_single_round_message( - stp.get_single_round_message(&self.resources.transaction_key_manager_service) - .await?, - ); - - // This call is needed to advance the state from `SingleRoundMessageReady` to `CollectingSingleSignature`, - // but the returned value is not used - let _single_round_sender_data = stp - .build_single_round_message(&self.resources.transaction_key_manager_service) - .await - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; - - self.resources - .output_manager_service - .confirm_pending_transaction(tx_id) - .await - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; - - // Prepare receiver part of the transaction - - // In generating an aggregate public key utxo, we can use a randomly generated spend key - let spending_key = PrivateKey::random(&mut OsRng); - let sum_keys = public_keys.iter().fold(PublicKey::default(), |acc, x| acc + x); - let encryption_private_key = public_key_to_output_encryption_key(&sum_keys)?; - - let sender_offset_private_key = stp - .get_recipient_sender_offset_private_key() - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))? - .ok_or(TransactionServiceProtocolError::new( - tx_id, - TransactionServiceError::InvalidKeyId("Missing sender offset keyid".to_string()), - ))?; - - let encryption_key_id = self - .resources - .transaction_key_manager_service - .import_key(encryption_private_key) - .await?; - - let sender_offset_public_key = self - .resources - .transaction_key_manager_service - .get_public_key_at_key_id(&sender_offset_private_key) - .await?; - - let spending_key_id = self - .resources - .transaction_key_manager_service - .import_key(spending_key.clone()) - .await?; - - let wallet_output = WalletOutputBuilder::new(amount, spending_key_id) - .with_features( - sender_message - .single() - .ok_or(TransactionServiceProtocolError::new( - tx_id, - TransactionServiceError::InvalidMessageError("Sent invalid message type".to_string()), - ))? - .features - .clone(), - ) - .with_script(script) - // We don't want the given utxo to be spendable as an input to a later transaction, so we set - // spendable height of the current utxo to be u64::MAx - .with_script_lock_height(u64::MAX) - .encrypt_data_for_recovery( - &self.resources.transaction_key_manager_service, - Some(&encryption_key_id), - PaymentId::Empty, - ) - .await? - .with_input_data( - ExecutionStack::default(), - ) - .with_covenant(covenant) - .with_sender_offset_public_key(sender_offset_public_key) - .with_script_key(TariKeyId::default()) - .with_minimum_value_promise(minimum_value_promise) - .sign_as_sender_and_receiver( - &self.resources.transaction_key_manager_service, - &sender_offset_private_key, - ) - .await - .unwrap() - .try_build(&self.resources.transaction_key_manager_service) - .await - .unwrap(); - - let tip_height = self.last_seen_tip_height.unwrap_or(0); - let consensus_constants = self.consensus_manager.consensus_constants(tip_height); - let rtp = ReceiverTransactionProtocol::new( - sender_message, - wallet_output.clone(), - &self.resources.transaction_key_manager_service, - consensus_constants, - ) - .await; - let recipient_reply = rtp.get_signed_data()?.clone(); - - // Start finalize - stp.add_single_recipient_info(recipient_reply, &self.resources.transaction_key_manager_service) - .await - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; - - // Finalize: - stp.finalize(&self.resources.transaction_key_manager_service) - .await - .map_err(|e| { - error!( - target: LOG_TARGET, - "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, - ); - TransactionServiceProtocolError::new(tx_id, e.into()) - })?; - info!( - target: LOG_TARGET, - "Finalized create n of m transaction TxId: {}", tx_id - ); - - // This event being sent is important, but not critical to the protocol being successful. Send only fails if - // there are no subscribers. - let _size = self - .event_publisher - .send(Arc::new(TransactionEvent::TransactionCompletedImmediately(tx_id))); - - // Broadcast create n of m aggregate public key transaction - let tx = stp - .get_transaction() - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; - let fee = stp - .get_fee_amount() - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; - self.resources - .output_manager_service - .add_output_with_tx_id(tx_id, wallet_output.clone(), Some(SpendingPriority::Normal)) - .await?; - self.submit_transaction( - transaction_broadcast_join_handles, - CompletedTransaction::new( - tx_id, - self.resources.interactive_tari_address.clone(), - self.resources.interactive_tari_address.clone(), - amount, - fee, - tx.clone(), - TransactionStatus::Completed, - "".to_string(), - Utc::now().naive_utc(), - TransactionDirection::Outbound, - None, - None, - None, - ) - .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?, - ) - .await?; - - // we want to print out the hash of the utxo - let output_hash = wallet_output - .hash(&self.resources.transaction_key_manager_service) - .await?; - Ok((tx_id, output_hash)) - } - async fn fetch_unspent_outputs_from_node( &mut self, hashes: Vec, @@ -1501,7 +1256,9 @@ where JoinHandle>>, >, ) -> Result { + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: start"); let mut transaction = self.db.get_completed_transaction(tx_id)?; + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: completed_transaction"); // Add the aggregate signature components transaction.transaction.script_offset = &transaction.transaction.script_offset + &script_offset; @@ -1510,11 +1267,13 @@ where &(transaction.transaction.body.outputs()[0].commitment.clone()), &transaction.transaction.body.outputs()[0].metadata_signature + &total_meta_data_signature, )?; + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: updated metadata_signature"); transaction.transaction.body.update_script_signature( &(transaction.transaction.body.inputs()[0].commitment()?.clone()), &transaction.transaction.body.inputs()[0].script_signature + &total_script_data_signature, )?; + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: updated script_signature"); // Validate the aggregate signatures and script offset let factory = CommitmentFactory::default(); @@ -1527,11 +1286,13 @@ where .commitment() .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?, ); + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: input_data {:?}", input.input_data); input_keys = input_keys + input .run_and_verify_script(&factory, Some(context)) .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?; } + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: validated inputs"); let mut output_keys = PublicKey::default(); for output in transaction.transaction.body.outputs() { output @@ -1539,6 +1300,7 @@ where .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?; output_keys = output_keys + output.sender_offset_public_key.clone(); } + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: validated outputs"); let lhs = input_keys - output_keys; if lhs != PublicKey::from_secret_key(&transaction.transaction.script_offset) { return Err(TransactionServiceError::ServiceError(format!( @@ -1546,6 +1308,7 @@ where tx_id ))); } + trace!(target: LOG_TARGET, "finalized_aggregate_encumbed_tx: validated script offstet"); // Update the wallet database let _res = self