docs: drop "wallet" references from nssa crate

Addresses the following review comments:

- "I'd keep this crate independent of wallet references"
  I replaced all with "supplied npk".

- Rename request on locals attested_keys/wallet_keys
  In mask_3_wallet_npk_mismatch_panics the two key sets play distinct
  roles, one produces the pre_state's account_id (the registered pair)
  and the other is supplied in private_account_keys as the mismatched
  npk. Collapsing both to `keys` would be misleading. I renamed to
  keys_a and keys_b with an inline comment noting which one is the
  registered one and which one is mismatched.
This commit is contained in:
Moudy 2026-04-21 01:02:22 +02:00
parent 34f8b6cac8
commit 00cae12d41
3 changed files with 24 additions and 22 deletions

View File

@ -68,8 +68,8 @@ pub struct ChainedCall {
/// PDA seeds authorized for the callee. For each callee `pre_state`, the outer circuit
/// checks whether its `AccountId` matches a public PDA derivation
/// `AccountId::from((&caller, seed))` (mask 0) or a private PDA derivation
/// `private_pda_account_id(&caller, seed, npk)` (mask 3, where `npk` is the wallet-supplied
/// npk for that `pre_state`). Programs stay privacy-agnostic: they emit seeds, the circuit
/// `private_pda_account_id(&caller, seed, npk)` (mask 3, where `npk` is the supplied npk
/// for that `pre_state`). Programs stay privacy-agnostic: they emit seeds, the circuit
/// resolves public vs private based on the `pre_state`'s mask.
pub pda_seeds: Vec<PdaSeed>,
}
@ -123,7 +123,7 @@ pub enum Claim {
/// The program requests ownership of the account through a PDA. The `pre_state`'s
/// visibility mask selects the derivation formula: mask 0 uses
/// `AccountId::from((&program_id, &seed))`, mask 3 uses
/// `private_pda_account_id(&program_id, &seed, &npk)` with the wallet-supplied npk for that
/// `private_pda_account_id(&program_id, &seed, &npk)` with the supplied npk for that
/// `pre_state`. Programs stay privacy-agnostic: they emit a seed, the circuit resolves the
/// rest from the mask.
Pda(PdaSeed),
@ -520,8 +520,8 @@ pub fn private_pda_account_id(
///
/// Returns only public-form derivations, suitable for contexts where all accounts are public
/// (e.g. the public-execution path). The privacy circuit must additionally check each mask-3
/// `pre_state` against `private_pda_account_id(caller, seed, npk)` with the wallet-supplied
/// npk for that `pre_state`.
/// `pre_state` against `private_pda_account_id(caller, seed, npk)` with the supplied npk for
/// that `pre_state`.
#[must_use]
pub fn compute_authorized_pdas(
caller_program_id: Option<ProgramId>,

View File

@ -2315,9 +2315,9 @@ pub mod tests {
}
/// A mask-3 account that no program claims via `Claim::Pda` and no caller authorizes via
/// `ChainedCall.pda_seeds` has no binding between its wallet-supplied npk and its
/// `account_id`, so the circuit must reject. Here `simple_balance_transfer` emits no claim
/// for the second account, leaving position 1 unbound.
/// `ChainedCall.pda_seeds` has no binding between its supplied npk and its `account_id`,
/// so the circuit must reject. Here `simple_balance_transfer` emits no claim for the
/// second account, leaving position 1 unbound.
#[test]
fn mask_3_without_binding_panics() {
let program = Program::simple_balance_transfer();
@ -2354,7 +2354,7 @@ pub mod tests {
/// reads the npk for that `pre_state` from `private_account_keys` at the `pre_state`'s
/// position, derives `AccountId` via `private_pda_account_id(program_id, seed, npk)`, and
/// asserts it equals the `pre_state`'s `account_id`. The equality both validates the claim
/// and binds the wallet-supplied npk to the `account_id`.
/// and binds the supplied npk to the `account_id`.
#[test]
fn mask_3_private_pda_claim_succeeds() {
let program = Program::pda_claimer();
@ -2384,29 +2384,31 @@ pub mod tests {
assert!(output.public_post_states.is_empty());
}
/// The wallet supplies an npk that does not match the `pre_state`'s `account_id` under
/// An npk is supplied that does not match the `pre_state`'s `account_id` under
/// `private_pda_account_id(program, claim_seed, npk)`. The claim equality check rejects.
#[test]
fn mask_3_wallet_npk_mismatch_panics() {
// `keys_a` produces the `pre_state`'s `account_id` (the registered pair), `keys_b` is
// the mismatched pair supplied in `private_account_keys` for that pre_state.
let program = Program::pda_claimer();
let attested_keys = test_private_account_keys_1();
let wallet_keys = test_private_account_keys_2();
let attested_npk = attested_keys.npk();
let wallet_npk = wallet_keys.npk();
let keys_a = test_private_account_keys_1();
let keys_b = test_private_account_keys_2();
let npk_a = keys_a.npk();
let npk_b = keys_b.npk();
let seed = PdaSeed::new([42; 32]);
let shared_secret = SharedSecretKey::new(&[55; 32], &wallet_keys.vpk());
let shared_secret = SharedSecretKey::new(&[55; 32], &keys_b.vpk());
// account_id is derived from `attested_npk`, but the wallet provides `wallet_npk` for
// this pre_state. `private_pda_account_id(program, seed, wallet_npk) != account_id`, so
// the claim check in the circuit must reject.
let account_id = private_pda_account_id(&program.id(), &seed, &attested_npk);
// `account_id` is derived from `npk_a`, but `npk_b` is supplied for this pre_state.
// `private_pda_account_id(program, seed, npk_b) != account_id`, so the claim check in
// the circuit must reject.
let account_id = private_pda_account_id(&program.id(), &seed, &npk_a);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![3],
vec![(wallet_npk, shared_secret)],
vec![(npk_b, shared_secret)],
vec![],
vec![None],
&program.into(),

View File

@ -187,7 +187,7 @@ impl ExecutionState {
// 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 wallet-supplied npk and the account_id, and must be rejected.
// the supplied npk and the account_id, and must be rejected.
for (pos, &mask) in visibility_mask.iter().enumerate() {
if mask == 3 {
assert!(
@ -575,7 +575,7 @@ fn compute_circuit_output(
.unwrap_or_else(|| panic!("Too many private accounts, output index overflow"));
}
3 => {
// Private PDA account. The wallet-supplied npk has already been bound to
// 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 `private_pda_account_id(owner, seed, npk) == account_id`. The post-loop