From 2700a467e3444df60fb7df71efdd54d50769a7ce Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Thu, 11 Jul 2024 11:35:57 +0200 Subject: [PATCH] fix: claim n of m faucet (#6389) Description --- Fixes n-of-m spending of faucets. Motivation and Context --- Allows spending of m-of-n How Has This Been Tested? --- Manual --------- Co-authored-by: Hansie Odendaal --- .../src/automation/commands.rs | 242 +++++++----------- .../minotari_console_wallet/src/cli.rs | 43 ++-- .../src/wallet_modes.rs | 79 +++--- .../core/src/transactions/aggregated_body.rs | 4 +- .../src/transactions/key_manager/inner.rs | 39 ++- .../src/transactions/key_manager/interface.rs | 14 +- .../src/transactions/key_manager/wrapper.rs | 22 +- .../transaction_output.rs | 1 - .../transaction_components/wallet_output.rs | 15 +- .../wallet_output_builder.rs | 17 +- .../transaction_protocol/sender.rs | 4 + .../src/key_manager_service/handle.rs | 5 + .../src/key_manager_service/interface.rs | 3 + .../src/key_manager_service/service.rs | 8 + .../src/output_manager_service/handle.rs | 67 ++++- .../src/output_manager_service/service.rs | 86 ++++++- .../wallet/src/transaction_service/handle.rs | 34 ++- .../wallet/src/transaction_service/service.rs | 137 ++++++---- 18 files changed, 482 insertions(+), 338 deletions(-) diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index c8b6acd6fe..24698586c3 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -67,7 +67,7 @@ use tari_core::{ covenants::Covenant, one_sided::FaucetHashDomain, transactions::{ - key_manager::{TransactionKeyManagerBranch, TransactionKeyManagerInterface}, + key_manager::TransactionKeyManagerInterface, tari_amount::{uT, MicroMinotari, Minotari}, transaction_components::{ encrypted_data::PaymentId, @@ -83,7 +83,7 @@ use tari_core::{ }, }, }; -use tari_crypto::ristretto::RistrettoSecretKey; +use tari_crypto::ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey}; use tari_key_manager::key_manager_service::KeyManagerInterface; use tari_script::{script, ExecutionStack, TariScript}; use tari_utilities::{hex::Hex, ByteArray}; @@ -142,31 +142,13 @@ pub async fn burn_tari( .map_err(CommandError::TransactionServiceError) } -pub async fn create_aggregate_signature_utxo( - mut wallet_transaction_service: TransactionServiceHandle, - amount: MicroMinotari, - fee_per_gram: MicroMinotari, - n: u8, - 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, 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, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -174,11 +156,12 @@ async fn encumber_aggregate_utxo( metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, -) -> Result<(TxId, Transaction, PublicKey), CommandError> { +) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), CommandError> { wallet_transaction_service .encumber_aggregate_utxo( fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -734,125 +717,69 @@ pub async fn command_runner( Err(e) => eprintln!("BurnMinotari error! {}", e), } }, - CreateKeyPair(args) => match key_manager_service.create_key_pair(args.key_branch).await { - Ok((key_id, pk)) => { - println!( - "New key pair: - 1. key id : {}, - 2. public key: {}", - key_id, - pk.to_hex() - ) - }, - Err(e) => eprintln!("CreateKeyPair error! {}", e), - }, - CreateAggregateSignatureUtxo(args) => match create_aggregate_signature_utxo( - transaction_service.clone(), - args.amount, - args.fee_per_gram, - args.n, - args.m, - args.public_keys - .iter() - .map(|pk| PublicKey::from(pk.clone())) - .collect::>(), - args.message, // 1. What is the message? => commitment - args.maturity, - ) - .await - { - Ok((tx_id, output_hash)) => { - println!( - "Created an utxo with n-of-m aggregate public key, with: - 1. n = {}, - 2. m = {}, - 3. tx id = {}, - 4. output hash = {}", - args.n, args.m, tx_id, output_hash - ) - }, - Err(e) => eprintln!("CreateAggregateSignatureUtxo error! {}", e), - }, - 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), - } - }, FaucetCreatePartyDetails(args) => { - let spend_key = wallet.get_wallet_id().await?.wallet_node_key_id.clone(); - let public_spend_key = key_manager_service.get_public_key_at_key_id(&spend_key).await?; - let (script_nonce, public_script_nonce) = key_manager_service - .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) + let wallet_spend_key_id = wallet.get_wallet_id().await?.wallet_node_key_id.clone(); + let wallet_public_spend_key = key_manager_service + .get_public_key_at_key_id(&wallet_spend_key_id) .await?; + let (script_nonce_key_id, public_script_nonce) = key_manager_service.get_random_key().await?; - let (sender_offset_key, public_sender_offset_key) = key_manager_service - .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) - .await?; - let (sender_offset_nonce, public_sender_offset_nonce) = key_manager_service - .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) - .await?; + let (sender_offset_key_id, public_sender_offset_key) = key_manager_service.get_random_key().await?; + + let (sender_offset_nonce_key_id, public_sender_offset_nonce) = + key_manager_service.get_random_key().await?; let commitment = Commitment::from_hex(&args.commitment)?; - let com_hash: [u8; 32] = + let commitment_hash: [u8; 32] = DomainSeparatedConsensusHasher::>::new("com_hash") .chain(&commitment) .finalize() .into(); let shared_secret = key_manager_service .get_diffie_hellman_shared_secret( - &sender_offset_key, - args.destination + &sender_offset_key_id, + args.recipient_address .public_view_key() .ok_or(CommandError::InvalidArgument("Missing public view key".to_string()))?, ) .await?; - let shared_secret_key = PublicKey::from_canonical_bytes(shared_secret.as_bytes())?; + let shared_secret_public_key = PublicKey::from_canonical_bytes(shared_secret.as_bytes())?; - let signature = key_manager_service.sign_message(&spend_key, &com_hash).await?; + let script_input_signature = key_manager_service + .sign_script_message(&wallet_spend_key_id, &commitment_hash) + .await?; println!( - "Sign message: - 1. signature: ({},{}), - 2. public spend key: {}, - 2. public spend key_id: {}, - 4. spend nonce key: {}, - 5. public spend nonce key: {}, - 6. sender offset key: {}, - 7. public sender offset key: {}, - 8. sender offset nonce key: {}, - 9. public sender offset nonce key: {}, - 10. shared secret: {}", - signature.get_signature().to_hex(), - signature.get_public_nonce().to_hex(), - public_spend_key, - spend_key, - script_nonce, + "Party details created with: + 1. script input signature: ({},{}), + 2. wallet public spend key: {}, + 3. wallet public spend key_id: {}, + 4. spend nonce key_id: {}, + 5. public spend nonce key: {}, + 6. sender offset key_id: {}, + 7. public sender offset key: {}, + 8. sender offset nonce key_id: {}, + 9. public sender offset nonce key: {}, + 10. public shared secret: {}", + script_input_signature.get_signature().to_hex(), + script_input_signature.get_public_nonce().to_hex(), + wallet_public_spend_key, + wallet_spend_key_id, + script_nonce_key_id, public_script_nonce, - sender_offset_key, + sender_offset_key_id, public_sender_offset_key, - sender_offset_nonce, + sender_offset_nonce_key_id, public_sender_offset_nonce, - shared_secret_key + shared_secret_public_key ); }, - EncumberAggregateUtxo(args) => { + FaucetEncumberAggregateUtxo(args) => { match encumber_aggregate_utxo( transaction_service.clone(), args.fee_per_gram, args.output_hash, + Commitment::from_hex(&args.commitment)?, args.script_input_shares .iter() .map(|v| v.clone().into()) @@ -881,22 +808,30 @@ pub async fn command_runner( ) .await { - Ok((tx_id, transaction, script_pubkey)) => { + Ok(( + tx_id, + transaction, + script_pubkey, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => { println!( - "Encumber aggregate utxo: - 1. Tx_id: {} - 2. input_commitment: {}, - 3. input_stack: {}, - 4. input_script: {}, - 5. total_script_key: {}, - 6. script_signature_ephemeral_commitment: {}, - 7. script_signature_ephemeral_pubkey: {}, - 8. output_commitment: {}, - 9. output_hash: {}, - 10. sender_offset_pubkey: {}, - 11. meta_signature_ephemeral_commitment: {}, - 12. meta_signature_ephemeral_pubkey: {}, - 13. total_public_offset: {}", + "Encumbered aggregate UTXO: + 1. tx_id: {}, + 2. input_commitment: {}, + 3. input_stack: {}, + 4. input_script: {}, + 5. total_script_key: {}, + 6. script_signature_ephemeral_commitment: {}, + 7. script_signature_ephemeral_pubkey: {}, + 8. output_commitment: {}, + 9. output_hash: {}, + 10. sender_offset_pubkey: {}, + 11. meta_signature_ephemeral_commitment: {}, + 12. meta_signature_ephemeral_pubkey: {}, + 13. total_public_offset: {}, + 14. encrypted_data: {}, + 15. output_features: {}", tx_id, transaction.body.inputs()[0].commitment().unwrap().to_hex(), transaction.body.inputs()[0].input_data.to_hex(), @@ -906,10 +841,7 @@ pub async fn command_runner( .script_signature .ephemeral_commitment() .to_hex(), - transaction.body.inputs()[0] - .script_signature - .ephemeral_pubkey() - .to_hex(), + total_script_nonce.to_hex(), transaction.body.outputs()[0].commitment().to_hex(), transaction.body.outputs()[0].hash().to_hex(), transaction.body.outputs()[0].sender_offset_public_key.to_hex(), @@ -917,17 +849,17 @@ pub async fn command_runner( .metadata_signature .ephemeral_commitment() .to_hex(), - transaction.body.outputs()[0] - .metadata_signature - .ephemeral_pubkey() - .to_hex(), + total_metadata_ephemeral_public_key.to_hex(), transaction.script_offset.to_hex(), + transaction.body.outputs()[0].encrypted_data.to_hex(), + serde_json::to_string(&transaction.body.outputs()[0].features) + .unwrap_or("Could not serialize output features".to_string()) ) }, Err(e) => println!("Encumber aggregate transaction error! {}", e), } }, - SpendAggregateUtxo(args) => { + FaucetSpendAggregateUtxo(args) => { let mut offset = PrivateKey::default(); for key in args.script_offset_keys { let secret_key = @@ -954,7 +886,7 @@ pub async fn command_runner( Err(e) => println!("Error completing transaction! {}", e), } }, - CreateScriptSig(args) => { + FaucetCreateScriptSig(args) => { let script = TariScript::from_hex(&args.input_script) .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; let input_data = ExecutionStack::from_hex(&args.input_stack) @@ -975,12 +907,12 @@ pub async fn command_runner( ); match key_manager_service - .sign_with_nonce_and_message(&args.private_key_id, &args.secret_nonce, challenge.as_slice()) + .sign_with_nonce_and_message(&args.private_key_id, &args.secret_nonce_key_id, &challenge) .await { Ok(signature) => { println!( - "Sign script sig: + "Script signature created: 1. signature: ({},{})", signature.get_signature().to_hex(), signature.get_public_nonce().to_hex(), @@ -989,18 +921,20 @@ pub async fn command_runner( Err(e) => eprintln!("SignMessage error! {}", e), } }, - CreateMetaSig(args) => { + FaucetCreateMetaSig(args) => { let offset = key_manager_service - .get_script_offset(&vec![args.secret_script_key], &vec![args - .secret_sender_offset_key + .get_script_offset(&vec![args.secret_script_key_id], &vec![args + .secret_sender_offset_key_id .clone()]) .await?; - let script = script!(Nop); + let script = script!(PushPubKey(Box::new(args.recipient_address.public_spend_key().clone()))); let commitment = Commitment::from_hex(&args.commitment).map_err(|e| CommandError::InvalidArgument(e.to_string()))?; let covenant = Covenant::default(); - let encrypted_data = EncryptedData::default(); - let output_features = OutputFeatures::default(); + let encrypted_data = EncryptedData::from_hex(&args.encrypted_data) + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; + let output_features = serde_json::from_str(&args.output_features) + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; let ephemeral_commitment = Commitment::from_hex(&args.ephemeral_commitment) .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; let ephemeral_pubkey = PublicKey::from_hex(&args.ephemeral_pubkey) @@ -1033,20 +967,20 @@ pub async fn command_runner( &encrypted_data, minimum_value_promise, ); - trace!(target: LOG_TARGET, "meta challange: {:?}", challenge); + trace!(target: LOG_TARGET, "meta challenge: {:?}", challenge); match key_manager_service .sign_with_nonce_and_message( - &args.secret_sender_offset_key, - &args.secret_nonce, - challenge.as_slice(), + &args.secret_sender_offset_key_id, + &args.secret_nonce_key_id, + &challenge, ) .await { Ok(signature) => { println!( - "1. Meta sig: - signature: ({},{}), - 2. Script offset: {}", + "Metadata signature created: + 1. signature: ({},{}), + 2. script offset: {}", signature.get_signature().to_hex(), signature.get_public_nonce().to_hex(), offset.to_hex(), diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index 3cb8e0873e..2b6fb23abc 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -119,14 +119,11 @@ pub enum CliCommands { GetBalance, SendMinotari(SendMinotariArgs), BurnMinotari(BurnMinotariArgs), - CreateKeyPair(CreateKeyPairArgs), - CreateAggregateSignatureUtxo(CreateAggregateSignatureUtxoArgs), - EncumberAggregateUtxo(EncumberAggregateUtxoArgs), - SpendAggregateUtxo(SpendAggregateUtxoArgs), - SignMessage(SignMessageArgs), + FaucetEncumberAggregateUtxo(FaucetEncumberAggregateUtxoArgs), + FaucetSpendAggregateUtxo(FaucetSpendAggregateUtxoArgs), FaucetCreatePartyDetails(FaucetCreatePartyDetailsArgs), - CreateScriptSig(CreateScriptSigArgs), - CreateMetaSig(CreateMetaSigArgs), + FaucetCreateScriptSig(FaucetCreateScriptSigArgs), + FaucetCreateMetaSig(FaucetCreateMetaSigArgs), SendOneSidedToStealthAddress(SendMinotariArgs), MakeItRain(MakeItRainArgs), CoinSplit(CoinSplitArgs), @@ -169,13 +166,13 @@ pub struct BurnMinotariArgs { } #[derive(Debug, Args, Clone)] -pub struct CreateKeyPairArgs { +pub struct FaucetCreateKeyPairArgs { #[clap(long)] pub key_branch: String, } #[derive(Debug, Args, Clone)] -pub struct CreateAggregateSignatureUtxoArgs { +pub struct FaucetCreateAggregateSignatureUtxoArgs { #[clap(long)] pub amount: MicroMinotari, #[clap(long)] @@ -197,11 +194,11 @@ pub struct FaucetCreatePartyDetailsArgs { #[clap(long)] pub commitment: String, #[clap(long)] - pub destination: TariAddress, + pub recipient_address: TariAddress, } #[derive(Debug, Args, Clone)] -pub struct SignMessageArgs { +pub struct FaucetSignMessageArgs { #[clap(long)] pub private_key_id: TariKeyId, #[clap(long)] @@ -209,10 +206,12 @@ pub struct SignMessageArgs { } #[derive(Debug, Args, Clone)] -pub struct EncumberAggregateUtxoArgs { +pub struct FaucetEncumberAggregateUtxoArgs { #[clap(long)] pub fee_per_gram: MicroMinotari, #[clap(long)] + pub commitment: String, + #[clap(long)] pub output_hash: String, #[clap(long)] pub script_input_shares: Vec, @@ -231,7 +230,7 @@ pub struct EncumberAggregateUtxoArgs { } #[derive(Debug, Args, Clone)] -pub struct SpendAggregateUtxoArgs { +pub struct FaucetSpendAggregateUtxoArgs { #[clap(long)] pub tx_id: u64, #[clap(long)] @@ -243,11 +242,11 @@ pub struct SpendAggregateUtxoArgs { } #[derive(Debug, Args, Clone)] -pub struct CreateScriptSigArgs { +pub struct FaucetCreateScriptSigArgs { #[clap(long)] pub private_key_id: TariKeyId, #[clap(long)] - pub secret_nonce: TariKeyId, + pub secret_nonce_key_id: TariKeyId, #[clap(long)] pub input_script: String, #[clap(long)] @@ -263,13 +262,13 @@ pub struct CreateScriptSigArgs { } #[derive(Debug, Args, Clone)] -pub struct CreateMetaSigArgs { +pub struct FaucetCreateMetaSigArgs { #[clap(long)] - pub secret_script_key: TariKeyId, + pub secret_script_key_id: TariKeyId, #[clap(long)] - pub secret_sender_offset_key: TariKeyId, + pub secret_sender_offset_key_id: TariKeyId, #[clap(long)] - pub secret_nonce: TariKeyId, + pub secret_nonce_key_id: TariKeyId, #[clap(long)] pub ephemeral_commitment: String, #[clap(long)] @@ -278,6 +277,12 @@ pub struct CreateMetaSigArgs { pub total_meta_key: UniPublicKey, #[clap(long)] pub commitment: String, + #[clap(long)] + pub encrypted_data: String, + #[clap(long)] + pub output_features: String, + #[clap(long)] + pub recipient_address: TariAddress, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index a51764cd9c..a35d719f20 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -496,25 +496,10 @@ mod test { burn-minotari --message Ups_these_funds_will_be_burned! 100T - create-key-pair --key-branch pie - - 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 \ + faucet-encumber-aggregate-utxo \ --fee-per-gram 1 \ --output-hash f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ + --commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ --script-input-shares=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ --script-input-shares=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ --script-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ @@ -529,7 +514,7 @@ mod test { --dh-shared-secret-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ --recipient-address f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA - spend-aggregate-utxo \ + faucet-spend-aggregate-utxo \ --tx-id 12345678 \ --meta-signatures=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ --meta-signatures=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ @@ -538,9 +523,13 @@ mod test { --script-offset-keys=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ --script-offset-keys=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 - create-script-sig \ + faucet-create-party-details \ + --commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ + --recipient-address f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA + + faucet-create-script-sig \ --private-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --secret-nonce imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ + --secret-nonce-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ --input-script ae010268593ed2d36a2d95f0ffe0f41649b97cc36fc4ef0c8ecd6bd28f9d56c76b793b08691435a5c813578f8a7f4973166dc1c6c15f37aec2a7d65b1583c8b2129364c916d5986a0c1b3dac7d6efb94bed688ba52fa8b962cf27c0446e2fea6d66a04 \ --input-stack 050857c14f72cf885aac9f08c9484cb7cb06b6cc20eab68c9bee1e8d5a85649b0a6d31c5cc49afc1e03ebbcf55c82f47e8cbc796c33e96c17a31eab027ee821f00 \ --ephemeral-commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ @@ -548,14 +537,17 @@ mod test { --total-script-key 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ --commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 - create-meta-sig \ - --secret-script-key imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --secret-sender-offset-key imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --secret-nonce imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ + faucet-create-meta-sig \ + --secret-script-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ + --secret-sender-offset-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ + --secret-nonce-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ --ephemeral-commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ --ephemeral-pubkey 8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ --total-meta-key 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 + --commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 \ + --encrypted-data 6a7aa2053ae187f60f27df0e10184bf93d02a84cd9548320ec7da546185fc23c6daa720974007c6106cfb0361eb9828e1af979b69fff724d2bcd0d86d5b9675ef1f65b424b22bee06e52fcaf4fd2a2ed \ + --output-features 'features' \ + --recipient-address f4FB7HhYCmLw4PsivjG8bAgUuxyPS6GTjFkhMWx6d9Nv4aoBESyaH5TdS1dAkSCg4qXqehpjZU9QrSUP2Ec7v4Gj8wf coin-split --message Make_many_dust_UTXOs! --fee-per-gram 2 0.001T 499 @@ -576,13 +568,11 @@ mod test { let mut get_balance = false; let mut send_tari = false; let mut burn_tari = false; - let mut create_key_pair = false; - let mut create_aggregate_signature_utxo = false; - let mut encumber_aggregate_utxo = false; - let mut spend_aggregate_utxo = false; - let mut sign_message = false; - let mut create_script_sig = false; - let mut create_meta_sig = false; + let mut faucet_encumber_aggregate_utxo = false; + let mut faucet_spend_aggregate_utxo = false; + let mut faucet_create_party_details = false; + let mut faucet_create_script_sig = false; + let mut faucet_create_meta_sig = false; let mut make_it_rain = false; let mut coin_split = false; let mut discover_peer = false; @@ -594,13 +584,11 @@ mod test { CliCommands::GetBalance => get_balance = true, CliCommands::SendMinotari(_) => send_tari = true, CliCommands::BurnMinotari(_) => burn_tari = true, - CliCommands::CreateKeyPair(_) => create_key_pair = true, - CliCommands::CreateAggregateSignatureUtxo(_) => create_aggregate_signature_utxo = true, - CliCommands::EncumberAggregateUtxo(_) => encumber_aggregate_utxo = true, - CliCommands::SpendAggregateUtxo(_) => spend_aggregate_utxo = true, - CliCommands::SignMessage(_) => sign_message = true, - CliCommands::CreateScriptSig(_) => create_script_sig = true, - CliCommands::CreateMetaSig(_) => create_meta_sig = true, + CliCommands::FaucetEncumberAggregateUtxo(_) => faucet_encumber_aggregate_utxo = true, + CliCommands::FaucetSpendAggregateUtxo(_) => faucet_spend_aggregate_utxo = true, + CliCommands::FaucetCreatePartyDetails(_) => faucet_create_party_details = true, + CliCommands::FaucetCreateScriptSig(_) => faucet_create_script_sig = true, + CliCommands::FaucetCreateMetaSig(_) => faucet_create_meta_sig = true, CliCommands::SendOneSidedToStealthAddress(_) => {}, CliCommands::MakeItRain(_) => make_it_rain = true, CliCommands::CoinSplit(_) => coin_split = true, @@ -628,20 +616,17 @@ mod test { CliCommands::RevalidateWalletDb => {}, CliCommands::RegisterValidatorNode(_) => {}, CliCommands::CreateTlsCerts => {}, - CliCommands::FaucetCreatePartyDetails(_) => {}, } } assert!( get_balance && send_tari && burn_tari && - create_key_pair && - create_aggregate_signature_utxo && - encumber_aggregate_utxo && - spend_aggregate_utxo && - sign_message && - create_script_sig && - create_meta_sig && + faucet_encumber_aggregate_utxo && + faucet_spend_aggregate_utxo && + faucet_create_party_details && + faucet_create_script_sig && + faucet_create_meta_sig && make_it_rain && coin_split && discover_peer && diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index 2a8bc7bb56..fbc0fc6fa6 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.clone(); + input.script_signature = script_signature; Ok(()) } diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index 6cbec53cee..045af0d275 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -63,6 +63,7 @@ use tari_key_manager::{ KeyManagerServiceError, }, }; +use tari_script::CheckSigSchnorrSignature; use tari_utilities::{hex::Hex, ByteArray}; use tokio::sync::RwLock; @@ -181,6 +182,13 @@ where TBackend: KeyManagerBackend + 'static Ok((key_id, key)) } + pub async fn get_random_key(&self) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError> { + let random_private_key = PrivateKey::random(&mut OsRng); + let key_id = self.import_key(random_private_key).await?; + let public_key = self.get_public_key_at_key_id(&key_id).await?; + Ok((key_id, public_key)) + } + 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?; @@ -1148,6 +1156,8 @@ where TBackend: KeyManagerBackend + 'static &commitment, ephemeral_commitment, txo_version, + None, + None, metadata_signature_message, ) .await?; @@ -1155,14 +1165,13 @@ where TBackend: KeyManagerBackend + 'static Ok(metadata_signature) } - pub async fn sign_message( + pub async fn sign_script_message( &self, private_key_id: &TariKeyId, challenge: &[u8], - ) -> Result { + ) -> 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)?; + let signature = CheckSigSchnorrSignature::sign(&private_key, challenge, &mut OsRng)?; Ok(signature) } @@ -1171,11 +1180,11 @@ where TBackend: KeyManagerBackend + 'static &self, private_key_id: &TariKeyId, nonce: &TariKeyId, - challenge: &[u8], + challenge: &[u8; 64], ) -> Result { let private_key = self.get_private_key(private_key_id).await?; let private_nonce = self.get_private_key(nonce).await?; - let signature = Signature::sign_with_nonce_and_message(&private_key, private_nonce, challenge)?; + let signature = Signature::sign_raw_uniform(&private_key, private_nonce, challenge)?; Ok(signature) } @@ -1214,6 +1223,8 @@ where TBackend: KeyManagerBackend + 'static &commitment, ephemeral_commitment, txo_version, + None, + None, metadata_signature_message, ) .await?; @@ -1262,6 +1273,9 @@ where TBackend: KeyManagerBackend + 'static Ok(metadata_signature) } + // In the case where the sender is an aggregated signer, we need to parse in the total public key shares, this is + // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated + // signers, this can be left as none pub async fn get_sender_partial_metadata_signature( &self, ephemeral_private_nonce_id: &TariKeyId, @@ -1269,14 +1283,23 @@ where TBackend: KeyManagerBackend + 'static commitment: &Commitment, ephemeral_commitment: &Commitment, txo_version: &TransactionOutputVersion, + aggregated_sender_offset_public_keys: Option<&PublicKey>, + aggregated_ephemeral_public_keys: Option<&PublicKey>, metadata_signature_message: &[u8; 32], ) -> Result { match &self.wallet_type { WalletType::Software => { let ephemeral_private_key = self.get_private_key(ephemeral_private_nonce_id).await?; - let ephemeral_pubkey = PublicKey::from_secret_key(&ephemeral_private_key); + let ephemeral_pubkey = match aggregated_ephemeral_public_keys { + Some(agg) => agg.clone(), + None => PublicKey::from_secret_key(&ephemeral_private_key), + }; + PublicKey::from_secret_key(&ephemeral_private_key); let sender_offset_private_key = self.get_private_key(sender_offset_key_id).await?; // Take the index and use it to find the key from ledger - let sender_offset_public_key = PublicKey::from_secret_key(&sender_offset_private_key); + let sender_offset_public_key = match aggregated_sender_offset_public_keys { + Some(agg) => agg.clone(), + None => PublicKey::from_secret_key(&sender_offset_private_key), + }; let challenge = TransactionOutput::finalize_metadata_signature_challenge( txo_version, diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index 43581ddf05..f9dd47d09b 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -29,6 +29,7 @@ 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 tari_script::CheckSigSchnorrSignature; use crate::transactions::{ tari_amount::MicroMinotari, @@ -261,13 +262,17 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { range_proof_type: RangeProofType, ) -> Result; - async fn sign_message(&self, private_key_id: &TariKeyId, challenge: &[u8]) -> Result; + async fn sign_script_message( + &self, + private_key_id: &TariKeyId, + challenge: &[u8], + ) -> Result; async fn sign_with_nonce_and_message( &self, private_key_id: &TariKeyId, nonce: &TariKeyId, - challenge: &[u8], + challenge: &[u8; 64], ) -> Result; async fn get_receiver_partial_metadata_signature( @@ -281,6 +286,9 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { range_proof_type: RangeProofType, ) -> Result; + // In the case where the sender is an aggregated signer, we need to parse in the other public key shares, this is + // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated + // signers, this can be left as none async fn get_sender_partial_metadata_signature( &self, ephemeral_private_nonce_id: &TariKeyId, @@ -288,6 +296,8 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { commitment: &Commitment, ephemeral_commitment: &Commitment, txo_version: &TransactionOutputVersion, + aggregated_sender_offset_public_keys: Option<&PublicKey>, + aggregated_ephemeral_public_keys: Option<&PublicKey>, metadata_signature_message: &[u8; 32], ) -> Result; diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index a33108171a..d023613783 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -39,6 +39,7 @@ use tari_key_manager::{ KeyManagerServiceError, }, }; +use tari_script::CheckSigSchnorrSignature; use tokio::sync::RwLock; use crate::transactions::{ @@ -119,6 +120,10 @@ where TBackend: KeyManagerBackend + 'static .await } + async fn get_random_key(&self) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError> { + self.transaction_key_manager_inner.read().await.get_random_key().await + } + async fn get_static_key + Send>(&self, branch: T) -> Result { self.transaction_key_manager_inner .read() @@ -437,11 +442,15 @@ where TBackend: KeyManagerBackend + 'static .await } - async fn sign_message(&self, private_key_id: &TariKeyId, challenge: &[u8]) -> Result { + async fn sign_script_message( + &self, + private_key_id: &TariKeyId, + challenge: &[u8], + ) -> Result { self.transaction_key_manager_inner .read() .await - .sign_message(private_key_id, challenge) + .sign_script_message(private_key_id, challenge) .await } @@ -449,7 +458,7 @@ where TBackend: KeyManagerBackend + 'static &self, private_key_id: &TariKeyId, nonce: &TariKeyId, - challenge: &[u8], + challenge: &[u8; 64], ) -> Result { self.transaction_key_manager_inner .read() @@ -483,6 +492,9 @@ where TBackend: KeyManagerBackend + 'static .await } + // In the case where the sender is an aggregated signer, we need to parse in the other public key shares, this is + // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated + // signers, this can be left as none async fn get_sender_partial_metadata_signature( &self, ephemeral_private_nonce_id: &TariKeyId, @@ -490,6 +502,8 @@ where TBackend: KeyManagerBackend + 'static commitment: &Commitment, ephemeral_commitment: &Commitment, txo_version: &TransactionOutputVersion, + aggregated_sender_offset_public_keys: Option<&PublicKey>, + aggregated_ephemeral_public_keys: Option<&PublicKey>, metadata_signature_message: &[u8; 32], ) -> Result { self.transaction_key_manager_inner @@ -501,6 +515,8 @@ where TBackend: KeyManagerBackend + 'static commitment, ephemeral_commitment, txo_version, + aggregated_sender_offset_public_keys, + aggregated_ephemeral_public_keys, metadata_signature_message, ) .await diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index 9099603da9..0dc042326a 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -405,7 +405,6 @@ impl TransactionOutput { encrypted_data, &minimum_value_promise, ); - TransactionOutput::finalize_metadata_signature_challenge( version, sender_offset_public_key, 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 0bf3a35152..86173ca5e8 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output.rs @@ -257,28 +257,27 @@ impl WalletOutput { pub async fn to_transaction_input_with_multi_party_script_signature( &self, factory: &CommitmentFactory, - script_signature_public_nonces: &[PublicKey], - script_public_key_shares: &[PublicKey], + aggregated_script_signature_public_nonces: &PublicKey, + aggregated_script_public_key_shares: &PublicKey, key_manager: &KM, ) -> 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(); + // TODO this is bad and insecure, we need to fix this, the nonces should live in the key manager and or only in + // the ledger. 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_public_nonces - .iter() - .fold(ephemeral_public_key_self, |acc, x| acc + x); + + let ephemeral_public_key = aggregated_script_signature_public_nonces + ephemeral_public_key_self; 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 script_public_key = aggregated_script_public_key_shares + script_public_key_self; let challenge = TransactionInput::build_script_signature_challenge( &version, 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 316b903585..083afd3eaa 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 @@ -206,8 +206,8 @@ impl WalletOutputBuilder { mut self, key_manager: &KM, sender_offset_key_id: &TariKeyId, - sender_offset_public_key_shares: &[PublicKey], - ephemeral_public_key_shares: &[PublicKey], + aggregated_sender_offset_public_key_shares: &PublicKey, + aggregated_ephemeral_public_key_shares: &PublicKey, ) -> Result { let script = self .script @@ -223,16 +223,13 @@ impl WalletOutputBuilder { ); 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 aggregate_sender_offset_public_key = + aggregated_sender_offset_public_key_shares + &sender_offset_public_key_self; 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 aggregate_ephemeral_pubkey = aggregated_ephemeral_public_key_shares + ephemeral_pubkey_self; let receiver_partial_metadata_signature = key_manager .get_receiver_partial_metadata_signature( @@ -257,6 +254,8 @@ impl WalletOutputBuilder { &commitment, ephemeral_commitment, &TransactionOutputVersion::get_current_version(), + Some(&aggregate_sender_offset_public_key), + Some(&aggregate_ephemeral_pubkey), &metadata_message, ) .await?; @@ -418,6 +417,8 @@ mod test { &commitment, receiver_metadata_signature.ephemeral_commitment(), &wallet_output.version, + None, + None, &metadata_message, ) .await diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index 04b58100ee..000b9f3c05 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -558,6 +558,8 @@ impl SenderTransactionProtocol { &received_output.commitment, received_output.metadata_signature.ephemeral_commitment(), &version, + None, + None, &metadata_message, ) .await?; @@ -1018,6 +1020,8 @@ mod test { &output.commitment, partial_metadata_signature.ephemeral_commitment(), &txo_version, + None, + None, &metadata_message, ) .await diff --git a/base_layer/key_manager/src/key_manager_service/handle.rs b/base_layer/key_manager/src/key_manager_service/handle.rs index b10ee8ccec..63ad546d57 100644 --- a/base_layer/key_manager/src/key_manager_service/handle.rs +++ b/base_layer/key_manager/src/key_manager_service/handle.rs @@ -84,6 +84,11 @@ where .await } + /// Gets a randomly generated key, which the key manager will manage + async fn get_random_key(&self) -> Result<(KeyId, PK), KeyManagerServiceError> { + (*self.key_manager_inner).read().await.get_random_key().await + } + async fn get_static_key + Send>(&self, branch: T) -> Result, KeyManagerServiceError> { (*self.key_manager_inner) .read() diff --git a/base_layer/key_manager/src/key_manager_service/interface.rs b/base_layer/key_manager/src/key_manager_service/interface.rs index 628d6a8374..807885da98 100644 --- a/base_layer/key_manager/src/key_manager_service/interface.rs +++ b/base_layer/key_manager/src/key_manager_service/interface.rs @@ -193,6 +193,9 @@ where /// Gets the next key id from the branch. This will auto-increment the branch key index by 1 async fn get_next_key + Send>(&self, branch: T) -> Result<(KeyId, PK), KeyManagerServiceError>; + /// Gets a randomly generated key, which the key manager will manage + async fn get_random_key(&self) -> Result<(KeyId, PK), KeyManagerServiceError>; + /// Gets the fixed key id from the branch. This will use the branch key with index 0 async fn get_static_key + Send>(&self, branch: T) -> Result, KeyManagerServiceError>; diff --git a/base_layer/key_manager/src/key_manager_service/service.rs b/base_layer/key_manager/src/key_manager_service/service.rs index f6ec353827..560ee2d556 100644 --- a/base_layer/key_manager/src/key_manager_service/service.rs +++ b/base_layer/key_manager/src/key_manager_service/service.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::collections::HashMap; +use argon2::password_hash::rand_core::OsRng; use blake2::Blake2b; use digest::consts::U64; use futures::lock::Mutex; @@ -115,6 +116,13 @@ where )) } + pub async fn get_random_key(&self) -> Result<(KeyId, PK), KeyManagerServiceError> { + let random_private_key = PK::K::random(&mut OsRng); + let key_id = self.import_key(random_private_key).await?; + let public_key = self.get_public_key_at_key_id(&key_id).await?; + Ok((key_id, public_key)) + } + pub async fn get_static_key(&self, branch: &str) -> Result, KeyManagerServiceError> { match self.key_managers.get(branch) { None => Err(KeyManagerServiceError::UnknownKeyBranch), diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 4d8f38cb76..c99400061c 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -37,6 +37,7 @@ use tari_core::{ SenderTransactionProtocol, }, }; +use tari_crypto::ristretto::pedersen::PedersenCommitment; use tari_script::TariScript; use tari_service_framework::reply_channel::SenderService; use tari_utilities::hex::Hex; @@ -64,6 +65,7 @@ pub enum OutputManagerRequest { tx_id: TxId, fee_per_gram: MicroMinotari, output_hash: String, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -154,10 +156,17 @@ impl fmt::Display for OutputManagerRequest { v.metadata_signature.u_y().to_hex(), v.metadata_signature.u_a().to_hex(), ), - EncumberAggregateUtxo { tx_id, output_hash, .. } => write!( + EncumberAggregateUtxo { + tx_id, + output_hash, + expected_commitment, + .. + } => write!( f, - "Encumber aggregate utxo with tx_id: {} and output_hash: {}", - tx_id, output_hash + "Encumber aggregate utxo with tx_id: {} and output: ({},{})", + tx_id, + expected_commitment.to_hex(), + output_hash ), GetRecipientTransaction(_) => write!(f, "GetRecipientTransaction"), ConfirmPendingTransaction(v) => write!(f, "ConfirmPendingTransaction ({})", v), @@ -232,7 +241,16 @@ pub enum OutputManagerResponse { ConvertedToTransactionOutput(Box), OutputMetadataSignatureUpdated, RecipientTransactionGenerated(ReceiverTransactionProtocol), - EncumberAggregateUtxo((Transaction, MicroMinotari, MicroMinotari, PublicKey)), + EncumberAggregateUtxo( + ( + Transaction, + MicroMinotari, + MicroMinotari, + PublicKey, + PublicKey, + PublicKey, + ), + ), OutputConfirmed, PendingTransactionConfirmed, PayToSelfTransaction((MicroMinotari, Transaction)), @@ -251,8 +269,13 @@ pub enum OutputManagerResponse { RewoundOutputs(Vec), ScanOutputs(Vec), AddKnownOneSidedPaymentScript, - CreateOutputWithFeatures { output: Box }, - CreatePayToSelfWithOutputs { transaction: Box, tx_id: TxId }, + CreateOutputWithFeatures { + output: Box, + }, + CreatePayToSelfWithOutputs { + transaction: Box, + tx_id: TxId, + }, ReinstatedCancelledInboundTx, ClaimHtlcTransaction((TxId, MicroMinotari, MicroMinotari, Transaction)), OutputInfoByTxId(OutputInfoByTxId), @@ -741,6 +764,7 @@ impl OutputManagerHandle { tx_id: TxId, fee_per_gram: MicroMinotari, output_hash: String, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -748,13 +772,24 @@ impl OutputManagerHandle { metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, - ) -> Result<(Transaction, MicroMinotari, MicroMinotari, PublicKey), OutputManagerError> { + ) -> Result< + ( + Transaction, + MicroMinotari, + MicroMinotari, + PublicKey, + PublicKey, + PublicKey, + ), + OutputManagerError, + > { match self .handle .call(OutputManagerRequest::EncumberAggregateUtxo { tx_id, fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -765,9 +800,21 @@ impl OutputManagerHandle { }) .await?? { - OutputManagerResponse::EncumberAggregateUtxo((transaction, amount, fee, total_script_key)) => { - Ok((transaction, amount, fee, total_script_key)) - }, + OutputManagerResponse::EncumberAggregateUtxo(( + transaction, + amount, + fee, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => Ok(( + transaction, + amount, + fee, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )), _ => Err(OutputManagerError::UnexpectedApiResponse), } } diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 81b50eed18..185a4f2832 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -22,7 +22,9 @@ use std::{convert::TryInto, fmt, sync::Arc}; +use blake2::Blake2b; use diesel::result::{DatabaseErrorKind, Error as DieselError}; +use digest::consts::U32; use futures::{pin_mut, StreamExt}; use log::*; use rand::{rngs::OsRng, RngCore}; @@ -35,13 +37,14 @@ use tari_common_types::{ use tari_comms::{types::CommsDHKE, NodeIdentity}; use tari_core::{ borsh::SerializedSize, - consensus::ConsensusConstants, + consensus::{ConsensusConstants, DomainSeparatedConsensusHasher}, covenants::Covenant, 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, + FaucetHashDomain, }, proto::base_node::FetchMatchingUtxos, transactions::{ @@ -67,7 +70,7 @@ use tari_core::{ SenderTransactionProtocol, }, }; -use tari_crypto::keys::SecretKey; +use tari_crypto::{keys::SecretKey, ristretto::pedersen::PedersenCommitment}; use tari_script::{ inputs, push_pubkey_script, @@ -247,6 +250,7 @@ where tx_id, fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -259,6 +263,7 @@ where tx_id, fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -496,7 +501,7 @@ where fee_per_gram: MicroMinotari, ) -> Result { let output = self - .fetch_outputs_from_node(vec![output_hash]) + .fetch_unspent_outputs_from_node(vec![output_hash]) .await? .pop() .ok_or_else(|| OutputManagerError::ServiceError("Output not found".to_string()))?; @@ -1176,6 +1181,7 @@ where tx_id: TxId, fee_per_gram: MicroMinotari, output_hash: String, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -1187,15 +1193,31 @@ where maturity: u64, range_proof_type: RangeProofType, minimum_value_promise: MicroMinotari, - ) -> Result<(Transaction, MicroMinotari, MicroMinotari, PublicKey), OutputManagerError> { + ) -> Result< + ( + Transaction, + MicroMinotari, + MicroMinotari, + PublicKey, + PublicKey, + PublicKey, + ), + OutputManagerError, + > { // 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]) + .fetch_unspent_outputs_from_node(vec![output_hash]) .await? .pop() .ok_or_else(|| OutputManagerError::ServiceError(format!("Output not found (TxId: {})", tx_id)))?; + if output.commitment != expected_commitment { + return Err(OutputManagerError::ServiceError(format!( + "Output commitment does not match expected commitment (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() { @@ -1217,6 +1239,21 @@ where { if output.verify_mask(&self.resources.factories.range_proof, &spending_key, amount.as_u64())? { let mut script_signatures = Vec::new(); + // lets add our own signature to the list + let script_challange: [u8; 32] = + DomainSeparatedConsensusHasher::>::new("com_hash") + .chain(output.commitment()) + .finalize() + .into(); + let self_signature = self + .resources + .key_manager + .sign_script_message(&self.resources.wallet_identity.wallet_node_key_id, &script_challange) + .await?; + script_signatures.push(StackItem::Signature(CheckSigSchnorrSignature::new( + self_signature.get_public_nonce().clone(), + self_signature.get_signature().clone(), + ))); for signature in &script_input_shares { script_signatures.push(StackItem::Signature(CheckSigSchnorrSignature::new( signature.get_public_nonce().clone(), @@ -1357,9 +1394,10 @@ where .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 + let aggregated_sender_offset_public_key_shares = sender_offset_public_key_shares .iter() - .fold(sender_offset_public_key_self, |acc, x| acc + x); + .fold(PublicKey::default(), |acc, x| acc + x); + let sender_offset_public_key = &aggregated_sender_offset_public_key_shares + sender_offset_public_key_self; let sender_message = TransactionSenderMessage::new_single_round_message( stp.get_single_round_message(&self.resources.key_manager) @@ -1367,6 +1405,9 @@ where .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); // Create the output with a partially signed metadata signature let output = WalletOutputBuilder::new(amount, spending_key_id) .with_features( @@ -1391,14 +1432,16 @@ where .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, + &aggregated_sender_offset_public_key_shares, + &aggregated_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))?; + let total_metadata_ephemeral_public_key = + aggregated_metadata_ephemeral_public_key_shares + output.metadata_signature.ephemeral_pubkey(); // Finalize the partial transaction - it will not be valid at this stage as the metadata and script // signatures are not yet complete. @@ -1416,24 +1459,39 @@ where .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); + let aggregated_script_signature_public_nonces = script_signature_public_nonces + .iter() + .fold(PublicKey::default(), |acc, x| acc + x); + let aggregated_script_public_key_shares = script_public_key_shares + .iter() + .fold(PublicKey::default(), |acc, x| acc + x); // 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_public_nonces, - &script_public_key_shares, + &aggregated_script_signature_public_nonces, + &aggregated_script_public_key_shares, &self.resources.key_manager, ) .await?; + let total_script_nonce = + aggregated_script_signature_public_nonces + updated_input.script_signature.ephemeral_pubkey(); let mut tx = stp.get_transaction()?.clone(); let mut tx_body = tx.body; - tx_body.update_script_signature(updated_input.commitment()?, &updated_input.script_signature.clone())?; + 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)) + Ok(( + tx, + amount, + fee, + total_script_public_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) } async fn create_pay_to_self_transaction( @@ -2375,7 +2433,7 @@ where Ok((tx_id, stp.into_transaction()?, accumulated_amount + fee)) } - async fn fetch_outputs_from_node( + async fn fetch_unspent_outputs_from_node( &mut self, hashes: Vec, ) -> Result, OutputManagerError> { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 9292551862..b865c4feae 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -52,6 +52,7 @@ use tari_core::{ }, }, }; +use tari_crypto::ristretto::pedersen::PedersenCommitment; use tari_service_framework::reply_channel::SenderService; use tari_utilities::hex::Hex; use tokio::sync::broadcast; @@ -112,6 +113,7 @@ pub enum TransactionServiceRequest { EncumberAggregateUtxo { fee_per_gram: MicroMinotari, output_hash: String, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -229,6 +231,7 @@ impl fmt::Display for TransactionServiceRequest { Self::EncumberAggregateUtxo { fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -238,12 +241,13 @@ impl fmt::Display for TransactionServiceRequest { recipient_address, .. } => f.write_str(&format!( - "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 \ - = {}", + "Creating encumber n-of-m utxo with: fee_per_gram = {}, output_hash = {}, commitment = {}, \ + 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 = {}", fee_per_gram, output_hash, + expected_commitment.to_hex(), script_input_shares .iter() .map(|v| format!( @@ -358,7 +362,7 @@ impl fmt::Display for TransactionServiceRequest { pub enum TransactionServiceResponse { TransactionSent(TxId), TransactionSentWithOutputHash(TxId, FixedHash), - EncumberAggregateUtxo(TxId, Box, Box), + EncumberAggregateUtxo(TxId, Box, Box, Box, Box), TransactionImported(TxId), BurntTransactionSent { tx_id: TxId, @@ -731,6 +735,7 @@ impl TransactionServiceHandle { &mut self, fee_per_gram: MicroMinotari, output_hash: String, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -738,12 +743,13 @@ impl TransactionServiceHandle { metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, - ) -> Result<(TxId, Transaction, PublicKey), TransactionServiceError> { + ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), TransactionServiceError> { match self .handle .call(TransactionServiceRequest::EncumberAggregateUtxo { fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -754,9 +760,19 @@ impl TransactionServiceHandle { }) .await?? { - TransactionServiceResponse::EncumberAggregateUtxo(tx_id, transaction, total_script_key) => { - Ok((tx_id, *transaction, *total_script_key)) - }, + TransactionServiceResponse::EncumberAggregateUtxo( + tx_id, + transaction, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + ) => Ok(( + tx_id, + *transaction, + *total_script_key, + *total_metadata_ephemeral_public_key, + *total_script_nonce, + )), _ => Err(TransactionServiceError::UnexpectedApiResponse), } } diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index cd03ce778c..7ee191d95d 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -38,7 +38,7 @@ use tari_common_types::{ burnt_proof::BurntProof, tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, - types::{ComAndPubSignature, FixedHash, PrivateKey, PublicKey, Signature}, + types::{CommitmentFactory, FixedHash, PrivateKey, PublicKey, Signature}, }; use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_comms_dht::outbound::OutboundMessageRequester; @@ -78,11 +78,20 @@ use tari_core::{ }; use tari_crypto::{ keys::{PublicKey as PKtrait, SecretKey}, + ristretto::pedersen::PedersenCommitment, tari_utilities::ByteArray, }; use tari_key_manager::key_manager_service::KeyId; use tari_p2p::domain_message::DomainMessage; -use tari_script::{inputs, push_pubkey_script, script, slice_to_boxed_message, ExecutionStack, TariScript}; +use tari_script::{ + inputs, + push_pubkey_script, + script, + slice_to_boxed_message, + ExecutionStack, + ScriptContext, + TariScript, +}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; use tokio::{ @@ -712,6 +721,7 @@ where TransactionServiceRequest::EncumberAggregateUtxo { fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -723,6 +733,7 @@ where .encumber_aggregate_tx( fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, @@ -732,13 +743,17 @@ where recipient_address, ) .await - .map(|(tx_id, tx, total_script_pubkey)| { - TransactionServiceResponse::EncumberAggregateUtxo( - tx_id, - Box::new(tx), - Box::new(total_script_pubkey), - ) - }), + .map( + |(tx_id, tx, total_script_pubkey, total_metadata_ephemeral_public_key, total_script_nonce)| { + TransactionServiceResponse::EncumberAggregateUtxo( + tx_id, + Box::new(tx), + Box::new(total_script_pubkey), + Box::new(total_metadata_ephemeral_public_key), + Box::new(total_script_nonce), + ) + }, + ), TransactionServiceRequest::FinalizeSentAggregateTransaction { tx_id, total_meta_data_signature, @@ -1366,6 +1381,7 @@ where &mut self, fee_per_gram: MicroMinotari, output_hash: String, + expected_commitment: PedersenCommitment, script_input_shares: Vec, script_public_key_shares: Vec, script_signature_public_nonces: Vec, @@ -1373,7 +1389,7 @@ where metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, - ) -> Result<(TxId, Transaction, PublicKey), TransactionServiceError> { + ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), TransactionServiceError> { let tx_id = TxId::new_random(); match self @@ -1383,37 +1399,51 @@ where tx_id, fee_per_gram, output_hash, + expected_commitment, script_input_shares, script_public_key_shares, script_signature_public_nonces, sender_offset_public_key_shares, metadata_ephemeral_public_key_shares, dh_shared_secret_shares, - recipient_address, + recipient_address.clone(), ) .await { - Ok((transaction, amount, fee, total_script_key)) => { + Ok(( + transaction, + amount, + fee, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => { let completed_tx = CompletedTransaction::new( tx_id, self.resources.wallet_identity.address.clone(), - self.resources.wallet_identity.address.clone(), + recipient_address, amount, fee, transaction.clone(), - TransactionStatus::Completed, + TransactionStatus::Pending, "claimed n-of-m utxo".to_string(), Utc::now().naive_utc(), - TransactionDirection::Inbound, + TransactionDirection::Outbound, None, None, None, ) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; self.db.insert_completed_transaction(tx_id, completed_tx)?; - Ok((tx_id, transaction, total_script_key)) + Ok(( + tx_id, + transaction, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) }, - Err(_) => Err(TransactionServiceError::UnexpectedApiResponse), + Err(e) => Err(e.into()), } } @@ -1430,50 +1460,51 @@ where ) -> Result { let mut transaction = self.db.get_completed_transaction(tx_id)?; + // Add the aggregate signature components transaction.transaction.script_offset = &transaction.transaction.script_offset + &script_offset; transaction.transaction.body.update_metadata_signature( - &transaction.transaction.body.outputs()[0].commitment.clone(), - ComAndPubSignature::new( - transaction.transaction.body.outputs()[0] - .metadata_signature - .ephemeral_commitment() - .clone(), - transaction.transaction.body.outputs()[0] - .metadata_signature - .ephemeral_pubkey() - .clone(), - transaction.transaction.body.outputs()[0] - .metadata_signature - .u_a() - .clone(), - transaction.transaction.body.outputs()[0] - .metadata_signature - .u_x() - .clone(), - transaction.transaction.body.outputs()[0].metadata_signature.u_y() + - total_meta_data_signature.get_signature(), - ), + &(transaction.transaction.body.outputs()[0].commitment.clone()), + &transaction.transaction.body.outputs()[0].metadata_signature + &total_meta_data_signature, )?; transaction.transaction.body.update_script_signature( - &transaction.transaction.body.inputs()[0].commitment()?.clone(), - &ComAndPubSignature::new( - transaction.transaction.body.inputs()[0] - .script_signature - .ephemeral_commitment() - .clone(), - transaction.transaction.body.inputs()[0] - .script_signature - .ephemeral_pubkey() - .clone(), - transaction.transaction.body.inputs()[0].script_signature.u_a().clone(), - transaction.transaction.body.inputs()[0].script_signature.u_x().clone(), - transaction.transaction.body.inputs()[0].script_signature.u_y() + - total_script_data_signature.get_signature(), - ), + &(transaction.transaction.body.inputs()[0].commitment()?.clone()), + &transaction.transaction.body.inputs()[0].script_signature + &total_script_data_signature, )?; + // Validate the aggregate signatures and script offset + let factory = CommitmentFactory::default(); + let mut input_keys = PublicKey::default(); + for input in transaction.transaction.body.inputs() { + let context = ScriptContext::new( + self.last_seen_tip_height.unwrap_or(0), + &[0; 32], + input + .commitment() + .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?, + ); + input_keys = input_keys + + input + .run_and_verify_script(&factory, Some(context)) + .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?; + } + let mut output_keys = PublicKey::default(); + for output in transaction.transaction.body.outputs() { + output + .verify_metadata_signature() + .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?; + output_keys = output_keys + output.sender_offset_public_key.clone(); + } + let lhs = input_keys - output_keys; + if lhs != PublicKey::from_secret_key(&transaction.transaction.script_offset) { + return Err(TransactionServiceError::ServiceError(format!( + "Invalid script offset (TxId: {})", + tx_id + ))); + } + + // Update the wallet database let _res = self .resources .output_manager_service