diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 80aca1df..8f9088e1 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/test_program_methods/auth_asserting_noop.bin b/artifacts/test_program_methods/auth_asserting_noop.bin new file mode 100644 index 00000000..8c82790e Binary files /dev/null and b/artifacts/test_program_methods/auth_asserting_noop.bin differ diff --git a/nssa/src/program.rs b/nssa/src/program.rs index f9c439bc..b8c3fe77 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -342,6 +342,16 @@ mod tests { } } + #[must_use] + pub fn auth_asserting_noop() -> Self { + use test_program_methods::{AUTH_ASSERTING_NOOP_ELF, AUTH_ASSERTING_NOOP_ID}; + + Self { + id: AUTH_ASSERTING_NOOP_ID, + elf: AUTH_ASSERTING_NOOP_ELF.to_vec(), + } + } + #[must_use] pub fn malicious_authorization_changer() -> Self { use test_program_methods::{ diff --git a/nssa/src/state.rs b/nssa/src/state.rs index fb6b90a7..2687d170 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2425,7 +2425,7 @@ pub mod tests { #[test] fn caller_pda_seeds_authorize_private_pda_for_callee() { let delegator = Program::private_pda_delegator(); - let noop = Program::noop(); + let callee = Program::auth_asserting_noop(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([77; 32]); @@ -2434,12 +2434,13 @@ pub mod tests { let account_id = private_pda_account_id(&delegator.id(), &seed, &npk); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); - let noop_id = noop.id(); - let program_with_deps = ProgramWithDependencies::new(delegator, [(noop_id, noop)].into()); + let callee_id = callee.id(); + let program_with_deps = + ProgramWithDependencies::new(delegator, [(callee_id, callee)].into()); let result = execute_and_prove( vec![pre_state], - Program::serialize_instruction((seed, seed, noop_id)).unwrap(), + Program::serialize_instruction((seed, seed, callee_id)).unwrap(), vec![3], vec![(npk, shared_secret)], vec![], @@ -2460,7 +2461,7 @@ pub mod tests { #[test] fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() { let delegator = Program::private_pda_delegator(); - let noop = Program::noop(); + let callee = Program::auth_asserting_noop(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let claim_seed = PdaSeed::new([77; 32]); @@ -2470,12 +2471,13 @@ pub mod tests { let account_id = private_pda_account_id(&delegator.id(), &claim_seed, &npk); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); - let noop_id = noop.id(); - let program_with_deps = ProgramWithDependencies::new(delegator, [(noop_id, noop)].into()); + let callee_id = callee.id(); + let program_with_deps = + ProgramWithDependencies::new(delegator, [(callee_id, callee)].into()); let result = execute_and_prove( vec![pre_state], - Program::serialize_instruction((claim_seed, wrong_delegated_seed, noop_id)).unwrap(), + Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(), vec![3], vec![(npk, shared_secret)], vec![], diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index d24f9b2b..46a5afa9 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -608,7 +608,13 @@ fn compute_circuit_output( let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); (new_nullifier, new_nonce) } else { - // New private PDA (like mask 2) + // 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(), diff --git a/test_program_methods/guest/src/bin/auth_asserting_noop.rs b/test_program_methods/guest/src/bin/auth_asserting_noop.rs new file mode 100644 index 00000000..0b6d9176 --- /dev/null +++ b/test_program_methods/guest/src/bin/auth_asserting_noop.rs @@ -0,0 +1,40 @@ +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; + +/// A variant of `noop` that asserts every `pre_state.is_authorized == true` before echoing +/// the `post_states`. Any unauthorized `pre_state` panics the guest, failing the whole +/// circuit proof. Used as a callee in private-PDA delegation tests to actually exercise the +/// authorization propagated through `ChainedCall.pda_seeds`. +type Instruction = (); + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + .. + }, + instruction_words, + ) = read_nssa_inputs::(); + + for pre in &pre_states { + assert!( + pre.is_authorized, + "auth_asserting_noop: pre_state {} is not authorized", + pre.account_id + ); + } + + let post_states = pre_states + .iter() + .map(|account| AccountPostState::new(account.account.clone())) + .collect(); + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + pre_states, + post_states, + ) + .write(); +}