From ed4329103e59b2bb264908456a9cdd483f69e14d Mon Sep 17 00:00:00 2001 From: agureev Date: Mon, 22 Jun 2026 22:25:23 +0400 Subject: [PATCH] feat!: remove output index from the ciphertext encryption/decryption BREAKING: Before: KDF for ChaCha ciphertext had the position index as an argument After: No such argument provided Mitigation: Remove the index when generating the decoding key --- lee/state_machine/core/src/encryption/mod.rs | 38 ++++--------------- .../privacy_preserving_transaction/message.rs | 1 - .../bin/privacy_preserving_circuit/output.rs | 11 ------ .../benches/primitives.rs | 8 ++-- 4 files changed, 10 insertions(+), 48 deletions(-) diff --git a/lee/state_machine/core/src/encryption/mod.rs b/lee/state_machine/core/src/encryption/mod.rs index 5fa80b60..0ad00eb4 100644 --- a/lee/state_machine/core/src/encryption/mod.rs +++ b/lee/state_machine/core/src/encryption/mod.rs @@ -87,13 +87,12 @@ impl EncryptionScheme { kind: &PrivateAccountKind, shared_secret: &SharedSecretKey, commitment: &Commitment, - output_index: u32, ) -> Ciphertext { // Plaintext: PrivateAccountKind::HEADER_LEN bytes header || account bytes. // Both variants produce the same header length — see PrivateAccountKind::to_header_bytes. let mut buffer = kind.to_header_bytes().to_vec(); buffer.extend_from_slice(&account.to_bytes()); - Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index); + Self::symmetric_transform(&mut buffer, shared_secret, commitment); Ciphertext(buffer) } @@ -101,43 +100,32 @@ impl EncryptionScheme { buffer: &mut [u8], shared_secret: &SharedSecretKey, commitment: &Commitment, - output_index: u32, ) { - let key = Self::kdf(shared_secret, commitment, output_index); + let key = Self::kdf(shared_secret, commitment); let mut cipher = ChaCha20::new(&key.into(), &[0; 12].into()); cipher.apply_keystream(buffer); } - fn kdf( - shared_secret: &SharedSecretKey, - commitment: &Commitment, - output_index: u32, - ) -> [u8; 32] { + fn kdf(shared_secret: &SharedSecretKey, commitment: &Commitment) -> [u8; 32] { let mut bytes = Vec::new(); bytes.extend_from_slice(b"LEE/v0.2/KDF-SHA256/"); bytes.extend_from_slice(&shared_secret.0); bytes.extend_from_slice(&commitment.to_byte_array()); - bytes.extend_from_slice(&output_index.to_le_bytes()); Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap() } #[cfg(feature = "host")] - #[expect( - clippy::print_stdout, - reason = "This is the current way to debug things. TODO: fix later" - )] #[must_use] pub fn decrypt( ciphertext: &Ciphertext, shared_secret: &SharedSecretKey, commitment: &Commitment, - output_index: u32, ) -> Option<(PrivateAccountKind, Account)> { use std::io::Cursor; let mut buffer = ciphertext.0.clone(); - Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index); + Self::symmetric_transform(&mut buffer, shared_secret, commitment); if buffer.len() < PrivateAccountKind::HEADER_LEN { return None; @@ -148,16 +136,6 @@ impl EncryptionScheme { let mut cursor = Cursor::new(&buffer[PrivateAccountKind::HEADER_LEN..]); Account::from_cursor(&mut cursor) - .inspect_err(|err| { - println!( - "Failed to decode {ciphertext:?} \n - with secret {:?} ,\n - commitment {commitment:?} ,\n - and output_index {output_index} ,\n - with error {err:?}", - shared_secret.0 - ); - }) .ok() .map(|account| (kind, account)) } @@ -182,7 +160,6 @@ mod tests { &PrivateAccountKind::Regular(42), &secret, &commitment, - 0, ); let pda_ct = EncryptionScheme::encrypt( &account, @@ -193,7 +170,6 @@ mod tests { }, &secret, &commitment, - 0, ); assert_eq!(account_ct.0.len(), pda_ct.0.len()); @@ -219,9 +195,9 @@ mod tests { let kind = PrivateAccountKind::Regular(0); let commitment = crate::Commitment::new(&AccountId::new([7_u8; 32]), &account); - let ct = EncryptionScheme::encrypt(&account, &kind, &sender_ss, &commitment, 0); + let ct = EncryptionScheme::encrypt(&account, &kind, &sender_ss, &commitment); let (decoded_kind, decoded_account) = - EncryptionScheme::decrypt(&ct, &receiver_ss, &commitment, 0) + EncryptionScheme::decrypt(&ct, &receiver_ss, &commitment) .expect("decryption must succeed with correct shared secret"); assert_eq!(decoded_account, account); @@ -229,7 +205,7 @@ mod tests { // Wrong shared secret must not decrypt correctly. let wrong_ss = SharedSecretKey([0_u8; 32]); - let bad = EncryptionScheme::decrypt(&ct, &wrong_ss, &commitment, 0); + let bad = EncryptionScheme::decrypt(&ct, &wrong_ss, &commitment); assert!( bad.is_none() || bad.is_some_and(|(_, a)| a.balance != 999), "wrong shared secret must not produce the correct plaintext" diff --git a/lee/state_machine/src/privacy_preserving_transaction/message.rs b/lee/state_machine/src/privacy_preserving_transaction/message.rs index b2594912..243910cb 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/message.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/message.rs @@ -205,7 +205,6 @@ pub mod tests { &PrivateAccountKind::Regular(0), &shared_secret, &commitment, - 2, ); let encrypted_account_data = EncryptedAccountData::new(ciphertext.clone(), &npk, &vpk, epk.clone()); 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 8c8ec2a4..89fec367 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs @@ -30,7 +30,6 @@ pub fn compute_circuit_output( "Invalid account_identities length" ); - let mut output_index = 0; for (pos, (account_identity, (pre_state, post_state))) in account_identities.iter().zip(states_iter).enumerate() { @@ -68,7 +67,6 @@ pub fn compute_circuit_output( emit_private_output( &mut output, - &mut output_index, post_state, &account_id, &PrivateAccountKind::Regular(*identifier), @@ -106,7 +104,6 @@ pub fn compute_circuit_output( emit_private_output( &mut output, - &mut output_index, post_state, &account_id, &PrivateAccountKind::Regular(*identifier), @@ -145,7 +142,6 @@ pub fn compute_circuit_output( emit_private_output( &mut output, - &mut output_index, post_state, &account_id, &PrivateAccountKind::Regular(*identifier), @@ -192,7 +188,6 @@ pub fn compute_circuit_output( .expect("PrivatePdaInit position must be in pda_seed_by_position"); emit_private_output( &mut output, - &mut output_index, post_state, &account_id, &PrivateAccountKind::Pda { @@ -240,7 +235,6 @@ pub fn compute_circuit_output( .expect("PrivatePdaUpdate position must be in pda_seed_by_position"); emit_private_output( &mut output, - &mut output_index, post_state, &account_id, &PrivateAccountKind::Pda { @@ -267,7 +261,6 @@ pub fn compute_circuit_output( )] fn emit_private_output( output: &mut PrivacyPreservingCircuitOutput, - output_index: &mut u32, post_state: Account, account_id: &AccountId, kind: &PrivateAccountKind, @@ -288,7 +281,6 @@ fn emit_private_output( kind, shared_secret, &commitment_post, - *output_index, ); output.new_commitments.push(commitment_post); @@ -299,9 +291,6 @@ fn emit_private_output( epk: epk.clone(), view_tag, }); - *output_index = output_index - .checked_add(1) - .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); } fn compute_update_nullifier_and_set_digest( diff --git a/tools/crypto_primitives_bench/benches/primitives.rs b/tools/crypto_primitives_bench/benches/primitives.rs index 11c11d9b..6aac79a6 100644 --- a/tools/crypto_primitives_bench/benches/primitives.rs +++ b/tools/crypto_primitives_bench/benches/primitives.rs @@ -52,16 +52,14 @@ fn bench_encryption(c: &mut Criterion) { let commitment = Commitment::new(&account_id, &account); let (shared, _epk) = SharedSecretKey::encapsulate(&recipient_kc.viewing_public_key); let kind = PrivateAccountKind::Regular(0_u128); - let output_index: u32 = 0; - let mut g = c.benchmark_group("encryption"); g.sample_size(50).noise_threshold(0.05); g.bench_function("encrypt", |b| { - b.iter(|| EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index)); + b.iter(|| EncryptionScheme::encrypt(&account, &kind, &shared, &commitment)); }); - let ct = EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index); + let ct = EncryptionScheme::encrypt(&account, &kind, &shared, &commitment); g.bench_function("decrypt", |b| { - b.iter(|| EncryptionScheme::decrypt(&ct, &shared, &commitment, output_index)); + b.iter(|| EncryptionScheme::decrypt(&ct, &shared, &commitment)); }); g.finish(); }