diff --git a/zcash_client_backend/src/proto.rs b/zcash_client_backend/src/proto.rs index e284c9981..4441333ff 100644 --- a/zcash_client_backend/src/proto.rs +++ b/zcash_client_backend/src/proto.rs @@ -1,7 +1,6 @@ //! Generated code for handling light client protobuf structs. use ff::PrimeField; -use group::GroupEncoding; use std::convert::{TryFrom, TryInto}; use zcash_primitives::{ @@ -103,13 +102,8 @@ impl compact_formats::CompactOutput { /// A convenience method that parses [`CompactOutput.epk`]. /// /// [`CompactOutput.epk`]: #structfield.epk - pub fn epk(&self) -> Result { - let p = jubjub::ExtendedPoint::from_bytes(&self.epk[..].try_into().map_err(|_| ())?); - if p.is_some().into() { - Ok(p.unwrap()) - } else { - Err(()) - } + pub fn ephemeral_key(&self) -> Result<[u8; 32], ()> { + self.epk[..].try_into().map_err(|_| ()) } } @@ -117,7 +111,7 @@ impl From> for compact_formats:: 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_epk(out.ephemeral_key.to_vec()); result.set_ciphertext(out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec()); result } @@ -129,7 +123,7 @@ impl TryFrom for CompactOutputDescription { fn try_from(value: compact_formats::CompactOutput) -> Result { Ok(CompactOutputDescription { cmu: value.cmu()?, - epk: value.epk()?, + ephemeral_key: value.ephemeral_key()?, enc_ciphertext: value.ciphertext, }) } diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 220aaf6e3..21513c2a4 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -54,7 +54,7 @@ pub struct WalletShieldedSpend { pub struct WalletShieldedOutput { pub index: usize, pub cmu: bls12_381::Scalar, - pub epk: jubjub::ExtendedPoint, + pub ephemeral_key: [u8; 32], pub account: AccountId, pub note: Note, pub to: PaymentAddress, diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 55d354bd6..572e31ba6 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -76,7 +76,7 @@ fn scan_output( return Some(WalletShieldedOutput { index, cmu: output.cmu, - epk: output.epk, + ephemeral_key: output.ephemeral_key, account: **account, note, to, diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index 07f1207c5..c7746fb69 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -1,5 +1,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use ff::Field; +use group::GroupEncoding; use rand_core::OsRng; use zcash_primitives::{ consensus::{NetworkUpgrade::Canopy, Parameters, TestNetwork, TEST_NETWORK}, @@ -49,7 +50,7 @@ fn bench_note_decryption(c: &mut Criterion) { let ne = sapling_note_encryption::<_, TestNetwork>(None, note, pa, MemoBytes::empty(), &mut rng); - let ephemeral_key = *ne.epk(); + let ephemeral_key = ne.epk().to_bytes(); let enc_ciphertext = ne.encrypt_note_plaintext(); let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng); diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index e57d5fe8f..32dc690dd 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -436,8 +436,8 @@ mod tests { use std::convert::TryInto; use zcash_note_encryption::{ - NoteEncryption, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, - OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + EphemeralKeyBytes, NoteEncryption, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, + NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; use super::{ @@ -538,7 +538,7 @@ mod tests { let output = OutputDescription { cv, cmu, - ephemeral_key: epk, + ephemeral_key: epk.to_bytes(), enc_ciphertext: ne.encrypt_note_plaintext(), out_ciphertext: ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng), zkproof: [0u8; GROTH_PROOF_SIZE], @@ -551,12 +551,17 @@ mod tests { ovk: &OutgoingViewingKey, cv: &jubjub::ExtendedPoint, cmu: &bls12_381::Scalar, - epk: &jubjub::ExtendedPoint, + ephemeral_key: &[u8; 32], enc_ciphertext: &mut [u8; ENC_CIPHERTEXT_SIZE], out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]), ) { - let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(epk)); + let ock = prf_ock( + &ovk, + &cv, + &cmu.to_repr(), + &EphemeralKeyBytes(*ephemeral_key), + ); let mut op = [0; OUT_CIPHERTEXT_SIZE]; assert_eq!( @@ -571,7 +576,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_bytes(&epk)); + let key = kdf_sapling(shared_secret, &EphemeralKeyBytes(*ephemeral_key)); let mut plaintext = { let mut buf = [0; ENC_CIPHERTEXT_SIZE]; @@ -664,7 +669,7 @@ mod tests { for &height in heights.iter() { let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); - output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng).to_bytes(); assert_eq!( try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output,), @@ -829,7 +834,7 @@ mod tests { for &height in heights.iter() { let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); - output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng).to_bytes(); assert_eq!( try_sapling_compact_note_decryption( @@ -1061,7 +1066,7 @@ mod tests { for &height in heights.iter() { let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); - output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng).to_bytes(); assert_eq!( try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,), @@ -1275,7 +1280,7 @@ mod tests { let cv = read_point!(tv.cv); let cmu = read_bls12_381_scalar!(tv.cmu); let esk = read_jubjub_scalar!(tv.esk); - let epk = read_point!(tv.epk); + let ephemeral_key = EphemeralKeyBytes(tv.epk); // // Test the individual components @@ -1284,11 +1289,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_bytes(&epk)); + let k_enc = kdf_sapling(shared_secret, &ephemeral_key); assert_eq!(k_enc.as_bytes(), tv.k_enc); let ovk = OutgoingViewingKey(tv.ovk); - let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(&epk)); + let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &ephemeral_key); assert_eq!(ock.as_ref(), tv.ock); let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap(); @@ -1298,7 +1303,7 @@ mod tests { let output = OutputDescription { cv, cmu, - ephemeral_key: epk, + ephemeral_key: ephemeral_key.0, enc_ciphertext: tv.c_enc, out_ciphertext: tv.c_out, zkproof: [0u8; GROTH_PROOF_SIZE], diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 154b9e625..964bb67f5 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -213,7 +213,7 @@ impl SpendDescriptionV5 { pub struct OutputDescription { pub cv: jubjub::ExtendedPoint, pub cmu: bls12_381::Scalar, - pub ephemeral_key: jubjub::ExtendedPoint, + pub ephemeral_key: [u8; 32], pub enc_ciphertext: [u8; 580], pub out_ciphertext: [u8; 80], pub zkproof: Proof, @@ -221,7 +221,7 @@ pub struct OutputDescription { impl ShieldedOutput> for OutputDescription { fn ephemeral_key(&self) -> EphemeralKeyBytes { - EphemeralKeyBytes(self.ephemeral_key.to_bytes()) + EphemeralKeyBytes(self.ephemeral_key) } fn cmstar_bytes(&self) -> [u8; 32] { @@ -255,9 +255,10 @@ impl OutputDescription { let cmu = read_base(&mut reader, "cmu")?; // Consensus rules (§4.5): - // - Canonical encoding is enforced here. + // - Canonical encoding is enforced in librustzcash_sapling_check_output by zcashd // - "Not small order" is enforced in SaplingVerificationContext::check_output() - let ephemeral_key = read_point(&mut reader, "ephemeral key")?; + let mut ephemeral_key = [0u8; 32]; + reader.read_exact(&mut ephemeral_key)?; let mut enc_ciphertext = [0u8; 580]; let mut out_ciphertext = [0u8; 80]; @@ -279,7 +280,7 @@ impl OutputDescription { pub fn write_v4(&self, mut writer: W) -> io::Result<()> { writer.write_all(&self.cv.to_bytes())?; writer.write_all(self.cmu.to_repr().as_ref())?; - writer.write_all(&self.ephemeral_key.to_bytes())?; + writer.write_all(&self.ephemeral_key)?; writer.write_all(&self.enc_ciphertext)?; writer.write_all(&self.out_ciphertext)?; writer.write_all(&self.zkproof) @@ -288,7 +289,7 @@ impl OutputDescription { pub fn write_v5_without_proof(&self, mut writer: W) -> io::Result<()> { writer.write_all(&self.cv.to_bytes())?; writer.write_all(self.cmu.to_repr().as_ref())?; - writer.write_all(&self.ephemeral_key.to_bytes())?; + writer.write_all(&self.ephemeral_key)?; writer.write_all(&self.enc_ciphertext)?; writer.write_all(&self.out_ciphertext) } @@ -298,7 +299,7 @@ impl OutputDescription { pub struct OutputDescriptionV5 { pub cv: jubjub::ExtendedPoint, pub cmu: bls12_381::Scalar, - pub ephemeral_key: jubjub::ExtendedPoint, + pub ephemeral_key: [u8; 32], pub enc_ciphertext: [u8; 580], pub out_ciphertext: [u8; 80], } @@ -307,7 +308,12 @@ impl OutputDescriptionV5 { pub fn read(mut reader: &mut R) -> io::Result { let cv = read_point(&mut reader, "cv")?; let cmu = read_base(&mut reader, "cmu")?; - let ephemeral_key = read_point(&mut reader, "ephemeral key")?; + + // Consensus rules (§4.5): + // - Canonical encoding is enforced in librustzcash_sapling_check_output by zcashd + // - "Not small order" is enforced in SaplingVerificationContext::check_output() + let mut ephemeral_key = [0u8; 32]; + reader.read_exact(&mut ephemeral_key)?; let mut enc_ciphertext = [0u8; 580]; let mut out_ciphertext = [0u8; 80]; @@ -339,7 +345,7 @@ impl OutputDescriptionV5 { } pub struct CompactOutputDescription { - pub epk: jubjub::ExtendedPoint, + pub ephemeral_key: [u8; 32], pub cmu: bls12_381::Scalar, pub enc_ciphertext: Vec, } @@ -347,7 +353,7 @@ pub struct CompactOutputDescription { impl From> for CompactOutputDescription { fn from(out: OutputDescription) -> CompactOutputDescription { CompactOutputDescription { - epk: out.ephemeral_key, + ephemeral_key: out.ephemeral_key, cmu: out.cmu, enc_ciphertext: out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec(), } @@ -356,7 +362,7 @@ impl From> for CompactOutputDescription { impl ShieldedOutput> for CompactOutputDescription { fn ephemeral_key(&self) -> EphemeralKeyBytes { - EphemeralKeyBytes(self.epk.to_bytes()) + EphemeralKeyBytes(self.ephemeral_key) } fn cmstar_bytes(&self) -> [u8; 32] { @@ -371,7 +377,7 @@ impl ShieldedOutput> for CompactOutpu #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use ff::Field; - use group::Group; + use group::{Group, GroupEncoding}; use proptest::collection::vec; use proptest::prelude::*; use rand::{rngs::StdRng, SeedableRng}; @@ -438,7 +444,7 @@ pub mod testing { .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), enc_ciphertext in vec(any::(), 580) .prop_map(|v| <[u8;580]>::try_from(v.as_slice()).unwrap()), - ephemeral_key in arb_extended_point(), + epk in arb_extended_point(), out_ciphertext in vec(any::(), 80) .prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()), zkproof in vec(any::(), GROTH_PROOF_SIZE) @@ -447,7 +453,7 @@ pub mod testing { OutputDescription { cv, cmu, - ephemeral_key, + ephemeral_key: epk.to_bytes(), enc_ciphertext, out_ciphertext, zkproof, diff --git a/zcash_primitives/src/transaction/components/sapling/builder.rs b/zcash_primitives/src/transaction/components/sapling/builder.rs index 28f039154..20634a865 100644 --- a/zcash_primitives/src/transaction/components/sapling/builder.rs +++ b/zcash_primitives/src/transaction/components/sapling/builder.rs @@ -4,6 +4,7 @@ use std::fmt; use std::sync::mpsc::Sender; use ff::Field; +use group::GroupEncoding; use rand::{seq::SliceRandom, RngCore}; use crate::{ @@ -136,12 +137,12 @@ impl SaplingOutput { let enc_ciphertext = encryptor.encrypt_note_plaintext(); let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng); - let ephemeral_key = *encryptor.epk(); + let epk = *encryptor.epk(); OutputDescription { cv, cmu, - ephemeral_key, + ephemeral_key: epk.to_bytes(), enc_ciphertext, out_ciphertext, zkproof, @@ -463,7 +464,7 @@ impl SaplingBuilder

{ OutputDescription { cv, cmu, - ephemeral_key: epk.into(), + ephemeral_key: epk.to_bytes(), enc_ciphertext, out_ciphertext, zkproof, diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index 0b5fdcc4f..fd049ac52 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -173,7 +173,7 @@ pub(crate) fn hash_sapling_outputs(shielded_outputs: &[OutputDescription]) let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION); for s_out in shielded_outputs { ch.write_all(&s_out.cmu.to_repr().as_ref()).unwrap(); - ch.write_all(&s_out.ephemeral_key.to_bytes()).unwrap(); + ch.write_all(&s_out.ephemeral_key).unwrap(); ch.write_all(&s_out.enc_ciphertext[..52]).unwrap(); mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap();