From 76caf29fe3e7a7741b036d8bfe1e63778b03a7af Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Tue, 25 Jun 2024 17:43:08 +0200 Subject: [PATCH] review comments --- .../minotari_app_utilities/src/utilities.rs | 2 +- .../src/automation/commands.rs | 163 +++--- .../minotari_console_wallet/src/cli.rs | 35 +- .../src/wallet_modes.rs | 46 +- .../get_script_signature_from_challenge.rs | 9 +- base_layer/core/src/common/one_sided.rs | 10 + .../core/src/transactions/aggregated_body.rs | 4 +- .../src/transactions/key_manager/inner.rs | 48 +- .../src/transactions/key_manager/interface.rs | 59 ++- .../src/transactions/key_manager/wrapper.rs | 29 +- .../range_proof_type.rs | 17 +- .../transaction_components/wallet_output.rs | 84 ++- .../wallet_output_builder.rs | 74 ++- .../src/output_manager_service/error.rs | 2 + .../src/output_manager_service/handle.rs | 63 ++- .../src/output_manager_service/service.rs | 482 ++++++++++++------ .../wallet/src/transaction_service/handle.rs | 121 +++-- .../wallet/src/transaction_service/service.rs | 119 +++-- 18 files changed, 962 insertions(+), 405 deletions(-) diff --git a/applications/minotari_app_utilities/src/utilities.rs b/applications/minotari_app_utilities/src/utilities.rs index 7c1c6ec46f..afbb03bea2 100644 --- a/applications/minotari_app_utilities/src/utilities.rs +++ b/applications/minotari_app_utilities/src/utilities.rs @@ -95,7 +95,7 @@ impl From for PublicKey { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum UniNodeId { PublicKey(PublicKey), NodeId(NodeId), diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 1f4dee6b38..1604e816f6 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -46,7 +46,6 @@ use minotari_wallet::{ WalletConfig, WalletSqlite, }; -use rand::rngs::OsRng; use serde::{de::DeserializeOwned, Serialize}; use sha2::Sha256; use tari_common_types::{ @@ -71,6 +70,7 @@ use tari_core::{ encrypted_data::PaymentId, EncryptedData, OutputFeatures, + RangeProofType, Transaction, TransactionInput, TransactionInputVersion, @@ -81,7 +81,7 @@ use tari_core::{ }, }, }; -use tari_crypto::{keys::SecretKey, ristretto::RistrettoSecretKey}; +use tari_crypto::ristretto::RistrettoSecretKey; use tari_script::{script, ExecutionStack, TariScript}; use tari_utilities::{hex::Hex, ByteArray}; use tokio::{ @@ -147,38 +147,50 @@ pub async fn create_aggregate_signature_utxo( m: u8, public_keys: Vec, message: String, + maturity: u64, ) -> Result<(TxId, FixedHash), CommandError> { let mut msg = [0u8; 32]; msg.copy_from_slice(message.as_bytes()); wallet_transaction_service - .create_aggregate_signature_utxo(amount, fee_per_gram, n, m, public_keys, msg) + .create_aggregate_signature_utxo(amount, fee_per_gram, n, m, public_keys, msg, maturity) .await .map_err(CommandError::TransactionServiceError) } /// encumbers a n-of-m transaction +#[allow(clippy::too_many_arguments)] async fn encumber_aggregate_utxo( mut wallet_transaction_service: TransactionServiceHandle, fee_per_gram: MicroMinotari, output_hash: String, - signatures: Vec, - total_script_pubkey: PublicKey, - total_offset_pubkey: PublicKey, - total_signature_nonce: PublicKey, - metadata_signature_nonce: PublicKey, - wallet_script_secret_key: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, ) -> Result<(TxId, Transaction, PublicKey), CommandError> { wallet_transaction_service .encumber_aggregate_utxo( fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, ) .await .map_err(CommandError::TransactionServiceError) @@ -321,18 +333,6 @@ pub async fn coin_split( Ok(tx_id) } -pub fn sign_message(private_key: String, challenge: String) -> Result { - let private_key = - PrivateKey::from_hex(private_key.as_str()).map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let challenge = challenge.as_bytes(); - - let nonce = PrivateKey::random(&mut OsRng); - let signature = Signature::sign_with_nonce_and_message(&private_key, nonce, challenge) - .map_err(CommandError::FailedSignature)?; - - Ok(signature) -} - async fn wait_for_comms(connectivity_requester: &ConnectivityRequester) -> Result<(), CommandError> { let mut connectivity = connectivity_requester.get_event_subscription(); print!("Waiting for connectivity... "); @@ -740,12 +740,12 @@ pub async fn command_runner( } }, CreateKeyPair(args) => match key_manager_service.create_key_pair(args.key_branch).await { - Ok((sk, pk)) => { + Ok((key_id, pk)) => { println!( "New key pair: - 1. secret key: {}, + 1. key id : {}, 2. public key: {}", - sk.to_hex(), + key_id, pk.to_hex() ) }, @@ -761,7 +761,8 @@ pub async fn command_runner( .iter() .map(|pk| PublicKey::from(pk.clone())) .collect::>(), - args.message, + args.message, // 1. What is the message? => commitment + args.maturity, ) .await { @@ -777,45 +778,59 @@ pub async fn command_runner( }, Err(e) => eprintln!("CreateAggregateSignatureUtxo error! {}", e), }, - SignMessage(args) => match sign_message(args.private_key, args.challenge) { - Ok(sgn) => { - println!( - "Sign message: + SignMessage(args) => { + match key_manager_service + .sign_message(&args.private_key_id, args.challenge.as_bytes()) + .await + { + // 1. What is the message/challenge? => commitment + Ok(sgn) => { + println!( + "Sign message: 1. signature: {}, 2. public nonce: {}", - sgn.get_signature().to_hex(), - sgn.get_public_nonce().to_hex(), - ) - }, - Err(e) => eprintln!("SignMessage error! {}", e), + sgn.get_signature().to_hex(), + sgn.get_public_nonce().to_hex(), + ) + }, + Err(e) => eprintln!("SignMessage error! {}", e), + } }, EncumberAggregateUtxo(args) => { - let mut total_script_pub_key = PublicKey::default(); - for sig in args.script_pubkeys { - total_script_pub_key = sig.into(); - } - let mut total_offset_pub_key = PublicKey::default(); - for sig in args.offset_pubkeys { - total_offset_pub_key = sig.into(); - } - let mut total_sig_nonce = PublicKey::default(); - for sig in args.script_signature_nonces { - total_sig_nonce = sig.into(); - } - let mut total_meta_nonce = PublicKey::default(); - for sig in args.metadata_signature_nonces { - total_meta_nonce = sig.into(); - } match encumber_aggregate_utxo( transaction_service.clone(), args.fee_per_gram, args.output_hash, - args.signatures.iter().map(|sgn| sgn.clone().into()).collect::>(), - total_script_pub_key, - total_offset_pub_key, - total_sig_nonce, - total_meta_nonce, - args.wallet_script_secret_key, + args.script_input_shares + .iter() + .map(|v| v.clone().into()) + .collect::>(), + args.script_public_key_shares + .iter() + .map(|v| v.clone().into()) + .collect::>(), + args.script_signature_shares + .iter() + .map(|v| v.clone().into()) + .collect::>(), + args.sender_offset_public_key_shares + .iter() + .map(|v| v.clone().into()) + .collect::>(), + args.metadata_ephemeral_public_key_shares + .iter() + .map(|v| v.clone().into()) + .collect::>(), + args.dh_shared_secret_shares + .iter() + .map(|v| v.clone().into()) + .collect::>(), + args.recipient_address, + PaymentId::from_bytes(args.payment_id.as_bytes()) + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?, + args.maturity, + args.range_proof_type, + args.minimum_value_promise, ) .await { @@ -893,8 +908,6 @@ pub async fn command_runner( } }, CreateScriptSig(args) => { - let private_key = - PrivateKey::from_hex(&args.secret_key).map_err(|e| CommandError::InvalidArgument(e.to_string()))?; let private_nonce = PrivateKey::from_hex(&args.secret_nonce) .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; let script = TariScript::from_hex(&args.input_script) @@ -916,16 +929,22 @@ pub async fn command_runner( &args.total_script_key.into(), &commitment, ); - // TODO: Change to `ComAndPubSignature` - let signature = Signature::sign_with_nonce_and_message(&private_key, private_nonce, challenge) - .map_err(CommandError::FailedSignature)?; - println!( - "Sign script sig: + + match key_manager_service + .sign_with_nonce_and_message(&args.private_key_id, &private_nonce, challenge.as_slice()) + .await + { + Ok(signature) => { + println!( + "Sign script sig: 1. signature: {}, 2. public nonce: {}", - signature.get_signature().to_hex(), - signature.get_public_nonce().to_hex(), - ) + signature.get_signature().to_hex(), + signature.get_public_nonce().to_hex(), + ) + }, + Err(e) => eprintln!("SignMessage error! {}", e), + } }, CreateMetaSig(args) => { let private_key = PrivateKey::from_hex(&args.secret_offset_key) diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index 5c4e65e66f..2aefd1392f 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -35,7 +35,12 @@ use minotari_app_utilities::{ use tari_common::configuration::{ConfigOverrideProvider, Network}; use tari_common_types::tari_address::TariAddress; use tari_comms::multiaddr::Multiaddr; -use tari_core::transactions::{tari_amount, tari_amount::MicroMinotari}; +use tari_core::transactions::{ + key_manager::TariKeyId, + tari_amount, + tari_amount::MicroMinotari, + transaction_components::RangeProofType, +}; use tari_key_manager::SeedWords; use tari_utilities::{ hex::{Hex, HexError}, @@ -186,13 +191,15 @@ pub struct CreateAggregateSignatureUtxoArgs { #[clap(long)] pub message: String, #[clap(long)] + pub maturity: u64, + #[clap(long)] pub public_keys: Vec, } #[derive(Debug, Args, Clone)] pub struct SignMessageArgs { #[clap(long)] - pub private_key: String, + pub private_key_id: TariKeyId, #[clap(long)] pub challenge: String, } @@ -204,17 +211,27 @@ pub struct EncumberAggregateUtxoArgs { #[clap(long)] pub output_hash: String, #[clap(long)] - pub wallet_script_secret_key: String, + pub script_input_shares: Vec, + #[clap(long)] + pub script_public_key_shares: Vec, + #[clap(long)] + pub script_signature_shares: Vec, + #[clap(long)] + pub sender_offset_public_key_shares: Vec, + #[clap(long)] + pub metadata_ephemeral_public_key_shares: Vec, + #[clap(long)] + pub dh_shared_secret_shares: Vec, #[clap(long)] - pub script_pubkeys: Vec, + pub recipient_address: TariAddress, #[clap(long)] - pub offset_pubkeys: Vec, + pub payment_id: String, #[clap(long)] - pub script_signature_nonces: Vec, + pub maturity: u64, #[clap(long)] - pub metadata_signature_nonces: Vec, + pub range_proof_type: RangeProofType, #[clap(long)] - pub signatures: Vec, + pub minimum_value_promise: MicroMinotari, } #[derive(Debug, Args, Clone)] @@ -232,7 +249,7 @@ pub struct SpendAggregateUtxoArgs { #[derive(Debug, Args, Clone)] pub struct CreateScriptSigArgs { #[clap(long)] - pub secret_key: String, + pub private_key_id: TariKeyId, #[clap(long)] pub secret_nonce: String, #[clap(long)] diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index cd236a734b..ad3a3acab7 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -498,24 +498,40 @@ mod test { create-key-pair --key-branch pie - create-aggregate-signature-utxo --amount 125T --m 2 --n 3 --fee-per-gram 1 --message ff \ + create-aggregate-signature-utxo \ + --amount 125T \ + --fee-per-gram 1 \ + --n 3 \ + --m 2 \ + --message ff \ + --maturity 0 \ --public-keys=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ --public-keys=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 + sign-message \ + --private-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ + --challenge f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 + encumber-aggregate-utxo \ --fee-per-gram 1 \ --output-hash f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --wallet-script-secret-key 2603fed9cf87097105913096da423ae4e3096e44a172185742ce5bc00d27016cd81118 \ - --script-pubkeys=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --script-pubkeys=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --offset-pubkeys=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --offset-pubkeys=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --script-signature-nonces=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --script-signature-nonces=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --metadata-signature-nonces=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --metadata-signature-nonces=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --signatures=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --signatures=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c + --script-input-shares=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ + --script-input-shares=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ + --script-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ + --script-public-key-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ + --script-signature-shares=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ + --script-signature-shares=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ + --sender-offset-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ + --sender-offset-public-key-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ + --metadata-ephemeral-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ + --metadata-ephemeral-public-key-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ + --dh-shared-secret-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ + --dh-shared-secret-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ + --recipient-address f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA \ + --payment-id 156486946518564 \ + --maturity 0 \ + --range-proof-type revealed_value \ + --minimum-value-promise 0 spend-aggregate-utxo \ --tx-id 12345678 \ @@ -526,12 +542,8 @@ mod test { --script-offset-keys=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ --script-offset-keys=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 - sign-message \ - --private-key 2603fed9cf87097105913096da423ae4e3096e44a172185742ce5bc00d27016cd81118 \ - --challenge f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 - create-script-sig \ - --secret-key 2603fed9cf87097105913096da423ae4e3096e44a172185742ce5bc00d27016cd81118 \ + --private-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ --secret-nonce 3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03 \ --input-script ae010268593ed2d36a2d95f0ffe0f41649b97cc36fc4ef0c8ecd6bd28f9d56c76b793b08691435a5c813578f8a7f4973166dc1c6c15f37aec2a7d65b1583c8b2129364c916d5986a0c1b3dac7d6efb94bed688ba52fa8b962cf27c0446e2fea6d66a04 \ --input-stack 050857c14f72cf885aac9f08c9484cb7cb06b6cc20eab68c9bee1e8d5a85649b0a6d31c5cc49afc1e03ebbcf55c82f47e8cbc796c33e96c17a31eab027ee821f00 \ diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature_from_challenge.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature_from_challenge.rs index 268f7f9630..1ba58fe0fb 100644 --- a/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature_from_challenge.rs +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature_from_challenge.rs @@ -48,9 +48,12 @@ pub fn handler_get_script_signature_from_challenge(comm: &mut Comm) -> Result<() let mut challenge = [0u8; 64]; challenge.clone_from_slice(&data[136..200]); - let r_a = derive_from_bip32_key(account, u32::random().into(), KeyType::Nonce)?; - let r_x = derive_from_bip32_key(account, u32::random().into(), KeyType::Nonce)?; - let r_y = derive_from_bip32_key(account, u32::random().into(), KeyType::Nonce)?; + let r_a: Zeroizing = + get_key_from_canonical_bytes::(&data[200..232])?.into(); + let r_x: Zeroizing = + get_key_from_canonical_bytes::(&data[232..264])?.into(); + let r_y: Zeroizing = + get_key_from_canonical_bytes::(&data[264..296])?.into(); let factory = ExtendedPedersenCommitmentFactory::default(); diff --git a/base_layer/core/src/common/one_sided.rs b/base_layer/core/src/common/one_sided.rs index a63907c410..752af555f4 100644 --- a/base_layer/core/src/common/one_sided.rs +++ b/base_layer/core/src/common/one_sided.rs @@ -67,6 +67,16 @@ pub fn secret_key_to_output_encryption_key(secret_key: &PrivateKey) -> Result Result { + PrivateKey::from_uniform_bytes( + WalletOutputEncryptionKeysDomainHasher::new() + .chain(public_key.as_bytes()) + .finalize() + .as_ref(), + ) +} + /// Generate an output spending key from a Diffie-Hellman shared secret pub fn shared_secret_to_output_spending_key(shared_secret: &CommsDHKE) -> Result { PrivateKey::from_uniform_bytes( diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index fbc0fc6fa6..2a8bc7bb56 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -109,7 +109,7 @@ impl AggregateBody { pub fn update_script_signature( &mut self, commitment: &Commitment, - script_signature: ComAndPubSignature, + script_signature: &ComAndPubSignature, ) -> Result<(), TransactionError> { let input = self .inputs @@ -119,7 +119,7 @@ impl AggregateBody { Err(_) => false, }) .ok_or(TransactionError::OutputNotFound(commitment.to_hex()))?; - input.script_signature = script_signature; + input.script_signature = script_signature.clone(); Ok(()) } diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index a3216c27a3..0c272998e0 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -63,7 +63,6 @@ use tari_key_manager::{ }; use tari_utilities::{hex::Hex, ByteArray}; use tokio::sync::RwLock; -use zeroize::Zeroizing; const LOG_TARGET: &str = "c::bn::key_manager::key_manager_service"; const TRANSACTION_KEY_MANAGER_MAX_SEARCH_DEPTH: u64 = 1_000_000; @@ -180,14 +179,10 @@ where TBackend: KeyManagerBackend + 'static Ok((key_id, key)) } - pub async fn create_key_pair( - &mut self, - branch: &str, - ) -> Result<(Zeroizing, PublicKey), KeyManagerServiceError> { + pub async fn create_key_pair(&mut self, branch: &str) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError> { self.add_key_manager_branch(branch)?; let (key_id, public_key) = self.get_next_key(branch).await?; - let private_key = Zeroizing::new(self.get_private_key(&key_id).await?); - Ok((private_key, public_key)) + Ok((key_id, public_key)) } pub async fn get_static_key(&self, branch: &str) -> Result { @@ -682,6 +677,9 @@ where TBackend: KeyManagerBackend + 'static spend_key_id: &TariKeyId, value: &PrivateKey, challenge: &[u8; 64], + r_a: &PrivateKey, + r_x: &PrivateKey, + r_y: &PrivateKey, ) -> Result { let spend_private_key = self.get_private_key(spend_key_id).await?; @@ -716,6 +714,9 @@ where TBackend: KeyManagerBackend + 'static data.extend_from_slice(value.as_bytes()); data.extend_from_slice(spend_private_key.as_bytes()); data.extend_from_slice(challenge); + data.extend_from_slice(r_a.as_bytes()); + data.extend_from_slice(r_x.as_bytes()); + data.extend_from_slice(r_y.as_bytes()); let command = ledger.build_command(Instruction::GetScriptSignatureFromChallenge, data); let transport = get_transport()?; @@ -755,18 +756,15 @@ where TBackend: KeyManagerBackend + 'static } }, (_, _) => { - let r_a = PrivateKey::random(&mut OsRng); - let r_x = PrivateKey::random(&mut OsRng); - let r_y = PrivateKey::random(&mut OsRng); let script_private_key = self.get_private_key(script_key_id).await?; let script_signature = ComAndPubSignature::sign( value, &spend_private_key, &script_private_key, - &r_a, - &r_x, - &r_y, + r_a, + r_x, + r_y, challenge.as_slice(), &*self.crypto_factories.commitment, )?; @@ -1043,6 +1041,30 @@ where TBackend: KeyManagerBackend + 'static Ok(metadata_signature) } + pub async fn sign_message( + &self, + private_key_id: &TariKeyId, + challenge: &[u8], + ) -> Result { + let private_key = self.get_private_key(private_key_id).await?; + let nonce = PrivateKey::random(&mut OsRng); + let signature = Signature::sign_with_nonce_and_message(&private_key, nonce, challenge)?; + + Ok(signature) + } + + pub async fn sign_with_nonce_and_message( + &self, + private_key_id: &TariKeyId, + nonce: &PrivateKey, + challenge: &[u8], + ) -> Result { + let private_key = self.get_private_key(private_key_id).await?; + let signature = Signature::sign_with_nonce_and_message(&private_key, nonce.clone(), challenge)?; + + Ok(signature) + } + pub async fn get_metadata_signature( &self, spending_key_id: &TariKeyId, diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index f517a95e00..fcf54be1de 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -29,7 +29,6 @@ use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey, Publi use tari_comms::types::CommsDHKE; use tari_crypto::{hashing::DomainSeparatedHash, ristretto::RistrettoComSig}; use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface, KeyManagerServiceError}; -use zeroize::Zeroizing; use crate::transactions::{ tari_amount::MicroMinotari, @@ -192,6 +191,9 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { spend_key_id: &TariKeyId, value: &PrivateKey, challenge: &[u8; 64], + r_a: &PrivateKey, + r_x: &PrivateKey, + r_y: &PrivateKey, ) -> Result; async fn get_partial_txo_kernel_signature( @@ -256,6 +258,15 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { range_proof_type: RangeProofType, ) -> Result; + async fn sign_message(&self, private_key_id: &TariKeyId, challenge: &[u8]) -> Result; + + async fn sign_with_nonce_and_message( + &self, + private_key_id: &TariKeyId, + nonce: &PrivateKey, + challenge: &[u8], + ) -> Result; + async fn get_receiver_partial_metadata_signature( &self, spend_key_id: &TariKeyId, @@ -287,7 +298,7 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { async fn create_key_pair + Send>( &self, branch: T, - ) -> Result<(Zeroizing, PublicKey), KeyManagerServiceError>; + ) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError>; } #[async_trait::async_trait] @@ -295,3 +306,47 @@ pub trait SecretTransactionKeyManagerInterface: TransactionKeyManagerInterface { /// Gets the pedersen commitment for the specified index async fn get_private_key(&self, key_id: &TariKeyId) -> Result; } + +#[cfg(test)] +mod test { + use core::iter; + use std::str::FromStr; + + use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; + use tari_common_types::types::{PrivateKey, PublicKey}; + use tari_crypto::keys::{PublicKey as PK, SecretKey as SK}; + + use crate::transactions::key_manager::TariKeyId; + + fn random_string(len: usize) -> String { + iter::repeat(()) + .map(|_| OsRng.sample(Alphanumeric) as char) + .take(len) + .collect() + } + + #[test] + fn key_id_converts_correctly() { + let managed_key_id: TariKeyId = TariKeyId::Managed { + branch: random_string(8), + index: { + let mut rng = rand::thread_rng(); + let random_value: u64 = rng.gen(); + random_value + }, + }; + let imported_key_id: TariKeyId = TariKeyId::Imported { + key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + }; + let zero_key_id: TariKeyId = TariKeyId::Zero; + + let managed_key_id_str = managed_key_id.to_string(); + let imported_key_id_str = imported_key_id.to_string(); + let zero_key_id_str = zero_key_id.to_string(); + + assert_eq!(managed_key_id, TariKeyId::from_str(&managed_key_id_str).unwrap()); + println!("imported_key_id_str: {}", imported_key_id_str); + assert_eq!(imported_key_id, TariKeyId::from_str(&imported_key_id_str).unwrap()); + assert_eq!(zero_key_id, TariKeyId::from_str(&zero_key_id_str).unwrap()); + } +} diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index 055c40a489..d1a74a9cb5 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -40,7 +40,6 @@ use tari_key_manager::{ }, }; use tokio::sync::RwLock; -use zeroize::Zeroizing; use crate::transactions::{ key_manager::{ @@ -303,11 +302,14 @@ where TBackend: KeyManagerBackend + 'static spend_key_id: &TariKeyId, value: &PrivateKey, challenge: &[u8; 64], + r_a: &PrivateKey, + r_x: &PrivateKey, + r_y: &PrivateKey, ) -> Result { self.transaction_key_manager_inner .read() .await - .get_script_signature_from_challenge(script_key_id, spend_key_id, value, challenge) + .get_script_signature_from_challenge(script_key_id, spend_key_id, value, challenge, r_a, r_x, r_y) .await } @@ -435,6 +437,27 @@ where TBackend: KeyManagerBackend + 'static .await } + async fn sign_message(&self, private_key_id: &TariKeyId, challenge: &[u8]) -> Result { + self.transaction_key_manager_inner + .read() + .await + .sign_message(private_key_id, challenge) + .await + } + + async fn sign_with_nonce_and_message( + &self, + private_key_id: &TariKeyId, + nonce: &PrivateKey, + challenge: &[u8], + ) -> Result { + self.transaction_key_manager_inner + .read() + .await + .sign_with_nonce_and_message(private_key_id, nonce, challenge) + .await + } + async fn get_receiver_partial_metadata_signature( &self, spend_key_id: &TariKeyId, @@ -499,7 +522,7 @@ where TBackend: KeyManagerBackend + 'static async fn create_key_pair + Send>( &self, branch: T, - ) -> Result<(Zeroizing, PublicKey), KeyManagerServiceError> { + ) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError> { self.transaction_key_manager_inner .write() .await diff --git a/base_layer/core/src/transactions/transaction_components/range_proof_type.rs b/base_layer/core/src/transactions/transaction_components/range_proof_type.rs index 1d9e88d9ad..50fb8b1195 100644 --- a/base_layer/core/src/transactions/transaction_components/range_proof_type.rs +++ b/base_layer/core/src/transactions/transaction_components/range_proof_type.rs @@ -23,7 +23,10 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; use borsh::{BorshDeserialize, BorshSerialize}; use num_derive::FromPrimitive; @@ -74,6 +77,18 @@ impl Display for RangeProofType { } } +impl FromStr for RangeProofType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "bullet_proof_plus" => Ok(RangeProofType::BulletProofPlus), + "revealed_value" => Ok(RangeProofType::RevealedValue), + _ => Err("Invalid range proof type".to_string()), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/base_layer/core/src/transactions/transaction_components/wallet_output.rs b/base_layer/core/src/transactions/transaction_components/wallet_output.rs index d98ffb1ba9..4b6f8aa8df 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output.rs @@ -38,8 +38,12 @@ use tari_common_types::types::{ PrivateKey, PublicKey, RangeProof, + Signature, +}; +use tari_crypto::{ + commitment::HomomorphicCommitmentFactory, + keys::{PublicKey as PK, SecretKey}, }; -use tari_crypto::{commitment::HomomorphicCommitmentFactory, keys::SecretKey}; use tari_script::{ExecutionStack, TariScript}; use super::TransactionOutputVersion; @@ -249,63 +253,89 @@ impl WalletOutput { )) } - /// It creates a transaction input given a partial script signature. The public keys - /// `partial_script_public_key` and `partial_total_nonce` exclude caller's private keys - pub async fn as_transaction_input_with_partial_signature( + /// It creates a transaction input given an updated multi-party script signature. The inputs + /// `script_signature_shares` and `script_public_key_shares` exclude the caller's data. + pub async fn to_transaction_input_with_multi_party_script_signature( &self, factory: &CommitmentFactory, - partial_script_public_key: PublicKey, - partial_total_nonce: PublicKey, + script_signature_shares: &[Signature], + script_public_key_shares: &[PublicKey], key_manager: &KM, - ) -> Result { + ) -> Result<(TransactionInput, PublicKey), TransactionError> { let value = self.value.into(); let commitment = key_manager.get_commitment(&self.spending_key_id, &value).await?; - let version = TransactionInputVersion::get_current_version(); - let script_nonce_a = PrivateKey::random(&mut OsRng); - let script_nonce_b = PrivateKey::random(&mut OsRng); - let ephemeral_commitment = factory.commit(&script_nonce_b, &script_nonce_a); - // TODO: Is this correct? It seems `PublicKey::default()` should be repalced with some non-default value - let ephemeral_pubkey = PublicKey::default() + &partial_total_nonce; - let script_public_key = - key_manager.get_public_key_at_key_id(&self.script_key_id).await? + &partial_script_public_key; + + let r_a = PrivateKey::random(&mut OsRng); + let r_x = PrivateKey::random(&mut OsRng); + let ephemeral_commitment = factory.commit(&r_x, &r_a); + + let r_y = PrivateKey::random(&mut OsRng); + let ephemeral_public_key_self = PublicKey::from_secret_key(&r_y); + let ephemeral_public_key = script_signature_shares + .iter() + .fold(ephemeral_public_key_self, |acc, x| acc + x.get_public_nonce()); + + let script_public_key_self = key_manager.get_public_key_at_key_id(&self.script_key_id).await?; + let script_public_key = script_public_key_shares + .iter() + .fold(script_public_key_self, |acc, x| acc + x); + let challenge = TransactionInput::build_script_signature_challenge( &version, &ephemeral_commitment, - &ephemeral_pubkey, + &ephemeral_public_key, &self.script, &self.input_data, &script_public_key, &commitment, ); let script_signature = key_manager - .get_script_signature_from_challenge(&self.script_key_id, &self.spending_key_id, &value, &challenge) + .get_script_signature_from_challenge( + &self.script_key_id, + &self.spending_key_id, + &value, + &challenge, + &r_a, + &r_x, + &r_y, + ) .await?; - // TODO: Is this correct? `partial_total_nonce` is already added to `ephemeral_pubkey` above - let script_signature = ComAndPubSignature::new( + let multi_party_script_signature = ComAndPubSignature::new( script_signature.ephemeral_commitment().clone(), - script_signature.ephemeral_pubkey().clone() + &partial_total_nonce, + script_signature_shares + .iter() + .fold(script_signature.ephemeral_pubkey().clone(), |acc, x| { + acc + x.get_public_nonce() + }), script_signature.u_a().clone(), script_signature.u_x().clone(), - script_signature.u_y().clone(), + script_signature_shares + .iter() + .fold(script_signature.u_y().clone(), |acc, x| acc + x.get_signature()), ); - Ok(TransactionInput::new_current_version( + let input = TransactionInput::new_current_version( SpentOutput::OutputData { features: self.features.clone(), commitment, script: self.script.clone(), sender_offset_public_key: self.sender_offset_public_key.clone(), covenant: self.covenant.clone(), - encrypted_data: Default::default(), - metadata_signature: Default::default(), + encrypted_data: self.encrypted_data.clone(), + metadata_signature: self.metadata_signature.clone(), version: self.version, minimum_value_promise: self.minimum_value_promise, - rangeproof_hash: Default::default(), + rangeproof_hash: match &self.range_proof { + Some(rp) => rp.hash(), + None => FixedHash::zero(), + }, }, self.input_data.clone(), - script_signature, - )) + multi_party_script_signature, + ); + + Ok((input, script_public_key)) } /// Commits an WalletOutput into a TransactionInput that only contains the hash of the spent output data 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 b4cdfce5b3..b8530684db 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 @@ -27,7 +27,7 @@ use tari_script::{ExecutionStack, TariScript}; use crate::{ covenants::Covenant, transactions::{ - key_manager::{TariKeyId, TransactionKeyManagerInterface}, + key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, transaction_components::{ encrypted_data::PaymentId, @@ -200,6 +200,76 @@ impl WalletOutputBuilder { Ok(self) } + /// Sign a partial multi-party metadata signature as the sender and receiver - `sender_offset_public_key_shares` and + /// `ephemeral_pubkey_shares` from other participants are combined to enable creation of the challenge. + pub async fn sign_partial_as_sender_and_receiver( + mut self, + key_manager: &KM, + sender_offset_key_id: &TariKeyId, + sender_offset_public_key_shares: &[PublicKey], + ephemeral_public_key_shares: &[PublicKey], + ) -> Result { + let script = self + .script + .as_ref() + .ok_or_else(|| TransactionError::BuilderError("Cannot sign metadata without a script".to_string()))?; + let metadata_message = TransactionOutput::metadata_signature_message_from_parts( + &self.version, + script, + &self.features, + &self.covenant, + &self.encrypted_data, + &self.minimum_value_promise, + ); + + let sender_offset_public_key_self = key_manager.get_public_key_at_key_id(sender_offset_key_id).await?; + let aggregate_sender_offset_public_key = sender_offset_public_key_shares + .iter() + .fold(sender_offset_public_key_self, |acc, x| acc + x); + + let (ephemeral_private_nonce_id, ephemeral_pubkey_self) = key_manager + .get_next_key(&TransactionKeyManagerBranch::MetadataEphemeralNonce.get_branch_key()) + .await?; + let aggregate_ephemeral_pubkey = ephemeral_public_key_shares + .iter() + .fold(ephemeral_pubkey_self, |acc, x| acc + x); + + let receiver_partial_metadata_signature = key_manager + .get_receiver_partial_metadata_signature( + &self.spending_key_id, + &self.value.into(), + &aggregate_sender_offset_public_key, + &aggregate_ephemeral_pubkey, + &TransactionOutputVersion::get_current_version(), + &metadata_message, + self.features.range_proof_type, + ) + .await?; + + let commitment = key_manager + .get_commitment(&self.spending_key_id, &self.value.into()) + .await?; + let ephemeral_commitment = receiver_partial_metadata_signature.ephemeral_commitment(); + let sender_partial_metadata_signature_self = key_manager + .get_sender_partial_metadata_signature( + &ephemeral_private_nonce_id, + sender_offset_key_id, + &commitment, + ephemeral_commitment, + &TransactionOutputVersion::get_current_version(), + &metadata_message, + ) + .await?; + + let metadata_signature = &receiver_partial_metadata_signature + &sender_partial_metadata_signature_self; + + self.metadata_signature = Some(metadata_signature); + self.metadata_signed_by_receiver = true; + self.metadata_signed_by_sender = true; + self.sender_offset_public_key = Some(aggregate_sender_offset_public_key); + Ok(self) + } + pub async fn try_build( self, key_manager: &KM, @@ -246,7 +316,7 @@ mod test { use tari_key_manager::key_manager_service::KeyManagerInterface; use super::*; - use crate::transactions::key_manager::{create_memory_db_key_manager, TransactionKeyManagerBranch}; + use crate::transactions::key_manager::create_memory_db_key_manager; #[tokio::test] async fn test_try_build() { diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index c7ae684627..411741340a 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -150,6 +150,8 @@ pub enum OutputManagerError { RangeProofError(String), #[error("Transaction is over sized: `{0}`")] TooManyInputsToFulfillTransaction(String), + #[error("Std I/O error: {0}")] + StdIoError(#[from] std::io::Error), } impl From for OutputManagerError { diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index b07175d5ec..16fe77468b 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -23,14 +23,24 @@ use std::{fmt, fmt::Formatter, sync::Arc}; use tari_common_types::{ + tari_address::TariAddress, transaction::TxId, types::{Commitment, FixedHash, HashOutput, PublicKey, Signature}, }; +use tari_comms::types::CommsDHKE; use tari_core::{ covenants::Covenant, transactions::{ tari_amount::MicroMinotari, - transaction_components::{OutputFeatures, Transaction, TransactionOutput, WalletOutput, WalletOutputBuilder}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + RangeProofType, + Transaction, + TransactionOutput, + WalletOutput, + WalletOutputBuilder, + }, transaction_protocol::{sender::TransactionSenderMessage, TransactionMetadata}, ReceiverTransactionProtocol, SenderTransactionProtocol, @@ -63,12 +73,17 @@ pub enum OutputManagerRequest { tx_id: TxId, fee_per_gram: MicroMinotari, output_hash: String, - signatures: Vec, - total_script_pubkey: PublicKey, - total_offset_pubkey: PublicKey, - total_signature_nonce: PublicKey, - metadata_signature_nonce: PublicKey, - wallet_script_secret_key: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, }, PrepareToSendTransaction { tx_id: TxId, @@ -739,12 +754,17 @@ impl OutputManagerHandle { tx_id: TxId, fee_per_gram: MicroMinotari, output_hash: String, - signatures: Vec, - total_script_pubkey: PublicKey, - total_offset_pubkey: PublicKey, - total_signature_nonce: PublicKey, - metadata_signature_nonce: PublicKey, - wallet_script_secret_key: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, ) -> Result<(Transaction, MicroMinotari, MicroMinotari, PublicKey), OutputManagerError> { match self .handle @@ -752,12 +772,17 @@ impl OutputManagerHandle { tx_id, fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, }) .await?? { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 53f998c14d..7351ce289e 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -30,16 +30,22 @@ use tari_common::configuration::Network; use tari_common_types::{ tari_address::TariAddress, transaction::TxId, - types::{BlockHash, Commitment, HashOutput, PrivateKey, PublicKey, Signature}, + types::{BlockHash, Commitment, FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::{types::CommsDHKE, NodeIdentity}; use tari_core::{ borsh::SerializedSize, consensus::ConsensusConstants, covenants::Covenant, - one_sided::{shared_secret_to_output_encryption_key, stealth_address_script_spending_key}, + 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::FetchMatchingUtxos, transactions::{ + aggregated_body::AggregateBody, fee::Fee, key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, @@ -48,6 +54,7 @@ use tari_core::{ EncryptedData, KernelFeatures, OutputFeatures, + RangeProofType, Transaction, TransactionError, TransactionOutput, @@ -62,7 +69,16 @@ use tari_core::{ }, }; use tari_crypto::keys::SecretKey; -use tari_script::{inputs, script, ExecutionStack, Opcode, TariScript}; +use tari_script::{ + inputs, + push_pubkey_script, + script, + CheckSigSchnorrSignature, + ExecutionStack, + Opcode, + StackItem, + TariScript, +}; use tari_service_framework::reply_channel; use tari_shutdown::ShutdownSignal; use tari_utilities::{hex::Hex, ByteArray}; @@ -233,23 +249,33 @@ where tx_id, fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, } => self .encumber_aggregate_utxo( tx_id, fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, ) .await .map(OutputManagerResponse::EncumberAggregateUtxo), @@ -1149,150 +1175,288 @@ where Ok((tx_id, stp.into_transaction()?)) } + /// Create a partial transaction in order to prepare output #[allow(clippy::too_many_lines)] pub async fn encumber_aggregate_utxo( &mut self, - _tx_id: TxId, - _fee_per_gram: MicroMinotari, - _output_hash: String, - _signatures: Vec, - _total_script_pubkey: PublicKey, - _total_offset_pubkey: PublicKey, - _total_signature_nonce: PublicKey, - _metadata_signature_nonce: PublicKey, - _wallet_script_secret_key: String, + tx_id: TxId, + fee_per_gram: MicroMinotari, + output_hash: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, ) -> Result<(Transaction, MicroMinotari, MicroMinotari, PublicKey), OutputManagerError> { - unimplemented!("encumber_aggregate_utxo"); - - // let script = script!(Nop); - // let covenant = Covenant::default(); - // let output_features = OutputFeatures::default(); - // - // let metadata_byte_size = self - // .resources - // .consensus_constants - // .transaction_weight_params() - // .round_up_features_and_scripts_size( - // output_features.get_serialized_size()? + - // script.get_serialized_size()? + - // covenant.get_serialized_size()?, - // ); - // - // let output_hash = - // FixedHash::from_hex(&output_hash).map_err(|e| OutputManagerError::ConversionError(e.to_string()))?; - // let db_input = self.resources.db.get_unspent_output(output_hash)?; - // let mut input: WalletOutput = db_input.clone().into(); - // - // let mut script_signatures = Vec::new(); - // - // for signature in &signatures { - // script_signatures.push(StackItem::Signature(CheckSigSchnorrSignature::new(signature.get_public_nonce(). - // clone(), signature.get_signature().clone()))); } - // input.input_data = ExecutionStack::new(script_signatures); - // - // let wallet_script_secret_key = PrivateKey::from_hex(&wallet_script_secret_key) - // .map_err(|e| OutputManagerError::ConversionError(e.to_string()))?; - // let total_script_key = PublicKey::from_secret_key(&wallet_script_secret_key) + &total_script_pubkey; - // input.script_key_id = self.resources.key_manager.import_key(wallet_script_secret_key).await?; - // - // let offset = PrivateKey::random(&mut OsRng); - // let nonce = PrivateKey::random(&mut OsRng); - // let sender_offset_private_key = PrivateKey::random(&mut OsRng); - // - // // Create builder with no recipients (other than ourselves) - // let mut builder = SenderTransactionProtocol::builder(self.resources.consensus_constants.clone(), - // self.resources.key_manager.clone()); builder - // .with_fee_per_gram(fee_per_gram) - // // .with_offset(offset.clone()) - // // .with_private_nonce(nonce.clone()) - // // .with_rewindable_outputs(self.resources.rewind_data.clone()) - // .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) - // .with_kernel_features(KernelFeatures::empty()) - // .with_tx_id(tx_id) - // .with_lock_height(0) - // // TODO: The input to this function has changed; it needs a `WalletOutput`, not a `TransactionInput` - // .with_input( - // input.clone().as_transaction_input_with_partial_signature( - // &self.resources.factories.commitment, - // total_script_pubkey, - // total_signature_nonce, - // &() - // )?).await?; - // - // - // - // let fee = self.get_fee_calc(); - // let fee = fee.calculate(fee_per_gram, 1, 1, 1, metadata_byte_size); - // let amount = input.value - fee; - // - // let (spending_key, script_private_key) = self.get_spend_and_script_keys().await?; - // let encrypted_value = EncryptedValue::default(); - // let minimum_amount_promise = MicroTari::zero(); - // let metadata_signature = TransactionOutput::create_metadata_signature( - // TransactionOutputVersion::get_current_version(), - // amount, - // &spending_key, - // &script, - // &output_features, - // &(&total_offset_pubkey + &PublicKey::from_secret_key(&sender_offset_private_key)), - // Some(&metadata_signature_nonce), - // Some(&sender_offset_private_key), - // &covenant, - // &encrypted_value, - // minimum_amount_promise, - // )?; - // - // let metadata_signature = ComSignature::new( - // metadata_signature.public_nonce() + &metadata_signature_nonce, - // metadata_signature.u().clone(), - // metadata_signature.v().clone(), - // ); - // let utxo = DbUnblindedOutput::rewindable_from_unblinded_output( - // UnblindedOutput::new_current_version( - // amount, - // spending_key, - // output_features, - // script, - // inputs!(PublicKey::from_secret_key(&script_private_key)), - // script_private_key, - // &total_offset_pubkey + &PublicKey::from_secret_key(&sender_offset_private_key), - // metadata_signature, - // u64::MAX, - // covenant, - // encrypted_value, - // minimum_amount_promise, - // ), - // &self.resources.factories, - // &self.resources.rewind_data, - // None, - // None, - // OutputSource::default(), - // )?; - // builder - // .with_output(utxo.unblinded_output.clone(), sender_offset_private_key.clone()) - // .map_err(|e| OutputManagerError::BuildError(e.message))?; - // let factories = CryptoFactories::default(); - // let mut stp = builder - // .build( - // &self.resources.factories, - // None, - // self.last_seen_tip_height.unwrap_or(u64::MAX), - // ) - // .map_err(|e| OutputManagerError::BuildError(e.message))?; - // - // trace!( - // target: LOG_TARGET, - // "Encumber send to self transaction ({}) outputs.", - // tx_id - // ); - // self.resources.db.encumber_outputs(tx_id, vec![db_input], vec![utxo])?; - // - // trace!(target: LOG_TARGET, "Finalize send-to-self transaction ({}).", tx_id); - // - // stp.finalize(&self.resources.key_manager).await?; - // let tx = stp.take_transaction()?; - // - // Ok((tx, amount, fee, total_script_key)) + // Fetch the output from the blockchain + let output_hash = + FixedHash::from_hex(&output_hash).map_err(|e| OutputManagerError::ConversionError(e.to_string()))?; + let output = self + .fetch_outputs_from_node(vec![output_hash]) + .await? + .pop() + .ok_or_else(|| OutputManagerError::ServiceError(format!("Output not found (TxId: {})", tx_id)))?; + + // Retrieve the list of n public keys from the script + let public_keys = + if let [Opcode::CheckMultiSigVerifyAggregatePubKey(_n, _m, keys, _msg)] = output.script.as_slice() { + keys.clone() + } else { + return Err(OutputManagerError::ServiceError(format!( + "Invalid script (TxId: {})", + tx_id + ))); + }; + // Create a deterministic encryption key from the sum of the public keys + let sum_public_keys = 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)?; + // Decrypt the output secrets and create a new input as WalletOutput (unblinded) + let input = if let Ok((amount, spending_key, 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())? { + let mut script_signatures = Vec::new(); + for signature in &script_input_shares { + script_signatures.push(StackItem::Signature(CheckSigSchnorrSignature::new( + signature.get_public_nonce().clone(), + signature.get_signature().clone(), + ))); + } + let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; + WalletOutput::new_with_rangeproof( + output.version, + amount, + spending_key_id, + output.features, + output.script, + ExecutionStack::new(script_signatures), + self.resources.wallet_identity.wallet_node_key_id.clone(), // Only of the master wallet + output.sender_offset_public_key, + output.metadata_signature, + 0, + output.covenant, + output.encrypted_data, + output.minimum_value_promise, + output.proof, + payment_id, + ) + } else { + return Err(OutputManagerError::ServiceError(format!( + "Could not verify mask (TxId: {})", + tx_id + ))); + } + } else { + return Err(OutputManagerError::ServiceError(format!( + "Could not decrypt output (TxId: {})", + tx_id + ))); + }; + + // The entire input will be spent to a single recipient with no change + let output_features = OutputFeatures { + maturity, + range_proof_type, + ..Default::default() + }; + let script = script!(PushPubKey(Box::new(recipient_address.public_spend_key().clone()))); + let metadata_byte_size = self + .resources + .consensus_constants + .transaction_weight_params() + .round_up_features_and_scripts_size( + output_features.get_serialized_size()? + + script.get_serialized_size()? + + Covenant::default().get_serialized_size()?, + ); + let fee = self.get_fee_calc(); + let fee = fee.calculate(fee_per_gram, 1, 1, 1, metadata_byte_size); + let amount = input.value - fee; + + // Create sender transaction protocol builder with recipient data and no change + let mut builder = SenderTransactionProtocol::builder( + self.resources.consensus_constants.clone(), + self.resources.key_manager.clone(), + ); + builder + .with_lock_height(0) + .with_fee_per_gram(fee_per_gram) + .with_kernel_features(KernelFeatures::empty()) + .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) + .with_input(input.clone()) + .await? + .with_recipient_data( + push_pubkey_script(recipient_address.public_spend_key()), + output_features, + Covenant::default(), + minimum_value_promise, + amount, + ) + .await? + .with_change_data( + script!(PushPubKey(Box::default())), + ExecutionStack::default(), + TariKeyId::default(), + TariKeyId::default(), + Covenant::default(), + ); + let mut stp = builder + .build() + .await + .map_err(|e| OutputManagerError::BuildError(e.message))?; + + // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, + // but the returned value is not used + let _single_round_sender_data = stp.build_single_round_message(&self.resources.key_manager).await?; + + self.confirm_encumberance(tx_id)?; + + // Prepare receiver part of the transaction + + // Diffie-Hellman shared secret `k_Ob * K_Sb = K_Ob * k_Sb` results in a public key, which is fed into + // KDFs to produce the spending and encryption keys. All player's shares are added together to produce the + // shared secret. + let sender_offset_private_key_id_self = + stp.get_recipient_sender_offset_private_key()? + .ok_or(OutputManagerError::ServiceError(format!( + "Missing sender offset private key ID (TxId: {})", + tx_id + )))?; + + let shared_secret = { + let mut key_sum = PublicKey::default(); + for key in &dh_shared_secret_shares { + key_sum = key_sum + &PublicKey::from_vec(&key.as_bytes().to_vec())?; + } + let shared_secret_self = self + .resources + .key_manager + .get_diffie_hellman_shared_secret( + &sender_offset_private_key_id_self, + recipient_address + .public_view_key() + .ok_or(OutputManagerError::ServiceError(format!( + "Missing public view key (TxId: {})", + tx_id + )))?, + ) + .await?; + key_sum = key_sum + &PublicKey::from_vec(&shared_secret_self.as_bytes().to_vec())?; + CommsDHKE::new(&PrivateKey::default(), &key_sum) + }; + + let spending_key = shared_secret_to_output_spending_key(&shared_secret)?; + let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; + + let encryption_private_key = shared_secret_to_output_encryption_key(&shared_secret)?; + let encryption_key_id = self.resources.key_manager.import_key(encryption_private_key).await?; + + let sender_offset_public_key_self = self + .resources + .key_manager + .get_public_key_at_key_id(&sender_offset_private_key_id_self) + .await?; + let sender_offset_public_key = sender_offset_public_key_shares + .iter() + .fold(sender_offset_public_key_self, |acc, x| acc + x); + + let sender_message = TransactionSenderMessage::new_single_round_message( + stp.get_single_round_message(&self.resources.key_manager) + .await + .map_err(|e| service_error_with_id(tx_id, e.to_string(), true))?, + ); + + // Create the output with a partially signed metadata signature + let output = WalletOutputBuilder::new(amount, spending_key_id) + .with_features( + sender_message + .single() + .ok_or( + OutputManagerError::InvalidSenderMessage)? + .features + .clone(), + ) + .with_script(script) + .encrypt_data_for_recovery( + &self.resources.key_manager, + Some(&encryption_key_id), + payment_id.clone(), + ) + .await? + .with_input_data(ExecutionStack::default()) // Just a placeholder in the wallet + .with_sender_offset_public_key(sender_offset_public_key) + .with_script_key(self.resources.wallet_identity.wallet_node_key_id.clone()) + .with_minimum_value_promise(minimum_value_promise) + .sign_partial_as_sender_and_receiver( + &self.resources.key_manager, + &sender_offset_private_key_id_self, + &sender_offset_public_key_shares, + &metadata_ephemeral_public_key_shares, + ) + .await + .map_err(|e|service_error_with_id(tx_id, e.to_string(), true))? + .try_build(&self.resources.key_manager) + .await + .map_err(|e|service_error_with_id(tx_id, e.to_string(), true))?; + + // Finalize the partial transaction - it will not be valid at this stage as the metadata and script + // signatures are not yet complete. + let rtp = ReceiverTransactionProtocol::new( + sender_message, + output, + &self.resources.key_manager, + &self.resources.consensus_constants.clone(), + ) + .await; + let recipient_reply = rtp.get_signed_data()?.clone(); + stp.add_presigned_recipient_info(recipient_reply)?; + stp.finalize(&self.resources.key_manager) + .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); + + // Update the input's script signature + let (updated_input, total_script_public_key) = input + .to_transaction_input_with_multi_party_script_signature( + &self.resources.factories.commitment, + &script_signature_shares, + &script_public_key_shares, + &self.resources.key_manager, + ) + .await?; + + let mut tx = stp.get_transaction()?.clone(); + let commitment = updated_input.commitment()?; + + let mut inputs = tx.body.inputs().clone(); + inputs + .iter_mut() + .find(|input| match input.commitment() { + Ok(c) => c == commitment, + Err(_) => false, + }) + .ok_or(OutputManagerError::ServiceError(format!( + "Output not found: {}", + commitment.to_hex() + )))? + .script_signature = updated_input.script_signature.clone(); + tx.body = AggregateBody::new(inputs, tx.body.outputs().clone(), tx.body.kernels().clone()); + + let mut tx_body = tx.body; + tx_body.update_script_signature(updated_input.commitment()?, &updated_input.script_signature.clone())?; + tx.body = tx_body; + + let fee = stp.get_fee_amount()?; + + Ok((tx, amount, fee, total_script_public_key)) } async fn create_pay_to_self_transaction( @@ -2631,6 +2795,14 @@ where } } +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 { + error!(target: LOG_TARGET, "{}", err_str); + } + OutputManagerError::ServiceError(err_str) +} + /// This struct holds the detailed balance of the Output Manager Service. #[derive(Debug, Clone, PartialEq)] pub struct Balance { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index e3d2d58672..a017a72213 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -46,6 +46,7 @@ use tari_core::{ BuildInfo, CodeTemplateRegistration, OutputFeatures, + RangeProofType, TemplateType, Transaction, TransactionOutput, @@ -107,16 +108,22 @@ pub enum TransactionServiceRequest { m: u8, public_keys: Vec, message: [u8; 32], + maturity: u64, }, EncumberAggregateUtxo { fee_per_gram: MicroMinotari, output_hash: String, - signatures: Vec, - total_script_pubkey: PublicKey, - total_offset_pubkey: PublicKey, - total_signature_nonce: PublicKey, - metadata_signature_nonce: PublicKey, - wallet_script_secret_key: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, }, FinalizeSentAggregateTransaction { tx_id: u64, @@ -219,6 +226,7 @@ impl fmt::Display for TransactionServiceRequest { m, public_keys: _, message: _, + maturity: _, } => f.write_str(&format!( "Creating a new n-of-m aggregate uxto with: amount = {}, n = {}, m = {}", amount, n, m @@ -226,23 +234,62 @@ impl fmt::Display for TransactionServiceRequest { Self::EncumberAggregateUtxo { fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, .. } => f.write_str(&format!( - "Creating encumber n-of-m utxo with: fee_per_gram = {}, output_hash = {}, signatures = {:?}, \ - total_script_pubkey = {}, total_offset_pubkey = {}, total_signature_nonce = {}, \ - metadata_signature_nonce = {}", + "Creating encumber n-of-m utxo with: fee_per_gram = {}, output_hash = {}, script_input_shares = {:?}, \ + script_public_key_shares = {:?}, script_signature_shares = {:?}, sender_offset_public_key_shares = \ + {:?}, metadata_ephemeral_public_key_shares = {:?}, dh_shared_secret_shares = {:?}, recipient_address \ + = {}, payment_id = {}, maturity = {}, range_proof_type = {}, minimum_value_promise = {}", fee_per_gram, output_hash, - signatures, - total_script_pubkey.to_hex(), - total_offset_pubkey.to_hex(), - total_signature_nonce.to_hex(), - metadata_signature_nonce.to_hex(), + script_input_shares + .iter() + .map(|v| format!( + "(sig: {}, nonce: {})", + v.get_signature().to_hex(), + v.get_public_nonce().to_hex() + )) + .collect::>(), + script_public_key_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + script_signature_shares + .iter() + .map(|v| format!( + "(sig: {}, nonce: {})", + v.get_signature().to_hex(), + v.get_public_nonce().to_hex() + )) + .collect::>(), + sender_offset_public_key_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + metadata_ephemeral_public_key_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + dh_shared_secret_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, )), Self::FinalizeSentAggregateTransaction { tx_id, @@ -677,6 +724,7 @@ impl TransactionServiceHandle { m: u8, public_keys: Vec, message: [u8; 32], + maturity: u64, ) -> Result<(TxId, FixedHash), TransactionServiceError> { match self .handle @@ -687,6 +735,7 @@ impl TransactionServiceHandle { m, public_keys, message, + maturity, }) .await?? { @@ -699,24 +748,34 @@ impl TransactionServiceHandle { &mut self, fee_per_gram: MicroMinotari, output_hash: String, - signatures: Vec, - total_script_pubkey: PublicKey, - total_offset_pubkey: PublicKey, - total_signature_nonce: PublicKey, - metadata_signature_nonce: PublicKey, - wallet_script_secret_key: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, ) -> Result<(TxId, Transaction, PublicKey), TransactionServiceError> { match self .handle .call(TransactionServiceRequest::EncumberAggregateUtxo { fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, }) .await?? { diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index c7a71eed3e..110876c11b 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -40,27 +40,31 @@ use tari_common_types::{ transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{ComAndPubSignature, FixedHash, PrivateKey, PublicKey, Signature}, }; -use tari_comms::{types::CommsPublicKey, NodeIdentity}; +use tari_comms::{ + types::{CommsDHKE, CommsPublicKey}, + NodeIdentity, +}; use tari_comms_dht::outbound::OutboundMessageRequester; use tari_core::{ consensus::ConsensusManager, covenants::Covenant, mempool::FeePerGramStat, one_sided::{ - secret_key_to_output_encryption_key, + 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, transactions::{ - key_manager::TransactionKeyManagerInterface, + key_manager::{TariKeyId, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, transaction_components::{ encrypted_data::PaymentId, CodeTemplateRegistration, KernelFeatures, OutputFeatures, + RangeProofType, Transaction, TransactionOutput, WalletOutputBuilder, @@ -81,16 +85,7 @@ use tari_crypto::{ }; use tari_key_manager::key_manager_service::KeyId; use tari_p2p::domain_message::DomainMessage; -use tari_script::{inputs, push_pubkey_script, script, ExecutionStack, TariScript}; -use tari_script::{ - inputs, - one_sided_payment_script, - script, - slice_to_boxed_message, - stealth_payment_script, - ExecutionStack, - TariScript, -}; +use tari_script::{inputs, push_pubkey_script, script, slice_to_boxed_message, ExecutionStack, TariScript}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; use tokio::{ @@ -701,6 +696,7 @@ where m, public_keys, message, + maturity, } => self .create_aggregate_signature_utxo( amount, @@ -709,6 +705,7 @@ where m, public_keys, message, + maturity, transaction_broadcast_join_handles, ) .await @@ -718,22 +715,35 @@ where TransactionServiceRequest::EncumberAggregateUtxo { fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, } => self .encumber_aggregate_tx( fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares + .iter() + .map(|v| CommsDHKE::new(&PrivateKey::default(), &v.clone())) + .collect(), + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, ) .await .map(|(tx_id, tx, total_script_pubkey)| { @@ -1169,6 +1179,7 @@ where m: u8, public_keys: Vec, message: [u8; 32], + maturity: u64, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, @@ -1176,13 +1187,13 @@ where let tx_id = TxId::new_random(); let msg = slice_to_boxed_message(message.as_bytes()); - let script = script!(CheckMultiSigVerifyAggregatePubKey(n, m, public_keys, msg)); + let script = script!(CheckMultiSigVerifyAggregatePubKey(n, m, public_keys.clone(), msg)); // Empty covenant let covenant = Covenant::default(); // Default range proof - let minimum_value_promise = MicroMinotari::zero(); + let minimum_value_promise = amount; // Prepare sender part of transaction let mut stp = self @@ -1192,12 +1203,16 @@ where tx_id, amount, UtxoSelectionCriteria::default(), - OutputFeatures::default(), + OutputFeatures { + range_proof_type: RangeProofType::RevealedValue, + maturity, + ..Default::default() + }, fee_per_gram, TransactionMetadata::default(), "".to_string(), script.clone(), - covenant, + covenant.clone(), minimum_value_promise, ) .await?; @@ -1223,7 +1238,8 @@ where // In generating an aggregate public key utxo, we can use a randomly generated spend key let spending_key = PrivateKey::random(&mut OsRng); - let encryption_private_key = secret_key_to_output_encryption_key(&spending_key)?; + 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() @@ -1251,8 +1267,6 @@ where .import_key(spending_key.clone()) .await?; - let minimum_value_promise = MicroMinotari::zero(); - let covenant = Covenant::default(); let wallet_output = WalletOutputBuilder::new(amount, spending_key_id) .with_features( sender_message @@ -1275,12 +1289,11 @@ where ) .await? .with_input_data( - // TODO: refactor this, when we have implemented the necessary logic - inputs!(PublicKey::from_secret_key(&spending_key)), + ExecutionStack::default(), ) .with_covenant(covenant) .with_sender_offset_public_key(sender_offset_public_key) - .with_script_key(self.resources.wallet_identity.wallet_node_key_id.clone()) + .with_script_key(TariKeyId::default()) .with_minimum_value_promise(minimum_value_promise) .sign_as_sender_and_receiver( &self.resources.transaction_key_manager_service, @@ -1373,12 +1386,17 @@ where &mut self, fee_per_gram: MicroMinotari, output_hash: String, - signatures: Vec, - total_script_pubkey: PublicKey, - total_offset_pubkey: PublicKey, - total_signature_nonce: PublicKey, - metadata_signature_nonce: PublicKey, - wallet_script_secret_key: String, + script_input_shares: Vec, + script_public_key_shares: Vec, + script_signature_shares: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, ) -> Result<(TxId, Transaction, PublicKey), TransactionServiceError> { let tx_id = TxId::new_random(); @@ -1389,12 +1407,17 @@ where tx_id, fee_per_gram, output_hash, - signatures, - total_script_pubkey, - total_offset_pubkey, - total_signature_nonce, - metadata_signature_nonce, - wallet_script_secret_key, + script_input_shares, + script_public_key_shares, + script_signature_shares, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + payment_id, + maturity, + range_proof_type, + minimum_value_promise, ) .await { @@ -1462,7 +1485,7 @@ where )?; transaction.transaction.body.update_script_signature( &transaction.transaction.body.inputs()[0].commitment()?.clone(), - ComAndPubSignature::new( + &ComAndPubSignature::new( transaction.transaction.body.inputs()[0] .script_signature .ephemeral_commitment()