Add kdf_personalized to OrchardDomain

This commit is contained in:
Kris Nuttycombe 2022-09-16 22:04:57 -06:00
parent 2103db8290
commit 082c8de59c
5 changed files with 77 additions and 25 deletions

View File

@ -7,6 +7,11 @@ and this project adheres to Rust's notion of
## [Unreleased] ## [Unreleased]
### Added
- Feature `encrypt-to-recipient`:
- an implementation of the `zcash_note_encryption::PayloadEncryptionDomain`
trait for `OrchardDomain`.
## [0.5.0] - 2023-06-06 ## [0.5.0] - 2023-06-06
### Changed ### Changed
- Migrated to `zcash_note_encryption 0.4`, `incrementalmerkletree 0.4`, `bridgetree 0.3`. - Migrated to `zcash_note_encryption 0.4`, `incrementalmerkletree 0.4`, `bridgetree 0.3`.

View File

@ -72,6 +72,7 @@ default = ["multicore"]
multicore = ["halo2_proofs/multicore"] multicore = ["halo2_proofs/multicore"]
dev-graph = ["halo2_proofs/dev-graph", "image", "plotters"] dev-graph = ["halo2_proofs/dev-graph", "image", "plotters"]
test-dependencies = ["proptest"] test-dependencies = ["proptest"]
encrypt-to-recipient = ["zcash_note_encryption/encrypt-to-recipient"]
[[bench]] [[bench]]
name = "note_decryption" name = "note_decryption"
@ -90,3 +91,6 @@ debug = true
[profile.bench] [profile.bench]
debug = true debug = true
[patch.crates-io]
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "353c306e06416a9a0d15f9d1208cc0bac77e1bb6" }

View File

@ -330,14 +330,8 @@ impl<T: Authorization, V> Bundle<T, V> {
.filter_map(|(idx, action)| { .filter_map(|(idx, action)| {
let domain = OrchardDomain::for_action(action); let domain = OrchardDomain::for_action(action);
keys.iter().find_map(move |key| { keys.iter().find_map(move |key| {
try_output_recovery_with_ovk( try_output_recovery_with_ovk(&domain, key, action)
&domain, .map(|(n, a, m)| (idx, key.clone(), n, a, m))
key,
action,
action.cv_net(),
&action.encrypted_note().out_ciphertext,
)
.map(|(n, a, m)| (idx, key.clone(), n, a, m))
}) })
}) })
.collect() .collect()
@ -353,13 +347,7 @@ impl<T: Authorization, V> Bundle<T, V> {
) -> Option<(Note, Address, [u8; 512])> { ) -> Option<(Note, Address, [u8; 512])> {
self.actions.get(action_idx).and_then(move |action| { self.actions.get(action_idx).and_then(move |action| {
let domain = OrchardDomain::for_action(action); let domain = OrchardDomain::for_action(action);
try_output_recovery_with_ovk( try_output_recovery_with_ovk(&domain, key, action)
&domain,
key,
action,
action.cv_net(),
&action.encrypted_note().out_ciphertext,
)
}) })
} }
} }

View File

