diff --git a/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin b/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin index ee1d76a6..0b8a4910 100644 Binary files a/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin and b/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin differ diff --git a/lee/privacy_preserving_circuit/src/output.rs b/lee/privacy_preserving_circuit/src/output.rs index 1576e80e..9ae530b2 100644 --- a/lee/privacy_preserving_circuit/src/output.rs +++ b/lee/privacy_preserving_circuit/src/output.rs @@ -271,12 +271,20 @@ pub fn compute_circuit_output( } fn emit_dummy_output(output: &mut PrivacyPreservingCircuitOutput, dummy: DummyInput) { + // Note: the nullifiers and commitments are generated from seeds. + // The prover is responsible for their randomness. let nullifier = Nullifier::for_dummy(&dummy.nullifier_seed); let commitment = Commitment::for_dummy(&nullifier, &dummy.commitment_seed); output .new_nullifiers .push((nullifier, dummy.commitment_root)); output.new_commitments.push(commitment); + // Note: the encrypted post states are pushed as fed into the circuit. + // That means that the prover is responsible for managing the randomness + // so as to not reveal the padding. + // + // In particular, it is recommended to generate the ML KEM ciphertext + // explicitly as these are not uniformly random. output.encrypted_private_post_states.push(dummy.note); } diff --git a/lez/wallet/src/account_manager.rs b/lez/wallet/src/account_manager.rs index 2a819191..f156a1ef 100644 --- a/lez/wallet/src/account_manager.rs +++ b/lez/wallet/src/account_manager.rs @@ -8,8 +8,7 @@ use lee_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivateAccountKind, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce}, encryption::{ - Ciphertext, EncryptedAccountData, EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN, ViewTag, - ViewingPublicKey, + Ciphertext, EncryptedAccountData, MlKem768EncapsulationKey, ViewTag, ViewingPublicKey, }, }; use rand::{RngCore as _, rngs::OsRng}; @@ -651,16 +650,18 @@ fn random_vec(len: usize) -> Vec { bytes } -/// Generates a dummy note: random bytes sized to a default-account ciphertext, a -/// random epk, and a random view tag. Not decryptable — it only pads the journal. +/// Generates a dummy note: random bytes sized to a default-account ciphertext, a real +/// ML-KEM ciphertext epk toward a throwaway key, and a random view tag. fn random_dummy_note() -> EncryptedAccountData { // Sized to a default-account ciphertext; matching real data sizes is a separate issue. let ciphertext_len = PrivateAccountKind::HEADER_LEN .checked_add(Account::default().to_bytes().len()) .expect("dummy ciphertext length fits in usize"); + let throwaway_ek = MlKem768EncapsulationKey::from_seed(&random_bytes(), &random_bytes()); + let (_, epk) = SharedSecretKey::encapsulate(&throwaway_ek); EncryptedAccountData { ciphertext: Ciphertext::from_inner(random_vec(ciphertext_len)), - epk: EphemeralPublicKey(random_vec(ML_KEM_768_CIPHERTEXT_LEN)), + epk, view_tag: random_view_tag(), } }