Skip to content

Commit

Permalink
Consolidate stealth payments
Browse files Browse the repository at this point in the history
Consolidated stealth payment code to enable singular definition in the code base and
the ability to make external library calls for the related functions.
  • Loading branch information
hansieodendaal committed Feb 8, 2023
1 parent eef6c7f commit 124f086
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 94 deletions.
62 changes: 61 additions & 1 deletion base_layer/wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::{
Expand Down Expand Up @@ -74,3 +84,53 @@ hash_domain!(
1
);
type WalletOutputSpendingKeysDomainHasher = DomainSeparatedHasher<Blake256, WalletOutputSpendingKeysDomain>;

/// Generate an output rewind key from a Diffie-Hellman shared secret
pub fn shared_secret_to_output_rewind_key(shared_secret: &CommsDHKE) -> Result<PrivateKey, ByteArrayError> {
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, ByteArrayError> {
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, ByteArrayError> {
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<Blake256> {
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<Blake256>,
destination_public_key: &PublicKey,
) -> PublicKey {
PublicKey::from_secret_key(
&PrivateKey::from_bytes(dh_domain_hasher.as_ref()).expect("'DomainSeparatedHash<Blake256>' has correct size"),
) + destination_public_key
}
43 changes: 11 additions & 32 deletions base_layer/wallet/src/output_manager_service/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down Expand Up @@ -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<Blake256>' 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;
}

Expand Down Expand Up @@ -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, ByteArrayError> {
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, ByteArrayError> {
PrivateKey::from_bytes(
WalletOutputEncryptionKeysDomainHasher::new()
.chain(shared_secret.as_bytes())
.finalize()
.as_ref(),
)
}

#[derive(Debug, Clone)]
struct UtxoSelection {
utxos: Vec<DbUnblindedOutput>,
Expand Down
76 changes: 19 additions & 57 deletions base_layer/wallet/src/transaction_service/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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,
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand Down Expand Up @@ -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, ByteArrayError> {
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, ByteArrayError> {
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, ByteArrayError> {
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 {
Expand All @@ -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
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions base_layer/wallet/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -701,7 +701,7 @@ async fn persist_one_sided_payment_script_for_node_identity(
output_manager_service: &mut OutputManagerHandle,
node_identity: Arc<NodeIdentity>,
) -> 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::<Blake256>()
Expand Down
4 changes: 2 additions & 2 deletions base_layer/wallet/tests/transaction_service_tests/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Blake256>().unwrap().to_vec(),
private_key: bob_node_identity.secret_key().clone(),
Expand Down
14 changes: 14 additions & 0 deletions infrastructure/tari_script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())))
}

0 comments on commit 124f086

Please sign in to comment.