From 854d96af72f0af65795df69a14af335763a9e726 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 11 Sep 2025 18:32:46 +0300 Subject: [PATCH] fix; privacy preserving tx gen 1 --- Cargo.lock | 1 + common/src/sequencer_client/mod.rs | 20 +++ integration_tests/src/lib.rs | 8 +- key_protocol/Cargo.toml | 1 + .../src/key_management/secret_holders.rs | 4 +- key_protocol/src/key_protocol_core/mod.rs | 28 ++-- nssa/src/lib.rs | 2 +- wallet/src/chain_storage/mod.rs | 38 ++++-- wallet/src/config.rs | 74 +++++++++- wallet/src/helperfunctions.rs | 27 +++- wallet/src/lib.rs | 126 +++++++++++++++--- 11 files changed, 279 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2491983..24e6b3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2333,6 +2333,7 @@ dependencies = [ "lazy_static", "log", "nssa", + "nssa-core", "rand 0.8.5", "serde", "serde_json", diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index 126de40..bb78e50 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -147,6 +147,26 @@ impl SequencerClient { Ok(resp_deser) } + ///Send transaction to sequencer + pub async fn send_tx_private( + &self, + transaction: nssa::PrivacyPreservingTransaction, + ) -> Result { + let transaction = EncodedTransaction::from(NSSATransaction::PrivacyPreserving(transaction)); + + let tx_req = SendTxRequest { + transaction: transaction.to_bytes(), + }; + + let req = serde_json::to_value(tx_req)?; + + let resp = self.call_method_with_payload("send_tx", req).await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + ///Get genesis id from sequencer pub async fn get_genesis_id(&self) -> Result { let genesis_req = GetGenesisIdRequest {}; diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 9b7b970..ac532ac 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -117,7 +117,7 @@ pub async fn test_success() { } pub async fn test_success_move_to_another_account() { - let command = Command::RegisterAccount {}; + let command = Command::RegisterAccountPublic {}; let wallet_config = fetch_config().unwrap(); @@ -130,10 +130,10 @@ pub async fn test_success_move_to_another_account() { let mut new_persistent_account_addr = String::new(); for per_acc in persistent_accounts { - if (per_acc.address.to_string() != ACC_RECEIVER) - && (per_acc.address.to_string() != ACC_SENDER) + if (per_acc.address().to_string() != ACC_RECEIVER) + && (per_acc.address().to_string() != ACC_SENDER) { - new_persistent_account_addr = per_acc.address.to_string(); + new_persistent_account_addr = per_acc.address().to_string(); } } diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 3ce874d..d453753 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -17,6 +17,7 @@ aes-gcm.workspace = true lazy_static.workspace = true bip39.workspace = true hmac-sha512.workspace = true +nssa-core = { path = "../nssa/core", features = ["host"] } [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 5c2e522..3f252b8 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -23,9 +23,9 @@ pub struct TopSecretKeyHolder { #[derive(Serialize, Deserialize, Debug, Clone)] ///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. pub struct PrivateKeyHolder { - pub(crate) nullifier_secret_key: [u8; 32], + pub nullifier_secret_key: [u8; 32], pub(crate) incoming_viewing_secret_key: Scalar, - pub(crate) outgoing_viewing_secret_key: Scalar, + pub outgoing_viewing_secret_key: Scalar, } impl SeedHolder { diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 58f60ce..24bb26e 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -13,7 +13,7 @@ pub struct NSSAUserData { ///Map for all user public accounts pub pub_account_signing_keys: HashMap, ///Map for all user private accounts - user_private_accounts: HashMap, + pub user_private_accounts: HashMap, } impl NSSAUserData { @@ -30,10 +30,10 @@ impl NSSAUserData { } fn valid_private_key_transaction_pairing_check( - accounts_keys_map: &HashMap, + accounts_keys_map: &HashMap, ) -> bool { let mut check_res = true; - for (addr, key) in accounts_keys_map { + for (addr, (key, _)) in accounts_keys_map { if nssa::Address::new(key.produce_user_address()) != *addr { check_res = false; } @@ -43,7 +43,7 @@ impl NSSAUserData { pub fn new_with_accounts( accounts_keys: HashMap, - accounts_key_chains: HashMap, + accounts_key_chains: HashMap, ) -> Result { if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) { anyhow::bail!( @@ -90,15 +90,27 @@ impl NSSAUserData { let key_chain = KeyChain::new_os_random(); let address = nssa::Address::new(key_chain.produce_user_address()); - self.user_private_accounts.insert(address, key_chain); + self.user_private_accounts + .insert(address, (key_chain, nssa_core::account::Account::default())); address } /// Returns the signing key for public transaction signatures - pub fn get_private_account_key_chain(&self, address: &nssa::Address) -> Option<&KeyChain> { + pub fn get_private_account( + &self, + address: &nssa::Address, + ) -> Option<&(KeyChain, nssa_core::account::Account)> { self.user_private_accounts.get(address) } + + /// Returns the signing key for public transaction signatures + pub fn get_private_account_mut( + &mut self, + address: &nssa::Address, + ) -> Option<&mut (KeyChain, nssa_core::account::Account)> { + self.user_private_accounts.get_mut(address) + } } impl Default for NSSAUserData { @@ -123,9 +135,7 @@ mod tests { assert!(is_private_key_generated); - let is_key_chain_generated = user_data - .get_private_account_key_chain(&addr_private) - .is_some(); + let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some(); assert!(is_key_chain_generated); } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 643fc7a..b9c0a1d 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -2,7 +2,7 @@ pub mod address; pub mod encoding; pub mod error; mod merkle_tree; -mod privacy_preserving_transaction; +pub mod privacy_preserving_transaction; pub mod program; pub mod public_transaction; mod signature; diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index d3b07b3..138057f 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use anyhow::Result; use key_protocol::key_protocol_core::NSSAUserData; -use crate::config::{PersistentAccountData, WalletConfig}; +use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig}; pub struct WalletChainStore { pub user_data: NSSAUserData, @@ -12,23 +12,39 @@ pub struct WalletChainStore { impl WalletChainStore { pub fn new(config: WalletConfig) -> Result { - let accounts_keys: HashMap = config - .initial_accounts - .clone() - .into_iter() - .map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key)) - .collect(); + let mut public_init_acc_map = HashMap::new(); + let mut private_init_acc_map = HashMap::new(); + + for init_acc_data in config.initial_accounts.clone() { + match init_acc_data { + InitialAccountData::Public(data) => { + public_init_acc_map.insert(data.address, data.pub_sign_key); + } + InitialAccountData::Private(data) => { + private_init_acc_map.insert(data.address, (data.key_chain, data.account)); + } + } + } Ok(Self { - user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?, + user_data: NSSAUserData::new_with_accounts(public_init_acc_map, private_init_acc_map)?, wallet_config: config, }) } pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { - self.user_data - .pub_account_signing_keys - .insert(acc_data.address, acc_data.pub_sign_key); + match acc_data { + PersistentAccountData::Public(acc_data) => { + self.user_data + .pub_account_signing_keys + .insert(acc_data.address, acc_data.pub_sign_key); + } + PersistentAccountData::Private(acc_data) => { + self.user_data + .user_private_accounts + .insert(acc_data.address, (acc_data.key_chain, acc_data.account)); + } + } } } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index b6355ce..2231b11 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,19 +1,87 @@ +use key_protocol::key_management::KeyChain; use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InitialAccountData { +pub struct InitialAccountDataPublic { pub address: nssa::Address, - pub account: nssa_core::account::Account, pub pub_sign_key: nssa::PrivateKey, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PersistentAccountData { +pub struct PersistentAccountDataPublic { pub address: nssa::Address, pub pub_sign_key: nssa::PrivateKey, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InitialAccountDataPrivate { + pub address: nssa::Address, + pub account: nssa_core::account::Account, + pub key_chain: KeyChain, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PersistentAccountDataPrivate { + pub address: nssa::Address, + pub account: nssa_core::account::Account, + pub key_chain: KeyChain, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum InitialAccountData { + Public(InitialAccountDataPublic), + Private(InitialAccountDataPrivate), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PersistentAccountData { + Public(PersistentAccountDataPublic), + Private(PersistentAccountDataPrivate), +} + +impl InitialAccountData { + pub fn address(&self) -> nssa::Address { + match &self { + Self::Public(acc) => acc.address, + Self::Private(acc) => acc.address, + } + } +} + +impl PersistentAccountData { + pub fn address(&self) -> nssa::Address { + match &self { + Self::Public(acc) => acc.address, + Self::Private(acc) => acc.address, + } + } +} + +impl From for InitialAccountData { + fn from(value: InitialAccountDataPublic) -> Self { + Self::Public(value) + } +} + +impl From for InitialAccountData { + fn from(value: InitialAccountDataPrivate) -> Self { + Self::Private(value) + } +} + +impl From for PersistentAccountData { + fn from(value: PersistentAccountDataPublic) -> Self { + Self::Public(value) + } +} + +impl From for PersistentAccountData { + fn from(value: PersistentAccountDataPrivate) -> Self { + Self::Private(value) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GasConfig { /// Gas spent per deploying one byte of data diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 45bb5f0..f7a319f 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -6,7 +6,10 @@ use nssa::Address; use crate::{ HOME_DIR_ENV_VAR, - config::{PersistentAccountData, WalletConfig}, + config::{ + PersistentAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic, + WalletConfig, + }, }; ///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. @@ -54,10 +57,24 @@ pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec Address { + pub fn create_new_account_public(&mut self) -> Address { self.storage .user_data .generate_new_public_transaction_private_key() } - pub fn search_for_initial_account(&self, acc_addr: Address) -> Option { - for initial_acc in &self.storage.wallet_config.initial_accounts { - if initial_acc.address == acc_addr { - return Some(initial_acc.account.clone()); - } - } - None + pub fn create_new_account_private(&mut self) -> Address { + self.storage + .user_data + .generate_new_privacy_preserving_transaction_key_chain() } pub async fn send_public_native_token_transfer( @@ -131,6 +126,92 @@ impl WalletCore { } } + pub async fn send_private_native_token_transfer( + &self, + from: Address, + to: Address, + balance_to_move: u128, + ) -> Result { + let from_data = self.storage.user_data.get_private_account(&from); + let to_data = self.storage.user_data.get_private_account(&to); + + let Some((from_keys, from_acc)) = from_data else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((to_keys, to_acc)) = to_data else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + if from_acc.balance >= balance_to_move { + let program = nssa::program::Program::authenticated_transfer_program(); + let sender_commitment = nssa_core::Commitment::new( + &nssa_core::NullifierPublicKey(from_keys.nullifer_public_key), + from_acc, + ); + + let sender_pre = nssa_core::account::AccountWithMetadata { + account: from_acc.clone(), + is_authorized: true, + }; + let recipient_pre = nssa_core::account::AccountWithMetadata { + account: to_acc.clone(), + is_authorized: false, + }; + + let eph_holder = EphemeralKeyHolder::new( + to_keys.nullifer_public_key, + from_keys.private_key_holder.outgoing_viewing_secret_key, + from_acc.nonce.try_into().unwrap(), + ); + + let shared_secret = + eph_holder.calculate_shared_secret_sender(to_keys.incoming_viewing_public_key); + + let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &nssa::program::Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 2], + &[from_acc.nonce + 1, to_acc.nonce + 1], + &[ + ( + nssa_core::NullifierPublicKey(from_keys.nullifer_public_key), + shared_secret, + ), + ( + nssa_core::NullifierPublicKey(to_keys.nullifer_public_key), + shared_secret, + ), + ], + &[( + from_keys.private_key_holder.nullifier_secret_key, + state.get_proof_for_commitment(&sender_commitment).unwrap(), + )], + &program, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + (sender_keys.npk(), sender_keys.ivk(), epk_1), + (recipient_keys.npk(), recipient_keys.ivk(), epk_2), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + } + ///Get account balance pub async fn get_account_balance(&self, acc: Address) -> Result { Ok(self @@ -176,8 +257,10 @@ pub enum Command { #[arg(long)] amount: u128, }, - ///Register new account - RegisterAccount {}, + ///Register new public account + RegisterAccountPublic {}, + ///Register new private account + RegisterAccountPrivate {}, ///Fetch transaction by `hash` FetchTx { #[arg(short, long)] @@ -225,8 +308,8 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { info!("Transaction data is {transfer_tx:?}"); } - Command::RegisterAccount {} => { - let addr = wallet_core.create_new_account(); + Command::RegisterAccountPublic {} => { + let addr = wallet_core.create_new_account_public(); let key = wallet_core .storage @@ -236,6 +319,19 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}"); } + Command::RegisterAccountPrivate {} => { + let addr = wallet_core.create_new_account_private(); + + let (key, account) = wallet_core + .storage + .user_data + .get_private_account(&addr) + .unwrap(); + + info!("Generated new account with addr {addr:#?}"); + info!("With key {key:#?}"); + info!("With account {account:#?}"); + } Command::FetchTx { tx_hash } => { let tx_obj = wallet_core .sequencer_client