From 76d374a453df29b4590425ff23c8b0617391088c Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 19 Sep 2025 15:03:01 +0300 Subject: [PATCH] fix: first integration test for privacy-preserving tx --- Cargo.lock | 1 + integration_tests/Cargo.toml | 2 + .../debug/sequencer/sequencer_config.json | 4 +- .../configs/debug/wallet/wallet_config.json | 32 ++-- integration_tests/src/lib.rs | 105 ++++++++++++- .../privacy_preserving_transaction/message.rs | 6 +- .../transaction.rs | 2 +- wallet/src/lib.rs | 27 +++- wallet/src/token_transfers.rs | 141 +++++++++++++++++- 9 files changed, 293 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3f2a74..8ffec5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2201,6 +2201,7 @@ dependencies = [ "hex", "log", "nssa", + "nssa-core", "sequencer_core", "sequencer_runner", "tempfile", diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index a01935f..5604a04 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -14,6 +14,8 @@ tokio.workspace = true hex.workspace = true tempfile.workspace = true +nssa-core = { path = "../nssa/core", features = ["host"] } + [dependencies.clap] features = ["derive", "env"] workspace = true diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/debug/sequencer/sequencer_config.json index 5ff989f..984a7c2 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/debug/sequencer/sequencer_config.json @@ -17,8 +17,8 @@ } ], "initial_commitments": [ - [35, 0, 179, 15, 181, 120, 158, 46, 231, 74, 28, 4, 110, 236, 183, 84, 149, 28, 183, 15, 187, 169, 209, 117, 163, 111, 196, 223, 142, 121, 183, 113], - [108, 161, 161, 147, 255, 252, 193, 27, 3, 122, 147, 112, 219, 15, 106, 73, 151, 176, 204, 223, 255, 183, 20, 8, 86, 134, 96, 59, 208, 251, 116, 101] + [180, 94, 202, 138, 152, 13, 198, 85, 53, 34, 255, 86, 236, 7, 221, 33, 122, 36, 153, 139, 78, 195, 176, 14, 75, 126, 237, 137, 97, 120, 122, 112], + [24, 182, 41, 27, 99, 75, 199, 242, 219, 65, 169, 113, 177, 133, 128, 230, 217, 250, 159, 47, 36, 57, 27, 163, 42, 99, 181, 16, 224, 135, 53, 24] ], "signing_key": [37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37] diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/debug/wallet/wallet_config.json index f30c9a5..fb1b7e5 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/debug/wallet/wallet_config.json @@ -90,14 +90,14 @@ "address": "6ffe0893c4b2c956fdb769b11fe4e3b2dd36ac4bd0ad90c810844051747c8c04", "account": { "program_owner": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 + 1793544791, + 852173979, + 3315478100, + 4158236927, + 146723505, + 3793635251, + 999304864, + 2535706995 ], "balance": 10000, "data": [], @@ -319,14 +319,14 @@ "address": "4ee9de60e33da96fd72929f1485fb365bcc9c1634dd44e4ba55b1ab96692674b", "account": { "program_owner": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 + 1793544791, + 852173979, + 3315478100, + 4158236927, + 146723505, + 3793635251, + 999304864, + 2535706995 ], "balance": 20000, "data": [], diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index e73b5a1..3e0323b 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -11,8 +11,8 @@ use sequencer_runner::startup_sequencer; use tempfile::TempDir; use tokio::task::JoinHandle; use wallet::{ - Command, - helperfunctions::{fetch_config, fetch_persistent_accounts}, + Command, WalletCore, + helperfunctions::{fetch_config, fetch_persistent_accounts, produce_account_addr_from_hex}, }; #[derive(Parser, Debug)] @@ -27,6 +27,11 @@ struct Args { pub const ACC_SENDER: &str = "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"; pub const ACC_RECEIVER: &str = "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766"; +pub const ACC_SENDER_PRIVATE: &str = + "6ffe0893c4b2c956fdb769b11fe4e3b2dd36ac4bd0ad90c810844051747c8c04"; +pub const ACC_RECEIVER_PRIVATE: &str = + "4ee9de60e33da96fd72929f1485fb365bcc9c1634dd44e4ba55b1ab96692674b"; + pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; #[allow(clippy::type_complexity)] @@ -303,6 +308,96 @@ pub async fn test_get_account_wallet_command() { assert_eq!(account.nonce, 0); } +pub async fn test_success_private_transfer_to_another_owned_account() { + let command = Command::SendNativeTokenTransferPrivate { + from: ACC_SENDER_PRIVATE.to_string(), + to: ACC_RECEIVER_PRIVATE.to_string(), + amount: 100, + }; + + let from = produce_account_addr_from_hex(ACC_SENDER_PRIVATE.to_string()).unwrap(); + let to = produce_account_addr_from_hex(ACC_RECEIVER_PRIVATE.to_string()).unwrap(); + + let wallet_config = fetch_config().unwrap(); + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + let mut wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let old_commitment1 = { + let from_acc = wallet_storage + .storage + .user_data + .get_private_account_mut(&from) + .unwrap(); + + nssa_core::Commitment::new(&from_acc.0.nullifer_public_key, &from_acc.1) + }; + + let old_commitment2 = { + let to_acc = wallet_storage + .storage + .user_data + .get_private_account_mut(&to) + .unwrap(); + + nssa_core::Commitment::new(&to_acc.0.nullifer_public_key, &to_acc.1) + }; + + println!("Old commitment {old_commitment1:?}"); + println!("Old commitment {old_commitment2:?}"); + + wallet::execute_subcommand(command).await.unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = { + let from_acc = wallet_storage + .storage + .user_data + .get_private_account_mut(&from) + .unwrap(); + + from_acc.1.balance -= 100; + from_acc.1.nonce += 1; + + nssa_core::Commitment::new(&from_acc.0.nullifer_public_key, &from_acc.1) + }; + + let new_commitment2 = { + let to_acc = wallet_storage + .storage + .user_data + .get_private_account_mut(&to) + .unwrap(); + + to_acc.1.balance += 100; + to_acc.1.nonce += 1; + + nssa_core::Commitment::new(&to_acc.0.nullifer_public_key, &to_acc.1) + }; + + println!("New commitment {new_commitment1:?}"); + println!("New commitment {new_commitment2:?}"); + + let proof1 = seq_client + .get_proof_for_commitment(new_commitment1) + .await + .unwrap() + .unwrap(); + let proof2 = seq_client + .get_proof_for_commitment(new_commitment2) + .await + .unwrap() + .unwrap(); + + println!("New proof is {proof1:#?}"); + println!("New proof is {proof2:#?}"); + + info!("Success!"); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -341,6 +436,12 @@ pub async fn main_tests_runner() -> Result<()> { "test_success_two_transactions" => { test_cleanup_wrap!(home_dir, test_success_two_transactions); } + "test_success_private_transfer_to_another_owned_account" => { + test_cleanup_wrap!( + home_dir, + test_success_private_transfer_to_another_owned_account + ); + } "all" => { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); test_cleanup_wrap!(home_dir, test_success); diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index a769c72..05a6edd 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -11,7 +11,7 @@ pub type ViewTag = u8; #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptedAccountData { - pub(crate) ciphertext: Ciphertext, + pub ciphertext: Ciphertext, pub(crate) epk: EphemeralPublicKey, pub(crate) view_tag: ViewTag, } @@ -47,8 +47,8 @@ pub struct Message { pub(crate) public_addresses: Vec
, pub(crate) nonces: Vec, pub(crate) public_post_states: Vec, - pub(crate) encrypted_private_post_states: Vec, - pub(crate) new_commitments: Vec, + pub encrypted_private_post_states: Vec, + pub new_commitments: Vec, pub(crate) new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 4ecac7f..43b182d 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -15,7 +15,7 @@ use super::witness_set::WitnessSet; #[derive(Debug, Clone, PartialEq, Eq)] pub struct PrivacyPreservingTransaction { - message: Message, + pub message: Message, witness_set: WitnessSet, } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index dc7559e..46c26d5 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -207,7 +207,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let from = produce_account_addr_from_hex(from)?; let to = produce_account_addr_from_hex(to)?; - let res = wallet_core + let (res, secret) = wallet_core .send_private_native_token_transfer(from, to, amount) .await?; @@ -215,7 +215,30 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?; - println!("Transaction data is {transfer_tx:?}"); + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let from_ebc = tx.message.encrypted_private_post_states[0].clone(); + let from_comm = tx.message.new_commitments[0].clone(); + + let to_ebc = tx.message.encrypted_private_post_states[1].clone(); + let to_comm = tx.message.new_commitments[1].clone(); + + let res_acc = nssa_core::EncryptionScheme::decrypt( + &from_ebc.ciphertext, + &secret, + &from_comm, + 0, + ) + .unwrap(); + + let res_acc_to = + nssa_core::EncryptionScheme::decrypt(&to_ebc.ciphertext, &secret, &to_comm, 1) + .unwrap(); + + println!("RES acc {res_acc:#?}"); + println!("RES acc to {res_acc_to:#?}"); + + println!("Transaction data is {:?}", tx.message); + } } Command::RegisterAccountPublic {} => { let addr = wallet_core.create_new_account_public(); diff --git a/wallet/src/token_transfers.rs b/wallet/src/token_transfers.rs index 1fb2e4d..28ed600 100644 --- a/wallet/src/token_transfers.rs +++ b/wallet/src/token_transfers.rs @@ -321,6 +321,122 @@ impl WalletCore { from: Address, to: Address, balance_to_move: u128, + ) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> { + 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); + }; + + let to_npk = to_keys.nullifer_public_key.clone(); + let to_ipk = to_keys.incoming_viewing_public_key.clone(); + + if from_acc.balance >= balance_to_move { + let program = nssa::program::Program::authenticated_transfer_program(); + let sender_commitment = + nssa_core::Commitment::new(&from_keys.nullifer_public_key, from_acc); + let receiver_commitment = + nssa_core::Commitment::new(&to_keys.nullifer_public_key, to_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: true, + }; + + let eph_holder = EphemeralKeyHolder::new( + to_npk.clone(), + 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_ipk.clone()); + + 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, 1], + &[from_acc.nonce + 1, to_acc.nonce + 1], + &[ + (from_keys.nullifer_public_key.clone(), shared_secret.clone()), + (to_npk.clone(), shared_secret.clone()), + ], + &[ + ( + from_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + ), + ( + to_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(receiver_commitment) + .await + .unwrap() + .unwrap(), + ), + ], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_keys.nullifer_public_key.clone(), + from_keys.incoming_viewing_public_key.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + shared_secret, + )) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + } + + pub async fn send_shiedled_native_token_transfer( + &self, + from: Address, + to: Address, + balance_to_move: u128, ) -> Result { let to_data = self.storage.user_data.get_private_account(&to); @@ -328,7 +444,7 @@ impl WalletCore { return Err(ExecutionFailureKind::KeyNotFoundError); }; - self.send_private_native_token_transfer_maybe_outer_account( + self.send_shielded_native_token_transfer_maybe_outer_account( from, to_keys.nullifer_public_key.clone(), to_keys.incoming_viewing_public_key.clone(), @@ -337,4 +453,27 @@ impl WalletCore { ) .await } + + pub async fn send_deshielded_native_token_transfer( + &self, + from: Address, + to: Address, + balance_to_move: u128, + ) -> Result { + let to_data = self.storage.user_data.get_private_account(&to); + + let Some((to_keys, to_acc)) = to_data else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + self.send_deshielded_native_token_transfer_maybe_outer_account( + from, + to, + to_keys.nullifer_public_key.clone(), + to_keys.incoming_viewing_public_key.clone(), + balance_to_move, + Some(to_acc.clone()), + ) + .await + } }