Skip to content

Commit

Permalink
feat: consolidate stealth payment code (#5171)
Browse files Browse the repository at this point in the history
Description
---
Consolidated stealth payment code to enable singular definition in the code base and the ability to make external library calls for the related functions. As a result, some duplicate code were removed. 
(_**Note:** No functional changes were made to the code._)

Motivation and Context
---
See above

How Has This Been Tested?
---
Unit tests passed
  • Loading branch information
hansieodendaal committed Feb 10, 2023
1 parent 022172c commit b7747a2
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 116 deletions.
24 changes: 2 additions & 22 deletions base_layer/wallet/src/lib.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Blake256, WalletOutputRewindKeysDomain>;

hash_domain!(
WalletOutputEncryptionKeysDomain,
"com.tari.tari_project.base_layer.wallet.output_encryption_keys",
1
);
type WalletOutputEncryptionKeysDomainHasher = DomainSeparatedHasher<Blake256, WalletOutputEncryptionKeysDomain>;

hash_domain!(
WalletOutputSpendingKeysDomain,
"com.tari.tari_project.base_layer.wallet.output_spending_keys",
1
);
type WalletOutputSpendingKeysDomainHasher = DomainSeparatedHasher<Blake256, WalletOutputSpendingKeysDomain>;
44 changes: 12 additions & 32 deletions base_layer/wallet/src/output_manager_service/service.rs
Expand Up @@ -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::{
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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<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 +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, 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
83 changes: 25 additions & 58 deletions base_layer/wallet/src/transaction_service/service.rs
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 Down Expand Up @@ -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";
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand Down Expand Up @@ -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, 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 +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
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions base_layer/wallet/src/util/mod.rs
Expand Up @@ -22,5 +22,6 @@

pub mod diesel_ext;
pub mod encryption;
pub mod one_sided;
pub mod wallet_identity;
pub mod watch;
107 changes: 107 additions & 0 deletions 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<Blake256, WalletOutputRewindKeysDomain>;
type WalletOutputEncryptionKeysDomainHasher = DomainSeparatedHasher<Blake256, WalletOutputEncryptionKeysDomain>;
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_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
}

0 comments on commit b7747a2

Please sign in to comment.