diff --git a/base_layer/wallet/src/lib.rs b/base_layer/wallet/src/lib.rs index 8463fb3b49..6be3201d64 100644 --- a/base_layer/wallet/src/lib.rs +++ b/base_layer/wallet/src/lib.rs @@ -19,11 +19,12 @@ 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}; #[macro_use] extern crate diesel; @@ -53,24 +54,3 @@ pub type WalletSqlite = Wallet< ContactsServiceSqliteDatabase, KeyManagerSqliteDatabase, >; - -hash_domain!( - WalletOutputRewindKeysDomain, - "com.tari.tari_project.base_layer.wallet.output_rewind_keys", - 1 -); -type WalletOutputRewindKeysDomainHasher = DomainSeparatedHasher; - -hash_domain!( - WalletOutputEncryptionKeysDomain, - "com.tari.tari_project.base_layer.wallet.output_encryption_keys", - 1 -); -type WalletOutputEncryptionKeysDomainHasher = DomainSeparatedHasher; - -hash_domain!( - WalletOutputSpendingKeysDomain, - "com.tari.tari_project.base_layer.wallet.output_spending_keys", - 1 -); -type WalletOutputSpendingKeysDomainHasher = DomainSeparatedHasher; diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index d49caca75d..2cdfb01c87 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -68,7 +68,7 @@ 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::{ @@ -97,9 +97,12 @@ use crate::{ }, tasks::TxoValidationTask, }, - types::WalletHasher, - WalletOutputEncryptionKeysDomainHasher, - WalletOutputRewindKeysDomainHasher, + util::one_sided::{ + diffie_hellman_stealth_domain_hasher, + 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 +2597,13 @@ 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_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 +2751,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..40975f25c2 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::{ @@ -117,13 +117,19 @@ use crate::{ }, utc::utc_duration_since, }, - types::WalletHasher, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::{ + one_sided::{ + diffie_hellman_stealth_domain_hasher, + shared_secret_to_output_encryption_key, + shared_secret_to_output_rewind_key, + shared_secret_to_output_spending_key, + stealth_address_script_spending_key, + }, + wallet_identity::WalletIdentity, + watch::Watch, + }, utxo_scanner_service::RECOVERY_KEY, OperationId, - WalletOutputEncryptionKeysDomainHasher, - WalletOutputRewindKeysDomainHasher, - WalletOutputSpendingKeysDomainHasher, }; const LOG_TARGET: &str = "wallet::transaction_service::service"; @@ -1351,7 +1357,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 +1532,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_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 +1544,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 +2707,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 +2717,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::util::one_sided::{diffie_hellman_stealth_domain_hasher, 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_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 +2747,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_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/util/mod.rs b/base_layer/wallet/src/util/mod.rs index b4f0ab0d64..042d558360 100644 --- a/base_layer/wallet/src/util/mod.rs +++ b/base_layer/wallet/src/util/mod.rs @@ -22,5 +22,6 @@ pub mod diesel_ext; pub mod encryption; +pub mod one_sided; pub mod wallet_identity; pub mod watch; diff --git a/base_layer/wallet/src/util/one_sided.rs b/base_layer/wallet/src/util/one_sided.rs new file mode 100644 index 0000000000..1dd8d02541 --- /dev/null +++ b/base_layer/wallet/src/util/one_sided.rs @@ -0,0 +1,107 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use core::result::Result; + +use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_comms::types::CommsDHKE; +use tari_crypto::{ + hash::blake2::Blake256, + hash_domain, + hashing::{DomainSeparatedHash, DomainSeparatedHasher}, + keys::PublicKey as PKtrait, +}; +use tari_utilities::{byte_array::ByteArrayError, ByteArray}; + +use crate::WalletHasher; + +hash_domain!( + WalletOutputRewindKeysDomain, + "com.tari.tari_project.base_layer.wallet.output_rewind_keys", + 1 +); + +hash_domain!( + WalletOutputEncryptionKeysDomain, + "com.tari.tari_project.base_layer.wallet.output_encryption_keys", + 1 +); + +hash_domain!( + WalletOutputSpendingKeysDomain, + "com.tari.tari_project.base_layer.wallet.output_spending_keys", + 1 +); + +type WalletOutputRewindKeysDomainHasher = DomainSeparatedHasher; +type WalletOutputEncryptionKeysDomainHasher = DomainSeparatedHasher; +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_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/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()))) +}