From 7fb80d55d64c3d25ff73542cc671c1f224372584 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 6 Jan 2023 20:31:27 +0000 Subject: [PATCH] Introduce `sapling::keys::SharedSecret` type --- zcash_primitives/CHANGELOG.md | 6 +- zcash_primitives/src/sapling/keys.rs | 71 ++++++++++++++++++- .../src/sapling/note_encryption.rs | 60 +++++----------- 3 files changed, 91 insertions(+), 46 deletions(-) diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index af969f624..c08152053 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -7,8 +7,10 @@ and this library adheres to Rust's notion of ## [Unreleased] ### Added -- `zcash_primitives::sapling::value`, containing types for handling Sapling note - values and value commitments. +- `zcash_primitives::sapling`: + - `keys::SharedSecret` + - `value`, containing types for handling Sapling note values and value + commitments. ### Changed - `zcash_primitives::transaction::components::sapling::builder`: diff --git a/zcash_primitives/src/sapling/keys.rs b/zcash_primitives/src/sapling/keys.rs index ac39cb1b8..0436c8e76 100644 --- a/zcash_primitives/src/sapling/keys.rs +++ b/zcash_primitives/src/sapling/keys.rs @@ -8,6 +8,7 @@ use std::io::{self, Read, Write}; use super::{ address::PaymentAddress, + note_encryption::KDF_SAPLING_PERSONALIZATION, spec::{crh_ivk, diversify_hash}, }; use crate::{ @@ -15,9 +16,11 @@ use crate::{ keys::prf_expand, }; +use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use ff::PrimeField; -use group::{Group, GroupEncoding}; +use group::{Curve, Group, GroupEncoding}; use subtle::CtOption; +use zcash_note_encryption::EphemeralKeyBytes; /// Errors that can occur in the decoding of Sapling spending keys. pub enum DecodingError { @@ -254,6 +257,72 @@ impl Diversifier { } } +/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{SharedSecret} := \mathbb{J}^{(r)}$ +/// +/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement]. +/// +/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement +#[derive(Debug)] +pub struct SharedSecret(jubjub::SubgroupPoint); + +impl SharedSecret { + /// TODO: Remove once public API is changed. + pub(crate) fn from_inner(epk: jubjub::SubgroupPoint) -> Self { + SharedSecret(epk) + } + + /// For checking test vectors only. + #[cfg(test)] + pub(crate) fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// Only for use in batched note encryption. + pub(crate) fn batch_to_affine( + shared_secrets: Vec>, + ) -> impl Iterator> { + // Filter out the positions for which ephemeral_key was not a valid encoding. + let secrets: Vec<_> = shared_secrets + .iter() + .filter_map(|s| s.as_ref().map(|s| jubjub::ExtendedPoint::from(s.0))) + .collect(); + + // Batch-normalize the shared secrets. + let mut secrets_affine = vec![jubjub::AffinePoint::identity(); secrets.len()]; + group::Curve::batch_normalize(&secrets, &mut secrets_affine); + + // Re-insert the invalid ephemeral_key positions. + let mut secrets_affine = secrets_affine.into_iter(); + shared_secrets + .into_iter() + .map(move |s| s.and_then(|_| secrets_affine.next())) + } + + /// Defined in [Zcash Protocol Spec ยง 5.4.5.4: Sapling Key Agreement][concretesaplingkdf]. + /// + /// [concretesaplingkdf]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkdf + pub(crate) fn kdf_sapling(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash { + Self::kdf_sapling_inner( + jubjub::ExtendedPoint::from(self.0).to_affine(), + ephemeral_key, + ) + } + + /// Only for direct use in batched note encryption. + pub(crate) fn kdf_sapling_inner( + secret: jubjub::AffinePoint, + ephemeral_key: &EphemeralKeyBytes, + ) -> Blake2bHash { + Blake2bParams::new() + .hash_length(32) + .personal(KDF_SAPLING_PERSONALIZATION) + .to_state() + .update(&secret.to_bytes()) + .update(ephemeral_key.as_ref()) + .finalize() + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::collection::vec; diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 407c07bdf..a0f59566c 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -6,7 +6,6 @@ use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use byteorder::{LittleEndian, WriteBytesExt}; use ff::PrimeField; use group::{cofactor::CofactorGroup, GroupEncoding, WnafBase, WnafScalar}; -use jubjub::{AffinePoint, ExtendedPoint}; use memuse::DynamicUsage; use rand_core::RngCore; @@ -21,8 +20,9 @@ use crate::{ consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, memo::MemoBytes, sapling::{ - keys::OutgoingViewingKey, value::ValueCommitment, Diversifier, Note, PaymentAddress, Rseed, - SaplingIvk, + keys::{OutgoingViewingKey, SharedSecret}, + value::ValueCommitment, + Diversifier, Note, PaymentAddress, Rseed, SaplingIvk, }, transaction::components::{ amount::Amount, @@ -78,19 +78,6 @@ fn sapling_ka_agree_prepared(esk: &PreparedScalar, pk_d: &PreparedBase) -> jubju (pk_d * esk).clear_cofactor() } -/// Sapling KDF for note encryption. -/// -/// Implements section 5.4.4.4 of the Zcash Protocol Specification. -fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash { - Blake2bParams::new() - .hash_length(32) - .personal(KDF_SAPLING_PERSONALIZATION) - .to_state() - .update(&dhsecret.to_bytes()) - .update(ephemeral_key.as_ref()) - .finalize() -} - /// Sapling PRF^ock. /// /// Implemented per section 5.4.2 of the Zcash Protocol Specification. @@ -241,7 +228,7 @@ impl Domain for SaplingDomain

{ /// /// Implements section 5.4.4.4 of the Zcash Protocol Specification. fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &EphemeralKeyBytes) -> Blake2bHash { - kdf_sapling(dhsecret, epk) + SharedSecret::from_inner(dhsecret).kdf_sapling(epk) } fn note_plaintext_bytes( @@ -361,30 +348,16 @@ impl BatchDomain for SaplingDomain

{ fn batch_kdf<'a>( items: impl Iterator, &'a EphemeralKeyBytes)>, ) -> Vec> { - let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip(); + let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items + .map(|(shared_secret, ephemeral_key)| { + (shared_secret.map(SharedSecret::from_inner), ephemeral_key) + }) + .unzip(); - let secrets: Vec<_> = shared_secrets - .iter() - .filter_map(|s| s.map(ExtendedPoint::from)) - .collect(); - let mut secrets_affine = vec![AffinePoint::identity(); shared_secrets.len()]; - group::Curve::batch_normalize(&secrets, &mut secrets_affine); - - let mut secrets_affine = secrets_affine.into_iter(); - shared_secrets - .into_iter() - .map(|s| s.and_then(|_| secrets_affine.next())) + SharedSecret::batch_to_affine(shared_secrets) .zip(ephemeral_keys.into_iter()) .map(|(secret, ephemeral_key)| { - secret.map(|dhsecret| { - Blake2bParams::new() - .hash_length(32) - .personal(KDF_SAPLING_PERSONALIZATION) - .to_state() - .update(&dhsecret.to_bytes()) - .update(ephemeral_key.as_ref()) - .finalize() - }) + secret.map(|dhsecret| SharedSecret::kdf_sapling_inner(dhsecret, ephemeral_key)) }) .collect() } @@ -583,7 +556,7 @@ mod tests { }; use super::{ - epk_bytes, kdf_sapling, prf_ock, sapling_ka_agree, sapling_note_encryption, + epk_bytes, prf_ock, sapling_ka_agree, sapling_note_encryption, try_sapling_compact_note_decryption, try_sapling_note_decryption, try_sapling_output_recovery, try_sapling_output_recovery_with_ock, SaplingDomain, }; @@ -597,6 +570,7 @@ mod tests { keys::OutgoingViewingKey, memo::MemoBytes, sapling::{ + keys::SharedSecret, note_encryption::PreparedIncomingViewingKey, util::generate_random_rseed, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment}, @@ -718,8 +692,8 @@ mod tests { let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap(); - let shared_secret = sapling_ka_agree(&esk, &pk_d.into()); - let key = kdf_sapling(shared_secret, ephemeral_key); + let shared_secret = SharedSecret::from_inner(sapling_ka_agree(&esk, &pk_d.into())); + let key = shared_secret.kdf_sapling(ephemeral_key); let mut plaintext = [0; NOTE_PLAINTEXT_SIZE]; plaintext.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); @@ -1439,10 +1413,10 @@ mod tests { // Test the individual components // - let shared_secret = sapling_ka_agree(&esk, &pk_d.into()); + let shared_secret = SharedSecret::from_inner(sapling_ka_agree(&esk, &pk_d.into())); assert_eq!(shared_secret.to_bytes(), tv.shared_secret); - let k_enc = kdf_sapling(shared_secret, &ephemeral_key); + let k_enc = shared_secret.kdf_sapling(&ephemeral_key); assert_eq!(k_enc.as_bytes(), tv.k_enc); let ovk = OutgoingViewingKey(tv.ovk);