From df05017f1ab33fb32c05e7c53980ff3557186d48 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 17 Dec 2021 01:32:50 +0000 Subject: [PATCH 1/5] zcash_note_encryption: Place pre-ZIP 212 APIs behind a feature flag Extracted from: https://github.com/zcash/librustzcash/commit/01c768dbeb6507c15734b92b3332eac9103e597e --- Cargo.toml | 1 + src/lib.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 70381e6..0605714 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ jubjub = "0.8" [features] default = ["std"] alloc = [] +pre-zip-212 = [] std = ["alloc", "blake2b_simd/std"] [lib] diff --git a/src/lib.rs b/src/lib.rs index 9d4f03c..062bf7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -291,12 +291,21 @@ impl NoteEncryption { memo: D::Memo, ) -> Self { let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); - Self::new_with_esk(esk, ovk, note, to, memo) + NoteEncryption { + epk: D::ka_derive_public(¬e, &esk), + esk, + note, + to, + memo, + ovk, + } } /// 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. + #[cfg(feature = "pre-zip-212")] + #[cfg_attr(docsrs, doc(cfg(feature = "pre-zip-212")))] pub fn new_with_esk( esk: D::EphemeralSecretKey, ovk: Option, From 34277d4e38e9b710222b91321ae45f90f3bb5816 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 17 Dec 2021 03:29:21 +0000 Subject: [PATCH 2/5] zcash_note_encryption: Use `*PlaintextBytes` structs in `Domain` APIs `Domain::parse_note_plaintext_without_memo_ivk` is used with both full note plaintexts and compact notes, so continues to accept a slice. For all other `Domain` APIs, we constrain the input to `NotePlaintextBytes` or `OutPlaintextBytes` as appropriate. Extracted from: https://github.com/zcash/librustzcash/commit/7c1687dcc157df3c28b589d2073104c9c11e5289 --- src/lib.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 062bf7f..788c4e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,19 +166,17 @@ pub trait Domain { pk_d: &Self::DiversifiedTransmissionKey, esk: &Self::EphemeralSecretKey, ephemeral_key: &EphemeralKeyBytes, - plaintext: &[u8], + plaintext: &NotePlaintextBytes, ) -> 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_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo; - fn extract_pk_d( - out_plaintext: &[u8; OUT_PLAINTEXT_SIZE], - ) -> Option; + fn extract_pk_d(out_plaintext: &OutPlaintextBytes) -> Option; - fn extract_esk(out_plaintext: &[u8; OUT_PLAINTEXT_SIZE]) -> Option; + fn extract_esk(out_plaintext: &OutPlaintextBytes) -> Option; } #[cfg(feature = "alloc")] @@ -420,14 +418,14 @@ fn try_note_decryption_inner>( let enc_ciphertext = output.enc_ciphertext(); assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); - let mut plaintext: [u8; NOTE_PLAINTEXT_SIZE] = - enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap(); + let mut plaintext = + NotePlaintextBytes(enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap()); ChaCha20Poly1305::new(key.as_ref().into()) .decrypt_in_place_detached( [0u8; 12][..].into(), &[], - &mut plaintext, + &mut plaintext.0, enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), ) .ok()?; @@ -437,7 +435,7 @@ fn try_note_decryption_inner>( ivk, ephemeral_key, &output.cmstar_bytes(), - &plaintext, + &plaintext.0, )?; let memo = domain.extract_memo(&plaintext); @@ -569,14 +567,14 @@ pub fn try_output_recovery_with_ock>( assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); - let mut op = [0; OUT_PLAINTEXT_SIZE]; - op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]); + let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]); + op.0.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]); ChaCha20Poly1305::new(ock.as_ref().into()) .decrypt_in_place_detached( [0u8; 12][..].into(), &[], - &mut op, + &mut op.0, out_ciphertext[OUT_PLAINTEXT_SIZE..].into(), ) .ok()?; @@ -591,14 +589,16 @@ pub fn try_output_recovery_with_ock>( // be okay. let key = D::kdf(shared_secret, &ephemeral_key); - let mut plaintext = [0; NOTE_PLAINTEXT_SIZE]; - plaintext.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); + let mut plaintext = NotePlaintextBytes([0; NOTE_PLAINTEXT_SIZE]); + plaintext + .0 + .copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); ChaCha20Poly1305::new(key.as_ref().into()) .decrypt_in_place_detached( [0u8; 12][..].into(), &[], - &mut plaintext, + &mut plaintext.0, enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), ) .ok()?; From 7ab76d857432c12e967c629f937bbd44e0761f04 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 17 Dec 2021 04:18:51 +0000 Subject: [PATCH 3/5] zcash_note_encryption: Remove `Domain::check_epk_bytes` `Domain::derive_esk` provides sufficient information to determine whether or not we need to enforce `EphemeralSecretKey`-specific decryption checks, as it returns `None` for pre-ZIP 212 notes. Extracted from: https://github.com/zcash/librustzcash/commit/d54e1f0bf7f81e5f68ef34bb8f286fcb6b2d75c1 --- src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 788c4e3..b4b8e4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,7 @@ pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); #[derive(Copy, Clone, PartialEq, Eq)] -pub enum NoteValidity { +enum NoteValidity { Valid, Invalid, } @@ -148,11 +148,6 @@ pub trait Domain { fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option; - fn check_epk_bytes NoteValidity>( - note: &Self::Note, - check: F, - ) -> NoteValidity; - fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment; fn parse_note_plaintext_without_memo_ivk( @@ -464,7 +459,7 @@ fn check_note_validity( cmstar_bytes: &D::ExtractedCommitmentBytes, ) -> NoteValidity { if &D::ExtractedCommitmentBytes::from(&D::cmstar(¬e)) == cmstar_bytes { - D::check_epk_bytes(¬e, |derived_esk| { + if let Some(derived_esk) = D::derive_esk(note) { if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) .ct_eq(&ephemeral_key) .into() @@ -473,7 +468,10 @@ fn check_note_validity( } else { NoteValidity::Invalid } - }) + } else { + // Before ZIP 212 + NoteValidity::Valid + } } else { // Published commitment doesn't match calculated commitment NoteValidity::Invalid From 1ac6d5b8fe48f38139495722348a7454f8860a9c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 17 Dec 2021 05:13:52 +0000 Subject: [PATCH 4/5] zcash_note_encryption: Constrain `ShieldedOutput` ciphertext size Previously we were returning the ciphertext as a slice, and then asserting its length within the APIs the caller passed it into. Now instead we require the caller to define whether or not the output is compact, to make the API more predictable. This doesn't place any additional constraints on users of this trait, because the assertions already prevented a full output from being passed to a compact trial decryption API. Extracted from: https://github.com/zcash/librustzcash/commit/4fcd83d74e87f526763b1c9af755d0fe4860fe15 --- src/batch.rs | 8 ++++---- src/lib.rs | 20 ++++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/batch.rs b/src/batch.rs index 93f2634..d2c3f6d 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -5,14 +5,14 @@ use core::iter; use crate::{ try_compact_note_decryption_inner, try_note_decryption_inner, BatchDomain, EphemeralKeyBytes, - ShieldedOutput, + ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, }; /// Trial decryption of a batch of notes with a set of recipients. /// /// This is the batched version of [`crate::try_note_decryption`]. #[allow(clippy::type_complexity)] -pub fn try_note_decryption>( +pub fn try_note_decryption>( ivks: &[D::IncomingViewingKey], outputs: &[(D, Output)], ) -> Vec> { @@ -22,14 +22,14 @@ pub fn try_note_decryption>( /// Trial decryption of a batch of notes for light clients with a set of recipients. /// /// This is the batched version of [`crate::try_compact_note_decryption`]. -pub fn try_compact_note_decryption>( +pub fn try_compact_note_decryption>( ivks: &[D::IncomingViewingKey], outputs: &[(D, Output)], ) -> Vec> { batch_note_decryption(ivks, outputs, try_compact_note_decryption_inner) } -fn batch_note_decryption, F, FR>( +fn batch_note_decryption, F, FR, const CS: usize>( ivks: &[D::IncomingViewingKey], outputs: &[(D, Output)], decrypt_inner: F, diff --git a/src/lib.rs b/src/lib.rs index b4b8e4b..ca2ec6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,10 +206,10 @@ pub trait BatchDomain: Domain { } } -pub trait ShieldedOutput { +pub trait ShieldedOutput { fn ephemeral_key(&self) -> EphemeralKeyBytes; fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes; - fn enc_ciphertext(&self) -> &[u8]; + fn enc_ciphertext(&self) -> &[u8; CIPHERTEXT_SIZE]; } /// A struct containing context required for encrypting Sapling and Orchard notes. @@ -389,7 +389,7 @@ impl NoteEncryption { /// /// Implements section 4.19.2 of the /// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk). -pub fn try_note_decryption>( +pub fn try_note_decryption>( domain: &D, ivk: &D::IncomingViewingKey, output: &Output, @@ -403,7 +403,7 @@ pub fn try_note_decryption>( try_note_decryption_inner(domain, ivk, &ephemeral_key, output, key) } -fn try_note_decryption_inner>( +fn try_note_decryption_inner>( domain: &D, ivk: &D::IncomingViewingKey, ephemeral_key: &EphemeralKeyBytes, @@ -411,7 +411,6 @@ fn try_note_decryption_inner>( key: D::SymmetricKey, ) -> Option<(D::Note, D::Recipient, D::Memo)> { let enc_ciphertext = output.enc_ciphertext(); - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); let mut plaintext = NotePlaintextBytes(enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap()); @@ -487,7 +486,7 @@ 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, output: &Output, @@ -501,15 +500,13 @@ pub fn try_compact_note_decryption>( try_compact_note_decryption_inner(domain, ivk, &ephemeral_key, output, key) } -fn try_compact_note_decryption_inner>( +fn try_compact_note_decryption_inner>( domain: &D, ivk: &D::IncomingViewingKey, ephemeral_key: &EphemeralKeyBytes, output: &Output, key: D::SymmetricKey, ) -> Option<(D::Note, D::Recipient)> { - assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE); - // Start from block 1 to skip over Poly1305 keying output let mut plaintext = [0; COMPACT_NOTE_SIZE]; plaintext.copy_from_slice(output.enc_ciphertext()); @@ -535,7 +532,7 @@ fn try_compact_note_decryption_inner>( /// Implements [Zcash Protocol Specification section 4.19.3][decryptovk]. /// /// [decryptovk]: https://zips.z.cash/protocol/nu5.pdf#decryptovk -pub fn try_output_recovery_with_ovk>( +pub fn try_output_recovery_with_ovk>( domain: &D, ovk: &D::OutgoingViewingKey, output: &Output, @@ -555,14 +552,13 @@ pub fn try_output_recovery_with_ovk>( /// 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_output_recovery_with_ovk`]. -pub fn try_output_recovery_with_ock>( +pub fn try_output_recovery_with_ock>( domain: &D, ock: &OutgoingCipherKey, output: &Output, out_ciphertext: &[u8], ) -> Option<(D::Note, D::Recipient, D::Memo)> { let enc_ciphertext = output.enc_ciphertext(); - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]); From 9994ddc70d1446eeb99ae5d41b195ba181e4f026 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 17 Dec 2021 05:22:24 +0000 Subject: [PATCH 5/5] zcash_note_encryption: Constrain outgoing ciphertext size This replaces a length assertion, making the API more predictable. Extracted from: https://github.com/zcash/librustzcash/commit/76f364593a6d5b190dd23d26f8202adcd031b2d3 --- src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ca2ec6f..646d0ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -537,7 +537,7 @@ pub fn try_output_recovery_with_ovk Option<(D::Note, D::Recipient, D::Memo)> { let ock = D::derive_ock(ovk, &cv, &output.cmstar_bytes(), &output.ephemeral_key()); try_output_recovery_with_ock(domain, &ock, output, out_ciphertext) @@ -556,10 +556,9 @@ pub fn try_output_recovery_with_ock Option<(D::Note, D::Recipient, D::Memo)> { let enc_ciphertext = output.enc_ciphertext(); - assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]); op.0.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);