Merge 640d4f6502d6c23f2d071bbf57609f6e43fd6066 into b31aecd2c064ed8544fb1195033dec5cc196942c

This commit is contained in:
Artem Gureev 2026-07-08 20:22:44 +04:00 committed by GitHub
commit 589536b7af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 1435 additions and 1500 deletions

View File

@ -60,6 +60,7 @@ allow-git = [
"https://github.com/logos-blockchain/logos-blockchain.git", "https://github.com/logos-blockchain/logos-blockchain.git",
"https://github.com/logos-blockchain/logos-blockchain-circuits.git", "https://github.com/logos-blockchain/logos-blockchain-circuits.git",
"https://github.com/logos-blockchain/logos-blockchain-rust-rapidsnark.git", "https://github.com/logos-blockchain/logos-blockchain-rust-rapidsnark.git",
"https://github.com/logos-blockchain/sponges",
"https://github.com/arkworks-rs/spongefish.git", "https://github.com/arkworks-rs/spongefish.git",
] ]
unknown-git = "deny" unknown-git = "deny"

View File

@ -28,10 +28,6 @@ jobs:
- uses: ./.github/actions/install-risc0 - uses: ./.github/actions/install-risc0
- uses: ./.github/actions/install-logos-blockchain-circuits
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install active toolchain - name: Install active toolchain
run: rustup install run: rustup install

9
Cargo.lock generated
View File

@ -1914,7 +1914,6 @@ dependencies = [
"criterion", "criterion",
"key_protocol", "key_protocol",
"lee_core", "lee_core",
"rand 0.8.6",
] ]
[[package]] [[package]]
@ -3850,7 +3849,6 @@ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
"async-stream", "async-stream",
"authenticated_transfer_core",
"borsh", "borsh",
"common", "common",
"futures", "futures",
@ -4441,11 +4439,11 @@ dependencies = [
[[package]] [[package]]
name = "keccak" name = "keccak"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/logos-blockchain/sponges?rev=3a56e99771beedf04946eab21a4a62adc2951377#3a56e99771beedf04946eab21a4a62adc2951377"
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.3.0", "cpufeatures 0.3.0",
"risc0-zkvm",
] ]
[[package]] [[package]]
@ -4553,7 +4551,6 @@ dependencies = [
"anyhow", "anyhow",
"borsh", "borsh",
"build_utils", "build_utils",
"env_logger",
"hex", "hex",
"hex-literal 1.1.0", "hex-literal 1.1.0",
"k256", "k256",
@ -4568,7 +4565,6 @@ dependencies = [
"test-case", "test-case",
"test_methods", "test_methods",
"thiserror 2.0.18", "thiserror 2.0.18",
"token_core",
] ]
[[package]] [[package]]
@ -10890,7 +10886,6 @@ dependencies = [
"risc0-zkvm", "risc0-zkvm",
"sequencer_service_rpc", "sequencer_service_rpc",
"serde_json", "serde_json",
"tempfile",
"tokio", "tokio",
"vault_core", "vault_core",
"wallet", "wallet",

View File

@ -194,6 +194,10 @@ opt-level = 'z'
lto = true lto = true
codegen-units = 1 codegen-units = 1
# Keccak speedup for in-guest ML KEM
[patch.crates-io]
keccak = { git = "https://github.com/logos-blockchain/sponges", rev = "3a56e99771beedf04946eab21a4a62adc2951377" }
# Keep backtraces but drop full DWARF type info to avoid LLD OOM/SIGBUS when # Keep backtraces but drop full DWARF type info to avoid LLD OOM/SIGBUS when
# linking large integration-test binaries on resource-constrained CI runners. # linking large integration-test binaries on resource-constrained CI runners.
[profile.dev] [profile.dev]
@ -339,4 +343,4 @@ clippy.cargo = { level = "deny", priority = -1 }
clippy.cargo-common-metadata = "allow" clippy.cargo-common-metadata = "allow"
# Reason: hard to address right now and mostly comes from dependencies # Reason: hard to address right now and mostly comes from dependencies
# so the fix would be just a long list of exceptions. # so the fix would be just a long list of exceptions.
clippy.multiple-crate-versions = "allow" clippy.multiple-crate-versions = "allow"

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

@ -141,7 +141,11 @@ async fn import_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?; let mut ctx = TestContext::new().await?;
let key_chain = KeyChain::new_os_random(); let key_chain = KeyChain::new_os_random();
let account_id = lee::AccountId::from((&key_chain.nullifier_public_key, 0)); let account_id = lee::AccountId::from((
&key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
0,
));
let account = lee::Account { let account = lee::Account {
program_owner: programs::authenticated_transfer().id(), program_owner: programs::authenticated_transfer().id(),
balance: 777, balance: 777,
@ -198,7 +202,11 @@ async fn import_private_account_second_time_overrides_account_data() -> Result<(
let mut ctx = TestContext::new().await?; let mut ctx = TestContext::new().await?;
let key_chain = KeyChain::new_os_random(); let key_chain = KeyChain::new_os_random();
let account_id = lee::AccountId::from((&key_chain.nullifier_public_key, 0)); let account_id = lee::AccountId::from((
&key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
0,
));
let key_chain_json = let key_chain_json =
serde_json::to_string(&key_chain).context("Failed to serialize key chain")?; serde_json::to_string(&key_chain).context("Failed to serialize key chain")?;

View File

@ -8,15 +8,14 @@ use integration_tests::{
private_mention, public_mention, send, sync_private, verify_commitment_is_in_state, private_mention, public_mention, send, sync_private, verify_commitment_is_in_state,
}; };
use lee::{ use lee::{
AccountId, SharedSecretKey, execute_and_prove, AccountId, execute_and_prove, privacy_preserving_transaction::circuit::ProgramWithDependencies,
privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, program::Program,
}; };
use lee_core::{ use lee_core::{
DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier, NullifierPublicKey,
ML_KEM_768_CIPHERTEXT_LEN, Nullifier, NullifierPublicKey,
account::{Account, AccountWithMetadata}, account::{Account, AccountWithMetadata},
compute_digest_for_path, compute_digest_for_path,
encryption::{EphemeralPublicKey, ViewingPublicKey}, encryption::ViewingPublicKey,
}; };
use log::info; use log::info;
use sequencer_service_rpc::RpcClient as _; use sequencer_service_rpc::RpcClient as _;
@ -499,14 +498,14 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
sync_private(&mut ctx).await?; sync_private(&mut ctx).await?;
// Both accounts must be discovered with the correct balances. // Both accounts must be discovered with the correct balances.
let account_id_1 = AccountId::for_regular_private_account(&npk, identifier_1); let account_id_1 = AccountId::for_regular_private_account(&npk, &vpk, identifier_1);
let acc_1 = ctx let acc_1 = ctx
.wallet() .wallet()
.get_account_private(account_id_1) .get_account_private(account_id_1)
.context("account for identifier 1 not found after sync")?; .context("account for identifier 1 not found after sync")?;
assert_eq!(acc_1.balance, 100); assert_eq!(acc_1.balance, 100);
let account_id_2 = AccountId::for_regular_private_account(&npk, identifier_2); let account_id_2 = AccountId::for_regular_private_account(&npk, &vpk, identifier_2);
let acc_2 = ctx let acc_2 = ctx
.wallet() .wallet()
.get_account_private(account_id_2) .get_account_private(account_id_2)
@ -562,11 +561,9 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> {
let nsk: lee_core::NullifierSecretKey = [3; 32]; let nsk: lee_core::NullifierSecretKey = [3; 32];
let npk = NullifierPublicKey::from(&nsk); let npk = NullifierPublicKey::from(&nsk);
let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap(); let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap();
let ssk = SharedSecretKey([55_u8; 32]);
let epk = EphemeralPublicKey(vec![55_u8; ML_KEM_768_CIPHERTEXT_LEN]);
let attacker_vault_id = { let attacker_vault_id = {
let seed = vault_core::compute_vault_seed(attacker_id); let seed = vault_core::compute_vault_seed(attacker_id);
AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337) AccountId::for_private_pda(&vault_program_id, &seed, &npk, &vpk, 1337)
}; };
let amount: u128 = 1; let amount: u128 = 1;
@ -600,10 +597,9 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk, vpk,
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk), random_seed: [0; 32],
npk, npk,
ssk,
identifier: 1337, identifier: 1337,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -632,8 +628,7 @@ async fn prove_init_with_commitment_root(
let nsk: lee_core::NullifierSecretKey = [7; 32]; let nsk: lee_core::NullifierSecretKey = [7; 32];
let npk = NullifierPublicKey::from(&nsk); let npk = NullifierPublicKey::from(&nsk);
let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap(); let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap();
let ssk = SharedSecretKey([55_u8; 32]); let recipient_account_id = AccountId::for_regular_private_account(&npk, &vpk, 0);
let recipient_account_id = AccountId::for_regular_private_account(&npk, 0);
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
let (output, _) = execute_and_prove( let (output, _) = execute_and_prove(
@ -644,10 +639,9 @@ async fn prove_init_with_commitment_root(
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk,
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk), random_seed: [0; 32],
npk, npk,
ssk,
identifier: 0, identifier: 0,
commitment_root, commitment_root,
}, },
@ -671,7 +665,8 @@ async fn init_with_dummy_commitment_root_produces_valid_root() -> Result<()> {
let nsk: lee_core::NullifierSecretKey = [7; 32]; let nsk: lee_core::NullifierSecretKey = [7; 32];
let npk = NullifierPublicKey::from(&nsk); let npk = NullifierPublicKey::from(&nsk);
let recipient_account_id = AccountId::for_regular_private_account(&npk, 0); let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap();
let recipient_account_id = AccountId::for_regular_private_account(&npk, &vpk, 0);
let output = prove_init_with_commitment_root(&ctx, expected_digest).await?; let output = prove_init_with_commitment_root(&ctx, expected_digest).await?;

View File

@ -11,7 +11,6 @@ use common::transaction::LeeTransaction;
use integration_tests::{ use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, sync_private, verify_commitment_is_in_state, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, sync_private, verify_commitment_is_in_state,
}; };
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use lee::{ use lee::{
AccountId, PrivacyPreservingTransaction, ProgramId, AccountId, PrivacyPreservingTransaction, ProgramId,
privacy_preserving_transaction::{ privacy_preserving_transaction::{
@ -22,7 +21,7 @@ use lee::{
program::Program, program::Program,
}; };
use lee_core::{ use lee_core::{
DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, DUMMY_COMMITMENT_HASH, InputAccountIdentity, NullifierPublicKey,
account::{Account, AccountWithMetadata}, account::{Account, AccountWithMetadata},
encryption::ViewingPublicKey, encryption::ViewingPublicKey,
program::PdaSeed, program::PdaSeed,
@ -48,7 +47,8 @@ async fn fund_private_pda(
amount: u128, amount: u128,
auth_transfer: &ProgramWithDependencies, auth_transfer: &ProgramWithDependencies,
) -> Result<()> { ) -> Result<()> {
let pda_account_id = AccountId::for_private_pda(&authority_program_id, &seed, &npk, identifier); let pda_account_id =
AccountId::for_private_pda(&authority_program_id, &seed, &npk, &vpk, identifier);
let sender_account = wallet let sender_account = wallet
.get_account_public(sender) .get_account_public(sender)
.await .await
@ -60,20 +60,15 @@ async fn fund_private_pda(
let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, sender); let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, sender);
let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_account_id); let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_account_id);
let eph_holder = EphemeralKeyHolder::new(&vpk);
let ssk = eph_holder.calculate_shared_secret_sender();
let epk = eph_holder.ephemeral_public_key().clone();
let instruction = Program::serialize_instruction(AuthTransferInstruction::Transfer { amount }) let instruction = Program::serialize_instruction(AuthTransferInstruction::Transfer { amount })
.context("failed to serialize auth_transfer instruction")?; .context("failed to serialize auth_transfer instruction")?;
let account_identities = vec![ let account_identities = vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk, vpk,
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk), random_seed: [0; 32],
npk, npk,
ssk,
identifier, identifier,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: Some((seed, authority_program_id)), seed: Some((seed, authority_program_id)),
@ -173,8 +168,8 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> {
let spend_program = let spend_program =
ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer)].into()); ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer)].into());
let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0); let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, &alice_vpk, 0);
let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1); let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, &alice_vpk, 1);
// Use two different public senders to avoid nonce conflicts between the back-to-back txs. // Use two different public senders to avoid nonce conflicts between the back-to-back txs.
let senders = ctx.existing_public_accounts(); let senders = ctx.existing_public_accounts();

View File

@ -15,7 +15,6 @@ use anyhow::{Context as _, Result};
use bytesize::ByteSize; use bytesize::ByteSize;
use common::transaction::LeeTransaction; use common::transaction::LeeTransaction;
use integration_tests::{TestContext, config::SequencerPartialConfig}; use integration_tests::{TestContext, config::SequencerPartialConfig};
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use lee::{ use lee::{
Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction, Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction,
privacy_preserving_transaction::{self as pptx, circuit}, privacy_preserving_transaction::{self as pptx, circuit},
@ -23,8 +22,7 @@ use lee::{
public_transaction as putx, public_transaction as putx,
}; };
use lee_core::{ use lee_core::{
DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, MembershipProof, DUMMY_COMMITMENT_HASH, InputAccountIdentity, MembershipProof, NullifierPublicKey,
NullifierPublicKey,
account::{AccountWithMetadata, Nonce, data::Data}, account::{AccountWithMetadata, Nonce, data::Data},
encryption::ViewingPublicKey, encryption::ViewingPublicKey,
}; };
@ -267,7 +265,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
data: Data::default(), data: Data::default(),
}, },
true, true,
AccountId::for_regular_private_account(&sender_npk, 0), AccountId::for_regular_private_account(&sender_npk, &sender_vpk, 0),
); );
let recipient_nsk = [2; 32]; let recipient_nsk = [2; 32];
let recipient_vpk = ViewingPublicKey::from_seed(&[101_u8; 32], &[102_u8; 32]); let recipient_vpk = ViewingPublicKey::from_seed(&[101_u8; 32], &[102_u8; 32]);
@ -275,17 +273,9 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
let recipient_pre = AccountWithMetadata::new( let recipient_pre = AccountWithMetadata::new(
Account::default(), Account::default(),
false, false,
AccountId::for_regular_private_account(&recipient_npk, 0), AccountId::for_regular_private_account(&recipient_npk, &recipient_vpk, 0),
); );
let eph_holder_from = EphemeralKeyHolder::new(&sender_vpk);
let sender_ss = eph_holder_from.calculate_shared_secret_sender();
let sender_epk = eph_holder_from.ephemeral_public_key().clone();
let eph_holder_to = EphemeralKeyHolder::new(&recipient_vpk);
let recipient_ss = eph_holder_to.calculate_shared_secret_sender();
let recipient_epk = eph_holder_to.ephemeral_public_key().clone();
let balance_to_move: u128 = 1; let balance_to_move: u128 = 1;
let proof: MembershipProof = ( let proof: MembershipProof = (
1, 1,
@ -302,18 +292,17 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
.unwrap(), .unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: sender_epk, vpk: sender_vpk,
view_tag: EncryptedAccountData::compute_view_tag(&sender_npk, &sender_vpk), random_seed: [0; 32],
ssk: sender_ss, view_tag: 0,
nsk: sender_nsk, nsk: sender_nsk,
membership_proof: proof, membership_proof: proof,
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: recipient_epk, vpk: recipient_vpk,
view_tag: EncryptedAccountData::compute_view_tag(&recipient_npk, &recipient_vpk), random_seed: [0; 32],
npk: recipient_npk, npk: recipient_npk,
ssk: recipient_ss,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}, },

View File

@ -1012,7 +1012,11 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
let (to, to_keys) = unsafe { let (to, to_keys) = unsafe {
let mut out_keys = FfiPrivateAccountKeys::default(); let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); let account_id = lee::AccountId::for_regular_private_account(
&out_keys.npk(),
&out_keys.vpk().unwrap(),
0_u128,
);
let to: FfiBytes32 = account_id.into(); let to: FfiBytes32 = account_id.into();
(to, out_keys) (to, out_keys)
}; };
@ -1155,7 +1159,11 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
let (to, to_keys) = unsafe { let (to, to_keys) = unsafe {
let mut out_keys = FfiPrivateAccountKeys::default(); let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); let account_id = lee::AccountId::for_regular_private_account(
&out_keys.npk(),
&out_keys.vpk().unwrap(),
0_u128,
);
let to: FfiBytes32 = account_id.into(); let to: FfiBytes32 = account_id.into();
(to, out_keys) (to, out_keys)
}; };
@ -1236,7 +1244,11 @@ fn restore_keys_from_seed_ffi() -> Result<()> {
let (private_account_id_1, private_account_1_keys) = unsafe { let (private_account_id_1, private_account_1_keys) = unsafe {
let mut out_keys = FfiPrivateAccountKeys::default(); let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); let account_id = lee::AccountId::for_regular_private_account(
&out_keys.npk(),
&out_keys.vpk().unwrap(),
0_u128,
);
let to: FfiBytes32 = account_id.into(); let to: FfiBytes32 = account_id.into();
(to, out_keys) (to, out_keys)
}; };
@ -1244,7 +1256,11 @@ fn restore_keys_from_seed_ffi() -> Result<()> {
let (private_account_id_2, private_account_2_keys) = unsafe { let (private_account_id_2, private_account_2_keys) = unsafe {
let mut out_keys = FfiPrivateAccountKeys::default(); let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap();
let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); let account_id = lee::AccountId::for_regular_private_account(
&out_keys.npk(),
&out_keys.vpk().unwrap(),
0_u128,
);
let to: FfiBytes32 = account_id.into(); let to: FfiBytes32 = account_id.into();
(to, out_keys) (to, out_keys)
}; };

View File

