From e4e90104a5a9edfdff20a06fe1b68fbff1fa0c76 Mon Sep 17 00:00:00 2001 From: agureev Date: Mon, 29 Jun 2026 22:34:25 +0400 Subject: [PATCH] WIP --- integration_tests/tests/account.rs | 18 +- .../tests/auth_transfer/private.rs | 11 +- integration_tests/tests/private_pda.rs | 12 +- integration_tests/tests/tps.rs | 7 +- integration_tests/tests/wallet_ffi.rs | 16 +- .../src/key_management/group_key_holder.rs | 21 +-- .../src/key_management/key_tree/mod.rs | 9 +- lee/state_machine/core/src/account.rs | 27 ++- lee/state_machine/core/src/circuit_io.rs | 34 ++-- lee/state_machine/core/src/nullifier.rs | 34 ++-- lee/state_machine/core/src/program.rs | 116 +++++++------ lee/state_machine/src/lib.rs | 2 +- .../privacy_preserving_transaction/circuit.rs | 49 +++--- .../privacy_preserving_transaction/message.rs | 9 +- lee/state_machine/src/state.rs | 163 +++++++++--------- lee/state_machine/src/validated_state_diff.rs | 15 +- lez/sequencer/core/src/lib.rs | 20 ++- lez/testnet_initial_state/src/lib.rs | 10 +- lez/wallet/src/account_manager.rs | 16 +- lez/wallet/src/cli/account.rs | 9 +- lez/wallet/src/lib.rs | 7 +- lez/wallet/src/storage/key_chain.rs | 38 ++-- .../execution_state.rs | 108 +++++------- .../bin/privacy_preserving_circuit/main.rs | 2 +- .../bin/privacy_preserving_circuit/output.rs | 77 +++++---- test_fixtures/src/config.rs | 11 +- .../benches/primitives.rs | 4 +- 27 files changed, 449 insertions(+), 396 deletions(-) diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs index 3490b42b..c65cb657 100644 --- a/integration_tests/tests/account.rs +++ b/integration_tests/tests/account.rs @@ -156,11 +156,12 @@ 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, - &key_chain.viewing_public_key, + let account_id = lee::PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key.clone(), 0, - )); + ) + .account_id(); let account = lee::Account { program_owner: Program::authenticated_transfer_program().id(), balance: 777, @@ -217,11 +218,12 @@ 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, - &key_chain.viewing_public_key, + let account_id = lee::PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key.clone(), 0, - )); + ) + .account_id(); let key_chain_json = serde_json::to_string(&key_chain).context("Failed to serialize key chain")?; diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index a862e334..208e692e 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -7,8 +7,8 @@ use integration_tests::{ public_mention, verify_commitment_is_in_state, }; use lee::{ - AccountId, execute_and_prove, privacy_preserving_transaction::circuit::ProgramWithDependencies, - program::Program, + AccountId, PrivateAddressPlaintext, execute_and_prove, + privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, }; use lee_core::{ InputAccountIdentity, NullifierPublicKey, account::AccountWithMetadata, @@ -599,14 +599,14 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> { .await?; // Both accounts must be discovered with the correct balances. - let account_id_1 = AccountId::for_regular_private_account(&npk, &vpk, identifier_1); + let account_id_1 = PrivateAddressPlaintext::new(npk, vpk.clone(), identifier_1).account_id(); 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, &vpk, identifier_2); + let account_id_2 = PrivateAddressPlaintext::new(npk, vpk.clone(), identifier_2).account_id(); let acc_2 = ctx .wallet() .get_account_private(account_id_2) @@ -667,7 +667,8 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> { let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap(); let attacker_vault_id = { let seed = vault_core::compute_vault_seed(attacker_id); - AccountId::for_private_pda(&vault_program_id, &seed, &npk, &vpk, 1337) + PrivateAddressPlaintext::new(npk, vpk.clone(), 1337) + .pda_account_id(&vault_program_id, &seed) }; let amount: u128 = 1; diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index e605ceee..1aa32d2d 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -13,7 +13,7 @@ use integration_tests::{ verify_commitment_is_in_state, }; use lee::{ - AccountId, PrivacyPreservingTransaction, ProgramId, + AccountId, PrivacyPreservingTransaction, PrivateAddressPlaintext, ProgramId, privacy_preserving_transaction::{ circuit::{ProgramWithDependencies, execute_and_prove}, message::Message, @@ -51,8 +51,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, &vpk, identifier); + let pda_account_id = PrivateAddressPlaintext::new(npk, vpk.clone(), identifier) + .pda_account_id(&authority_program_id, &seed); let sender_account = wallet .get_account_public(sender) .await @@ -177,8 +177,10 @@ 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, &alice_vpk, 0); - let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, &alice_vpk, 1); + let alice_pda_0_id = PrivateAddressPlaintext::new(alice_npk, alice_vpk.clone(), 0) + .pda_account_id(&proxy_id, &seed); + let alice_pda_1_id = PrivateAddressPlaintext::new(alice_npk, alice_vpk.clone(), 1) + .pda_account_id(&proxy_id, &seed); // Use two different public senders to avoid nonce conflicts between the back-to-back txs. let senders = ctx.existing_public_accounts(); diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 78116c49..3f9e2435 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -16,7 +16,8 @@ use bytesize::ByteSize; use common::transaction::LeeTransaction; use integration_tests::{TestContext, config::SequencerPartialConfig}; use lee::{ - Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction, + Account, AccountId, PrivacyPreservingTransaction, PrivateAddressPlaintext, PrivateKey, + PublicKey, PublicTransaction, privacy_preserving_transaction::{self as pptx, circuit}, program::Program, public_transaction as putx, @@ -265,7 +266,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { data: Data::default(), }, true, - AccountId::for_regular_private_account(&sender_npk, &sender_vpk, 0), + PrivateAddressPlaintext::new(sender_npk, sender_vpk.clone(), 0).account_id(), ); let recipient_nsk = [2; 32]; let recipient_vpk = ViewingPublicKey::from_seed(&[101_u8; 32], &[102_u8; 32]); @@ -273,7 +274,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { let recipient_pre = AccountWithMetadata::new( Account::default(), false, - AccountId::for_regular_private_account(&recipient_npk, &recipient_vpk, 0), + PrivateAddressPlaintext::new(recipient_npk, recipient_vpk.clone(), 0).account_id(), ); let balance_to_move: u128 = 1; diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index a68a3db6..ba420743 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -907,11 +907,9 @@ 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(), - &out_keys.vpk().unwrap(), - 0_u128, - ); + let account_id = + lee::PrivateAddressPlaintext::new(out_keys.npk(), out_keys.vpk().unwrap(), 0_u128) + .account_id(); let to: FfiBytes32 = account_id.into(); (to, out_keys) }; @@ -1048,11 +1046,9 @@ 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(), - &out_keys.vpk().unwrap(), - 0_u128, - ); + let account_id = + lee::PrivateAddressPlaintext::new(out_keys.npk(), out_keys.vpk().unwrap(), 0_u128) + .account_id(); let to: FfiBytes32 = account_id.into(); (to, out_keys) }; diff --git a/lee/key_protocol/src/key_management/group_key_holder.rs b/lee/key_protocol/src/key_management/group_key_holder.rs index 7bb94792..849d7b8c 100644 --- a/lee/key_protocol/src/key_management/group_key_holder.rs +++ b/lee/key_protocol/src/key_management/group_key_holder.rs @@ -323,11 +323,11 @@ mod tests { /// Pins the end-to-end derivation for a fixed (GMS, `ProgramId`, `PdaSeed`). Any change /// to `secret_spending_key_for_pda`, the `PrivateKeyHolder` nsk/npk chain, or the - /// `AccountId::for_private_pda` formula breaks this test. Mirrors the pinned-value + /// `PrivateAddressPlaintext::pda_account_id` formula breaks this test. Mirrors the pinned-value /// pattern from `for_private_pda_matches_pinned_value` in `lee_core`. #[test] fn pinned_end_to_end_derivation_for_private_pda() { - use lee_core::{account::AccountId, program::ProgramId}; + use lee_core::{account::PrivateAddressPlaintext, program::ProgramId}; let gms = [42_u8; 32]; let seed = PdaSeed::new([1; 32]); @@ -337,7 +337,8 @@ mod tests { 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 account_id = PrivateAddressPlaintext::new(npk, vpk.clone(), u128::MAX) + .pda_account_id(&program_id, &seed); let expected_npk = NullifierPublicKey([ 136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79, @@ -345,8 +346,8 @@ 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, &vpk, u128::MAX); + let expected_account_id = PrivateAddressPlaintext::new(expected_npk, vpk, u128::MAX) + .pda_account_id(&program_id, &seed); assert_eq!(npk, expected_npk); assert_eq!(account_id, expected_account_id); @@ -524,7 +525,7 @@ mod tests { /// Full lifecycle: create group, distribute GMS via seal/unseal, verify key agreement. #[test] fn group_pda_lifecycle() { - use lee_core::account::AccountId; + use lee_core::account::PrivateAddressPlaintext; let alice_holder = GroupKeyHolder::new(); let pda_seed = PdaSeed::new([42_u8; 32]); @@ -549,10 +550,10 @@ mod tests { 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); + let alice_account_id = PrivateAddressPlaintext::new(alice_npk, alice_vpk, 0) + .pda_account_id(&program_id, &pda_seed); + let bob_account_id = PrivateAddressPlaintext::new(bob_npk, bob_group_vpk, 0) + .pda_account_id(&program_id, &pda_seed); assert_eq!(alice_account_id, bob_account_id); } diff --git a/lee/key_protocol/src/key_management/key_tree/mod.rs b/lee/key_protocol/src/key_management/key_tree/mod.rs index e7d490d2..c592647b 100644 --- a/lee/key_protocol/src/key_management/key_tree/mod.rs +++ b/lee/key_protocol/src/key_management/key_tree/mod.rs @@ -275,11 +275,12 @@ impl KeyTree { identifier: Identifier, ) -> Option { 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, + let account_id = lee::PrivateAddressPlaintext::new( + node.value.0.nullifier_public_key, + node.value.0.viewing_public_key.clone(), identifier, - ); + ) + .account_id(); if self.account_id_map.contains_key(&account_id) { return None; } diff --git a/lee/state_machine/core/src/account.rs b/lee/state_machine/core/src/account.rs index dc8a49a9..bc7e55d0 100644 --- a/lee/state_machine/core/src/account.rs +++ b/lee/state_machine/core/src/account.rs @@ -10,7 +10,10 @@ use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; -use crate::{NullifierSecretKey, program::ProgramId}; +use crate::{ + Identifier, NullifierPublicKey, NullifierSecretKey, encryption::ViewingPublicKey, + program::ProgramId, +}; pub mod data; @@ -179,6 +182,28 @@ impl AccountId { } } +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +pub struct PrivateAddressPlaintext { + pub npk: NullifierPublicKey, + pub vpk: ViewingPublicKey, + pub identifier: Identifier, +} + +impl PrivateAddressPlaintext { + #[must_use] + pub const fn new( + npk: NullifierPublicKey, + vpk: ViewingPublicKey, + identifier: Identifier, + ) -> Self { + Self { + npk, + vpk, + identifier, + } + } +} + impl AsRef<[u8]> for AccountId { fn as_ref(&self) -> &[u8] { &self.value diff --git a/lee/state_machine/core/src/circuit_io.rs b/lee/state_machine/core/src/circuit_io.rs index 9bead310..1eed0053 100644 --- a/lee/state_machine/core/src/circuit_io.rs +++ b/lee/state_machine/core/src/circuit_io.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, - account::{Account, AccountWithMetadata}, + account::{Account, AccountWithMetadata, PrivateAddressPlaintext}, encryption::{EncryptedAccountData, ViewingPublicKey}, program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow}, }; @@ -28,7 +28,7 @@ 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), vpk, identifier)` + /// `PrivateAddressPlaintext::new(NullifierPublicKey::from(nsk), vpk, identifier).account_id()` /// and matched against `pre_state.account_id`. PrivateAuthorizedInit { vpk: ViewingPublicKey, @@ -55,8 +55,7 @@ pub enum InputAccountIdentity { }, /// Init of a private PDA, unauthorized. The npk-to-account_id binding is proven upstream /// via `Claim::Pda(seed)` or a caller's `pda_seeds` match. The identifier diversifies the - /// PDA within the `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it - /// as the 4th input. + /// PDA within the `(program_id, seed, npk)` family. PrivatePdaInit { vpk: ViewingPublicKey, random_seed: [u8; 32], @@ -64,10 +63,10 @@ pub enum InputAccountIdentity { identifier: Identifier, /// When `Some((seed, authority_program_id))`, the circuit binds this position via the /// external derivation check - /// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) == - /// pre_state.account_id` rather than requiring a `Claim::Pda` or caller - /// `pda_seeds` to establish the binding. The `pre_state` must have `is_authorized - /// == false`. + /// `PrivateAddressPlaintext::new(npk, vpk, + /// identifier).pda_account_id(authority_program_id, seed) == pre_state.account_id` + /// rather than requiring a `Claim::Pda` or caller `pda_seeds` to establish the + /// binding. The `pre_state` must have `is_authorized == false`. seed: Option<(PdaSeed, ProgramId)>, }, /// Update of an existing private PDA, with membership proof. `npk` is derived @@ -81,9 +80,10 @@ pub enum InputAccountIdentity { identifier: Identifier, /// When `Some((seed, authority_program_id))`, the circuit binds this position via the /// external derivation check - /// `AccountId::for_private_pda(authority_program_id, seed, npk, vpk, identifier) == - /// pre_state.account_id` rather than requiring a caller `pda_seeds` to establish - /// the binding. The `pre_state` must have `is_authorized == false`. + /// `PrivateAddressPlaintext::new(npk, vpk, + /// identifier).pda_account_id(authority_program_id, seed) == 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)>, }, } @@ -103,22 +103,24 @@ impl InputAccountIdentity { } #[must_use] - pub fn npk_vpk_if_private_pda( - &self, - ) -> Option<(NullifierPublicKey, ViewingPublicKey, Identifier)> { + pub fn private_pda_address(&self) -> Option { match self { Self::PrivatePdaInit { npk, vpk, identifier, .. - } => Some((*npk, vpk.clone(), *identifier)), + } => Some(PrivateAddressPlaintext::new(*npk, vpk.clone(), *identifier)), Self::PrivatePdaUpdate { nsk, vpk, identifier, .. - } => Some((NullifierPublicKey::from(nsk), vpk.clone(), *identifier)), + } => Some(PrivateAddressPlaintext::new( + NullifierPublicKey::from(nsk), + vpk.clone(), + *identifier, + )), Self::Public | Self::PrivateAuthorizedInit { .. } | Self::PrivateAuthorizedUpdate { .. } diff --git a/lee/state_machine/core/src/nullifier.rs b/lee/state_machine/core/src/nullifier.rs index 0374a31a..8eaf4740 100644 --- a/lee/state_machine/core/src/nullifier.rs +++ b/lee/state_machine/core/src/nullifier.rs @@ -2,7 +2,11 @@ use borsh::{BorshDeserialize, BorshSerialize}; use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; -use crate::{Commitment, account::AccountId, encryption::ViewingPublicKey}; +use crate::{ + Commitment, + account::{AccountId, PrivateAddressPlaintext}, + encryption::ViewingPublicKey, +}; const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00"; @@ -12,22 +16,18 @@ pub type Identifier = u128; #[cfg_attr(any(feature = "host", test), derive(Hash))] pub struct NullifierPublicKey(pub [u8; 32]); -impl AccountId { +impl PrivateAddressPlaintext { /// 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, - vpk: &ViewingPublicKey, - identifier: Identifier, - ) -> Self { + pub fn account_id(&self) -> AccountId { 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..64 + ViewingPublicKey::LEN].copy_from_slice(vpk.to_bytes()); - bytes[64 + ViewingPublicKey::LEN..].copy_from_slice(&identifier.to_le_bytes()); + bytes[32..64].copy_from_slice(&self.npk.0); + bytes[64..64 + ViewingPublicKey::LEN].copy_from_slice(self.vpk.to_bytes()); + bytes[64 + ViewingPublicKey::LEN..].copy_from_slice(&self.identifier.to_le_bytes()); - Self::new( + AccountId::new( Impl::hash_bytes(&bytes) .as_bytes() .try_into() @@ -36,12 +36,6 @@ impl AccountId { } } -impl From<(&NullifierPublicKey, &ViewingPublicKey, Identifier)> for AccountId { - fn from((npk, vpk, identifier): (&NullifierPublicKey, &ViewingPublicKey, Identifier)) -> Self { - Self::for_regular_private_account(npk, vpk, identifier) - } -} - impl AsRef<[u8]> for NullifierPublicKey { fn as_ref(&self) -> &[u8] { self.0.as_slice() @@ -168,7 +162,7 @@ mod tests { 220, 68, 135, 10, 171, 182, 80, 54, 74, 228, 244, 236, 7, ]); - let account_id = AccountId::for_regular_private_account(&npk, &vpk, 0); + let account_id = PrivateAddressPlaintext::new(npk, vpk, 0).account_id(); assert_eq!(account_id, expected_account_id); } @@ -186,7 +180,7 @@ mod tests { 189, 170, 32, 181, 255, 231, 19, 92, 235, 59, 153, 185, 172, 206, ]); - let account_id = AccountId::for_regular_private_account(&npk, &vpk, 1); + let account_id = PrivateAddressPlaintext::new(npk, vpk, 1).account_id(); assert_eq!(account_id, expected_account_id); } @@ -205,7 +199,7 @@ mod tests { 159, 112, 84, 100, 133, 244, 16, 34, 221, 35, 128, 131, 98, 159, ]); - let account_id = AccountId::for_regular_private_account(&npk, &vpk, identifier); + let account_id = PrivateAddressPlaintext::new(npk, vpk, identifier).account_id(); assert_eq!(account_id, expected_account_id); } diff --git a/lee/state_machine/core/src/program.rs b/lee/state_machine/core/src/program.rs index 1c99cb36..4af96dc7 100644 --- a/lee/state_machine/core/src/program.rs +++ b/lee/state_machine/core/src/program.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ BlockId, Identifier, NullifierPublicKey, Timestamp, - account::{Account, AccountId, AccountWithMetadata}, + account::{Account, AccountId, AccountWithMetadata, PrivateAddressPlaintext}, encryption::ViewingPublicKey, }; @@ -143,41 +143,6 @@ impl AccountId { ) } - /// Derives an [`AccountId`] for a private PDA from the program ID, seed, nullifier public - /// key, and identifier. - /// - /// Unlike public PDAs ([`AccountId::for_public_pda`]), this includes the `npk` in the - /// derivation, making the address unique per group of controllers sharing viewing keys. - /// The `identifier` further diversifies the address, so a single `(program_id, seed, npk)` - /// tuple controls a family of 2^128 addresses. - #[must_use] - pub fn for_private_pda( - 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; 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..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() - .try_into() - .expect("Hash output must be exactly 32 bytes long"), - ) - } - /// Derives the [`AccountId`] for a private account from the nullifier public key and kind. #[must_use] pub fn for_private_account( @@ -187,17 +152,49 @@ impl AccountId { ) -> Self { match kind { PrivateAccountKind::Regular(identifier) => { - Self::for_regular_private_account(npk, vpk, *identifier) + PrivateAddressPlaintext::new(*npk, vpk.clone(), *identifier).account_id() } PrivateAccountKind::Pda { program_id, seed, identifier, - } => Self::for_private_pda(program_id, seed, npk, vpk, *identifier), + } => PrivateAddressPlaintext::new(*npk, vpk.clone(), *identifier) + .pda_account_id(program_id, seed), } } } +impl PrivateAddressPlaintext { + /// Derives an [`AccountId`] for a private PDA from the program ID, seed, nullifier public + /// key, and identifier. + /// + /// Unlike public PDAs ([`AccountId::for_public_pda`]), this includes the `npk` in the + /// derivation, making the address unique per group of controllers sharing viewing keys. + /// The `identifier` further diversifies the address, so a single `(program_id, seed, npk)` + /// tuple controls a family of 2^128 addresses. + #[must_use] + pub fn pda_account_id(&self, program_id: &ProgramId, seed: &PdaSeed) -> AccountId { + 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; 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(&self.npk.to_byte_array()); + bytes[128..128 + ViewingPublicKey::LEN].copy_from_slice(self.vpk.to_bytes()); + bytes[128 + ViewingPublicKey::LEN..].copy_from_slice(&self.identifier.to_le_bytes()); + AccountId::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) + } +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct ChainedCall { /// The program ID of the program to execute. @@ -626,7 +623,7 @@ pub enum ExecutionValidationError { /// /// Returns only public-form derivations, suitable for contexts where all accounts are public /// (e.g. the public-execution path). The privacy circuit must additionally check each mask-3 -/// `pre_state` against [`AccountId::for_private_pda`] with the supplied npk for that +/// `pre_state` against [`PrivateAddressPlaintext::pda_account_id`] with the supplied npk for that /// `pre_state`. #[must_use] pub fn compute_public_authorized_pdas( @@ -950,11 +947,11 @@ mod tests { assert_eq!(account_post_state.account_mut(), &mut account); } - // ---- AccountId::for_private_pda tests ---- + // ---- PrivateAddressPlaintext::pda_account_id tests ---- - /// Pins `AccountId::for_private_pda` against a hardcoded expected output for a specific - /// `(program_id, seed, npk, identifier)` tuple. Any change to `PRIVATE_PDA_PREFIX`, byte - /// ordering, or the underlying hash breaks this test. + /// Pins `PrivateAddressPlaintext::pda_account_id` against a hardcoded expected output for a + /// specific `(program_id, seed, npk, identifier)` tuple. Any change to + /// `PRIVATE_PDA_PREFIX`, byte ordering, or the underlying hash breaks this test. #[test] fn for_private_pda_matches_pinned_value() { let program_id: ProgramId = [1; 8]; @@ -967,7 +964,7 @@ mod tests { 156, 13, 55, 32, 139, 91, 222, 209, 83, 172, 148, 123, 179, ]); assert_eq!( - AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, identifier), + PrivateAddressPlaintext::new(npk, vpk, identifier).pda_account_id(&program_id, &seed), expected ); } @@ -981,8 +978,9 @@ mod tests { 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, &vpk, u128::MAX), - AccountId::for_private_pda(&program_id, &seed, &npk_b, &vpk, u128::MAX), + PrivateAddressPlaintext::new(npk_a, vpk.clone(), u128::MAX) + .pda_account_id(&program_id, &seed), + PrivateAddressPlaintext::new(npk_b, vpk, u128::MAX).pda_account_id(&program_id, &seed), ); } @@ -995,8 +993,9 @@ mod tests { 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, &vpk, u128::MAX), - AccountId::for_private_pda(&program_id, &seed_b, &npk, &vpk, u128::MAX), + PrivateAddressPlaintext::new(npk, vpk.clone(), u128::MAX) + .pda_account_id(&program_id, &seed_a), + PrivateAddressPlaintext::new(npk, vpk, u128::MAX).pda_account_id(&program_id, &seed_b), ); } @@ -1009,8 +1008,9 @@ mod tests { 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, &vpk, u128::MAX), - AccountId::for_private_pda(&program_id_b, &seed, &npk, &vpk, u128::MAX), + PrivateAddressPlaintext::new(npk, vpk.clone(), u128::MAX) + .pda_account_id(&program_id_a, &seed), + PrivateAddressPlaintext::new(npk, vpk, u128::MAX).pda_account_id(&program_id_b, &seed), ); } @@ -1023,12 +1023,12 @@ mod tests { 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, &vpk, 0), - AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 1), + PrivateAddressPlaintext::new(npk, vpk.clone(), 0).pda_account_id(&program_id, &seed), + PrivateAddressPlaintext::new(npk, vpk.clone(), 1).pda_account_id(&program_id, &seed), ); assert_ne!( - AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, 0), - AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, u128::MAX), + PrivateAddressPlaintext::new(npk, vpk.clone(), 0).pda_account_id(&program_id, &seed), + PrivateAddressPlaintext::new(npk, vpk, u128::MAX).pda_account_id(&program_id, &seed), ); } @@ -1040,7 +1040,8 @@ mod tests { let seed = PdaSeed::new([2; 32]); let npk = NullifierPublicKey([3; 32]); 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 private_id = + PrivateAddressPlaintext::new(npk, vpk, u128::MAX).pda_account_id(&program_id, &seed); let public_id = AccountId::for_public_pda(&program_id, &seed); assert_ne!(private_id, public_id); } @@ -1082,7 +1083,7 @@ mod tests { assert_eq!( AccountId::for_private_account(&npk, &vpk, &PrivateAccountKind::Regular(identifier)), - AccountId::for_regular_private_account(&npk, &vpk, identifier), + PrivateAddressPlaintext::new(npk, vpk.clone(), identifier).account_id(), ); assert_eq!( AccountId::for_private_account( @@ -1094,7 +1095,8 @@ mod tests { identifier } ), - AccountId::for_private_pda(&program_id, &seed, &npk, &vpk, identifier), + PrivateAddressPlaintext::new(npk, vpk.clone(), identifier) + .pda_account_id(&program_id, &seed), ); } diff --git a/lee/state_machine/src/lib.rs b/lee/state_machine/src/lib.rs index 129821b5..9adac01f 100644 --- a/lee/state_machine/src/lib.rs +++ b/lee/state_machine/src/lib.rs @@ -5,7 +5,7 @@ pub use lee_core::{ GENESIS_BLOCK_ID, SharedSecretKey, - account::{Account, AccountId, Data}, + account::{Account, AccountId, Data, PrivateAddressPlaintext}, encryption::EphemeralPublicKey, program::ProgramId, }; diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit.rs index 4f2597c4..8c6c0785 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit.rs @@ -180,7 +180,9 @@ mod tests { use lee_core::{ Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, EphemeralSecretKey, Nullifier, PrivacyPreservingCircuitOutput, SharedSecretKey, - account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, + account::{ + Account, AccountId, AccountWithMetadata, Nonce, PrivateAddressPlaintext, data::Data, + }, program::{PdaSeed, PrivateAccountKind}, }; @@ -236,7 +238,8 @@ mod tests { ); let recipient_account_id = - AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0); + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let balance_to_move: u128 = 37; @@ -316,14 +319,15 @@ mod tests { data: Data::default(), }, true, - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let sender_account_id = - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0); + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(); let commitment_sender = Commitment::new(&sender_account_id, &sender_pre.account); let recipient_account_id = - AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0); + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let balance_to_move: u128 = 37; @@ -429,7 +433,7 @@ mod tests { let pre = AccountWithMetadata::new( Account::default(), false, - AccountId::for_regular_private_account(&account_keys.npk(), &account_keys.vpk(), 0), + PrivateAddressPlaintext::new(account_keys.npk(), account_keys.vpk(), 0).account_id(), ); let validity_window_chain_caller = Program::validity_window_chain_caller(); @@ -473,8 +477,8 @@ mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let identifier: u128 = 99; - let account_id = - AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), identifier); + let account_id = PrivateAddressPlaintext::new(npk, keys.vpk(), identifier) + .pda_account_id(&program.id(), &seed); 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; @@ -517,7 +521,8 @@ mod tests { let seed = PdaSeed::new([42; 32]); // PDA (new, private PDA) - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 0); + let pda_id = + PrivateAddressPlaintext::new(npk, keys.vpk(), 0).pda_account_id(&program.id(), &seed); let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id); let auth_id = auth_transfer.id(); @@ -556,7 +561,8 @@ mod tests { let seed = PdaSeed::new([42; 32]); // PDA (new, private PDA) - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 0); + let pda_id = + PrivateAddressPlaintext::new(npk, keys.vpk(), 0).pda_account_id(&program.id(), &seed); let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id); // Recipient (public) @@ -624,7 +630,8 @@ mod tests { // Recipient: shared private account (new, unauthorized) let shared_account_id = - AccountId::from((&shared_npk, &shared_keys.vpk(), shared_identifier)); + PrivateAddressPlaintext::new(shared_npk, shared_keys.vpk(), shared_identifier) + .account_id(); let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id); let balance_to_move: u128 = 100; @@ -662,7 +669,7 @@ mod tests { let keys = test_private_account_keys_1(); let identifier: u128 = 99; let account_id = - AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + PrivateAddressPlaintext::new(keys.npk(), keys.vpk(), identifier).account_id(); let nonce = Nonce::private_account_nonce_init(&account_id); let esk = EphemeralSecretKey::new(&account_id, &[0; 32], &nonce); let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0; @@ -696,7 +703,7 @@ mod tests { let keys = test_private_account_keys_1(); let identifier: u128 = 99; let account_id = - AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + PrivateAddressPlaintext::new(keys.npk(), keys.vpk(), identifier).account_id(); let pre = AccountWithMetadata::new(Account::default(), true, account_id); let (output, _) = execute_and_prove( @@ -742,7 +749,7 @@ mod tests { let identifier: u128 = 99; // create an account id with one set of viewing keys let account_id = - AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + PrivateAddressPlaintext::new(keys.npk(), keys.vpk(), identifier).account_id(); let pre = AccountWithMetadata::new(Account::default(), true, account_id); let result = execute_and_prove( @@ -771,7 +778,7 @@ mod tests { let keys = test_private_account_keys_1(); let identifier: u128 = 99; let recipient_id = - AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + PrivateAddressPlaintext::new(keys.npk(), keys.vpk(), identifier).account_id(); let init_nonce = Nonce::private_account_nonce_init(&recipient_id); let esk = EphemeralSecretKey::new(&recipient_id, &[0; 32], &init_nonce); let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0; @@ -820,7 +827,7 @@ mod tests { let keys = test_private_account_keys_1(); let identifier: u128 = 99; let account_id = - AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + PrivateAddressPlaintext::new(keys.npk(), keys.vpk(), identifier).account_id(); let update_nonce = Nonce::default().private_account_nonce_increment(&keys.nsk); let esk = EphemeralSecretKey::new(&account_id, &[0; 32], &update_nonce); let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0; @@ -873,8 +880,8 @@ mod tests { let seed = PdaSeed::new([42; 32]); let identifier: u128 = 99; let auth_transfer_id = auth_transfer.id(); - let pda_id = - AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), identifier); + let pda_id = PrivateAddressPlaintext::new(npk, keys.vpk(), identifier) + .pda_account_id(&program.id(), &seed); let update_nonce = Nonce::default().private_account_nonce_increment(&keys.nsk); let esk = EphemeralSecretKey::new(&pda_id, &[0; 32], &update_nonce); let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0; @@ -930,7 +937,8 @@ mod tests { let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); - let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5); + let account_id = + PrivateAddressPlaintext::new(npk, keys.vpk(), 5).pda_account_id(&program.id(), &seed); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let result = execute_and_prove( @@ -957,7 +965,8 @@ mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let auth_transfer_id = auth_transfer.id(); - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), 5); + let pda_id = + PrivateAddressPlaintext::new(npk, keys.vpk(), 5).pda_account_id(&program.id(), &seed); let pda_account = Account { program_owner: auth_transfer_id, balance: 1, diff --git a/lee/state_machine/src/privacy_preserving_transaction/message.rs b/lee/state_machine/src/privacy_preserving_transaction/message.rs index 73e62021..72f15bf3 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/message.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/message.rs @@ -119,10 +119,12 @@ pub mod tests { let encrypted_private_post_states = Vec::new(); - let account_id2 = lee_core::account::AccountId::for_regular_private_account(&npk2, &vpk, 0); + let account_id2 = + lee_core::account::PrivateAddressPlaintext::new(npk2, vpk.clone(), 0).account_id(); let new_commitments = vec![Commitment::new(&account_id2, &account2)]; - let account_id1 = lee_core::account::AccountId::for_regular_private_account(&npk1, &vpk, 0); + let account_id1 = + lee_core::account::PrivateAddressPlaintext::new(npk1, vpk, 0).account_id(); let old_commitment = Commitment::new(&account_id1, &account1); let new_nullifiers = vec![( Nullifier::for_account_update(&old_commitment, &nsk1), @@ -198,7 +200,8 @@ 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, &vpk, 0); + let account_id = + lee_core::account::PrivateAddressPlaintext::new(npk, vpk.clone(), 0).account_id(); let commitment = Commitment::new(&account_id, &account); let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&vpk, &EphemeralSecretKey([0_u8; 32])); diff --git a/lee/state_machine/src/state.rs b/lee/state_machine/src/state.rs index becb14ff..43353662 100644 --- a/lee/state_machine/src/state.rs +++ b/lee/state_machine/src/state.rs @@ -420,7 +420,9 @@ pub mod tests { use lee_core::{ BlockId, Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, NullifierSecretKey, Timestamp, - account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, + account::{ + Account, AccountId, AccountWithMetadata, Nonce, PrivateAddressPlaintext, data::Data, + }, encryption::ViewingPublicKey, program::{ BlockValidityWindow, ExecutionValidationError, PdaSeed, ProgramId, @@ -517,7 +519,7 @@ pub mod tests { #[must_use] pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self { - let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), 0); + let account_id = PrivateAddressPlaintext::new(keys.npk(), keys.vpk(), 0).account_id(); let commitment = Commitment::new(&account_id, account); self.private_state.0.extend(&[commitment]); self @@ -729,8 +731,8 @@ pub mod tests { ..Account::default() }; - 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 account_id1 = PrivateAddressPlaintext::new(keys1.npk(), keys1.vpk(), 0).account_id(); + let account_id2 = PrivateAddressPlaintext::new(keys2.npk(), keys2.vpk(), 0).account_id(); let init_commitment1 = Commitment::new(&account_id1, &account); let init_commitment2 = Commitment::new(&account_id2, &account); @@ -1406,7 +1408,8 @@ pub mod tests { let recipient = AccountWithMetadata::new( Account::default(), false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let (output, proof) = circuit::execute_and_prove( @@ -1448,17 +1451,18 @@ pub mod tests { ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_account_id = - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0); + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_pre = AccountWithMetadata::new( sender_private_account.clone(), true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let recipient_pre = AccountWithMetadata::new( Account::default(), false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let (output, proof) = circuit::execute_and_prove( @@ -1504,12 +1508,12 @@ pub mod tests { ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_account_id = - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0); + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_pre = AccountWithMetadata::new( sender_private_account.clone(), true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let recipient_pre = AccountWithMetadata::new( state.get_account_by_id(*recipient_account_id), @@ -1615,9 +1619,10 @@ pub mod tests { ); let sender_account_id = - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0); + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(); let recipient_account_id = - AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0); + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(); let expected_new_commitment_1 = Commitment::new( &sender_account_id, &Account { @@ -1769,7 +1774,7 @@ pub mod tests { ); let sender_account_id = - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0); + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(); let expected_new_commitment = Commitment::new( &sender_account_id, &Account { @@ -2081,12 +2086,13 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let private_account_2 = AccountWithMetadata::new( Account::default(), false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); // Setting the recipient nsk to authorize the sender. @@ -2129,7 +2135,7 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let private_account_2 = AccountWithMetadata::new( Account { @@ -2138,7 +2144,8 @@ pub mod tests { ..Account::default() }, false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let result = execute_and_prove( @@ -2177,7 +2184,7 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let private_account_2 = AccountWithMetadata::new( Account { @@ -2186,7 +2193,8 @@ pub mod tests { ..Account::default() }, false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let result = execute_and_prove( @@ -2225,7 +2233,7 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let private_account_2 = AccountWithMetadata::new( Account { @@ -2234,7 +2242,8 @@ pub mod tests { ..Account::default() }, false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let result = execute_and_prove( @@ -2273,7 +2282,7 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let private_account_2 = AccountWithMetadata::new( Account { @@ -2282,7 +2291,8 @@ pub mod tests { ..Account::default() }, false, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let result = execute_and_prove( @@ -2322,13 +2332,14 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let private_account_2 = AccountWithMetadata::new( Account::default(), // This should be set to false in normal circumstances true, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let result = execute_and_prove( @@ -2397,9 +2408,10 @@ pub mod tests { /// Happy path: a program claims a new private PDA via `Claim::Pda(seed)`. The circuit /// reads the npk for that `pre_state` from `private_account_keys` at the `pre_state`'s - /// position, derives `AccountId` via `AccountId::for_private_pda(program_id, seed, npk)`, and - /// asserts it equals the `pre_state`'s `account_id`. The equality both validates the claim - /// and binds the supplied npk to the `account_id`. + /// position, derives `AccountId` via `PrivateAddressPlaintext::new(npk, + /// ..).pda_account_id(program_id, seed)`, and asserts it equals the `pre_state`'s + /// `account_id`. The equality both validates the claim and binds the supplied npk to the + /// `account_id`. #[test] fn private_pda_claim_succeeds() { let program = Program::pda_claimer(); @@ -2407,8 +2419,8 @@ pub mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); - let account_id = - AccountId::for_private_pda(&program.id(), &seed, &npk, &keys.vpk(), u128::MAX); + let account_id = PrivateAddressPlaintext::new(npk, keys.vpk(), u128::MAX) + .pda_account_id(&program.id(), &seed); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let result = execute_and_prove( @@ -2433,7 +2445,8 @@ pub mod tests { } /// An npk is supplied that does not match the `pre_state`'s `account_id` under - /// `AccountId::for_private_pda(program, claim_seed, npk)`. The claim equality check rejects. + /// `PrivateAddressPlaintext::new(npk, ..).pda_account_id(program, claim_seed)`. The claim + /// equality check rejects. #[test] fn private_pda_npk_mismatch_fails() { // `keys_a` produces the `pre_state`'s `account_id` (the registered pair), `keys_b` is @@ -2446,10 +2459,10 @@ pub mod tests { let seed = PdaSeed::new([42; 32]); // `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, &keys_a.vpk(), u128::MAX); + // `PrivateAddressPlaintext::new(npk_b, ..).pda_account_id(program, seed) != account_id`, so + // the claim check in the circuit must reject. + let account_id = PrivateAddressPlaintext::new(npk_a, keys_a.vpk(), u128::MAX) + .pda_account_id(&program.id(), &seed); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let result = execute_and_prove( @@ -2472,7 +2485,7 @@ pub mod tests { /// private PDA via `Claim::Pda(seed)`, then chains to a callee (`noop`) delegating the same /// seed via `ChainedCall.pda_seeds`. In the callee's step, the `pre_state`'s authorization /// is established via the private derivation - /// `AccountId::for_private_pda(delegator, seed, npk) == pre.account_id`. + /// `PrivateAddressPlaintext::new(npk, ..).pda_account_id(delegator, seed) == pre.account_id`. #[test] fn caller_pda_seeds_authorize_private_pda_for_callee() { let delegator = Program::private_pda_delegator(); @@ -2481,8 +2494,8 @@ pub mod tests { let npk = keys.npk(); let seed = PdaSeed::new([77; 32]); - let account_id = - AccountId::for_private_pda(&delegator.id(), &seed, &npk, &keys.vpk(), u128::MAX); + let account_id = PrivateAddressPlaintext::new(npk, keys.vpk(), u128::MAX) + .pda_account_id(&delegator.id(), &seed); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let callee_id = callee.id(); @@ -2521,8 +2534,8 @@ pub mod tests { let claim_seed = PdaSeed::new([77; 32]); let wrong_delegated_seed = PdaSeed::new([88; 32]); - let account_id = - AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk, &keys.vpk(), u128::MAX); + let account_id = PrivateAddressPlaintext::new(npk, keys.vpk(), u128::MAX) + .pda_account_id(&delegator.id(), &claim_seed); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let callee_id = callee.id(); @@ -2560,20 +2573,10 @@ pub mod tests { let keys_b = test_private_account_keys_2(); let seed = PdaSeed::new([55; 32]); - 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 account_a = PrivateAddressPlaintext::new(keys_a.npk(), keys_a.vpk(), u128::MAX) + .pda_account_id(&program.id(), &seed); + let account_b = PrivateAddressPlaintext::new(keys_b.npk(), keys_b.vpk(), u128::MAX) + .pda_account_id(&program.id(), &seed); let pre_a = AccountWithMetadata::new(Account::default(), false, account_a); let pre_b = AccountWithMetadata::new(Account::default(), false, account_b); @@ -2617,8 +2620,8 @@ pub mod tests { // 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, &keys.vpk(), u128::MAX); + let account_id = PrivateAddressPlaintext::new(npk, keys.vpk(), u128::MAX) + .pda_account_id(&program.id(), &seed); let owned_pre_state = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2711,7 +2714,7 @@ pub mod tests { ..Account::default() }, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let result = execute_and_prove( @@ -3045,7 +3048,7 @@ pub mod tests { ..Account::default() }; let sender_account_id = - AccountId::for_regular_private_account(&sender_keys.npk(), &sender_keys.vpk(), 0); + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(); 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_genesis_accounts( @@ -3056,7 +3059,7 @@ pub mod tests { let sender_pre = AccountWithMetadata::new( sender_private_account, true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap(); let recipient_account_id = @@ -3129,7 +3132,7 @@ pub mod tests { ..Account::default() }, true, - (&from_keys.npk(), &from_keys.vpk(), 0), + PrivateAddressPlaintext::new(from_keys.npk(), from_keys.vpk(), 0).account_id(), ); let to_account = AccountWithMetadata::new( Account { @@ -3137,13 +3140,13 @@ pub mod tests { ..Account::default() }, true, - (&to_keys.npk(), &to_keys.vpk(), 0), + PrivateAddressPlaintext::new(to_keys.npk(), to_keys.vpk(), 0).account_id(), ); let from_account_id = - AccountId::for_regular_private_account(&from_keys.npk(), &from_keys.vpk(), 0); + PrivateAddressPlaintext::new(from_keys.npk(), from_keys.vpk(), 0).account_id(); let to_account_id = - AccountId::for_regular_private_account(&to_keys.npk(), &to_keys.vpk(), 0); + PrivateAddressPlaintext::new(to_keys.npk(), to_keys.vpk(), 0).account_id(); 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); @@ -3450,7 +3453,7 @@ pub mod tests { let authorized_account = AccountWithMetadata::new( Account::default(), true, - (&private_keys.npk(), &private_keys.vpk(), 0), + PrivateAddressPlaintext::new(private_keys.npk(), private_keys.vpk(), 0).account_id(), ); let program = Program::authenticated_transfer_program(); @@ -3483,7 +3486,7 @@ pub mod tests { assert!(result.is_ok()); let account_id = - AccountId::for_regular_private_account(&private_keys.npk(), &private_keys.vpk(), 0); + PrivateAddressPlaintext::new(private_keys.npk(), private_keys.vpk(), 0).account_id(); let nullifier = Nullifier::for_account_initialization(&account_id); assert!(state.private_state.1.contains(&nullifier)); } @@ -3500,7 +3503,7 @@ pub mod tests { let unauthorized_account = AccountWithMetadata::new( Account::default(), false, - (&private_keys.npk(), &private_keys.vpk(), 0), + PrivateAddressPlaintext::new(private_keys.npk(), private_keys.vpk(), 0).account_id(), ); let program = Program::claimer(); @@ -3528,7 +3531,7 @@ pub mod tests { .unwrap(); let account_id = - AccountId::for_regular_private_account(&private_keys.npk(), &private_keys.vpk(), 0); + PrivateAddressPlaintext::new(private_keys.npk(), private_keys.vpk(), 0).account_id(); let nullifier = Nullifier::for_account_initialization(&account_id); assert!(state.private_state.1.contains(&nullifier)); } @@ -3544,7 +3547,7 @@ pub mod tests { let authorized_account = AccountWithMetadata::new( Account::default(), true, - (&private_keys.npk(), &private_keys.vpk(), 0), + PrivateAddressPlaintext::new(private_keys.npk(), private_keys.vpk(), 0).account_id(), ); let claimer_program = Program::claimer(); @@ -3581,7 +3584,7 @@ pub mod tests { // Verify the account is now initialized (nullifier exists) let account_id = - AccountId::for_regular_private_account(&private_keys.npk(), &private_keys.vpk(), 0); + PrivateAddressPlaintext::new(private_keys.npk(), private_keys.vpk(), 0).account_id(); let nullifier = Nullifier::for_account_initialization(&account_id); assert!(state.private_state.1.contains(&nullifier)); @@ -3671,7 +3674,7 @@ pub mod tests { let private_account = AccountWithMetadata::new( Account::default(), true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); // Don't change data (None) and don't claim (false) let instruction: (Option>, bool) = (None, false); @@ -3700,7 +3703,7 @@ pub mod tests { let private_account = AccountWithMetadata::new( Account::default(), true, - (&sender_keys.npk(), &sender_keys.vpk(), 0), + PrivateAddressPlaintext::new(sender_keys.npk(), sender_keys.vpk(), 0).account_id(), ); // Change data but don't claim (false) - should fail let new_data = vec![1, 2, 3, 4, 5]; @@ -3743,11 +3746,13 @@ pub mod tests { let recipient_account = AccountWithMetadata::new( Account::default(), true, - (&recipient_keys.npk(), &recipient_keys.vpk(), 0), + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(), ); let recipient_account_id = - AccountId::for_regular_private_account(&recipient_keys.npk(), &recipient_keys.vpk(), 0); + PrivateAddressPlaintext::new(recipient_keys.npk(), recipient_keys.vpk(), 0) + .account_id(); let recipient_commitment = Commitment::new(&recipient_account_id, &recipient_account.account); let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id); @@ -3916,7 +3921,7 @@ pub mod tests { let pre = AccountWithMetadata::new( Account::default(), false, - (&account_keys.npk(), &account_keys.vpk(), 0), + PrivateAddressPlaintext::new(account_keys.npk(), account_keys.vpk(), 0).account_id(), ); let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); let tx = { @@ -3981,7 +3986,7 @@ pub mod tests { let pre = AccountWithMetadata::new( Account::default(), false, - (&account_keys.npk(), &account_keys.vpk(), 0), + PrivateAddressPlaintext::new(account_keys.npk(), account_keys.vpk(), 0).account_id(), ); let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); let tx = { @@ -4512,10 +4517,10 @@ pub mod tests { ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer.clone())].into()); let funder_id = funder_keys.account_id(); - 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 alice_pda_0_id = PrivateAddressPlaintext::new(alice_npk, alice_keys.vpk(), 0) + .pda_account_id(&proxy_id, &seed); + let alice_pda_1_id = PrivateAddressPlaintext::new(alice_npk, alice_keys.vpk(), 1) + .pda_account_id(&proxy_id, &seed); let recipient_id = test_public_account_keys_2().account_id(); let recipient_signing_key = test_public_account_keys_2().signing_key; diff --git a/lee/state_machine/src/validated_state_diff.rs b/lee/state_machine/src/validated_state_diff.rs index 1953d93a..7befe5d9 100644 --- a/lee/state_machine/src/validated_state_diff.rs +++ b/lee/state_machine/src/validated_state_diff.rs @@ -511,7 +511,7 @@ fn n_unique(data: &[T]) -> usize { #[cfg(test)] mod tests { - use lee_core::account::{AccountId, Nonce}; + use lee_core::account::{AccountId, Nonce, PrivateAddressPlaintext}; use crate::{ PrivateKey, PublicKey, V03State, @@ -603,7 +603,7 @@ mod tests { // Attacker controls a private account. let attacker_keys = test_private_account_keys_1(); let attacker_id = - AccountId::for_regular_private_account(&attacker_keys.npk(), &attacker_keys.vpk(), 0); + PrivateAddressPlaintext::new(attacker_keys.npk(), attacker_keys.vpk(), 0).account_id(); let victim_id = AccountId::new([20_u8; 32]); let recipient_id = AccountId::new([42_u8; 32]); @@ -709,9 +709,10 @@ mod tests { /// There are two routes, both closed: /// /// - **mask=1 (`PrivateAuthorizedUpdate`)**: the circuit derives `account_id = - /// AccountId::for_regular_private_account(&npk_from(nsk), identifier)` and asserts it matches - /// `pre_state.account_id`. Passing this check requires the victim's `nsk`, which the attacker - /// does not have. `execute_and_prove` panics inside the ZKVM and no proof is produced. + /// PrivateAddressPlaintext::new(npk_from(nsk), vpk, identifier).account_id()` and asserts it + /// matches `pre_state.account_id`. Passing this check requires the victim's `nsk`, which the + /// attacker does not have. `execute_and_prove` panics inside the ZKVM and no proof is + /// produced. /// /// - **mask=0 (`Public`)**: the circuit places the account in `public_pre_states` and /// `execute_and_prove` succeeds. The host-side validator then reconstructs @@ -754,12 +755,12 @@ mod tests { // Attacker controls a private account. let attacker_keys = test_private_account_keys_1(); let attacker_id = - AccountId::for_regular_private_account(&attacker_keys.npk(), &attacker_keys.vpk(), 0); + PrivateAddressPlaintext::new(attacker_keys.npk(), attacker_keys.vpk(), 0).account_id(); // 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(), &victim_keys.vpk(), 0); + PrivateAddressPlaintext::new(victim_keys.npk(), victim_keys.vpk(), 0).account_id(); let victim_balance = 5_000_u128; let recipient_id = AccountId::new([42_u8; 32]); diff --git a/lez/sequencer/core/src/lib.rs b/lez/sequencer/core/src/lib.rs index e185b05f..8f2ee154 100644 --- a/lez/sequencer/core/src/lib.rs +++ b/lez/sequencer/core/src/lib.rs @@ -819,7 +819,7 @@ mod tests { }; use key_protocol::key_management::KeyChain; use lee::{ - Account, AccountId, Data, PrivacyPreservingTransaction, V03State, + Account, Data, PrivacyPreservingTransaction, PrivateAddressPlaintext, V03State, error::LeeError, execute_and_prove, privacy_preserving_transaction::{Message, circuit::ProgramWithDependencies}, @@ -1604,11 +1604,12 @@ mod tests { #[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, - &sender_keys.viewing_public_key, + let sender_account_id = PrivateAddressPlaintext::new( + sender_keys.nullifier_public_key, + sender_keys.viewing_public_key.clone(), 0, - ); + ) + .account_id(); let sender_private_account = Account { program_owner: Program::authenticated_transfer_program().id(), balance: 100, @@ -1631,11 +1632,12 @@ mod tests { let sender_pre = AccountWithMetadata::new( sender_private_account, true, - ( - &sender_keys.nullifier_public_key, - &sender_keys.viewing_public_key, + PrivateAddressPlaintext::new( + sender_keys.nullifier_public_key, + sender_keys.viewing_public_key.clone(), 0, - ), + ) + .account_id(), ); let bridge_pre = AccountWithMetadata::new( state.get_account_by_id(bridge_account_id), diff --git a/lez/testnet_initial_state/src/lib.rs b/lez/testnet_initial_state/src/lib.rs index 663b89ff..f71359ef 100644 --- a/lez/testnet_initial_state/src/lib.rs +++ b/lez/testnet_initial_state/src/lib.rs @@ -106,11 +106,12 @@ pub struct PrivateAccountPrivateInitialData { impl PrivateAccountPrivateInitialData { #[must_use] 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, + lee::PrivateAddressPlaintext::new( + self.key_chain.nullifier_public_key, + self.key_chain.viewing_public_key.clone(), self.identifier, ) + .account_id() } } @@ -219,7 +220,8 @@ pub fn initial_state() -> V03State { .map(|init_comm_data| { let npk = &init_comm_data.npk; let account_id = - lee::AccountId::for_regular_private_account(npk, &init_comm_data.vpk, 0); + lee::PrivateAddressPlaintext::new(*npk, init_comm_data.vpk.clone(), 0) + .account_id(); let mut acc = init_comm_data.account.clone(); diff --git a/lez/wallet/src/account_manager.rs b/lez/wallet/src/account_manager.rs index f77d1062..226d4b5b 100644 --- a/lez/wallet/src/account_manager.rs +++ b/lez/wallet/src/account_manager.rs @@ -2,7 +2,7 @@ use core::fmt; use anyhow::Result; use keycard_wallet::{KeycardWallet, python_path}; -use lee::{AccountId, PrivateKey, PublicKey, Signature}; +use lee::{AccountId, PrivateAddressPlaintext, PrivateKey, PublicKey, Signature}; use lee_core::{ Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, @@ -30,7 +30,7 @@ pub enum AccountIdentity { identifier: Identifier, }, /// An owned private PDA: wallet holds the nsk/npk; `account_id` was derived via - /// [`AccountId::for_private_pda`]. + /// [`PrivateAddressPlaintext::pda_account_id`]. PrivatePdaOwned(AccountId), /// A foreign private PDA: wallet knows the recipient's npk/vpk but not their nsk. /// Uses a default (uninitialised) account. @@ -50,7 +50,7 @@ pub enum AccountIdentity { identifier: Identifier, }, /// A shared private PDA with externally-provided keys (e.g. from GMS). - /// `account_id` was derived via [`AccountId::for_private_pda`]. + /// `account_id` was derived via [`PrivateAddressPlaintext::pda_account_id`]. PrivatePdaShared { account_id: AccountId, nsk: NullifierSecretKey, @@ -261,7 +261,11 @@ impl AccountManager { identifier, } => { let acc = lee_core::account::Account::default(); - let auth_acc = AccountWithMetadata::new(acc, false, (&npk, &vpk, identifier)); + let auth_acc = AccountWithMetadata::new( + acc, + false, + PrivateAddressPlaintext::new(npk, vpk.clone(), identifier).account_id(), + ); let mut random_seed: [u8; 32] = [0; 32]; OsRng.fill_bytes(&mut random_seed); let pre = AccountPreparedData { @@ -309,7 +313,9 @@ impl AccountManager { vpk, identifier, } => { - let account_id = lee::AccountId::from((&npk, &vpk, identifier)); + let account_id = + lee::PrivateAddressPlaintext::new(npk, vpk.clone(), identifier) + .account_id(); let pre = private_shared_acc_preparation( wallet, account_id, nsk, npk, vpk, identifier, false, ) diff --git a/lez/wallet/src/cli/account.rs b/lez/wallet/src/cli/account.rs index 2f3dfa9f..42e3df4c 100644 --- a/lez/wallet/src/cli/account.rs +++ b/lez/wallet/src/cli/account.rs @@ -525,11 +525,12 @@ 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, - &key_chain.viewing_public_key, + let account_id = lee::PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key.clone(), identifier, - )); + ) + .account_id(); wallet_core .storage_mut() diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index 010e438e..d71a795b 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -17,7 +17,7 @@ use common::{HashType, transaction::LeeTransaction}; use config::WalletConfig; use key_protocol::key_management::key_tree::chain_index::ChainIndex; use lee::{ - Account, AccountId, PrivacyPreservingTransaction, + Account, AccountId, PrivacyPreservingTransaction, PrivateAddressPlaintext, privacy_preserving_transaction::{ circuit::ProgramWithDependencies, message::EncryptedAccountData, }, @@ -377,7 +377,8 @@ 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, &vpk, identifier); + let account_id = PrivateAddressPlaintext::new(npk, vpk.clone(), identifier) + .pda_account_id(&program_id, &pda_seed); self.register_shared_account( account_id, @@ -419,7 +420,7 @@ impl WalletCore { let keys = holder.derive_keys_for_shared_account(&derivation_seed); let npk = keys.generate_nullifier_public_key(); let vpk = keys.generate_viewing_public_key(); - let account_id = AccountId::from((&npk, &vpk, identifier)); + let account_id = PrivateAddressPlaintext::new(npk, vpk.clone(), identifier).account_id(); self.register_shared_account(account_id, group_name, identifier, None, None); diff --git a/lez/wallet/src/storage/key_chain.rs b/lez/wallet/src/storage/key_chain.rs index 5db90119..bfecb9d6 100644 --- a/lez/wallet/src/storage/key_chain.rs +++ b/lez/wallet/src/storage/key_chain.rs @@ -696,6 +696,8 @@ impl Default for UserKeyChain { #[cfg(test)] mod tests { + use lee::PrivateAddressPlaintext; + use super::*; #[test] @@ -734,11 +736,12 @@ 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, - &key_chain.viewing_public_key, + let account_id = PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key.clone(), 0, - )); + ) + .account_id(); let account = lee_core::account::Account::default(); user_data.add_imported_private_account(key_chain, None, 0, account); @@ -753,11 +756,12 @@ 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, - &key_chain.viewing_public_key, + let account_id = PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key.clone(), 0, - )); + ) + .account_id(); let account = lee_core::account::Account::default(); user_data.add_imported_private_account(key_chain, None, 0, account.clone()); @@ -802,11 +806,12 @@ 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, - &key_chain.viewing_public_key, + let account_id = PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key, 0, - )); + ) + .account_id(); let new_account = lee_core::account::Account { balance: 100, @@ -827,11 +832,12 @@ 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, - &key_chain.viewing_public_key, + let account_id1 = PrivateAddressPlaintext::new( + key_chain.nullifier_public_key, + key_chain.viewing_public_key.clone(), 0, - )); + ) + .account_id(); let account = lee_core::account::Account::default(); user_data.add_imported_private_account(key_chain, None, 0, account); diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs b/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs index 09ad30af..1cc3dc37 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs @@ -4,9 +4,8 @@ use std::{ }; use lee_core::{ - Identifier, InputAccountIdentity, NullifierPublicKey, - account::{Account, AccountId, AccountWithMetadata}, - encryption::ViewingPublicKey, + InputAccountIdentity, + account::{Account, AccountId, AccountWithMetadata, PrivateAddressPlaintext}, program::{ AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow, @@ -22,15 +21,15 @@ 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, 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` - /// under the private derivation. Binding is an idempotent property, not an event: the same - /// position can legitimately be bound through both paths in the same tx (e.g. a program - /// claims a private PDA and then delegates it to a callee), and the map uses `contains_key`, - /// not `assert!(insert)`. After the main loop, every private-PDA position must appear in this - /// map; otherwise the npk is unbound and the circuit rejects. + /// their `AccountId` via a proven + /// `PrivateAddressPlaintext::new(npk, vpk, identifier).pda_account_id(program_id, seed)` + /// 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` under the private derivation. Binding is an idempotent property, not + /// an event: the same position can legitimately be bound through both paths in the same tx + /// (e.g. a program claims a private PDA and then delegates it to a callee), and the map + /// uses `contains_key`, not `assert!(insert)`. After the main loop, every private-PDA + /// position must appear in this map; otherwise the npk is unbound and the circuit rejects. /// The stored `(ProgramId, PdaSeed)` is the owner program and seed, used in /// `compute_circuit_output` to construct `PrivateAccountKind::Pda { program_id, seed, /// identifier }`. @@ -44,13 +43,12 @@ 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, vpk, - /// identifier) supplied for that position. Built once in `derive_from_outputs` by walking - /// `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, vpk, identifier) == - /// pre_state.account_id`. - private_pda_by_position: HashMap, + /// Map from a private-PDA `pre_state`'s position in `account_identities` to the + /// `PrivateAddressPlaintext` supplied for that position. Built once in `derive_from_outputs` + /// by walking `account_identities` and consulting `private_pda_address`. Used later by the + /// claim and caller-seeds authorization paths to verify + /// `address.pda_account_id(program_id, seed) == pre_state.account_id`. + private_pda_by_position: HashMap, authorized_accounts: HashSet, } @@ -61,17 +59,14 @@ impl ExecutionState { program_id: ProgramId, program_outputs: Vec, ) -> Self { - // 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 + // Build position → `PrivateAddressPlaintext` 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(); + let mut private_pda_by_position: HashMap = 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)); + if let Some(address) = account_identity.private_pda_address() { + private_pda_by_position.insert(pos, address); } } @@ -312,19 +307,16 @@ impl ExecutionState { 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, - ); + let expected = self + .private_pda_by_position + .get(&pre_state_position) + .expect( + "private PDA pre_state must have an address in the position map", + ) + .pda_account_id(authority_program_id, seed); assert_eq!( pre_account_id, expected, "External seed mismatch for PrivatePdaInit at position {pre_state_position}" @@ -332,20 +324,16 @@ impl ExecutionState { 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, - ); + let expected = self + .private_pda_by_position + .get(&pre_state_position) + .expect( + "private PDA pre_state must have an address in the position map", + ) + .pda_account_id(authority_program_id, seed); assert_eq!( pre_account_id, expected, "External seed mismatch for PrivatePdaUpdate at position {pre_state_position}" @@ -424,19 +412,13 @@ impl ExecutionState { match claim { Claim::Authorized => {} Claim::Pda(seed) => { - let (npk, vpk, identifier) = self + let pda = 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, - ); + "private PDA pre_state must have an address in the position map", + ) + .pda_account_id(&program_id, &seed); assert_eq!( pre_account_id, pda, "Invalid private PDA claim for account {pre_account_id}" @@ -561,7 +543,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, - private_pda_by_position: &HashMap, + private_pda_by_position: &HashMap, authorized_accounts: &mut HashSet, pre_account_id: AccountId, pre_state_position: usize, @@ -575,10 +557,8 @@ 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, vpk, identifier)) = - private_pda_by_position.get(&pre_state_position) - && AccountId::for_private_pda(&caller, seed, npk, vpk, *identifier) - == pre_account_id + if let Some(address) = private_pda_by_position.get(&pre_state_position) + && address.pda_account_id(&caller, seed) == pre_account_id { return Some((*seed, true, caller)); } diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/main.rs b/program_methods/guest/src/bin/privacy_preserving_circuit/main.rs index a342665d..c09072e9 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit/main.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit/main.rs @@ -17,7 +17,7 @@ fn main() { program_outputs, ); - let output = output::compute_circuit_output(execution_state, &account_identities); + let output = output::compute_circuit_output(execution_state, account_identities); env::commit(&output); } diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs b/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs index c799a1db..c5fe0570 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs @@ -2,7 +2,7 @@ use lee_core::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey, - account::{Account, AccountId, Nonce}, + account::{Account, AccountId, Nonce, PrivateAddressPlaintext}, compute_digest_for_path, encryption::ViewingPublicKey, }; @@ -11,7 +11,7 @@ use crate::execution_state::ExecutionState; pub fn compute_circuit_output( execution_state: ExecutionState, - account_identities: &[InputAccountIdentity], + account_identities: Vec, ) -> PrivacyPreservingCircuitOutput { let (block_validity_window, timestamp_validity_window, pda_seed_by_position, states_iter) = execution_state.into_parts(); @@ -33,7 +33,7 @@ pub fn compute_circuit_output( let mut output_index = 0; for (pos, (account_identity, (pre_state, post_state))) in - account_identities.iter().zip(states_iter).enumerate() + account_identities.into_iter().zip(states_iter).enumerate() { match account_identity { InputAccountIdentity::Public => { @@ -46,8 +46,9 @@ pub fn compute_circuit_output( nsk, identifier, } => { - let npk = NullifierPublicKey::from(nsk); - let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier); + let address = + PrivateAddressPlaintext::new(NullifierPublicKey::from(&nsk), vpk, identifier); + let account_id = address.account_id(); assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); assert!( @@ -71,10 +72,10 @@ pub fn compute_circuit_output( &mut output_index, post_state, &account_id, - &PrivateAccountKind::Regular(*identifier), - &npk, - vpk, - random_seed, + &PrivateAccountKind::Regular(address.identifier), + &address.npk, + &address.vpk, + &random_seed, new_nullifier, new_nonce, ); @@ -86,8 +87,9 @@ pub fn compute_circuit_output( membership_proof, identifier, } => { - let npk = NullifierPublicKey::from(nsk); - let account_id = AccountId::for_regular_private_account(&npk, vpk, *identifier); + let address = + PrivateAddressPlaintext::new(NullifierPublicKey::from(&nsk), vpk, identifier); + let account_id = address.account_id(); assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); assert!( @@ -96,22 +98,25 @@ pub fn compute_circuit_output( ); let new_nullifier = compute_update_nullifier_and_set_digest( - membership_proof, + &membership_proof, &pre_state.account, &account_id, - nsk, + &nsk, ); - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(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), - &npk, - vpk, - random_seed, + &PrivateAccountKind::Regular(address.identifier), + &address.npk, + &address.vpk, + &random_seed, new_nullifier, new_nonce, ); @@ -122,7 +127,8 @@ pub fn compute_circuit_output( npk, identifier, } => { - let account_id = AccountId::for_regular_private_account(npk, vpk, *identifier); + let address = PrivateAddressPlaintext::new(npk, vpk, identifier); + let account_id = address.account_id(); assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); assert_eq!( @@ -146,10 +152,10 @@ pub fn compute_circuit_output( &mut output_index, post_state, &account_id, - &PrivateAccountKind::Regular(*identifier), - npk, - vpk, - random_seed, + &PrivateAccountKind::Regular(address.identifier), + &address.npk, + &address.vpk, + &random_seed, new_nullifier, new_nonce, ); @@ -195,11 +201,11 @@ pub fn compute_circuit_output( &PrivateAccountKind::Pda { program_id: *authority_program_id, seed: *seed, - identifier: *identifier, + identifier, }, - npk, - vpk, - random_seed, + &npk, + &vpk, + &random_seed, new_nullifier, new_nonce, ); @@ -223,15 +229,18 @@ pub fn compute_circuit_output( ); let new_nullifier = compute_update_nullifier_and_set_digest( - membership_proof, + &membership_proof, &pre_state.account, &pre_state.account_id, - nsk, + &nsk, ); - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + let new_nonce = pre_state + .account + .nonce + .private_account_nonce_increment(&nsk); let account_id = pre_state.account_id; - let npk = NullifierPublicKey::from(nsk); + let npk = NullifierPublicKey::from(&nsk); let (authority_program_id, seed) = pda_seed_by_position .get(&pos) .expect("PrivatePdaUpdate position must be in pda_seed_by_position"); @@ -243,11 +252,11 @@ pub fn compute_circuit_output( &PrivateAccountKind::Pda { program_id: *authority_program_id, seed: *seed, - identifier: *identifier, + identifier, }, &npk, - vpk, - random_seed, + &vpk, + &random_seed, new_nullifier, new_nonce, ); diff --git a/test_fixtures/src/config.rs b/test_fixtures/src/config.rs index 5dee5a4b..dd48b5d1 100644 --- a/test_fixtures/src/config.rs +++ b/test_fixtures/src/config.rs @@ -4,7 +4,7 @@ use anyhow::{Context as _, Result}; use bytesize::ByteSize; use indexer_service::{ChannelId, ClientConfig, IndexerConfig}; use key_protocol::key_management::KeyChain; -use lee::{AccountId, PrivateKey, PublicKey}; +use lee::{AccountId, PrivateAddressPlaintext, PrivateKey, PublicKey}; use lee_core::Identifier; use sequencer_core::config::{BedrockConfig, GenesisAction, SequencerConfig}; use url::Url; @@ -23,11 +23,12 @@ pub struct InitialPrivateAccountForWallet { impl InitialPrivateAccountForWallet { #[must_use] pub fn account_id(&self) -> AccountId { - AccountId::from(( - &self.key_chain.nullifier_public_key, - &self.key_chain.viewing_public_key, + PrivateAddressPlaintext::new( + self.key_chain.nullifier_public_key, + self.key_chain.viewing_public_key.clone(), self.identifier, - )) + ) + .account_id() } } diff --git a/tools/crypto_primitives_bench/benches/primitives.rs b/tools/crypto_primitives_bench/benches/primitives.rs index 9a42305c..dade11e3 100644 --- a/tools/crypto_primitives_bench/benches/primitives.rs +++ b/tools/crypto_primitives_bench/benches/primitives.rs @@ -12,7 +12,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use key_protocol::key_management::KeyChain; use lee_core::{ Commitment, EncryptionScheme, SharedSecretKey, - account::{Account, AccountId}, + account::{Account, PrivateAddressPlaintext}, program::PrivateAccountKind, }; @@ -49,7 +49,7 @@ fn bench_encryption(c: &mut Criterion) { let npk = recipient_kc.nullifier_public_key; let account = Account::default(); let account_id = - AccountId::for_regular_private_account(&npk, &recipient_kc.viewing_public_key, 0); + PrivateAddressPlaintext::new(npk, recipient_kc.viewing_public_key.clone(), 0).account_id(); let commitment = Commitment::new(&account_id, &account); let (shared, _epk) = SharedSecretKey::encapsulate(&recipient_kc.viewing_public_key); let kind = PrivateAccountKind::Regular(0_u128);