From 5baccdf052d29c698b90dd3eff9c0b61b984632b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 4 Mar 2021 14:45:41 -0700 Subject: [PATCH 01/24] Move note_encryption into the sapling module. --- zcash_client_backend/src/decrypt.rs | 6 ++++-- zcash_client_backend/src/welding_rig.rs | 12 ++++++++---- zcash_client_sqlite/src/lib.rs | 6 ++++-- zcash_client_sqlite/src/wallet/transact.rs | 3 +-- zcash_primitives/src/lib.rs | 1 - zcash_primitives/src/sapling.rs | 1 + .../src/{ => sapling}/note_encryption.rs | 0 zcash_primitives/src/transaction/builder.rs | 6 +++--- 8 files changed, 21 insertions(+), 14 deletions(-) rename zcash_primitives/src/{ => sapling}/note_encryption.rs (100%) diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 1ab01f336..3e76a7c5f 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -3,8 +3,10 @@ use std::collections::HashMap; use zcash_primitives::{ consensus::{self, BlockHeight}, memo::MemoBytes, - note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery}, - sapling::{Note, PaymentAddress}, + sapling::{ + note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery}, + Note, PaymentAddress, + }, transaction::Transaction, zip32::ExtendedFullViewingKey, }; diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index c20201d0d..4abb711ce 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -6,8 +6,10 @@ use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_primitives::{ consensus::{self, BlockHeight}, merkle_tree::{CommitmentTree, IncrementalWitness}, - note_encryption::try_sapling_compact_note_decryption, - sapling::{Node, Note, Nullifier, PaymentAddress, SaplingIvk}, + sapling::{ + note_encryption::try_sapling_compact_note_decryption, Node, Note, Nullifier, + PaymentAddress, SaplingIvk, + }, transaction::TxId, zip32::ExtendedFullViewingKey, }; @@ -305,8 +307,10 @@ mod tests { constants::SPENDING_KEY_GENERATOR, memo::MemoBytes, merkle_tree::CommitmentTree, - note_encryption::SaplingNoteEncryption, - sapling::{util::generate_random_rseed, Note, Nullifier, SaplingIvk}, + sapling::{ + note_encryption::SaplingNoteEncryption, util::generate_random_rseed, Note, Nullifier, + SaplingIvk, + }, transaction::components::Amount, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index d9cb368dc..bb4a0cff6 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -563,8 +563,10 @@ mod tests { block::BlockHash, consensus::{BlockHeight, Network, NetworkUpgrade, Parameters}, memo::MemoBytes, - note_encryption::SaplingNoteEncryption, - sapling::{util::generate_random_rseed, Note, Nullifier, PaymentAddress}, + sapling::{ + note_encryption::SaplingNoteEncryption, util::generate_random_rseed, Note, Nullifier, + PaymentAddress, + }, transaction::components::Amount, zip32::ExtendedFullViewingKey, }; diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 1a77a6637..e4b52e31e 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -155,8 +155,7 @@ mod tests { block::BlockHash, consensus::BlockHeight, legacy::TransparentAddress, - note_encryption::try_sapling_output_recovery, - sapling::prover::TxProver, + sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver}, transaction::{components::Amount, Transaction}, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index 67d0f3f7b..a2a113d40 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -15,7 +15,6 @@ pub mod constants; pub mod legacy; pub mod memo; pub mod merkle_tree; -pub mod note_encryption; pub mod sapling; pub mod serialize; pub mod transaction; diff --git a/zcash_primitives/src/sapling.rs b/zcash_primitives/src/sapling.rs index 59d347971..a0e8c910b 100644 --- a/zcash_primitives/src/sapling.rs +++ b/zcash_primitives/src/sapling.rs @@ -2,6 +2,7 @@ pub mod group_hash; pub mod keys; +pub mod note_encryption; pub mod pedersen_hash; pub mod prover; pub mod redjubjub; diff --git a/zcash_primitives/src/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs similarity index 100% rename from zcash_primitives/src/note_encryption.rs rename to zcash_primitives/src/sapling/note_encryption.rs diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 37da07f3b..faafbfd53 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -15,10 +15,10 @@ use crate::{ legacy::TransparentAddress, memo::MemoBytes, merkle_tree::MerklePath, - note_encryption::SaplingNoteEncryption, sapling::{ - keys::OutgoingViewingKey, prover::TxProver, redjubjub::PrivateKey, spend_sig_internal, - util::generate_random_rseed_internal, Diversifier, Node, Note, PaymentAddress, + keys::OutgoingViewingKey, note_encryption::SaplingNoteEncryption, prover::TxProver, + redjubjub::PrivateKey, spend_sig_internal, util::generate_random_rseed_internal, + Diversifier, Node, Note, PaymentAddress, }, transaction::{ components::{ From e77839232deb53397c0f242213e14d6e434e4a10 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 17 Mar 2021 18:22:21 -0600 Subject: [PATCH 02/24] Generalize note encryption and decryption. This commit introduces a `Domain` trait which defines the types and operations that are shared between Sapling and Orchard note encryption and decryption processes. --- zcash_client_backend/src/data_api/wallet.rs | 2 +- zcash_client_backend/src/welding_rig.rs | 6 +- zcash_client_sqlite/src/lib.rs | 14 +- zcash_primitives/benches/note_decryption.rs | 14 +- zcash_primitives/src/sapling.rs | 4 +- .../src/sapling/note_encryption.rs | 623 +++++++++++++----- zcash_primitives/src/transaction/builder.rs | 32 +- 7 files changed, 489 insertions(+), 206 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 85b1028e6..82a9a9a9b 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -205,7 +205,7 @@ where .unwrap(); //DiversifyHash would have to unexpectedly return the zero point for this to be None let note = from - .create_note(u64::from(selected.note_value), selected.rseed) + .create_note(selected.note_value.into(), selected.rseed) .unwrap(); let merkle_path = selected.witness.path().expect("the tree is not empty"); diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 4abb711ce..0eb076454 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -308,7 +308,7 @@ mod tests { memo::MemoBytes, merkle_tree::CommitmentTree, sapling::{ - note_encryption::SaplingNoteEncryption, util::generate_random_rseed, Note, Nullifier, + note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier, SaplingIvk, }, transaction::components::Amount, @@ -372,7 +372,7 @@ mod tests { value: value.into(), rseed, }; - let encryptor = SaplingNoteEncryption::new( + let encryptor = sapling_note_encryption::<_, Network>( Some(extfvk.fvk.ovk), note.clone(), to, @@ -399,7 +399,7 @@ mod tests { let mut cout = CompactOutput::new(); cout.set_cmu(cmu); cout.set_epk(epk); - cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec()); let mut ctx = CompactTx::new(); let mut txid = vec![0; 32]; rng.fill_bytes(&mut txid); diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index bb4a0cff6..0d9047574 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -564,7 +564,7 @@ mod tests { consensus::{BlockHeight, Network, NetworkUpgrade, Parameters}, memo::MemoBytes, sapling::{ - note_encryption::SaplingNoteEncryption, util::generate_random_rseed, Note, Nullifier, + note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier, PaymentAddress, }, transaction::components::Amount, @@ -616,7 +616,7 @@ mod tests { value: value.into(), rseed, }; - let encryptor = SaplingNoteEncryption::new( + let encryptor = sapling_note_encryption::<_, Network>( Some(extfvk.fvk.ovk), note.clone(), to, @@ -631,7 +631,7 @@ mod tests { let mut cout = CompactOutput::new(); cout.set_cmu(cmu); cout.set_epk(epk); - cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec()); let mut ctx = CompactTx::new(); let mut txid = vec![0; 32]; rng.fill_bytes(&mut txid); @@ -676,7 +676,7 @@ mod tests { value: value.into(), rseed, }; - let encryptor = SaplingNoteEncryption::new( + let encryptor = sapling_note_encryption::<_, Network>( Some(extfvk.fvk.ovk), note.clone(), to, @@ -690,7 +690,7 @@ mod tests { let mut cout = CompactOutput::new(); cout.set_cmu(cmu); cout.set_epk(epk); - cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec()); cout }); @@ -704,7 +704,7 @@ mod tests { value: (in_value - value).into(), rseed, }; - let encryptor = SaplingNoteEncryption::new( + let encryptor = sapling_note_encryption::<_, Network>( Some(extfvk.fvk.ovk), note.clone(), change_addr, @@ -718,7 +718,7 @@ mod tests { let mut cout = CompactOutput::new(); cout.set_cmu(cmu); cout.set_epk(epk); - cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec()); cout }); diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index bbb15ca36..f08ef58d9 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -2,11 +2,12 @@ use criterion::{criterion_group, criterion_main, Criterion}; use ff::Field; use rand_core::OsRng; use zcash_primitives::{ - consensus::{NetworkUpgrade::Canopy, Parameters, TEST_NETWORK}, + consensus::{NetworkUpgrade::Canopy, Parameters, TestNetwork, TEST_NETWORK}, memo::MemoBytes, - note_encryption::{try_sapling_note_decryption, SaplingNoteEncryption}, sapling::{ - util::generate_random_rseed, Diversifier, PaymentAddress, SaplingIvk, ValueCommitment, + note_encryption::{sapling_note_encryption, try_sapling_note_decryption, Memo}, + util::generate_random_rseed, + Diversifier, PaymentAddress, SaplingIvk, ValueCommitment, }, transaction::components::{OutputDescription, GROTH_PROOF_SIZE}, }; @@ -37,10 +38,11 @@ fn bench_note_decryption(c: &mut Criterion) { let note = pa.create_note(value, rseed).unwrap(); let cmu = note.cmu(); - let mut ne = SaplingNoteEncryption::new(None, note, pa, MemoBytes::empty(), &mut rng); - let ephemeral_key = ne.epk().clone().into(); + let mut ne = + sapling_note_encryption::<_, TestNetwork>(None, note, pa, MemoBytes::empty(), &mut rng); + let ephemeral_key = *ne.epk(); let enc_ciphertext = ne.encrypt_note_plaintext(); - let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu); + let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng); OutputDescription { cv, diff --git a/zcash_primitives/src/sapling.rs b/zcash_primitives/src/sapling.rs index a0e8c910b..47ef2264b 100644 --- a/zcash_primitives/src/sapling.rs +++ b/zcash_primitives/src/sapling.rs @@ -326,10 +326,10 @@ impl PaymentAddress { self.diversifier.g_d() } - pub fn create_note(&self, value: u64, randomness: Rseed) -> Option { + pub fn create_note(&self, value: u64, rseed: Rseed) -> Option { self.g_d().map(|g_d| Note { value, - rseed: randomness, + rseed, g_d, pk_d: self.pk_d, }) diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index e9de95669..ae1785e23 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -4,13 +4,14 @@ use crate::{ consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, memo::MemoBytes, sapling::{Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, + transaction::components::amount::Amount, }; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{LittleEndian, WriteBytesExt}; use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; use ff::PrimeField; use group::{cofactor::CofactorGroup, GroupEncoding}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::RngCore; use std::convert::TryInto; use crate::sapling::keys::OutgoingViewingKey; @@ -112,10 +113,11 @@ pub fn prf_ock( /// use ff::Field; /// use rand_core::OsRng; /// use zcash_primitives::{ +/// consensus::TestNetwork, /// memo::MemoBytes, -/// note_encryption::SaplingNoteEncryption, /// sapling::{ /// keys::{OutgoingViewingKey, prf_expand}, +/// note_encryption::sapling_note_encryption, /// Diversifier, PaymentAddress, Rseed, ValueCommitment /// }, /// }; @@ -137,86 +139,208 @@ pub fn prf_ock( /// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); /// let cmu = note.cmu(); /// -/// let mut enc = SaplingNoteEncryption::new(ovk, note, to, MemoBytes::empty(), &mut rng); +/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng); /// let encCiphertext = enc.encrypt_note_plaintext(); -/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu); +/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng); /// ``` -pub struct SaplingNoteEncryption { - epk: jubjub::SubgroupPoint, - esk: jubjub::Fr, - note: Note, - to: PaymentAddress, - memo: MemoBytes, +pub struct NoteEncryption { + epk: D::EphemeralPublicKey, + esk: D::EphemeralSecretKey, + note: D::Note, + to: D::Recipient, + memo: D::Memo, /// `None` represents the `ovk = ⊥` case. - ovk: Option, - rng: R, + ovk: Option, } -impl SaplingNoteEncryption { - /// Creates a new encryption context for the given note. +//FIXME: use constant-time checks for equality +#[derive(Eq, PartialEq)] +pub struct EphemeralKeyBytes([u8; 32]); + +impl From<[u8; 32]> for EphemeralKeyBytes { + fn from(value: [u8; 32]) -> EphemeralKeyBytes { + EphemeralKeyBytes(value) + } +} + +pub struct NotePlaintextBytes([u8; NOTE_PLAINTEXT_SIZE]); +pub struct OutPlaintextBytes([u8; OUT_PLAINTEXT_SIZE]); + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum EpkValidity { + Valid, + Invalid, +} + +pub trait Domain { + type EphemeralSecretKey; + type EphemeralPublicKey; + type SharedSecret; + type SymmetricKey: AsRef<[u8]>; + type Note; + type Recipient; + type DiversifiedTransmissionKey; + type IncomingViewingKey; + type OutgoingViewingKey; + type ValueCommitment; + type NoteCommitment; + type ExtractedCommitment: Eq; + type Memo; + + fn derive_esk(note: &Self::Note) -> Option; + + fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; + + fn ka_derive_public( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> Self::EphemeralPublicKey; + + fn ka_agree_enc( + esk: &Self::EphemeralSecretKey, + pk_d: &Self::DiversifiedTransmissionKey, + ) -> Self::SharedSecret; + + fn ka_agree_dec( + ivk: &Self::IncomingViewingKey, + epk: &Self::EphemeralPublicKey, + ) -> Self::SharedSecret; + + fn kdf(secret: Self::SharedSecret, epk: &Self::EphemeralPublicKey) -> Self::SymmetricKey; + + // for right now, we just need `recipient` to get `d`; in the future when we + // can get that from a Sapling note, the recipient parameter will be able + // to be removed. + fn to_note_plaintext_bytes( + note: &Self::Note, + recipient: &Self::Recipient, + memo: &Self::Memo, + ) -> NotePlaintextBytes; + + fn get_ock( + ovk: &Self::OutgoingViewingKey, + cv: &Self::ValueCommitment, + cm: &Self::NoteCommitment, + epk: &Self::EphemeralPublicKey, + ) -> OutgoingCipherKey; + + fn to_outgoing_plaintext_bytes( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> OutPlaintextBytes; + + fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; + + fn check_epk_bytes EpkValidity>( + note: &Self::Note, + check: F, + ) -> EpkValidity; + + fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment; + + fn parse_note_plaintext_without_memo( + &self, + ivk: &Self::IncomingViewingKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)>; + + // &self is passed here in anticipation of future changes + // to memo handling where the memos may no longer be + // part of the note plaintext. + fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo; +} + +pub trait ShieldedOutput<'a, D: Domain> { + fn ivk(&'a self) -> &'a D::IncomingViewingKey; + fn epk(&'a self) -> &'a D::EphemeralPublicKey; + fn cmstar(&'a self) -> &'a D::ExtractedCommitment; +} + +pub struct SaplingDomain { + params: P, + height: BlockHeight, +} + +impl Domain for SaplingDomain

{ + type EphemeralSecretKey = jubjub::Scalar; + type EphemeralPublicKey = jubjub::ExtendedPoint; + type SharedSecret = jubjub::SubgroupPoint; + type SymmetricKey = Blake2bHash; + type Note = Note; + type Recipient = PaymentAddress; + type DiversifiedTransmissionKey = jubjub::SubgroupPoint; + type IncomingViewingKey = SaplingIvk; + type OutgoingViewingKey = OutgoingViewingKey; + type ValueCommitment = jubjub::ExtendedPoint; + type NoteCommitment = bls12_381::Scalar; + type ExtractedCommitment = [u8; 32]; + type Memo = MemoBytes; + + fn derive_esk(note: &Self::Note) -> Option { + note.derive_esk() + } + + fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey { + note.pk_d + } + + fn ka_derive_public( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> Self::EphemeralPublicKey { + // epk is an element of jubjub's prime-order subgroup, + // but Self::EphemeralPublicKey is a full group element + // for efficency of encryption. The conversion here is fine + // because the output of this function is only used for + // encoding and the byte encoding is unaffected by the conversion. + (note.g_d * esk).into() + } + + fn ka_agree_enc( + esk: &Self::EphemeralSecretKey, + pk_d: &Self::DiversifiedTransmissionKey, + ) -> Self::SharedSecret { + sapling_ka_agree(esk, pk_d.into()) + } + + fn ka_agree_dec( + ivk: &Self::IncomingViewingKey, + epk: &Self::EphemeralPublicKey, + ) -> Self::SharedSecret { + sapling_ka_agree(&ivk.0, epk) + } + + /// Sapling KDF for note encryption. /// - /// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be - /// recovered by the sender. - pub fn new( - ovk: Option, - note: Note, - to: PaymentAddress, - memo: MemoBytes, - rng: R, - ) -> Self { - Self::new_internal(ovk, note, to, memo, rng) - } -} - -impl SaplingNoteEncryption { - pub(crate) fn new_internal( - ovk: Option, - note: Note, - to: PaymentAddress, - memo: MemoBytes, - mut rng: R, - ) -> Self { - let esk = note.generate_or_derive_esk_internal(&mut rng); - let epk = note.g_d * esk; - - SaplingNoteEncryption { - epk, - esk, - note, - to, - memo, - ovk, - rng, - } + /// Implements section 5.4.4.4 of the Zcash Protocol Specification. + fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash { + Blake2bParams::new() + .hash_length(32) + .personal(KDF_SAPLING_PERSONALIZATION) + .to_state() + .update(&dhsecret.to_bytes()) + .update(&epk.to_bytes()) + .finalize() } - /// Exposes the ephemeral secret key being used to encrypt this note. - pub fn esk(&self) -> &jubjub::Fr { - &self.esk - } - - /// Exposes the ephemeral public key being used to encrypt this note. - pub fn epk(&self) -> &jubjub::SubgroupPoint { - &self.epk - } - - /// Generates `encCiphertext` for this note. - pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { - let shared_secret = sapling_ka_agree(&self.esk, self.to.pk_d().into()); - let key = kdf_sapling(shared_secret, &self.epk.into()); - + fn to_note_plaintext_bytes( + note: &Self::Note, + to: &Self::Recipient, + memo: &Self::Memo, + ) -> NotePlaintextBytes { // Note plaintext encoding is defined in section 5.5 of the Zcash Protocol // Specification. let mut input = [0; NOTE_PLAINTEXT_SIZE]; - input[0] = match self.note.rseed { + input[0] = match note.rseed { Rseed::BeforeZip212(_) => 1, Rseed::AfterZip212(_) => 2, }; - input[1..12].copy_from_slice(&self.to.diversifier().0); + input[1..12].copy_from_slice(&to.diversifier().0); (&mut input[12..20]) - .write_u64::(self.note.value) + .write_u64::(note.value) .unwrap(); - match self.note.rseed { + + match note.rseed { Rseed::BeforeZip212(rcm) => { input[20..COMPACT_NOTE_SIZE].copy_from_slice(rcm.to_repr().as_ref()); } @@ -224,12 +348,151 @@ impl SaplingNoteEncryption { input[20..COMPACT_NOTE_SIZE].copy_from_slice(&rseed); } } - input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(self.memo.as_array()); + + input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(&memo.as_array()[..]); + + NotePlaintextBytes(input) + } + + fn get_ock( + ovk: &Self::OutgoingViewingKey, + cv: &Self::ValueCommitment, + cmu: &Self::NoteCommitment, + epk: &Self::EphemeralPublicKey, + ) -> OutgoingCipherKey { + prf_ock(ovk, &cv, &cmu, epk) + } + + fn to_outgoing_plaintext_bytes( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> OutPlaintextBytes { + let mut input = [0u8; OUT_PLAINTEXT_SIZE]; + input[0..32].copy_from_slice(¬e.pk_d.to_bytes()); + input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(esk.to_repr().as_ref()); + + OutPlaintextBytes(input) + } + + fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes { + EphemeralKeyBytes(epk.to_bytes()) + } + + fn check_epk_bytes EpkValidity>( + note: &Note, + check: F, + ) -> EpkValidity { + if let Some(derived_esk) = note.derive_esk() { + check(&derived_esk) + } else { + // Before ZIP 212 + EpkValidity::Valid + } + } + + fn parse_note_plaintext_without_memo( + &self, + ivk: &Self::IncomingViewingKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)> { + assert!(plaintext.len() >= COMPACT_NOTE_SIZE); + + // Check note plaintext version + if !plaintext_version_is_valid(&self.params, self.height, plaintext[0]) { + return None; + } + + // The unwraps below are guaranteed to succeed by the assertion above + let diversifier = Diversifier(plaintext[1..12].try_into().unwrap()); + let value = Amount::from_u64_le_bytes(plaintext[12..20].try_into().unwrap()).ok()?; + let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(); + + let rseed = if plaintext[0] == 0x01 { + let rcm = jubjub::Fr::from_repr(r)?; + Rseed::BeforeZip212(rcm) + } else { + Rseed::AfterZip212(r) + }; + + let pk_d = diversifier.g_d()? * ivk.0; + + let to = PaymentAddress::from_parts(diversifier, pk_d)?; + let note = to.create_note(value.into(), rseed)?; + Some((note, to)) + } + + fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment { + note.cmu().to_bytes() + } + + fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo { + MemoBytes::from_bytes(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap() + } +} + +/// Creates a new encryption context for the given note. +/// +/// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be +/// recovered by the sender. +pub fn sapling_note_encryption( + ovk: Option, + note: Note, + to: PaymentAddress, + memo: MemoBytes, + rng: &mut R, +) -> NoteEncryption> { + let esk = note.generate_or_derive_esk_internal(rng); + + NoteEncryption { + epk: SaplingDomain::

::ka_derive_public(¬e, &esk), + esk, + note, + to, + memo, + ovk, + } +} + +impl NoteEncryption { + pub fn new_internal( + ovk: Option, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + ) -> Self { + let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); + + NoteEncryption { + epk: D::ka_derive_public(¬e, &esk), + esk, + note, + to, + memo, + ovk, + } + } + + /// Exposes the ephemeral secret key being used to encrypt this note. + pub fn esk(&self) -> &D::EphemeralSecretKey { + &self.esk + } + + /// Exposes the ephemeral public key being used to encrypt this note. + pub fn epk(&self) -> &D::EphemeralPublicKey { + &self.epk + } + + /// Generates `encCiphertext` for this note. + pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { + let pk_d = D::get_pk_d(&self.note); + let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); + let key = D::kdf(shared_secret, &self.epk); + let input = D::to_note_plaintext_bytes(&self.note, &self.to, &self.memo); let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; assert_eq!( ChachaPolyIetf::aead_cipher() - .seal_to(&mut output, &input, &[], &key.as_bytes(), &[0u8; 12]) + .seal_to(&mut output, &input.0, &[], key.as_ref(), &[0u8; 12]) .unwrap(), ENC_CIPHERTEXT_SIZE ); @@ -238,17 +501,15 @@ impl SaplingNoteEncryption { } /// Generates `outCiphertext` for this note. - pub fn encrypt_outgoing_plaintext( + pub fn encrypt_outgoing_plaintext( &mut self, - cv: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, + cv: &D::ValueCommitment, + cm: &D::NoteCommitment, + rng: &mut R, ) -> [u8; OUT_CIPHERTEXT_SIZE] { let (ock, input) = if let Some(ovk) = &self.ovk { - let ock = prf_ock(ovk, &cv, &cmu, &self.epk.into()); - - let mut input = [0u8; OUT_PLAINTEXT_SIZE]; - input[0..32].copy_from_slice(&self.note.pk_d.to_bytes()); - input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(self.esk.to_repr().as_ref()); + let ock = D::get_ock(ovk, &cv, &cm, &self.epk); + let input = D::to_outgoing_plaintext_bytes(&self.note, &self.esk); (ock, input) } else { @@ -256,16 +517,16 @@ impl SaplingNoteEncryption { let mut ock = OutgoingCipherKey([0; 32]); let mut input = [0u8; OUT_PLAINTEXT_SIZE]; - self.rng.fill_bytes(&mut ock.0); - self.rng.fill_bytes(&mut input); + rng.fill_bytes(&mut ock.0); + rng.fill_bytes(&mut input); - (ock, input) + (ock, OutPlaintextBytes(input)) }; let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; assert_eq!( ChachaPolyIetf::aead_cipher() - .seal_to(&mut output, &input, &[], ock.as_ref(), &[0u8; 12]) + .seal_to(&mut output, &input.0, &[], ock.as_ref(), &[0u8; 12]) .unwrap(), OUT_CIPHERTEXT_SIZE ); @@ -274,56 +535,6 @@ impl SaplingNoteEncryption { } } -fn parse_note_plaintext_without_memo( - params: &P, - height: BlockHeight, - ivk: &SaplingIvk, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - plaintext: &[u8], -) -> Option<(Note, PaymentAddress)> { - // Check note plaintext version - if !plaintext_version_is_valid(params, height, plaintext[0]) { - return None; - } - - let mut d = [0u8; 11]; - d.copy_from_slice(&plaintext[1..12]); - - let v = (&plaintext[12..20]).read_u64::().ok()?; - - let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE] - .try_into() - .expect("slice is the correct length"); - - let rseed = if plaintext[0] == 0x01 { - let rcm = jubjub::Fr::from_repr(r)?; - Rseed::BeforeZip212(rcm) - } else { - Rseed::AfterZip212(r) - }; - - let diversifier = Diversifier(d); - let pk_d = diversifier.g_d()? * ivk.0; - - let to = PaymentAddress::from_parts(diversifier, pk_d)?; - let note = to.create_note(v, rseed).unwrap(); - - if note.cmu() != *cmu { - // Published commitment doesn't match calculated commitment - return None; - } - - if let Some(derived_esk) = note.derive_esk() { - // This enforces that epk is a jubjub::SubgroupPoint. - if (note.g_d * derived_esk).to_bytes() != epk.to_bytes() { - return None; - } - } - - Some((note, to)) -} - #[allow(clippy::if_same_then_else)] #[allow(clippy::needless_bool)] pub fn plaintext_version_is_valid( @@ -357,18 +568,18 @@ pub fn plaintext_version_is_valid( /// `PaymentAddress` to which the note was sent. /// /// Implements section 4.17.2 of the Zcash Protocol Specification. -pub fn try_sapling_note_decryption( - params: &P, - height: BlockHeight, - ivk: &SaplingIvk, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, +pub fn try_note_decryption( + domain: &D, + //output: &ShieldedOutput, + ivk: &D::IncomingViewingKey, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, enc_ciphertext: &[u8], -) -> Option<(Note, PaymentAddress, MemoBytes)> { +) -> Option<(D::Note, D::Recipient, D::Memo)> { assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); - let shared_secret = sapling_ka_agree(&ivk.0, &epk); - let key = kdf_sapling(shared_secret, &epk); + let shared_secret = D::ka_agree_dec(ivk, epk); + let key = D::kdf(shared_secret, epk); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( @@ -377,21 +588,64 @@ pub fn try_sapling_note_decryption( &mut plaintext, &enc_ciphertext, &[], - key.as_bytes(), + key.as_ref(), &[0u8; 12] ) .ok()?, NOTE_PLAINTEXT_SIZE ); - let (note, to) = parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)?; - - // Memo is the correct length by definition. - let memo = MemoBytes::from_bytes(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap(); + let (note, to) = parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext)?; + let memo = domain.extract_memo(&plaintext); Some((note, to, memo)) } +fn parse_note_plaintext_without_memo( + domain: &D, + ivk: &D::IncomingViewingKey, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, + plaintext: &[u8], +) -> Option<(D::Note, D::Recipient)> { + let (note, to) = domain.parse_note_plaintext_without_memo(ivk, &plaintext)?; + + if &D::extract_note_commitment(¬e) != cmstar { + // Published commitment doesn't match calculated commitment + return None; + } else { + let epk_bytes = D::to_epk_bytes(epk); + let validity = D::check_epk_bytes(¬e, |derived_esk| { + if D::to_epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { + EpkValidity::Valid + } else { + EpkValidity::Invalid + } + }); + + if validity != EpkValidity::Valid { + return None; + } + } + + Some((note, to)) +} + +pub fn try_sapling_note_decryption( + params: &P, + height: BlockHeight, + ivk: &SaplingIvk, + epk: &jubjub::ExtendedPoint, + cmu: &bls12_381::Scalar, + enc_ciphertext: &[u8], +) -> Option<(Note, PaymentAddress, MemoBytes)> { + let domain = SaplingDomain { + params: params.clone(), + height, + }; + try_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) +} + /// Trial decryption of the compact note plaintext by the recipient for light clients. /// /// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the @@ -401,6 +655,26 @@ pub fn try_sapling_note_decryption( /// Implements the procedure specified in [`ZIP 307`]. /// /// [`ZIP 307`]: https://zips.z.cash/zip-0307 +pub fn try_compact_note_decryption( + domain: &D, + ivk: &D::IncomingViewingKey, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, + enc_ciphertext: &[u8], +) -> Option<(D::Note, D::Recipient)> { + assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE); + + let shared_secret = D::ka_agree_dec(&ivk, epk); + let key = D::kdf(shared_secret, &epk); + + // Start from block 1 to skip over Poly1305 keying output + let mut plaintext = [0; COMPACT_NOTE_SIZE]; + plaintext.copy_from_slice(&enc_ciphertext); + ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); + + parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext) +} + pub fn try_sapling_compact_note_decryption( params: &P, height: BlockHeight, @@ -409,17 +683,12 @@ pub fn try_sapling_compact_note_decryption( cmu: &bls12_381::Scalar, enc_ciphertext: &[u8], ) -> Option<(Note, PaymentAddress)> { - assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE); + let domain = SaplingDomain { + params: params.clone(), + height, + }; - let shared_secret = sapling_ka_agree(&ivk.0, epk); - let key = kdf_sapling(shared_secret, &epk); - - // Start from block 1 to skip over Poly1305 keying output - let mut plaintext = [0; COMPACT_NOTE_SIZE]; - plaintext.copy_from_slice(&enc_ciphertext); - ChaCha20Ietf::xor(key.as_bytes(), &[0u8; 12], 1, &mut plaintext); - - parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext) + try_compact_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) } /// Recovery of the full note plaintext by the sender. @@ -491,7 +760,7 @@ pub fn try_sapling_output_recovery_with_ock( let mut d = [0u8; 11]; d.copy_from_slice(&plaintext[1..12]); - let v = (&plaintext[12..20]).read_u64::().ok()?; + let v = Amount::from_u64_le_bytes(plaintext[12..20].try_into().unwrap()).ok()?; let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE] .try_into() @@ -513,7 +782,7 @@ pub fn try_sapling_output_recovery_with_ock( } let to = PaymentAddress::from_parts(diversifier, pk_d)?; - let note = to.create_note(v, rseed).unwrap(); + let note = to.create_note(v.into(), rseed).unwrap(); if note.cmu() != *cmu { // Published commitment doesn't match calculated commitment @@ -569,9 +838,9 @@ mod tests { use std::convert::TryInto; use super::{ - kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_compact_note_decryption, - try_sapling_note_decryption, try_sapling_output_recovery, - try_sapling_output_recovery_with_ock, OutgoingCipherKey, SaplingNoteEncryption, + kdf_sapling, 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, OutgoingCipherKey, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; @@ -580,7 +849,7 @@ mod tests { consensus::{ BlockHeight, NetworkUpgrade::{Canopy, Sapling}, - Parameters, TEST_NETWORK, ZIP212_GRACE_PERIOD, + Parameters, TestNetwork, TEST_NETWORK, ZIP212_GRACE_PERIOD, }, memo::MemoBytes, sapling::util::generate_random_rseed, @@ -588,6 +857,7 @@ mod tests { keys::OutgoingViewingKey, Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment, }, + transaction::components::amount::Amount, }; fn random_enc_ciphertext( @@ -672,23 +942,29 @@ mod tests { let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d); // Construct the value commitment for the proof instance - let value = 100; + let value = Amount::from_u64(100).unwrap(); let value_commitment = ValueCommitment { - value, + value: value.into(), randomness: jubjub::Fr::random(&mut rng), }; let cv = value_commitment.commitment().into(); let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng); - let note = pa.create_note(value, rseed).unwrap(); + let note = pa.create_note(value.into(), rseed).unwrap(); let cmu = note.cmu(); let ovk = OutgoingViewingKey([0; 32]); - let mut ne = SaplingNoteEncryption::new(Some(ovk), note, pa, MemoBytes::empty(), &mut rng); - let epk = ne.epk().clone().into(); + let mut ne = sapling_note_encryption::<_, TestNetwork>( + Some(ovk), + note, + pa, + MemoBytes::empty(), + &mut rng, + ); + let epk = *ne.epk(); let enc_ciphertext = ne.encrypt_note_plaintext(); - let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu); + let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng); let ock = prf_ock(&ovk, &cv, &cmu, &epk); (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) @@ -1716,19 +1992,22 @@ mod tests { // Test encryption // - let mut ne = SaplingNoteEncryption::new( + let mut ne = sapling_note_encryption::<_, TestNetwork>( Some(ovk), note, to, MemoBytes::from_bytes(&tv.memo).unwrap(), - OsRng, + &mut OsRng, ); // Swap in the ephemeral keypair from the test vectors ne.esk = esk; - ne.epk = epk.into_subgroup().unwrap(); + ne.epk = epk; - assert_eq!(&ne.encrypt_note_plaintext()[..], &tv.c_enc[..]); - assert_eq!(&ne.encrypt_outgoing_plaintext(&cv, &cmu)[..], &tv.c_out[..]); + assert_eq!(&ne.encrypt_note_plaintext().as_ref()[..], &tv.c_enc[..]); + assert_eq!( + &ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng)[..], + &tv.c_out[..] + ); } } } diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index faafbfd53..ca3cc2a64 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -16,7 +16,7 @@ use crate::{ memo::MemoBytes, merkle_tree::MerklePath, sapling::{ - keys::OutgoingViewingKey, note_encryption::SaplingNoteEncryption, prover::TxProver, + keys::OutgoingViewingKey, note_encryption::sapling_note_encryption, prover::TxProver, redjubjub::PrivateKey, spend_sig_internal, util::generate_random_rseed_internal, Diversifier, Node, Note, PaymentAddress, }, @@ -93,16 +93,17 @@ struct SpendDescriptionInfo { merkle_path: MerklePath, } -pub struct SaplingOutput { +pub struct SaplingOutput { /// `None` represents the `ovk = ⊥` case. ovk: Option, to: PaymentAddress, note: Note, memo: MemoBytes, + _params: PhantomData

, } -impl SaplingOutput { - pub fn new( +impl SaplingOutput

{ + pub fn new( params: &P, height: BlockHeight, rng: &mut R, @@ -114,7 +115,7 @@ impl SaplingOutput { Self::new_internal(params, height, rng, ovk, to, value, memo) } - fn new_internal( + fn new_internal( params: &P, height: BlockHeight, rng: &mut R, @@ -142,25 +143,26 @@ impl SaplingOutput { to, note, memo: memo.unwrap_or_else(MemoBytes::empty), + _params: PhantomData::default(), }) } - pub fn build( + pub fn build( self, - prover: &P, - ctx: &mut P::SaplingProvingContext, + prover: &Pr, + ctx: &mut Pr::SaplingProvingContext, rng: &mut R, ) -> OutputDescription { self.build_internal(prover, ctx, rng) } - fn build_internal( + fn build_internal( self, - prover: &P, - ctx: &mut P::SaplingProvingContext, + prover: &Pr, + ctx: &mut Pr::SaplingProvingContext, rng: &mut R, ) -> OutputDescription { - let mut encryptor = SaplingNoteEncryption::new_internal( + let mut encryptor = sapling_note_encryption::( self.ovk, self.note.clone(), self.to.clone(), @@ -179,9 +181,9 @@ impl SaplingOutput { let cmu = self.note.cmu(); let enc_ciphertext = encryptor.encrypt_note_plaintext(); - let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu); + let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng); - let ephemeral_key = encryptor.epk().clone().into(); + let ephemeral_key = *encryptor.epk(); OutputDescription { cv, @@ -371,7 +373,7 @@ pub struct Builder<'a, P: consensus::Parameters, R: RngCore> { fee: Amount, anchor: Option, spends: Vec, - outputs: Vec, + outputs: Vec>, transparent_inputs: TransparentInputs, #[cfg(feature = "zfuture")] tze_inputs: TzeInputs<'a, TransactionData>, From 266285b536a07a0385985f0bec4b285b2c09db50 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 22 Mar 2021 14:59:25 -0600 Subject: [PATCH 03/24] Move generalized note encryption functionality to zcash_note_encryption crate. --- components/zcash_note_encryption/Cargo.toml | 9 + components/zcash_note_encryption/src/lib.rs | 390 ++++++++++++++++- zcash_primitives/Cargo.toml | 1 + .../src/sapling/note_encryption.rs | 412 +----------------- 4 files changed, 413 insertions(+), 399 deletions(-) diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml index 46e1bec5b..b749ed0e9 100644 --- a/components/zcash_note_encryption/Cargo.toml +++ b/components/zcash_note_encryption/Cargo.toml @@ -11,3 +11,12 @@ license = "MIT OR Apache-2.0" edition = "2018" [dependencies] +blake2b_simd = "0.5" +byteorder = "1" +crypto_api_chachapoly = "0.4" +ff = "0.8" +group = "0.8" +rand_core = "0.5.1" + +[dev-dependencies] +zcash_primitives = { version = "0.4", path = "../../zcash_primitives" } diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 0ee39e7ab..a008edbae 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -1,8 +1,386 @@ -#[cfg(test)] -mod tests { - #[allow(clippy::eq_op)] - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +//! Implementation of in-band secret distribution abstractions +//! for Zcash transactions. The implementations here provide +//! functionality that is shared between the Sapling and Orchard +//! protocols. + +use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; +use rand_core::RngCore; + +pub const COMPACT_NOTE_SIZE: usize = 1 + // version + 11 + // diversifier + 8 + // value + 32; // rcv +pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; +pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d + 32; // esk +pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16; +pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16; + +/// A symmetric key that can be used to recover a single Sapling or Orchard output. +pub struct OutgoingCipherKey(pub [u8; 32]); + +impl From<[u8; 32]> for OutgoingCipherKey { + fn from(ock: [u8; 32]) -> Self { + OutgoingCipherKey(ock) } } + +impl AsRef<[u8]> for OutgoingCipherKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +//FIXME: use constant-time checks for equality +#[derive(Eq, PartialEq)] +pub struct EphemeralKeyBytes(pub [u8; 32]); + +impl From<[u8; 32]> for EphemeralKeyBytes { + fn from(value: [u8; 32]) -> EphemeralKeyBytes { + EphemeralKeyBytes(value) + } +} + +pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); +pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum EpkValidity { + Valid, + Invalid, +} + +pub trait Domain { + type EphemeralSecretKey; + type EphemeralPublicKey; + type SharedSecret; + type SymmetricKey: AsRef<[u8]>; + type Note; + type Recipient; + type DiversifiedTransmissionKey; + type IncomingViewingKey; + type OutgoingViewingKey; + type ValueCommitment; + type NoteCommitment; + type ExtractedCommitment: Eq; + type Memo; + + fn derive_esk(note: &Self::Note) -> Option; + + fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; + + fn ka_derive_public( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> Self::EphemeralPublicKey; + + fn ka_agree_enc( + esk: &Self::EphemeralSecretKey, + pk_d: &Self::DiversifiedTransmissionKey, + ) -> Self::SharedSecret; + + fn ka_agree_dec( + ivk: &Self::IncomingViewingKey, + epk: &Self::EphemeralPublicKey, + ) -> Self::SharedSecret; + + fn kdf(secret: Self::SharedSecret, epk: &Self::EphemeralPublicKey) -> Self::SymmetricKey; + + // for right now, we just need `recipient` to get `d`; in the future when we + // can get that from a Sapling note, the recipient parameter will be able + // to be removed. + fn to_note_plaintext_bytes( + note: &Self::Note, + recipient: &Self::Recipient, + memo: &Self::Memo, + ) -> NotePlaintextBytes; + + fn get_ock( + ovk: &Self::OutgoingViewingKey, + cv: &Self::ValueCommitment, + cm: &Self::NoteCommitment, + epk: &Self::EphemeralPublicKey, + ) -> OutgoingCipherKey; + + fn to_outgoing_plaintext_bytes( + note: &Self::Note, + esk: &Self::EphemeralSecretKey, + ) -> OutPlaintextBytes; + + fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; + + fn check_epk_bytes EpkValidity>( + note: &Self::Note, + check: F, + ) -> EpkValidity; + + fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment; + + fn parse_note_plaintext_without_memo( + &self, + ivk: &Self::IncomingViewingKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)>; + + // &self is passed here in anticipation of future changes + // to memo handling where the memos may no longer be + // part of the note plaintext. + fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo; +} + +pub trait ShieldedOutput<'a, D: Domain> { + fn ivk(&'a self) -> &'a D::IncomingViewingKey; + fn epk(&'a self) -> &'a D::EphemeralPublicKey; + fn cmstar(&'a self) -> &'a D::ExtractedCommitment; +} + +/// A struct containing context required for encrypting Sapling and Orchard notes. +/// +/// This struct provides a safe API for encrypting Sapling and Orchard notes. In particular, it +/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are +/// consistent with each other. +/// +/// Implements section 4.17.1 of the Zcash Protocol Specification. +/// NB: the example code is only covering the pre-Canopy case. +/// +/// # Examples +/// +/// ``` +/// extern crate ff; +/// extern crate rand_core; +/// extern crate zcash_primitives; +/// +/// use ff::Field; +/// use rand_core::OsRng; +/// use zcash_primitives::{ +/// consensus::TestNetwork, +/// sapling::{ +/// keys::{OutgoingViewingKey, prf_expand}, +/// note_encryption::{Memo, sapling_note_encryption}, +/// Diversifier, PaymentAddress, Rseed, ValueCommitment +/// }, +/// }; +/// +/// let mut rng = OsRng; +/// +/// let diversifier = Diversifier([0; 11]); +/// let pk_d = diversifier.g_d().unwrap(); +/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap(); +/// let ovk = Some(OutgoingViewingKey([0; 32])); +/// +/// let value = 1000; +/// let rcv = jubjub::Fr::random(&mut rng); +/// let cv = ValueCommitment { +/// value, +/// randomness: rcv.clone(), +/// }; +/// let rcm = jubjub::Fr::random(&mut rng); +/// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); +/// let cmu = note.cmu(); +/// +/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, Memo::default(), &mut rng); +/// let encCiphertext = enc.encrypt_note_plaintext(); +/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng); +/// ``` +pub struct NoteEncryption { + epk: D::EphemeralPublicKey, + esk: D::EphemeralSecretKey, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + /// `None` represents the `ovk = ⊥` case. + ovk: Option, +} + +impl NoteEncryption { + /// Construct a new note encryption context for the specified note, + /// recipient, and memo. + pub fn new( + ovk: Option, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + ) -> Self { + let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); + Self::new_with_esk(esk, ovk, note, to, memo) + } + + /// For use only with Sapling. + pub fn new_with_esk( + esk: D::EphemeralSecretKey, + ovk: Option, + note: D::Note, + to: D::Recipient, + memo: D::Memo, + ) -> Self { + NoteEncryption { + epk: D::ka_derive_public(¬e, &esk), + esk, + note, + to, + memo, + ovk, + } + } + + /// Exposes the ephemeral secret key being used to encrypt this note. + pub fn esk(&self) -> &D::EphemeralSecretKey { + &self.esk + } + + /// Exposes the ephemeral public key being used to encrypt this note. + pub fn epk(&self) -> &D::EphemeralPublicKey { + &self.epk + } + + /// Generates `encCiphertext` for this note. + pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { + let pk_d = D::get_pk_d(&self.note); + let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); + let key = D::kdf(shared_secret, &self.epk); + let input = D::to_note_plaintext_bytes(&self.note, &self.to, &self.memo); + + let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .seal_to(&mut output, &input.0, &[], key.as_ref(), &[0u8; 12]) + .unwrap(), + ENC_CIPHERTEXT_SIZE + ); + + output + } + + /// Generates `outCiphertext` for this note. + pub fn encrypt_outgoing_plaintext( + &mut self, + cv: &D::ValueCommitment, + cm: &D::NoteCommitment, + rng: &mut R, + ) -> [u8; OUT_CIPHERTEXT_SIZE] { + let (ock, input) = if let Some(ovk) = &self.ovk { + let ock = D::get_ock(ovk, &cv, &cm, &self.epk); + let input = D::to_outgoing_plaintext_bytes(&self.note, &self.esk); + + (ock, input) + } else { + // ovk = ⊥ + let mut ock = OutgoingCipherKey([0; 32]); + let mut input = [0u8; OUT_PLAINTEXT_SIZE]; + + rng.fill_bytes(&mut ock.0); + rng.fill_bytes(&mut input); + + (ock, OutPlaintextBytes(input)) + }; + + let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .seal_to(&mut output, &input.0, &[], ock.as_ref(), &[0u8; 12]) + .unwrap(), + OUT_CIPHERTEXT_SIZE + ); + + output + } +} + +/// Trial decryption of the full note plaintext by the recipient. +/// +/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`. +/// If successful, the corresponding Sapling note and memo are returned, along with the +/// `PaymentAddress` to which the note was sent. +/// +/// Implements section 4.17.2 of the Zcash Protocol Specification. +pub fn try_note_decryption( + domain: &D, + //output: &ShieldedOutput, + ivk: &D::IncomingViewingKey, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, + enc_ciphertext: &[u8], +) -> Option<(D::Note, D::Recipient, D::Memo)> { + assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); + + let shared_secret = D::ka_agree_dec(ivk, epk); + let key = D::kdf(shared_secret, epk); + + let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .open_to( + &mut plaintext, + &enc_ciphertext, + &[], + key.as_ref(), + &[0u8; 12] + ) + .ok()?, + NOTE_PLAINTEXT_SIZE + ); + + let (note, to) = parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext)?; + let memo = domain.extract_memo(&plaintext); + + Some((note, to, memo)) +} + +fn parse_note_plaintext_without_memo( + domain: &D, + ivk: &D::IncomingViewingKey, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, + plaintext: &[u8], +) -> Option<(D::Note, D::Recipient)> { + let (note, to) = domain.parse_note_plaintext_without_memo(ivk, &plaintext)?; + + if &D::extract_note_commitment(¬e) != cmstar { + // Published commitment doesn't match calculated commitment + return None; + } else { + let epk_bytes = D::to_epk_bytes(epk); + let validity = D::check_epk_bytes(¬e, |derived_esk| { + if D::to_epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { + EpkValidity::Valid + } else { + EpkValidity::Invalid + } + }); + + if validity != EpkValidity::Valid { + return None; + } + } + + Some((note, to)) +} + +/// Trial decryption of the compact note plaintext by the recipient for light clients. +/// +/// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the +/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the +/// `PaymentAddress` to which the note was sent. +/// +/// Implements the procedure specified in [`ZIP 307`]. +/// +/// [`ZIP 307`]: https://zips.z.cash/zip-0307 +pub fn try_compact_note_decryption( + domain: &D, + ivk: &D::IncomingViewingKey, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, + enc_ciphertext: &[u8], +) -> Option<(D::Note, D::Recipient)> { + assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE); + + let shared_secret = D::ka_agree_dec(&ivk, epk); + let key = D::kdf(shared_secret, &epk); + + // Start from block 1 to skip over Poly1305 keying output + let mut plaintext = [0; COMPACT_NOTE_SIZE]; + plaintext.copy_from_slice(&enc_ciphertext); + ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); + + parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext) +} diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index d794fbae7..73f9ec9d0 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -37,6 +37,7 @@ ripemd160 = { version = "0.9", optional = true } secp256k1 = { version = "0.20", optional = true } sha2 = "0.9" subtle = "2.2.3" +zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" } # Temporary workaround for https://github.com/myrrlyn/funty/issues/3 funty = "=1.1.0" diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index ae1785e23..0ddf26697 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -1,34 +1,28 @@ //! Implementation of in-band secret distribution for Zcash transactions. - -use crate::{ - consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, - memo::MemoBytes, - sapling::{Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, - transaction::components::amount::Amount, -}; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use byteorder::{LittleEndian, WriteBytesExt}; -use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; +use crypto_api_chachapoly::ChachaPolyIetf; use ff::PrimeField; use group::{cofactor::CofactorGroup, GroupEncoding}; use rand_core::RngCore; use std::convert::TryInto; -use crate::sapling::keys::OutgoingViewingKey; +use zcash_note_encryption::{ + try_compact_note_decryption, try_note_decryption, Domain, EphemeralKeyBytes, EpkValidity, + NoteEncryption, NotePlaintextBytes, OutPlaintextBytes, OutgoingCipherKey, COMPACT_NOTE_SIZE, + ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, +}; + +use crate::{ + consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, + memo::MemoBytes, + sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, + transaction::components::amount::Amount, +}; pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF"; pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock"; -const COMPACT_NOTE_SIZE: usize = 1 + // version - 11 + // diversifier - 8 + // value - 32; // rcv -const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; -const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d - 32; // esk -pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16; -pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16; - /// Sapling key agreement for note encryption. /// /// Implements section 5.4.4.3 of the Zcash Protocol Specification. @@ -54,21 +48,6 @@ fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> .finalize() } -/// A symmetric key that can be used to recover a single Sapling output. -pub struct OutgoingCipherKey([u8; 32]); - -impl From<[u8; 32]> for OutgoingCipherKey { - fn from(ock: [u8; 32]) -> Self { - OutgoingCipherKey(ock) - } -} - -impl AsRef<[u8]> for OutgoingCipherKey { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - /// Sapling PRF^ock. /// /// Implemented per section 5.4.2 of the Zcash Protocol Specification. @@ -94,168 +73,6 @@ pub fn prf_ock( ) } -/// An API for encrypting Sapling notes. -/// -/// This struct provides a safe API for encrypting Sapling notes. In particular, it -/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts -/// are consistent with each other. -/// -/// Implements section 4.17.1 of the Zcash Protocol Specification. -/// NB: the example code is only covering the pre-Canopy case. -/// -/// # Examples -/// -/// ``` -/// extern crate ff; -/// extern crate rand_core; -/// extern crate zcash_primitives; -/// -/// use ff::Field; -/// use rand_core::OsRng; -/// use zcash_primitives::{ -/// consensus::TestNetwork, -/// memo::MemoBytes, -/// sapling::{ -/// keys::{OutgoingViewingKey, prf_expand}, -/// note_encryption::sapling_note_encryption, -/// Diversifier, PaymentAddress, Rseed, ValueCommitment -/// }, -/// }; -/// -/// let mut rng = OsRng; -/// -/// let diversifier = Diversifier([0; 11]); -/// let pk_d = diversifier.g_d().unwrap(); -/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap(); -/// let ovk = Some(OutgoingViewingKey([0; 32])); -/// -/// let value = 1000; -/// let rcv = jubjub::Fr::random(&mut rng); -/// let cv = ValueCommitment { -/// value, -/// randomness: rcv.clone(), -/// }; -/// let rcm = jubjub::Fr::random(&mut rng); -/// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); -/// let cmu = note.cmu(); -/// -/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng); -/// let encCiphertext = enc.encrypt_note_plaintext(); -/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng); -/// ``` -pub struct NoteEncryption { - epk: D::EphemeralPublicKey, - esk: D::EphemeralSecretKey, - note: D::Note, - to: D::Recipient, - memo: D::Memo, - /// `None` represents the `ovk = ⊥` case. - ovk: Option, -} - -//FIXME: use constant-time checks for equality -#[derive(Eq, PartialEq)] -pub struct EphemeralKeyBytes([u8; 32]); - -impl From<[u8; 32]> for EphemeralKeyBytes { - fn from(value: [u8; 32]) -> EphemeralKeyBytes { - EphemeralKeyBytes(value) - } -} - -pub struct NotePlaintextBytes([u8; NOTE_PLAINTEXT_SIZE]); -pub struct OutPlaintextBytes([u8; OUT_PLAINTEXT_SIZE]); - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum EpkValidity { - Valid, - Invalid, -} - -pub trait Domain { - type EphemeralSecretKey; - type EphemeralPublicKey; - type SharedSecret; - type SymmetricKey: AsRef<[u8]>; - type Note; - type Recipient; - type DiversifiedTransmissionKey; - type IncomingViewingKey; - type OutgoingViewingKey; - type ValueCommitment; - type NoteCommitment; - type ExtractedCommitment: Eq; - type Memo; - - fn derive_esk(note: &Self::Note) -> Option; - - fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; - - fn ka_derive_public( - note: &Self::Note, - esk: &Self::EphemeralSecretKey, - ) -> Self::EphemeralPublicKey; - - fn ka_agree_enc( - esk: &Self::EphemeralSecretKey, - pk_d: &Self::DiversifiedTransmissionKey, - ) -> Self::SharedSecret; - - fn ka_agree_dec( - ivk: &Self::IncomingViewingKey, - epk: &Self::EphemeralPublicKey, - ) -> Self::SharedSecret; - - fn kdf(secret: Self::SharedSecret, epk: &Self::EphemeralPublicKey) -> Self::SymmetricKey; - - // for right now, we just need `recipient` to get `d`; in the future when we - // can get that from a Sapling note, the recipient parameter will be able - // to be removed. - fn to_note_plaintext_bytes( - note: &Self::Note, - recipient: &Self::Recipient, - memo: &Self::Memo, - ) -> NotePlaintextBytes; - - fn get_ock( - ovk: &Self::OutgoingViewingKey, - cv: &Self::ValueCommitment, - cm: &Self::NoteCommitment, - epk: &Self::EphemeralPublicKey, - ) -> OutgoingCipherKey; - - fn to_outgoing_plaintext_bytes( - note: &Self::Note, - esk: &Self::EphemeralSecretKey, - ) -> OutPlaintextBytes; - - fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; - - fn check_epk_bytes EpkValidity>( - note: &Self::Note, - check: F, - ) -> EpkValidity; - - fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment; - - fn parse_note_plaintext_without_memo( - &self, - ivk: &Self::IncomingViewingKey, - plaintext: &[u8], - ) -> Option<(Self::Note, Self::Recipient)>; - - // &self is passed here in anticipation of future changes - // to memo handling where the memos may no longer be - // part of the note plaintext. - fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo; -} - -pub trait ShieldedOutput<'a, D: Domain> { - fn ivk(&'a self) -> &'a D::IncomingViewingKey; - fn epk(&'a self) -> &'a D::EphemeralPublicKey; - fn cmstar(&'a self) -> &'a D::ExtractedCommitment; -} - pub struct SaplingDomain { params: P, height: BlockHeight, @@ -442,97 +259,7 @@ pub fn sapling_note_encryption( rng: &mut R, ) -> NoteEncryption> { let esk = note.generate_or_derive_esk_internal(rng); - - NoteEncryption { - epk: SaplingDomain::

::ka_derive_public(¬e, &esk), - esk, - note, - to, - memo, - ovk, - } -} - -impl NoteEncryption { - pub fn new_internal( - ovk: Option, - note: D::Note, - to: D::Recipient, - memo: D::Memo, - ) -> Self { - let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); - - NoteEncryption { - epk: D::ka_derive_public(¬e, &esk), - esk, - note, - to, - memo, - ovk, - } - } - - /// Exposes the ephemeral secret key being used to encrypt this note. - pub fn esk(&self) -> &D::EphemeralSecretKey { - &self.esk - } - - /// Exposes the ephemeral public key being used to encrypt this note. - pub fn epk(&self) -> &D::EphemeralPublicKey { - &self.epk - } - - /// Generates `encCiphertext` for this note. - pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { - let pk_d = D::get_pk_d(&self.note); - let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); - let key = D::kdf(shared_secret, &self.epk); - let input = D::to_note_plaintext_bytes(&self.note, &self.to, &self.memo); - - let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; - assert_eq!( - ChachaPolyIetf::aead_cipher() - .seal_to(&mut output, &input.0, &[], key.as_ref(), &[0u8; 12]) - .unwrap(), - ENC_CIPHERTEXT_SIZE - ); - - output - } - - /// Generates `outCiphertext` for this note. - pub fn encrypt_outgoing_plaintext( - &mut self, - cv: &D::ValueCommitment, - cm: &D::NoteCommitment, - rng: &mut R, - ) -> [u8; OUT_CIPHERTEXT_SIZE] { - let (ock, input) = if let Some(ovk) = &self.ovk { - let ock = D::get_ock(ovk, &cv, &cm, &self.epk); - let input = D::to_outgoing_plaintext_bytes(&self.note, &self.esk); - - (ock, input) - } else { - // ovk = ⊥ - let mut ock = OutgoingCipherKey([0; 32]); - let mut input = [0u8; OUT_PLAINTEXT_SIZE]; - - rng.fill_bytes(&mut ock.0); - rng.fill_bytes(&mut input); - - (ock, OutPlaintextBytes(input)) - }; - - let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; - assert_eq!( - ChachaPolyIetf::aead_cipher() - .seal_to(&mut output, &input.0, &[], ock.as_ref(), &[0u8; 12]) - .unwrap(), - OUT_CIPHERTEXT_SIZE - ); - - output - } + NoteEncryption::new_with_esk(esk, ovk, note, to, memo) } #[allow(clippy::if_same_then_else)] @@ -561,76 +288,6 @@ pub fn plaintext_version_is_valid( } } -/// Trial decryption of the full note plaintext by the recipient. -/// -/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`. -/// If successful, the corresponding Sapling note and memo are returned, along with the -/// `PaymentAddress` to which the note was sent. -/// -/// Implements section 4.17.2 of the Zcash Protocol Specification. -pub fn try_note_decryption( - domain: &D, - //output: &ShieldedOutput, - ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - enc_ciphertext: &[u8], -) -> Option<(D::Note, D::Recipient, D::Memo)> { - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); - - let shared_secret = D::ka_agree_dec(ivk, epk); - let key = D::kdf(shared_secret, epk); - - let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; - assert_eq!( - ChachaPolyIetf::aead_cipher() - .open_to( - &mut plaintext, - &enc_ciphertext, - &[], - key.as_ref(), - &[0u8; 12] - ) - .ok()?, - NOTE_PLAINTEXT_SIZE - ); - - let (note, to) = parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext)?; - let memo = domain.extract_memo(&plaintext); - - Some((note, to, memo)) -} - -fn parse_note_plaintext_without_memo( - domain: &D, - ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - plaintext: &[u8], -) -> Option<(D::Note, D::Recipient)> { - let (note, to) = domain.parse_note_plaintext_without_memo(ivk, &plaintext)?; - - if &D::extract_note_commitment(¬e) != cmstar { - // Published commitment doesn't match calculated commitment - return None; - } else { - let epk_bytes = D::to_epk_bytes(epk); - let validity = D::check_epk_bytes(¬e, |derived_esk| { - if D::to_epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { - EpkValidity::Valid - } else { - EpkValidity::Invalid - } - }); - - if validity != EpkValidity::Valid { - return None; - } - } - - Some((note, to)) -} - pub fn try_sapling_note_decryption( params: &P, height: BlockHeight, @@ -646,35 +303,6 @@ pub fn try_sapling_note_decryption( try_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) } -/// Trial decryption of the compact note plaintext by the recipient for light clients. -/// -/// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the -/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the -/// `PaymentAddress` to which the note was sent. -/// -/// Implements the procedure specified in [`ZIP 307`]. -/// -/// [`ZIP 307`]: https://zips.z.cash/zip-0307 -pub fn try_compact_note_decryption( - domain: &D, - ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - enc_ciphertext: &[u8], -) -> Option<(D::Note, D::Recipient)> { - assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE); - - let shared_secret = D::ka_agree_dec(&ivk, epk); - let key = D::kdf(shared_secret, &epk); - - // Start from block 1 to skip over Poly1305 keying output - let mut plaintext = [0; COMPACT_NOTE_SIZE]; - plaintext.copy_from_slice(&enc_ciphertext); - ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); - - parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext) -} - pub fn try_sapling_compact_note_decryption( params: &P, height: BlockHeight, @@ -836,13 +464,14 @@ mod tests { use rand_core::OsRng; use rand_core::{CryptoRng, RngCore}; use std::convert::TryInto; + use zcash_note_encryption::NoteEncryption; use super::{ kdf_sapling, 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, OutgoingCipherKey, - COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, - OUT_PLAINTEXT_SIZE, + SaplingDomain, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, + OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; use crate::{ @@ -1992,16 +1621,13 @@ mod tests { // Test encryption // - let mut ne = sapling_note_encryption::<_, TestNetwork>( + let mut ne = NoteEncryption::>::new_with_esk( + esk, Some(ovk), note, to, MemoBytes::from_bytes(&tv.memo).unwrap(), - &mut OsRng, ); - // Swap in the ephemeral keypair from the test vectors - ne.esk = esk; - ne.epk = epk; assert_eq!(&ne.encrypt_note_plaintext().as_ref()[..], &tv.c_enc[..]); assert_eq!( From 5b13bb3a1e98099a2ed849b47cf1b31cf82af47a Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 23 Mar 2021 11:29:16 -0600 Subject: [PATCH 04/24] Add try_output_recovery_with_ovk to shared note encryption code. --- components/zcash_note_encryption/src/lib.rs | 117 +++++++-- .../src/sapling/note_encryption.rs | 226 ++++++++---------- 2 files changed, 196 insertions(+), 147 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index a008edbae..e40271731 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -45,7 +45,7 @@ pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); #[derive(Copy, Clone, PartialEq, Eq)] -pub enum EpkValidity { +pub enum NoteValidity { Valid, Invalid, } @@ -109,23 +109,37 @@ pub trait Domain { fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; - fn check_epk_bytes EpkValidity>( + fn check_epk_bytes NoteValidity>( note: &Self::Note, check: F, - ) -> EpkValidity; + ) -> NoteValidity; fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment; - fn parse_note_plaintext_without_memo( + fn parse_note_plaintext_without_memo_ivk( &self, ivk: &Self::IncomingViewingKey, plaintext: &[u8], ) -> Option<(Self::Note, Self::Recipient)>; + fn parse_note_plaintext_without_memo_ovk( + &self, + pk_d: &Self::DiversifiedTransmissionKey, + esk: &Self::EphemeralSecretKey, + epk: &Self::EphemeralPublicKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)>; + // &self is passed here in anticipation of future changes // to memo handling where the memos may no longer be // part of the note plaintext. fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo; + + fn extract_pk_d( + out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE], + ) -> Option; + + fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option; } pub trait ShieldedOutput<'a, D: Domain> { @@ -320,40 +334,47 @@ pub fn try_note_decryption( NOTE_PLAINTEXT_SIZE ); - let (note, to) = parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext)?; + let (note, to) = parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext)?; let memo = domain.extract_memo(&plaintext); Some((note, to, memo)) } -fn parse_note_plaintext_without_memo( +fn parse_note_plaintext_without_memo_ivk( domain: &D, ivk: &D::IncomingViewingKey, epk: &D::EphemeralPublicKey, cmstar: &D::ExtractedCommitment, plaintext: &[u8], ) -> Option<(D::Note, D::Recipient)> { - let (note, to) = domain.parse_note_plaintext_without_memo(ivk, &plaintext)?; + let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, &plaintext)?; + let validity = check_note_validity::(¬e, epk, cmstar); + if validity == NoteValidity::Valid { + Some((note, to)) + } else { + None + } +} + +fn check_note_validity( + note: &D::Note, + epk: &D::EphemeralPublicKey, + cmstar: &D::ExtractedCommitment, +) -> NoteValidity { if &D::extract_note_commitment(¬e) != cmstar { // Published commitment doesn't match calculated commitment - return None; + NoteValidity::Invalid } else { let epk_bytes = D::to_epk_bytes(epk); - let validity = D::check_epk_bytes(¬e, |derived_esk| { + D::check_epk_bytes(¬e, |derived_esk| { if D::to_epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { - EpkValidity::Valid + NoteValidity::Valid } else { - EpkValidity::Invalid + NoteValidity::Invalid } - }); - - if validity != EpkValidity::Valid { - return None; - } + }) } - - Some((note, to)) } /// Trial decryption of the compact note plaintext by the recipient for light clients. @@ -382,5 +403,63 @@ pub fn try_compact_note_decryption( plaintext.copy_from_slice(&enc_ciphertext); ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); - parse_note_plaintext_without_memo(domain, ivk, epk, cmstar, &plaintext) + parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext) +} + +/// Recovery of the full note plaintext by the sender. +/// +/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`. +/// If successful, the corresponding Sapling note and memo are returned, along with the +/// `PaymentAddress` to which the note was sent. +/// +/// Implements part of section 4.17.3 of the Zcash Protocol Specification. +/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. +pub fn try_output_recovery_with_ock( + domain: &D, + ock: &OutgoingCipherKey, + cmstar: &D::ExtractedCommitment, + epk: &D::EphemeralPublicKey, + enc_ciphertext: &[u8], + out_ciphertext: &[u8], +) -> Option<(D::Note, D::Recipient, D::Memo)> { + assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); + assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); + + let mut op = [0; OUT_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12]) + .ok()?, + OUT_PLAINTEXT_SIZE + ); + + let pk_d = D::extract_pk_d(&op)?; + let esk = D::extract_esk(&op)?; + + let shared_secret = D::ka_agree_enc(&esk, &pk_d); + let key = D::kdf(shared_secret, &epk); + + let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .open_to( + &mut plaintext, + &enc_ciphertext, + &[], + key.as_ref(), + &[0u8; 12] + ) + .ok()?, + NOTE_PLAINTEXT_SIZE + ); + + let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &epk, &plaintext)?; + let memo = domain.extract_memo(&plaintext); + + let validity = check_note_validity::(¬e, epk, cmstar); + if validity == NoteValidity::Valid { + Some((note, to, memo)) + } else { + None + } } diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 0ddf26697..d32c2fa27 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -1,16 +1,16 @@ //! Implementation of in-band secret distribution for Zcash transactions. use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use byteorder::{LittleEndian, WriteBytesExt}; -use crypto_api_chachapoly::ChachaPolyIetf; use ff::PrimeField; use group::{cofactor::CofactorGroup, GroupEncoding}; use rand_core::RngCore; use std::convert::TryInto; use zcash_note_encryption::{ - try_compact_note_decryption, try_note_decryption, Domain, EphemeralKeyBytes, EpkValidity, - NoteEncryption, NotePlaintextBytes, OutPlaintextBytes, OutgoingCipherKey, COMPACT_NOTE_SIZE, - ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock, Domain, + EphemeralKeyBytes, NoteEncryption, NotePlaintextBytes, NoteValidity, OutPlaintextBytes, + OutgoingCipherKey, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, + OUT_PLAINTEXT_SIZE, }; use crate::{ @@ -73,6 +73,40 @@ pub fn prf_ock( ) } +fn sapling_parse_note_plaintext_without_memo( + domain: &SaplingDomain

, + plaintext: &[u8], + get_validated_pk_d: F, +) -> Option<(Note, PaymentAddress)> +where + F: FnOnce(&Diversifier) -> Option, +{ + assert!(plaintext.len() >= COMPACT_NOTE_SIZE); + + // Check note plaintext version + if !plaintext_version_is_valid(&domain.params, domain.height, plaintext[0]) { + return None; + } + + // The unwraps below are guaranteed to succeed by the assertion above + let diversifier = Diversifier(plaintext[1..12].try_into().unwrap()); + let value = Amount::from_u64_le_bytes(plaintext[12..20].try_into().unwrap()).ok()?; + let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(); + + let rseed = if plaintext[0] == 0x01 { + let rcm = jubjub::Fr::from_repr(r)?; + Rseed::BeforeZip212(rcm) + } else { + Rseed::AfterZip212(r) + }; + + let pk_d = get_validated_pk_d(&diversifier)?; + + let to = PaymentAddress::from_parts(diversifier, pk_d)?; + let note = to.create_note(value.into(), rseed)?; + Some((note, to)) +} + pub struct SaplingDomain { params: P, height: BlockHeight, @@ -131,13 +165,7 @@ impl Domain for SaplingDomain

{ /// /// Implements section 5.4.4.4 of the Zcash Protocol Specification. fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash { - Blake2bParams::new() - .hash_length(32) - .personal(KDF_SAPLING_PERSONALIZATION) - .to_state() - .update(&dhsecret.to_bytes()) - .update(&epk.to_bytes()) - .finalize() + kdf_sapling(dhsecret, epk) } fn to_note_plaintext_bytes( @@ -195,53 +223,68 @@ impl Domain for SaplingDomain

{ EphemeralKeyBytes(epk.to_bytes()) } - fn check_epk_bytes EpkValidity>( + fn check_epk_bytes NoteValidity>( note: &Note, check: F, - ) -> EpkValidity { + ) -> NoteValidity { if let Some(derived_esk) = note.derive_esk() { check(&derived_esk) } else { // Before ZIP 212 - EpkValidity::Valid + NoteValidity::Valid } } - fn parse_note_plaintext_without_memo( + fn parse_note_plaintext_without_memo_ivk( &self, ivk: &Self::IncomingViewingKey, plaintext: &[u8], ) -> Option<(Self::Note, Self::Recipient)> { - assert!(plaintext.len() >= COMPACT_NOTE_SIZE); + sapling_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| { + Some(diversifier.g_d()? * ivk.0) + }) + } - // Check note plaintext version - if !plaintext_version_is_valid(&self.params, self.height, plaintext[0]) { - return None; - } - - // The unwraps below are guaranteed to succeed by the assertion above - let diversifier = Diversifier(plaintext[1..12].try_into().unwrap()); - let value = Amount::from_u64_le_bytes(plaintext[12..20].try_into().unwrap()).ok()?; - let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(); - - let rseed = if plaintext[0] == 0x01 { - let rcm = jubjub::Fr::from_repr(r)?; - Rseed::BeforeZip212(rcm) - } else { - Rseed::AfterZip212(r) - }; - - let pk_d = diversifier.g_d()? * ivk.0; - - let to = PaymentAddress::from_parts(diversifier, pk_d)?; - let note = to.create_note(value.into(), rseed)?; - Some((note, to)) + fn parse_note_plaintext_without_memo_ovk( + &self, + pk_d: &Self::DiversifiedTransmissionKey, + esk: &Self::EphemeralSecretKey, + epk: &Self::EphemeralPublicKey, + plaintext: &[u8], + ) -> Option<(Self::Note, Self::Recipient)> { + sapling_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| { + if (diversifier.g_d()? * esk).to_bytes() == epk.to_bytes() { + Some(*pk_d) + } else { + None + } + }) } fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment { note.cmu().to_bytes() } + fn extract_pk_d(op: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option { + let pk_d = jubjub::SubgroupPoint::from_bytes( + op[0..32].try_into().expect("slice is the correct length"), + ); + + if pk_d.is_none().into() { + None + } else { + Some(pk_d.unwrap()) + } + } + + fn extract_esk(op: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option { + jubjub::Fr::from_repr( + op[32..OUT_PLAINTEXT_SIZE] + .try_into() + .expect("slice is the correct length"), + ) + } + fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo { MemoBytes::from_bytes(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap() } @@ -336,94 +379,19 @@ pub fn try_sapling_output_recovery_with_ock( enc_ciphertext: &[u8], out_ciphertext: &[u8], ) -> Option<(Note, PaymentAddress, MemoBytes)> { - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); - assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); - - let mut op = [0; OUT_CIPHERTEXT_SIZE]; - assert_eq!( - ChachaPolyIetf::aead_cipher() - .open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12]) - .ok()?, - OUT_PLAINTEXT_SIZE - ); - - let pk_d = { - let pk_d = jubjub::SubgroupPoint::from_bytes( - op[0..32].try_into().expect("slice is the correct length"), - ); - if pk_d.is_none().into() { - return None; - } - pk_d.unwrap() + let domain = SaplingDomain { + params: params.clone(), + height, }; - let esk = jubjub::Fr::from_repr( - op[32..OUT_PLAINTEXT_SIZE] - .try_into() - .expect("slice is the correct length"), - )?; - - let shared_secret = sapling_ka_agree(&esk, &pk_d.into()); - let key = kdf_sapling(shared_secret, &epk); - - let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; - assert_eq!( - ChachaPolyIetf::aead_cipher() - .open_to( - &mut plaintext, - &enc_ciphertext, - &[], - key.as_bytes(), - &[0u8; 12] - ) - .ok()?, - NOTE_PLAINTEXT_SIZE - ); - - // Check note plaintext version - if !plaintext_version_is_valid(params, height, plaintext[0]) { - return None; - } - - let mut d = [0u8; 11]; - d.copy_from_slice(&plaintext[1..12]); - - let v = Amount::from_u64_le_bytes(plaintext[12..20].try_into().unwrap()).ok()?; - - let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE] - .try_into() - .expect("slice is the correct length"); - - let rseed = if plaintext[0] == 0x01 { - let rcm = jubjub::Fr::from_repr(r)?; - Rseed::BeforeZip212(rcm) - } else { - Rseed::AfterZip212(r) - }; - - let memo = MemoBytes::from_bytes(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap(); - - let diversifier = Diversifier(d); - if (diversifier.g_d()? * esk).to_bytes() != epk.to_bytes() { - // Published epk doesn't match calculated epk - return None; - } - - let to = PaymentAddress::from_parts(diversifier, pk_d)?; - let note = to.create_note(v.into(), rseed).unwrap(); - - if note.cmu() != *cmu { - // Published commitment doesn't match calculated commitment - return None; - } - - if let Some(derived_esk) = note.derive_esk() { - if derived_esk != esk { - return None; - } - } - - Some((note, to, memo)) + try_output_recovery_with_ock( + &domain, + ock, + &cmu.to_bytes(), + epk, + enc_ciphertext, + out_ciphertext, + ) } /// Recovery of the full note plaintext by the sender. @@ -464,14 +432,16 @@ mod tests { use rand_core::OsRng; use rand_core::{CryptoRng, RngCore}; use std::convert::TryInto; - use zcash_note_encryption::NoteEncryption; + + use zcash_note_encryption::{ + NoteEncryption, OutgoingCipherKey, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, + NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + }; use super::{ kdf_sapling, 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, OutgoingCipherKey, - SaplingDomain, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, - OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + try_sapling_output_recovery, try_sapling_output_recovery_with_ock, SaplingDomain, }; use crate::{ From be225daabfe612cc7f1c87442a848efba4c7d6ad Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Mar 2021 08:43:40 -0600 Subject: [PATCH 05/24] Update zcash_primitives dev dependency for zcash_note_encryption --- components/zcash_note_encryption/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml index b749ed0e9..caf262480 100644 --- a/components/zcash_note_encryption/Cargo.toml +++ b/components/zcash_note_encryption/Cargo.toml @@ -19,4 +19,4 @@ group = "0.8" rand_core = "0.5.1" [dev-dependencies] -zcash_primitives = { version = "0.4", path = "../../zcash_primitives" } +zcash_primitives = { version = "0.5", path = "../../zcash_primitives" } From 213cd6cce91cbbc6f97fbebcc7200e41975c689c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Mar 2021 08:51:44 -0600 Subject: [PATCH 06/24] Fix Clippy complaints. --- components/zcash_note_encryption/src/lib.rs | 14 +++++++------- zcash_primitives/src/sapling/note_encryption.rs | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index e40271731..2b965cc5e 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -89,7 +89,7 @@ pub trait Domain { // for right now, we just need `recipient` to get `d`; in the future when we // can get that from a Sapling note, the recipient parameter will be able // to be removed. - fn to_note_plaintext_bytes( + fn note_plaintext_bytes( note: &Self::Note, recipient: &Self::Recipient, memo: &Self::Memo, @@ -102,12 +102,12 @@ pub trait Domain { epk: &Self::EphemeralPublicKey, ) -> OutgoingCipherKey; - fn to_outgoing_plaintext_bytes( + fn outgoing_plaintext_bytes( note: &Self::Note, esk: &Self::EphemeralSecretKey, ) -> OutPlaintextBytes; - fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; + fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; fn check_epk_bytes NoteValidity>( note: &Self::Note, @@ -252,7 +252,7 @@ impl NoteEncryption { let pk_d = D::get_pk_d(&self.note); let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); let key = D::kdf(shared_secret, &self.epk); - let input = D::to_note_plaintext_bytes(&self.note, &self.to, &self.memo); + let input = D::note_plaintext_bytes(&self.note, &self.to, &self.memo); let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; assert_eq!( @@ -274,7 +274,7 @@ impl NoteEncryption { ) -> [u8; OUT_CIPHERTEXT_SIZE] { let (ock, input) = if let Some(ovk) = &self.ovk { let ock = D::get_ock(ovk, &cv, &cm, &self.epk); - let input = D::to_outgoing_plaintext_bytes(&self.note, &self.esk); + let input = D::outgoing_plaintext_bytes(&self.note, &self.esk); (ock, input) } else { @@ -366,9 +366,9 @@ fn check_note_validity( // Published commitment doesn't match calculated commitment NoteValidity::Invalid } else { - let epk_bytes = D::to_epk_bytes(epk); + let epk_bytes = D::epk_bytes(epk); D::check_epk_bytes(¬e, |derived_esk| { - if D::to_epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { + if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { NoteValidity::Valid } else { NoteValidity::Invalid diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index d32c2fa27..a515d16aa 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -168,7 +168,7 @@ impl Domain for SaplingDomain

{ kdf_sapling(dhsecret, epk) } - fn to_note_plaintext_bytes( + fn note_plaintext_bytes( note: &Self::Note, to: &Self::Recipient, memo: &Self::Memo, @@ -208,7 +208,7 @@ impl Domain for SaplingDomain

{ prf_ock(ovk, &cv, &cmu, epk) } - fn to_outgoing_plaintext_bytes( + fn outgoing_plaintext_bytes( note: &Self::Note, esk: &Self::EphemeralSecretKey, ) -> OutPlaintextBytes { @@ -219,7 +219,7 @@ impl Domain for SaplingDomain

{ OutPlaintextBytes(input) } - fn to_epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes { + fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes { EphemeralKeyBytes(epk.to_bytes()) } From dad8663c55ffb739da9758b9971d00ccd5ccfb73 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Mar 2021 19:29:42 -0600 Subject: [PATCH 07/24] Fix zcash_note_encryption doctests. --- components/zcash_note_encryption/Cargo.toml | 1 + components/zcash_note_encryption/src/lib.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml index caf262480..044e9131d 100644 --- a/components/zcash_note_encryption/Cargo.toml +++ b/components/zcash_note_encryption/Cargo.toml @@ -20,3 +20,4 @@ rand_core = "0.5.1" [dev-dependencies] zcash_primitives = { version = "0.5", path = "../../zcash_primitives" } +jubjub = "0.5.1" diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 2b965cc5e..4f3471939 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -168,9 +168,10 @@ pub trait ShieldedOutput<'a, D: Domain> { /// use rand_core::OsRng; /// use zcash_primitives::{ /// consensus::TestNetwork, +/// memo::MemoBytes, /// sapling::{ /// keys::{OutgoingViewingKey, prf_expand}, -/// note_encryption::{Memo, sapling_note_encryption}, +/// note_encryption::{sapling_note_encryption}, /// Diversifier, PaymentAddress, Rseed, ValueCommitment /// }, /// }; @@ -192,7 +193,7 @@ pub trait ShieldedOutput<'a, D: Domain> { /// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); /// let cmu = note.cmu(); /// -/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, Memo::default(), &mut rng); +/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng); /// let encCiphertext = enc.encrypt_note_plaintext(); /// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng); /// ``` From 879eea863adf4dc1c598536362ceb796c07aebc7 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 5 Apr 2021 09:49:32 -0600 Subject: [PATCH 08/24] Apply suggestions from code review Co-authored-by: str4d --- components/zcash_note_encryption/src/lib.rs | 6 ++---- zcash_primitives/src/sapling/note_encryption.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 4f3471939..520168098 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -350,8 +350,7 @@ fn parse_note_plaintext_without_memo_ivk( ) -> Option<(D::Note, D::Recipient)> { let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, &plaintext)?; - let validity = check_note_validity::(¬e, epk, cmstar); - if validity == NoteValidity::Valid { + if let NoteValidity::Valid = check_note_validity::(¬e, epk, cmstar) { Some((note, to)) } else { None @@ -457,8 +456,7 @@ pub fn try_output_recovery_with_ock( let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &epk, &plaintext)?; let memo = domain.extract_memo(&plaintext); - let validity = check_note_validity::(¬e, epk, cmstar); - if validity == NoteValidity::Valid { + if let NoteValidity::Valid = check_note_validity::(¬e, epk, cmstar) { Some((note, to, memo)) } else { None diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index a515d16aa..daad0e2cd 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -205,7 +205,7 @@ impl Domain for SaplingDomain

{ cmu: &Self::NoteCommitment, epk: &Self::EphemeralPublicKey, ) -> OutgoingCipherKey { - prf_ock(ovk, &cv, &cmu, epk) + prf_ock(ovk, cv, cmu, epk) } fn outgoing_plaintext_bytes( From a560101bb2f2c9635aaa8a77256fef0c49a113eb Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 8 Apr 2021 10:08:00 -0600 Subject: [PATCH 09/24] Remove spurious mut references. --- components/zcash_note_encryption/src/lib.rs | 2 +- zcash_primitives/benches/note_decryption.rs | 4 ++-- zcash_primitives/src/sapling/note_encryption.rs | 6 +++--- zcash_primitives/src/transaction/builder.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 520168098..c4e2d87a7 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -268,7 +268,7 @@ impl NoteEncryption { /// Generates `outCiphertext` for this note. pub fn encrypt_outgoing_plaintext( - &mut self, + &self, cv: &D::ValueCommitment, cm: &D::NoteCommitment, rng: &mut R, diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index f08ef58d9..f9e142ed7 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -5,7 +5,7 @@ use zcash_primitives::{ consensus::{NetworkUpgrade::Canopy, Parameters, TestNetwork, TEST_NETWORK}, memo::MemoBytes, sapling::{ - note_encryption::{sapling_note_encryption, try_sapling_note_decryption, Memo}, + note_encryption::{sapling_note_encryption, try_sapling_note_decryption}, util::generate_random_rseed, Diversifier, PaymentAddress, SaplingIvk, ValueCommitment, }, @@ -38,7 +38,7 @@ fn bench_note_decryption(c: &mut Criterion) { let note = pa.create_note(value, rseed).unwrap(); let cmu = note.cmu(); - let mut ne = + let ne = sapling_note_encryption::<_, TestNetwork>(None, note, pa, MemoBytes::empty(), &mut rng); let ephemeral_key = *ne.epk(); let enc_ciphertext = ne.encrypt_note_plaintext(); diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index daad0e2cd..dd1e2e4dc 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -554,7 +554,7 @@ mod tests { let cmu = note.cmu(); let ovk = OutgoingViewingKey([0; 32]); - let mut ne = sapling_note_encryption::<_, TestNetwork>( + let ne = sapling_note_encryption::<_, TestNetwork>( Some(ovk), note, pa, @@ -1591,7 +1591,7 @@ mod tests { // Test encryption // - let mut ne = NoteEncryption::>::new_with_esk( + let ne = NoteEncryption::>::new_with_esk( esk, Some(ovk), note, @@ -1599,7 +1599,7 @@ mod tests { MemoBytes::from_bytes(&tv.memo).unwrap(), ); - assert_eq!(&ne.encrypt_note_plaintext().as_ref()[..], &tv.c_enc[..]); + assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]); assert_eq!( &ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng)[..], &tv.c_out[..] diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index ca3cc2a64..8cdc954d0 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -162,7 +162,7 @@ impl SaplingOutput

{ ctx: &mut Pr::SaplingProvingContext, rng: &mut R, ) -> OutputDescription { - let mut encryptor = sapling_note_encryption::( + let encryptor = sapling_note_encryption::( self.ovk, self.note.clone(), self.to.clone(), From 24e62d3a7b33fe5a7a7681afabb36a63299161c7 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 8 Apr 2021 10:08:58 -0600 Subject: [PATCH 10/24] Update comments describing COMPACT_NOTE_SIZE components. Co-authored-by: ebfull --- components/zcash_note_encryption/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index c4e2d87a7..be9390334 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -9,7 +9,7 @@ use rand_core::RngCore; pub const COMPACT_NOTE_SIZE: usize = 1 + // version 11 + // diversifier 8 + // value - 32; // rcv + 32; // rseed (or rcm prior to ZIP 212) pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d 32; // esk From cfdbafe2e39cf4270103b8810e613cbb2cb8e55a Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 8 Apr 2021 10:12:47 -0600 Subject: [PATCH 11/24] Add myself to crate contributors. --- components/zcash_note_encryption/Cargo.toml | 1 + zcash_client_backend/Cargo.toml | 1 + zcash_client_sqlite/Cargo.toml | 1 + zcash_primitives/Cargo.toml | 1 + 4 files changed, 4 insertions(+) diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml index 044e9131d..2785e27ae 100644 --- a/components/zcash_note_encryption/Cargo.toml +++ b/components/zcash_note_encryption/Cargo.toml @@ -4,6 +4,7 @@ description = "TBD" version = "0.0.0" authors = [ "Jack Grigg ", + "Kris Nuttycombe " ] homepage = "https://github.com/zcash/librustzcash" repository = "https://github.com/zcash/librustzcash" diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index aa470a21a..527311658 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -4,6 +4,7 @@ description = "APIs for creating shielded Zcash light clients" version = "0.5.0" authors = [ "Jack Grigg ", + "Kris Nuttycombe " ] homepage = "https://github.com/zcash/librustzcash" repository = "https://github.com/zcash/librustzcash" diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 86cb68d84..5e1efe0f6 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -4,6 +4,7 @@ description = "An SQLite-based Zcash light client" version = "0.3.0" authors = [ "Jack Grigg ", + "Kris Nuttycombe " ] homepage = "https://github.com/zcash/librustzcash" repository = "https://github.com/zcash/librustzcash" diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 73f9ec9d0..a950331cf 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -4,6 +4,7 @@ description = "Rust implementations of the Zcash primitives" version = "0.5.0" authors = [ "Jack Grigg ", + "Kris Nuttycombe " ] homepage = "https://github.com/zcash/librustzcash" repository = "https://github.com/zcash/librustzcash" From e654cc4ce67472106c59e416d384b2081597becb Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 09:13:04 -0600 Subject: [PATCH 12/24] Use constant-time equality for EphemeralKeyBytes. Fixes #370 --- components/zcash_note_encryption/Cargo.toml | 1 + components/zcash_note_encryption/src/lib.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml index 2785e27ae..bed67dafe 100644 --- a/components/zcash_note_encryption/Cargo.toml +++ b/components/zcash_note_encryption/Cargo.toml @@ -18,6 +18,7 @@ crypto_api_chachapoly = "0.4" ff = "0.8" group = "0.8" rand_core = "0.5.1" +subtle = "2.2.3" [dev-dependencies] zcash_primitives = { version = "0.5", path = "../../zcash_primitives" } diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index be9390334..5d0456fb0 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -5,6 +5,7 @@ use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; use rand_core::RngCore; +use subtle::{ConstantTimeEq, Choice}; pub const COMPACT_NOTE_SIZE: usize = 1 + // version 11 + // diversifier @@ -31,8 +32,6 @@ impl AsRef<[u8]> for OutgoingCipherKey { } } -//FIXME: use constant-time checks for equality -#[derive(Eq, PartialEq)] pub struct EphemeralKeyBytes(pub [u8; 32]); impl From<[u8; 32]> for EphemeralKeyBytes { @@ -41,6 +40,12 @@ impl From<[u8; 32]> for EphemeralKeyBytes { } } +impl ConstantTimeEq for EphemeralKeyBytes { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); @@ -368,7 +373,7 @@ fn check_note_validity( } else { let epk_bytes = D::epk_bytes(epk); D::check_epk_bytes(¬e, |derived_esk| { - if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) == epk_bytes { + if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)).ct_eq(&epk_bytes).into() { NoteValidity::Valid } else { NoteValidity::Invalid From 4f22f1d578668187ce14716bb01021199cd5f01f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 09:18:51 -0600 Subject: [PATCH 13/24] Apply suggestions from code review Co-authored-by: Daira Hopwood --- components/zcash_note_encryption/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 5d0456fb0..887c1f155 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -14,8 +14,9 @@ pub const COMPACT_NOTE_SIZE: usize = 1 + // version pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d 32; // esk -pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16; -pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16; +pub const AEAD_TAG_SIZE: usize = 16; +pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE; +pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + AEAD_TAG_SIZE; /// A symmetric key that can be used to recover a single Sapling or Orchard output. pub struct OutgoingCipherKey(pub [u8; 32]); @@ -159,7 +160,8 @@ pub trait ShieldedOutput<'a, D: Domain> { /// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are /// consistent with each other. /// -/// Implements section 4.17.1 of the Zcash Protocol Specification. +/// Implements section 4.19 of the Zcash Protocol Specification. +/// /// NB: the example code is only covering the pre-Canopy case. /// /// # Examples @@ -248,7 +250,7 @@ impl NoteEncryption { &self.esk } - /// Exposes the ephemeral public key being used to encrypt this note. + /// Exposes the encoding of the ephemeral public key being used to encrypt this note. pub fn epk(&self) -> &D::EphemeralPublicKey { &self.epk } From 324fc36521ef474fde25faa2cb976104533c1bd2 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 5 Apr 2021 11:51:07 -0600 Subject: [PATCH 14/24] Use ShieldedOutput trait for note encryption/decryption. This change modifies note encryption and decryption functions to treat a shielded output as a single value instead of handling the parts of an output as independent arguments. --- components/zcash_note_encryption/src/lib.rs | 71 +-- zcash_client_backend/Cargo.toml | 5 +- zcash_client_backend/src/decrypt.rs | 38 +- zcash_client_backend/src/proto.rs | 27 +- zcash_client_backend/src/welding_rig.rs | 41 +- zcash_client_sqlite/src/wallet/transact.rs | 4 +- zcash_primitives/benches/note_decryption.rs | 23 +- .../src/sapling/note_encryption.rs | 541 +++++++----------- .../src/transaction/components/sapling.rs | 70 ++- 9 files changed, 378 insertions(+), 442 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 887c1f155..36ccc765e 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -5,7 +5,7 @@ use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; use rand_core::RngCore; -use subtle::{ConstantTimeEq, Choice}; +use subtle::{Choice, ConstantTimeEq}; pub const COMPACT_NOTE_SIZE: usize = 1 + // version 11 + // diversifier @@ -148,10 +148,10 @@ pub trait Domain { fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option; } -pub trait ShieldedOutput<'a, D: Domain> { - fn ivk(&'a self) -> &'a D::IncomingViewingKey; - fn epk(&'a self) -> &'a D::EphemeralPublicKey; - fn cmstar(&'a self) -> &'a D::ExtractedCommitment; +pub trait ShieldedOutput { + fn epk(&self) -> &D::EphemeralPublicKey; + fn cmstar(&self) -> D::ExtractedCommitment; + fn enc_ciphertext(&self) -> &[u8]; } /// A struct containing context required for encrypting Sapling and Orchard notes. @@ -315,25 +315,22 @@ impl NoteEncryption { /// `PaymentAddress` to which the note was sent. /// /// Implements section 4.17.2 of the Zcash Protocol Specification. -pub fn try_note_decryption( +pub fn try_note_decryption>( domain: &D, - //output: &ShieldedOutput, ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(D::Note, D::Recipient, D::Memo)> { - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); + assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE); - let shared_secret = D::ka_agree_dec(ivk, epk); - let key = D::kdf(shared_secret, epk); + let shared_secret = D::ka_agree_dec(ivk, output.epk()); + let key = D::kdf(shared_secret, output.epk()); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( ChachaPolyIetf::aead_cipher() .open_to( &mut plaintext, - &enc_ciphertext, + output.enc_ciphertext(), &[], key.as_ref(), &[0u8; 12] @@ -342,7 +339,13 @@ pub fn try_note_decryption( NOTE_PLAINTEXT_SIZE ); - let (note, to) = parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext)?; + let (note, to) = parse_note_plaintext_without_memo_ivk( + domain, + ivk, + output.epk(), + &output.cmstar(), + &plaintext, + )?; let memo = domain.extract_memo(&plaintext); Some((note, to, memo)) @@ -375,7 +378,10 @@ fn check_note_validity( } else { let epk_bytes = D::epk_bytes(epk); D::check_epk_bytes(¬e, |derived_esk| { - if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)).ct_eq(&epk_bytes).into() { + if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) + .ct_eq(&epk_bytes) + .into() + { NoteValidity::Valid } else { NoteValidity::Invalid @@ -393,24 +399,22 @@ fn check_note_validity( /// Implements the procedure specified in [`ZIP 307`]. /// /// [`ZIP 307`]: https://zips.z.cash/zip-0307 -pub fn try_compact_note_decryption( +pub fn try_compact_note_decryption>( domain: &D, ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(D::Note, D::Recipient)> { - assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE); + assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE); - let shared_secret = D::ka_agree_dec(&ivk, epk); - let key = D::kdf(shared_secret, &epk); + let shared_secret = D::ka_agree_dec(&ivk, output.epk()); + let key = D::kdf(shared_secret, output.epk()); // Start from block 1 to skip over Poly1305 keying output let mut plaintext = [0; COMPACT_NOTE_SIZE]; - plaintext.copy_from_slice(&enc_ciphertext); + plaintext.copy_from_slice(output.enc_ciphertext()); ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); - parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext) + parse_note_plaintext_without_memo_ivk(domain, ivk, output.epk(), &output.cmstar(), &plaintext) } /// Recovery of the full note plaintext by the sender. @@ -421,15 +425,13 @@ pub fn try_compact_note_decryption( /// /// Implements part of section 4.17.3 of the Zcash Protocol Specification. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. -pub fn try_output_recovery_with_ock( +pub fn try_output_recovery_with_ock>( domain: &D, ock: &OutgoingCipherKey, - cmstar: &D::ExtractedCommitment, - epk: &D::EphemeralPublicKey, - enc_ciphertext: &[u8], + output: &Output, out_ciphertext: &[u8], ) -> Option<(D::Note, D::Recipient, D::Memo)> { - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); + assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE); assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); let mut op = [0; OUT_CIPHERTEXT_SIZE]; @@ -444,14 +446,14 @@ pub fn try_output_recovery_with_ock( let esk = D::extract_esk(&op)?; let shared_secret = D::ka_agree_enc(&esk, &pk_d); - let key = D::kdf(shared_secret, &epk); + let key = D::kdf(shared_secret, output.epk()); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( ChachaPolyIetf::aead_cipher() .open_to( &mut plaintext, - &enc_ciphertext, + output.enc_ciphertext(), &[], key.as_ref(), &[0u8; 12] @@ -460,10 +462,11 @@ pub fn try_output_recovery_with_ock( NOTE_PLAINTEXT_SIZE ); - let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &epk, &plaintext)?; + let (note, to) = + domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?; let memo = domain.extract_memo(&plaintext); - if let NoteValidity::Valid = check_note_validity::(¬e, epk, cmstar) { + if let NoteValidity::Valid = check_note_validity::(¬e, output.epk(), &output.cmstar()) { Some((note, to, memo)) } else { None diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 527311658..da0518aa7 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -22,13 +22,14 @@ group = "0.8" hex = "0.4" jubjub = "0.5.1" nom = "6.1" +percent-encoding = "2.1.0" +proptest = { version = "0.10.1", optional = true } protobuf = "2.20" rand_core = "0.5.1" subtle = "2.2.3" time = "0.2" +zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" } zcash_primitives = { version = "0.5", path = "../zcash_primitives" } -proptest = { version = "0.10.1", optional = true } -percent-encoding = "2.1.0" [build-dependencies] protobuf-codegen-pure = "2.20" diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 3e76a7c5f..6a82f1c1f 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -49,29 +49,21 @@ pub fn decrypt_transaction( let ovk = extfvk.fvk.ovk; for (index, output) in tx.shielded_outputs.iter().enumerate() { - let ((note, to, memo), outgoing) = match try_sapling_note_decryption( - params, - height, - &ivk, - &output.ephemeral_key, - &output.cmu, - &output.enc_ciphertext, - ) { - Some(ret) => (ret, false), - None => match try_sapling_output_recovery( - params, - height, - &ovk, - &output.cv, - &output.cmu, - &output.ephemeral_key, - &output.enc_ciphertext, - &output.out_ciphertext, - ) { - Some(ret) => (ret, true), - None => continue, - }, - }; + let ((note, to, memo), outgoing) = + match try_sapling_note_decryption(params, height, &ivk, output) { + Some(ret) => (ret, false), + None => match try_sapling_output_recovery( + params, + height, + &ovk, + &output.cv, + output, + &output.out_ciphertext, + ) { + Some(ret) => (ret, true), + None => continue, + }, + }; decrypted.push(DecryptedOutput { index, note, diff --git a/zcash_client_backend/src/proto.rs b/zcash_client_backend/src/proto.rs index 8247abdc9..9493c4c26 100644 --- a/zcash_client_backend/src/proto.rs +++ b/zcash_client_backend/src/proto.rs @@ -2,14 +2,17 @@ use ff::PrimeField; use group::GroupEncoding; -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use zcash_primitives::{ block::{BlockHash, BlockHeader}, consensus::BlockHeight, sapling::Nullifier, + transaction::components::sapling::{CompactOutputDescription, OutputDescription}, }; +use zcash_note_encryption::COMPACT_NOTE_SIZE; + pub mod compact_formats; impl compact_formats::CompactBlock { @@ -98,6 +101,28 @@ impl compact_formats::CompactOutput { } } +impl From for compact_formats::CompactOutput { + fn from(out: OutputDescription) -> compact_formats::CompactOutput { + let mut result = compact_formats::CompactOutput::new(); + result.set_cmu(out.cmu.to_repr().to_vec()); + result.set_epk(out.ephemeral_key.to_bytes().to_vec()); + result.set_ciphertext(out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec()); + result + } +} + +impl TryFrom for CompactOutputDescription { + type Error = (); + + fn try_from(value: compact_formats::CompactOutput) -> Result { + Ok(CompactOutputDescription { + cmu: value.cmu()?, + epk: value.epk()?, + enc_ciphertext: value.ciphertext, + }) + } +} + impl compact_formats::CompactSpend { pub fn nf(&self) -> Result { Nullifier::from_slice(&self.nf).map_err(|_| ()) diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 0eb076454..bff4fd659 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -2,15 +2,16 @@ use ff::PrimeField; use std::collections::HashSet; +use std::convert::TryFrom; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_primitives::{ consensus::{self, BlockHeight}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{ - note_encryption::try_sapling_compact_note_decryption, Node, Note, Nullifier, - PaymentAddress, SaplingIvk, + note_encryption::{try_sapling_compact_note_decryption, SaplingShieldedOutput}, + Node, Note, Nullifier, PaymentAddress, SaplingIvk, }, - transaction::TxId, + transaction::{components::sapling::CompactOutputDescription, TxId}, zip32::ExtendedFullViewingKey, }; @@ -38,12 +39,10 @@ fn scan_output( block_witnesses: &mut [&mut IncrementalWitness], new_witnesses: &mut [&mut IncrementalWitness], ) -> Option> { - let cmu = output.cmu().ok()?; - let epk = output.epk().ok()?; - let ct = output.ciphertext; + let output = CompactOutputDescription::try_from(output).ok()?; // Increment tree and witnesses - let node = Node::new(cmu.to_repr()); + let node = Node::new(output.cmu.to_repr()); for witness in existing_witnesses { witness.append(node).unwrap(); } @@ -56,7 +55,7 @@ fn scan_output( tree.append(node).unwrap(); for (account, vk) in vks.iter() { - let (note, to) = match vk.try_decryption(params, height, &epk, &cmu, &ct) { + let (note, to) = match vk.try_decryption(params, height, &output) { Some(ret) => ret, None => continue, }; @@ -74,8 +73,8 @@ fn scan_output( return Some(WalletShieldedOutput { index, - cmu, - epk, + cmu: output.cmu, + epk: output.epk, account: **account, note, to, @@ -108,13 +107,11 @@ pub trait ScanningKey { /// Attempts to decrypt a Sapling note and payment address /// from the specified ciphertext using this scanning key. - fn try_decryption( + fn try_decryption>( &self, params: &P, height: BlockHeight, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - ct: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress)>; /// Produces the nullifier for the specified note and witness, if possible. @@ -132,15 +129,13 @@ pub trait ScanningKey { impl ScanningKey for ExtendedFullViewingKey { type Nf = Nullifier; - fn try_decryption( + fn try_decryption>( &self, params: &P, height: BlockHeight, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - ct: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress)> { - try_sapling_compact_note_decryption(params, height, &self.fvk.vk.ivk(), &epk, &cmu, &ct) + try_sapling_compact_note_decryption(params, height, &self.fvk.vk.ivk(), output) } fn nf(&self, note: &Note, witness: &IncrementalWitness) -> Self::Nf { @@ -155,15 +150,13 @@ impl ScanningKey for ExtendedFullViewingKey { impl ScanningKey for SaplingIvk { type Nf = (); - fn try_decryption( + fn try_decryption>( &self, params: &P, height: BlockHeight, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - ct: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress)> { - try_sapling_compact_note_decryption(params, height, self, &epk, &cmu, &ct) + try_sapling_compact_note_decryption(params, height, self, output) } fn nf(&self, _note: &Note, _witness: &IncrementalWitness) {} diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index e4b52e31e..768558a90 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -635,9 +635,7 @@ mod tests { sapling_activation_height(), &extfvk.fvk.ovk, &output.cv, - &output.cmu, - &output.ephemeral_key, - &output.enc_ciphertext, + output, &output.out_ciphertext, ) }; diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index f9e142ed7..6200d5202 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -57,30 +57,11 @@ fn bench_note_decryption(c: &mut Criterion) { let mut group = c.benchmark_group("Sapling note decryption"); group.bench_function("valid", |b| { - b.iter(|| { - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &valid_ivk, - &output.ephemeral_key, - &output.cmu, - &output.enc_ciphertext, - ) - .unwrap() - }) + b.iter(|| try_sapling_note_decryption(&TEST_NETWORK, height, &valid_ivk, &output).unwrap()) }); group.bench_function("invalid", |b| { - b.iter(|| { - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &invalid_ivk, - &output.ephemeral_key, - &output.cmu, - &output.enc_ciphertext, - ) - }) + b.iter(|| try_sapling_note_decryption(&TEST_NETWORK, height, &invalid_ivk, &output)) }); } diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index dd1e2e4dc..fe14133ec 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -9,7 +9,7 @@ use std::convert::TryInto; use zcash_note_encryption::{ try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock, Domain, EphemeralKeyBytes, NoteEncryption, NotePlaintextBytes, NoteValidity, OutPlaintextBytes, - OutgoingCipherKey, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, + OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; @@ -290,6 +290,12 @@ impl Domain for SaplingDomain

{ } } +pub trait SaplingShieldedOutput: + ShieldedOutput> +{ + fn cmu(&self) -> &bls12_381::Scalar; +} + /// Creates a new encryption context for the given note. /// /// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be @@ -331,35 +337,34 @@ pub fn plaintext_version_is_valid( } } -pub fn try_sapling_note_decryption( +pub fn try_sapling_note_decryption>( params: &P, height: BlockHeight, ivk: &SaplingIvk, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { params: params.clone(), height, }; - try_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) + try_note_decryption(&domain, ivk, output) } -pub fn try_sapling_compact_note_decryption( +pub fn try_sapling_compact_note_decryption< + P: consensus::Parameters, + Output: SaplingShieldedOutput

, +>( params: &P, height: BlockHeight, ivk: &SaplingIvk, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress)> { let domain = SaplingDomain { params: params.clone(), height, }; - try_compact_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) + try_compact_note_decryption(&domain, ivk, output) } /// Recovery of the full note plaintext by the sender. @@ -370,13 +375,14 @@ pub fn try_sapling_compact_note_decryption( /// /// Implements part of section 4.17.3 of the Zcash Protocol Specification. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. -pub fn try_sapling_output_recovery_with_ock( +pub fn try_sapling_output_recovery_with_ock< + P: consensus::Parameters, + Output: SaplingShieldedOutput

, +>( params: &P, height: BlockHeight, ock: &OutgoingCipherKey, - cmu: &bls12_381::Scalar, - epk: &jubjub::ExtendedPoint, - enc_ciphertext: &[u8], + output: &Output, out_ciphertext: &[u8], ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { @@ -384,14 +390,7 @@ pub fn try_sapling_output_recovery_with_ock( height, }; - try_output_recovery_with_ock( - &domain, - ock, - &cmu.to_bytes(), - epk, - enc_ciphertext, - out_ciphertext, - ) + try_output_recovery_with_ock(&domain, ock, output, out_ciphertext) } /// Recovery of the full note plaintext by the sender. @@ -402,23 +401,19 @@ pub fn try_sapling_output_recovery_with_ock( /// /// Implements section 4.17.3 of the Zcash Protocol Specification. #[allow(clippy::too_many_arguments)] -pub fn try_sapling_output_recovery( +pub fn try_sapling_output_recovery>( params: &P, height: BlockHeight, ovk: &OutgoingViewingKey, cv: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - epk: &jubjub::ExtendedPoint, - enc_ciphertext: &[u8], + output: &Output, out_ciphertext: &[u8], ) -> Option<(Note, PaymentAddress, MemoBytes)> { - try_sapling_output_recovery_with_ock::

( + try_sapling_output_recovery_with_ock( params, height, - &prf_ock(&ovk, &cv, &cmu, &epk), - cmu, - epk, - enc_ciphertext, + &prf_ock(&ovk, &cv, output.cmu(), output.epk()), + output, out_ciphertext, ) } @@ -434,8 +429,8 @@ mod tests { use std::convert::TryInto; use zcash_note_encryption::{ - NoteEncryption, OutgoingCipherKey, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, - NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + NoteEncryption, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, + OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; use super::{ @@ -456,7 +451,11 @@ mod tests { keys::OutgoingViewingKey, Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment, }, - transaction::components::amount::Amount, + transaction::components::{ + amount::Amount, + sapling::{CompactOutputDescription, OutputDescription}, + GROTH_PROOF_SIZE, + }, }; fn random_enc_ciphertext( @@ -466,33 +465,18 @@ mod tests { OutgoingViewingKey, OutgoingCipherKey, SaplingIvk, - jubjub::ExtendedPoint, - bls12_381::Scalar, - jubjub::ExtendedPoint, - [u8; ENC_CIPHERTEXT_SIZE], - [u8; OUT_CIPHERTEXT_SIZE], + OutputDescription, ) { let ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); - let (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext_with(height, &ivk, rng); + let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, rng); - assert!(try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ) - .is_some()); + assert!(try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output).is_some()); assert!(try_sapling_compact_note_decryption( &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output.clone()), ) .is_some()); @@ -500,42 +484,30 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext, + &output.cv, + &output, + &output.out_ciphertext, ); let ock_output_recovery = try_sapling_output_recovery_with_ock( &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext, + &output, + &output.out_ciphertext, ); assert!(ovk_output_recovery.is_some()); assert!(ock_output_recovery.is_some()); assert_eq!(ovk_output_recovery, ock_output_recovery); - (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext) + (ovk, ock, ivk, output) } fn random_enc_ciphertext_with( height: BlockHeight, ivk: &SaplingIvk, mut rng: &mut R, - ) -> ( - OutgoingViewingKey, - OutgoingCipherKey, - jubjub::ExtendedPoint, - bls12_381::Scalar, - jubjub::ExtendedPoint, - [u8; ENC_CIPHERTEXT_SIZE], - [u8; OUT_CIPHERTEXT_SIZE], - ) { + ) -> (OutgoingViewingKey, OutgoingCipherKey, OutputDescription) { let diversifier = Diversifier([0; 11]); let pk_d = diversifier.g_d().unwrap() * ivk.0; let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d); @@ -561,12 +533,19 @@ mod tests { MemoBytes::empty(), &mut rng, ); - let epk = *ne.epk(); - let enc_ciphertext = ne.encrypt_note_plaintext(); - let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng); - let ock = prf_ock(&ovk, &cv, &cmu, &epk); + let ephemeral_key = *ne.epk(); + let ock = prf_ock(&ovk, &cv, &cmu, &ephemeral_key); - (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) + let output = OutputDescription { + cv, + cmu, + ephemeral_key, + enc_ciphertext: ne.encrypt_note_plaintext(), + out_ciphertext: ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng), + zkproof: [0u8; GROTH_PROOF_SIZE], + }; + + (ovk, ock, output) } fn reencrypt_enc_ciphertext( @@ -661,16 +640,14 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_note_decryption( &TEST_NETWORK, height, &SaplingIvk(jubjub::Fr::random(&mut rng)), - &epk, - &cmu, - &enc_ciphertext + &output ), None ); @@ -686,17 +663,12 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &jubjub::ExtendedPoint::random(&mut rng), - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output,), None ); } @@ -711,17 +683,11 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &bls12_381::Scalar::random(&mut rng), - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -736,19 +702,11 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, cmu, epk, mut enc_ciphertext, _) = - random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; - enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -766,27 +724,19 @@ mod tests { let leadbytes = [0x02, 0x03, 0x01]; for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[0] = leadbyte, ); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -801,27 +751,19 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -836,28 +778,20 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -872,16 +806,14 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_compact_note_decryption( &TEST_NETWORK, height, &SaplingIvk(jubjub::Fr::random(&mut rng)), - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -897,16 +829,15 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( try_sapling_compact_note_decryption( &TEST_NETWORK, height, &ivk, - &jubjub::ExtendedPoint::random(&mut rng), - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -922,16 +853,15 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( try_sapling_compact_note_decryption( &TEST_NETWORK, height, &ivk, - &epk, - &bls12_381::Scalar::random(&mut rng), - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -950,16 +880,15 @@ mod tests { let leadbytes = [0x02, 0x03, 0x01]; for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[0] = leadbyte, ); assert_eq!( @@ -967,9 +896,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -985,16 +912,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( @@ -1002,9 +928,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -1020,16 +944,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( @@ -1037,9 +960,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -1055,8 +976,7 @@ mod tests { ]; for &height in heights.iter() { - let (mut ovk, _, _, cv, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (mut ovk, _, _, output) = random_enc_ciphertext(height, &mut rng); ovk.0[0] ^= 0xff; assert_eq!( @@ -1064,11 +984,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1084,18 +1002,15 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, _, _, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_output_recovery_with_ock( &TEST_NETWORK, height, &OutgoingCipherKey([0u8; 32]), - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1111,8 +1026,7 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, _, _, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_output_recovery( @@ -1120,10 +1034,8 @@ mod tests { height, &ovk, &jubjub::ExtendedPoint::random(&mut rng), - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1139,19 +1051,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, _, epk, enc_ctext, out_ctext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &bls12_381::Scalar::random(&mut rng), - &epk, - &enc_ctext, - &out_ctext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1161,10 +1071,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &bls12_381::Scalar::random(&mut rng), - &epk, - &enc_ctext, - &out_ctext + &output, + &output.out_ciphertext ), None ); @@ -1180,19 +1088,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, _, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &jubjub::ExtendedPoint::random(&mut rng), - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1202,10 +1108,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &jubjub::ExtendedPoint::random(&mut rng), - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1221,20 +1125,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); - enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; + output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1243,10 +1144,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1262,20 +1161,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, enc_ciphertext, mut out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); - out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; + output.out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1284,10 +1180,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1306,16 +1200,15 @@ mod tests { let leadbytes = [0x02, 0x03, 0x01]; for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[0] = leadbyte, ); assert_eq!( @@ -1323,11 +1216,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1336,10 +1227,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1355,16 +1244,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( @@ -1372,11 +1260,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1385,10 +1271,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1404,16 +1288,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( @@ -1421,11 +1304,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1434,10 +1315,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1454,19 +1333,16 @@ mod tests { for &height in heights.iter() { let ivk = SaplingIvk(jubjub::Fr::zero()); - let (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext_with(height, &ivk, &mut rng); + let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, &mut rng); assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1475,10 +1351,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1540,12 +1414,21 @@ mod tests { let note = to.create_note(tv.v, Rseed::BeforeZip212(rcm)).unwrap(); assert_eq!(note.cmu(), cmu); + let output = OutputDescription { + cv, + cmu, + ephemeral_key: epk, + enc_ciphertext: tv.c_enc, + out_ciphertext: tv.c_out, + zkproof: [0u8; GROTH_PROOF_SIZE], + }; + // // Test decryption // (Tested first because it only requires immutable references.) // - match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &epk, &cmu, &tv.c_enc) { + match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); assert_eq!(decrypted_to, to); @@ -1558,9 +1441,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &tv.c_enc[..COMPACT_NOTE_SIZE], + &CompactOutputDescription::from(output.clone()), ) { Some((decrypted_note, decrypted_to)) => { assert_eq!(decrypted_note, note); @@ -1573,11 +1454,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &tv.c_enc, - &tv.c_out, + &output.cv, + &output, + &output.out_ciphertext, ) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index e5184948a..ed48c18ff 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -3,11 +3,19 @@ use group::GroupEncoding; use std::io::{self, Read, Write}; -use crate::sapling::{ - redjubjub::{PublicKey, Signature}, - Nullifier, +use zcash_note_encryption::ShieldedOutput; + +use crate::{ + consensus, + sapling::{ + note_encryption::{SaplingDomain, SaplingShieldedOutput}, + redjubjub::{PublicKey, Signature}, + Nullifier, + }, }; +use zcash_note_encryption::COMPACT_NOTE_SIZE; + use super::GROTH_PROOF_SIZE; #[derive(Clone)] @@ -110,6 +118,26 @@ pub struct OutputDescription { pub zkproof: [u8; GROTH_PROOF_SIZE], } +impl ShieldedOutput> for OutputDescription { + fn epk(&self) -> &jubjub::ExtendedPoint { + &self.ephemeral_key + } + + fn cmstar(&self) -> [u8; 32] { + self.cmu.to_repr() + } + + fn enc_ciphertext(&self) -> &[u8] { + &self.enc_ciphertext + } +} + +impl SaplingShieldedOutput

for OutputDescription { + fn cmu(&self) -> &bls12_381::Scalar { + &self.cmu + } +} + impl std::fmt::Debug for OutputDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( @@ -191,3 +219,39 @@ impl OutputDescription { writer.write_all(&self.zkproof) } } + +pub struct CompactOutputDescription { + pub epk: jubjub::ExtendedPoint, + pub cmu: bls12_381::Scalar, + pub enc_ciphertext: Vec, +} + +impl From for CompactOutputDescription { + fn from(out: OutputDescription) -> CompactOutputDescription { + CompactOutputDescription { + epk: out.ephemeral_key, + cmu: out.cmu, + enc_ciphertext: out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec(), + } + } +} + +impl ShieldedOutput> for CompactOutputDescription { + fn epk(&self) -> &jubjub::ExtendedPoint { + &self.epk + } + + fn cmstar(&self) -> [u8; 32] { + self.cmu.to_repr() + } + + fn enc_ciphertext(&self) -> &[u8] { + &self.enc_ciphertext + } +} + +impl SaplingShieldedOutput

for CompactOutputDescription { + fn cmu(&self) -> &bls12_381::Scalar { + &self.cmu + } +} From 6fc1d1d1c0be68ec69f10392c1d1a0004bf17951 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 16:19:50 -0600 Subject: [PATCH 15/24] Use ephemeral_key bytes instead of the epk abstract point where specified. --- components/zcash_note_encryption/src/lib.rs | 26 +++++++---- .../src/sapling/note_encryption.rs | 43 +++++++++++-------- zcash_primitives/src/transaction/builder.rs | 1 - 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 36ccc765e..5063a6cb6 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -35,6 +35,12 @@ impl AsRef<[u8]> for OutgoingCipherKey { pub struct EphemeralKeyBytes(pub [u8; 32]); +impl AsRef<[u8]> for EphemeralKeyBytes { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + impl From<[u8; 32]> for EphemeralKeyBytes { fn from(value: [u8; 32]) -> EphemeralKeyBytes { EphemeralKeyBytes(value) @@ -90,7 +96,7 @@ pub trait Domain { epk: &Self::EphemeralPublicKey, ) -> Self::SharedSecret; - fn kdf(secret: Self::SharedSecret, epk: &Self::EphemeralPublicKey) -> Self::SymmetricKey; + fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey; // for right now, we just need `recipient` to get `d`; in the future when we // can get that from a Sapling note, the recipient parameter will be able @@ -101,11 +107,11 @@ pub trait Domain { memo: &Self::Memo, ) -> NotePlaintextBytes; - fn get_ock( + fn derive_ock( ovk: &Self::OutgoingViewingKey, cv: &Self::ValueCommitment, cm: &Self::NoteCommitment, - epk: &Self::EphemeralPublicKey, + ephemeral_key: &EphemeralKeyBytes, ) -> OutgoingCipherKey; fn outgoing_plaintext_bytes( @@ -227,7 +233,9 @@ impl NoteEncryption { Self::new_with_esk(esk, ovk, note, to, memo) } - /// For use only with Sapling. + /// For use only with Sapling. This method is preserved in order that test code + /// be able to generate pre-ZIP-212 ciphertexts so that tests can continue to + /// cover pre-ZIP-212 transaction decryption. pub fn new_with_esk( esk: D::EphemeralSecretKey, ovk: Option, @@ -259,7 +267,7 @@ impl NoteEncryption { pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { let pk_d = D::get_pk_d(&self.note); let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); - let key = D::kdf(shared_secret, &self.epk); + let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk)); let input = D::note_plaintext_bytes(&self.note, &self.to, &self.memo); let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; @@ -281,7 +289,7 @@ impl NoteEncryption { rng: &mut R, ) -> [u8; OUT_CIPHERTEXT_SIZE] { let (ock, input) = if let Some(ovk) = &self.ovk { - let ock = D::get_ock(ovk, &cv, &cm, &self.epk); + let ock = D::derive_ock(ovk, &cv, &cm, &D::epk_bytes(&self.epk)); let input = D::outgoing_plaintext_bytes(&self.note, &self.esk); (ock, input) @@ -323,7 +331,7 @@ pub fn try_note_decryption>( assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE); let shared_secret = D::ka_agree_dec(ivk, output.epk()); - let key = D::kdf(shared_secret, output.epk()); + let key = D::kdf(shared_secret, &D::epk_bytes(output.epk())); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( @@ -407,7 +415,7 @@ pub fn try_compact_note_decryption>( assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE); let shared_secret = D::ka_agree_dec(&ivk, output.epk()); - let key = D::kdf(shared_secret, output.epk()); + let key = D::kdf(shared_secret, &D::epk_bytes(output.epk())); // Start from block 1 to skip over Poly1305 keying output let mut plaintext = [0; COMPACT_NOTE_SIZE]; @@ -446,7 +454,7 @@ pub fn try_output_recovery_with_ock>( let esk = D::extract_esk(&op)?; let shared_secret = D::ka_agree_enc(&esk, &pk_d); - let key = D::kdf(shared_secret, output.epk()); + let key = D::kdf(shared_secret, &D::epk_bytes(output.epk())); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index fe14133ec..6a067b85d 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -38,13 +38,13 @@ pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubju /// Sapling KDF for note encryption. /// /// Implements section 5.4.4.4 of the Zcash Protocol Specification. -fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash { +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(&epk.to_bytes()) + .update(ephemeral_key.as_ref()) .finalize() } @@ -55,7 +55,7 @@ pub fn prf_ock( ovk: &OutgoingViewingKey, cv: &jubjub::ExtendedPoint, cmu: &bls12_381::Scalar, - epk: &jubjub::ExtendedPoint, + ephemeral_key: &EphemeralKeyBytes, ) -> OutgoingCipherKey { OutgoingCipherKey( Blake2bParams::new() @@ -65,7 +65,7 @@ pub fn prf_ock( .update(&ovk.0) .update(&cv.to_bytes()) .update(&cmu.to_repr()) - .update(&epk.to_bytes()) + .update(ephemeral_key.as_ref()) .finalize() .as_bytes() .try_into() @@ -73,6 +73,10 @@ pub fn prf_ock( ) } +fn epk_bytes(epk: &jubjub::ExtendedPoint) -> EphemeralKeyBytes { + EphemeralKeyBytes(epk.to_bytes()) +} + fn sapling_parse_note_plaintext_without_memo( domain: &SaplingDomain

, plaintext: &[u8], @@ -164,7 +168,7 @@ impl Domain for SaplingDomain

{ /// Sapling KDF for note encryption. /// /// Implements section 5.4.4.4 of the Zcash Protocol Specification. - fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash { + fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &EphemeralKeyBytes) -> Blake2bHash { kdf_sapling(dhsecret, epk) } @@ -199,11 +203,11 @@ impl Domain for SaplingDomain

{ NotePlaintextBytes(input) } - fn get_ock( + fn derive_ock( ovk: &Self::OutgoingViewingKey, cv: &Self::ValueCommitment, cmu: &Self::NoteCommitment, - epk: &Self::EphemeralPublicKey, + epk: &EphemeralKeyBytes, ) -> OutgoingCipherKey { prf_ock(ovk, cv, cmu, epk) } @@ -220,7 +224,7 @@ impl Domain for SaplingDomain

{ } fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes { - EphemeralKeyBytes(epk.to_bytes()) + epk_bytes(epk) } fn check_epk_bytes NoteValidity>( @@ -412,7 +416,12 @@ pub fn try_sapling_output_recovery::epk_bytes(output.epk()), + ), output, out_ciphertext, ) @@ -434,7 +443,7 @@ mod tests { }; use super::{ - kdf_sapling, prf_ock, sapling_ka_agree, sapling_note_encryption, + epk_bytes, kdf_sapling, 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, }; @@ -533,13 +542,13 @@ mod tests { MemoBytes::empty(), &mut rng, ); - let ephemeral_key = *ne.epk(); - let ock = prf_ock(&ovk, &cv, &cmu, &ephemeral_key); + let epk = *ne.epk(); + let ock = prf_ock(&ovk, &cv, &cmu, &epk_bytes(&epk)); let output = OutputDescription { cv, cmu, - ephemeral_key, + ephemeral_key: epk, enc_ciphertext: ne.encrypt_note_plaintext(), out_ciphertext: ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng), zkproof: [0u8; GROTH_PROOF_SIZE], @@ -557,7 +566,7 @@ mod tests { out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]), ) { - let ock = prf_ock(&ovk, &cv, &cmu, &epk); + let ock = prf_ock(&ovk, &cv, &cmu, &epk_bytes(epk)); let mut op = [0; OUT_CIPHERTEXT_SIZE]; assert_eq!( @@ -572,7 +581,7 @@ 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, &epk); + let key = kdf_sapling(shared_secret, &epk_bytes(&epk)); let mut plaintext = { let mut buf = [0; ENC_CIPHERTEXT_SIZE]; @@ -1403,11 +1412,11 @@ mod tests { let shared_secret = sapling_ka_agree(&esk, &pk_d.into()); assert_eq!(shared_secret.to_bytes(), tv.shared_secret); - let k_enc = kdf_sapling(shared_secret, &epk); + let k_enc = kdf_sapling(shared_secret, &epk_bytes(&epk)); assert_eq!(k_enc.as_bytes(), tv.k_enc); let ovk = OutgoingViewingKey(tv.ovk); - let ock = prf_ock(&ovk, &cv, &cmu, &epk); + let ock = prf_ock(&ovk, &cv, &cmu, &epk_bytes(&epk)); assert_eq!(ock.as_ref(), tv.ock); let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap(); diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 8cdc954d0..db58da4ed 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -182,7 +182,6 @@ impl SaplingOutput

{ let enc_ciphertext = encryptor.encrypt_note_plaintext(); let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng); - let ephemeral_key = *encryptor.epk(); OutputDescription { From f34e87884a73b676ebad2d4697e26dcf21c272d5 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 16:42:04 -0600 Subject: [PATCH 16/24] Update documentation for note encryption traits. --- components/zcash_note_encryption/src/lib.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 5063a6cb6..b4df91ed6 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -166,8 +166,8 @@ pub trait ShieldedOutput { /// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are /// consistent with each other. /// -/// Implements section 4.19 of the Zcash Protocol Specification. -/// +/// Implements section 4.19 of the +/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband) /// NB: the example code is only covering the pre-Canopy case. /// /// # Examples @@ -180,11 +180,12 @@ pub trait ShieldedOutput { /// use ff::Field; /// use rand_core::OsRng; /// use zcash_primitives::{ -/// consensus::TestNetwork, +/// consensus::{TEST_NETWORK, TestNetwork, NetworkUpgrade, Parameters}, /// memo::MemoBytes, /// sapling::{ /// keys::{OutgoingViewingKey, prf_expand}, -/// note_encryption::{sapling_note_encryption}, +/// note_encryption::sapling_note_encryption, +/// util::generate_random_rseed, /// Diversifier, PaymentAddress, Rseed, ValueCommitment /// }, /// }; @@ -202,8 +203,9 @@ pub trait ShieldedOutput { /// value, /// randomness: rcv.clone(), /// }; -/// let rcm = jubjub::Fr::random(&mut rng); -/// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); +/// let height = TEST_NETWORK.activation_height(NetworkUpgrade::Canopy).unwrap(); +/// let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng); +/// let note = to.create_note(value, rseed).unwrap(); /// let cmu = note.cmu(); /// /// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng); @@ -322,7 +324,8 @@ impl NoteEncryption { /// If successful, the corresponding Sapling note and memo are returned, along with the /// `PaymentAddress` to which the note was sent. /// -/// Implements section 4.17.2 of the Zcash Protocol Specification. +/// Implements section 4.19.2 of the +/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk) pub fn try_note_decryption>( domain: &D, ivk: &D::IncomingViewingKey, @@ -431,7 +434,8 @@ pub fn try_compact_note_decryption>( /// If successful, the corresponding Sapling note and memo are returned, along with the /// `PaymentAddress` to which the note was sent. /// -/// Implements part of section 4.17.3 of the Zcash Protocol Specification. +/// Implements part of section 4.19.3 of the +/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk) /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. pub fn try_output_recovery_with_ock>( domain: &D, From fae1a1517a5194f4d13feec9dc23f80645451e05 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 18:40:17 -0600 Subject: [PATCH 17/24] Simplify try_sapling_output_recovery. --- zcash_client_backend/src/decrypt.rs | 2 - zcash_client_backend/src/welding_rig.rs | 9 ++- zcash_client_sqlite/src/wallet/transact.rs | 2 - .../src/sapling/note_encryption.rs | 76 +++++-------------- .../src/transaction/components/sapling.rs | 14 +--- 5 files changed, 24 insertions(+), 79 deletions(-) diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 6a82f1c1f..9887586b3 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -56,9 +56,7 @@ pub fn decrypt_transaction( params, height, &ovk, - &output.cv, output, - &output.out_ciphertext, ) { Some(ret) => (ret, true), None => continue, diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index bff4fd659..b48a4eec9 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -4,11 +4,12 @@ use ff::PrimeField; use std::collections::HashSet; use std::convert::TryFrom; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; +use zcash_note_encryption::ShieldedOutput; use zcash_primitives::{ consensus::{self, BlockHeight}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{ - note_encryption::{try_sapling_compact_note_decryption, SaplingShieldedOutput}, + note_encryption::{try_sapling_compact_note_decryption, SaplingDomain}, Node, Note, Nullifier, PaymentAddress, SaplingIvk, }, transaction::{components::sapling::CompactOutputDescription, TxId}, @@ -107,7 +108,7 @@ pub trait ScanningKey { /// Attempts to decrypt a Sapling note and payment address /// from the specified ciphertext using this scanning key. - fn try_decryption>( + fn try_decryption>>( &self, params: &P, height: BlockHeight, @@ -129,7 +130,7 @@ pub trait ScanningKey { impl ScanningKey for ExtendedFullViewingKey { type Nf = Nullifier; - fn try_decryption>( + fn try_decryption>>( &self, params: &P, height: BlockHeight, @@ -150,7 +151,7 @@ impl ScanningKey for ExtendedFullViewingKey { impl ScanningKey for SaplingIvk { type Nf = (); - fn try_decryption>( + fn try_decryption>>( &self, params: &P, height: BlockHeight, diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 768558a90..5fccb430f 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -634,9 +634,7 @@ mod tests { &network, sapling_activation_height(), &extfvk.fvk.ovk, - &output.cv, output, - &output.out_ciphertext, ) }; diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 6a067b85d..d8936b37e 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -17,7 +17,10 @@ use crate::{ consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, memo::MemoBytes, sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, - transaction::components::amount::Amount, + transaction::components::{ + amount::Amount, + sapling::OutputDescription + } }; pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF"; @@ -294,12 +297,6 @@ impl Domain for SaplingDomain

{ } } -pub trait SaplingShieldedOutput: - ShieldedOutput> -{ - fn cmu(&self) -> &bls12_381::Scalar; -} - /// Creates a new encryption context for the given note. /// /// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be @@ -341,7 +338,7 @@ pub fn plaintext_version_is_valid( } } -pub fn try_sapling_note_decryption>( +pub fn try_sapling_note_decryption>>( params: &P, height: BlockHeight, ivk: &SaplingIvk, @@ -356,7 +353,7 @@ pub fn try_sapling_note_decryption, + Output: ShieldedOutput>, >( params: &P, height: BlockHeight, @@ -377,24 +374,22 @@ pub fn try_sapling_compact_note_decryption< /// If successful, the corresponding Sapling note and memo are returned, along with the /// `PaymentAddress` to which the note was sent. /// -/// Implements part of section 4.17.3 of the Zcash Protocol Specification. +/// Implements part of section 4.19.3 of the Zcash Protocol Specification. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. pub fn try_sapling_output_recovery_with_ock< P: consensus::Parameters, - Output: SaplingShieldedOutput

, >( params: &P, height: BlockHeight, ock: &OutgoingCipherKey, - output: &Output, - out_ciphertext: &[u8], + output: &OutputDescription, ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { params: params.clone(), height, }; - try_output_recovery_with_ock(&domain, ock, output, out_ciphertext) + try_output_recovery_with_ock(&domain, ock, output, &output.out_ciphertext) } /// Recovery of the full note plaintext by the sender. @@ -403,27 +398,24 @@ pub fn try_sapling_output_recovery_with_ock< /// If successful, the corresponding Sapling note and memo are returned, along with the /// `PaymentAddress` to which the note was sent. /// -/// Implements section 4.17.3 of the Zcash Protocol Specification. +/// Implements section 4.19.3 of the Zcash Protocol Specification. #[allow(clippy::too_many_arguments)] -pub fn try_sapling_output_recovery>( +pub fn try_sapling_output_recovery( params: &P, height: BlockHeight, ovk: &OutgoingViewingKey, - cv: &jubjub::ExtendedPoint, - output: &Output, - out_ciphertext: &[u8], + output: &OutputDescription, ) -> Option<(Note, PaymentAddress, MemoBytes)> { try_sapling_output_recovery_with_ock( params, height, &prf_ock( &ovk, - &cv, - output.cmu(), - &SaplingDomain::

::epk_bytes(output.epk()), + &output.cv, + &output.cmu, + &epk_bytes(&output.ephemeral_key), ), output, - out_ciphertext, ) } @@ -493,9 +485,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext, ); let ock_output_recovery = try_sapling_output_recovery_with_ock( @@ -503,7 +493,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext, ); assert!(ovk_output_recovery.is_some()); assert!(ock_output_recovery.is_some()); @@ -993,9 +982,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1019,7 +1006,6 @@ mod tests { height, &OutgoingCipherKey([0u8; 32]), &output, - &output.out_ciphertext ), None ); @@ -1035,16 +1021,16 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, _, output) = random_enc_ciphertext(height, &mut rng); + let (ovk, _, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.cv = jubjub::ExtendedPoint::random(&mut rng); + assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &jubjub::ExtendedPoint::random(&mut rng), &output, - &output.out_ciphertext ), None ); @@ -1068,9 +1054,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1081,7 +1065,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1105,9 +1088,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1118,7 +1099,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1142,9 +1122,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1154,7 +1132,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1178,9 +1155,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1190,7 +1165,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1225,9 +1199,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1237,7 +1209,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1269,9 +1240,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1281,7 +1250,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1313,9 +1281,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1325,7 +1291,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1349,9 +1314,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext ), None ); @@ -1361,7 +1324,6 @@ mod tests { height, &ock, &output, - &output.out_ciphertext ), None ); @@ -1463,9 +1425,7 @@ mod tests { &TEST_NETWORK, height, &ovk, - &output.cv, &output, - &output.out_ciphertext, ) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index ed48c18ff..dcdabb8fe 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -8,7 +8,7 @@ use zcash_note_encryption::ShieldedOutput; use crate::{ consensus, sapling::{ - note_encryption::{SaplingDomain, SaplingShieldedOutput}, + note_encryption::{SaplingDomain}, redjubjub::{PublicKey, Signature}, Nullifier, }, @@ -132,12 +132,6 @@ impl ShieldedOutput> for OutputDescri } } -impl SaplingShieldedOutput

for OutputDescription { - fn cmu(&self) -> &bls12_381::Scalar { - &self.cmu - } -} - impl std::fmt::Debug for OutputDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( @@ -249,9 +243,3 @@ impl ShieldedOutput> for CompactOutpu &self.enc_ciphertext } } - -impl SaplingShieldedOutput

for CompactOutputDescription { - fn cmu(&self) -> &bls12_381::Scalar { - &self.cmu - } -} From 389e6ca6a38c4b7418d49bd13639a9511c981885 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 18:43:21 -0600 Subject: [PATCH 18/24] Minor comment on epk canonicity. --- components/zcash_note_encryption/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index b4df91ed6..d4ab8df9f 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -458,6 +458,9 @@ pub fn try_output_recovery_with_ock>( let esk = D::extract_esk(&op)?; let shared_secret = D::ka_agree_enc(&esk, &pk_d); + // The small-order point check at the point of output parsing rejects + // non-canonical encodings, so reencoding here for the KDF should + // be okay. let key = D::kdf(shared_secret, &D::epk_bytes(output.epk())); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; From 12cb8265d8cf7baae174879288583c133f197c57 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 12 Apr 2021 18:47:45 -0600 Subject: [PATCH 19/24] Fix formatting. --- zcash_client_backend/src/decrypt.rs | 7 +- .../src/sapling/note_encryption.rs | 163 +++--------------- .../src/transaction/components/sapling.rs | 2 +- 3 files changed, 30 insertions(+), 142 deletions(-) diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 9887586b3..b304ce7a0 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -52,12 +52,7 @@ pub fn decrypt_transaction( let ((note, to, memo), outgoing) = match try_sapling_note_decryption(params, height, &ivk, output) { Some(ret) => (ret, false), - None => match try_sapling_output_recovery( - params, - height, - &ovk, - output, - ) { + None => match try_sapling_output_recovery(params, height, &ovk, output) { Some(ret) => (ret, true), None => continue, }, diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index d8936b37e..c75605a99 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -17,10 +17,7 @@ use crate::{ consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, memo::MemoBytes, sapling::{keys::OutgoingViewingKey, Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, - transaction::components::{ - amount::Amount, - sapling::OutputDescription - } + transaction::components::{amount::Amount, sapling::OutputDescription}, }; pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF"; @@ -338,7 +335,10 @@ pub fn plaintext_version_is_valid( } } -pub fn try_sapling_note_decryption>>( +pub fn try_sapling_note_decryption< + P: consensus::Parameters, + Output: ShieldedOutput>, +>( params: &P, height: BlockHeight, ivk: &SaplingIvk, @@ -376,9 +376,7 @@ pub fn try_sapling_compact_note_decryption< /// /// Implements part of section 4.19.3 of the Zcash Protocol Specification. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. -pub fn try_sapling_output_recovery_with_ock< - P: consensus::Parameters, ->( +pub fn try_sapling_output_recovery_with_ock( params: &P, height: BlockHeight, ock: &OutgoingCipherKey, @@ -481,19 +479,10 @@ mod tests { ) .is_some()); - let ovk_output_recovery = try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ); + let ovk_output_recovery = try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output); - let ock_output_recovery = try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ); + let ock_output_recovery = + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output); assert!(ovk_output_recovery.is_some()); assert!(ock_output_recovery.is_some()); assert_eq!(ovk_output_recovery, ock_output_recovery); @@ -978,12 +967,7 @@ mod tests { ovk.0[0] ^= 0xff; assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); } @@ -1024,14 +1008,8 @@ mod tests { let (ovk, _, _, mut output) = random_enc_ciphertext(height, &mut rng); output.cv = jubjub::ExtendedPoint::random(&mut rng); - assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); } @@ -1050,22 +1028,12 @@ mod tests { output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1084,22 +1052,12 @@ mod tests { output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1118,21 +1076,11 @@ mod tests { output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1151,21 +1099,11 @@ mod tests { output.out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1195,21 +1133,11 @@ mod tests { |pt| pt[0] = leadbyte, ); assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1236,21 +1164,11 @@ mod tests { |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1277,21 +1195,11 @@ mod tests { |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1310,21 +1218,11 @@ mod tests { let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, &mut rng); assert_eq!( - try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ), + try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), None ); assert_eq!( - try_sapling_output_recovery_with_ock( - &TEST_NETWORK, - height, - &ock, - &output, - ), + try_sapling_output_recovery_with_ock(&TEST_NETWORK, height, &ock, &output,), None ); } @@ -1421,12 +1319,7 @@ mod tests { None => panic!("Compact note decryption failed"), } - match try_sapling_output_recovery( - &TEST_NETWORK, - height, - &ovk, - &output, - ) { + match try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); assert_eq!(decrypted_to, to); diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index dcdabb8fe..6f75ec677 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -8,7 +8,7 @@ use zcash_note_encryption::ShieldedOutput; use crate::{ consensus, sapling::{ - note_encryption::{SaplingDomain}, + note_encryption::SaplingDomain, redjubjub::{PublicKey, Signature}, Nullifier, }, From 00d04de54711be2be2fce237c9a2f42a0e0101a9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 14 Apr 2021 09:24:42 -0600 Subject: [PATCH 20/24] Make cmstar check follow the spec more closely. --- components/zcash_note_encryption/src/lib.rs | 15 +++++++++------ zcash_primitives/src/sapling/note_encryption.rs | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index d4ab8df9f..7302a515d 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -5,6 +5,7 @@ use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; use rand_core::RngCore; +use std::convert::TryFrom; use subtle::{Choice, ConstantTimeEq}; pub const COMPACT_NOTE_SIZE: usize = 1 + // version @@ -74,7 +75,7 @@ pub trait Domain { type OutgoingViewingKey; type ValueCommitment; type NoteCommitment; - type ExtractedCommitment: Eq; + type ExtractedCommitment: Eq + TryFrom; type Memo; fn derive_esk(note: &Self::Note) -> Option; @@ -126,7 +127,7 @@ pub trait Domain { check: F, ) -> NoteValidity; - fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment; + fn note_commitment(note: &Self::Note) -> Self::NoteCommitment; fn parse_note_plaintext_without_memo_ivk( &self, @@ -383,10 +384,9 @@ fn check_note_validity( epk: &D::EphemeralPublicKey, cmstar: &D::ExtractedCommitment, ) -> NoteValidity { - if &D::extract_note_commitment(¬e) != cmstar { - // Published commitment doesn't match calculated commitment - NoteValidity::Invalid - } else { + if D::ExtractedCommitment::try_from(D::note_commitment(¬e)) + .map_or(false, |cs| &cs == cmstar) + { let epk_bytes = D::epk_bytes(epk); D::check_epk_bytes(¬e, |derived_esk| { if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) @@ -398,6 +398,9 @@ fn check_note_validity( NoteValidity::Invalid } }) + } else { + // Published commitment doesn't match calculated commitment + NoteValidity::Invalid } } diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index c75605a99..1be32b689 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -265,8 +265,8 @@ impl Domain for SaplingDomain

{ }) } - fn extract_note_commitment(note: &Self::Note) -> Self::ExtractedCommitment { - note.cmu().to_bytes() + fn note_commitment(note: &Self::Note) -> Self::NoteCommitment { + note.cmu() } fn extract_pk_d(op: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option { From b2b3efd4c2e15a1debf75145cfe9f61937121627 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 15 Apr 2021 15:15:54 -0600 Subject: [PATCH 21/24] Fix naming cmstar -> cmstar_bytes and cm -> cmstar --- components/zcash_note_encryption/src/lib.rs | 30 +++++++++---------- .../src/sapling/note_encryption.rs | 8 ++--- .../src/transaction/components/sapling.rs | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 7302a515d..ddb844711 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -74,8 +74,8 @@ pub trait Domain { type IncomingViewingKey; type OutgoingViewingKey; type ValueCommitment; - type NoteCommitment; - type ExtractedCommitment: Eq + TryFrom; + type ExtractedCommitment; + type ExtractedCommitmentBytes: Eq + TryFrom; type Memo; fn derive_esk(note: &Self::Note) -> Option; @@ -111,7 +111,7 @@ pub trait Domain { fn derive_ock( ovk: &Self::OutgoingViewingKey, cv: &Self::ValueCommitment, - cm: &Self::NoteCommitment, + cmstar: &Self::ExtractedCommitment, ephemeral_key: &EphemeralKeyBytes, ) -> OutgoingCipherKey; @@ -127,7 +127,7 @@ pub trait Domain { check: F, ) -> NoteValidity; - fn note_commitment(note: &Self::Note) -> Self::NoteCommitment; + fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment; fn parse_note_plaintext_without_memo_ivk( &self, @@ -157,7 +157,7 @@ pub trait Domain { pub trait ShieldedOutput { fn epk(&self) -> &D::EphemeralPublicKey; - fn cmstar(&self) -> D::ExtractedCommitment; + fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes; fn enc_ciphertext(&self) -> &[u8]; } @@ -288,11 +288,11 @@ impl NoteEncryption { pub fn encrypt_outgoing_plaintext( &self, cv: &D::ValueCommitment, - cm: &D::NoteCommitment, + cmstar: &D::ExtractedCommitment, rng: &mut R, ) -> [u8; OUT_CIPHERTEXT_SIZE] { let (ock, input) = if let Some(ovk) = &self.ovk { - let ock = D::derive_ock(ovk, &cv, &cm, &D::epk_bytes(&self.epk)); + let ock = D::derive_ock(ovk, &cv, &cmstar, &D::epk_bytes(&self.epk)); let input = D::outgoing_plaintext_bytes(&self.note, &self.esk); (ock, input) @@ -355,7 +355,7 @@ pub fn try_note_decryption>( domain, ivk, output.epk(), - &output.cmstar(), + &output.cmstar_bytes(), &plaintext, )?; let memo = domain.extract_memo(&plaintext); @@ -367,12 +367,12 @@ fn parse_note_plaintext_without_memo_ivk( domain: &D, ivk: &D::IncomingViewingKey, epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, + cmstar_bytes: &D::ExtractedCommitmentBytes, plaintext: &[u8], ) -> Option<(D::Note, D::Recipient)> { let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, &plaintext)?; - if let NoteValidity::Valid = check_note_validity::(¬e, epk, cmstar) { + if let NoteValidity::Valid = check_note_validity::(¬e, epk, cmstar_bytes) { Some((note, to)) } else { None @@ -382,10 +382,10 @@ fn parse_note_plaintext_without_memo_ivk( fn check_note_validity( note: &D::Note, epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, + cmstar_bytes: &D::ExtractedCommitmentBytes, ) -> NoteValidity { - if D::ExtractedCommitment::try_from(D::note_commitment(¬e)) - .map_or(false, |cs| &cs == cmstar) + if D::ExtractedCommitmentBytes::try_from(D::cmstar(¬e)) + .map_or(false, |cs| &cs == cmstar_bytes) { let epk_bytes = D::epk_bytes(epk); D::check_epk_bytes(¬e, |derived_esk| { @@ -428,7 +428,7 @@ pub fn try_compact_note_decryption>( plaintext.copy_from_slice(output.enc_ciphertext()); ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); - parse_note_plaintext_without_memo_ivk(domain, ivk, output.epk(), &output.cmstar(), &plaintext) + parse_note_plaintext_without_memo_ivk(domain, ivk, output.epk(), &output.cmstar_bytes(), &plaintext) } /// Recovery of the full note plaintext by the sender. @@ -484,7 +484,7 @@ pub fn try_output_recovery_with_ock>( domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?; let memo = domain.extract_memo(&plaintext); - if let NoteValidity::Valid = check_note_validity::(¬e, output.epk(), &output.cmstar()) { + if let NoteValidity::Valid = check_note_validity::(¬e, output.epk(), &output.cmstar_bytes()) { Some((note, to, memo)) } else { None diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 1be32b689..d92d48c6c 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -127,8 +127,8 @@ impl Domain for SaplingDomain

{ type IncomingViewingKey = SaplingIvk; type OutgoingViewingKey = OutgoingViewingKey; type ValueCommitment = jubjub::ExtendedPoint; - type NoteCommitment = bls12_381::Scalar; - type ExtractedCommitment = [u8; 32]; + type ExtractedCommitment = bls12_381::Scalar; + type ExtractedCommitmentBytes = [u8; 32]; type Memo = MemoBytes; fn derive_esk(note: &Self::Note) -> Option { @@ -206,7 +206,7 @@ impl Domain for SaplingDomain

{ fn derive_ock( ovk: &Self::OutgoingViewingKey, cv: &Self::ValueCommitment, - cmu: &Self::NoteCommitment, + cmu: &Self::ExtractedCommitment, epk: &EphemeralKeyBytes, ) -> OutgoingCipherKey { prf_ock(ovk, cv, cmu, epk) @@ -265,7 +265,7 @@ impl Domain for SaplingDomain

{ }) } - fn note_commitment(note: &Self::Note) -> Self::NoteCommitment { + fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment { note.cmu() } diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 6f75ec677..f008b75b3 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -123,7 +123,7 @@ impl ShieldedOutput> for OutputDescri &self.ephemeral_key } - fn cmstar(&self) -> [u8; 32] { + fn cmstar_bytes(&self) -> [u8; 32] { self.cmu.to_repr() } @@ -235,7 +235,7 @@ impl ShieldedOutput> for CompactOutpu &self.epk } - fn cmstar(&self) -> [u8; 32] { + fn cmstar_bytes(&self) -> [u8; 32] { self.cmu.to_repr() } From 3434cc8b6ac37a65e638a194e260fd083570cb68 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 15 Apr 2021 15:32:45 -0600 Subject: [PATCH 22/24] Add a note about canonicity of ephemeral public keys. --- zcash_primitives/src/sapling/note_encryption.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index d92d48c6c..5ce39ca83 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -118,6 +118,9 @@ pub struct SaplingDomain { impl Domain for SaplingDomain

{ type EphemeralSecretKey = jubjub::Scalar; + // It is acceptable for this to be a point because we enforce by consensus that + // points must not be small-order, and all points with non-canonical serialization + // are small-order. type EphemeralPublicKey = jubjub::ExtendedPoint; type SharedSecret = jubjub::SubgroupPoint; type SymmetricKey = Blake2bHash; From dc0f6e71153b93467fa0a34b3b9b059243d47a72 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 16 Apr 2021 00:10:05 +0100 Subject: [PATCH 23/24] Update comment about which case is covered by example code --- components/zcash_note_encryption/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index ddb844711..27796b49f 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -169,7 +169,7 @@ pub trait ShieldedOutput { /// /// Implements section 4.19 of the /// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband) -/// NB: the example code is only covering the pre-Canopy case. +/// NB: the example code is only covering the post-Canopy case. /// /// # Examples /// From 28a45028ab1e9490e7f1c052ac4ae75e9bfe29e7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 16 Apr 2021 14:03:55 +1200 Subject: [PATCH 24/24] cargo fmt --- components/zcash_note_encryption/src/lib.rs | 12 ++++++++++-- zcash_primitives/src/sapling/note_encryption.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 27796b49f..481ee1e81 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -428,7 +428,13 @@ pub fn try_compact_note_decryption>( plaintext.copy_from_slice(output.enc_ciphertext()); ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext); - parse_note_plaintext_without_memo_ivk(domain, ivk, output.epk(), &output.cmstar_bytes(), &plaintext) + parse_note_plaintext_without_memo_ivk( + domain, + ivk, + output.epk(), + &output.cmstar_bytes(), + &plaintext, + ) } /// Recovery of the full note plaintext by the sender. @@ -484,7 +490,9 @@ pub fn try_output_recovery_with_ock>( domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?; let memo = domain.extract_memo(&plaintext); - if let NoteValidity::Valid = check_note_validity::(¬e, output.epk(), &output.cmstar_bytes()) { + if let NoteValidity::Valid = + check_note_validity::(¬e, output.epk(), &output.cmstar_bytes()) + { Some((note, to, memo)) } else { None diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 5ce39ca83..f50e2416d 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -118,7 +118,7 @@ pub struct SaplingDomain { impl Domain for SaplingDomain

{ type EphemeralSecretKey = jubjub::Scalar; - // It is acceptable for this to be a point because we enforce by consensus that + // It is acceptable for this to be a point because we enforce by consensus that // points must not be small-order, and all points with non-canonical serialization // are small-order. type EphemeralPublicKey = jubjub::ExtendedPoint;