@ -351,10 +351,10 @@ mod tests {
let program_id: ProgramId = [9; 8]; let program_id: ProgramId = [9; 8];
let holder = GroupKeyHolder::from_gms(gms); let holder = GroupKeyHolder::from_gms(gms);
let npk = holder let keys = holder.derive_keys_for_pda(&TEST_PROGRAM_ID, &seed);
.derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) let npk = keys.generate_nullifier_public_key();
.generate_nullifier_public_key(); let vpk = keys.generate_viewing_public_key();
let account_id = AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX); let account_id = AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, u128::MAX);
let expected_npk = NullifierPublicKey([ let expected_npk = NullifierPublicKey([
136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79, 136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79,
@ -363,7 +363,7 @@ mod tests {
// AccountId is derived from (program_id, seed, npk), so it changes when npk changes. // AccountId is derived from (program_id, seed, npk), so it changes when npk changes.
// We verify npk is pinned, and AccountId is deterministically derived from it. // We verify npk is pinned, and AccountId is deterministically derived from it.
let expected_account_id = let expected_account_id =
AccountId::for_private_pda(&program_id, &seed, &expected_npk, u128::MAX); AccountId::for_private_pda(&program_id, &seed, &expected_npk, &vpk, u128::MAX);
assert_eq!(npk, expected_npk); assert_eq!(npk, expected_npk);
assert_eq!(account_id, expected_account_id); assert_eq!(account_id, expected_account_id);
@ -560,13 +560,16 @@ mod tests {
let bob_holder = let bob_holder =
GroupKeyHolder::unseal(&sealed, &bob_vsk).expect("Bob should unseal the GMS"); GroupKeyHolder::unseal(&sealed, &bob_vsk).expect("Bob should unseal the GMS");
let bob_npk = bob_holder let bob_group_keys = bob_holder.derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed);
.derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed) let bob_npk = bob_group_keys.generate_nullifier_public_key();
.generate_nullifier_public_key();
assert_eq!(alice_npk, bob_npk); assert_eq!(alice_npk, bob_npk);
let alice_account_id = AccountId::for_private_pda(&program_id, &pda_seed, &alice_npk, 0); let alice_vpk = alice_keys.generate_viewing_public_key();
let bob_account_id = AccountId::for_private_pda(&program_id, &pda_seed, &bob_npk, 0); let bob_group_vpk = bob_group_keys.generate_viewing_public_key();
let alice_account_id =
AccountId::for_private_pda(&program_id, &pda_seed, &alice_npk, &alice_vpk, 0);
let bob_account_id =
AccountId::for_private_pda(&program_id, &pda_seed, &bob_npk, &bob_group_vpk, 0);
assert_eq!(alice_account_id, bob_account_id); assert_eq!(alice_account_id, bob_account_id);
} }

View File

@ -94,10 +94,11 @@ impl KeyTreeNode for ChildKeysPrivate {
fn account_ids(&self) -> impl Iterator<Item = lee::AccountId> { fn account_ids(&self) -> impl Iterator<Item = lee::AccountId> {
let npk = self.value.0.nullifier_public_key; let npk = self.value.0.nullifier_public_key;
let vpk = self.value.0.viewing_public_key.clone();
self.value self.value
.1 .1
.keys() .keys()
.map(move |kind| lee::AccountId::for_private_account(&npk, kind)) .map(move |kind| lee::AccountId::for_private_account(&npk, &vpk, kind))
} }
} }

View File

@ -268,6 +268,7 @@ impl KeyTree<ChildKeysPrivate> {
let node = self.key_map.get(cci)?; let node = self.key_map.get(cci)?;
let account_id = lee::AccountId::for_regular_private_account( let account_id = lee::AccountId::for_regular_private_account(
&node.value.0.nullifier_public_key, &node.value.0.nullifier_public_key,
&node.value.0.viewing_public_key,
identifier, identifier,
); );
if self.account_id_map.contains_key(&account_id) { if self.account_id_map.contains_key(&account_id) {

View File

@ -6,6 +6,7 @@ use std::{
use lee_core::{ use lee_core::{
Identifier, InputAccountIdentity, NullifierPublicKey, Identifier, InputAccountIdentity, NullifierPublicKey,
account::{Account, AccountId, AccountWithMetadata}, account::{Account, AccountId, AccountWithMetadata},
encryption::ViewingPublicKey,
program::{ program::{
AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID, AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID,
MAX_NUMBER_CHAINED_CALLS, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow, MAX_NUMBER_CHAINED_CALLS, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow,
@ -21,7 +22,7 @@ pub struct ExecutionState {
block_validity_window: BlockValidityWindow, block_validity_window: BlockValidityWindow,
timestamp_validity_window: TimestampValidityWindow, timestamp_validity_window: TimestampValidityWindow,
/// Positions (in `pre_states`) of private-PDA accounts whose supplied npk has been bound to /// Positions (in `pre_states`) of private-PDA accounts whose supplied npk has been bound to
/// their `AccountId` via a proven `AccountId::for_private_pda(program_id, seed, npk, /// their `AccountId` via a proven `AccountId::for_private_pda(program_id, seed, npk, vpk,
/// identifier)` check. /// identifier)` check.
/// Two proof paths populate this set: a `Claim::Pda(seed)` in a program's `post_state` on /// Two proof paths populate this set: a `Claim::Pda(seed)` in a program's `post_state` on
/// that `pre_state`, or a caller's `ChainedCall.pda_seeds` entry matching that `pre_state` /// that `pre_state`, or a caller's `ChainedCall.pda_seeds` entry matching that `pre_state`
@ -43,12 +44,13 @@ pub struct ExecutionState {
/// `AccountId` entry or as an equality check against the existing one, making the rule: one /// `AccountId` entry or as an equality check against the existing one, making the rule: one
/// `(program, seed)` → one account per tx. /// `(program, seed)` → one account per tx.
pda_family_binding: HashMap<(ProgramId, PdaSeed), AccountId>, pda_family_binding: HashMap<(ProgramId, PdaSeed), AccountId>,
/// Map from a private-PDA `pre_state`'s position in `account_identities` to the (npk, /// Map from a private-PDA `pre_state`'s position in `account_identities` to the (npk, vpk,
/// identifier) supplied for that position. Built once in `derive_from_outputs` by walking /// identifier) supplied for that position. Built once in `derive_from_outputs` by walking
/// `account_identities` and consulting `npk_if_private_pda`. Used later by the claim and /// `account_identities` and consulting `npk_vpk_if_private_pda`. Used later by the claim and
/// caller-seeds authorization paths to verify /// caller-seeds authorization paths to verify
/// `AccountId::for_private_pda(program_id, seed, npk, identifier) == pre_state.account_id`. /// `AccountId::for_private_pda(program_id, seed, npk, vpk, identifier) ==
private_pda_npk_by_position: HashMap<usize, (NullifierPublicKey, Identifier)>, /// pre_state.account_id`.
private_pda_by_position: HashMap<usize, (NullifierPublicKey, ViewingPublicKey, Identifier)>,
authorized_accounts: HashSet<AccountId>, authorized_accounts: HashSet<AccountId>,
} }
@ -59,9 +61,48 @@ impl ExecutionState {
program_id: ProgramId, program_id: ProgramId,
program_outputs: Vec<ProgramOutput>, program_outputs: Vec<ProgramOutput>,
) -> Self { ) -> Self {
let private_pda_npk_by_position = build_private_pda_npk_map(account_identities); // Build position → (npk, identifier) map for private-PDA pre_states, indexed by position
let (block_validity_window, timestamp_validity_window) = // in `account_identities`. The vec is documented as 1:1 with the program's pre_state
intersect_validity_windows(&program_outputs); // order, so position here matches `pre_state_position` used downstream in
// `validate_and_sync_states`.
let mut private_pda_by_position: HashMap<
usize,
(NullifierPublicKey, ViewingPublicKey, Identifier),
> = HashMap::new();
for (pos, account_identity) in account_identities.iter().enumerate() {
if let Some((npk, vpk, identifier)) = account_identity.npk_vpk_if_private_pda() {
private_pda_by_position.insert(pos, (npk, vpk, identifier));
}
}
let block_valid_from = program_outputs
.iter()
.filter_map(|output| output.block_validity_window.start())
.max();
let block_valid_until = program_outputs
.iter()
.filter_map(|output| output.block_validity_window.end())
.min();
let ts_valid_from = program_outputs
.iter()
.filter_map(|output| output.timestamp_validity_window.start())
.max();
let ts_valid_until = program_outputs
.iter()
.filter_map(|output| output.timestamp_validity_window.end())
.min();
let block_validity_window: BlockValidityWindow = (block_valid_from, block_valid_until)
.try_into()
.expect(
"There should be non empty intersection in the program output block validity windows",
);
let timestamp_validity_window: TimestampValidityWindow =
(ts_valid_from, ts_valid_until)
.try_into()
.expect(
"There should be non empty intersection in the program output timestamp validity windows",
);
let mut execution_state = Self { let mut execution_state = Self {
pre_states: Vec::new(), pre_states: Vec::new(),
@ -70,7 +111,7 @@ impl ExecutionState {
timestamp_validity_window, timestamp_validity_window,
private_pda_bound_positions: HashMap::new(), private_pda_bound_positions: HashMap::new(),
pda_family_binding: HashMap::new(), pda_family_binding: HashMap::new(),
private_pda_npk_by_position, private_pda_by_position,
authorized_accounts: HashSet::new(), authorized_accounts: HashSet::new(),
}; };
@ -99,7 +140,49 @@ impl ExecutionState {
panic!("Insufficient program outputs for chained calls"); panic!("Insufficient program outputs for chained calls");
}; };
verify_program_output(&chained_call, caller_program_id, &program_output); // Check that instruction data in chained call is the instruction data in program output
assert_eq!(
chained_call.instruction_data, program_output.instruction_data,
"Mismatched instruction data between chained call and program output"
);
// Check that `program_output` is consistent with the execution of the corresponding
// program.
let program_output_words =
&to_vec(&program_output).expect("program_output must be serializable");
env::verify(chained_call.program_id, program_output_words).unwrap_or_else(
|_: Infallible| unreachable!("Infallible error is never constructed"),
);
// Verify that the program output's self_program_id matches the expected program ID.
// This ensures the proof commits to which program produced the output.
assert_eq!(
program_output.self_program_id, chained_call.program_id,
"Program output self_program_id does not match chained call program_id"
);
// Verify that the program output's caller_program_id matches the actual caller.
// This prevents a malicious user from privately executing an internal function
// by spoofing caller_program_id (e.g. passing caller_program_id = self_program_id
// to bypass access control checks).
assert_eq!(
program_output.caller_program_id, caller_program_id,
"Program output caller_program_id does not match actual caller"
);
// Check that the program is well behaved.
// See the # Programs section for the definition of the `validate_execution` method.
let validated_execution = validate_execution(
&program_output.pre_states,
&program_output.post_states,
chained_call.program_id,
);
if let Err(err) = validated_execution {
panic!(
"Invalid program behavior in program {:?}: {err}",
chained_call.program_id
);
}
for next_call in program_output.chained_calls.iter().rev() { for next_call in program_output.chained_calls.iter().rev() {
chained_calls.push_front((next_call.clone(), Some(chained_call.program_id))); chained_calls.push_front((next_call.clone(), Some(chained_call.program_id)));
@ -123,8 +206,41 @@ impl ExecutionState {
"Inner call without a chained call found", "Inner call without a chained call found",
); );
execution_state.assert_all_pda_positions_bound(account_identities); // Every private-PDA pre_state must have had its npk bound to its account_id, either via
execution_state.assert_modified_accounts_claimed(); // a `Claim::Pda(seed)` in some program's post_state or via a caller's `pda_seeds`
// matching the private derivation. An unbound private-PDA pre_state has no
// cryptographic link between the supplied npk and the account_id, and must be rejected.
for (pos, account_identity) in account_identities.iter().enumerate() {
if account_identity.is_private_pda() {
assert!(
execution_state
.private_pda_bound_positions
.contains_key(&pos),
"private PDA pre_state at position {pos} has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds"
);
}
}
// Check that all modified uninitialized accounts were claimed
for (account_id, post) in execution_state
.pre_states
.iter()
.filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID)
.map(|a| {
let post = execution_state
.post_states
.get(&a.account_id)
.expect("Post state must exist for pre state");
(a, post)
})
.filter(|(pre_default, post)| pre_default.account != **post)
.map(|(pre, post)| (pre.account_id, post))
{
assert_ne!(
post.program_owner, DEFAULT_PROGRAM_ID,
"Account {account_id} was modified but not claimed"
);
}
execution_state execution_state
} }
@ -142,185 +258,209 @@ impl ExecutionState {
for (pre, mut post) in output_pre_states.into_iter().zip(output_post_states) { for (pre, mut post) in output_pre_states.into_iter().zip(output_post_states) {
let pre_account_id = pre.account_id; let pre_account_id = pre.account_id;
let pre_is_authorized = pre.is_authorized; let pre_is_authorized = pre.is_authorized;
let post_states_entry = self.post_states.entry(pre.account_id);
match &post_states_entry {
Entry::Occupied(occupied) => {
#[expect(
clippy::shadow_unrelated,
reason = "Shadowing is intentional to use all fields"
)]
let AccountWithMetadata {
account: pre_account,
account_id: pre_account_id,
is_authorized: pre_is_authorized,
} = pre;
if let Some(existing) = self.post_states.get(&pre.account_id) { // Ensure that new pre state is the same as known post state
#[expect( assert_eq!(
clippy::shadow_unrelated, occupied.get(),
reason = "Shadowing is intentional to use all fields" &pre_account,
)] "Inconsistent pre state for account {pre_account_id}",
let AccountWithMetadata {
account: pre_account,
account_id: pre_account_id,
is_authorized: pre_is_authorized,
} = pre;
assert_eq!(
existing, &pre_account,
"Inconsistent pre state for account {pre_account_id}",
);
let (previous_is_authorized, pre_state_position) = self
.pre_states
.iter()
.enumerate()
.find(|(_, acc)| acc.account_id == pre_account_id)
.map_or_else(
|| panic!(
"Pre state must exist in execution state for account {pre_account_id}",
),
|(pos, acc)| (acc.is_authorized, pos),
); );
let is_authorized = resolve_authorization_and_record_bindings( let (previous_is_authorized, pre_state_position) = self
&mut self.pda_family_binding, .pre_states
&mut self.private_pda_bound_positions, .iter()
&self.private_pda_npk_by_position, .enumerate()
&mut self.authorized_accounts, .find(|(_, acc)| acc.account_id == pre_account_id)
pre_account_id, .map_or_else(
pre_state_position, || panic!(
caller_program_id, "Pre state must exist in execution state for account {pre_account_id}",
caller_pda_seeds, ),
previous_is_authorized, |(pos, acc)| (acc.is_authorized, pos)
); );
assert_eq!( let is_authorized = resolve_authorization_and_record_bindings(
pre_is_authorized, is_authorized, &mut self.pda_family_binding,
"Inconsistent authorization for account {pre_account_id}", &mut self.private_pda_bound_positions,
); &self.private_pda_by_position,
} else { &mut self.authorized_accounts,
let pre_state_position = self.pre_states.len(); pre_account_id,
resolve_external_seed( pre_state_position,
account_identities, caller_program_id,
pre_state_position, caller_pda_seeds,
pre_account_id, previous_is_authorized,
pre.is_authorized, );
&mut self.private_pda_bound_positions,
&mut self.pda_family_binding, assert_eq!(
); pre_is_authorized, is_authorized,
self.pre_states.push(pre); "Inconsistent authorization for account {pre_account_id}",
);
}
Entry::Vacant(_) => {
// 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,
vpk,
identifier,
seed: Some((seed, authority_program_id)),
..
}) => {
let expected = AccountId::for_private_pda(
authority_program_id,
seed,
npk,
vpk,
*identifier,
);
assert_eq!(
pre_account_id, expected,
"External seed mismatch for PrivatePdaInit 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))
}
_ => 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 {
assert!(
!pre.is_authorized,
"Private PDA with externally-provided seed must not be authorized at position {pre_state_position}"
);
bind_private_pda_position(
&mut self.private_pda_bound_positions,
pre_state_position,
authority_program_id,
seed,
);
assert_family_binding(
&mut self.pda_family_binding,
authority_program_id,
seed,
pre_account_id,
);
}
self.pre_states.push(pre);
}
} }
if let Some(claim) = post.required_claim() { if let Some(claim) = post.required_claim() {
self.process_claim( // The invoked program can only claim accounts with default program id.
account_identities, assert_eq!(
&mut post, post.account().program_owner,
pre_account_id, DEFAULT_PROGRAM_ID,
pre_is_authorized, "Cannot claim an initialized account {pre_account_id}"
program_id,
claim,
); );
}
self.post_states.insert(pre_account_id, post.into_account()); let pre_state_position = self
} .pre_states
} .iter()
.position(|acc| acc.account_id == pre_account_id)
.expect("Pre state must exist at this point");
fn process_claim( let account_identity = &account_identities[pre_state_position];
&mut self, if account_identity.is_public() {
account_identities: &[InputAccountIdentity], match claim {
post: &mut AccountPostState, Claim::Authorized => {
pre_account_id: AccountId, // Note: no need to check authorized pdas because we have already
pre_is_authorized: bool, // checked consistency of authorization above.
program_id: ProgramId, assert!(
claim: Claim, pre_is_authorized,
) { "Cannot claim unauthorized account {pre_account_id}"
assert_eq!( );
post.account().program_owner, }
DEFAULT_PROGRAM_ID, Claim::Pda(seed) => {
"Cannot claim an initialized account {pre_account_id}" let pda = AccountId::for_public_pda(&program_id, &seed);
); assert_eq!(
pre_account_id, pda,
let pre_state_position = self "Invalid PDA claim for account {pre_account_id} which does not match derived PDA {pda}"
.pre_states );
.iter() assert_family_binding(
.position(|acc| acc.account_id == pre_account_id) &mut self.pda_family_binding,
.expect("Pre state must exist at this point"); program_id,
seed,
let account_identity = &account_identities[pre_state_position]; pre_account_id,
if account_identity.is_public() { );
match claim { }
Claim::Authorized => { }
assert!( } else {
pre_is_authorized, // Private accounts: don't enforce the claim semantics. Unauthorized private
"Cannot claim unauthorized account {pre_account_id}" // claiming is intentionally allowed
); match claim {
Claim::Authorized => {}
Claim::Pda(seed) => {
let (npk, vpk, identifier) = self
.private_pda_by_position
.get(&pre_state_position)
.expect(
"private PDA pre_state must have an npk in the position map",
);
let pda = AccountId::for_private_pda(
&program_id,
&seed,
npk,
vpk,
*identifier,
);
assert_eq!(
pre_account_id, pda,
"Invalid private PDA claim for account {pre_account_id}"
);
bind_private_pda_position(
&mut self.private_pda_bound_positions,
pre_state_position,
program_id,
seed,
);
assert_family_binding(
&mut self.pda_family_binding,
program_id,
seed,
pre_account_id,
);
}
}
} }
Claim::Pda(seed) => {
let pda = AccountId::for_public_pda(&program_id, &seed);
assert_eq!(
pre_account_id, pda,
"Invalid PDA claim for account {pre_account_id} which does not match derived PDA {pda}"
);
assert_family_binding(
&mut self.pda_family_binding,
program_id,
seed,
pre_account_id,
);
}
}
} else {
match claim {
Claim::Authorized => {}
Claim::Pda(seed) => {
let (npk, identifier) = self
.private_pda_npk_by_position
.get(&pre_state_position)
.expect("private PDA pre_state must have an npk in the position map");
let pda = AccountId::for_private_pda(&program_id, &seed, npk, *identifier);
assert_eq!(
pre_account_id, pda,
"Invalid private PDA claim for account {pre_account_id}"
);
bind_private_pda_position(
&mut self.private_pda_bound_positions,
pre_state_position,
program_id,
seed,
);
assert_family_binding(
&mut self.pda_family_binding,
program_id,
seed,
pre_account_id,
);
}
}
}
post.account_mut().program_owner = program_id; post.account_mut().program_owner = program_id;
}
fn assert_all_pda_positions_bound(&self, account_identities: &[InputAccountIdentity]) {
for (pos, account_identity) in account_identities.iter().enumerate() {
if account_identity.is_private_pda() {
assert!(
self.private_pda_bound_positions.contains_key(&pos),
"private PDA pre_state at position {pos} has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds"
);
} }
}
}
fn assert_modified_accounts_claimed(&self) { post_states_entry.insert_entry(post.into_account());
for (account_id, post) in self
.pre_states
.iter()
.filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID)
.map(|a| {
let post = self
.post_states
.get(&a.account_id)
.expect("Post state must exist for pre state");
(a, post)
})
.filter(|(pre_default, post)| pre_default.account != **post)
.map(|(pre, post)| (pre.account_id, post))
{
assert_ne!(
post.program_owner, DEFAULT_PROGRAM_ID,
"Account {account_id} was modified but not claimed"
);
} }
} }
@ -359,150 +499,6 @@ impl ExecutionState {
} }
} }
fn verify_program_output(
chained_call: &ChainedCall,
caller_program_id: Option<ProgramId>,
program_output: &ProgramOutput,
) {
assert_eq!(
chained_call.instruction_data, program_output.instruction_data,
"Mismatched instruction data between chained call and program output"
);
let program_output_words =
&to_vec(program_output).expect("program_output must be serializable");
env::verify(chained_call.program_id, program_output_words)
.unwrap_or_else(|_: Infallible| unreachable!("Infallible error is never constructed"));
assert_eq!(
program_output.self_program_id, chained_call.program_id,
"Program output self_program_id does not match chained call program_id"
);
assert_eq!(
program_output.caller_program_id, caller_program_id,
"Program output caller_program_id does not match actual caller"
);
if let Err(err) = validate_execution(
&program_output.pre_states,
&program_output.post_states,
chained_call.program_id,
) {
panic!(
"Invalid program behavior in program {:?}: {err}",
chained_call.program_id
);
}
}
fn build_private_pda_npk_map(
account_identities: &[InputAccountIdentity],
) -> HashMap<usize, (NullifierPublicKey, Identifier)> {
account_identities
.iter()
.enumerate()
.filter_map(|(pos, identity)| {
identity
.npk_if_private_pda()
.map(|(npk, identifier)| (pos, (npk, identifier)))
})
.collect()
}
fn intersect_validity_windows(
program_outputs: &[ProgramOutput],
) -> (BlockValidityWindow, TimestampValidityWindow) {
let block_valid_from = program_outputs
.iter()
.filter_map(|output| output.block_validity_window.start())
.max();
let block_valid_until = program_outputs
.iter()
.filter_map(|output| output.block_validity_window.end())
.min();
let ts_valid_from = program_outputs
.iter()
.filter_map(|output| output.timestamp_validity_window.start())
.max();
let ts_valid_until = program_outputs
.iter()
.filter_map(|output| output.timestamp_validity_window.end())
.min();
let block_validity_window: BlockValidityWindow =
(block_valid_from, block_valid_until).try_into().expect(
"There should be non empty intersection in the program output block validity windows",
);
let timestamp_validity_window: TimestampValidityWindow = (ts_valid_from, ts_valid_until)
.try_into()
.expect(
"There should be non empty intersection in the program output timestamp validity windows",
);
(block_validity_window, timestamp_validity_window)
}
fn resolve_external_seed(
account_identities: &[InputAccountIdentity],
pre_state_position: usize,
pre_account_id: AccountId,
is_authorized: bool,
private_pda_bound_positions: &mut HashMap<usize, (ProgramId, PdaSeed)>,
pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>,
) {
let external_seed = match account_identities.get(pre_state_position) {
Some(InputAccountIdentity::PrivatePdaInit {
npk,
identifier,
seed: Some((seed, authority_program_id)),
..
}) => {
let expected = AccountId::for_private_pda(authority_program_id, seed, npk, *identifier);
assert_eq!(
pre_account_id, expected,
"External seed mismatch for PrivatePdaInit at position {pre_state_position}"
);
Some((*seed, *authority_program_id))
}
Some(InputAccountIdentity::PrivatePdaUpdate {
nsk,
identifier,
seed: Some((seed, authority_program_id)),
..
}) => {
let npk = NullifierPublicKey::from(nsk);
let expected =
AccountId::for_private_pda(authority_program_id, seed, &npk, *identifier);
assert_eq!(
pre_account_id, expected,
"External seed mismatch for PrivatePdaUpdate at position {pre_state_position}"
);
Some((*seed, *authority_program_id))
}
_ => None,
};
if let Some((seed, authority_program_id)) = external_seed {
assert!(
!is_authorized,
"Private PDA with externally-provided seed must not be authorized at position {pre_state_position}"
);
bind_private_pda_position(
private_pda_bound_positions,
pre_state_position,
authority_program_id,
seed,
);
assert_family_binding(
pda_family_binding,
authority_program_id,
seed,
pre_account_id,
);
}
}
/// Record or re-verify the `(program_id, seed) → account_id` family binding for the /// Record or re-verify the `(program_id, seed) → account_id` family binding for the
/// transaction. Any claim or caller-seed authorization that resolves a `pre_state` under /// transaction. Any claim or caller-seed authorization that resolves a `pre_state` under
/// `(program_id, seed)` must agree with every prior resolution of the same pair; otherwise a /// `(program_id, seed)` must agree with every prior resolution of the same pair; otherwise a
@ -565,7 +561,7 @@ fn bind_private_pda_position(
fn resolve_authorization_and_record_bindings( fn resolve_authorization_and_record_bindings(
pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>, pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>,
private_pda_bound_positions: &mut HashMap<usize, (ProgramId, PdaSeed)>, private_pda_bound_positions: &mut HashMap<usize, (ProgramId, PdaSeed)>,
private_pda_npk_by_position: &HashMap<usize, (NullifierPublicKey, Identifier)>, private_pda_by_position: &HashMap<usize, (NullifierPublicKey, ViewingPublicKey, Identifier)>,
authorized_accounts: &mut HashSet<AccountId>, authorized_accounts: &mut HashSet<AccountId>,
pre_account_id: AccountId, pre_account_id: AccountId,
pre_state_position: usize, pre_state_position: usize,
@ -579,9 +575,10 @@ fn resolve_authorization_and_record_bindings(
if AccountId::for_public_pda(&caller, seed) == pre_account_id { if AccountId::for_public_pda(&caller, seed) == pre_account_id {
return Some((*seed, false, caller)); return Some((*seed, false, caller));
} }
if let Some((npk, identifier)) = if let Some((npk, vpk, identifier)) =
private_pda_npk_by_position.get(&pre_state_position) private_pda_by_position.get(&pre_state_position)
&& AccountId::for_private_pda(&caller, seed, npk, *identifier) == pre_account_id && AccountId::for_private_pda(&caller, seed, npk, vpk, *identifier)
== pre_account_id
{ {
return Some((*seed, true, caller)); return Some((*seed, true, caller));
} }

View File

@ -1,244 +1,14 @@
use lee_core::{ use lee_core::{
Commitment, CommitmentSetDigest, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, Commitment, CommitmentSetDigest, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey,
InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey,
PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
account::{Account, AccountId, Nonce}, account::{Account, AccountId, Nonce},
compute_digest_for_path, compute_digest_for_path,
encryption::{ViewTag, ViewingPublicKey},
}; };
use crate::execution_state::ExecutionState; use crate::execution_state::ExecutionState;
struct PrivateOutputHandler<'ctx> {
output: &'ctx mut PrivacyPreservingCircuitOutput,
output_index: &'ctx mut u32,
pre_state: &'ctx lee_core::account::AccountWithMetadata,
post_state: Account,
epk: &'ctx EphemeralPublicKey,
view_tag: u8,
ssk: &'ctx SharedSecretKey,
identifier: u128,
}
impl PrivateOutputHandler<'_> {
fn authorized_init(self, nsk: &NullifierSecretKey, commitment_root: &CommitmentSetDigest) {
let npk = NullifierPublicKey::from(nsk);
let account_id =
derive_and_verify_account_id(&npk, self.identifier, self.pre_state.account_id);
assert!(
self.pre_state.is_authorized,
"Pre-state not authorized for authenticated private account"
);
assert_eq!(
self.pre_state.account,
Account::default(),
"Found new private account with non default values"
);
let (new_nullifier, new_nonce) = init_nullifier_and_nonce(&account_id, commitment_root);
let kind = PrivateAccountKind::Regular(self.identifier);
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
}
fn authorized_update(self, nsk: &NullifierSecretKey, membership_proof: &MembershipProof) {
let npk = NullifierPublicKey::from(nsk);
let account_id =
derive_and_verify_account_id(&npk, self.identifier, self.pre_state.account_id);
assert!(
self.pre_state.is_authorized,
"Pre-state not authorized for authenticated private account"
);
let new_nullifier = compute_update_nullifier_and_set_digest(
membership_proof,
&self.pre_state.account,
&account_id,
nsk,
);
let new_nonce = self
.pre_state
.account
.nonce
.private_account_nonce_increment(nsk);
let kind = PrivateAccountKind::Regular(self.identifier);
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
}
fn unauthorized(self, npk: &NullifierPublicKey, commitment_root: &CommitmentSetDigest) {
let account_id =
derive_and_verify_account_id(npk, self.identifier, self.pre_state.account_id);
assert_eq!(
self.pre_state.account,
Account::default(),
"Found new private account with non default values",
);
assert!(
!self.pre_state.is_authorized,
"Found new private account marked as authorized."
);
let (new_nullifier, new_nonce) = init_nullifier_and_nonce(&account_id, commitment_root);
let kind = PrivateAccountKind::Regular(self.identifier);
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
}
fn pda_init(
self,
commitment_root: &CommitmentSetDigest,
pos: usize,
pda_seed_by_position: &std::collections::HashMap<
usize,
(lee_core::program::ProgramId, lee_core::program::PdaSeed),
>,
) {
// 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.
assert!(
!self.pre_state.is_authorized,
"PrivatePdaInit requires unauthorized pre_state"
);
assert_eq!(
self.pre_state.account,
Account::default(),
"New private PDA must be default"
);
let (new_nullifier, new_nonce) =
init_nullifier_and_nonce(&self.pre_state.account_id, commitment_root);
let account_id = self.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 kind = PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: self.identifier,
};
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
}
fn pda_update(
self,
nsk: &NullifierSecretKey,
membership_proof: &MembershipProof,
external_seed: Option<&(lee_core::program::PdaSeed, lee_core::program::ProgramId)>,
pos: usize,
pda_seed_by_position: &std::collections::HashMap<
usize,
(lee_core::program::ProgramId, lee_core::program::PdaSeed),
>,
) {
// 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.
assert!(
self.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,
&self.pre_state.account,
&self.pre_state.account_id,
nsk,
);
let new_nonce = self
.pre_state
.account
.nonce
.private_account_nonce_increment(nsk);
let account_id = self.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");
let kind = PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: self.identifier,
};
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
}
fn emit_private_output(
self,
account_id: &AccountId,
kind: &PrivateAccountKind,
new_nullifier: (Nullifier, CommitmentSetDigest),
new_nonce: Nonce,
) {
self.output.new_nullifiers.push(new_nullifier);
let mut post_with_updated_nonce = self.post_state;
post_with_updated_nonce.nonce = new_nonce;
let commitment_post = Commitment::new(account_id, &post_with_updated_nonce);
let encrypted_account = EncryptionScheme::encrypt(
&post_with_updated_nonce,
kind,
self.ssk,
&commitment_post,
*self.output_index,
);
self.output.new_commitments.push(commitment_post);
self.output
.encrypted_private_post_states
.push(EncryptedAccountData {
ciphertext: encrypted_account,
epk: self.epk.clone(),
view_tag: self.view_tag,
});
*self.output_index = self
.output_index
.checked_add(1)
.unwrap_or_else(|| panic!("Too many private accounts, output index overflow"));
}
}
fn init_nullifier_and_nonce(
account_id: &AccountId,
commitment_root: &CommitmentSetDigest,
) -> ((Nullifier, CommitmentSetDigest), Nonce) {
let nullifier = (
Nullifier::for_account_initialization(account_id),
*commitment_root,
);
let nonce = Nonce::private_account_nonce_init(account_id);
(nullifier, nonce)
}
fn derive_and_verify_account_id(
npk: &NullifierPublicKey,
identifier: u128,
pre_state_account_id: AccountId,
) -> AccountId {
let account_id = AccountId::for_regular_private_account(npk, identifier);
assert_eq!(account_id, pre_state_account_id, "AccountId mismatch");
account_id
}
fn compute_update_nullifier_and_set_digest(
membership_proof: &MembershipProof,
pre_account: &Account,
account_id: &AccountId,
nsk: &NullifierSecretKey,
) -> (Nullifier, CommitmentSetDigest) {
let commitment_pre = Commitment::new(account_id, pre_account);
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
(nullifier, set_digest)
}
pub fn compute_circuit_output( pub fn compute_circuit_output(
execution_state: ExecutionState, execution_state: ExecutionState,
account_identities: &[InputAccountIdentity], account_identities: &[InputAccountIdentity],
@ -271,105 +41,285 @@ pub fn compute_circuit_output(
output.public_post_states.push(post_state); output.public_post_states.push(post_state);
} }
InputAccountIdentity::PrivateAuthorizedInit { InputAccountIdentity::PrivateAuthorizedInit {
epk, vpk,
view_tag, random_seed,
ssk,
nsk, nsk,
identifier, identifier,
commitment_root, commitment_root,
} => PrivateOutputHandler { } => {
output: &mut output, let npk = NullifierPublicKey::from(nsk);
output_index: &mut output_index, let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier);
pre_state: &pre_state,
post_state, assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
epk, assert!(
view_tag: *view_tag, pre_state.is_authorized,
ssk, "Pre-state not authorized for authenticated private account"
identifier: *identifier, );
assert_eq!(
pre_state.account,
Account::default(),
"Found new private account with non default values"
);
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,
&mut output_index,
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
} }
.authorized_init(nsk, commitment_root),
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk, vpk,
random_seed,
view_tag, view_tag,
ssk,
nsk, nsk,
membership_proof, membership_proof,
identifier, identifier,
} => PrivateOutputHandler { } => {
output: &mut output, let npk = NullifierPublicKey::from(nsk);
output_index: &mut output_index, let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier);
pre_state: &pre_state,
post_state, assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
epk, assert!(
view_tag: *view_tag, pre_state.is_authorized,
ssk, "Pre-state not authorized for authenticated private account"
identifier: *identifier, );
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,
&mut output_index,
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
*view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
} }
.authorized_update(nsk, membership_proof),
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk, vpk,
view_tag, random_seed,
npk, npk,
ssk,
identifier, identifier,
commitment_root, commitment_root,
} => PrivateOutputHandler { } => {
output: &mut output, let account_id = AccountId::for_regular_private_account(npk, vpk, *identifier);
output_index: &mut output_index,
pre_state: &pre_state, assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
post_state, assert_eq!(
epk, pre_state.account,
view_tag: *view_tag, Account::default(),
ssk, "Found new private account with non default values",
identifier: *identifier, );
assert!(
!pre_state.is_authorized,
"Found new private account marked as authorized."
);
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,
&mut output_index,
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
} }
.unauthorized(npk, commitment_root),
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk, vpk,
view_tag, random_seed,
npk: _, npk,
ssk,
identifier, identifier,
commitment_root, commitment_root,
seed: _, seed: _,
} => PrivateOutputHandler { } => {
output: &mut output, // The npk-to-account_id binding is established upstream in
output_index: &mut output_index, // `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds`
pre_state: &pre_state, // match. Here we only enforce the init pre-conditions. The supplied npk on
post_state, // the variant has been recorded into `private_pda_by_position` and used
epk, // for the binding check; we use `pre_state.account_id` directly for nullifier
view_tag: *view_tag, // and commitment derivation.
ssk, assert!(
identifier: *identifier, !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,
&mut output_index,
post_state,
&account_id,
&PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: *identifier,
},
view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
} }
.pda_init(commitment_root, pos, &pda_seed_by_position),
InputAccountIdentity::PrivatePdaUpdate { InputAccountIdentity::PrivatePdaUpdate {
epk, vpk,
random_seed,
view_tag, view_tag,
ssk,
nsk, nsk,
membership_proof, membership_proof,
identifier, identifier,
seed: external_seed, seed: external_seed,
} => PrivateOutputHandler { } => {
output: &mut output, // With an external seed the binding comes from the circuit input and the
output_index: &mut output_index, // pre_state is intentionally unauthorized; without one the binding comes from
pre_state: &pre_state, // a Claim or caller pda_seeds, so the pre_state must already be authorized.
post_state, // When `external_seed` is `Some`, execution_state already asserted
epk, // `!pre_state.is_authorized`.
view_tag: *view_tag, assert!(
ssk, pre_state.is_authorized ^ external_seed.is_some(),
identifier: *identifier, "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,
&mut output_index,
post_state,
&account_id,
&PrivateAccountKind::Pda {
program_id: *authority_program_id,
seed: *seed,
identifier: *identifier,
},
*view_tag,
vpk,
random_seed,
new_nullifier,
new_nonce,
);
} }
.pda_update(
nsk,
membership_proof,
external_seed.as_ref(),
pos,
&pda_seed_by_position,
),
} }
} }
output output
} }
#[expect(
clippy::too_many_arguments,
reason = "Inputs are distinct concerns from the variant arms; bundling would be artificial"
)]
fn emit_private_output(
output: &mut PrivacyPreservingCircuitOutput,
output_index: &mut u32,
post_state: Account,
account_id: &AccountId,
kind: &PrivateAccountKind,
view_tag: ViewTag,
vpk: &ViewingPublicKey,
random_seed: &[u8; 32],
new_nullifier: (Nullifier, CommitmentSetDigest),
new_nonce: Nonce,
) {
output.new_nullifiers.push(new_nullifier);
let mut post_with_updated_nonce = post_state;
post_with_updated_nonce.nonce = new_nonce;
let commitment_post = Commitment::new(account_id, &post_with_updated_nonce);
let esk = EphemeralSecretKey::new(account_id, random_seed, &new_nonce);
let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(vpk, &esk);
let encrypted_account = EncryptionScheme::encrypt(
&post_with_updated_nonce,
kind,
&shared_secret,
&commitment_post,
*output_index,
);
output.new_commitments.push(commitment_post);
output
.encrypted_private_post_states
.push(EncryptedAccountData {
ciphertext: encrypted_account,
epk,
view_tag,
});
*output_index = output_index
.checked_add(1)
.unwrap_or_else(|| panic!("Too many private accounts, output index overflow"));
}
fn compute_update_nullifier_and_set_digest(
membership_proof: &MembershipProof,
pre_account: &Account,
account_id: &AccountId,
nsk: &NullifierSecretKey,
) -> (Nullifier, CommitmentSetDigest) {
let commitment_pre = Commitment::new(account_id, pre_account);
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
(nullifier, set_digest)
}

