refactor: unify PDA AccountId construction via AccountId::for_{public,private}_pda

Addresses the following review comment:

- "I think this should be a constructor `AccountId::for_private_pda`.
  Consider also removing the existing `impl From<(ProgramId, Seed)> for
  AccountId` for public pdas in favor of a `AccountId::for_public_pda`
  to have a unified way of constructing pdas"

I replaced `impl From<(&ProgramId, &PdaSeed)> for AccountId` with
`AccountId::for_public_pda(program_id: &ProgramId, seed: &PdaSeed) ->
Self` and replaced the free function `private_pda_account_id(...)`
with `AccountId::for_private_pda(program_id: &ProgramId, seed:
&PdaSeed, npk: &NullifierPublicKey) -> Self`. Both live in an inherent
`impl AccountId` block in nssa/core/src/program.rs next to the PDA
derivation logic. Migrated all call sites across nssa/core,
nssa/src/state.rs, nssa/src/validated_state_diff.rs,
program_methods/guest/src/bin/privacy_preserving_circuit.rs,
programs/amm/core, programs/associated_token_account/core, the example
tail-call binary, and the ATA tutorial doc. Test function names that
referenced the old free function were also renamed
(private_pda_account_id_* to for_private_pda_*).
This commit is contained in:
Moudy 2026-04-21 12:35:19 +02:00
parent 68d43d7f2b
commit 0183eac5cc
43 changed files with 110 additions and 100 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -52,7 +52,7 @@ The derivation works as follows:
```
seed = SHA256(owner_id || definition_id)
ata_address = AccountId::from((ata_program_id, seed))
ata_address = AccountId::for_public_pda(ata_program_id, seed)
```
Because the computation is pure, anyone who knows the owner and definition can reproduce the exact same ATA address — no network call required.

View File

@ -46,7 +46,7 @@ async fn main() {
let program = Program::new(bytecode).unwrap();
// Compute the PDA to pass it as input account to the public execution
let pda = AccountId::from((&program.id(), &PDA_SEED));
let pda = AccountId::for_public_pda(&program.id(), &PDA_SEED);
let account_ids = vec![pda];
let instruction_data = ();
let nonces = vec![];

View File

@ -37,8 +37,10 @@ impl PdaSeed {
}
}
impl From<(&ProgramId, &PdaSeed)> for AccountId {
fn from(value: (&ProgramId, &PdaSeed)) -> Self {
impl AccountId {
/// Derives an [`AccountId`] for a public PDA from the program ID and seed.
#[must_use]
pub fn for_public_pda(program_id: &ProgramId, seed: &PdaSeed) -> Self {
use risc0_zkvm::sha::{Impl, Sha256 as _};
const PROGRAM_DERIVED_ACCOUNT_ID_PREFIX: &[u8; 32] =
b"/NSSA/v0.2/AccountId/PDA/\x00\x00\x00\x00\x00\x00\x00";
@ -46,9 +48,38 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId {
let mut bytes = [0; 96];
bytes[0..32].copy_from_slice(PROGRAM_DERIVED_ACCOUNT_ID_PREFIX);
let program_id_bytes: &[u8] =
bytemuck::try_cast_slice(value.0).expect("ProgramId should be castable to &[u8]");
bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]");
bytes[32..64].copy_from_slice(program_id_bytes);
bytes[64..].copy_from_slice(&value.1.0);
bytes[64..].copy_from_slice(&seed.0);
Self::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Hash output must be exactly 32 bytes long"),
)
}
/// Derives an [`AccountId`] for a private PDA from the program ID, seed, and nullifier
/// public key.
///
/// Unlike public PDAs ([`AccountId::for_public_pda`]), this includes the `npk` in the
/// derivation, making the address unique per group of controllers sharing viewing keys.
#[must_use]
pub fn for_private_pda(
program_id: &ProgramId,
seed: &PdaSeed,
npk: &NullifierPublicKey,
) -> Self {
use risc0_zkvm::sha::{Impl, Sha256 as _};
const PRIVATE_PDA_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/PrivatePDA/\x00";
let mut bytes = [0_u8; 128];
bytes[0..32].copy_from_slice(PRIVATE_PDA_PREFIX);
let program_id_bytes: &[u8] =
bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]");
bytes[32..64].copy_from_slice(program_id_bytes);
bytes[64..96].copy_from_slice(&seed.0);
bytes[96..128].copy_from_slice(&npk.to_byte_array());
Self::new(
Impl::hash_bytes(&bytes)
.as_bytes()
@ -67,9 +98,9 @@ pub struct ChainedCall {
pub instruction_data: InstructionData,
/// 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 supplied npk
/// for that `pre_state`). Programs stay privacy-agnostic: they emit seeds, the circuit
/// `AccountId::for_public_pda(&caller, seed)` (mask 0) or a private PDA derivation
/// `AccountId::for_private_pda(&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>,
}
@ -122,8 +153,8 @@ pub enum Claim {
Authorized,
/// 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 supplied npk for that
/// `AccountId::for_public_pda(&program_id, &seed)`, mask 3 uses
/// `AccountId::for_private_pda(&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),
@ -488,40 +519,12 @@ pub enum ExecutionValidationError {
},
}
/// Derives an [`AccountId`] for a private PDA from the program ID, seed, and nullifier public key.
///
/// Unlike public PDAs (`AccountId::from((&ProgramId, &PdaSeed))`), this includes the `npk` in the
/// derivation, making the address unique per group of controllers sharing viewing keys.
#[must_use]
pub fn private_pda_account_id(
program_id: &ProgramId,
seed: &PdaSeed,
npk: &NullifierPublicKey,
) -> AccountId {
use risc0_zkvm::sha::{Impl, Sha256 as _};
const PRIVATE_PDA_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/PrivatePDA/\x00";
let mut bytes = [0_u8; 128];
bytes[0..32].copy_from_slice(PRIVATE_PDA_PREFIX);
let program_id_bytes: &[u8] =
bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]");
bytes[32..64].copy_from_slice(program_id_bytes);
bytes[64..96].copy_from_slice(&seed.0);
bytes[96..128].copy_from_slice(&npk.to_byte_array());
AccountId::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Hash output must be exactly 32 bytes long"),
)
}
/// Computes the set of public-PDA `AccountId`s the callee is authorized to mutate.
///
/// 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 supplied npk for
/// that `pre_state`.
/// `pre_state` against [`AccountId::for_private_pda`] with the supplied npk for that
/// `pre_state`.
#[must_use]
pub fn compute_authorized_pdas(
caller_program_id: Option<ProgramId>,
@ -532,7 +535,7 @@ pub fn compute_authorized_pdas(
};
pda_seeds
.iter()
.map(|seed| AccountId::from((&caller, seed)))
.map(|seed| AccountId::for_public_pda(&caller, seed))
.collect()
}
@ -845,13 +848,13 @@ mod tests {
assert_eq!(account_post_state.account_mut(), &mut account);
}
// ---- private_pda_account_id tests ----
// ---- AccountId::for_private_pda tests ----
/// Pins `private_pda_account_id` against a hardcoded expected output for a specific
/// Pins `AccountId::for_private_pda` against a hardcoded expected output for a specific
/// `(program_id, seed, npk)` triple. Any change to `PRIVATE_PDA_PREFIX`, byte ordering,
/// or the underlying hash breaks this test.
#[test]
fn private_pda_account_id_matches_pinned_value() {
fn for_private_pda_matches_pinned_value() {
let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]);
@ -859,68 +862,71 @@ mod tests {
132, 198, 103, 173, 244, 211, 188, 217, 249, 99, 126, 205, 152, 120, 192, 47, 13, 53,
133, 3, 17, 69, 92, 243, 140, 94, 182, 211, 218, 75, 215, 45,
]);
assert_eq!(private_pda_account_id(&program_id, &seed, &npk), expected);
assert_eq!(
AccountId::for_private_pda(&program_id, &seed, &npk),
expected
);
}
/// Two groups with different viewing keys at the same (program, seed) get different addresses.
#[test]
fn private_pda_account_id_differs_for_different_npk() {
fn for_private_pda_differs_for_different_npk() {
let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]);
let npk_a = NullifierPublicKey([3; 32]);
let npk_b = NullifierPublicKey([4; 32]);
assert_ne!(
private_pda_account_id(&program_id, &seed, &npk_a),
private_pda_account_id(&program_id, &seed, &npk_b),
AccountId::for_private_pda(&program_id, &seed, &npk_a),
AccountId::for_private_pda(&program_id, &seed, &npk_b),
);
}
/// Different seeds produce different addresses, even with the same program and npk.
#[test]
fn private_pda_account_id_differs_for_different_seed() {
fn for_private_pda_differs_for_different_seed() {
let program_id: ProgramId = [1; 8];
let seed_a = PdaSeed::new([2; 32]);
let seed_b = PdaSeed::new([5; 32]);
let npk = NullifierPublicKey([3; 32]);
assert_ne!(
private_pda_account_id(&program_id, &seed_a, &npk),
private_pda_account_id(&program_id, &seed_b, &npk),
AccountId::for_private_pda(&program_id, &seed_a, &npk),
AccountId::for_private_pda(&program_id, &seed_b, &npk),
);
}
/// Different programs produce different addresses, even with the same seed and npk.
#[test]
fn private_pda_account_id_differs_for_different_program_id() {
fn for_private_pda_differs_for_different_program_id() {
let program_id_a: ProgramId = [1; 8];
let program_id_b: ProgramId = [9; 8];
let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]);
assert_ne!(
private_pda_account_id(&program_id_a, &seed, &npk),
private_pda_account_id(&program_id_b, &seed, &npk),
AccountId::for_private_pda(&program_id_a, &seed, &npk),
AccountId::for_private_pda(&program_id_b, &seed, &npk),
);
}
/// A private PDA at the same (program, seed) has a different address than a public PDA,
/// because the private formula uses a different prefix and includes npk.
#[test]
fn private_pda_account_id_differs_from_public_pda() {
fn for_private_pda_differs_from_public_pda() {
let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]);
let private_id = private_pda_account_id(&program_id, &seed, &npk);
let public_id = AccountId::from((&program_id, &seed));
let private_id = AccountId::for_private_pda(&program_id, &seed, &npk);
let public_id = AccountId::for_public_pda(&program_id, &seed);
assert_ne!(private_id, public_id);
}
/// A private PDA address differs from a standard private account address at the same `npk`,
/// because the private PDA formula includes `program_id` and `seed`.
#[test]
fn private_pda_account_id_differs_from_standard_private() {
fn for_private_pda_differs_from_standard_private() {
let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]);
let private_pda_id = private_pda_account_id(&program_id, &seed, &npk);
let private_pda_id = AccountId::for_private_pda(&program_id, &seed, &npk);
let standard_private_id = AccountId::from(&npk);
assert_ne!(private_pda_id, standard_private_id);
}
@ -933,7 +939,7 @@ mod tests {
let caller: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]);
let result = compute_authorized_pdas(Some(caller), &[seed]);
let expected = AccountId::from((&caller, &seed));
let expected = AccountId::for_public_pda(&caller, &seed);
assert!(result.contains(&expected));
assert_eq!(result.len(), 1);
}

