refactor(lee): refactor private kinds

This commit is contained in:
agureev 2026-08-04 15:55:09 +04:00
parent 57d45f14fa
commit d05035eb9e
34 changed files with 741 additions and 696 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -12,7 +12,8 @@ use lee::{
program::Program,
};
use lee_core::{
DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier, NullifierPublicKey,
DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier, NullifierPublicKey, NullifierWitness,
PrivateWitness, WitnessKind,
account::{Account, AccountWithMetadata},
encryption::ViewingPublicKey,
};
@ -629,14 +630,16 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> {
instruction,
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk,
random_seed: [0; 32],
npk,
identifier: 1337,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program_with_deps,
);
@ -671,13 +674,16 @@ async fn prove_init_with_commitment_root(
})?,
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivateForeignInit {
InputAccountIdentity::Private(PrivateWitness {
vpk,
random_seed: [0; 32],
npk,
identifier: 0,
commitment_root,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk,
commitment_root,
},
}),
],
&program.into(),
)?;

View File

@ -21,7 +21,8 @@ use lee::{
program::Program,
};
use lee_core::{
DUMMY_COMMITMENT_HASH, InputAccountIdentity, NullifierPublicKey,
DUMMY_COMMITMENT_HASH, InputAccountIdentity, NullifierPublicKey, NullifierWitness,
PrivateWitness, WitnessKind,
account::{Account, AccountWithMetadata},
encryption::ViewingPublicKey,
program::PdaSeed,
@ -65,14 +66,18 @@ async fn fund_private_pda(
let account_identities = vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk,
random_seed: [0; 32],
npk,
identifier,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: Some((seed, authority_program_id)),
},
kind: WitnessKind::Pda {
binding: Some((authority_program_id, seed)),
},
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
];
let (output, proof) = execute_and_prove(

View File

@ -23,6 +23,7 @@ use lee::{
};
use lee_core::{
DUMMY_COMMITMENT_HASH, InputAccountIdentity, MembershipProof, NullifierPublicKey,
NullifierWitness, PrivateWitness, WitnessKind,
account::{AccountWithMetadata, Nonce, data::Data},
encryption::ViewingPublicKey,
};
@ -291,21 +292,27 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
})
.unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_vpk,
random_seed: [0; 32],
view_tag: 0,
nsk: sender_nsk,
membership_proof: proof,
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_nsk,
membership_proof: proof,
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_vpk,
random_seed: [0; 32],
npk: recipient_npk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
)

View File