View File

@ -28,10 +28,8 @@ build_utils.workspace = true
[dev-dependencies] [dev-dependencies]
lee_core = { workspace = true, features = ["test_utils"] } lee_core = { workspace = true, features = ["test_utils"] }
token_core.workspace = true
test_methods = { path = "test_methods" } test_methods = { path = "test_methods" }
env_logger.workspace = true
hex-literal = "1.0.0" hex-literal = "1.0.0"
test-case = "3.3.1" test-case = "3.3.1"

View File

@ -16,7 +16,7 @@ thiserror.workspace = true
bytemuck.workspace = true bytemuck.workspace = true
bytesize.workspace = true bytesize.workspace = true
base58.workspace = true base58.workspace = true
ml-kem = { workspace = true, optional = true, features = ["getrandom"] } ml-kem = { workspace = true }
chacha20 = { version = "0.10" } chacha20 = { version = "0.10" }
[dev-dependencies] [dev-dependencies]
@ -24,5 +24,5 @@ serde_json.workspace = true
[features] [features]
default = [] default = []
host = ["dep:ml-kem"] host = ["ml-kem/getrandom"]
test_utils = ["host"] test_utils = ["host"]

View File

@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey, Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey,
NullifierSecretKey, SharedSecretKey, NullifierSecretKey,
account::{Account, AccountWithMetadata}, account::{Account, AccountWithMetadata},
encryption::{EncryptedAccountData, EphemeralPublicKey, ViewTag}, encryption::{EncryptedAccountData, ViewTag, ViewingPublicKey},
program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow}, program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow},
}; };
@ -14,15 +14,13 @@ pub struct PrivacyPreservingCircuitInput {
pub program_outputs: Vec<ProgramOutput>, pub program_outputs: Vec<ProgramOutput>,
/// One entry per `pre_state`, in the same order as the program's `pre_states`. /// One entry per `pre_state`, in the same order as the program's `pre_states`.
/// Length must equal the number of `pre_states` derived from `program_outputs`. /// Length must equal the number of `pre_states` derived from `program_outputs`.
/// The guest's `private_pda_npk_by_position` and `private_pda_bound_positions` /// The guest's `private_pda_by_position` and `private_pda_bound_positions`
/// rely on this position alignment. /// rely on this position alignment.
pub account_identities: Vec<InputAccountIdentity>, pub account_identities: Vec<InputAccountIdentity>,
/// Program ID. /// Program ID.
pub program_id: ProgramId, pub program_id: ProgramId,
} }
/// Per-account input to the privacy-preserving circuit. Each variant carries exactly the fields
/// the guest needs for that account's code path.
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub enum InputAccountIdentity { pub enum InputAccountIdentity {
/// Public account. The guest reads pre/post state from `program_outputs` and emits no /// Public account. The guest reads pre/post state from `program_outputs` and emits no
@ -30,12 +28,11 @@ pub enum InputAccountIdentity {
Public, Public,
/// Init of an authorized standalone private account: no membership proof. The `pre_state` /// Init of an authorized standalone private account: no membership proof. The `pre_state`
/// must be `Account::default()`. The `account_id` is derived as /// must be `Account::default()`. The `account_id` is derived as
/// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), identifier)` and /// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), vpk, identifier)`
/// matched against `pre_state.account_id`. /// and matched against `pre_state.account_id`.
PrivateAuthorizedInit { PrivateAuthorizedInit {
epk: EphemeralPublicKey, vpk: ViewingPublicKey,
view_tag: ViewTag, random_seed: [u8; 32],
ssk: SharedSecretKey,
nsk: NullifierSecretKey, nsk: NullifierSecretKey,
identifier: Identifier, identifier: Identifier,
commitment_root: CommitmentSetDigest, commitment_root: CommitmentSetDigest,
@ -43,9 +40,9 @@ pub enum InputAccountIdentity {
/// Update of an authorized standalone private account: existing on-chain commitment, with /// Update of an authorized standalone private account: existing on-chain commitment, with
/// membership proof. /// membership proof.
PrivateAuthorizedUpdate { PrivateAuthorizedUpdate {
epk: EphemeralPublicKey, vpk: ViewingPublicKey,
random_seed: [u8; 32],
view_tag: ViewTag, view_tag: ViewTag,
ssk: SharedSecretKey,
nsk: NullifierSecretKey, nsk: NullifierSecretKey,
membership_proof: MembershipProof, membership_proof: MembershipProof,
identifier: Identifier, identifier: Identifier,
@ -53,10 +50,9 @@ pub enum InputAccountIdentity {
/// Init of a standalone private account the caller does not own (e.g. a recipient who /// 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. /// doesn't yet exist on chain). No `nsk`, no membership proof.
PrivateUnauthorized { PrivateUnauthorized {
epk: EphemeralPublicKey, vpk: ViewingPublicKey,
view_tag: ViewTag, random_seed: [u8; 32],
npk: NullifierPublicKey, npk: NullifierPublicKey,
ssk: SharedSecretKey,
identifier: Identifier, identifier: Identifier,
commitment_root: CommitmentSetDigest, commitment_root: CommitmentSetDigest,
}, },
@ -65,15 +61,14 @@ pub enum InputAccountIdentity {
/// PDA within the `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it /// PDA within the `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it
/// as the 4th input. /// as the 4th input.
PrivatePdaInit { PrivatePdaInit {
epk: EphemeralPublicKey, vpk: ViewingPublicKey,
view_tag: ViewTag, random_seed: [u8; 32],
npk: NullifierPublicKey, npk: NullifierPublicKey,
ssk: SharedSecretKey,
identifier: Identifier, identifier: Identifier,
commitment_root: CommitmentSetDigest, commitment_root: CommitmentSetDigest,
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the /// When `Some((seed, authority_program_id))`, the circuit binds this position via the
/// external derivation check /// external derivation check
/// `AccountId::for_private_pda(authority_program_id, seed, npk, identifier) == /// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) ==
/// pre_state.account_id` rather than requiring a `Claim::Pda` or caller /// 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 /// `pda_seeds` to establish the binding. The `pre_state` must have `is_authorized
/// == false`. /// == false`.
@ -83,15 +78,15 @@ pub enum InputAccountIdentity {
/// from `nsk`. Authorization may be established upstream by a caller `pda_seeds` match or a /// from `nsk`. Authorization may be established upstream by a caller `pda_seeds` match or a
/// previously-seen authorization in a chained call. /// previously-seen authorization in a chained call.
PrivatePdaUpdate { PrivatePdaUpdate {
epk: EphemeralPublicKey, vpk: ViewingPublicKey,
random_seed: [u8; 32],
view_tag: ViewTag, view_tag: ViewTag,
ssk: SharedSecretKey,
nsk: NullifierSecretKey, nsk: NullifierSecretKey,
membership_proof: MembershipProof, membership_proof: MembershipProof,
identifier: Identifier, identifier: Identifier,
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the /// When `Some((seed, authority_program_id))`, the circuit binds this position via the
/// external derivation check /// external derivation check
/// `AccountId::for_private_pda(authority_program_id, seed, npk, identifier) == /// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) ==
/// pre_state.account_id` rather than requiring a caller `pda_seeds` to establish /// pre_state.account_id` rather than requiring a caller `pda_seeds` to establish
/// the binding. The `pre_state` must have `is_authorized == false`. /// the binding. The `pre_state` must have `is_authorized == false`.
seed: Option<(PdaSeed, ProgramId)>, seed: Option<(PdaSeed, ProgramId)>,
@ -112,17 +107,23 @@ impl InputAccountIdentity {
) )
} }
/// For private PDA variants, return the `(npk, identifier)` pair. `Init` carries both
/// directly; `Update` derives `npk` from `nsk`. For non-PDA variants returns `None`.
#[must_use] #[must_use]
pub fn npk_if_private_pda(&self) -> Option<(NullifierPublicKey, Identifier)> { pub fn npk_vpk_if_private_pda(
&self,
) -> Option<(NullifierPublicKey, ViewingPublicKey, Identifier)> {
match self { match self {
Self::PrivatePdaInit { Self::PrivatePdaInit {
npk, identifier, .. npk,
} => Some((*npk, *identifier)), vpk,
identifier,
..
} => Some((*npk, vpk.clone(), *identifier)),
Self::PrivatePdaUpdate { Self::PrivatePdaUpdate {
nsk, identifier, .. nsk,
} => Some((NullifierPublicKey::from(nsk), *identifier)), vpk,
identifier,
..
} => Some((NullifierPublicKey::from(nsk), vpk.clone(), *identifier)),
Self::Public Self::Public
| Self::PrivateAuthorizedInit { .. } | Self::PrivateAuthorizedInit { .. }
| Self::PrivateAuthorizedUpdate { .. } | Self::PrivateAuthorizedUpdate { .. }
@ -161,7 +162,7 @@ mod tests {
use crate::{ use crate::{
Commitment, Nullifier, Commitment, Nullifier,
account::{Account, AccountId, AccountWithMetadata, Nonce}, account::{Account, AccountId, AccountWithMetadata, Nonce},
encryption::Ciphertext, encryption::{Ciphertext, EphemeralPublicKey},
}; };
#[test] #[test]

View File

@ -5,11 +5,9 @@ use chacha20::{
}; };
use risc0_zkvm::sha::{Impl, Sha256 as _}; use risc0_zkvm::sha::{Impl, Sha256 as _};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
pub use shared_key_derivation::{MlKem768EncapsulationKey, ViewingPublicKey}; pub use shared_key_derivation::{MlKem768EncapsulationKey, ViewingPublicKey};
use crate::{Commitment, account::Account, program::PrivateAccountKind}; use crate::{Commitment, account::Account, program::PrivateAccountKind};
#[cfg(feature = "host")]
pub mod shared_key_derivation; pub mod shared_key_derivation;
/// Length in bytes of an ML-KEM-768 ciphertext (the `EphemeralPublicKey` payload). /// Length in bytes of an ML-KEM-768 ciphertext (the `EphemeralPublicKey` payload).
@ -17,6 +15,31 @@ pub const ML_KEM_768_CIPHERTEXT_LEN: usize = 1088;
pub type Scalar = [u8; 32]; pub type Scalar = [u8; 32];
#[derive(Serialize, Deserialize, Clone, Copy)]
pub struct EphemeralSecretKey(pub [u8; 32]);
impl EphemeralSecretKey {
/// Derives an ephemeral secret key from OS randomness and account-specific values.
///
/// For updates, `nonce` carries `nsk`-derived entropy, making `esk` strong even
/// with a compromised RNG. For inits, `nonce` is deterministic, so `random_seed`
/// is the sole entropy source.
#[must_use]
pub fn new(
account_id: &crate::account::AccountId,
random_seed: &[u8; 32],
nonce: &crate::account::Nonce,
) -> Self {
const PREFIX: &[u8; 14] = b"/LEE/v0.3/esk/";
let mut input = [0_u8; 14 + 32 + 32 + 16];
input[0..14].copy_from_slice(PREFIX);
input[14..46].copy_from_slice(account_id.value());
input[46..78].copy_from_slice(random_seed);
input[78..94].copy_from_slice(&nonce.0.to_le_bytes());
Self(Impl::hash_bytes(&input).as_bytes().try_into().unwrap())
}
}
#[derive(Serialize, Deserialize, Clone, Copy)] #[derive(Serialize, Deserialize, Clone, Copy)]
pub struct SharedSecretKey(pub [u8; 32]); pub struct SharedSecretKey(pub [u8; 32]);
@ -55,6 +78,18 @@ pub struct EncryptedAccountData {
pub view_tag: ViewTag, pub view_tag: ViewTag,
} }
impl EncryptedAccountData {
#[must_use]
pub fn compute_view_tag(npk: &crate::NullifierPublicKey, vpk: &ViewingPublicKey) -> ViewTag {
const PREFIX: &[u8; 18] = b"/LEE/v0.3/ViewTag/";
let mut bytes = [0_u8; 18 + 32 + ViewingPublicKey::LEN];
bytes[0..18].copy_from_slice(PREFIX);
bytes[18..50].copy_from_slice(&npk.to_byte_array());
bytes[50..].copy_from_slice(vpk.to_bytes());
Impl::hash_bytes(&bytes).as_bytes()[0]
}
}
#[cfg(feature = "host")] #[cfg(feature = "host")]
impl EncryptedAccountData { impl EncryptedAccountData {
#[must_use] #[must_use]
@ -71,16 +106,6 @@ impl EncryptedAccountData {
view_tag, view_tag,
} }
} }
/// Computes the tag as the first byte of SHA256("/LEE/v0.3/ViewTag/" || npk || vpk).
#[must_use]
pub fn compute_view_tag(npk: &crate::NullifierPublicKey, vpk: &ViewingPublicKey) -> ViewTag {
let mut bytes = Vec::new();
bytes.extend_from_slice(b"/LEE/v0.3/ViewTag/");
bytes.extend_from_slice(&npk.to_byte_array());
bytes.extend_from_slice(vpk.to_bytes());
Impl::hash_bytes(&bytes).as_bytes()[0]
}
} }
impl EncryptionScheme { impl EncryptionScheme {
@ -116,12 +141,12 @@ impl EncryptionScheme {
commitment: &Commitment, commitment: &Commitment,
output_index: u32, output_index: u32,
) -> [u8; 32] { ) -> [u8; 32] {
let mut bytes = Vec::new(); const PREFIX: &[u8; 20] = b"LEE/v0.2/KDF-SHA256/";
let mut bytes = [0_u8; 20 + 32 + 32 + 4];
bytes.extend_from_slice(b"LEE/v0.2/KDF-SHA256/"); bytes[0..20].copy_from_slice(PREFIX);
bytes.extend_from_slice(&shared_secret.0); bytes[20..52].copy_from_slice(&shared_secret.0);
bytes.extend_from_slice(&commitment.to_byte_array()); bytes[52..84].copy_from_slice(&commitment.to_byte_array());
bytes.extend_from_slice(&output_index.to_le_bytes()); bytes[84..88].copy_from_slice(&output_index.to_le_bytes());
Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap() Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()
} }
@ -238,4 +263,41 @@ mod tests {
"wrong shared secret must not produce the correct plaintext" "wrong shared secret must not produce the correct plaintext"
); );
} }
#[test]
fn esk_is_deterministic() {
let account_id = AccountId::new([1_u8; 32]);
let random_seed = [2_u8; 32];
let nonce = crate::account::Nonce(42);
let esk1 = EphemeralSecretKey::new(&account_id, &random_seed, &nonce);
let esk2 = EphemeralSecretKey::new(&account_id, &random_seed, &nonce);
assert_eq!(esk1.0, esk2.0);
}
#[test]
fn esk_differs_for_different_account_id() {
let random_seed = [2_u8; 32];
let nonce = crate::account::Nonce(42);
let esk_a = EphemeralSecretKey::new(&AccountId::new([0_u8; 32]), &random_seed, &nonce);
let esk_b = EphemeralSecretKey::new(&AccountId::new([1_u8; 32]), &random_seed, &nonce);
assert_ne!(esk_a.0, esk_b.0);
}
#[test]
fn esk_differs_for_different_random_seed() {
let account_id = AccountId::new([1_u8; 32]);
let nonce = crate::account::Nonce(42);
let esk_a = EphemeralSecretKey::new(&account_id, &[0_u8; 32], &nonce);
let esk_b = EphemeralSecretKey::new(&account_id, &[1_u8; 32], &nonce);
assert_ne!(esk_a.0, esk_b.0);
}
#[test]
fn esk_differs_for_different_nonce() {
let account_id = AccountId::new([1_u8; 32]);
let random_seed = [2_u8; 32];
let esk_a = EphemeralSecretKey::new(&account_id, &random_seed, &crate::account::Nonce(0));
let esk_b = EphemeralSecretKey::new(&account_id, &random_seed, &crate::account::Nonce(1));
assert_ne!(esk_a.0, esk_b.0);
}
} }

View File

@ -1,5 +1,7 @@
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use ml_kem::{Decapsulate as _, Encapsulate as _, KeyExport as _, Seed}; #[cfg(feature = "host")]
use ml_kem::Encapsulate as _;
use ml_kem::{Decapsulate as _, KeyExport as _, Seed};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{EphemeralPublicKey, SharedSecretKey}; use crate::{EphemeralPublicKey, SharedSecretKey};
@ -26,6 +28,7 @@ impl MlKem768EncapsulationKey {
pub const LEN: usize = 1184; pub const LEN: usize = 1184;
/// Construct from raw bytes, returning an error if the length is not [`Self::LEN`]. /// Construct from raw bytes, returning an error if the length is not [`Self::LEN`].
#[cfg(feature = "host")]
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, crate::error::LeeCoreError> { pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, crate::error::LeeCoreError> {
if bytes.len() != Self::LEN { if bytes.len() != Self::LEN {
return Err(crate::error::LeeCoreError::DeserializationError(format!( return Err(crate::error::LeeCoreError::DeserializationError(format!(
@ -59,6 +62,7 @@ impl SharedSecretKey {
/// Returns `(shared_secret, ciphertext)`. The ciphertext must be included in the transaction /// Returns `(shared_secret, ciphertext)`. The ciphertext must be included in the transaction
/// as the `EphemeralPublicKey`; the receiver recovers the same shared secret via /// as the `EphemeralPublicKey`; the receiver recovers the same shared secret via
/// [`Self::decapsulate`]. /// [`Self::decapsulate`].
#[cfg(feature = "host")]
#[must_use] #[must_use]
pub fn encapsulate(ek: &MlKem768EncapsulationKey) -> (Self, EphemeralPublicKey) { pub fn encapsulate(ek: &MlKem768EncapsulationKey) -> (Self, EphemeralPublicKey) {
let ek_bytes: ml_kem::kem::Key<ml_kem::EncapsulationKey768> = let ek_bytes: ml_kem::kem::Key<ml_kem::EncapsulationKey768> =
@ -76,30 +80,18 @@ impl SharedSecretKey {
(Self(ss_bytes), EphemeralPublicKey(ct.to_vec())) (Self(ss_bytes), EphemeralPublicKey(ct.to_vec()))
} }
/// Deterministically encapsulate a shared secret toward `ek` for use in tests. /// Deterministically encapsulate a shared secret toward `ek` using a
/// pre-derived `esk` as the ML-KEM encapsulation randomness.
/// ///
/// The shared secret has no secret entropy — it is fully determined by `ek`, /// The `esk` must be derived via `derive_esk(account_id, random_seed, nonce)`
/// `message_hash`, and `output_index`, all of which are public. This makes it /// which binds it to the account and incorporates OS entropy.
/// unsuitable for real encryption but useful for producing stable, reproducible
/// shared secrets in unit tests. Use a distinct `output_index` per output to
/// avoid EPK collisions across multiple outputs in the same test.
///
/// For production use [`Self::encapsulate`], which draws randomness from the OS.
#[cfg(any(test, feature = "test_utils"))]
#[must_use] #[must_use]
pub fn encapsulate_deterministic( pub fn encapsulate_deterministic(
ek: &MlKem768EncapsulationKey, ek: &MlKem768EncapsulationKey,
message_hash: &[u8; 32], esk: &crate::encryption::EphemeralSecretKey,
output_index: u32,
) -> (Self, EphemeralPublicKey) { ) -> (Self, EphemeralPublicKey) {
use risc0_zkvm::sha::{Impl, Sha256 as _}; let m: ml_kem::B32 = ml_kem::array::Array::try_from(esk.0.as_slice())
.expect("EphemeralSecretKey is 32 bytes");
let mut input = Vec::with_capacity(36);
input.extend_from_slice(message_hash);
input.extend_from_slice(&output_index.to_le_bytes());
let hash = Impl::hash_bytes(&input);
let m: ml_kem::B32 =
ml_kem::array::Array::try_from(hash.as_bytes()).expect("SHA-256 output is 32 bytes");
let ek_bytes: ml_kem::kem::Key<ml_kem::EncapsulationKey768> = let ek_bytes: ml_kem::kem::Key<ml_kem::EncapsulationKey768> =
ek.0.as_slice() ek.0.as_slice()

View File

@ -11,8 +11,8 @@ pub use commitment::{
compute_digest_for_path, compute_digest_for_path,
}; };
pub use encryption::{ pub use encryption::{
EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, EphemeralSecretKey,
SharedSecretKey, ViewTag, ML_KEM_768_CIPHERTEXT_LEN, SharedSecretKey, ViewTag,
}; };
pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey}; pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey};
pub use program::PrivateAccountKind; pub use program::PrivateAccountKind;

View File

@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use risc0_zkvm::sha::{Impl, Sha256 as _}; use risc0_zkvm::sha::{Impl, Sha256 as _};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Commitment, account::AccountId}; use crate::{Commitment, account::AccountId, encryption::ViewingPublicKey};
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00"; const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
@ -16,12 +16,16 @@ impl AccountId {
/// Derives an [`AccountId`] for a regular (non-PDA) private account from the nullifier public /// Derives an [`AccountId`] for a regular (non-PDA) private account from the nullifier public
/// key and identifier. /// key and identifier.
#[must_use] #[must_use]
pub fn for_regular_private_account(npk: &NullifierPublicKey, identifier: Identifier) -> Self { pub fn for_regular_private_account(
// 32 bytes prefix || 32 bytes npk || 16 bytes identifier npk: &NullifierPublicKey,
let mut bytes = [0; 80]; vpk: &ViewingPublicKey,
identifier: Identifier,
) -> Self {
let mut bytes = [0_u8; 32 + 32 + ViewingPublicKey::LEN + 16];
bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX); bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
bytes[32..64].copy_from_slice(&npk.0); bytes[32..64].copy_from_slice(&npk.0);
bytes[64..80].copy_from_slice(&identifier.to_le_bytes()); bytes[64..64 + ViewingPublicKey::LEN].copy_from_slice(vpk.to_bytes());
bytes[64 + ViewingPublicKey::LEN..].copy_from_slice(&identifier.to_le_bytes());
Self::new( Self::new(
Impl::hash_bytes(&bytes) Impl::hash_bytes(&bytes)
@ -32,9 +36,9 @@ impl AccountId {
} }
} }
impl From<(&NullifierPublicKey, Identifier)> for AccountId { impl From<(&NullifierPublicKey, &ViewingPublicKey, Identifier)> for AccountId {
fn from((npk, identifier): (&NullifierPublicKey, Identifier)) -> Self { fn from((npk, vpk, identifier): (&NullifierPublicKey, &ViewingPublicKey, Identifier)) -> Self {
Self::for_regular_private_account(npk, identifier) Self::for_regular_private_account(npk, vpk, identifier)
} }
} }
@ -158,12 +162,13 @@ mod tests {
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
]; ];
let npk = NullifierPublicKey::from(&nsk); let npk = NullifierPublicKey::from(&nsk);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
let expected_account_id = AccountId::new([ let expected_account_id = AccountId::new([
165, 52, 40, 32, 231, 171, 113, 10, 65, 241, 156, 72, 154, 207, 122, 192, 15, 46, 50, 242, 239, 57, 244, 89, 109, 65, 201, 223, 100, 43, 87, 205, 83, 148, 161, 176, 22, 208,
253, 105, 164, 89, 84, 40, 191, 182, 119, 64, 255, 67, 142, 220, 68, 135, 10, 171, 182, 80, 54, 74, 228, 244, 236, 7,
]); ]);
let account_id = AccountId::for_regular_private_account(&npk, 0); let account_id = AccountId::for_regular_private_account(&npk, &vpk, 0);
assert_eq!(account_id, expected_account_id); assert_eq!(account_id, expected_account_id);
} }
@ -175,12 +180,13 @@ mod tests {
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
]; ];
let npk = NullifierPublicKey::from(&nsk); let npk = NullifierPublicKey::from(&nsk);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
let expected_account_id = AccountId::new([ let expected_account_id = AccountId::new([
203, 201, 109, 245, 40, 54, 195, 12, 55, 33, 0, 86, 245, 65, 70, 156, 24, 249, 26, 95, 149, 125, 157, 109, 119, 81, 9, 163, 231, 181, 214, 43, 57, 113, 221, 72, 180, 149,
56, 247, 99, 121, 165, 182, 234, 255, 19, 127, 191, 72, 189, 170, 32, 181, 255, 231, 19, 92, 235, 59, 153, 185, 172, 206,
]); ]);
let account_id = AccountId::for_regular_private_account(&npk, 1); let account_id = AccountId::for_regular_private_account(&npk, &vpk, 1);
assert_eq!(account_id, expected_account_id); assert_eq!(account_id, expected_account_id);
} }
@ -193,12 +199,13 @@ mod tests {
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
]; ];
let npk = NullifierPublicKey::from(&nsk); let npk = NullifierPublicKey::from(&nsk);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
let expected_account_id = AccountId::new([ let expected_account_id = AccountId::new([
178, 16, 226, 206, 217, 38, 38, 45, 155, 240, 226, 253, 168, 87, 146, 70, 72, 32, 174, 30, 232, 222, 201, 233, 125, 124, 194, 58, 39, 121, 96, 185, 84, 168, 109, 80, 111,
19, 245, 25, 214, 162, 209, 135, 252, 82, 27, 2, 174, 196, 159, 112, 84, 100, 133, 244, 16, 34, 221, 35, 128, 131, 98, 159,
]); ]);
let account_id = AccountId::for_regular_private_account(&npk, identifier); let account_id = AccountId::for_regular_private_account(&npk, &vpk, identifier);
assert_eq!(account_id, expected_account_id); assert_eq!(account_id, expected_account_id);
} }

View File

@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
BlockId, Identifier, NullifierPublicKey, Timestamp, BlockId, Identifier, NullifierPublicKey, Timestamp,
account::{Account, AccountId, AccountWithMetadata}, account::{Account, AccountId, AccountWithMetadata},
encryption::ViewingPublicKey,
}; };
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
@ -154,19 +155,21 @@ impl AccountId {
program_id: &ProgramId, program_id: &ProgramId,
seed: &PdaSeed, seed: &PdaSeed,
npk: &NullifierPublicKey, npk: &NullifierPublicKey,
vpk: &ViewingPublicKey,
identifier: Identifier, identifier: Identifier,
) -> Self { ) -> Self {
use risc0_zkvm::sha::{Impl, Sha256 as _}; use risc0_zkvm::sha::{Impl, Sha256 as _};
const PRIVATE_PDA_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/PrivatePDA/\x00"; const PRIVATE_PDA_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/PrivatePDA/\x00";
let mut bytes = [0_u8; 144]; let mut bytes = [0_u8; 32 + 32 + 32 + 32 + ViewingPublicKey::LEN + 16];
bytes[0..32].copy_from_slice(PRIVATE_PDA_PREFIX); bytes[0..32].copy_from_slice(PRIVATE_PDA_PREFIX);
let program_id_bytes: &[u8] = let program_id_bytes: &[u8] =
bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]"); bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]");
bytes[32..64].copy_from_slice(program_id_bytes); bytes[32..64].copy_from_slice(program_id_bytes);
bytes[64..96].copy_from_slice(&seed.0); bytes[64..96].copy_from_slice(&seed.0);
bytes[96..128].copy_from_slice(&npk.to_byte_array()); bytes[96..128].copy_from_slice(&npk.to_byte_array());
bytes[128..144].copy_from_slice(&identifier.to_le_bytes()); bytes[128..128 + ViewingPublicKey::LEN].copy_from_slice(vpk.to_bytes());
bytes[128 + ViewingPublicKey::LEN..].copy_from_slice(&identifier.to_le_bytes());
Self::new( Self::new(
Impl::hash_bytes(&bytes) Impl::hash_bytes(&bytes)
.as_bytes() .as_bytes()
@ -177,16 +180,20 @@ impl AccountId {
/// Derives the [`AccountId`] for a private account from the nullifier public key and kind. /// Derives the [`AccountId`] for a private account from the nullifier public key and kind.
#[must_use] #[must_use]
pub fn for_private_account(npk: &NullifierPublicKey, kind: &PrivateAccountKind) -> Self { pub fn for_private_account(
npk: &NullifierPublicKey,
vpk: &ViewingPublicKey,
kind: &PrivateAccountKind,
) -> Self {
match kind { match kind {
PrivateAccountKind::Regular(identifier) => { PrivateAccountKind::Regular(identifier) => {
Self::for_regular_private_account(npk, *identifier) Self::for_regular_private_account(npk, vpk, *identifier)
} }
PrivateAccountKind::Pda { PrivateAccountKind::Pda {
program_id, program_id,
seed, seed,
identifier, identifier,
} => Self::for_private_pda(program_id, seed, npk, *identifier), } => Self::for_private_pda(program_id, seed, npk, vpk, *identifier),
} }
} }
} }

View File

@ -184,13 +184,14 @@ fn for_private_pda_matches_pinned_value() {
let program_id: ProgramId = [1; 8]; let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]); let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]); let npk = NullifierPublicKey([3; 32]);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
let identifier: Identifier = u128::MAX; let identifier: Identifier = u128::MAX;
let expected = AccountId::new([ let expected = AccountId::new([
59, 239, 182, 97, 14, 220, 96, 115, 238, 133, 143, 33, 234, 82, 237, 255, 148, 110, 54, 5, 87, 128, 244, 206, 244, 65, 130, 178, 88, 225, 183, 0, 159, 201, 201, 212, 206, 6, 156,
124, 98, 159, 245, 101, 146, 182, 150, 54, 37, 62, 25, 17, 13, 55, 32, 139, 91, 222, 209, 83, 172, 148, 123, 179,
]); ]);
assert_eq!( assert_eq!(
AccountId::for_private_pda(&program_id, &seed, &npk, identifier), AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, identifier),
expected expected
); );
} }
@ -202,9 +203,10 @@ fn for_private_pda_differs_for_different_npk() {
let seed = PdaSeed::new([2; 32]); let seed = PdaSeed::new([2; 32]);
let npk_a = NullifierPublicKey([3; 32]); let npk_a = NullifierPublicKey([3; 32]);
let npk_b = NullifierPublicKey([4; 32]); let npk_b = NullifierPublicKey([4; 32]);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
assert_ne!( assert_ne!(
AccountId::for_private_pda(&program_id, &seed, &npk_a, u128::MAX), AccountId::for_private_pda(&program_id, &seed, &npk_a, &vpk, u128::MAX),
AccountId::for_private_pda(&program_id, &seed, &npk_b, u128::MAX), AccountId::for_private_pda(&program_id, &seed, &npk_b, &vpk, u128::MAX),
); );
} }
@ -215,9 +217,10 @@ fn for_private_pda_differs_for_different_seed() {
let seed_a = PdaSeed::new([2; 32]); let seed_a = PdaSeed::new([2; 32]);
let seed_b = PdaSeed::new([5; 32]); let seed_b = PdaSeed::new([5; 32]);
let npk = NullifierPublicKey([3; 32]); let npk = NullifierPublicKey([3; 32]);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
assert_ne!( assert_ne!(
AccountId::for_private_pda(&program_id, &seed_a, &npk, u128::MAX), AccountId::for_private_pda(&program_id, &seed_a, &npk, &vpk, u128::MAX),
AccountId::for_private_pda(&program_id, &seed_b, &npk, u128::MAX), AccountId::for_private_pda(&program_id, &seed_b, &npk, &vpk, u128::MAX),
); );
} }
@ -228,9 +231,10 @@ fn for_private_pda_differs_for_different_program_id() {
let program_id_b: ProgramId = [9; 8]; let program_id_b: ProgramId = [9; 8];
let seed = PdaSeed::new([2; 32]); let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]); let npk = NullifierPublicKey([3; 32]);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
assert_ne!( assert_ne!(
AccountId::for_private_pda(&program_id_a, &seed, &npk, u128::MAX), AccountId::for_private_pda(&program_id_a, &seed, &npk, &vpk, u128::MAX),
AccountId::for_private_pda(&program_id_b, &seed, &npk, u128::MAX), AccountId::for_private_pda(&program_id_b, &seed, &npk, &vpk, u128::MAX),
); );
} }
@ -241,13 +245,14 @@ fn for_private_pda_differs_for_different_identifier() {
let program_id: ProgramId = [1; 8]; let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]); let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]); let npk = NullifierPublicKey([3; 32]);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
assert_ne!( assert_ne!(
AccountId::for_private_pda(&program_id, &seed, &npk, 0), AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 0),
AccountId::for_private_pda(&program_id, &seed, &npk, 1), AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 1),
); );
assert_ne!( assert_ne!(
AccountId::for_private_pda(&program_id, &seed, &npk, 0), AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 0),
AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX), AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, u128::MAX),
); );
} }
@ -258,7 +263,8 @@ fn for_private_pda_differs_from_public_pda() {
let program_id: ProgramId = [1; 8]; let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]); let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]); let npk = NullifierPublicKey([3; 32]);
let private_id = AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX); let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
let private_id = AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, u128::MAX);
let public_id = AccountId::for_public_pda(&program_id, &seed); let public_id = AccountId::for_public_pda(&program_id, &seed);
assert_ne!(private_id, public_id); assert_ne!(private_id, public_id);
} }
@ -295,22 +301,24 @@ fn for_private_account_dispatches_correctly() {
let program_id: ProgramId = [1; 8]; let program_id: ProgramId = [1; 8];
let seed = PdaSeed::new([2; 32]); let seed = PdaSeed::new([2; 32]);
let npk = NullifierPublicKey([3; 32]); let npk = NullifierPublicKey([3; 32]);
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
let identifier: Identifier = 77; let identifier: Identifier = 77;
assert_eq!( assert_eq!(
AccountId::for_private_account(&npk, &PrivateAccountKind::Regular(identifier)), AccountId::for_private_account(&npk, &vpk, &PrivateAccountKind::Regular(identifier)),
AccountId::for_regular_private_account(&npk, identifier), AccountId::for_regular_private_account(&npk, &vpk, identifier),
); );
assert_eq!( assert_eq!(
AccountId::for_private_account( AccountId::for_private_account(
&npk, &npk,
&vpk,
&PrivateAccountKind::Pda { &PrivateAccountKind::Pda {
program_id, program_id,
seed, seed,
identifier identifier
} }
), ),
AccountId::for_private_pda(&program_id, &seed, &npk, identifier), AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, identifier),
); );
} }

