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:
parent
99d877e22d
commit
83c6a2d1ca
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue