diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 0d80e26f..af1ceacd 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -27,9 +27,10 @@ pub struct PrivacyPreservingCircuitInput { /// Program ID. pub program_id: ProgramId, /// Private PDA info for mask-3 accounts. - /// Unlike the other private_account_* fields which are parallel arrays indexed by private + /// Unlike the other `private_account_*` fields which are parallel arrays indexed by private /// account position, this is a separate lookup table. The circuit matches entries by - /// (program_id, seed) against the chained calls' pda_seeds to resolve private PDA authorization. + /// (`program_id`, `seed`) against the chained calls' `pda_seeds` to resolve private PDA + /// authorization. pub private_pda_info: Vec<(ProgramId, PdaSeed, NullifierPublicKey)>, } diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 1e3143de..610b8e48 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -58,34 +58,6 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId { } } -/// 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 = [0u8; 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"), - ) -} - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct ChainedCall { /// The program ID of the program to execute. @@ -436,6 +408,34 @@ impl WrappedBalanceSum { } } +/// 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"), + ) +} + #[must_use] pub fn compute_authorized_pdas( caller_program_id: Option,