View File

@ -1,8 +1,8 @@
#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
use lee_core::{ use lee_core::{
Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, EphemeralSecretKey, Nullifier,
Nullifier, PrivacyPreservingCircuitOutput, SharedSecretKey, PrivacyPreservingCircuitOutput, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
program::{PdaSeed, PrivateAccountKind}, program::{PdaSeed, PrivateAccountKind},
}; };
@ -58,7 +58,8 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts()
AccountId::new([0; 32]), AccountId::new([0; 32]),
); );
let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
let balance_to_move: u128 = 37; let balance_to_move: u128 = 37;
@ -79,8 +80,9 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts()
let expected_sender_pre = sender.clone(); let expected_sender_pre = sender.clone();
let shared_secret = let init_nonce = Nonce::private_account_nonce_init(&recipient_account_id);
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0).0; 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( let (output, proof) = execute_and_prove(
vec![sender, recipient], vec![sender, recipient],
@ -88,13 +90,9 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts()
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: shared_secret,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}, },
@ -138,12 +136,14 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
data: Data::default(), data: Data::default(),
}, },
true, true,
AccountId::for_regular_private_account(&sender_keys.npk(), 0), AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 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 commitment_sender = Commitment::new(&sender_account_id, &sender_pre.account);
let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
let balance_to_move: u128 = 37; let balance_to_move: u128 = 37;
@ -179,23 +179,26 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
Commitment::new(&recipient_account_id, &expected_private_account_2), Commitment::new(&recipient_account_id, &expected_private_account_2),
]; ];
let shared_secret_1 = let esk_1 = EphemeralSecretKey::new(
SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0; &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 = let shared_secret_2 =
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 1).0; SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &esk_2).0;
let (output, proof) = execute_and_prove( let (output, proof) = execute_and_prove(
vec![sender_pre, recipient], vec![sender_pre, recipient],
Program::serialize_instruction(balance_to_move).unwrap(), Program::serialize_instruction(balance_to_move).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: shared_secret_1,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: commitment_set membership_proof: commitment_set
.get_proof_for(&commitment_sender) .get_proof_for(&commitment_sender)
@ -203,13 +206,9 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: shared_secret_2,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}, },
@ -250,7 +249,7 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
let pre = AccountWithMetadata::new( let pre = AccountWithMetadata::new(
Account::default(), Account::default(),
false, false,
AccountId::for_regular_private_account(&account_keys.npk(), 0), 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_chain_caller = crate::test_methods::validity_window_chain_caller();
@ -265,9 +264,6 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
)) ))
.unwrap(); .unwrap();
let shared_secret =
SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0).0;
let program_with_deps = ProgramWithDependencies::new( let program_with_deps = ProgramWithDependencies::new(
validity_window_chain_caller, validity_window_chain_caller,
[(validity_window.id(), validity_window)].into(), [(validity_window.id(), validity_window)].into(),
@ -277,13 +273,9 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
vec![pre], vec![pre],
instruction, instruction,
vec![InputAccountIdentity::PrivateUnauthorized { vec![InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: account_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&account_keys.npk(),
&account_keys.vpk(),
),
npk: account_keys.npk(), npk: account_keys.npk(),
ssk: shared_secret,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}], }],
@ -302,19 +294,21 @@ fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() {
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let identifier: u128 = 99; let identifier: u128 = 99;
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; 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 account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let (output, _proof) = execute_and_prove( let (output, _proof) = execute_and_prove(
vec![pre_state], vec![pre_state],
Program::serialize_instruction(seed).unwrap(), Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier, identifier,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -343,11 +337,8 @@ fn private_pda_init() {
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let shared_secret_pda =
SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
// PDA (new, private PDA) // PDA (new, private PDA)
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); 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 pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id);
let auth_id = simple_transfer.id(); let auth_id = simple_transfer.id();
@ -361,10 +352,9 @@ fn private_pda_init() {
vec![pda_pre], vec![pda_pre],
instruction, instruction,
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret_pda,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -386,11 +376,8 @@ fn private_pda_withdraw() {
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let shared_secret_pda =
SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
// PDA (new, private PDA) // PDA (new, private PDA)
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); 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 pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id);
// Recipient (public) // Recipient (public)
@ -417,10 +404,9 @@ fn private_pda_withdraw() {
instruction, instruction,
vec![ vec![
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret_pda,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -445,8 +431,6 @@ fn shared_account_receives_via_simple_transfer() {
let shared_keys = test_private_account_keys_1(); let shared_keys = test_private_account_keys_1();
let shared_npk = shared_keys.npk(); let shared_npk = shared_keys.npk();
let shared_identifier: u128 = 42; let shared_identifier: u128 = 42;
let shared_secret =
SharedSecretKey::encapsulate_deterministic(&shared_keys.vpk(), &[0_u8; 32], 0).0;
// Sender: public account with balance, owned by auth-transfer // Sender: public account with balance, owned by auth-transfer
let sender_id = AccountId::new([99; 32]); let sender_id = AccountId::new([99; 32]);
@ -461,7 +445,7 @@ fn shared_account_receives_via_simple_transfer() {
); );
// Recipient: shared private account (new, unauthorized) // Recipient: shared private account (new, unauthorized)
let shared_account_id = AccountId::from((&shared_npk, shared_identifier)); let shared_account_id = AccountId::from((&shared_npk, &shared_keys.vpk(), shared_identifier));
let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id); let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id);
let balance_to_move: u128 = 100; let balance_to_move: u128 = 100;
@ -473,10 +457,9 @@ fn shared_account_receives_via_simple_transfer() {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: shared_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&shared_npk, &shared_keys.vpk()), random_seed: [0; 32],
npk: shared_npk, npk: shared_npk,
ssk: shared_secret,
identifier: shared_identifier, identifier: shared_identifier,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}, },
@ -496,17 +479,21 @@ fn private_authorized_init_encrypts_regular_kind_with_identifier() {
let program = crate::test_methods::claimer(); let program = crate::test_methods::claimer();
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let identifier: u128 = 99; let identifier: u128 = 99;
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let account_id = AccountId::for_regular_private_account(&keys.npk(), 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 pre = AccountWithMetadata::new(Account::default(), true, account_id);
let (output, _) = execute_and_prove( let (output, _) = execute_and_prove(
vec![pre], vec![pre],
Program::serialize_instruction(()).unwrap(), Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit { vec![InputAccountIdentity::PrivateAuthorizedInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), random_seed: [0; 32],
ssk,
nsk: keys.nsk, nsk: keys.nsk,
identifier, identifier,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
@ -528,18 +515,22 @@ fn private_unauthorized_init_encrypts_regular_kind_with_identifier() {
let program = crate::test_methods::claimer(); let program = crate::test_methods::claimer();
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let identifier: u128 = 99; let identifier: u128 = 99;
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let recipient_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let recipient_id = AccountId::for_regular_private_account(&keys.npk(), 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(), false, recipient_id); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_id);
let (output, _) = execute_and_prove( let (output, _) = execute_and_prove(
vec![recipient], vec![recipient],
Program::serialize_instruction(()).unwrap(), Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateUnauthorized { vec![InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), random_seed: [0; 32],
npk: keys.npk(), npk: keys.npk(),
ssk,
identifier, identifier,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}], }],
@ -560,8 +551,13 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() {
let program = crate::test_methods::noop(); let program = crate::test_methods::noop();
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let identifier: u128 = 99; let identifier: u128 = 99;
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
let account_id = AccountId::for_regular_private_account(&keys.npk(), 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 { let account = Account {
program_owner: program.id(), program_owner: program.id(),
balance: 1, balance: 1,
@ -577,9 +573,9 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() {
vec![sender], vec![sender],
Program::serialize_instruction(()).unwrap(), Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate { vec![InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), random_seed: [0; 32],
ssk, view_tag: 0,
nsk: keys.nsk, nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(), membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
identifier, identifier,
@ -604,10 +600,14 @@ fn private_pda_update_encrypts_pda_kind_with_identifier() {
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let identifier: u128 = 99; let identifier: u128 = 99;
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let simple_transfer_id = simple_transfer.id(); let simple_transfer_id = simple_transfer.id();
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier); 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 { let pda_account = Account {
program_owner: simple_transfer_id, program_owner: simple_transfer_id,
balance: 1, balance: 1,
@ -630,9 +630,9 @@ fn private_pda_update_encrypts_pda_kind_with_identifier() {
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(), Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
vec![ vec![
InputAccountIdentity::PrivatePdaUpdate { InputAccountIdentity::PrivatePdaUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
ssk, view_tag: 0,
nsk: keys.nsk, nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(), membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
identifier, identifier,
@ -660,19 +660,16 @@ fn private_pda_init_identifier_mismatch_fails() {
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5);
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let result = execute_and_prove( let result = execute_and_prove(
vec![pre_state], vec![pre_state],
Program::serialize_instruction(seed).unwrap(), Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier: 99, identifier: 99,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -690,10 +687,8 @@ fn private_pda_update_identifier_mismatch_fails() {
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let simple_transfer_id = simple_transfer.id(); let simple_transfer_id = simple_transfer.id();
let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5); let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5);
let pda_account = Account { let pda_account = Account {
program_owner: simple_transfer_id, program_owner: simple_transfer_id,
balance: 1, balance: 1,
@ -714,9 +709,9 @@ fn private_pda_update_identifier_mismatch_fails() {
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(), Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
vec![ vec![
InputAccountIdentity::PrivatePdaUpdate { InputAccountIdentity::PrivatePdaUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
ssk, view_tag: 0,
nsk: keys.nsk, nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(), membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
identifier: 99, identifier: 99,

View File

@ -103,8 +103,8 @@ impl Message {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use lee_core::{ use lee_core::{
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, PrivateAccountKind, Commitment, EncryptionScheme, EphemeralSecretKey, Nullifier, NullifierPublicKey,
SharedSecretKey, PrivateAccountKind, SharedSecretKey,
account::{Account, AccountId, Nonce}, account::{Account, AccountId, Nonce},
encryption::ViewingPublicKey, encryption::ViewingPublicKey,
program::{BlockValidityWindow, TimestampValidityWindow}, program::{BlockValidityWindow, TimestampValidityWindow},
@ -123,6 +123,7 @@ pub mod tests {
let npk1 = NullifierPublicKey::from(&nsk1); let npk1 = NullifierPublicKey::from(&nsk1);
let npk2 = NullifierPublicKey::from(&nsk2); let npk2 = NullifierPublicKey::from(&nsk2);
let vpk = ViewingPublicKey::from_seed(&[7; 32], &[8; 32]);
let public_account_ids = vec![AccountId::new([1; 32])]; let public_account_ids = vec![AccountId::new([1; 32])];
@ -132,10 +133,10 @@ pub mod tests {
let encrypted_private_post_states = Vec::new(); let encrypted_private_post_states = Vec::new();
let account_id2 = lee_core::account::AccountId::for_regular_private_account(&npk2, 0); let account_id2 = lee_core::account::AccountId::for_regular_private_account(&npk2, &vpk, 0);
let new_commitments = vec![Commitment::new(&account_id2, &account2)]; let new_commitments = vec![Commitment::new(&account_id2, &account2)];
let account_id1 = lee_core::account::AccountId::for_regular_private_account(&npk1, 0); let account_id1 = lee_core::account::AccountId::for_regular_private_account(&npk1, &vpk, 0);
let old_commitment = Commitment::new(&account_id1, &account1); let old_commitment = Commitment::new(&account_id1, &account1);
let new_nullifiers = vec![( let new_nullifiers = vec![(
Nullifier::for_account_update(&old_commitment, &nsk1), Nullifier::for_account_update(&old_commitment, &nsk1),
@ -225,9 +226,10 @@ pub mod tests {
let npk = NullifierPublicKey::from(&[1; 32]); let npk = NullifierPublicKey::from(&[1; 32]);
let vpk = ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]); let vpk = ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]);
let account = Account::default(); let account = Account::default();
let account_id = lee_core::account::AccountId::for_regular_private_account(&npk, 0); let account_id = lee_core::account::AccountId::for_regular_private_account(&npk, &vpk, 0);
let commitment = Commitment::new(&account_id, &account); let commitment = Commitment::new(&account_id, &account);
let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&vpk, &[0_u8; 32], 0); let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&vpk, &EphemeralSecretKey([0_u8; 32]));
let ciphertext = EncryptionScheme::encrypt( let ciphertext = EncryptionScheme::encrypt(
&account, &account,
&PrivateAccountKind::Regular(0), &PrivateAccountKind::Regular(0),

View File

@ -60,8 +60,11 @@ fn public_changer_claimer_data_change_no_claim_fails() {
fn private_changer_claimer_no_data_change_no_claim_succeeds() { fn private_changer_claimer_no_data_change_no_claim_succeeds() {
let program = crate::test_methods::changer_claimer(); let program = crate::test_methods::changer_claimer();
let sender_keys = test_private_account_keys_1(); let sender_keys = test_private_account_keys_1();
let private_account = let private_account = AccountWithMetadata::new(
AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0)); Account::default(),
true,
(&sender_keys.npk(), &sender_keys.vpk(), 0),
);
// Don't change data (None) and don't claim (false) // Don't change data (None) and don't claim (false)
let instruction: (Option<Vec<u8>>, bool) = (None, false); let instruction: (Option<Vec<u8>>, bool) = (None, false);
@ -69,12 +72,9 @@ fn private_changer_claimer_no_data_change_no_claim_succeeds() {
vec![private_account], vec![private_account],
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate { vec![InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
@ -90,8 +90,11 @@ fn private_changer_claimer_no_data_change_no_claim_succeeds() {
fn private_changer_claimer_data_change_no_claim_fails() { fn private_changer_claimer_data_change_no_claim_fails() {
let program = crate::test_methods::changer_claimer(); let program = crate::test_methods::changer_claimer();
let sender_keys = test_private_account_keys_1(); let sender_keys = test_private_account_keys_1();
let private_account = let private_account = AccountWithMetadata::new(
AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0)); Account::default(),
true,
(&sender_keys.npk(), &sender_keys.vpk(), 0),
);
// Change data but don't claim (false) - should fail // Change data but don't claim (false) - should fail
let new_data = vec![1, 2, 3, 4, 5]; let new_data = vec![1, 2, 3, 4, 5];
let instruction: (Option<Vec<u8>>, bool) = (Some(new_data), false); let instruction: (Option<Vec<u8>>, bool) = (Some(new_data), false);
@ -100,12 +103,9 @@ fn private_changer_claimer_data_change_no_claim_fails() {
vec![private_account], vec![private_account],
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedUpdate { vec![InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,

View File

@ -45,10 +45,13 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() {
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
);
let private_account_2 = AccountWithMetadata::new(
Account::default(),
false,
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let private_account_2 =
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
// Setting the recipient nsk to authorize the sender. // Setting the recipient nsk to authorize the sender.
// This should be set to the sender private account in a normal circumstance. // This should be set to the sender private account in a normal circumstance.
@ -59,32 +62,19 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() {
Program::serialize_instruction(10_u128).unwrap(), Program::serialize_instruction(10_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
.0,
nsk: recipient_keys.nsk, nsk: recipient_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: SharedSecretKey::encapsulate_deterministic(
&recipient_keys.vpk(),
&[0_u8; 32],
0,
)
.0,
commitment_root: DUMMY_COMMITMENT_HASH,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}, },
], ],
&program.into(), &program.into(),
@ -105,7 +95,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let private_account_2 = AccountWithMetadata::new( let private_account_2 = AccountWithMetadata::new(
Account { Account {
@ -114,7 +104,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
..Account::default() ..Account::default()
}, },
false, false,
(&recipient_keys.npk(), 0), (&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let result = execute_and_prove( let result = execute_and_prove(
@ -122,32 +112,19 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
Program::serialize_instruction(10_u128).unwrap(), Program::serialize_instruction(10_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
.0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: SharedSecretKey::encapsulate_deterministic(
&recipient_keys.vpk(),
&[0_u8; 32],
0,
)
.0,
commitment_root: DUMMY_COMMITMENT_HASH,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}, },
], ],
&program.into(), &program.into(),
@ -168,7 +145,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let private_account_2 = AccountWithMetadata::new( let private_account_2 = AccountWithMetadata::new(
Account { Account {
@ -177,7 +154,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
..Account::default() ..Account::default()
}, },
false, false,
(&recipient_keys.npk(), 0), (&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let result = execute_and_prove( let result = execute_and_prove(
@ -185,32 +162,19 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
Program::serialize_instruction(10_u128).unwrap(), Program::serialize_instruction(10_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
.0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: SharedSecretKey::encapsulate_deterministic(
&recipient_keys.vpk(),
&[0_u8; 32],
0,
)
.0,
commitment_root: DUMMY_COMMITMENT_HASH,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}, },
], ],
&program.into(), &program.into(),
@ -231,7 +195,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let private_account_2 = AccountWithMetadata::new( let private_account_2 = AccountWithMetadata::new(
Account { Account {
@ -240,7 +204,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
..Account::default() ..Account::default()
}, },
false, false,
(&recipient_keys.npk(), 0), (&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let result = execute_and_prove( let result = execute_and_prove(
@ -248,32 +212,19 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
Program::serialize_instruction(10_u128).unwrap(), Program::serialize_instruction(10_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
.0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: SharedSecretKey::encapsulate_deterministic(
&recipient_keys.vpk(),
&[0_u8; 32],
0,
)
.0,
commitment_root: DUMMY_COMMITMENT_HASH,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}, },
], ],
&program.into(), &program.into(),
@ -294,7 +245,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let private_account_2 = AccountWithMetadata::new( let private_account_2 = AccountWithMetadata::new(
Account { Account {
@ -303,7 +254,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
..Account::default() ..Account::default()
}, },
false, false,
(&recipient_keys.npk(), 0), (&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let result = execute_and_prove( let result = execute_and_prove(
@ -311,32 +262,19 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
Program::serialize_instruction(10_u128).unwrap(), Program::serialize_instruction(10_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
.0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: SharedSecretKey::encapsulate_deterministic(
&recipient_keys.vpk(),
&[0_u8; 32],
0,
)
.0,
commitment_root: DUMMY_COMMITMENT_HASH,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}, },
], ],
&program.into(), &program.into(),
@ -358,13 +296,13 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let private_account_2 = AccountWithMetadata::new( let private_account_2 = AccountWithMetadata::new(
Account::default(), Account::default(),
// This should be set to false in normal circumstances // This should be set to false in normal circumstances
true, true,
(&recipient_keys.npk(), 0), (&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let result = execute_and_prove( let result = execute_and_prove(
@ -372,32 +310,19 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu
Program::serialize_instruction(10_u128).unwrap(), Program::serialize_instruction(10_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
.0,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (0, vec![]), membership_proof: (0, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: SharedSecretKey::encapsulate_deterministic(
&recipient_keys.vpk(),
&[0_u8; 32],
0,
)
.0,
commitment_root: DUMMY_COMMITMENT_HASH,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH,
}, },
], ],
&program.into(), &program.into(),
@ -415,7 +340,6 @@ fn private_pda_without_binding_fails() {
let program = crate::test_methods::simple_balance_transfer(); let program = crate::test_methods::simple_balance_transfer();
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let public_account_1 = AccountWithMetadata::new( let public_account_1 = AccountWithMetadata::new(
Account { Account {
program_owner: program.id(), program_owner: program.id(),
@ -434,10 +358,9 @@ fn private_pda_without_binding_fails() {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -460,19 +383,17 @@ fn private_pda_claim_succeeds() {
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, u128::MAX); let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), u128::MAX);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let result = execute_and_prove( let result = execute_and_prove(
vec![pre_state], vec![pre_state],
Program::serialize_instruction(seed).unwrap(), Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -500,22 +421,21 @@ fn private_pda_npk_mismatch_fails() {
let npk_a = keys_a.npk(); let npk_a = keys_a.npk();
let npk_b = keys_b.npk(); let npk_b = keys_b.npk();
let seed = PdaSeed::new([42; 32]); let seed = PdaSeed::new([42; 32]);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys_b.vpk(), &[0_u8; 32], 0).0;
// `account_id` is derived from `npk_a`, but `npk_b` is supplied for this pre_state. // `account_id` is derived from `npk_a`, but `npk_b` is supplied for this pre_state.
// `AccountId::for_private_pda(program, seed, npk_b) != account_id`, so the claim check in // `AccountId::for_private_pda(program, seed, npk_b) != account_id`, so the claim check in
// the circuit must reject. // the circuit must reject.
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk_a, u128::MAX); let account_id =
AccountId::for_private_pda(&program.id(), &seed, &npk_a, &keys_a.vpk(), u128::MAX);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let result = execute_and_prove( let result = execute_and_prove(
vec![pre_state], vec![pre_state],
Program::serialize_instruction(seed).unwrap(), Program::serialize_instruction(seed).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys_b.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk_b, &keys_b.vpk()), random_seed: [0; 32],
npk: npk_b, npk: npk_b,
ssk: shared_secret,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -538,9 +458,9 @@ fn caller_pda_seeds_authorize_private_pda_for_callee() {
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let seed = PdaSeed::new([77; 32]); let seed = PdaSeed::new([77; 32]);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let account_id = AccountId::for_private_pda(&delegator.id(), &seed, &npk, u128::MAX); let account_id =
AccountId::for_private_pda(&delegator.id(), &seed, &npk, &keys.vpk(), u128::MAX);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let callee_id = callee.id(); let callee_id = callee.id();
@ -550,10 +470,9 @@ fn caller_pda_seeds_authorize_private_pda_for_callee() {
vec![pre_state], vec![pre_state],
Program::serialize_instruction((seed, seed, callee_id)).unwrap(), Program::serialize_instruction((seed, seed, callee_id)).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -579,9 +498,9 @@ fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() {
let npk = keys.npk(); let npk = keys.npk();
let claim_seed = PdaSeed::new([77; 32]); let claim_seed = PdaSeed::new([77; 32]);
let wrong_delegated_seed = PdaSeed::new([88; 32]); let wrong_delegated_seed = PdaSeed::new([88; 32]);
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let account_id = AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk, u128::MAX); let account_id =
AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk, &keys.vpk(), u128::MAX);
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
let callee_id = callee.id(); let callee_id = callee.id();
@ -591,10 +510,9 @@ fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() {
vec![pre_state], vec![pre_state],
Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(), Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -619,11 +537,21 @@ fn two_private_pda_claims_under_same_seed_are_rejected() {
let keys_a = test_private_account_keys_1(); let keys_a = test_private_account_keys_1();
let keys_b = test_private_account_keys_2(); let keys_b = test_private_account_keys_2();
let seed = PdaSeed::new([55; 32]); let seed = PdaSeed::new([55; 32]);
let shared_a = SharedSecretKey::encapsulate_deterministic(&keys_a.vpk(), &[0_u8; 32], 0).0;
let shared_b = SharedSecretKey::encapsulate_deterministic(&keys_b.vpk(), &[0_u8; 32], 0).0;
let account_a = AccountId::for_private_pda(&program.id(), &seed, &keys_a.npk(), u128::MAX); let account_a = AccountId::for_private_pda(
let account_b = AccountId::for_private_pda(&program.id(), &seed, &keys_b.npk(), u128::MAX); &program.id(),
&seed,
&keys_a.npk(),
&keys_a.vpk(),
u128::MAX,
);
let account_b = AccountId::for_private_pda(
&program.id(),
&seed,
&keys_b.npk(),
&keys_b.vpk(),
u128::MAX,
);
let pre_a = AccountWithMetadata::new(Account::default(), false, account_a); let pre_a = AccountWithMetadata::new(Account::default(), false, account_a);
let pre_b = AccountWithMetadata::new(Account::default(), false, account_b); let pre_b = AccountWithMetadata::new(Account::default(), false, account_b);
@ -633,19 +561,17 @@ fn two_private_pda_claims_under_same_seed_are_rejected() {
Program::serialize_instruction(seed).unwrap(), Program::serialize_instruction(seed).unwrap(),
vec![ vec![
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys_a.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&keys_a.npk(), &keys_a.vpk()), random_seed: [0; 32],
npk: keys_a.npk(), npk: keys_a.npk(),
ssk: shared_a,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
}, },
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys_b.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&keys_b.npk(), &keys_b.vpk()), random_seed: [0; 32],
npk: keys_b.npk(), npk: keys_b.npk(),
ssk: shared_b,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -667,12 +593,11 @@ fn private_pda_top_level_reuse_rejected_by_binding_check() {
let program = crate::test_methods::noop(); let program = crate::test_methods::noop();
let keys = test_private_account_keys_1(); let keys = test_private_account_keys_1();
let npk = keys.npk(); let npk = keys.npk();
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
let seed = PdaSeed::new([99; 32]); let seed = PdaSeed::new([99; 32]);
// Simulate a previously-claimed private PDA: program_owner != DEFAULT, is_authorized = // Simulate a previously-claimed private PDA: program_owner != DEFAULT, is_authorized =
// true, account_id derived via the private formula. // true, account_id derived via the private formula.
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, u128::MAX); let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), u128::MAX);
let owned_pre_state = AccountWithMetadata::new( let owned_pre_state = AccountWithMetadata::new(
Account { Account {
program_owner: program.id(), program_owner: program.id(),
@ -686,10 +611,9 @@ fn private_pda_top_level_reuse_rejected_by_binding_check() {
vec![owned_pre_state], vec![owned_pre_state],
Program::serialize_instruction(()).unwrap(), Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivatePdaInit { vec![InputAccountIdentity::PrivatePdaInit {
epk: EphemeralPublicKey(Vec::new()), vpk: keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), random_seed: [0; 32],
npk, npk,
ssk: shared_secret,
identifier: u128::MAX, identifier: u128::MAX,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: None, seed: None,
@ -766,33 +690,25 @@ fn circuit_should_fail_if_there_are_repeated_ids() {
..Account::default() ..Account::default()
}, },
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let shared_secret =
SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0;
let result = execute_and_prove( let result = execute_and_prove(
vec![private_account_1.clone(), private_account_1], vec![private_account_1.clone(), private_account_1],
Program::serialize_instruction(100_u128).unwrap(), Program::serialize_instruction(100_u128).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: shared_secret,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (1, vec![]), membership_proof: (1, vec![]),
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: shared_secret,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: (1, vec![]), membership_proof: (1, vec![]),
identifier: 0, identifier: 0,
@ -812,14 +728,15 @@ fn private_authorized_uninitialized_account() {
let private_keys = test_private_account_keys_1(); let private_keys = test_private_account_keys_1();
// Create an authorized private account with default values (new account being initialized) // Create an authorized private account with default values (new account being initialized)
let authorized_account = let authorized_account = AccountWithMetadata::new(
AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0)); Account::default(),
true,
(&private_keys.npk(), &private_keys.vpk(), 0),
);
let program = crate::test_methods::simple_balance_transfer(); let program = crate::test_methods::simple_balance_transfer();
// Set up parameters for the new account // Set up parameters for the new account
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0);
let instruction: u128 = 0; let instruction: u128 = 0;
@ -828,12 +745,8 @@ fn private_authorized_uninitialized_account() {
vec![authorized_account], vec![authorized_account],
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit { vec![InputAccountIdentity::PrivateAuthorizedInit {
epk, vpk: private_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&private_keys.npk(),
&private_keys.vpk(),
),
ssk: shared_secret,
nsk: private_keys.nsk, nsk: private_keys.nsk,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
@ -851,7 +764,8 @@ fn private_authorized_uninitialized_account() {
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0); let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
assert!(result.is_ok()); assert!(result.is_ok());
let account_id = AccountId::for_regular_private_account(&private_keys.npk(), 0); let account_id =
AccountId::for_regular_private_account(&private_keys.npk(), &private_keys.vpk(), 0);
let nullifier = Nullifier::for_account_initialization(&account_id); let nullifier = Nullifier::for_account_initialization(&account_id);
assert!(state.private_state.1.contains(&nullifier)); assert!(state.private_state.1.contains(&nullifier));
} }
@ -865,24 +779,21 @@ fn private_unauthorized_uninitialized_account_can_still_be_claimed() {
// especially PDAs. Private PDAs are not useful in practice because there is no way to // especially PDAs. Private PDAs are not useful in practice because there is no way to
// operate them without the corresponding private keys, so unauthorized private claiming // operate them without the corresponding private keys, so unauthorized private claiming
// remains allowed. // remains allowed.
let unauthorized_account = let unauthorized_account = AccountWithMetadata::new(
AccountWithMetadata::new(Account::default(), false, (&private_keys.npk(), 0)); Account::default(),
false,
(&private_keys.npk(), &private_keys.vpk(), 0),
);
let program = crate::test_methods::claimer(); let program = crate::test_methods::claimer();
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0);
let (output, proof) = execute_and_prove( let (output, proof) = execute_and_prove(
vec![unauthorized_account], vec![unauthorized_account],
Program::serialize_instruction(()).unwrap(), Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateUnauthorized { vec![InputAccountIdentity::PrivateUnauthorized {
epk, vpk: private_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&private_keys.npk(),
&private_keys.vpk(),
),
npk: private_keys.npk(), npk: private_keys.npk(),
ssk: shared_secret,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}], }],
@ -899,7 +810,8 @@ fn private_unauthorized_uninitialized_account_can_still_be_claimed() {
.transition_from_privacy_preserving_transaction(&tx, 1, 0) .transition_from_privacy_preserving_transaction(&tx, 1, 0)
.unwrap(); .unwrap();
let account_id = AccountId::for_regular_private_account(&private_keys.npk(), 0); let account_id =
AccountId::for_regular_private_account(&private_keys.npk(), &private_keys.vpk(), 0);
let nullifier = Nullifier::for_account_initialization(&account_id); let nullifier = Nullifier::for_account_initialization(&account_id);
assert!(state.private_state.1.contains(&nullifier)); assert!(state.private_state.1.contains(&nullifier));
} }
@ -912,14 +824,15 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
let private_keys = test_private_account_keys_1(); let private_keys = test_private_account_keys_1();
// Step 1: Create a new private account with authorization // Step 1: Create a new private account with authorization
let authorized_account = let authorized_account = AccountWithMetadata::new(
AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0)); Account::default(),
true,
(&private_keys.npk(), &private_keys.vpk(), 0),
);
let claimer_program = crate::test_methods::claimer(); let claimer_program = crate::test_methods::claimer();
// Set up parameters for claiming the new account // Set up parameters for claiming the new account
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0);
let instruction = (); let instruction = ();
@ -928,12 +841,8 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
vec![authorized_account.clone()], vec![authorized_account.clone()],
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit { vec![InputAccountIdentity::PrivateAuthorizedInit {
epk, vpk: private_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&private_keys.npk(),
&private_keys.vpk(),
),
ssk: shared_secret,
nsk: private_keys.nsk, nsk: private_keys.nsk,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
@ -955,7 +864,8 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
); );
// Verify the account is now initialized (nullifier exists) // Verify the account is now initialized (nullifier exists)
let account_id = AccountId::for_regular_private_account(&private_keys.npk(), 0); let account_id =
AccountId::for_regular_private_account(&private_keys.npk(), &private_keys.vpk(), 0);
let nullifier = Nullifier::for_account_initialization(&account_id); let nullifier = Nullifier::for_account_initialization(&account_id);
assert!(state.private_state.1.contains(&nullifier)); assert!(state.private_state.1.contains(&nullifier));
@ -967,20 +877,14 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
}; };
let noop_program = crate::test_methods::noop(); let noop_program = crate::test_methods::noop();
let shared_secret2 =
SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0).0;
// Step 3: Try to execute noop program with authentication but without initialization // Step 3: Try to execute noop program with authentication but without initialization
let res = execute_and_prove( let res = execute_and_prove(
vec![account_metadata], vec![account_metadata],
Program::serialize_instruction(()).unwrap(), Program::serialize_instruction(()).unwrap(),
vec![InputAccountIdentity::PrivateAuthorizedInit { vec![InputAccountIdentity::PrivateAuthorizedInit {
epk: EphemeralPublicKey(Vec::new()), vpk: private_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&private_keys.npk(),
&private_keys.vpk(),
),
ssk: shared_secret2,
nsk: private_keys.nsk, nsk: private_keys.nsk,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
@ -1010,8 +914,10 @@ fn two_private_pda_family_members_receive_and_spend() {
); );
let funder_id = funder_keys.account_id(); let funder_id = funder_keys.account_id();
let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0); let alice_pda_0_id =
let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1); AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, &alice_keys.vpk(), 0);
let alice_pda_1_id =
AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, &alice_keys.vpk(), 1);
let recipient_id = test_public_account_keys_2().account_id(); let recipient_id = test_public_account_keys_2().account_id();
let recipient_signing_key = test_public_account_keys_2().signing_key; let recipient_signing_key = test_public_account_keys_2().signing_key;
@ -1031,11 +937,6 @@ fn two_private_pda_family_members_receive_and_spend() {
..Account::default() ..Account::default()
}; };
let (alice_shared_0, alice_epk_0) =
SharedSecretKey::encapsulate_deterministic(&alice_keys.vpk(), &[0_u8; 32], 0);
let (alice_shared_1, alice_epk_1) =
SharedSecretKey::encapsulate_deterministic(&alice_keys.vpk(), &[0_u8; 32], 1);
// Fund alice_pda_0 via authenticated_transfer directly. // Fund alice_pda_0 via authenticated_transfer directly.
{ {
let funder_account = state.get_account_by_id(funder_id); let funder_account = state.get_account_by_id(funder_id);
@ -1049,10 +950,9 @@ fn two_private_pda_family_members_receive_and_spend() {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk: alice_epk_0.clone(), vpk: alice_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()), random_seed: [0; 32],
npk: alice_npk, npk: alice_npk,
ssk: alice_shared_0,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: Some((seed, proxy_id)), seed: Some((seed, proxy_id)),
@ -1086,10 +986,9 @@ fn two_private_pda_family_members_receive_and_spend() {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaInit { InputAccountIdentity::PrivatePdaInit {
epk: alice_epk_1.clone(), vpk: alice_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()), random_seed: [0; 32],
npk: alice_npk, npk: alice_npk,
ssk: alice_shared_1,
identifier: 1, identifier: 1,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
seed: Some((seed, proxy_id)), seed: Some((seed, proxy_id)),
@ -1127,9 +1026,9 @@ fn two_private_pda_family_members_receive_and_spend() {
Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(), Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(),
vec![ vec![
InputAccountIdentity::PrivatePdaUpdate { InputAccountIdentity::PrivatePdaUpdate {
epk: alice_epk_0, vpk: alice_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()), random_seed: [0; 32],
ssk: alice_shared_0, view_tag: 0,
nsk: alice_keys.nsk, nsk: alice_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&commitment_pda_0) .get_proof_for_commitment(&commitment_pda_0)
@ -1165,9 +1064,9 @@ fn two_private_pda_family_members_receive_and_spend() {
Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(), Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(),
vec![ vec![
InputAccountIdentity::PrivatePdaUpdate { InputAccountIdentity::PrivatePdaUpdate {
epk: alice_epk_1, vpk: alice_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()), random_seed: [0; 32],
ssk: alice_shared_1, view_tag: 0,
nsk: alice_keys.nsk, nsk: alice_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&commitment_pda_1) .get_proof_for_commitment(&commitment_pda_1)
@ -1205,7 +1104,6 @@ fn two_private_pda_family_members_receive_and_spend() {
}; };
let commitment_pda_1_after_spend = let commitment_pda_1_after_spend =
Commitment::new(&alice_pda_1_id, &alice_pda_1_account_after_spend); Commitment::new(&alice_pda_1_id, &alice_pda_1_account_after_spend);
let alice_shared_1_refund = SharedSecretKey([12; 32]);
{ {
let recipient_account = state.get_account_by_id(recipient_id); let recipient_account = state.get_account_by_id(recipient_id);
let recipient_nonce = recipient_account.nonce; let recipient_nonce = recipient_account.nonce;
@ -1218,10 +1116,10 @@ fn two_private_pda_family_members_receive_and_spend() {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivatePdaUpdate { InputAccountIdentity::PrivatePdaUpdate {
epk: EphemeralPublicKey(vec![12_u8; ML_KEM_768_CIPHERTEXT_LEN]), vpk: alice_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()), random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk, nsk: alice_keys.nsk,
ssk: alice_shared_1_refund,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&commitment_pda_1_after_spend) .get_proof_for_commitment(&commitment_pda_1_after_spend)
.expect("pda_1 after spend must be in state"), .expect("pda_1 after spend must be in state"),

View File

@ -303,19 +303,21 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() {
balance: 100, balance: 100,
..Account::default() ..Account::default()
}; };
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account); let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account);
let sender_init_nullifier = Nullifier::for_account_initialization(&sender_account_id); let sender_init_nullifier = Nullifier::for_account_initialization(&sender_account_id);
let mut state = let mut state =
V03State::new().with_private_accounts([(sender_commitment.clone(), sender_init_nullifier)]); V03State::new().with_private_accounts([(sender_commitment.clone(), sender_init_nullifier)]);
let sender_pre = let sender_pre = AccountWithMetadata::new(
AccountWithMetadata::new(sender_private_account, true, (&sender_keys.npk(), 0)); sender_private_account,
true,
(&sender_keys.npk(), &sender_keys.vpk(), 0),
);
let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap(); let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap();
let recipient_account_id = let recipient_account_id =
AccountId::from(&PublicKey::new_from_private_key(&recipient_private_key)); AccountId::from(&PublicKey::new_from_private_key(&recipient_private_key));
let recipient_pre = AccountWithMetadata::new(Account::default(), true, recipient_account_id); let recipient_pre = AccountWithMetadata::new(Account::default(), true, recipient_account_id);
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0);
let balance = 37; let balance = 37;
@ -324,12 +326,9 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() {
Program::serialize_instruction(balance).unwrap(), Program::serialize_instruction(balance).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk, vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: shared_secret,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&sender_commitment) .get_proof_for_commitment(&sender_commitment)
@ -383,7 +382,7 @@ fn private_chained_call(number_of_calls: u32) {
..Account::default() ..Account::default()
}, },
true, true,
(&from_keys.npk(), 0), (&from_keys.npk(), &from_keys.vpk(), 0),
); );
let to_account = AccountWithMetadata::new( let to_account = AccountWithMetadata::new(
Account { Account {
@ -391,11 +390,12 @@ fn private_chained_call(number_of_calls: u32) {
..Account::default() ..Account::default()
}, },
true, true,
(&to_keys.npk(), 0), (&to_keys.npk(), &to_keys.vpk(), 0),
); );
let from_account_id = AccountId::for_regular_private_account(&from_keys.npk(), 0); let from_account_id =
let to_account_id = AccountId::for_regular_private_account(&to_keys.npk(), 0); AccountId::for_regular_private_account(&from_keys.npk(), &from_keys.vpk(), 0);
let to_account_id = AccountId::for_regular_private_account(&to_keys.npk(), &to_keys.vpk(), 0);
let from_commitment = Commitment::new(&from_account_id, &from_account.account); let from_commitment = Commitment::new(&from_account_id, &from_account.account);
let to_commitment = Commitment::new(&to_account_id, &to_account.account); let to_commitment = Commitment::new(&to_account_id, &to_account.account);
let from_init_nullifier = Nullifier::for_account_initialization(&from_account_id); let from_init_nullifier = Nullifier::for_account_initialization(&from_account_id);
@ -414,12 +414,6 @@ fn private_chained_call(number_of_calls: u32) {
None, None,
); );
let (from_ss, from_epk) =
SharedSecretKey::encapsulate_deterministic(&from_keys.vpk(), &[0_u8; 32], 0);
let (to_ss, to_epk) =
SharedSecretKey::encapsulate_deterministic(&to_keys.vpk(), &[0_u8; 32], 1);
let mut dependencies = HashMap::new(); let mut dependencies = HashMap::new();
dependencies.insert(simple_transfers.id(), simple_transfers); dependencies.insert(simple_transfers.id(), simple_transfers);
@ -448,9 +442,9 @@ fn private_chained_call(number_of_calls: u32) {
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: to_epk, vpk: from_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag(&to_keys.npk(), &to_keys.vpk()), random_seed: [0; 32],
ssk: to_ss, view_tag: 0,
nsk: from_keys.nsk, nsk: from_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&from_commitment) .get_proof_for_commitment(&from_commitment)
@ -458,12 +452,9 @@ fn private_chained_call(number_of_calls: u32) {
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: from_epk, vpk: to_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&from_keys.npk(), view_tag: 0,
&from_keys.vpk(),
),
ssk: from_ss,
nsk: to_keys.nsk, nsk: to_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&to_commitment) .get_proof_for_commitment(&to_commitment)

View File

@ -43,8 +43,8 @@ fn new_includes_nullifiers_for_private_accounts() {
..Account::default() ..Account::default()
}; };
let account_id1 = AccountId::for_regular_private_account(&keys1.npk(), 0); let account_id1 = AccountId::for_regular_private_account(&keys1.npk(), &keys1.vpk(), 0);
let account_id2 = AccountId::for_regular_private_account(&keys2.npk(), 0); let account_id2 = AccountId::for_regular_private_account(&keys2.npk(), &keys2.vpk(), 0);
let init_commitment1 = Commitment::new(&account_id1, &account); let init_commitment1 = Commitment::new(&account_id1, &account);
let init_commitment2 = Commitment::new(&account_id2, &account); let init_commitment2 = Commitment::new(&account_id2, &account);

View File

@ -7,10 +7,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use lee_core::{ use lee_core::{
BlockId, Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, BlockId, Commitment, DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier,
Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Timestamp, NullifierPublicKey, NullifierSecretKey, Timestamp,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
encryption::{EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN, ViewingPublicKey}, encryption::ViewingPublicKey,
program::{ program::{
BlockValidityWindow, ExecutionValidationError, MAX_NUMBER_CHAINED_CALLS, PdaSeed, BlockValidityWindow, ExecutionValidationError, MAX_NUMBER_CHAINED_CALLS, PdaSeed,
ProgramId, TimestampValidityWindow, WrappedBalanceSum, ProgramId, TimestampValidityWindow, WrappedBalanceSum,
@ -118,7 +118,7 @@ impl V03State {
#[must_use] #[must_use]
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self { pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
let account_id = AccountId::for_regular_private_account(&keys.npk(), 0); let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), 0);
let commitment = Commitment::new(&account_id, account); let commitment = Commitment::new(&account_id, account);
self.private_state.0.extend(&[commitment]); self.private_state.0.extend(&[commitment]);
self self
@ -267,10 +267,11 @@ fn shielded_balance_transfer_for_tests(
let sender_nonce = sender.account.nonce; let sender_nonce = sender.account.nonce;
let recipient = AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); let recipient = AccountWithMetadata::new(
Account::default(),
let (shared_secret, epk) = false,
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0); (&recipient_keys.npk(), &recipient_keys.vpk(), 0),
);
let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove( let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove(
vec![sender, recipient], vec![sender, recipient],
@ -278,13 +279,9 @@ fn shielded_balance_transfer_for_tests(
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk, vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: shared_secret,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}, },
@ -312,33 +309,28 @@ fn private_balance_transfer_for_tests(
state: &V03State, state: &V03State,
) -> PrivacyPreservingTransaction { ) -> PrivacyPreservingTransaction {
let program = crate::test_methods::simple_balance_transfer(); let program = crate::test_methods::simple_balance_transfer();
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account);
let sender_pre = AccountWithMetadata::new( let sender_pre = AccountWithMetadata::new(
sender_private_account.clone(), sender_private_account.clone(),
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
);
let recipient_pre = AccountWithMetadata::new(
Account::default(),
false,
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
); );
let recipient_pre =
AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
let (shared_secret_1, epk_1) =
SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0);
let (shared_secret_2, epk_2) =
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 1);
let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove( let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove(
vec![sender_pre, recipient_pre], vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(), Program::serialize_instruction(balance_to_move).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: epk_1, vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: shared_secret_1,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&sender_commitment) .get_proof_for_commitment(&sender_commitment)
@ -346,13 +338,9 @@ fn private_balance_transfer_for_tests(
identifier: 0, identifier: 0,
}, },
InputAccountIdentity::PrivateUnauthorized { InputAccountIdentity::PrivateUnauthorized {
epk: epk_2, vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(),
&recipient_keys.vpk(),
),
npk: recipient_keys.npk(), npk: recipient_keys.npk(),
ssk: shared_secret_2,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}, },
@ -376,12 +364,13 @@ fn deshielded_balance_transfer_for_tests(
state: &V03State, state: &V03State,
) -> PrivacyPreservingTransaction { ) -> PrivacyPreservingTransaction {
let program = crate::test_methods::simple_balance_transfer(); let program = crate::test_methods::simple_balance_transfer();
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account);
let sender_pre = AccountWithMetadata::new( let sender_pre = AccountWithMetadata::new(
sender_private_account.clone(), sender_private_account.clone(),
true, true,
(&sender_keys.npk(), 0), (&sender_keys.npk(), &sender_keys.vpk(), 0),
); );
let recipient_pre = AccountWithMetadata::new( let recipient_pre = AccountWithMetadata::new(
state.get_account_by_id(*recipient_account_id), state.get_account_by_id(*recipient_account_id),
@ -389,20 +378,14 @@ fn deshielded_balance_transfer_for_tests(
*recipient_account_id, *recipient_account_id,
); );
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0);
let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove( let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove(
vec![sender_pre, recipient_pre], vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(), Program::serialize_instruction(balance_to_move).unwrap(),
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk, vpk: sender_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.npk(), view_tag: 0,
&sender_keys.vpk(),
),
ssk: shared_secret,
nsk: sender_keys.nsk, nsk: sender_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&sender_commitment) .get_proof_for_commitment(&sender_commitment)

View File

@ -68,8 +68,10 @@ fn transition_from_privacy_preserving_transaction_private() {
&state, &state,
); );
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_account_id =
let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let expected_new_commitment_1 = Commitment::new( let expected_new_commitment_1 = Commitment::new(
&sender_account_id, &sender_account_id,
&Account { &Account {
@ -203,7 +205,8 @@ fn transition_from_privacy_preserving_transaction_deshielded() {
&state, &state,
); );
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_account_id =
AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0);
let expected_new_commitment = Commitment::new( let expected_new_commitment = Commitment::new(
&sender_account_id, &sender_account_id,
&Account { &Account {
@ -487,10 +490,14 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
false, false,
sender_keys.account_id(), sender_keys.account_id(),
); );
let recipient_account = let recipient_account = AccountWithMetadata::new(
AccountWithMetadata::new(Account::default(), true, (&recipient_keys.npk(), 0)); Account::default(),
true,
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
);
let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); let recipient_account_id =
AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0);
let recipient_commitment = Commitment::new(&recipient_account_id, &recipient_account.account); let recipient_commitment = Commitment::new(&recipient_account_id, &recipient_account.account);
let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id); let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id);
let state = V03State::new() let state = V03State::new()
@ -504,9 +511,6 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
let balance_to_transfer = 10_u128; let balance_to_transfer = 10_u128;
let instruction = (balance_to_transfer, simple_transfers.id()); let instruction = (balance_to_transfer, simple_transfers.id());
let recipient =
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0).0;
let mut dependencies = HashMap::new(); let mut dependencies = HashMap::new();
dependencies.insert(simple_transfers.id(), simple_transfers); dependencies.insert(simple_transfers.id(), simple_transfers);
let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies); let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies);
@ -518,12 +522,9 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
vec![ vec![
InputAccountIdentity::Public, InputAccountIdentity::Public,
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(Vec::new()), vpk: recipient_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&recipient_keys.npk(), view_tag: 0,
&recipient_keys.vpk(),
),
ssk: recipient,
nsk: recipient_keys.nsk, nsk: recipient_keys.nsk,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&recipient_commitment) .get_proof_for_commitment(&recipient_commitment)

View File

@ -124,12 +124,13 @@ fn validity_window_works_in_privacy_preserving_transactions(
let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap(); let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap();
let validity_window_program = crate::test_methods::validity_window(); let validity_window_program = crate::test_methods::validity_window();
let account_keys = test_private_account_keys_1(); let account_keys = test_private_account_keys_1();
let pre = AccountWithMetadata::new(Account::default(), false, (&account_keys.npk(), 0)); let pre = AccountWithMetadata::new(
Account::default(),
false,
(&account_keys.npk(), &account_keys.vpk(), 0),
);
let mut state = V03State::new().with_test_programs(); let mut state = V03State::new().with_test_programs();
let tx = { let tx = {
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0);
let instruction = ( let instruction = (
block_validity_window, block_validity_window,
TimestampValidityWindow::new_unbounded(), TimestampValidityWindow::new_unbounded(),
@ -138,13 +139,9 @@ fn validity_window_works_in_privacy_preserving_transactions(
vec![pre], vec![pre],
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateUnauthorized { vec![InputAccountIdentity::PrivateUnauthorized {
epk, vpk: account_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&account_keys.npk(),
&account_keys.vpk(),
),
npk: account_keys.npk(), npk: account_keys.npk(),
ssk: shared_secret,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}], }],
@ -192,12 +189,13 @@ fn timestamp_validity_window_works_in_privacy_preserving_transactions(
let timestamp_validity_window: TimestampValidityWindow = validity_window.try_into().unwrap(); let timestamp_validity_window: TimestampValidityWindow = validity_window.try_into().unwrap();
let validity_window_program = crate::test_methods::validity_window(); let validity_window_program = crate::test_methods::validity_window();
let account_keys = test_private_account_keys_1(); let account_keys = test_private_account_keys_1();
let pre = AccountWithMetadata::new(Account::default(), false, (&account_keys.npk(), 0)); let pre = AccountWithMetadata::new(
Account::default(),
false,
(&account_keys.npk(), &account_keys.vpk(), 0),
);
let mut state = V03State::new().with_test_programs(); let mut state = V03State::new().with_test_programs();
let tx = { let tx = {
let (shared_secret, epk) =
SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0);
let instruction = ( let instruction = (
BlockValidityWindow::new_unbounded(), BlockValidityWindow::new_unbounded(),
timestamp_validity_window, timestamp_validity_window,
@ -206,13 +204,9 @@ fn timestamp_validity_window_works_in_privacy_preserving_transactions(
vec![pre], vec![pre],
Program::serialize_instruction(instruction).unwrap(), Program::serialize_instruction(instruction).unwrap(),
vec![InputAccountIdentity::PrivateUnauthorized { vec![InputAccountIdentity::PrivateUnauthorized {
epk, vpk: account_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&account_keys.npk(),
&account_keys.vpk(),
),
npk: account_keys.npk(), npk: account_keys.npk(),
ssk: shared_secret,
identifier: 0, identifier: 0,
commitment_root: DUMMY_COMMITMENT_HASH, commitment_root: DUMMY_COMMITMENT_HASH,
}], }],

View File

@ -78,7 +78,7 @@ fn public_diff_reflects_a_successful_transfer() {
#[test] #[test]
fn privacy_malicious_programs_cannot_drain_public_victim() { fn privacy_malicious_programs_cannot_drain_public_victim() {
use lee_core::{ use lee_core::{
Commitment, EncryptedAccountData, InputAccountIdentity, SharedSecretKey, Commitment, InputAccountIdentity,
account::{Account, AccountWithMetadata}, account::{Account, AccountWithMetadata},
}; };
@ -105,8 +105,8 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
// Attacker controls a private account. // Attacker controls a private account.
let attacker_keys = test_private_account_keys_1(); let attacker_keys = test_private_account_keys_1();
let attacker_id = AccountId::for_regular_private_account(&attacker_keys.npk(), 0); let attacker_id =
let (attacker_ssk, attacker_epk) = SharedSecretKey::encapsulate(&attacker_keys.vpk()); AccountId::for_regular_private_account(&attacker_keys.npk(), &attacker_keys.vpk(), 0);
let victim_id = AccountId::new([20_u8; 32]); let victim_id = AccountId::new([20_u8; 32]);
let recipient_id = AccountId::new([42_u8; 32]); let recipient_id = AccountId::new([42_u8; 32]);
@ -165,12 +165,9 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
// [2] recipient — 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![ let account_identities = vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: attacker_epk, vpk: attacker_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&attacker_keys.npk(), view_tag: 0,
&attacker_keys.vpk(),
),
ssk: attacker_ssk,
nsk: attacker_keys.nsk, nsk: attacker_keys.nsk,
membership_proof, membership_proof,
identifier: 0, identifier: 0,
@ -232,7 +229,7 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
#[test] #[test]
fn privacy_malicious_programs_cannot_drain_private_victim() { fn privacy_malicious_programs_cannot_drain_private_victim() {
use lee_core::{ use lee_core::{
Commitment, EncryptedAccountData, InputAccountIdentity, SharedSecretKey, Commitment, InputAccountIdentity,
account::{Account, AccountWithMetadata}, account::{Account, AccountWithMetadata},
}; };
@ -262,12 +259,13 @@ fn privacy_malicious_programs_cannot_drain_private_victim() {
// Attacker controls a private account. // Attacker controls a private account.
let attacker_keys = test_private_account_keys_1(); let attacker_keys = test_private_account_keys_1();
let attacker_id = AccountId::for_regular_private_account(&attacker_keys.npk(), 0); let attacker_id =
let (attacker_ssk, attacker_epk) = SharedSecretKey::encapsulate(&attacker_keys.vpk()); AccountId::for_regular_private_account(&attacker_keys.npk(), &attacker_keys.vpk(), 0);
// Victim is a private account — not registered in public chain state. // Victim is a private account — not registered in public chain state.
let victim_keys = test_private_account_keys_2(); let victim_keys = test_private_account_keys_2();
let victim_id = AccountId::for_regular_private_account(&victim_keys.npk(), 0); let victim_id =
AccountId::for_regular_private_account(&victim_keys.npk(), &victim_keys.vpk(), 0);
let victim_balance = 5_000_u128; let victim_balance = 5_000_u128;
let recipient_id = AccountId::new([42_u8; 32]); let recipient_id = AccountId::new([42_u8; 32]);
@ -328,12 +326,9 @@ fn privacy_malicious_programs_cannot_drain_private_victim() {
// so PrivateAuthorizedUpdate is not an option. // so PrivateAuthorizedUpdate is not an option.
let account_identities = vec![ let account_identities = vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: attacker_epk, vpk: attacker_keys.vpk(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&attacker_keys.npk(), view_tag: 0,
&attacker_keys.vpk(),
),
ssk: attacker_ssk,
nsk: attacker_keys.nsk, nsk: attacker_keys.nsk,
membership_proof, membership_proof,
identifier: 0, identifier: 0,

View File

@ -30,4 +30,3 @@ tokio.workspace = true
[dev-dependencies] [dev-dependencies]
tempfile.workspace = true tempfile.workspace = true
authenticated_transfer_core.workspace = true

View File

@ -10,15 +10,15 @@ use common::{
}; };
use key_protocol::key_management::KeyChain; use key_protocol::key_management::KeyChain;
use lee::{ use lee::{
Account, AccountId, Data, EphemeralPublicKey, PrivacyPreservingTransaction, PrivateKey, Account, AccountId, Data, PrivacyPreservingTransaction, PrivateKey, PublicKey,
PublicKey, PublicTransaction, SharedSecretKey, V03State, PublicTransaction, V03State,
error::LeeError, error::LeeError,
execute_and_prove, execute_and_prove,
privacy_preserving_transaction::{Message, circuit::ProgramWithDependencies}, privacy_preserving_transaction::{Message, circuit::ProgramWithDependencies},
program::Program, program::Program,
}; };
use lee_core::{ use lee_core::{
Commitment, EncryptedAccountData, InputAccountIdentity, Nullifier, Commitment, InputAccountIdentity, Nullifier,
account::{AccountWithMetadata, Nonce}, account::{AccountWithMetadata, Nonce},
program::PdaSeed, program::PdaSeed,
}; };
@ -790,8 +790,11 @@ async fn block_production_aborts_when_clock_account_data_is_corrupted() {
#[test] #[test]
fn private_bridge_withdraw_invocation_is_dropped() { fn private_bridge_withdraw_invocation_is_dropped() {
let sender_keys = KeyChain::new_os_random(); let sender_keys = KeyChain::new_os_random();
let sender_account_id = let sender_account_id = AccountId::for_regular_private_account(
AccountId::for_regular_private_account(&sender_keys.nullifier_public_key, 0); &sender_keys.nullifier_public_key,
&sender_keys.viewing_public_key,
0,
);
let sender_private_account = Account { let sender_private_account = Account {
program_owner: programs::authenticated_transfer().id(), program_owner: programs::authenticated_transfer().id(),
balance: 100, balance: 100,
@ -812,7 +815,11 @@ fn private_bridge_withdraw_invocation_is_dropped() {
let sender_pre = AccountWithMetadata::new( let sender_pre = AccountWithMetadata::new(
sender_private_account, sender_private_account,
true, true,
(&sender_keys.nullifier_public_key, 0), (
&sender_keys.nullifier_public_key,
&sender_keys.viewing_public_key,
0,
),
); );
let bridge_pre = AccountWithMetadata::new( let bridge_pre = AccountWithMetadata::new(
state.get_account_by_id(bridge_account_id), state.get_account_by_id(bridge_account_id),
@ -820,8 +827,6 @@ fn private_bridge_withdraw_invocation_is_dropped() {
bridge_account_id, bridge_account_id,
); );
let shared_secret = SharedSecretKey::encapsulate(&sender_keys.viewing_public_key).0;
let instruction = Program::serialize_instruction(bridge_core::Instruction::Withdraw { let instruction = Program::serialize_instruction(bridge_core::Instruction::Withdraw {
amount: 1, amount: 1,
bedrock_account_pk: [0; 32], bedrock_account_pk: [0; 32],
@ -842,12 +847,9 @@ fn private_bridge_withdraw_invocation_is_dropped() {
instruction, instruction,
vec![ vec![
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: EphemeralPublicKey(vec![12_u8; 1088]), vpk: sender_keys.viewing_public_key.clone(),
view_tag: EncryptedAccountData::compute_view_tag( random_seed: [0; 32],
&sender_keys.nullifier_public_key, view_tag: 0,
&sender_keys.viewing_public_key,
),
ssk: shared_secret,
nsk: sender_keys.private_key_holder.nullifier_secret_key, nsk: sender_keys.private_key_holder.nullifier_secret_key,
membership_proof: state membership_proof: state
.get_proof_for_commitment(&sender_commitment) .get_proof_for_commitment(&sender_commitment)

View File

@ -86,6 +86,7 @@ pub struct PublicAccountPublicInitialData {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PrivateAccountPublicInitialData { pub struct PrivateAccountPublicInitialData {
pub npk: lee_core::NullifierPublicKey, pub npk: lee_core::NullifierPublicKey,
pub vpk: lee_core::encryption::ViewingPublicKey,
pub account: lee_core::account::Account, pub account: lee_core::account::Account,
} }
@ -108,6 +109,7 @@ impl PrivateAccountPrivateInitialData {
pub fn account_id(&self) -> lee::AccountId { pub fn account_id(&self) -> lee::AccountId {
lee::AccountId::for_regular_private_account( lee::AccountId::for_regular_private_account(
&self.key_chain.nullifier_public_key, &self.key_chain.nullifier_public_key,
&self.key_chain.viewing_public_key,
self.identifier, self.identifier,
) )
} }
@ -183,6 +185,7 @@ fn initial_commitments() -> Vec<PrivateAccountPublicInitialData> {
.into_iter() .into_iter()
.map(|data| PrivateAccountPublicInitialData { .map(|data| PrivateAccountPublicInitialData {
npk: data.key_chain.nullifier_public_key, npk: data.key_chain.nullifier_public_key,
vpk: data.key_chain.viewing_public_key.clone(),
account: data.account, account: data.account,
}) })
.collect() .collect()
@ -193,7 +196,8 @@ fn initial_private_accounts() -> Vec<(lee_core::Commitment, lee_core::Nullifier)
.iter() .iter()
.map(|init_comm_data| { .map(|init_comm_data| {
let npk = &init_comm_data.npk; let npk = &init_comm_data.npk;
let account_id = lee::AccountId::for_regular_private_account(npk, 0); let account_id =
lee::AccountId::for_regular_private_account(npk, &init_comm_data.vpk, 0);
let mut acc = init_comm_data.account.clone(); let mut acc = init_comm_data.account.clone();
@ -304,8 +308,8 @@ mod tests {
const PUB_ACC_A_TEXT_ADDR: &str = "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV"; const PUB_ACC_A_TEXT_ADDR: &str = "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV";
const PUB_ACC_B_TEXT_ADDR: &str = "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"; const PUB_ACC_B_TEXT_ADDR: &str = "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo";
const PRIV_ACC_A_TEXT_ADDR: &str = "4eGX3M3rgjHsme8n3sSp89af8JRZtYVTesbJjLqaX1VQ"; const PRIV_ACC_A_TEXT_ADDR: &str = "EVesBKsYRVtkjnTcsbk8tWHkBn2xZmzAXzwgrP3ZaVoZ";
const PRIV_ACC_B_TEXT_ADDR: &str = "3m6HQmCgmAvsxZtxAHPqqEqoBG4335fCG8TzxigyW7rE"; const PRIV_ACC_B_TEXT_ADDR: &str = "94MXhZnueurjX6v37CYDKVEKYBiyhYArvtEdceq2XDQP";
#[test] #[test]
fn pub_state_consistency() { fn pub_state_consistency() {
@ -440,6 +444,10 @@ mod tests {
init_comms[0], init_comms[0],
PrivateAccountPublicInitialData { PrivateAccountPublicInitialData {
npk: NullifierPublicKey(NPK_PRIV_ACC_A), npk: NullifierPublicKey(NPK_PRIV_ACC_A),
vpk: init_private_accs_keys[0]
.key_chain
.viewing_public_key
.clone(),
account: Account { account: Account {
program_owner: DEFAULT_PROGRAM_OWNER, program_owner: DEFAULT_PROGRAM_OWNER,
balance: PRIV_ACC_A_INITIAL_BALANCE, balance: PRIV_ACC_A_INITIAL_BALANCE,
@ -453,6 +461,10 @@ mod tests {
init_comms[1], init_comms[1],
PrivateAccountPublicInitialData { PrivateAccountPublicInitialData {
npk: NullifierPublicKey(NPK_PRIV_ACC_B), npk: NullifierPublicKey(NPK_PRIV_ACC_B),
vpk: init_private_accs_keys[1]
.key_chain
.viewing_public_key
.clone(),
account: Account { account: Account {
program_owner: DEFAULT_PROGRAM_OWNER, program_owner: DEFAULT_PROGRAM_OWNER,
balance: PRIV_ACC_B_INITIAL_BALANCE, balance: PRIV_ACC_B_INITIAL_BALANCE,

View File

@ -28,9 +28,6 @@ vault_core.workspace = true
[build-dependencies] [build-dependencies]
cbindgen = "0.29" cbindgen = "0.29"
[dev-dependencies]
tempfile = "3"
[features] [features]
default = ["prove"] default = ["prove"]
prove = ["lee/prove"] prove = ["lee/prove"]

View File

@ -645,9 +645,10 @@ mod tests {
let identifier = u128::from_le_bytes([45; 16]); let identifier = u128::from_le_bytes([45; 16]);
let private_reg_acc_id = let private_reg_acc_id =
AccountId::for_private_account(&npk, &PrivateAccountKind::Regular(identifier)); AccountId::for_private_account(&npk, &vpk, &PrivateAccountKind::Regular(identifier));
let private_pda_acc_id = AccountId::for_private_account( let private_pda_acc_id = AccountId::for_private_account(
&npk, &npk,
&vpk,
&PrivateAccountKind::Pda { &PrivateAccountKind::Pda {
program_id: [46; 8], program_id: [46; 8],
seed: PdaSeed::new([47; 32]), seed: PdaSeed::new([47; 32]),

View File

@ -1,15 +1,15 @@
use core::fmt; use core::fmt;
use anyhow::Result; use anyhow::Result;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use keycard_wallet::{KeycardWallet, python_path}; use keycard_wallet::{KeycardWallet, python_path};
use lee::{AccountId, PrivateKey, PublicKey, Signature}; use lee::{AccountId, PrivateKey, PublicKey, Signature};
use lee_core::{ use lee_core::{
CommitmentSetDigest, DUMMY_COMMITMENT_HASH, Identifier, InputAccountIdentity, MembershipProof, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, Identifier, InputAccountIdentity, MembershipProof,
NullifierPublicKey, NullifierSecretKey, SharedSecretKey, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{AccountWithMetadata, Nonce}, account::{AccountWithMetadata, Nonce},
encryption::{EncryptedAccountData, EphemeralPublicKey, ViewingPublicKey}, encryption::{ViewTag, ViewingPublicKey},
}; };
use rand::{RngCore as _, rngs::OsRng};
use crate::{ExecutionFailureKind, WalletCore}; use crate::{ExecutionFailureKind, WalletCore};
@ -201,15 +201,38 @@ impl AccountManager {
for account in accounts { for account in accounts {
let state = match account { let state = match account {
AccountIdentity::Public(account_id) => { AccountIdentity::Public(account_id) => {
prepare_public_state(wallet, account_id, true).await? let acc = wallet
.get_account_public(account_id)
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let sk = wallet.get_account_public_signing_key(account_id).cloned();
let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id);
State::Public { account, sk }
} }
AccountIdentity::PublicNoSign(account_id) => { AccountIdentity::PublicNoSign(account_id) => {
prepare_public_state(wallet, account_id, false).await? let acc = wallet
.get_account_public(account_id)
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let sk = None;
let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id);
State::Public { account, sk }
} }
AccountIdentity::PublicKeycard { AccountIdentity::PublicKeycard {
account_id, account_id,
key_path, key_path,
} => { } => {
let acc = wallet
.get_account_public(account_id)
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let account = AccountWithMetadata::new(acc.clone(), true, account_id);
if pin.is_none() { if pin.is_none() {
pin = Some( pin = Some(
crate::helperfunctions::read_pin() crate::helperfunctions::read_pin()
@ -225,40 +248,75 @@ impl AccountManager {
.to_owned(), .to_owned(),
); );
} }
prepare_public_keycard_state(wallet, account_id, key_path).await?
State::PublicKeycard { account, key_path }
}
AccountIdentity::PrivateOwned(account_id) => {
let pre = private_key_tree_acc_preparation(wallet, account_id, false).await?;
State::Private(pre)
} }
AccountIdentity::PrivateOwned(account_id) => State::Private(
private_key_tree_acc_preparation(wallet, account_id, false).await?,
),
AccountIdentity::PrivateForeign { AccountIdentity::PrivateForeign {
npk, npk,
vpk, vpk,
identifier, identifier,
} => State::Private(private_foreign_acc_preparation(npk, vpk, identifier)), } => {
AccountIdentity::PrivatePdaOwned(account_id) => State::Private( let acc = lee_core::account::Account::default();
private_key_tree_acc_preparation(wallet, account_id, true).await?, let auth_acc = AccountWithMetadata::new(acc, false, (&npk, &vpk, identifier));
), let mut random_seed: [u8; 32] = [0; 32];
OsRng.fill_bytes(&mut random_seed);
let pre = AccountPreparedData {
nsk: None,
npk,
identifier,
vpk,
pre_state: auth_acc,
proof: None,
random_seed,
is_pda: false,
};
State::Private(pre)
}
AccountIdentity::PrivatePdaOwned(account_id) => {
let pre = private_key_tree_acc_preparation(wallet, account_id, true).await?;
State::Private(pre)
}
AccountIdentity::PrivatePdaForeign { AccountIdentity::PrivatePdaForeign {
account_id, account_id,
npk, npk,
vpk, vpk,
identifier, identifier,
} => State::Private(private_pda_foreign_acc_preparation( } => {
account_id, npk, vpk, identifier, let acc = lee_core::account::Account::default();
)), let auth_acc = AccountWithMetadata::new(acc, false, account_id);
let mut random_seed: [u8; 32] = [0; 32];
OsRng.fill_bytes(&mut random_seed);
let pre = AccountPreparedData {
nsk: None,
npk,
identifier,
vpk,
pre_state: auth_acc,
proof: None,
random_seed,
is_pda: true,
};
State::Private(pre)
}
AccountIdentity::PrivateShared { AccountIdentity::PrivateShared {
nsk, nsk,
npk, npk,
vpk, vpk,
identifier, identifier,
} => { } => {
let account_id = lee::AccountId::from((&npk, identifier)); let account_id = lee::AccountId::from((&npk, &vpk, identifier));
State::Private( let pre = private_shared_acc_preparation(
private_shared_acc_preparation( wallet, account_id, nsk, npk, vpk, identifier, false,
wallet, account_id, nsk, npk, vpk, identifier, false,
)
.await?,
) )
.await?;
State::Private(pre)
} }
AccountIdentity::PrivatePdaShared { AccountIdentity::PrivatePdaShared {
account_id, account_id,
@ -266,12 +324,14 @@ impl AccountManager {
npk, npk,
vpk, vpk,
identifier, identifier,
} => State::Private( } => {
private_shared_acc_preparation( let pre = private_shared_acc_preparation(
wallet, account_id, nsk, npk, vpk, identifier, true, wallet, account_id, nsk, npk, vpk, identifier, true,
) )
.await?, .await?;
),
State::Private(pre)
}
}; };
states.push(state); states.push(state);
@ -327,9 +387,26 @@ impl AccountManager {
self.states self.states
.iter() .iter()
.filter_map(|state| match state { .filter_map(|state| match state {
State::Private(pre) => Some(PrivateAccountKeys { ssk: pre.ssk }), State::Private(pre) => Some(pre),
State::Public { .. } | State::PublicKeycard { .. } => None, State::Public { .. } | State::PublicKeycard { .. } => None,
}) })
.map(|pre| {
let nonce = if pre.proof.is_some() {
pre.pre_state.account.nonce.private_account_nonce_increment(
pre.nsk.as_ref().expect("update variant must have nsk"),
)
} else {
lee_core::account::Nonce::private_account_nonce_init(&pre.pre_state.account_id)
};
let esk = lee_core::EphemeralSecretKey::new(
&pre.pre_state.account_id,
&pre.random_seed,
&nonce,
);
PrivateAccountKeys {
ssk: SharedSecretKey::encapsulate_deterministic(&pre.vpk, &esk).0,
}
})
.collect() .collect()
} }
@ -344,19 +421,18 @@ impl AccountManager {
State::Public { .. } | State::PublicKeycard { .. } => InputAccountIdentity::Public, State::Public { .. } | State::PublicKeycard { .. } => InputAccountIdentity::Public,
State::Private(pre) if pre.is_pda => match (pre.nsk, pre.proof.clone()) { State::Private(pre) if pre.is_pda => match (pre.nsk, pre.proof.clone()) {
(Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate { (Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate {
epk: pre.epk.clone(), vpk: pre.vpk.clone(),
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), random_seed: pre.random_seed,
ssk: pre.ssk, view_tag: random_view_tag(),
nsk, nsk,
membership_proof, membership_proof,
identifier: pre.identifier, identifier: pre.identifier,
seed: None, seed: None,
}, },
_ => InputAccountIdentity::PrivatePdaInit { _ => InputAccountIdentity::PrivatePdaInit {
epk: pre.epk.clone(), vpk: pre.vpk.clone(),
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), random_seed: pre.random_seed,
npk: pre.npk, npk: pre.npk,
ssk: pre.ssk,
identifier: pre.identifier, identifier: pre.identifier,
commitment_root: self.dummy_commitment_root, commitment_root: self.dummy_commitment_root,
seed: None, seed: None,
@ -365,27 +441,25 @@ impl AccountManager {
State::Private(pre) => match (pre.nsk, pre.proof.clone()) { State::Private(pre) => match (pre.nsk, pre.proof.clone()) {
(Some(nsk), Some(membership_proof)) => { (Some(nsk), Some(membership_proof)) => {
InputAccountIdentity::PrivateAuthorizedUpdate { InputAccountIdentity::PrivateAuthorizedUpdate {
epk: pre.epk.clone(), vpk: pre.vpk.clone(),
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), random_seed: pre.random_seed,
ssk: pre.ssk, view_tag: random_view_tag(),
nsk, nsk,
membership_proof, membership_proof,
identifier: pre.identifier, identifier: pre.identifier,
} }
} }
(Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit { (Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit {
epk: pre.epk.clone(), vpk: pre.vpk.clone(),
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), random_seed: pre.random_seed,
ssk: pre.ssk,
nsk, nsk,
identifier: pre.identifier, identifier: pre.identifier,
commitment_root: self.dummy_commitment_root, commitment_root: self.dummy_commitment_root,
}, },
(None, _) => InputAccountIdentity::PrivateUnauthorized { (None, _) => InputAccountIdentity::PrivateUnauthorized {
epk: pre.epk.clone(), vpk: pre.vpk.clone(),
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), random_seed: pre.random_seed,
npk: pre.npk, npk: pre.npk,
ssk: pre.ssk,
identifier: pre.identifier, identifier: pre.identifier,
commitment_root: self.dummy_commitment_root, commitment_root: self.dummy_commitment_root,
}, },
@ -462,49 +536,12 @@ struct AccountPreparedData {
vpk: ViewingPublicKey, vpk: ViewingPublicKey,
pre_state: AccountWithMetadata, pre_state: AccountWithMetadata,
proof: Option<MembershipProof>, proof: Option<MembershipProof>,
/// Cached shared-secret key derived once at `AccountManager::new`. Reused for both the random_seed: [u8; 32],
/// circuit input variant (`account_identities()`) and the message ephemeral-key tuples
/// (`private_account_keys()`), so all consumers see the same key. The corresponding
/// `EphemeralKeyHolder` uses `OsRng` and would produce a different value on a second call.
ssk: SharedSecretKey,
/// Cached ephemeral public key, paired with `ssk`.
epk: EphemeralPublicKey,
/// True when this account is a private PDA (owned or foreign). Used by `account_identities()` /// 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 `PrivatePdaInit`/`PrivatePdaUpdate` rather than the standalone private variants.
is_pda: bool, is_pda: bool,
} }
async fn prepare_public_state(
wallet: &WalletCore,
account_id: AccountId,
lookup_signing_key: bool,
) -> Result<State, ExecutionFailureKind> {
let acc = wallet
.get_account_public(account_id)
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let sk = if lookup_signing_key {
wallet.get_account_public_signing_key(account_id).cloned()
} else {
None
};
let account = AccountWithMetadata::new(acc, sk.is_some(), account_id);
Ok(State::Public { account, sk })
}
async fn prepare_public_keycard_state(
wallet: &WalletCore,
account_id: AccountId,
key_path: String,
) -> Result<State, ExecutionFailureKind> {
let acc = wallet
.get_account_public(account_id)
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let account = AccountWithMetadata::new(acc, true, account_id);
Ok(State::PublicKeycard { account, key_path })
}
async fn private_key_tree_acc_preparation( async fn private_key_tree_acc_preparation(
wallet: &WalletCore, wallet: &WalletCore,
account_id: AccountId, account_id: AccountId,
@ -530,9 +567,8 @@ async fn private_key_tree_acc_preparation(
// support from that in the wallet. // support from that in the wallet.
let sender_pre = AccountWithMetadata::new(from_acc.account.clone(), true, account_id); let sender_pre = AccountWithMetadata::new(from_acc.account.clone(), true, account_id);
let eph_holder = EphemeralKeyHolder::new(&from_vpk); let mut random_seed: [u8; 32] = [0; 32];
let ssk = eph_holder.calculate_shared_secret_sender(); OsRng.fill_bytes(&mut random_seed);
let epk = eph_holder.ephemeral_public_key().clone();
Ok(AccountPreparedData { Ok(AccountPreparedData {
nsk: Some(nsk), nsk: Some(nsk),
@ -541,8 +577,7 @@ async fn private_key_tree_acc_preparation(
vpk: from_vpk, vpk: from_vpk,
pre_state: sender_pre, pre_state: sender_pre,
proof, proof,
ssk, random_seed,
epk,
is_pda, is_pda,
}) })
} }
@ -570,9 +605,8 @@ async fn private_shared_acc_preparation(
.await .await
.unwrap_or(None); .unwrap_or(None);
let eph_holder = EphemeralKeyHolder::new(&vpk); let mut random_seed: [u8; 32] = [0; 32];
let ssk = eph_holder.calculate_shared_secret_sender(); OsRng.fill_bytes(&mut random_seed);
let epk = eph_holder.ephemeral_public_key().clone();
Ok(AccountPreparedData { Ok(AccountPreparedData {
nsk: Some(nsk), nsk: Some(nsk),
@ -581,59 +615,16 @@ async fn private_shared_acc_preparation(
vpk, vpk,
pre_state, pre_state,
proof, proof,
ssk, random_seed,
epk,
is_pda, is_pda,
}) })
} }
fn private_foreign_acc_preparation( /// Generate random byte using OS randomness.
npk: NullifierPublicKey, fn random_view_tag() -> ViewTag {
vpk: ViewingPublicKey, let mut byte: [u8; 1] = [0; 1];
identifier: Identifier, OsRng.fill_bytes(&mut byte);
) -> AccountPreparedData { byte[0]
let acc = lee_core::account::Account::default();
let pre_state = AccountWithMetadata::new(acc, false, (&npk, identifier));
let eph_holder = EphemeralKeyHolder::new(&vpk);
let ssk = eph_holder.calculate_shared_secret_sender();
let epk = eph_holder.ephemeral_public_key().clone();
AccountPreparedData {
nsk: None,
npk,
identifier,
vpk,
pre_state,
proof: None,
ssk,
epk,
is_pda: false,
}
}
fn private_pda_foreign_acc_preparation(
account_id: AccountId,
npk: NullifierPublicKey,
vpk: ViewingPublicKey,
identifier: Identifier,
) -> AccountPreparedData {
let acc = lee_core::account::Account::default();
let pre_state = AccountWithMetadata::new(acc, false, account_id);
let eph_holder = EphemeralKeyHolder::new(&vpk);
let ssk = eph_holder.calculate_shared_secret_sender();
let epk = eph_holder.ephemeral_public_key().clone();
AccountPreparedData {
nsk: None,
npk,
identifier,
vpk,
pre_state,
proof: None,
ssk,
epk,
is_pda: true,
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -581,8 +581,11 @@ impl WalletSubcommand for ImportSubcommand {
let key_chain: KeyChain = serde_json::from_str(&key_chain_json) let key_chain: KeyChain = serde_json::from_str(&key_chain_json)
.map_err(|err| anyhow::anyhow!("Invalid key chain JSON: {err}"))?; .map_err(|err| anyhow::anyhow!("Invalid key chain JSON: {err}"))?;
let account = lee::Account::from(account_state); let account = lee::Account::from(account_state);
let account_id = let account_id = lee::AccountId::from((
lee::AccountId::from((&key_chain.nullifier_public_key, identifier)); &key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
identifier,
));
wallet_core wallet_core
.storage_mut() .storage_mut()

View File

@ -369,7 +369,7 @@ impl WalletCore {
let keys = holder.derive_keys_for_pda(&program_id, &pda_seed); let keys = holder.derive_keys_for_pda(&program_id, &pda_seed);
let npk = keys.generate_nullifier_public_key(); let npk = keys.generate_nullifier_public_key();
let vpk = keys.generate_viewing_public_key(); let vpk = keys.generate_viewing_public_key();
let account_id = AccountId::for_private_pda(&program_id, &pda_seed, &npk, identifier); let account_id = AccountId::for_private_pda(&program_id, &pda_seed, &npk, &vpk, identifier);
self.register_shared_account( self.register_shared_account(
account_id, account_id,
@ -403,7 +403,7 @@ impl WalletCore {
let keys = holder.derive_regular_shared_account_keys_from_identifier(identifier); let keys = holder.derive_regular_shared_account_keys_from_identifier(identifier);
let npk = keys.generate_nullifier_public_key(); let npk = keys.generate_nullifier_public_key();
let vpk = keys.generate_viewing_public_key(); let vpk = keys.generate_viewing_public_key();
let account_id = AccountId::from((&npk, identifier)); let account_id = AccountId::from((&npk, &vpk, identifier));
self.register_shared_account(account_id, group_name, identifier, None, None); self.register_shared_account(account_id, group_name, identifier, None, None);
@ -803,7 +803,11 @@ impl WalletCore {
) )
.map(|(kind, res_acc)| { .map(|(kind, res_acc)| {
let npk = &key_chain.nullifier_public_key; let npk = &key_chain.nullifier_public_key;
let account_id = lee::AccountId::for_private_account(npk, &kind); let account_id = lee::AccountId::for_private_account(
npk,
&key_chain.viewing_public_key,
&kind,
);
let nsk = key_chain.private_key_holder.nullifier_secret_key; let nsk = key_chain.private_key_holder.nullifier_secret_key;
(account_id, kind, res_acc, nsk) (account_id, kind, res_acc, nsk)
}) })

View File

@ -212,7 +212,8 @@ impl UserKeyChain {
.1 .1
.first_key_value() .first_key_value()
.expect("Newly created key chain node must have at least one account"); .expect("Newly created key chain node must have at least one account");
let account_id = AccountId::for_private_account(&npk, kind); let account_id =
AccountId::for_private_account(&npk, &node.value.0.viewing_public_key, kind);
(account_id, chain_index) (account_id, chain_index)
} }
@ -248,8 +249,11 @@ impl UserKeyChain {
#[must_use] #[must_use]
pub fn private_account(&self, account_id: AccountId) -> Option<FoundPrivateAccount<'_>> { pub fn private_account(&self, account_id: AccountId) -> Option<FoundPrivateAccount<'_>> {
self.private_accounts().find_map(|found| { self.private_accounts().find_map(|found| {
let expected_id = let expected_id = AccountId::for_private_account(
AccountId::for_private_account(&found.key_chain.nullifier_public_key, found.kind); &found.key_chain.nullifier_public_key,
&found.key_chain.viewing_public_key,
found.kind,
);
(expected_id == account_id).then_some(found) (expected_id == account_id).then_some(found)
}) })
} }
@ -305,8 +309,11 @@ impl UserKeyChain {
.iter() .iter()
.flat_map(|(key, data)| { .flat_map(|(key, data)| {
data.accounts.keys().map(|kind| { data.accounts.keys().map(|kind| {
let account_id = let account_id = AccountId::for_private_account(
AccountId::for_private_account(&key.key_chain.nullifier_public_key, kind); &key.key_chain.nullifier_public_key,
&key.key_chain.viewing_public_key,
kind,
);
(account_id, &key.key_chain, key.chain_index.as_ref()) (account_id, &key.key_chain, key.chain_index.as_ref())
}) })
}) })
@ -346,8 +353,11 @@ impl UserKeyChain {
// For each (regular) found account the user owns, compute its nullifier and put // For each (regular) found account the user owns, compute its nullifier and put
// into the map. This is the next nullifier it will look for. // into the map. This is the next nullifier it will look for.
for found in self.private_accounts() { for found in self.private_accounts() {
let account_id = let account_id = AccountId::for_private_account(
AccountId::for_private_account(&found.key_chain.nullifier_public_key, found.kind); &found.key_chain.nullifier_public_key,
&found.key_chain.viewing_public_key,
found.kind,
);
let nsk = found.key_chain.private_key_holder.nullifier_secret_key; let nsk = found.key_chain.private_key_holder.nullifier_secret_key;
index.track(account_id, found.account, &nsk); index.track(account_id, found.account, &nsk);
} }
@ -496,8 +506,11 @@ impl UserKeyChain {
// Then try to update imported account // Then try to update imported account
for (key, data) in &mut self.imported_private_accounts { for (key, data) in &mut self.imported_private_accounts {
for (kind, imported_account) in &mut data.accounts { for (kind, imported_account) in &mut data.accounts {
let expected_id = let expected_id = AccountId::for_private_account(
AccountId::for_private_account(&key.key_chain.nullifier_public_key, kind); &key.key_chain.nullifier_public_key,
&key.key_chain.viewing_public_key,
kind,
);
if expected_id == account_id { if expected_id == account_id {
debug!("Updating imported private account {account_id}"); debug!("Updating imported private account {account_id}");
*imported_account = account; *imported_account = account;
@ -534,8 +547,11 @@ impl UserKeyChain {
// Node not yet in account_id_map — find it by checking all nodes // Node not yet in account_id_map — find it by checking all nodes
for (ci, node) in &mut self.private_key_tree.key_map { for (ci, node) in &mut self.private_key_tree.key_map {
let expected_id = let expected_id = lee::AccountId::for_private_account(
lee::AccountId::for_private_account(&node.value.0.nullifier_public_key, &kind); &node.value.0.nullifier_public_key,
&node.value.0.viewing_public_key,
&kind,
);
if expected_id == account_id { if expected_id == account_id {
match node.value.1.entry(kind) { match node.value.1.entry(kind) {
Entry::Occupied(mut occupied) => { Entry::Occupied(mut occupied) => {
@ -585,8 +601,11 @@ impl UserKeyChain {
.iter() .iter()
.flat_map(|(key, data)| { .flat_map(|(key, data)| {
data.accounts.keys().map(|kind| { data.accounts.keys().map(|kind| {
let account_id = let account_id = AccountId::for_private_account(
AccountId::for_private_account(&key.key_chain.nullifier_public_key, kind); &key.key_chain.nullifier_public_key,
&key.key_chain.viewing_public_key,
kind,
);
(account_id, key.chain_index.as_ref()) (account_id, key.chain_index.as_ref())
}) })
}) })
@ -851,6 +870,7 @@ mod tests {
let identifier = 0; let identifier = 0;
let account_id = AccountId::for_private_account( let account_id = AccountId::for_private_account(
&key_chain.nullifier_public_key, &key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
&PrivateAccountKind::Regular(identifier), &PrivateAccountKind::Regular(identifier),
); );
@ -913,7 +933,7 @@ mod tests {
let npk = keys.generate_nullifier_public_key(); let npk = keys.generate_nullifier_public_key();
let vpk = keys.generate_viewing_public_key(); let vpk = keys.generate_viewing_public_key();
let nsk = keys.nullifier_secret_key; let nsk = keys.nullifier_secret_key;
let account_id = AccountId::from((&npk, identifier)); let account_id = AccountId::from((&npk, &vpk, identifier));
kc.insert_group_key_holder(label.clone(), holder); kc.insert_group_key_holder(label.clone(), holder);
let old_account = Account::default(); let old_account = Account::default();
@ -975,6 +995,7 @@ mod tests {
let identifier = 0; let identifier = 0;
let account_id = AccountId::for_private_account( let account_id = AccountId::for_private_account(
&key_chain.nullifier_public_key, &key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
&PrivateAccountKind::Regular(identifier), &PrivateAccountKind::Regular(identifier),
); );
let account = Account::default(); let account = Account::default();
@ -1032,7 +1053,11 @@ mod tests {
let mut user_data = UserKeyChain::default(); let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random(); let key_chain = KeyChain::new_os_random();
let account_id = AccountId::from((&key_chain.nullifier_public_key, 0)); let account_id = AccountId::from((
&key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
0,
));
let account = lee_core::account::Account::default(); let account = lee_core::account::Account::default();
user_data.add_imported_private_account(key_chain, None, 0, account); user_data.add_imported_private_account(key_chain, None, 0, account);
@ -1047,7 +1072,11 @@ mod tests {
let mut user_data = UserKeyChain::default(); let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random(); let key_chain = KeyChain::new_os_random();
let account_id = AccountId::from((&key_chain.nullifier_public_key, 0)); let account_id = AccountId::from((
&key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
0,
));
let account = lee_core::account::Account::default(); let account = lee_core::account::Account::default();
user_data.add_imported_private_account(key_chain, None, 0, account.clone()); user_data.add_imported_private_account(key_chain, None, 0, account.clone());
@ -1092,7 +1121,11 @@ mod tests {
let mut user_data = UserKeyChain::default(); let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random(); let key_chain = KeyChain::new_os_random();
let account_id = AccountId::from((&key_chain.nullifier_public_key, 0)); let account_id = AccountId::from((
&key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
0,
));
let new_account = lee_core::account::Account { let new_account = lee_core::account::Account {
balance: 100, balance: 100,
@ -1113,7 +1146,11 @@ mod tests {
let mut user_data = UserKeyChain::default(); let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random(); let key_chain = KeyChain::new_os_random();
let account_id1 = AccountId::from((&key_chain.nullifier_public_key, 0)); let account_id1 = AccountId::from((
&key_chain.nullifier_public_key,
&key_chain.viewing_public_key,
0,
));
let account = lee_core::account::Account::default(); let account = lee_core::account::Account::default();
user_data.add_imported_private_account(key_chain, None, 0, account); user_data.add_imported_private_account(key_chain, None, 0, account);

View File

@ -23,7 +23,11 @@ pub struct InitialPrivateAccountForWallet {
impl InitialPrivateAccountForWallet { impl InitialPrivateAccountForWallet {
#[must_use] #[must_use]
pub fn account_id(&self) -> AccountId { pub fn account_id(&self) -> AccountId {
AccountId::from((&self.key_chain.nullifier_public_key, self.identifier)) AccountId::from((
&self.key_chain.nullifier_public_key,
&self.key_chain.viewing_public_key,
self.identifier,
))
} }
} }

View File

@ -11,7 +11,6 @@ workspace = true
[dev-dependencies] [dev-dependencies]
key_protocol.workspace = true key_protocol.workspace = true
lee_core = { workspace = true, features = ["host"] } lee_core = { workspace = true, features = ["host"] }
rand = { workspace = true }
criterion.workspace = true criterion.workspace = true
[[bench]] [[bench]]

View File

@ -48,7 +48,8 @@ fn bench_encryption(c: &mut Criterion) {
let recipient_kc = KeyChain::new_os_random(); let recipient_kc = KeyChain::new_os_random();
let npk = recipient_kc.nullifier_public_key; let npk = recipient_kc.nullifier_public_key;
let account = Account::default(); let account = Account::default();
let account_id = AccountId::for_regular_private_account(&npk, 0); let account_id =
AccountId::for_regular_private_account(&npk, &recipient_kc.viewing_public_key, 0);
let commitment = Commitment::new(&account_id, &account); let commitment = Commitment::new(&account_id, &account);
let (shared, _epk) = SharedSecretKey::encapsulate(&recipient_kc.viewing_public_key); let (shared, _epk) = SharedSecretKey::encapsulate(&recipient_kc.viewing_public_key);
let kind = PrivateAccountKind::Regular(0_u128); let kind = PrivateAccountKind::Regular(0_u128);