mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-09 15:29:34 +00:00
Merge 640d4f6502d6c23f2d071bbf57609f6e43fd6066 into b31aecd2c064ed8544fb1195033dec5cc196942c
This commit is contained in:
commit
589536b7af
@ -60,6 +60,7 @@ allow-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-rust-rapidsnark.git",
|
||||
"https://github.com/logos-blockchain/sponges",
|
||||
"https://github.com/arkworks-rs/spongefish.git",
|
||||
]
|
||||
unknown-git = "deny"
|
||||
|
||||
4
.github/workflows/bench-regression.yml
vendored
4
.github/workflows/bench-regression.yml
vendored
@ -28,10 +28,6 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
|
||||
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -1914,7 +1914,6 @@ dependencies = [
|
||||
"criterion",
|
||||
"key_protocol",
|
||||
"lee_core",
|
||||
"rand 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3850,7 +3849,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"async-stream",
|
||||
"authenticated_transfer_core",
|
||||
"borsh",
|
||||
"common",
|
||||
"futures",
|
||||
@ -4441,11 +4439,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
|
||||
source = "git+https://github.com/logos-blockchain/sponges?rev=3a56e99771beedf04946eab21a4a62adc2951377#3a56e99771beedf04946eab21a4a62adc2951377"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"risc0-zkvm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4553,7 +4551,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"borsh",
|
||||
"build_utils",
|
||||
"env_logger",
|
||||
"hex",
|
||||
"hex-literal 1.1.0",
|
||||
"k256",
|
||||
@ -4568,7 +4565,6 @@ dependencies = [
|
||||
"test-case",
|
||||
"test_methods",
|
||||
"thiserror 2.0.18",
|
||||
"token_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -10890,7 +10886,6 @@ dependencies = [
|
||||
"risc0-zkvm",
|
||||
"sequencer_service_rpc",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"vault_core",
|
||||
"wallet",
|
||||
|
||||
@ -194,6 +194,10 @@ opt-level = 'z'
|
||||
lto = true
|
||||
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
|
||||
# linking large integration-test binaries on resource-constrained CI runners.
|
||||
[profile.dev]
|
||||
@ -339,4 +343,4 @@ clippy.cargo = { level = "deny", priority = -1 }
|
||||
clippy.cargo-common-metadata = "allow"
|
||||
# Reason: hard to address right now and mostly comes from dependencies
|
||||
# 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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -141,7 +141,11 @@ async fn import_private_account() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
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 {
|
||||
program_owner: programs::authenticated_transfer().id(),
|
||||
balance: 777,
|
||||
@ -198,7 +202,11 @@ async fn import_private_account_second_time_overrides_account_data() -> Result<(
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
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 =
|
||||
serde_json::to_string(&key_chain).context("Failed to serialize key chain")?;
|
||||
|
||||
|
||||
@ -8,15 +8,14 @@ use integration_tests::{
|
||||
private_mention, public_mention, send, sync_private, verify_commitment_is_in_state,
|
||||
};
|
||||
use lee::{
|
||||
AccountId, SharedSecretKey, execute_and_prove,
|
||||
privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
|
||||
AccountId, execute_and_prove, privacy_preserving_transaction::circuit::ProgramWithDependencies,
|
||||
program::Program,
|
||||
};
|
||||
use lee_core::{
|
||||
DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity,
|
||||
ML_KEM_768_CIPHERTEXT_LEN, Nullifier, NullifierPublicKey,
|
||||
DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier, NullifierPublicKey,
|
||||
account::{Account, AccountWithMetadata},
|
||||
compute_digest_for_path,
|
||||
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
||||
encryption::ViewingPublicKey,
|
||||
};
|
||||
use log::info;
|
||||
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?;
|
||||
|
||||
// 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
|
||||
.wallet()
|
||||
.get_account_private(account_id_1)
|
||||
.context("account for identifier 1 not found after sync")?;
|
||||
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
|
||||
.wallet()
|
||||
.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 npk = NullifierPublicKey::from(&nsk);
|
||||
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 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;
|
||||
|
||||
@ -600,10 +597,9 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk),
|
||||
vpk,
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 1337,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -632,8 +628,7 @@ async fn prove_init_with_commitment_root(
|
||||
let nsk: lee_core::NullifierSecretKey = [7; 32];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
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, 0);
|
||||
let recipient_account_id = AccountId::for_regular_private_account(&npk, &vpk, 0);
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
|
||||
|
||||
let (output, _) = execute_and_prove(
|
||||
@ -644,10 +639,9 @@ async fn prove_init_with_commitment_root(
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk),
|
||||
vpk,
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 0,
|
||||
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 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?;
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ use common::transaction::LeeTransaction;
|
||||
use integration_tests::{
|
||||
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::{
|
||||
AccountId, PrivacyPreservingTransaction, ProgramId,
|
||||
privacy_preserving_transaction::{
|
||||
@ -22,7 +21,7 @@ use lee::{
|
||||
program::Program,
|
||||
};
|
||||
use lee_core::{
|
||||
DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, NullifierPublicKey,
|
||||
DUMMY_COMMITMENT_HASH, InputAccountIdentity, NullifierPublicKey,
|
||||
account::{Account, AccountWithMetadata},
|
||||
encryption::ViewingPublicKey,
|
||||
program::PdaSeed,
|
||||
@ -48,7 +47,8 @@ async fn fund_private_pda(
|
||||
amount: u128,
|
||||
auth_transfer: &ProgramWithDependencies,
|
||||
) -> 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
|
||||
.get_account_public(sender)
|
||||
.await
|
||||
@ -60,20 +60,15 @@ async fn fund_private_pda(
|
||||
let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, sender);
|
||||
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 })
|
||||
.context("failed to serialize auth_transfer instruction")?;
|
||||
|
||||
let account_identities = vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk),
|
||||
vpk,
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk,
|
||||
identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: Some((seed, authority_program_id)),
|
||||
@ -173,8 +168,8 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> {
|
||||
let spend_program =
|
||||
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_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1);
|
||||
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, &alice_vpk, 1);
|
||||
|
||||
// Use two different public senders to avoid nonce conflicts between the back-to-back txs.
|
||||
let senders = ctx.existing_public_accounts();
|
||||
|
||||
@ -15,7 +15,6 @@ use anyhow::{Context as _, Result};
|
||||
use bytesize::ByteSize;
|
||||
use common::transaction::LeeTransaction;
|
||||
use integration_tests::{TestContext, config::SequencerPartialConfig};
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use lee::{
|
||||
Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction,
|
||||
privacy_preserving_transaction::{self as pptx, circuit},
|
||||
@ -23,8 +22,7 @@ use lee::{
|
||||
public_transaction as putx,
|
||||
};
|
||||
use lee_core::{
|
||||
DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, MembershipProof,
|
||||
NullifierPublicKey,
|
||||
DUMMY_COMMITMENT_HASH, InputAccountIdentity, MembershipProof, NullifierPublicKey,
|
||||
account::{AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::ViewingPublicKey,
|
||||
};
|
||||
@ -267,7 +265,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
data: Data::default(),
|
||||
},
|
||||
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_vpk = ViewingPublicKey::from_seed(&[101_u8; 32], &[102_u8; 32]);
|
||||
@ -275,17 +273,9 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
let recipient_pre = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
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 proof: MembershipProof = (
|
||||
1,
|
||||
@ -302,18 +292,17 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
.unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: sender_epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&sender_npk, &sender_vpk),
|
||||
ssk: sender_ss,
|
||||
vpk: sender_vpk,
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_nsk,
|
||||
membership_proof: proof,
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: recipient_epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&recipient_npk, &recipient_vpk),
|
||||
vpk: recipient_vpk,
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_npk,
|
||||
ssk: recipient_ss,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
|
||||
@ -1012,7 +1012,11 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
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();
|
||||
(to, out_keys)
|
||||
};
|
||||
@ -1155,7 +1159,11 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
let (to, to_keys) = unsafe {
|
||||
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||
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();
|
||||
(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 mut out_keys = FfiPrivateAccountKeys::default();
|
||||
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();
|
||||
(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 mut out_keys = FfiPrivateAccountKeys::default();
|
||||
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();
|
||||
(to, out_keys)
|
||||
};
|
||||
|
||||
@ -351,10 +351,10 @@ mod tests {
|
||||
let program_id: ProgramId = [9; 8];
|
||||
|
||||
let holder = GroupKeyHolder::from_gms(gms);
|
||||
let npk = holder
|
||||
.derive_keys_for_pda(&TEST_PROGRAM_ID, &seed)
|
||||
.generate_nullifier_public_key();
|
||||
let account_id = AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX);
|
||||
let keys = holder.derive_keys_for_pda(&TEST_PROGRAM_ID, &seed);
|
||||
let npk = keys.generate_nullifier_public_key();
|
||||
let vpk = keys.generate_viewing_public_key();
|
||||
let account_id = AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, u128::MAX);
|
||||
|
||||
let expected_npk = NullifierPublicKey([
|
||||
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.
|
||||
// We verify npk is pinned, and AccountId is deterministically derived from it.
|
||||
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!(account_id, expected_account_id);
|
||||
@ -560,13 +560,16 @@ mod tests {
|
||||
let bob_holder =
|
||||
GroupKeyHolder::unseal(&sealed, &bob_vsk).expect("Bob should unseal the GMS");
|
||||
|
||||
let bob_npk = bob_holder
|
||||
.derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed)
|
||||
.generate_nullifier_public_key();
|
||||
let bob_group_keys = bob_holder.derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed);
|
||||
let bob_npk = bob_group_keys.generate_nullifier_public_key();
|
||||
assert_eq!(alice_npk, bob_npk);
|
||||
|
||||
let alice_account_id = AccountId::for_private_pda(&program_id, &pda_seed, &alice_npk, 0);
|
||||
let bob_account_id = AccountId::for_private_pda(&program_id, &pda_seed, &bob_npk, 0);
|
||||
let alice_vpk = alice_keys.generate_viewing_public_key();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -94,10 +94,11 @@ impl KeyTreeNode for ChildKeysPrivate {
|
||||
|
||||
fn account_ids(&self) -> impl Iterator<Item = lee::AccountId> {
|
||||
let npk = self.value.0.nullifier_public_key;
|
||||
let vpk = self.value.0.viewing_public_key.clone();
|
||||
self.value
|
||||
.1
|
||||
.keys()
|
||||
.map(move |kind| lee::AccountId::for_private_account(&npk, kind))
|
||||
.map(move |kind| lee::AccountId::for_private_account(&npk, &vpk, kind))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -268,6 +268,7 @@ impl KeyTree<ChildKeysPrivate> {
|
||||
let node = self.key_map.get(cci)?;
|
||||
let account_id = lee::AccountId::for_regular_private_account(
|
||||
&node.value.0.nullifier_public_key,
|
||||
&node.value.0.viewing_public_key,
|
||||
identifier,
|
||||
);
|
||||
if self.account_id_map.contains_key(&account_id) {
|
||||
|
||||
@ -6,6 +6,7 @@ use std::{
|
||||
use lee_core::{
|
||||
Identifier, InputAccountIdentity, NullifierPublicKey,
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
encryption::ViewingPublicKey,
|
||||
program::{
|
||||
AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID,
|
||||
MAX_NUMBER_CHAINED_CALLS, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow,
|
||||
@ -21,7 +22,7 @@ pub struct ExecutionState {
|
||||
block_validity_window: BlockValidityWindow,
|
||||
timestamp_validity_window: TimestampValidityWindow,
|
||||
/// 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.
|
||||
/// 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`
|
||||
@ -43,12 +44,13 @@ pub struct ExecutionState {
|
||||
/// `AccountId` entry or as an equality check against the existing one, making the rule: one
|
||||
/// `(program, seed)` → one account per tx.
|
||||
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
|
||||
/// `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
|
||||
/// `AccountId::for_private_pda(program_id, seed, npk, identifier) == pre_state.account_id`.
|
||||
private_pda_npk_by_position: HashMap<usize, (NullifierPublicKey, Identifier)>,
|
||||
/// `AccountId::for_private_pda(program_id, seed, npk, vpk, identifier) ==
|
||||
/// pre_state.account_id`.
|
||||
private_pda_by_position: HashMap<usize, (NullifierPublicKey, ViewingPublicKey, Identifier)>,
|
||||
authorized_accounts: HashSet<AccountId>,
|
||||
}
|
||||
|
||||
@ -59,9 +61,48 @@ impl ExecutionState {
|
||||
program_id: ProgramId,
|
||||
program_outputs: Vec<ProgramOutput>,
|
||||
) -> Self {
|
||||
let private_pda_npk_by_position = build_private_pda_npk_map(account_identities);
|
||||
let (block_validity_window, timestamp_validity_window) =
|
||||
intersect_validity_windows(&program_outputs);
|
||||
// Build position → (npk, identifier) map for private-PDA pre_states, indexed by position
|
||||
// in `account_identities`. The vec is documented as 1:1 with the program's pre_state
|
||||
// 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 {
|
||||
pre_states: Vec::new(),
|
||||
@ -70,7 +111,7 @@ impl ExecutionState {
|
||||
timestamp_validity_window,
|
||||
private_pda_bound_positions: HashMap::new(),
|
||||
pda_family_binding: HashMap::new(),
|
||||
private_pda_npk_by_position,
|
||||
private_pda_by_position,
|
||||
authorized_accounts: HashSet::new(),
|
||||
};
|
||||
|
||||
@ -99,7 +140,49 @@ impl ExecutionState {
|
||||
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() {
|
||||
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",
|
||||
);
|
||||
|
||||
execution_state.assert_all_pda_positions_bound(account_identities);
|
||||
execution_state.assert_modified_accounts_claimed();
|
||||
// Every private-PDA pre_state must have had its npk bound to its account_id, either via
|
||||
// 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
|
||||
}
|
||||
@ -142,185 +258,209 @@ impl ExecutionState {
|
||||
for (pre, mut post) in output_pre_states.into_iter().zip(output_post_states) {
|
||||
let pre_account_id = pre.account_id;
|
||||
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) {
|
||||
#[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;
|
||||
|
||||
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),
|
||||
// Ensure that new pre state is the same as known post state
|
||||
assert_eq!(
|
||||
occupied.get(),
|
||||
&pre_account,
|
||||
"Inconsistent pre state for account {pre_account_id}",
|
||||
);
|
||||
|
||||
let is_authorized = resolve_authorization_and_record_bindings(
|
||||
&mut self.pda_family_binding,
|
||||
&mut self.private_pda_bound_positions,
|
||||
&self.private_pda_npk_by_position,
|
||||
&mut self.authorized_accounts,
|
||||
pre_account_id,
|
||||
pre_state_position,
|
||||
caller_program_id,
|
||||
caller_pda_seeds,
|
||||
previous_is_authorized,
|
||||
);
|
||||
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)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pre_is_authorized, is_authorized,
|
||||
"Inconsistent authorization for account {pre_account_id}",
|
||||
);
|
||||
} else {
|
||||
let pre_state_position = self.pre_states.len();
|
||||
resolve_external_seed(
|
||||
account_identities,
|
||||
pre_state_position,
|
||||
pre_account_id,
|
||||
pre.is_authorized,
|
||||
&mut self.private_pda_bound_positions,
|
||||
&mut self.pda_family_binding,
|
||||
);
|
||||
self.pre_states.push(pre);
|
||||
let is_authorized = resolve_authorization_and_record_bindings(
|
||||
&mut self.pda_family_binding,
|
||||
&mut self.private_pda_bound_positions,
|
||||
&self.private_pda_by_position,
|
||||
&mut self.authorized_accounts,
|
||||
pre_account_id,
|
||||
pre_state_position,
|
||||
caller_program_id,
|
||||
caller_pda_seeds,
|
||||
previous_is_authorized,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pre_is_authorized, is_authorized,
|
||||
"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() {
|
||||
self.process_claim(
|
||||
account_identities,
|
||||
&mut post,
|
||||
pre_account_id,
|
||||
pre_is_authorized,
|
||||
program_id,
|
||||
claim,
|
||||
// The invoked program can only claim accounts with default program id.
|
||||
assert_eq!(
|
||||
post.account().program_owner,
|
||||
DEFAULT_PROGRAM_ID,
|
||||
"Cannot claim an initialized account {pre_account_id}"
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
account_identities: &[InputAccountIdentity],
|
||||
post: &mut AccountPostState,
|
||||
pre_account_id: AccountId,
|
||||
pre_is_authorized: bool,
|
||||
program_id: ProgramId,
|
||||
claim: Claim,
|
||||
) {
|
||||
assert_eq!(
|
||||
post.account().program_owner,
|
||||
DEFAULT_PROGRAM_ID,
|
||||
"Cannot claim an initialized account {pre_account_id}"
|
||||
);
|
||||
|
||||
let pre_state_position = self
|
||||
.pre_states
|
||||
.iter()
|
||||
.position(|acc| acc.account_id == pre_account_id)
|
||||
.expect("Pre state must exist at this point");
|
||||
|
||||
let account_identity = &account_identities[pre_state_position];
|
||||
if account_identity.is_public() {
|
||||
match claim {
|
||||
Claim::Authorized => {
|
||||
assert!(
|
||||
pre_is_authorized,
|
||||
"Cannot claim unauthorized account {pre_account_id}"
|
||||
);
|
||||
let account_identity = &account_identities[pre_state_position];
|
||||
if account_identity.is_public() {
|
||||
match claim {
|
||||
Claim::Authorized => {
|
||||
// Note: no need to check authorized pdas because we have already
|
||||
// checked consistency of authorization above.
|
||||
assert!(
|
||||
pre_is_authorized,
|
||||
"Cannot claim unauthorized account {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 {
|
||||
// Private accounts: don't enforce the claim semantics. Unauthorized private
|
||||
// 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;
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
post.account_mut().program_owner = program_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_modified_accounts_claimed(&self) {
|
||||
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"
|
||||
);
|
||||
post_states_entry.insert_entry(post.into_account());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
/// 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
|
||||
@ -565,7 +561,7 @@ fn bind_private_pda_position(
|
||||
fn resolve_authorization_and_record_bindings(
|
||||
pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>,
|
||||
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>,
|
||||
pre_account_id: AccountId,
|
||||
pre_state_position: usize,
|
||||
@ -579,9 +575,10 @@ fn resolve_authorization_and_record_bindings(
|
||||
if AccountId::for_public_pda(&caller, seed) == pre_account_id {
|
||||
return Some((*seed, false, caller));
|
||||
}
|
||||
if let Some((npk, identifier)) =
|
||||
private_pda_npk_by_position.get(&pre_state_position)
|
||||
&& AccountId::for_private_pda(&caller, seed, npk, *identifier) == pre_account_id
|
||||
if let Some((npk, vpk, identifier)) =
|
||||
private_pda_by_position.get(&pre_state_position)
|
||||
&& AccountId::for_private_pda(&caller, seed, npk, vpk, *identifier)
|
||||
== pre_account_id
|
||||
{
|
||||
return Some((*seed, true, caller));
|
||||
}
|
||||
|
||||
@ -1,244 +1,14 @@
|
||||
use lee_core::{
|
||||
Commitment, CommitmentSetDigest, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey,
|
||||
Commitment, CommitmentSetDigest, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey,
|
||||
InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey,
|
||||
PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
|
||||
account::{Account, AccountId, Nonce},
|
||||
compute_digest_for_path,
|
||||
encryption::{ViewTag, ViewingPublicKey},
|
||||
};
|
||||
|
||||
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(
|
||||
execution_state: ExecutionState,
|
||||
account_identities: &[InputAccountIdentity],
|
||||
@ -271,105 +41,285 @@ pub fn compute_circuit_output(
|
||||
output.public_post_states.push(post_state);
|
||||
}
|
||||
InputAccountIdentity::PrivateAuthorizedInit {
|
||||
epk,
|
||||
view_tag,
|
||||
ssk,
|
||||
vpk,
|
||||
random_seed,
|
||||
nsk,
|
||||
identifier,
|
||||
commitment_root,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
} => {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier);
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
assert!(
|
||||
pre_state.is_authorized,
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
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 {
|
||||
epk,
|
||||
vpk,
|
||||
random_seed,
|
||||
view_tag,
|
||||
ssk,
|
||||
nsk,
|
||||
membership_proof,
|
||||
identifier,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
} => {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier);
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
assert!(
|
||||
pre_state.is_authorized,
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
|
||||
let new_nullifier = compute_update_nullifier_and_set_digest(
|
||||
membership_proof,
|
||||
&pre_state.account,
|
||||
&account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
|
||||
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&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 {
|
||||
epk,
|
||||
view_tag,
|
||||
vpk,
|
||||
random_seed,
|
||||
npk,
|
||||
ssk,
|
||||
identifier,
|
||||
commitment_root,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
} => {
|
||||
let account_id = AccountId::for_regular_private_account(npk, vpk, *identifier);
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
assert_eq!(
|
||||
pre_state.account,
|
||||
Account::default(),
|
||||
"Found new private account with non default values",
|
||||
);
|
||||
assert!(
|
||||
!pre_state.is_authorized,
|
||||
"Found new private account marked as 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 {
|
||||
epk,
|
||||
view_tag,
|
||||
npk: _,
|
||||
ssk,
|
||||
vpk,
|
||||
random_seed,
|
||||
npk,
|
||||
identifier,
|
||||
commitment_root,
|
||||
seed: _,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
} => {
|
||||
// The npk-to-account_id binding is established upstream in
|
||||
// `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds`
|
||||
// match. Here we only enforce the init pre-conditions. The supplied npk on
|
||||
// the variant has been recorded into `private_pda_by_position` and used
|
||||
// for the binding check; we use `pre_state.account_id` directly for nullifier
|
||||
// and commitment derivation.
|
||||
assert!(
|
||||
!pre_state.is_authorized,
|
||||
"PrivatePdaInit requires unauthorized pre_state"
|
||||
);
|
||||
assert_eq!(
|
||||
pre_state.account,
|
||||
Account::default(),
|
||||
"New private PDA must be default"
|
||||
);
|
||||
|
||||
let new_nullifier = (
|
||||
Nullifier::for_account_initialization(&pre_state.account_id),
|
||||
*commitment_root,
|
||||
);
|
||||
let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id);
|
||||
|
||||
let account_id = pre_state.account_id;
|
||||
let (authority_program_id, seed) = pda_seed_by_position
|
||||
.get(&pos)
|
||||
.expect("PrivatePdaInit position must be in pda_seed_by_position");
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(npk, vpk);
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&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 {
|
||||
epk,
|
||||
vpk,
|
||||
random_seed,
|
||||
view_tag,
|
||||
ssk,
|
||||
nsk,
|
||||
membership_proof,
|
||||
identifier,
|
||||
seed: external_seed,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
} => {
|
||||
// With an external seed the binding comes from the circuit input and the
|
||||
// pre_state is intentionally unauthorized; without one the binding comes from
|
||||
// a Claim or caller pda_seeds, so the pre_state must already be authorized.
|
||||
// When `external_seed` is `Some`, execution_state already asserted
|
||||
// `!pre_state.is_authorized`.
|
||||
assert!(
|
||||
pre_state.is_authorized ^ external_seed.is_some(),
|
||||
"PrivatePdaUpdate requires authorized pre_state or external seed"
|
||||
);
|
||||
|
||||
let new_nullifier = compute_update_nullifier_and_set_digest(
|
||||
membership_proof,
|
||||
&pre_state.account,
|
||||
&pre_state.account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
|
||||
|
||||
let account_id = pre_state.account_id;
|
||||
let (authority_program_id, seed) = pda_seed_by_position
|
||||
.get(&pos)
|
||||
.expect("PrivatePdaUpdate position must be in pda_seed_by_position");
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&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
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
@ -28,10 +28,8 @@ build_utils.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
lee_core = { workspace = true, features = ["test_utils"] }
|
||||
token_core.workspace = true
|
||||
test_methods = { path = "test_methods" }
|
||||
|
||||
env_logger.workspace = true
|
||||
hex-literal = "1.0.0"
|
||||
test-case = "3.3.1"
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ thiserror.workspace = true
|
||||
bytemuck.workspace = true
|
||||
bytesize.workspace = true
|
||||
base58.workspace = true
|
||||
ml-kem = { workspace = true, optional = true, features = ["getrandom"] }
|
||||
ml-kem = { workspace = true }
|
||||
chacha20 = { version = "0.10" }
|
||||
|
||||
[dev-dependencies]
|
||||
@ -24,5 +24,5 @@ serde_json.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
host = ["dep:ml-kem"]
|
||||
host = ["ml-kem/getrandom"]
|
||||
test_utils = ["host"]
|
||||
|
||||
@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey,
|
||||
NullifierSecretKey, SharedSecretKey,
|
||||
NullifierSecretKey,
|
||||
account::{Account, AccountWithMetadata},
|
||||
encryption::{EncryptedAccountData, EphemeralPublicKey, ViewTag},
|
||||
encryption::{EncryptedAccountData, ViewTag, ViewingPublicKey},
|
||||
program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow},
|
||||
};
|
||||
|
||||
@ -14,15 +14,13 @@ pub struct PrivacyPreservingCircuitInput {
|
||||
pub program_outputs: Vec<ProgramOutput>,
|
||||
/// 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`.
|
||||
/// 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.
|
||||
pub account_identities: Vec<InputAccountIdentity>,
|
||||
/// Program ID.
|
||||
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)]
|
||||
pub enum InputAccountIdentity {
|
||||
/// Public account. The guest reads pre/post state from `program_outputs` and emits no
|
||||
@ -30,12 +28,11 @@ pub enum InputAccountIdentity {
|
||||
Public,
|
||||
/// Init of an authorized standalone private account: no membership proof. The `pre_state`
|
||||
/// must be `Account::default()`. The `account_id` is derived as
|
||||
/// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), identifier)` and
|
||||
/// matched against `pre_state.account_id`.
|
||||
/// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), vpk, identifier)`
|
||||
/// and matched against `pre_state.account_id`.
|
||||
PrivateAuthorizedInit {
|
||||
epk: EphemeralPublicKey,
|
||||
view_tag: ViewTag,
|
||||
ssk: SharedSecretKey,
|
||||
vpk: ViewingPublicKey,
|
||||
random_seed: [u8; 32],
|
||||
nsk: NullifierSecretKey,
|
||||
identifier: Identifier,
|
||||
commitment_root: CommitmentSetDigest,
|
||||
@ -43,9 +40,9 @@ pub enum InputAccountIdentity {
|
||||
/// Update of an authorized standalone private account: existing on-chain commitment, with
|
||||
/// membership proof.
|
||||
PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey,
|
||||
vpk: ViewingPublicKey,
|
||||
random_seed: [u8; 32],
|
||||
view_tag: ViewTag,
|
||||
ssk: SharedSecretKey,
|
||||
nsk: NullifierSecretKey,
|
||||
membership_proof: MembershipProof,
|
||||
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
|
||||
/// doesn't yet exist on chain). No `nsk`, no membership proof.
|
||||
PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey,
|
||||
view_tag: ViewTag,
|
||||
vpk: ViewingPublicKey,
|
||||
random_seed: [u8; 32],
|
||||
npk: NullifierPublicKey,
|
||||
ssk: SharedSecretKey,
|
||||
identifier: Identifier,
|
||||
commitment_root: CommitmentSetDigest,
|
||||
},
|
||||
@ -65,15 +61,14 @@ pub enum InputAccountIdentity {
|
||||
/// PDA within the `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it
|
||||
/// as the 4th input.
|
||||
PrivatePdaInit {
|
||||
epk: EphemeralPublicKey,
|
||||
view_tag: ViewTag,
|
||||
vpk: ViewingPublicKey,
|
||||
random_seed: [u8; 32],
|
||||
npk: NullifierPublicKey,
|
||||
ssk: SharedSecretKey,
|
||||
identifier: Identifier,
|
||||
commitment_root: CommitmentSetDigest,
|
||||
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the
|
||||
/// external derivation check
|
||||
/// `AccountId::for_private_pda(authority_program_id, seed, npk, identifier) ==
|
||||
/// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) ==
|
||||
/// pre_state.account_id` rather than requiring a `Claim::Pda` or caller
|
||||
/// `pda_seeds` to establish the binding. The `pre_state` must have `is_authorized
|
||||
/// == false`.
|
||||
@ -83,15 +78,15 @@ pub enum InputAccountIdentity {
|
||||
/// from `nsk`. Authorization may be established upstream by a caller `pda_seeds` match or a
|
||||
/// previously-seen authorization in a chained call.
|
||||
PrivatePdaUpdate {
|
||||
epk: EphemeralPublicKey,
|
||||
vpk: ViewingPublicKey,
|
||||
random_seed: [u8; 32],
|
||||
view_tag: ViewTag,
|
||||
ssk: SharedSecretKey,
|
||||
nsk: NullifierSecretKey,
|
||||
membership_proof: MembershipProof,
|
||||
identifier: Identifier,
|
||||
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the
|
||||
/// external derivation check
|
||||
/// `AccountId::for_private_pda(authority_program_id, seed, npk, 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
|
||||
/// the binding. The `pre_state` must have `is_authorized == false`.
|
||||
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]
|
||||
pub fn npk_if_private_pda(&self) -> Option<(NullifierPublicKey, Identifier)> {
|
||||
pub fn npk_vpk_if_private_pda(
|
||||
&self,
|
||||
) -> Option<(NullifierPublicKey, ViewingPublicKey, Identifier)> {
|
||||
match self {
|
||||
Self::PrivatePdaInit {
|
||||
npk, identifier, ..
|
||||
} => Some((*npk, *identifier)),
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
..
|
||||
} => Some((*npk, vpk.clone(), *identifier)),
|
||||
Self::PrivatePdaUpdate {
|
||||
nsk, identifier, ..
|
||||
} => Some((NullifierPublicKey::from(nsk), *identifier)),
|
||||
nsk,
|
||||
vpk,
|
||||
identifier,
|
||||
..
|
||||
} => Some((NullifierPublicKey::from(nsk), vpk.clone(), *identifier)),
|
||||
Self::Public
|
||||
| Self::PrivateAuthorizedInit { .. }
|
||||
| Self::PrivateAuthorizedUpdate { .. }
|
||||
@ -161,7 +162,7 @@ mod tests {
|
||||
use crate::{
|
||||
Commitment, Nullifier,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||
encryption::Ciphertext,
|
||||
encryption::{Ciphertext, EphemeralPublicKey},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@ -5,11 +5,9 @@ use chacha20::{
|
||||
};
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "host")]
|
||||
pub use shared_key_derivation::{MlKem768EncapsulationKey, ViewingPublicKey};
|
||||
|
||||
use crate::{Commitment, account::Account, program::PrivateAccountKind};
|
||||
#[cfg(feature = "host")]
|
||||
pub mod shared_key_derivation;
|
||||
|
||||
/// 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];
|
||||
|
||||
#[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)]
|
||||
pub struct SharedSecretKey(pub [u8; 32]);
|
||||
|
||||
@ -55,6 +78,18 @@ pub struct EncryptedAccountData {
|
||||
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")]
|
||||
impl EncryptedAccountData {
|
||||
#[must_use]
|
||||
@ -71,16 +106,6 @@ impl EncryptedAccountData {
|
||||
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 {
|
||||
@ -116,12 +141,12 @@ impl EncryptionScheme {
|
||||
commitment: &Commitment,
|
||||
output_index: u32,
|
||||
) -> [u8; 32] {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
bytes.extend_from_slice(b"LEE/v0.2/KDF-SHA256/");
|
||||
bytes.extend_from_slice(&shared_secret.0);
|
||||
bytes.extend_from_slice(&commitment.to_byte_array());
|
||||
bytes.extend_from_slice(&output_index.to_le_bytes());
|
||||
const PREFIX: &[u8; 20] = b"LEE/v0.2/KDF-SHA256/";
|
||||
let mut bytes = [0_u8; 20 + 32 + 32 + 4];
|
||||
bytes[0..20].copy_from_slice(PREFIX);
|
||||
bytes[20..52].copy_from_slice(&shared_secret.0);
|
||||
bytes[52..84].copy_from_slice(&commitment.to_byte_array());
|
||||
bytes[84..88].copy_from_slice(&output_index.to_le_bytes());
|
||||
|
||||
Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()
|
||||
}
|
||||
@ -238,4 +263,41 @@ mod tests {
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
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 crate::{EphemeralPublicKey, SharedSecretKey};
|
||||
@ -26,6 +28,7 @@ impl MlKem768EncapsulationKey {
|
||||
pub const LEN: usize = 1184;
|
||||
|
||||
/// 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> {
|
||||
if bytes.len() != Self::LEN {
|
||||
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
|
||||
/// as the `EphemeralPublicKey`; the receiver recovers the same shared secret via
|
||||
/// [`Self::decapsulate`].
|
||||
#[cfg(feature = "host")]
|
||||
#[must_use]
|
||||
pub fn encapsulate(ek: &MlKem768EncapsulationKey) -> (Self, EphemeralPublicKey) {
|
||||
let ek_bytes: ml_kem::kem::Key<ml_kem::EncapsulationKey768> =
|
||||
@ -76,30 +80,18 @@ impl SharedSecretKey {
|
||||
(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`,
|
||||
/// `message_hash`, and `output_index`, all of which are public. This makes it
|
||||
/// 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"))]
|
||||
/// The `esk` must be derived via `derive_esk(account_id, random_seed, nonce)`
|
||||
/// which binds it to the account and incorporates OS entropy.
|
||||
#[must_use]
|
||||
pub fn encapsulate_deterministic(
|
||||
ek: &MlKem768EncapsulationKey,
|
||||
message_hash: &[u8; 32],
|
||||
output_index: u32,
|
||||
esk: &crate::encryption::EphemeralSecretKey,
|
||||
) -> (Self, EphemeralPublicKey) {
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
|
||||
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 m: ml_kem::B32 = ml_kem::array::Array::try_from(esk.0.as_slice())
|
||||
.expect("EphemeralSecretKey is 32 bytes");
|
||||
|
||||
let ek_bytes: ml_kem::kem::Key<ml_kem::EncapsulationKey768> =
|
||||
ek.0.as_slice()
|
||||
|
||||
@ -11,8 +11,8 @@ pub use commitment::{
|
||||
compute_digest_for_path,
|
||||
};
|
||||
pub use encryption::{
|
||||
EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN,
|
||||
SharedSecretKey, ViewTag,
|
||||
EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, EphemeralSecretKey,
|
||||
ML_KEM_768_CIPHERTEXT_LEN, SharedSecretKey, ViewTag,
|
||||
};
|
||||
pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey};
|
||||
pub use program::PrivateAccountKind;
|
||||
|
||||
@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
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";
|
||||
|
||||
@ -16,12 +16,16 @@ impl AccountId {
|
||||
/// Derives an [`AccountId`] for a regular (non-PDA) private account from the nullifier public
|
||||
/// key and identifier.
|
||||
#[must_use]
|
||||
pub fn for_regular_private_account(npk: &NullifierPublicKey, identifier: Identifier) -> Self {
|
||||
// 32 bytes prefix || 32 bytes npk || 16 bytes identifier
|
||||
let mut bytes = [0; 80];
|
||||
pub fn for_regular_private_account(
|
||||
npk: &NullifierPublicKey,
|
||||
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[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(
|
||||
Impl::hash_bytes(&bytes)
|
||||
@ -32,9 +36,9 @@ impl AccountId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&NullifierPublicKey, Identifier)> for AccountId {
|
||||
fn from((npk, identifier): (&NullifierPublicKey, Identifier)) -> Self {
|
||||
Self::for_regular_private_account(npk, identifier)
|
||||
impl From<(&NullifierPublicKey, &ViewingPublicKey, Identifier)> for AccountId {
|
||||
fn from((npk, vpk, identifier): (&NullifierPublicKey, &ViewingPublicKey, Identifier)) -> Self {
|
||||
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,
|
||||
];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
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,
|
||||
253, 105, 164, 89, 84, 40, 191, 182, 119, 64, 255, 67, 142,
|
||||
242, 239, 57, 244, 89, 109, 65, 201, 223, 100, 43, 87, 205, 83, 148, 161, 176, 22, 208,
|
||||
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);
|
||||
}
|
||||
@ -175,12 +180,13 @@ mod tests {
|
||||
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
|
||||
];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
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,
|
||||
56, 247, 99, 121, 165, 182, 234, 255, 19, 127, 191, 72,
|
||||
149, 125, 157, 109, 119, 81, 9, 163, 231, 181, 214, 43, 57, 113, 221, 72, 180, 149,
|
||||
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);
|
||||
}
|
||||
@ -193,12 +199,13 @@ mod tests {
|
||||
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
|
||||
];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
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,
|
||||
19, 245, 25, 214, 162, 209, 135, 252, 82, 27, 2, 174, 196,
|
||||
30, 232, 222, 201, 233, 125, 124, 194, 58, 39, 121, 96, 185, 84, 168, 109, 80, 111,
|
||||
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);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
BlockId, Identifier, NullifierPublicKey, Timestamp,
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
encryption::ViewingPublicKey,
|
||||
};
|
||||
|
||||
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
|
||||
@ -154,19 +155,21 @@ impl AccountId {
|
||||
program_id: &ProgramId,
|
||||
seed: &PdaSeed,
|
||||
npk: &NullifierPublicKey,
|
||||
vpk: &ViewingPublicKey,
|
||||
identifier: Identifier,
|
||||
) -> Self {
|
||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||
const PRIVATE_PDA_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/PrivatePDA/\x00";
|
||||
|
||||
let mut bytes = [0_u8; 144];
|
||||
let mut bytes = [0_u8; 32 + 32 + 32 + 32 + ViewingPublicKey::LEN + 16];
|
||||
bytes[0..32].copy_from_slice(PRIVATE_PDA_PREFIX);
|
||||
let program_id_bytes: &[u8] =
|
||||
bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]");
|
||||
bytes[32..64].copy_from_slice(program_id_bytes);
|
||||
bytes[64..96].copy_from_slice(&seed.0);
|
||||
bytes[96..128].copy_from_slice(&npk.to_byte_array());
|
||||
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(
|
||||
Impl::hash_bytes(&bytes)
|
||||
.as_bytes()
|
||||
@ -177,16 +180,20 @@ impl AccountId {
|
||||
|
||||
/// Derives the [`AccountId`] for a private account from the nullifier public key and kind.
|
||||
#[must_use]
|
||||
pub fn for_private_account(npk: &NullifierPublicKey, kind: &PrivateAccountKind) -> Self {
|
||||
pub fn for_private_account(
|
||||
npk: &NullifierPublicKey,
|
||||
vpk: &ViewingPublicKey,
|
||||
kind: &PrivateAccountKind,
|
||||
) -> Self {
|
||||
match kind {
|
||||
PrivateAccountKind::Regular(identifier) => {
|
||||
Self::for_regular_private_account(npk, *identifier)
|
||||
Self::for_regular_private_account(npk, vpk, *identifier)
|
||||
}
|
||||
PrivateAccountKind::Pda {
|
||||
program_id,
|
||||
seed,
|
||||
identifier,
|
||||
} => Self::for_private_pda(program_id, seed, npk, *identifier),
|
||||
} => Self::for_private_pda(program_id, seed, npk, vpk, *identifier),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,13 +184,14 @@ fn for_private_pda_matches_pinned_value() {
|
||||
let program_id: ProgramId = [1; 8];
|
||||
let seed = PdaSeed::new([2; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
let identifier: Identifier = u128::MAX;
|
||||
let expected = AccountId::new([
|
||||
59, 239, 182, 97, 14, 220, 96, 115, 238, 133, 143, 33, 234, 82, 237, 255, 148, 110, 54,
|
||||
124, 98, 159, 245, 101, 146, 182, 150, 54, 37, 62, 25, 17,
|
||||
5, 87, 128, 244, 206, 244, 65, 130, 178, 88, 225, 183, 0, 159, 201, 201, 212, 206, 6, 156,
|
||||
13, 55, 32, 139, 91, 222, 209, 83, 172, 148, 123, 179,
|
||||
]);
|
||||
assert_eq!(
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, identifier),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, identifier),
|
||||
expected
|
||||
);
|
||||
}
|
||||
@ -202,9 +203,10 @@ fn for_private_pda_differs_for_different_npk() {
|
||||
let seed = PdaSeed::new([2; 32]);
|
||||
let npk_a = NullifierPublicKey([3; 32]);
|
||||
let npk_b = NullifierPublicKey([4; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk_a, u128::MAX),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk_b, u128::MAX),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk_a, &vpk, 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_b = PdaSeed::new([5; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id, &seed_a, &npk, u128::MAX),
|
||||
AccountId::for_private_pda(&program_id, &seed_b, &npk, u128::MAX),
|
||||
AccountId::for_private_pda(&program_id, &seed_a, &npk, &vpk, 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 seed = PdaSeed::new([2; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id_a, &seed, &npk, u128::MAX),
|
||||
AccountId::for_private_pda(&program_id_b, &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, &vpk, u128::MAX),
|
||||
);
|
||||
}
|
||||
|
||||
@ -241,13 +245,14 @@ fn for_private_pda_differs_for_different_identifier() {
|
||||
let program_id: ProgramId = [1; 8];
|
||||
let seed = PdaSeed::new([2; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, 0),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, 1),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 0),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 1),
|
||||
);
|
||||
assert_ne!(
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, 0),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 0),
|
||||
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 seed = PdaSeed::new([2; 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);
|
||||
assert_ne!(private_id, public_id);
|
||||
}
|
||||
@ -295,22 +301,24 @@ fn for_private_account_dispatches_correctly() {
|
||||
let program_id: ProgramId = [1; 8];
|
||||
let seed = PdaSeed::new([2; 32]);
|
||||
let npk = NullifierPublicKey([3; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[1_u8; 32], &[2_u8; 32]);
|
||||
let identifier: Identifier = 77;
|
||||
|
||||
assert_eq!(
|
||||
AccountId::for_private_account(&npk, &PrivateAccountKind::Regular(identifier)),
|
||||
AccountId::for_regular_private_account(&npk, identifier),
|
||||
AccountId::for_private_account(&npk, &vpk, &PrivateAccountKind::Regular(identifier)),
|
||||
AccountId::for_regular_private_account(&npk, &vpk, identifier),
|
||||
);
|
||||
assert_eq!(
|
||||
AccountId::for_private_account(
|
||||
&npk,
|
||||
&vpk,
|
||||
&PrivateAccountKind::Pda {
|
||||
program_id,
|
||||
seed,
|
||||
identifier
|
||||
}
|
||||
),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, identifier),
|
||||
AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, identifier),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
|
||||
|
||||
use lee_core::{
|
||||
Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey,
|
||||
Nullifier, PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, EphemeralSecretKey, Nullifier,
|
||||
PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||
program::{PdaSeed, PrivateAccountKind},
|
||||
};
|
||||
@ -58,7 +58,8 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts()
|
||||
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 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 shared_secret =
|
||||
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0).0;
|
||||
let init_nonce = Nonce::private_account_nonce_init(&recipient_account_id);
|
||||
let esk = EphemeralSecretKey::new(&recipient_account_id, &[0; 32], &init_nonce);
|
||||
let shared_secret = SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &esk).0;
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender, recipient],
|
||||
@ -88,13 +90,9 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts()
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
@ -138,12 +136,14 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
|
||||
data: Data::default(),
|
||||
},
|
||||
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 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 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),
|
||||
];
|
||||
|
||||
let shared_secret_1 =
|
||||
SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0;
|
||||
let esk_1 = EphemeralSecretKey::new(
|
||||
&sender_account_id,
|
||||
&[0; 32],
|
||||
&sender_nonce.private_account_nonce_increment(&sender_keys.nsk),
|
||||
);
|
||||
let shared_secret_1 = SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &esk_1).0;
|
||||
|
||||
let init_nonce_2 = Nonce::private_account_nonce_init(&recipient_account_id);
|
||||
let esk_2 = EphemeralSecretKey::new(&recipient_account_id, &[0; 32], &init_nonce_2);
|
||||
let shared_secret_2 =
|
||||
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 1).0;
|
||||
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &esk_2).0;
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender_pre, recipient],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret_1,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: commitment_set
|
||||
.get_proof_for(&commitment_sender)
|
||||
@ -203,13 +206,9 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret_2,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
@ -250,7 +249,7 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
|
||||
let pre = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
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();
|
||||
@ -265,9 +264,6 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let shared_secret =
|
||||
SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0).0;
|
||||
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
validity_window_chain_caller,
|
||||
[(validity_window.id(), validity_window)].into(),
|
||||
@ -277,13 +273,9 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
|
||||
vec![pre],
|
||||
instruction,
|
||||
vec![InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&account_keys.npk(),
|
||||
&account_keys.vpk(),
|
||||
),
|
||||
vpk: account_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: account_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
@ -302,19 +294,21 @@ fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() {
|
||||
let npk = keys.npk();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
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 (output, _proof) = execute_and_prove(
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -343,11 +337,8 @@ fn private_pda_init() {
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
let shared_secret_pda =
|
||||
SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
|
||||
|
||||
// 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 auth_id = simple_transfer.id();
|
||||
@ -361,10 +352,9 @@ fn private_pda_init() {
|
||||
vec![pda_pre],
|
||||
instruction,
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret_pda,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -386,11 +376,8 @@ fn private_pda_withdraw() {
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
let shared_secret_pda =
|
||||
SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
|
||||
|
||||
// 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);
|
||||
|
||||
// Recipient (public)
|
||||
@ -417,10 +404,9 @@ fn private_pda_withdraw() {
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret_pda,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -445,8 +431,6 @@ fn shared_account_receives_via_simple_transfer() {
|
||||
let shared_keys = test_private_account_keys_1();
|
||||
let shared_npk = shared_keys.npk();
|
||||
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
|
||||
let sender_id = AccountId::new([99; 32]);
|
||||
@ -461,7 +445,7 @@ fn shared_account_receives_via_simple_transfer() {
|
||||
);
|
||||
|
||||
// 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 balance_to_move: u128 = 100;
|
||||
@ -473,10 +457,9 @@ fn shared_account_receives_via_simple_transfer() {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&shared_npk, &shared_keys.vpk()),
|
||||
vpk: shared_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: shared_npk,
|
||||
ssk: shared_secret,
|
||||
identifier: shared_identifier,
|
||||
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 keys = test_private_account_keys_1();
|
||||
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(), identifier);
|
||||
let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
|
||||
let esk = EphemeralSecretKey::new(
|
||||
&account_id,
|
||||
&[0; 32],
|
||||
&Nonce::private_account_nonce_init(&account_id),
|
||||
);
|
||||
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
|
||||
let pre = AccountWithMetadata::new(Account::default(), true, account_id);
|
||||
|
||||
let (output, _) = execute_and_prove(
|
||||
vec![pre],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()),
|
||||
ssk,
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
nsk: keys.nsk,
|
||||
identifier,
|
||||
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 keys = test_private_account_keys_1();
|
||||
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(), identifier);
|
||||
let recipient_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
|
||||
let esk = EphemeralSecretKey::new(
|
||||
&recipient_id,
|
||||
&[0; 32],
|
||||
&Nonce::private_account_nonce_init(&recipient_id),
|
||||
);
|
||||
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_id);
|
||||
|
||||
let (output, _) = execute_and_prove(
|
||||
vec![recipient],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: keys.npk(),
|
||||
ssk,
|
||||
identifier,
|
||||
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 keys = test_private_account_keys_1();
|
||||
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(), identifier);
|
||||
let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier);
|
||||
let esk = EphemeralSecretKey::new(
|
||||
&account_id,
|
||||
&[0; 32],
|
||||
&Nonce::default().private_account_nonce_increment(&keys.nsk),
|
||||
);
|
||||
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0;
|
||||
let account = Account {
|
||||
program_owner: program.id(),
|
||||
balance: 1,
|
||||
@ -577,9 +573,9 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() {
|
||||
vec![sender],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()),
|
||||
ssk,
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: keys.nsk,
|
||||
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
|
||||
identifier,
|
||||
@ -604,10 +600,14 @@ fn private_pda_update_encrypts_pda_kind_with_identifier() {
|
||||
let npk = keys.npk();
|
||||
let seed = PdaSeed::new([42; 32]);
|
||||
let identifier: u128 = 99;
|
||||
let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
|
||||
|
||||
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 {
|
||||
program_owner: simple_transfer_id,
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
ssk,
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: keys.nsk,
|
||||
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
|
||||
identifier,
|
||||
@ -660,19 +660,16 @@ fn private_pda_init_identifier_mismatch_fails() {
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
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, 5);
|
||||
let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5);
|
||||
let pre_state = AccountWithMetadata::new(Account::default(), false, account_id);
|
||||
|
||||
let result = execute_and_prove(
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: 99,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -690,10 +687,8 @@ fn private_pda_update_identifier_mismatch_fails() {
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
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 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 {
|
||||
program_owner: simple_transfer_id,
|
||||
balance: 1,
|
||||
@ -714,9 +709,9 @@ fn private_pda_update_identifier_mismatch_fails() {
|
||||
Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
ssk,
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: keys.nsk,
|
||||
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
|
||||
identifier: 99,
|
||||
|
||||
@ -103,8 +103,8 @@ impl Message {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use lee_core::{
|
||||
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, PrivateAccountKind,
|
||||
SharedSecretKey,
|
||||
Commitment, EncryptionScheme, EphemeralSecretKey, Nullifier, NullifierPublicKey,
|
||||
PrivateAccountKind, SharedSecretKey,
|
||||
account::{Account, AccountId, Nonce},
|
||||
encryption::ViewingPublicKey,
|
||||
program::{BlockValidityWindow, TimestampValidityWindow},
|
||||
@ -123,6 +123,7 @@ pub mod tests {
|
||||
|
||||
let npk1 = NullifierPublicKey::from(&nsk1);
|
||||
let npk2 = NullifierPublicKey::from(&nsk2);
|
||||
let vpk = ViewingPublicKey::from_seed(&[7; 32], &[8; 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 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 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 new_nullifiers = vec![(
|
||||
Nullifier::for_account_update(&old_commitment, &nsk1),
|
||||
@ -225,9 +226,10 @@ pub mod tests {
|
||||
let npk = NullifierPublicKey::from(&[1; 32]);
|
||||
let vpk = ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]);
|
||||
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 (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(
|
||||
&account,
|
||||
&PrivateAccountKind::Regular(0),
|
||||
|
||||
@ -60,8 +60,11 @@ fn public_changer_claimer_data_change_no_claim_fails() {
|
||||
fn private_changer_claimer_no_data_change_no_claim_succeeds() {
|
||||
let program = crate::test_methods::changer_claimer();
|
||||
let sender_keys = test_private_account_keys_1();
|
||||
let private_account =
|
||||
AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0));
|
||||
let private_account = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
true,
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
// Don't change data (None) and don't claim (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],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
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() {
|
||||
let program = crate::test_methods::changer_claimer();
|
||||
let sender_keys = test_private_account_keys_1();
|
||||
let private_account =
|
||||
AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0));
|
||||
let private_account = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
true,
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
// Change data but don't claim (false) - should fail
|
||||
let new_data = vec![1, 2, 3, 4, 5];
|
||||
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],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
|
||||
@ -45,10 +45,13 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() {
|
||||
..Account::default()
|
||||
},
|
||||
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.
|
||||
// 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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
|
||||
.0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: recipient_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(
|
||||
&recipient_keys.vpk(),
|
||||
&[0_u8; 32],
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -105,7 +95,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -114,7 +104,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
(&recipient_keys.npk(), 0),
|
||||
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
|
||||
.0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(
|
||||
&recipient_keys.vpk(),
|
||||
&[0_u8; 32],
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -168,7 +145,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -177,7 +154,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
(&recipient_keys.npk(), 0),
|
||||
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
|
||||
.0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(
|
||||
&recipient_keys.vpk(),
|
||||
&[0_u8; 32],
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -231,7 +195,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -240,7 +204,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
(&recipient_keys.npk(), 0),
|
||||
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
|
||||
.0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(
|
||||
&recipient_keys.vpk(),
|
||||
&[0_u8; 32],
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -294,7 +245,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -303,7 +254,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
|
||||
..Account::default()
|
||||
},
|
||||
false,
|
||||
(&recipient_keys.npk(), 0),
|
||||
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
|
||||
.0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(
|
||||
&recipient_keys.vpk(),
|
||||
&[0_u8; 32],
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -358,13 +296,13 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let private_account_2 = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
// This should be set to false in normal circumstances
|
||||
true,
|
||||
(&recipient_keys.npk(), 0),
|
||||
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0)
|
||||
.0,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (0, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: SharedSecretKey::encapsulate_deterministic(
|
||||
&recipient_keys.vpk(),
|
||||
&[0_u8; 32],
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -415,7 +340,6 @@ fn private_pda_without_binding_fails() {
|
||||
let program = crate::test_methods::simple_balance_transfer();
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
|
||||
let public_account_1 = AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: program.id(),
|
||||
@ -434,10 +358,9 @@ fn private_pda_without_binding_fails() {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -460,19 +383,17 @@ fn private_pda_claim_succeeds() {
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
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 result = execute_and_prove(
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -500,22 +421,21 @@ fn private_pda_npk_mismatch_fails() {
|
||||
let npk_a = keys_a.npk();
|
||||
let npk_b = keys_b.npk();
|
||||
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.
|
||||
// `AccountId::for_private_pda(program, seed, npk_b) != account_id`, so the claim check in
|
||||
// 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 result = execute_and_prove(
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction(seed).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk_b, &keys_b.vpk()),
|
||||
vpk: keys_b.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: npk_b,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -538,9 +458,9 @@ fn caller_pda_seeds_authorize_private_pda_for_callee() {
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
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 callee_id = callee.id();
|
||||
@ -550,10 +470,9 @@ fn caller_pda_seeds_authorize_private_pda_for_callee() {
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction((seed, seed, callee_id)).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -579,9 +498,9 @@ fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() {
|
||||
let npk = keys.npk();
|
||||
let claim_seed = PdaSeed::new([77; 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 callee_id = callee.id();
|
||||
@ -591,10 +510,9 @@ fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() {
|
||||
vec![pre_state],
|
||||
Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
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_b = test_private_account_keys_2();
|
||||
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_b = AccountId::for_private_pda(&program.id(), &seed, &keys_b.npk(), u128::MAX);
|
||||
let account_a = AccountId::for_private_pda(
|
||||
&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_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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&keys_a.npk(), &keys_a.vpk()),
|
||||
vpk: keys_a.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: keys_a.npk(),
|
||||
ssk: shared_a,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
},
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&keys_b.npk(), &keys_b.vpk()),
|
||||
vpk: keys_b.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: keys_b.npk(),
|
||||
ssk: shared_b,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -667,12 +593,11 @@ fn private_pda_top_level_reuse_rejected_by_binding_check() {
|
||||
let program = crate::test_methods::noop();
|
||||
let keys = test_private_account_keys_1();
|
||||
let npk = keys.npk();
|
||||
let shared_secret = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0;
|
||||
let seed = PdaSeed::new([99; 32]);
|
||||
|
||||
// Simulate a previously-claimed private PDA: program_owner != DEFAULT, is_authorized =
|
||||
// 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(
|
||||
Account {
|
||||
program_owner: program.id(),
|
||||
@ -686,10 +611,9 @@ fn private_pda_top_level_reuse_rejected_by_binding_check() {
|
||||
vec![owned_pre_state],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![InputAccountIdentity::PrivatePdaInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()),
|
||||
vpk: keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
@ -766,33 +690,25 @@ fn circuit_should_fail_if_there_are_repeated_ids() {
|
||||
..Account::default()
|
||||
},
|
||||
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(
|
||||
vec![private_account_1.clone(), private_account_1],
|
||||
Program::serialize_instruction(100_u128).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (1, vec![]),
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: (1, vec![]),
|
||||
identifier: 0,
|
||||
@ -812,14 +728,15 @@ fn private_authorized_uninitialized_account() {
|
||||
let private_keys = test_private_account_keys_1();
|
||||
|
||||
// Create an authorized private account with default values (new account being initialized)
|
||||
let authorized_account =
|
||||
AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0));
|
||||
let authorized_account = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
true,
|
||||
(&private_keys.npk(), &private_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
let program = crate::test_methods::simple_balance_transfer();
|
||||
|
||||
// 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;
|
||||
|
||||
@ -828,12 +745,8 @@ fn private_authorized_uninitialized_account() {
|
||||
vec![authorized_account],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedInit {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&private_keys.npk(),
|
||||
&private_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: private_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
nsk: private_keys.nsk,
|
||||
identifier: 0,
|
||||
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);
|
||||
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);
|
||||
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
|
||||
// operate them without the corresponding private keys, so unauthorized private claiming
|
||||
// remains allowed.
|
||||
let unauthorized_account =
|
||||
AccountWithMetadata::new(Account::default(), false, (&private_keys.npk(), 0));
|
||||
let unauthorized_account = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
(&private_keys.npk(), &private_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
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(
|
||||
vec![unauthorized_account],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateUnauthorized {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&private_keys.npk(),
|
||||
&private_keys.vpk(),
|
||||
),
|
||||
vpk: private_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: private_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
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)
|
||||
.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);
|
||||
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();
|
||||
|
||||
// Step 1: Create a new private account with authorization
|
||||
let authorized_account =
|
||||
AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0));
|
||||
let authorized_account = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
true,
|
||||
(&private_keys.npk(), &private_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
let claimer_program = crate::test_methods::claimer();
|
||||
|
||||
// Set up parameters for claiming the new account
|
||||
let (shared_secret, epk) =
|
||||
SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0);
|
||||
|
||||
let instruction = ();
|
||||
|
||||
@ -928,12 +841,8 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() {
|
||||
vec![authorized_account.clone()],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedInit {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&private_keys.npk(),
|
||||
&private_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: private_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
nsk: private_keys.nsk,
|
||||
identifier: 0,
|
||||
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)
|
||||
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);
|
||||
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 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
|
||||
let res = execute_and_prove(
|
||||
vec![account_metadata],
|
||||
Program::serialize_instruction(()).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateAuthorizedInit {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&private_keys.npk(),
|
||||
&private_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret2,
|
||||
vpk: private_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
nsk: private_keys.nsk,
|
||||
identifier: 0,
|
||||
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 alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0);
|
||||
let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1);
|
||||
let alice_pda_0_id =
|
||||
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_signing_key = test_public_account_keys_2().signing_key;
|
||||
|
||||
@ -1031,11 +937,6 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
..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.
|
||||
{
|
||||
let funder_account = state.get_account_by_id(funder_id);
|
||||
@ -1049,10 +950,9 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk: alice_epk_0.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()),
|
||||
vpk: alice_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: alice_npk,
|
||||
ssk: alice_shared_0,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: Some((seed, proxy_id)),
|
||||
@ -1086,10 +986,9 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk: alice_epk_1.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()),
|
||||
vpk: alice_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: alice_npk,
|
||||
ssk: alice_shared_1,
|
||||
identifier: 1,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
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(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk: alice_epk_0,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()),
|
||||
ssk: alice_shared_0,
|
||||
vpk: alice_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: alice_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&commitment_pda_0)
|
||||
@ -1165,9 +1064,9 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk: alice_epk_1,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()),
|
||||
ssk: alice_shared_1,
|
||||
vpk: alice_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: alice_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&commitment_pda_1)
|
||||
@ -1205,7 +1104,6 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
};
|
||||
let commitment_pda_1_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_nonce = recipient_account.nonce;
|
||||
@ -1218,10 +1116,10 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk: EphemeralPublicKey(vec![12_u8; ML_KEM_768_CIPHERTEXT_LEN]),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&alice_npk, &alice_keys.vpk()),
|
||||
vpk: alice_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: alice_keys.nsk,
|
||||
ssk: alice_shared_1_refund,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&commitment_pda_1_after_spend)
|
||||
.expect("pda_1 after spend must be in state"),
|
||||
|
||||
@ -303,19 +303,21 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() {
|
||||
balance: 100,
|
||||
..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_init_nullifier = Nullifier::for_account_initialization(&sender_account_id);
|
||||
let mut state =
|
||||
V03State::new().with_private_accounts([(sender_commitment.clone(), sender_init_nullifier)]);
|
||||
let sender_pre =
|
||||
AccountWithMetadata::new(sender_private_account, true, (&sender_keys.npk(), 0));
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
sender_private_account,
|
||||
true,
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||
let recipient_account_id =
|
||||
AccountId::from(&PublicKey::new_from_private_key(&recipient_private_key));
|
||||
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;
|
||||
|
||||
@ -324,12 +326,9 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() {
|
||||
Program::serialize_instruction(balance).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&sender_commitment)
|
||||
@ -383,7 +382,7 @@ fn private_chained_call(number_of_calls: u32) {
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
(&from_keys.npk(), 0),
|
||||
(&from_keys.npk(), &from_keys.vpk(), 0),
|
||||
);
|
||||
let to_account = AccountWithMetadata::new(
|
||||
Account {
|
||||
@ -391,11 +390,12 @@ fn private_chained_call(number_of_calls: u32) {
|
||||
..Account::default()
|
||||
},
|
||||
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 to_account_id = AccountId::for_regular_private_account(&to_keys.npk(), 0);
|
||||
let from_account_id =
|
||||
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 to_commitment = Commitment::new(&to_account_id, &to_account.account);
|
||||
let from_init_nullifier = Nullifier::for_account_initialization(&from_account_id);
|
||||
@ -414,12 +414,6 @@ fn private_chained_call(number_of_calls: u32) {
|
||||
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();
|
||||
|
||||
dependencies.insert(simple_transfers.id(), simple_transfers);
|
||||
@ -448,9 +442,9 @@ fn private_chained_call(number_of_calls: u32) {
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: to_epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&to_keys.npk(), &to_keys.vpk()),
|
||||
ssk: to_ss,
|
||||
vpk: from_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: from_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&from_commitment)
|
||||
@ -458,12 +452,9 @@ fn private_chained_call(number_of_calls: u32) {
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: from_epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&from_keys.npk(),
|
||||
&from_keys.vpk(),
|
||||
),
|
||||
ssk: from_ss,
|
||||
vpk: to_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: to_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&to_commitment)
|
||||
|
||||
@ -43,8 +43,8 @@ fn new_includes_nullifiers_for_private_accounts() {
|
||||
..Account::default()
|
||||
};
|
||||
|
||||
let account_id1 = AccountId::for_regular_private_account(&keys1.npk(), 0);
|
||||
let account_id2 = AccountId::for_regular_private_account(&keys2.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(), &keys2.vpk(), 0);
|
||||
|
||||
let init_commitment1 = Commitment::new(&account_id1, &account);
|
||||
let init_commitment2 = Commitment::new(&account_id2, &account);
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use lee_core::{
|
||||
BlockId, Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity,
|
||||
Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Timestamp,
|
||||
BlockId, Commitment, DUMMY_COMMITMENT_HASH, InputAccountIdentity, Nullifier,
|
||||
NullifierPublicKey, NullifierSecretKey, Timestamp,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::{EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN, ViewingPublicKey},
|
||||
encryption::ViewingPublicKey,
|
||||
program::{
|
||||
BlockValidityWindow, ExecutionValidationError, MAX_NUMBER_CHAINED_CALLS, PdaSeed,
|
||||
ProgramId, TimestampValidityWindow, WrappedBalanceSum,
|
||||
@ -118,7 +118,7 @@ impl V03State {
|
||||
|
||||
#[must_use]
|
||||
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);
|
||||
self.private_state.0.extend(&[commitment]);
|
||||
self
|
||||
@ -267,10 +267,11 @@ fn shielded_balance_transfer_for_tests(
|
||||
|
||||
let sender_nonce = sender.account.nonce;
|
||||
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0));
|
||||
|
||||
let (shared_secret, epk) =
|
||||
SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0);
|
||||
let recipient = AccountWithMetadata::new(
|
||||
Account::default(),
|
||||
false,
|
||||
(&recipient_keys.npk(), &recipient_keys.vpk(), 0),
|
||||
);
|
||||
|
||||
let (output, proof) = crate::privacy_preserving_transaction::circuit::execute_and_prove(
|
||||
vec![sender, recipient],
|
||||
@ -278,13 +279,9 @@ fn shielded_balance_transfer_for_tests(
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
@ -312,33 +309,28 @@ fn private_balance_transfer_for_tests(
|
||||
state: &V03State,
|
||||
) -> PrivacyPreservingTransaction {
|
||||
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_pre = AccountWithMetadata::new(
|
||||
sender_private_account.clone(),
|
||||
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(
|
||||
vec![sender_pre, recipient_pre],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: epk_1,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret_1,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&sender_commitment)
|
||||
@ -346,13 +338,9 @@ fn private_balance_transfer_for_tests(
|
||||
identifier: 0,
|
||||
},
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: epk_2,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret_2,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
@ -376,12 +364,13 @@ fn deshielded_balance_transfer_for_tests(
|
||||
state: &V03State,
|
||||
) -> PrivacyPreservingTransaction {
|
||||
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_pre = AccountWithMetadata::new(
|
||||
sender_private_account.clone(),
|
||||
true,
|
||||
(&sender_keys.npk(), 0),
|
||||
(&sender_keys.npk(), &sender_keys.vpk(), 0),
|
||||
);
|
||||
let recipient_pre = AccountWithMetadata::new(
|
||||
state.get_account_by_id(*recipient_account_id),
|
||||
@ -389,20 +378,14 @@ fn deshielded_balance_transfer_for_tests(
|
||||
*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(
|
||||
vec![sender_pre, recipient_pre],
|
||||
Program::serialize_instruction(balance_to_move).unwrap(),
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.npk(),
|
||||
&sender_keys.vpk(),
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: sender_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&sender_commitment)
|
||||
|
||||
@ -68,8 +68,10 @@ fn transition_from_privacy_preserving_transaction_private() {
|
||||
&state,
|
||||
);
|
||||
|
||||
let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0);
|
||||
let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0);
|
||||
let sender_account_id =
|
||||
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(
|
||||
&sender_account_id,
|
||||
&Account {
|
||||
@ -203,7 +205,8 @@ fn transition_from_privacy_preserving_transaction_deshielded() {
|
||||
&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(
|
||||
&sender_account_id,
|
||||
&Account {
|
||||
@ -487,10 +490,14 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
|
||||
false,
|
||||
sender_keys.account_id(),
|
||||
);
|
||||
let recipient_account =
|
||||
AccountWithMetadata::new(Account::default(), true, (&recipient_keys.npk(), 0));
|
||||
let recipient_account = AccountWithMetadata::new(
|
||||
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_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id);
|
||||
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 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();
|
||||
dependencies.insert(simple_transfers.id(), simple_transfers);
|
||||
let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies);
|
||||
@ -518,12 +522,9 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&recipient_keys.npk(),
|
||||
&recipient_keys.vpk(),
|
||||
),
|
||||
ssk: recipient,
|
||||
vpk: recipient_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: recipient_keys.nsk,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&recipient_commitment)
|
||||
|
||||
@ -124,12 +124,13 @@ fn validity_window_works_in_privacy_preserving_transactions(
|
||||
let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap();
|
||||
let validity_window_program = crate::test_methods::validity_window();
|
||||
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 tx = {
|
||||
let (shared_secret, epk) =
|
||||
SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0);
|
||||
|
||||
let instruction = (
|
||||
block_validity_window,
|
||||
TimestampValidityWindow::new_unbounded(),
|
||||
@ -138,13 +139,9 @@ fn validity_window_works_in_privacy_preserving_transactions(
|
||||
vec![pre],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateUnauthorized {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&account_keys.npk(),
|
||||
&account_keys.vpk(),
|
||||
),
|
||||
vpk: account_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: account_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
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 validity_window_program = crate::test_methods::validity_window();
|
||||
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 tx = {
|
||||
let (shared_secret, epk) =
|
||||
SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0);
|
||||
|
||||
let instruction = (
|
||||
BlockValidityWindow::new_unbounded(),
|
||||
timestamp_validity_window,
|
||||
@ -206,13 +204,9 @@ fn timestamp_validity_window_works_in_privacy_preserving_transactions(
|
||||
vec![pre],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![InputAccountIdentity::PrivateUnauthorized {
|
||||
epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&account_keys.npk(),
|
||||
&account_keys.vpk(),
|
||||
),
|
||||
vpk: account_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
npk: account_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
|
||||
@ -78,7 +78,7 @@ fn public_diff_reflects_a_successful_transfer() {
|
||||
#[test]
|
||||
fn privacy_malicious_programs_cannot_drain_public_victim() {
|
||||
use lee_core::{
|
||||
Commitment, EncryptedAccountData, InputAccountIdentity, SharedSecretKey,
|
||||
Commitment, InputAccountIdentity,
|
||||
account::{Account, AccountWithMetadata},
|
||||
};
|
||||
|
||||
@ -105,8 +105,8 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
|
||||
|
||||
// Attacker controls a private account.
|
||||
let attacker_keys = test_private_account_keys_1();
|
||||
let attacker_id = AccountId::for_regular_private_account(&attacker_keys.npk(), 0);
|
||||
let (attacker_ssk, attacker_epk) = SharedSecretKey::encapsulate(&attacker_keys.vpk());
|
||||
let attacker_id =
|
||||
AccountId::for_regular_private_account(&attacker_keys.npk(), &attacker_keys.vpk(), 0);
|
||||
|
||||
let victim_id = AccountId::new([20_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
|
||||
let account_identities = vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: attacker_epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&attacker_keys.npk(),
|
||||
&attacker_keys.vpk(),
|
||||
),
|
||||
ssk: attacker_ssk,
|
||||
vpk: attacker_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: attacker_keys.nsk,
|
||||
membership_proof,
|
||||
identifier: 0,
|
||||
@ -232,7 +229,7 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
|
||||
#[test]
|
||||
fn privacy_malicious_programs_cannot_drain_private_victim() {
|
||||
use lee_core::{
|
||||
Commitment, EncryptedAccountData, InputAccountIdentity, SharedSecretKey,
|
||||
Commitment, InputAccountIdentity,
|
||||
account::{Account, AccountWithMetadata},
|
||||
};
|
||||
|
||||
@ -262,12 +259,13 @@ fn privacy_malicious_programs_cannot_drain_private_victim() {
|
||||
|
||||
// Attacker controls a private account.
|
||||
let attacker_keys = test_private_account_keys_1();
|
||||
let attacker_id = AccountId::for_regular_private_account(&attacker_keys.npk(), 0);
|
||||
let (attacker_ssk, attacker_epk) = SharedSecretKey::encapsulate(&attacker_keys.vpk());
|
||||
let attacker_id =
|
||||
AccountId::for_regular_private_account(&attacker_keys.npk(), &attacker_keys.vpk(), 0);
|
||||
|
||||
// Victim is a private account — not registered in public chain state.
|
||||
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 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.
|
||||
let account_identities = vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: attacker_epk,
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&attacker_keys.npk(),
|
||||
&attacker_keys.vpk(),
|
||||
),
|
||||
ssk: attacker_ssk,
|
||||
vpk: attacker_keys.vpk(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: attacker_keys.nsk,
|
||||
membership_proof,
|
||||
identifier: 0,
|
||||
|
||||
@ -30,4 +30,3 @@ tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
authenticated_transfer_core.workspace = true
|
||||
|
||||
@ -10,15 +10,15 @@ use common::{
|
||||
};
|
||||
use key_protocol::key_management::KeyChain;
|
||||
use lee::{
|
||||
Account, AccountId, Data, EphemeralPublicKey, PrivacyPreservingTransaction, PrivateKey,
|
||||
PublicKey, PublicTransaction, SharedSecretKey, V03State,
|
||||
Account, AccountId, Data, PrivacyPreservingTransaction, PrivateKey, PublicKey,
|
||||
PublicTransaction, V03State,
|
||||
error::LeeError,
|
||||
execute_and_prove,
|
||||
privacy_preserving_transaction::{Message, circuit::ProgramWithDependencies},
|
||||
program::Program,
|
||||
};
|
||||
use lee_core::{
|
||||
Commitment, EncryptedAccountData, InputAccountIdentity, Nullifier,
|
||||
Commitment, InputAccountIdentity, Nullifier,
|
||||
account::{AccountWithMetadata, Nonce},
|
||||
program::PdaSeed,
|
||||
};
|
||||
@ -790,8 +790,11 @@ async fn block_production_aborts_when_clock_account_data_is_corrupted() {
|
||||
#[test]
|
||||
fn private_bridge_withdraw_invocation_is_dropped() {
|
||||
let sender_keys = KeyChain::new_os_random();
|
||||
let sender_account_id =
|
||||
AccountId::for_regular_private_account(&sender_keys.nullifier_public_key, 0);
|
||||
let sender_account_id = AccountId::for_regular_private_account(
|
||||
&sender_keys.nullifier_public_key,
|
||||
&sender_keys.viewing_public_key,
|
||||
0,
|
||||
);
|
||||
let sender_private_account = Account {
|
||||
program_owner: programs::authenticated_transfer().id(),
|
||||
balance: 100,
|
||||
@ -812,7 +815,11 @@ fn private_bridge_withdraw_invocation_is_dropped() {
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
sender_private_account,
|
||||
true,
|
||||
(&sender_keys.nullifier_public_key, 0),
|
||||
(
|
||||
&sender_keys.nullifier_public_key,
|
||||
&sender_keys.viewing_public_key,
|
||||
0,
|
||||
),
|
||||
);
|
||||
let bridge_pre = AccountWithMetadata::new(
|
||||
state.get_account_by_id(bridge_account_id),
|
||||
@ -820,8 +827,6 @@ fn private_bridge_withdraw_invocation_is_dropped() {
|
||||
bridge_account_id,
|
||||
);
|
||||
|
||||
let shared_secret = SharedSecretKey::encapsulate(&sender_keys.viewing_public_key).0;
|
||||
|
||||
let instruction = Program::serialize_instruction(bridge_core::Instruction::Withdraw {
|
||||
amount: 1,
|
||||
bedrock_account_pk: [0; 32],
|
||||
@ -842,12 +847,9 @@ fn private_bridge_withdraw_invocation_is_dropped() {
|
||||
instruction,
|
||||
vec![
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: EphemeralPublicKey(vec![12_u8; 1088]),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(
|
||||
&sender_keys.nullifier_public_key,
|
||||
&sender_keys.viewing_public_key,
|
||||
),
|
||||
ssk: shared_secret,
|
||||
vpk: sender_keys.viewing_public_key.clone(),
|
||||
random_seed: [0; 32],
|
||||
view_tag: 0,
|
||||
nsk: sender_keys.private_key_holder.nullifier_secret_key,
|
||||
membership_proof: state
|
||||
.get_proof_for_commitment(&sender_commitment)
|
||||
|
||||
@ -86,6 +86,7 @@ pub struct PublicAccountPublicInitialData {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PrivateAccountPublicInitialData {
|
||||
pub npk: lee_core::NullifierPublicKey,
|
||||
pub vpk: lee_core::encryption::ViewingPublicKey,
|
||||
pub account: lee_core::account::Account,
|
||||
}
|
||||
|
||||
@ -108,6 +109,7 @@ impl PrivateAccountPrivateInitialData {
|
||||
pub fn account_id(&self) -> lee::AccountId {
|
||||
lee::AccountId::for_regular_private_account(
|
||||
&self.key_chain.nullifier_public_key,
|
||||
&self.key_chain.viewing_public_key,
|
||||
self.identifier,
|
||||
)
|
||||
}
|
||||
@ -183,6 +185,7 @@ fn initial_commitments() -> Vec<PrivateAccountPublicInitialData> {
|
||||
.into_iter()
|
||||
.map(|data| PrivateAccountPublicInitialData {
|
||||
npk: data.key_chain.nullifier_public_key,
|
||||
vpk: data.key_chain.viewing_public_key.clone(),
|
||||
account: data.account,
|
||||
})
|
||||
.collect()
|
||||
@ -193,7 +196,8 @@ fn initial_private_accounts() -> Vec<(lee_core::Commitment, lee_core::Nullifier)
|
||||
.iter()
|
||||
.map(|init_comm_data| {
|
||||
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();
|
||||
|
||||
@ -304,8 +308,8 @@ mod tests {
|
||||
const PUB_ACC_A_TEXT_ADDR: &str = "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV";
|
||||
const PUB_ACC_B_TEXT_ADDR: &str = "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo";
|
||||
|
||||
const PRIV_ACC_A_TEXT_ADDR: &str = "4eGX3M3rgjHsme8n3sSp89af8JRZtYVTesbJjLqaX1VQ";
|
||||
const PRIV_ACC_B_TEXT_ADDR: &str = "3m6HQmCgmAvsxZtxAHPqqEqoBG4335fCG8TzxigyW7rE";
|
||||
const PRIV_ACC_A_TEXT_ADDR: &str = "EVesBKsYRVtkjnTcsbk8tWHkBn2xZmzAXzwgrP3ZaVoZ";
|
||||
const PRIV_ACC_B_TEXT_ADDR: &str = "94MXhZnueurjX6v37CYDKVEKYBiyhYArvtEdceq2XDQP";
|
||||
|
||||
#[test]
|
||||
fn pub_state_consistency() {
|
||||
@ -440,6 +444,10 @@ mod tests {
|
||||
init_comms[0],
|
||||
PrivateAccountPublicInitialData {
|
||||
npk: NullifierPublicKey(NPK_PRIV_ACC_A),
|
||||
vpk: init_private_accs_keys[0]
|
||||
.key_chain
|
||||
.viewing_public_key
|
||||
.clone(),
|
||||
account: Account {
|
||||
program_owner: DEFAULT_PROGRAM_OWNER,
|
||||
balance: PRIV_ACC_A_INITIAL_BALANCE,
|
||||
@ -453,6 +461,10 @@ mod tests {
|
||||
init_comms[1],
|
||||
PrivateAccountPublicInitialData {
|
||||
npk: NullifierPublicKey(NPK_PRIV_ACC_B),
|
||||
vpk: init_private_accs_keys[1]
|
||||
.key_chain
|
||||
.viewing_public_key
|
||||
.clone(),
|
||||
account: Account {
|
||||
program_owner: DEFAULT_PROGRAM_OWNER,
|
||||
balance: PRIV_ACC_B_INITIAL_BALANCE,
|
||||
|
||||
@ -28,9 +28,6 @@ vault_core.workspace = true
|
||||
[build-dependencies]
|
||||
cbindgen = "0.29"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[features]
|
||||
default = ["prove"]
|
||||
prove = ["lee/prove"]
|
||||
|
||||
@ -645,9 +645,10 @@ mod tests {
|
||||
let identifier = u128::from_le_bytes([45; 16]);
|
||||
|
||||
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(
|
||||
&npk,
|
||||
&vpk,
|
||||
&PrivateAccountKind::Pda {
|
||||
program_id: [46; 8],
|
||||
seed: PdaSeed::new([47; 32]),
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
use core::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use keycard_wallet::{KeycardWallet, python_path};
|
||||
use lee::{AccountId, PrivateKey, PublicKey, Signature};
|
||||
use lee_core::{
|
||||
CommitmentSetDigest, DUMMY_COMMITMENT_HASH, Identifier, InputAccountIdentity, MembershipProof,
|
||||
NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::{AccountWithMetadata, Nonce},
|
||||
encryption::{EncryptedAccountData, EphemeralPublicKey, ViewingPublicKey},
|
||||
encryption::{ViewTag, ViewingPublicKey},
|
||||
};
|
||||
use rand::{RngCore as _, rngs::OsRng};
|
||||
|
||||
use crate::{ExecutionFailureKind, WalletCore};
|
||||
|
||||
@ -201,15 +201,38 @@ impl AccountManager {
|
||||
for account in accounts {
|
||||
let state = match account {
|
||||
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) => {
|
||||
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 {
|
||||
account_id,
|
||||
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() {
|
||||
pin = Some(
|
||||
crate::helperfunctions::read_pin()
|
||||
@ -225,40 +248,75 @@ impl AccountManager {
|
||||
.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 {
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
} => State::Private(private_foreign_acc_preparation(npk, vpk, identifier)),
|
||||
AccountIdentity::PrivatePdaOwned(account_id) => State::Private(
|
||||
private_key_tree_acc_preparation(wallet, account_id, true).await?,
|
||||
),
|
||||
} => {
|
||||
let acc = lee_core::account::Account::default();
|
||||
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 {
|
||||
account_id,
|
||||
npk,
|
||||
vpk,
|
||||
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 {
|
||||
nsk,
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
} => {
|
||||
let account_id = lee::AccountId::from((&npk, identifier));
|
||||
State::Private(
|
||||
private_shared_acc_preparation(
|
||||
wallet, account_id, nsk, npk, vpk, identifier, false,
|
||||
)
|
||||
.await?,
|
||||
let account_id = lee::AccountId::from((&npk, &vpk, identifier));
|
||||
let pre = private_shared_acc_preparation(
|
||||
wallet, account_id, nsk, npk, vpk, identifier, false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
State::Private(pre)
|
||||
}
|
||||
AccountIdentity::PrivatePdaShared {
|
||||
account_id,
|
||||
@ -266,12 +324,14 @@ impl AccountManager {
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
} => State::Private(
|
||||
private_shared_acc_preparation(
|
||||
} => {
|
||||
let pre = private_shared_acc_preparation(
|
||||
wallet, account_id, nsk, npk, vpk, identifier, true,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
.await?;
|
||||
|
||||
State::Private(pre)
|
||||
}
|
||||
};
|
||||
|
||||
states.push(state);
|
||||
@ -327,9 +387,26 @@ impl AccountManager {
|
||||
self.states
|
||||
.iter()
|
||||
.filter_map(|state| match state {
|
||||
State::Private(pre) => Some(PrivateAccountKeys { ssk: pre.ssk }),
|
||||
State::Private(pre) => Some(pre),
|
||||
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()
|
||||
}
|
||||
|
||||
@ -344,19 +421,18 @@ impl AccountManager {
|
||||
State::Public { .. } | State::PublicKeycard { .. } => InputAccountIdentity::Public,
|
||||
State::Private(pre) if pre.is_pda => match (pre.nsk, pre.proof.clone()) {
|
||||
(Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk: pre.epk.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk),
|
||||
ssk: pre.ssk,
|
||||
vpk: pre.vpk.clone(),
|
||||
random_seed: pre.random_seed,
|
||||
view_tag: random_view_tag(),
|
||||
nsk,
|
||||
membership_proof,
|
||||
identifier: pre.identifier,
|
||||
seed: None,
|
||||
},
|
||||
_ => InputAccountIdentity::PrivatePdaInit {
|
||||
epk: pre.epk.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk),
|
||||
vpk: pre.vpk.clone(),
|
||||
random_seed: pre.random_seed,
|
||||
npk: pre.npk,
|
||||
ssk: pre.ssk,
|
||||
identifier: pre.identifier,
|
||||
commitment_root: self.dummy_commitment_root,
|
||||
seed: None,
|
||||
@ -365,27 +441,25 @@ impl AccountManager {
|
||||
State::Private(pre) => match (pre.nsk, pre.proof.clone()) {
|
||||
(Some(nsk), Some(membership_proof)) => {
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk: pre.epk.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk),
|
||||
ssk: pre.ssk,
|
||||
vpk: pre.vpk.clone(),
|
||||
random_seed: pre.random_seed,
|
||||
view_tag: random_view_tag(),
|
||||
nsk,
|
||||
membership_proof,
|
||||
identifier: pre.identifier,
|
||||
}
|
||||
}
|
||||
(Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit {
|
||||
epk: pre.epk.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk),
|
||||
ssk: pre.ssk,
|
||||
vpk: pre.vpk.clone(),
|
||||
random_seed: pre.random_seed,
|
||||
nsk,
|
||||
identifier: pre.identifier,
|
||||
commitment_root: self.dummy_commitment_root,
|
||||
},
|
||||
(None, _) => InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: pre.epk.clone(),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk),
|
||||
vpk: pre.vpk.clone(),
|
||||
random_seed: pre.random_seed,
|
||||
npk: pre.npk,
|
||||
ssk: pre.ssk,
|
||||
identifier: pre.identifier,
|
||||
commitment_root: self.dummy_commitment_root,
|
||||
},
|
||||
@ -462,49 +536,12 @@ struct AccountPreparedData {
|
||||
vpk: ViewingPublicKey,
|
||||
pre_state: AccountWithMetadata,
|
||||
proof: Option<MembershipProof>,
|
||||
/// Cached shared-secret key derived once at `AccountManager::new`. Reused for both the
|
||||
/// 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,
|
||||
random_seed: [u8; 32],
|
||||
/// True when this account is a private PDA (owned or foreign). Used by `account_identities()`
|
||||
/// to select `PrivatePdaInit`/`PrivatePdaUpdate` rather than the standalone private variants.
|
||||
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(
|
||||
wallet: &WalletCore,
|
||||
account_id: AccountId,
|
||||
@ -530,9 +567,8 @@ async fn private_key_tree_acc_preparation(
|
||||
// support from that in the wallet.
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.account.clone(), true, account_id);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&from_vpk);
|
||||
let ssk = eph_holder.calculate_shared_secret_sender();
|
||||
let epk = eph_holder.ephemeral_public_key().clone();
|
||||
let mut random_seed: [u8; 32] = [0; 32];
|
||||
OsRng.fill_bytes(&mut random_seed);
|
||||
|
||||
Ok(AccountPreparedData {
|
||||
nsk: Some(nsk),
|
||||
@ -541,8 +577,7 @@ async fn private_key_tree_acc_preparation(
|
||||
vpk: from_vpk,
|
||||
pre_state: sender_pre,
|
||||
proof,
|
||||
ssk,
|
||||
epk,
|
||||
random_seed,
|
||||
is_pda,
|
||||
})
|
||||
}
|
||||
@ -570,9 +605,8 @@ async fn private_shared_acc_preparation(
|
||||
.await
|
||||
.unwrap_or(None);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&vpk);
|
||||
let ssk = eph_holder.calculate_shared_secret_sender();
|
||||
let epk = eph_holder.ephemeral_public_key().clone();
|
||||
let mut random_seed: [u8; 32] = [0; 32];
|
||||
OsRng.fill_bytes(&mut random_seed);
|
||||
|
||||
Ok(AccountPreparedData {
|
||||
nsk: Some(nsk),
|
||||
@ -581,59 +615,16 @@ async fn private_shared_acc_preparation(
|
||||
vpk,
|
||||
pre_state,
|
||||
proof,
|
||||
ssk,
|
||||
epk,
|
||||
random_seed,
|
||||
is_pda,
|
||||
})
|
||||
}
|
||||
|
||||
fn private_foreign_acc_preparation(
|
||||
npk: NullifierPublicKey,
|
||||
vpk: ViewingPublicKey,
|
||||
identifier: Identifier,
|
||||
) -> AccountPreparedData {
|
||||
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,
|
||||
}
|
||||
/// Generate random byte using OS randomness.
|
||||
fn random_view_tag() -> ViewTag {
|
||||
let mut byte: [u8; 1] = [0; 1];
|
||||
OsRng.fill_bytes(&mut byte);
|
||||
byte[0]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -581,8 +581,11 @@ impl WalletSubcommand for ImportSubcommand {
|
||||
let key_chain: KeyChain = serde_json::from_str(&key_chain_json)
|
||||
.map_err(|err| anyhow::anyhow!("Invalid key chain JSON: {err}"))?;
|
||||
let account = lee::Account::from(account_state);
|
||||
let account_id =
|
||||
lee::AccountId::from((&key_chain.nullifier_public_key, identifier));
|
||||
let account_id = lee::AccountId::from((
|
||||
&key_chain.nullifier_public_key,
|
||||
&key_chain.viewing_public_key,
|
||||
identifier,
|
||||
));
|
||||
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
|
||||
@ -369,7 +369,7 @@ impl WalletCore {
|
||||
let keys = holder.derive_keys_for_pda(&program_id, &pda_seed);
|
||||
let npk = keys.generate_nullifier_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(
|
||||
account_id,
|
||||
@ -403,7 +403,7 @@ impl WalletCore {
|
||||
let keys = holder.derive_regular_shared_account_keys_from_identifier(identifier);
|
||||
let npk = keys.generate_nullifier_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);
|
||||
|
||||
@ -803,7 +803,11 @@ impl WalletCore {
|
||||
)
|
||||
.map(|(kind, res_acc)| {
|
||||
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;
|
||||
(account_id, kind, res_acc, nsk)
|
||||
})
|
||||
|
||||
@ -212,7 +212,8 @@ impl UserKeyChain {
|
||||
.1
|
||||
.first_key_value()
|
||||
.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)
|
||||
}
|
||||
|
||||
@ -248,8 +249,11 @@ impl UserKeyChain {
|
||||
#[must_use]
|
||||
pub fn private_account(&self, account_id: AccountId) -> Option<FoundPrivateAccount<'_>> {
|
||||
self.private_accounts().find_map(|found| {
|
||||
let expected_id =
|
||||
AccountId::for_private_account(&found.key_chain.nullifier_public_key, found.kind);
|
||||
let expected_id = AccountId::for_private_account(
|
||||
&found.key_chain.nullifier_public_key,
|
||||
&found.key_chain.viewing_public_key,
|
||||
found.kind,
|
||||
);
|
||||
(expected_id == account_id).then_some(found)
|
||||
})
|
||||
}
|
||||
@ -305,8 +309,11 @@ impl UserKeyChain {
|
||||
.iter()
|
||||
.flat_map(|(key, data)| {
|
||||
data.accounts.keys().map(|kind| {
|
||||
let account_id =
|
||||
AccountId::for_private_account(&key.key_chain.nullifier_public_key, kind);
|
||||
let account_id = AccountId::for_private_account(
|
||||
&key.key_chain.nullifier_public_key,
|
||||
&key.key_chain.viewing_public_key,
|
||||
kind,
|
||||
);
|
||||
(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
|
||||
// into the map. This is the next nullifier it will look for.
|
||||
for found in self.private_accounts() {
|
||||
let account_id =
|
||||
AccountId::for_private_account(&found.key_chain.nullifier_public_key, found.kind);
|
||||
let account_id = AccountId::for_private_account(
|
||||
&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;
|
||||
index.track(account_id, found.account, &nsk);
|
||||
}
|
||||
@ -496,8 +506,11 @@ impl UserKeyChain {
|
||||
// Then try to update imported account
|
||||
for (key, data) in &mut self.imported_private_accounts {
|
||||
for (kind, imported_account) in &mut data.accounts {
|
||||
let expected_id =
|
||||
AccountId::for_private_account(&key.key_chain.nullifier_public_key, kind);
|
||||
let expected_id = AccountId::for_private_account(
|
||||
&key.key_chain.nullifier_public_key,
|
||||
&key.key_chain.viewing_public_key,
|
||||
kind,
|
||||
);
|
||||
if expected_id == account_id {
|
||||
debug!("Updating imported private account {account_id}");
|
||||
*imported_account = account;
|
||||
@ -534,8 +547,11 @@ impl UserKeyChain {
|
||||
|
||||
// Node not yet in account_id_map — find it by checking all nodes
|
||||
for (ci, node) in &mut self.private_key_tree.key_map {
|
||||
let expected_id =
|
||||
lee::AccountId::for_private_account(&node.value.0.nullifier_public_key, &kind);
|
||||
let expected_id = lee::AccountId::for_private_account(
|
||||
&node.value.0.nullifier_public_key,
|
||||
&node.value.0.viewing_public_key,
|
||||
&kind,
|
||||
);
|
||||
if expected_id == account_id {
|
||||
match node.value.1.entry(kind) {
|
||||
Entry::Occupied(mut occupied) => {
|
||||
@ -585,8 +601,11 @@ impl UserKeyChain {
|
||||
.iter()
|
||||
.flat_map(|(key, data)| {
|
||||
data.accounts.keys().map(|kind| {
|
||||
let account_id =
|
||||
AccountId::for_private_account(&key.key_chain.nullifier_public_key, kind);
|
||||
let account_id = AccountId::for_private_account(
|
||||
&key.key_chain.nullifier_public_key,
|
||||
&key.key_chain.viewing_public_key,
|
||||
kind,
|
||||
);
|
||||
(account_id, key.chain_index.as_ref())
|
||||
})
|
||||
})
|
||||
@ -851,6 +870,7 @@ mod tests {
|
||||
let identifier = 0;
|
||||
let account_id = AccountId::for_private_account(
|
||||
&key_chain.nullifier_public_key,
|
||||
&key_chain.viewing_public_key,
|
||||
&PrivateAccountKind::Regular(identifier),
|
||||
);
|
||||
|
||||
@ -913,7 +933,7 @@ mod tests {
|
||||
let npk = keys.generate_nullifier_public_key();
|
||||
let vpk = keys.generate_viewing_public_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);
|
||||
let old_account = Account::default();
|
||||
@ -975,6 +995,7 @@ mod tests {
|
||||
let identifier = 0;
|
||||
let account_id = AccountId::for_private_account(
|
||||
&key_chain.nullifier_public_key,
|
||||
&key_chain.viewing_public_key,
|
||||
&PrivateAccountKind::Regular(identifier),
|
||||
);
|
||||
let account = Account::default();
|
||||
@ -1032,7 +1053,11 @@ mod tests {
|
||||
let mut user_data = UserKeyChain::default();
|
||||
|
||||
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();
|
||||
|
||||
user_data.add_imported_private_account(key_chain, None, 0, account);
|
||||
@ -1047,7 +1072,11 @@ mod tests {
|
||||
let mut user_data = UserKeyChain::default();
|
||||
|
||||
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();
|
||||
|
||||
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 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 {
|
||||
balance: 100,
|
||||
@ -1113,7 +1146,11 @@ mod tests {
|
||||
let mut user_data = UserKeyChain::default();
|
||||
|
||||
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();
|
||||
user_data.add_imported_private_account(key_chain, None, 0, account);
|
||||
|
||||
|
||||
@ -23,7 +23,11 @@ pub struct InitialPrivateAccountForWallet {
|
||||
impl InitialPrivateAccountForWallet {
|
||||
#[must_use]
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ workspace = true
|
||||
[dev-dependencies]
|
||||
key_protocol.workspace = true
|
||||
lee_core = { workspace = true, features = ["host"] }
|
||||
rand = { workspace = true }
|
||||
criterion.workspace = true
|
||||
|
||||
[[bench]]
|
||||
|
||||
@ -48,7 +48,8 @@ fn bench_encryption(c: &mut Criterion) {
|
||||
let recipient_kc = KeyChain::new_os_random();
|
||||
let npk = recipient_kc.nullifier_public_key;
|
||||
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 (shared, _epk) = SharedSecretKey::encapsulate(&recipient_kc.viewing_public_key);
|
||||
let kind = PrivateAccountKind::Regular(0_u128);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user