2025-11-07 19:21:43 -03:00
use std ::collections ::HashSet ;
2026-03-20 13:16:52 -03:00
use borsh ::{ BorshDeserialize , BorshSerialize } ;
2025-11-26 00:27:20 +03:00
use risc0_zkvm ::{ DeserializeOwned , guest ::env , serde ::Deserializer } ;
2025-08-14 14:09:04 -03:00
use serde ::{ Deserialize , Serialize } ;
2025-08-06 20:05:04 -03:00
2026-03-28 03:57:14 -03:00
use crate ::{
2026-05-01 00:05:50 -03:00
BlockId , Identifier , NullifierPublicKey , Timestamp ,
2026-03-28 03:57:14 -03:00
account ::{ Account , AccountId , AccountWithMetadata } ,
} ;
2025-11-26 00:27:20 +03:00
2025-08-08 16:19:50 -03:00
pub const DEFAULT_PROGRAM_ID : ProgramId = [ 0 ; 8 ] ;
2025-11-07 20:42:00 -03:00
pub const MAX_NUMBER_CHAINED_CALLS : usize = 10 ;
2025-08-06 20:05:04 -03:00
2026-03-04 18:42:33 +03:00
pub type ProgramId = [ u32 ; 8 ] ;
pub type InstructionData = Vec < u32 > ;
2025-08-14 14:30:04 -03:00
pub struct ProgramInput < T > {
2026-04-01 21:15:02 +02:00
pub self_program_id : ProgramId ,
2026-04-02 15:35:02 +02:00
pub caller_program_id : Option < ProgramId > ,
2025-08-14 14:30:04 -03:00
pub pre_states : Vec < AccountWithMetadata > ,
pub instruction : T ,
}
2026-03-03 23:21:08 +03:00
/// A 32-byte seed used to compute a *Program-Derived `AccountId`* (PDA).
2025-11-28 17:09:38 -03:00
///
2025-12-02 10:48:21 -03:00
/// Each program can derive up to `2^256` unique account IDs by choosing different
2025-11-28 17:09:38 -03:00
/// seeds. PDAs allow programs to control namespaced account identifiers without
/// collisions between programs.
2026-05-08 22:15:57 -03:00
#[ derive(
Debug ,
Clone ,
Copy ,
Eq ,
PartialEq ,
Hash ,
Serialize ,
Deserialize ,
BorshSerialize ,
BorshDeserialize ,
) ]
2025-11-27 12:08:27 -03:00
pub struct PdaSeed ( [ u8 ; 32 ] ) ;
2025-11-27 13:49:56 -03:00
impl PdaSeed {
2026-03-03 23:21:08 +03:00
#[ must_use ]
2025-12-12 13:00:16 -03:00
pub const fn new ( value : [ u8 ; 32 ] ) -> Self {
2025-11-27 13:49:56 -03:00
Self ( value )
}
2026-05-01 01:13:02 -03:00
#[ must_use ]
pub const fn as_bytes ( & self ) -> & [ u8 ; 32 ] {
& self . 0
}
2025-11-27 13:49:56 -03:00
}
2026-04-27 02:43:26 +02:00
impl AsRef < [ u8 ] > for PdaSeed {
fn as_ref ( & self ) -> & [ u8 ] {
& self . 0
}
}
2026-05-07 01:38:41 -03:00
/// Discriminates the type of private account a ciphertext belongs to, carrying the data needed
/// to reconstruct the account's [`AccountId`] on the receiver side.
///
/// [`AccountId`]: crate::account::AccountId
2026-05-08 22:13:56 -03:00
#[ derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize) ]
2026-05-07 01:38:41 -03:00
pub enum PrivateAccountKind {
Regular ( Identifier ) ,
Pda {
program_id : ProgramId ,
seed : PdaSeed ,
identifier : Identifier ,
} ,
}
impl PrivateAccountKind {
2026-05-08 22:13:56 -03:00
/// Borsh layout (all integers little-endian, variant index is u8):
2026-05-07 13:21:05 -03:00
///
/// ```text
/// Regular(ident): 0x00 || ident (16 LE) || [0u8; 64]
/// Pda { program_id, seed, ident }: 0x01 || program_id (32) || seed (32) || ident (16 LE)
/// ```
2026-05-08 22:13:56 -03:00
///
/// Both variants are zero-padded to the same length so all ciphertexts are the same size,
/// preventing observers from distinguishing `Regular` from `Pda` via ciphertext length.
/// `HEADER_LEN` equals the borsh size of the largest variant (`Pda`): 1 + 32 + 32 + 16 = 81.
2026-05-07 01:38:41 -03:00
pub const HEADER_LEN : usize = 81 ;
#[ must_use ]
2026-05-07 13:21:05 -03:00
pub const fn identifier ( & self ) -> Identifier {
2026-05-07 01:38:41 -03:00
match self {
2026-05-07 13:21:05 -03:00
Self ::Regular ( identifier ) | Self ::Pda { identifier , .. } = > * identifier ,
2026-05-07 01:38:41 -03:00
}
}
#[ must_use ]
pub fn to_header_bytes ( & self ) -> [ u8 ; Self ::HEADER_LEN ] {
2026-05-08 22:15:57 -03:00
let mut bytes = [ 0_ u8 ; Self ::HEADER_LEN ] ;
2026-05-08 22:13:56 -03:00
let serialized = borsh ::to_vec ( self ) . expect ( " borsh serialization is infallible " ) ;
bytes [ .. serialized . len ( ) ] . copy_from_slice ( & serialized ) ;
2026-05-07 01:38:41 -03:00
bytes
}
#[ cfg(feature = " host " ) ]
#[ must_use ]
pub fn from_header_bytes ( bytes : & [ u8 ; Self ::HEADER_LEN ] ) -> Option < Self > {
2026-05-08 22:13:56 -03:00
BorshDeserialize ::deserialize ( & mut bytes . as_ref ( ) ) . ok ( )
2026-05-07 01:38:41 -03:00
}
}
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_*).
2026-04-21 12:35:19 +02:00
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 {
2026-03-04 18:42:33 +03:00
use risc0_zkvm ::sha ::{ Impl , Sha256 as _ } ;
2025-11-27 13:10:38 -03:00
const PROGRAM_DERIVED_ACCOUNT_ID_PREFIX : & [ u8 ; 32 ] =
b " /NSSA/v0.2/AccountId/PDA/ \x00 \x00 \x00 \x00 \x00 \x00 \x00 " ;
let mut bytes = [ 0 ; 96 ] ;
bytes [ 0 .. 32 ] . copy_from_slice ( PROGRAM_DERIVED_ACCOUNT_ID_PREFIX ) ;
let program_id_bytes : & [ u8 ] =
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_*).
2026-04-21 12:35:19 +02:00
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 ( & seed . 0 ) ;
Self ::new (
Impl ::hash_bytes ( & bytes )
. as_bytes ( )
. try_into ( )
. expect ( " Hash output must be exactly 32 bytes long " ) ,
)
}
2026-05-01 00:05:50 -03:00
/// Derives an [`AccountId`] for a private PDA from the program ID, seed, nullifier public
/// key, and identifier.
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_*).
2026-04-21 12:35:19 +02:00
///
/// 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.
2026-05-01 00:05:50 -03:00
/// The `identifier` further diversifies the address, so a single `(program_id, seed, npk)`
/// tuple controls a family of 2^128 addresses.
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_*).
2026-04-21 12:35:19 +02:00
#[ must_use ]
pub fn for_private_pda (
program_id : & ProgramId ,
seed : & PdaSeed ,
npk : & NullifierPublicKey ,
2026-05-01 00:05:50 -03:00
identifier : Identifier ,
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_*).
2026-04-21 12:35:19 +02:00
) -> Self {
use risc0_zkvm ::sha ::{ Impl , Sha256 as _ } ;
const PRIVATE_PDA_PREFIX : & [ u8 ; 32 ] = b " /LEE/v0.3/AccountId/PrivatePDA/ \x00 " ;
2026-05-01 00:05:50 -03:00
let mut bytes = [ 0_ u8 ; 144 ] ;
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_*).
2026-04-21 12:35:19 +02:00
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] " ) ;
2025-11-27 13:10:38 -03:00
bytes [ 32 .. 64 ] . copy_from_slice ( program_id_bytes ) ;
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_*).
2026-04-21 12:35:19 +02:00
bytes [ 64 .. 96 ] . copy_from_slice ( & seed . 0 ) ;
bytes [ 96 .. 128 ] . copy_from_slice ( & npk . to_byte_array ( ) ) ;
2026-05-01 00:05:50 -03:00
bytes [ 128 .. 144 ] . copy_from_slice ( & identifier . to_le_bytes ( ) ) ;
2026-03-09 18:27:56 +03:00
Self ::new (
2025-11-27 13:10:38 -03:00
Impl ::hash_bytes ( & bytes )
. as_bytes ( )
. try_into ( )
. expect ( " Hash output must be exactly 32 bytes long " ) ,
)
}
2026-05-04 21:40:30 -03:00
/// Derives the [`AccountId`] for a private account from the nullifier public key and kind.
#[ must_use ]
pub fn for_private_account ( npk : & NullifierPublicKey , kind : & PrivateAccountKind ) -> Self {
match kind {
2026-05-08 21:41:48 -03:00
PrivateAccountKind ::Regular ( identifier ) = > {
Self ::for_regular_private_account ( npk , * identifier )
}
2026-05-07 01:41:35 -03:00
PrivateAccountKind ::Pda {
program_id ,
seed ,
identifier ,
} = > Self ::for_private_pda ( program_id , seed , npk , * identifier ) ,
2026-05-04 21:40:30 -03:00
}
}
2025-11-27 13:10:38 -03:00
}
2026-01-16 04:11:33 +03:00
#[ derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq) ]
2025-10-29 00:40:56 -03:00
pub struct ChainedCall {
2026-03-10 00:17:43 +03:00
/// The program ID of the program to execute.
2025-10-29 00:40:56 -03:00
pub program_id : ProgramId ,
2026-01-16 04:11:33 +03:00
pub pre_states : Vec < AccountWithMetadata > ,
2026-03-10 00:17:43 +03:00
/// The instruction data to pass.
2025-10-29 00:40:56 -03:00
pub instruction_data : InstructionData ,
2026-04-22 15:34:15 +02:00
/// PDA seeds authorized for the callee. For each seed, the callee is authorized to
/// mutate the `AccountId` derived from `(caller_program_id, seed)`, regardless of
/// whether the account is public or private.
2025-11-27 13:10:38 -03:00
pub pda_seeds : Vec < PdaSeed > ,
2025-10-29 00:40:56 -03:00
}
2026-01-16 04:11:33 +03:00
impl ChainedCall {
/// Creates a new chained call serializing the given instruction.
pub fn new < I : Serialize > (
program_id : ProgramId ,
pre_states : Vec < AccountWithMetadata > ,
instruction : & I ,
) -> Self {
Self {
program_id ,
pre_states ,
instruction_data : risc0_zkvm ::serde ::to_vec ( instruction )
. expect ( " Serialization to Vec<u32> should not fail " ) ,
pda_seeds : Vec ::new ( ) ,
}
}
2026-03-03 23:21:08 +03:00
#[ must_use ]
2026-01-16 04:11:33 +03:00
pub fn with_pda_seeds ( mut self , pda_seeds : Vec < PdaSeed > ) -> Self {
self . pda_seeds = pda_seeds ;
self
}
}
2025-12-04 10:10:01 -03:00
/// Represents the final state of an `Account` after a program execution.
2026-03-09 18:27:56 +03:00
///
2025-12-04 10:10:01 -03:00
/// A post state may optionally request that the executing program
/// becomes the owner of the account (a “claim”). This is used to signal
/// that the program intends to take ownership of the account.
2026-03-27 21:43:28 +03:00
#[ derive(Debug, Clone, Serialize, Deserialize) ]
2026-01-16 04:11:33 +03:00
#[ cfg_attr(any(feature = " host " , test), derive(PartialEq, Eq)) ]
2025-12-02 17:12:32 -03:00
pub struct AccountPostState {
2025-12-04 16:26:40 -03:00
account : Account ,
2026-03-27 21:43:28 +03:00
claim : Option < Claim > ,
}
/// A claim request for an account, indicating that the executing program intends to take ownership
/// of the account.
#[ derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize) ]
pub enum Claim {
/// The program requests ownership of the account which was authorized by the signer.
///
/// Note that it's possible to successfully execute program outputting [`AccountPostState`] with
/// `is_authorized == false` and `claim == Some(Claim::Authorized)`.
/// This will give no error if program had authorization in pre state and may be useful
/// if program decides to give up authorization for a chained call.
Authorized ,
2026-04-22 15:34:15 +02:00
/// The program requests ownership of the account through a PDA. The program emits the
/// seed; the `AccountId` is derived from `(program_id, seed)`, regardless of whether the
/// account is public or private.
2026-03-27 21:43:28 +03:00
Pda ( PdaSeed ) ,
2025-12-02 17:12:32 -03:00
}
2025-12-03 16:39:33 -03:00
impl AccountPostState {
2025-12-04 10:10:01 -03:00
/// Creates a post state without a claim request.
/// The executing program is not requesting ownership of the account.
2026-03-03 23:21:08 +03:00
#[ must_use ]
2026-03-09 18:27:56 +03:00
pub const fn new ( account : Account ) -> Self {
2025-12-03 16:39:33 -03:00
Self {
2025-12-02 17:12:32 -03:00
account ,
2026-03-27 21:43:28 +03:00
claim : None ,
2025-12-02 17:12:32 -03:00
}
}
2025-12-03 17:06:09 -03:00
2025-12-04 10:10:01 -03:00
/// Creates a post state that requests ownership of the account.
/// This indicates that the executing program intends to claim the
/// account as its own and is allowed to mutate it.
2026-03-03 23:21:08 +03:00
#[ must_use ]
2026-03-27 21:43:28 +03:00
pub const fn new_claimed ( account : Account , claim : Claim ) -> Self {
2025-12-03 16:39:33 -03:00
Self {
account ,
2026-03-27 21:43:28 +03:00
claim : Some ( claim ) ,
2025-12-03 16:39:33 -03:00
}
}
2026-01-17 03:31:50 +03:00
/// Creates a post state that requests ownership of the account
/// if the account's program owner is the default program ID.
2026-03-03 23:21:08 +03:00
#[ must_use ]
2026-03-27 21:43:28 +03:00
pub fn new_claimed_if_default ( account : Account , claim : Claim ) -> Self {
let is_default_owner = account . program_owner = = DEFAULT_PROGRAM_ID ;
Self {
account ,
claim : is_default_owner . then_some ( claim ) ,
}
2026-01-17 03:31:50 +03:00
}
2026-03-27 21:43:28 +03:00
/// Returns whether this post state requires a claim.
2026-03-03 23:21:08 +03:00
#[ must_use ]
2026-03-27 21:43:28 +03:00
pub const fn required_claim ( & self ) -> Option < Claim > {
2025-12-03 16:39:33 -03:00
self . claim
}
2025-12-04 16:26:40 -03:00
2026-03-10 00:17:43 +03:00
/// Returns the underlying account.
2026-03-03 23:21:08 +03:00
#[ must_use ]
2026-03-09 18:27:56 +03:00
pub const fn account ( & self ) -> & Account {
2025-12-04 16:26:40 -03:00
& self . account
}
2026-03-10 00:17:43 +03:00
/// Returns the underlying account.
2026-03-27 21:43:28 +03:00
#[ must_use ]
2026-03-09 18:27:56 +03:00
pub const fn account_mut ( & mut self ) -> & mut Account {
2025-12-04 16:26:40 -03:00
& mut self . account
}
2025-12-24 22:58:33 +03:00
2026-03-10 00:17:43 +03:00
/// Consumes the post state and returns the underlying account.
2026-03-03 23:21:08 +03:00
#[ must_use ]
2025-12-24 22:58:33 +03:00
pub fn into_account ( self ) -> Account {
self . account
}
2025-12-02 17:12:32 -03:00
}
2026-03-28 03:13:46 -03:00
pub type BlockValidityWindow = ValidityWindow < BlockId > ;
pub type TimestampValidityWindow = ValidityWindow < Timestamp > ;
2026-03-26 15:27:37 -03:00
#[ derive(Clone, Copy, Serialize, Deserialize) ]
2026-03-20 13:16:52 -03:00
#[ cfg_attr(
any ( feature = " host " , test ) ,
derive ( Debug , PartialEq , Eq , BorshSerialize , BorshDeserialize )
) ]
2026-03-28 01:13:48 -03:00
pub struct ValidityWindow < T > {
from : Option < T > ,
to : Option < T > ,
2026-03-20 13:16:52 -03:00
}
2026-03-28 01:13:48 -03:00
impl < T > ValidityWindow < T > {
/// Creates a window with no bounds.
2026-03-20 13:16:52 -03:00
#[ must_use ]
pub const fn new_unbounded ( ) -> Self {
Self {
from : None ,
to : None ,
}
}
2026-03-28 01:13:48 -03:00
}
2026-03-20 13:16:52 -03:00
2026-03-28 01:13:48 -03:00
impl < T : Copy + PartialOrd > ValidityWindow < T > {
/// Valid for values in the range [from, to), where `from` is included and `to` is excluded.
2026-03-20 13:16:52 -03:00
#[ must_use ]
2026-03-28 01:13:48 -03:00
pub fn is_valid_for ( & self , value : T ) -> bool {
2026-03-28 03:54:57 -03:00
self . from . is_none_or ( | start | value > = start ) & & self . to . is_none_or ( | end | value < end )
2026-03-20 13:16:52 -03:00
}
2026-03-25 14:55:23 -03:00
/// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`.
2026-03-28 01:13:48 -03:00
fn check_window ( & self ) -> Result < ( ) , InvalidWindow > {
if let ( Some ( from ) , Some ( to ) ) = ( self . from , self . to )
& & from > = to
2026-03-24 10:24:05 +01:00
{
return Err ( InvalidWindow ) ;
}
Ok ( ( ) )
2026-03-20 13:16:52 -03:00
}
2026-03-28 01:13:48 -03:00
/// Inclusive lower bound. `None` means no lower bound.
2026-03-20 13:16:52 -03:00
#[ must_use ]
2026-03-28 03:57:14 -03:00
pub const fn start ( & self ) -> Option < T > {
2026-03-20 13:16:52 -03:00
self . from
}
2026-03-28 01:13:48 -03:00
/// Exclusive upper bound. `None` means no upper bound.
2026-03-20 13:16:52 -03:00
#[ must_use ]
2026-03-28 03:57:14 -03:00
pub const fn end ( & self ) -> Option < T > {
2026-03-20 13:16:52 -03:00
self . to
}
2026-03-24 10:24:05 +01:00
}
2026-03-28 01:13:48 -03:00
impl < T : Copy + PartialOrd > TryFrom < ( Option < T > , Option < T > ) > for ValidityWindow < T > {
2026-03-24 10:24:05 +01:00
type Error = InvalidWindow ;
2026-03-28 01:13:48 -03:00
fn try_from ( value : ( Option < T > , Option < T > ) ) -> Result < Self , Self ::Error > {
2026-03-24 10:24:05 +01:00
let this = Self {
from : value . 0 ,
to : value . 1 ,
2026-03-20 13:16:52 -03:00
} ;
this . check_window ( ) ? ;
Ok ( this )
}
}
2026-03-28 01:13:48 -03:00
impl < T : Copy + PartialOrd > TryFrom < std ::ops ::Range < T > > for ValidityWindow < T > {
2026-03-25 14:55:23 -03:00
type Error = InvalidWindow ;
2026-03-28 01:13:48 -03:00
fn try_from ( value : std ::ops ::Range < T > ) -> Result < Self , Self ::Error > {
2026-03-25 14:55:23 -03:00
( Some ( value . start ) , Some ( value . end ) ) . try_into ( )
}
}
2026-03-28 01:13:48 -03:00
impl < T : Copy + PartialOrd > From < std ::ops ::RangeFrom < T > > for ValidityWindow < T > {
fn from ( value : std ::ops ::RangeFrom < T > ) -> Self {
2026-03-25 14:55:23 -03:00
Self {
from : Some ( value . start ) ,
to : None ,
}
}
}
2026-03-28 01:13:48 -03:00
impl < T : Copy + PartialOrd > From < std ::ops ::RangeTo < T > > for ValidityWindow < T > {
fn from ( value : std ::ops ::RangeTo < T > ) -> Self {
2026-03-25 14:55:23 -03:00
Self {
from : None ,
to : Some ( value . end ) ,
}
}
}
2026-03-28 01:13:48 -03:00
impl < T > From < std ::ops ::RangeFull > for ValidityWindow < T > {
2026-03-25 14:55:23 -03:00
fn from ( _ : std ::ops ::RangeFull ) -> Self {
Self ::new_unbounded ( )
}
}
2026-03-20 13:16:52 -03:00
#[ derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq) ]
#[ error( " Invalid window " ) ]
pub struct InvalidWindow ;
2026-03-19 12:10:02 -03:00
2025-08-19 10:39:47 -03:00
#[ derive(Serialize, Deserialize, Clone) ]
#[ cfg_attr(any(feature = " host " , test), derive(Debug, PartialEq, Eq)) ]
2026-03-25 16:03:39 -03:00
#[ must_use = " ProgramOutput does nothing unless written " ]
2025-08-14 14:09:04 -03:00
pub struct ProgramOutput {
2026-04-02 00:39:24 +02:00
/// The program ID of the program that produced this output.
pub self_program_id : ProgramId ,
2026-04-07 19:03:06 +02:00
/// The program ID of the caller that invoked this program via a chained call,
/// or `None` if this is a top-level call.
pub caller_program_id : Option < ProgramId > ,
2026-03-10 00:17:43 +03:00
/// The instruction data the program received to produce this output.
2025-11-18 01:38:47 -03:00
pub instruction_data : InstructionData ,
2026-03-10 00:17:43 +03:00
/// The account pre states the program received to produce this output.
2025-08-14 14:09:04 -03:00
pub pre_states : Vec < AccountWithMetadata > ,
2026-03-19 19:41:02 -03:00
/// The account post states the program execution produced.
2025-12-02 17:12:32 -03:00
pub post_states : Vec < AccountPostState > ,
2026-03-19 19:41:02 -03:00
/// The list of chained calls to other programs.
2025-11-12 19:18:04 -03:00
pub chained_calls : Vec < ChainedCall > ,
2026-03-28 01:13:48 -03:00
/// The block ID window where the program output is valid.
2026-03-28 03:13:46 -03:00
pub block_validity_window : BlockValidityWindow ,
2026-03-28 01:13:48 -03:00
/// The timestamp window where the program output is valid.
2026-03-28 03:13:46 -03:00
pub timestamp_validity_window : TimestampValidityWindow ,
2025-08-14 14:09:04 -03:00
}
2026-03-19 12:10:02 -03:00
impl ProgramOutput {
2026-03-19 15:03:45 -03:00
pub const fn new (
2026-04-02 00:39:24 +02:00
self_program_id : ProgramId ,
2026-04-07 19:03:06 +02:00
caller_program_id : Option < ProgramId > ,
2026-03-19 15:03:45 -03:00
instruction_data : InstructionData ,
pre_states : Vec < AccountWithMetadata > ,
post_states : Vec < AccountPostState > ,
) -> Self {
Self {
2026-04-02 00:39:24 +02:00
self_program_id ,
2026-04-07 19:03:06 +02:00
caller_program_id ,
2026-03-19 15:03:45 -03:00
instruction_data ,
pre_states ,
post_states ,
chained_calls : Vec ::new ( ) ,
2026-03-28 01:13:48 -03:00
block_validity_window : ValidityWindow ::new_unbounded ( ) ,
timestamp_validity_window : ValidityWindow ::new_unbounded ( ) ,
2026-03-19 15:03:45 -03:00
}
}
pub fn write ( self ) {
env ::commit ( & self ) ;
}
pub fn with_chained_calls ( mut self , chained_calls : Vec < ChainedCall > ) -> Self {
self . chained_calls = chained_calls ;
self
}
2026-03-28 01:13:48 -03:00
/// Sets the block ID validity window from an infallible range conversion (`1..`, `..5`, `..`).
2026-03-28 03:13:46 -03:00
pub fn with_block_validity_window < W : Into < BlockValidityWindow > > ( mut self , window : W ) -> Self {
2026-03-28 01:13:48 -03:00
self . block_validity_window = window . into ( ) ;
self
}
/// Sets the block ID validity window from a fallible range conversion (`1..5`).
/// Returns `Err` if the range is empty.
2026-03-28 03:54:57 -03:00
pub fn try_with_block_validity_window <
W : TryInto < BlockValidityWindow , Error = InvalidWindow > ,
> (
2026-03-28 01:13:48 -03:00
mut self ,
window : W ,
) -> Result < Self , InvalidWindow > {
self . block_validity_window = window . try_into ( ) ? ;
Ok ( self )
}
/// Sets the timestamp validity window from an infallible range conversion.
2026-03-28 03:54:57 -03:00
pub fn with_timestamp_validity_window < W : Into < TimestampValidityWindow > > (
mut self ,
window : W ,
) -> Self {
2026-03-28 01:13:48 -03:00
self . timestamp_validity_window = window . into ( ) ;
2026-03-25 17:42:17 -03:00
self
}
2026-03-28 01:13:48 -03:00
/// Sets the timestamp validity window from a fallible range conversion.
2026-03-25 17:42:17 -03:00
/// Returns `Err` if the range is empty.
2026-03-28 03:54:57 -03:00
pub fn try_with_timestamp_validity_window <
W : TryInto < TimestampValidityWindow , Error = InvalidWindow > ,
> (
2026-03-25 17:42:17 -03:00
mut self ,
window : W ,
) -> Result < Self , InvalidWindow > {
2026-03-28 01:13:48 -03:00
self . timestamp_validity_window = window . try_into ( ) ? ;
2026-03-25 17:42:17 -03:00
Ok ( self )
}
2026-03-24 10:24:05 +01:00
pub fn valid_from_timestamp ( mut self , ts : Option < Timestamp > ) -> Result < Self , InvalidWindow > {
2026-03-28 01:13:48 -03:00
self . timestamp_validity_window = ( ts , self . timestamp_validity_window . end ( ) ) . try_into ( ) ? ;
2026-03-24 10:24:05 +01:00
Ok ( self )
}
pub fn valid_until_timestamp ( mut self , ts : Option < Timestamp > ) -> Result < Self , InvalidWindow > {
2026-03-28 01:13:48 -03:00
self . timestamp_validity_window = ( self . timestamp_validity_window . start ( ) , ts ) . try_into ( ) ? ;
2026-03-24 10:24:05 +01:00
Ok ( self )
}
2026-03-19 12:10:02 -03:00
}
2026-03-04 18:42:33 +03:00
/// Representation of a number as `lo + hi * 2^128`.
2026-04-07 00:03:01 +03:00
#[ derive(Debug, PartialEq, Eq) ]
pub struct WrappedBalanceSum {
2026-03-04 18:42:33 +03:00
lo : u128 ,
hi : u128 ,
}
impl WrappedBalanceSum {
/// Constructs a [`WrappedBalanceSum`] from an iterator of balances.
///
/// Returns [`None`] if balance sum overflows `lo + hi * 2^128` representation, which is not
/// expected in practical scenarios.
2026-04-07 00:03:01 +03:00
pub fn from_balances ( balances : impl Iterator < Item = u128 > ) -> Option < Self > {
2026-03-09 18:27:56 +03:00
let mut wrapped = Self { lo : 0 , hi : 0 } ;
2026-03-04 18:42:33 +03:00
for balance in balances {
let ( new_sum , did_overflow ) = wrapped . lo . overflowing_add ( balance ) ;
if did_overflow {
wrapped . hi = wrapped . hi . checked_add ( 1 ) ? ;
}
wrapped . lo = new_sum ;
}
Some ( wrapped )
}
}
2026-04-07 00:03:01 +03:00
impl std ::fmt ::Display for WrappedBalanceSum {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
if self . hi = = 0 {
write! ( f , " {} " , self . lo )
} else {
write! ( f , " {} * 2^128 + {} " , self . hi , self . lo )
}
}
}
impl From < u128 > for WrappedBalanceSum {
fn from ( value : u128 ) -> Self {
Self { lo : value , hi : 0 }
}
}
#[ derive(thiserror::Error, Debug) ]
pub enum ExecutionValidationError {
#[ error( " Pre-state account IDs are not unique " ) ]
PreStateAccountIdsNotUnique ,
#[ error(
" Pre-state and post-state lengths do not match: pre-state length {pre_state_length}, post-state length {post_state_length} "
) ]
MismatchedPreStatePostStateLength {
pre_state_length : usize ,
post_state_length : usize ,
} ,
#[ error( " Unallowed modification of nonce for account {account_id} " ) ]
ModifiedNonce { account_id : AccountId } ,
#[ error( " Unallowed modification of program owner for account {account_id} " ) ]
ModifiedProgramOwner { account_id : AccountId } ,
#[ error(
" Trying to decrease balance of account {account_id} owned by {owner_program_id:?} in a program {executing_program_id:?} which is not the owner "
) ]
UnauthorizedBalanceDecrease {
account_id : AccountId ,
owner_program_id : ProgramId ,
executing_program_id : ProgramId ,
} ,
#[ error(
" Unauthorized modification of data for account {account_id} which is not default and not owned by executing program {executing_program_id:?} "
) ]
UnauthorizedDataModification {
account_id : AccountId ,
executing_program_id : ProgramId ,
} ,
#[ error(
" Post-state for account {account_id} has default program owner but pre-state was not default "
) ]
NonDefaultAccountWithDefaultOwner { account_id : AccountId } ,
#[ error( " Total balance across accounts overflowed 2^256 - 1 " ) ]
BalanceSumOverflow ,
#[ error(
" Total balance across accounts is not preserved: total balance in pre-states {total_balance_pre_states}, total balance in post-states {total_balance_post_states} "
) ]
MismatchedTotalBalance {
total_balance_pre_states : WrappedBalanceSum ,
total_balance_post_states : WrappedBalanceSum ,
} ,
}
2026-04-17 07:29:40 +02:00
/// Computes the set of public-PDA `AccountId`s the callee is authorized to mutate.
2026-04-16 16:53:54 +02:00
///
2026-04-17 07:29:40 +02:00
/// 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
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_*).
2026-04-21 12:35:19 +02:00
/// `pre_state` against [`AccountId::for_private_pda`] with the supplied npk for that
/// `pre_state`.
2026-03-04 18:42:33 +03:00
#[ must_use ]
2026-04-22 15:34:15 +02:00
pub fn compute_public_authorized_pdas (
2026-03-04 18:42:33 +03:00
caller_program_id : Option < ProgramId > ,
pda_seeds : & [ PdaSeed ] ,
) -> HashSet < AccountId > {
2026-04-16 16:53:54 +02:00
let Some ( caller ) = caller_program_id else {
return HashSet ::new ( ) ;
} ;
2026-04-17 07:29:40 +02:00
pda_seeds
2026-04-16 16:53:54 +02:00
. iter ( )
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_*).
2026-04-21 12:35:19 +02:00
. map ( | seed | AccountId ::for_public_pda ( & caller , seed ) )
2026-04-17 07:29:40 +02:00
. collect ( )
2026-03-04 18:42:33 +03:00
}
2026-03-03 23:21:08 +03:00
/// Reads the NSSA inputs from the guest environment.
#[ must_use ]
2025-11-18 01:38:47 -03:00
pub fn read_nssa_inputs < T : DeserializeOwned > ( ) -> ( ProgramInput < T > , InstructionData ) {
2026-04-01 21:15:02 +02:00
let self_program_id : ProgramId = env ::read ( ) ;
2026-04-02 15:35:02 +02:00
let caller_program_id : Option < ProgramId > = env ::read ( ) ;
2025-08-10 18:51:55 -03:00
let pre_states : Vec < AccountWithMetadata > = env ::read ( ) ;
2025-09-08 19:29:56 -03:00
let instruction_words : InstructionData = env ::read ( ) ;
let instruction = T ::deserialize ( & mut Deserializer ::new ( instruction_words . as_ref ( ) ) ) . unwrap ( ) ;
2025-11-18 01:38:47 -03:00
(
ProgramInput {
2026-04-01 21:15:02 +02:00
self_program_id ,
2026-04-02 15:35:02 +02:00
caller_program_id ,
2025-11-18 01:38:47 -03:00
pre_states ,
instruction ,
} ,
instruction_words ,
)
2025-08-10 18:51:55 -03:00
}
2025-08-14 14:09:04 -03:00
2026-03-10 00:17:43 +03:00
/// Validates well-behaved program execution.
2025-08-06 20:05:04 -03:00
///
/// # Parameters
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
/// - `post_states`: The list of resulting accounts after executing the program logic.
/// - `executing_program_id`: The identifier of the program that was executed.
2025-08-10 09:57:10 -03:00
pub fn validate_execution (
2025-08-06 20:05:04 -03:00
pre_states : & [ AccountWithMetadata ] ,
2025-12-02 17:12:32 -03:00
post_states : & [ AccountPostState ] ,
2025-08-06 20:05:04 -03:00
executing_program_id : ProgramId ,
2026-04-07 00:03:01 +03:00
) -> Result < ( ) , ExecutionValidationError > {
2025-11-07 19:21:43 -03:00
// 1. Check account ids are all different
if ! validate_uniqueness_of_account_ids ( pre_states ) {
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::PreStateAccountIdsNotUnique ) ;
2025-11-07 19:21:43 -03:00
}
// 2. Lengths must match
2025-08-06 20:05:04 -03:00
if pre_states . len ( ) ! = post_states . len ( ) {
2026-04-07 00:03:01 +03:00
return Err (
ExecutionValidationError ::MismatchedPreStatePostStateLength {
pre_state_length : pre_states . len ( ) ,
post_state_length : post_states . len ( ) ,
} ,
) ;
2025-08-06 20:05:04 -03:00
}
for ( pre , post ) in pre_states . iter ( ) . zip ( post_states ) {
2025-11-07 19:21:43 -03:00
// 3. Nonce must remain unchanged
2025-12-02 17:12:32 -03:00
if pre . account . nonce ! = post . account . nonce {
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::ModifiedNonce {
account_id : pre . account_id ,
} ) ;
2025-08-06 20:05:04 -03:00
}
2025-11-07 19:21:43 -03:00
// 4. Program ownership changes are not allowed
2025-12-02 17:12:32 -03:00
if pre . account . program_owner ! = post . account . program_owner {
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::ModifiedProgramOwner {
account_id : pre . account_id ,
} ) ;
2025-08-06 20:05:04 -03:00
}
2025-09-15 16:22:48 -03:00
let account_program_owner = pre . account . program_owner ;
2025-11-07 19:21:43 -03:00
// 5. Decreasing balance only allowed if owned by executing program
2025-12-02 17:12:32 -03:00
if post . account . balance < pre . account . balance
& & account_program_owner ! = executing_program_id
{
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::UnauthorizedBalanceDecrease {
account_id : pre . account_id ,
owner_program_id : account_program_owner ,
executing_program_id ,
} ) ;
2025-08-06 20:05:04 -03:00
}
2025-11-07 19:21:43 -03:00
// 6. Data changes only allowed if owned by executing program or if account pre state has
2025-09-15 16:22:48 -03:00
// default values
2025-12-02 17:12:32 -03:00
if pre . account . data ! = post . account . data
2025-09-15 16:22:48 -03:00
& & pre . account ! = Account ::default ( )
& & account_program_owner ! = executing_program_id
2025-08-06 20:05:04 -03:00
{
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::UnauthorizedDataModification {
account_id : pre . account_id ,
executing_program_id ,
} ) ;
2025-08-06 20:05:04 -03:00
}
2025-10-30 10:52:31 -03:00
2025-11-26 16:37:04 -03:00
// 7. If a post state has default program owner, the pre state must have been a default
2025-11-26 00:27:20 +03:00
// account
2025-12-02 17:12:32 -03:00
if post . account . program_owner = = DEFAULT_PROGRAM_ID & & pre . account ! = Account ::default ( ) {
2026-04-07 00:03:01 +03:00
return Err (
ExecutionValidationError ::NonDefaultAccountWithDefaultOwner {
account_id : pre . account_id ,
} ,
) ;
2025-10-30 10:52:31 -03:00
}
2025-08-06 20:05:04 -03:00
}
2025-11-07 19:21:43 -03:00
// 8. Total balance is preserved
2025-12-06 21:37:51 +03:00
let Some ( total_balance_pre_states ) =
WrappedBalanceSum ::from_balances ( pre_states . iter ( ) . map ( | pre | pre . account . balance ) )
else {
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::BalanceSumOverflow ) ;
2025-12-06 21:37:51 +03:00
} ;
let Some ( total_balance_post_states ) =
WrappedBalanceSum ::from_balances ( post_states . iter ( ) . map ( | post | post . account . balance ) )
else {
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::BalanceSumOverflow ) ;
2025-12-06 21:37:51 +03:00
} ;
2025-08-06 20:05:04 -03:00
if total_balance_pre_states ! = total_balance_post_states {
2026-04-07 00:03:01 +03:00
return Err ( ExecutionValidationError ::MismatchedTotalBalance {
total_balance_pre_states ,
total_balance_post_states ,
} ) ;
2025-08-06 20:05:04 -03:00
}
2026-04-07 00:03:01 +03:00
Ok ( ( ) )
2025-08-06 20:05:04 -03:00
}
2025-12-02 17:12:32 -03:00
2025-11-07 19:21:43 -03:00
fn validate_uniqueness_of_account_ids ( pre_states : & [ AccountWithMetadata ] ) -> bool {
let number_of_accounts = pre_states . len ( ) ;
let number_of_account_ids = pre_states
. iter ( )
2025-11-22 17:48:29 -03:00
. map ( | account | & account . account_id )
2025-11-07 19:21:43 -03:00
. collect ::< HashSet < _ > > ( )
. len ( ) ;
number_of_accounts = = number_of_account_ids
}
2025-12-09 22:27:38 -03:00
2025-12-02 17:12:32 -03:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2026-03-25 14:55:23 -03:00
#[ test ]
2026-03-28 01:13:48 -03:00
fn validity_window_unbounded_accepts_any_value ( ) {
let w : ValidityWindow < u64 > = ValidityWindow ::new_unbounded ( ) ;
assert! ( w . is_valid_for ( 0 ) ) ;
assert! ( w . is_valid_for ( u64 ::MAX ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_bounded_range_includes_from_excludes_to ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( Some ( 5 ) , Some ( 10 ) ) . try_into ( ) . unwrap ( ) ;
assert! ( ! w . is_valid_for ( 4 ) ) ;
assert! ( w . is_valid_for ( 5 ) ) ;
assert! ( w . is_valid_for ( 9 ) ) ;
assert! ( ! w . is_valid_for ( 10 ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_only_from_bound ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( Some ( 5 ) , None ) . try_into ( ) . unwrap ( ) ;
assert! ( ! w . is_valid_for ( 4 ) ) ;
assert! ( w . is_valid_for ( 5 ) ) ;
assert! ( w . is_valid_for ( u64 ::MAX ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_only_to_bound ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( None , Some ( 5 ) ) . try_into ( ) . unwrap ( ) ;
assert! ( w . is_valid_for ( 0 ) ) ;
assert! ( w . is_valid_for ( 4 ) ) ;
assert! ( ! w . is_valid_for ( 5 ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_adjacent_bounds_are_invalid ( ) {
// [5, 5) is an empty range — from == to
2026-03-28 01:13:48 -03:00
assert! ( ValidityWindow ::< u64 > ::try_from ( ( Some ( 5 ) , Some ( 5 ) ) ) . is_err ( ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_inverted_bounds_are_invalid ( ) {
2026-03-28 01:13:48 -03:00
assert! ( ValidityWindow ::< u64 > ::try_from ( ( Some ( 10 ) , Some ( 5 ) ) ) . is_err ( ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_getters_match_construction ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( Some ( 3 ) , Some ( 7 ) ) . try_into ( ) . unwrap ( ) ;
2026-03-25 20:25:53 -03:00
assert_eq! ( w . start ( ) , Some ( 3 ) ) ;
assert_eq! ( w . end ( ) , Some ( 7 ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_getters_for_unbounded ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ValidityWindow ::new_unbounded ( ) ;
2026-03-25 20:25:53 -03:00
assert_eq! ( w . start ( ) , None ) ;
assert_eq! ( w . end ( ) , None ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_from_range ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ValidityWindow ::try_from ( 5_ u64 .. 10 ) . unwrap ( ) ;
2026-03-25 20:25:53 -03:00
assert_eq! ( w . start ( ) , Some ( 5 ) ) ;
assert_eq! ( w . end ( ) , Some ( 10 ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_from_range_empty_is_invalid ( ) {
2026-03-28 01:13:48 -03:00
assert! ( ValidityWindow ::< u64 > ::try_from ( 5_ u64 .. 5 ) . is_err ( ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_from_range_inverted_is_invalid ( ) {
2026-03-25 20:25:53 -03:00
let from = 10_ u64 ;
let to = 5_ u64 ;
2026-03-28 01:13:48 -03:00
assert! ( ValidityWindow ::< u64 > ::try_from ( from .. to ) . is_err ( ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_from_range_from ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( 5_ u64 .. ) . into ( ) ;
2026-03-25 20:25:53 -03:00
assert_eq! ( w . start ( ) , Some ( 5 ) ) ;
assert_eq! ( w . end ( ) , None ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_from_range_to ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( .. 10_ u64 ) . into ( ) ;
2026-03-25 20:25:53 -03:00
assert_eq! ( w . start ( ) , None ) ;
assert_eq! ( w . end ( ) , Some ( 10 ) ) ;
2026-03-25 14:55:23 -03:00
}
#[ test ]
fn validity_window_from_range_full ( ) {
2026-03-28 01:13:48 -03:00
let w : ValidityWindow < u64 > = ( .. ) . into ( ) ;
2026-03-25 20:25:53 -03:00
assert_eq! ( w . start ( ) , None ) ;
assert_eq! ( w . end ( ) , None ) ;
2026-03-25 14:55:23 -03:00
}
2026-03-25 17:42:17 -03:00
#[ test ]
2026-03-28 01:13:48 -03:00
fn program_output_try_with_block_validity_window_range ( ) {
2026-04-07 19:03:06 +02:00
let output = ProgramOutput ::new ( DEFAULT_PROGRAM_ID , None , vec! [ ] , vec! [ ] , vec! [ ] )
2026-03-28 01:13:48 -03:00
. try_with_block_validity_window ( 10_ u64 .. 100 )
2026-03-25 17:42:17 -03:00
. unwrap ( ) ;
2026-03-28 01:13:48 -03:00
assert_eq! ( output . block_validity_window . start ( ) , Some ( 10 ) ) ;
assert_eq! ( output . block_validity_window . end ( ) , Some ( 100 ) ) ;
2026-03-25 17:42:17 -03:00
}
#[ test ]
2026-03-28 01:13:48 -03:00
fn program_output_with_block_validity_window_range_from ( ) {
2026-04-07 19:03:06 +02:00
let output = ProgramOutput ::new ( DEFAULT_PROGRAM_ID , None , vec! [ ] , vec! [ ] , vec! [ ] )
2026-04-02 12:10:37 +02:00
. with_block_validity_window ( 10_ u64 .. ) ;
2026-03-28 01:13:48 -03:00
assert_eq! ( output . block_validity_window . start ( ) , Some ( 10 ) ) ;
assert_eq! ( output . block_validity_window . end ( ) , None ) ;
2026-03-25 17:42:17 -03:00
}
#[ test ]
2026-03-28 01:13:48 -03:00
fn program_output_with_block_validity_window_range_to ( ) {
2026-04-07 19:03:06 +02:00
let output = ProgramOutput ::new ( DEFAULT_PROGRAM_ID , None , vec! [ ] , vec! [ ] , vec! [ ] )
2026-04-02 12:10:37 +02:00
. with_block_validity_window ( .. 100_ u64 ) ;
2026-03-28 01:13:48 -03:00
assert_eq! ( output . block_validity_window . start ( ) , None ) ;
assert_eq! ( output . block_validity_window . end ( ) , Some ( 100 ) ) ;
2026-03-25 17:42:17 -03:00
}
#[ test ]
2026-03-28 01:13:48 -03:00
fn program_output_try_with_block_validity_window_empty_range_fails ( ) {
2026-04-07 19:03:06 +02:00
let result = ProgramOutput ::new ( DEFAULT_PROGRAM_ID , None , vec! [ ] , vec! [ ] , vec! [ ] )
2026-04-02 12:10:37 +02:00
. try_with_block_validity_window ( 5_ u64 .. 5 ) ;
2026-03-25 17:42:17 -03:00
assert! ( result . is_err ( ) ) ;
}
2025-12-02 17:12:32 -03:00
#[ test ]
2026-03-04 18:42:33 +03:00
fn post_state_new_with_claim_constructor ( ) {
2025-12-02 17:12:32 -03:00
let account = Account {
program_owner : [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ,
balance : 1337 ,
2025-12-05 02:17:09 +03:00
data : vec ! [ 0xde , 0xad , 0xbe , 0xef ] . try_into ( ) . unwrap ( ) ,
2026-03-18 13:10:36 -04:00
nonce : 10_ u128 . into ( ) ,
2025-12-02 17:12:32 -03:00
} ;
2026-03-27 21:43:28 +03:00
let account_post_state = AccountPostState ::new_claimed ( account . clone ( ) , Claim ::Authorized ) ;
2025-12-02 17:12:32 -03:00
assert_eq! ( account , account_post_state . account ) ;
2026-03-27 21:43:28 +03:00
assert_eq! ( account_post_state . required_claim ( ) , Some ( Claim ::Authorized ) ) ;
2025-12-02 17:12:32 -03:00
}
2025-12-03 16:39:33 -03:00
#[ test ]
2026-03-04 18:42:33 +03:00
fn post_state_new_without_claim_constructor ( ) {
2025-12-03 16:39:33 -03:00
let account = Account {
program_owner : [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ,
balance : 1337 ,
2025-12-05 02:17:09 +03:00
data : vec ! [ 0xde , 0xad , 0xbe , 0xef ] . try_into ( ) . unwrap ( ) ,
2026-03-18 13:10:36 -04:00
nonce : 10_ u128 . into ( ) ,
2025-12-03 16:39:33 -03:00
} ;
let account_post_state = AccountPostState ::new ( account . clone ( ) ) ;
assert_eq! ( account , account_post_state . account ) ;
2026-03-27 21:43:28 +03:00
assert! ( account_post_state . required_claim ( ) . is_none ( ) ) ;
2025-12-03 16:39:33 -03:00
}
2025-12-04 16:26:40 -03:00
#[ test ]
2026-03-04 18:42:33 +03:00
fn post_state_account_getter ( ) {
2025-12-04 16:26:40 -03:00
let mut account = Account {
program_owner : [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ,
balance : 1337 ,
2025-12-05 02:17:09 +03:00
data : vec ! [ 0xde , 0xad , 0xbe , 0xef ] . try_into ( ) . unwrap ( ) ,
2026-03-18 13:10:36 -04:00
nonce : 10_ u128 . into ( ) ,
2025-12-04 16:26:40 -03:00
} ;
let mut account_post_state = AccountPostState ::new ( account . clone ( ) ) ;
assert_eq! ( account_post_state . account ( ) , & account ) ;
assert_eq! ( account_post_state . account_mut ( ) , & mut account ) ;
}
2026-04-15 16:01:10 +02:00
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_*).
2026-04-21 12:35:19 +02:00
// ---- AccountId::for_private_pda tests ----
2026-04-15 16:01:10 +02:00
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_*).
2026-04-21 12:35:19 +02:00
/// Pins `AccountId::for_private_pda` against a hardcoded expected output for a specific
2026-05-01 00:05:50 -03:00
/// `(program_id, seed, npk, identifier)` tuple. Any change to `PRIVATE_PDA_PREFIX`, byte
/// ordering, or the underlying hash breaks this test.
2026-04-15 16:01:10 +02:00
#[ test ]
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_*).
2026-04-21 12:35:19 +02:00
fn for_private_pda_matches_pinned_value ( ) {
2026-04-15 16:01:10 +02:00
let program_id : ProgramId = [ 1 ; 8 ] ;
let seed = PdaSeed ::new ( [ 2 ; 32 ] ) ;
let npk = NullifierPublicKey ( [ 3 ; 32 ] ) ;
2026-05-01 00:05:50 -03:00
let identifier : Identifier = u128 ::MAX ;
2026-04-16 17:15:34 +02:00
let expected = AccountId ::new ( [
2026-05-07 01:41:35 -03:00
59 , 239 , 182 , 97 , 14 , 220 , 96 , 115 , 238 , 133 , 143 , 33 , 234 , 82 , 237 , 255 , 148 , 110 , 54 ,
124 , 98 , 159 , 245 , 101 , 146 , 182 , 150 , 54 , 37 , 62 , 25 , 17 ,
2026-04-16 17:15:34 +02:00
] ) ;
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_*).
2026-04-21 12:35:19 +02:00
assert_eq! (
2026-05-01 00:05:50 -03:00
AccountId ::for_private_pda ( & program_id , & seed , & npk , identifier ) ,
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_*).
2026-04-21 12:35:19 +02:00
expected
) ;
2026-04-15 16:01:10 +02:00
}
/// Two groups with different viewing keys at the same (program, seed) get different addresses.
#[ test ]
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_*).
2026-04-21 12:35:19 +02:00
fn for_private_pda_differs_for_different_npk ( ) {
2026-04-15 16:01:10 +02:00
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! (
2026-05-01 00:05:50 -03:00
AccountId ::for_private_pda ( & program_id , & seed , & npk_a , u128 ::MAX ) ,
AccountId ::for_private_pda ( & program_id , & seed , & npk_b , u128 ::MAX ) ,
2026-04-15 16:01:10 +02:00
) ;
}
/// Different seeds produce different addresses, even with the same program and npk.
#[ test ]
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_*).
2026-04-21 12:35:19 +02:00
fn for_private_pda_differs_for_different_seed ( ) {
2026-04-15 16:01:10 +02:00
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! (
2026-05-01 00:05:50 -03:00
AccountId ::for_private_pda ( & program_id , & seed_a , & npk , u128 ::MAX ) ,
AccountId ::for_private_pda ( & program_id , & seed_b , & npk , u128 ::MAX ) ,
2026-04-15 16:01:10 +02:00
) ;
}
/// Different programs produce different addresses, even with the same seed and npk.
#[ test ]
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_*).
2026-04-21 12:35:19 +02:00
fn for_private_pda_differs_for_different_program_id ( ) {
2026-04-15 16:01:10 +02:00
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! (
2026-05-01 00:05:50 -03:00
AccountId ::for_private_pda ( & program_id_a , & seed , & npk , u128 ::MAX ) ,
AccountId ::for_private_pda ( & program_id_b , & seed , & npk , u128 ::MAX ) ,
2026-04-15 16:01:10 +02:00
) ;
}
2026-05-07 13:21:05 -03:00
/// Different identifiers produce different addresses for the same `(program_id, seed, npk)`,
/// confirming that each `(program_id, seed, npk)` tuple controls a family of 2^128 addresses.
2026-05-05 20:27:17 -03:00
#[ test ]
fn for_private_pda_differs_for_different_identifier ( ) {
let program_id : ProgramId = [ 1 ; 8 ] ;
let seed = PdaSeed ::new ( [ 2 ; 32 ] ) ;
let npk = NullifierPublicKey ( [ 3 ; 32 ] ) ;
assert_ne! (
AccountId ::for_private_pda ( & program_id , & seed , & npk , 0 ) ,
AccountId ::for_private_pda ( & program_id , & seed , & npk , 1 ) ,
) ;
assert_ne! (
AccountId ::for_private_pda ( & program_id , & seed , & npk , 0 ) ,
AccountId ::for_private_pda ( & program_id , & seed , & npk , u128 ::MAX ) ,
) ;
}
2026-04-15 16:01:10 +02:00
/// 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 ]
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_*).
2026-04-21 12:35:19 +02:00
fn for_private_pda_differs_from_public_pda ( ) {
2026-04-15 16:01:10 +02:00
let program_id : ProgramId = [ 1 ; 8 ] ;
let seed = PdaSeed ::new ( [ 2 ; 32 ] ) ;
let npk = NullifierPublicKey ( [ 3 ; 32 ] ) ;
2026-05-01 00:05:50 -03:00
let private_id = AccountId ::for_private_pda ( & program_id , & seed , & npk , u128 ::MAX ) ;
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_*).
2026-04-21 12:35:19 +02:00
let public_id = AccountId ::for_public_pda ( & program_id , & seed ) ;
2026-04-15 16:01:10 +02:00
assert_ne! ( private_id , public_id ) ;
}
2026-05-07 12:27:51 -03:00
#[ cfg(feature = " host " ) ]
#[ test ]
fn private_account_kind_header_round_trips ( ) {
let regular = PrivateAccountKind ::Regular ( 42 ) ;
let pda = PrivateAccountKind ::Pda {
2026-05-07 13:21:05 -03:00
program_id : [ 1_ u32 ; 8 ] ,
seed : PdaSeed ::new ( [ 2_ u8 ; 32 ] ) ,
2026-05-07 12:27:51 -03:00
identifier : u128 ::MAX ,
} ;
assert_eq! (
PrivateAccountKind ::from_header_bytes ( & regular . to_header_bytes ( ) ) ,
Some ( regular )
) ;
assert_eq! (
PrivateAccountKind ::from_header_bytes ( & pda . to_header_bytes ( ) ) ,
Some ( pda )
) ;
}
#[ cfg(feature = " host " ) ]
#[ test ]
fn private_account_kind_unknown_discriminant_returns_none ( ) {
2026-05-07 13:21:05 -03:00
let mut bytes = [ 0_ u8 ; PrivateAccountKind ::HEADER_LEN ] ;
2026-05-07 12:27:51 -03:00
bytes [ 0 ] = 0xFF ;
assert_eq! ( PrivateAccountKind ::from_header_bytes ( & bytes ) , None ) ;
}
#[ test ]
fn for_private_account_dispatches_correctly ( ) {
let program_id : ProgramId = [ 1 ; 8 ] ;
let seed = PdaSeed ::new ( [ 2 ; 32 ] ) ;
let npk = NullifierPublicKey ( [ 3 ; 32 ] ) ;
let identifier : Identifier = 77 ;
assert_eq! (
AccountId ::for_private_account ( & npk , & PrivateAccountKind ::Regular ( identifier ) ) ,
2026-05-08 21:41:48 -03:00
AccountId ::for_regular_private_account ( & npk , identifier ) ,
2026-05-07 12:27:51 -03:00
) ;
assert_eq! (
AccountId ::for_private_account (
& npk ,
2026-05-07 13:21:05 -03:00
& PrivateAccountKind ::Pda {
program_id ,
seed ,
identifier
}
2026-05-07 12:27:51 -03:00
) ,
AccountId ::for_private_pda ( & program_id , & seed , & npk , identifier ) ,
) ;
}
2026-04-15 16:01:10 +02:00
#[ test ]
2026-04-22 15:34:15 +02:00
fn compute_public_authorized_pdas_with_seeds ( ) {
2026-04-15 16:01:10 +02:00
let caller : ProgramId = [ 1 ; 8 ] ;
let seed = PdaSeed ::new ( [ 2 ; 32 ] ) ;
2026-04-22 15:34:15 +02:00
let result = compute_public_authorized_pdas ( Some ( caller ) , & [ seed ] ) ;
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_*).
2026-04-21 12:35:19 +02:00
let expected = AccountId ::for_public_pda ( & caller , & seed ) ;
2026-04-15 16:01:10 +02:00
assert! ( result . contains ( & expected ) ) ;
2026-04-16 16:53:54 +02:00
assert_eq! ( result . len ( ) , 1 ) ;
2026-04-15 16:01:10 +02:00
}
2026-04-16 16:53:54 +02:00
/// With no caller (top-level call), the result is always empty.
2026-04-15 16:01:10 +02:00
#[ test ]
2026-04-22 15:34:15 +02:00
fn compute_public_authorized_pdas_no_caller_returns_empty ( ) {
2026-04-15 16:01:10 +02:00
let seed = PdaSeed ::new ( [ 2 ; 32 ] ) ;
2026-04-22 15:34:15 +02:00
let result = compute_public_authorized_pdas ( None , & [ seed ] ) ;
2026-04-15 16:01:10 +02:00
assert! ( result . is_empty ( ) ) ;
}
2025-12-02 17:12:32 -03:00
}