@ -4,7 +4,7 @@ use std::{
};
use lee_core::{
Identifier, InputAccountIdentity, NullifierPublicKey,
Identifier, InputAccountIdentity, NullifierPublicKey, PrivateWitness, WitnessKind,
account::{Account, AccountId, AccountWithMetadata},
encryption::ViewingPublicKey,
program::{
@ -311,53 +311,35 @@ impl ExecutionState {
// Pre state for the initial call
let pre_state_position = self.pre_states.len();
let external_seed = match account_identities.get(pre_state_position) {
Some(InputAccountIdentity::PrivatePdaInit {
npk,
Some(InputAccountIdentity::Private(PrivateWitness {
vpk,
identifier,
seed: Some((seed, authority_program_id)),
kind:
WitnessKind::Pda {
binding: Some((authority_program_id, seed)),
},
nullifier,
..
}) => {
})) => {
let expected = AccountId::for_private_pda(
authority_program_id,
seed,
npk,
&nullifier.npk(),
vpk,
*identifier,
);
assert_eq!(
pre_account_id, expected,
"External seed mismatch for PrivatePdaInit at position {pre_state_position}"
"External seed mismatch for private PDA at position {pre_state_position}"
);
Some((*seed, *authority_program_id))
}
Some(InputAccountIdentity::PrivatePdaUpdate {
nsk,
vpk,
identifier,
seed: Some((seed, authority_program_id)),
..
}) => {
let npk = NullifierPublicKey::from(nsk);
let expected = AccountId::for_private_pda(
authority_program_id,
seed,
&npk,
vpk,
*identifier,
);
assert_eq!(
pre_account_id, expected,
"External seed mismatch for PrivatePdaUpdate at position {pre_state_position}"
);
Some((*seed, *authority_program_id))
Some((*authority_program_id, *seed))
}
_ => None,
};
// External seed is only consulted the first time the account is seen.
// Subsequent calls need no re-check because the entry is already recorded on
// private_pda_bound_positions.
if let Some((seed, authority_program_id)) = external_seed {
if let Some((authority_program_id, seed)) = external_seed {
assert!(
!pre.is_authorized,
"Private PDA with externally-provided seed must not be authorized at position {pre_state_position}"

View File

@ -1,8 +1,8 @@
use lee_core::{
Commitment, CommitmentSetDigest, DummyInput, EncryptedAccountData, EncryptionScheme,
EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey,
NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, PrivateAction,
PublicAction, SharedSecretKey,
EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierSecretKey,
NullifierWitness, PrivacyPreservingCircuitOutput, PrivateAccountKind, PrivateAction,
PrivateWitness, PublicAction, SharedSecretKey, WitnessKind,
account::{Account, AccountId, Nonce},
compute_digest_for_path,
encryption::{ViewTag, ViewingPublicKey},
@ -40,39 +40,107 @@ pub fn compute_circuit_output(
post: post_state,
});
}
InputAccountIdentity::PrivateAuthorizedInit {
InputAccountIdentity::Private(PrivateWitness {
vpk,
random_seed,
nsk,
identifier,
commitment_root,
} => {
let npk = NullifierPublicKey::from(nsk);
let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier);
kind,
nullifier,
}) => {
let account_id = match kind {
WitnessKind::Regular => {
let derived = AccountId::for_regular_private_account(
&nullifier.npk(),
vpk,
*identifier,
);
assert_eq!(derived, pre_state.account_id, "AccountId mismatch");
derived
}
// The npk-to-account_id binding is established upstream in
// `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds`
// match. Here we only enforce the lifecycle pre-conditions. The supplied npk
// on the witness has been recorded into `private_pda_by_position` and used
// for the binding check; we use `pre_state.account_id` directly for nullifier
// and commitment derivation.
WitnessKind::Pda { .. } => pre_state.account_id,
};
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
assert!(
pre_state.is_authorized,
"Pre-state not authorized for authenticated private account"
);
assert_eq!(
pre_state.account,
Account::default(),
"Found new private account with non default values"
);
match (kind, nullifier) {
(WitnessKind::Regular, _) => assert!(
pre_state.is_authorized,
"Regular private account pre-state must be authorized"
),
(WitnessKind::Pda { .. }, NullifierWitness::Init { .. }) => assert!(
!pre_state.is_authorized,
"Private PDA init requires unauthorized pre_state"
),
// With an external seed the binding comes from the circuit input and the
// pre_state is intentionally unauthorized; without one the binding comes from
// a Claim or caller pda_seeds, so the pre_state must already be authorized.
// When `binding` is `Some`, execution_state already asserted
// `!pre_state.is_authorized`.
(WitnessKind::Pda { binding }, NullifierWitness::Update { .. }) => assert!(
pre_state.is_authorized ^ binding.is_some(),
"Private PDA update requires authorized pre_state or external seed"
),
}
let new_nullifier = (
Nullifier::for_account_initialization(&account_id),
*commitment_root,
);
let new_nonce = Nonce::private_account_nonce_init(&account_id);
let view_tag = EncryptedAccountData::compute_view_tag(&npk, vpk);
let (new_nullifier, new_nonce, view_tag) = match nullifier {
NullifierWitness::Init {
npk,
commitment_root,
} => {
assert_eq!(
pre_state.account,
Account::default(),
"Private account init requires a default pre-state"
);
(
(
Nullifier::for_account_initialization(&account_id),
*commitment_root,
),
Nonce::private_account_nonce_init(&account_id),
EncryptedAccountData::compute_view_tag(npk, vpk),
)
}
NullifierWitness::Update {
view_tag,
nsk,
membership_proof,
} => (
compute_update_nullifier_and_set_digest(
membership_proof,
&pre_state.account,
&account_id,
nsk,
),
pre_state.account.nonce.private_account_nonce_increment(nsk),
*view_tag,
),
};
let account_kind = match kind {
WitnessKind::Regular => PrivateAccountKind::Regular(*identifier),
WitnessKind::Pda { .. } => {
let (authority_program_id, seed) = pda_seed_by_position
.get(&pos)
.expect("private PDA position must be in pda_seed_by_position");
PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: *identifier,
}
}
};
emit_private_output(
&mut output,
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
&account_kind,
view_tag,
vpk,
random_seed,
@ -80,180 +148,6 @@ pub fn compute_circuit_output(
new_nonce,
);
}
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk,
random_seed,
view_tag,
nsk,
membership_proof,
identifier,
} => {
let npk = NullifierPublicKey::from(nsk);
let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier);
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
assert!(
pre_state.is_authorized,
"Pre-state not authorized for authenticated private account"
);
let new_nullifier = compute_update_nullifier_and_set_digest(
membership_proof,
&pre_state.account,
&account_id,
nsk,
);
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
emit_private_output(
&mut output,
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
*view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
}
InputAccountIdentity::PrivateForeignInit {
vpk,
random_seed,
npk,
identifier,
commitment_root,
} => {
let account_id = AccountId::for_regular_private_account(npk, vpk, *identifier);
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
assert_eq!(
pre_state.account,
Account::default(),
"Found new private account with non default values",
);
assert!(
pre_state.is_authorized,
"Found new private account marked as unauthorized."
);
let new_nullifier = (
Nullifier::for_account_initialization(&account_id),
*commitment_root,
);
let new_nonce = Nonce::private_account_nonce_init(&account_id);
let view_tag = EncryptedAccountData::compute_view_tag(npk, vpk);
emit_private_output(
&mut output,
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
}
InputAccountIdentity::PrivatePdaInit {
vpk,
random_seed,
npk,
identifier,
commitment_root,
seed: _,
} => {
// The npk-to-account_id binding is established upstream in
// `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds`
// match. Here we only enforce the init pre-conditions. The supplied npk on
// the variant has been recorded into `private_pda_by_position` and used
// for the binding check; we use `pre_state.account_id` directly for nullifier
// and commitment derivation.
assert!(
!pre_state.is_authorized,
"PrivatePdaInit requires unauthorized pre_state"
);
assert_eq!(
pre_state.account,
Account::default(),
"New private PDA must be default"
);
let new_nullifier = (
Nullifier::for_account_initialization(&pre_state.account_id),
*commitment_root,
);
let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id);
let account_id = pre_state.account_id;
let (authority_program_id, seed) = pda_seed_by_position
.get(&pos)
.expect("PrivatePdaInit position must be in pda_seed_by_position");
let view_tag = EncryptedAccountData::compute_view_tag(npk, vpk);
emit_private_output(
&mut output,
post_state,
&account_id,
&PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: *identifier,
},
view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
}
InputAccountIdentity::PrivatePdaUpdate {
vpk,
random_seed,
view_tag,
nsk,
membership_proof,
identifier,
seed: external_seed,
} => {
// With an external seed the binding comes from the circuit input and the
// pre_state is intentionally unauthorized; without one the binding comes from
// a Claim or caller pda_seeds, so the pre_state must already be authorized.
// When `external_seed` is `Some`, execution_state already asserted
// `!pre_state.is_authorized`.
assert!(
pre_state.is_authorized ^ external_seed.is_some(),
"PrivatePdaUpdate requires authorized pre_state or external seed"
);
let new_nullifier = compute_update_nullifier_and_set_digest(
membership_proof,
&pre_state.account,
&pre_state.account_id,
nsk,
);
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
let account_id = pre_state.account_id;
let (authority_program_id, seed) = pda_seed_by_position
.get(&pos)
.expect("PrivatePdaUpdate position must be in pda_seed_by_position");
emit_private_output(
&mut output,
post_state,
&account_id,
&PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: *identifier,
},
*view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
}
}
}

View File

@ -24,74 +24,63 @@ pub struct PrivacyPreservingCircuitInput {
}
#[derive(Serialize, Deserialize, Clone)]
#[expect(
clippy::large_enum_variant,
reason = "Private carries the ML-KEM viewing key and dominates; boxing it would add a guest heap allocation per witness, and the footprint matches the pre-refactor enum"
)]
pub enum InputAccountIdentity {
/// Public account. The guest reads pre/post state from `program_outputs` and emits no
/// commitment, ciphertext, or nullifier.
Public,
/// Init of an authorized standalone private account: no membership proof. The `pre_state`
/// must be `Account::default()`. The `account_id` is derived as
/// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), vpk, identifier)`
/// and matched against `pre_state.account_id`.
PrivateAuthorizedInit {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
nsk: NullifierSecretKey,
identifier: Identifier,
commitment_root: CommitmentSetDigest,
},
/// Update of an authorized standalone private account: existing on-chain commitment, with
/// membership proof.
PrivateAuthorizedUpdate {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
view_tag: ViewTag,
nsk: NullifierSecretKey,
membership_proof: MembershipProof,
identifier: Identifier,
},
/// Init of a standalone private account the caller does not own (e.g. a recipient who
/// doesn't yet exist on chain). No `nsk`, no membership proof.
PrivateForeignInit {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
npk: NullifierPublicKey,
identifier: Identifier,
commitment_root: CommitmentSetDigest,
},
/// Init of a private PDA, unauthorized. The npk-to-account_id binding is proven upstream
/// via `Claim::Pda(seed)` or a caller's `pda_seeds` match. The identifier diversifies the
/// PDA within the `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it
/// as the 4th input.
PrivatePdaInit {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
npk: NullifierPublicKey,
identifier: Identifier,
commitment_root: CommitmentSetDigest,
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the
Private(PrivateWitness),
}
#[derive(Serialize, Deserialize, Clone)]
pub struct PrivateWitness {
pub vpk: ViewingPublicKey,
pub random_seed: [u8; 32],
pub identifier: Identifier,
pub kind: WitnessKind,
pub nullifier: NullifierWitness,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum WitnessKind {
/// Standalone private account. The `account_id` is derived as
/// `AccountId::for_regular_private_account(&npk, vpk, identifier)` and matched against
/// `pre_state.account_id`.
Regular,
/// Private PDA. The npk-to-account_id binding is proven upstream via `Claim::Pda(seed)` or a
/// caller's `pda_seeds` match. The identifier diversifies the PDA within the
/// `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it as the 4th input.
/// An init is unauthorized; on an update, authorization may be established upstream by a
/// caller `pda_seeds` match or a previously-seen authorization in a chained call.
Pda {
/// When `Some((authority_program_id, seed))`, the circuit binds this position via the
/// external derivation check
/// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) ==
/// pre_state.account_id` rather than requiring a `Claim::Pda` or caller
/// `pda_seeds` to establish the binding. The `pre_state` must have `is_authorized
/// == false`.
seed: Option<(PdaSeed, ProgramId)>,
binding: Option<(ProgramId, PdaSeed)>,
},
/// Update of an existing private PDA, with membership proof. `npk` is derived
/// from `nsk`. Authorization may be established upstream by a caller `pda_seeds` match or a
/// previously-seen authorization in a chained call.
PrivatePdaUpdate {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
}
#[derive(Serialize, Deserialize, Clone)]
pub enum NullifierWitness {
/// Init of a private account: no membership proof. The `pre_state` must be
/// `Account::default()`. `npk` is supplied directly, so the caller need not own the account
/// (e.g. a recipient who doesn't yet exist on chain).
Init {
npk: NullifierPublicKey,
commitment_root: CommitmentSetDigest,
},
/// Update of a private account: existing on-chain commitment, with membership proof. `npk`
/// is derived from `nsk`.
Update {
view_tag: ViewTag,
nsk: NullifierSecretKey,
membership_proof: MembershipProof,
identifier: Identifier,
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the
/// external derivation check
/// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) ==
/// pre_state.account_id` rather than requiring a caller `pda_seeds` to establish
/// the binding. The `pre_state` must have `is_authorized == false`.
seed: Option<(PdaSeed, ProgramId)>,
},
}
@ -119,7 +108,10 @@ impl InputAccountIdentity {
pub const fn is_private_pda(&self) -> bool {
matches!(
self,
Self::PrivatePdaInit { .. } | Self::PrivatePdaUpdate { .. }
Self::Private(PrivateWitness {
kind: WitnessKind::Pda { .. },
..
})
)
}
@ -128,22 +120,24 @@ impl InputAccountIdentity {
&self,
) -> Option<(NullifierPublicKey, ViewingPublicKey, Identifier)> {
match self {
Self::PrivatePdaInit {
npk,
Self::Private(PrivateWitness {
vpk,
identifier,
kind: WitnessKind::Pda { .. },
nullifier,
..
} => Some((*npk, vpk.clone(), *identifier)),
Self::PrivatePdaUpdate {
nsk,
vpk,
identifier,
..
} => Some((NullifierPublicKey::from(nsk), vpk.clone(), *identifier)),
Self::Public
| Self::PrivateAuthorizedInit { .. }
| Self::PrivateAuthorizedUpdate { .. }
| Self::PrivateForeignInit { .. } => None,
}) => Some((nullifier.npk(), vpk.clone(), *identifier)),
Self::Public | Self::Private(_) => None,
}
}
}
impl NullifierWitness {
#[must_use]
pub fn npk(&self) -> NullifierPublicKey {
match self {
Self::Init { npk, .. } => *npk,
Self::Update { nsk, .. } => NullifierPublicKey::from(nsk),
}
}
}

View File

@ -4,8 +4,8 @@
)]
pub use circuit_io::{
DummyInput, InputAccountIdentity, PrivacyPreservingCircuitInput,
PrivacyPreservingCircuitOutput, PrivateAction, PublicAction,
DummyInput, InputAccountIdentity, NullifierWitness, PrivacyPreservingCircuitInput,
PrivacyPreservingCircuitOutput, PrivateAction, PrivateWitness, PublicAction, WitnessKind,
};
pub use commitment::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof,

View File

@ -2,7 +2,8 @@
use lee_core::{
Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey,
Nullifier, PrivacyPreservingCircuitOutput, SharedSecretKey,
Nullifier, NullifierPublicKey, NullifierWitness, PrivacyPreservingCircuitOutput,
PrivateWitness, SharedSecretKey, WitnessKind,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
program::{PdaSeed, PrivateAccountKind},
};
@ -88,13 +89,16 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts()
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivateForeignInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&crate::test_methods::simple_balance_transfer().into(),
)
@ -191,23 +195,29 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
vec![sender_pre, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: commitment_set
.get_proof_for(&commitment_sender)
.expect("sender's commitment must be in the set"),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: commitment_set
.get_proof_for(&commitment_sender)
.expect("sender's commitment must be in the set"),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
)
@ -270,13 +280,16 @@ fn init_note_view_tag_is_derived_from_account_keys() {
let (output, proof) = execute_and_prove(
vec![account],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateForeignInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk: keys.npk(),
identifier,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
@ -312,14 +325,17 @@ fn update_note_view_tag_is_the_supplied_value() {
let (output, proof) = execute_and_prove(
vec![sender],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: fed_tag,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
identifier,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: fed_tag,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
},
})],
&program.into(),
)
.unwrap();
@ -361,13 +377,16 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
let result = execute_and_prove(
vec![pre],
instruction,
vec![InputAccountIdentity::PrivateForeignInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: account_keys.vpk(),
random_seed: [0; 32],
npk: account_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: account_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program_with_deps,
);
@ -394,14 +413,16 @@ fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() {
let (output, _proof) = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.clone().into(),
)
.unwrap();
@ -440,14 +461,16 @@ fn private_pda_init() {
let result = execute_and_prove(
vec![pda_pre],
instruction,
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program_with_deps,
);
@ -492,14 +515,16 @@ fn private_pda_withdraw() {
vec![pda_pre, recipient_pre],
instruction,
vec![
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
InputAccountIdentity::Public,
],
&program_with_deps,
@ -545,13 +570,16 @@ fn shared_account_receives_via_simple_transfer() {
instruction,
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivateForeignInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: shared_keys.vpk(),
random_seed: [0; 32],
npk: shared_npk,
identifier: shared_identifier,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: shared_npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -561,8 +589,9 @@ fn shared_account_receives_via_simple_transfer() {
assert_eq!(output.private_actions.len(), 1);
}
/// `PrivateAuthorizedInit` with a non-default identifier produces a ciphertext that decrypts
/// to `PrivateAccountKind::Regular` carrying the correct identifier.
/// A regular init with an npk derived from the held `nsk` and a non-default identifier
/// produces a ciphertext that decrypts to `PrivateAccountKind::Regular` carrying the correct
/// identifier.
#[test]
fn private_authorized_init_encrypts_regular_kind_with_identifier() {
let program = crate::test_methods::claimer();
@ -580,13 +609,16 @@ fn private_authorized_init_encrypts_regular_kind_with_identifier() {
let (output, _) = execute_and_prove(
vec![pre],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
nsk: keys.nsk,
identifier,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: NullifierPublicKey::from(&keys.nsk),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
@ -597,8 +629,9 @@ fn private_authorized_init_encrypts_regular_kind_with_identifier() {
);
}
/// `PrivateForeignInit` with a non-default identifier produces a ciphertext that decrypts
/// to `PrivateAccountKind::Regular` carrying the correct identifier.
/// A regular init with a directly-supplied npk (the caller does not own the account) and a
/// non-default identifier produces a ciphertext that decrypts to `PrivateAccountKind::Regular`
/// carrying the correct identifier.
#[test]
fn private_foreign_init_encrypts_regular_kind_with_identifier() {
let program = crate::test_methods::claimer();
@ -616,13 +649,16 @@ fn private_foreign_init_encrypts_regular_kind_with_identifier() {
let (output, _) = execute_and_prove(
vec![recipient],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateForeignInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk: keys.npk(),
identifier,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
@ -633,7 +669,7 @@ fn private_foreign_init_encrypts_regular_kind_with_identifier() {
);
}
/// `PrivateAuthorizedUpdate` with a non-default identifier produces a ciphertext that decrypts
/// A regular update with a non-default identifier produces a ciphertext that decrypts
/// to `PrivateAccountKind::Regular` carrying the correct identifier.
#[test]
fn private_authorized_update_encrypts_regular_kind_with_identifier() {
@ -661,14 +697,17 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() {
let (output, _) = execute_and_prove(
vec![sender],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
identifier,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
},
})],
&program.into(),
)
.unwrap();
@ -679,7 +718,7 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() {
);
}
/// `PrivatePdaUpdate` with a non-default identifier produces a ciphertext that decrypts
/// A private-PDA update with a non-default identifier produces a ciphertext that decrypts
/// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`.
#[test]
fn private_pda_update_encrypts_pda_kind_with_identifier() {
@ -718,15 +757,17 @@ fn private_pda_update_encrypts_pda_kind_with_identifier() {
vec![pda_pre, recipient_pre],
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
vec![
InputAccountIdentity::PrivatePdaUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
identifier,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
},
}),
InputAccountIdentity::Public,
],
&program_with_deps,
@ -755,14 +796,16 @@ fn private_pda_init_identifier_mismatch_fails() {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: 99,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
);
@ -797,15 +840,17 @@ fn private_pda_update_identifier_mismatch_fails() {
vec![pda_pre, recipient_pre],
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
vec![
InputAccountIdentity::PrivatePdaUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
identifier: 99,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
},
}),
InputAccountIdentity::Public,
],
&program_with_deps,

View File

@ -71,14 +71,17 @@ fn private_changer_claimer_no_data_change_no_claim_succeeds() {
let result = execute_and_prove(
vec![private_account],
Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
})],
&program.into(),
);
@ -102,14 +105,17 @@ fn private_changer_claimer_data_change_no_claim_fails() {
let result = execute_and_prove(
vec![private_account],
Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
})],
&program.into(),
);

