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/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index 1aa0f88cc8..790876a27f 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -172,7 +172,7 @@ where TBackend: KeyManagerBackend + 'static let mut km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_next_key", branch))? .write() .await; self.db.increment_key_index(branch)?; @@ -187,9 +187,9 @@ where TBackend: KeyManagerBackend + 'static } pub async fn get_random_key(&self) -> Result, KeyManagerServiceError> { + debug!(target: LOG_TARGET, "get_random_key: wallet type {}", self.wallet_type); match &self.wallet_type { WalletType::Ledger(ledger) => { - debug!(target: LOG_TARGET, "get_random_key: wallet type {}", self.wallet_type); #[cfg(not(feature = "ledger"))] { Err(KeyManagerServiceError::LedgerError(format!( @@ -201,9 +201,16 @@ where TBackend: KeyManagerBackend + 'static { let random_index = OsRng.next_u64(); - let public_key = - ledger_get_public_key(ledger.account, random_index, TransactionKeyManagerBranch::RandomKey) - .map_err(|e| KeyManagerServiceError::LedgerError(e.to_string()))?; + let branch = TransactionKeyManagerBranch::RandomKey; + debug!( + target: LOG_TARGET, + "get_random_key: (ledger) account {}, random_index {}, branch {:?}", + ledger.account, + random_index, + branch + ); + let public_key = ledger_get_public_key(ledger.account, random_index, branch) + .map_err(|e| KeyManagerServiceError::LedgerError(e.to_string()))?; Ok(KeyAndId { key_id: KeyId::Managed { branch: TransactionKeyManagerBranch::RandomKey.get_branch_key(), @@ -227,7 +234,7 @@ where TBackend: KeyManagerBackend + 'static pub async fn get_static_key(&self, branch: &str) -> Result { match self.key_managers.get(branch) { - None => Err(self.unknown_key_branch_error(branch)), + None => Err(self.unknown_key_branch_error("get_static_key", branch)), Some(_) => Ok(KeyId::Managed { branch: branch.to_string(), index: 0, @@ -236,62 +243,70 @@ where TBackend: KeyManagerBackend + 'static } pub async fn get_public_key_at_key_id(&self, key_id: &TariKeyId) -> Result { + debug!(target: LOG_TARGET, "get_public_key_at_key_id: key_id {}, wallet type {}", key_id, self.wallet_type); match key_id { KeyId::Managed { branch, index } => { - match &self.wallet_type { - // If we have the unique case of being a ledger wallet, and the key is a Managed EphemeralNonce, or - // SenderOffset than we fetch from the ledger, all other keys are fetched below. - WalletType::Ledger(ledger) => match TransactionKeyManagerBranch::from_key(branch) { + // If we have the unique case of being a ledger wallet, and the key is a Managed EphemeralNonce, or + // SenderOffset than we fetch from the ledger, all other keys are fetched below. + if let WalletType::Ledger(ledger) = &self.wallet_type { + match TransactionKeyManagerBranch::from_key(branch) { TransactionKeyManagerBranch::SenderOffsetLedger | TransactionKeyManagerBranch::RandomKey | TransactionKeyManagerBranch::PreMine => { - debug!(target: LOG_TARGET, "get_public_key_at_key_id: wallet type {}", self.wallet_type); #[cfg(not(feature = "ledger"))] { - Err(KeyManagerServiceError::LedgerError( + return Err(KeyManagerServiceError::LedgerError( "Ledger is not supported in this build, please enable the \"ledger\" feature for \ core" .to_string(), - )) + )); } #[cfg(feature = "ledger")] { + debug!( + target: LOG_TARGET, + "get_public_key_at_key_id: (ledger) account {}, index {}, branch {:?}", + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch) + ); let public_key = ledger_get_public_key( ledger.account, *index, TransactionKeyManagerBranch::from_key(branch), ) .map_err(|e| KeyManagerServiceError::LedgerError(e.to_string()))?; - Ok(public_key) + return Ok(public_key); } }, TransactionKeyManagerBranch::DataEncryption => { let view_key = ledger .view_key .clone() - .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible)?; - Ok(PublicKey::from_secret_key(&view_key)) + .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible(key_id.to_string()))?; + return Ok(PublicKey::from_secret_key(&view_key)); }, - _ => Err(self.branch_not_supported_error(branch)), - }, - _ => { - let km = self - .key_managers - .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? - .read() - .await; - Ok(km.derive_public_key(*index)?.key) - }, + _ => { + // Nothing here + }, + } } + + let km = self + .key_managers + .get(branch) + .ok_or_else(|| self.unknown_key_branch_error("get_public_key_at_key_id", branch))? + .read() + .await; + Ok(km.derive_public_key(*index)?.key) }, KeyId::Derived { branch, label, index } => { let public_alpha = self.get_spend_key().await?.pub_key; let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_public_key_at_key_id", branch))? .read() .await; let branch_key = km.get_private_key(*index)?; @@ -311,12 +326,18 @@ where TBackend: KeyManagerBackend + 'static } } - fn unknown_key_branch_error(&self, branch: &str) -> KeyManagerServiceError { - KeyManagerServiceError::UnknownKeyBranch(format!("{}, {}", branch, self.wallet_type)) + fn unknown_key_branch_error(&self, caller: &str, branch: &str) -> KeyManagerServiceError { + KeyManagerServiceError::UnknownKeyBranch(format!( + "{}: branch: {}, wallet_type: {}", + caller, branch, self.wallet_type + )) } - fn branch_not_supported_error(&self, branch: &str) -> KeyManagerServiceError { - KeyManagerServiceError::BranchNotSupported(format!("{}, {}", branch, self.wallet_type)) + fn key_id_not_supported_error(&self, caller: &str, expected: &str, key_id: &TariKeyId) -> TransactionError { + TransactionError::UnsupportedTariKeyId(format!( + "{}: Expected '{}', got {}, wallet_type: {}", + caller, expected, key_id, self.wallet_type + )) } #[allow(clippy::too_many_lines)] @@ -330,14 +351,14 @@ where TBackend: KeyManagerBackend + 'static return wallet .view_key .clone() - .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible); + .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible(key_id.to_string())); } // If we're trying to access any of the private keys, just say no bueno if &TransactionKeyManagerBranch::Spend.get_branch_key() == branch || &TransactionKeyManagerBranch::SenderOffset.get_branch_key() == branch { - return Err(KeyManagerServiceError::LedgerPrivateKeyInaccessible); + return Err(KeyManagerServiceError::LedgerPrivateKeyInaccessible(key_id.to_string())); } }, WalletType::ProvidedKeys(wallet) => { @@ -347,10 +368,9 @@ where TBackend: KeyManagerBackend + 'static // If we're trying to access any of the private keys, just say no bueno if &TransactionKeyManagerBranch::Spend.get_branch_key() == branch { - return wallet - .private_spend_key - .clone() - .ok_or(KeyManagerServiceError::ImportedPrivateKeyInaccessible); + return wallet.private_spend_key.clone().ok_or( + KeyManagerServiceError::ImportedPrivateKeyInaccessible(key_id.to_string()), + ); } }, } @@ -358,26 +378,26 @@ where TBackend: KeyManagerBackend + 'static let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_private_key", branch))? .read() .await; let key = km.get_private_key(*index)?; Ok(key) }, KeyId::Derived { branch, label, index } => match &self.wallet_type { - WalletType::Ledger(_) => Err(KeyManagerServiceError::LedgerPrivateKeyInaccessible), + WalletType::Ledger(_) => Err(KeyManagerServiceError::LedgerPrivateKeyInaccessible(key_id.to_string())), WalletType::DerivedKeys => { let km = self .key_managers .get(&TransactionKeyManagerBranch::Spend.get_branch_key()) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_private_key", branch))? .read() .await; let private_alpha = km.get_private_key(0)?; let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_private_key", branch))? .read() .await; let branch_key = km.get_private_key(*index)?; @@ -390,15 +410,14 @@ where TBackend: KeyManagerBackend + 'static Ok(private_key) }, WalletType::ProvidedKeys(wallet) => { - let private_alpha = wallet - .private_spend_key - .clone() - .ok_or(KeyManagerServiceError::ImportedPrivateKeyInaccessible)?; + let private_alpha = wallet.private_spend_key.clone().ok_or( + KeyManagerServiceError::ImportedPrivateKeyInaccessible(key_id.to_string()), + )?; let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_private_key", branch))? .read() .await; let branch_key = km.get_private_key(*index)?; @@ -502,7 +521,7 @@ where TBackend: KeyManagerBackend + 'static WalletType::Ledger(ledger) => ledger .view_key .clone() - .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible), + .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible("n/a".to_string())), WalletType::ProvidedKeys(wallet) => Ok(wallet.view_key.clone()), } } @@ -523,7 +542,7 @@ where TBackend: KeyManagerBackend + 'static let km = self .key_managers .get(&branch) - .ok_or(self.unknown_key_branch_error(&branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_private_comms_key", &branch))? .read() .await; let key = km.get_private_key(index)?; @@ -580,7 +599,7 @@ where TBackend: KeyManagerBackend + 'static let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("find_key_index", branch))? .read() .await; @@ -611,7 +630,7 @@ where TBackend: KeyManagerBackend + 'static let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("find_private_key_index", branch))? .read() .await; @@ -647,7 +666,7 @@ where TBackend: KeyManagerBackend + 'static let mut km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("update_current_key_index_if_higher", branch))? .write() .await; let current_index = km.key_index(); @@ -691,44 +710,50 @@ where TBackend: KeyManagerBackend + 'static secret_key_id: &TariKeyId, public_key: &PublicKey, ) -> Result { - match &self.wallet_type { - WalletType::Ledger(ledger) => match secret_key_id { - KeyId::Managed { branch, index } => match TransactionKeyManagerBranch::from_key(branch) { - TransactionKeyManagerBranch::SenderOffsetLedger => { - debug!(target: LOG_TARGET, "get_diffie_hellman_shared_secret: wallet type {}", self.wallet_type); - #[cfg(not(feature = "ledger"))] - { - Err(TransactionError::LedgerNotSupported(format!( - "Ledger {} (has index {}) is not supported in this build, please enable the \ - \"ledger\" feature for core", - ledger, index - ))) - } + debug!( + target: LOG_TARGET, + "get_diffie_hellman_shared_secret: secret_key_id {}, wallet type {}", + secret_key_id, + self.wallet_type + ); + // Only WalletType::Ledger -> KeyId::Managed -> TransactionKeyManagerBranch::SenderOffsetLedger goes to the + // Ledger. + if let WalletType::Ledger(ledger) = &self.wallet_type { + if let KeyId::Managed { branch, index } = secret_key_id { + if branch == &TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key() { + #[cfg(not(feature = "ledger"))] + { + return Err(TransactionError::LedgerNotSupported(format!( + "Ledger {} (has index {}) is not supported in this build, please enable the \"ledger\" \ + feature for core", + ledger, index + ))); + } - #[cfg(feature = "ledger")] - { - ledger_get_dh_shared_secret( - ledger.account, - *index, - TransactionKeyManagerBranch::from_key(branch), - public_key, - ) - .map_err(TransactionError::LedgerDeviceError) - } - }, - _ => Err(TransactionError::from(self.branch_not_supported_error(branch))), - }, - _ => Err(TransactionError::UnsupportedTariKeyId(format!( - "Expected 'KeyId::Managed', got {}", - secret_key_id - ))), - }, - _ => { - let secret_key = self.get_private_key(secret_key_id).await?; - let shared_secret = CommsDHKE::new(&secret_key, public_key); - Ok(shared_secret) - }, + #[cfg(feature = "ledger")] + { + debug!( + target: LOG_TARGET, + "get_diffie_hellman_shared_secret: (ledger) account {}, index {}, branch {:?}", + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch) + ); + return ledger_get_dh_shared_secret( + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch), + public_key, + ) + .map_err(TransactionError::LedgerDeviceError); + } + } + } } + + let secret_key = self.get_private_key(secret_key_id).await?; + let shared_secret = CommsDHKE::new(&secret_key, public_key); + Ok(shared_secret) } pub async fn get_diffie_hellman_stealth_domain_hasher( @@ -736,45 +761,51 @@ where TBackend: KeyManagerBackend + 'static secret_key_id: &TariKeyId, public_key: &PublicKey, ) -> Result>, TransactionError> { - match &self.wallet_type { - WalletType::Ledger(ledger) => match secret_key_id { - KeyId::Managed { branch, index } => match TransactionKeyManagerBranch::from_key(branch) { - TransactionKeyManagerBranch::SenderOffsetLedger => { - debug!(target: LOG_TARGET, "get_diffie_hellman_stealth_domain_hasher: allet type {}", self.wallet_type); - #[cfg(not(feature = "ledger"))] - { - Err(TransactionError::LedgerNotSupported(format!( - "Ledger {} (has index {}) is not supported in this build, please enable the \ - \"ledger\" feature for core", - ledger, index - ))) - } + debug!( + target: LOG_TARGET, + "get_diffie_hellman_stealth_domain_hasher: secret_key_id {}, wallet type {}", + secret_key_id, + self.wallet_type + ); + // Only WalletType::Ledger -> KeyId::Managed -> TransactionKeyManagerBranch::SenderOffsetLedger goes to the + // Ledger. + if let WalletType::Ledger(ledger) = &self.wallet_type { + if let KeyId::Managed { branch, index } = secret_key_id { + if branch == &TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key() { + #[cfg(not(feature = "ledger"))] + { + return Err(TransactionError::LedgerNotSupported(format!( + "Ledger {} (has index {}) is not supported in this build, please enable the \"ledger\" \ + feature for core", + ledger, index + ))); + } - #[cfg(feature = "ledger")] - { - ledger_get_dh_shared_secret( - ledger.account, - *index, - TransactionKeyManagerBranch::from_key(branch), - public_key, - ) - .map_err(TransactionError::LedgerDeviceError) - .map(diffie_hellman_stealth_domain_hasher) - } - }, - _ => Err(TransactionError::from(self.branch_not_supported_error(branch))), - }, - _ => Err(TransactionError::UnsupportedTariKeyId(format!( - "Expected 'KeyId::Managed', got {}", - secret_key_id - ))), - }, - _ => { - let secret_key = self.get_private_key(secret_key_id).await?; - let dh = CommsDHKE::new(&secret_key, public_key); - Ok(diffie_hellman_stealth_domain_hasher(dh)) - }, + #[cfg(feature = "ledger")] + { + debug!( + target: LOG_TARGET, + "get_diffie_hellman_stealth_domain_hasher: (ledger) account {}, index {}, branch {:?}", + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch) + ); + return ledger_get_dh_shared_secret( + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch), + public_key, + ) + .map_err(TransactionError::LedgerDeviceError) + .map(diffie_hellman_stealth_domain_hasher); + } + } + } } + + let secret_key = self.get_private_key(secret_key_id).await?; + let dh = CommsDHKE::new(&secret_key, public_key); + Ok(diffie_hellman_stealth_domain_hasher(dh)) } pub async fn import_add_offset_to_private_key( @@ -829,19 +860,17 @@ where TBackend: KeyManagerBackend + 'static txi_version: &TransactionInputVersion, script_message: &[u8; 32], ) -> Result { + debug!( + target: LOG_TARGET, + "get_script_signature: script_key_id {}, wallet type {}", + script_key_id, + self.wallet_type + ); let commitment = self.get_commitment(commitment_mask_key_id, value).await?; let commitment_private_key = self.get_private_key(commitment_mask_key_id).await?; - match (&self.wallet_type, script_key_id) { - ( - WalletType::Ledger(ledger), - KeyId::Derived { - branch, - label: _, - index, - }, - ) => { - debug!(target: LOG_TARGET, "get_script_signature: wallet type {}", self.wallet_type); + match &self.wallet_type { + WalletType::Ledger(ledger) => { #[cfg(not(feature = "ledger"))] { Err(TransactionError::LedgerNotSupported(format!( @@ -853,16 +882,38 @@ where TBackend: KeyManagerBackend + 'static #[cfg(feature = "ledger")] { + let (branch, index) = match script_key_id { + TariKeyId::Managed { branch: b, index: i } => (b, i), + TariKeyId::Derived { + branch: b, + label: _label, + index: i, + } => (b, i), + _ => { + return Err(self.key_id_not_supported_error( + "get_script_signature", + "KeyId::Managed or KeyId::Derived", + script_key_id, + )); + }, + }; let km = self .key_managers .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? + .ok_or_else(|| self.unknown_key_branch_error("get_script_signature", branch))? .read() .await; let branch_key = km .get_private_key(*index) .map_err(|e| TransactionError::KeyManagerError(e.to_string()))?; + debug!( + target: LOG_TARGET, + "get_script_signature: (ledger) account {}, index {}, branch {:?}", + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch) + ); let signature = ledger_get_script_signature( ledger.account, ledger.network, @@ -877,11 +928,7 @@ where TBackend: KeyManagerBackend + 'static Ok(signature) } }, - (WalletType::Ledger(_ledger), key_id) => Err(TransactionError::UnsupportedTariKeyId(format!( - "Expected 'KeyId::Derived', got {}", - key_id - ))), - (_, _) => { + _ => { let r_a = PrivateKey::random(&mut OsRng); let r_x = PrivateKey::random(&mut OsRng); let r_y = PrivateKey::random(&mut OsRng); @@ -1014,40 +1061,52 @@ where TBackend: KeyManagerBackend + 'static script_key_ids: &[TariKeyId], sender_offset_key_ids: &[TariKeyId], ) -> Result { - let mut total_script_private_key = PrivateKey::default(); - let mut derived_key_commitments = vec![]; - for script_key_id in script_key_ids { - match script_key_id { - KeyId::Imported { .. } | KeyId::Managed { .. } | KeyId::Zero => { - total_script_private_key = total_script_private_key + self.get_private_key(script_key_id).await? - }, - KeyId::Derived { - branch, - label: _, - index, - } => match &self.wallet_type { - WalletType::DerivedKeys | WalletType::ProvidedKeys(_) => { - total_script_private_key = - total_script_private_key + self.get_private_key(script_key_id).await?; - }, - WalletType::Ledger(_) => { - let km = self - .key_managers - .get(branch) - .ok_or(self.unknown_key_branch_error(branch))? - .read() - .await; - let branch_key = km - .get_private_key(*index) - .map_err(|e| TransactionError::KeyManagerError(e.to_string()))?; - derived_key_commitments.push(branch_key); - }, - }, - } - } + debug!( + target: LOG_TARGET, + "get_script_offset: script_key_ids {:?}, sender_offset_key_ids {:?}, wallet type {}", + script_key_ids, + sender_offset_key_ids, + self.wallet_type + ); + // let mut total_script_private_key = PrivateKey::default(); + // let mut derived_key_commitments = vec![]; + // for script_key_id in script_key_ids { + // match script_key_id { + // KeyId::Imported { .. } | KeyId::Managed { .. } | KeyId::Zero => { + // total_script_private_key = total_script_private_key + self.get_private_key(script_key_id).await? + // }, + // KeyId::Derived { + // branch, + // label: _, + // index, + // } => match &self.wallet_type { + // WalletType::DerivedKeys | WalletType::ProvidedKeys(_) => { + // total_script_private_key = + // total_script_private_key + self.get_private_key(script_key_id).await?; + // }, + // WalletType::Ledger(_) => { + // let km = self + // .key_managers + // .get(branch) + // .ok_or_else(|| self.unknown_key_branch_error("get_script_offset", branch))? + // .read() + // .await; + // let branch_key = km + // .get_private_key(*index) + // .map_err(|e| TransactionError::KeyManagerError(e.to_string()))?; + // derived_key_commitments.push(branch_key); + // }, + // }, + // } + // } match &self.wallet_type { WalletType::DerivedKeys | WalletType::ProvidedKeys(_) => { + let mut total_script_private_key = PrivateKey::default(); + for script_key_id in script_key_ids { + total_script_private_key = total_script_private_key + self.get_private_key(script_key_id).await? + } + let mut total_sender_offset_private_key = PrivateKey::default(); for sender_offset_key_id in sender_offset_key_ids { total_sender_offset_private_key = @@ -1057,7 +1116,6 @@ where TBackend: KeyManagerBackend + 'static Ok(script_offset) }, WalletType::Ledger(ledger) => { - debug!(target: LOG_TARGET, "get_script_offset: wallet type {}", self.wallet_type); #[cfg(not(feature = "ledger"))] { Err(TransactionError::LedgerNotSupported(format!( @@ -1068,6 +1126,35 @@ where TBackend: KeyManagerBackend + 'static #[cfg(feature = "ledger")] { + let mut derived_key_commitments = vec![]; + for script_key_id in script_key_ids { + let (branch, index) = match script_key_id { + TariKeyId::Managed { branch: b, index: i } => (b, i), + TariKeyId::Derived { + branch: b, + label: _label, + index: i, + } => (b, i), + _ => { + return Err(self.key_id_not_supported_error( + "get_script_offset", + "KeyId::Managed or KeyId::Derived", + script_key_id, + )); + }, + }; + let km = self + .key_managers + .get(branch) + .ok_or_else(|| self.unknown_key_branch_error("get_script_offset", branch))? + .read() + .await; + let branch_key = km + .get_private_key(*index) + .map_err(|e| TransactionError::KeyManagerError(e.to_string()))?; + derived_key_commitments.push(branch_key); + } + let mut sender_offset_indexes = vec![]; for sender_offset_key_id in sender_offset_key_ids { match sender_offset_key_id { @@ -1083,6 +1170,7 @@ where TBackend: KeyManagerBackend + 'static } } + debug!(target: LOG_TARGET, "get_script_offset: (ledger) account {}", ledger.account); let script_offset = ledger_get_script_offset(ledger.account, &derived_key_commitments, &sender_offset_indexes) .map_err(|e| TransactionError::InvalidSignatureError(e.to_string()))?; @@ -1139,9 +1227,14 @@ where TBackend: KeyManagerBackend + 'static private_key_id: &TariKeyId, challenge: &[u8], ) -> Result { + debug!( + target: LOG_TARGET, + "sign_script_message: private_key_id {}, wallet type {}", + private_key_id, + self.wallet_type + ); match &self.wallet_type { WalletType::Ledger(ledger) => { - debug!(target: LOG_TARGET, "sign_script_message: wallet type {}", self.wallet_type); #[cfg(not(feature = "ledger"))] { Err(TransactionError::LedgerNotSupported(format!( @@ -1154,6 +1247,13 @@ where TBackend: KeyManagerBackend + 'static { match private_key_id { KeyId::Managed { branch, index } => { + debug!( + target: LOG_TARGET, + "sign_script_message: (ledger) account {}, index {}, branch {:?}", + ledger.account, + *index, + TransactionKeyManagerBranch::from_key(branch) + ); let signature = ledger_get_script_schnorr_signature( ledger.account, *index, @@ -1162,10 +1262,11 @@ where TBackend: KeyManagerBackend + 'static )?; Ok(signature) }, - _ => Err(TransactionError::UnsupportedTariKeyId(format!( - "Expected 'KeyId::Managed', got {}", - private_key_id - ))), + _ => Err(self.key_id_not_supported_error( + "sign_script_message", + "KeyId::Managed", + private_key_id, + )), } } }, @@ -1184,9 +1285,15 @@ where TBackend: KeyManagerBackend + 'static nonce_key_id: &TariKeyId, challenge: &[u8; 64], ) -> Result { + debug!( + target: LOG_TARGET, + "sign_with_nonce_and_challenge: private_key_id {}, nonce_key_id {}, wallet type {}", + private_key_id, + nonce_key_id, + self.wallet_type + ); match &self.wallet_type { WalletType::Ledger(ledger) => { - debug!(target: LOG_TARGET, "sign_with_nonce_and_challenge: wallet type {}", self.wallet_type); #[cfg(not(feature = "ledger"))] { Err(TransactionError::LedgerNotSupported(format!( @@ -1206,6 +1313,16 @@ where TBackend: KeyManagerBackend + 'static branch: nonce_branch, index: nonce_index, } => { + debug!( + target: LOG_TARGET, + "sign_with_nonce_and_challenge: (ledger) account {}, k_index {}, k_branch {:?}, \ + nonce_index {}, nonce_branch {:?}", + ledger.account, + private_key_index, + TransactionKeyManagerBranch::from_key(private_key_branch), + nonce_index, + TransactionKeyManagerBranch::from_key(nonce_branch) + ); let signature = ledger_get_raw_schnorr_signature( ledger.account, *private_key_index, @@ -1217,15 +1334,17 @@ where TBackend: KeyManagerBackend + 'static .map_err(|e| KeyManagerServiceError::LedgerError(e.to_string()))?; Ok(signature) }, - _ => Err(TransactionError::UnsupportedTariKeyId(format!( - "Expected 'KeyId::Managed', got {}", - nonce_key_id - ))), + _ => Err(self.key_id_not_supported_error( + "sign_with_nonce_and_challenge", + "KeyId::Managed", + nonce_key_id, + )), }, - _ => Err(TransactionError::UnsupportedTariKeyId(format!( - "Expected 'KeyId::Managed', got {}", - private_key_id - ))), + _ => Err(self.key_id_not_supported_error( + "sign_with_nonce_and_challenge", + "KeyId::Managed", + private_key_id, + )), } } }, 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/key_manager/src/key_manager_service/error.rs b/base_layer/key_manager/src/key_manager_service/error.rs index 1b54466747..6f5949250e 100644 --- a/base_layer/key_manager/src/key_manager_service/error.rs +++ b/base_layer/key_manager/src/key_manager_service/error.rs @@ -53,14 +53,14 @@ pub enum KeyManagerServiceError { UnknownError(String), #[error("Ledger error: `{0}`")] LedgerError(String), - #[error("The Ledger private key cannot be accessed or read")] - LedgerPrivateKeyInaccessible, - #[error("The Ledger view key cannot be accessed or read")] - LedgerViewKeyInaccessible, + #[error("The Ledger private key cannot be accessed or read: `{0}`")] + LedgerPrivateKeyInaccessible(String), + #[error("The Ledger view key cannot be accessed or read: `{0}`")] + LedgerViewKeyInaccessible(String), #[error("Tari Key Manager storage error: `{0}`")] StorageError(#[from] StorageError), - #[error("The imported private key cannot be accessed or read")] - ImportedPrivateKeyInaccessible, + #[error("The imported private key cannot be accessed or read: `{0}")] + ImportedPrivateKeyInaccessible(String), } impl From for KeyManagerServiceError { 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