From 082c8de59c666c2361b70aba0c4429c96c6c5b16 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 16 Sep 2022 22:04:57 -0600 Subject: [PATCH] Add kdf_personalized to OrchardDomain --- CHANGELOG.md | 5 ++++ Cargo.toml | 4 +++ src/bundle.rs | 18 +++---------- src/keys.rs | 15 ++++++++--- src/note_encryption.rs | 60 +++++++++++++++++++++++++++++++++++++----- 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ac4429..d5b5648a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to Rust's notion of ## [Unreleased] +### Added +- Feature `encrypt-to-recipient`: + - an implementation of the `zcash_note_encryption::PayloadEncryptionDomain` + trait for `OrchardDomain`. + ## [0.5.0] - 2023-06-06 ### Changed - Migrated to `zcash_note_encryption 0.4`, `incrementalmerkletree 0.4`, `bridgetree 0.3`. diff --git a/Cargo.toml b/Cargo.toml index 124bf9cf..9467a6cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ default = ["multicore"] multicore = ["halo2_proofs/multicore"] dev-graph = ["halo2_proofs/dev-graph", "image", "plotters"] test-dependencies = ["proptest"] +encrypt-to-recipient = ["zcash_note_encryption/encrypt-to-recipient"] [[bench]] name = "note_decryption" @@ -90,3 +91,6 @@ debug = true [profile.bench] debug = true + +[patch.crates-io] +zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "353c306e06416a9a0d15f9d1208cc0bac77e1bb6" } diff --git a/src/bundle.rs b/src/bundle.rs index 228b9d10..ad816131 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -330,14 +330,8 @@ impl Bundle { .filter_map(|(idx, action)| { let domain = OrchardDomain::for_action(action); keys.iter().find_map(move |key| { - try_output_recovery_with_ovk( - &domain, - key, - action, - action.cv_net(), - &action.encrypted_note().out_ciphertext, - ) - .map(|(n, a, m)| (idx, key.clone(), n, a, m)) + try_output_recovery_with_ovk(&domain, key, action) + .map(|(n, a, m)| (idx, key.clone(), n, a, m)) }) }) .collect() @@ -353,13 +347,7 @@ impl Bundle { ) -> Option<(Note, Address, [u8; 512])> { self.actions.get(action_idx).and_then(move |action| { let domain = OrchardDomain::for_action(action); - try_output_recovery_with_ovk( - &domain, - key, - action, - action.cv_net(), - &action.encrypted_note().out_ciphertext, - ) + try_output_recovery_with_ovk(&domain, key, action) }) } } diff --git a/src/keys.rs b/src/keys.rs index a7856f91..5d7494d6 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -27,7 +27,7 @@ use crate::{ zip32::{self, ChildIndex, ExtendedSpendingKey}, }; -const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF"; +pub(crate) const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF"; const ZIP32_PURPOSE: u32 = 32; /// A spending key, from which all key material is derived. @@ -919,17 +919,26 @@ impl SharedSecret { /// /// [concreteorchardkdf]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf pub(crate) fn kdf_orchard(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash { - Self::kdf_orchard_inner(self.0.to_affine(), ephemeral_key) + self.kdf_orchard_personalized(KDF_ORCHARD_PERSONALIZATION, ephemeral_key) + } + + pub(crate) fn kdf_orchard_personalized( + self, + personalization: &[u8; 16], + ephemeral_key: &EphemeralKeyBytes, + ) -> Blake2bHash { + Self::kdf_orchard_inner(personalization, self.0.to_affine(), ephemeral_key) } /// Only for direct use in batched note encryption. pub(crate) fn kdf_orchard_inner( + personalization: &[u8; 16], secret: pallas::Affine, ephemeral_key: &EphemeralKeyBytes, ) -> Blake2bHash { Params::new() .hash_length(32) - .personal(KDF_ORCHARD_PERSONALIZATION) + .personal(personalization) .to_state() .update(&secret.to_bytes()) .update(&ephemeral_key.0) diff --git a/src/note_encryption.rs b/src/note_encryption.rs index dc0766f0..a34f20b0 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -5,9 +5,9 @@ use core::fmt; use blake2b_simd::{Hash, Params}; use group::ff::PrimeField; use zcash_note_encryption::{ - BatchDomain, Domain, EphemeralKeyBytes, NotePlaintextBytes, OutPlaintextBytes, - OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, - OUT_PLAINTEXT_SIZE, + BatchDomain, Domain, EphemeralKeyBytes, KeyedOutput, NotePlaintextBytes, OutPlaintextBytes, + OutgoingCipherKey, RecoverableOutput, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, + NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; use crate::{ @@ -15,12 +15,16 @@ use crate::{ keys::{ DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret, + KDF_ORCHARD_PERSONALIZATION, }, note::{ExtractedNoteCommitment, Nullifier, RandomSeed}, value::{NoteValue, ValueCommitment}, Address, Note, }; +#[cfg(feature = "encrypt-to-recipient")] +use zcash_note_encryption::PayloadEncryptionDomain; + const PRF_OCK_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_Orchardock"; /// Defined in [Zcash Protocol Spec ยง 5.4.2: Pseudo Random Functions][concreteprfs]. @@ -245,16 +249,44 @@ impl BatchDomain for OrchardDomain { SharedSecret::batch_to_affine(shared_secrets) .zip(ephemeral_keys.into_iter()) .map(|(secret, ephemeral_key)| { - secret.map(|dhsecret| SharedSecret::kdf_orchard_inner(dhsecret, ephemeral_key)) + secret.map(|dhsecret| { + SharedSecret::kdf_orchard_inner( + KDF_ORCHARD_PERSONALIZATION, + dhsecret, + ephemeral_key, + ) + }) }) .collect() } } +#[cfg(feature = "encrypt-to-recipient")] +const ORCHARD_ASSOC_KEY_KDF_PERS_PREFIX: &[u8; 8] = b"ZcashOAK"; + +/// This implementation of `PayloadEncryptionDomain` permits the use of an 8-byte personalization +/// suffix. The personalization prefix is fixed to `b"ZcashOAK"` to ensure that no collision with +/// key personalization used for note encryption or with Sapling associated keys is possible. +#[cfg(feature = "encrypt-to-recipient")] +impl PayloadEncryptionDomain for OrchardDomain { + type KdfPersonalization = [u8; 8]; + + fn kdf_personalized( + personalization: &Self::KdfPersonalization, + secret: Self::SharedSecret, + ephemeral_key: &EphemeralKeyBytes, + ) -> Self::SymmetricKey { + let mut oak_pers = [0u8; 16]; + oak_pers[..8].copy_from_slice(ORCHARD_ASSOC_KEY_KDF_PERS_PREFIX); + oak_pers[8..].copy_from_slice(personalization); + secret.kdf_orchard_personalized(&oak_pers, ephemeral_key) + } +} + /// Implementation of in-band secret distribution for Orchard bundles. pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption; -impl ShieldedOutput for Action { +impl KeyedOutput for Action { fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.encrypted_note().epk_bytes) } @@ -262,12 +294,24 @@ impl ShieldedOutput for Action { fn cmstar_bytes(&self) -> [u8; 32] { self.cmx().to_bytes() } +} +impl ShieldedOutput for Action { fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] { &self.encrypted_note().enc_ciphertext } } +impl RecoverableOutput for Action { + fn cv(&self) -> &ValueCommitment { + self.cv_net() + } + + fn out_ciphertext(&self) -> &[u8; OUT_CIPHERTEXT_SIZE] { + &self.encrypted_note().out_ciphertext + } +} + /// A compact Action for light clients. pub struct CompactAction { nullifier: Nullifier, @@ -295,7 +339,7 @@ impl From<&Action> for CompactAction { } } -impl ShieldedOutput for CompactAction { +impl KeyedOutput for CompactAction { fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(self.ephemeral_key.0) } @@ -303,7 +347,9 @@ impl ShieldedOutput for CompactAction { fn cmstar_bytes(&self) -> [u8; 32] { self.cmx.to_bytes() } +} +impl ShieldedOutput for CompactAction { fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] { &self.enc_ciphertext } @@ -437,7 +483,7 @@ mod tests { None => panic!("Compact note decryption failed"), } - match try_output_recovery_with_ovk(&domain, &ovk, &action, &cv_net, &tv.c_out) { + match try_output_recovery_with_ovk(&domain, &ovk, &action) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); assert_eq!(decrypted_to, recipient);