View File

@ -55,27 +55,33 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() {
// Setting the recipient nsk to authorize the sender.
// This should be set to the sender private account in a normal circumstance.
// `PrivateAuthorizedUpdate` derives npk from nsk and asserts equality with
// A regular update derives npk from nsk and asserts equality with
// `pre_state.account_id`, so a mismatched nsk fails that check.
let result = execute_and_prove(
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: (0, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -111,21 +117,27 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -161,21 +173,27 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -211,21 +229,27 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -261,21 +285,27 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -309,21 +339,27 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu
vec![private_account_1, private_account_2],
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -357,14 +393,16 @@ fn private_pda_without_binding_fails() {
Program::serialize_instruction(10_u128).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -390,14 +428,16 @@ fn private_pda_claim_succeeds() {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
);
@ -429,14 +469,16 @@ fn private_pda_npk_mismatch_fails() {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys_b.vpk(),
random_seed: [0; 32],
npk: npk_b,
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk: npk_b,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
);
@ -466,14 +508,16 @@ fn caller_pda_seeds_authorize_private_pda_for_callee() {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction((seed, seed, callee_id)).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program_with_deps,
);
@ -505,14 +549,16 @@ fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() {
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program_with_deps,
);
@ -556,22 +602,26 @@ fn two_private_pda_claims_under_same_seed_are_rejected() {
vec![pre_a, pre_b],
Program::serialize_instruction(seed).unwrap(),
vec![
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: keys_a.vpk(),
random_seed: [0; 32],
npk: keys_a.npk(),
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
},
InputAccountIdentity::PrivatePdaInit {
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk: keys_a.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: keys_b.vpk(),
random_seed: [0; 32],
npk: keys_b.npk(),
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk: keys_b.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
@ -582,7 +632,7 @@ fn two_private_pda_claims_under_same_seed_are_rejected() {
/// A private PDA that is reused at top level without an external seed in the identity still
/// fails binding. The noop program emits no `Claim::Pda` and there is no caller
/// `ChainedCall.pda_seeds`, so position 0 is never bound and the assertion fires.
/// Supplying `seed: Some((seed, owner_program_id))` in the `PrivatePdaUpdate` identity is
/// Supplying `binding: Some((owner_program_id, seed))` in the witness's `WitnessKind::Pda` is
/// the correct path for top-level reuse; this test pins the failure when no seed is provided.
#[test]
fn private_pda_top_level_reuse_rejected_by_binding_check() {
@ -606,14 +656,16 @@ fn private_pda_top_level_reuse_rejected_by_binding_check() {
let result = execute_and_prove(
vec![owned_pre_state],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
npk,
identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: None,
}],
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
);
@ -693,22 +745,28 @@ fn circuit_should_fail_if_there_are_repeated_ids() {
vec![private_account_1.clone(), private_account_1],
Program::serialize_instruction(100_u128).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
identifier: 0,
},
InputAccountIdentity::PrivateAuthorizedUpdate {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
},
}),
],
&program.into(),
);
@ -740,13 +798,16 @@ fn private_authorized_uninitialized_account() {
let (output, proof) = execute_and_prove(
vec![authorized_account],
Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: private_keys.vpk(),
random_seed: [0; 32],
nsk: private_keys.nsk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: NullifierPublicKey::from(&private_keys.nsk),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
@ -786,13 +847,16 @@ fn private_unauthorized_uninitialized_account_can_still_be_claimed() {
let (output, proof) = execute_and_prove(
vec![unauthorized_account],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateForeignInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: private_keys.vpk(),
random_seed: [0; 32],
npk: private_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: private_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
@ -836,13 +900,16 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
let (output, proof) = execute_and_prove(
vec![authorized_account.clone()],
Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: private_keys.vpk(),
random_seed: [0; 32],
nsk: private_keys.nsk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: NullifierPublicKey::from(&private_keys.nsk),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&claimer_program.into(),
)
.unwrap();
@ -878,13 +945,16 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
let res = execute_and_prove(
vec![account_metadata],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: private_keys.vpk(),
random_seed: [0; 32],
nsk: private_keys.nsk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: NullifierPublicKey::from(&private_keys.nsk),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&noop_program.into(),
);
@ -945,14 +1015,18 @@ fn two_private_pda_family_members_receive_and_spend() {
Program::serialize_instruction(amount).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
npk: alice_npk,
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: Some((seed, proxy_id)),
},
kind: WitnessKind::Pda {
binding: Some((proxy_id, seed)),
},
nullifier: NullifierWitness::Init {
npk: alice_npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&simple_transfer.clone().into(),
)
@ -980,14 +1054,18 @@ fn two_private_pda_family_members_receive_and_spend() {
Program::serialize_instruction(amount).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
npk: alice_npk,
identifier: 1,
commitment_root: DUMMY_COMMITMENT_HASH,
seed: Some((seed, proxy_id)),
},
kind: WitnessKind::Pda {
binding: Some((proxy_id, seed)),
},
nullifier: NullifierWitness::Init {
npk: alice_npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&simple_transfer.into(),
)
@ -1019,17 +1097,19 @@ fn two_private_pda_family_members_receive_and_spend() {
],
Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(),
vec![
InputAccountIdentity::PrivatePdaUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_0)
.expect("pda_0 must be in state"),
identifier: 0,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_0)
.expect("pda_0 must be in state"),
},
}),
InputAccountIdentity::Public,
],
&spend_with_deps,
@ -1056,17 +1136,19 @@ fn two_private_pda_family_members_receive_and_spend() {
],
Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(),
vec![
InputAccountIdentity::PrivatePdaUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_1)
.expect("pda_1 must be in state"),
identifier: 1,
seed: None,
},
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_1)
.expect("pda_1 must be in state"),
},
}),
InputAccountIdentity::Public,
],
&spend_with_deps,
@ -1085,7 +1167,7 @@ fn two_private_pda_family_members_receive_and_spend() {
assert_eq!(state.get_account_by_id(recipient_id).balance, 2 * amount);
// Re-fund alice_pda_1 top-level via simple_transfer using PrivatePdaUpdate with an
// Re-fund alice_pda_1 top-level via simple_transfer using a private-PDA update with an
// external seed.
let alice_pda_1_account_after_spend = Account {
program_owner: simple_transfer_id,
@ -1108,17 +1190,21 @@ fn two_private_pda_family_members_receive_and_spend() {
Program::serialize_instruction(amount).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_1_after_spend)
.expect("pda_1 after spend must be in state"),
identifier: 1,
seed: Some((seed, proxy_id)),
},
kind: WitnessKind::Pda {
binding: Some((proxy_id, seed)),
},
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_1_after_spend)
.expect("pda_1 after spend must be in state"),
},
}),
],
&crate::test_methods::simple_balance_transfer().into(),
)

