From 0b4d7bc9c6055a7562efe55af1647405062f2de9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 15 Oct 2022 21:00:09 +0000 Subject: [PATCH] Migrate note encryption to use prepared w-NAF APIs --- CHANGELOG.md | 5 +++- Cargo.toml | 2 +- benches/note_decryption.rs | 5 ++-- src/bundle.rs | 8 ++++--- src/keys.rs | 48 ++++++++++++++++++++++++++++++-------- src/note_encryption.rs | 14 ++++++----- src/spec.rs | 39 +++++++++++++++++++++++++++---- tests/builder.rs | 4 ++-- 8 files changed, 96 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 760b2742..6deb97bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,10 @@ and this project adheres to Rust's notion of ### Added - `orchard::Proof::add_to_batch` - `orchard::address::Address::diversifier` -- `orchard::keys::Diversifier::from_bytes` +- `orchard::keys:`: + - `Diversifier::from_bytes` + - `PreparedEphemeralPublicKey` + - `PreparedIncomingViewingKey` - `orchard::note`: - `RandomSeed` - `Note::{from_parts, rseed}` diff --git a/Cargo.toml b/Cargo.toml index cd6bf741..0c4ffeb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ bitvec = "1" blake2b_simd = "1" ff = "0.12" fpe = "0.5" -group = "0.12" +group = "0.12.1" halo2_gadgets = "0.2" halo2_proofs = "0.2" hex = "0.4" diff --git a/benches/note_decryption.rs b/benches/note_decryption.rs index cab432fe..5385e4eb 100644 --- a/benches/note_decryption.rs +++ b/benches/note_decryption.rs @@ -3,7 +3,7 @@ use orchard::{ builder::Builder, bundle::Flags, circuit::ProvingKey, - keys::{FullViewingKey, Scope, SpendingKey}, + keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey}, note_encryption::{CompactAction, OrchardDomain}, value::NoteValue, Anchor, Bundle, @@ -21,6 +21,7 @@ fn bench_note_decryption(c: &mut Criterion) { let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap()); let valid_ivk = fvk.to_ivk(Scope::External); let recipient = valid_ivk.address_at(0u32); + let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk); // Compact actions don't have the full AEAD ciphertext, so ZIP 307 trial-decryption // relies on an invalid ivk resulting in random noise for which the note commitment @@ -39,7 +40,7 @@ fn bench_note_decryption(c: &mut Criterion) { let mut sk = [0; 32]; sk[..4].copy_from_slice(&i.to_le_bytes()); let fvk = FullViewingKey::from(&SpendingKey::from_bytes(sk).unwrap()); - fvk.to_ivk(Scope::External) + PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)) }) .collect(); diff --git a/src/bundle.rs b/src/bundle.rs index c70ca635..2e924fa9 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -17,7 +17,7 @@ use crate::{ address::Address, bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, circuit::{Instance, Proof, VerifyingKey}, - keys::{IncomingViewingKey, OutgoingViewingKey}, + keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, note::Note, note_encryption::OrchardDomain, primitives::redpallas::{self, Binding, SpendAuth}, @@ -290,7 +290,8 @@ impl Bundle { .filter_map(|(idx, action)| { let domain = OrchardDomain::for_action(action); keys.iter().find_map(move |ivk| { - try_note_decryption(&domain, ivk, action) + let prepared_ivk = PreparedIncomingViewingKey::new(ivk); + try_note_decryption(&domain, &prepared_ivk, action) .map(|(n, a, m)| (idx, ivk.clone(), n, a, m)) }) }) @@ -305,9 +306,10 @@ impl Bundle { action_idx: usize, key: &IncomingViewingKey, ) -> Option<(Note, Address, [u8; 512])> { + let prepared_ivk = PreparedIncomingViewingKey::new(key); self.actions.get(action_idx).and_then(move |action| { let domain = OrchardDomain::for_action(action); - try_note_decryption(&domain, key, action) + try_note_decryption(&domain, &prepared_ivk, action) }) } diff --git a/src/keys.rs b/src/keys.rs index e277264d..24145c23 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -20,8 +20,9 @@ use crate::{ address::Address, primitives::redpallas::{self, SpendAuth}, spec::{ - commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, - NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, + commit_ivk, diversify_hash, extract_p, ka_orchard, ka_orchard_prepared, prf_nf, to_base, + to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, + PreparedNonIdentityBase, PreparedNonZeroScalar, PrfExpand, }, zip32::{self, ChildIndex, ExtendedSpendingKey}, }; @@ -627,7 +628,8 @@ impl KeyAgreementPrivateKey { /// Returns the payment address for this key corresponding to the given diversifier. fn address(&self, d: Diversifier) -> Address { - let pk_d = DiversifiedTransmissionKey::derive_inner(self, &d); + let prepared_ivk = PreparedIncomingViewingKey::new_inner(&self); + let pk_d = DiversifiedTransmissionKey::derive(&prepared_ivk, &d); Address::from_parts(d, pk_d) } } @@ -704,6 +706,22 @@ impl IncomingViewingKey { } } +/// An Orchard incoming viewing key that has been precomputed for trial decryption. +#[derive(Clone, Debug)] +pub struct PreparedIncomingViewingKey(PreparedNonZeroScalar); + +impl PreparedIncomingViewingKey { + /// Performs the necessary precomputations to use an `IncomingViewingKey` for note + /// decryption. + pub fn new(ivk: &IncomingViewingKey) -> Self { + Self::new_inner(&ivk.ivk) + } + + fn new_inner(ivk: &KeyAgreementPrivateKey) -> Self { + Self(PreparedNonZeroScalar::new(&ivk.0)) + } +} + /// A key that provides the capability to recover outgoing transaction information from /// the block chain. /// @@ -753,13 +771,9 @@ impl DiversifiedTransmissionKey { /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents - pub(crate) fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self { - Self::derive_inner(&ivk.ivk, d) - } - - fn derive_inner(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self { - let g_d = diversify_hash(d.as_array()); - DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d)) + pub(crate) fn derive(ivk: &PreparedIncomingViewingKey, d: &Diversifier) -> Self { + let g_d = PreparedNonIdentityBase::new(diversify_hash(d.as_array())); + DiversifiedTransmissionKey(ka_orchard_prepared(&ivk.0, &g_d)) } /// $abst_P(bytes)$ @@ -841,6 +855,20 @@ impl EphemeralPublicKey { } } +/// A Sapling ephemeral public key that has been precomputed for trial decryption. +#[derive(Clone, Debug)] +pub struct PreparedEphemeralPublicKey(PreparedNonIdentityBase); + +impl PreparedEphemeralPublicKey { + pub(crate) fn new(epk: EphemeralPublicKey) -> Self { + PreparedEphemeralPublicKey(PreparedNonIdentityBase::new(epk.0)) + } + + pub(crate) fn agree(&self, ivk: &PreparedIncomingViewingKey) -> SharedSecret { + SharedSecret(ka_orchard_prepared(&ivk.0, &self.0)) + } +} + /// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{SharedSecret} := \mathbb{P}^{\ast}$ /// /// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement]. diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 0b249eec..762cc560 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -14,7 +14,7 @@ use crate::{ action::Action, keys::{ DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey, - IncomingViewingKey, OutgoingViewingKey, SharedSecret, + OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret, }, note::{ExtractedNoteCommitment, Nullifier, RandomSeed}, spec::diversify_hash, @@ -102,13 +102,13 @@ impl OrchardDomain { impl Domain for OrchardDomain { type EphemeralSecretKey = EphemeralSecretKey; type EphemeralPublicKey = EphemeralPublicKey; - type PreparedEphemeralPublicKey = EphemeralPublicKey; + type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey; type SharedSecret = SharedSecret; type SymmetricKey = Hash; type Note = Note; type Recipient = Address; type DiversifiedTransmissionKey = DiversifiedTransmissionKey; - type IncomingViewingKey = IncomingViewingKey; + type IncomingViewingKey = PreparedIncomingViewingKey; type OutgoingViewingKey = OutgoingViewingKey; type ValueCommitment = ValueCommitment; type ExtractedCommitment = ExtractedNoteCommitment; @@ -124,7 +124,7 @@ impl Domain for OrchardDomain { } fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey { - epk + PreparedEphemeralPublicKey::new(epk) } fn ka_derive_public( @@ -352,7 +352,7 @@ mod tests { action::Action, keys::{ DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey, - OutgoingViewingKey, + OutgoingViewingKey, PreparedIncomingViewingKey, }, note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext}, primitives::redpallas, @@ -370,7 +370,9 @@ mod tests { // // Recipient key material - let ivk = IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap(); + let ivk = PreparedIncomingViewingKey::new( + &IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap(), + ); let ovk = OutgoingViewingKey::from(tv.ovk); let d = Diversifier::from_bytes(tv.default_d); let pk_d = DiversifiedTransmissionKey::from_bytes(&tv.default_pk_d).unwrap(); diff --git a/src/spec.rs b/src/spec.rs index 67f7a7c4..ab5413ae 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -4,8 +4,7 @@ use core::iter; use core::ops::Deref; use ff::{Field, PrimeField, PrimeFieldBits}; -use group::GroupEncoding; -use group::{Curve, Group}; +use group::{Curve, Group, GroupEncoding, WnafBase, WnafScalar}; use halo2_gadgets::{poseidon::primitives as poseidon, sinsemilla::primitives as sinsemilla}; use halo2_proofs::arithmetic::{CurveAffine, CurveExt, FieldExt}; use pasta_curves::pallas; @@ -140,6 +139,26 @@ impl Deref for NonZeroPallasScalar { } } +const PREPARED_WINDOW_SIZE: usize = 4; + +#[derive(Clone, Debug)] +pub(crate) struct PreparedNonIdentityBase(WnafBase); + +impl PreparedNonIdentityBase { + pub(crate) fn new(base: NonIdentityPallasPoint) -> Self { + PreparedNonIdentityBase(WnafBase::new(base.0)) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PreparedNonZeroScalar(WnafScalar); + +impl PreparedNonZeroScalar { + pub(crate) fn new(scalar: &NonZeroPallasScalar) -> Self { + PreparedNonZeroScalar(WnafScalar::new(scalar)) + } +} + /// $\mathsf{ToBase}^\mathsf{Orchard}(x) := LEOS2IP_{\ell_\mathsf{PRFexpand}}(x) (mod q_P)$ /// /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. @@ -213,8 +232,20 @@ pub(crate) fn ka_orchard( sk: &NonZeroPallasScalar, b: &NonIdentityPallasPoint, ) -> NonIdentityPallasPoint { - let mut wnaf = group::Wnaf::new(); - NonIdentityPallasPoint(wnaf.scalar(sk.deref()).base(*b.deref())) + ka_orchard_prepared( + &PreparedNonZeroScalar::new(sk), + &PreparedNonIdentityBase::new(*b), + ) +} + +/// Defined in [Zcash Protocol Spec § 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement]. +/// +/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement +pub(crate) fn ka_orchard_prepared( + sk: &PreparedNonZeroScalar, + b: &PreparedNonIdentityBase, +) -> NonIdentityPallasPoint { + NonIdentityPallasPoint(&b.0 * &sk.0) } /// Coordinate extractor for Pallas. diff --git a/tests/builder.rs b/tests/builder.rs index 35c53976..f9b0f271 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -3,7 +3,7 @@ use orchard::{ builder::Builder, bundle::{Authorized, Flags}, circuit::{ProvingKey, VerifyingKey}, - keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, + keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::ExtractedNoteCommitment, note_encryption::OrchardDomain, tree::{MerkleHashOrchard, MerklePath}, @@ -57,7 +57,7 @@ fn bundle_chain() { // Create a shielded bundle spending the previous output. let shielded_bundle: Bundle<_, i64> = { - let ivk = fvk.to_ivk(Scope::External); + let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External)); let (note, _, _) = shielding_bundle .actions() .iter()