View File

@ -368,7 +368,7 @@ pub mod tests {
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
program::{
BlockValidityWindow, ExecutionValidationError, PdaSeed, ProgramId,
TimestampValidityWindow, WrappedBalanceSum, private_pda_account_id,
TimestampValidityWindow, WrappedBalanceSum,
},
};
@ -2352,7 +2352,7 @@ pub mod tests {
/// Happy path: a program claims a new mask-3 account via `Claim::Pda(seed)`. The circuit
/// 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
/// position, derives `AccountId` via `AccountId::for_private_pda(program_id, seed, npk)`, and
/// asserts it equals the `pre_state`'s `account_id`. The equality both validates the claim
/// and binds the supplied npk to the `account_id`.
#[test]
@ -2363,7 +2363,7 @@ pub mod tests {
let seed = PdaSeed::new([42; 32]);
let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk());
let account_id = private_pda_account_id(&program.id(), &seed, &npk);
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let result = execute_and_prove(
@ -2385,7 +2385,7 @@ pub mod tests {
}
/// 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.
/// `AccountId::for_private_pda(program, claim_seed, npk)`. The claim equality check rejects.
#[test]
fn private_pda_npk_mismatch_fails() {
// `keys_a` produces the `pre_state`'s `account_id` (the registered pair), `keys_b` is
@ -2399,9 +2399,9 @@ pub mod tests {
let shared_secret = SharedSecretKey::new(&[55; 32], &keys_b.vpk());
// `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
// `AccountId::for_private_pda(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 account_id = AccountId::for_private_pda(&program.id(), &seed, &npk_a);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let result = execute_and_prove(
@ -2421,7 +2421,7 @@ pub mod tests {
/// private PDA via `Claim::Pda(seed)`, then chains to a callee (`noop`) delegating the same
/// seed via `ChainedCall.pda_seeds`. In the callee's step, the `pre_state`'s authorization
/// is established via the private derivation
/// `private_pda_account_id(delegator, seed, npk) == pre.account_id`.
/// `AccountId::for_private_pda(delegator, seed, npk) == pre.account_id`.
#[test]
fn caller_pda_seeds_authorize_private_pda_for_callee() {
let delegator = Program::private_pda_delegator();
@ -2431,7 +2431,7 @@ pub mod tests {
let seed = PdaSeed::new([77; 32]);
let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk());
let account_id = private_pda_account_id(&delegator.id(), &seed, &npk);
let account_id = AccountId::for_private_pda(&delegator.id(), &seed, &npk);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let callee_id = callee.id();
@ -2468,7 +2468,7 @@ pub mod tests {
let wrong_delegated_seed = PdaSeed::new([88; 32]);
let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk());
let account_id = private_pda_account_id(&delegator.id(), &claim_seed, &npk);
let account_id = AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let callee_id = callee.id();
@ -2505,8 +2505,8 @@ pub mod tests {
let shared_a = SharedSecretKey::new(&[66; 32], &keys_a.vpk());
let shared_b = SharedSecretKey::new(&[77; 32], &keys_b.vpk());
let account_a = private_pda_account_id(&program.id(), &seed, &keys_a.npk());
let account_b = private_pda_account_id(&program.id(), &seed, &keys_b.npk());
let account_a = AccountId::for_private_pda(&program.id(), &seed, &keys_a.npk());
let account_b = AccountId::for_private_pda(&program.id(), &seed, &keys_b.npk());
let pre_a = AccountWithMetadata::new(Account::default(), false, account_a);
let pre_b = AccountWithMetadata::new(Account::default(), false, account_b);
@ -2533,7 +2533,7 @@ pub mod tests {
/// `ChainedCall.pda_seeds`, so position 0 is never bound and the assertion fires.
// TODO: a follow-up PR in the Private PDAs series needs to let the wallet supply a
// `(seed, original_owner_program_id)` side input per mask-3 `pre_state` so the circuit
// can re-verify `private_pda_account_id(owner, seed, npk) == pre.account_id` without a
// can re-verify `AccountId::for_private_pda(owner, seed, npk) == pre.account_id` without a
// claim.
#[test]
fn private_pda_top_level_reuse_rejected_by_binding_check() {
@ -2545,7 +2545,7 @@ pub mod tests {
// Simulate a previously-claimed private PDA: program_owner != DEFAULT, is_authorized =
// true, account_id derived via the private formula.
let account_id = private_pda_account_id(&program.id(), &seed, &npk);
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk);
let owned_pre_state = AccountWithMetadata::new(
Account {
program_owner: program.id(),
@ -2967,7 +2967,7 @@ pub mod tests {
fn execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() {
let chain_caller = Program::chain_caller();
let pda_seed = PdaSeed::new([37; 32]);
let from = AccountId::from((&chain_caller.id(), &pda_seed));
let from = AccountId::for_public_pda(&chain_caller.id(), &pda_seed);
let to = AccountId::new([2; 32]);
let initial_balance = 1000;
let initial_data = [(from, initial_balance), (to, 0)];
@ -3279,7 +3279,8 @@ pub mod tests {
let pinata_definition_id = AccountId::new([1; 32]);
let pinata_token_definition_id = AccountId::new([2; 32]);
// Total supply of pinata token will be in an account under a PDA.
let pinata_token_holding_id = AccountId::from((&pinata_token.id(), &PdaSeed::new([0; 32])));
let pinata_token_holding_id =
AccountId::for_public_pda(&pinata_token.id(), &PdaSeed::new([0; 32]));
let winner_token_holding_id = AccountId::new([3; 32]);
let expected_winner_account_holding = token_core::TokenHolding::Fungible {
@ -4296,8 +4297,8 @@ pub mod tests {
let callback = Program::flash_swap_callback();
let token = Program::authenticated_transfer_program();
let vault_id = AccountId::from((&initiator.id(), &PdaSeed::new([0_u8; 32])));
let receiver_id = AccountId::from((&callback.id(), &PdaSeed::new([1_u8; 32])));
let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32]));
let receiver_id = AccountId::for_public_pda(&callback.id(), &PdaSeed::new([1_u8; 32]));
let initial_balance: u128 = 1000;
let amount_out: u128 = 100;
@ -4347,8 +4348,8 @@ pub mod tests {
let callback = Program::flash_swap_callback();
let token = Program::authenticated_transfer_program();
let vault_id = AccountId::from((&initiator.id(), &PdaSeed::new([0_u8; 32])));
let receiver_id = AccountId::from((&callback.id(), &PdaSeed::new([1_u8; 32])));
let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32]));
let receiver_id = AccountId::for_public_pda(&callback.id(), &PdaSeed::new([1_u8; 32]));
let initial_balance: u128 = 1000;
let amount_out: u128 = 100;
@ -4405,8 +4406,8 @@ pub mod tests {
let callback = Program::flash_swap_callback();
let token = Program::authenticated_transfer_program();
let vault_id = AccountId::from((&initiator.id(), &PdaSeed::new([0_u8; 32])));
let receiver_id = AccountId::from((&callback.id(), &PdaSeed::new([1_u8; 32])));
let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32]));
let receiver_id = AccountId::for_public_pda(&callback.id(), &PdaSeed::new([1_u8; 32]));
let initial_balance: u128 = 1000;
@ -4454,7 +4455,7 @@ pub mod tests {
let initiator = Program::flash_swap_initiator();
let token = Program::authenticated_transfer_program();
let vault_id = AccountId::from((&initiator.id(), &PdaSeed::new([0_u8; 32])));
let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32]));
let vault_account = Account {
program_owner: token.id(),

View File

@ -225,7 +225,7 @@ impl ValidatedStateDiff {
// The program can only claim accounts that correspond to the PDAs it is
// authorized to claim. The public-execution path only sees public
// accounts, so the public-PDA derivation is the correct formula here.
let pda = AccountId::from((&chained_call.program_id, &seed));
let pda = AccountId::for_public_pda(&chained_call.program_id, &seed);
ensure!(
account_id == pda,
InvalidProgramBehaviorError::MismatchedPdaClaim {

View File

@ -12,7 +12,7 @@ use nssa_core::{
program::{
AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID,
MAX_NUMBER_CHAINED_CALLS, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow,
private_pda_account_id, validate_execution,
validate_execution,
},
};
use risc0_zkvm::{guest::env, serde::to_vec};
@ -24,7 +24,8 @@ struct ExecutionState {
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 `private_pda_account_id(program_id, seed, npk)` check.
/// 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
@ -279,12 +280,13 @@ impl ExecutionState {
let matched_caller_seed: Option<(PdaSeed, bool, ProgramId)> = caller_program_id
.and_then(|caller| {
caller_pda_seeds.iter().find_map(|seed| {
if AccountId::from((&caller, seed)) == pre_account_id {
if AccountId::for_public_pda(&caller, seed) == pre_account_id {
return Some((*seed, false, caller));
}
if let Some(npk) =
private_pda_npk_by_position.get(&pre_state_position)
&& private_pda_account_id(&caller, seed, npk) == pre_account_id
&& AccountId::for_private_pda(&caller, seed, npk)
== pre_account_id
{
return Some((*seed, true, caller));
}
@ -343,7 +345,7 @@ impl ExecutionState {
);
}
Claim::Pda(seed) => {
let pda = AccountId::from((&program_id, &seed));
let pda = AccountId::for_public_pda(&program_id, &seed);
assert_eq!(
pre_account_id, pda,
"Invalid PDA claim for account {pre_account_id} which does not match derived PDA {pda}"
@ -367,7 +369,7 @@ impl ExecutionState {
let npk = private_pda_npk_by_position.get(&pre_state_position).expect(
"private PDA pre_state must have an npk in the position map",
);
let pda = private_pda_account_id(&program_id, &seed, npk);
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}"
@ -576,10 +578,11 @@ fn compute_circuit_output(
// 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
// 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.
// 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, shared_secret)) = private_keys_iter.next() else {
panic!("Missing private account key");
};

View File

@ -135,10 +135,10 @@ pub fn compute_pool_pda(
definition_token_a_id: AccountId,
definition_token_b_id: AccountId,
) -> AccountId {
AccountId::from((
AccountId::for_public_pda(
&amm_program_id,
&compute_pool_pda_seed(definition_token_a_id, definition_token_b_id),
))
)
}
#[must_use]
@ -175,10 +175,10 @@ pub fn compute_vault_pda(
pool_id: AccountId,
definition_token_id: AccountId,
) -> AccountId {
AccountId::from((
AccountId::for_public_pda(
&amm_program_id,
&compute_vault_pda_seed(pool_id, definition_token_id),
))
)
}
#[must_use]
@ -199,7 +199,7 @@ pub fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId
#[must_use]
pub fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId {
AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id)))
AccountId::for_public_pda(&amm_program_id, &compute_liquidity_token_pda_seed(pool_id))
}
#[must_use]

View File

@ -61,7 +61,7 @@ pub fn compute_ata_seed(owner_id: AccountId, definition_id: AccountId) -> PdaSee
}
pub fn get_associated_token_account_id(ata_program_id: &ProgramId, seed: &PdaSeed) -> AccountId {
AccountId::from((ata_program_id, seed))
AccountId::for_public_pda(ata_program_id, seed)
}
/// Verify the ATA's address matches `(ata_program_id, owner, definition)` and return