From 35ffb65df03ea0bbb76ed84a3e784772696b7f89 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 12:10:27 -0300 Subject: [PATCH 01/46] add privacy preserving transaction scaffolding --- nssa/core/src/account/commitment.rs | 2 ++ nssa/core/src/account/mod.rs | 6 ++++++ nssa/core/src/account/nullifier.rs | 2 ++ nssa/src/lib.rs | 1 + .../privacy_preserving_transaction/message.rs | 16 ++++++++++++++++ nssa/src/privacy_preserving_transaction/mod.rs | 3 +++ .../transaction.rs | 10 ++++++++++ .../witness_set.rs | 2 ++ nssa/src/program.rs | 7 +++++++ 9 files changed, 49 insertions(+) create mode 100644 nssa/core/src/account/commitment.rs create mode 100644 nssa/core/src/account/nullifier.rs create mode 100644 nssa/src/privacy_preserving_transaction/message.rs create mode 100644 nssa/src/privacy_preserving_transaction/mod.rs create mode 100644 nssa/src/privacy_preserving_transaction/transaction.rs create mode 100644 nssa/src/privacy_preserving_transaction/witness_set.rs diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs new file mode 100644 index 0000000..5bb7532 --- /dev/null +++ b/nssa/core/src/account/commitment.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Commitment([u8; 32]); diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account/mod.rs index 9d564c4..32b32da 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account/mod.rs @@ -2,6 +2,12 @@ use serde::{Deserialize, Serialize}; use crate::program::ProgramId; +mod commitment; +mod nullifier; + +pub use commitment::Commitment; +pub use nullifier::Nullifier; + pub type Nonce = u128; type Data = Vec; diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs new file mode 100644 index 0000000..3280f19 --- /dev/null +++ b/nssa/core/src/account/nullifier.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Nullifier([u8; 32]); diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index ab4fe02..83180e9 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -2,6 +2,7 @@ mod address; pub mod error; pub mod program; pub mod public_transaction; +mod privacy_preserving_transaction; mod signature; mod state; diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs new file mode 100644 index 0000000..edea889 --- /dev/null +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -0,0 +1,16 @@ +use nssa_core::account::{Account, Commitment, Nonce, Nullifier}; + +use crate::Address; + +#[derive(Debug, Clone, PartialEq, Eq)] +struct EncryptedAccountData; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Message { + public_addresses: Vec
, + nonces: Vec, + public_post_states: Vec, + encrypted_private_post_states: Vec, + new_commitments: Vec, + new_nullifiers: Vec, +} diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs new file mode 100644 index 0000000..898d27c --- /dev/null +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -0,0 +1,3 @@ +mod transaction; +mod message; +mod witness_set; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs new file mode 100644 index 0000000..7322c75 --- /dev/null +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -0,0 +1,10 @@ +use super::message::Message; +use super::witness_set::WitnessSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PrivacyPreservingTransaction { + message: Message, + witness_set: WitnessSet, +} + + diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs new file mode 100644 index 0000000..132bb49 --- /dev/null +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WitnessSet; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index e2762ba..b4b15ec 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -57,6 +57,13 @@ impl Program { Ok(post_states) } + pub fn prove( + &self, + pre_states: &[AccountWithMetadata], + instruction_data: &InstructionData, + ) { + } + /// Writes inputs to `env_builder` in the order expected by the programs fn write_inputs( pre_states: &[AccountWithMetadata], From 035f95022986777f5fe23cf7312b1d49c1e4ef86 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 13:28:23 -0300 Subject: [PATCH 02/46] add execute_and_prove --- nssa/src/error.rs | 13 ++++++-- nssa/src/program.rs | 73 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 11a2f41..de4a7cc 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -7,9 +7,6 @@ pub enum NssaError { #[error("Invalid input: {0}")] InvalidInput(String), - #[error("Risc0 error: {0}")] - ProgramExecutionFailed(String), - #[error("Program violated execution rules")] InvalidProgramBehavior, @@ -24,4 +21,14 @@ pub enum NssaError { #[error("Invalid Public Key")] InvalidPublicKey, + + #[error("Risc0 error: {0}")] + ProgramWriteInputFailed(String), + + #[error("Risc0 error: {0}")] + ProgramExecutionFailed(String), + + #[error("Risc0 error: {0}")] + ProgramProveFailed(String), + } diff --git a/nssa/src/program.rs b/nssa/src/program.rs index b4b15ec..2195501 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -3,7 +3,9 @@ use nssa_core::{ program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId}, }; use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; -use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; +use risc0_zkvm::{ + ExecutorEnv, ExecutorEnvBuilder, Receipt, default_executor, default_prover, serde::to_vec, +}; use serde::Serialize; use crate::error::NssaError; @@ -47,21 +49,39 @@ impl Program { .decode() .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; + // TODO: Move this logic to `V01State::transition_from_public_transaction`. + self.claim_accounts_with_default_program_owner(&mut post_states); + + Ok(post_states) + } + + fn claim_accounts_with_default_program_owner(&self, post_states: &mut [Account]) { // Claim any output account with default program owner field for account in post_states.iter_mut() { if account.program_owner == DEFAULT_PROGRAM_ID { account.program_owner = self.id; } } - - Ok(post_states) } - pub fn prove( + /// Executes and proves the program `P`. + /// Returns the proof + fn execute_and_prove( &self, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, - ) { + ) -> Result { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + Self::write_inputs(pre_states, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Prove the program + let prover = default_prover(); + let prove_info = prover + .prove(env, self.elf) + .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))?; + Ok(prove_info.receipt) } /// Writes inputs to `env_builder` in the order expected by the programs @@ -73,7 +93,7 @@ impl Program { let pre_states = pre_states.to_vec(); env_builder .write(&(pre_states, instruction_data)) - .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; + .map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?; Ok(()) } @@ -210,4 +230,45 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(recipient_post, expected_recipient_post); } + + #[test] + fn test_program_execute_and_prove() { + let program = Program::simple_balance_transfer(); + let balance_to_move: u128 = 11223344556677; + let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); + let sender = AccountWithMetadata { + account: Account { + balance: 77665544332211, + ..Account::default() + }, + is_authorized: false, + }; + let recipient = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let expected_sender_post = Account { + balance: 77665544332211 - balance_to_move, + ..Account::default() + }; + let expected_recipient_post = Account { + balance: balance_to_move, + ..Account::default() + }; + let receipt = program + .execute_and_prove(&[sender, recipient], &instruction_data) + .unwrap(); + let [sender_post, recipient_post] = receipt + .journal + .decode::>() + .unwrap() + .try_into() + .unwrap(); + + let output = assert_eq!(sender_post, expected_sender_post); + + assert_eq!(recipient_post, expected_recipient_post); + assert!(receipt.verify(program.id()).is_ok()); + } } From c5a4e83e3e7bfbaa23cd2a2634b5208c064aa340 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 14:09:04 -0300 Subject: [PATCH 03/46] add pre states to program output --- nssa/core/src/program.rs | 16 ++++++++++++++ .../guest/src/bin/authenticated_transfer.rs | 5 ++--- nssa/src/program.rs | 21 +++++++++++-------- .../guest/src/bin/burner.rs | 7 +++---- .../guest/src/bin/data_changer.rs | 8 +++---- .../guest/src/bin/extra_output.rs | 10 +++++---- .../guest/src/bin/minter.rs | 7 +++---- .../guest/src/bin/missing_output.rs | 7 +++---- .../guest/src/bin/nonce_changer.rs | 7 +++---- .../guest/src/bin/program_owner_changer.rs | 7 +++---- .../guest/src/bin/simple_balance_transfer.rs | 8 ++++--- 11 files changed, 59 insertions(+), 44 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 9b99a61..e7b13db 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,17 +1,33 @@ use crate::account::{Account, AccountWithMetadata}; use risc0_zkvm::serde::Deserializer; use risc0_zkvm::{DeserializeOwned, guest::env}; +use serde::{Deserialize, Serialize}; pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; +#[derive(Serialize, Deserialize)] +pub struct ProgramOutput { + pub pre_states: Vec, + pub post_states: Vec, +} + pub fn read_nssa_inputs() -> (Vec, T) { let pre_states: Vec = env::read(); let words: InstructionData = env::read(); let instruction_data = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap(); (pre_states, instruction_data) } + +pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec) { + let output = ProgramOutput { + pre_states, + post_states, + }; + env::commit(&output); +} + /// Validates well-behaved program execution /// /// # Parameters diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index a89b090..74edad6 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = u128; @@ -32,5 +31,5 @@ fn main() { sender_post.balance -= balance_to_move; receiver_post.balance += balance_to_move; - env::commit(&vec![sender_post, receiver_post]); + write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]); } diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 2195501..7ab6d74 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -1,6 +1,6 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, - program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId}, + program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId, ProgramOutput}, }; use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; use risc0_zkvm::{ @@ -44,7 +44,10 @@ impl Program { .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; // Get outputs - let mut post_states: Vec = session_info + let ProgramOutput { + post_states: mut post_states, + .. + } = session_info .journal .decode() .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; @@ -107,7 +110,10 @@ impl Program { #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountWithMetadata}; + use nssa_core::{ + account::{Account, AccountWithMetadata}, + program::ProgramOutput, + }; use crate::program::Program; @@ -256,15 +262,12 @@ mod tests { balance: balance_to_move, ..Account::default() }; + let receipt = program .execute_and_prove(&[sender, recipient], &instruction_data) .unwrap(); - let [sender_post, recipient_post] = receipt - .journal - .decode::>() - .unwrap() - .try_into() - .unwrap(); + let ProgramOutput { post_states, .. } = receipt.journal.decode().unwrap(); + let [sender_post, recipient_post] = post_states.try_into().unwrap(); let output = assert_eq!(sender_post, expected_sender_post); diff --git a/nssa/test_program_methods/guest/src/bin/burner.rs b/nssa/test_program_methods/guest/src/bin/burner.rs index 018c203..40ba46e 100644 --- a/nssa/test_program_methods/guest/src/bin/burner.rs +++ b/nssa/test_program_methods/guest/src/bin/burner.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = u128; @@ -11,9 +10,9 @@ fn main() { Err(_) => return, }; - let account_pre = pre.account; + let account_pre = &pre.account; let mut account_post = account_pre.clone(); account_post.balance -= balance_to_burn; - env::commit(&vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post]); } diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index fa1efd3..389bba0 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -11,10 +10,9 @@ fn main() { Err(_) => return, }; - let account_pre = pre.account; + let account_pre = &pre.account; let mut account_post = account_pre.clone(); account_post.data.push(0); - env::commit(&vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post]); } - diff --git a/nssa/test_program_methods/guest/src/bin/extra_output.rs b/nssa/test_program_methods/guest/src/bin/extra_output.rs index c02c5e2..35069c6 100644 --- a/nssa/test_program_methods/guest/src/bin/extra_output.rs +++ b/nssa/test_program_methods/guest/src/bin/extra_output.rs @@ -1,5 +1,7 @@ -use nssa_core::{account::Account, program::read_nssa_inputs}; -use risc0_zkvm::guest::env; +use nssa_core::{ + account::Account, + program::{read_nssa_inputs, write_nssa_outputs}, +}; type Instruction = (); @@ -11,7 +13,7 @@ fn main() { Err(_) => return, }; - let account_pre = pre.account; + let account_pre = pre.account.clone(); - env::commit(&vec![account_pre, Account::default()]); + write_nssa_outputs(vec![pre], vec![account_pre, Account::default()]); } diff --git a/nssa/test_program_methods/guest/src/bin/minter.rs b/nssa/test_program_methods/guest/src/bin/minter.rs index b82d9e9..56ce113 100644 --- a/nssa/test_program_methods/guest/src/bin/minter.rs +++ b/nssa/test_program_methods/guest/src/bin/minter.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -11,9 +10,9 @@ fn main() { Err(_) => return, }; - let account_pre = pre.account; + let account_pre = &pre.account; let mut account_post = account_pre.clone(); account_post.balance += 1; - env::commit(&vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post]); } diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index 61aa8c5..d2bf37e 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -11,7 +10,7 @@ fn main() { Err(_) => return, }; - let account_pre1 = pre1.account; + let account_pre1 = pre1.account.clone(); - env::commit(&vec![account_pre1]); + write_nssa_outputs(vec![pre1], vec![account_pre1]); } diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs index eb1365e..0aa7bbb 100644 --- a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -11,9 +10,9 @@ fn main() { Err(_) => return, }; - let account_pre = pre.account; + let account_pre = &pre.account; let mut account_post = account_pre.clone(); account_post.nonce += 1; - env::commit(&vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post]); } diff --git a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs index 4d11438..990c0fb 100644 --- a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -11,9 +10,9 @@ fn main() { Err(_) => return, }; - let account_pre = pre.account; + let account_pre = &pre.account; let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - env::commit(&vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post]); } diff --git a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs index 047e252..7f131f6 100644 --- a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -1,5 +1,4 @@ -use nssa_core::program::read_nssa_inputs; -use risc0_zkvm::guest::env; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; type Instruction = u128; @@ -16,5 +15,8 @@ fn main() { sender_post.balance -= balance; receiver_post.balance += balance; - env::commit(&vec![sender_post, receiver_post]); + write_nssa_outputs( + vec![sender_pre, receiver_pre], + vec![sender_post, receiver_post], + ); } From d1ebb831efc3017a85e9eef7997eec837b9ae858 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 14:30:04 -0300 Subject: [PATCH 04/46] refactor program input --- nssa/core/src/program.rs | 14 +++++++++++--- .../guest/src/bin/authenticated_transfer.rs | 11 ++++++----- nssa/test_program_methods/guest/src/bin/burner.rs | 9 ++++++--- .../guest/src/bin/data_changer.rs | 6 +++--- .../guest/src/bin/extra_output.rs | 6 +++--- nssa/test_program_methods/guest/src/bin/minter.rs | 6 +++--- .../guest/src/bin/missing_output.rs | 6 +++--- .../guest/src/bin/nonce_changer.rs | 6 +++--- .../guest/src/bin/program_owner_changer.rs | 6 +++--- .../guest/src/bin/simple_balance_transfer.rs | 9 ++++++--- 10 files changed, 47 insertions(+), 32 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index e7b13db..ff8fd57 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -7,17 +7,25 @@ pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; +pub struct ProgramInput { + pub pre_states: Vec, + pub instruction: T, +} + #[derive(Serialize, Deserialize)] pub struct ProgramOutput { pub pre_states: Vec, pub post_states: Vec, } -pub fn read_nssa_inputs() -> (Vec, T) { +pub fn read_nssa_inputs() -> ProgramInput { let pre_states: Vec = env::read(); let words: InstructionData = env::read(); - let instruction_data = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap(); - (pre_states, instruction_data) + let instruction = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap(); + ProgramInput { + pre_states, + instruction, + } } pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec) { diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 74edad6..9e7f399 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,16 +1,17 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; - -type Instruction = u128; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { // Read input accounts. // It is expected to receive only two accounts: [sender_account, receiver_account] - let (input_accounts, balance_to_move) = read_nssa_inputs::(); + let ProgramInput { + pre_states, + instruction: balance_to_move, + } = read_nssa_inputs(); // Continue only if input_accounts is an array of two elements - let [sender, receiver] = match input_accounts.try_into() { + let [sender, receiver] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/burner.rs b/nssa/test_program_methods/guest/src/bin/burner.rs index 40ba46e..1ef7373 100644 --- a/nssa/test_program_methods/guest/src/bin/burner.rs +++ b/nssa/test_program_methods/guest/src/bin/burner.rs @@ -1,11 +1,14 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = u128; fn main() { - let (input_accounts, balance_to_burn) = read_nssa_inputs::(); + let ProgramInput { + pre_states, + instruction: balance_to_burn, + } = read_nssa_inputs::(); - let [pre] = match input_accounts.try_into() { + let [pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index 389bba0..c7d34a2 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -1,11 +1,11 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = (); fn main() { - let (input_accounts, _) = read_nssa_inputs::(); + let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre] = match input_accounts.try_into() { + let [pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/extra_output.rs b/nssa/test_program_methods/guest/src/bin/extra_output.rs index 35069c6..3543d51 100644 --- a/nssa/test_program_methods/guest/src/bin/extra_output.rs +++ b/nssa/test_program_methods/guest/src/bin/extra_output.rs @@ -1,14 +1,14 @@ use nssa_core::{ account::Account, - program::{read_nssa_inputs, write_nssa_outputs}, + program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}, }; type Instruction = (); fn main() { - let (input_accounts, _) = read_nssa_inputs::(); + let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre] = match input_accounts.try_into() { + let [pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/minter.rs b/nssa/test_program_methods/guest/src/bin/minter.rs index 56ce113..2ec97a9 100644 --- a/nssa/test_program_methods/guest/src/bin/minter.rs +++ b/nssa/test_program_methods/guest/src/bin/minter.rs @@ -1,11 +1,11 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = (); fn main() { - let (input_accounts, _) = read_nssa_inputs::(); + let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre] = match input_accounts.try_into() { + let [pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index d2bf37e..2174266 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -1,11 +1,11 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = (); fn main() { - let (input_accounts, _) = read_nssa_inputs::(); + let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre1, _] = match input_accounts.try_into() { + let [pre1, _] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs index 0aa7bbb..b3b2599 100644 --- a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs @@ -1,11 +1,11 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = (); fn main() { - let (input_accounts, _) = read_nssa_inputs::(); + let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre] = match input_accounts.try_into() { + let [pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs index 990c0fb..49947cd 100644 --- a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -1,11 +1,11 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = (); fn main() { - let (input_accounts, _) = read_nssa_inputs::(); + let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre] = match input_accounts.try_into() { + let [pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; diff --git a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs index 7f131f6..13263c5 100644 --- a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -1,11 +1,14 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; type Instruction = u128; fn main() { - let (input_accounts, balance) = read_nssa_inputs::(); + let ProgramInput { + pre_states, + instruction: balance, + } = read_nssa_inputs::(); - let [sender_pre, receiver_pre] = match input_accounts.try_into() { + let [sender_pre, receiver_pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; From a694e705ea5159d554053b37e490d10aa03e358b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 14:48:20 -0300 Subject: [PATCH 05/46] move claiming accounts logic to state transition --- nssa/src/program.rs | 18 +----------------- nssa/src/state.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 7ab6d74..b104eca 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -45,28 +45,15 @@ impl Program { // Get outputs let ProgramOutput { - post_states: mut post_states, - .. + mut post_states, .. } = session_info .journal .decode() .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; - // TODO: Move this logic to `V01State::transition_from_public_transaction`. - self.claim_accounts_with_default_program_owner(&mut post_states); - Ok(post_states) } - fn claim_accounts_with_default_program_owner(&self, post_states: &mut [Account]) { - // Claim any output account with default program owner field - for account in post_states.iter_mut() { - if account.program_owner == DEFAULT_PROGRAM_ID { - account.program_owner = self.id; - } - } - } - /// Executes and proves the program `P`. /// Returns the proof fn execute_and_prove( @@ -218,13 +205,10 @@ mod tests { let expected_sender_post = Account { balance: 77665544332211 - balance_to_move, - program_owner: program.id(), ..Account::default() }; let expected_recipient_post = Account { balance: balance_to_move, - // Program claims the account since the pre_state has default prorgam owner - program_owner: program.id(), ..Account::default() }; let [sender_post, recipient_post] = program diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f6d932e..3060185 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,7 +1,10 @@ use crate::{ address::Address, error::NssaError, program::Program, public_transaction::PublicTransaction, }; -use nssa_core::{account::Account, program::ProgramId}; +use nssa_core::{ + account::Account, + program::{DEFAULT_PROGRAM_ID, ProgramId}, +}; use std::collections::HashMap; pub struct V01State { @@ -48,7 +51,12 @@ impl V01State { for (address, post) in state_diff.into_iter() { let current_account = self.get_account_by_address_mut(address); + *current_account = post; + // The invoked program claims the accounts with default program id. + if current_account.program_owner == DEFAULT_PROGRAM_ID { + current_account.program_owner = tx.message().program_id; + } } for address in tx.signer_addresses() { From 507988832f7027d6e153f3d96fbc736f1c509df9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 15:34:21 -0300 Subject: [PATCH 06/46] add transition_from_privacy_preserving_transaction method and relevant scaffolding --- nssa/core/src/account/commitment.rs | 2 +- nssa/core/src/account/nullifier.rs | 2 +- .../privacy_preserving_transaction/message.rs | 12 ++--- .../src/privacy_preserving_transaction/mod.rs | 2 + .../transaction.rs | 30 +++++++++++ .../witness_set.rs | 21 +++++++- nssa/src/state.rs | 52 +++++++++++++++++-- 7 files changed, 109 insertions(+), 12 deletions(-) diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index 5bb7532..a0bcfee 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -1,2 +1,2 @@ -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Commitment([u8; 32]); diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 3280f19..4479af0 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -1,2 +1,2 @@ -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Nullifier([u8; 32]); diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index edea889..1585be1 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -7,10 +7,10 @@ struct EncryptedAccountData; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { - public_addresses: Vec
, - nonces: Vec, - public_post_states: Vec, - encrypted_private_post_states: Vec, - new_commitments: Vec, - new_nullifiers: Vec, + 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(crate) new_nullifiers: Vec, } diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 898d27c..2d3bba3 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -1,3 +1,5 @@ mod transaction; mod message; mod witness_set; + +pub use transaction::PrivacyPreservingTransaction; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 7322c75..0640454 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,3 +1,10 @@ +use std::collections::HashMap; + +use nssa_core::account::Account; + +use crate::error::NssaError; +use crate::{Address, V01State}; + use super::message::Message; use super::witness_set::WitnessSet; @@ -7,4 +14,27 @@ pub struct PrivacyPreservingTransaction { witness_set: WitnessSet, } +impl PrivacyPreservingTransaction { + pub(crate) fn validate( + &self, + arg: &mut V01State, + ) -> Result, NssaError> { + todo!() + } + pub fn message(&self) -> &Message { + &self.message + } + + pub fn witness_set(&self) -> &WitnessSet { + &self.witness_set + } + + pub(crate) fn signer_addresses(&self) -> Vec
{ + self.witness_set + .signatures_and_public_keys() + .iter() + .map(|(_, public_key)| Address::from_public_key(public_key)) + .collect() + } +} diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 132bb49..6a65cb6 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,2 +1,21 @@ +use crate::{privacy_preserving_transaction::message::Message, PrivateKey, PublicKey, Signature}; + #[derive(Debug, Clone, PartialEq, Eq)] -pub struct WitnessSet; +pub struct WitnessSet { + pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, +} + + +impl WitnessSet { + pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self { + todo!() + } + + pub fn is_valid_for(&self, message: &Message) -> bool { + todo!() + } + + pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] { + &self.signatures_and_public_keys + } +} diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3060185..3e266d8 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,14 +1,31 @@ use crate::{ - address::Address, error::NssaError, program::Program, public_transaction::PublicTransaction, + address::Address, error::NssaError, + privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program, + public_transaction::PublicTransaction, }; use nssa_core::{ - account::Account, + account::{Account, Commitment, Nullifier}, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; + +struct CommitmentSet(HashSet); + +impl CommitmentSet { + fn extend(&mut self, commitments: &[Commitment]) { + self.0.extend(commitments) + } + + fn set_commitment(&self) -> [u8; 32] { + // TODO: implement + [0; 32] + } +} +type NullifierSet = HashSet; pub struct V01State { public_state: HashMap, + private_state: (CommitmentSet, NullifierSet), builtin_programs: HashMap, } @@ -31,6 +48,7 @@ impl V01State { let mut this = Self { public_state, + private_state: (CommitmentSet(HashSet::new()), NullifierSet::new()), builtin_programs: HashMap::new(), }; @@ -67,6 +85,34 @@ impl V01State { Ok(()) } + pub fn transition_from_privacy_preserving_transaction( + &mut self, + tx: &PrivacyPreservingTransaction, + ) -> Result<(), NssaError> { + // 1. Verify the transaction satisfies acceptance criteria + let public_state_diff = tx.validate(self)?; + let message = tx.message(); + + // 2. Add new commitments + self.private_state.0.extend(message.new_commitments); + + // 3. Add new nullifiers + self.private_state.1.extend(message.new_nullifiers); + + // 4. Update public accounts + for (address, post) in public_state_diff.into_iter() { + let current_account = self.get_account_by_address_mut(address); + *current_account = post; + } + + // // 5. Increment nonces + for address in tx.signer_addresses() { + let current_account = self.get_account_by_address_mut(address); + current_account.nonce += 1; + } + Ok(()) + } + fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account { self.public_state.entry(address).or_default() } From 1a10dade25d40d5a0e6a015e90db963918178347 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 14 Aug 2025 16:20:09 -0300 Subject: [PATCH 07/46] add priv preserving tx validation criteria scaffolding --- .../privacy_preserving_transaction/message.rs | 4 +- .../transaction.rs | 119 +++++++++++++++++- .../witness_set.rs | 8 ++ nssa/src/public_transaction/transaction.rs | 13 +- nssa/src/state.rs | 34 +++-- 5 files changed, 158 insertions(+), 20 deletions(-) diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 1585be1..5bbdcd9 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -3,14 +3,14 @@ use nssa_core::account::{Account, Commitment, Nonce, Nullifier}; use crate::Address; #[derive(Debug, Clone, PartialEq, Eq)] -struct EncryptedAccountData; +pub struct EncryptedAccountData; #[derive(Debug, Clone, PartialEq, Eq)] 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) encrypted_private_post_states: EncryptedAccountData, pub(crate) new_commitments: Vec, pub(crate) new_nullifiers: Vec, } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 0640454..b0cdbe2 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,8 +1,10 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use nssa_core::account::Account; +use nssa_core::account::{Account, AccountWithMetadata}; use crate::error::NssaError; +use crate::privacy_preserving_transaction::message::EncryptedAccountData; +use crate::state::CommitmentSetDigest; use crate::{Address, V01State}; use super::message::Message; @@ -15,11 +17,100 @@ pub struct PrivacyPreservingTransaction { } impl PrivacyPreservingTransaction { - pub(crate) fn validate( + pub(crate) fn validate_and_produce_public_state_diff( &self, - arg: &mut V01State, + state: &mut V01State, ) -> Result, NssaError> { - todo!() + let message = &self.message; + let witness_set = &self.witness_set; + + // 1. Commitments or nullifiers are non empty + if message.new_commitments.is_empty() && message.new_nullifiers.is_empty() { + return Err(NssaError::InvalidInput( + "Empty commitments and empty nullifiers found in message".into(), + )); + } + + // 2. Check there are no duplicate addresses in the public_addresses list. + if n_unique(&message.public_addresses) != message.public_addresses.len() { + return Err(NssaError::InvalidInput( + "Duplicate addresses found in message".into(), + )); + } + + // Check there are no duplicate nullifiers in the new_nullifiers list + if n_unique(&message.new_nullifiers) != message.new_nullifiers.len() { + return Err(NssaError::InvalidInput( + "Duplicate nullifiers found in message".into(), + )); + } + + // Check there are no duplicate commitments in the new_nullifiers list + if n_unique(&message.new_commitments) != message.new_commitments.len() { + return Err(NssaError::InvalidInput( + "Duplicate commitments found in message".into(), + )); + } + + // 3. Nonce checks and Valid signatures + // Check exactly one nonce is provided for each signature + if message.nonces.len() != witness_set.signatures_and_public_keys.len() { + return Err(NssaError::InvalidInput( + "Mismatch between number of nonces and signatures/public keys".into(), + )); + } + + // Check the signatures are valid + if !witness_set.is_valid_for(message) { + return Err(NssaError::InvalidInput( + "Invalid signature for given message and public key".into(), + )); + } + + let signer_addresses = self.signer_addresses(); + // Check nonces corresponds to the current nonces on the public state. + for (address, nonce) in signer_addresses.iter().zip(&message.nonces) { + let current_nonce = state.get_account_by_address(address).nonce; + if current_nonce != *nonce { + return Err(NssaError::InvalidInput("Nonce mismatch".into())); + } + } + + // Build pre_states for proof verification + let public_pre_states: Vec<_> = message + .public_addresses + .iter() + .map(|address| AccountWithMetadata { + account: state.get_account_by_address(address), + is_authorized: signer_addresses.contains(address), + }) + .collect(); + + let set_commitment = state.commitment_set_digest(); + + // 4. Proof verification + check_privacy_preserving_circuit_execution_proof_is_valid( + witness_set.proof, + &public_pre_states, + &message.public_post_states, + &message.encrypted_private_post_states, + &message.new_commitments, + &message.new_nullifiers, + set_commitment, + )?; + + // 5. Commitment freshness + state.check_commitments_are_new(&message.new_commitments)?; + + // 6. Nullifier uniqueness + state.check_nullifiers_are_new(&message.new_nullifiers)?; + + Ok(message + .public_addresses + .iter() + .cloned() + .zip(message.public_post_states.clone()) + .collect()) } pub fn message(&self) -> &Message { @@ -38,3 +129,21 @@ impl PrivacyPreservingTransaction { .collect() } } + +fn check_privacy_preserving_circuit_execution_proof_is_valid( + proof: (), + public_pre_states: &[AccountWithMetadata], + public_post_states: &[Account], + encrypted_private_post_states: &EncryptedAccountData, + new_commitments: &[nssa_core::account::Commitment], + new_nullifiers: &[nssa_core::account::Nullifier], + commitment_set_digest: CommitmentSetDigest, +) -> Result<(), NssaError> { + todo!() +} + +use std::hash::Hash; +fn n_unique(data: &[T]) -> usize { + let set: HashSet<&T> = data.iter().collect(); + set.len() +} diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 6a65cb6..7db63fd 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,8 +1,12 @@ use crate::{privacy_preserving_transaction::message::Message, PrivateKey, PublicKey, Signature}; + +type Proof = (); + #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, + pub(super) proof: Proof } @@ -18,4 +22,8 @@ impl WitnessSet { pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] { &self.signatures_and_public_keys } + + pub fn proof(&self) -> &Proof { + &self.proof + } } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index bd16644..53d859c 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -50,7 +50,7 @@ impl PublicTransaction { hasher.finalize_fixed().into() } - pub(crate) fn validate_and_compute_post_states( + pub(crate) fn validate_and_produce_public_state_diff( &self, state: &V01State, ) -> Result, NssaError> { @@ -64,6 +64,7 @@ impl PublicTransaction { )); } + // Check exactly one nonce is provided for each signature if message.nonces.len() != witness_set.signatures_and_public_keys.len() { return Err(NssaError::InvalidInput( "Mismatch between number of nonces and signatures/public keys".into(), @@ -232,7 +233,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -252,7 +253,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -273,7 +274,7 @@ pub mod tests { let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -293,7 +294,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -309,7 +310,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3e266d8..53e5c91 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -10,15 +10,16 @@ use nssa_core::{ use std::collections::{HashMap, HashSet}; struct CommitmentSet(HashSet); +pub type CommitmentSetDigest = [u32; 8]; impl CommitmentSet { - fn extend(&mut self, commitments: &[Commitment]) { + fn extend(&mut self, commitments: Vec) { self.0.extend(commitments) } - fn set_commitment(&self) -> [u8; 32] { + fn digest(&self) -> CommitmentSetDigest { // TODO: implement - [0; 32] + [0; 8] } } type NullifierSet = HashSet; @@ -65,7 +66,7 @@ impl V01State { &mut self, tx: &PublicTransaction, ) -> Result<(), NssaError> { - let state_diff = tx.validate_and_compute_post_states(self)?; + let state_diff = tx.validate_and_produce_public_state_diff(self)?; for (address, post) in state_diff.into_iter() { let current_account = self.get_account_by_address_mut(address); @@ -90,14 +91,15 @@ impl V01State { tx: &PrivacyPreservingTransaction, ) -> Result<(), NssaError> { // 1. Verify the transaction satisfies acceptance criteria - let public_state_diff = tx.validate(self)?; + let public_state_diff = tx.validate_and_produce_public_state_diff(self)?; + let message = tx.message(); // 2. Add new commitments - self.private_state.0.extend(message.new_commitments); + self.private_state.0.extend(message.new_commitments.clone()); // 3. Add new nullifiers - self.private_state.1.extend(message.new_nullifiers); + self.private_state.1.extend(message.new_nullifiers.clone()); // 4. Update public accounts for (address, post) in public_state_diff.into_iter() { @@ -127,6 +129,24 @@ impl V01State { pub(crate) fn builtin_programs(&self) -> &HashMap { &self.builtin_programs } + + pub fn commitment_set_digest(&self) -> CommitmentSetDigest { + self.private_state.0.digest() + } + + pub(crate) fn check_commitments_are_new( + &self, + new_commitments: &[Commitment], + ) -> Result<(), NssaError> { + todo!() + } + + pub(crate) fn check_nullifiers_are_new( + &self, + new_nullifiers: &[Nullifier], + ) -> Result<(), NssaError> { + todo!() + } } #[cfg(test)] From b20a97e5a1bf9c8aed9eec000d37ed7d26d7b8fa Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 07:39:41 -0300 Subject: [PATCH 08/46] wip --- nssa/core/src/account/commitment.rs | 13 +- nssa/core/src/account/mod.rs | 2 +- nssa/core/src/account/nullifier.rs | 23 +++- nssa/core/src/lib.rs | 77 ++++++++++++ .../src/bin/privacy_preserving_circuit.rs | 118 ++++++++++++++++++ .../privacy_preserving_transaction/message.rs | 8 +- .../transaction.rs | 11 +- .../witness_set.rs | 2 +- nssa/src/state.rs | 3 +- 9 files changed, 242 insertions(+), 15 deletions(-) create mode 100644 nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index a0bcfee..8f1b072 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -1,2 +1,13 @@ -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +use crate::account::{Account, NullifierPublicKey}; + + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct Commitment([u8; 32]); + +impl Commitment { + pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self { + todo!() + } +} diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account/mod.rs index 32b32da..722a4fb 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account/mod.rs @@ -6,7 +6,7 @@ mod commitment; mod nullifier; pub use commitment::Commitment; -pub use nullifier::Nullifier; +pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey}; pub type Nonce = u128; type Data = Vec; diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 4479af0..600093c 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -1,2 +1,23 @@ -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +use crate::account::Commitment; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct NullifierPublicKey([u8; 32]); + +impl From<&NullifierSecretKey> for NullifierPublicKey { + fn from(_value: &NullifierSecretKey) -> Self { + todo!() + } +} + +pub type NullifierSecretKey = [u8; 32]; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct Nullifier([u8; 32]); + +impl Nullifier { + pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { + todo!() + } +} diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index d20620e..54556e5 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,2 +1,79 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + account::{ + Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, + NullifierSecretKey, + }, + program::{ProgramId, ProgramOutput}, +}; + pub mod account; pub mod program; + +pub type CommitmentSetDigest = [u32; 8]; +pub type MembershipProof = Vec<[u8; 32]>; +pub fn verify_membership_proof( + commitment: &Commitment, + proof: &MembershipProof, + digest: &CommitmentSetDigest, +) -> bool { + todo!() +} + +pub type IncomingViewingPublicKey = [u8; 32]; +pub type EphemeralSecretKey = [u8; 32]; +pub struct EphemeralPublicKey; + +impl From<&EphemeralSecretKey> for EphemeralPublicKey { + fn from(value: &EphemeralSecretKey) -> Self { + todo!() + } +} + +pub struct Tag(u8); +impl Tag { + pub fn new(Npk: &NullifierPublicKey, Ipk: &IncomingViewingPublicKey) -> Self { + todo!() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct EncryptedAccountData; + +impl EncryptedAccountData { + pub fn new( + account: &Account, + esk: &EphemeralSecretKey, + Npk: &NullifierPublicKey, + Ivk: &IncomingViewingPublicKey, + ) -> Self { + // TODO: implement + Self + } +} + +#[derive(Serialize, Deserialize)] +pub struct PrivacyPreservingCircuitInput { + pub program_output: ProgramOutput, + pub visibility_mask: Vec, + pub private_account_data: Vec<( + Nonce, + NullifierPublicKey, + IncomingViewingPublicKey, + EphemeralSecretKey, + )>, + pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, + pub program_id: ProgramId, + pub commitment_set_digest: CommitmentSetDigest, +} + +#[derive(Serialize, Deserialize)] +pub struct PrivacyPreservingCircuitOutput { + pub public_pre_states: Vec, + pub public_post_states: Vec, + pub encrypted_private_post_states: Vec, + pub new_commitments: Vec, + pub new_nullifiers: Vec, + pub commitment_set_digest: CommitmentSetDigest, +} diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs new file mode 100644 index 0000000..6caebee --- /dev/null +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -0,0 +1,118 @@ +use risc0_zkvm::{guest::env, serde::to_vec}; + +use nssa_core::{ + account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, + program::{validate_execution, ProgramOutput}, + verify_membership_proof, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, + IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, Tag, +}; + +fn main() { + let PrivacyPreservingCircuitInput { + program_output, + visibility_mask, + private_account_data, + private_account_auth, + program_id, + commitment_set_digest, + } = env::read(); + + // TODO: Check that `program_execution_proof` is one of the allowed built-in programs + // assert!(BUILTIN_PROGRAM_IDS.contains(executing_program_id)); + + // Check that `program_output` is consistent with the execution of the corresponding program. + env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap(); + + let ProgramOutput { + pre_states, + post_states, + } = program_output; + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + validate_execution(&pre_states, &post_states, program_id); + + let n_accounts = pre_states.len(); + assert_eq!(visibility_mask.len(), n_accounts); + + let n_private_accounts = visibility_mask.iter().filter(|&&flag| flag != 0).count(); + assert_eq!(private_account_data.len(), n_private_accounts); + + let n_auth_private_accounts = visibility_mask.iter().filter(|&&flag| flag == 1).count(); + assert_eq!(private_account_auth.len(), n_auth_private_accounts); + + // These lists will be the public outputs of this circuit + // and will be populated next. + let mut public_pre_states: Vec = Vec::new(); + let mut public_post_states: Vec = Vec::new(); + let mut encrypted_private_post_states: Vec = Vec::new(); + let mut new_commitments: Vec = Vec::new(); + let mut new_nullifiers: Vec = Vec::new(); + + for i in 0..n_accounts { + // visibility_mask[i] equal to 0 means public + if visibility_mask[i] == 0 { + // If the account is marked as public, add the pre and post + // states to the corresponding lists. + public_pre_states.push(pre_states[i].clone()); + public_post_states.push(post_states[i].clone()); + } else { + let (new_nonce, Npk, Ipk, esk) = &private_account_data[i]; + + // Verify authentication + if visibility_mask[i] == 1 { + let (nsk, membership_proof) = &private_account_auth[i]; + + // 1. Compute Npk from the provided nsk and assert it is equal to the provided Npk + let expected_Npk = NullifierPublicKey::from(nsk); + assert_eq!(&expected_Npk, Npk); + // 2. Compute the commitment of the pre_state account using the provided Npk + let commitment_pre = Commitment::new(Npk, &pre_states[i].account); + // 3. Verify that the commitment belongs to the global commitment set + assert!(verify_membership_proof( + &commitment_pre, + membership_proof, + &commitment_set_digest, + )); + // At this point the account is correctly authenticated as a private account. + // Assert that `pre_states` marked this account as authenticated. + assert!(pre_states[i].is_authorized); + // Compute the nullifier of the pre state version of this private account + // and include it in the `new_nullifiers` list. + let nullifier = Nullifier::new(&commitment_pre, nsk); + new_nullifiers.push(nullifier); + } else if visibility_mask[i] == 2 { + assert_eq!(pre_states[i].account, Account::default()); + assert!(!pre_states[i].is_authorized); + } else { + panic!(); + } + + // Update the nonce for the post state of this private account. + let mut post_with_updated_nonce = post_states[i].clone(); + post_with_updated_nonce.nonce = *new_nonce; + + // Compute the commitment of the post state of the private account, + // with the updated nonce, and include it in the `new_commitments` list. + let commitment_post = Commitment::new(Npk, &post_with_updated_nonce); + new_commitments.push(commitment_post); + + // Encrypt the post state of the private account with the updated + // nonce and include it in the `encrypted_private_post_states` list. + // + let encrypted_account = EncryptedAccountData::new(&post_with_updated_nonce, esk, Npk, Ipk); + encrypted_private_post_states.push(encrypted_account); + } + } + + let output = PrivacyPreservingCircuitOutput { + public_pre_states, + public_post_states, + encrypted_private_post_states, + new_commitments, + new_nullifiers, + commitment_set_digest, + }; + + env::commit(&output); +} diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 5bbdcd9..08cad42 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -1,10 +1,10 @@ -use nssa_core::account::{Account, Commitment, Nonce, Nullifier}; +use nssa_core::{ + EncryptedAccountData, + account::{Account, Commitment, Nonce, Nullifier}, +}; use crate::Address; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EncryptedAccountData; - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { pub(crate) public_addresses: Vec
, diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index b0cdbe2..33b1aa5 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,10 +1,9 @@ use std::collections::{HashMap, HashSet}; +use nssa_core::{CommitmentSetDigest, EncryptedAccountData}; use nssa_core::account::{Account, AccountWithMetadata}; use crate::error::NssaError; -use crate::privacy_preserving_transaction::message::EncryptedAccountData; -use crate::state::CommitmentSetDigest; use crate::{Address, V01State}; use super::message::Message; @@ -45,7 +44,7 @@ impl PrivacyPreservingTransaction { )); } - // Check there are no duplicate commitments in the new_nullifiers list + // Check there are no duplicate commitments in the new_commitments list if n_unique(&message.new_commitments) != message.new_commitments.len() { return Err(NssaError::InvalidInput( "Duplicate commitments found in message".into(), @@ -61,7 +60,7 @@ impl PrivacyPreservingTransaction { } // Check the signatures are valid - if !witness_set.is_valid_for(message) { + if !witness_set.signatures_are_valid_for(message) { return Err(NssaError::InvalidInput( "Invalid signature for given message and public key".into(), )); @@ -89,7 +88,7 @@ impl PrivacyPreservingTransaction { let set_commitment = state.commitment_set_digest(); // 4. Proof verification - check_privacy_preserving_circuit_execution_proof_is_valid( + check_privacy_preserving_circuit_proof_is_valid( witness_set.proof, &public_pre_states, &message.public_post_states, @@ -130,7 +129,7 @@ impl PrivacyPreservingTransaction { } } -fn check_privacy_preserving_circuit_execution_proof_is_valid( +fn check_privacy_preserving_circuit_proof_is_valid( proof: (), public_pre_states: &[AccountWithMetadata], public_post_states: &[Account], diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 7db63fd..190099d 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -15,7 +15,7 @@ impl WitnessSet { todo!() } - pub fn is_valid_for(&self, message: &Message) -> bool { + pub fn signatures_are_valid_for(&self, message: &Message) -> bool { todo!() } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 53e5c91..aa42f3d 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -4,13 +4,13 @@ use crate::{ public_transaction::PublicTransaction, }; use nssa_core::{ + CommitmentSetDigest, account::{Account, Commitment, Nullifier}, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; use std::collections::{HashMap, HashSet}; struct CommitmentSet(HashSet); -pub type CommitmentSetDigest = [u32; 8]; impl CommitmentSet { fn extend(&mut self, commitments: Vec) { @@ -22,6 +22,7 @@ impl CommitmentSet { [0; 8] } } + type NullifierSet = HashSet; pub struct V01State { From acbde736f0235c4f44fc53e90da1ed5df8e707fb Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 09:21:07 -0300 Subject: [PATCH 09/46] wip --- nssa/core/src/account/commitment.rs | 10 ++- nssa/core/src/account/encoding.rs | 47 ++++++++++++ nssa/core/src/account/mod.rs | 2 + nssa/core/src/account/nullifier.rs | 6 ++ .../privacy_preserving_transaction/message.rs | 76 ++++++++++++++++++- .../transaction.rs | 2 +- .../witness_set.rs | 13 +++- 7 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 nssa/core/src/account/encoding.rs diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index 8f1b072..a288c0a 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -1,13 +1,19 @@ +use risc0_zkvm::{ + serde::to_vec, + sha::{Impl, Sha256}, +}; use serde::{Deserialize, Serialize}; use crate::account::{Account, NullifierPublicKey}; - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct Commitment([u8; 32]); impl Commitment { pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self { - todo!() + let mut bytes = Vec::new(); + bytes.extend_from_slice(&Npk.to_bytes()); + bytes.extend_from_slice(&account.to_bytes()); + Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } } diff --git a/nssa/core/src/account/encoding.rs b/nssa/core/src/account/encoding.rs new file mode 100644 index 0000000..c560a9d --- /dev/null +++ b/nssa/core/src/account/encoding.rs @@ -0,0 +1,47 @@ +use risc0_zkvm::{ + serde::to_vec, + sha::{Impl, Sha256}, +}; + +use crate::account::Account; + +impl Account { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + for word in &self.program_owner { + bytes.extend_from_slice(&word.to_le_bytes()); + } + bytes.extend_from_slice(&self.balance.to_le_bytes()); + bytes.extend_from_slice(&self.nonce.to_le_bytes()); + let hashed_data: [u8; 32] = Impl::hash_bytes(&self.data).as_bytes().try_into().unwrap(); + bytes.extend_from_slice(&hashed_data); + bytes + } +} + +#[cfg(test)] +mod tests { + use crate::account::Account; + + #[test] + fn test_enconding() { + let account = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 123456789012345678901234567890123456, + nonce: 42, + data: b"hola mundo".to_vec(), + }; + + // program owner || balance || nonce || hash(data) + let expected_bytes = [ + 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, + 0, 0, 0, 192, 186, 220, 114, 113, 65, 236, 234, 222, 15, 215, 191, 227, 198, 23, 0, 42, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 137, 65, 102, 211, 51, 100, 53, 200, + 0, 190, 163, 111, 242, 27, 41, 234, 168, 1, 165, 47, 88, 76, 0, 108, 73, 40, 154, 13, + 207, 110, 47, + ]; + + let bytes = account.to_bytes(); + assert_eq!(bytes, expected_bytes); + } +} diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account/mod.rs index 722a4fb..1358eb1 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account/mod.rs @@ -4,11 +4,13 @@ use crate::program::ProgramId; mod commitment; mod nullifier; +mod encoding; pub use commitment::Commitment; pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey}; pub type Nonce = u128; +// TODO: Consider changing `Data` to `Vec` for r0 friendlinenss type Data = Vec; /// Account to be used both in public and private contexts diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 600093c..155afe2 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -5,6 +5,12 @@ use crate::account::Commitment; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct NullifierPublicKey([u8; 32]); +impl NullifierPublicKey { + pub(crate) fn to_bytes(&self) -> [u8; 32] { + self.0 + } +} + impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(_value: &NullifierSecretKey) -> Self { todo!() diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 08cad42..b2461c1 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -10,7 +10,81 @@ pub struct Message { pub(crate) public_addresses: Vec
, pub(crate) nonces: Vec, pub(crate) public_post_states: Vec, - pub(crate) encrypted_private_post_states: EncryptedAccountData, + pub(crate) encrypted_private_post_states: Vec, pub(crate) new_commitments: Vec, pub(crate) new_nullifiers: Vec, } + +impl Message { + pub fn new( + public_addresses: Vec
, + nonces: Vec, + public_post_states: Vec, + encrypted_private_post_states: Vec, + new_commitments: Vec, + new_nullifiers: Vec, + ) -> Self { + Self { + public_addresses, + nonces, + public_post_states, + encrypted_private_post_states, + new_commitments, + new_nullifiers, + } + } +} + +#[cfg(test)] +mod tests { + use nssa_core::account::{ + Account, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, + }; + + use crate::{Address, privacy_preserving_transaction::message::Message}; + + #[test] + fn test_constructor() { + let account1 = Account::default(); + let account2 = Account::default(); + + let nsk1 = [11; 32]; + let nsk2 = [12; 32]; + + let Npk1 = NullifierPublicKey::from(&nsk1); + let Npk2 = NullifierPublicKey::from(&nsk2); + + let public_addresses = vec![Address::new([1; 32])]; + + let nonces = vec![1, 2, 3]; + + let public_post_states = vec![Account::default()]; + + let encrypted_private_post_states = Vec::new(); + + let new_commitments = vec![Commitment::new(&Npk2, &account2)]; + + let old_commitment = Commitment::new(&Npk1, &account1); + let new_nullifiers = vec![Nullifier::new(&old_commitment, &nsk1)]; + + let expected_message = Message { + public_addresses: public_addresses.clone(), + nonces: nonces.clone(), + public_post_states: public_post_states.clone(), + encrypted_private_post_states: encrypted_private_post_states.clone(), + new_commitments: new_commitments.clone(), + new_nullifiers: new_nullifiers.clone(), + }; + + let message = Message::new( + public_addresses, + nonces, + public_post_states, + encrypted_private_post_states, + new_commitments, + new_nullifiers, + ); + + assert_eq!(message, expected_message); + } +} diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 33b1aa5..055cc02 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -133,7 +133,7 @@ fn check_privacy_preserving_circuit_proof_is_valid( proof: (), public_pre_states: &[AccountWithMetadata], public_post_states: &[Account], - encrypted_private_post_states: &EncryptedAccountData, + encrypted_private_post_states: &[EncryptedAccountData], new_commitments: &[nssa_core::account::Commitment], new_nullifiers: &[nssa_core::account::Nullifier], commitment_set_digest: CommitmentSetDigest, diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 190099d..1405601 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,21 +1,26 @@ -use crate::{privacy_preserving_transaction::message::Message, PrivateKey, PublicKey, Signature}; - +use crate::{PrivateKey, PublicKey, Signature, privacy_preserving_transaction::message::Message}; type Proof = (); #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, - pub(super) proof: Proof + pub(super) proof: Proof, } - impl WitnessSet { pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self { todo!() } pub fn signatures_are_valid_for(&self, message: &Message) -> bool { + // let message_bytes = message.to_bytes(); + // for (signature, public_key) in self.signatures_and_public_keys() { + // if !signature.is_valid_for(&message_bytes, public_key) { + // return false; + // } + // } + // true todo!() } From 2813d536bb40add40dc71785e8a48971e0870405 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 09:50:11 -0300 Subject: [PATCH 10/46] add nullifier constructor --- nssa/core/src/account/commitment.rs | 2 +- nssa/core/src/account/encoding.rs | 15 +++++++++++++- nssa/core/src/account/nullifier.rs | 31 +++++++++++++++++++++-------- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index a288c0a..b9d9abf 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::account::{Account, NullifierPublicKey}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -pub struct Commitment([u8; 32]); +pub struct Commitment(pub(super) [u8; 32]); impl Commitment { pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self { diff --git a/nssa/core/src/account/encoding.rs b/nssa/core/src/account/encoding.rs index c560a9d..982cdee 100644 --- a/nssa/core/src/account/encoding.rs +++ b/nssa/core/src/account/encoding.rs @@ -3,7 +3,7 @@ use risc0_zkvm::{ sha::{Impl, Sha256}, }; -use crate::account::Account; +use crate::account::{Account, Commitment, NullifierPublicKey}; impl Account { pub fn to_bytes(&self) -> Vec { @@ -19,6 +19,19 @@ impl Account { } } +impl Commitment { + pub(crate) fn to_bytes(&self) -> [u8; 32] { + self.0 + } +} + + +impl NullifierPublicKey { + pub(crate) fn to_bytes(&self) -> [u8; 32] { + self.0 + } +} + #[cfg(test)] mod tests { use crate::account::Account; diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 155afe2..8df2a3e 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -1,15 +1,10 @@ +use risc0_zkvm::sha::{Impl, Sha256}; use serde::{Deserialize, Serialize}; use crate::account::Commitment; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct NullifierPublicKey([u8; 32]); - -impl NullifierPublicKey { - pub(crate) fn to_bytes(&self) -> [u8; 32] { - self.0 - } -} +pub struct NullifierPublicKey(pub(super) [u8; 32]); impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(_value: &NullifierSecretKey) -> Self { @@ -24,6 +19,26 @@ pub struct Nullifier([u8; 32]); impl Nullifier { pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { - todo!() + let mut bytes = Vec::new(); + bytes.extend_from_slice(&commitment.to_bytes()); + bytes.extend_from_slice(nsk); + Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_constructor() { + let commitment = Commitment((0..32u8).collect::>().try_into().unwrap()); + let nsk = [0x42; 32]; + let expected_nullifier = Nullifier([ + 97, 87, 111, 191, 0, 44, 125, 145, 237, 104, 31, 230, 203, 254, 68, 176, 126, 17, 240, + 205, 249, 143, 11, 43, 15, 198, 189, 219, 191, 49, 36, 61, + ]); + let nullifier = Nullifier::new(&commitment, &nsk); + assert_eq!(nullifier, expected_nullifier); } } From 1e1ab787bcc052a6c72bbe0d1ac9a8a8ff78c73d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 11:53:43 -0300 Subject: [PATCH 11/46] add nullifier pk from secret --- nssa/core/src/account/commitment.rs | 2 +- nssa/core/src/account/encoding.rs | 4 ++-- nssa/core/src/account/nullifier.rs | 28 +++++++++++++++++++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index b9d9abf..9a3fc20 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -12,7 +12,7 @@ pub struct Commitment(pub(super) [u8; 32]); impl Commitment { pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self { let mut bytes = Vec::new(); - bytes.extend_from_slice(&Npk.to_bytes()); + bytes.extend_from_slice(&Npk.to_byte_array()); bytes.extend_from_slice(&account.to_bytes()); Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } diff --git a/nssa/core/src/account/encoding.rs b/nssa/core/src/account/encoding.rs index 982cdee..91737b6 100644 --- a/nssa/core/src/account/encoding.rs +++ b/nssa/core/src/account/encoding.rs @@ -20,14 +20,14 @@ impl Account { } impl Commitment { - pub(crate) fn to_bytes(&self) -> [u8; 32] { + pub(crate) fn to_byte_array(&self) -> [u8; 32] { self.0 } } impl NullifierPublicKey { - pub(crate) fn to_bytes(&self) -> [u8; 32] { + pub(crate) fn to_byte_array(&self) -> [u8; 32] { self.0 } } diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 8df2a3e..13f81db 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -7,8 +7,16 @@ use crate::account::Commitment; pub struct NullifierPublicKey(pub(super) [u8; 32]); impl From<&NullifierSecretKey> for NullifierPublicKey { - fn from(_value: &NullifierSecretKey) -> Self { - todo!() + fn from(value: &NullifierSecretKey) -> Self { + let mut bytes = Vec::new(); + const PREFIX: &[u8; 9] = b"NSSA_keys"; + const SUFFIX_1: &[u8; 1] = &[7]; + const SUFFIX_2: &[u8; 22] = &[0; 22]; + bytes.extend_from_slice(PREFIX); + bytes.extend_from_slice(value); + bytes.extend_from_slice(SUFFIX_1); + bytes.extend_from_slice(SUFFIX_2); + Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } } @@ -20,7 +28,7 @@ pub struct Nullifier([u8; 32]); impl Nullifier { pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { let mut bytes = Vec::new(); - bytes.extend_from_slice(&commitment.to_bytes()); + bytes.extend_from_slice(&commitment.to_byte_array()); bytes.extend_from_slice(nsk); Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } @@ -41,4 +49,18 @@ mod tests { let nullifier = Nullifier::new(&commitment, &nsk); assert_eq!(nullifier, expected_nullifier); } + + #[test] + fn test_from_secret_key() { + let nsk = [ + 50, 139, 109, 225, 82, 86, 80, 108, 140, 248, 232, 229, 96, 80, 148, 250, 15, 9, 155, + 44, 196, 224, 115, 180, 160, 44, 113, 133, 15, 196, 253, 42, + ]; + let expected_Npk = NullifierPublicKey([ + 38, 90, 215, 216, 195, 66, 157, 77, 161, 59, 121, 18, 118, 37, 57, 199, 189, 251, 95, + 130, 12, 9, 171, 169, 140, 221, 87, 242, 46, 243, 111, 85, + ]); + let Npk = NullifierPublicKey::from(&nsk); + assert_eq!(Npk, expected_Npk); + } } From a185e522032b5942e1fdfe54e05be7f652e60c9d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 14:28:26 -0300 Subject: [PATCH 12/46] add encodings --- nssa/Cargo.toml | 2 +- nssa/core/Cargo.toml | 5 + nssa/core/src/account/commitment.rs | 16 +- nssa/core/src/account/encoding.rs | 141 ++++++++++++++++-- nssa/core/src/account/nullifier.rs | 10 +- nssa/core/src/error.rs | 12 ++ nssa/core/src/lib.rs | 20 +++ nssa/src/error.rs | 5 + .../encoding.rs | 140 +++++++++++++++++ .../privacy_preserving_transaction/message.rs | 2 +- .../src/privacy_preserving_transaction/mod.rs | 1 + .../transaction.rs | 5 + .../witness_set.rs | 2 +- nssa/src/public_transaction/encoding.rs | 12 +- nssa/src/state.rs | 22 ++- 15 files changed, 366 insertions(+), 29 deletions(-) create mode 100644 nssa/core/src/error.rs create mode 100644 nssa/src/privacy_preserving_transaction/encoding.rs diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index fef52ef..19e751a 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] thiserror = "2.0.12" risc0-zkvm = "2.3.1" -nssa-core = { path = "core" } +nssa-core = { path = "core", features=["host"]} program-methods = { path = "program_methods" } serde = "1.0.219" sha2 = "0.10.9" diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index feb907d..e74eb90 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -6,3 +6,8 @@ edition = "2024" [dependencies] risc0-zkvm = "2.3.1" serde = { version = "1.0", default-features = false } +thiserror = { version = "2.0.12", optional = true } + +[features] +default = [] +host = ["thiserror"] diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index 9a3fc20..60034cb 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -13,7 +13,21 @@ impl Commitment { pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self { let mut bytes = Vec::new(); bytes.extend_from_slice(&Npk.to_byte_array()); - bytes.extend_from_slice(&account.to_bytes()); + let account_bytes_with_hashed_data = { + let mut this = Vec::new(); + for word in &account.program_owner { + this.extend_from_slice(&word.to_le_bytes()); + } + this.extend_from_slice(&account.balance.to_le_bytes()); + this.extend_from_slice(&account.nonce.to_le_bytes()); + let hashed_data: [u8; 32] = Impl::hash_bytes(&account.data) + .as_bytes() + .try_into() + .unwrap(); + this.extend_from_slice(&hashed_data); + this + }; + bytes.extend_from_slice(&account_bytes_with_hashed_data); Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } } diff --git a/nssa/core/src/account/encoding.rs b/nssa/core/src/account/encoding.rs index 91737b6..ae96ebe 100644 --- a/nssa/core/src/account/encoding.rs +++ b/nssa/core/src/account/encoding.rs @@ -1,9 +1,14 @@ -use risc0_zkvm::{ - serde::to_vec, - sha::{Impl, Sha256}, -}; +use risc0_zkvm::sha::{Impl, Sha256}; -use crate::account::{Account, Commitment, NullifierPublicKey}; +#[cfg(feature = "host")] +use std::io::Cursor; + +#[cfg(feature = "host")] +use std::io::Read; + +use crate::account::{Account, Commitment, Nullifier, NullifierPublicKey}; +#[cfg(feature = "host")] +use crate::error::NssaCoreError; impl Account { pub fn to_bytes(&self) -> Vec { @@ -13,28 +18,82 @@ impl Account { } bytes.extend_from_slice(&self.balance.to_le_bytes()); bytes.extend_from_slice(&self.nonce.to_le_bytes()); - let hashed_data: [u8; 32] = Impl::hash_bytes(&self.data).as_bytes().try_into().unwrap(); - bytes.extend_from_slice(&hashed_data); + let data_length: u32 = self.data.len() as u32; + bytes.extend_from_slice(&data_length.to_le_bytes()); + bytes.extend_from_slice(self.data.as_slice()); bytes } + + #[cfg(feature = "host")] + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let mut u32_bytes = [0u8; 4]; + let mut u128_bytes = [0u8; 16]; + + // program owner + let mut program_owner = [0u32; 8]; + for word in &mut program_owner { + cursor.read_exact(&mut u32_bytes)?; + *word = u32::from_le_bytes(u32_bytes); + } + + // balance + cursor.read_exact(&mut u128_bytes)?; + let balance = u128::from_le_bytes(u128_bytes); + + // nonce + cursor.read_exact(&mut u128_bytes)?; + let nonce = u128::from_le_bytes(u128_bytes); + + //data + cursor.read_exact(&mut u32_bytes)?; + let data_length = u32::from_le_bytes(u32_bytes); + let mut data = vec![0; data_length as usize]; + cursor.read_exact(&mut data)?; + + Ok(Self { + program_owner, + balance, + data, + nonce, + }) + } } impl Commitment { - pub(crate) fn to_byte_array(&self) -> [u8; 32] { + pub fn to_byte_array(&self) -> [u8; 32] { + self.0 + } + + #[cfg(feature = "host")] + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let mut bytes = [0u8; 32]; + cursor.read_exact(&mut bytes)?; + Ok(Self(bytes)) + } +} + +impl NullifierPublicKey { + pub fn to_byte_array(&self) -> [u8; 32] { self.0 } } - -impl NullifierPublicKey { - pub(crate) fn to_byte_array(&self) -> [u8; 32] { +#[cfg(feature = "host")] +impl Nullifier { + pub fn to_byte_array(&self) -> [u8; 32] { self.0 } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let mut bytes = [0u8; 32]; + cursor.read_exact(&mut bytes)?; + Ok(Self(bytes)) + } } #[cfg(test)] mod tests { - use crate::account::Account; + use super::*; #[test] fn test_enconding() { @@ -49,12 +108,64 @@ mod tests { let expected_bytes = [ 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 192, 186, 220, 114, 113, 65, 236, 234, 222, 15, 215, 191, 227, 198, 23, 0, 42, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 137, 65, 102, 211, 51, 100, 53, 200, - 0, 190, 163, 111, 242, 27, 41, 234, 168, 1, 165, 47, 88, 76, 0, 108, 73, 40, 154, 13, - 207, 110, 47, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 104, 111, 108, 97, 32, 109, + 117, 110, 100, 111, ]; let bytes = account.to_bytes(); assert_eq!(bytes, expected_bytes); } + + #[test] + fn test_commitment_to_bytes() { + let commitment = Commitment((0..32).collect::>().try_into().unwrap()); + let expected_bytes: [u8; 32] = (0..32).collect::>().try_into().unwrap(); + + let bytes = commitment.to_byte_array(); + assert_eq!(expected_bytes, bytes); + } + + #[test] + fn test_nullifier_to_bytes() { + let nullifier = Nullifier((0..32).collect::>().try_into().unwrap()); + let expected_bytes: [u8; 32] = (0..32).collect::>().try_into().unwrap(); + + let bytes = nullifier.to_byte_array(); + assert_eq!(expected_bytes, bytes); + } + + #[cfg(feature = "host")] + #[test] + fn test_commitment_to_bytes_roundtrip() { + let commitment = Commitment((0..32).collect::>().try_into().unwrap()); + let bytes = commitment.to_byte_array(); + let mut cursor = Cursor::new(bytes.as_ref()); + let commitment_from_cursor = Commitment::from_cursor(&mut cursor).unwrap(); + assert_eq!(commitment, commitment_from_cursor); + } + + #[cfg(feature = "host")] + #[test] + fn test_nullifier_to_bytes_roundtrip() { + let nullifier = Nullifier((0..32).collect::>().try_into().unwrap()); + let bytes = nullifier.to_byte_array(); + let mut cursor = Cursor::new(bytes.as_ref()); + let nullifier_from_cursor = Nullifier::from_cursor(&mut cursor).unwrap(); + assert_eq!(nullifier, nullifier_from_cursor); + } + + #[cfg(feature = "host")] + #[test] + fn test_account_to_bytes_roundtrip() { + let account = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 123456789012345678901234567890123456, + nonce: 42, + data: b"hola mundo".to_vec(), + }; + let bytes = account.to_bytes(); + let mut cursor = Cursor::new(bytes.as_ref()); + let account_from_cursor = Account::from_cursor(&mut cursor).unwrap(); + assert_eq!(account, account_from_cursor); + } } diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 13f81db..06c3e22 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -23,7 +23,7 @@ impl From<&NullifierSecretKey> for NullifierPublicKey { pub type NullifierSecretKey = [u8; 32]; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -pub struct Nullifier([u8; 32]); +pub struct Nullifier(pub(super) [u8; 32]); impl Nullifier { pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { @@ -53,12 +53,12 @@ mod tests { #[test] fn test_from_secret_key() { let nsk = [ - 50, 139, 109, 225, 82, 86, 80, 108, 140, 248, 232, 229, 96, 80, 148, 250, 15, 9, 155, - 44, 196, 224, 115, 180, 160, 44, 113, 133, 15, 196, 253, 42, + 57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30, + 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, ]; let expected_Npk = NullifierPublicKey([ - 38, 90, 215, 216, 195, 66, 157, 77, 161, 59, 121, 18, 118, 37, 57, 199, 189, 251, 95, - 130, 12, 9, 171, 169, 140, 221, 87, 242, 46, 243, 111, 85, + 202, 120, 42, 189, 194, 218, 78, 244, 31, 6, 108, 169, 29, 61, 22, 221, 69, 138, 197, + 161, 241, 39, 142, 242, 242, 50, 188, 201, 99, 28, 176, 238, ]); let Npk = NullifierPublicKey::from(&nsk); assert_eq!(Npk, expected_Npk); diff --git a/nssa/core/src/error.rs b/nssa/core/src/error.rs new file mode 100644 index 0000000..b9e5020 --- /dev/null +++ b/nssa/core/src/error.rs @@ -0,0 +1,12 @@ +use std::io; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NssaCoreError { + #[error("Invalid transaction: {0}")] + DeserializationError(String), + + #[error("IO error: {0}")] + Io(#[from] io::Error), +} diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 54556e5..c0f8d58 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "host")] +use crate::error::NssaCoreError; use crate::{ account::{ Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, @@ -8,9 +10,15 @@ use crate::{ program::{ProgramId, ProgramOutput}, }; +#[cfg(feature = "host")] +use std::io::Cursor; + pub mod account; pub mod program; +#[cfg(feature = "host")] +pub mod error; + pub type CommitmentSetDigest = [u32; 8]; pub type MembershipProof = Vec<[u8; 32]>; pub fn verify_membership_proof( @@ -51,6 +59,18 @@ impl EncryptedAccountData { // TODO: implement Self } + + #[cfg(feature = "host")] + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + todo!() + } +} + +impl EncryptedAccountData { + pub fn to_bytes(&self) -> Vec { + // TODO: implement + vec![0] + } } #[derive(Serialize, Deserialize)] diff --git a/nssa/src/error.rs b/nssa/src/error.rs index de4a7cc..038d217 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -31,4 +31,9 @@ pub enum NssaError { #[error("Risc0 error: {0}")] ProgramProveFailed(String), + #[error("Invalid transaction: {0}")] + TransactionDeserializationError(String), + + #[error("Core error")] + Core(#[from] nssa_core::error::NssaCoreError), } diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs new file mode 100644 index 0000000..2a19ebe --- /dev/null +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -0,0 +1,140 @@ +use std::io::{Cursor, Read}; + +use nssa_core::{ + EncryptedAccountData, + account::{Account, Commitment, Nullifier}, +}; + +use crate::{Address, error::NssaError}; + +use super::message::Message; + +const MESSAGE_ENCODING_PREFIX_LEN: usize = 37; +const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = + b"NSSA/v0.1/TxMessage/PrivacyPreserving"; + +impl Message { + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); + + // Public addresses + let public_addresses_len: u32 = self.public_addresses.len() as u32; + bytes.extend_from_slice(&public_addresses_len.to_le_bytes()); + for address in &self.public_addresses { + bytes.extend_from_slice(address.value()); + } + // Nonces + let nonces_len = self.nonces.len() as u32; + bytes.extend(&nonces_len.to_le_bytes()); + for nonce in &self.nonces { + bytes.extend(&nonce.to_le_bytes()); + } + // Public post states + let public_post_states_len: u32 = self.public_post_states.len() as u32; + bytes.extend_from_slice(&public_post_states_len.to_le_bytes()); + for account in &self.public_post_states { + bytes.extend_from_slice(&account.to_bytes()); + } + + // Encrypted post states + let encrypted_accounts_post_states_len: u32 = + self.encrypted_private_post_states.len() as u32; + for encrypted_account in &self.encrypted_private_post_states { + bytes.extend_from_slice(&encrypted_account.to_bytes()); + } + + // New commitments + let new_commitments_len: u32 = self.new_commitments.len() as u32; + for commitment in &self.new_commitments { + bytes.extend_from_slice(&commitment.to_byte_array()); + } + + // New nullifiers + let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32; + for nullifier in &self.new_nullifiers { + bytes.extend_from_slice(&nullifier.to_byte_array()); + } + bytes + } + + pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let prefix = { + let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN]; + cursor.read_exact(&mut this)?; + this + }; + let prefix = { + let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN]; + cursor.read_exact(&mut this)?; + this + }; + if &prefix != MESSAGE_ENCODING_PREFIX { + return Err(NssaError::TransactionDeserializationError( + "Invalid privacy preserving message prefix".to_string(), + )); + } + + let mut len_bytes = [0u8; 4]; + + // Public addresses + cursor.read_exact(&mut len_bytes)?; + let public_addresses_len = u32::from_le_bytes(len_bytes) as usize; + let mut public_addresses = Vec::with_capacity(public_addresses_len); + for _ in 0..public_addresses_len { + let mut value = [0u8; 32]; + cursor.read_exact(&mut value)?; + public_addresses.push(Address::new(value)) + } + + // Nonces + cursor.read_exact(&mut len_bytes)?; + let nonces_len = u32::from_le_bytes(len_bytes) as usize; + let mut nonces = Vec::with_capacity(nonces_len as usize); + for _ in 0..nonces_len { + let mut buf = [0u8; 16]; + cursor.read_exact(&mut buf)?; + nonces.push(u128::from_le_bytes(buf)) + } + + // Public post states + cursor.read_exact(&mut len_bytes)?; + let public_post_states_len = u32::from_le_bytes(len_bytes) as usize; + let mut public_post_states = Vec::with_capacity(public_post_states_len); + for _ in 0..public_post_states_len { + public_post_states.push(Account::from_cursor(cursor)?); + } + + // Encrypted private post states + cursor.read_exact(&mut len_bytes)?; + let encrypted_len = u32::from_le_bytes(len_bytes) as usize; + let mut encrypted_private_post_states = Vec::with_capacity(encrypted_len); + for _ in 0..encrypted_len { + encrypted_private_post_states.push(EncryptedAccountData::from_cursor(cursor)?); + } + + // New commitments + cursor.read_exact(&mut len_bytes)?; + let new_commitments_len = u32::from_le_bytes(len_bytes) as usize; + let mut new_commitments = Vec::with_capacity(new_commitments_len); + for _ in 0..new_commitments_len { + new_commitments.push(Commitment::from_cursor(cursor)?); + } + + // New nullifiers + cursor.read_exact(&mut len_bytes)?; + let new_nullifiers_len = u32::from_le_bytes(len_bytes) as usize; + let mut new_nullifiers = Vec::with_capacity(new_nullifiers_len); + for _ in 0..new_nullifiers_len { + new_nullifiers.push(Nullifier::from_cursor(cursor)?); + } + + Ok(Self { + public_addresses, + nonces, + public_post_states, + encrypted_private_post_states, + new_commitments, + new_nullifiers, + }) + } +} diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index b2461c1..f6e5e65 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -36,7 +36,7 @@ impl Message { } #[cfg(test)] -mod tests { +pub mod tests { use nssa_core::account::{ Account, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, }; diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 2d3bba3..8d77b90 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -1,5 +1,6 @@ mod transaction; mod message; mod witness_set; +mod encoding; pub use transaction::PrivacyPreservingTransaction; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 055cc02..1b97591 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -146,3 +146,8 @@ fn n_unique(data: &[T]) -> usize { let set: HashSet<&T> = data.iter().collect(); set.len() } + +#[cfg(test)] +mod tests { + +} diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 1405601..285a475 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -9,7 +9,7 @@ pub struct WitnessSet { } impl WitnessSet { - pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self { + pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self { todo!() } diff --git a/nssa/src/public_transaction/encoding.rs b/nssa/src/public_transaction/encoding.rs index 3c004a0..0e3c8ba 100644 --- a/nssa/src/public_transaction/encoding.rs +++ b/nssa/src/public_transaction/encoding.rs @@ -8,8 +8,9 @@ use crate::{ public_transaction::{Message, WitnessSet}, }; -const MESSAGE_ENCODING_PREFIX_LEN: usize = 19; -const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"NSSA/v0.1/TxMessage"; +const MESSAGE_ENCODING_PREFIX_LEN: usize = 37; +const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = + b"NSSA/v0.1/TxMessage/Public\0\0\0\0\0\0\0\0\0\0\0"; impl Message { /// Serializes a `Message` into bytes in the following layout: @@ -52,7 +53,12 @@ impl Message { cursor.read_exact(&mut this)?; this }; - assert_eq!(&prefix, MESSAGE_ENCODING_PREFIX); + if &prefix != MESSAGE_ENCODING_PREFIX { + return Err(NssaError::TransactionDeserializationError( + "Invalid public message prefix".to_string(), + )); + } + let program_id: ProgramId = { let mut this = [0u32; 8]; for item in &mut this { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index aa42f3d..101fa25 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -21,6 +21,10 @@ impl CommitmentSet { // TODO: implement [0; 8] } + + fn contains(&self, commitment: &Commitment) -> bool { + self.0.contains(commitment) + } } type NullifierSet = HashSet; @@ -139,14 +143,28 @@ impl V01State { &self, new_commitments: &[Commitment], ) -> Result<(), NssaError> { - todo!() + for commitment in new_commitments.iter() { + if self.private_state.0.contains(commitment) { + return Err(NssaError::InvalidInput( + "Commitment already seen".to_string(), + )); + } + } + Ok(()) } pub(crate) fn check_nullifiers_are_new( &self, new_nullifiers: &[Nullifier], ) -> Result<(), NssaError> { - todo!() + for nullifier in new_nullifiers.iter() { + if self.private_state.1.contains(nullifier) { + return Err(NssaError::InvalidInput( + "Nullifier already seen".to_string(), + )); + } + } + Ok(()) } } From 330d79379f9a4d4d74b399e3b23200b0fbeb6f1d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 14:55:50 -0300 Subject: [PATCH 13/46] add message serialization roundtrip test --- nssa/core/src/lib.rs | 7 ++-- .../encoding.rs | 11 +++--- .../privacy_preserving_transaction/message.rs | 38 ++++++++++++++----- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index c0f8d58..a247d19 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -47,7 +47,7 @@ impl Tag { } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct EncryptedAccountData; +pub struct EncryptedAccountData(u8); impl EncryptedAccountData { pub fn new( @@ -57,12 +57,13 @@ impl EncryptedAccountData { Ivk: &IncomingViewingPublicKey, ) -> Self { // TODO: implement - Self + Self(0) } #[cfg(feature = "host")] pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - todo!() + let dummy_value = EncryptedAccountData(0); + Ok(dummy_value) } } diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index 2a19ebe..78cb47c 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -39,18 +39,21 @@ impl Message { // Encrypted post states let encrypted_accounts_post_states_len: u32 = self.encrypted_private_post_states.len() as u32; + bytes.extend_from_slice(&encrypted_accounts_post_states_len.to_le_bytes()); for encrypted_account in &self.encrypted_private_post_states { bytes.extend_from_slice(&encrypted_account.to_bytes()); } // New commitments let new_commitments_len: u32 = self.new_commitments.len() as u32; + bytes.extend_from_slice(&new_commitments_len.to_le_bytes()); for commitment in &self.new_commitments { bytes.extend_from_slice(&commitment.to_byte_array()); } // New nullifiers let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32; + bytes.extend_from_slice(&new_nullifiers_len.to_le_bytes()); for nullifier in &self.new_nullifiers { bytes.extend_from_slice(&nullifier.to_byte_array()); } @@ -58,11 +61,6 @@ impl Message { } pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let prefix = { - let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN]; - cursor.read_exact(&mut this)?; - this - }; let prefix = { let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN]; cursor.read_exact(&mut this)?; @@ -138,3 +136,6 @@ impl Message { }) } } + +#[cfg(test)] +mod tests {} diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index f6e5e65..5250e07 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -37,14 +37,15 @@ impl Message { #[cfg(test)] pub mod tests { + use std::io::Cursor; + use nssa_core::account::{ Account, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, }; use crate::{Address, privacy_preserving_transaction::message::Message}; - #[test] - fn test_constructor() { + fn message_for_tests() -> Message { let account1 = Account::default(); let account2 = Account::default(); @@ -67,24 +68,41 @@ pub mod tests { let old_commitment = Commitment::new(&Npk1, &account1); let new_nullifiers = vec![Nullifier::new(&old_commitment, &nsk1)]; - let expected_message = Message { + Message { public_addresses: public_addresses.clone(), nonces: nonces.clone(), public_post_states: public_post_states.clone(), encrypted_private_post_states: encrypted_private_post_states.clone(), new_commitments: new_commitments.clone(), new_nullifiers: new_nullifiers.clone(), - }; + } + } + + #[test] + fn test_constructor() { + let message = message_for_tests(); + let expected_message = message.clone(); let message = Message::new( - public_addresses, - nonces, - public_post_states, - encrypted_private_post_states, - new_commitments, - new_nullifiers, + message.public_addresses, + message.nonces, + message.public_post_states, + message.encrypted_private_post_states, + message.new_commitments, + message.new_nullifiers, ); assert_eq!(message, expected_message); } + + #[test] + fn test_message_serialization_roundtrip() { + let message = message_for_tests(); + + let bytes = message.to_bytes(); + let mut cursor = Cursor::new(bytes.as_ref()); + let message_from_cursor = Message::from_cursor(&mut cursor).unwrap(); + + assert_eq!(message, message_from_cursor); + } } From 562fe2e5e60f1b9e4cd7ce4c9adc92c9c8730919 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 18:18:16 -0300 Subject: [PATCH 14/46] wip execute offchain --- nssa/Cargo.toml | 1 + nssa/core/src/account/encoding.rs | 2 + nssa/core/src/account/mod.rs | 4 +- nssa/core/src/error.rs | 2 +- nssa/core/src/lib.rs | 7 +- nssa/core/src/program.rs | 76 ++++++++++++++++++- .../src/bin/privacy_preserving_circuit.rs | 9 ++- nssa/src/lib.rs | 3 +- .../encoding.rs | 9 +-- .../privacy_preserving_transaction/message.rs | 2 +- .../src/privacy_preserving_transaction/mod.rs | 63 ++++++++++++++- .../transaction.rs | 16 +++- .../witness_set.rs | 37 ++++++--- nssa/src/program.rs | 30 +++++--- nssa/src/public_transaction/encoding.rs | 6 +- 15 files changed, 221 insertions(+), 46 deletions(-) diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 19e751a..f479999 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -12,6 +12,7 @@ serde = "1.0.219" sha2 = "0.10.9" secp256k1 = "0.31.1" rand = "0.8" +borsh = "1.5.7" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/core/src/account/encoding.rs b/nssa/core/src/account/encoding.rs index ae96ebe..5b1dd0e 100644 --- a/nssa/core/src/account/encoding.rs +++ b/nssa/core/src/account/encoding.rs @@ -1,3 +1,5 @@ +// TODO: Consider switching to deriving Borsh + use risc0_zkvm::sha::{Impl, Sha256}; #[cfg(feature = "host")] diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account/mod.rs index 1358eb1..2bb2285 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account/mod.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::program::ProgramId; mod commitment; -mod nullifier; mod encoding; +mod nullifier; pub use commitment::Commitment; pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey}; @@ -22,7 +22,7 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, diff --git a/nssa/core/src/error.rs b/nssa/core/src/error.rs index b9e5020..8f05323 100644 --- a/nssa/core/src/error.rs +++ b/nssa/core/src/error.rs @@ -4,7 +4,7 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum NssaCoreError { - #[error("Invalid transaction: {0}")] + #[error("Deserialization error: {0}")] DeserializationError(String), #[error("IO error: {0}")] diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index a247d19..bd01216 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,7 +1,9 @@ +use risc0_zkvm::serde::to_vec; use serde::{Deserialize, Serialize}; #[cfg(feature = "host")] use crate::error::NssaCoreError; + use crate::{ account::{ Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, @@ -78,8 +80,8 @@ impl EncryptedAccountData { pub struct PrivacyPreservingCircuitInput { pub program_output: ProgramOutput, pub visibility_mask: Vec, - pub private_account_data: Vec<( - Nonce, + pub private_account_nonces: Vec, + pub private_account_keys: Vec<( NullifierPublicKey, IncomingViewingPublicKey, EphemeralSecretKey, @@ -98,3 +100,4 @@ pub struct PrivacyPreservingCircuitOutput { pub new_nullifiers: Vec, pub commitment_set_digest: CommitmentSetDigest, } + diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index ff8fd57..f5615e9 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -3,6 +3,9 @@ use risc0_zkvm::serde::Deserializer; use risc0_zkvm::{DeserializeOwned, guest::env}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "host")] +use crate::error::NssaCoreError; + pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; @@ -12,12 +15,26 @@ pub struct ProgramInput { pub instruction: T, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ProgramOutput { pub pre_states: Vec, pub post_states: Vec, } +#[cfg(feature = "host")] +impl ProgramOutput { + pub fn to_bytes(&self) -> Result, NssaCoreError> { + use risc0_zkvm::serde::to_vec; + + let mut result = Vec::new(); + let b = to_vec(self).map_err(|e| NssaCoreError::DeserializationError(e.to_string()))?; + for word in &b { + result.extend_from_slice(&word.to_le_bytes()); + } + Ok(result) + } +} + pub fn read_nssa_inputs() -> ProgramInput { let pre_states: Vec = env::read(); let words: InstructionData = env::read(); @@ -86,3 +103,60 @@ pub fn validate_execution( true } + +#[cfg(test)] +mod tests { + use risc0_zkvm::Journal; + + use crate::{ + account::{Account, AccountWithMetadata}, + program::ProgramOutput, + }; + + #[test] + fn test_program_output_to_bytes_is_compatible_with_journal_decode() { + let account_pre1 = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 1112223333444455556666, + data: b"test data 1".to_vec(), + nonce: 3344556677889900, + }; + let account_pre2 = Account { + program_owner: [9, 8, 7, 6, 5, 4, 3, 2], + balance: 18446744073709551615, + data: b"test data 2".to_vec(), + nonce: 3344556677889901, + }; + let account_post1 = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 1, + data: b"other test data 1".to_vec(), + nonce: 113, + }; + let account_post2 = Account { + program_owner: [9, 8, 7, 6, 5, 4, 3, 2], + balance: 2, + data: b"other test data 2".to_vec(), + nonce: 112, + }; + + let program_output = ProgramOutput { + pre_states: vec![ + AccountWithMetadata { + account: account_pre1, + is_authorized: true, + }, + AccountWithMetadata { + account: account_pre2, + is_authorized: false, + }, + ], + post_states: vec![account_post1, account_post2], + }; + + let bytes = program_output.to_bytes().unwrap(); + let journal = Journal::new(bytes); + let decoded_program_output: ProgramOutput = journal.decode().unwrap(); + assert_eq!(program_output, decoded_program_output); + } +} diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 6caebee..f165d86 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -11,7 +11,8 @@ fn main() { let PrivacyPreservingCircuitInput { program_output, visibility_mask, - private_account_data, + private_account_nonces, + private_account_keys, private_account_auth, program_id, commitment_set_digest, @@ -36,7 +37,8 @@ fn main() { assert_eq!(visibility_mask.len(), n_accounts); let n_private_accounts = visibility_mask.iter().filter(|&&flag| flag != 0).count(); - assert_eq!(private_account_data.len(), n_private_accounts); + assert_eq!(private_account_nonces.len(), n_private_accounts); + assert_eq!(private_account_keys.len(), n_private_accounts); let n_auth_private_accounts = visibility_mask.iter().filter(|&&flag| flag == 1).count(); assert_eq!(private_account_auth.len(), n_auth_private_accounts); @@ -57,7 +59,8 @@ fn main() { public_pre_states.push(pre_states[i].clone()); public_post_states.push(post_states[i].clone()); } else { - let (new_nonce, Npk, Ipk, esk) = &private_account_data[i]; + let new_nonce = &private_account_nonces[i]; + let (Npk, Ipk, esk) = &private_account_keys[i]; // Verify authentication if visibility_mask[i] == 1 { diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 83180e9..2327514 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -1,8 +1,8 @@ mod address; pub mod error; +mod privacy_preserving_transaction; pub mod program; pub mod public_transaction; -mod privacy_preserving_transaction; mod signature; mod state; @@ -12,3 +12,4 @@ pub use signature::PrivateKey; pub use signature::PublicKey; pub use signature::Signature; pub use state::V01State; + diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index 78cb47c..3ff2953 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -1,3 +1,5 @@ +// TODO: Consider switching to deriving Borsh + use std::io::{Cursor, Read}; use nssa_core::{ @@ -9,9 +11,9 @@ use crate::{Address, error::NssaError}; use super::message::Message; -const MESSAGE_ENCODING_PREFIX_LEN: usize = 37; +const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = - b"NSSA/v0.1/TxMessage/PrivacyPreserving"; + b"\x01/NSSA/v0.1/TxMessage/"; impl Message { pub(crate) fn to_bytes(&self) -> Vec { @@ -136,6 +138,3 @@ impl Message { }) } } - -#[cfg(test)] -mod tests {} diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 5250e07..4dddc2d 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -45,7 +45,7 @@ pub mod tests { use crate::{Address, privacy_preserving_transaction::message::Message}; - fn message_for_tests() -> Message { + pub fn message_for_tests() -> Message { let account1 = Account::default(); let account2 = Account::default(); diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 8d77b90..4fed05e 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -1,6 +1,63 @@ -mod transaction; -mod message; -mod witness_set; mod encoding; +mod message; +mod transaction; +mod witness_set; pub use transaction::PrivacyPreservingTransaction; + +pub mod offchain { +// use nssa_core::{ +// account::{Account, AccountWithMetadata, NullifierSecretKey}, program::{InstructionData, ProgramOutput}, PrivacyPreservingCircuitInput +// }; +// +// use crate::{error::NssaError, program::Program}; +// + // pub type Proof = (); +// +// pub fn execute_offchain( +// pre_states: &[AccountWithMetadata], +// instruction_data: &InstructionData, +// private_account_keys: &[(NullifierSecretKey, ] +// visibility_mask: &[u8], +// commitment_set_digest: [u32; 8], +// program: Program, +// ) -> Result<(Proof, Vec), NssaError> { +// // Prove inner program and get post state of the accounts +// let inner_proof = program.execute_and_prove(pre_states, instruction_data)?; +// +// let program_output: ProgramOutput = inner_proof.journal.decode()?; +// +// // Sample fresh random nonces for the outputs of this execution +// let output_nonces: Vec<_> = (0..inputs.len()).map(|_| new_random_nonce()).collect(); +// +// let privacy_preserving_circuit_input = PrivacyPreservingCircuitInput { +// program_output, +// visibility_mask, +// private_account_data: todo!(), +// private_account_auth: todo!(), +// program_id: todo!(), +// commitment_set_digest, +// }; +// // +// // // Prove outer program. +// // let mut env_builder = ExecutorEnv::builder(); +// // env_builder.add_assumption(inner_receipt); +// // env_builder.write(&inner_program_output).unwrap(); +// // env_builder.write(&visibilities).unwrap(); +// // env_builder.write(&output_nonces).unwrap(); +// // env_builder.write(&commitment_tree_root).unwrap(); +// // env_builder.write(&P::PROGRAM_ID).unwrap(); +// // let env = env_builder.build().unwrap(); +// // let prover = default_prover(); +// // let prove_info = prover.prove(env, OUTER_ELF).unwrap(); +// // +// // // Build private accounts. +// // let private_outputs = build_private_outputs_from_inner_results( +// // &inner_program_output, +// // visibilities, +// // &output_nonces, +// // ); +// // +// // Ok((prove_info.receipt, private_outputs)) +// } +} diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 1b97591..84fea64 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,9 +1,10 @@ use std::collections::{HashMap, HashSet}; -use nssa_core::{CommitmentSetDigest, EncryptedAccountData}; use nssa_core::account::{Account, AccountWithMetadata}; +use nssa_core::{CommitmentSetDigest, EncryptedAccountData}; use crate::error::NssaError; +use crate::program::Proof; use crate::{Address, V01State}; use super::message::Message; @@ -16,6 +17,13 @@ pub struct PrivacyPreservingTransaction { } impl PrivacyPreservingTransaction { + pub fn new(message: Message, witness_set: WitnessSet) -> Self { + Self { + message, + witness_set, + } + } + pub(crate) fn validate_and_produce_public_state_diff( &self, state: &mut V01State, @@ -89,7 +97,7 @@ impl PrivacyPreservingTransaction { // 4. Proof verification check_privacy_preserving_circuit_proof_is_valid( - witness_set.proof, + &witness_set.proof, &public_pre_states, &message.public_post_states, &message.encrypted_private_post_states, @@ -130,7 +138,7 @@ impl PrivacyPreservingTransaction { } fn check_privacy_preserving_circuit_proof_is_valid( - proof: (), + proof: &Proof, public_pre_states: &[AccountWithMetadata], public_post_states: &[Account], encrypted_private_post_states: &[EncryptedAccountData], @@ -149,5 +157,5 @@ fn n_unique(data: &[T]) -> usize { #[cfg(test)] mod tests { - + use crate::privacy_preserving_transaction::message::tests::message_for_tests; } diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 285a475..920adb0 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,6 +1,7 @@ -use crate::{PrivateKey, PublicKey, Signature, privacy_preserving_transaction::message::Message}; - -type Proof = (); +use crate::{ + PrivateKey, PublicKey, Signature, privacy_preserving_transaction::message::Message, + program::Proof, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { @@ -10,18 +11,30 @@ pub struct WitnessSet { impl WitnessSet { pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self { - todo!() + let message_bytes = message.to_bytes(); + let signatures_and_public_keys = private_keys + .iter() + .map(|&key| { + ( + Signature::new(key, &message_bytes), + PublicKey::new_from_private_key(key), + ) + }) + .collect(); + Self { + proof, + signatures_and_public_keys, + } } pub fn signatures_are_valid_for(&self, message: &Message) -> bool { - // let message_bytes = message.to_bytes(); - // for (signature, public_key) in self.signatures_and_public_keys() { - // if !signature.is_valid_for(&message_bytes, public_key) { - // return false; - // } - // } - // true - todo!() + let message_bytes = message.to_bytes(); + for (signature, public_key) in self.signatures_and_public_keys() { + if !signature.is_valid_for(&message_bytes, public_key) { + return false; + } + } + true } pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] { diff --git a/nssa/src/program.rs b/nssa/src/program.rs index b104eca..0bd6dcb 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -1,15 +1,19 @@ +use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ account::{Account, AccountWithMetadata}, program::{DEFAULT_PROGRAM_ID, InstructionData, ProgramId, ProgramOutput}, }; use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; use risc0_zkvm::{ - ExecutorEnv, ExecutorEnvBuilder, Receipt, default_executor, default_prover, serde::to_vec, + ExecutorEnv, ExecutorEnvBuilder, Journal, Receipt, default_executor, default_prover, + serde::to_vec, }; use serde::Serialize; use crate::error::NssaError; +pub type Proof = Vec; + #[derive(Debug, PartialEq, Eq)] pub struct Program { id: ProgramId, @@ -56,11 +60,11 @@ impl Program { /// Executes and proves the program `P`. /// Returns the proof - fn execute_and_prove( + pub(crate) fn execute_and_prove( &self, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, - ) -> Result { + ) -> Result<(Proof, ProgramOutput), NssaError> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); Self::write_inputs(pre_states, instruction_data, &mut env_builder)?; @@ -71,7 +75,9 @@ impl Program { let prove_info = prover .prove(env, self.elf) .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))?; - Ok(prove_info.receipt) + let proof = borsh::to_vec(&prove_info.receipt.inner).unwrap(); + let program_output = prove_info.receipt.journal.decode().unwrap(); + Ok((proof, program_output)) } /// Writes inputs to `env_builder` in the order expected by the programs @@ -101,6 +107,7 @@ mod tests { account::{Account, AccountWithMetadata}, program::ProgramOutput, }; + use risc0_zkvm::{InnerReceipt, Receipt, serde::to_vec}; use crate::program::Program; @@ -247,15 +254,20 @@ mod tests { ..Account::default() }; - let receipt = program + let (proof, program_output) = program .execute_and_prove(&[sender, recipient], &instruction_data) .unwrap(); - let ProgramOutput { post_states, .. } = receipt.journal.decode().unwrap(); + + let ProgramOutput { post_states, .. } = program_output.clone(); let [sender_post, recipient_post] = post_states.try_into().unwrap(); - let output = assert_eq!(sender_post, expected_sender_post); - + assert_eq!(sender_post, expected_sender_post); assert_eq!(recipient_post, expected_recipient_post); - assert!(receipt.verify(program.id()).is_ok()); + + let journal = program_output.to_bytes().unwrap(); + let inner: InnerReceipt = borsh::from_slice(&proof).unwrap(); + let receipt = Receipt::new(inner, journal); + + receipt.verify(program.id()).unwrap(); } } diff --git a/nssa/src/public_transaction/encoding.rs b/nssa/src/public_transaction/encoding.rs index 0e3c8ba..70b5288 100644 --- a/nssa/src/public_transaction/encoding.rs +++ b/nssa/src/public_transaction/encoding.rs @@ -1,3 +1,5 @@ +// TODO: Consider switching to deriving Borsh + use std::io::{Cursor, Read}; use nssa_core::program::ProgramId; @@ -8,9 +10,9 @@ use crate::{ public_transaction::{Message, WitnessSet}, }; -const MESSAGE_ENCODING_PREFIX_LEN: usize = 37; +const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = - b"NSSA/v0.1/TxMessage/Public\0\0\0\0\0\0\0\0\0\0\0"; + b"\x00/NSSA/v0.1/TxMessage/"; impl Message { /// Serializes a `Message` into bytes in the following layout: From f905e79f4c74025d796a84fbec0b21e973a31c8b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 18 Aug 2025 19:57:21 -0300 Subject: [PATCH 15/46] wip --- nssa/core/src/account/nullifier.rs | 2 +- nssa/core/src/lib.rs | 1 - nssa/core/src/program.rs | 71 -------- nssa/src/error.rs | 6 + .../src/privacy_preserving_transaction/mod.rs | 152 +++++++++++------- .../transaction.rs | 2 +- .../witness_set.rs | 4 +- nssa/src/program.rs | 73 +-------- 8 files changed, 112 insertions(+), 199 deletions(-) diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 06c3e22..bb83854 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::account::Commitment; -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NullifierPublicKey(pub(super) [u8; 32]); impl From<&NullifierSecretKey> for NullifierPublicKey { diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index bd01216..dd2e893 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -100,4 +100,3 @@ pub struct PrivacyPreservingCircuitOutput { pub new_nullifiers: Vec, pub commitment_set_digest: CommitmentSetDigest, } - diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index f5615e9..b942536 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -21,20 +21,6 @@ pub struct ProgramOutput { pub post_states: Vec, } -#[cfg(feature = "host")] -impl ProgramOutput { - pub fn to_bytes(&self) -> Result, NssaCoreError> { - use risc0_zkvm::serde::to_vec; - - let mut result = Vec::new(); - let b = to_vec(self).map_err(|e| NssaCoreError::DeserializationError(e.to_string()))?; - for word in &b { - result.extend_from_slice(&word.to_le_bytes()); - } - Ok(result) - } -} - pub fn read_nssa_inputs() -> ProgramInput { let pre_states: Vec = env::read(); let words: InstructionData = env::read(); @@ -103,60 +89,3 @@ pub fn validate_execution( true } - -#[cfg(test)] -mod tests { - use risc0_zkvm::Journal; - - use crate::{ - account::{Account, AccountWithMetadata}, - program::ProgramOutput, - }; - - #[test] - fn test_program_output_to_bytes_is_compatible_with_journal_decode() { - let account_pre1 = Account { - program_owner: [1, 2, 3, 4, 5, 6, 7, 8], - balance: 1112223333444455556666, - data: b"test data 1".to_vec(), - nonce: 3344556677889900, - }; - let account_pre2 = Account { - program_owner: [9, 8, 7, 6, 5, 4, 3, 2], - balance: 18446744073709551615, - data: b"test data 2".to_vec(), - nonce: 3344556677889901, - }; - let account_post1 = Account { - program_owner: [1, 2, 3, 4, 5, 6, 7, 8], - balance: 1, - data: b"other test data 1".to_vec(), - nonce: 113, - }; - let account_post2 = Account { - program_owner: [9, 8, 7, 6, 5, 4, 3, 2], - balance: 2, - data: b"other test data 2".to_vec(), - nonce: 112, - }; - - let program_output = ProgramOutput { - pre_states: vec![ - AccountWithMetadata { - account: account_pre1, - is_authorized: true, - }, - AccountWithMetadata { - account: account_pre2, - is_authorized: false, - }, - ], - post_states: vec![account_post1, account_post2], - }; - - let bytes = program_output.to_bytes().unwrap(); - let journal = Journal::new(bytes); - let decoded_program_output: ProgramOutput = journal.decode().unwrap(); - assert_eq!(program_output, decoded_program_output); - } -} diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 038d217..e470513 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -36,4 +36,10 @@ pub enum NssaError { #[error("Core error")] Core(#[from] nssa_core::error::NssaCoreError), + + #[error("Program output deserialization error: {0}")] + ProgramOutputDeserializationError(String), + + #[error("Circuit output deserialization error: {0}")] + CircuitOutputDeserializationError(String), } diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 4fed05e..3618e3e 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -5,59 +5,101 @@ mod witness_set; pub use transaction::PrivacyPreservingTransaction; -pub mod offchain { -// use nssa_core::{ -// account::{Account, AccountWithMetadata, NullifierSecretKey}, program::{InstructionData, ProgramOutput}, PrivacyPreservingCircuitInput -// }; -// -// use crate::{error::NssaError, program::Program}; -// - // pub type Proof = (); -// -// pub fn execute_offchain( -// pre_states: &[AccountWithMetadata], -// instruction_data: &InstructionData, -// private_account_keys: &[(NullifierSecretKey, ] -// visibility_mask: &[u8], -// commitment_set_digest: [u32; 8], -// program: Program, -// ) -> Result<(Proof, Vec), NssaError> { -// // Prove inner program and get post state of the accounts -// let inner_proof = program.execute_and_prove(pre_states, instruction_data)?; -// -// let program_output: ProgramOutput = inner_proof.journal.decode()?; -// -// // Sample fresh random nonces for the outputs of this execution -// let output_nonces: Vec<_> = (0..inputs.len()).map(|_| new_random_nonce()).collect(); -// -// let privacy_preserving_circuit_input = PrivacyPreservingCircuitInput { -// program_output, -// visibility_mask, -// private_account_data: todo!(), -// private_account_auth: todo!(), -// program_id: todo!(), -// commitment_set_digest, -// }; -// // -// // // Prove outer program. -// // let mut env_builder = ExecutorEnv::builder(); -// // env_builder.add_assumption(inner_receipt); -// // env_builder.write(&inner_program_output).unwrap(); -// // env_builder.write(&visibilities).unwrap(); -// // env_builder.write(&output_nonces).unwrap(); -// // env_builder.write(&commitment_tree_root).unwrap(); -// // env_builder.write(&P::PROGRAM_ID).unwrap(); -// // let env = env_builder.build().unwrap(); -// // let prover = default_prover(); -// // let prove_info = prover.prove(env, OUTER_ELF).unwrap(); -// // -// // // Build private accounts. -// // let private_outputs = build_private_outputs_from_inner_results( -// // &inner_program_output, -// // visibilities, -// // &output_nonces, -// // ); -// // -// // Ok((prove_info.receipt, private_outputs)) -// } +pub mod circuit { + use nssa_core::{ + CommitmentSetDigest, EphemeralSecretKey, IncomingViewingPublicKey, MembershipProof, + PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, + program::{InstructionData, ProgramOutput}, + }; + use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; + + use crate::{error::NssaError, program::Program}; + + use program_methods::PRIVACY_PRESERVING_CIRCUIT_ELF; + + pub type Proof = Vec; + + /// Executes and proves the program `P`. + /// Returns the proof + fn execute_and_prove_program( + program: &Program, + pre_states: &[AccountWithMetadata], + instruction_data: &InstructionData, + ) -> Result { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + Program::write_inputs(pre_states, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Prove the program + let prover = default_prover(); + Ok(prover + .prove(env, program.elf()) + .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))? + .receipt) + } + + pub fn prove_privacy_preserving_execution_circuit( + pre_states: &[AccountWithMetadata], + instruction_data: &InstructionData, + private_account_keys: &[( + NullifierPublicKey, + IncomingViewingPublicKey, + EphemeralSecretKey, + )], + private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, + visibility_mask: &[u8], + commitment_set_digest: CommitmentSetDigest, + program: &Program, + ) -> Result<(Proof, PrivacyPreservingCircuitOutput), NssaError> { + let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; + + let program_output: ProgramOutput = inner_receipt + .journal + .decode() + .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + + let private_account_nonces: Vec<_> = (0..private_account_keys.len()) + .map(|_| new_random_nonce()) + .collect(); + + let circuit_input = PrivacyPreservingCircuitInput { + program_output, + visibility_mask: visibility_mask.to_vec(), + private_account_nonces: private_account_nonces.to_vec(), + private_account_keys: private_account_keys.to_vec(), + private_account_auth: private_account_auth.to_vec(), + program_id: program.id(), + commitment_set_digest, + }; + + // Prove circuit. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&circuit_input).unwrap(); + let env = env_builder.build().unwrap(); + let prover = default_prover(); + let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); + + let proof = borsh::to_vec(&prove_info.receipt.inner)?; + + let circuit_output: PrivacyPreservingCircuitOutput = prove_info + .receipt + .journal + .decode() + .map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?; + + Ok((proof, circuit_output)) + } + + fn new_random_nonce() -> Nonce { + todo!() + } +} + +#[cfg(test)] +mod tests { + use crate::program::Program; + } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 84fea64..9f5718d 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -4,7 +4,7 @@ use nssa_core::account::{Account, AccountWithMetadata}; use nssa_core::{CommitmentSetDigest, EncryptedAccountData}; use crate::error::NssaError; -use crate::program::Proof; +use crate::privacy_preserving_transaction::circuit::Proof; use crate::{Address, V01State}; use super::message::Message; diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 920adb0..fe897ce 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,6 +1,6 @@ use crate::{ - PrivateKey, PublicKey, Signature, privacy_preserving_transaction::message::Message, - program::Proof, + PrivateKey, PublicKey, Signature, + privacy_preserving_transaction::{circuit::Proof, message::Message}, }; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 0bd6dcb..410d674 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -12,8 +12,6 @@ use serde::Serialize; use crate::error::NssaError; -pub type Proof = Vec; - #[derive(Debug, PartialEq, Eq)] pub struct Program { id: ProgramId, @@ -25,6 +23,10 @@ impl Program { self.id } + pub(crate) fn elf(&self) -> &'static [u8] { + self.elf + } + pub fn serialize_instruction( instruction: T, ) -> Result { @@ -58,30 +60,8 @@ impl Program { Ok(post_states) } - /// Executes and proves the program `P`. - /// Returns the proof - pub(crate) fn execute_and_prove( - &self, - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - ) -> Result<(Proof, ProgramOutput), NssaError> { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - Self::write_inputs(pre_states, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Prove the program - let prover = default_prover(); - let prove_info = prover - .prove(env, self.elf) - .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))?; - let proof = borsh::to_vec(&prove_info.receipt.inner).unwrap(); - let program_output = prove_info.receipt.journal.decode().unwrap(); - Ok((proof, program_output)) - } - /// Writes inputs to `env_builder` in the order expected by the programs - fn write_inputs( + pub(crate) fn write_inputs( pre_states: &[AccountWithMetadata], instruction_data: &[u32], env_builder: &mut ExecutorEnvBuilder, @@ -227,47 +207,4 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(recipient_post, expected_recipient_post); } - - #[test] - fn test_program_execute_and_prove() { - let program = Program::simple_balance_transfer(); - let balance_to_move: u128 = 11223344556677; - let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let sender = AccountWithMetadata { - account: Account { - balance: 77665544332211, - ..Account::default() - }, - is_authorized: false, - }; - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - }; - - let expected_sender_post = Account { - balance: 77665544332211 - balance_to_move, - ..Account::default() - }; - let expected_recipient_post = Account { - balance: balance_to_move, - ..Account::default() - }; - - let (proof, program_output) = program - .execute_and_prove(&[sender, recipient], &instruction_data) - .unwrap(); - - let ProgramOutput { post_states, .. } = program_output.clone(); - let [sender_post, recipient_post] = post_states.try_into().unwrap(); - - assert_eq!(sender_post, expected_sender_post); - assert_eq!(recipient_post, expected_recipient_post); - - let journal = program_output.to_bytes().unwrap(); - let inner: InnerReceipt = borsh::from_slice(&proof).unwrap(); - let receipt = Receipt::new(inner, journal); - - receipt.verify(program.id()).unwrap(); - } } From d2ab8dd4d43228e497ee22c12026828235ccd77b Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Tue, 19 Aug 2025 06:41:36 +0300 Subject: [PATCH 16/46] fix: comments fix --- nssa/src/address.rs | 15 --------------- sequencer_core/src/sequencer_store/mod.rs | 10 +--------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/nssa/src/address.rs b/nssa/src/address.rs index d54af10..93304d5 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -1,12 +1,9 @@ use std::{fmt::Display, str::FromStr}; -use anyhow::anyhow; use serde::{Deserialize, Serialize}; use crate::signature::PublicKey; -pub const LENGTH_MISMATCH_ERROR_MESSAGE: &str = "Slice length != 32 "; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Address { value: [u8; 32], @@ -28,18 +25,6 @@ impl AsRef<[u8]> for Address { } } -impl TryFrom> for Address { - type Error = anyhow::Error; - - fn try_from(value: Vec) -> Result { - let addr_val: [u8; 32] = value - .try_into() - .map_err(|_| anyhow!(LENGTH_MISMATCH_ERROR_MESSAGE))?; - - Ok(Address::new(addr_val)) - } -} - impl From<&PublicKey> for Address { fn from(value: &PublicKey) -> Self { // TODO: Check specs diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index 3d9708d..4254ed7 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -23,15 +23,7 @@ impl SequecerChainStore { ) -> Self { let init_accs: Vec<(Address, u128)> = initial_accounts .iter() - .map(|acc_data| { - ( - hex::decode(acc_data.addr.clone()) - .unwrap() - .try_into() - .unwrap(), - acc_data.balance, - ) - }) + .map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance)) .collect(); let state = nssa::V01State::new_with_genesis_accounts(&init_accs); From 769e372e8f2698998b374f6f80c39c5d214f0dcf Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 19 Aug 2025 10:39:47 -0300 Subject: [PATCH 17/46] add test of privacy preserving circuit proof generation --- nssa/core/src/account/commitment.rs | 3 +- nssa/core/src/account/mod.rs | 6 +- nssa/core/src/account/nullifier.rs | 6 +- nssa/core/src/lib.rs | 15 ++- nssa/core/src/program.rs | 3 +- .../src/bin/privacy_preserving_circuit.rs | 125 ++++++++++-------- .../src/privacy_preserving_transaction/mod.rs | 80 ++++++++++- 7 files changed, 174 insertions(+), 64 deletions(-) diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index 60034cb..487789f 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::account::{Account, NullifierPublicKey}; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq, Hash))] pub struct Commitment(pub(super) [u8; 32]); impl Commitment { diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account/mod.rs index 2bb2285..6f5e017 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account/mod.rs @@ -14,7 +14,8 @@ pub type Nonce = u128; type Data = Vec; /// Account to be used both in public and private contexts -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct Account { pub program_owner: ProgramId, pub balance: u128, @@ -22,7 +23,8 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index bb83854..8faef0b 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -3,7 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::account::Commitment; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] pub struct NullifierPublicKey(pub(super) [u8; 32]); impl From<&NullifierSecretKey> for NullifierPublicKey { @@ -22,7 +23,8 @@ impl From<&NullifierSecretKey> for NullifierPublicKey { pub type NullifierSecretKey = [u8; 32]; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq, Hash))] pub struct Nullifier(pub(super) [u8; 32]); impl Nullifier { diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index dd2e893..3003fef 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -48,7 +48,8 @@ impl Tag { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] pub struct EncryptedAccountData(u8); impl EncryptedAccountData { @@ -100,3 +101,15 @@ pub struct PrivacyPreservingCircuitOutput { pub new_nullifiers: Vec, pub commitment_set_digest: CommitmentSetDigest, } + +#[cfg(feature = "host")] +impl PrivacyPreservingCircuitOutput { + pub fn to_bytes(&self) -> Vec { + let words = to_vec(&self).unwrap(); + let mut result = Vec::with_capacity(4 * words.len()); + for word in &words { + result.extend_from_slice(&word.to_le_bytes()); + } + result + } +} diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index b942536..f84bd61 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -15,7 +15,8 @@ pub struct ProgramInput { pub instruction: T, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ProgramOutput { pub pre_states: Vec, pub post_states: Vec, diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index f165d86..f61d74a 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -34,14 +34,22 @@ fn main() { validate_execution(&pre_states, &post_states, program_id); let n_accounts = pre_states.len(); - assert_eq!(visibility_mask.len(), n_accounts); + if visibility_mask.len() != n_accounts { + panic!(); + } let n_private_accounts = visibility_mask.iter().filter(|&&flag| flag != 0).count(); - assert_eq!(private_account_nonces.len(), n_private_accounts); - assert_eq!(private_account_keys.len(), n_private_accounts); + if private_account_nonces.len() != n_private_accounts { + panic!(); + } + if private_account_keys.len() != n_private_accounts { + panic!(); + } let n_auth_private_accounts = visibility_mask.iter().filter(|&&flag| flag == 1).count(); - assert_eq!(private_account_auth.len(), n_auth_private_accounts); + if private_account_auth.len() != n_auth_private_accounts { + panic!(); + } // These lists will be the public outputs of this circuit // and will be populated next. @@ -51,60 +59,71 @@ fn main() { let mut new_commitments: Vec = Vec::new(); let mut new_nullifiers: Vec = Vec::new(); + let mut private_nonces_iter = private_account_nonces.iter(); + let mut private_keys_iter = private_account_keys.iter(); + let mut private_auth_iter = private_account_auth.iter(); + for i in 0..n_accounts { - // visibility_mask[i] equal to 0 means public - if visibility_mask[i] == 0 { - // If the account is marked as public, add the pre and post - // states to the corresponding lists. - public_pre_states.push(pre_states[i].clone()); - public_post_states.push(post_states[i].clone()); - } else { - let new_nonce = &private_account_nonces[i]; - let (Npk, Ipk, esk) = &private_account_keys[i]; - - // Verify authentication - if visibility_mask[i] == 1 { - let (nsk, membership_proof) = &private_account_auth[i]; - - // 1. Compute Npk from the provided nsk and assert it is equal to the provided Npk - let expected_Npk = NullifierPublicKey::from(nsk); - assert_eq!(&expected_Npk, Npk); - // 2. Compute the commitment of the pre_state account using the provided Npk - let commitment_pre = Commitment::new(Npk, &pre_states[i].account); - // 3. Verify that the commitment belongs to the global commitment set - assert!(verify_membership_proof( - &commitment_pre, - membership_proof, - &commitment_set_digest, - )); - // At this point the account is correctly authenticated as a private account. - // Assert that `pre_states` marked this account as authenticated. - assert!(pre_states[i].is_authorized); - // Compute the nullifier of the pre state version of this private account - // and include it in the `new_nullifiers` list. - let nullifier = Nullifier::new(&commitment_pre, nsk); - new_nullifiers.push(nullifier); - } else if visibility_mask[i] == 2 { - assert_eq!(pre_states[i].account, Account::default()); - assert!(!pre_states[i].is_authorized); - } else { - panic!(); + match visibility_mask[i] { + 0 => { + // Public account + public_pre_states.push(pre_states[i].clone()); + public_post_states.push(post_states[i].clone()); } + 1 | 2 => { + let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); + let (Npk, Ipk, esk) = private_keys_iter.next().expect("Missing private keys"); - // Update the nonce for the post state of this private account. - let mut post_with_updated_nonce = post_states[i].clone(); - post_with_updated_nonce.nonce = *new_nonce; + if visibility_mask[i] == 1 { + // Private account with authentication + let (nsk, membership_proof) = + private_auth_iter.next().expect("Missing private auth"); - // Compute the commitment of the post state of the private account, - // with the updated nonce, and include it in the `new_commitments` list. - let commitment_post = Commitment::new(Npk, &post_with_updated_nonce); - new_commitments.push(commitment_post); + // Verify Npk + let expected_Npk = NullifierPublicKey::from(nsk); + if &expected_Npk != Npk { + panic!("Npk mismatch"); + } - // Encrypt the post state of the private account with the updated - // nonce and include it in the `encrypted_private_post_states` list. - // - let encrypted_account = EncryptedAccountData::new(&post_with_updated_nonce, esk, Npk, Ipk); - encrypted_private_post_states.push(encrypted_account); + // Verify pre-state commitment membership + let commitment_pre = Commitment::new(Npk, &pre_states[i].account); + if !verify_membership_proof( + &commitment_pre, + membership_proof, + &commitment_set_digest, + ) { + panic!("Membership proof invalid"); + } + + // Check pre_state authorization + if !pre_states[i].is_authorized { + panic!("Pre-state not authorized"); + } + + // Compute nullifier + let nullifier = Nullifier::new(&commitment_pre, nsk); + new_nullifiers.push(nullifier); + } else { + // Private account marked as empty + if pre_states[i].account != Account::default() || pre_states[i].is_authorized { + panic!("Invalid empty private account pre-state"); + } + } + + // Update post-state with new nonce + let mut post_with_updated_nonce = post_states[i].clone(); + post_with_updated_nonce.nonce = *new_nonce; + + // Compute commitment and push + let commitment_post = Commitment::new(Npk, &post_with_updated_nonce); + new_commitments.push(commitment_post); + + // Encrypt and push post state + let encrypted_account = + EncryptedAccountData::new(&post_with_updated_nonce, esk, Npk, Ipk); + encrypted_private_post_states.push(encrypted_account); + } + _ => panic!("Invalid visibility mask value"), } } diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 3618e3e..0caba33 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -3,6 +3,7 @@ mod message; mod transaction; mod witness_set; +pub use message::Message; pub use transaction::PrivacyPreservingTransaction; pub mod circuit { @@ -12,6 +13,7 @@ pub mod circuit { account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, program::{InstructionData, ProgramOutput}, }; + use rand::{Rng, RngCore, rngs::OsRng}; use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; use crate::{error::NssaError, program::Program}; @@ -48,7 +50,7 @@ pub mod circuit { IncomingViewingPublicKey, EphemeralSecretKey, )], - private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, + private_account_auth: &[(NullifierSecretKey, MembershipProof)], visibility_mask: &[u8], commitment_set_digest: CommitmentSetDigest, program: &Program, @@ -93,13 +95,83 @@ pub mod circuit { Ok((proof, circuit_output)) } - fn new_random_nonce() -> Nonce { - todo!() + fn new_random_nonce() -> u128 { + let mut u128_bytes = [0u8; 16]; + OsRng.fill_bytes(&mut u128_bytes); + u128::from_le_bytes(u128_bytes) } } #[cfg(test)] mod tests { - use crate::program::Program; + use nssa_core::{ + EncryptedAccountData, + account::{Account, AccountWithMetadata, NullifierPublicKey, NullifierSecretKey}, + }; + use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; + use risc0_zkvm::{InnerReceipt, Journal, Receipt}; + use crate::{ + Address, V01State, + privacy_preserving_transaction::circuit::prove_privacy_preserving_execution_circuit, + program::Program, + }; + + use super::*; + + #[test] + fn test() { + let sender = AccountWithMetadata { + account: Account { + balance: 100, + ..Account::default() + }, + is_authorized: false, + }; + let recipient = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let balance_to_move: u128 = 37; + + let expected_sender_post = Account { + balance: 100 - balance_to_move, + ..Default::default() + }; + + let expected_sender_pre = sender.clone(); + let pre_states = vec![sender, recipient]; + let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); + let private_account_keys = vec![(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])]; + let private_account_auth = vec![]; + let visibility_mask = vec![0, 2]; + let commitment_set_digest = [99; 8]; + let program = Program::simple_balance_transfer(); + let (proof, output) = prove_privacy_preserving_execution_circuit( + &pre_states, + &instruction_data, + &private_account_keys, + &private_account_auth, + &visibility_mask, + commitment_set_digest, + &program, + ) + .unwrap(); + + let inner: InnerReceipt = borsh::from_slice(&proof).unwrap(); + let receipt = Receipt::new(inner, output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).unwrap(); + + let [sender_pre] = output.public_pre_states.try_into().unwrap(); + let [sender_post] = output.public_post_states.try_into().unwrap(); + assert_eq!(sender_pre, expected_sender_pre); + assert_eq!(sender_post, expected_sender_post); + assert_eq!(output.new_commitments.len(), 1); + assert_eq!(output.new_nullifiers.len(), 0); + assert_eq!(output.commitment_set_digest, commitment_set_digest); + assert_eq!(output.encrypted_private_post_states.len(), 1); + // TODO: replace with real assert when encryption is implemented + assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); + } } From 538bb72556d986f66b6562765ff7bf34263ccb65 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 19 Aug 2025 10:47:24 -0300 Subject: [PATCH 18/46] encapsulate proof --- .../src/privacy_preserving_transaction/mod.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 0caba33..be229b7 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -14,13 +14,22 @@ pub mod circuit { program::{InstructionData, ProgramOutput}, }; use rand::{Rng, RngCore, rngs::OsRng}; - use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; + use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; use crate::{error::NssaError, program::Program}; - use program_methods::PRIVACY_PRESERVING_CIRCUIT_ELF; + use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}; - pub type Proof = Vec; + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Proof(Vec); + + impl Proof { + pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { + let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); + let receipt = Receipt::new(inner, circuit_output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() + } + } /// Executes and proves the program `P`. /// Returns the proof @@ -84,7 +93,7 @@ pub mod circuit { let prover = default_prover(); let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); - let proof = borsh::to_vec(&prove_info.receipt.inner)?; + let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); let circuit_output: PrivacyPreservingCircuitOutput = prove_info .receipt @@ -108,7 +117,6 @@ mod tests { EncryptedAccountData, account::{Account, AccountWithMetadata, NullifierPublicKey, NullifierSecretKey}, }; - use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; use risc0_zkvm::{InnerReceipt, Journal, Receipt}; use crate::{ @@ -159,9 +167,7 @@ mod tests { ) .unwrap(); - let inner: InnerReceipt = borsh::from_slice(&proof).unwrap(); - let receipt = Receipt::new(inner, output.to_bytes()); - receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).unwrap(); + assert!(proof.is_valid_for(&output)); let [sender_pre] = output.public_pre_states.try_into().unwrap(); let [sender_post] = output.public_post_states.try_into().unwrap(); From 8239855e88f0c2fbfae158b48670dbf629e0d125 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 19 Aug 2025 12:52:52 -0300 Subject: [PATCH 19/46] add test. refactor --- nssa/core/src/lib.rs | 59 ++++++++++++++++- .../src/bin/privacy_preserving_circuit.rs | 34 +++++----- nssa/src/error.rs | 3 + .../src/privacy_preserving_transaction/mod.rs | 65 +++++++++++++++++-- .../transaction.rs | 15 ++++- 5 files changed, 151 insertions(+), 25 deletions(-) diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 3003fef..7f717f0 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -28,7 +28,8 @@ pub fn verify_membership_proof( proof: &MembershipProof, digest: &CommitmentSetDigest, ) -> bool { - todo!() + // TODO: implement + true } pub type IncomingViewingPublicKey = [u8; 32]; @@ -93,6 +94,7 @@ pub struct PrivacyPreservingCircuitInput { } #[derive(Serialize, Deserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct PrivacyPreservingCircuitOutput { pub public_pre_states: Vec, pub public_post_states: Vec, @@ -113,3 +115,58 @@ impl PrivacyPreservingCircuitOutput { result } } + +#[cfg(test)] +mod tests { + use risc0_zkvm::serde::from_slice; + + use crate::{ + EncryptedAccountData, PrivacyPreservingCircuitOutput, + account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, + }; + + #[test] + fn test_privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() { + let output = PrivacyPreservingCircuitOutput { + public_pre_states: vec![ + AccountWithMetadata { + account: Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 12345678901234567890, + data: b"test data".to_vec(), + nonce: 18446744073709551614, + }, + is_authorized: true, + }, + AccountWithMetadata { + account: Account { + program_owner: [9, 9, 9, 8, 8, 8, 7, 7], + balance: 123123123456456567112, + data: b"test data".to_vec(), + nonce: 9999999999999999999999, + }, + is_authorized: false, + }, + ], + public_post_states: vec![Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 100, + data: b"post state data".to_vec(), + nonce: 18446744073709551615, + }], + encrypted_private_post_states: vec![EncryptedAccountData(0)], + new_commitments: vec![Commitment::new( + &NullifierPublicKey::from(&[1; 32]), + &Account::default(), + )], + new_nullifiers: vec![Nullifier::new( + &Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()), + &[1; 32], + )], + commitment_set_digest: [0, 1, 0, 1, 0, 1, 0, 1], + }; + let bytes = output.to_bytes(); + let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); + assert_eq!(output, output_from_slice); + } +} diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index f61d74a..48c1b7c 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -38,19 +38,6 @@ fn main() { panic!(); } - let n_private_accounts = visibility_mask.iter().filter(|&&flag| flag != 0).count(); - if private_account_nonces.len() != n_private_accounts { - panic!(); - } - if private_account_keys.len() != n_private_accounts { - panic!(); - } - - let n_auth_private_accounts = visibility_mask.iter().filter(|&&flag| flag == 1).count(); - if private_account_auth.len() != n_auth_private_accounts { - panic!(); - } - // These lists will be the public outputs of this circuit // and will be populated next. let mut public_pre_states: Vec = Vec::new(); @@ -104,9 +91,12 @@ fn main() { let nullifier = Nullifier::new(&commitment_pre, nsk); new_nullifiers.push(nullifier); } else { - // Private account marked as empty - if pre_states[i].account != Account::default() || pre_states[i].is_authorized { - panic!("Invalid empty private account pre-state"); + if pre_states[i].account != Account::default() { + panic!("Found new private account with non default values."); + } + + if pre_states[i].is_authorized { + panic!("Found new private account marked as authorized."); } } @@ -127,6 +117,18 @@ fn main() { } } + if private_nonces_iter.next().is_some() { + panic!("Too many nonces."); + } + + if private_keys_iter.next().is_some() { + panic!("Too many private accounts keys."); + } + + if private_auth_iter.next().is_some() { + panic!("Too many private account authentication keys."); + } + let output = PrivacyPreservingCircuitOutput { public_pre_states, public_post_states, diff --git a/nssa/src/error.rs b/nssa/src/error.rs index e470513..1d6d6ad 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -42,4 +42,7 @@ pub enum NssaError { #[error("Circuit output deserialization error: {0}")] CircuitOutputDeserializationError(String), + + #[error("Invalid privacy preserving execution circuit proof")] + InvalidPrivacyPreservingProof, } diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index be229b7..17fe677 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -31,8 +31,6 @@ pub mod circuit { } } - /// Executes and proves the program `P`. - /// Returns the proof fn execute_and_prove_program( program: &Program, pre_states: &[AccountWithMetadata], @@ -128,14 +126,15 @@ mod tests { use super::*; #[test] - fn test() { + fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { let sender = AccountWithMetadata { account: Account { balance: 100, ..Account::default() }, - is_authorized: false, + is_authorized: true, }; + let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, @@ -155,7 +154,7 @@ mod tests { let private_account_auth = vec![]; let visibility_mask = vec![0, 2]; let commitment_set_digest = [99; 8]; - let program = Program::simple_balance_transfer(); + let program = Program::authenticated_transfer_program(); let (proof, output) = prove_privacy_preserving_execution_circuit( &pre_states, &instruction_data, @@ -177,7 +176,61 @@ mod tests { assert_eq!(output.new_nullifiers.len(), 0); assert_eq!(output.commitment_set_digest, commitment_set_digest); assert_eq!(output.encrypted_private_post_states.len(), 1); - // TODO: replace with real assert when encryption is implemented + // TODO: replace with real assertion when encryption is implemented assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); } + + #[test] + fn prove_privacy_preserving_execution_circuit_fully_private() { + let sender = AccountWithMetadata { + account: Account { + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + + let recipient = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let balance_to_move: u128 = 37; + + let expected_sender_pre = sender.clone(); + let pre_states = vec![sender, recipient]; + let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); + let private_key = [1; 32]; + let private_account_keys = vec![ + (NullifierPublicKey::from(&private_key), [2; 32], [3; 32]), + (NullifierPublicKey::from(&[2; 32]), [4; 32], [5; 32]), + ]; + // TODO: Replace dummy authentication path when implemented + let private_account_auth = vec![(private_key, vec![])]; + let visibility_mask = vec![1, 2]; + let commitment_set_digest = [99; 8]; + let program = Program::authenticated_transfer_program(); + let (proof, output) = prove_privacy_preserving_execution_circuit( + &pre_states, + &instruction_data, + &private_account_keys, + &private_account_auth, + &visibility_mask, + commitment_set_digest, + &program, + ) + .unwrap(); + + assert!(proof.is_valid_for(&output)); + + assert_eq!(output.public_post_states.len(), 0); + assert_eq!(output.public_pre_states.len(), 0); + assert_eq!(output.new_commitments.len(), 2); + assert_eq!(output.new_nullifiers.len(), 1); + assert_eq!(output.commitment_set_digest, commitment_set_digest); + assert_eq!(output.encrypted_private_post_states.len(), 2); + // TODO: replace with real assertion when encryption is implemented + assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); + assert_eq!(output.encrypted_private_post_states[1].to_bytes(), vec![0]); + } } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 9f5718d..54d8356 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use nssa_core::account::{Account, AccountWithMetadata}; -use nssa_core::{CommitmentSetDigest, EncryptedAccountData}; +use nssa_core::{CommitmentSetDigest, EncryptedAccountData, PrivacyPreservingCircuitOutput}; use crate::error::NssaError; use crate::privacy_preserving_transaction::circuit::Proof; @@ -146,7 +146,18 @@ fn check_privacy_preserving_circuit_proof_is_valid( new_nullifiers: &[nssa_core::account::Nullifier], commitment_set_digest: CommitmentSetDigest, ) -> Result<(), NssaError> { - todo!() + let output = PrivacyPreservingCircuitOutput { + public_pre_states: public_pre_states.to_vec(), + public_post_states: public_post_states.to_vec(), + encrypted_private_post_states: encrypted_private_post_states.to_vec(), + new_commitments: new_commitments.to_vec(), + new_nullifiers: new_nullifiers.to_vec(), + commitment_set_digest, + }; + proof + .is_valid_for(&output) + .then_some(()) + .ok_or(NssaError::InvalidPrivacyPreservingProof) } use std::hash::Hash; From ceba630cffcbab919304e5e09419cd11fd6dd147 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 19 Aug 2025 15:22:35 -0300 Subject: [PATCH 20/46] merkle tree wip --- nssa/core/Cargo.toml | 3 +- nssa/core/src/lib.rs | 7 +-- nssa/src/lib.rs | 1 + nssa/src/merkle_tree.rs | 135 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 nssa/src/merkle_tree.rs diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index e74eb90..1cdc78f 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -7,7 +7,8 @@ edition = "2024" risc0-zkvm = "2.3.1" serde = { version = "1.0", default-features = false } thiserror = { version = "2.0.12", optional = true } +bytemuck = { version = "1.13", optional = true } [features] default = [] -host = ["thiserror"] +host = ["thiserror", "bytemuck"] diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 7f717f0..a616237 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -107,12 +107,7 @@ pub struct PrivacyPreservingCircuitOutput { #[cfg(feature = "host")] impl PrivacyPreservingCircuitOutput { pub fn to_bytes(&self) -> Vec { - let words = to_vec(&self).unwrap(); - let mut result = Vec::with_capacity(4 * words.len()); - for word in &words { - result.extend_from_slice(&word.to_le_bytes()); - } - result + bytemuck::cast_slice(&to_vec(&self).unwrap()).to_vec() } } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 2327514..2156b51 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -5,6 +5,7 @@ pub mod program; pub mod public_transaction; mod signature; mod state; +mod merkle_tree; pub use address::Address; pub use public_transaction::PublicTransaction; diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs new file mode 100644 index 0000000..5c2821b --- /dev/null +++ b/nssa/src/merkle_tree.rs @@ -0,0 +1,135 @@ +use std::collections::HashMap; + +use sha2::{Digest, Sha256}; + +type Value = [u8; 32]; +type Node = [u8; 32]; + +/// Compute parent as the hash of two child nodes +fn hash_two(left: &Node, right: &Node) -> Node { + let mut hasher = Sha256::new(); + hasher.update(left); + hasher.update(right); + hasher.finalize().into() +} + +fn hash_value(value: &Value) -> Node { + let mut hasher = Sha256::new(); + hasher.update(value); + hasher.finalize().into() +} + +#[derive(Debug)] +pub struct MerkleTree { + index_map: HashMap, + node_map: HashMap, + capacity: usize, + length: usize, +} + +impl MerkleTree { + pub fn root(&self) -> Node { + *self.node_map.get(&0).unwrap() + } + + pub fn new(mut values: Vec) -> Self { + Self::deduplicate_values(&mut values); + + let capacity = values.len().next_power_of_two(); + let length = values.len(); + + let base_length = capacity; + + let mut node_map: HashMap = values + .iter() + .enumerate() + .map(|(index, value)| (index + base_length - 1, hash_value(value))) + .collect(); + node_map.extend( + (values.len()..base_length) + .map(|index| (index + base_length - 1, [0; 32])) + .collect::>(), + ); + + let mut current_layer_length = base_length; + let mut current_layer_first_index = base_length - 1; + + while current_layer_length > 1 { + let next_layer_length = current_layer_length >> 1; + let next_layer_first_index = current_layer_first_index >> 1; + + let next_layer = (next_layer_first_index..(next_layer_first_index + next_layer_length)) + .map(|index| { + let left_child = node_map.get(&((index << 1) + 1)).unwrap(); + let right_child = node_map.get(&((index << 1) + 2)).unwrap(); + (index, hash_two(&left_child, &right_child)) + }) + .collect::>(); + + node_map.extend(&next_layer); + + current_layer_length = next_layer_length; + current_layer_first_index = next_layer_first_index; + } + + let index_map = values + .into_iter() + .enumerate() + .map(|(index, value)| (value, index)) + .collect(); + + Self { + index_map, + node_map, + capacity, + length, + } + } + + fn deduplicate_values(values: &mut [Value]) { + // TODO: implement + } +} + +#[cfg(test)] +mod tests { + use nssa_core::account::{Account, NullifierPublicKey}; + + use super::*; + + #[test] + fn test_merkle_tree_1() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; + let tree = MerkleTree::new(values); + let expected_root = [ + 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, + 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, + ]; + + assert_eq!(tree.root(), expected_root); + assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); + assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); + assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); + assert_eq!(*tree.index_map.get(&[4; 32]).unwrap(), 3); + assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 4); + } + + #[test] + fn test_merkle_tree_2() { + let values = vec![[1; 32], [2; 32], [3; 32], [0; 32]]; + let tree = MerkleTree::new(values); + let expected_root = [ + 201, 187, 184, 48, 150, 223, 133, 21, 122, 20, 110, 125, 119, 4, 85, 169, 132, 18, 222, + 224, 99, 49, 135, 238, 134, 254, 230, 200, 164, 91, 131, 26, + ]; + + assert_eq!(tree.root(), expected_root); + assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); + assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); + assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); + assert_eq!(*tree.index_map.get(&[0; 32]).unwrap(), 3); + assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 4); + } +} From 15e2c131b262e2590a9623329dd090ba84ef0518 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 19 Aug 2025 16:39:32 -0300 Subject: [PATCH 21/46] add merkle tests --- nssa/src/merkle_tree.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index 5c2821b..31488ee 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -132,4 +132,43 @@ mod tests { assert_eq!(tree.capacity, 4); assert_eq!(tree.length, 4); } + + #[test] + fn test_merkle_tree_3() { + let values = vec![[1; 32], [2; 32], [3; 32]]; + let tree = MerkleTree::new(values); + let expected_root = [ + 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, + 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, + ]; + + assert_eq!(tree.root(), expected_root); + assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); + assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); + assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); + assert!(tree.index_map.get(&[0; 32]).is_none()); + assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 3); + } + + #[test] + fn test_merkle_tree_4() { + let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; + let tree = MerkleTree::new(values); + let expected_root = [ + 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, + 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, + ]; + + assert_eq!(tree.root(), expected_root); + assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); + assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); + assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); + assert_eq!(*tree.index_map.get(&[14; 32]).unwrap(), 3); + assert_eq!(*tree.index_map.get(&[15; 32]).unwrap(), 4); + assert_eq!(tree.capacity, 8); + assert_eq!(tree.length, 5); + } } + +// From c41eb6b51791817e92fa6517ca25644fad9b320f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 15:27:57 -0300 Subject: [PATCH 22/46] add with_capacity --- nssa/src/merkle_tree.rs | 374 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 368 insertions(+), 6 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index 31488ee..cf44322 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use sha2::{Digest, Sha256}; @@ -19,7 +19,7 @@ fn hash_value(value: &Value) -> Node { hasher.finalize().into() } -#[derive(Debug)] +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct MerkleTree { index_map: HashMap, node_map: HashMap, @@ -32,8 +32,78 @@ impl MerkleTree { *self.node_map.get(&0).unwrap() } - pub fn new(mut values: Vec) -> Self { - Self::deduplicate_values(&mut values); + pub fn with_capacity(capacity: usize) -> Self { + let base_length = capacity.next_power_of_two(); + let mut node_map = HashMap::::new(); + let mut current_layer_length = base_length; + let mut default_value_index = 0; + while current_layer_length > 0 { + let first_index = current_layer_length - 1; + let default_layer_value = default_values::DEFAULT_VALUES[default_value_index]; + let new_layer = (first_index..(first_index + current_layer_length)) + .map(|index| (index, default_layer_value)) + .collect::>(); + node_map.extend(new_layer); + + current_layer_length >>= 1; + default_value_index += 1; + } + Self { + index_map: HashMap::new(), + node_map, + capacity: base_length, + length: 0, + } + } + + pub fn insert(&mut self, value: Value) -> bool { + if self.index_map.contains_key(&value) { + return false; + } + + if self.capacity == self.length { + // TODO: implement extend capacity + return false; + } + + let new_index = self.length; + self.index_map.insert(value, new_index); + self.length += 1; + + let base_length = self.capacity; + let mut layer_node = hash_value(&value); + let mut layer_index = new_index + base_length - 1; + self.node_map.insert(layer_index, layer_node); + + let mut layer = self.capacity.trailing_zeros(); + while layer > 0 { + let is_left_child = layer_index & 1 == 1; + + let (parent_index, new_parent_node) = if is_left_child { + let parent_index = (layer_index - 1) >> 1; + let sibling = self.node_map.get(&(layer_index + 1)).unwrap(); + let new_parent_node = hash_two(&layer_node, sibling); + (parent_index, new_parent_node) + } else { + let parent_index = (layer_index - 2) >> 1; + let sibling = self.node_map.get(&(layer_index - 1)).unwrap(); + let new_parent_node = hash_two(sibling, &layer_node); + (parent_index, new_parent_node) + }; + + let node = self.node_map.get_mut(&parent_index).unwrap(); + *node = new_parent_node; + + layer -= 1; + layer_index = parent_index; + layer_node = new_parent_node + } + + true + } + + pub fn new(values: Vec) -> Self { + let values = Self::deduplicate_values_and_keep_order(values); let capacity = values.len().next_power_of_two(); let length = values.len(); @@ -86,8 +156,22 @@ impl MerkleTree { } } - fn deduplicate_values(values: &mut [Value]) { - // TODO: implement + fn add_value(&mut self, new_value: Value) { + if self.capacity < self.length { + } else { + } + } + + fn deduplicate_values_and_keep_order(values: Vec) -> Vec { + let mut result = Vec::new(); + let mut seen = HashSet::new(); + for value in values.into_iter() { + if !seen.contains(&value) { + seen.insert(value.clone()); + result.push(value); + } + } + result } } @@ -169,6 +253,284 @@ mod tests { assert_eq!(tree.capacity, 8); assert_eq!(tree.length, 5); } + + #[test] + fn test_merkle_tree_5() { + let values = vec![ + [11; 32], [12; 32], [12; 32], [13; 32], [14; 32], [15; 32], [15; 32], [13; 32], + [13; 32], [15; 32], [11; 32], + ]; + let tree = MerkleTree::new(values); + let expected_root = [ + 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, + 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, + ]; + + assert_eq!(tree.root(), expected_root); + assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); + assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); + assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); + assert_eq!(*tree.index_map.get(&[14; 32]).unwrap(), 3); + assert_eq!(*tree.index_map.get(&[15; 32]).unwrap(), 4); + assert_eq!(tree.capacity, 8); + assert_eq!(tree.length, 5); + } + + #[test] + fn test_with_capacity_4() { + let tree = MerkleTree::with_capacity(4); + + assert_eq!(tree.length, 0); + assert!(tree.index_map.is_empty()); + assert_eq!(tree.node_map.len(), 7); + for i in 3..7 { + assert_eq!( + *tree.node_map.get(&i).unwrap(), + default_values::DEFAULT_VALUES[0] + ) + } + for i in 1..3 { + assert_eq!( + *tree.node_map.get(&i).unwrap(), + default_values::DEFAULT_VALUES[1] + ) + } + assert_eq!( + *tree.node_map.get(&0).unwrap(), + default_values::DEFAULT_VALUES[2] + ) + } + + #[test] + fn test_with_capacity_5() { + let tree = MerkleTree::with_capacity(5); + + assert_eq!(tree.length, 0); + assert!(tree.index_map.is_empty()); + assert_eq!(tree.node_map.len(), 15); + for i in 7..15 { + assert_eq!( + *tree.node_map.get(&i).unwrap(), + default_values::DEFAULT_VALUES[0] + ) + } + for i in 3..7 { + assert_eq!( + *tree.node_map.get(&i).unwrap(), + default_values::DEFAULT_VALUES[1] + ) + } + for i in 1..3 { + assert_eq!( + *tree.node_map.get(&i).unwrap(), + default_values::DEFAULT_VALUES[2] + ) + } + assert_eq!( + *tree.node_map.get(&0).unwrap(), + default_values::DEFAULT_VALUES[3] + ) + } + + #[test] + fn test_insert_value_1() { + let mut tree = MerkleTree::with_capacity(3); + + let values = vec![[1; 32], [2; 32], [3; 32]]; + let expected_tree = MerkleTree::new(values.clone()); + + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[2]); + + assert_eq!(expected_tree, tree); + } + + #[test] + fn test_insert_value_2() { + let mut tree = MerkleTree::with_capacity(4); + + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; + let expected_tree = MerkleTree::new(values.clone()); + + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[2]); + tree.insert(values[3]); + + assert_eq!(expected_tree, tree); + } + + #[test] + fn test_insert_value_3() { + let mut tree = MerkleTree::with_capacity(5); + + let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; + let expected_tree = MerkleTree::new(values.clone()); + + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[2]); + tree.insert(values[3]); + tree.insert(values[4]); + + assert_eq!(expected_tree, tree); + } + + #[test] + fn test_insert_value_4() { + let mut tree = MerkleTree::with_capacity(5); + + let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; + let expected_tree = MerkleTree::new(values.clone()); + + tree.insert(values[0]); + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[1]); + tree.insert(values[2]); + tree.insert(values[3]); + tree.insert(values[2]); + tree.insert(values[0]); + tree.insert(values[4]); + tree.insert(values[2]); + tree.insert(values[4]); + + assert_eq!(expected_tree, tree); + } +} + +mod default_values { + pub(crate) const DEFAULT_VALUES: [[u8; 32]; 32] = [ + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ], + [ + 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, + 35, 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, + ], + [ + 219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, + 236, 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113, + ], + [ + 199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, + 237, 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60, + ], + [ + 83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, + 213, 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60, + ], + [ + 158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, + 3, 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48, + ], + [ + 216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, + 46, 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161, + ], + [ + 135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, + 6, 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44, + ], + [ + 38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, + 83, 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147, + ], + [ + 80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, + 90, 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225, + ], + [ + 255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, + 225, 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43, + ], + [ + 108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, + 23, 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32, + ], + [ + 183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, + 15, 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95, + ], + [ + 223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, + 103, 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94, + ], + [ + 181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, + 117, 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132, + ], + [ + 212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, + 167, 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187, + ], + [ + 143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, + 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235, + ], + [ + 141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, + 190, 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171, + ], + [ + 149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, + 84, 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164, + ], + [ + 248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, + 108, 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127, + ], + [ + 205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, + 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170, + ], + [ + 138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, + 138, 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156, + ], + [ + 254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, + 155, 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103, + ], + [ + 231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, + 236, 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215, + ], + [ + 49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, + 200, 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192, + ], + [ + 33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, + 67, 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68, + ], + [ + 97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, + 179, 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101, + ], + [ + 124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, + 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196, + ], + [ + 132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, + 184, 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225, + ], + [ + 136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, + 176, 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54, + ], + [ + 181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, + 192, 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92, + ], + [ + 152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, + 124, 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247, + ], + ]; } // From 499b23c3353df463722d7decf0580b5589718e69 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 16:21:05 -0300 Subject: [PATCH 23/46] refactor --- nssa/src/merkle_tree.rs | 158 +++++++++++----------------------------- 1 file changed, 42 insertions(+), 116 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index cf44322..c759a73 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -32,27 +32,31 @@ impl MerkleTree { *self.node_map.get(&0).unwrap() } - pub fn with_capacity(capacity: usize) -> Self { - let base_length = capacity.next_power_of_two(); - let mut node_map = HashMap::::new(); - let mut current_layer_length = base_length; - let mut default_value_index = 0; - while current_layer_length > 0 { - let first_index = current_layer_length - 1; - let default_layer_value = default_values::DEFAULT_VALUES[default_value_index]; - let new_layer = (first_index..(first_index + current_layer_length)) - .map(|index| (index, default_layer_value)) - .collect::>(); - node_map.extend(new_layer); + fn get_node(&self, index: &usize) -> &Node { + self.node_map.get(&index).unwrap_or_else(|| { + let index_depth = usize::BITS as usize - (index + 1).leading_zeros() as usize - 1; + let total_levels = self.capacity.trailing_zeros() as usize; + if total_levels >= index_depth { + &default_values::DEFAULT_VALUES[total_levels - index_depth] + } else { + //TODO: implement error handling + panic!(); + } + }) + } - current_layer_length >>= 1; - default_value_index += 1; - } + fn set_node(&mut self, index: usize, node: Node) { + self.node_map.insert(index, node); + } + + pub fn with_capacity(capacity: usize) -> Self { + let capacity = capacity.next_power_of_two(); + let length = 0; Self { index_map: HashMap::new(), - node_map, - capacity: base_length, - length: 0, + node_map: HashMap::new(), + capacity, + length, } } @@ -81,18 +85,17 @@ impl MerkleTree { let (parent_index, new_parent_node) = if is_left_child { let parent_index = (layer_index - 1) >> 1; - let sibling = self.node_map.get(&(layer_index + 1)).unwrap(); + let sibling = self.get_node(&(layer_index + 1)); let new_parent_node = hash_two(&layer_node, sibling); (parent_index, new_parent_node) } else { let parent_index = (layer_index - 2) >> 1; - let sibling = self.node_map.get(&(layer_index - 1)).unwrap(); + let sibling = self.get_node(&(layer_index - 1)); let new_parent_node = hash_two(sibling, &layer_node); (parent_index, new_parent_node) }; - let node = self.node_map.get_mut(&parent_index).unwrap(); - *node = new_parent_node; + self.set_node(parent_index, new_parent_node); layer -= 1; layer_index = parent_index; @@ -103,75 +106,19 @@ impl MerkleTree { } pub fn new(values: Vec) -> Self { - let values = Self::deduplicate_values_and_keep_order(values); - - let capacity = values.len().next_power_of_two(); - let length = values.len(); - - let base_length = capacity; - - let mut node_map: HashMap = values - .iter() - .enumerate() - .map(|(index, value)| (index + base_length - 1, hash_value(value))) - .collect(); - node_map.extend( - (values.len()..base_length) - .map(|index| (index + base_length - 1, [0; 32])) - .collect::>(), - ); - - let mut current_layer_length = base_length; - let mut current_layer_first_index = base_length - 1; - - while current_layer_length > 1 { - let next_layer_length = current_layer_length >> 1; - let next_layer_first_index = current_layer_first_index >> 1; - - let next_layer = (next_layer_first_index..(next_layer_first_index + next_layer_length)) - .map(|index| { - let left_child = node_map.get(&((index << 1) + 1)).unwrap(); - let right_child = node_map.get(&((index << 1) + 2)).unwrap(); - (index, hash_two(&left_child, &right_child)) - }) - .collect::>(); - - node_map.extend(&next_layer); - - current_layer_length = next_layer_length; - current_layer_first_index = next_layer_first_index; - } - - let index_map = values - .into_iter() - .enumerate() - .map(|(index, value)| (value, index)) - .collect(); - - Self { - index_map, - node_map, - capacity, - length, - } - } - - fn add_value(&mut self, new_value: Value) { - if self.capacity < self.length { - } else { - } - } - - fn deduplicate_values_and_keep_order(values: Vec) -> Vec { - let mut result = Vec::new(); + let mut deduplicated_values = Vec::with_capacity(values.len()); let mut seen = HashSet::new(); for value in values.into_iter() { if !seen.contains(&value) { - seen.insert(value.clone()); - result.push(value); + deduplicated_values.push(value); + seen.insert(value); } } - result + let mut this = Self::with_capacity(deduplicated_values.len()); + for value in deduplicated_values.into_iter() { + this.insert(value); + } + this } } @@ -282,23 +229,14 @@ mod tests { assert_eq!(tree.length, 0); assert!(tree.index_map.is_empty()); - assert_eq!(tree.node_map.len(), 7); + assert!(tree.node_map.is_empty()); for i in 3..7 { - assert_eq!( - *tree.node_map.get(&i).unwrap(), - default_values::DEFAULT_VALUES[0] - ) + assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[0], "{i}"); } for i in 1..3 { - assert_eq!( - *tree.node_map.get(&i).unwrap(), - default_values::DEFAULT_VALUES[1] - ) + assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[1], "{i}"); } - assert_eq!( - *tree.node_map.get(&0).unwrap(), - default_values::DEFAULT_VALUES[2] - ) + assert_eq!(*tree.get_node(&0), default_values::DEFAULT_VALUES[2]); } #[test] @@ -307,29 +245,17 @@ mod tests { assert_eq!(tree.length, 0); assert!(tree.index_map.is_empty()); - assert_eq!(tree.node_map.len(), 15); + assert!(tree.node_map.is_empty()); for i in 7..15 { - assert_eq!( - *tree.node_map.get(&i).unwrap(), - default_values::DEFAULT_VALUES[0] - ) + assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[0]) } for i in 3..7 { - assert_eq!( - *tree.node_map.get(&i).unwrap(), - default_values::DEFAULT_VALUES[1] - ) + assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[1]) } for i in 1..3 { - assert_eq!( - *tree.node_map.get(&i).unwrap(), - default_values::DEFAULT_VALUES[2] - ) + assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[2]) } - assert_eq!( - *tree.node_map.get(&0).unwrap(), - default_values::DEFAULT_VALUES[3] - ) + assert_eq!(*tree.get_node(&0), default_values::DEFAULT_VALUES[3]) } #[test] From b3f6b2756e2a094c6fccffb88955d6fdcbd5b9d7 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 18:26:54 -0300 Subject: [PATCH 24/46] add extend capacity --- nssa/src/merkle_tree.rs | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index c759a73..09105fb 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -21,6 +21,7 @@ fn hash_value(value: &Value) -> Node { #[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct MerkleTree { + values: Vec, index_map: HashMap, node_map: HashMap, capacity: usize, @@ -53,6 +54,7 @@ impl MerkleTree { let capacity = capacity.next_power_of_two(); let length = 0; Self { + values: Vec::new(), index_map: HashMap::new(), node_map: HashMap::new(), capacity, @@ -60,17 +62,25 @@ impl MerkleTree { } } + fn extend_capacity(&mut self) { + let mut this = Self::with_capacity(self.capacity << 1); + for value in self.values.iter() { + this.insert(*value); + } + *self = this; + } + pub fn insert(&mut self, value: Value) -> bool { if self.index_map.contains_key(&value) { return false; } if self.capacity == self.length { - // TODO: implement extend capacity - return false; + self.extend_capacity(); } let new_index = self.length; + self.values.push(value); self.index_map.insert(value, new_index); self.length += 1; @@ -131,13 +141,14 @@ mod tests { #[test] fn test_merkle_tree_1() { let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; - let tree = MerkleTree::new(values); + let tree = MerkleTree::new(values.clone()); let expected_root = [ 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, ]; assert_eq!(tree.root(), expected_root); + assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); @@ -149,13 +160,14 @@ mod tests { #[test] fn test_merkle_tree_2() { let values = vec![[1; 32], [2; 32], [3; 32], [0; 32]]; - let tree = MerkleTree::new(values); + let tree = MerkleTree::new(values.clone()); let expected_root = [ 201, 187, 184, 48, 150, 223, 133, 21, 122, 20, 110, 125, 119, 4, 85, 169, 132, 18, 222, 224, 99, 49, 135, 238, 134, 254, 230, 200, 164, 91, 131, 26, ]; assert_eq!(tree.root(), expected_root); + assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); @@ -167,13 +179,14 @@ mod tests { #[test] fn test_merkle_tree_3() { let values = vec![[1; 32], [2; 32], [3; 32]]; - let tree = MerkleTree::new(values); + let tree = MerkleTree::new(values.clone()); let expected_root = [ 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, ]; assert_eq!(tree.root(), expected_root); + assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); @@ -185,13 +198,14 @@ mod tests { #[test] fn test_merkle_tree_4() { let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; - let tree = MerkleTree::new(values); + let tree = MerkleTree::new(values.clone()); let expected_root = [ 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, ]; assert_eq!(tree.root(), expected_root); + assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); @@ -214,6 +228,10 @@ mod tests { ]; assert_eq!(tree.root(), expected_root); + assert_eq!( + tree.values, + vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]] + ); assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); @@ -260,7 +278,7 @@ mod tests { #[test] fn test_insert_value_1() { - let mut tree = MerkleTree::with_capacity(3); + let mut tree = MerkleTree::with_capacity(1); let values = vec![[1; 32], [2; 32], [3; 32]]; let expected_tree = MerkleTree::new(values.clone()); @@ -274,7 +292,7 @@ mod tests { #[test] fn test_insert_value_2() { - let mut tree = MerkleTree::with_capacity(4); + let mut tree = MerkleTree::with_capacity(1); let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; let expected_tree = MerkleTree::new(values.clone()); @@ -289,7 +307,7 @@ mod tests { #[test] fn test_insert_value_3() { - let mut tree = MerkleTree::with_capacity(5); + let mut tree = MerkleTree::with_capacity(1); let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; let expected_tree = MerkleTree::new(values.clone()); @@ -305,7 +323,7 @@ mod tests { #[test] fn test_insert_value_4() { - let mut tree = MerkleTree::with_capacity(5); + let mut tree = MerkleTree::with_capacity(1); let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; let expected_tree = MerkleTree::new(values.clone()); From 44a4f2f9f32d4999432e71bd88000edbff19a6cc Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 19:13:32 -0300 Subject: [PATCH 25/46] fix --- nssa/src/merkle_tree.rs | 79 ++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index 09105fb..917a836 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -25,12 +25,23 @@ pub struct MerkleTree { index_map: HashMap, node_map: HashMap, capacity: usize, - length: usize, } impl MerkleTree { pub fn root(&self) -> Node { - *self.node_map.get(&0).unwrap() + let root_index = self.root_index(); + *self.get_node(&root_index) + } + + fn root_index(&self) -> usize { + let total_levels = self.capacity.trailing_zeros() as usize; + let used_levels = self.size().trailing_zeros() as usize; + let diff = total_levels - used_levels; + if diff == 0 { 0 } else { (1 << diff) - 1 } + } + + fn size(&self) -> usize { + self.values.len().next_power_of_two() } fn get_node(&self, index: &usize) -> &Node { @@ -52,13 +63,11 @@ impl MerkleTree { pub fn with_capacity(capacity: usize) -> Self { let capacity = capacity.next_power_of_two(); - let length = 0; Self { - values: Vec::new(), - index_map: HashMap::new(), - node_map: HashMap::new(), + values: Vec::with_capacity(capacity), + index_map: HashMap::with_capacity(capacity), + node_map: HashMap::with_capacity(capacity << 1), capacity, - length, } } @@ -75,22 +84,22 @@ impl MerkleTree { return false; } - if self.capacity == self.length { + if self.capacity == self.values.len() { self.extend_capacity(); } - let new_index = self.length; + let new_index = self.values.len(); self.values.push(value); self.index_map.insert(value, new_index); - self.length += 1; let base_length = self.capacity; let mut layer_node = hash_value(&value); let mut layer_index = new_index + base_length - 1; self.node_map.insert(layer_index, layer_node); - let mut layer = self.capacity.trailing_zeros(); - while layer > 0 { + let mut layer = 0; + let mut top_layer = self.size().trailing_zeros(); + while layer < top_layer { let is_left_child = layer_index & 1 == 1; let (parent_index, new_parent_node) = if is_left_child { @@ -107,7 +116,7 @@ impl MerkleTree { self.set_node(parent_index, new_parent_node); - layer -= 1; + layer += 1; layer_index = parent_index; layer_node = new_parent_node } @@ -154,7 +163,6 @@ mod tests { assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); assert_eq!(*tree.index_map.get(&[4; 32]).unwrap(), 3); assert_eq!(tree.capacity, 4); - assert_eq!(tree.length, 4); } #[test] @@ -173,7 +181,6 @@ mod tests { assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); assert_eq!(*tree.index_map.get(&[0; 32]).unwrap(), 3); assert_eq!(tree.capacity, 4); - assert_eq!(tree.length, 4); } #[test] @@ -192,7 +199,6 @@ mod tests { assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); assert!(tree.index_map.get(&[0; 32]).is_none()); assert_eq!(tree.capacity, 4); - assert_eq!(tree.length, 3); } #[test] @@ -212,7 +218,6 @@ mod tests { assert_eq!(*tree.index_map.get(&[14; 32]).unwrap(), 3); assert_eq!(*tree.index_map.get(&[15; 32]).unwrap(), 4); assert_eq!(tree.capacity, 8); - assert_eq!(tree.length, 5); } #[test] @@ -238,14 +243,12 @@ mod tests { assert_eq!(*tree.index_map.get(&[14; 32]).unwrap(), 3); assert_eq!(*tree.index_map.get(&[15; 32]).unwrap(), 4); assert_eq!(tree.capacity, 8); - assert_eq!(tree.length, 5); } #[test] fn test_with_capacity_4() { let tree = MerkleTree::with_capacity(4); - assert_eq!(tree.length, 0); assert!(tree.index_map.is_empty()); assert!(tree.node_map.is_empty()); for i in 3..7 { @@ -261,7 +264,6 @@ mod tests { fn test_with_capacity_5() { let tree = MerkleTree::with_capacity(5); - assert_eq!(tree.length, 0); assert!(tree.index_map.is_empty()); assert!(tree.node_map.is_empty()); for i in 7..15 { @@ -276,6 +278,43 @@ mod tests { assert_eq!(*tree.get_node(&0), default_values::DEFAULT_VALUES[3]) } + #[test] + fn test_with_capacity_6() { + let mut tree = MerkleTree::with_capacity(100); + + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; + + let expected_root = [ + 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, + 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, + ]; + + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[2]); + tree.insert(values[3]); + + assert_eq!(tree.root(), expected_root); + } + + #[test] + fn test_with_capacity_7() { + let mut tree = MerkleTree::with_capacity(599); + + let values = vec![[1; 32], [2; 32], [3; 32]]; + + let expected_root = [ + 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, + 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, + ]; + + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[2]); + + assert_eq!(tree.root(), expected_root); + } + #[test] fn test_insert_value_1() { let mut tree = MerkleTree::with_capacity(1); From 096a404859604be8529561436e1604c43b88ab2e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 19:57:44 -0300 Subject: [PATCH 26/46] add auth paths --- nssa/src/merkle_tree.rs | 119 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index 917a836..132d537 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -34,14 +34,13 @@ impl MerkleTree { } fn root_index(&self) -> usize { - let total_levels = self.capacity.trailing_zeros() as usize; - let used_levels = self.size().trailing_zeros() as usize; - let diff = total_levels - used_levels; + let capacity_depth = self.capacity.trailing_zeros() as usize; + let diff = capacity_depth - self.depth(); if diff == 0 { 0 } else { (1 << diff) - 1 } } - fn size(&self) -> usize { - self.values.len().next_power_of_two() + fn depth(&self) -> usize { + self.values.len().next_power_of_two().trailing_zeros() as usize } fn get_node(&self, index: &usize) -> &Node { @@ -71,7 +70,7 @@ impl MerkleTree { } } - fn extend_capacity(&mut self) { + fn reallocate_to_double_capacity(&mut self) { let mut this = Self::with_capacity(self.capacity << 1); for value in self.values.iter() { this.insert(*value); @@ -85,7 +84,7 @@ impl MerkleTree { } if self.capacity == self.values.len() { - self.extend_capacity(); + self.reallocate_to_double_capacity(); } let new_index = self.values.len(); @@ -98,7 +97,7 @@ impl MerkleTree { self.node_map.insert(layer_index, layer_node); let mut layer = 0; - let mut top_layer = self.size().trailing_zeros(); + let mut top_layer = self.depth(); while layer < top_layer { let is_left_child = layer_index & 1 == 1; @@ -139,6 +138,28 @@ impl MerkleTree { } this } + + pub fn get_authentication_path_for(&self, value: &Value) -> Option<(usize, Vec)> { + let mut result = Vec::with_capacity(self.depth()); + let value_index = self.index_map.get(value)?; + let base_length = self.capacity; + let mut layer_index = base_length + value_index - 1; + let mut layer = 0; + let top_layer = self.depth(); + while layer < top_layer { + let is_left_child = layer_index & 1 == 1; + let (sibling, parent_index) = if is_left_child { + (self.get_node(&(layer_index + 1)), (layer_index - 1) >> 1) + } else { + (self.get_node(&(layer_index - 1)), (layer_index - 2) >> 1) + }; + result.push(*sibling); + + layer += 1; + layer_index = parent_index; + } + Some((*value_index, result)) + } } #[cfg(test)] @@ -245,6 +266,18 @@ mod tests { assert_eq!(tree.capacity, 8); } + #[test] + fn test_merkle_tree_6() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(values); + let expected_root = [ + 6, 156, 184, 37, 154, 6, 254, 110, 219, 63, 167, 255, 121, 51, 166, 221, 125, 202, 111, + 202, 41, 147, 20, 55, 151, 148, 166, 136, 146, 108, 55, 146, + ]; + + assert_eq!(tree.root(), expected_root); + } + #[test] fn test_with_capacity_4() { let tree = MerkleTree::with_capacity(4); @@ -381,6 +414,76 @@ mod tests { assert_eq!(expected_tree, tree); } + + #[test] + fn test_authentication_path_1() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; + let tree = MerkleTree::new(values); + let expected_authentication_path = ( + 2, + vec![ + [ + 159, 79, 182, 143, 62, 29, 172, 130, 32, 47, 154, 165, 129, 206, 11, 191, 31, + 118, 93, 240, 233, 172, 60, 140, 87, 226, 15, 104, 90, 186, 184, 237, + ], + [ + 80, 162, 125, 71, 70, 243, 87, 203, 112, 12, 190, 157, 72, 131, 183, 127, 182, + 79, 1, 40, 130, 138, 52, 137, 220, 106, 111, 33, 221, 191, 36, 20, + ], + ], + ); + + let authentication_path = tree.get_authentication_path_for(&[3; 32]).unwrap(); + assert_eq!(authentication_path, expected_authentication_path); + } + + #[test] + fn test_authentication_path_2() { + let values = vec![[1; 32], [2; 32], [3; 32]]; + let tree = MerkleTree::new(values); + let expected_authentication_path = ( + 0, + vec![ + [ + 117, 135, 123, 180, 29, 57, 59, 95, 184, 69, 92, 230, 14, 205, 141, 218, 0, 29, + 6, 49, 100, 150, 177, 77, 250, 127, 137, 86, 86, 238, 202, 74, + ], + [ + 164, 27, 133, 93, 45, 180, 222, 144, 82, 205, 123, 229, 236, 103, 214, 88, 102, + 41, 203, 159, 110, 50, 70, 164, 175, 165, 186, 49, 63, 7, 169, 197, + ], + ], + ); + + let authentication_path = tree.get_authentication_path_for(&[1; 32]).unwrap(); + assert_eq!(authentication_path, expected_authentication_path); + } + + #[test] + fn test_authentication_path_3() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(values); + let expected_authentication_path = ( + 4, + vec![ + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + [ + 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, + 0, 61, 35, 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, + ], + [ + 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, + 32, 207, 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, + ], + ], + ); + + let authentication_path = tree.get_authentication_path_for(&[5; 32]).unwrap(); + assert_eq!(authentication_path, expected_authentication_path); + } } mod default_values { From e9fbce210667383270b7d6ef685c48bede52d175 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 20:02:28 -0300 Subject: [PATCH 27/46] add test --- nssa/src/merkle_tree.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index 132d537..dfb14ed 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -348,6 +348,26 @@ mod tests { assert_eq!(tree.root(), expected_root); } + + #[test] + fn test_with_capacity_8() { + let mut tree = MerkleTree::with_capacity(1); + + let values = vec![[1; 32], [2; 32], [3; 32]]; + + let expected_root = [ + 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, + 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, + ]; + + tree.insert(values[0]); + tree.insert(values[1]); + tree.insert(values[2]); + + assert_eq!(tree.root(), expected_root); + } + + #[test] fn test_insert_value_1() { let mut tree = MerkleTree::with_capacity(1); From 0554500f2f82897f4e951f5b5f12c0c96f4c41ed Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 20 Aug 2025 20:25:56 -0300 Subject: [PATCH 28/46] remove vec attribute --- nssa/src/merkle_tree.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index dfb14ed..c716b3c 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -21,7 +21,6 @@ fn hash_value(value: &Value) -> Node { #[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct MerkleTree { - values: Vec, index_map: HashMap, node_map: HashMap, capacity: usize, @@ -40,7 +39,7 @@ impl MerkleTree { } fn depth(&self) -> usize { - self.values.len().next_power_of_two().trailing_zeros() as usize + self.index_map.len().next_power_of_two().trailing_zeros() as usize } fn get_node(&self, index: &usize) -> &Node { @@ -63,7 +62,6 @@ impl MerkleTree { pub fn with_capacity(capacity: usize) -> Self { let capacity = capacity.next_power_of_two(); Self { - values: Vec::with_capacity(capacity), index_map: HashMap::with_capacity(capacity), node_map: HashMap::with_capacity(capacity << 1), capacity, @@ -72,7 +70,9 @@ impl MerkleTree { fn reallocate_to_double_capacity(&mut self) { let mut this = Self::with_capacity(self.capacity << 1); - for value in self.values.iter() { + let mut pairs: Vec<_> = self.index_map.iter().collect(); + pairs.sort_by_key(|&(_, index)| index); + for (value, _) in pairs { this.insert(*value); } *self = this; @@ -83,12 +83,11 @@ impl MerkleTree { return false; } - if self.capacity == self.values.len() { + if self.capacity == self.index_map.len() { self.reallocate_to_double_capacity(); } - let new_index = self.values.len(); - self.values.push(value); + let new_index = self.index_map.len(); self.index_map.insert(value, new_index); let base_length = self.capacity; @@ -178,7 +177,6 @@ mod tests { ]; assert_eq!(tree.root(), expected_root); - assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); @@ -196,7 +194,6 @@ mod tests { ]; assert_eq!(tree.root(), expected_root); - assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); @@ -214,7 +211,6 @@ mod tests { ]; assert_eq!(tree.root(), expected_root); - assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); @@ -232,7 +228,6 @@ mod tests { ]; assert_eq!(tree.root(), expected_root); - assert_eq!(tree.values, values); assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); @@ -254,10 +249,6 @@ mod tests { ]; assert_eq!(tree.root(), expected_root); - assert_eq!( - tree.values, - vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]] - ); assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); From 6d56ee51db4ffcf15ce8678623d95e798f9c7da8 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 21 Aug 2025 08:11:33 -0300 Subject: [PATCH 29/46] add verification reference impl and tests --- nssa/src/merkle_tree.rs | 49 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index c716b3c..697cd7c 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -161,6 +161,22 @@ impl MerkleTree { } } +// Reference implementation +fn verify_authentication_path(value: &Value, index: usize, path: &[Node], root: &Node) -> bool { + let mut result = hash_value(value); + let mut level_index = index; + for node in path { + let is_left_child = level_index & 1 == 0; + if is_left_child { + result = hash_two(&result, node); + } else { + result = hash_two(node, &result); + } + level_index >>= 1; + } + &result == root +} + #[cfg(test)] mod tests { use nssa_core::account::{Account, NullifierPublicKey}; @@ -339,7 +355,6 @@ mod tests { assert_eq!(tree.root(), expected_root); } - #[test] fn test_with_capacity_8() { let mut tree = MerkleTree::with_capacity(1); @@ -358,7 +373,6 @@ mod tests { assert_eq!(tree.root(), expected_root); } - #[test] fn test_insert_value_1() { let mut tree = MerkleTree::with_capacity(1); @@ -495,6 +509,37 @@ mod tests { let authentication_path = tree.get_authentication_path_for(&[5; 32]).unwrap(); assert_eq!(authentication_path, expected_authentication_path); } + + #[test] + fn test_authentication_path_4() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(values); + let value = [6; 32]; + assert!(tree.get_authentication_path_for(&value).is_none()); + } + + + #[test] + fn test_authentication_path_5() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(values); + let value = [0; 32]; + assert!(tree.get_authentication_path_for(&value).is_none()); + } + + #[test] + fn test_authentication_path_6() { + let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(values); + let value = [5; 32]; + let (index, path) = tree.get_authentication_path_for(&value).unwrap(); + assert!(verify_authentication_path( + &value, + index, + &path, + &tree.root() + )); + } } mod default_values { From 8a0e2d780a2b26d98da23d016401f472eddb2ad8 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 21 Aug 2025 10:19:08 -0300 Subject: [PATCH 30/46] fix test with valid merkle proofs --- nssa/Cargo.toml | 1 + nssa/core/src/lib.rs | 35 +++++++++++++++---- nssa/src/merkle_tree.rs | 7 ++-- .../src/privacy_preserving_transaction/mod.rs | 29 +++++++++------ nssa/src/state.rs | 32 ++++++++++------- 5 files changed, 73 insertions(+), 31 deletions(-) diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index f479999..942834e 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -13,6 +13,7 @@ sha2 = "0.10.9" secp256k1 = "0.31.1" rand = "0.8" borsh = "1.5.7" +bytemuck = "1.13" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index a616237..56de12c 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,4 +1,7 @@ -use risc0_zkvm::serde::to_vec; +use risc0_zkvm::{ + serde::to_vec, + sha::{Impl, Sha256}, +}; use serde::{Deserialize, Serialize}; #[cfg(feature = "host")] @@ -21,15 +24,35 @@ pub mod program; #[cfg(feature = "host")] pub mod error; -pub type CommitmentSetDigest = [u32; 8]; -pub type MembershipProof = Vec<[u8; 32]>; +pub type CommitmentSetDigest = [u8; 32]; +pub type MembershipProof = (usize, Vec<[u8; 32]>); pub fn verify_membership_proof( commitment: &Commitment, proof: &MembershipProof, digest: &CommitmentSetDigest, ) -> bool { - // TODO: implement - true + let value_bytes = commitment.to_byte_array(); + let mut result: [u8; 32] = Impl::hash_bytes(&value_bytes) + .as_bytes() + .try_into() + .unwrap(); + let mut level_index = proof.0; + for node in &proof.1 { + let is_left_child = level_index & 1 == 0; + if is_left_child { + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(&result); + bytes[32..].copy_from_slice(node); + result = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); + } else { + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(node); + bytes[32..].copy_from_slice(&result); + result = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); + } + level_index >>= 1; + } + &result == digest } pub type IncomingViewingPublicKey = [u8; 32]; @@ -158,7 +181,7 @@ mod tests { &Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()), &[1; 32], )], - commitment_set_digest: [0, 1, 0, 1, 0, 1, 0, 1], + commitment_set_digest: [0xab; 32], }; let bytes = output.to_bytes(); let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree.rs index 697cd7c..9126150 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree.rs @@ -93,7 +93,7 @@ impl MerkleTree { let base_length = self.capacity; let mut layer_node = hash_value(&value); let mut layer_index = new_index + base_length - 1; - self.node_map.insert(layer_index, layer_node); + self.set_node(layer_index, layer_node); let mut layer = 0; let mut top_layer = self.depth(); @@ -159,6 +159,10 @@ impl MerkleTree { } Some((*value_index, result)) } + + pub(crate) fn contains(&self, value: &[u8; 32]) -> bool { + self.index_map.contains_key(value) + } } // Reference implementation @@ -518,7 +522,6 @@ mod tests { assert!(tree.get_authentication_path_for(&value).is_none()); } - #[test] fn test_authentication_path_5() { let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 17fe677..f4e2002 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -113,14 +113,16 @@ pub mod circuit { mod tests { use nssa_core::{ EncryptedAccountData, - account::{Account, AccountWithMetadata, NullifierPublicKey, NullifierSecretKey}, + account::{ + Account, AccountWithMetadata, Commitment, NullifierPublicKey, NullifierSecretKey, + }, }; use risc0_zkvm::{InnerReceipt, Journal, Receipt}; use crate::{ - Address, V01State, + Address, V01State, merkle_tree::MerkleTree, privacy_preserving_transaction::circuit::prove_privacy_preserving_execution_circuit, - program::Program, + program::Program, state::CommitmentSet, }; use super::*; @@ -153,7 +155,7 @@ mod tests { let private_account_keys = vec![(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])]; let private_account_auth = vec![]; let visibility_mask = vec![0, 2]; - let commitment_set_digest = [99; 8]; + let commitment_set_digest = [99; 32]; let program = Program::authenticated_transfer_program(); let (proof, output) = prove_privacy_preserving_execution_circuit( &pre_states, @@ -190,6 +192,10 @@ mod tests { is_authorized: true, }; + let private_key = [1; 32]; + let Npk = NullifierPublicKey::from(&private_key); + let commitment = Commitment::new(&Npk, &sender.account); + let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, @@ -197,19 +203,22 @@ mod tests { let balance_to_move: u128 = 37; + let commitment_set = CommitmentSet(MerkleTree::new(vec![commitment.to_byte_array()])); + let expected_sender_pre = sender.clone(); - let pre_states = vec![sender, recipient]; + let pre_states = vec![sender.clone(), recipient]; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let private_key = [1; 32]; let private_account_keys = vec![ - (NullifierPublicKey::from(&private_key), [2; 32], [3; 32]), + (Npk.clone(), [2; 32], [3; 32]), (NullifierPublicKey::from(&[2; 32]), [4; 32], [5; 32]), ]; - // TODO: Replace dummy authentication path when implemented - let private_account_auth = vec![(private_key, vec![])]; + let private_account_auth = vec![( + private_key, + commitment_set.get_proof_for(&commitment).unwrap(), + )]; let visibility_mask = vec![1, 2]; - let commitment_set_digest = [99; 8]; let program = Program::authenticated_transfer_program(); + let mut commitment_set_digest = commitment_set.digest(); let (proof, output) = prove_privacy_preserving_execution_circuit( &pre_states, &instruction_data, diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 101fa25..5a94bc3 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,29 +1,32 @@ use crate::{ - address::Address, error::NssaError, + address::Address, error::NssaError, merkle_tree::MerkleTree, privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program, public_transaction::PublicTransaction, }; use nssa_core::{ - CommitmentSetDigest, - account::{Account, Commitment, Nullifier}, - program::{DEFAULT_PROGRAM_ID, ProgramId}, + account::{Account, Commitment, Nullifier}, program::{ProgramId, DEFAULT_PROGRAM_ID}, CommitmentSetDigest, MembershipProof }; use std::collections::{HashMap, HashSet}; -struct CommitmentSet(HashSet); +pub(crate) struct CommitmentSet(pub(crate) MerkleTree); impl CommitmentSet { - fn extend(&mut self, commitments: Vec) { - self.0.extend(commitments) + fn extend(&mut self, commitments: &[Commitment]) { + for commitment in commitments { + self.0.insert(commitment.to_byte_array()); + } } - fn digest(&self) -> CommitmentSetDigest { - // TODO: implement - [0; 8] + pub fn digest(&self) -> CommitmentSetDigest { + self.0.root() + } + + pub fn get_proof_for(&self, commitment: &Commitment) -> Option { + self.0.get_authentication_path_for(&commitment.to_byte_array()) } fn contains(&self, commitment: &Commitment) -> bool { - self.0.contains(commitment) + self.0.contains(&commitment.to_byte_array()) } } @@ -54,7 +57,10 @@ impl V01State { let mut this = Self { public_state, - private_state: (CommitmentSet(HashSet::new()), NullifierSet::new()), + private_state: ( + CommitmentSet(MerkleTree::with_capacity(32)), + NullifierSet::new(), + ), builtin_programs: HashMap::new(), }; @@ -101,7 +107,7 @@ impl V01State { let message = tx.message(); // 2. Add new commitments - self.private_state.0.extend(message.new_commitments.clone()); + self.private_state.0.extend(&message.new_commitments); // 3. Add new nullifiers self.private_state.1.extend(message.new_nullifiers.clone()); From a5dc01d85eefc9594b4c034d7d82df887bda881e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 21 Aug 2025 10:45:16 -0300 Subject: [PATCH 31/46] improve test to match exact new commitments and new nullifiers --- .../src/privacy_preserving_transaction/mod.rs | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index f4e2002..5b76861 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -13,7 +13,6 @@ pub mod circuit { account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, program::{InstructionData, ProgramOutput}, }; - use rand::{Rng, RngCore, rngs::OsRng}; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; use crate::{error::NssaError, program::Program}; @@ -59,6 +58,7 @@ pub mod circuit { )], private_account_auth: &[(NullifierSecretKey, MembershipProof)], visibility_mask: &[u8], + private_account_nonces: &[u128], commitment_set_digest: CommitmentSetDigest, program: &Program, ) -> Result<(Proof, PrivacyPreservingCircuitOutput), NssaError> { @@ -69,10 +69,6 @@ pub mod circuit { .decode() .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; - let private_account_nonces: Vec<_> = (0..private_account_keys.len()) - .map(|_| new_random_nonce()) - .collect(); - let circuit_input = PrivacyPreservingCircuitInput { program_output, visibility_mask: visibility_mask.to_vec(), @@ -101,12 +97,6 @@ pub mod circuit { Ok((proof, circuit_output)) } - - fn new_random_nonce() -> u128 { - let mut u128_bytes = [0u8; 16]; - OsRng.fill_bytes(&mut u128_bytes); - u128::from_le_bytes(u128_bytes) - } } #[cfg(test)] @@ -114,7 +104,8 @@ mod tests { use nssa_core::{ EncryptedAccountData, account::{ - Account, AccountWithMetadata, Commitment, NullifierPublicKey, NullifierSecretKey, + Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey, + NullifierSecretKey, }, }; use risc0_zkvm::{InnerReceipt, Journal, Receipt}; @@ -125,6 +116,8 @@ mod tests { program::Program, state::CommitmentSet, }; + use rand::{Rng, RngCore, rngs::OsRng}; + use super::*; #[test] @@ -153,6 +146,8 @@ mod tests { let pre_states = vec![sender, recipient]; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); let private_account_keys = vec![(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])]; + let private_account_nonces = vec![0xdeadbeef]; + let private_account_auth = vec![]; let visibility_mask = vec![0, 2]; let commitment_set_digest = [99; 32]; @@ -163,6 +158,7 @@ mod tests { &private_account_keys, &private_account_auth, &visibility_mask, + &private_account_nonces, commitment_set_digest, &program, ) @@ -187,58 +183,74 @@ mod tests { let sender = AccountWithMetadata { account: Account { balance: 100, + nonce: 0xdeadbeef, ..Account::default() }, is_authorized: true, }; - - let private_key = [1; 32]; - let Npk = NullifierPublicKey::from(&private_key); - let commitment = Commitment::new(&Npk, &sender.account); - + let private_key_1 = [1; 32]; + let Npk1 = NullifierPublicKey::from(&private_key_1); + let commitment_sender = Commitment::new(&Npk1, &sender.account); let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, }; - + let private_key_2 = [2; 32]; + let Npk2 = NullifierPublicKey::from(&private_key_2); let balance_to_move: u128 = 37; - - let commitment_set = CommitmentSet(MerkleTree::new(vec![commitment.to_byte_array()])); - - let expected_sender_pre = sender.clone(); + let private_account_nonces = vec![0xdeadbeef1, 0xdeadbeef2]; + let commitment_set = + CommitmentSet(MerkleTree::new(vec![commitment_sender.to_byte_array()])); let pre_states = vec![sender.clone(), recipient]; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); let private_account_keys = vec![ - (Npk.clone(), [2; 32], [3; 32]), - (NullifierPublicKey::from(&[2; 32]), [4; 32], [5; 32]), + (Npk1.clone(), [2; 32], [3; 32]), + (Npk2.clone(), [4; 32], [5; 32]), ]; let private_account_auth = vec![( - private_key, - commitment_set.get_proof_for(&commitment).unwrap(), + private_key_1, + commitment_set.get_proof_for(&commitment_sender).unwrap(), )]; let visibility_mask = vec![1, 2]; let program = Program::authenticated_transfer_program(); let mut commitment_set_digest = commitment_set.digest(); + + let expected_private_account_1 = Account { + balance: 100 - balance_to_move, + nonce: private_account_nonces[0], + ..Default::default() + }; + let expected_private_account_2 = Account { + balance: balance_to_move, + nonce: private_account_nonces[1], + ..Default::default() + }; + let expected_new_commitments = vec![ + Commitment::new(&Npk1, &expected_private_account_1), + Commitment::new(&Npk2, &expected_private_account_2), + ]; + let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &private_key_1)]; + let (proof, output) = prove_privacy_preserving_execution_circuit( &pre_states, &instruction_data, &private_account_keys, &private_account_auth, &visibility_mask, + &private_account_nonces, commitment_set_digest, &program, ) .unwrap(); assert!(proof.is_valid_for(&output)); - - assert_eq!(output.public_post_states.len(), 0); - assert_eq!(output.public_pre_states.len(), 0); - assert_eq!(output.new_commitments.len(), 2); - assert_eq!(output.new_nullifiers.len(), 1); + assert!(output.public_pre_states.is_empty()); + assert!(output.public_post_states.is_empty()); + assert_eq!(output.new_commitments, expected_new_commitments); + assert_eq!(output.new_nullifiers, expected_new_nullifiers); assert_eq!(output.commitment_set_digest, commitment_set_digest); - assert_eq!(output.encrypted_private_post_states.len(), 2); // TODO: replace with real assertion when encryption is implemented + assert_eq!(output.encrypted_private_post_states.len(), 2); assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); assert_eq!(output.encrypted_private_post_states[1].to_bytes(), vec![0]); } From b6cabe8fb89f40354de23af02417af4cf12d999c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 21 Aug 2025 14:32:57 -0300 Subject: [PATCH 32/46] refactor. Add test for state transition from privacy preserving transaction --- .../src/bin/privacy_preserving_circuit.rs | 14 +- .../src/privacy_preserving_transaction/mod.rs | 187 ++++++++---------- .../transaction.rs | 5 - nssa/src/state.rs | 86 +++++++- 4 files changed, 176 insertions(+), 116 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 48c1b7c..cffa8fd 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -2,7 +2,7 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use nssa_core::{ account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, - program::{validate_execution, ProgramOutput}, + program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, verify_membership_proof, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, Tag, }; @@ -101,16 +101,20 @@ fn main() { } // Update post-state with new nonce - let mut post_with_updated_nonce = post_states[i].clone(); - post_with_updated_nonce.nonce = *new_nonce; + let mut post_with_updated_values = post_states[i].clone(); + post_with_updated_values.nonce = *new_nonce; + + if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { + post_with_updated_values.program_owner = program_id; + } // Compute commitment and push - let commitment_post = Commitment::new(Npk, &post_with_updated_nonce); + let commitment_post = Commitment::new(Npk, &post_with_updated_values); new_commitments.push(commitment_post); // Encrypt and push post state let encrypted_account = - EncryptedAccountData::new(&post_with_updated_nonce, esk, Npk, Ipk); + EncryptedAccountData::new(&post_with_updated_values, esk, Npk, Ipk); encrypted_private_post_states.push(encrypted_account); } _ => panic!("Invalid visibility mask value"), diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 5b76861..0ad639d 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -4,6 +4,7 @@ mod transaction; mod witness_set; pub use message::Message; +pub use witness_set::WitnessSet; pub use transaction::PrivacyPreservingTransaction; pub mod circuit { @@ -11,7 +12,7 @@ pub mod circuit { CommitmentSetDigest, EphemeralSecretKey, IncomingViewingPublicKey, MembershipProof, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, - program::{InstructionData, ProgramOutput}, + program::{InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -30,6 +31,56 @@ pub mod circuit { } } + pub fn execute_and_prove( + pre_states: &[AccountWithMetadata], + instruction_data: &InstructionData, + visibility_mask: &[u8], + private_account_nonces: &[u128], + private_account_keys: &[( + NullifierPublicKey, + IncomingViewingPublicKey, + EphemeralSecretKey, + )], + private_account_auth: &[(NullifierSecretKey, MembershipProof)], + program: &Program, + commitment_set_digest: &CommitmentSetDigest, + ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { + let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; + + let program_output: ProgramOutput = inner_receipt + .journal + .decode() + .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + + let circuit_input = PrivacyPreservingCircuitInput { + program_output, + visibility_mask: visibility_mask.to_vec(), + private_account_nonces: private_account_nonces.to_vec(), + private_account_keys: private_account_keys.to_vec(), + private_account_auth: private_account_auth.to_vec(), + program_id: program.id(), + commitment_set_digest: *commitment_set_digest, + }; + + // Prove circuit. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&circuit_input).unwrap(); + let env = env_builder.build().unwrap(); + let prover = default_prover(); + let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); + + let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); + + let circuit_output: PrivacyPreservingCircuitOutput = prove_info + .receipt + .journal + .decode() + .map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?; + + Ok((circuit_output, proof)) + } + fn execute_and_prove_program( program: &Program, pre_states: &[AccountWithMetadata], @@ -47,56 +98,6 @@ pub mod circuit { .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))? .receipt) } - - pub fn prove_privacy_preserving_execution_circuit( - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - private_account_keys: &[( - NullifierPublicKey, - IncomingViewingPublicKey, - EphemeralSecretKey, - )], - private_account_auth: &[(NullifierSecretKey, MembershipProof)], - visibility_mask: &[u8], - private_account_nonces: &[u128], - commitment_set_digest: CommitmentSetDigest, - program: &Program, - ) -> Result<(Proof, PrivacyPreservingCircuitOutput), NssaError> { - let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; - - let program_output: ProgramOutput = inner_receipt - .journal - .decode() - .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; - - let circuit_input = PrivacyPreservingCircuitInput { - program_output, - visibility_mask: visibility_mask.to_vec(), - private_account_nonces: private_account_nonces.to_vec(), - private_account_keys: private_account_keys.to_vec(), - private_account_auth: private_account_auth.to_vec(), - program_id: program.id(), - commitment_set_digest, - }; - - // Prove circuit. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&circuit_input).unwrap(); - let env = env_builder.build().unwrap(); - let prover = default_prover(); - let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); - - let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); - - let circuit_output: PrivacyPreservingCircuitOutput = prove_info - .receipt - .journal - .decode() - .map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?; - - Ok((proof, circuit_output)) - } } #[cfg(test)] @@ -111,9 +112,11 @@ mod tests { use risc0_zkvm::{InnerReceipt, Journal, Receipt}; use crate::{ - Address, V01State, merkle_tree::MerkleTree, - privacy_preserving_transaction::circuit::prove_privacy_preserving_execution_circuit, - program::Program, state::CommitmentSet, + Address, V01State, + merkle_tree::MerkleTree, + privacy_preserving_transaction::circuit::{Proof, execute_and_prove}, + program::Program, + state::CommitmentSet, }; use rand::{Rng, RngCore, rngs::OsRng}; @@ -143,24 +146,15 @@ mod tests { }; let expected_sender_pre = sender.clone(); - let pre_states = vec![sender, recipient]; - let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let private_account_keys = vec![(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])]; - let private_account_nonces = vec![0xdeadbeef]; - - let private_account_auth = vec![]; - let visibility_mask = vec![0, 2]; - let commitment_set_digest = [99; 32]; - let program = Program::authenticated_transfer_program(); - let (proof, output) = prove_privacy_preserving_execution_circuit( - &pre_states, - &instruction_data, - &private_account_keys, - &private_account_auth, - &visibility_mask, - &private_account_nonces, - commitment_set_digest, - &program, + let (output, proof) = execute_and_prove( + &[sender, recipient], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[0, 2], + &[0xdeadbeef], + &[(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])], + &[], + &Program::authenticated_transfer_program(), + &[99; 32], ) .unwrap(); @@ -172,7 +166,7 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(output.new_commitments.len(), 1); assert_eq!(output.new_nullifiers.len(), 0); - assert_eq!(output.commitment_set_digest, commitment_set_digest); + assert_eq!(output.commitment_set_digest, [99; 32]); assert_eq!(output.encrypted_private_post_states.len(), 1); // TODO: replace with real assertion when encryption is implemented assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); @@ -195,34 +189,19 @@ mod tests { account: Account::default(), is_authorized: false, }; - let private_key_2 = [2; 32]; - let Npk2 = NullifierPublicKey::from(&private_key_2); + let Npk2 = NullifierPublicKey::from(&[99; 32]); let balance_to_move: u128 = 37; - let private_account_nonces = vec![0xdeadbeef1, 0xdeadbeef2]; let commitment_set = CommitmentSet(MerkleTree::new(vec![commitment_sender.to_byte_array()])); - let pre_states = vec![sender.clone(), recipient]; - let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let private_account_keys = vec![ - (Npk1.clone(), [2; 32], [3; 32]), - (Npk2.clone(), [4; 32], [5; 32]), - ]; - let private_account_auth = vec![( - private_key_1, - commitment_set.get_proof_for(&commitment_sender).unwrap(), - )]; - let visibility_mask = vec![1, 2]; - let program = Program::authenticated_transfer_program(); - let mut commitment_set_digest = commitment_set.digest(); let expected_private_account_1 = Account { balance: 100 - balance_to_move, - nonce: private_account_nonces[0], + nonce: 0xdeadbeef1, ..Default::default() }; let expected_private_account_2 = Account { balance: balance_to_move, - nonce: private_account_nonces[1], + nonce: 0xdeadbeef2, ..Default::default() }; let expected_new_commitments = vec![ @@ -231,15 +210,21 @@ mod tests { ]; let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &private_key_1)]; - let (proof, output) = prove_privacy_preserving_execution_circuit( - &pre_states, - &instruction_data, - &private_account_keys, - &private_account_auth, - &visibility_mask, - &private_account_nonces, - commitment_set_digest, - &program, + let (output, proof) = execute_and_prove( + &[sender.clone(), recipient], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + (Npk1.clone(), [2; 32], [3; 32]), + (Npk2.clone(), [4; 32], [5; 32]), + ], + &[( + private_key_1, + commitment_set.get_proof_for(&commitment_sender).unwrap(), + )], + &Program::authenticated_transfer_program(), + &commitment_set.digest(), ) .unwrap(); @@ -248,7 +233,7 @@ mod tests { assert!(output.public_post_states.is_empty()); assert_eq!(output.new_commitments, expected_new_commitments); assert_eq!(output.new_nullifiers, expected_new_nullifiers); - assert_eq!(output.commitment_set_digest, commitment_set_digest); + assert_eq!(output.commitment_set_digest, commitment_set.digest()); // TODO: replace with real assertion when encryption is implemented assert_eq!(output.encrypted_private_post_states.len(), 2); assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 54d8356..b556a9c 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -165,8 +165,3 @@ fn n_unique(data: &[T]) -> usize { let set: HashSet<&T> = data.iter().collect(); set.len() } - -#[cfg(test)] -mod tests { - use crate::privacy_preserving_transaction::message::tests::message_for_tests; -} diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 5a94bc3..c05977f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -4,7 +4,9 @@ use crate::{ public_transaction::PublicTransaction, }; use nssa_core::{ - account::{Account, Commitment, Nullifier}, program::{ProgramId, DEFAULT_PROGRAM_ID}, CommitmentSetDigest, MembershipProof + CommitmentSetDigest, MembershipProof, + account::{Account, Commitment, Nullifier}, + program::{DEFAULT_PROGRAM_ID, ProgramId}, }; use std::collections::{HashMap, HashSet}; @@ -22,7 +24,8 @@ impl CommitmentSet { } pub fn get_proof_for(&self, commitment: &Commitment) -> Option { - self.0.get_authentication_path_for(&commitment.to_byte_array()) + self.0 + .get_authentication_path_for(&commitment.to_byte_array()) } fn contains(&self, commitment: &Commitment) -> bool { @@ -123,6 +126,7 @@ impl V01State { let current_account = self.get_account_by_address_mut(address); current_account.nonce += 1; } + Ok(()) } @@ -180,10 +184,16 @@ mod tests { use std::collections::HashMap; use crate::{ - Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program, - public_transaction, signature::PrivateKey, + Address, PublicKey, PublicTransaction, V01State, + error::NssaError, + privacy_preserving_transaction::{ + Message, PrivacyPreservingTransaction, WitnessSet, circuit::execute_and_prove, + }, + program::Program, + public_transaction, + signature::PrivateKey, }; - use nssa_core::account::Account; + use nssa_core::account::{Account, AccountWithMetadata, NullifierPublicKey}; fn transfer_transaction( from: Address, @@ -670,4 +680,70 @@ mod tests { assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } + + #[test] + fn test_transition_from_privacy_preserving_transaction() { + let sender = AccountWithMetadata { + account: Account { + balance: 200, + program_owner: Program::authenticated_transfer_program().id(), + ..Account::default() + }, + is_authorized: true, + }; + let sender_signing_key = PrivateKey::try_new([1; 32]).unwrap(); + let sender_address = + Address::from_public_key(&PublicKey::new_from_private_key(&sender_signing_key)); + + let recipient = AccountWithMetadata { + account: Account { + ..Account::default() + }, + is_authorized: false, + }; + + let recipient_npk = NullifierPublicKey::from(&[1; 32]); + let recipient_ivk = [2; 32]; + let esk = [3; 32]; + + let balance_to_move: u128 = 37; + + let mut state = V01State::new_with_genesis_accounts(&[(*sender_address.value(), 200)]); + + let (output, proof) = execute_and_prove( + &[sender, recipient], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[0, 2], + &[0xdeadbeef], + &[(recipient_npk, recipient_ivk, esk)], + &[], + &Program::authenticated_transfer_program(), + &state.commitment_set_digest(), + ) + .unwrap(); + + let message = Message::new( + vec![sender_address.clone()], + vec![0], + output.public_post_states, + output.encrypted_private_post_states, + output.new_commitments.clone(), + output.new_nullifiers, + ); + + let witness_set = WitnessSet::for_message(&message, proof, &[&sender_signing_key]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + assert!(!state.private_state.0.contains(&output.new_commitments[0])); + + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + assert!(state.private_state.0.contains(&output.new_commitments[0])); + assert_eq!( + state.get_account_by_address(&sender_address).balance, + 200 - 37 + ); + } } From 9b7c2587fe6d0630a5b445d9d33cb77e59fd4bbf Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 21 Aug 2025 15:52:35 -0300 Subject: [PATCH 33/46] minor changes --- .../src/bin/privacy_preserving_circuit.rs | 3 ++- .../src/privacy_preserving_transaction/mod.rs | 8 +++++-- nssa/src/state.rs | 21 +++++++------------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index cffa8fd..e5f64b3 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -105,10 +105,11 @@ fn main() { post_with_updated_values.nonce = *new_nonce; if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { + // Claim account post_with_updated_values.program_owner = program_id; } - // Compute commitment and push + // Compute commitment let commitment_post = Commitment::new(Npk, &post_with_updated_values); new_commitments.push(commitment_post); diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 0ad639d..fc34896 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -4,8 +4,8 @@ mod transaction; mod witness_set; pub use message::Message; -pub use witness_set::WitnessSet; pub use transaction::PrivacyPreservingTransaction; +pub use witness_set::WitnessSet; pub mod circuit { use nssa_core::{ @@ -194,12 +194,16 @@ mod tests { let commitment_set = CommitmentSet(MerkleTree::new(vec![commitment_sender.to_byte_array()])); + let program = Program::authenticated_transfer_program(); + let expected_private_account_1 = Account { + program_owner: program.id(), balance: 100 - balance_to_move, nonce: 0xdeadbeef1, ..Default::default() }; let expected_private_account_2 = Account { + program_owner: program.id(), balance: balance_to_move, nonce: 0xdeadbeef2, ..Default::default() @@ -223,7 +227,7 @@ mod tests { private_key_1, commitment_set.get_proof_for(&commitment_sender).unwrap(), )], - &Program::authenticated_transfer_program(), + &program, &commitment_set.digest(), ) .unwrap(); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index c05977f..f34839d 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -683,22 +683,19 @@ mod tests { #[test] fn test_transition_from_privacy_preserving_transaction() { - let sender = AccountWithMetadata { - account: Account { - balance: 200, - program_owner: Program::authenticated_transfer_program().id(), - ..Account::default() - }, - is_authorized: true, - }; let sender_signing_key = PrivateKey::try_new([1; 32]).unwrap(); let sender_address = Address::from_public_key(&PublicKey::new_from_private_key(&sender_signing_key)); + let mut state = V01State::new_with_genesis_accounts(&[(*sender_address.value(), 200)]); + + let sender = AccountWithMetadata { + account: state.get_account_by_address(&sender_address), + is_authorized: true, + }; + let recipient = AccountWithMetadata { - account: Account { - ..Account::default() - }, + account: Account::default(), is_authorized: false, }; @@ -708,8 +705,6 @@ mod tests { let balance_to_move: u128 = 37; - let mut state = V01State::new_with_genesis_accounts(&[(*sender_address.value(), 200)]); - let (output, proof) = execute_and_prove( &[sender, recipient], &Program::serialize_instruction(balance_to_move).unwrap(), From d53edf7b6152c96c8fe583227bf7ec689b8e1232 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 08:28:38 -0300 Subject: [PATCH 34/46] remove unused dep --- nssa/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 3ec3947..0939c39 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -15,7 +15,6 @@ rand = "0.8" borsh = "1.5.7" bytemuck = "1.13" hex = "0.4.3" -anyhow.workspace = true [dev-dependencies] test-program-methods = { path = "test_program_methods" } From 79727f01953b5a16855c0700781046b0fdd06bf1 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 08:32:05 -0300 Subject: [PATCH 35/46] refactor circuit module into file --- .../privacy_preserving_transaction/circuit.rs | 235 +++++++++++++++++ .../src/privacy_preserving_transaction/mod.rs | 240 +----------------- 2 files changed, 237 insertions(+), 238 deletions(-) create mode 100644 nssa/src/privacy_preserving_transaction/circuit.rs diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs new file mode 100644 index 0000000..00ee78c --- /dev/null +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -0,0 +1,235 @@ +use nssa_core::{ + CommitmentSetDigest, EphemeralSecretKey, IncomingViewingPublicKey, MembershipProof, + PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, + program::{InstructionData, ProgramId, ProgramOutput}, +}; +use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; + +use crate::{error::NssaError, program::Program}; + +use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Proof(Vec); + +impl Proof { + pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { + let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); + let receipt = Receipt::new(inner, circuit_output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() + } +} + +pub fn execute_and_prove( + pre_states: &[AccountWithMetadata], + instruction_data: &InstructionData, + visibility_mask: &[u8], + private_account_nonces: &[u128], + private_account_keys: &[( + NullifierPublicKey, + IncomingViewingPublicKey, + EphemeralSecretKey, + )], + private_account_auth: &[(NullifierSecretKey, MembershipProof)], + program: &Program, + commitment_set_digest: &CommitmentSetDigest, +) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { + let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; + + let program_output: ProgramOutput = inner_receipt + .journal + .decode() + .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + + let circuit_input = PrivacyPreservingCircuitInput { + program_output, + visibility_mask: visibility_mask.to_vec(), + private_account_nonces: private_account_nonces.to_vec(), + private_account_keys: private_account_keys.to_vec(), + private_account_auth: private_account_auth.to_vec(), + program_id: program.id(), + commitment_set_digest: *commitment_set_digest, + }; + + // Prove circuit. + let mut env_builder = ExecutorEnv::builder(); + env_builder.add_assumption(inner_receipt); + env_builder.write(&circuit_input).unwrap(); + let env = env_builder.build().unwrap(); + let prover = default_prover(); + let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); + + let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); + + let circuit_output: PrivacyPreservingCircuitOutput = prove_info + .receipt + .journal + .decode() + .map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?; + + Ok((circuit_output, proof)) +} + +fn execute_and_prove_program( + program: &Program, + pre_states: &[AccountWithMetadata], + instruction_data: &InstructionData, +) -> Result { + // Write inputs to the program + let mut env_builder = ExecutorEnv::builder(); + Program::write_inputs(pre_states, instruction_data, &mut env_builder)?; + let env = env_builder.build().unwrap(); + + // Prove the program + let prover = default_prover(); + Ok(prover + .prove(env, program.elf()) + .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))? + .receipt) +} + +#[cfg(test)] +mod tests { + use nssa_core::{ + EncryptedAccountData, + account::{ + Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey, + NullifierSecretKey, + }, + }; + use risc0_zkvm::{InnerReceipt, Journal, Receipt}; + + use crate::{ + Address, V01State, + merkle_tree::MerkleTree, + privacy_preserving_transaction::circuit::{Proof, execute_and_prove}, + program::Program, + state::CommitmentSet, + }; + + use rand::{Rng, RngCore, rngs::OsRng}; + + use super::*; + + #[test] + fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { + let sender = AccountWithMetadata { + account: Account { + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + + let recipient = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let balance_to_move: u128 = 37; + + let expected_sender_post = Account { + balance: 100 - balance_to_move, + ..Default::default() + }; + + let expected_sender_pre = sender.clone(); + let (output, proof) = execute_and_prove( + &[sender, recipient], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[0, 2], + &[0xdeadbeef], + &[(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])], + &[], + &Program::authenticated_transfer_program(), + &[99; 32], + ) + .unwrap(); + + assert!(proof.is_valid_for(&output)); + + let [sender_pre] = output.public_pre_states.try_into().unwrap(); + let [sender_post] = output.public_post_states.try_into().unwrap(); + assert_eq!(sender_pre, expected_sender_pre); + assert_eq!(sender_post, expected_sender_post); + assert_eq!(output.new_commitments.len(), 1); + assert_eq!(output.new_nullifiers.len(), 0); + assert_eq!(output.commitment_set_digest, [99; 32]); + assert_eq!(output.encrypted_private_post_states.len(), 1); + // TODO: replace with real assertion when encryption is implemented + assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); + } + + #[test] + fn prove_privacy_preserving_execution_circuit_fully_private() { + let sender = AccountWithMetadata { + account: Account { + balance: 100, + nonce: 0xdeadbeef, + ..Account::default() + }, + is_authorized: true, + }; + let private_key_1 = [1; 32]; + let Npk1 = NullifierPublicKey::from(&private_key_1); + let commitment_sender = Commitment::new(&Npk1, &sender.account); + let recipient = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + let Npk2 = NullifierPublicKey::from(&[99; 32]); + let balance_to_move: u128 = 37; + let commitment_set = + CommitmentSet(MerkleTree::new(vec![commitment_sender.to_byte_array()])); + + let program = Program::authenticated_transfer_program(); + + let expected_private_account_1 = Account { + program_owner: program.id(), + balance: 100 - balance_to_move, + nonce: 0xdeadbeef1, + ..Default::default() + }; + let expected_private_account_2 = Account { + program_owner: program.id(), + balance: balance_to_move, + nonce: 0xdeadbeef2, + ..Default::default() + }; + let expected_new_commitments = vec![ + Commitment::new(&Npk1, &expected_private_account_1), + Commitment::new(&Npk2, &expected_private_account_2), + ]; + let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &private_key_1)]; + + let (output, proof) = execute_and_prove( + &[sender.clone(), recipient], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + (Npk1.clone(), [2; 32], [3; 32]), + (Npk2.clone(), [4; 32], [5; 32]), + ], + &[( + private_key_1, + commitment_set.get_proof_for(&commitment_sender).unwrap(), + )], + &program, + &commitment_set.digest(), + ) + .unwrap(); + + assert!(proof.is_valid_for(&output)); + assert!(output.public_pre_states.is_empty()); + assert!(output.public_post_states.is_empty()); + assert_eq!(output.new_commitments, expected_new_commitments); + assert_eq!(output.new_nullifiers, expected_new_nullifiers); + assert_eq!(output.commitment_set_digest, commitment_set.digest()); + // TODO: replace with real assertion when encryption is implemented + assert_eq!(output.encrypted_private_post_states.len(), 2); + assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); + assert_eq!(output.encrypted_private_post_states[1].to_bytes(), vec![0]); + } +} diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index fc34896..1d36118 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -3,244 +3,8 @@ mod message; mod transaction; mod witness_set; +pub mod circuit; + pub use message::Message; pub use transaction::PrivacyPreservingTransaction; pub use witness_set::WitnessSet; - -pub mod circuit { - use nssa_core::{ - CommitmentSetDigest, EphemeralSecretKey, IncomingViewingPublicKey, MembershipProof, - PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, - account::{Account, AccountWithMetadata, Nonce, NullifierPublicKey, NullifierSecretKey}, - program::{InstructionData, ProgramId, ProgramOutput}, - }; - use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; - - use crate::{error::NssaError, program::Program}; - - use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}; - - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct Proof(Vec); - - impl Proof { - pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { - let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); - let receipt = Receipt::new(inner, circuit_output.to_bytes()); - receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() - } - } - - pub fn execute_and_prove( - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - visibility_mask: &[u8], - private_account_nonces: &[u128], - private_account_keys: &[( - NullifierPublicKey, - IncomingViewingPublicKey, - EphemeralSecretKey, - )], - private_account_auth: &[(NullifierSecretKey, MembershipProof)], - program: &Program, - commitment_set_digest: &CommitmentSetDigest, - ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { - let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; - - let program_output: ProgramOutput = inner_receipt - .journal - .decode() - .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; - - let circuit_input = PrivacyPreservingCircuitInput { - program_output, - visibility_mask: visibility_mask.to_vec(), - private_account_nonces: private_account_nonces.to_vec(), - private_account_keys: private_account_keys.to_vec(), - private_account_auth: private_account_auth.to_vec(), - program_id: program.id(), - commitment_set_digest: *commitment_set_digest, - }; - - // Prove circuit. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); - env_builder.write(&circuit_input).unwrap(); - let env = env_builder.build().unwrap(); - let prover = default_prover(); - let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); - - let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); - - let circuit_output: PrivacyPreservingCircuitOutput = prove_info - .receipt - .journal - .decode() - .map_err(|e| NssaError::CircuitOutputDeserializationError(e.to_string()))?; - - Ok((circuit_output, proof)) - } - - fn execute_and_prove_program( - program: &Program, - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - ) -> Result { - // Write inputs to the program - let mut env_builder = ExecutorEnv::builder(); - Program::write_inputs(pre_states, instruction_data, &mut env_builder)?; - let env = env_builder.build().unwrap(); - - // Prove the program - let prover = default_prover(); - Ok(prover - .prove(env, program.elf()) - .map_err(|e| NssaError::ProgramProveFailed(e.to_string()))? - .receipt) - } -} - -#[cfg(test)] -mod tests { - use nssa_core::{ - EncryptedAccountData, - account::{ - Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey, - NullifierSecretKey, - }, - }; - use risc0_zkvm::{InnerReceipt, Journal, Receipt}; - - use crate::{ - Address, V01State, - merkle_tree::MerkleTree, - privacy_preserving_transaction::circuit::{Proof, execute_and_prove}, - program::Program, - state::CommitmentSet, - }; - - use rand::{Rng, RngCore, rngs::OsRng}; - - use super::*; - - #[test] - fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { - let sender = AccountWithMetadata { - account: Account { - balance: 100, - ..Account::default() - }, - is_authorized: true, - }; - - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - }; - - let balance_to_move: u128 = 37; - - let expected_sender_post = Account { - balance: 100 - balance_to_move, - ..Default::default() - }; - - let expected_sender_pre = sender.clone(); - let (output, proof) = execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])], - &[], - &Program::authenticated_transfer_program(), - &[99; 32], - ) - .unwrap(); - - assert!(proof.is_valid_for(&output)); - - let [sender_pre] = output.public_pre_states.try_into().unwrap(); - let [sender_post] = output.public_post_states.try_into().unwrap(); - assert_eq!(sender_pre, expected_sender_pre); - assert_eq!(sender_post, expected_sender_post); - assert_eq!(output.new_commitments.len(), 1); - assert_eq!(output.new_nullifiers.len(), 0); - assert_eq!(output.commitment_set_digest, [99; 32]); - assert_eq!(output.encrypted_private_post_states.len(), 1); - // TODO: replace with real assertion when encryption is implemented - assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); - } - - #[test] - fn prove_privacy_preserving_execution_circuit_fully_private() { - let sender = AccountWithMetadata { - account: Account { - balance: 100, - nonce: 0xdeadbeef, - ..Account::default() - }, - is_authorized: true, - }; - let private_key_1 = [1; 32]; - let Npk1 = NullifierPublicKey::from(&private_key_1); - let commitment_sender = Commitment::new(&Npk1, &sender.account); - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - }; - let Npk2 = NullifierPublicKey::from(&[99; 32]); - let balance_to_move: u128 = 37; - let commitment_set = - CommitmentSet(MerkleTree::new(vec![commitment_sender.to_byte_array()])); - - let program = Program::authenticated_transfer_program(); - - let expected_private_account_1 = Account { - program_owner: program.id(), - balance: 100 - balance_to_move, - nonce: 0xdeadbeef1, - ..Default::default() - }; - let expected_private_account_2 = Account { - program_owner: program.id(), - balance: balance_to_move, - nonce: 0xdeadbeef2, - ..Default::default() - }; - let expected_new_commitments = vec![ - Commitment::new(&Npk1, &expected_private_account_1), - Commitment::new(&Npk2, &expected_private_account_2), - ]; - let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &private_key_1)]; - - let (output, proof) = execute_and_prove( - &[sender.clone(), recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ - (Npk1.clone(), [2; 32], [3; 32]), - (Npk2.clone(), [4; 32], [5; 32]), - ], - &[( - private_key_1, - commitment_set.get_proof_for(&commitment_sender).unwrap(), - )], - &program, - &commitment_set.digest(), - ) - .unwrap(); - - assert!(proof.is_valid_for(&output)); - assert!(output.public_pre_states.is_empty()); - assert!(output.public_post_states.is_empty()); - assert_eq!(output.new_commitments, expected_new_commitments); - assert_eq!(output.new_nullifiers, expected_new_nullifiers); - assert_eq!(output.commitment_set_digest, commitment_set.digest()); - // TODO: replace with real assertion when encryption is implemented - assert_eq!(output.encrypted_private_post_states.len(), 2); - assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); - assert_eq!(output.encrypted_private_post_states[1].to_bytes(), vec![0]); - } -} From 02ad6129d60de4135b994e31047fb9dbbd2c8e56 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 08:34:47 -0300 Subject: [PATCH 36/46] refactor merkle tree into file --- nssa/src/merkle_tree/default_values.rs | 130 +++++++++++++++++ .../{merkle_tree.rs => merkle_tree/mod.rs} | 135 +----------------- 2 files changed, 133 insertions(+), 132 deletions(-) create mode 100644 nssa/src/merkle_tree/default_values.rs rename nssa/src/{merkle_tree.rs => merkle_tree/mod.rs} (74%) diff --git a/nssa/src/merkle_tree/default_values.rs b/nssa/src/merkle_tree/default_values.rs new file mode 100644 index 0000000..0316644 --- /dev/null +++ b/nssa/src/merkle_tree/default_values.rs @@ -0,0 +1,130 @@ +pub(crate) const DEFAULT_VALUES: [[u8; 32]; 32] = [ + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ], + [ + 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35, + 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, + ], + [ + 219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, 236, + 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113, + ], + [ + 199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, 237, + 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60, + ], + [ + 83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, 213, + 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60, + ], + [ + 158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, 3, + 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48, + ], + [ + 216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, 46, + 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161, + ], + [ + 135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, 6, + 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44, + ], + [ + 38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, 83, + 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147, + ], + [ + 80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, 90, + 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225, + ], + [ + 255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, 225, + 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43, + ], + [ + 108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, 23, + 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32, + ], + [ + 183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, 15, + 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95, + ], + [ + 223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, 103, + 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94, + ], + [ + 181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, 117, + 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132, + ], + [ + 212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, 167, + 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187, + ], + [ + 143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, + 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235, + ], + [ + 141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, 190, + 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171, + ], + [ + 149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, 84, + 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164, + ], + [ + 248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, 108, + 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127, + ], + [ + 205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, + 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170, + ], + [ + 138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, 138, + 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156, + ], + [ + 254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, 155, + 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103, + ], + [ + 231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, 236, + 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215, + ], + [ + 49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, 200, + 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192, + ], + [ + 33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, 67, + 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68, + ], + [ + 97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, 179, + 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101, + ], + [ + 124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, + 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196, + ], + [ + 132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, 184, + 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225, + ], + [ + 136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, 176, + 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54, + ], + [ + 181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, 192, + 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92, + ], + [ + 152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, 124, + 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247, + ], +]; diff --git a/nssa/src/merkle_tree.rs b/nssa/src/merkle_tree/mod.rs similarity index 74% rename from nssa/src/merkle_tree.rs rename to nssa/src/merkle_tree/mod.rs index 9126150..6b5e2ed 100644 --- a/nssa/src/merkle_tree.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -2,6 +2,8 @@ use std::collections::{HashMap, HashSet}; use sha2::{Digest, Sha256}; +mod default_values; + type Value = [u8; 32]; type Node = [u8; 32]; @@ -545,137 +547,6 @@ mod tests { } } -mod default_values { - pub(crate) const DEFAULT_VALUES: [[u8; 32]; 32] = [ - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ], - [ - 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, - 35, 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, - ], - [ - 219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, - 236, 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113, - ], - [ - 199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, - 237, 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60, - ], - [ - 83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, - 213, 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60, - ], - [ - 158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, - 3, 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48, - ], - [ - 216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, - 46, 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161, - ], - [ - 135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, - 6, 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44, - ], - [ - 38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, - 83, 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147, - ], - [ - 80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, - 90, 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225, - ], - [ - 255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, - 225, 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43, - ], - [ - 108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, - 23, 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32, - ], - [ - 183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, - 15, 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95, - ], - [ - 223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, - 103, 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94, - ], - [ - 181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, - 117, 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132, - ], - [ - 212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, - 167, 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187, - ], - [ - 143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, - 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235, - ], - [ - 141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, - 190, 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171, - ], - [ - 149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, - 84, 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164, - ], - [ - 248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, - 108, 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127, - ], - [ - 205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, - 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170, - ], - [ - 138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, - 138, 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156, - ], - [ - 254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, - 155, 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103, - ], - [ - 231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, - 236, 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215, - ], - [ - 49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, - 200, 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192, - ], - [ - 33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, - 67, 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68, - ], - [ - 97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, - 179, 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101, - ], - [ - 124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, - 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196, - ], - [ - 132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, - 184, 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225, - ], - [ - 136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, - 176, 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54, - ], - [ - 181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, - 192, 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92, - ], - [ - 152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, - 124, 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247, - ], - ]; -} // + From 1ca3d68d7e04feb41e2c7828b8342fc3ab550cdd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 08:53:40 -0300 Subject: [PATCH 37/46] remove serde for nssa::address --- accounts/src/account_core/mod.rs | 68 +++----------------------------- nssa/src/address.rs | 24 ----------- nssa/src/merkle_tree/mod.rs | 2 - 3 files changed, 6 insertions(+), 88 deletions(-) diff --git a/accounts/src/account_core/mod.rs b/accounts/src/account_core/mod.rs index 329e1e1..944f595 100644 --- a/accounts/src/account_core/mod.rs +++ b/accounts/src/account_core/mod.rs @@ -27,7 +27,7 @@ pub struct Account { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct AccountForSerialization { pub key_holder: AddressKeyHolder, - pub address: Address, + pub address: String, pub balance: u64, pub utxos: HashMap, } @@ -36,8 +36,8 @@ impl From for AccountForSerialization { fn from(value: Account) -> Self { AccountForSerialization { key_holder: value.key_holder, - address: value.address, balance: value.balance, + address: value.address.to_string(), utxos: value .utxos .into_iter() @@ -49,9 +49,12 @@ impl From for AccountForSerialization { impl From for Account { fn from(value: AccountForSerialization) -> Self { + let public_key = + nssa::PublicKey::new_from_private_key(value.key_holder.get_pub_account_signing_key()); + let address = nssa::Address::from(&public_key); Account { key_holder: value.key_holder, - address: value.address, + address, balance: value.balance, utxos: value .utxos @@ -82,34 +85,6 @@ impl<'de> Deserialize<'de> for Account { } } -///A strucure, which represents all the visible(public) information -/// -/// known to each node about account `address` -/// -/// Main usage is to encode data for other account -#[derive(Serialize, Clone)] -pub struct AccountPublicMask { - pub nullifier_public_key: AffinePoint, - pub viewing_public_key: AffinePoint, - pub address: Address, - pub balance: u64, -} - -impl AccountPublicMask { - pub fn encrypt_data( - ephemeral_key_holder: &EphemeralKeyHolder, - viewing_public_key_receiver: AffinePoint, - data: &[u8], - ) -> (CipherText, Nonce) { - //Using of parent Account fuction - Account::encrypt_data(ephemeral_key_holder, viewing_public_key_receiver, data) - } - - pub fn make_tag(&self) -> Tag { - self.address.value()[0] - } -} - impl Account { pub fn new() -> Self { let key_holder = AddressKeyHolder::new_os_random(); @@ -201,16 +176,6 @@ impl Account { pub fn make_tag(&self) -> Tag { self.address.value()[0] } - - ///Produce account public mask - pub fn make_account_public_mask(&self) -> AccountPublicMask { - AccountPublicMask { - nullifier_public_key: self.key_holder.nullifer_public_key, - viewing_public_key: self.key_holder.viewing_public_key, - address: self.address, - balance: self.balance, - } - } } impl Default for Account { @@ -253,25 +218,4 @@ mod tests { assert_eq!(account.balance, 500); } - - #[test] - fn test_add_asset() { - let mut account = Account::new(); - let asset = "dummy_asset"; - let amount = 1000u128; - - let result = account.add_asset(asset, amount, false); - - assert!(result.is_ok()); - assert_eq!(account.utxos.len(), 1); - } - - #[test] - fn accounts_accounts_mask_tag_consistency() { - let account = Account::new(); - - let account_mask = account.make_account_public_mask(); - - assert_eq!(account.make_tag(), account_mask.make_tag()); - } } diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 93304d5..7e4bc1e 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -1,7 +1,5 @@ use std::{fmt::Display, str::FromStr}; -use serde::{Deserialize, Serialize}; - use crate::signature::PublicKey; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -59,28 +57,6 @@ impl Display for Address { } } -impl Serialize for Address { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let hex_string = self.to_string(); - - hex_string.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Address { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let hex_string = String::deserialize(deserializer)?; - - Address::from_str(&hex_string).map_err(serde::de::Error::custom) - } -} - #[cfg(test)] mod tests { use crate::{Address, address::AddressError}; diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index 6b5e2ed..ff4ac76 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -547,6 +547,4 @@ mod tests { } } - // - From 1d2b0bbed7ed149f697874f05976f88de9904aa7 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 10:34:09 -0300 Subject: [PATCH 38/46] add test for transition from private transaction --- nssa/src/state.rs | 216 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 186 insertions(+), 30 deletions(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 91b4bdb..ba35e6e 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -13,21 +13,21 @@ use std::collections::{HashMap, HashSet}; pub(crate) struct CommitmentSet(pub(crate) MerkleTree); impl CommitmentSet { + pub(crate) fn digest(&self) -> CommitmentSetDigest { + self.0.root() + } + + pub(crate) fn get_proof_for(&self, commitment: &Commitment) -> Option { + self.0 + .get_authentication_path_for(&commitment.to_byte_array()) + } + fn extend(&mut self, commitments: &[Commitment]) { for commitment in commitments { self.0.insert(commitment.to_byte_array()); } } - pub fn digest(&self) -> CommitmentSetDigest { - self.0.root() - } - - pub fn get_proof_for(&self, commitment: &Commitment) -> Option { - self.0 - .get_authentication_path_for(&commitment.to_byte_array()) - } - fn contains(&self, commitment: &Commitment) -> bool { self.0.contains(&commitment.to_byte_array()) } @@ -186,13 +186,19 @@ mod tests { Address, PublicKey, PublicTransaction, V01State, error::NssaError, privacy_preserving_transaction::{ - Message, PrivacyPreservingTransaction, WitnessSet, circuit::execute_and_prove, + Message, PrivacyPreservingTransaction, WitnessSet, circuit, }, program::Program, public_transaction, signature::PrivateKey, }; - use nssa_core::account::{Account, AccountWithMetadata, NullifierPublicKey}; + use nssa_core::{ + IncomingViewingPublicKey, + account::{ + Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, + NullifierSecretKey, + }, + }; fn transfer_transaction( from: Address, @@ -440,6 +446,20 @@ mod tests { self.force_insert_account(Address::new([252; 32]), account); self } + + pub fn with_private_account(mut self) -> (Self, NullifierSecretKey, Account) { + let private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: 0xdeadbeef, + data: vec![], + }; + let nsk = [255; 32]; + let npk = NullifierPublicKey::from(&nsk); + let commitment = Commitment::new(&npk, &private_account); + self.private_state.0.extend(&[commitment]); + (self, nsk, private_account) + } } #[test] @@ -680,31 +700,27 @@ mod tests { assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } - #[test] - fn test_transition_from_privacy_preserving_transaction() { - let sender_signing_key = PrivateKey::try_new([1; 32]).unwrap(); - let sender_address = - Address::from(&PublicKey::new_from_private_key(&sender_signing_key)); - - let mut state = V01State::new_with_genesis_accounts(&[(sender_address, 200)]); - + fn shielded_balance_transfer_for_tests( + sender_signing_key: PrivateKey, + recipient_npk: NullifierPublicKey, + recipient_ivk: IncomingViewingPublicKey, + balance_to_move: u128, + state: &V01State, + ) -> PrivacyPreservingTransaction { + let sender_address = Address::from(&PublicKey::new_from_private_key(&sender_signing_key)); + let esk = [3; 32]; let sender = AccountWithMetadata { account: state.get_account_by_address(&sender_address), is_authorized: true, }; + let sender_nonce = sender.account.nonce; let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, }; - let recipient_npk = NullifierPublicKey::from(&[1; 32]); - let recipient_ivk = [2; 32]; - let esk = [3; 32]; - - let balance_to_move: u128 = 37; - - let (output, proof) = execute_and_prove( + let (output, proof) = circuit::execute_and_prove( &[sender, recipient], &Program::serialize_instruction(balance_to_move).unwrap(), &[0, 2], @@ -718,7 +734,7 @@ mod tests { let message = Message::new( vec![sender_address.clone()], - vec![0], + vec![sender_nonce], output.public_post_states, output.encrypted_private_post_states, output.new_commitments.clone(), @@ -726,18 +742,158 @@ mod tests { ); let witness_set = WitnessSet::for_message(&message, proof, &[&sender_signing_key]); - let tx = PrivacyPreservingTransaction::new(message, witness_set); + PrivacyPreservingTransaction::new(message, witness_set) + } - assert!(!state.private_state.0.contains(&output.new_commitments[0])); + fn private_balance_transfer_for_tests( + sender_nsk: NullifierSecretKey, + sender_private_account: &Account, + sender_ivk: IncomingViewingPublicKey, + recipient_npk: NullifierPublicKey, + recipient_ivk: IncomingViewingPublicKey, + balance_to_move: u128, + new_nonces: [Nonce; 2], + state: &V01State, + ) -> PrivacyPreservingTransaction { + let program = Program::authenticated_transfer_program(); + let sender_npk = NullifierPublicKey::from(&sender_nsk); + let recipient_private_account = Account::default(); + let sender_commitment = Commitment::new(&sender_npk, &sender_private_account); + let esk1 = [3; 32]; + let esk2 = [4; 32]; + let sender_pre = AccountWithMetadata { + account: sender_private_account.clone(), + is_authorized: true, + }; + let recipient_pre = AccountWithMetadata { + account: recipient_private_account, + is_authorized: false, + }; + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 2], + &new_nonces, + &[ + (sender_npk, sender_ivk, esk1), + (recipient_npk, recipient_ivk, esk2), + ], + &[( + sender_nsk, + state + .private_state + .0 + .get_proof_for(&sender_commitment) + .unwrap(), + )], + &program, + &state.private_state.0.digest(), + ) + .unwrap(); + + let message = Message::new( + vec![], + vec![], + output.public_post_states, + output.encrypted_private_post_states, + output.new_commitments.clone(), + output.new_nullifiers, + ); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + PrivacyPreservingTransaction::new(message, witness_set) + } + + #[test] + fn test_transition_from_privacy_preserving_transaction_shielded() { + let sender_signing_key = PrivateKey::try_new([1; 32]).unwrap(); + let sender_address = Address::from(&PublicKey::new_from_private_key(&sender_signing_key)); + let mut state = V01State::new_with_genesis_accounts(&[(sender_address, 200)]); + let recipient_npk = NullifierPublicKey::from(&[1; 32]); + let recipient_ivk = [2; 32]; + + let tx = shielded_balance_transfer_for_tests( + sender_signing_key, + recipient_npk, + recipient_ivk, + 37, + &state, + ); + + let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); + assert!(!state.private_state.0.contains(&expected_new_commitment)); state .transition_from_privacy_preserving_transaction(&tx) .unwrap(); - assert!(state.private_state.0.contains(&output.new_commitments[0])); + assert!(state.private_state.0.contains(&expected_new_commitment)); + assert_eq!( state.get_account_by_address(&sender_address).balance, 200 - 37 ); } + + #[test] + fn test_transition_from_privacy_preserving_transaction_private() { + let (mut state, sender_nsk, sender_private_account) = + V01State::new_with_genesis_accounts(&[]).with_private_account(); + let recipient_npk = NullifierPublicKey::from(&[99; 32]); + let sender_ivk = [44; 32]; + let recipient_ivk = [45; 32]; + let balance_to_move = 37; + + let tx = private_balance_transfer_for_tests( + sender_nsk, + &sender_private_account, + sender_ivk, + recipient_npk.clone(), + recipient_ivk, + balance_to_move, + [0xdeadbeef1, 0xdeadbeef2], + &state, + ); + + let sender_npk = NullifierPublicKey::from(&sender_nsk); + let sender_pre_commitment = Commitment::new(&sender_npk, &sender_private_account); + + let expected_new_commitment_1 = Commitment::new( + &sender_npk, + &Account { + program_owner: Program::authenticated_transfer_program().id(), + nonce: 0xdeadbeef1, + balance: sender_private_account.balance - balance_to_move, + ..sender_private_account + }, + ); + let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_nsk); + + let expected_new_commitment_2 = Commitment::new( + &recipient_npk, + &Account { + program_owner: Program::authenticated_transfer_program().id(), + nonce: 0xdeadbeef2, + balance: balance_to_move, + ..Account::default() + }, + ); + + let previous_public_state = state.public_state.clone(); + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(!state.private_state.0.contains(&expected_new_commitment_1)); + assert!(!state.private_state.0.contains(&expected_new_commitment_2)); + assert!(!state.private_state.1.contains(&expected_new_nullifier)); + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + assert_eq!(state.public_state, previous_public_state); + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(state.private_state.0.contains(&expected_new_commitment_1)); + assert!(state.private_state.0.contains(&expected_new_commitment_2)); + assert!(state.private_state.1.contains(&expected_new_nullifier)); + } } From f7e1b85091f96ca667a997718f70057ba9ede994 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 11:03:10 -0300 Subject: [PATCH 39/46] add transition from privacy preserving transaction deshielded --- nssa/src/state.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ba35e6e..72fae14 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -806,6 +806,61 @@ mod tests { PrivacyPreservingTransaction::new(message, witness_set) } + fn deshielded_balance_transfer_for_tests( + sender_nsk: NullifierSecretKey, + sender_private_account: &Account, + sender_ivk: IncomingViewingPublicKey, + recipient_addres: &Address, + balance_to_move: u128, + new_nonce: Nonce, + state: &V01State, + ) -> PrivacyPreservingTransaction { + let program = Program::authenticated_transfer_program(); + let sender_npk = NullifierPublicKey::from(&sender_nsk); + let sender_commitment = Commitment::new(&sender_npk, &sender_private_account); + let esk = [3; 32]; + let sender_pre = AccountWithMetadata { + account: sender_private_account.clone(), + is_authorized: true, + }; + let recipient_pre = AccountWithMetadata { + account: state.get_account_by_address(recipient_addres), + is_authorized: false, + }; + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 0], + &[new_nonce], + &[(sender_npk, sender_ivk, esk)], + &[( + sender_nsk, + state + .private_state + .0 + .get_proof_for(&sender_commitment) + .unwrap(), + )], + &program, + &state.private_state.0.digest(), + ) + .unwrap(); + + let message = Message::new( + vec![recipient_addres.clone()], + vec![], + output.public_post_states, + output.encrypted_private_post_states, + output.new_commitments.clone(), + output.new_nullifiers, + ); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + PrivacyPreservingTransaction::new(message, witness_set) + } + #[test] fn test_transition_from_privacy_preserving_transaction_shielded() { let sender_signing_key = PrivateKey::try_new([1; 32]).unwrap(); @@ -886,6 +941,7 @@ mod tests { assert!(!state.private_state.0.contains(&expected_new_commitment_1)); assert!(!state.private_state.0.contains(&expected_new_commitment_2)); assert!(!state.private_state.1.contains(&expected_new_nullifier)); + state .transition_from_privacy_preserving_transaction(&tx) .unwrap(); @@ -896,4 +952,56 @@ mod tests { assert!(state.private_state.0.contains(&expected_new_commitment_2)); assert!(state.private_state.1.contains(&expected_new_nullifier)); } + + #[test] + fn test_transition_from_privacy_preserving_transaction_deshielded() { + let recipient_address = Address::new([125; 32]); + let recipient_initial_balance = 400; + let (mut state, sender_nsk, sender_private_account) = + V01State::new_with_genesis_accounts(&[(recipient_address, recipient_initial_balance)]) + .with_private_account(); + + let sender_ivk = [44; 32]; + let balance_to_move = 37; + + let tx = deshielded_balance_transfer_for_tests( + sender_nsk, + &sender_private_account, + sender_ivk, + &recipient_address, + balance_to_move, + 0xdeadbeef1, + &state, + ); + + let sender_npk = NullifierPublicKey::from(&sender_nsk); + let sender_pre_commitment = Commitment::new(&sender_npk, &sender_private_account); + + let expected_new_commitment = Commitment::new( + &sender_npk, + &Account { + program_owner: Program::authenticated_transfer_program().id(), + nonce: 0xdeadbeef1, + balance: sender_private_account.balance - balance_to_move, + ..sender_private_account + }, + ); + let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_nsk); + + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(!state.private_state.0.contains(&expected_new_commitment)); + assert!(!state.private_state.1.contains(&expected_new_nullifier)); + + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(state.private_state.0.contains(&expected_new_commitment)); + assert!(state.private_state.1.contains(&expected_new_nullifier)); + assert_eq!( + state.get_account_by_address(&recipient_address).balance, + recipient_initial_balance + balance_to_move + ); + } } From 75211903d7f69fd918c9f19a8c2821346fcf84bc Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 12:29:45 -0300 Subject: [PATCH 40/46] polish tests --- nssa/src/state.rs | 215 +++++++++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 90 deletions(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 72fae14..aef4046 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -178,7 +178,7 @@ impl V01State { } #[cfg(test)] -mod tests { +pub mod tests { use std::collections::HashMap; @@ -199,6 +199,7 @@ mod tests { NullifierSecretKey, }, }; + use program_methods::AUTHENTICATED_TRANSFER_ID; fn transfer_transaction( from: Address, @@ -447,18 +448,10 @@ mod tests { self } - pub fn with_private_account(mut self) -> (Self, NullifierSecretKey, Account) { - let private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: 100, - nonce: 0xdeadbeef, - data: vec![], - }; - let nsk = [255; 32]; - let npk = NullifierPublicKey::from(&nsk); - let commitment = Commitment::new(&npk, &private_account); + pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self { + let commitment = Commitment::new(&keys.npk(), &account); self.private_state.0.extend(&[commitment]); - (self, nsk, private_account) + self } } @@ -700,17 +693,56 @@ mod tests { assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } + struct TestPublicKeys { + signing_key: PrivateKey, + } + + impl TestPublicKeys { + fn address(&self) -> Address { + Address::from(&PublicKey::new_from_private_key(&self.signing_key)) + } + } + + fn test_public_account_keys_1() -> TestPublicKeys { + TestPublicKeys { + signing_key: PrivateKey::try_new([37; 32]).unwrap(), + } + } + + struct TestPrivateKeys { + nsk: NullifierSecretKey, + ivk: IncomingViewingPublicKey, + } + + impl TestPrivateKeys { + fn npk(&self) -> NullifierPublicKey { + NullifierPublicKey::from(&self.nsk) + } + } + + fn test_private_account_keys_1() -> TestPrivateKeys { + TestPrivateKeys { + nsk: [13; 32], + ivk: [31; 32], + } + } + + fn test_private_account_keys_2() -> TestPrivateKeys { + TestPrivateKeys { + nsk: [38; 32], + ivk: [83; 32], + } + } + fn shielded_balance_transfer_for_tests( - sender_signing_key: PrivateKey, - recipient_npk: NullifierPublicKey, - recipient_ivk: IncomingViewingPublicKey, + sender_keys: &TestPublicKeys, + recipient_keys: &TestPrivateKeys, balance_to_move: u128, state: &V01State, ) -> PrivacyPreservingTransaction { - let sender_address = Address::from(&PublicKey::new_from_private_key(&sender_signing_key)); let esk = [3; 32]; let sender = AccountWithMetadata { - account: state.get_account_by_address(&sender_address), + account: state.get_account_by_address(&sender_keys.address()), is_authorized: true, }; let sender_nonce = sender.account.nonce; @@ -725,7 +757,7 @@ mod tests { &Program::serialize_instruction(balance_to_move).unwrap(), &[0, 2], &[0xdeadbeef], - &[(recipient_npk, recipient_ivk, esk)], + &[(recipient_keys.npk(), recipient_keys.ivk, esk)], &[], &Program::authenticated_transfer_program(), &state.commitment_set_digest(), @@ -733,7 +765,7 @@ mod tests { .unwrap(); let message = Message::new( - vec![sender_address.clone()], + vec![sender_keys.address()], vec![sender_nonce], output.public_post_states, output.encrypted_private_post_states, @@ -741,32 +773,26 @@ mod tests { output.new_nullifiers, ); - let witness_set = WitnessSet::for_message(&message, proof, &[&sender_signing_key]); + let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]); PrivacyPreservingTransaction::new(message, witness_set) } fn private_balance_transfer_for_tests( - sender_nsk: NullifierSecretKey, + sender_keys: &TestPrivateKeys, sender_private_account: &Account, - sender_ivk: IncomingViewingPublicKey, - recipient_npk: NullifierPublicKey, - recipient_ivk: IncomingViewingPublicKey, + recipient_keys: &TestPrivateKeys, balance_to_move: u128, new_nonces: [Nonce; 2], state: &V01State, ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); - let sender_npk = NullifierPublicKey::from(&sender_nsk); - let recipient_private_account = Account::default(); - let sender_commitment = Commitment::new(&sender_npk, &sender_private_account); - let esk1 = [3; 32]; - let esk2 = [4; 32]; + let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), is_authorized: true, }; let recipient_pre = AccountWithMetadata { - account: recipient_private_account, + account: Account::default(), is_authorized: false, }; @@ -776,11 +802,11 @@ mod tests { &[1, 2], &new_nonces, &[ - (sender_npk, sender_ivk, esk1), - (recipient_npk, recipient_ivk, esk2), + (sender_keys.npk(), sender_keys.ivk, [3; 32]), + (recipient_keys.npk(), recipient_keys.ivk, [4; 32]), ], &[( - sender_nsk, + sender_keys.nsk, state .private_state .0 @@ -807,24 +833,21 @@ mod tests { } fn deshielded_balance_transfer_for_tests( - sender_nsk: NullifierSecretKey, + sender_keys: &TestPrivateKeys, sender_private_account: &Account, - sender_ivk: IncomingViewingPublicKey, - recipient_addres: &Address, + recipient_address: &Address, balance_to_move: u128, new_nonce: Nonce, state: &V01State, ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); - let sender_npk = NullifierPublicKey::from(&sender_nsk); - let sender_commitment = Commitment::new(&sender_npk, &sender_private_account); - let esk = [3; 32]; + let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), is_authorized: true, }; let recipient_pre = AccountWithMetadata { - account: state.get_account_by_address(recipient_addres), + account: state.get_account_by_address(recipient_address), is_authorized: false, }; @@ -833,9 +856,9 @@ mod tests { &Program::serialize_instruction(balance_to_move).unwrap(), &[1, 0], &[new_nonce], - &[(sender_npk, sender_ivk, esk)], + &[(sender_keys.npk(), sender_keys.ivk, [3; 32])], &[( - sender_nsk, + sender_keys.nsk, state .private_state .0 @@ -848,7 +871,7 @@ mod tests { .unwrap(); let message = Message::new( - vec![recipient_addres.clone()], + vec![recipient_address.clone()], vec![], output.public_post_states, output.encrypted_private_post_states, @@ -863,17 +886,17 @@ mod tests { #[test] fn test_transition_from_privacy_preserving_transaction_shielded() { - let sender_signing_key = PrivateKey::try_new([1; 32]).unwrap(); - let sender_address = Address::from(&PublicKey::new_from_private_key(&sender_signing_key)); - let mut state = V01State::new_with_genesis_accounts(&[(sender_address, 200)]); - let recipient_npk = NullifierPublicKey::from(&[1; 32]); - let recipient_ivk = [2; 32]; + let sender_keys = test_public_account_keys_1(); + let recipient_keys = test_private_account_keys_1(); + + let mut state = V01State::new_with_genesis_accounts(&[(sender_keys.address(), 200)]); + + let balance_to_move = 37; let tx = shielded_balance_transfer_for_tests( - sender_signing_key, - recipient_npk, - recipient_ivk, - 37, + &sender_keys, + &recipient_keys, + balance_to_move, &state, ); @@ -887,50 +910,54 @@ mod tests { assert!(state.private_state.0.contains(&expected_new_commitment)); assert_eq!( - state.get_account_by_address(&sender_address).balance, - 200 - 37 + state.get_account_by_address(&sender_keys.address()).balance, + 200 - balance_to_move ); } #[test] fn test_transition_from_privacy_preserving_transaction_private() { - let (mut state, sender_nsk, sender_private_account) = - V01State::new_with_genesis_accounts(&[]).with_private_account(); - let recipient_npk = NullifierPublicKey::from(&[99; 32]); - let sender_ivk = [44; 32]; - let recipient_ivk = [45; 32]; + let sender_keys = test_private_account_keys_1(); + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: 0xdeadbeef, + data: vec![], + }; + let recipient_keys = test_private_account_keys_2(); + + let mut state = V01State::new_with_genesis_accounts(&[]) + .with_private_account(&sender_keys, &sender_private_account); + let balance_to_move = 37; let tx = private_balance_transfer_for_tests( - sender_nsk, + &sender_keys, &sender_private_account, - sender_ivk, - recipient_npk.clone(), - recipient_ivk, + &recipient_keys, balance_to_move, - [0xdeadbeef1, 0xdeadbeef2], + [0xcafecafe, 0xfecafeca], &state, ); - let sender_npk = NullifierPublicKey::from(&sender_nsk); - let sender_pre_commitment = Commitment::new(&sender_npk, &sender_private_account); - let expected_new_commitment_1 = Commitment::new( - &sender_npk, + &sender_keys.npk(), &Account { program_owner: Program::authenticated_transfer_program().id(), - nonce: 0xdeadbeef1, + nonce: 0xcafecafe, balance: sender_private_account.balance - balance_to_move, - ..sender_private_account + data: vec![], }, ); - let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_nsk); + + let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); + let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_keys.nsk); let expected_new_commitment_2 = Commitment::new( - &recipient_npk, + &recipient_keys.npk(), &Account { program_owner: Program::authenticated_transfer_program().id(), - nonce: 0xdeadbeef2, + nonce: 0xfecafeca, balance: balance_to_move, ..Account::default() }, @@ -955,38 +982,44 @@ mod tests { #[test] fn test_transition_from_privacy_preserving_transaction_deshielded() { - let recipient_address = Address::new([125; 32]); + let sender_keys = test_private_account_keys_1(); + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: 0xdeadbeef, + data: vec![], + }; + let recipient_keys = test_public_account_keys_1(); let recipient_initial_balance = 400; - let (mut state, sender_nsk, sender_private_account) = - V01State::new_with_genesis_accounts(&[(recipient_address, recipient_initial_balance)]) - .with_private_account(); + let mut state = V01State::new_with_genesis_accounts(&[( + recipient_keys.address(), + recipient_initial_balance, + )]) + .with_private_account(&sender_keys, &sender_private_account); - let sender_ivk = [44; 32]; let balance_to_move = 37; let tx = deshielded_balance_transfer_for_tests( - sender_nsk, + &sender_keys, &sender_private_account, - sender_ivk, - &recipient_address, + &recipient_keys.address(), balance_to_move, - 0xdeadbeef1, + 0xcafecafe, &state, ); - let sender_npk = NullifierPublicKey::from(&sender_nsk); - let sender_pre_commitment = Commitment::new(&sender_npk, &sender_private_account); - let expected_new_commitment = Commitment::new( - &sender_npk, + &sender_keys.npk(), &Account { program_owner: Program::authenticated_transfer_program().id(), - nonce: 0xdeadbeef1, + nonce: 0xcafecafe, balance: sender_private_account.balance - balance_to_move, - ..sender_private_account + data: vec![], }, ); - let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_nsk); + + let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); + let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_keys.nsk); assert!(state.private_state.0.contains(&sender_pre_commitment)); assert!(!state.private_state.0.contains(&expected_new_commitment)); @@ -1000,7 +1033,9 @@ mod tests { assert!(state.private_state.0.contains(&expected_new_commitment)); assert!(state.private_state.1.contains(&expected_new_nullifier)); assert_eq!( - state.get_account_by_address(&recipient_address).balance, + state + .get_account_by_address(&recipient_keys.address()) + .balance, recipient_initial_balance + balance_to_move ); } From d00c5510274bb93614846d2642cd38ffab8d2232 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 13:42:37 -0300 Subject: [PATCH 41/46] minor refactor --- nssa/src/merkle_tree/mod.rs | 78 +++++++++---------- .../privacy_preserving_transaction/circuit.rs | 31 ++++---- nssa/src/state.rs | 20 ++--- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index ff4ac76..fffcb7d 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -124,13 +124,13 @@ impl MerkleTree { true } - pub fn new(values: Vec) -> Self { - let mut deduplicated_values = Vec::with_capacity(values.len()); + pub fn new(values: &[Value]) -> Self { + let mut deduplicated_values: Vec = Vec::with_capacity(values.len()); let mut seen = HashSet::new(); for value in values.into_iter() { - if !seen.contains(&value) { - deduplicated_values.push(value); - seen.insert(value); + if !seen.contains(value) { + deduplicated_values.push(*value); + seen.insert(*value); } } let mut this = Self::with_capacity(deduplicated_values.len()); @@ -191,8 +191,8 @@ mod tests { #[test] fn test_merkle_tree_1() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; - let tree = MerkleTree::new(values.clone()); + let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; + let tree = MerkleTree::new(&values); let expected_root = [ 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, @@ -208,8 +208,8 @@ mod tests { #[test] fn test_merkle_tree_2() { - let values = vec![[1; 32], [2; 32], [3; 32], [0; 32]]; - let tree = MerkleTree::new(values.clone()); + let values = [[1; 32], [2; 32], [3; 32], [0; 32]]; + let tree = MerkleTree::new(&values); let expected_root = [ 201, 187, 184, 48, 150, 223, 133, 21, 122, 20, 110, 125, 119, 4, 85, 169, 132, 18, 222, 224, 99, 49, 135, 238, 134, 254, 230, 200, 164, 91, 131, 26, @@ -225,8 +225,8 @@ mod tests { #[test] fn test_merkle_tree_3() { - let values = vec![[1; 32], [2; 32], [3; 32]]; - let tree = MerkleTree::new(values.clone()); + let values = [[1; 32], [2; 32], [3; 32]]; + let tree = MerkleTree::new(&values); let expected_root = [ 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, @@ -242,8 +242,8 @@ mod tests { #[test] fn test_merkle_tree_4() { - let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; - let tree = MerkleTree::new(values.clone()); + let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; + let tree = MerkleTree::new(&values); let expected_root = [ 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, @@ -260,11 +260,11 @@ mod tests { #[test] fn test_merkle_tree_5() { - let values = vec![ + let values = [ [11; 32], [12; 32], [12; 32], [13; 32], [14; 32], [15; 32], [15; 32], [13; 32], [13; 32], [15; 32], [11; 32], ]; - let tree = MerkleTree::new(values); + let tree = MerkleTree::new(&values); let expected_root = [ 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, @@ -281,8 +281,8 @@ mod tests { #[test] fn test_merkle_tree_6() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; - let tree = MerkleTree::new(values); + let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(&values); let expected_root = [ 6, 156, 184, 37, 154, 6, 254, 110, 219, 63, 167, 255, 121, 51, 166, 221, 125, 202, 111, 202, 41, 147, 20, 55, 151, 148, 166, 136, 146, 108, 55, 146, @@ -328,7 +328,7 @@ mod tests { fn test_with_capacity_6() { let mut tree = MerkleTree::with_capacity(100); - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; + let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let expected_root = [ 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, @@ -347,7 +347,7 @@ mod tests { fn test_with_capacity_7() { let mut tree = MerkleTree::with_capacity(599); - let values = vec![[1; 32], [2; 32], [3; 32]]; + let values = [[1; 32], [2; 32], [3; 32]]; let expected_root = [ 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, @@ -365,7 +365,7 @@ mod tests { fn test_with_capacity_8() { let mut tree = MerkleTree::with_capacity(1); - let values = vec![[1; 32], [2; 32], [3; 32]]; + let values = [[1; 32], [2; 32], [3; 32]]; let expected_root = [ 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, @@ -383,8 +383,8 @@ mod tests { fn test_insert_value_1() { let mut tree = MerkleTree::with_capacity(1); - let values = vec![[1; 32], [2; 32], [3; 32]]; - let expected_tree = MerkleTree::new(values.clone()); + let values = [[1; 32], [2; 32], [3; 32]]; + let expected_tree = MerkleTree::new(&values); tree.insert(values[0]); tree.insert(values[1]); @@ -397,8 +397,8 @@ mod tests { fn test_insert_value_2() { let mut tree = MerkleTree::with_capacity(1); - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; - let expected_tree = MerkleTree::new(values.clone()); + let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; + let expected_tree = MerkleTree::new(&values); tree.insert(values[0]); tree.insert(values[1]); @@ -412,8 +412,8 @@ mod tests { fn test_insert_value_3() { let mut tree = MerkleTree::with_capacity(1); - let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; - let expected_tree = MerkleTree::new(values.clone()); + let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; + let expected_tree = MerkleTree::new(&values); tree.insert(values[0]); tree.insert(values[1]); @@ -428,8 +428,8 @@ mod tests { fn test_insert_value_4() { let mut tree = MerkleTree::with_capacity(1); - let values = vec![[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; - let expected_tree = MerkleTree::new(values.clone()); + let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; + let expected_tree = MerkleTree::new(&values); tree.insert(values[0]); tree.insert(values[0]); @@ -448,8 +448,8 @@ mod tests { #[test] fn test_authentication_path_1() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32]]; - let tree = MerkleTree::new(values); + let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; + let tree = MerkleTree::new(&values); let expected_authentication_path = ( 2, vec![ @@ -470,8 +470,8 @@ mod tests { #[test] fn test_authentication_path_2() { - let values = vec![[1; 32], [2; 32], [3; 32]]; - let tree = MerkleTree::new(values); + let values = [[1; 32], [2; 32], [3; 32]]; + let tree = MerkleTree::new(&values); let expected_authentication_path = ( 0, vec![ @@ -492,8 +492,8 @@ mod tests { #[test] fn test_authentication_path_3() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; - let tree = MerkleTree::new(values); + let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(&values); let expected_authentication_path = ( 4, vec![ @@ -518,8 +518,8 @@ mod tests { #[test] fn test_authentication_path_4() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; - let tree = MerkleTree::new(values); + let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(&values); let value = [6; 32]; assert!(tree.get_authentication_path_for(&value).is_none()); } @@ -527,15 +527,15 @@ mod tests { #[test] fn test_authentication_path_5() { let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; - let tree = MerkleTree::new(values); + let tree = MerkleTree::new(&values); let value = [0; 32]; assert!(tree.get_authentication_path_for(&value).is_none()); } #[test] fn test_authentication_path_6() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; - let tree = MerkleTree::new(values); + let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; + let tree = MerkleTree::new(&values); let value = [5; 32]; let (index, path) = tree.get_authentication_path_for(&value).unwrap(); assert!(verify_authentication_path( diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 00ee78c..16a5d0d 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -105,7 +105,10 @@ mod tests { merkle_tree::MerkleTree, privacy_preserving_transaction::circuit::{Proof, execute_and_prove}, program::Program, - state::CommitmentSet, + state::{ + CommitmentSet, + tests::{test_private_account_keys_1, test_private_account_keys_2}, + }, }; use rand::{Rng, RngCore, rngs::OsRng}; @@ -163,7 +166,7 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_fully_private() { - let sender = AccountWithMetadata { + let sender_pre = AccountWithMetadata { account: Account { balance: 100, nonce: 0xdeadbeef, @@ -171,18 +174,16 @@ mod tests { }, is_authorized: true, }; - let private_key_1 = [1; 32]; - let Npk1 = NullifierPublicKey::from(&private_key_1); - let commitment_sender = Commitment::new(&Npk1, &sender.account); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account); let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, }; - let Npk2 = NullifierPublicKey::from(&[99; 32]); let balance_to_move: u128 = 37; - let commitment_set = - CommitmentSet(MerkleTree::new(vec![commitment_sender.to_byte_array()])); + let commitment_set = CommitmentSet(MerkleTree::new(&[commitment_sender.to_byte_array()])); let program = Program::authenticated_transfer_program(); let expected_private_account_1 = Account { @@ -198,22 +199,22 @@ mod tests { ..Default::default() }; let expected_new_commitments = vec![ - Commitment::new(&Npk1, &expected_private_account_1), - Commitment::new(&Npk2, &expected_private_account_2), + Commitment::new(&sender_keys.npk(), &expected_private_account_1), + Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &private_key_1)]; + let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &sender_keys.nsk)]; let (output, proof) = execute_and_prove( - &[sender.clone(), recipient], + &[sender_pre.clone(), recipient], &Program::serialize_instruction(balance_to_move).unwrap(), &[1, 2], &[0xdeadbeef1, 0xdeadbeef2], &[ - (Npk1.clone(), [2; 32], [3; 32]), - (Npk2.clone(), [4; 32], [5; 32]), + (sender_keys.npk(), sender_keys.ivk, [3; 32]), + (recipient_keys.npk(), recipient_keys.ivk, [5; 32]), ], &[( - private_key_1, + sender_keys.nsk, commitment_set.get_proof_for(&commitment_sender).unwrap(), )], &program, diff --git a/nssa/src/state.rs b/nssa/src/state.rs index aef4046..350743f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -22,7 +22,7 @@ impl CommitmentSet { .get_authentication_path_for(&commitment.to_byte_array()) } - fn extend(&mut self, commitments: &[Commitment]) { + pub(crate) fn extend(&mut self, commitments: &[Commitment]) { for commitment in commitments { self.0.insert(commitment.to_byte_array()); } @@ -693,12 +693,12 @@ pub mod tests { assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } - struct TestPublicKeys { - signing_key: PrivateKey, + pub struct TestPublicKeys { + pub signing_key: PrivateKey, } impl TestPublicKeys { - fn address(&self) -> Address { + pub fn address(&self) -> Address { Address::from(&PublicKey::new_from_private_key(&self.signing_key)) } } @@ -709,25 +709,25 @@ pub mod tests { } } - struct TestPrivateKeys { - nsk: NullifierSecretKey, - ivk: IncomingViewingPublicKey, + pub struct TestPrivateKeys { + pub nsk: NullifierSecretKey, + pub ivk: IncomingViewingPublicKey, } impl TestPrivateKeys { - fn npk(&self) -> NullifierPublicKey { + pub fn npk(&self) -> NullifierPublicKey { NullifierPublicKey::from(&self.nsk) } } - fn test_private_account_keys_1() -> TestPrivateKeys { + pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], ivk: [31; 32], } } - fn test_private_account_keys_2() -> TestPrivateKeys { + pub fn test_private_account_keys_2() -> TestPrivateKeys { TestPrivateKeys { nsk: [38; 32], ivk: [83; 32], From 96ca181f2d2247e07ac341d713dae2ce7bbbe6fb Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 14:59:57 -0300 Subject: [PATCH 42/46] implement encryption/decryption of private outputs --- Cargo.toml | 2 +- nssa/core/Cargo.toml | 4 +- nssa/core/src/lib.rs | 187 ++++++++++++++-- nssa/program_methods/Cargo.toml | 1 + nssa/program_methods/guest/Cargo.lock | 206 ++++++++++++++++++ nssa/program_methods/guest/Cargo.toml | 4 + .../src/bin/privacy_preserving_circuit.rs | 24 +- .../privacy_preserving_transaction/circuit.rs | 41 +++- nssa/src/state.rs | 18 +- 9 files changed, 445 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe70572..8eefa02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ version = "0.8.5" [workspace.dependencies.k256] features = ["ecdsa-core", "arithmetic", "expose-field", "serde", "pem"] -version = "0.13.4" +version = "0.13.3" [workspace.dependencies.elliptic-curve] features = ["arithmetic"] diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index 1cdc78f..de7e4d6 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] -risc0-zkvm = "2.3.1" +risc0-zkvm = { version = "2.3.1"} serde = { version = "1.0", default-features = false } thiserror = { version = "2.0.12", optional = true } bytemuck = { version = "1.13", optional = true } +chacha20 = { version = "0.9", default-features = false } +k256 = "0.13.3" [features] default = [] diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 56de12c..ba57f63 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,3 +1,7 @@ +use chacha20::{ + ChaCha20, + cipher::{KeyIvInit, StreamCipher}, +}; use risc0_zkvm::{ serde::to_vec, sha::{Impl, Sha256}, @@ -16,11 +20,19 @@ use crate::{ }; #[cfg(feature = "host")] -use std::io::Cursor; +use std::io::{Cursor, Read}; pub mod account; pub mod program; +use k256::{ + AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, PublicKey, Scalar, + elliptic_curve::{ + PrimeField, + sec1::{FromEncodedPoint, ToEncodedPoint}, + }, +}; + #[cfg(feature = "host")] pub mod error; @@ -55,49 +67,167 @@ pub fn verify_membership_proof( &result == digest } -pub type IncomingViewingPublicKey = [u8; 32]; +pub type EphemeralPublicKey = Secp256k1Point; +pub type IncomingViewingPublicKey = Secp256k1Point; + pub type EphemeralSecretKey = [u8; 32]; -pub struct EphemeralPublicKey; impl From<&EphemeralSecretKey> for EphemeralPublicKey { fn from(value: &EphemeralSecretKey) -> Self { - todo!() + Secp256k1Point::from_scalar(*value) } } -pub struct Tag(u8); -impl Tag { - pub fn new(Npk: &NullifierPublicKey, Ipk: &IncomingViewingPublicKey) -> Self { - todo!() +#[derive(Serialize, Deserialize, Clone)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +pub struct Secp256k1Point(pub Vec); +impl Secp256k1Point { + pub fn from_scalar(value: [u8; 32]) -> Secp256k1Point { + let x_bytes: FieldBytes = value.into(); + let x = Scalar::from_repr(x_bytes).unwrap(); + + let p = ProjectivePoint::GENERATOR * x; + let q = AffinePoint::from(p); + let enc = q.to_encoded_point(true); + + Self(enc.as_bytes().to_vec()) } } #[derive(Serialize, Deserialize)] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] -pub struct EncryptedAccountData(u8); +pub struct EncryptedAccountData { + ciphertext: Vec, + epk: EphemeralPublicKey, + view_tag: u8, +} impl EncryptedAccountData { + #[cfg(feature = "host")] + pub fn decrypt(self, isk: &[u8; 32], output_index: u32) -> Option { + let ss_bytes = Self::ecdh(isk, &self.epk.0.clone().try_into().unwrap()); + let ipk = IncomingViewingPublicKey::from_scalar(*isk); + + let key = Self::kdf( + ss_bytes, + &self.epk, + &ipk, + // &commitment.to_byte_array(), + output_index, + ); + let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); + let mut buffer = self.ciphertext; + + cipher.apply_keystream(&mut buffer); + let mut cursor = Cursor::new(buffer.as_slice()); + Account::from_cursor(&mut cursor).ok() + } + pub fn new( account: &Account, + // commitment: &Commitment, esk: &EphemeralSecretKey, - Npk: &NullifierPublicKey, - Ivk: &IncomingViewingPublicKey, + npk: &NullifierPublicKey, + ipk: &IncomingViewingPublicKey, + output_index: u32, ) -> Self { - // TODO: implement - Self(0) + let mut buffer = account.to_bytes().to_vec(); + + let ss_bytes = Self::ecdh(esk, &ipk.0.clone().try_into().unwrap()); + let epk = EphemeralPublicKey::from(esk); + + let key = Self::kdf( + ss_bytes, + &epk, + ipk, + // &commitment.to_byte_array(), + output_index, + ); + let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); + cipher.apply_keystream(&mut buffer); + + let view_tag = Self::view_tag(&npk, &ipk); + Self { + ciphertext: buffer, + epk, + view_tag, + } + } + + pub fn kdf( + ss_bytes: [u8; 32], + epk: &EphemeralPublicKey, + ipk: &IncomingViewingPublicKey, + // commitment: &[u8; 32], + output_index: u32, + ) -> [u8; 32] { + let mut bytes = Vec::new(); + + bytes.extend_from_slice(b"NSSA/v0.1/KDF-SHA256"); + bytes.extend_from_slice(&ss_bytes); + bytes.extend_from_slice(&epk.0[..]); + bytes.extend_from_slice(&ipk.0[..]); + // bytes.extend_from_slice(&commitment[..]); + bytes.extend_from_slice(&output_index.to_le_bytes()); + + Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap() + } + + pub fn ecdh(scalar: &[u8; 32], point: &[u8; 33]) -> [u8; 32] { + let scalar = Scalar::from_repr((*scalar).into()).unwrap(); + + let encoded = EncodedPoint::from_bytes(point).unwrap(); + let pubkey_affine = AffinePoint::from_encoded_point(&encoded).unwrap(); + + let shared = ProjectivePoint::from(pubkey_affine) * scalar; + let shared_affine = shared.to_affine(); + + let encoded = shared_affine.to_encoded_point(false); + let x_bytes_slice = encoded.x().unwrap(); + let mut x_bytes = [0u8; 32]; + x_bytes.copy_from_slice(x_bytes_slice); + + x_bytes } #[cfg(feature = "host")] pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let dummy_value = EncryptedAccountData(0); - Ok(dummy_value) + let mut u32_bytes = [0; 4]; + + cursor.read_exact(&mut u32_bytes)?; + let ciphertext_lenght = u32::from_le_bytes(u32_bytes); + let mut ciphertext = vec![0; ciphertext_lenght as usize]; + cursor.read_exact(&mut ciphertext)?; + + let mut epk_bytes = vec![0; 33]; + cursor.read_exact(&mut epk_bytes)?; + + let mut tag_bytes = [0; 1]; + cursor.read_exact(&mut tag_bytes)?; + + Ok(Self { + ciphertext, + epk: Secp256k1Point(epk_bytes), + view_tag: tag_bytes[0], + }) + } + + fn view_tag(npk: &NullifierPublicKey, ipk: &&IncomingViewingPublicKey) -> u8 { + // TODO: implement + 0 } } impl EncryptedAccountData { pub fn to_bytes(&self) -> Vec { - // TODO: implement - vec![0] + let mut bytes = Vec::new(); + let ciphertext_length: u32 = self.ciphertext.len() as u32; + bytes.extend_from_slice(&ciphertext_length.to_le_bytes()); + bytes.extend_from_slice(&self.ciphertext); + bytes.extend_from_slice(&self.epk.0); + bytes.push(self.view_tag); + + bytes } } @@ -136,10 +266,12 @@ impl PrivacyPreservingCircuitOutput { #[cfg(test)] mod tests { + use std::io::Cursor; + use risc0_zkvm::serde::from_slice; use crate::{ - EncryptedAccountData, PrivacyPreservingCircuitOutput, + EncryptedAccountData, EphemeralPublicKey, PrivacyPreservingCircuitOutput, Secp256k1Point, account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, }; @@ -172,7 +304,11 @@ mod tests { data: b"post state data".to_vec(), nonce: 18446744073709551615, }], - encrypted_private_post_states: vec![EncryptedAccountData(0)], + encrypted_private_post_states: vec![EncryptedAccountData { + ciphertext: vec![255, 255, 1, 1, 2, 2], + epk: EphemeralPublicKey::from_scalar([123; 32]), + view_tag: 1, + }], new_commitments: vec![Commitment::new( &NullifierPublicKey::from(&[1; 32]), &Account::default(), @@ -187,4 +323,17 @@ mod tests { let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); assert_eq!(output, output_from_slice); } + + #[test] + fn test_encrypted_account_data_to_bytes_roundtrip() { + let data = EncryptedAccountData { + ciphertext: vec![255, 255, 1, 1, 2, 2], + epk: EphemeralPublicKey::from_scalar([123; 32]), + view_tag: 95, + }; + let bytes = data.to_bytes(); + let mut cursor = Cursor::new(bytes.as_slice()); + let data_from_cursor = EncryptedAccountData::from_cursor(&mut cursor).unwrap(); + assert_eq!(data, data_from_cursor); + } } diff --git a/nssa/program_methods/Cargo.toml b/nssa/program_methods/Cargo.toml index ea7e36b..0f31c9b 100644 --- a/nssa/program_methods/Cargo.toml +++ b/nssa/program_methods/Cargo.toml @@ -8,3 +8,4 @@ risc0-build = { version = "2.3.1" } [package.metadata.risc0] methods = ["guest"] + diff --git a/nssa/program_methods/guest/Cargo.lock b/nssa/program_methods/guest/Cargo.lock index 1d3063f..380583f 100644 --- a/nssa/program_methods/guest/Cargo.lock +++ b/nssa/program_methods/guest/Cargo.lock @@ -297,12 +297,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bincode" version = "1.3.3" @@ -484,6 +496,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "cobs" version = "0.3.0" @@ -535,6 +568,18 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -580,6 +625,16 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -709,6 +764,20 @@ dependencies = [ "proc-macro-error", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.6.0" @@ -733,6 +802,25 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -796,6 +884,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fnv" version = "1.0.7" @@ -908,6 +1006,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -943,6 +1042,17 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -994,6 +1104,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.3.1" @@ -1223,6 +1342,15 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "io-uring" version = "0.7.9" @@ -1275,6 +1403,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "keccak" version = "0.1.5" @@ -1445,6 +1587,8 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" name = "nssa-core" version = "0.1.0" dependencies = [ + "chacha20", + "k256", "risc0-zkvm", "serde", ] @@ -1531,6 +1675,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "postcard" version = "1.1.3" @@ -1749,6 +1903,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] [[package]] name = "rand_core" @@ -1841,6 +1998,16 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2162,6 +2329,20 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.26" @@ -2241,6 +2422,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "slab" version = "0.4.10" @@ -2279,6 +2470,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stability" version = "0.2.1" @@ -3193,3 +3394,8 @@ dependencies = [ "quote", "syn 2.0.104", ] + +[[patch.unused]] +name = "k256" +version = "0.13.3" +source = "git+https://github.com/risc0/RustCrypto-elliptic-curves?tag=k256%2Fv0.13.3-risczero.1#ff5d67b095cfcc2569b7789f2079ed87ef2c7756" diff --git a/nssa/program_methods/guest/Cargo.toml b/nssa/program_methods/guest/Cargo.toml index 4b377c8..79a0c1a 100644 --- a/nssa/program_methods/guest/Cargo.toml +++ b/nssa/program_methods/guest/Cargo.toml @@ -8,3 +8,7 @@ edition = "2021" [dependencies] risc0-zkvm = { version = "2.3.1", default-features = false, features = ['std'] } nssa-core = { path = "../../core" } + +[patch.crates-io] +k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.1" } + diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index e5f64b3..181bdc9 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -4,7 +4,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, verify_membership_proof, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, - IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, Tag, + IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, }; fn main() { @@ -50,12 +50,18 @@ fn main() { let mut private_keys_iter = private_account_keys.iter(); let mut private_auth_iter = private_account_auth.iter(); + let mut output_index = 0; for i in 0..n_accounts { match visibility_mask[i] { 0 => { + let mut post = post_states[i].clone(); + if post.program_owner == DEFAULT_PROGRAM_ID { + // Claim account + post.program_owner = program_id; + } // Public account public_pre_states.push(pre_states[i].clone()); - public_post_states.push(post_states[i].clone()); + public_post_states.push(post); } 1 | 2 => { let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); @@ -111,12 +117,20 @@ fn main() { // Compute commitment let commitment_post = Commitment::new(Npk, &post_with_updated_values); - new_commitments.push(commitment_post); // Encrypt and push post state - let encrypted_account = - EncryptedAccountData::new(&post_with_updated_values, esk, Npk, Ipk); + let encrypted_account = EncryptedAccountData::new( + &post_with_updated_values, + // &commitment_post, + esk, + Npk, + Ipk, + output_index, + ); + + new_commitments.push(commitment_post); encrypted_private_post_states.push(encrypted_account); + output_index += 1; } _ => panic!("Invalid visibility mask value"), } diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 16a5d0d..8bbdb79 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -117,6 +117,7 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { + let program = Program::authenticated_transfer_program(); let sender = AccountWithMetadata { account: Account { balance: 100, @@ -133,17 +134,26 @@ mod tests { let balance_to_move: u128 = 37; let expected_sender_post = Account { + program_owner: program.id(), balance: 100 - balance_to_move, - ..Default::default() + ..Account::default() + }; + + let expected_recipient_post = Account { + program_owner: program.id(), + balance: balance_to_move, + nonce: 0xdeadbeef, + data: vec![], }; let expected_sender_pre = sender.clone(); + let recipient_keys = test_private_account_keys_1(); let (output, proof) = execute_and_prove( &[sender, recipient], &Program::serialize_instruction(balance_to_move).unwrap(), &[0, 2], &[0xdeadbeef], - &[(NullifierPublicKey::from(&[1; 32]), [2; 32], [3; 32])], + &[(recipient_keys.npk(), recipient_keys.ivk(), [3; 32])], &[], &Program::authenticated_transfer_program(), &[99; 32], @@ -160,8 +170,12 @@ mod tests { assert_eq!(output.new_nullifiers.len(), 0); assert_eq!(output.commitment_set_digest, [99; 32]); assert_eq!(output.encrypted_private_post_states.len(), 1); - // TODO: replace with real assertion when encryption is implemented - assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); + + let recipient_post = output.encrypted_private_post_states[0] + .clone() + .decrypt(&recipient_keys.isk, 0) + .unwrap(); + assert_eq!(recipient_post, expected_recipient_post); } #[test] @@ -210,8 +224,8 @@ mod tests { &[1, 2], &[0xdeadbeef1, 0xdeadbeef2], &[ - (sender_keys.npk(), sender_keys.ivk, [3; 32]), - (recipient_keys.npk(), recipient_keys.ivk, [5; 32]), + (sender_keys.npk(), sender_keys.ivk(), [3; 32]), + (recipient_keys.npk(), recipient_keys.ivk(), [5; 32]), ], &[( sender_keys.nsk, @@ -228,9 +242,18 @@ mod tests { assert_eq!(output.new_commitments, expected_new_commitments); assert_eq!(output.new_nullifiers, expected_new_nullifiers); assert_eq!(output.commitment_set_digest, commitment_set.digest()); - // TODO: replace with real assertion when encryption is implemented assert_eq!(output.encrypted_private_post_states.len(), 2); - assert_eq!(output.encrypted_private_post_states[0].to_bytes(), vec![0]); - assert_eq!(output.encrypted_private_post_states[1].to_bytes(), vec![0]); + + let recipient_post_1 = output.encrypted_private_post_states[0] + .clone() + .decrypt(&sender_keys.isk, 0) + .unwrap(); + assert_eq!(recipient_post_1, expected_private_account_1); + + let recipient_post_2 = output.encrypted_private_post_states[1] + .clone() + .decrypt(&recipient_keys.isk, 1) + .unwrap(); + assert_eq!(recipient_post_2, expected_private_account_2); } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 350743f..fc7bcaf 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -711,26 +711,30 @@ pub mod tests { pub struct TestPrivateKeys { pub nsk: NullifierSecretKey, - pub ivk: IncomingViewingPublicKey, + pub isk: [u8; 32], } impl TestPrivateKeys { pub fn npk(&self) -> NullifierPublicKey { NullifierPublicKey::from(&self.nsk) } + + pub fn ivk(&self) -> IncomingViewingPublicKey { + IncomingViewingPublicKey::from_scalar(self.isk) + } } pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], - ivk: [31; 32], + isk: [31; 32], } } pub fn test_private_account_keys_2() -> TestPrivateKeys { TestPrivateKeys { nsk: [38; 32], - ivk: [83; 32], + isk: [83; 32], } } @@ -757,7 +761,7 @@ pub mod tests { &Program::serialize_instruction(balance_to_move).unwrap(), &[0, 2], &[0xdeadbeef], - &[(recipient_keys.npk(), recipient_keys.ivk, esk)], + &[(recipient_keys.npk(), recipient_keys.ivk(), esk)], &[], &Program::authenticated_transfer_program(), &state.commitment_set_digest(), @@ -802,8 +806,8 @@ pub mod tests { &[1, 2], &new_nonces, &[ - (sender_keys.npk(), sender_keys.ivk, [3; 32]), - (recipient_keys.npk(), recipient_keys.ivk, [4; 32]), + (sender_keys.npk(), sender_keys.ivk(), [3; 32]), + (recipient_keys.npk(), recipient_keys.ivk(), [4; 32]), ], &[( sender_keys.nsk, @@ -856,7 +860,7 @@ pub mod tests { &Program::serialize_instruction(balance_to_move).unwrap(), &[1, 0], &[new_nonce], - &[(sender_keys.npk(), sender_keys.ivk, [3; 32])], + &[(sender_keys.npk(), sender_keys.ivk(), [3; 32])], &[( sender_keys.nsk, state From f2a7c574e59faf38cbc5d0e0b5c63ecff6acf736 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 22 Aug 2025 18:49:46 -0300 Subject: [PATCH 43/46] fix nonce in circuit --- .../guest/src/bin/privacy_preserving_circuit.rs | 6 ++++-- nssa/src/privacy_preserving_transaction/circuit.rs | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 181bdc9..e240b7d 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -54,13 +54,15 @@ fn main() { for i in 0..n_accounts { match visibility_mask[i] { 0 => { + // Public account + public_pre_states.push(pre_states[i].clone()); + let mut post = post_states[i].clone(); + post.nonce += 1; if post.program_owner == DEFAULT_PROGRAM_ID { // Claim account post.program_owner = program_id; } - // Public account - public_pre_states.push(pre_states[i].clone()); public_post_states.push(post); } 1 | 2 => { diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 8bbdb79..639b31f 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -136,7 +136,8 @@ mod tests { let expected_sender_post = Account { program_owner: program.id(), balance: 100 - balance_to_move, - ..Account::default() + nonce: 1, + data: vec![], }; let expected_recipient_post = Account { From 2e371300d3bd836a37f825eb160589a13087f97b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 25 Aug 2025 07:44:56 -0300 Subject: [PATCH 44/46] refactor mt --- nssa/Cargo.toml | 1 + nssa/src/merkle_tree/default_values.rs | 162 ++---- nssa/src/merkle_tree/mod.rs | 474 +++++++++--------- .../privacy_preserving_transaction/circuit.rs | 8 +- nssa/src/state.rs | 32 +- 5 files changed, 291 insertions(+), 386 deletions(-) diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 0939c39..c050c65 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -18,3 +18,4 @@ hex = "0.4.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } +hex-literal = "1.0.0" diff --git a/nssa/src/merkle_tree/default_values.rs b/nssa/src/merkle_tree/default_values.rs index 0316644..c011557 100644 --- a/nssa/src/merkle_tree/default_values.rs +++ b/nssa/src/merkle_tree/default_values.rs @@ -1,130 +1,36 @@ +use hex_literal::hex; + pub(crate) const DEFAULT_VALUES: [[u8; 32]; 32] = [ - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35, - 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, - ], - [ - 219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, 236, - 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113, - ], - [ - 199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, 237, - 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60, - ], - [ - 83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, 213, - 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60, - ], - [ - 158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, 3, - 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48, - ], - [ - 216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, 46, - 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161, - ], - [ - 135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, 6, - 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44, - ], - [ - 38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, 83, - 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147, - ], - [ - 80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, 90, - 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225, - ], - [ - 255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, 225, - 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43, - ], - [ - 108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, 23, - 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32, - ], - [ - 183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, 15, - 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95, - ], - [ - 223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, 103, - 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94, - ], - [ - 181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, 117, - 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132, - ], - [ - 212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, 167, - 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187, - ], - [ - 143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, - 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235, - ], - [ - 141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, 190, - 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171, - ], - [ - 149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, 84, - 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164, - ], - [ - 248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, 108, - 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127, - ], - [ - 205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, - 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170, - ], - [ - 138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, 138, - 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156, - ], - [ - 254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, 155, - 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103, - ], - [ - 231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, 236, - 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215, - ], - [ - 49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, 200, - 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192, - ], - [ - 33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, 67, - 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68, - ], - [ - 97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, 179, - 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101, - ], - [ - 124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, - 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196, - ], - [ - 132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, 184, - 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225, - ], - [ - 136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, 176, - 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54, - ], - [ - 181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, 192, - 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92, - ], - [ - 152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, 124, - 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247, - ], + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + hex!("f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"), + hex!("c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c"), + hex!("536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c"), + hex!("9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30"), + hex!("d88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1"), + hex!("87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c"), + hex!("26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193"), + hex!("506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1"), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b"), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220"), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f"), + hex!("df6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e"), + hex!("b58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784"), + hex!("d49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb"), + hex!("8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb"), + hex!("8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab"), + hex!("95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4"), + hex!("f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f"), + hex!("cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa"), + hex!("8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c"), + hex!("feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167"), + hex!("e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7"), + hex!("31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0"), + hex!("21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544"), + hex!("619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765"), + hex!("7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4"), + hex!("848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1"), + hex!("8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636"), + hex!("b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c"), + hex!("985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7"), ]; diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index fffcb7d..30ddf79 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -23,147 +23,135 @@ fn hash_value(value: &Value) -> Node { #[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct MerkleTree { - index_map: HashMap, - node_map: HashMap, + nodes: Vec, capacity: usize, + length: usize, } impl MerkleTree { pub fn root(&self) -> Node { let root_index = self.root_index(); - *self.get_node(&root_index) + *self.get_node(root_index) } fn root_index(&self) -> usize { + let tree_depth = self.depth(); let capacity_depth = self.capacity.trailing_zeros() as usize; - let diff = capacity_depth - self.depth(); - if diff == 0 { 0 } else { (1 << diff) - 1 } + + if tree_depth == capacity_depth { + 0 + } else { + (1 << (capacity_depth - tree_depth)) - 1 + } } + /// Number of levels required to hold all values fn depth(&self) -> usize { - self.index_map.len().next_power_of_two().trailing_zeros() as usize + let result = self.length.next_power_of_two().trailing_zeros() as usize; + result } - fn get_node(&self, index: &usize) -> &Node { - self.node_map.get(&index).unwrap_or_else(|| { - let index_depth = usize::BITS as usize - (index + 1).leading_zeros() as usize - 1; - let total_levels = self.capacity.trailing_zeros() as usize; - if total_levels >= index_depth { - &default_values::DEFAULT_VALUES[total_levels - index_depth] - } else { - //TODO: implement error handling - panic!(); - } - }) + fn get_node(&self, index: usize) -> &Node { + &self.nodes[index] } fn set_node(&mut self, index: usize, node: Node) { - self.node_map.insert(index, node); + self.nodes[index] = node; } pub fn with_capacity(capacity: usize) -> Self { let capacity = capacity.next_power_of_two(); + let total_depth = capacity.trailing_zeros() as usize; + + let nodes = default_values::DEFAULT_VALUES[..(total_depth + 1)] + .iter() + .rev() + .enumerate() + .flat_map(|(level, default_value)| std::iter::repeat_n(default_value, 1 << level)) + .cloned() + .collect(); + Self { - index_map: HashMap::with_capacity(capacity), - node_map: HashMap::with_capacity(capacity << 1), + nodes, capacity, + length: 0, } } fn reallocate_to_double_capacity(&mut self) { - let mut this = Self::with_capacity(self.capacity << 1); - let mut pairs: Vec<_> = self.index_map.iter().collect(); - pairs.sort_by_key(|&(_, index)| index); - for (value, _) in pairs { - this.insert(*value); + let old_capacity = self.capacity; + let new_capacity = old_capacity << 1; + + let mut this = Self::with_capacity(new_capacity); + + for (index, value) in self.nodes.iter().enumerate() { + let offset = prev_power_of_two(index + 1); + let new_index = index + offset; + this.set_node(new_index, *value); } + + this.length = self.length; + *self = this; } - pub fn insert(&mut self, value: Value) -> bool { - if self.index_map.contains_key(&value) { - return false; - } - - if self.capacity == self.index_map.len() { + pub fn insert(&mut self, value: Value) -> usize { + if self.length == self.capacity { self.reallocate_to_double_capacity(); } - let new_index = self.index_map.len(); - self.index_map.insert(value, new_index); + let new_index = self.length; - let base_length = self.capacity; - let mut layer_node = hash_value(&value); - let mut layer_index = new_index + base_length - 1; - self.set_node(layer_index, layer_node); + let mut node_index = new_index + self.capacity - 1; + let mut node_hash = hash_value(&value); - let mut layer = 0; - let mut top_layer = self.depth(); - while layer < top_layer { - let is_left_child = layer_index & 1 == 1; + self.set_node(node_index, node_hash); + self.length += 1; - let (parent_index, new_parent_node) = if is_left_child { - let parent_index = (layer_index - 1) >> 1; - let sibling = self.get_node(&(layer_index + 1)); - let new_parent_node = hash_two(&layer_node, sibling); - (parent_index, new_parent_node) - } else { - let parent_index = (layer_index - 2) >> 1; - let sibling = self.get_node(&(layer_index - 1)); - let new_parent_node = hash_two(sibling, &layer_node); - (parent_index, new_parent_node) - }; - - self.set_node(parent_index, new_parent_node); - - layer += 1; - layer_index = parent_index; - layer_node = new_parent_node + let root_index = self.root_index(); + for _ in 0..self.depth() { + let parent_index = (node_index - 1) >> 1; + let left_child = self.get_node((parent_index << 1) + 1); + let right_child = self.get_node((parent_index << 1) + 2); + node_hash = hash_two(left_child, right_child); + self.set_node(parent_index, node_hash); + node_index = parent_index; } - true + new_index } pub fn new(values: &[Value]) -> Self { - let mut deduplicated_values: Vec = Vec::with_capacity(values.len()); - let mut seen = HashSet::new(); - for value in values.into_iter() { - if !seen.contains(value) { - deduplicated_values.push(*value); - seen.insert(*value); - } - } - let mut this = Self::with_capacity(deduplicated_values.len()); - for value in deduplicated_values.into_iter() { + let mut this = Self::with_capacity(values.len()); + for value in values.iter().cloned() { this.insert(value); } this } - pub fn get_authentication_path_for(&self, value: &Value) -> Option<(usize, Vec)> { - let mut result = Vec::with_capacity(self.depth()); - let value_index = self.index_map.get(value)?; - let base_length = self.capacity; - let mut layer_index = base_length + value_index - 1; - let mut layer = 0; - let top_layer = self.depth(); - while layer < top_layer { - let is_left_child = layer_index & 1 == 1; - let (sibling, parent_index) = if is_left_child { - (self.get_node(&(layer_index + 1)), (layer_index - 1) >> 1) - } else { - (self.get_node(&(layer_index - 1)), (layer_index - 2) >> 1) - }; - result.push(*sibling); - - layer += 1; - layer_index = parent_index; + pub fn get_authentication_path_for(&self, index: usize) -> Option> { + if index >= self.length { + return None; } - Some((*value_index, result)) - } - pub(crate) fn contains(&self, value: &[u8; 32]) -> bool { - self.index_map.contains_key(value) + let mut path = Vec::with_capacity(self.depth()); + + let mut node_index = self.capacity + index - 1; + let root_index = self.root_index(); + + while node_index != root_index { + let parent_index = (node_index - 1) >> 1; + let is_left_child = node_index & 1 == 1; + let sibling_index = if is_left_child { + node_index + 1 + } else { + node_index - 1 + }; + path.push(*self.get_node(sibling_index)); + node_index = parent_index; + } + + Some(path) } } @@ -183,26 +171,34 @@ fn verify_authentication_path(value: &Value, index: usize, path: &[Node], root: &result == root } +fn prev_power_of_two(x: usize) -> usize { + if x == 0 { + return 0; // define as 0 + } + 1 << (usize::BITS as usize - x.leading_zeros() as usize - 1) +} + #[cfg(test)] mod tests { + use hex_literal::hex; use nssa_core::account::{Account, NullifierPublicKey}; use super::*; + #[test] + fn test_merkle_tree_0() { + let values = [[0; 32]]; + let tree = MerkleTree::new(&values); + let expected_root = assert_eq!(tree.length, 1); + assert_eq!(tree.root(), hash_value(&[0; 32])); + } #[test] fn test_merkle_tree_1() { let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let tree = MerkleTree::new(&values); - let expected_root = [ - 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, - 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, - ]; - + let expected_root = + hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"); assert_eq!(tree.root(), expected_root); - assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); - assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); - assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); - assert_eq!(*tree.index_map.get(&[4; 32]).unwrap(), 3); assert_eq!(tree.capacity, 4); } @@ -210,16 +206,9 @@ mod tests { fn test_merkle_tree_2() { let values = [[1; 32], [2; 32], [3; 32], [0; 32]]; let tree = MerkleTree::new(&values); - let expected_root = [ - 201, 187, 184, 48, 150, 223, 133, 21, 122, 20, 110, 125, 119, 4, 85, 169, 132, 18, 222, - 224, 99, 49, 135, 238, 134, 254, 230, 200, 164, 91, 131, 26, - ]; - + let expected_root = + hex!("c9bbb83096df85157a146e7d770455a98412dee0633187ee86fee6c8a45b831a"); assert_eq!(tree.root(), expected_root); - assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); - assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); - assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); - assert_eq!(*tree.index_map.get(&[0; 32]).unwrap(), 3); assert_eq!(tree.capacity, 4); } @@ -227,16 +216,9 @@ mod tests { fn test_merkle_tree_3() { let values = [[1; 32], [2; 32], [3; 32]]; let tree = MerkleTree::new(&values); - let expected_root = [ - 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, - 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, - ]; - + let expected_root = + hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); assert_eq!(tree.root(), expected_root); - assert_eq!(*tree.index_map.get(&[1; 32]).unwrap(), 0); - assert_eq!(*tree.index_map.get(&[2; 32]).unwrap(), 1); - assert_eq!(*tree.index_map.get(&[3; 32]).unwrap(), 2); - assert!(tree.index_map.get(&[0; 32]).is_none()); assert_eq!(tree.capacity, 4); } @@ -244,17 +226,10 @@ mod tests { fn test_merkle_tree_4() { let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; let tree = MerkleTree::new(&values); - let expected_root = [ - 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, - 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, - ]; + let expected_root = + hex!("ef418aed5aa20702d4d94c92da79a4012f2e36f1008bfdb3cd1e38749dca2499"); assert_eq!(tree.root(), expected_root); - assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); - assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); - assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); - assert_eq!(*tree.index_map.get(&[14; 32]).unwrap(), 3); - assert_eq!(*tree.index_map.get(&[15; 32]).unwrap(), 4); assert_eq!(tree.capacity, 8); } @@ -265,17 +240,9 @@ mod tests { [13; 32], [15; 32], [11; 32], ]; let tree = MerkleTree::new(&values); - let expected_root = [ - 239, 65, 138, 237, 90, 162, 7, 2, 212, 217, 76, 146, 218, 121, 164, 1, 47, 46, 54, 241, - 0, 139, 253, 179, 205, 30, 56, 116, 157, 202, 36, 153, - ]; - + let expected_root = + hex!("ef418aed5aa20702d4d94c92da79a4012f2e36f1008bfdb3cd1e38749dca2499"); assert_eq!(tree.root(), expected_root); - assert_eq!(*tree.index_map.get(&[11; 32]).unwrap(), 0); - assert_eq!(*tree.index_map.get(&[12; 32]).unwrap(), 1); - assert_eq!(*tree.index_map.get(&[13; 32]).unwrap(), 2); - assert_eq!(*tree.index_map.get(&[14; 32]).unwrap(), 3); - assert_eq!(*tree.index_map.get(&[15; 32]).unwrap(), 4); assert_eq!(tree.capacity, 8); } @@ -283,11 +250,8 @@ mod tests { fn test_merkle_tree_6() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); - let expected_root = [ - 6, 156, 184, 37, 154, 6, 254, 110, 219, 63, 167, 255, 121, 51, 166, 221, 125, 202, 111, - 202, 41, 147, 20, 55, 151, 148, 166, 136, 146, 108, 55, 146, - ]; - + let expected_root = + hex!("069cb8259a06fe6edb3fa7ff7933a6dd7dca6fca299314379794a688926c3792"); assert_eq!(tree.root(), expected_root); } @@ -295,33 +259,33 @@ mod tests { fn test_with_capacity_4() { let tree = MerkleTree::with_capacity(4); - assert!(tree.index_map.is_empty()); - assert!(tree.node_map.is_empty()); + assert_eq!(tree.length, 0); + assert_eq!(tree.nodes.len(), 7); for i in 3..7 { - assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[0], "{i}"); + assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0], "{i}"); } for i in 1..3 { - assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[1], "{i}"); + assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1], "{i}"); } - assert_eq!(*tree.get_node(&0), default_values::DEFAULT_VALUES[2]); + assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[2]); } #[test] fn test_with_capacity_5() { let tree = MerkleTree::with_capacity(5); - assert!(tree.index_map.is_empty()); - assert!(tree.node_map.is_empty()); + assert_eq!(tree.length, 0); + assert_eq!(tree.nodes.len(), 15); for i in 7..15 { - assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[0]) + assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0]) } for i in 3..7 { - assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[1]) + assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1]) } for i in 1..3 { - assert_eq!(*tree.get_node(&i), default_values::DEFAULT_VALUES[2]) + assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[2]) } - assert_eq!(*tree.get_node(&0), default_values::DEFAULT_VALUES[3]) + assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[3]) } #[test] @@ -330,10 +294,8 @@ mod tests { let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; - let expected_root = [ - 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, 32, 207, - 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, - ]; + let expected_root = + hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"); tree.insert(values[0]); tree.insert(values[1]); @@ -349,10 +311,8 @@ mod tests { let values = [[1; 32], [2; 32], [3; 32]]; - let expected_root = [ - 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, - 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, - ]; + let expected_root = + hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); tree.insert(values[0]); tree.insert(values[1]); @@ -367,10 +327,8 @@ mod tests { let values = [[1; 32], [2; 32], [3; 32]]; - let expected_root = [ - 200, 211, 216, 210, 177, 63, 39, 206, 236, 205, 198, 153, 17, 152, 113, 249, 243, 46, - 167, 237, 134, 255, 69, 208, 173, 17, 247, 123, 40, 205, 117, 104, - ]; + let expected_root = + hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); tree.insert(values[0]); tree.insert(values[1]); @@ -424,47 +382,16 @@ mod tests { assert_eq!(expected_tree, tree); } - #[test] - fn test_insert_value_4() { - let mut tree = MerkleTree::with_capacity(1); - - let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; - let expected_tree = MerkleTree::new(&values); - - tree.insert(values[0]); - tree.insert(values[0]); - tree.insert(values[1]); - tree.insert(values[1]); - tree.insert(values[2]); - tree.insert(values[3]); - tree.insert(values[2]); - tree.insert(values[0]); - tree.insert(values[4]); - tree.insert(values[2]); - tree.insert(values[4]); - - assert_eq!(expected_tree, tree); - } - #[test] fn test_authentication_path_1() { let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let tree = MerkleTree::new(&values); - let expected_authentication_path = ( - 2, - vec![ - [ - 159, 79, 182, 143, 62, 29, 172, 130, 32, 47, 154, 165, 129, 206, 11, 191, 31, - 118, 93, 240, 233, 172, 60, 140, 87, 226, 15, 104, 90, 186, 184, 237, - ], - [ - 80, 162, 125, 71, 70, 243, 87, 203, 112, 12, 190, 157, 72, 131, 183, 127, 182, - 79, 1, 40, 130, 138, 52, 137, 220, 106, 111, 33, 221, 191, 36, 20, - ], - ], - ); + let expected_authentication_path = vec![ + hex!("9f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed"), + hex!("50a27d4746f357cb700cbe9d4883b77fb64f0128828a3489dc6a6f21ddbf2414"), + ]; - let authentication_path = tree.get_authentication_path_for(&[3; 32]).unwrap(); + let authentication_path = tree.get_authentication_path_for(2).unwrap(); assert_eq!(authentication_path, expected_authentication_path); } @@ -472,21 +399,12 @@ mod tests { fn test_authentication_path_2() { let values = [[1; 32], [2; 32], [3; 32]]; let tree = MerkleTree::new(&values); - let expected_authentication_path = ( - 0, - vec![ - [ - 117, 135, 123, 180, 29, 57, 59, 95, 184, 69, 92, 230, 14, 205, 141, 218, 0, 29, - 6, 49, 100, 150, 177, 77, 250, 127, 137, 86, 86, 238, 202, 74, - ], - [ - 164, 27, 133, 93, 45, 180, 222, 144, 82, 205, 123, 229, 236, 103, 214, 88, 102, - 41, 203, 159, 110, 50, 70, 164, 175, 165, 186, 49, 63, 7, 169, 197, - ], - ], - ); + let expected_authentication_path = vec![ + hex!("75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a"), + hex!("a41b855d2db4de9052cd7be5ec67d6586629cb9f6e3246a4afa5ba313f07a9c5"), + ]; - let authentication_path = tree.get_authentication_path_for(&[1; 32]).unwrap(); + let authentication_path = tree.get_authentication_path_for(0).unwrap(); assert_eq!(authentication_path, expected_authentication_path); } @@ -494,25 +412,13 @@ mod tests { fn test_authentication_path_3() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); - let expected_authentication_path = ( - 4, - vec![ - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ], - [ - 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, - 0, 61, 35, 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, - ], - [ - 72, 199, 63, 120, 33, 165, 138, 141, 42, 112, 62, 91, 57, 197, 113, 192, 170, - 32, 207, 20, 171, 205, 10, 248, 242, 185, 85, 188, 32, 41, 152, 222, - ], - ], - ); + let expected_authentication_path = vec![ + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + hex!("f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"), + hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"), + ]; - let authentication_path = tree.get_authentication_path_for(&[5; 32]).unwrap(); + let authentication_path = tree.get_authentication_path_for(4).unwrap(); assert_eq!(authentication_path, expected_authentication_path); } @@ -520,24 +426,16 @@ mod tests { fn test_authentication_path_4() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); - let value = [6; 32]; - assert!(tree.get_authentication_path_for(&value).is_none()); + assert!(tree.get_authentication_path_for(5).is_none()); } #[test] fn test_authentication_path_5() { - let values = vec![[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; - let tree = MerkleTree::new(&values); - let value = [0; 32]; - assert!(tree.get_authentication_path_for(&value).is_none()); - } - - #[test] - fn test_authentication_path_6() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); - let value = [5; 32]; - let (index, path) = tree.get_authentication_path_for(&value).unwrap(); + let index = 4; + let value = values[index]; + let path = tree.get_authentication_path_for(index).unwrap(); assert!(verify_authentication_path( &value, index, @@ -545,6 +443,92 @@ mod tests { &tree.root() )); } + + #[test] + fn test_tree_with_63_insertions() { + let values = [ + hex!("cd00acab0f45736e6c6311f1953becc0b69a062e7c2a7310875d28bdf9ef9c5b"), + hex!("0df5a6afbcc7bf126caf7084acfc593593ab512e6ca433c61c1a922be40a04ea"), + hex!("23c1258620266c7bedb6d1ee32f6da9413e4010ace975239dccb34e727e07c40"), + hex!("f33ccc3a11476b0ef62326ca5ec292056759b05e6a28023d2d1ce66165611353"), + hex!("77f914ab016b8049f6bea7704000e413a393865918a3824f9285c3db0aacff23"), + hex!("910a1c23188e54d57fd167ddb0f8bf68c6b70ed9ec76ef56c4b7f2632f82ca7f"), + hex!("047ee85526197d1e7403a559cf6d2f22c1926c8ad59481a2e2f1b697af45e40b"), + hex!("9d355cf89fb382ae34bf80566b28489278d10f2cebb5b0ea42fab1bac5adae0c"), + hex!("604018b95232596b2685a9bc737b6cccb53b10e483d2d9a2f4a755410b02a188"), + hex!("a16708ef7b6bf1796063addaf57d6a566b6f87b0bbe42af43a4590d05f1684cb"), + hex!("820f2dfa271cd2fd41e1452406d5dad552c85c1223c45d45dbd7446759fdc6b8"), + hex!("680b6912d7e219f8805d4d28adb4428dd78fea0dc1b8cdb2412645c4b1962c88"), + hex!("14d5471ce6c45506753982b17cac5790ac7bc29e6f388f31052d7dfd62b294e5"), + hex!("8b364200172b777d4aa16d2098b5eb98ac3dd4a1b9597e5c2bf6f6930031f230"), + hex!("9bb45b910711874339dda8a21a9aad73822286f5e52d7d3de0ed78dfbba329a5"), + hex!("d6806d5df5cb25ce5d531042f09b3cb34fb9e47c61182b63cccd9d44392f6027"), + hex!("b8cfa90ebc8fd09c04682d93a08fddd3e8e57715174dcc92451edd191264a58b"), + hex!("3463c7f81d00f809b3dfa83195447c927fb4045b3913dac6f45bee6c4010d7ed"), + hex!("1d6ad7f7d677905feb506c58f4b404a79370ebc567296abea3a368b61d5a8239"), + hex!("a58085ecf00963cb22da23c901b9b3ddc56462bb96ff03c923d67708e10dd29c"), + hex!("c3319f4a65fb5bbb8447137b0972c03cbd84ebf7d9da194e0fcbd68c2d4d5bdb"), + hex!("4aa31e90e0090faf3648d05e5d5499df2c78ebed4d6e6c23d8147de5d67dae73"), + hex!("9f33b1d2c8bc7bd265336de1033ede6344bc41260313bdcb43f1108b83b9be92"), + hex!("6500d4ad93d41c16ec81eaa5e70f173194aabe5c1072ac263b5727296f5b7cac"), + hex!("3584f5d260003669fad98786e13171376b0f19410cb232ce65606cbff79e6768"), + hex!("c8410946ebf56f13141c894a34ced85a5230088af70dcea581e44f52847830ac"), + hex!("71dd90281cdebb70422f2d04ae446d5d2d5ea64b803c16128d37e3fcd5d1a4cc"), + hex!("c05acf8d77ab4d659a538bd35af590864a7ad9c055ff5d6cda9d5aecfccecba3"), + hex!("f1df98822ea084cce9021aa9cc81b1746cd1e84a75690da63e10fd877633ed77"), + hex!("2ca822bc8f67bceb0a71a0d06fea7349036ef3e5ec21795a851e4182bd35ce01"), + hex!("7fd2179abc3bcf89b4d8092988ba8c23952b3bbd3d7caea6b5ea0c13cf19f68b"), + hex!("91b6ad516e017f6aa5a2e95776538bd3a3e933c1b1d32bb5e0f00a9db63c9c24"), + hex!("cd31a8b5eef5ca0be5ef1cb261d0bf0a74d774a3152bb99739cfd296a1d0b85e"), + hex!("3fb16f48b2bf93f3815979e6638f975d7f935088ec37db0be0f07965fbc78339"), + hex!("c60c61b99bf486af5f4bf780a69860dafcd35c1474306a8575666fb5449bcec0"), + hex!("8048d0d7e14091251f3f6c6b10bf6b5880a014b513f9f8c2395501dbffa6192a"), + hex!("778b5af10b9dbe80b60a8e4f0bb91caf4476bcb812801099760754ae623fbd84"), + hex!("d3ac25467920a4e08998b7a3226b8b54bfe66ac58cfedc71f15b2402fee0054a"), + hex!("029aa94598fae2961a0d43937b8a9a3138bcfeae99a7cb15f77fac7c506f8432"), + hex!("2eee5ef52fe669cb6882a68c893abdc1262dcf4424e4ba7a479da7cf1c10171d"), + hex!("de3fb3d070e3a90f0eed8b5e65088a8dc0e4e3c342b9c0bf33bab714eae5dfec"), + hex!("14d40177e833ab45bbfdc5f2b11fba7efaebb3f69facc554f24b549a2efe8538"), + hex!("5734355069702448774fb2df95f1d562e1b9fe1514aeb6b922554ee9d2d01068"), + hex!("8a273d49ac110343cec2cf3359d16eb2906b446bd9ec9833e2a640cebc8d5155"), + hex!("e3fa984dd3cbeb9a7e827ed32d3d4e6a6ba643a55d82be97d9ddb06ee809fa3e"), + hex!("90b1d5a364e17c8b7965396b06ec6e13749b5fc16500731518ad8fc30ae33e77"), + hex!("7517376541b2e8ec83cbab04522b54a26610908a9872feb663451385aea58eb1"), + hex!("5cba2e4cf7448e526d161133c4b2ea7c919ac4813a7308612595f46f11dea6cd"), + hex!("c721911b300bec0691c8a2dfaabfef1d66b7b6258918914d3c3ad690729f05b7"), + hex!("d0d0a70d8ae0d27806fa0b711c507290c260a89cbca0436d339d1dccdd087d62"), + hex!("2a625c28ea763c5e82dd0a93ecfca7ec371ccbb363cd42be359c2c875f58009d"), + hex!("174ef0119932ed890397d9f3837dd85f9100558b6fc9085d4af947ae8cf74bbc"), + hex!("b497bc267151e8efa3c6daa461e6804b01a3f05f44f1f4d5b41d5f0d3f5219b1"), + hex!("e987e91f5734630ddd7e6b58733b4fcdbc316ee9e8cac0e94c36c91cf58e59cc"), + hex!("55019ad8bbe656c51eb042190c1c8da53f42baf43fd2350ebea38fc7cca2fae3"), + hex!("c45a638edd18a6d9f5ad20b870c81b8626459bcb22dae7d58add7a6b6c6a84a8"), + hex!("d42d3a5fb2ad50b2027fe5a36d59dd71e49a63e4b1b299073c96bbf7ba5d68a1"), + hex!("9599e561054bcd3f647eb018ab0b069d3176497d42be9c4466551cbb959be47c"), + hex!("42f33b23775327ff71aea6569548255f3cc9929da73373cc9bb1743d417f7cda"), + hex!("ab24294f44fc6fdbeb96e0f6e93c4f6d97d035b73b9a337c353e18c6d0603bdd"), + hex!("33954ec63520334f99b640a2982ac966b68c363fed383d621a1ab573934f1d33"), + hex!("5e2a1f7df963d1fd8f50a285387cfbb5df581426619b325563e20bf7886c62b7"), + hex!("13ffde471d4e27c473254e766fd1328ad80c42cab4d4955cffeae43d866f86e5"), + ]; + + let expected_root = + hex!("1cf9b214217d7823f9de51b8f6cb34d0a99436a3a1bb762f90b815672a6afcc0"); + + let mut tree_less_capacity = MerkleTree::with_capacity(1); + let mut tree_exact_capacity = MerkleTree::with_capacity(64); + let mut tree_more_capacity = MerkleTree::with_capacity(128); + + for value in &values { + tree_less_capacity.insert(*value); + tree_exact_capacity.insert(*value); + tree_more_capacity.insert(*value); + } + + assert_eq!(tree_more_capacity.root(), expected_root); + assert_eq!(tree_less_capacity.root(), expected_root); + assert_eq!(tree_exact_capacity.root(), expected_root); + } } // diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 639b31f..41e462a 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -192,13 +192,15 @@ mod tests { let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account); + let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, }; let balance_to_move: u128 = 37; - let commitment_set = CommitmentSet(MerkleTree::new(&[commitment_sender.to_byte_array()])); + let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &sender_keys.nsk)]; + let program = Program::authenticated_transfer_program(); let expected_private_account_1 = Account { @@ -217,7 +219,9 @@ mod tests { Commitment::new(&sender_keys.npk(), &expected_private_account_1), Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &sender_keys.nsk)]; + + let mut commitment_set = CommitmentSet::with_capacity(2); + commitment_set.extend(&[commitment_sender.clone()]); let (output, proof) = execute_and_prove( &[sender_pre.clone(), recipient], diff --git a/nssa/src/state.rs b/nssa/src/state.rs index fc7bcaf..69af23d 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -10,26 +10,39 @@ use nssa_core::{ }; use std::collections::{HashMap, HashSet}; -pub(crate) struct CommitmentSet(pub(crate) MerkleTree); +pub(crate) struct CommitmentSet { + merkle_tree: MerkleTree, + commitments: HashMap, +} impl CommitmentSet { pub(crate) fn digest(&self) -> CommitmentSetDigest { - self.0.root() + self.merkle_tree.root() } pub(crate) fn get_proof_for(&self, commitment: &Commitment) -> Option { - self.0 - .get_authentication_path_for(&commitment.to_byte_array()) + let index = *self.commitments.get(commitment)?; + self.merkle_tree + .get_authentication_path_for(index) + .map(|path| (index, path)) } pub(crate) fn extend(&mut self, commitments: &[Commitment]) { - for commitment in commitments { - self.0.insert(commitment.to_byte_array()); + for commitment in commitments.iter().cloned() { + let index = self.merkle_tree.insert(commitment.to_byte_array()); + self.commitments.insert(commitment, index); } } fn contains(&self, commitment: &Commitment) -> bool { - self.0.contains(&commitment.to_byte_array()) + self.commitments.contains_key(commitment) + } + + pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet { + Self { + merkle_tree: MerkleTree::with_capacity(capacity), + commitments: HashMap::new(), + } } } @@ -59,10 +72,7 @@ impl V01State { let mut this = Self { public_state, - private_state: ( - CommitmentSet(MerkleTree::with_capacity(32)), - NullifierSet::new(), - ), + private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()), builtin_programs: HashMap::new(), }; From 9ff6a5bc8552a58ad8a251be1692a7eb97165bf3 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 25 Aug 2025 07:57:35 -0300 Subject: [PATCH 45/46] fix --- nssa/src/merkle_tree/default_values.rs | 162 +++++++++++++++++++------ nssa/src/merkle_tree/mod.rs | 23 +++- 2 files changed, 148 insertions(+), 37 deletions(-) diff --git a/nssa/src/merkle_tree/default_values.rs b/nssa/src/merkle_tree/default_values.rs index c011557..0316644 100644 --- a/nssa/src/merkle_tree/default_values.rs +++ b/nssa/src/merkle_tree/default_values.rs @@ -1,36 +1,130 @@ -use hex_literal::hex; - pub(crate) const DEFAULT_VALUES: [[u8; 32]; 32] = [ - hex!("0000000000000000000000000000000000000000000000000000000000000000"), - hex!("f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"), - hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"), - hex!("c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c"), - hex!("536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c"), - hex!("9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30"), - hex!("d88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1"), - hex!("87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c"), - hex!("26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193"), - hex!("506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1"), - hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b"), - hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220"), - hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f"), - hex!("df6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e"), - hex!("b58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784"), - hex!("d49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb"), - hex!("8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb"), - hex!("8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab"), - hex!("95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4"), - hex!("f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f"), - hex!("cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa"), - hex!("8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c"), - hex!("feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167"), - hex!("e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7"), - hex!("31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0"), - hex!("21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544"), - hex!("619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765"), - hex!("7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4"), - hex!("848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1"), - hex!("8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636"), - hex!("b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c"), - hex!("985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7"), + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ], + [ + 245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35, + 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75, + ], + [ + 219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, 236, + 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113, + ], + [ + 199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, 237, + 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60, + ], + [ + 83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, 213, + 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60, + ], + [ + 158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, 3, + 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48, + ], + [ + 216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, 46, + 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161, + ], + [ + 135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, 6, + 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44, + ], + [ + 38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, 83, + 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147, + ], + [ + 80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, 90, + 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225, + ], + [ + 255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, 225, + 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43, + ], + [ + 108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, 23, + 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32, + ], + [ + 183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, 15, + 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95, + ], + [ + 223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, 103, + 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94, + ], + [ + 181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, 117, + 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132, + ], + [ + 212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, 167, + 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187, + ], + [ + 143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, + 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235, + ], + [ + 141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, 190, + 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171, + ], + [ + 149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, 84, + 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164, + ], + [ + 248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, 108, + 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127, + ], + [ + 205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, + 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170, + ], + [ + 138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, 138, + 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156, + ], + [ + 254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, 155, + 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103, + ], + [ + 231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, 236, + 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215, + ], + [ + 49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, 200, + 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192, + ], + [ + 33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, 67, + 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68, + ], + [ + 97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, 179, + 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101, + ], + [ + 124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, + 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196, + ], + [ + 132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, 184, + 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225, + ], + [ + 136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, 176, + 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54, + ], + [ + 181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, 192, + 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92, + ], + [ + 152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, 124, + 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247, + ], ]; diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index 30ddf79..1b60f34 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -184,12 +184,24 @@ mod tests { use nssa_core::account::{Account, NullifierPublicKey}; use super::*; + + #[test] + fn test_empty_merkle_tree() { + let tree = MerkleTree::with_capacity(4); + let expected_root = + hex!("0000000000000000000000000000000000000000000000000000000000000000"); + assert_eq!(tree.root(), expected_root); + assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 0); + } + #[test] fn test_merkle_tree_0() { let values = [[0; 32]]; let tree = MerkleTree::new(&values); - let expected_root = assert_eq!(tree.length, 1); assert_eq!(tree.root(), hash_value(&[0; 32])); + assert_eq!(tree.capacity, 1); + assert_eq!(tree.length, 1); } #[test] @@ -200,6 +212,7 @@ mod tests { hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"); assert_eq!(tree.root(), expected_root); assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 4) } #[test] @@ -210,6 +223,7 @@ mod tests { hex!("c9bbb83096df85157a146e7d770455a98412dee0633187ee86fee6c8a45b831a"); assert_eq!(tree.root(), expected_root); assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 4); } #[test] @@ -220,6 +234,7 @@ mod tests { hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); assert_eq!(tree.root(), expected_root); assert_eq!(tree.capacity, 4); + assert_eq!(tree.length, 3); } #[test] @@ -231,6 +246,7 @@ mod tests { assert_eq!(tree.root(), expected_root); assert_eq!(tree.capacity, 8); + assert_eq!(tree.length, 5); } #[test] @@ -241,9 +257,10 @@ mod tests { ]; let tree = MerkleTree::new(&values); let expected_root = - hex!("ef418aed5aa20702d4d94c92da79a4012f2e36f1008bfdb3cd1e38749dca2499"); + hex!("3f72d2ff55921a86c48e5988ec3e19ee9d0d5aa3e23197842970a903508ed767"); assert_eq!(tree.root(), expected_root); - assert_eq!(tree.capacity, 8); + assert_eq!(tree.capacity, 16); + assert_eq!(tree.length, 11); } #[test] From 20897596b0b1158a2ca168f5fe73c8d7362e001a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 25 Aug 2025 09:22:59 -0300 Subject: [PATCH 46/46] add root history --- nssa/core/src/lib.rs | 21 +++++---- .../src/bin/privacy_preserving_circuit.rs | 20 +++------ nssa/src/merkle_tree/mod.rs | 36 ++++++++-------- .../privacy_preserving_transaction/circuit.rs | 17 +++----- .../encoding.rs | 12 ++++-- .../privacy_preserving_transaction/message.rs | 8 ++-- .../transaction.rs | 13 ++---- nssa/src/state.rs | 43 ++++++++++++++----- 8 files changed, 91 insertions(+), 79 deletions(-) diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index ba57f63..e523f6f 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -38,11 +38,10 @@ pub mod error; pub type CommitmentSetDigest = [u8; 32]; pub type MembershipProof = (usize, Vec<[u8; 32]>); -pub fn verify_membership_proof( +pub fn compute_root_associated_to_path( commitment: &Commitment, proof: &MembershipProof, - digest: &CommitmentSetDigest, -) -> bool { +) -> CommitmentSetDigest { let value_bytes = commitment.to_byte_array(); let mut result: [u8; 32] = Impl::hash_bytes(&value_bytes) .as_bytes() @@ -64,7 +63,7 @@ pub fn verify_membership_proof( } level_index >>= 1; } - &result == digest + result } pub type EphemeralPublicKey = Secp256k1Point; @@ -243,7 +242,6 @@ pub struct PrivacyPreservingCircuitInput { )>, pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, pub program_id: ProgramId, - pub commitment_set_digest: CommitmentSetDigest, } #[derive(Serialize, Deserialize)] @@ -253,8 +251,7 @@ pub struct PrivacyPreservingCircuitOutput { pub public_post_states: Vec, pub encrypted_private_post_states: Vec, pub new_commitments: Vec, - pub new_nullifiers: Vec, - pub commitment_set_digest: CommitmentSetDigest, + pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, } #[cfg(feature = "host")] @@ -313,11 +310,13 @@ mod tests { &NullifierPublicKey::from(&[1; 32]), &Account::default(), )], - new_nullifiers: vec![Nullifier::new( - &Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()), - &[1; 32], + new_nullifiers: vec![( + Nullifier::new( + &Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()), + &[1; 32], + ), + [0xab; 32], )], - commitment_set_digest: [0xab; 32], }; let bytes = output.to_bytes(); let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index e240b7d..51108af 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -2,8 +2,9 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use nssa_core::{ account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, + compute_root_associated_to_path, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, - verify_membership_proof, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, + CommitmentSetDigest, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, }; @@ -15,7 +16,6 @@ fn main() { private_account_keys, private_account_auth, program_id, - commitment_set_digest, } = env::read(); // TODO: Check that `program_execution_proof` is one of the allowed built-in programs @@ -44,7 +44,7 @@ fn main() { let mut public_post_states: Vec = Vec::new(); let mut encrypted_private_post_states: Vec = Vec::new(); let mut new_commitments: Vec = Vec::new(); - let mut new_nullifiers: Vec = Vec::new(); + let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new(); let mut private_nonces_iter = private_account_nonces.iter(); let mut private_keys_iter = private_account_keys.iter(); @@ -80,15 +80,10 @@ fn main() { panic!("Npk mismatch"); } - // Verify pre-state commitment membership + // Compute commitment set digest associated with provided auth path let commitment_pre = Commitment::new(Npk, &pre_states[i].account); - if !verify_membership_proof( - &commitment_pre, - membership_proof, - &commitment_set_digest, - ) { - panic!("Membership proof invalid"); - } + let set_digest = + compute_root_associated_to_path(&commitment_pre, membership_proof); // Check pre_state authorization if !pre_states[i].is_authorized { @@ -97,7 +92,7 @@ fn main() { // Compute nullifier let nullifier = Nullifier::new(&commitment_pre, nsk); - new_nullifiers.push(nullifier); + new_nullifiers.push((nullifier, set_digest)); } else { if pre_states[i].account != Account::default() { panic!("Found new private account with non default values."); @@ -156,7 +151,6 @@ fn main() { encrypted_private_post_states, new_commitments, new_nullifiers, - commitment_set_digest, }; env::commit(&output); diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index 1b60f34..4b0b06f 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -173,7 +173,7 @@ fn verify_authentication_path(value: &Value, index: usize, path: &[Node], root: fn prev_power_of_two(x: usize) -> usize { if x == 0 { - return 0; // define as 0 + return 0; } 1 << (usize::BITS as usize - x.leading_zeros() as usize - 1) } @@ -314,10 +314,10 @@ mod tests { let expected_root = hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"); - tree.insert(values[0]); - tree.insert(values[1]); - tree.insert(values[2]); - tree.insert(values[3]); + assert_eq!(0, tree.insert(values[0])); + assert_eq!(1, tree.insert(values[1])); + assert_eq!(2, tree.insert(values[2])); + assert_eq!(3, tree.insert(values[3])); assert_eq!(tree.root(), expected_root); } @@ -331,9 +331,9 @@ mod tests { let expected_root = hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); - tree.insert(values[0]); - tree.insert(values[1]); - tree.insert(values[2]); + assert_eq!(0, tree.insert(values[0])); + assert_eq!(1, tree.insert(values[1])); + assert_eq!(2, tree.insert(values[2])); assert_eq!(tree.root(), expected_root); } @@ -347,9 +347,9 @@ mod tests { let expected_root = hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); - tree.insert(values[0]); - tree.insert(values[1]); - tree.insert(values[2]); + assert_eq!(0, tree.insert(values[0])); + assert_eq!(1, tree.insert(values[1])); + assert_eq!(2, tree.insert(values[2])); assert_eq!(tree.root(), expected_root); } @@ -361,9 +361,9 @@ mod tests { let values = [[1; 32], [2; 32], [3; 32]]; let expected_tree = MerkleTree::new(&values); - tree.insert(values[0]); - tree.insert(values[1]); - tree.insert(values[2]); + assert_eq!(0, tree.insert(values[0])); + assert_eq!(1, tree.insert(values[1])); + assert_eq!(2, tree.insert(values[2])); assert_eq!(expected_tree, tree); } @@ -375,10 +375,10 @@ mod tests { let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let expected_tree = MerkleTree::new(&values); - tree.insert(values[0]); - tree.insert(values[1]); - tree.insert(values[2]); - tree.insert(values[3]); + assert_eq!(0, tree.insert(values[0])); + assert_eq!(1, tree.insert(values[1])); + assert_eq!(2, tree.insert(values[2])); + assert_eq!(3, tree.insert(values[3])); assert_eq!(expected_tree, tree); } diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 41e462a..031790a 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -33,7 +33,6 @@ pub fn execute_and_prove( )], private_account_auth: &[(NullifierSecretKey, MembershipProof)], program: &Program, - commitment_set_digest: &CommitmentSetDigest, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; @@ -49,7 +48,6 @@ pub fn execute_and_prove( private_account_keys: private_account_keys.to_vec(), private_account_auth: private_account_auth.to_vec(), program_id: program.id(), - commitment_set_digest: *commitment_set_digest, }; // Prove circuit. @@ -157,7 +155,6 @@ mod tests { &[(recipient_keys.npk(), recipient_keys.ivk(), [3; 32])], &[], &Program::authenticated_transfer_program(), - &[99; 32], ) .unwrap(); @@ -169,7 +166,6 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(output.new_commitments.len(), 1); assert_eq!(output.new_nullifiers.len(), 0); - assert_eq!(output.commitment_set_digest, [99; 32]); assert_eq!(output.encrypted_private_post_states.len(), 1); let recipient_post = output.encrypted_private_post_states[0] @@ -199,7 +195,13 @@ mod tests { }; let balance_to_move: u128 = 37; - let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &sender_keys.nsk)]; + let mut commitment_set = CommitmentSet::with_capacity(2); + commitment_set.extend(&[commitment_sender.clone()]); + + let expected_new_nullifiers = vec![( + Nullifier::new(&commitment_sender, &sender_keys.nsk), + commitment_set.digest(), + )]; let program = Program::authenticated_transfer_program(); @@ -220,9 +222,6 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let mut commitment_set = CommitmentSet::with_capacity(2); - commitment_set.extend(&[commitment_sender.clone()]); - let (output, proof) = execute_and_prove( &[sender_pre.clone(), recipient], &Program::serialize_instruction(balance_to_move).unwrap(), @@ -237,7 +236,6 @@ mod tests { commitment_set.get_proof_for(&commitment_sender).unwrap(), )], &program, - &commitment_set.digest(), ) .unwrap(); @@ -246,7 +244,6 @@ mod tests { assert!(output.public_post_states.is_empty()); assert_eq!(output.new_commitments, expected_new_commitments); assert_eq!(output.new_nullifiers, expected_new_nullifiers); - assert_eq!(output.commitment_set_digest, commitment_set.digest()); assert_eq!(output.encrypted_private_post_states.len(), 2); let recipient_post_1 = output.encrypted_private_post_states[0] diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index 3ff2953..0ab2db8 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -12,8 +12,7 @@ use crate::{Address, error::NssaError}; use super::message::Message; const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; -const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = - b"\x01/NSSA/v0.1/TxMessage/"; +const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; impl Message { pub(crate) fn to_bytes(&self) -> Vec { @@ -56,9 +55,11 @@ impl Message { // New nullifiers let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32; bytes.extend_from_slice(&new_nullifiers_len.to_le_bytes()); - for nullifier in &self.new_nullifiers { + for (nullifier, commitment_set_digest) in &self.new_nullifiers { bytes.extend_from_slice(&nullifier.to_byte_array()); + bytes.extend_from_slice(commitment_set_digest); } + bytes } @@ -125,7 +126,10 @@ impl Message { let new_nullifiers_len = u32::from_le_bytes(len_bytes) as usize; let mut new_nullifiers = Vec::with_capacity(new_nullifiers_len); for _ in 0..new_nullifiers_len { - new_nullifiers.push(Nullifier::from_cursor(cursor)?); + let nullifier = Nullifier::from_cursor(cursor)?; + let mut commitment_set_digest = [0; 32]; + cursor.read_exact(&mut commitment_set_digest); + new_nullifiers.push((nullifier, commitment_set_digest)); } Ok(Self { diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 4dddc2d..b4de4a7 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -1,5 +1,5 @@ use nssa_core::{ - EncryptedAccountData, + CommitmentSetDigest, EncryptedAccountData, account::{Account, Commitment, Nonce, Nullifier}, }; @@ -12,7 +12,7 @@ pub struct Message { pub(crate) public_post_states: Vec, pub(crate) encrypted_private_post_states: Vec, pub(crate) new_commitments: Vec, - pub(crate) new_nullifiers: Vec, + pub(crate) new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, } impl Message { @@ -22,7 +22,7 @@ impl Message { public_post_states: Vec, encrypted_private_post_states: Vec, new_commitments: Vec, - new_nullifiers: Vec, + new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, ) -> Self { Self { public_addresses, @@ -66,7 +66,7 @@ pub mod tests { let new_commitments = vec![Commitment::new(&Npk2, &account2)]; let old_commitment = Commitment::new(&Npk1, &account1); - let new_nullifiers = vec![Nullifier::new(&old_commitment, &nsk1)]; + let new_nullifiers = vec![(Nullifier::new(&old_commitment, &nsk1), [0; 32])]; Message { public_addresses: public_addresses.clone(), diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 44dfaec..49f3d8d 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use nssa_core::account::{Account, AccountWithMetadata}; +use nssa_core::account::{Account, AccountWithMetadata, Commitment, Nullifier}; use nssa_core::{CommitmentSetDigest, EncryptedAccountData, PrivacyPreservingCircuitOutput}; use crate::error::NssaError; @@ -93,8 +93,6 @@ impl PrivacyPreservingTransaction { }) .collect(); - let set_commitment = state.commitment_set_digest(); - // 4. Proof verification check_privacy_preserving_circuit_proof_is_valid( &witness_set.proof, @@ -103,14 +101,13 @@ impl PrivacyPreservingTransaction { &message.encrypted_private_post_states, &message.new_commitments, &message.new_nullifiers, - set_commitment, )?; // 5. Commitment freshness state.check_commitments_are_new(&message.new_commitments)?; // 6. Nullifier uniqueness - state.check_nullifiers_are_new(&message.new_nullifiers)?; + state.check_nullifiers_are_valid(&message.new_nullifiers)?; Ok(message .public_addresses @@ -142,9 +139,8 @@ fn check_privacy_preserving_circuit_proof_is_valid( public_pre_states: &[AccountWithMetadata], public_post_states: &[Account], encrypted_private_post_states: &[EncryptedAccountData], - new_commitments: &[nssa_core::account::Commitment], - new_nullifiers: &[nssa_core::account::Nullifier], - commitment_set_digest: CommitmentSetDigest, + new_commitments: &[Commitment], + new_nullifiers: &[(Nullifier, CommitmentSetDigest)], ) -> Result<(), NssaError> { let output = PrivacyPreservingCircuitOutput { public_pre_states: public_pre_states.to_vec(), @@ -152,7 +148,6 @@ fn check_privacy_preserving_circuit_proof_is_valid( encrypted_private_post_states: encrypted_private_post_states.to_vec(), new_commitments: new_commitments.to_vec(), new_nullifiers: new_nullifiers.to_vec(), - commitment_set_digest, }; proof .is_valid_for(&output) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 69af23d..3ac1e15 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -13,6 +13,7 @@ use std::collections::{HashMap, HashSet}; pub(crate) struct CommitmentSet { merkle_tree: MerkleTree, commitments: HashMap, + pub root_history: HashSet, } impl CommitmentSet { @@ -22,9 +23,11 @@ impl CommitmentSet { pub(crate) fn get_proof_for(&self, commitment: &Commitment) -> Option { let index = *self.commitments.get(commitment)?; - self.merkle_tree + let proof = self + .merkle_tree .get_authentication_path_for(index) - .map(|path| (index, path)) + .map(|path| (index, path)); + proof } pub(crate) fn extend(&mut self, commitments: &[Commitment]) { @@ -32,6 +35,7 @@ impl CommitmentSet { let index = self.merkle_tree.insert(commitment.to_byte_array()); self.commitments.insert(commitment, index); } + self.root_history.insert(self.digest()); } fn contains(&self, commitment: &Commitment) -> bool { @@ -42,6 +46,7 @@ impl CommitmentSet { Self { merkle_tree: MerkleTree::with_capacity(capacity), commitments: HashMap::new(), + root_history: HashSet::new(), } } } @@ -50,7 +55,7 @@ type NullifierSet = HashSet; pub struct V01State { public_state: HashMap, - private_state: (CommitmentSet, NullifierSet), + pub private_state: (CommitmentSet, NullifierSet), builtin_programs: HashMap, } @@ -122,7 +127,13 @@ impl V01State { self.private_state.0.extend(&message.new_commitments); // 3. Add new nullifiers - self.private_state.1.extend(message.new_nullifiers.clone()); + let new_nullifiers = message + .new_nullifiers + .iter() + .cloned() + .map(|(nullifier, _)| nullifier) + .collect::>(); + self.private_state.1.extend(new_nullifiers); // 4. Update public accounts for (address, post) in public_state_diff.into_iter() { @@ -172,19 +183,34 @@ impl V01State { Ok(()) } - pub(crate) fn check_nullifiers_are_new( + pub(crate) fn check_nullifiers_are_valid( &self, - new_nullifiers: &[Nullifier], + new_nullifiers: &[(Nullifier, CommitmentSetDigest)], ) -> Result<(), NssaError> { - for nullifier in new_nullifiers.iter() { + for (nullifier, digest) in new_nullifiers.iter() { if self.private_state.1.contains(nullifier) { return Err(NssaError::InvalidInput( "Nullifier already seen".to_string(), )); } + if !self.private_state.0.root_history.contains(digest) { + return Err(NssaError::InvalidInput( + "Unrecognized commitment set digest".to_string(), + )); + } } Ok(()) } + + pub(crate) fn check_commitment_set_digest_is_valid( + &self, + commitment_set_digest: &CommitmentSetDigest, + ) -> bool { + self.private_state + .0 + .root_history + .contains(commitment_set_digest) + } } #[cfg(test)] @@ -774,7 +800,6 @@ pub mod tests { &[(recipient_keys.npk(), recipient_keys.ivk(), esk)], &[], &Program::authenticated_transfer_program(), - &state.commitment_set_digest(), ) .unwrap(); @@ -828,7 +853,6 @@ pub mod tests { .unwrap(), )], &program, - &state.private_state.0.digest(), ) .unwrap(); @@ -880,7 +904,6 @@ pub mod tests { .unwrap(), )], &program, - &state.private_state.0.digest(), ) .unwrap();