@ -27,7 +27,7 @@ use crate::{
zip32::{self, ChildIndex, ExtendedSpendingKey}, 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; const ZIP32_PURPOSE: u32 = 32;
/// A spending key, from which all key material is derived. /// 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 /// [concreteorchardkdf]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf
pub(crate) fn kdf_orchard(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash { 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. /// Only for direct use in batched note encryption.
pub(crate) fn kdf_orchard_inner( pub(crate) fn kdf_orchard_inner(
personalization: &[u8; 16],
secret: pallas::Affine, secret: pallas::Affine,
ephemeral_key: &EphemeralKeyBytes, ephemeral_key: &EphemeralKeyBytes,
) -> Blake2bHash { ) -> Blake2bHash {
Params::new() Params::new()
.hash_length(32) .hash_length(32)
.personal(KDF_ORCHARD_PERSONALIZATION) .personal(personalization)
.to_state() .to_state()
.update(&secret.to_bytes()) .update(&secret.to_bytes())
.update(&ephemeral_key.0) .update(&ephemeral_key.0)

View File

@ -5,9 +5,9 @@ use core::fmt;
use blake2b_simd::{Hash, Params}; use blake2b_simd::{Hash, Params};
use group::ff::PrimeField; use group::ff::PrimeField;
use zcash_note_encryption::{ use zcash_note_encryption::{
BatchDomain, Domain, EphemeralKeyBytes, NotePlaintextBytes, OutPlaintextBytes, BatchDomain, Domain, EphemeralKeyBytes, KeyedOutput, NotePlaintextBytes, OutPlaintextBytes,
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OutgoingCipherKey, RecoverableOutput, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE,
OUT_PLAINTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE,
}; };
use crate::{ use crate::{
@ -15,12 +15,16 @@ use crate::{
keys::{ keys::{
DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey, DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey,
OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret, OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret,
KDF_ORCHARD_PERSONALIZATION,
}, },
note::{ExtractedNoteCommitment, Nullifier, RandomSeed}, note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
value::{NoteValue, ValueCommitment}, value::{NoteValue, ValueCommitment},
Address, Note, Address, Note,
}; };
#[cfg(feature = "encrypt-to-recipient")]
use zcash_note_encryption::PayloadEncryptionDomain;
const PRF_OCK_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_Orchardock"; const PRF_OCK_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_Orchardock";
/// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs]. /// 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) SharedSecret::batch_to_affine(shared_secrets)
.zip(ephemeral_keys.into_iter()) .zip(ephemeral_keys.into_iter())
.map(|(secret, ephemeral_key)| { .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() .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. /// Implementation of in-band secret distribution for Orchard bundles.
pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption<OrchardDomain>; pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption<OrchardDomain>;
impl<T> ShieldedOutput<OrchardDomain, ENC_CIPHERTEXT_SIZE> for Action<T> { impl<T> KeyedOutput<OrchardDomain> for Action<T> {
fn ephemeral_key(&self) -> EphemeralKeyBytes { fn ephemeral_key(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.encrypted_note().epk_bytes) EphemeralKeyBytes(self.encrypted_note().epk_bytes)
} }
@ -262,12 +294,24 @@ impl<T> ShieldedOutput<OrchardDomain, ENC_CIPHERTEXT_SIZE> for Action<T> {
fn cmstar_bytes(&self) -> [u8; 32] { fn cmstar_bytes(&self) -> [u8; 32] {
self.cmx().to_bytes() self.cmx().to_bytes()
} }
}
impl<T> ShieldedOutput<OrchardDomain, ENC_CIPHERTEXT_SIZE> for Action<T> {
fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] { fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
&self.encrypted_note().enc_ciphertext &self.encrypted_note().enc_ciphertext
} }
} }
impl<T> RecoverableOutput<OrchardDomain, ENC_CIPHERTEXT_SIZE> for Action<T> {
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. /// A compact Action for light clients.
pub struct CompactAction { pub struct CompactAction {
nullifier: Nullifier, nullifier: Nullifier,
@ -295,7 +339,7 @@ impl<T> From<&Action<T>> for CompactAction {
} }
} }
impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactAction { impl KeyedOutput<OrchardDomain> for CompactAction {
fn ephemeral_key(&self) -> EphemeralKeyBytes { fn ephemeral_key(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.ephemeral_key.0) EphemeralKeyBytes(self.ephemeral_key.0)
} }
@ -303,7 +347,9 @@ impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactAction {
fn cmstar_bytes(&self) -> [u8; 32] { fn cmstar_bytes(&self) -> [u8; 32] {
self.cmx.to_bytes() self.cmx.to_bytes()
} }
}
impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactAction {
fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] { fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
&self.enc_ciphertext &self.enc_ciphertext
} }
@ -437,7 +483,7 @@ mod tests {
None => panic!("Compact note decryption failed"), 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)) => { Some((decrypted_note, decrypted_to, decrypted_memo)) => {
assert_eq!(decrypted_note, note); assert_eq!(decrypted_note, note);
assert_eq!(decrypted_to, recipient); assert_eq!(decrypted_to, recipient);