mirror of https://github.com/zcash/orchard.git
Add kdf_personalized to OrchardDomain
This commit is contained in:
parent
2103db8290
commit
082c8de59c
|
@ -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`.
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/keys.rs
15
src/keys.rs
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue