diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 5c4ccbb79..6131cda5d 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -121,6 +121,8 @@ pub trait Domain { fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; + fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option; + fn check_epk_bytes NoteValidity>( note: &Self::Note, check: F, @@ -138,7 +140,7 @@ pub trait Domain { &self, pk_d: &Self::DiversifiedTransmissionKey, esk: &Self::EphemeralSecretKey, - epk: &Self::EphemeralPublicKey, + ephemeral_key: &EphemeralKeyBytes, plaintext: &[u8], ) -> Option<(Self::Note, Self::Recipient)>; @@ -155,7 +157,7 @@ pub trait Domain { } pub trait ShieldedOutput { - fn epk(&self) -> &D::EphemeralPublicKey; + fn ephemeral_key(&self) -> EphemeralKeyBytes; fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes; fn enc_ciphertext(&self) -> &[u8]; } @@ -332,9 +334,11 @@ pub fn try_note_decryption>( output: &Output, ) -> Option<(D::Note, D::Recipient, D::Memo)> { assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE); + let ephemeral_key = output.ephemeral_key(); - let shared_secret = D::ka_agree_dec(ivk, output.epk()); - let key = D::kdf(shared_secret, &D::epk_bytes(output.epk())); + let epk = D::epk(&ephemeral_key)?; + let shared_secret = D::ka_agree_dec(ivk, &epk); + let key = D::kdf(shared_secret, &ephemeral_key); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( @@ -353,7 +357,7 @@ pub fn try_note_decryption>( let (note, to) = parse_note_plaintext_without_memo_ivk( domain, ivk, - output.epk(), + &ephemeral_key, &output.cmstar_bytes(), &plaintext, )?; @@ -365,13 +369,13 @@ pub fn try_note_decryption>( fn parse_note_plaintext_without_memo_ivk( domain: &D, ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, + ephemeral_key: &EphemeralKeyBytes, 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_bytes) { + if let NoteValidity::Valid = check_note_validity::(¬e, ephemeral_key, cmstar_bytes) { Some((note, to)) } else { None @@ -380,14 +384,13 @@ fn parse_note_plaintext_without_memo_ivk( fn check_note_validity( note: &D::Note, - epk: &D::EphemeralPublicKey, + ephemeral_key: &EphemeralKeyBytes, cmstar_bytes: &D::ExtractedCommitmentBytes, ) -> NoteValidity { if &D::ExtractedCommitmentBytes::from(&D::cmstar(¬e)) == cmstar_bytes { - 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) + .ct_eq(&ephemeral_key) .into() { NoteValidity::Valid @@ -416,9 +419,11 @@ pub fn try_compact_note_decryption>( output: &Output, ) -> Option<(D::Note, D::Recipient)> { assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE); + let ephemeral_key = output.ephemeral_key(); - let shared_secret = D::ka_agree_dec(&ivk, output.epk()); - let key = D::kdf(shared_secret, &D::epk_bytes(output.epk())); + let epk = D::epk(&ephemeral_key)?; + let shared_secret = D::ka_agree_dec(&ivk, &epk); + let key = D::kdf(shared_secret, &ephemeral_key); // Start from block 1 to skip over Poly1305 keying output let mut plaintext = [0; COMPACT_NOTE_SIZE]; @@ -428,7 +433,7 @@ pub fn try_compact_note_decryption>( parse_note_plaintext_without_memo_ivk( domain, ivk, - output.epk(), + &ephemeral_key, &output.cmstar_bytes(), &plaintext, ) @@ -450,12 +455,7 @@ pub fn try_output_recovery_with_ovk>( cv: &D::ValueCommitment, out_ciphertext: &[u8], ) -> Option<(D::Note, D::Recipient, D::Memo)> { - let ock = D::derive_ock( - ovk, - &cv, - &output.cmstar_bytes(), - &D::epk_bytes(&output.epk()), - ); + let ock = D::derive_ock(ovk, &cv, &output.cmstar_bytes(), &output.ephemeral_key()); try_output_recovery_with_ock(domain, &ock, output, out_ciphertext) } @@ -488,11 +488,12 @@ pub fn try_output_recovery_with_ock>( let pk_d = D::extract_pk_d(&op)?; let esk = D::extract_esk(&op)?; + let ephemeral_key = output.ephemeral_key(); 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 key = D::kdf(shared_secret, &ephemeral_key); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( @@ -509,7 +510,7 @@ pub fn try_output_recovery_with_ock>( ); let (note, to) = - domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?; + domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &ephemeral_key, &plaintext)?; let memo = domain.extract_memo(&plaintext); // ZIP 212: Check that the esk provided to this function is consistent with the esk we @@ -521,7 +522,7 @@ pub fn try_output_recovery_with_ock>( } if let NoteValidity::Valid = - check_note_validity::(¬e, output.epk(), &output.cmstar_bytes()) + check_note_validity::(¬e, &ephemeral_key, &output.cmstar_bytes()) { Some((note, to, memo)) } else { diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index 5589985a6..e57d5fe8f 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -233,6 +233,13 @@ impl Domain for SaplingDomain

{ epk_bytes(epk) } + fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option { + // ZIP 216: We unconditionally reject non-canonical encodings, because these have + // always been rejected by consensus (due to small-order checks). + // https://zips.z.cash/zip-0216#specification + jubjub::ExtendedPoint::from_bytes(&ephemeral_key.0).into() + } + fn check_epk_bytes NoteValidity>( note: &Note, check: F, @@ -259,11 +266,11 @@ impl Domain for SaplingDomain

{ &self, pk_d: &Self::DiversifiedTransmissionKey, esk: &Self::EphemeralSecretKey, - epk: &Self::EphemeralPublicKey, + ephemeral_key: &EphemeralKeyBytes, 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() { + if (diversifier.g_d()? * esk).to_bytes() == ephemeral_key.0 { Some(*pk_d) } else { None diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 9866d4458..154b9e625 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -3,7 +3,7 @@ use ff::PrimeField; use group::GroupEncoding; use std::io::{self, Read, Write}; -use zcash_note_encryption::{ShieldedOutput, COMPACT_NOTE_SIZE}; +use zcash_note_encryption::{EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE}; use crate::{ consensus, @@ -220,8 +220,8 @@ pub struct OutputDescription { } impl ShieldedOutput> for OutputDescription { - fn epk(&self) -> &jubjub::ExtendedPoint { - &self.ephemeral_key + fn ephemeral_key(&self) -> EphemeralKeyBytes { + EphemeralKeyBytes(self.ephemeral_key.to_bytes()) } fn cmstar_bytes(&self) -> [u8; 32] { @@ -355,8 +355,8 @@ impl From> for CompactOutputDescription { } impl ShieldedOutput> for CompactOutputDescription { - fn epk(&self) -> &jubjub::ExtendedPoint { - &self.epk + fn ephemeral_key(&self) -> EphemeralKeyBytes { + EphemeralKeyBytes(self.epk.to_bytes()) } fn cmstar_bytes(&self) -> [u8; 32] {