From 124f086db492be6e0c53c662f7f986f035f37bae Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Wed, 8 Feb 2023 14:02:47 +0200 Subject: [PATCH] Consolidate stealth payments Consolidated stealth payment code to enable singular definition in the code base and the ability to make external library calls for the related functions. --- base_layer/wallet/src/lib.rs | 62 ++++++++++++++- .../src/output_manager_service/service.rs | 43 +++-------- .../wallet/src/transaction_service/service.rs | 76 +++++-------------- base_layer/wallet/src/wallet.rs | 4 +- .../transaction_service_tests/service.rs | 4 +- infrastructure/tari_script/src/lib.rs | 14 ++++ 6 files changed, 109 insertions(+), 94 deletions(-) diff --git a/base_layer/wallet/src/lib.rs b/base_layer/wallet/src/lib.rs index 8463fb3b49..3d2610ef08 100644 --- a/base_layer/wallet/src/lib.rs +++ b/base_layer/wallet/src/lib.rs @@ -19,11 +19,18 @@ pub mod storage; pub mod test_utils; pub mod transaction_service; pub mod types; + +pub use types::WalletHasher; // For use externally to the code base pub mod util; pub mod wallet; pub use operation_id::OperationId; -use tari_crypto::{hash::blake2::Blake256, hash_domain, hashing::DomainSeparatedHasher}; +use tari_crypto::{ + hash::blake2::Blake256, + hash_domain, + hashing::{DomainSeparatedHash, DomainSeparatedHasher}, + keys::PublicKey as PKtrait, +}; #[macro_use] extern crate diesel; @@ -36,6 +43,9 @@ pub mod schema; pub mod utxo_scanner_service; pub use config::{TransactionStage, WalletConfig}; +use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_comms::types::CommsDHKE; +use tari_utilities::{ByteArray, ByteArrayError}; pub use wallet::Wallet; use crate::{ @@ -74,3 +84,53 @@ hash_domain!( 1 ); type WalletOutputSpendingKeysDomainHasher = DomainSeparatedHasher; + +/// Generate an output rewind key from a Diffie-Hellman shared secret +pub fn shared_secret_to_output_rewind_key(shared_secret: &CommsDHKE) -> Result { + PrivateKey::from_bytes( + WalletOutputRewindKeysDomainHasher::new() + .chain(shared_secret.as_bytes()) + .finalize() + .as_ref(), + ) +} + +/// Generate an output encryption key from a Diffie-Hellman shared secret +pub fn shared_secret_to_output_encryption_key(shared_secret: &CommsDHKE) -> Result { + PrivateKey::from_bytes( + WalletOutputEncryptionKeysDomainHasher::new() + .chain(shared_secret.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_bytes( + WalletOutputSpendingKeysDomainHasher::new() + .chain(shared_secret.as_bytes()) + .finalize() + .as_ref(), + ) +} + +/// Stealth address domain separated hasher using Diffie-Hellman shared secret +pub fn diffie_hellman_stealth_address_wallet_domain_hasher( + private_key: &PrivateKey, + public_key: &PublicKey, +) -> DomainSeparatedHash { + WalletHasher::new_with_label("stealth_address") + .chain(CommsDHKE::new(private_key, public_key).as_bytes()) + .finalize() +} + +/// Stealth payment script spending key +pub fn stealth_address_script_spending_key( + dh_domain_hasher: &DomainSeparatedHash, + destination_public_key: &PublicKey, +) -> PublicKey { + PublicKey::from_secret_key( + &PrivateKey::from_bytes(dh_domain_hasher.as_ref()).expect("'DomainSeparatedHash' has correct size"), + ) + destination_public_key +} diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index d49caca75d..27c69e0a5e 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -68,12 +68,13 @@ use tari_crypto::{ use tari_script::{inputs, script, Opcode, TariScript}; use tari_service_framework::reply_channel; use tari_shutdown::ShutdownSignal; -use tari_utilities::{hex::Hex, ByteArray, ByteArrayError}; +use tari_utilities::{hex::Hex, ByteArray}; use tokio::sync::Mutex; use crate::{ base_node_service::handle::{BaseNodeEvent, BaseNodeServiceHandle}, connectivity_service::WalletConnectivityInterface, + diffie_hellman_stealth_address_wallet_domain_hasher, key_manager_service::KeyManagerInterface, output_manager_service::{ config::OutputManagerServiceConfig, @@ -97,9 +98,9 @@ use crate::{ }, tasks::TxoValidationTask, }, - types::WalletHasher, - WalletOutputEncryptionKeysDomainHasher, - WalletOutputRewindKeysDomainHasher, + shared_secret_to_output_encryption_key, + shared_secret_to_output_rewind_key, + stealth_address_script_spending_key, }; const LOG_TARGET: &str = "wallet::output_manager_service"; @@ -2594,16 +2595,14 @@ where // NOTE: [RFC 203 on Stealth Addresses](https://rfc.tari.com/RFC-0203_StealthAddresses.html) [Opcode::PushPubKey(nonce), Opcode::Drop, Opcode::PushPubKey(scanned_pk)] => { // Compute the stealth address offset - let stealth_address_offset = PrivateKey::from_bytes( - WalletHasher::new_with_label("stealth_address") - .chain(CommsDHKE::new(&wallet_sk, nonce.as_ref()).as_bytes()) - .finalize() - .as_ref(), - ) - .unwrap(); + let stealth_address_hasher = + diffie_hellman_stealth_address_wallet_domain_hasher(&wallet_sk, nonce.as_ref()); + let stealth_address_offset = PrivateKey::from_bytes(stealth_address_hasher.as_ref()) + .expect("'DomainSeparatedHash' has correct size"); // matching spending (public) keys - if &(PublicKey::from_secret_key(&stealth_address_offset) + wallet_pk) != scanned_pk.as_ref() { + let script_spending_key = stealth_address_script_spending_key(&stealth_address_hasher, wallet_pk); + if &script_spending_key != scanned_pk.as_ref() { continue; } @@ -2751,26 +2750,6 @@ impl fmt::Display for Balance { } } -/// Generate an output rewind key from a Diffie-Hellman shared secret -fn shared_secret_to_output_rewind_key(shared_secret: &CommsDHKE) -> Result { - PrivateKey::from_bytes( - WalletOutputRewindKeysDomainHasher::new() - .chain(shared_secret.as_bytes()) - .finalize() - .as_ref(), - ) -} - -/// Generate an output encryption key from a Diffie-Hellman shared secret -fn shared_secret_to_output_encryption_key(shared_secret: &CommsDHKE) -> Result { - PrivateKey::from_bytes( - WalletOutputEncryptionKeysDomainHasher::new() - .chain(shared_secret.as_bytes()) - .finalize() - .as_ref(), - ) -} - #[derive(Debug, Clone)] struct UtxoSelection { utxos: Vec, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 1bbf85cb26..4336316f8f 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -69,10 +69,10 @@ use tari_core::{ use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::{PublicKey as PKtrait, SecretKey}, - tari_utilities::{ByteArray, ByteArrayError}, + tari_utilities::ByteArray, }; use tari_p2p::domain_message::DomainMessage; -use tari_script::{inputs, script, TariScript}; +use tari_script::{inputs, one_sided_payment_script, script, stealth_payment_script, TariScript}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; use tokio::{ @@ -83,11 +83,16 @@ use tokio::{ use crate::{ base_node_service::handle::{BaseNodeEvent, BaseNodeServiceHandle}, connectivity_service::WalletConnectivityInterface, + diffie_hellman_stealth_address_wallet_domain_hasher, output_manager_service::{ handle::{OutputManagerEvent, OutputManagerHandle}, storage::models::SpendingPriority, UtxoSelectionCriteria, }, + shared_secret_to_output_encryption_key, + shared_secret_to_output_rewind_key, + shared_secret_to_output_spending_key, + stealth_address_script_spending_key, storage::database::{WalletBackend, WalletDatabase}, transaction_service::{ config::TransactionServiceConfig, @@ -117,13 +122,9 @@ use crate::{ }, utc::utc_duration_since, }, - types::WalletHasher, util::{wallet_identity::WalletIdentity, watch::Watch}, utxo_scanner_service::RECOVERY_KEY, OperationId, - WalletOutputEncryptionKeysDomainHasher, - WalletOutputRewindKeysDomainHasher, - WalletOutputSpendingKeysDomainHasher, }; const LOG_TARGET: &str = "wallet::transaction_service::service"; @@ -1351,7 +1352,7 @@ where fee_per_gram, message, transaction_broadcast_join_handles, - script!(PushPubKey(Box::new(dest_pubkey))), + one_sided_payment_script(&dest_pubkey), ) .await } @@ -1526,12 +1527,9 @@ where let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); let dest_pubkey = destination.public_key().clone(); - let c = WalletHasher::new_with_label("stealth_address") - .chain((dest_pubkey.clone() * nonce_private_key).as_bytes()) - .finalize(); + let c = diffie_hellman_stealth_address_wallet_domain_hasher(&nonce_private_key, &dest_pubkey); - let script_spending_key = - PublicKey::from_secret_key(&PrivateKey::from_bytes(c.as_ref()).unwrap()) + dest_pubkey; + let script_spending_key = stealth_address_script_spending_key(&c, &dest_pubkey); self.send_one_sided_or_stealth( destination, @@ -1541,7 +1539,7 @@ where fee_per_gram, message, transaction_broadcast_join_handles, - script!(PushPubKey(Box::new(nonce_public_key)) Drop PushPubKey(Box::new(script_spending_key))), + stealth_payment_script(&nonce_public_key, &script_spending_key), ) .await } @@ -2704,36 +2702,6 @@ pub struct PendingCoinbaseSpendingKey { pub spending_key: PrivateKey, } -/// Generate an output rewind key from a Diffie-Hellman shared secret -fn shared_secret_to_output_rewind_key(shared_secret: &CommsDHKE) -> Result { - PrivateKey::from_bytes( - WalletOutputRewindKeysDomainHasher::new() - .chain(shared_secret.as_bytes()) - .finalize() - .as_ref(), - ) -} - -/// Generate an output encryption key from a Diffie-Hellman shared secret -fn shared_secret_to_output_encryption_key(shared_secret: &CommsDHKE) -> Result { - PrivateKey::from_bytes( - WalletOutputEncryptionKeysDomainHasher::new() - .chain(shared_secret.as_bytes()) - .finalize() - .as_ref(), - ) -} - -/// Generate an output spending key from a Diffie-Hellman shared secret -fn shared_secret_to_output_spending_key(shared_secret: &CommsDHKE) -> Result { - PrivateKey::from_bytes( - WalletOutputSpendingKeysDomainHasher::new() - .chain(shared_secret.as_bytes()) - .finalize() - .as_ref(), - ) -} - /// Contains the generated TxId and TransactionStatus transaction send result #[derive(Debug)] pub struct TransactionSendResult { @@ -2744,31 +2712,28 @@ pub struct TransactionSendResult { #[cfg(test)] mod tests { use tari_crypto::ristretto::RistrettoSecretKey; - use tari_script::Opcode; - use WalletHasher; + use tari_script::{stealth_payment_script, Opcode}; use super::*; + use crate::stealth_address_script_spending_key; #[test] fn test_stealth_addresses() { // recipient's keys let (a, big_a) = PublicKey::random_keypair(&mut OsRng); - let (b, big_b) = PublicKey::random_keypair(&mut OsRng); + let (_b, big_b) = PublicKey::random_keypair(&mut OsRng); // Sender generates a random nonce key-pair: R=r⋅G let (r, big_r) = PublicKey::random_keypair(&mut OsRng); // Sender calculates a ECDH shared secret: c=H(r⋅a⋅G)=H(a⋅R)=H(r⋅A), // where H(⋅) is a cryptographic hash function - let c = WalletHasher::new_with_label("stealth_address") - .chain(CommsDHKE::new(&r, &big_a).as_bytes()) - .finalize(); + let c = diffie_hellman_stealth_address_wallet_domain_hasher(&r, &big_a); // using spending key `Ks=c⋅G+B` as the last public key in the one-sided payment script - let sender_spending_key = - PublicKey::from_secret_key(&RistrettoSecretKey::from_bytes(c.as_ref()).unwrap()) + big_b.clone(); + let sender_spending_key = stealth_address_script_spending_key(&c, &big_b); - let script = script!(PushPubKey(Box::new(big_r)) Drop PushPubKey(Box::new(sender_spending_key.clone()))); + let script = stealth_payment_script(&big_r, &sender_spending_key); // ---------------------------------------------------------------------------- // imitating the receiving end, scanning and extraction @@ -2777,13 +2742,10 @@ mod tests { if let [Opcode::PushPubKey(big_r), Opcode::Drop, Opcode::PushPubKey(provided_spending_key)] = script.as_slice() { // calculating Ks with the provided R nonce from the script - let c = WalletHasher::new_with_label("stealth_address") - .chain(CommsDHKE::new(&a, big_r).as_bytes()) - .finalize(); + let c = diffie_hellman_stealth_address_wallet_domain_hasher(&a, big_r); // computing a spending key `Ks=(c+b)G` for comparison - let receiver_spending_key = - PublicKey::from_secret_key(&(RistrettoSecretKey::from_bytes(c.as_ref()).unwrap() + b)); + let receiver_spending_key = stealth_address_script_spending_key(&c, &big_b); // computing a scanning key `Ks=cG+B` for comparison let scanning_key = PublicKey::from_secret_key(&RistrettoSecretKey::from_bytes(c.as_ref()).unwrap()) + big_b; diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 481027cdfd..ddfc66ee33 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -67,7 +67,7 @@ use tari_p2p::{ services::liveness::{config::LivenessConfig, LivenessInitializer}, PeerSeedsConfig, }; -use tari_script::{script, ExecutionStack, TariScript}; +use tari_script::{one_sided_payment_script, ExecutionStack, TariScript}; use tari_service_framework::StackBuilder; use tari_shutdown::ShutdownSignal; use tari_utilities::ByteArray; @@ -701,7 +701,7 @@ async fn persist_one_sided_payment_script_for_node_identity( output_manager_service: &mut OutputManagerHandle, node_identity: Arc, ) -> Result<(), WalletError> { - let script = script!(PushPubKey(Box::new(node_identity.public_key().clone()))); + let script = one_sided_payment_script(node_identity.public_key()); let known_script = KnownOneSidedPaymentScript { script_hash: script .as_hash::() diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 3d6c95225a..5586fcdf5c 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -98,7 +98,7 @@ use tari_crypto::{ }; use tari_key_manager::cipher_seed::CipherSeed; use tari_p2p::{comms_connector::pubsub_connector, domain_message::DomainMessage, Network}; -use tari_script::{inputs, script, ExecutionStack, TariScript}; +use tari_script::{inputs, one_sided_payment_script, script, ExecutionStack, TariScript}; use tari_service_framework::{reply_channel, RegisterHandle, StackBuilder}; use tari_shutdown::{Shutdown, ShutdownSignal}; use tari_test_utils::random; @@ -861,7 +861,7 @@ async fn recover_one_sided_transaction() { shutdown.to_signal(), ) .await; - let script = script!(PushPubKey(Box::new(bob_node_identity.public_key().clone()))); + let script = one_sided_payment_script(bob_node_identity.public_key()); let known_script = KnownOneSidedPaymentScript { script_hash: script.as_hash::().unwrap().to_vec(), private_key: bob_node_identity.secret_key().clone(), diff --git a/infrastructure/tari_script/src/lib.rs b/infrastructure/tari_script/src/lib.rs index 4f1a26e33e..41e2ddf8c7 100644 --- a/infrastructure/tari_script/src/lib.rs +++ b/infrastructure/tari_script/src/lib.rs @@ -27,9 +27,23 @@ pub use op_codes::{slice_to_boxed_hash, slice_to_hash, HashValue, Message, Opcod pub use script::TariScript; pub use script_context::ScriptContext; pub use stack::{ExecutionStack, StackItem}; +use tari_crypto::ristretto::RistrettoPublicKey; // As hex: c5a1ea6d3e0a6a0d650c99489bcd563e37a06221fd04b8f3a842a982b2813907 pub const DEFAULT_SCRIPT_HASH: HashValue = [ 0xc5, 0xa1, 0xea, 0x6d, 0x3e, 0x0a, 0x6a, 0x0d, 0x65, 0x0c, 0x99, 0x48, 0x9b, 0xcd, 0x56, 0x3e, 0x37, 0xa0, 0x62, 0x21, 0xfd, 0x04, 0xb8, 0xf3, 0xa8, 0x42, 0xa9, 0x82, 0xb2, 0x81, 0x39, 0x07, ]; + +/// The standard payment script to be used for one-sided payment to stealth addresses +pub fn stealth_payment_script( + nonce_public_key: &RistrettoPublicKey, + script_spending_key: &RistrettoPublicKey, +) -> TariScript { + script!(PushPubKey(Box::new(nonce_public_key.clone())) Drop PushPubKey(Box::new(script_spending_key.clone()))) +} + +/// The standard payment script to be used for one-sided payment to public addresses +pub fn one_sided_payment_script(destination_public_key: &RistrettoPublicKey) -> TariScript { + script!(PushPubKey(Box::new(destination_public_key.clone()))) +}