diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 36caad85..14c31fe5 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index 9f5474a9..62d2666b 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index bdbcef61..e18985fb 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/program_methods/clock.bin index d3ca0dab..0196a5a2 100644 Binary files a/artifacts/program_methods/clock.bin and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 5e6a011b..9c095998 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 57a201c4..3449f24e 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index dd613143..1b9010d2 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index 6366eba6..8fe80774 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/auth_asserting_noop.bin b/artifacts/test_program_methods/auth_asserting_noop.bin index f9e4d1d4..8f407d8b 100644 Binary files a/artifacts/test_program_methods/auth_asserting_noop.bin and b/artifacts/test_program_methods/auth_asserting_noop.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index 94a90236..ae5a0de7 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index 58331d6c..b3bc1511 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin index 2760b7a3..982e3c00 100644 Binary files a/artifacts/test_program_methods/changer_claimer.bin and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index ff504da1..3ababa7e 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/clock_chain_caller.bin b/artifacts/test_program_methods/clock_chain_caller.bin index 37c9a004..59358133 100644 Binary files a/artifacts/test_program_methods/clock_chain_caller.bin and b/artifacts/test_program_methods/clock_chain_caller.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 3d69b8cb..c8767e68 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index 873ce66a..5030fc51 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/flash_swap_callback.bin b/artifacts/test_program_methods/flash_swap_callback.bin index 0846f255..83280d9f 100644 Binary files a/artifacts/test_program_methods/flash_swap_callback.bin and b/artifacts/test_program_methods/flash_swap_callback.bin differ diff --git a/artifacts/test_program_methods/flash_swap_initiator.bin b/artifacts/test_program_methods/flash_swap_initiator.bin index 1e285245..239ef3b4 100644 Binary files a/artifacts/test_program_methods/flash_swap_initiator.bin and b/artifacts/test_program_methods/flash_swap_initiator.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin index cc757683..502660bf 100644 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/malicious_caller_program_id.bin b/artifacts/test_program_methods/malicious_caller_program_id.bin index f152051d..78434fcc 100644 Binary files a/artifacts/test_program_methods/malicious_caller_program_id.bin and b/artifacts/test_program_methods/malicious_caller_program_id.bin differ diff --git a/artifacts/test_program_methods/malicious_self_program_id.bin b/artifacts/test_program_methods/malicious_self_program_id.bin index 6d83b95b..268b53da 100644 Binary files a/artifacts/test_program_methods/malicious_self_program_id.bin and b/artifacts/test_program_methods/malicious_self_program_id.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index 29bcd715..328650c5 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index c7cc1571..f6deddbd 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index 8f2b1e39..f59d957e 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index 993c1451..f6093b02 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index 579db977..b236e8df 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/pda_claimer.bin b/artifacts/test_program_methods/pda_claimer.bin index 1a541384..9bb41bc9 100644 Binary files a/artifacts/test_program_methods/pda_claimer.bin and b/artifacts/test_program_methods/pda_claimer.bin differ diff --git a/artifacts/test_program_methods/pinata_cooldown.bin b/artifacts/test_program_methods/pinata_cooldown.bin index 2b0d979a..44d0d1bb 100644 Binary files a/artifacts/test_program_methods/pinata_cooldown.bin and b/artifacts/test_program_methods/pinata_cooldown.bin differ diff --git a/artifacts/test_program_methods/private_pda_delegator.bin b/artifacts/test_program_methods/private_pda_delegator.bin index 4b55e871..653eb30c 100644 Binary files a/artifacts/test_program_methods/private_pda_delegator.bin and b/artifacts/test_program_methods/private_pda_delegator.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index 3bdabade..910544e6 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 0aaf1a23..2b7bdea1 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/artifacts/test_program_methods/time_locked_transfer.bin b/artifacts/test_program_methods/time_locked_transfer.bin index 5700322e..daba80c1 100644 Binary files a/artifacts/test_program_methods/time_locked_transfer.bin and b/artifacts/test_program_methods/time_locked_transfer.bin differ diff --git a/artifacts/test_program_methods/two_pda_claimer.bin b/artifacts/test_program_methods/two_pda_claimer.bin index 600b819d..f5a7f3e6 100644 Binary files a/artifacts/test_program_methods/two_pda_claimer.bin and b/artifacts/test_program_methods/two_pda_claimer.bin differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin index 02ccc149..ffdabf5c 100644 Binary files a/artifacts/test_program_methods/validity_window.bin and b/artifacts/test_program_methods/validity_window.bin differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin index d239c750..a9ef479a 100644 Binary files a/artifacts/test_program_methods/validity_window_chain_caller.bin and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 41de30ed..df74daba 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -27,7 +27,7 @@ use nssa::{ public_transaction as putx, }; use nssa_core::{ - MembershipProof, NullifierPublicKey, + InputAccountIdentity, MembershipProof, NullifierPublicKey, account::{AccountWithMetadata, Nonce, data::Data}, encryption::ViewingPublicKey, }; @@ -251,10 +251,19 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(balance_to_move).unwrap(), - vec![1, 2], - vec![(sender_npk, 0, sender_ss), (recipient_npk, 0, recipient_ss)], - vec![sender_nsk], - vec![Some(proof)], + vec![ + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: sender_ss, + nsk: sender_nsk, + membership_proof: proof, + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_npk, + ssk: recipient_ss, + identifier: 0, + }, + ], &program.into(), ) .unwrap(); diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index c71003de..f52357ee 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -12,23 +12,92 @@ use crate::{ pub struct PrivacyPreservingCircuitInput { /// Outputs of the program execution. pub program_outputs: Vec, - /// Visibility mask for accounts. - /// - /// - `0` - public account - /// - `1` - private account with authentication - /// - `2` - private account without authentication - /// - `3` - private PDA account - pub visibility_mask: Vec, - /// Public keys and identifiers of private accounts. - pub private_account_keys: Vec<(NullifierPublicKey, Identifier, SharedSecretKey)>, - /// Nullifier secret keys for authorized private accounts. - pub private_account_nsks: Vec, - /// Membership proofs for private accounts. Can be [`None`] for uninitialized accounts. - pub private_account_membership_proofs: Vec>, + /// One entry per `pre_state`, in the same order as the program's `pre_states`. + /// Length must equal the number of `pre_states` derived from `program_outputs`. + /// The guest's `private_pda_npk_by_position` and `private_pda_bound_positions` + /// rely on this position alignment. + pub account_identities: Vec, /// Program ID. pub program_id: ProgramId, } +/// Per-account input to the privacy-preserving circuit. Each variant carries exactly the fields +/// the guest needs for that account's code path. +#[derive(Serialize, Deserialize, Clone)] +pub enum InputAccountIdentity { + /// Public account. The guest reads pre/post state from `program_outputs` and emits no + /// commitment, ciphertext, or nullifier. + Public, + /// Init of an authorized standalone private account: no membership proof. The `pre_state` + /// must be `Account::default()`. The `account_id` is derived as + /// `AccountId::from((&NullifierPublicKey::from(nsk), identifier))` and matched against + /// `pre_state.account_id`. + PrivateAuthorizedInit { + ssk: SharedSecretKey, + nsk: NullifierSecretKey, + identifier: Identifier, + }, + /// Update of an authorized standalone private account: existing on-chain commitment, with + /// membership proof. + PrivateAuthorizedUpdate { + ssk: SharedSecretKey, + nsk: NullifierSecretKey, + membership_proof: MembershipProof, + identifier: Identifier, + }, + /// Init of a standalone private account the caller does not own (e.g. a recipient who + /// doesn't yet exist on chain). No `nsk`, no membership proof. + PrivateUnauthorized { + npk: NullifierPublicKey, + ssk: SharedSecretKey, + identifier: Identifier, + }, + /// Init of a private PDA, unauthorized. The npk-to-account_id binding is proven upstream + /// via `Claim::Pda(seed)` or a caller's `pda_seeds` match. Identifier is fixed by + /// convention to `PRIVATE_PDA_FIXED_IDENTIFIER` and not carried per-input. + PrivatePdaInit { + npk: NullifierPublicKey, + ssk: SharedSecretKey, + }, + /// Update of an existing private PDA, authorized, with membership proof. `npk` is derived + /// from `nsk`. Authorization is established upstream by a caller `pda_seeds` match or a + /// previously-seen authorization in a chained call. Identifier is fixed. + PrivatePdaUpdate { + ssk: SharedSecretKey, + nsk: NullifierSecretKey, + membership_proof: MembershipProof, + }, +} + +impl InputAccountIdentity { + #[must_use] + pub const fn is_public(&self) -> bool { + matches!(self, Self::Public) + } + + #[must_use] + pub const fn is_private_pda(&self) -> bool { + matches!( + self, + Self::PrivatePdaInit { .. } | Self::PrivatePdaUpdate { .. } + ) + } + + /// For private PDA variants, return the nullifier public key. `Init` carries it directly; + /// `Update` derives it from `nsk`. For non-PDA variants returns `None`. + #[must_use] + pub fn npk_if_private_pda(&self) -> Option { + match self { + Self::PrivatePdaInit { npk, .. } => Some(*npk), + Self::PrivatePdaUpdate { nsk, .. } => Some(NullifierPublicKey::from(nsk)), + Self::Public + | Self::PrivateAuthorizedInit { .. } + | Self::PrivateAuthorizedUpdate { .. } + | Self::PrivateUnauthorized { .. } => None, + } + } +} + #[derive(Serialize, Deserialize)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct PrivacyPreservingCircuitOutput { diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 478d475c..d660aed0 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -3,7 +3,9 @@ reason = "We prefer to group methods by functionality rather than by type for encoding" )] -pub use circuit_io::{PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput}; +pub use circuit_io::{ + InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, +}; pub use commitment::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof, compute_digest_for_path, diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index f5bd8cea..8cfbab84 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -2,8 +2,7 @@ use std::collections::{HashMap, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ - Identifier, MembershipProof, NullifierPublicKey, NullifierSecretKey, - PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, + InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, account::AccountWithMetadata, program::{ChainedCall, InstructionData, ProgramId, ProgramOutput}, }; @@ -63,14 +62,10 @@ impl From for ProgramWithDependencies { /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit. -/// TODO: too many parameters. pub fn execute_and_prove( pre_states: Vec, instruction_data: InstructionData, - visibility_mask: Vec, - private_account_keys: Vec<(NullifierPublicKey, Identifier, SharedSecretKey)>, - private_account_nsks: Vec, - private_account_membership_proofs: Vec>, + account_identities: Vec, program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { let ProgramWithDependencies { @@ -128,10 +123,7 @@ pub fn execute_and_prove( let circuit_input = PrivacyPreservingCircuitInput { program_outputs, - visibility_mask, - private_account_keys, - private_account_nsks, - private_account_membership_proofs, + account_identities, program_id: program_with_dependencies.program.id(), }; @@ -240,10 +232,14 @@ mod tests { let (output, proof) = execute_and_prove( vec![sender, recipient], Program::serialize_instruction(balance_to_move).unwrap(), - vec![0, 2], - vec![(recipient_keys.npk(), 0, shared_secret)], - vec![], - vec![None], + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: shared_secret, + identifier: 0, + }, + ], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -333,13 +329,21 @@ mod tests { let (output, proof) = execute_and_prove( vec![sender_pre, recipient], Program::serialize_instruction(balance_to_move).unwrap(), - vec![1, 2], vec![ - (sender_keys.npk(), 0, shared_secret_1), - (recipient_keys.npk(), 0, shared_secret_2), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: shared_secret_1, + nsk: sender_keys.nsk, + membership_proof: commitment_set + .get_proof_for(&commitment_sender) + .expect("sender's commitment must be in the set"), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: shared_secret_2, + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![commitment_set.get_proof_for(&commitment_sender), None], &program.into(), ) .unwrap(); @@ -402,10 +406,11 @@ mod tests { let result = execute_and_prove( vec![pre], instruction, - vec![2], - vec![(account_keys.npk(), 0, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivateUnauthorized { + npk: account_keys.npk(), + ssk: shared_secret, + identifier: 0, + }], &program_with_deps, ); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ff16175c..9e4d8524 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -362,8 +362,8 @@ pub mod tests { use std::collections::HashMap; use nssa_core::{ - BlockId, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, - Timestamp, + BlockId, Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, + NullifierSecretKey, SharedSecretKey, Timestamp, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey}, program::{ @@ -1294,10 +1294,14 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender, recipient], Program::serialize_instruction(balance_to_move).unwrap(), - vec![0, 2], - vec![(recipient_keys.npk(), 0, shared_secret)], - vec![], - vec![None], + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: shared_secret, + identifier: 0, + }, + ], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -1343,13 +1347,21 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(balance_to_move).unwrap(), - vec![1, 2], vec![ - (sender_keys.npk(), 0, shared_secret_1), - (recipient_keys.npk(), 0, shared_secret_2), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: shared_secret_1, + nsk: sender_keys.nsk, + membership_proof: state + .get_proof_for_commitment(&sender_commitment) + .expect("sender's commitment must be in state"), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: shared_secret_2, + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![state.get_proof_for_commitment(&sender_commitment), None], &program.into(), ) .unwrap(); @@ -1398,10 +1410,17 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(balance_to_move).unwrap(), - vec![1, 0], - vec![(sender_keys.npk(), 0, shared_secret)], - vec![sender_keys.nsk], - vec![state.get_proof_for_commitment(&sender_commitment)], + vec![ + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: shared_secret, + nsk: sender_keys.nsk, + membership_proof: state + .get_proof_for_commitment(&sender_commitment) + .expect("sender's commitment must be in state"), + identifier: 0, + }, + InputAccountIdentity::Public, + ], &program.into(), ) .unwrap(); @@ -1615,10 +1634,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(10_u128).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1641,10 +1657,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(10_u128).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1667,10 +1680,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(()).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1693,10 +1703,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(vec![0]).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1727,10 +1734,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(large_data).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1753,10 +1757,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(()).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1788,10 +1789,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account_1, public_account_2], Program::serialize_instruction(()).unwrap(), - vec![0, 0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public, InputAccountIdentity::Public], &program.into(), ); @@ -1814,10 +1812,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(()).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -1849,10 +1844,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account_1, public_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![0, 0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public, InputAccountIdentity::Public], &program.into(), ); @@ -1881,177 +1873,11 @@ pub mod tests { AccountId::new([1; 32]), ); - // Setting only one visibility mask for a circuit execution with two pre_state accounts. - let visibility_mask = [0]; + // Single account_identity entry for a circuit execution with two pre_state accounts. let result = execute_and_prove( vec![public_account_1, public_account_2], Program::serialize_instruction(10_u128).unwrap(), - visibility_mask.to_vec(), - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn 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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn 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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); - - // Setting only one key for an execution with two private accounts. - let private_account_keys = [( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - )]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - private_account_keys.to_vec(), - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn circuit_fails_if_insufficient_commitment_proofs_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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - - // Setting no second commitment proof. - let private_account_membership_proofs = [Some((0, vec![]))]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - private_account_membership_proofs.to_vec(), - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn 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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - - // Setting no auth key for an execution with one non default private accounts. - let private_account_nsks = []; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - private_account_nsks.to_vec(), - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -2075,33 +1901,26 @@ pub mod tests { let private_account_2 = AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - let private_account_keys = [ - // First private account is the sender - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - // Second private account is the recipient - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ]; - - // 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. - let private_account_nsks = [recipient_keys.nsk]; - let private_account_membership_proofs = [Some((0, vec![]))]; + // Setting the recipient nsk to authorize the sender. + // This should be set to the sender private account in a normal circumstance. + // `PrivateAuthorizedUpdate` derives npk from nsk and asserts equality with + // `pre_state.account_id`, so a mismatched nsk fails that check. let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - private_account_keys.to_vec(), - private_account_nsks.to_vec(), - private_account_membership_proofs.to_vec(), + vec![ + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + nsk: recipient_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + identifier: 0, + }, + ], &program.into(), ); @@ -2135,21 +1954,19 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], &program.into(), ); @@ -2183,21 +2000,19 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], &program.into(), ); @@ -2231,21 +2046,19 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], &program.into(), ); @@ -2279,21 +2092,19 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], &program.into(), ); @@ -2325,21 +2136,19 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateUnauthorized { + npk: recipient_keys.npk(), + ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + identifier: 0, + }, ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], &program.into(), ); @@ -2368,14 +2177,16 @@ pub mod tests { let private_pda_account = AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); - let visibility_mask = [0, 3]; let result = execute_and_prove( vec![public_account_1, private_pda_account], Program::serialize_instruction(10_u128).unwrap(), - visibility_mask.to_vec(), - vec![(npk, 0, shared_secret)], - vec![], - vec![None], + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::PrivatePdaInit { + npk, + ssk: shared_secret, + }, + ], &program.into(), ); @@ -2401,10 +2212,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_state], Program::serialize_instruction(seed).unwrap(), - vec![3], - vec![(npk, u128::MAX, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivatePdaInit { + npk, + ssk: shared_secret, + }], &program.into(), ); @@ -2439,10 +2250,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_state], Program::serialize_instruction(seed).unwrap(), - vec![3], - vec![(npk_b, 0, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivatePdaInit { + npk: npk_b, + ssk: shared_secret, + }], &program.into(), ); @@ -2473,10 +2284,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_state], Program::serialize_instruction((seed, seed, callee_id)).unwrap(), - vec![3], - vec![(npk, u128::MAX, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivatePdaInit { + npk, + ssk: shared_secret, + }], &program_with_deps, ); @@ -2510,10 +2321,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_state], Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(), - vec![3], - vec![(npk, 0, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivatePdaInit { + npk, + ssk: shared_secret, + }], &program_with_deps, ); @@ -2546,10 +2357,16 @@ pub mod tests { let result = execute_and_prove( vec![pre_a, pre_b], Program::serialize_instruction(seed).unwrap(), - vec![3, 3], - vec![(keys_a.npk(), 0, shared_a), (keys_b.npk(), 0, shared_b)], - vec![], - vec![None, None], + vec![ + InputAccountIdentity::PrivatePdaInit { + npk: keys_a.npk(), + ssk: shared_a, + }, + InputAccountIdentity::PrivatePdaInit { + npk: keys_b.npk(), + ssk: shared_b, + }, + ], &program.into(), ); @@ -2590,146 +2407,10 @@ pub mod tests { let result = execute_and_prove( vec![owned_pre_state], Program::serialize_instruction(()).unwrap(), - vec![3], - vec![(npk, 0, shared_secret)], - vec![], - vec![None], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn 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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn 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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - - // Setting three private account keys for a circuit execution with only two private - // accounts. - let private_account_keys = [ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[57; 32], &sender_keys.vpk()), - ), - ]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - vec![1, 2], - private_account_keys.to_vec(), - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn 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::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - (&sender_keys.npk(), 0), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - - // 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_nsks = [sender_keys.nsk, recipient_keys.nsk]; - let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10_u128).unwrap(), - visibility_mask.to_vec(), - vec![ - ( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - 0, - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - private_account_nsks.to_vec(), - private_account_membership_proofs.to_vec(), + vec![InputAccountIdentity::PrivatePdaInit { + npk, + ssk: shared_secret, + }], &program.into(), ); @@ -2806,20 +2487,24 @@ pub mod tests { (&sender_keys.npk(), 0), ); - let visibility_mask = [1, 1]; - let private_account_nsks = [sender_keys.nsk, sender_keys.nsk]; - let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.vpk()); let result = execute_and_prove( vec![private_account_1.clone(), private_account_1], Program::serialize_instruction(100_u128).unwrap(), - visibility_mask.to_vec(), vec![ - (sender_keys.npk(), 0, shared_secret), - (sender_keys.npk(), 0, shared_secret), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: shared_secret, + nsk: sender_keys.nsk, + membership_proof: (1, vec![]), + identifier: 0, + }, + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: shared_secret, + nsk: sender_keys.nsk, + membership_proof: (1, vec![]), + identifier: 0, + }, ], - private_account_nsks.to_vec(), - private_account_membership_proofs.to_vec(), &program.into(), ); @@ -3110,10 +2795,7 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(0_u128).unwrap(), - vec![0], - vec![], - vec![], - vec![], + vec![InputAccountIdentity::Public], &program.into(), ); @@ -3152,10 +2834,17 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(37_u128).unwrap(), - vec![1, 0], - vec![(sender_keys.npk(), 0, shared_secret)], - vec![sender_keys.nsk], - vec![state.get_proof_for_commitment(&sender_commitment)], + vec![ + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: shared_secret, + nsk: sender_keys.nsk, + membership_proof: state + .get_proof_for_commitment(&sender_commitment) + .expect("sender's commitment must be in state"), + identifier: 0, + }, + InputAccountIdentity::Public, + ], &program.into(), ) .unwrap(); @@ -3273,12 +2962,23 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![to_account, from_account], Program::serialize_instruction(instruction).unwrap(), - vec![1, 1], - vec![(from_keys.npk(), 0, to_ss), (to_keys.npk(), 0, from_ss)], - vec![from_keys.nsk, to_keys.nsk], vec![ - state.get_proof_for_commitment(&from_commitment), - state.get_proof_for_commitment(&to_commitment), + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: to_ss, + nsk: from_keys.nsk, + membership_proof: state + .get_proof_for_commitment(&from_commitment) + .expect("from's commitment must be in state"), + identifier: 0, + }, + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: from_ss, + nsk: to_keys.nsk, + membership_proof: state + .get_proof_for_commitment(&to_commitment) + .expect("to's commitment must be in state"), + identifier: 0, + }, ], &program_with_deps, ) @@ -3542,10 +3242,11 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![authorized_account], Program::serialize_instruction(balance).unwrap(), - vec![1], - vec![(private_keys.npk(), 0, shared_secret)], - vec![private_keys.nsk], - vec![None], + vec![InputAccountIdentity::PrivateAuthorizedInit { + ssk: shared_secret, + nsk: private_keys.nsk, + identifier: 0, + }], &program.into(), ) .unwrap(); @@ -3590,10 +3291,11 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![unauthorized_account], Program::serialize_instruction(0_u128).unwrap(), - vec![2], - vec![(private_keys.npk(), 0, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivateUnauthorized { + npk: private_keys.npk(), + ssk: shared_secret, + identifier: 0, + }], &program.into(), ) .unwrap(); @@ -3642,10 +3344,11 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![authorized_account.clone()], Program::serialize_instruction(balance).unwrap(), - vec![1], - vec![(private_keys.npk(), 0, shared_secret)], - vec![private_keys.nsk], - vec![None], + vec![InputAccountIdentity::PrivateAuthorizedInit { + ssk: shared_secret, + nsk: private_keys.nsk, + identifier: 0, + }], &claimer_program.into(), ) .unwrap(); @@ -3688,10 +3391,11 @@ pub mod tests { let res = execute_and_prove( vec![account_metadata], Program::serialize_instruction(()).unwrap(), - vec![1], - vec![(private_keys.npk(), 0, shared_secret2)], - vec![private_keys.nsk], - vec![None], + vec![InputAccountIdentity::PrivateAuthorizedInit { + ssk: shared_secret2, + nsk: private_keys.nsk, + identifier: 0, + }], &noop_program.into(), ); @@ -3764,14 +3468,12 @@ pub mod tests { let result = execute_and_prove( vec![private_account], Program::serialize_instruction(instruction).unwrap(), - vec![1], - vec![( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), - )], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], + vec![InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }], &program.into(), ); @@ -3792,14 +3494,12 @@ pub mod tests { let result = execute_and_prove( vec![private_account], Program::serialize_instruction(instruction).unwrap(), - vec![1], - vec![( - sender_keys.npk(), - 0, - SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), - )], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], + vec![InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), + nsk: sender_keys.nsk, + membership_proof: (0, vec![]), + identifier: 0, + }], &program.into(), ); @@ -3852,10 +3552,17 @@ pub mod tests { let result = execute_and_prove( vec![sender_account, recipient_account], Program::serialize_instruction(instruction).unwrap(), - vec![0, 1], - vec![(recipient_keys.npk(), 0, recipient)], - vec![recipient_keys.nsk], - vec![state.get_proof_for_commitment(&recipient_commitment)], + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: recipient, + nsk: recipient_keys.nsk, + membership_proof: state + .get_proof_for_commitment(&recipient_commitment) + .expect("recipient's commitment must be in state"), + identifier: 0, + }, + ], &program_with_deps, ); @@ -4002,10 +3709,11 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![pre], Program::serialize_instruction(instruction).unwrap(), - vec![2], - vec![(account_keys.npk(), 0, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivateUnauthorized { + npk: account_keys.npk(), + ssk: shared_secret, + identifier: 0, + }], &validity_window_program.into(), ) .unwrap(); @@ -4071,10 +3779,11 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![pre], Program::serialize_instruction(instruction).unwrap(), - vec![2], - vec![(account_keys.npk(), 0, shared_secret)], - vec![], - vec![None], + vec![InputAccountIdentity::PrivateUnauthorized { + npk: account_keys.npk(), + ssk: shared_secret, + identifier: 0, + }], &validity_window_program.into(), ) .unwrap(); diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 70979b7e..f658ea53 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -5,7 +5,7 @@ use std::{ use nssa_core::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Identifier, - MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, + InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce}, compute_digest_for_path, @@ -17,7 +17,7 @@ use nssa_core::{ }; use risc0_zkvm::{guest::env, serde::to_vec}; -const PRIVATE_PDA_FIXED_IDENTIFIER: u128 = u128::MAX; +const PRIVATE_PDA_FIXED_IDENTIFIER: Identifier = u128::MAX; /// State of the involved accounts before and after program execution. struct ExecutionState { @@ -25,16 +25,16 @@ struct ExecutionState { post_states: HashMap, block_validity_window: BlockValidityWindow, timestamp_validity_window: TimestampValidityWindow, - /// Positions (in `pre_states`) of mask-3 accounts whose supplied npk has been bound to - /// their `AccountId` via a proven `AccountId::for_private_pda(program_id, seed, npk)` + /// Positions (in `pre_states`) of private-PDA accounts whose supplied npk has been bound + /// to their `AccountId` via a proven `AccountId::for_private_pda(program_id, seed, npk)` /// check. /// Two proof paths populate this set: a `Claim::Pda(seed)` in a program's `post_state` on /// that `pre_state`, or a caller's `ChainedCall.pda_seeds` entry matching that `pre_state` /// under the private derivation. Binding is an idempotent property, not an event: the same /// position can legitimately be bound through both paths in the same tx (e.g. a program /// claims a private PDA and then delegates it to a callee), and the set uses `contains`, - /// not `assert!(insert)`. After the main loop, every mask-3 position must appear in this - /// set; otherwise the npk is unbound and the circuit rejects. + /// not `assert!(insert)`. After the main loop, every private-PDA position must appear in + /// this set; otherwise the npk is unbound and the circuit rejects. private_pda_bound_positions: HashSet, /// Across the whole transaction, each `(program_id, seed)` pair may resolve to at most one /// `AccountId`. A seed under a program can derive a family of accounts, one public PDA and @@ -45,39 +45,29 @@ struct ExecutionState { /// `AccountId` entry or as an equality check against the existing one, making the rule: one /// `(program, seed)` → one account per tx. pda_family_binding: HashMap<(ProgramId, PdaSeed), AccountId>, - /// Map from a mask-3 `pre_state`'s position in `visibility_mask` to the npk supplied for - /// that position in `private_account_keys`. Built once in `derive_from_outputs` by walking - /// `visibility_mask` in lock-step with `private_account_keys`, used later by the claim and - /// caller-seeds authorization paths. + /// Map from a private-PDA `pre_state`'s position in `account_identities` to the npk that + /// variant supplies for that position. Populated once in `derive_from_outputs` by walking + /// `account_identities` and consulting `npk_if_private_pda`. Used later by the claim and + /// caller-seeds authorization paths to verify + /// `AccountId::for_private_pda(program_id, seed, npk) == pre_state.account_id`. private_pda_npk_by_position: HashMap, } impl ExecutionState { /// Validate program outputs and derive the overall execution state. pub fn derive_from_outputs( - visibility_mask: &[u8], - private_account_keys: &[(NullifierPublicKey, Identifier, SharedSecretKey)], + account_identities: &[InputAccountIdentity], program_id: ProgramId, program_outputs: Vec, ) -> Self { - // Build position → npk map for mask-3 pre_states. `private_account_keys` is consumed in - // pre_state order across all masks 1/2/3, so walk `visibility_mask` in lock-step. The - // downstream `compute_circuit_output` also consumes the same iterator and its trailing - // assertions catch an over-supply of keys; under-supply surfaces here. + // Build position → npk map for private-PDA pre_states, indexed by position in + // `account_identities`. The vec is documented as 1:1 with the program's pre_state order, + // so position here matches `pre_state_position` used downstream in + // `validate_and_sync_states`. let mut private_pda_npk_by_position: HashMap = HashMap::new(); - { - let mut keys_iter = private_account_keys.iter(); - for (pos, &mask) in visibility_mask.iter().enumerate() { - if matches!(mask, 1..=3) { - let (npk, _, _) = keys_iter.next().unwrap_or_else(|| { - panic!( - "private_account_keys shorter than visibility_mask demands: no key for masked position {pos} (mask {mask})" - ) - }); - if mask == 3 { - private_pda_npk_by_position.insert(pos, *npk); - } - } + for (pos, account_identity) in account_identities.iter().enumerate() { + if let Some(npk) = account_identity.npk_if_private_pda() { + private_pda_npk_by_position.insert(pos, npk); } } @@ -194,7 +184,7 @@ impl ExecutionState { } execution_state.validate_and_sync_states( - visibility_mask, + account_identities, chained_call.program_id, caller_program_id, &chained_call.pda_seeds, @@ -211,12 +201,12 @@ impl ExecutionState { "Inner call without a chained call found", ); - // Every mask-3 pre_state must have had its npk bound to its account_id, either via a - // `Claim::Pda(seed)` in some program's post_state or via a caller's `pda_seeds` matching - // the private derivation. An unbound mask-3 pre_state has no cryptographic link between - // the supplied npk and the account_id, and must be rejected. - for (pos, &mask) in visibility_mask.iter().enumerate() { - if mask == 3 { + // Every private-PDA pre_state must have had its npk bound to its account_id, either via + // a `Claim::Pda(seed)` in some program's post_state or via a caller's `pda_seeds` + // matching the private derivation. An unbound private-PDA pre_state has no + // cryptographic link between the supplied npk and the account_id, and must be rejected. + for (pos, account_identity) in account_identities.iter().enumerate() { + if account_identity.is_private_pda() { assert!( execution_state.private_pda_bound_positions.contains(&pos), "private PDA pre_state at position {pos} has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds" @@ -251,7 +241,7 @@ impl ExecutionState { /// Validate program pre and post states and populate the execution state. fn validate_and_sync_states( &mut self, - visibility_mask: &[u8], + account_identities: &[InputAccountIdentity], program_id: ProgramId, caller_program_id: Option, caller_pda_seeds: &[PdaSeed], @@ -329,9 +319,9 @@ impl ExecutionState { .position(|acc| acc.account_id == pre_account_id) .expect("Pre state must exist at this point"); - let mask = visibility_mask[pre_state_position]; - match mask { - 0 => match claim { + let account_identity = &account_identities[pre_state_position]; + if account_identity.is_public() { + match claim { Claim::Authorized => { // Note: no need to check authorized pdas because we have already // checked consistency of authorization above. @@ -353,40 +343,40 @@ impl ExecutionState { pre_account_id, ); } - }, - 3 => { - match claim { - Claim::Authorized => { - assert!( - pre_is_authorized, - "Cannot claim unauthorized private PDA {pre_account_id}" - ); - } - Claim::Pda(seed) => { - let npk = self + } + } else if account_identity.is_private_pda() { + match claim { + Claim::Authorized => { + assert!( + pre_is_authorized, + "Cannot claim unauthorized private PDA {pre_account_id}" + ); + } + Claim::Pda(seed) => { + let npk = self .private_pda_npk_by_position .get(&pre_state_position) - .expect("private PDA pre_state must have an npk in the position map"); - let pda = AccountId::for_private_pda(&program_id, &seed, npk); - assert_eq!( - pre_account_id, pda, - "Invalid private PDA claim for account {pre_account_id}" + .expect( + "private PDA pre_state must have an npk in the position map", ); - self.private_pda_bound_positions.insert(pre_state_position); - assert_family_binding( - &mut self.pda_family_binding, - program_id, - seed, - pre_account_id, - ); - } + let pda = AccountId::for_private_pda(&program_id, &seed, npk); + assert_eq!( + pre_account_id, pda, + "Invalid private PDA claim for account {pre_account_id}" + ); + self.private_pda_bound_positions.insert(pre_state_position); + assert_family_binding( + &mut self.pda_family_binding, + program_id, + seed, + pre_account_id, + ); } } - _ => { - // Mask 1/2: standard private accounts don't enforce the claim semantics. - // Unauthorized private claiming is intentionally allowed since operating - // these accounts requires the npk/nsk keypair anyway. - } + } else { + // Standalone private accounts: don't enforce the claim semantics. + // Unauthorized private claiming is intentionally allowed since operating + // these accounts requires the npk/nsk keypair anyway. } post.account_mut().program_owner = program_id; @@ -488,10 +478,7 @@ fn resolve_authorization_and_record_bindings( fn compute_circuit_output( execution_state: ExecutionState, - visibility_mask: &[u8], - private_account_keys: &[(NullifierPublicKey, Identifier, SharedSecretKey)], - private_account_nsks: &[NullifierSecretKey], - private_account_membership_proofs: &[Option], + account_identities: &[InputAccountIdentity], ) -> PrivacyPreservingCircuitOutput { let mut output = PrivacyPreservingCircuitOutput { public_pre_states: Vec::new(), @@ -505,290 +492,268 @@ fn compute_circuit_output( let states_iter = execution_state.into_states_iter(); assert_eq!( - visibility_mask.len(), + account_identities.len(), states_iter.len(), - "Invalid visibility mask length" + "Invalid account_identities length" ); - let mut private_keys_iter = private_account_keys.iter(); - let mut private_nsks_iter = private_account_nsks.iter(); - let mut private_membership_proofs_iter = private_account_membership_proofs.iter(); - let mut output_index = 0; - for (account_visibility_mask, (pre_state, post_state)) in - visibility_mask.iter().copied().zip(states_iter) - { - match account_visibility_mask { - 0 => { - // Public account + for (account_identity, (pre_state, post_state)) in account_identities.iter().zip(states_iter) { + match account_identity { + InputAccountIdentity::Public => { output.public_pre_states.push(pre_state); output.public_post_states.push(post_state); } - 1 | 2 => { - let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else { - panic!("Missing private account key"); - }; + InputAccountIdentity::PrivateAuthorizedInit { + ssk, + nsk, + identifier, + } => { assert_ne!( *identifier, PRIVATE_PDA_FIXED_IDENTIFIER, "Identifier must be different from {PRIVATE_PDA_FIXED_IDENTIFIER}. This is reserved for private PDA." ); + let npk = NullifierPublicKey::from(nsk); + let account_id = AccountId::from((&npk, *identifier)); + assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); + assert!( + pre_state.is_authorized, + "Pre-state not authorized for authenticated private account" + ); + assert_eq!( + pre_state.account, + Account::default(), + "Found new private account with non default values" + ); + + let new_nullifier = ( + Nullifier::for_account_initialization(&account_id), + DUMMY_COMMITMENT_HASH, + ); + let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + + emit_private_output( + &mut output, + &mut output_index, + post_state, + &account_id, + *identifier, + ssk, + new_nullifier, + new_nonce, + ); + } + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk, + nsk, + membership_proof, + identifier, + } => { + assert_ne!( + *identifier, PRIVATE_PDA_FIXED_IDENTIFIER, + "Identifier must be different from {PRIVATE_PDA_FIXED_IDENTIFIER}. This is reserved for private PDA." + ); + let npk = NullifierPublicKey::from(nsk); + let account_id = AccountId::from((&npk, *identifier)); + + assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); + assert!( + pre_state.is_authorized, + "Pre-state not authorized for authenticated private account" + ); + + let new_nullifier = compute_update_nullifier_and_set_digest( + membership_proof, + &pre_state.account, + &account_id, + nsk, + ); + let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + + emit_private_output( + &mut output, + &mut output_index, + post_state, + &account_id, + *identifier, + ssk, + new_nullifier, + new_nonce, + ); + } + InputAccountIdentity::PrivateUnauthorized { + npk, + ssk, + identifier, + } => { + assert_ne!( + *identifier, PRIVATE_PDA_FIXED_IDENTIFIER, + "Identifier must be different from {PRIVATE_PDA_FIXED_IDENTIFIER}. This is reserved for private PDA." + ); let account_id = AccountId::from((npk, *identifier)); assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); - - let (new_nullifier, new_nonce) = if account_visibility_mask == 1 { - // Private account with authentication - - let Some(nsk) = private_nsks_iter.next() else { - panic!("Missing private account nullifier secret key"); - }; - - // Verify the nullifier public key - assert_eq!( - npk, - &NullifierPublicKey::from(nsk), - "Nullifier public key mismatch" - ); - - // Check pre_state authorization - assert!( - pre_state.is_authorized, - "Pre-state not authorized for authenticated private account" - ); - - let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { - panic!("Missing membership proof"); - }; - - let new_nullifier = compute_nullifier_and_set_digest( - membership_proof_opt.as_ref(), - &pre_state.account, - &account_id, - nsk, - ); - - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); - - (new_nullifier, new_nonce) - } else { - // Private account without authentication - - assert_eq!( - pre_state.account, - Account::default(), - "Found new private account with non default values", - ); - - assert!( - !pre_state.is_authorized, - "Found new private account marked as authorized." - ); - - let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { - panic!("Missing membership proof"); - }; - - assert!( - membership_proof_opt.is_none(), - "Membership proof must be None for unauthorized accounts" - ); - - let nullifier = Nullifier::for_account_initialization(&account_id); - - let new_nonce = Nonce::private_account_nonce_init(&account_id); - - ((nullifier, DUMMY_COMMITMENT_HASH), new_nonce) - }; - output.new_nullifiers.push(new_nullifier); - - // Update post-state with new nonce - let mut post_with_updated_nonce = post_state; - post_with_updated_nonce.nonce = new_nonce; - - // Compute commitment - let commitment_post = Commitment::new(&account_id, &post_with_updated_nonce); - - // Encrypt and push post state - let encrypted_account = EncryptionScheme::encrypt( - &post_with_updated_nonce, - *identifier, - shared_secret, - &commitment_post, - output_index, - ); - - output.new_commitments.push(commitment_post); - output.ciphertexts.push(encrypted_account); - output_index = output_index - .checked_add(1) - .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); - } - 3 => { - // Private PDA account. The supplied npk has already been bound to - // `pre_state.account_id` upstream in `validate_and_sync_states`, either via a - // `Claim::Pda(seed)` match or via a caller `pda_seeds` match, both of which - // assert `AccountId::for_private_pda(owner, seed, npk) == account_id`. The - // post-loop assertion in `derive_from_outputs` (see the - // `private_pda_bound_positions` check) guarantees that every mask-3 - // position has been through at least one such binding, so this - // branch can safely use the wallet npk without re-verifying. - let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else { - panic!("Missing private account key"); - }; - assert_eq!( - *identifier, PRIVATE_PDA_FIXED_IDENTIFIER, - "Identifier for private PDAs must be {PRIVATE_PDA_FIXED_IDENTIFIER}." + pre_state.account, + Account::default(), + "Found new private account with non default values", + ); + assert!( + !pre_state.is_authorized, + "Found new private account marked as authorized." ); - let (new_nullifier, new_nonce) = if pre_state.is_authorized { - // Existing private PDA with authentication (like mask 1) - let Some(nsk) = private_nsks_iter.next() else { - panic!("Missing private account nullifier secret key"); - }; - assert_eq!( - npk, - &NullifierPublicKey::from(nsk), - "Nullifier public key mismatch" - ); - - let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { - panic!("Missing membership proof"); - }; - - let new_nullifier = compute_nullifier_and_set_digest( - membership_proof_opt.as_ref(), - &pre_state.account, - &pre_state.account_id, - nsk, - ); - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); - (new_nullifier, new_nonce) - } else { - // New private PDA (like mask 2). The default + unauthorized requirement - // here rules out use cases like a fully-private multisig, which would need - // a non-default, non-authorized private PDA input account. - // TODO(private-pdas-pr-2/3): relax this once the wallet can supply a - // `(seed, owner)` side input so the npk-to-account_id binding can be - // re-verified for an existing private PDA without a `Claim::Pda` or caller - // `pda_seeds` match. - assert_eq!( - pre_state.account, - Account::default(), - "New private PDA must be default" - ); - - let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { - panic!("Missing membership proof"); - }; - assert!( - membership_proof_opt.is_none(), - "Membership proof must be None for new accounts" - ); - - let nullifier = Nullifier::for_account_initialization(&pre_state.account_id); - let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id); - ((nullifier, DUMMY_COMMITMENT_HASH), new_nonce) - }; - output.new_nullifiers.push(new_nullifier); - - let mut post_with_updated_nonce = post_state; - post_with_updated_nonce.nonce = new_nonce; - - let commitment_post = - Commitment::new(&pre_state.account_id, &post_with_updated_nonce); - - let encrypted_account = EncryptionScheme::encrypt( - &post_with_updated_nonce, - PRIVATE_PDA_FIXED_IDENTIFIER, - shared_secret, - &commitment_post, - output_index, + let new_nullifier = ( + Nullifier::for_account_initialization(&account_id), + DUMMY_COMMITMENT_HASH, ); + let new_nonce = Nonce::private_account_nonce_init(&account_id); - output.new_commitments.push(commitment_post); - output.ciphertexts.push(encrypted_account); - output_index = output_index - .checked_add(1) - .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); + emit_private_output( + &mut output, + &mut output_index, + post_state, + &account_id, + *identifier, + ssk, + new_nullifier, + new_nonce, + ); + } + InputAccountIdentity::PrivatePdaInit { npk: _, ssk } => { + // The npk-to-account_id binding is established upstream in + // `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds` + // match. Here we only enforce the init pre-conditions. The supplied npk on + // the variant has been recorded into `private_pda_npk_by_position` and used + // for the binding check; we use `pre_state.account_id` directly for nullifier + // and commitment derivation. + assert!( + !pre_state.is_authorized, + "PrivatePdaInit requires unauthorized pre_state" + ); + assert_eq!( + pre_state.account, + Account::default(), + "New private PDA must be default" + ); + + let new_nullifier = ( + Nullifier::for_account_initialization(&pre_state.account_id), + DUMMY_COMMITMENT_HASH, + ); + let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id); + + let account_id = pre_state.account_id; + emit_private_output( + &mut output, + &mut output_index, + post_state, + &account_id, + PRIVATE_PDA_FIXED_IDENTIFIER, + ssk, + new_nullifier, + new_nonce, + ); + } + InputAccountIdentity::PrivatePdaUpdate { + ssk, + nsk, + membership_proof, + } => { + // The npk binding is established upstream. Authorization must already be set; + // an unauthorized PrivatePdaUpdate would mean the prover supplied an nsk for an + // unbound PDA, which the upstream binding check would have rejected anyway, + // but we assert here to fail fast and document the precondition. + assert!( + pre_state.is_authorized, + "PrivatePdaUpdate requires authorized pre_state" + ); + + let new_nullifier = compute_update_nullifier_and_set_digest( + membership_proof, + &pre_state.account, + &pre_state.account_id, + nsk, + ); + let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + + let account_id = pre_state.account_id; + emit_private_output( + &mut output, + &mut output_index, + post_state, + &account_id, + PRIVATE_PDA_FIXED_IDENTIFIER, + ssk, + new_nullifier, + new_nonce, + ); } - _ => panic!("Invalid visibility mask value"), } } - assert!( - private_keys_iter.next().is_none(), - "Too many private account keys" - ); - - assert!( - private_nsks_iter.next().is_none(), - "Too many private account nullifier secret keys" - ); - - assert!( - private_membership_proofs_iter.next().is_none(), - "Too many private account membership proofs" - ); - output } -fn compute_nullifier_and_set_digest( - membership_proof_opt: Option<&MembershipProof>, +#[expect( + clippy::too_many_arguments, + reason = "All seven inputs are distinct concerns from the variant arms; bundling would be artificial" +)] +fn emit_private_output( + output: &mut PrivacyPreservingCircuitOutput, + output_index: &mut u32, + post_state: Account, + account_id: &AccountId, + identifier: Identifier, + shared_secret: &SharedSecretKey, + new_nullifier: (Nullifier, CommitmentSetDigest), + new_nonce: Nonce, +) { + output.new_nullifiers.push(new_nullifier); + + let mut post_with_updated_nonce = post_state; + post_with_updated_nonce.nonce = new_nonce; + + let commitment_post = Commitment::new(account_id, &post_with_updated_nonce); + let encrypted_account = EncryptionScheme::encrypt( + &post_with_updated_nonce, + identifier, + shared_secret, + &commitment_post, + *output_index, + ); + + output.new_commitments.push(commitment_post); + output.ciphertexts.push(encrypted_account); + *output_index = output_index + .checked_add(1) + .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); +} + +fn compute_update_nullifier_and_set_digest( + membership_proof: &MembershipProof, pre_account: &Account, account_id: &AccountId, nsk: &NullifierSecretKey, ) -> (Nullifier, CommitmentSetDigest) { - membership_proof_opt.as_ref().map_or_else( - || { - assert_eq!( - *pre_account, - Account::default(), - "Found new private account with non default values" - ); - - // Compute initialization nullifier - let nullifier = Nullifier::for_account_initialization(account_id); - (nullifier, DUMMY_COMMITMENT_HASH) - }, - |membership_proof| { - // Compute commitment set digest associated with provided auth path - let commitment_pre = Commitment::new(account_id, pre_account); - let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); - - // Compute update nullifier - let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); - (nullifier, set_digest) - }, - ) + let commitment_pre = Commitment::new(account_id, pre_account); + let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); + let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); + (nullifier, set_digest) } fn main() { let PrivacyPreservingCircuitInput { program_outputs, - visibility_mask, - private_account_keys, - private_account_nsks, - private_account_membership_proofs, + account_identities, program_id, } = env::read(); - let execution_state = ExecutionState::derive_from_outputs( - &visibility_mask, - &private_account_keys, - program_id, - program_outputs, - ); + let execution_state = + ExecutionState::derive_from_outputs(&account_identities, program_id, program_outputs); - let output = compute_circuit_output( - execution_state, - &visibility_mask, - &private_account_keys, - &private_account_nsks, - &private_account_membership_proofs, - ); + let output = compute_circuit_output(execution_state, &account_identities); env::commit(&output); } diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 22c09d85..5f118a2f 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -1076,7 +1076,7 @@ mod tests { program::Program, }; use nssa_core::{ - SharedSecretKey, + InputAccountIdentity, SharedSecretKey, account::AccountWithMetadata, encryption::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey}, }; @@ -1114,10 +1114,11 @@ mod tests { (&npk, 0), )], Program::serialize_instruction(0_u128).unwrap(), - vec![1], - vec![(npk, 0, shared_secret)], - vec![nsk], - vec![None], + vec![InputAccountIdentity::PrivateAuthorizedInit { + ssk: shared_secret, + nsk, + identifier: 0, + }], &Program::authenticated_transfer_program().into(), ) .unwrap(); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index f0c69fea..c8244ef9 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -413,13 +413,7 @@ impl WalletCore { let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( pre_states, instruction_data, - acc_manager.visibility_mask().to_vec(), - private_account_keys - .iter() - .map(|keys| (keys.npk, keys.identifier, keys.ssk)) - .collect::>(), - acc_manager.private_account_auth(), - acc_manager.private_account_membership_proofs(), + acc_manager.account_identities(), &program.to_owned(), ) .unwrap(); diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index 3df2ecc1..a4ed970f 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -2,7 +2,8 @@ use anyhow::Result; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; use nssa::{AccountId, PrivateKey}; use nssa_core::{ - Identifier, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey, + SharedSecretKey, account::{AccountWithMetadata, Nonce}, encryption::{EphemeralPublicKey, ViewingPublicKey}, }; @@ -34,7 +35,7 @@ impl PrivacyPreservingAccount { | Self::PrivateForeign { npk: _, vpk: _, - identifier: _ + identifier: _, } ) } @@ -42,7 +43,6 @@ impl PrivacyPreservingAccount { pub struct PrivateAccountKeys { pub npk: NullifierPublicKey, - pub identifier: Identifier, pub ssk: SharedSecretKey, pub vpk: ViewingPublicKey, pub epk: EphemeralPublicKey, @@ -58,7 +58,6 @@ enum State { pub struct AccountManager { states: Vec, - visibility_mask: Vec, } impl AccountManager { @@ -66,11 +65,10 @@ impl AccountManager { wallet: &WalletCore, accounts: Vec, ) -> Result { - let mut pre_states = Vec::with_capacity(accounts.len()); - let mut visibility_mask = Vec::with_capacity(accounts.len()); + let mut states = Vec::with_capacity(accounts.len()); for account in accounts { - let (state, mask) = match account { + let state = match account { PrivacyPreservingAccount::Public(account_id) => { let acc = wallet .get_account_public(account_id) @@ -80,13 +78,12 @@ impl AccountManager { let sk = wallet.get_account_public_signing_key(account_id).cloned(); let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id); - (State::Public { account, sk }, 0) + State::Public { account, sk } } PrivacyPreservingAccount::PrivateOwned(account_id) => { let pre = private_acc_preparation(wallet, account_id).await?; - let mask = if pre.pre_state.is_authorized { 1 } else { 2 }; - (State::Private(pre), mask) + State::Private(pre) } PrivacyPreservingAccount::PrivateForeign { npk, @@ -95,6 +92,9 @@ impl AccountManager { } => { let acc = nssa_core::account::Account::default(); let auth_acc = AccountWithMetadata::new(acc, false, (&npk, identifier)); + let eph_holder = EphemeralKeyHolder::new(&npk); + let ssk = eph_holder.calculate_shared_secret_sender(&vpk); + let epk = eph_holder.generate_ephemeral_public_key(); let pre = AccountPreparedData { nsk: None, npk, @@ -102,20 +102,18 @@ impl AccountManager { vpk, pre_state: auth_acc, proof: None, + ssk, + epk, }; - (State::Private(pre), 2) + State::Private(pre) } }; - pre_states.push(state); - visibility_mask.push(mask); + states.push(state); } - Ok(Self { - states: pre_states, - visibility_mask, - }) + Ok(Self { states }) } pub fn pre_states(&self) -> Vec { @@ -128,10 +126,6 @@ impl AccountManager { .collect() } - pub fn visibility_mask(&self) -> &[u8] { - &self.visibility_mask - } - pub fn public_account_nonces(&self) -> Vec { self.states .iter() @@ -146,38 +140,46 @@ impl AccountManager { self.states .iter() .filter_map(|state| match state { - State::Private(pre) => { - let eph_holder = EphemeralKeyHolder::new(&pre.npk); + State::Private(pre) => Some(PrivateAccountKeys { + npk: pre.npk, + ssk: pre.ssk, + vpk: pre.vpk.clone(), + epk: pre.epk.clone(), + }), + State::Public { .. } => None, + }) + .collect() + } - Some(PrivateAccountKeys { - npk: pre.npk, + /// Build the per-account input vec for the privacy-preserving circuit. Each variant carries + /// exactly the fields the circuit's code path for that account needs, with the ephemeral + /// keys (`ssk`) drawn from the cached values that `private_account_keys` and the message + /// construction also use, so all three views agree on the same ephemeral key. + pub fn account_identities(&self) -> Vec { + self.states + .iter() + .map(|state| match state { + State::Public { .. } => InputAccountIdentity::Public, + State::Private(pre) => match (pre.nsk, pre.proof.clone()) { + (Some(nsk), Some(membership_proof)) => { + InputAccountIdentity::PrivateAuthorizedUpdate { + ssk: pre.ssk, + nsk, + membership_proof, + identifier: pre.identifier, + } + } + (Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit { + ssk: pre.ssk, + nsk, identifier: pre.identifier, - ssk: eph_holder.calculate_shared_secret_sender(&pre.vpk), - vpk: pre.vpk.clone(), - epk: eph_holder.generate_ephemeral_public_key(), - }) - } - State::Public { .. } => None, - }) - .collect() - } - - pub fn private_account_auth(&self) -> Vec { - self.states - .iter() - .filter_map(|state| match state { - State::Private(pre) => pre.nsk, - State::Public { .. } => None, - }) - .collect() - } - - pub fn private_account_membership_proofs(&self) -> Vec> { - self.states - .iter() - .filter_map(|state| match state { - State::Private(pre) => Some(pre.proof.clone()), - State::Public { .. } => None, + }, + (None, _) => InputAccountIdentity::PrivateUnauthorized { + npk: pre.npk, + ssk: pre.ssk, + identifier: pre.identifier, + }, + }, }) .collect() } @@ -210,6 +212,13 @@ struct AccountPreparedData { vpk: ViewingPublicKey, pre_state: AccountWithMetadata, proof: Option, + /// Cached shared-secret key derived once at `AccountManager::new`. Reused for both the + /// circuit input variant (`account_identities()`) and the message ephemeral-key tuples + /// (`private_account_keys()`), so all consumers see the same key. The corresponding + /// `EphemeralKeyHolder` uses `OsRng` and would produce a different value on a second call. + ssk: SharedSecretKey, + /// Cached ephemeral public key, paired with `ssk`. + epk: EphemeralPublicKey, } async fn private_acc_preparation( @@ -237,6 +246,10 @@ async fn private_acc_preparation( // support from that in the wallet. let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, (&from_npk, from_identifier)); + let eph_holder = EphemeralKeyHolder::new(&from_npk); + let ssk = eph_holder.calculate_shared_secret_sender(&from_vpk); + let epk = eph_holder.generate_ephemeral_public_key(); + Ok(AccountPreparedData { nsk: Some(nsk), npk: from_npk, @@ -244,5 +257,7 @@ async fn private_acc_preparation( vpk: from_vpk, pre_state: sender_pre, proof, + ssk, + epk, }) }