fix: address PR review feedback

- Rename PrivacyPreservingCircuitInputAccount to InputAccountIdentity (drop the PrivacyPreservingCircuit prefix; add Identity suffix)
- Rename PrivacyPreservingCircuitInput.accounts to account_identities
- Rename AccountManager.accounts() to account_identities() and loop variables to account_identity
- Drop legacy mask-1/2/3 references from variant doc comments and guest comments
- Remove the explanatory comments about deleted parallel-vec tests; moved to the PR description
- Rebake privacy_preserving_circuit and test program artifacts
This commit is contained in:
Moudy 2026-04-30 15:44:51 +02:00
parent 55a4a1d83b
commit 98da9b26cc
44 changed files with 135 additions and 138 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -27,7 +27,7 @@ use nssa::{
public_transaction as putx,
};
use nssa_core::{
MembershipProof, NullifierPublicKey, PrivacyPreservingCircuitInputAccount,
MembershipProof, NullifierPublicKey, InputAccountIdentity,
account::{AccountWithMetadata, Nonce, data::Data},
encryption::ViewingPublicKey,
};
@ -249,12 +249,12 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: sender_ss,
nsk: sender_nsk,
membership_proof: proof,
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_npk,
ssk: recipient_ss,
},

View File

@ -16,7 +16,7 @@ pub struct PrivacyPreservingCircuitInput {
/// 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 accounts: Vec<PrivacyPreservingCircuitInputAccount>,
pub account_identities: Vec<InputAccountIdentity>,
/// Program ID.
pub program_id: ProgramId,
}
@ -24,38 +24,39 @@ pub struct PrivacyPreservingCircuitInput {
/// 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 PrivacyPreservingCircuitInputAccount {
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 (mask 1, no membership proof). The
/// `pre_state` must be `Account::default()`. `npk` is derived from `nsk` and matched
/// against `pre_state.account_id` via `AccountId::from(npk)`.
/// Init of an authorized standalone private account: no membership proof. The `pre_state`
/// must be `Account::default()`. `npk` is derived from `nsk` and matched against
/// `pre_state.account_id` via `AccountId::from(npk)`.
PrivateAuthorizedInit {
ssk: SharedSecretKey,
nsk: NullifierSecretKey,
},
/// Update of an authorized standalone private account (mask 1, with membership proof).
/// Update of an authorized standalone private account: existing on-chain commitment, with
/// membership proof.
PrivateAuthorizedUpdate {
ssk: SharedSecretKey,
nsk: NullifierSecretKey,
membership_proof: MembershipProof,
},
/// Unauthorized init of a standalone private account (mask 2). Used for recipients who
/// don't yet exist on chain. No `nsk`, no membership proof.
/// 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,
},
/// Init of a private PDA (mask 3, unauthorized). The npk-to-account_id binding is proven
/// upstream via `Claim::Pda(seed)` or a caller's `pda_seeds` match.
/// 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.
PrivatePdaInit {
npk: NullifierPublicKey,
ssk: SharedSecretKey,
},
/// Update of an existing private PDA (mask 3, 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.
/// 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.
PrivatePdaUpdate {
ssk: SharedSecretKey,
nsk: NullifierSecretKey,
@ -63,7 +64,7 @@ pub enum PrivacyPreservingCircuitInputAccount {
},
}
impl PrivacyPreservingCircuitInputAccount {
impl InputAccountIdentity {
#[must_use]
pub const fn is_public(&self) -> bool {
matches!(self, Self::Public)

View File

@ -4,8 +4,7 @@
)]
pub use circuit_io::{
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitInputAccount,
PrivacyPreservingCircuitOutput,
InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
};
pub use commitment::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof,

View File

@ -2,7 +2,7 @@ use std::collections::{HashMap, VecDeque};
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitInputAccount,
PrivacyPreservingCircuitInput, InputAccountIdentity,
PrivacyPreservingCircuitOutput,
account::AccountWithMetadata,
program::{ChainedCall, InstructionData, ProgramId, ProgramOutput},
@ -66,7 +66,7 @@ impl From<Program> for ProgramWithDependencies {
pub fn execute_and_prove(
pre_states: Vec<AccountWithMetadata>,
instruction_data: InstructionData,
accounts: Vec<PrivacyPreservingCircuitInputAccount>,
account_identities: Vec<InputAccountIdentity>,
program_with_dependencies: &ProgramWithDependencies,
) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> {
let ProgramWithDependencies {
@ -124,7 +124,7 @@ pub fn execute_and_prove(
let circuit_input = PrivacyPreservingCircuitInput {
program_outputs,
accounts,
account_identities,
program_id: program_with_dependencies.program.id(),
};
@ -237,8 +237,8 @@ mod tests {
vec![sender, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::Public,
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::Public,
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: shared_secret,
},
@ -336,14 +336,14 @@ mod tests {
vec![sender_pre, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
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"),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: shared_secret_2,
},
@ -410,7 +410,7 @@ mod tests {
let result = execute_and_prove(
vec![pre],
instruction,
vec![PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
vec![InputAccountIdentity::PrivateUnauthorized {
npk: account_keys.npk(),
ssk: shared_secret,
}],

View File

@ -363,7 +363,7 @@ pub mod tests {
use nssa_core::{
BlockId, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey,
PrivacyPreservingCircuitInputAccount, SharedSecretKey, Timestamp,
InputAccountIdentity, SharedSecretKey, Timestamp,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
program::{
@ -1293,8 +1293,8 @@ pub mod tests {
vec![sender, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::Public,
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::Public,
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: shared_secret,
},
@ -1341,14 +1341,14 @@ pub mod tests {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
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"),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: shared_secret_2,
},
@ -1398,14 +1398,14 @@ pub mod tests {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
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"),
},
PrivacyPreservingCircuitInputAccount::Public,
InputAccountIdentity::Public,
],
&program.into(),
)
@ -1617,7 +1617,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(10_u128).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1640,7 +1640,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(10_u128).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1663,7 +1663,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(()).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1686,7 +1686,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(vec![0]).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1717,7 +1717,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(large_data).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1740,7 +1740,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(()).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1773,8 +1773,8 @@ pub mod tests {
vec![public_account_1, public_account_2],
Program::serialize_instruction(()).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::Public,
PrivacyPreservingCircuitInputAccount::Public,
InputAccountIdentity::Public,
InputAccountIdentity::Public,
],
&program.into(),
);
@ -1798,7 +1798,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(()).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -1831,8 +1831,8 @@ pub mod tests {
vec![public_account_1, public_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::Public,
PrivacyPreservingCircuitInputAccount::Public,
InputAccountIdentity::Public,
InputAccountIdentity::Public,
],
&program.into(),
);
@ -1866,18 +1866,13 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account_1, public_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
// The four `circuit_fails_if_insufficient_*_are_provided` tests have been removed in this
// refactor: each variant of `PrivacyPreservingCircuitInputAccount` carries exactly the fields
// the corresponding circuit code path needs, so the parallel-vec length mismatches those
// tests exercised are now compile-time impossibilities.
#[test]
fn circuit_fails_if_invalid_auth_keys_are_provided() {
let program = Program::simple_balance_transfer();
@ -1903,12 +1898,12 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
nsk: recipient_keys.nsk,
membership_proof: (0, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
},
@ -1947,12 +1942,12 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
},
@ -1991,12 +1986,12 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
},
@ -2035,12 +2030,12 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
},
@ -2079,12 +2074,12 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
},
@ -2121,12 +2116,12 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
InputAccountIdentity::PrivateUnauthorized {
npk: recipient_keys.npk(),
ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()),
},
@ -2163,8 +2158,8 @@ pub mod tests {
vec![public_account_1, private_pda_account],
Program::serialize_instruction(10_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::Public,
PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit {
npk,
ssk: shared_secret,
},
@ -2194,7 +2189,7 @@ pub mod tests {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
vec![InputAccountIdentity::PrivatePdaInit {
npk,
ssk: shared_secret,
}],
@ -2232,7 +2227,7 @@ pub mod tests {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
vec![InputAccountIdentity::PrivatePdaInit {
npk: npk_b,
ssk: shared_secret,
}],
@ -2266,7 +2261,7 @@ pub mod tests {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction((seed, seed, callee_id)).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
vec![InputAccountIdentity::PrivatePdaInit {
npk,
ssk: shared_secret,
}],
@ -2303,7 +2298,7 @@ pub mod tests {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
vec![InputAccountIdentity::PrivatePdaInit {
npk,
ssk: shared_secret,
}],
@ -2340,11 +2335,11 @@ pub mod tests {
vec![pre_a, pre_b],
Program::serialize_instruction(seed).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
InputAccountIdentity::PrivatePdaInit {
npk: keys_a.npk(),
ssk: shared_a,
},
PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
InputAccountIdentity::PrivatePdaInit {
npk: keys_b.npk(),
ssk: shared_b,
},
@ -2389,7 +2384,7 @@ pub mod tests {
let result = execute_and_prove(
vec![owned_pre_state],
Program::serialize_instruction(()).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivatePdaInit {
vec![InputAccountIdentity::PrivatePdaInit {
npk,
ssk: shared_secret,
}],
@ -2399,11 +2394,6 @@ pub mod tests {
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
// The three `circuit_should_fail_with_too_many_*` tests have been removed in this refactor:
// each variant of `PrivacyPreservingCircuitInputAccount` carries exactly the fields the
// corresponding circuit code path needs, so the parallel-vec over-supply mismatches those
// tests exercised are now compile-time impossibilities.
#[test]
fn private_accounts_can_only_be_initialized_once() {
let sender_keys = test_private_account_keys_1();
@ -2479,12 +2469,12 @@ pub mod tests {
vec![private_account_1.clone(), private_account_1],
Program::serialize_instruction(100_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: shared_secret,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
},
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: shared_secret,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
@ -2780,7 +2770,7 @@ pub mod tests {
let result = execute_and_prove(
vec![public_account],
Program::serialize_instruction(0_u128).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::Public],
vec![InputAccountIdentity::Public],
&program.into(),
);
@ -2818,14 +2808,14 @@ pub mod tests {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(37_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
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"),
},
PrivacyPreservingCircuitInputAccount::Public,
InputAccountIdentity::Public,
],
&program.into(),
)
@ -2943,14 +2933,14 @@ pub mod tests {
vec![to_account, from_account],
Program::serialize_instruction(instruction).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
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"),
},
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: from_ss,
nsk: to_keys.nsk,
membership_proof: state
@ -3221,7 +3211,7 @@ pub mod tests {
vec![authorized_account],
Program::serialize_instruction(balance).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedInit {
InputAccountIdentity::PrivateAuthorizedInit {
ssk: shared_secret,
nsk: private_keys.nsk,
},
@ -3269,7 +3259,7 @@ pub mod tests {
let (output, proof) = execute_and_prove(
vec![unauthorized_account],
Program::serialize_instruction(0_u128).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
vec![InputAccountIdentity::PrivateUnauthorized {
npk: private_keys.npk(),
ssk: shared_secret,
}],
@ -3321,7 +3311,7 @@ pub mod tests {
vec![authorized_account.clone()],
Program::serialize_instruction(balance).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedInit {
InputAccountIdentity::PrivateAuthorizedInit {
ssk: shared_secret,
nsk: private_keys.nsk,
},
@ -3368,7 +3358,7 @@ pub mod tests {
vec![account_metadata],
Program::serialize_instruction(()).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedInit {
InputAccountIdentity::PrivateAuthorizedInit {
ssk: shared_secret2,
nsk: private_keys.nsk,
},
@ -3446,7 +3436,7 @@ pub mod tests {
vec![private_account],
Program::serialize_instruction(instruction).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[3; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
@ -3473,7 +3463,7 @@ pub mod tests {
vec![private_account],
Program::serialize_instruction(instruction).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: SharedSecretKey::new(&[3; 32], &sender_keys.vpk()),
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
@ -3531,8 +3521,8 @@ pub mod tests {
vec![sender_account, recipient_account],
Program::serialize_instruction(instruction).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::Public,
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::Public,
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: recipient,
nsk: recipient_keys.nsk,
membership_proof: state
@ -3686,7 +3676,7 @@ pub mod tests {
let (output, proof) = circuit::execute_and_prove(
vec![pre],
Program::serialize_instruction(instruction).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
vec![InputAccountIdentity::PrivateUnauthorized {
npk: account_keys.npk(),
ssk: shared_secret,
}],
@ -3755,7 +3745,7 @@ pub mod tests {
let (output, proof) = circuit::execute_and_prove(
vec![pre],
Program::serialize_instruction(instruction).unwrap(),
vec![PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
vec![InputAccountIdentity::PrivateUnauthorized {
npk: account_keys.npk(),
ssk: shared_secret,
}],

View File

@ -6,7 +6,7 @@ use std::{
use nssa_core::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, MembershipProof,
Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
PrivacyPreservingCircuitInputAccount, PrivacyPreservingCircuitOutput, SharedSecretKey,
InputAccountIdentity, PrivacyPreservingCircuitOutput, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce},
compute_digest_for_path,
program::{
@ -23,16 +23,16 @@ struct ExecutionState {
post_states: HashMap<AccountId, Account>,
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<usize>,
/// 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
@ -43,9 +43,9 @@ 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 private-PDA `pre_state`'s position in `accounts` to the npk that variant
/// supplies for that position. Populated once in `derive_from_outputs` by walking
/// `accounts` and consulting `npk_if_private_pda`. Used later by the claim and
/// 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<usize, NullifierPublicKey>,
@ -54,16 +54,17 @@ struct ExecutionState {
impl ExecutionState {
/// Validate program outputs and derive the overall execution state.
pub fn derive_from_outputs(
accounts: &[PrivacyPreservingCircuitInputAccount],
account_identities: &[InputAccountIdentity],
program_id: ProgramId,
program_outputs: Vec<ProgramOutput>,
) -> Self {
// Build position → npk map for private-PDA pre_states, indexed by position in `accounts`.
// The `accounts` 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`.
// 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<usize, NullifierPublicKey> = HashMap::new();
for (pos, account) in accounts.iter().enumerate() {
if let Some(npk) = account.npk_if_private_pda() {
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);
}
}
@ -181,7 +182,7 @@ impl ExecutionState {
}
execution_state.validate_and_sync_states(
accounts,
account_identities,
chained_call.program_id,
caller_program_id,
&chained_call.pda_seeds,
@ -202,8 +203,8 @@ impl ExecutionState {
// 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) in accounts.iter().enumerate() {
if account.is_private_pda() {
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"
@ -238,7 +239,7 @@ impl ExecutionState {
/// Validate program pre and post states and populate the execution state.
fn validate_and_sync_states(
&mut self,
accounts: &[PrivacyPreservingCircuitInputAccount],
account_identities: &[InputAccountIdentity],
program_id: ProgramId,
caller_program_id: Option<ProgramId>,
caller_pda_seeds: &[PdaSeed],
@ -316,8 +317,8 @@ impl ExecutionState {
.position(|acc| acc.account_id == pre_account_id)
.expect("Pre state must exist at this point");
let account = &accounts[pre_state_position];
if account.is_public() {
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
@ -341,7 +342,7 @@ impl ExecutionState {
);
}
}
} else if account.is_private_pda() {
} else if account_identity.is_private_pda() {
match claim {
Claim::Authorized => {
assert!(
@ -371,7 +372,7 @@ impl ExecutionState {
}
}
} else {
// Standalone private accounts (mask 1/2): don't enforce the claim semantics.
// 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.
}
@ -475,7 +476,7 @@ fn resolve_authorization_and_record_bindings(
fn compute_circuit_output(
execution_state: ExecutionState,
accounts: &[PrivacyPreservingCircuitInputAccount],
account_identities: &[InputAccountIdentity],
) -> PrivacyPreservingCircuitOutput {
let mut output = PrivacyPreservingCircuitOutput {
public_pre_states: Vec::new(),
@ -488,16 +489,22 @@ fn compute_circuit_output(
};
let states_iter = execution_state.into_states_iter();
assert_eq!(accounts.len(), states_iter.len(), "Invalid accounts length");
assert_eq!(
account_identities.len(),
states_iter.len(),
"Invalid account_identities length"
);
let mut output_index = 0;
for (account, (pre_state, post_state)) in accounts.iter().zip(states_iter) {
match account {
PrivacyPreservingCircuitInputAccount::Public => {
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);
}
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedInit { ssk, nsk } => {
InputAccountIdentity::PrivateAuthorizedInit { ssk, nsk } => {
let npk = NullifierPublicKey::from(nsk);
assert_eq!(
@ -531,7 +538,7 @@ fn compute_circuit_output(
new_nonce,
);
}
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk,
nsk,
membership_proof,
@ -566,7 +573,7 @@ fn compute_circuit_output(
new_nonce,
);
}
PrivacyPreservingCircuitInputAccount::PrivateUnauthorized { npk, ssk } => {
InputAccountIdentity::PrivateUnauthorized { npk, ssk } => {
assert_eq!(
AccountId::from(npk),
pre_state.account_id,
@ -598,7 +605,7 @@ fn compute_circuit_output(
new_nonce,
);
}
PrivacyPreservingCircuitInputAccount::PrivatePdaInit { npk, ssk } => {
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.
@ -628,7 +635,7 @@ fn compute_circuit_output(
new_nonce,
);
}
PrivacyPreservingCircuitInputAccount::PrivatePdaUpdate {
InputAccountIdentity::PrivatePdaUpdate {
ssk,
nsk,
membership_proof,
@ -712,14 +719,14 @@ fn compute_update_nullifier_and_set_digest(
fn main() {
let PrivacyPreservingCircuitInput {
program_outputs,
accounts,
account_identities,
program_id,
} = env::read();
let execution_state =
ExecutionState::derive_from_outputs(&accounts, program_id, program_outputs);
ExecutionState::derive_from_outputs(&account_identities, program_id, program_outputs);
let output = compute_circuit_output(execution_state, &accounts);
let output = compute_circuit_output(execution_state, &account_identities);
env::commit(&output);
}

View File

@ -1075,7 +1075,7 @@ mod tests {
program::Program,
};
use nssa_core::{
PrivacyPreservingCircuitInputAccount, SharedSecretKey,
InputAccountIdentity, SharedSecretKey,
account::AccountWithMetadata,
encryption::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey},
};
@ -1110,7 +1110,7 @@ mod tests {
vec![AccountWithMetadata::new(Account::default(), true, &npk)],
Program::serialize_instruction(0_u128).unwrap(),
vec![
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedInit {
InputAccountIdentity::PrivateAuthorizedInit {
ssk: shared_secret,
nsk,
},

View File

@ -390,7 +390,7 @@ impl WalletCore {
let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove(
pre_states,
instruction_data,
acc_manager.accounts(),
acc_manager.account_identities(),
&program.to_owned(),
)
.unwrap();

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use nssa::{AccountId, PrivateKey};
use nssa_core::{
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInputAccount,
MembershipProof, NullifierPublicKey, NullifierSecretKey, InputAccountIdentity,
SharedSecretKey,
account::{AccountWithMetadata, Nonce},
encryption::{EphemeralPublicKey, ViewingPublicKey},
@ -144,26 +144,26 @@ impl AccountManager {
/// 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 accounts(&self) -> Vec<PrivacyPreservingCircuitInputAccount> {
pub fn account_identities(&self) -> Vec<InputAccountIdentity> {
self.states
.iter()
.map(|state| match state {
State::Public { .. } => PrivacyPreservingCircuitInputAccount::Public,
State::Public { .. } => InputAccountIdentity::Public,
State::Private(pre) => match (pre.nsk, pre.proof.clone()) {
(Some(nsk), Some(membership_proof)) => {
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedUpdate {
InputAccountIdentity::PrivateAuthorizedUpdate {
ssk: pre.ssk,
nsk,
membership_proof,
}
}
(Some(nsk), None) => {
PrivacyPreservingCircuitInputAccount::PrivateAuthorizedInit {
InputAccountIdentity::PrivateAuthorizedInit {
ssk: pre.ssk,
nsk,
}
}
(None, _) => PrivacyPreservingCircuitInputAccount::PrivateUnauthorized {
(None, _) => InputAccountIdentity::PrivateUnauthorized {
npk: pre.npk,
ssk: pre.ssk,
},