2026-08-04 15:55:09 +04:00

861 lines
31 KiB
Rust

#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
use lee_core::{
Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey,
Nullifier, NullifierPublicKey, NullifierWitness, PrivacyPreservingCircuitOutput,
PrivateWitness, SharedSecretKey, WitnessKind,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
program::{PdaSeed, PrivateAccountKind},
};
use super::*;
use crate::{
error::LeeError,
privacy_preserving_transaction::circuit::execute_and_prove,
program::Program,
state::{
CommitmentSet,
tests::{test_private_account_keys_1, test_private_account_keys_2},
},
};
fn decrypt_kind(
output: &PrivacyPreservingCircuitOutput,
ssk: &SharedSecretKey,
idx: usize,
) -> PrivateAccountKind {
let (kind, _) = EncryptionScheme::decrypt(
&output.private_actions[idx].encrypted_post_state.ciphertext,
ssk,
&output.private_actions[idx].nullifier,
)
.unwrap();
kind
}
#[test]
fn proof_inner_roundtrip() {
// `Proof::from_inner(b).into_inner()` must return exactly `b`. Catches
// mutations of `into_inner` returning `vec![]`, `vec![0]`, or `vec![1]`,
// and of `from_inner` discarding its argument.
let bytes = vec![0xDE_u8, 0xAD, 0xBE, 0xEF];
assert_eq!(Proof::from_inner(bytes.clone()).into_inner(), bytes);
assert!(Proof::from_inner(vec![]).into_inner().is_empty());
assert_eq!(Proof::from_inner(vec![0xFF]).into_inner(), vec![0xFF_u8]);
}
#[test]
fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() {
let recipient_keys = test_private_account_keys_1();
let program = crate::test_methods::simple_balance_transfer();
let sender = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
true,
AccountId::new([0; 32]),
);
let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let recipient = AccountWithMetadata::new(Account::default(), true, recipient_account_id);
let balance_to_move: u128 = 37;
let expected_sender_post = Account {
program_owner: program.id(),
balance: 100 - balance_to_move,
nonce: Nonce::default(),
data: Data::default(),
};
let expected_recipient_post = Account {
program_owner: program.id(),
balance: balance_to_move,
nonce: Nonce::private_account_nonce_init(&recipient_account_id),
data: Data::default(),
};
let expected_sender_pre = sender.clone();
let init_nonce = Nonce::private_account_nonce_init(&recipient_account_id);
let esk = EphemeralSecretKey::new(&recipient_account_id, &[0; 32], &init_nonce);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &esk).0;
let (output, proof) = execute_and_prove(
vec![sender, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::Public,
InputAccountIdentity::Private(PrivateWitness {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
identifier: 0,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&crate::test_methods::simple_balance_transfer().into(),
)
.unwrap();
assert!(proof.is_valid_for(&output));
let [action] = output.public_actions.try_into().unwrap();
let (sender_pre, sender_post) = (action.pre, action.post);
assert_eq!(sender_pre, expected_sender_pre);
assert_eq!(sender_post, expected_sender_post);
assert_eq!(output.private_actions.len(), 1);
let (_identifier, recipient_post) = EncryptionScheme::decrypt(
&output.private_actions[0].encrypted_post_state.ciphertext,
&shared_secret,
&output.private_actions[0].nullifier,
)
.unwrap();
assert_eq!(recipient_post, expected_recipient_post);
}
#[test]
fn prove_privacy_preserving_execution_circuit_fully_private() {
let program = crate::test_methods::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let sender_nonce = Nonce(0xdead_beef);
let sender_pre = AccountWithMetadata::new(
Account {
balance: 100,
nonce: sender_nonce,
program_owner: program.id(),
data: Data::default(),
},
true,
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0),
);
let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let commitment_sender = Commitment::new(&sender_account_id, &sender_pre.account);
let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let recipient = AccountWithMetadata::new(Account::default(), true, recipient_account_id);
let balance_to_move: u128 = 37;
let mut commitment_set = CommitmentSet::with_capacity(2);
commitment_set.extend(std::slice::from_ref(&commitment_sender));
let expected_new_nullifiers = vec![
(
Nullifier::for_account_update(&commitment_sender, &sender_keys.nsk),
commitment_set.digest(),
),
(
Nullifier::for_account_initialization(&recipient_account_id),
DUMMY_COMMITMENT_HASH,
),
];
let program = crate::test_methods::simple_balance_transfer();
let expected_private_account_1 = Account {
program_owner: program.id(),
balance: 100 - balance_to_move,
nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
..Default::default()
};
let expected_private_account_2 = Account {
program_owner: program.id(),
balance: balance_to_move,
nonce: Nonce::private_account_nonce_init(&recipient_account_id),
..Default::default()
};
let expected_new_commitments = vec![
Commitment::new(&sender_account_id, &expected_private_account_1),
Commitment::new(&recipient_account_id, &expected_private_account_2),
];
let esk_1 = EphemeralSecretKey::new(
&sender_account_id,
&[0; 32],
&sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
);
let shared_secret_1 = SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &esk_1).0;
let init_nonce_2 = Nonce::private_account_nonce_init(&recipient_account_id);
let esk_2 = EphemeralSecretKey::new(&recipient_account_id, &[0; 32], &init_nonce_2);
let shared_secret_2 =
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &esk_2).0;
let (output, proof) = execute_and_prove(
vec![sender_pre, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![
InputAccountIdentity::Private(PrivateWitness {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
identifier: 0,
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],
identifier: 0,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: recipient_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
)
.unwrap();
assert!(proof.is_valid_for(&output));
assert!(output.public_actions.is_empty());
let sender_nullifier = expected_new_nullifiers[0].0;
let recipient_nullifier = expected_new_nullifiers[1].0;
let mut sorted_commitments = expected_new_commitments;
sorted_commitments.sort_unstable_by_key(Commitment::to_byte_array);
assert_eq!(output.commitments(), sorted_commitments);
let mut sorted_nullifiers = expected_new_nullifiers;
sorted_nullifiers.sort_unstable_by_key(|(nullifier, _)| nullifier.to_byte_array());
assert_eq!(output.nullifiers(), sorted_nullifiers);
assert_eq!(output.private_actions.len(), 2);
let sender_slot = output
.private_actions
.iter()
.position(|action| action.nullifier == sender_nullifier)
.unwrap();
let (_identifier, sender_post) = EncryptionScheme::decrypt(
&output.private_actions[sender_slot]
.encrypted_post_state
.ciphertext,
&shared_secret_1,
&output.private_actions[sender_slot].nullifier,
)
.unwrap();
assert_eq!(sender_post, expected_private_account_1);
let recipient_slot = output
.private_actions
.iter()
.position(|action| action.nullifier == recipient_nullifier)
.unwrap();
let (_identifier, recipient_post) = EncryptionScheme::decrypt(
&output.private_actions[recipient_slot]
.encrypted_post_state
.ciphertext,
&shared_secret_2,
&output.private_actions[recipient_slot].nullifier,
)
.unwrap();
assert_eq!(recipient_post, expected_private_account_2);
}
#[test]
fn init_note_view_tag_is_derived_from_account_keys() {
let program = crate::test_methods::noop();
let keys = test_private_account_keys_1();
let identifier: u128 = 0;
let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let account = AccountWithMetadata::new(Account::default(), true, account_id);
let (output, proof) = execute_and_prove(
vec![account],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
assert!(proof.is_valid_for(&output));
assert_eq!(output.private_actions.len(), 1);
assert_eq!(
output.private_actions[0].encrypted_post_state.view_tag,
EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()),
);
}
#[test]
fn update_note_view_tag_is_the_supplied_value() {
let program = crate::test_methods::noop();
let keys = test_private_account_keys_1();
let identifier: u128 = 99;
let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let account = Account {
program_owner: program.id(),
balance: 1,
..Account::default()
};
let commitment = Commitment::new(&account_id, &account);
let mut commitment_set = CommitmentSet::with_capacity(1);
commitment_set.extend(std::slice::from_ref(&commitment));
let sender = AccountWithMetadata::new(account, true, account_id);
// A tag deliberately different from the address-derived one, so a passthrough is
// distinguishable from re-derivation.
let fed_tag = EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()).wrapping_add(1);
let (output, proof) = execute_and_prove(
vec![sender],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
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();
assert!(proof.is_valid_for(&output));
assert_eq!(output.private_actions.len(), 1);
assert_eq!(
output.private_actions[0].encrypted_post_state.view_tag,
fed_tag
);
}
#[test]
fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
let account_keys = test_private_account_keys_1();
let pre = AccountWithMetadata::new(
Account::default(),
true,
AccountId::for_regular_private_account(&account_keys.npk(), &account_keys.vpk(), 0),
);
let validity_window_chain_caller = crate::test_methods::validity_window_chain_caller();
let validity_window = crate::test_methods::validity_window();
let instruction = Program::serialize_instruction((
Some(1_u64),
Some(4_u64),
validity_window.id(),
Some(4_u64),
Some(7_u64),
))
.unwrap();
let program_with_deps = ProgramWithDependencies::new(
validity_window_chain_caller,
[(validity_window.id(), validity_window)].into(),
);
let result = execute_and_prove(
vec![pre],
instruction,
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: account_keys.vpk(),
random_seed: [0; 32],
identifier: 0,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: account_keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program_with_deps,
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
/// A private PDA claimed with a non-default identifier produces a ciphertext that decrypts
/// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`.
#[test]
fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() {
let program = crate::test_methods::pda_claimer();
let keys = test_private_account_keys_1();
let npk = keys.npk();
let seed = PdaSeed::new([42; 32]);
let identifier: u128 = 99;
let account_id =
AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), identifier);
let init_nonce = Nonce::private_account_nonce_init(&account_id);
let esk = EphemeralSecretKey::new(&account_id, &[0; 32], &init_nonce);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let (output, _proof) = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier,
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.clone().into(),
)
.unwrap();
assert_eq!(
decrypt_kind(&output, &shared_secret, 0),
PrivateAccountKind::Pda {
program_id: program.id(),
seed,
identifier
},
);
}
/// PDA init: initializes a new PDA under `simple_balance_transfer`'s ownership.
/// The `simple_transfer_proxy` program chains to `simple_balance_transfer` with `pda_seeds`
/// to establish authorization and the private PDA binding.
#[test]
fn private_pda_init() {
let program = crate::test_methods::simple_transfer_proxy();
let simple_transfer = crate::test_methods::simple_balance_transfer();
let keys = test_private_account_keys_1();
let npk = keys.npk();
let seed = PdaSeed::new([42; 32]);
// PDA (new, private PDA)
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 0);
let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id);
let auth_id = simple_transfer.id();
let program_with_deps =
ProgramWithDependencies::new(program, [(auth_id, simple_transfer)].into());
// is_withdraw=false triggers init path (1 pre-state)
let instruction = Program::serialize_instruction((seed, auth_id, 0_u128, false)).unwrap();
let result = execute_and_prove(
vec![pda_pre],
instruction,
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier: 0,
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program_with_deps,
);
let (output, _proof) = result.expect("PDA init should succeed");
assert_eq!(output.private_actions.len(), 1);
}
/// PDA withdraw: chains to `simple_balance_transfer` to move balance from PDA to recipient.
/// Uses a default PDA (amount=0) because testing with a pre-funded PDA requires a
/// two-tx sequence with membership proofs.
#[test]
fn private_pda_withdraw() {
let program = crate::test_methods::simple_transfer_proxy();
let simple_transfer = crate::test_methods::simple_balance_transfer();
let keys = test_private_account_keys_1();
let npk = keys.npk();
let seed = PdaSeed::new([42; 32]);
// PDA (new, private PDA)
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 0);
let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id);
// Recipient (public)
let recipient_id = AccountId::new([88; 32]);
let recipient_pre = AccountWithMetadata::new(
Account {
program_owner: simple_transfer.id(),
balance: 10000,
..Account::default()
},
true,
recipient_id,
);
let auth_id = simple_transfer.id();
let program_with_deps =
ProgramWithDependencies::new(program, [(auth_id, simple_transfer)].into());
// is_withdraw=true, amount=0 (PDA has no balance yet)
let instruction = Program::serialize_instruction((seed, auth_id, 0_u128, true)).unwrap();
let result = execute_and_prove(
vec![pda_pre, recipient_pre],
instruction,
vec![
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier: 0,
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
InputAccountIdentity::Public,
],
&program_with_deps,
);
let (output, _proof) = result.expect("PDA withdraw should succeed");
assert_eq!(output.private_actions.len(), 1);
}
/// Shared regular private account: receives funds via `authenticated_transfer` directly,
/// no custom program needed. This demonstrates the non-PDA shared account flow where
/// keys are derived from GMS via `derive_keys_for_shared_account`. The shared account
/// uses the standard foreign private account path and works with auth-transfer's
/// transfer path like any other private account.
#[test]
fn shared_account_receives_via_simple_transfer() {
let program = crate::test_methods::simple_balance_transfer();
let shared_keys = test_private_account_keys_1();
let shared_npk = shared_keys.npk();
let shared_identifier: u128 = 42;
// Sender: public account with balance, owned by auth-transfer
let sender_id = AccountId::new([99; 32]);
let sender = AccountWithMetadata::new(
Account {
program_owner: program.id(),
balance: 1000,
..Account::default()
},
true,
sender_id,
);
// Recipient: shared private account (new, foreign)
let shared_account_id = AccountId::from((&shared_npk, &shared_keys.vpk(), shared_identifier));
let recipient = AccountWithMetadata::new(Account::default(), true, shared_account_id);
let balance_to_move: u128 = 100;
let instruction = Program::serialize_instruction(balance_to_move).unwrap();
let result = execute_and_prove(
vec![sender, recipient],
instruction,
vec![
InputAccountIdentity::Public,
InputAccountIdentity::Private(PrivateWitness {
vpk: shared_keys.vpk(),
random_seed: [0; 32],
identifier: shared_identifier,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: shared_npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
}),
],
&program.into(),
);
let (output, _proof) = result.expect("shared account receive should succeed");
// Sender is public (no commitment), recipient is private (1 commitment)
assert_eq!(output.private_actions.len(), 1);
}
/// 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();
let keys = test_private_account_keys_1();
let identifier: u128 = 99;
let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let esk = EphemeralSecretKey::new(
&account_id,
&[0; 32],
&Nonce::private_account_nonce_init(&account_id),
);
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
let pre = AccountWithMetadata::new(Account::default(), true, account_id);
let (output, _) = execute_and_prove(
vec![pre],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: NullifierPublicKey::from(&keys.nsk),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
assert_eq!(
decrypt_kind(&output, &ssk, 0),
PrivateAccountKind::Regular(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();
let keys = test_private_account_keys_1();
let identifier: u128 = 99;
let recipient_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let esk = EphemeralSecretKey::new(
&recipient_id,
&[0; 32],
&Nonce::private_account_nonce_init(&recipient_id),
);
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
let recipient = AccountWithMetadata::new(Account::default(), true, recipient_id);
let (output, _) = execute_and_prove(
vec![recipient],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier,
kind: WitnessKind::Regular,
nullifier: NullifierWitness::Init {
npk: keys.npk(),
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
)
.unwrap();
assert_eq!(
decrypt_kind(&output, &ssk, 0),
PrivateAccountKind::Regular(identifier)
);
}
/// 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() {
let program = crate::test_methods::noop();
let keys = test_private_account_keys_1();
let identifier: u128 = 99;
let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let esk = EphemeralSecretKey::new(
&account_id,
&[0; 32],
&Nonce::default().private_account_nonce_increment(&keys.nsk),
);
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
let account = Account {
program_owner: program.id(),
balance: 1,
..Account::default()
};
let commitment = Commitment::new(&account_id, &account);
let mut commitment_set = CommitmentSet::with_capacity(1);
commitment_set.extend(std::slice::from_ref(&commitment));
let sender = AccountWithMetadata::new(account, true, account_id);
let (output, _) = execute_and_prove(
vec![sender],
Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
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();
assert_eq!(
decrypt_kind(&output, &ssk, 0),
PrivateAccountKind::Regular(identifier)
);
}
/// 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() {
let program = crate::test_methods::pda_spend_proxy();
let simple_transfer = crate::test_methods::simple_balance_transfer();
let keys = test_private_account_keys_1();
let npk = keys.npk();
let seed = PdaSeed::new([42; 32]);
let identifier: u128 = 99;
let simple_transfer_id = simple_transfer.id();
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), identifier);
let esk = EphemeralSecretKey::new(
&pda_id,
&[0; 32],
&Nonce::default().private_account_nonce_increment(&keys.nsk),
);
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
let pda_account = Account {
program_owner: simple_transfer_id,
balance: 1,
..Account::default()
};
let pda_commitment = Commitment::new(&pda_id, &pda_account);
let mut commitment_set = CommitmentSet::with_capacity(1);
commitment_set.extend(std::slice::from_ref(&pda_commitment));
let pda_pre = AccountWithMetadata::new(pda_account, true, pda_id);
let recipient_pre = AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32]));
let program_with_deps = ProgramWithDependencies::new(
program.clone(),
[(simple_transfer_id, simple_transfer)].into(),
);
let (output, _) = execute_and_prove(
vec![pda_pre, recipient_pre],
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
vec![
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier,
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,
)
.unwrap();
assert_eq!(
decrypt_kind(&output, &ssk, 0),
PrivateAccountKind::Pda {
program_id: program.id(),
seed,
identifier
},
);
}
#[test]
fn private_pda_init_identifier_mismatch_fails() {
let program = crate::test_methods::pda_claimer();
let keys = test_private_account_keys_1();
let npk = keys.npk();
let seed = PdaSeed::new([42; 32]);
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5);
let pre_state = AccountWithMetadata::new(Account::default(), true, account_id);
let result = execute_and_prove(
vec![pre_state],
Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier: 99,
kind: WitnessKind::Pda { binding: None },
nullifier: NullifierWitness::Init {
npk,
commitment_root: DUMMY_COMMITMENT_HASH,
},
})],
&program.into(),
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}
#[test]
fn private_pda_update_identifier_mismatch_fails() {
let program = crate::test_methods::pda_spend_proxy();
let simple_transfer = crate::test_methods::simple_balance_transfer();
let keys = test_private_account_keys_1();
let npk = keys.npk();
let seed = PdaSeed::new([42; 32]);
let simple_transfer_id = simple_transfer.id();
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5);
let pda_account = Account {
program_owner: simple_transfer_id,
balance: 1,
..Account::default()
};
let pda_commitment = Commitment::new(&pda_id, &pda_account);
let mut commitment_set = CommitmentSet::with_capacity(1);
commitment_set.extend(std::slice::from_ref(&pda_commitment));
let pda_pre = AccountWithMetadata::new(pda_account, true, pda_id);
let recipient_pre = AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32]));
let program_with_deps =
ProgramWithDependencies::new(program, [(simple_transfer_id, simple_transfer)].into());
let result = execute_and_prove(
vec![pda_pre, recipient_pre],
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
vec![
InputAccountIdentity::Private(PrivateWitness {
vpk: keys.vpk(),
random_seed: [0; 32],
identifier: 99,
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,
);
assert!(matches!(result, Err(LeeError::CircuitProvingError(_))));
}