View File

@ -325,16 +325,19 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
.expect("sender's commitment must be in state"),
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
.expect("sender's commitment must be in state"),
},
}),
InputAccountIdentity::Public,
],
&program.into(),
@ -439,26 +442,32 @@ fn private_chained_call(number_of_calls: u32) {
vec![to_account, from_account],
Program::serialize_instruction(instruction).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: from_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: from_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&from_commitment)
.expect("from's commitment must be in state"),
identifier: 0,
},
InputAccountIdentity::PrivateAuthorizedUpdate {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: from_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&from_commitment)
.expect("from's commitment must be in state"),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: to_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: to_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&to_commitment)
.expect("to's commitment must be in state"),
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: to_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&to_commitment)
.expect("to's commitment must be in state"),
},
}),
],
&program_with_deps,
)

View File

@ -8,7 +8,8 @@ use std::collections::HashMap;
use lee_core::{
BlockId, Commitment, DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier,
NullifierPublicKey, NullifierSecretKey, Timestamp,
NullifierPublicKey, NullifierSecretKey, NullifierWitness, PrivateWitness, Timestamp,
WitnessKind,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
encryption::ViewingPublicKey,
program::{
@ -279,13 +280,16 @@ fn shielded_balance_transfer_for_tests(
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivateForeignInit {
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&crate::test_methods::simple_balance_transfer().into(),
)
@ -323,23 +327,29 @@ fn private_balance_transfer_for_tests(
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
.expect("sender's commitment must be in state"),
identifier: 0,
},
InputAccountIdentity::PrivateForeignInit {
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
.expect("sender's commitment must be in state"),
},
}),
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
npk: recipient_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
)
@ -378,16 +388,19 @@ fn deshielded_balance_transfer_for_tests(
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
.expect("sender's commitment must be in state"),
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
.expect("sender's commitment must be in state"),
},
}),
InputAccountIdentity::Public,
],
&program.into(),

