Store OutputDescription `ephemeral_key` as bytes

This removes an unnecessary `to_bytes` during trial decryption of notes,
and more closely matches the protocol spec. We retain the consensus rule
canonicity check on epk due to `SaplingVerificationContext::check_output`
taking a `jubjub::ExtendedPoint`, forcing `zcashd` to parse the bytes.
This commit is contained in:
Jack Grigg 2021-08-06 16:54:48 +01:00
parent 99d877e22d
commit 83c6a2d1ca
8 changed files with 51 additions and 44 deletions

View File

@ -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<jubjub::ExtendedPoint, ()> {
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<A: sapling::Authorization> From<OutputDescription<A>> for compact_formats::
fn from(out: OutputDescription<A>) -> 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<compact_formats::CompactOutput> for CompactOutputDescription {
fn try_from(value: compact_formats::CompactOutput) -> Result<Self, Self::Error> {
Ok(CompactOutputDescription {
cmu: value.cmu()?,
epk: value.epk()?,
ephemeral_key: value.ephemeral_key()?,
enc_ciphertext: value.ciphertext,
})
}

View File

@ -54,7 +54,7 @@ pub struct WalletShieldedSpend {
pub struct WalletShieldedOutput<N> {
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,

View File

@ -76,7 +76,7 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
return Some(WalletShieldedOutput {
index,
cmu: output.cmu,
epk: output.epk,
ephemeral_key: output.ephemeral_key,
account: **account,
note,
to,

View File

@ -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);

View File

@ -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],

View File

@ -213,7 +213,7 @@ impl SpendDescriptionV5 {
pub struct OutputDescription<Proof> {
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<Proof> {
impl<P: consensus::Parameters, A> ShieldedOutput<SaplingDomain<P>> for OutputDescription<A> {
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<GrothProofBytes> {
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<GrothProofBytes> {
pub fn write_v4<W: Write>(&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<GrothProofBytes> {
pub fn write_v5_without_proof<W: Write>(&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<GrothProofBytes> {
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<R: Read>(mut reader: &mut R) -> io::Result<Self> {
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<u8>,
}
@ -347,7 +353,7 @@ pub struct CompactOutputDescription {
impl<A> From<OutputDescription<A>> for CompactOutputDescription {
fn from(out: OutputDescription<A>) -> 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<A> From<OutputDescription<A>> for CompactOutputDescription {
impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> 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<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> 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::<u8>(), 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::<u8>(), 80)
.prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()),
zkproof in vec(any::<u8>(), 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,

View File

@ -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<P: consensus::Parameters> SaplingBuilder<P> {
OutputDescription {
cv,
cmu,
ephemeral_key: epk.into(),
ephemeral_key: epk.to_bytes(),
enc_ciphertext,
out_ciphertext,
zkproof,

View File

@ -173,7 +173,7 @@ pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>])
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();