From 28fe9e2113b3bdb083d5121c28fd70deab6ed32b Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 15 Oct 2025 15:44:52 +0300 Subject: [PATCH] fix: merge updates --- .../src/cli/native_token_transfer_program.rs | 28 ++++- wallet/src/cli/pinata_program.rs | 2 +- wallet/src/token_transfers/private.rs | 103 ++++++++++++++++-- wallet/src/token_transfers/shielded.rs | 89 ++++++++++++--- 4 files changed, 190 insertions(+), 32 deletions(-) diff --git a/wallet/src/cli/native_token_transfer_program.rs b/wallet/src/cli/native_token_transfer_program.rs index 355c7f3..6d43d64 100644 --- a/wallet/src/cli/native_token_transfer_program.rs +++ b/wallet/src/cli/native_token_transfer_program.rs @@ -126,9 +126,17 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { let from: Address = from.parse().unwrap(); let to: Address = to.parse().unwrap(); - let (res, [secret_from, secret_to]) = wallet_core - .send_private_native_token_transfer_owned_account(from, to, amount) - .await?; + let to_initialization = wallet_core.check_private_account_initialized(&to).await?; + + let (res, [secret_from, secret_to]) = if let Some(to_proof) = to_initialization { + wallet_core + .send_private_native_token_transfer_owned_account_already_initialized(from, to, amount, to_proof) + .await? + } else { + wallet_core + .send_private_native_token_transfer_owned_account_not_initialized(from, to, amount) + .await? + }; println!("Results of tx send is {res:#?}"); @@ -206,9 +214,17 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let from: Address = from.parse().unwrap(); let to: Address = to.parse().unwrap(); - let (res, secret) = wallet_core - .send_shielded_native_token_transfer(from, to, amount) - .await?; + let to_initialization = wallet_core.check_private_account_initialized(&to).await?; + + let (res, secret) = if let Some(to_proof) = to_initialization { + wallet_core + .send_shielded_native_token_transfer_already_initialized(from, to, amount, to_proof) + .await? + } else { + wallet_core + .send_shielded_native_token_transfer_not_initialized(from, to, amount) + .await? + }; println!("Results of tx send is {res:#?}"); diff --git a/wallet/src/cli/pinata_program.rs b/wallet/src/cli/pinata_program.rs index eb5c716..b197ae0 100644 --- a/wallet/src/cli/pinata_program.rs +++ b/wallet/src/cli/pinata_program.rs @@ -102,7 +102,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { pinata_addr, winner_addr, solution, - winner_proof + winner_proof, ) .await? } else { diff --git a/wallet/src/token_transfers/private.rs b/wallet/src/token_transfers/private.rs index 5253acd..0c6751b 100644 --- a/wallet/src/token_transfers/private.rs +++ b/wallet/src/token_transfers/private.rs @@ -6,7 +6,7 @@ use nssa::{ program::Program, }; use nssa_core::{ - Commitment, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, + Commitment, MembershipProof, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, encryption::IncomingViewingPublicKey, }; @@ -98,11 +98,12 @@ impl WalletCore { } } - pub async fn send_private_native_token_transfer_owned_account( + pub async fn send_private_native_token_transfer_owned_account_already_initialized( &self, from: Address, to: Address, balance_to_move: u128, + to_proof: MembershipProof, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { let Some((from_keys, from_acc)) = self.storage.user_data.get_private_account(&from).cloned() @@ -124,7 +125,6 @@ impl WalletCore { let program = Program::authenticated_transfer_program(); let sender_commitment = Commitment::new(&from_npk, &from_acc); - let receiver_commitment = Commitment::new(&to_npk, &to_acc); let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk); let recipient_pre = AccountWithMetadata::new(to_acc.clone(), true, &to_npk); @@ -153,14 +153,7 @@ impl WalletCore { .unwrap() .unwrap(), ), - ( - to_keys.private_key_holder.nullifier_secret_key, - self.sequencer_client - .get_proof_for_commitment(receiver_commitment) - .await - .unwrap() - .unwrap(), - ), + (to_keys.private_key_holder.nullifier_secret_key, to_proof), ], &program, ) @@ -196,4 +189,92 @@ impl WalletCore { Err(ExecutionFailureKind::InsufficientFundsError) } } + + pub async fn send_private_native_token_transfer_owned_account_not_initialized( + &self, + from: Address, + to: Address, + balance_to_move: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let Some((from_keys, from_acc)) = + self.storage.user_data.get_private_account(&from).cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((to_keys, to_acc)) = self.storage.user_data.get_private_account(&to).cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let from_npk = from_keys.nullifer_public_key; + let from_ipk = from_keys.incoming_viewing_public_key; + 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 = Program::authenticated_transfer_program(); + + let sender_commitment = Commitment::new(&from_npk, &from_acc); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[( + from_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + )], + &program, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + } } diff --git a/wallet/src/token_transfers/shielded.rs b/wallet/src/token_transfers/shielded.rs index 1cf1cb7..8fabedd 100644 --- a/wallet/src/token_transfers/shielded.rs +++ b/wallet/src/token_transfers/shielded.rs @@ -6,14 +6,84 @@ use nssa::{ program::Program, }; use nssa_core::{ - Commitment, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, + MembershipProof, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, encryption::IncomingViewingPublicKey, }; use crate::{WalletCore, helperfunctions::produce_random_nonces}; impl WalletCore { - pub async fn send_shielded_native_token_transfer( + pub async fn send_shielded_native_token_transfer_already_initialized( + &self, + from: Address, + to: Address, + balance_to_move: u128, + to_proof: MembershipProof, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((to_keys, to_acc)) = self.storage.user_data.get_private_account(&to).cloned() + 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 = Program::authenticated_transfer_program(); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), true, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &nssa::program::Program::serialize_instruction(balance_to_move).unwrap(), + &[0, 1], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[(to_keys.private_key_holder.nullifier_secret_key, to_proof)], + &program, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + shared_secret, + )) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + } + + pub async fn send_shielded_native_token_transfer_not_initialized( &self, from: Address, to: Address, @@ -34,10 +104,8 @@ impl WalletCore { if from_acc.balance >= balance_to_move { let program = Program::authenticated_transfer_program(); - let receiver_commitment = Commitment::new(&to_npk, &to_acc); - let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); - let recipient_pre = AccountWithMetadata::new(to_acc.clone(), true, &to_npk); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); let eph_holder = EphemeralKeyHolder::new(&to_npk); let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); @@ -45,17 +113,10 @@ impl WalletCore { let (output, proof) = circuit::execute_and_prove( &[sender_pre, recipient_pre], &nssa::program::Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 1], + &[0, 2], &produce_random_nonces(1), &[(to_npk.clone(), shared_secret.clone())], - &[( - to_keys.private_key_holder.nullifier_secret_key, - self.sequencer_client - .get_proof_for_commitment(receiver_commitment) - .await - .unwrap() - .unwrap(), - )], + &[], &program, ) .unwrap();