diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 046f21bb..317fd705 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index cae6ed4e..dd841336 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index cebe1042..ec15731b 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/bridge.bin b/artifacts/program_methods/bridge.bin index 9810c6ac..69c18210 100644 Binary files a/artifacts/program_methods/bridge.bin and b/artifacts/program_methods/bridge.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/program_methods/clock.bin index 1124913f..ddbfea9b 100644 Binary files a/artifacts/program_methods/clock.bin and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/faucet.bin b/artifacts/program_methods/faucet.bin index ca45b686..4994d29e 100644 Binary files a/artifacts/program_methods/faucet.bin and b/artifacts/program_methods/faucet.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 118f19d3..84ea7e23 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index f3ecb0e9..cb240b78 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 66f6d5b6..a7f6d3e9 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index a36fbbc8..59989832 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/program_methods/vault.bin b/artifacts/program_methods/vault.bin index 7628b459..81e79348 100644 Binary files a/artifacts/program_methods/vault.bin and b/artifacts/program_methods/vault.bin differ diff --git a/artifacts/test_program_methods/auth_asserting_noop.bin b/artifacts/test_program_methods/auth_asserting_noop.bin index 3884195d..0e9e0dc1 100644 Binary files a/artifacts/test_program_methods/auth_asserting_noop.bin and b/artifacts/test_program_methods/auth_asserting_noop.bin differ diff --git a/artifacts/test_program_methods/auth_transfer_proxy.bin b/artifacts/test_program_methods/auth_transfer_proxy.bin index 6afda3a5..c5863615 100644 Binary files a/artifacts/test_program_methods/auth_transfer_proxy.bin and b/artifacts/test_program_methods/auth_transfer_proxy.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index dd2db03f..c7684bfc 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index 58291896..39b6cb2f 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin index bb2feabb..d1f2cb8a 100644 Binary files a/artifacts/test_program_methods/changer_claimer.bin and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index b0f3a67c..035fdb23 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/clock_chain_caller.bin b/artifacts/test_program_methods/clock_chain_caller.bin index 5cda6717..b9406338 100644 Binary files a/artifacts/test_program_methods/clock_chain_caller.bin and b/artifacts/test_program_methods/clock_chain_caller.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 898cea24..42429550 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index a74d931d..9685f34f 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/faucet_chain_caller.bin b/artifacts/test_program_methods/faucet_chain_caller.bin index 8effbf85..b72df852 100644 Binary files a/artifacts/test_program_methods/faucet_chain_caller.bin and b/artifacts/test_program_methods/faucet_chain_caller.bin differ diff --git a/artifacts/test_program_methods/flash_swap_callback.bin b/artifacts/test_program_methods/flash_swap_callback.bin index e4aa09dc..2f9020c9 100644 Binary files a/artifacts/test_program_methods/flash_swap_callback.bin and b/artifacts/test_program_methods/flash_swap_callback.bin differ diff --git a/artifacts/test_program_methods/flash_swap_initiator.bin b/artifacts/test_program_methods/flash_swap_initiator.bin index f0d031b8..6ef7044b 100644 Binary files a/artifacts/test_program_methods/flash_swap_initiator.bin and b/artifacts/test_program_methods/flash_swap_initiator.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin index 589bc620..37716f90 100644 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/malicious_caller_program_id.bin b/artifacts/test_program_methods/malicious_caller_program_id.bin index 8c0d1350..09507957 100644 Binary files a/artifacts/test_program_methods/malicious_caller_program_id.bin and b/artifacts/test_program_methods/malicious_caller_program_id.bin differ diff --git a/artifacts/test_program_methods/malicious_injector.bin b/artifacts/test_program_methods/malicious_injector.bin index 8bcccb4e..cb90bbc5 100644 Binary files a/artifacts/test_program_methods/malicious_injector.bin and b/artifacts/test_program_methods/malicious_injector.bin differ diff --git a/artifacts/test_program_methods/malicious_launderer.bin b/artifacts/test_program_methods/malicious_launderer.bin index 70392d9c..368602ca 100644 Binary files a/artifacts/test_program_methods/malicious_launderer.bin and b/artifacts/test_program_methods/malicious_launderer.bin differ diff --git a/artifacts/test_program_methods/malicious_self_program_id.bin b/artifacts/test_program_methods/malicious_self_program_id.bin index 62b8af74..dd0343d0 100644 Binary files a/artifacts/test_program_methods/malicious_self_program_id.bin and b/artifacts/test_program_methods/malicious_self_program_id.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index c4b115ab..7bf2b4ed 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index ae599f60..6bac61a9 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index 1435dea9..83a6c3b8 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index bc479a80..45fd8ea8 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index e0d52639..4c8248e0 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/pda_claimer.bin b/artifacts/test_program_methods/pda_claimer.bin index f34373ff..5dc4031e 100644 Binary files a/artifacts/test_program_methods/pda_claimer.bin and b/artifacts/test_program_methods/pda_claimer.bin differ diff --git a/artifacts/test_program_methods/pda_spend_proxy.bin b/artifacts/test_program_methods/pda_spend_proxy.bin index 33e38f00..6b6e6a41 100644 Binary files a/artifacts/test_program_methods/pda_spend_proxy.bin and b/artifacts/test_program_methods/pda_spend_proxy.bin differ diff --git a/artifacts/test_program_methods/pinata_cooldown.bin b/artifacts/test_program_methods/pinata_cooldown.bin index 65879893..d807c71c 100644 Binary files a/artifacts/test_program_methods/pinata_cooldown.bin and b/artifacts/test_program_methods/pinata_cooldown.bin differ diff --git a/artifacts/test_program_methods/private_pda_delegator.bin b/artifacts/test_program_methods/private_pda_delegator.bin index 7652eaa7..5fb95519 100644 Binary files a/artifacts/test_program_methods/private_pda_delegator.bin and b/artifacts/test_program_methods/private_pda_delegator.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index 81d9a7e1..44ab9808 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index ef9b3006..73f85aef 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/artifacts/test_program_methods/time_locked_transfer.bin b/artifacts/test_program_methods/time_locked_transfer.bin index ba50eebd..4955beb3 100644 Binary files a/artifacts/test_program_methods/time_locked_transfer.bin and b/artifacts/test_program_methods/time_locked_transfer.bin differ diff --git a/artifacts/test_program_methods/two_pda_claimer.bin b/artifacts/test_program_methods/two_pda_claimer.bin index 32a8b581..15c990c2 100644 Binary files a/artifacts/test_program_methods/two_pda_claimer.bin and b/artifacts/test_program_methods/two_pda_claimer.bin differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin index 85a38041..65aa5988 100644 Binary files a/artifacts/test_program_methods/validity_window.bin and b/artifacts/test_program_methods/validity_window.bin differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin index 99f379c5..c13a3810 100644 Binary files a/artifacts/test_program_methods/validity_window_chain_caller.bin and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 9eea9b04..45a1b085 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -11,7 +11,7 @@ use lee::{ privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, }; use lee_core::{ - InputAccountIdentity, NullifierPublicKey, + EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, account::AccountWithMetadata, encryption::{EphemeralPublicKey, ViewingPublicKey}, }; @@ -665,9 +665,9 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> { let auth_transfer_program_id = Program::authenticated_transfer_program().id(); let nsk: lee_core::NullifierSecretKey = [3; 32]; let npk = NullifierPublicKey::from(&nsk); - let _vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap(); + let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap(); let ssk = SharedSecretKey([55_u8; 32]); - let _epk = EphemeralPublicKey(vec![55_u8; 1088]); + let epk = EphemeralPublicKey(vec![55_u8; 1088]); let attacker_vault_id = { let seed = vault_core::compute_vault_seed(attacker_id); AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337) @@ -712,6 +712,8 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { + epk, + view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk), npk, ssk, identifier: 1337, diff --git a/integration_tests/tests/bridge.rs b/integration_tests/tests/bridge.rs index 81f62f2b..054da0a0 100644 --- a/integration_tests/tests/bridge.rs +++ b/integration_tests/tests/bridge.rs @@ -150,7 +150,6 @@ async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> { let message = privacy_preserving_transaction::Message::try_from_circuit_output( vec![bridge_account_id, recipient_vault_id], vec![bridge_pre.account.nonce, vault_pre.account.nonce], - vec![], output, ) .context("Failed to build privacy-preserving bridge deposit message")?; diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index ea7cafab..f96faa52 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -23,7 +23,7 @@ use lee::{ program::Program, }; use lee_core::{ - InputAccountIdentity, NullifierPublicKey, + EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, account::{Account, AccountWithMetadata}, encryption::ViewingPublicKey, program::PdaSeed, @@ -74,6 +74,8 @@ async fn fund_private_pda( let account_identities = vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { + epk, + view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk), npk, ssk, identifier, @@ -89,13 +91,9 @@ async fn fund_private_pda( ) .map_err(|e| anyhow::anyhow!("circuit proving failed: {e}"))?; - let message = Message::try_from_circuit_output( - vec![sender], - vec![sender_account.nonce], - vec![(npk, vpk, epk)], - output, - ) - .map_err(|e| anyhow::anyhow!("message build failed: {e}"))?; + let message = + Message::try_from_circuit_output(vec![sender], vec![sender_account.nonce], output) + .map_err(|e| anyhow::anyhow!("message build failed: {e}"))?; let witness_set = WitnessSet::for_message(&message, proof, &[sender_sk]); let tx = PrivacyPreservingTransaction::new(message, witness_set); diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index daf52609..459f3d61 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -23,7 +23,7 @@ use lee::{ public_transaction as putx, }; use lee_core::{ - InputAccountIdentity, MembershipProof, NullifierPublicKey, + EncryptedAccountData, InputAccountIdentity, MembershipProof, NullifierPublicKey, account::{AccountWithMetadata, Nonce, data::Data}, encryption::ViewingPublicKey, }; @@ -301,12 +301,16 @@ 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, nsk: sender_nsk, membership_proof: proof, identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: recipient_epk, + view_tag: EncryptedAccountData::compute_view_tag(&recipient_npk, &recipient_vpk), npk: recipient_npk, ssk: recipient_ss, identifier: 0, @@ -315,16 +319,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { &program.into(), ) .unwrap(); - let message = pptx::message::Message::try_from_circuit_output( - vec![], - vec![], - vec![ - (sender_npk, sender_vpk, sender_epk), - (recipient_npk, recipient_vpk, recipient_epk), - ], - output, - ) - .unwrap(); + let message = pptx::message::Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = pptx::witness_set::WitnessSet::for_message(&message, proof, &[]); pptx::PrivacyPreservingTransaction::new(message, witness_set) } diff --git a/lee/state_machine/core/src/circuit_io.rs b/lee/state_machine/core/src/circuit_io.rs index b1c2e44f..78bfa24f 100644 --- a/lee/state_machine/core/src/circuit_io.rs +++ b/lee/state_machine/core/src/circuit_io.rs @@ -4,7 +4,7 @@ use crate::{ Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata}, - encryption::Ciphertext, + encryption::{EncryptedAccountData, EphemeralPublicKey, ViewTag}, program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow}, }; @@ -33,6 +33,8 @@ pub enum InputAccountIdentity { /// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), identifier)` and /// matched against `pre_state.account_id`. PrivateAuthorizedInit { + epk: EphemeralPublicKey, + view_tag: ViewTag, ssk: SharedSecretKey, nsk: NullifierSecretKey, identifier: Identifier, @@ -40,6 +42,8 @@ pub enum InputAccountIdentity { /// Update of an authorized standalone private account: existing on-chain commitment, with /// membership proof. PrivateAuthorizedUpdate { + epk: EphemeralPublicKey, + view_tag: ViewTag, ssk: SharedSecretKey, nsk: NullifierSecretKey, membership_proof: MembershipProof, @@ -48,6 +52,8 @@ 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, npk: NullifierPublicKey, ssk: SharedSecretKey, identifier: Identifier, @@ -57,6 +63,8 @@ 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, npk: NullifierPublicKey, ssk: SharedSecretKey, identifier: Identifier, @@ -72,6 +80,8 @@ 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, + view_tag: ViewTag, ssk: SharedSecretKey, nsk: NullifierSecretKey, membership_proof: MembershipProof, @@ -123,7 +133,7 @@ impl InputAccountIdentity { pub struct PrivacyPreservingCircuitOutput { pub public_pre_states: Vec, pub public_post_states: Vec, - pub ciphertexts: Vec, + pub encrypted_private_post_states: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, pub block_validity_window: BlockValidityWindow, @@ -148,6 +158,7 @@ mod tests { use crate::{ Commitment, Nullifier, account::{Account, AccountId, AccountWithMetadata, Nonce}, + encryption::Ciphertext, }; #[test] @@ -181,7 +192,11 @@ mod tests { data: b"post state data".to_vec().try_into().unwrap(), nonce: Nonce(0xFFFF_FFFF_FFFF_FFFF), }], - ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])], + encrypted_private_post_states: vec![EncryptedAccountData { + ciphertext: Ciphertext(vec![255, 255, 1, 1, 2, 2]), + epk: EphemeralPublicKey(vec![9, 9, 9]), + view_tag: 42, + }], new_commitments: vec![Commitment::new( &AccountId::new([1; 32]), &Account::default(), diff --git a/lee/state_machine/core/src/encryption/mod.rs b/lee/state_machine/core/src/encryption/mod.rs index 37745d4f..5fa80b60 100644 --- a/lee/state_machine/core/src/encryption/mod.rs +++ b/lee/state_machine/core/src/encryption/mod.rs @@ -6,7 +6,7 @@ use chacha20::{ use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; #[cfg(feature = "host")] -pub use shared_key_derivation::{EphemeralPublicKey, MlKem768EncapsulationKey, ViewingPublicKey}; +pub use shared_key_derivation::{MlKem768EncapsulationKey, ViewingPublicKey}; use crate::{Commitment, account::Account, program::PrivateAccountKind}; #[cfg(feature = "host")] @@ -17,6 +17,11 @@ pub type Scalar = [u8; 32]; #[derive(Serialize, Deserialize, Clone, Copy)] pub struct SharedSecretKey(pub [u8; 32]); +/// The ML-KEM-768 ciphertext produced during encapsulation; transmitted on-wire in place of the +/// former ECDH ephemeral public key. Always 1088 bytes for ML-KEM-768. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct EphemeralPublicKey(pub Vec); + pub struct EncryptionScheme; #[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] @@ -36,6 +41,45 @@ impl std::fmt::Debug for Ciphertext { } } +pub type ViewTag = u8; + +/// Encrypted private-account note for one output. +#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] +pub struct EncryptedAccountData { + pub ciphertext: Ciphertext, + pub epk: EphemeralPublicKey, + pub view_tag: ViewTag, +} + +#[cfg(feature = "host")] +impl EncryptedAccountData { + #[must_use] + pub fn new( + ciphertext: Ciphertext, + npk: &crate::NullifierPublicKey, + vpk: &ViewingPublicKey, + epk: EphemeralPublicKey, + ) -> Self { + let view_tag = Self::compute_view_tag(npk, vpk); + Self { + ciphertext, + epk, + 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 { #[must_use] pub fn encrypt( diff --git a/lee/state_machine/core/src/encryption/shared_key_derivation.rs b/lee/state_machine/core/src/encryption/shared_key_derivation.rs index a3b2fd32..5c982c6f 100644 --- a/lee/state_machine/core/src/encryption/shared_key_derivation.rs +++ b/lee/state_machine/core/src/encryption/shared_key_derivation.rs @@ -2,12 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use ml_kem::{Decapsulate as _, Encapsulate as _, KeyExport as _, Seed}; use serde::{Deserialize, Serialize}; -use crate::SharedSecretKey; - -/// The ML-KEM-768 ciphertext produced during encapsulation; transmitted on-wire in place of the -/// former ECDH ephemeral public key. Always 1088 bytes for ML-KEM-768. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct EphemeralPublicKey(pub Vec); +use crate::{EphemeralPublicKey, SharedSecretKey}; /// ML-KEM-768 encapsulation key bytes (1184 bytes, opaque to this crate). #[derive( diff --git a/lee/state_machine/core/src/lib.rs b/lee/state_machine/core/src/lib.rs index 466e1f5d..9ad2858e 100644 --- a/lee/state_machine/core/src/lib.rs +++ b/lee/state_machine/core/src/lib.rs @@ -10,7 +10,9 @@ pub use commitment::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof, compute_digest_for_path, }; -pub use encryption::{EncryptionScheme, SharedSecretKey}; +pub use encryption::{ + EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, SharedSecretKey, ViewTag, +}; pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey}; pub use program::PrivateAccountKind; diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit.rs index 6c986550..87c7c5bb 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit.rs @@ -178,8 +178,8 @@ mod tests { #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] use lee_core::{ - Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, - PrivacyPreservingCircuitOutput, SharedSecretKey, + Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, + EphemeralPublicKey, Nullifier, PrivacyPreservingCircuitOutput, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, program::{PdaSeed, PrivateAccountKind}, }; @@ -201,7 +201,7 @@ mod tests { idx: usize, ) -> PrivateAccountKind { let (kind, _) = EncryptionScheme::decrypt( - &output.ciphertexts[idx], + &output.encrypted_private_post_states[idx].ciphertext, ssk, &output.new_commitments[idx], u32::try_from(idx).expect("idx fits in u32"), @@ -268,6 +268,11 @@ mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: shared_secret, identifier: 0, @@ -285,10 +290,10 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(output.new_commitments.len(), 1); assert_eq!(output.new_nullifiers.len(), 1); - assert_eq!(output.ciphertexts.len(), 1); + assert_eq!(output.encrypted_private_post_states.len(), 1); let (_identifier, recipient_post) = EncryptionScheme::decrypt( - &output.ciphertexts[0], + &output.encrypted_private_post_states[0].ciphertext, &shared_secret, &output.new_commitments[0], 0, @@ -367,6 +372,11 @@ mod tests { .unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &sender_keys.npk(), + &sender_keys.vpk(), + ), ssk: shared_secret_1, nsk: sender_keys.nsk, membership_proof: commitment_set @@ -375,6 +385,11 @@ mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: shared_secret_2, identifier: 0, @@ -389,10 +404,10 @@ mod tests { assert!(output.public_post_states.is_empty()); assert_eq!(output.new_commitments, expected_new_commitments); assert_eq!(output.new_nullifiers, expected_new_nullifiers); - assert_eq!(output.ciphertexts.len(), 2); + assert_eq!(output.encrypted_private_post_states.len(), 2); let (_identifier, sender_post) = EncryptionScheme::decrypt( - &output.ciphertexts[0], + &output.encrypted_private_post_states[0].ciphertext, &shared_secret_1, &expected_new_commitments[0], 0, @@ -401,7 +416,7 @@ mod tests { assert_eq!(sender_post, expected_private_account_1); let (_identifier, recipient_post) = EncryptionScheme::decrypt( - &output.ciphertexts[1], + &output.encrypted_private_post_states[1].ciphertext, &shared_secret_2, &expected_new_commitments[1], 1, @@ -443,6 +458,11 @@ mod tests { vec![pre], instruction, vec![InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &account_keys.npk(), + &account_keys.vpk(), + ), npk: account_keys.npk(), ssk: shared_secret, identifier: 0, @@ -472,6 +492,8 @@ mod tests { vec![pre_state], Program::serialize_instruction(seed).unwrap(), vec![InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret, identifier, @@ -519,6 +541,8 @@ mod tests { vec![pda_pre], instruction, vec![InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret_pda, identifier: 0, @@ -572,6 +596,8 @@ mod tests { instruction, vec![ InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret_pda, identifier: 0, @@ -629,6 +655,11 @@ mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &shared_npk, + &shared_keys.vpk(), + ), npk: shared_npk, ssk: shared_secret, identifier: shared_identifier, @@ -658,6 +689,8 @@ mod tests { Program::serialize_instruction(authenticated_transfer_core::Instruction::Initialize) .unwrap(), vec![InputAccountIdentity::PrivateAuthorizedInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), ssk, nsk: keys.nsk, identifier, @@ -702,6 +735,8 @@ mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), npk: keys.npk(), ssk, identifier, @@ -746,6 +781,8 @@ mod tests { .unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), ssk, nsk: keys.nsk, membership_proof: commitment_set.get_proof_for(&commitment).unwrap(), @@ -800,6 +837,8 @@ mod tests { Program::serialize_instruction((seed, 1_u128, auth_transfer_id, false)).unwrap(), vec![ InputAccountIdentity::PrivatePdaUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), ssk, nsk: keys.nsk, membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(), @@ -838,6 +877,8 @@ mod tests { vec![pre_state], Program::serialize_instruction(seed).unwrap(), vec![InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret, identifier: 99, @@ -881,6 +922,8 @@ mod tests { Program::serialize_instruction((seed, 1_u128, auth_transfer_id, false)).unwrap(), vec![ InputAccountIdentity::PrivatePdaUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), ssk, nsk: keys.nsk, membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(), diff --git a/lee/state_machine/src/privacy_preserving_transaction/message.rs b/lee/state_machine/src/privacy_preserving_transaction/message.rs index 6a289a0a..b2594912 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/message.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/message.rs @@ -1,52 +1,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use lee_core::{ - Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput, + Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, account::{Account, Nonce}, - encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey}, program::{BlockValidityWindow, TimestampValidityWindow}, }; +pub use lee_core::{EncryptedAccountData, ViewTag}; use sha2::{Digest as _, Sha256}; use crate::{AccountId, error::LeeError}; const PREFIX: &[u8; 32] = b"/LEE/v0.3/Message/Privacy/\x00\x00\x00\x00\x00\x00"; -pub type ViewTag = u8; - -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct EncryptedAccountData { - pub ciphertext: Ciphertext, - pub epk: EphemeralPublicKey, - pub view_tag: ViewTag, -} - -impl EncryptedAccountData { - fn new( - ciphertext: Ciphertext, - npk: &NullifierPublicKey, - vpk: &ViewingPublicKey, - epk: EphemeralPublicKey, - ) -> Self { - let view_tag = Self::compute_view_tag(npk, vpk); - Self { - ciphertext, - epk, - 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: &NullifierPublicKey, vpk: &ViewingPublicKey) -> ViewTag { - let mut hasher = Sha256::new(); - hasher.update(b"/LEE/v0.3/ViewTag/"); - hasher.update(npk.to_byte_array()); - hasher.update(vpk.to_bytes()); - let digest: [u8; 32] = hasher.finalize().into(); - digest[0] - } -} - #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Message { pub public_account_ids: Vec, @@ -92,28 +56,13 @@ impl Message { pub fn try_from_circuit_output( public_account_ids: Vec, nonces: Vec, - public_keys: Vec<(NullifierPublicKey, ViewingPublicKey, EphemeralPublicKey)>, output: PrivacyPreservingCircuitOutput, ) -> Result { - if public_keys.len() != output.ciphertexts.len() { - return Err(LeeError::InvalidInput( - "Ephemeral public keys and ciphertexts length mismatch".into(), - )); - } - - let encrypted_private_post_states = output - .ciphertexts - .into_iter() - .zip(public_keys) - .map(|(ciphertext, (npk, vpk, epk))| { - EncryptedAccountData::new(ciphertext, &npk, &vpk, epk) - }) - .collect(); Ok(Self { public_account_ids, nonces, public_post_states: output.public_post_states, - encrypted_private_post_states, + encrypted_private_post_states: output.encrypted_private_post_states, new_commitments: output.new_commitments, new_nullifiers: output.new_nullifiers, block_validity_window: output.block_validity_window, diff --git a/lee/state_machine/src/state.rs b/lee/state_machine/src/state.rs index c238c253..c7152917 100644 --- a/lee/state_machine/src/state.rs +++ b/lee/state_machine/src/state.rs @@ -418,8 +418,8 @@ pub mod tests { use authenticated_transfer_core::Instruction as AuthTransferInstruction; use lee_core::{ - BlockId, Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, - NullifierSecretKey, SharedSecretKey, Timestamp, + BlockId, Commitment, EncryptedAccountData, InputAccountIdentity, Nullifier, + NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Timestamp, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, ViewingPublicKey}, program::{ @@ -1418,6 +1418,11 @@ pub mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateUnauthorized { + epk, + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: shared_secret, identifier: 0, @@ -1430,7 +1435,6 @@ pub mod tests { let message = Message::try_from_circuit_output( vec![sender_keys.account_id()], vec![sender_nonce], - vec![(recipient_keys.npk(), recipient_keys.vpk(), epk)], output, ) .unwrap(); @@ -1471,6 +1475,11 @@ pub mod tests { .unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { + epk: epk_1, + view_tag: EncryptedAccountData::compute_view_tag( + &sender_keys.npk(), + &sender_keys.vpk(), + ), ssk: shared_secret_1, nsk: sender_keys.nsk, membership_proof: state @@ -1479,6 +1488,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: epk_2, + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: shared_secret_2, identifier: 0, @@ -1488,16 +1502,7 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![ - (sender_keys.npk(), sender_keys.vpk(), epk_1), - (recipient_keys.npk(), recipient_keys.vpk(), epk_2), - ], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -1536,6 +1541,11 @@ pub mod tests { .unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { + epk, + view_tag: EncryptedAccountData::compute_view_tag( + &sender_keys.npk(), + &sender_keys.vpk(), + ), ssk: shared_secret, nsk: sender_keys.nsk, membership_proof: state @@ -1549,13 +1559,8 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![*recipient_account_id], - vec![], - vec![(sender_keys.npk(), sender_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![*recipient_account_id], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -1672,6 +1677,79 @@ pub mod tests { assert!(state.private_state.1.contains(&expected_new_nullifier)); } + fn valid_private_transfer_tx_and_state() -> (V03State, PrivacyPreservingTransaction) { + let sender_keys = test_private_account_keys_1(); + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: Nonce(0xdead_beef), + ..Account::default() + }; + let recipient_keys = test_private_account_keys_2(); + let state = V03State::new_with_genesis_accounts(&[], vec![], 0) + .with_private_account(&sender_keys, &sender_private_account); + let tx = private_balance_transfer_for_tests( + &sender_keys, + &sender_private_account, + &recipient_keys, + 37, + &state, + ); + (state, tx) + } + + /// After a valid fully-private tx is proven, tampering with a note's epk should + /// make the shielding proof invalid. + #[test] + fn privacy_tampered_epk_is_rejected() { + use crate::validated_state_diff::ValidatedStateDiff; + + let (state, mut tx) = valid_private_transfer_tx_and_state(); + + // Baseline: the untampered tx verifies + assert!( + ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0).is_ok(), + "the unmodified private transfer must verify" + ); + + // Flip a byte of the first note's epk + tx.message.encrypted_private_post_states[0].epk.0[0] ^= 0xFF; + + assert!( + matches!( + ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0), + Err(LeeError::InvalidPrivacyPreservingProof) + ), + "a tampered epk must be rejected by proof verification" + ); + } + + /// After a valid fully-private tx is proven, tampering with a note's view tag should + /// make the shielding proof invalid. + #[test] + fn privacy_tampered_view_tag_is_rejected() { + use crate::validated_state_diff::ValidatedStateDiff; + + let (state, mut tx) = valid_private_transfer_tx_and_state(); + + // Baseline: the untampered tx verifies. + assert!( + ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0).is_ok(), + "the unmodified private transfer must verify" + ); + + // Flip the first note's view_tag + tx.message.encrypted_private_post_states[0].view_tag ^= 0xFF; + + assert!( + matches!( + ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0), + Err(LeeError::InvalidPrivacyPreservingProof) + ), + "a tampered view_tag must be rejected by proof verification" + ); + } + #[test] fn transition_from_privacy_preserving_transaction_deshielded() { let sender_keys = test_private_account_keys_1(); @@ -2034,6 +2112,11 @@ pub mod tests { 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], @@ -2045,6 +2128,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), @@ -2090,6 +2178,11 @@ pub mod tests { 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], @@ -2101,6 +2194,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), @@ -2146,6 +2244,11 @@ pub mod tests { 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], @@ -2157,6 +2260,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), @@ -2202,6 +2310,11 @@ pub mod tests { 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], @@ -2213,6 +2326,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), @@ -2258,6 +2376,11 @@ pub mod tests { 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], @@ -2269,6 +2392,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), @@ -2312,6 +2440,11 @@ pub mod tests { 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], @@ -2323,6 +2456,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), @@ -2368,6 +2506,8 @@ pub mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret, identifier: u128::MAX, @@ -2401,6 +2541,8 @@ pub mod tests { vec![pre_state], Program::serialize_instruction(seed).unwrap(), vec![InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret, identifier: u128::MAX, @@ -2412,7 +2554,7 @@ pub mod tests { let (output, _proof) = result.expect("private PDA claim should succeed"); assert_eq!(output.new_nullifiers.len(), 1); assert_eq!(output.new_commitments.len(), 1); - assert_eq!(output.ciphertexts.len(), 1); + assert_eq!(output.encrypted_private_post_states.len(), 1); assert!(output.public_pre_states.is_empty()); assert!(output.public_post_states.is_empty()); } @@ -2442,6 +2584,8 @@ pub mod tests { 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()), npk: npk_b, ssk: shared_secret, identifier: u128::MAX, @@ -2479,6 +2623,8 @@ pub mod tests { 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()), npk, ssk: shared_secret, identifier: u128::MAX, @@ -2519,6 +2665,8 @@ pub mod tests { 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()), npk, ssk: shared_secret, identifier: u128::MAX, @@ -2558,12 +2706,16 @@ pub mod tests { Program::serialize_instruction(seed).unwrap(), vec![ InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys_a.npk(), &keys_a.vpk()), npk: keys_a.npk(), ssk: shared_a, identifier: u128::MAX, seed: None, }, InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys_b.npk(), &keys_b.vpk()), npk: keys_b.npk(), ssk: shared_b, identifier: u128::MAX, @@ -2606,6 +2758,8 @@ pub mod tests { vec![owned_pre_state], Program::serialize_instruction(()).unwrap(), vec![InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, &keys.vpk()), npk, ssk: shared_secret, identifier: u128::MAX, @@ -2694,12 +2848,22 @@ pub mod tests { 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, 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, nsk: sender_keys.nsk, membership_proof: (1, vec![]), @@ -3045,6 +3209,11 @@ pub mod tests { .unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { + epk, + view_tag: EncryptedAccountData::compute_view_tag( + &sender_keys.npk(), + &sender_keys.vpk(), + ), ssk: shared_secret, nsk: sender_keys.nsk, membership_proof: state @@ -3058,13 +3227,9 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![recipient_account_id], - vec![Nonce(0)], - vec![(sender_keys.npk(), sender_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![recipient_account_id], vec![Nonce(0)], output) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_private_key]); let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -3171,6 +3336,11 @@ pub mod tests { 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, nsk: from_keys.nsk, membership_proof: state @@ -3179,6 +3349,11 @@ pub mod tests { identifier: 0, }, InputAccountIdentity::PrivateAuthorizedUpdate { + epk: from_epk, + view_tag: EncryptedAccountData::compute_view_tag( + &from_keys.npk(), + &from_keys.vpk(), + ), ssk: from_ss, nsk: to_keys.nsk, membership_proof: state @@ -3191,16 +3366,7 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![ - (to_keys.npk(), to_keys.vpk(), to_epk), - (from_keys.npk(), from_keys.vpk(), from_epk), - ], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); let transaction = PrivacyPreservingTransaction::new(message, witness_set); @@ -3448,6 +3614,11 @@ pub mod tests { 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, nsk: private_keys.nsk, identifier: 0, @@ -3457,13 +3628,7 @@ pub mod tests { .unwrap(); // Create message from circuit output - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![(private_keys.npk(), private_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -3496,6 +3661,11 @@ pub mod tests { vec![unauthorized_account], Program::serialize_instruction(0_u128).unwrap(), vec![InputAccountIdentity::PrivateUnauthorized { + epk, + view_tag: EncryptedAccountData::compute_view_tag( + &private_keys.npk(), + &private_keys.vpk(), + ), npk: private_keys.npk(), ssk: shared_secret, identifier: 0, @@ -3504,13 +3674,7 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![(private_keys.npk(), private_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -3548,6 +3712,11 @@ pub mod tests { 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, nsk: private_keys.nsk, identifier: 0, @@ -3556,13 +3725,7 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![(private_keys.npk(), private_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -3595,6 +3758,11 @@ pub mod tests { 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, nsk: private_keys.nsk, identifier: 0, @@ -3672,6 +3840,11 @@ pub mod tests { 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, nsk: sender_keys.nsk, @@ -3699,6 +3872,11 @@ pub mod tests { 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, nsk: sender_keys.nsk, @@ -3760,6 +3938,11 @@ pub mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateAuthorizedUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag( + &recipient_keys.npk(), + &recipient_keys.vpk(), + ), ssk: recipient, nsk: recipient_keys.nsk, membership_proof: state @@ -3914,6 +4097,11 @@ pub mod tests { vec![pre], Program::serialize_instruction(instruction).unwrap(), vec![InputAccountIdentity::PrivateUnauthorized { + epk, + view_tag: EncryptedAccountData::compute_view_tag( + &account_keys.npk(), + &account_keys.vpk(), + ), npk: account_keys.npk(), ssk: shared_secret, identifier: 0, @@ -3922,13 +4110,7 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![(account_keys.npk(), account_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); PrivacyPreservingTransaction::new(message, witness_set) @@ -3983,6 +4165,11 @@ pub mod tests { vec![pre], Program::serialize_instruction(instruction).unwrap(), vec![InputAccountIdentity::PrivateUnauthorized { + epk, + view_tag: EncryptedAccountData::compute_view_tag( + &account_keys.npk(), + &account_keys.vpk(), + ), npk: account_keys.npk(), ssk: shared_secret, identifier: 0, @@ -3991,13 +4178,7 @@ pub mod tests { ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![(account_keys.npk(), account_keys.vpk(), epk)], - output, - ) - .unwrap(); + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); PrivacyPreservingTransaction::new(message, witness_set) @@ -4546,6 +4727,11 @@ pub mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { + epk: alice_epk_0.clone(), + view_tag: EncryptedAccountData::compute_view_tag( + &alice_npk, + &alice_keys.vpk(), + ), npk: alice_npk, ssk: alice_shared_0, identifier: 0, @@ -4555,13 +4741,9 @@ pub mod tests { &auth_transfer.clone().into(), ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![funder_id], - vec![funder_nonce], - vec![(alice_npk, alice_keys.vpk(), alice_epk_0.clone())], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![funder_id], vec![funder_nonce], output) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&funder_keys.signing_key]); state .transition_from_privacy_preserving_transaction( @@ -4586,6 +4768,11 @@ pub mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { + epk: alice_epk_1.clone(), + view_tag: EncryptedAccountData::compute_view_tag( + &alice_npk, + &alice_keys.vpk(), + ), npk: alice_npk, ssk: alice_shared_1, identifier: 1, @@ -4595,13 +4782,9 @@ pub mod tests { &auth_transfer.into(), ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![funder_id], - vec![funder_nonce], - vec![(alice_npk, alice_keys.vpk(), alice_epk_1.clone())], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![funder_id], vec![funder_nonce], output) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&funder_keys.signing_key]); state .transition_from_privacy_preserving_transaction( @@ -4629,6 +4812,11 @@ pub mod tests { Program::serialize_instruction((seed, amount, auth_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, nsk: alice_keys.nsk, membership_proof: state @@ -4642,13 +4830,9 @@ pub mod tests { &spend_with_deps, ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![recipient_id], - vec![Nonce(0)], - vec![(alice_npk, alice_keys.vpk(), alice_epk_0)], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![recipient_id], vec![Nonce(0)], output) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_signing_key]); state .transition_from_privacy_preserving_transaction( @@ -4670,6 +4854,11 @@ pub mod tests { Program::serialize_instruction((seed, amount, auth_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, nsk: alice_keys.nsk, membership_proof: state @@ -4683,13 +4872,8 @@ pub mod tests { &spend_with_deps, ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![recipient_id], - vec![], - vec![(alice_npk, alice_keys.vpk(), alice_epk_1)], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![recipient_id], vec![], output).unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[]); state .transition_from_privacy_preserving_transaction( @@ -4732,6 +4916,11 @@ pub mod tests { vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaUpdate { + epk: EphemeralPublicKey(vec![12_u8; 1088]), + view_tag: EncryptedAccountData::compute_view_tag( + &alice_npk, + &alice_keys.vpk(), + ), nsk: alice_keys.nsk, ssk: alice_shared_1_refund, membership_proof: state @@ -4744,17 +4933,9 @@ pub mod tests { &Program::authenticated_transfer_program().into(), ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![recipient_id], - vec![recipient_nonce], - vec![( - alice_npk, - alice_keys.vpk(), - EphemeralPublicKey(vec![12_u8; 1088]), - )], - output, - ) - .unwrap(); + let message = + Message::try_from_circuit_output(vec![recipient_id], vec![recipient_nonce], output) + .unwrap(); let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_signing_key]); state .transition_from_privacy_preserving_transaction( diff --git a/lee/state_machine/src/validated_state_diff.rs b/lee/state_machine/src/validated_state_diff.rs index 3bd77cae..0d71d485 100644 --- a/lee/state_machine/src/validated_state_diff.rs +++ b/lee/state_machine/src/validated_state_diff.rs @@ -492,12 +492,7 @@ fn check_privacy_preserving_circuit_proof_is_valid( let output = PrivacyPreservingCircuitOutput { public_pre_states: public_pre_states.to_vec(), public_post_states: message.public_post_states.clone(), - ciphertexts: message - .encrypted_private_post_states - .iter() - .cloned() - .map(|value| value.ciphertext) - .collect(), + encrypted_private_post_states: message.encrypted_private_post_states.clone(), new_commitments: message.new_commitments.clone(), new_nullifiers: message.new_nullifiers.clone(), block_validity_window: message.block_validity_window, @@ -580,7 +575,7 @@ mod tests { #[test] fn privacy_malicious_programs_cannot_drain_public_victim() { use lee_core::{ - Commitment, InputAccountIdentity, SharedSecretKey, + Commitment, EncryptedAccountData, InputAccountIdentity, SharedSecretKey, account::{Account, AccountWithMetadata}, }; @@ -664,6 +659,11 @@ mod tests { // [2] recipient — first seen in authenticated_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, nsk: attacker_keys.nsk, membership_proof, @@ -688,7 +688,6 @@ mod tests { let message = Message::try_from_circuit_output( vec![victim_id, recipient_id], vec![], // no public signers, no nonces - vec![(attacker_keys.npk(), attacker_keys.vpk(), attacker_epk)], circuit_output, ) .unwrap(); @@ -728,7 +727,7 @@ mod tests { #[test] fn privacy_malicious_programs_cannot_drain_private_victim() { use lee_core::{ - Commitment, InputAccountIdentity, SharedSecretKey, + Commitment, EncryptedAccountData, InputAccountIdentity, SharedSecretKey, account::{Account, AccountWithMetadata}, }; @@ -820,6 +819,11 @@ mod tests { // 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, nsk: attacker_keys.nsk, membership_proof, @@ -845,7 +849,6 @@ mod tests { let message = Message::try_from_circuit_output( vec![victim_id, recipient_id], vec![], // no public signers, no nonces - vec![(attacker_keys.npk(), attacker_keys.vpk(), attacker_epk)], circuit_output, ) .unwrap(); diff --git a/lez/wallet/src/account_manager.rs b/lez/wallet/src/account_manager.rs index ffff57c6..19f4be6d 100644 --- a/lez/wallet/src/account_manager.rs +++ b/lez/wallet/src/account_manager.rs @@ -6,7 +6,7 @@ use lee_core::{ Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{AccountWithMetadata, Nonce}, - encryption::{EphemeralPublicKey, ViewingPublicKey}, + encryption::{EncryptedAccountData, EphemeralPublicKey, ViewingPublicKey}, }; use crate::{ExecutionFailureKind, WalletCore}; @@ -100,10 +100,7 @@ impl AccountIdentity { } pub struct PrivateAccountKeys { - pub npk: NullifierPublicKey, pub ssk: SharedSecretKey, - pub vpk: ViewingPublicKey, - pub epk: EphemeralPublicKey, } enum State { @@ -307,12 +304,7 @@ impl AccountManager { self.states .iter() .filter_map(|state| match state { - State::Private(pre) => Some(PrivateAccountKeys { - npk: pre.npk, - ssk: pre.ssk, - vpk: pre.vpk.clone(), - epk: pre.epk.clone(), - }), + State::Private(pre) => Some(PrivateAccountKeys { ssk: pre.ssk }), State::Public { .. } | State::PublicKeycard { .. } => None, }) .collect() @@ -329,6 +321,8 @@ 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, nsk, membership_proof, @@ -336,6 +330,8 @@ impl AccountManager { seed: None, }, _ => InputAccountIdentity::PrivatePdaInit { + epk: pre.epk.clone(), + view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), npk: pre.npk, ssk: pre.ssk, identifier: pre.identifier, @@ -345,6 +341,8 @@ 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, nsk, membership_proof, @@ -352,11 +350,15 @@ impl AccountManager { } } (Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit { + epk: pre.epk.clone(), + view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), ssk: pre.ssk, nsk, identifier: pre.identifier, }, (None, _) => InputAccountIdentity::PrivateUnauthorized { + epk: pre.epk.clone(), + view_tag: EncryptedAccountData::compute_view_tag(&pre.npk, &pre.vpk), npk: pre.npk, ssk: pre.ssk, identifier: pre.identifier, diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index 0afcda6d..7dece16a 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -587,10 +587,6 @@ impl WalletCore { lee::privacy_preserving_transaction::message::Message::try_from_circuit_output( acc_manager.public_account_ids(), acc_manager.public_account_nonces(), - private_account_keys - .iter() - .map(|keys| (keys.npk, keys.vpk.clone(), keys.epk.clone())) - .collect(), 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 621a461c..8c8ec2a4 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs @@ -1,7 +1,7 @@ use lee_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, InputAccountIdentity, - MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, - PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey, + Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, + EphemeralPublicKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, + NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey, account::{Account, AccountId, Nonce}, compute_digest_for_path, }; @@ -17,7 +17,7 @@ pub fn compute_circuit_output( let mut output = PrivacyPreservingCircuitOutput { public_pre_states: Vec::new(), public_post_states: Vec::new(), - ciphertexts: Vec::new(), + encrypted_private_post_states: Vec::new(), new_commitments: Vec::new(), new_nullifiers: Vec::new(), block_validity_window, @@ -40,6 +40,8 @@ pub fn compute_circuit_output( output.public_post_states.push(post_state); } InputAccountIdentity::PrivateAuthorizedInit { + epk, + view_tag, ssk, nsk, identifier, @@ -71,11 +73,15 @@ pub fn compute_circuit_output( &account_id, &PrivateAccountKind::Regular(*identifier), ssk, + epk, + *view_tag, new_nullifier, new_nonce, ); } InputAccountIdentity::PrivateAuthorizedUpdate { + epk, + view_tag, ssk, nsk, membership_proof, @@ -105,11 +111,15 @@ pub fn compute_circuit_output( &account_id, &PrivateAccountKind::Regular(*identifier), ssk, + epk, + *view_tag, new_nullifier, new_nonce, ); } InputAccountIdentity::PrivateUnauthorized { + epk, + view_tag, npk, ssk, identifier, @@ -140,11 +150,15 @@ pub fn compute_circuit_output( &account_id, &PrivateAccountKind::Regular(*identifier), ssk, + epk, + *view_tag, new_nullifier, new_nonce, ); } InputAccountIdentity::PrivatePdaInit { + epk, + view_tag, npk: _, ssk, identifier, @@ -187,11 +201,15 @@ pub fn compute_circuit_output( identifier: *identifier, }, ssk, + epk, + *view_tag, new_nullifier, new_nonce, ); } InputAccountIdentity::PrivatePdaUpdate { + epk, + view_tag, ssk, nsk, membership_proof, @@ -231,6 +249,8 @@ pub fn compute_circuit_output( identifier: *identifier, }, ssk, + epk, + *view_tag, new_nullifier, new_nonce, ); @@ -243,7 +263,7 @@ pub fn compute_circuit_output( #[expect( clippy::too_many_arguments, - reason = "All seven inputs are distinct concerns from the variant arms; bundling would be artificial" + reason = "Inputs are distinct concerns from the variant arms; bundling would be artificial" )] fn emit_private_output( output: &mut PrivacyPreservingCircuitOutput, @@ -252,6 +272,8 @@ fn emit_private_output( account_id: &AccountId, kind: &PrivateAccountKind, shared_secret: &SharedSecretKey, + epk: &EphemeralPublicKey, + view_tag: u8, new_nullifier: (Nullifier, CommitmentSetDigest), new_nonce: Nonce, ) { @@ -270,7 +292,13 @@ fn emit_private_output( ); output.new_commitments.push(commitment_post); - output.ciphertexts.push(encrypted_account); + output + .encrypted_private_post_states + .push(EncryptedAccountData { + ciphertext: encrypted_account, + epk: epk.clone(), + view_tag, + }); *output_index = output_index .checked_add(1) .unwrap_or_else(|| panic!("Too many private accounts, output index overflow"));