From 5190b486c4046910a66dd4481bf6ba1e8bbd8fac Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 12:38:31 -0300 Subject: [PATCH 1/9] fix circuit and add test --- .../src/bin/privacy_preserving_circuit.rs | 4 ++- nssa/src/error.rs | 3 ++ .../privacy_preserving_transaction/circuit.rs | 4 ++- nssa/src/state.rs | 32 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 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 83f593a..947f6ea 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -32,7 +32,9 @@ fn main() { // 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); + if !validate_execution(&pre_states, &post_states, program_id) { + panic!(); + } let n_accounts = pre_states.len(); if visibility_mask.len() != n_accounts { diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 1d6d6ad..0e85789 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -45,4 +45,7 @@ pub enum NssaError { #[error("Invalid privacy preserving execution circuit proof")] InvalidPrivacyPreservingProof, + + #[error("Circuit proving error")] + CircuitProvingError(String), } diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index ed32f98..24cdc4a 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -55,7 +55,9 @@ pub fn execute_and_prove( 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 prove_info = prover + .prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF) + .map_err(|e| NssaError::CircuitProvingError(e.to_string()))?; let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 93ee06c..d0733ec 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -214,6 +214,7 @@ pub mod tests { use crate::{ Address, PublicKey, PublicTransaction, V01State, error::NssaError, + execute_and_prove, privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet, }, @@ -1063,4 +1064,35 @@ pub mod tests { recipient_initial_balance + balance_to_move ); } + + #[test] + fn test_burner_program_should_fail_in_privacy_preserving_circuit() { + let keys = test_private_account_keys_1(); + let private_account = AccountWithMetadata { + account: Account { + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let state = V01State::new_with_genesis_accounts(&[]) + .with_private_account(&keys, &private_account.account); + + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&keys.npk(), &private_account.account)) + .unwrap(); + + let program = Program::burner(); + let result = execute_and_prove( + &[private_account], + &Program::serialize_instruction(10u128).unwrap(), + &[1], + &[0xdeadbeef], + &[(keys.npk(), SharedSecretKey::new(&[0xca; 32], &keys.ivk()))], + &[(keys.nsk, membership_proof)], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From f3d63806c3e69d0259bedc7a02716732e6b948e9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 12:56:01 -0300 Subject: [PATCH 2/9] fix tests --- .../guest/src/bin/privacy_preserving_circuit.rs | 4 ++-- nssa/src/privacy_preserving_transaction/circuit.rs | 5 ++++- 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 947f6ea..5ec45eb 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -33,12 +33,12 @@ fn main() { // Check that the program is well behaved. // See the # Programs section for the definition of the `validate_execution` method. if !validate_execution(&pre_states, &post_states, program_id) { - panic!(); + panic!("Bad behaved program"); } let n_accounts = pre_states.len(); if visibility_mask.len() != n_accounts { - panic!(); + panic!("Invalid visibility mask length"); } // These lists will be the public outputs of this circuit diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 24cdc4a..05d71da 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -111,6 +111,7 @@ mod tests { let program = Program::authenticated_transfer_program(); let sender = AccountWithMetadata { account: Account { + program_owner: program.id(), balance: 100, ..Account::default() }, @@ -177,11 +178,13 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_fully_private() { + let program = Program::authenticated_transfer_program(); let sender_pre = AccountWithMetadata { account: Account { balance: 100, nonce: 0xdeadbeef, - ..Account::default() + program_owner: program.id(), + data: vec![], }, is_authorized: true, }; From a1e4c06bc7d5a73ffcff42994202e7fc0ae78d6f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 14:09:50 -0300 Subject: [PATCH 3/9] add tests --- nssa/src/state.rs | 213 ++++++++++++++++-- .../guest/src/bin/missing_output.rs | 4 +- 2 files changed, 201 insertions(+), 16 deletions(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index d0733ec..cb3326f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1067,29 +1067,214 @@ pub mod tests { #[test] fn test_burner_program_should_fail_in_privacy_preserving_circuit() { - let keys = test_private_account_keys_1(); - let private_account = AccountWithMetadata { + let program = Program::burner(); + let public_account = AccountWithMetadata { account: Account { + program_owner: program.id(), balance: 100, ..Account::default() }, is_authorized: true, }; - let state = V01State::new_with_genesis_accounts(&[]) - .with_private_account(&keys, &private_account.account); - let membership_proof = state - .get_proof_for_commitment(&Commitment::new(&keys.npk(), &private_account.account)) - .unwrap(); - - let program = Program::burner(); let result = execute_and_prove( - &[private_account], + &[public_account], &Program::serialize_instruction(10u128).unwrap(), - &[1], - &[0xdeadbeef], - &[(keys.npk(), SharedSecretKey::new(&[0xca; 32], &keys.ivk()))], - &[(keys.nsk, membership_proof)], + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_minter_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::minter(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(10u128).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::nonce_changer_program(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() { + let program = Program::data_changer(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::extra_output_program(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::missing_output_program(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(()).unwrap(), + &[0, 0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() { + let program = Program::program_owner_changer(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[0, 0], + &[], + &[], + &[], &program, ); 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 2174266..7b6016c 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -5,12 +5,12 @@ type Instruction = (); fn main() { let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre1, _] = match pre_states.try_into() { + let [pre1, pre2] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; let account_pre1 = pre1.account.clone(); - write_nssa_outputs(vec![pre1], vec![account_pre1]); + write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]); } From f08ed175094151fc16c0d33b314fe7d013e0e773 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 14:50:16 -0300 Subject: [PATCH 4/9] test incorrect number of masks in visibility mask list --- nssa/src/state.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index cb3326f..3655b1c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1280,4 +1280,39 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + // Setting only one visibility mask for a circuit execution with two pre_state accounts. + let visibility_mask = [0]; + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From 9b4506de3b6d5b46c9e537f8626f628edf59e6f0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 14:41:01 -0300 Subject: [PATCH 5/9] fix sequencer runner --- sequencer_runner/configs/debug/sequencer_config.json | 11 +++++++++-- sequencer_runner/src/lib.rs | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 010a9ba..f7c6369 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -7,6 +7,13 @@ "block_create_timeout_millis": 10000, "port": 3040, "initial_accounts": [ - + { + "addr": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "balance": 10000 + }, + { + "addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", + "balance": 20000 + } ] -} \ No newline at end of file +} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 625350b..d7a574e 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -75,7 +75,9 @@ pub async fn main_runner() -> Result<()> { } //ToDo: Add restart on failures - let (_, _) = startup_sequencer(app_config).await?; + let (_, main_loop_handle) = startup_sequencer(app_config).await?; + + main_loop_handle.await??; Ok(()) } From 83542b310ceaea9f16a36e641a5eb05ce887f974 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 15:20:40 -0300 Subject: [PATCH 6/9] fix double nonce increment in privacy preserving tx --- .../src/bin/privacy_preserving_circuit.rs | 4 +++- nssa/src/state.rs | 24 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 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 5ec45eb..2484ec2 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -61,7 +61,9 @@ fn main() { public_pre_states.push(pre_states[i].clone()); let mut post = post_states[i].clone(); - post.nonce += 1; + if pre_states[i].is_authorized { + post.nonce += 1; + } if post.program_owner == DEFAULT_PROGRAM_ID { // Claim account post.program_owner = program_id; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3655b1c..3ba10b3 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -140,12 +140,6 @@ impl V01State { *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(()) } @@ -925,6 +919,13 @@ pub mod tests { &state, ); + let expected_sender_post = { + let mut this = state.get_account_by_address(&sender_keys.address()); + this.balance -= balance_to_move; + this.nonce += 1; + this + }; + let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); assert!(!state.private_state.0.contains(&expected_new_commitment)); @@ -932,6 +933,8 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); + let sender_post = state.get_account_by_address(&sender_keys.address()); + assert_eq!(sender_post, expected_sender_post); assert!(state.private_state.0.contains(&expected_new_commitment)); assert_eq!( @@ -1024,6 +1027,12 @@ pub mod tests { let balance_to_move = 37; + let expected_recipient_post = { + let mut this = state.get_account_by_address(&recipient_keys.address()); + this.balance += balance_to_move; + this + }; + let tx = deshielded_balance_transfer_for_tests( &sender_keys, &sender_private_account, @@ -1054,6 +1063,9 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); + + let recipient_post = state.get_account_by_address(&recipient_keys.address()); + assert_eq!(recipient_post, expected_recipient_post); 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)); From 63517b6b1c7649171978ac6e71b935a62cb13e71 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 16:25:02 -0300 Subject: [PATCH 7/9] add circuit tests --- nssa/src/state.rs | 418 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 417 insertions(+), 1 deletion(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3ba10b3..511ad88 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1063,7 +1063,6 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); - let recipient_post = state.get_account_by_address(&recipient_keys.address()); assert_eq!(recipient_post, expected_recipient_post); assert!(state.private_state.0.contains(&sender_pre_commitment)); @@ -1327,4 +1326,421 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_circuit_fails_if_insufficient_nonces_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting only one nonce for an execution with two private accounts. + let private_account_nonces = [0xdeadbeef1]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &private_account_nonces, + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting only one key for an execution with two private accounts. + let private_account_keys = [( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + )]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting no auth key for an execution with one non default private accounts. + let private_account_auth = []; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_invalid_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let private_account_keys = [ + // First private account is the sender + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + // Second private account is the recipient + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ]; + let private_account_auth = [ + // Setting the recipient key to authorize the sender. + // This should be set to the sender private account in + // a normal circumstance. The recipient can't authorize this. + (recipient_keys.nsk, (0, vec![])), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default balance + balance: 1, + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default program_owner + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default data + data: b"hola mundo".to_vec(), + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default nonce + nonce: 0xdeadbeef, + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + // This should be set to false in normal circumstances + is_authorized: true, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_invalid_visibility_mask_value() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let visibility_mask = [0, 3]; + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From efb7108c58b2cd93bb87298222a27c2839444820 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 16:44:55 -0300 Subject: [PATCH 8/9] add more tests --- .../src/bin/privacy_preserving_circuit.rs | 2 +- nssa/src/state.rs | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) 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 2484ec2..61f431c 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -140,7 +140,7 @@ fn main() { } if private_keys_iter.next().is_some() { - panic!("Too many private accounts keys."); + panic!("Too many private account keys."); } if private_auth_iter.next().is_some() { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 511ad88..aad03ee 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1743,4 +1743,141 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_circuit_should_fail_with_too_many_nonces() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting three new private account nonces for a circuit execution with only two private + // accounts. + let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &private_account_nonces, + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_private_account_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting three private account keys for a circuit execution with only two private + // accounts. + let private_account_keys = [ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ( + sender_keys.npk(), + SharedSecretKey::new(&[57; 32], &sender_keys.ivk()), + ), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_private_account_auth_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting two private account keys for a circuit execution with only one non default + // private account (visibility mask equal to 1 means that auth keys are expected). + let visibility_mask = [1, 2]; + let private_account_auth = [ + (sender_keys.nsk, (0, vec![])), + (recipient_keys.nsk, (1, vec![])), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From f75fab89b0c00637ea965f269a0769c32547fced Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 17 Sep 2025 09:38:46 +0300 Subject: [PATCH 9/9] fix: lint fix --- nssa/src/privacy_preserving_transaction/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index fef44bc..1421f62 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -220,7 +220,7 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = [3; 32].into(); + let esk_1 = [3; 32]; let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let esk_2 = [5; 32];