View File

@ -521,16 +521,19 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
Program::serialize_instruction(instruction).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&recipient_commitment)
.expect("recipient's commitment must be in state"),
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&recipient_commitment)
.expect("recipient's commitment must be in state"),
},
}),
],
&program_with_deps,
);

View File

@ -138,13 +138,16 @@ fn validity_window_works_in_privacy_preserving_transactions(
let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove(
vec![pre],
Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateForeignInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: account_keys.vpk(),
random_seed: [0; 32],
npk: account_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: account_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&validity_window_program.into(),
)
.unwrap();
@ -203,13 +206,16 @@ fn timestamp_validity_window_works_in_privacy_preserving_transactions(
let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove(
vec![pre],
Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateForeignInit {
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: account_keys.vpk(),
random_seed: [0; 32],
npk: account_keys.npk(),
identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}],
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: account_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&validity_window_program.into(),
)
.unwrap();

View File

@ -78,7 +78,7 @@ fn public_diff_reflects_a_successful_transfer() {
#[test]
fn privacy_malicious_programs_cannot_drain_public_victim() {
use lee_core::{
Commitment, InputAccountIdentity,
Commitment, InputAccountIdentity, NullifierWitness, PrivateWitness, WitnessKind,
account::{Account, AccountWithMetadata},
};
@ -164,14 +164,17 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
// [1] victim — first seen in simple_balance_transfer's program_output.pre_states
// [2] recipient — first seen in simple_balance_transfer's program_output.pre_states
let account_identities = vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: attacker_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: attacker_keys.nsk,
membership_proof,
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: attacker_keys.nsk,
membership_proof,
},
}),
InputAccountIdentity::Public, // victim
InputAccountIdentity::Public, // recipient
];
@ -213,7 +216,7 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
/// verbatim, the attacker must choose how to declare the victim in `account_identities`.
/// There are two routes, both closed:
///
/// - **mask=1 (`PrivateAuthorizedUpdate`)**: the circuit derives `account_id =
/// - **mask=1 (regular update)**: the circuit derives `account_id =
/// AccountId::for_regular_private_account(&npk_from(nsk), identifier)` and asserts it matches
/// `pre_state.account_id`. Passing this check requires the victim's `nsk`, which the attacker
/// does not have. `execute_and_prove` panics inside the ZKVM and no proof is produced.
@ -227,7 +230,7 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
#[test]
fn privacy_malicious_programs_cannot_drain_private_victim() {
use lee_core::{
Commitment, InputAccountIdentity,
Commitment, InputAccountIdentity, NullifierWitness, PrivateWitness, WitnessKind,
account::{Account, AccountWithMetadata},
};
@ -321,16 +324,19 @@ fn privacy_malicious_programs_cannot_drain_private_victim() {
// [2] recipient — first seen in simple_balance_transfer's program_output.pre_states
//
// Victim is marked Public: the attacker has no nsk for the victim's private account,
// so PrivateAuthorizedUpdate is not an option.
// so a regular update is not an option.
let account_identities = vec![
InputAccountIdentity::PrivateAuthorizedUpdate {
InputAccountIdentity::Private(PrivateWitness {
vpk: attacker_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: attacker_keys.nsk,
membership_proof,
identifier: 0,
},
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Update {
view_tag: 0,
nsk: attacker_keys.nsk,
membership_proof,
},
}),
InputAccountIdentity::Public, // victim — attacker lacks victim's nsk
InputAccountIdentity::Public, // recipient
];

View File

@ -5,7 +5,8 @@ use keycard_wallet::KeycardWallet;
use lee::{AccountId, PrivateKey, PublicKey, Signature};
use lee_core::{
Commitment, CommitmentSetDigest, DummyInput, Identifier, InputAccountIdentity, MembershipProof,
NullifierPublicKey, NullifierSecretKey, PrivateAccountKind, SharedSecretKey,
NullifierPublicKey, NullifierSecretKey, NullifierWitness, PrivateAccountKind, PrivateWitness,
SharedSecretKey, WitnessKind,
account::{Account, AccountWithMetadata, Nonce},
compute_digest_for_path,
encryption::{
@ -430,60 +431,42 @@ impl AccountManager {
self.dummy_inputs(Self::MAX_PRIVATE_ACCOUNTS.saturating_sub(private_count))
}
/// Build the per-account input vec for the privacy-preserving circuit. Each variant carries
/// exactly the fields the circuit's code path for that account needs, with the ephemeral
/// keys (`ssk`) drawn from the cached values that `private_account_keys` and the message
/// construction also use, so all three views agree on the same ephemeral key.
/// Build the per-account input vec for the privacy-preserving circuit. The `kind` and
/// `nullifier` axes select exactly the fields the circuit's code path for that account
/// needs, with the ephemeral keys (`ssk`) drawn from the cached values that
/// `private_account_keys` and the message construction also use, so all three views agree
/// on the same ephemeral key.
pub fn account_identities(&self) -> Vec<InputAccountIdentity> {
self.states
.iter()
.map(|state| match state {
State::Public { .. } | State::PublicKeycard { .. } => InputAccountIdentity::Public,
State::Private(pre) if pre.is_pda => match (pre.nsk, pre.proof.clone()) {
(Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
view_tag: random_view_tag(),
nsk,
membership_proof,
identifier: pre.identifier,
seed: None,
State::Private(pre) => InputAccountIdentity::Private(PrivateWitness {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
identifier: pre.identifier,
kind: if pre.is_pda {
WitnessKind::Pda { binding: None }
} else {
WitnessKind::Regular
},
_ => InputAccountIdentity::PrivatePdaInit {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
npk: pre.npk,
identifier: pre.identifier,
commitment_root: self.dummy_commitment_root,
seed: None,
},
},
State::Private(pre) => match (pre.nsk, pre.proof.clone()) {
(Some(nsk), Some(membership_proof)) => {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
nullifier: match (pre.nsk, pre.proof.clone()) {
(Some(nsk), Some(membership_proof)) => NullifierWitness::Update {
view_tag: random_view_tag(),
nsk,
membership_proof,
identifier: pre.identifier,
}
}
(Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
nsk,
identifier: pre.identifier,
commitment_root: self.dummy_commitment_root,
},
(nsk, _) => NullifierWitness::Init {
// A regular init recomputes the npk from the key the wallet holds;
// a PDA's stored npk is the owner's, so it is passed through.
npk: match nsk {
Some(nsk) if !pre.is_pda => NullifierPublicKey::from(&nsk),
_ => pre.npk,
},
commitment_root: self.dummy_commitment_root,
},
},
(None, _) => InputAccountIdentity::PrivateForeignInit {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
npk: pre.npk,
identifier: pre.identifier,
commitment_root: self.dummy_commitment_root,
},
},
}),
})
.collect()
}
@ -552,7 +535,7 @@ struct AccountPreparedData {
proof: Option<MembershipProof>,
random_seed: [u8; 32],
/// True when this account is a private PDA (owned or foreign). Used by `account_identities()`
/// to select `PrivatePdaInit`/`PrivatePdaUpdate` rather than the standalone private variants.
/// to select `WitnessKind::Pda` rather than `WitnessKind::Regular`.
is_pda: bool,
}