zcash_primitives: Move Sapling bundle parsing into `transaction::components::sapling`

This commit is contained in:
Jack Grigg 2023-11-10 01:11:36 +00:00
parent e1a5539094
commit 68ae453e5d
2 changed files with 199 additions and 188 deletions

View File

@ -2,20 +2,24 @@ use ff::PrimeField;
use std::io::{self, Read, Write};
use zcash_encoding::{Array, CompactSize, Vector};
use zcash_note_encryption::EphemeralKeyBytes;
use crate::sapling::{
bundle::{
Authorized, GrothProofBytes, OutputDescription, OutputDescriptionV5, SpendDescription,
SpendDescriptionV5,
use crate::{
sapling::{
bundle::{
Authorized, Bundle, GrothProofBytes, OutputDescription, OutputDescriptionV5,
SpendDescription, SpendDescriptionV5,
},
note::ExtractedNoteCommitment,
redjubjub::{self, PublicKey, Signature},
value::ValueCommitment,
Nullifier,
},
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
Nullifier,
transaction::Transaction,
};
use super::GROTH_PROOF_SIZE;
use super::{Amount, GROTH_PROOF_SIZE};
pub mod fees;
@ -68,7 +72,7 @@ pub fn read_zkproof<R: Read>(mut reader: R) -> io::Result<GrothProofBytes> {
Ok(zkproof)
}
pub(crate) fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
let mut nullifier = Nullifier([0u8; 32]);
reader.read_exact(&mut nullifier.0)?;
Ok(nullifier)
@ -77,18 +81,18 @@ pub(crate) fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
/// Consensus rules (§4.4):
/// - Canonical encoding is enforced here.
/// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
pub(crate) fn read_rk<R: Read>(mut reader: R) -> io::Result<PublicKey> {
fn read_rk<R: Read>(mut reader: R) -> io::Result<PublicKey> {
PublicKey::read(&mut reader)
}
/// Consensus rules (§4.4):
/// - Canonical encoding is enforced here.
/// - Signature validity is enforced in SaplingVerificationContext::check_spend()
pub(crate) fn read_spend_auth_sig<R: Read>(mut reader: R) -> io::Result<Signature> {
fn read_spend_auth_sig<R: Read>(mut reader: R) -> io::Result<Signature> {
Signature::read(&mut reader)
}
pub(crate) fn read_spend_v4<R: Read>(mut reader: R) -> io::Result<SpendDescription<Authorized>> {
fn read_spend_v4<R: Read>(mut reader: R) -> io::Result<SpendDescription<Authorized>> {
// Consensus rules (§4.4) & (§4.5):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::(check_spend()/check_output())
@ -112,10 +116,7 @@ pub(crate) fn read_spend_v4<R: Read>(mut reader: R) -> io::Result<SpendDescripti
))
}
pub(crate) fn write_spend_v4<W: Write>(
mut writer: W,
spend: &SpendDescription<Authorized>,
) -> io::Result<()> {
fn write_spend_v4<W: Write>(mut writer: W, spend: &SpendDescription<Authorized>) -> io::Result<()> {
writer.write_all(&spend.cv().to_bytes())?;
writer.write_all(spend.anchor().to_repr().as_ref())?;
writer.write_all(&spend.nullifier().0)?;
@ -124,7 +125,7 @@ pub(crate) fn write_spend_v4<W: Write>(
spend.spend_auth_sig().write(&mut writer)
}
pub(crate) fn write_spend_v5_without_witness_data<W: Write>(
fn write_spend_v5_without_witness_data<W: Write>(
mut writer: W,
spend: &SpendDescription<Authorized>,
) -> io::Result<()> {
@ -133,7 +134,7 @@ pub(crate) fn write_spend_v5_without_witness_data<W: Write>(
spend.rk().write(&mut writer)
}
pub(crate) fn read_spend_v5<R: Read>(mut reader: &mut R) -> io::Result<SpendDescriptionV5> {
fn read_spend_v5<R: Read>(mut reader: &mut R) -> io::Result<SpendDescriptionV5> {
let cv = read_value_commitment(&mut reader)?;
let nullifier = read_nullifier(&mut reader)?;
let rk = read_rk(&mut reader)?;
@ -141,9 +142,7 @@ pub(crate) fn read_spend_v5<R: Read>(mut reader: &mut R) -> io::Result<SpendDesc
Ok(SpendDescriptionV5::from_parts(cv, nullifier, rk))
}
pub(crate) fn read_output_v4<R: Read>(
mut reader: &mut R,
) -> io::Result<OutputDescription<GrothProofBytes>> {
fn read_output_v4<R: Read>(mut reader: &mut R) -> io::Result<OutputDescription<GrothProofBytes>> {
// Consensus rules (§4.5):
// - Canonical encoding is enforced here.
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
@ -188,7 +187,7 @@ pub(crate) fn write_output_v4<W: Write>(
writer.write_all(output.zkproof())
}
pub(crate) fn write_output_v5_without_proof<W: Write>(
fn write_output_v5_without_proof<W: Write>(
mut writer: W,
output: &OutputDescription<GrothProofBytes>,
) -> io::Result<()> {
@ -199,7 +198,7 @@ pub(crate) fn write_output_v5_without_proof<W: Write>(
writer.write_all(output.out_ciphertext())
}
pub(crate) fn read_output_v5<R: Read>(mut reader: &mut R) -> io::Result<OutputDescriptionV5> {
fn read_output_v5<R: Read>(mut reader: &mut R) -> io::Result<OutputDescriptionV5> {
let cv = read_value_commitment(&mut reader)?;
let cmu = read_cmu(&mut reader)?;
@ -223,6 +222,169 @@ pub(crate) fn read_output_v5<R: Read>(mut reader: &mut R) -> io::Result<OutputDe
))
}
/// Reads the Sapling components of a v4 transaction.
#[allow(clippy::type_complexity)]
pub(crate) fn read_v4_components<R: Read>(
mut reader: R,
tx_has_sapling: bool,
) -> io::Result<(
Amount,
Vec<SpendDescription<Authorized>>,
Vec<OutputDescription<GrothProofBytes>>,
)> {
if tx_has_sapling {
let vb = Transaction::read_amount(&mut reader)?;
#[allow(clippy::redundant_closure)]
let ss: Vec<SpendDescription<Authorized>> =
Vector::read(&mut reader, |r| read_spend_v4(r))?;
#[allow(clippy::redundant_closure)]
let so: Vec<OutputDescription<GrothProofBytes>> =
Vector::read(&mut reader, |r| read_output_v4(r))?;
Ok((vb, ss, so))
} else {
Ok((Amount::zero(), vec![], vec![]))
}
}
/// Writes the Sapling components of a v4 transaction.
pub(crate) fn write_v4_components<W: Write>(
mut writer: W,
bundle: Option<&Bundle<Authorized>>,
tx_has_sapling: bool,
) -> io::Result<()> {
if tx_has_sapling {
writer.write_all(
&bundle
.map_or(Amount::zero(), |b| *b.value_balance())
.to_i64_le_bytes(),
)?;
Vector::write(
&mut writer,
bundle.map_or(&[], |b| b.shielded_spends()),
|w, e| write_spend_v4(w, e),
)?;
Vector::write(
&mut writer,
bundle.map_or(&[], |b| b.shielded_outputs()),
|w, e| write_output_v4(w, e),
)?;
} else if bundle.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Sapling components may not be present if Sapling is not active.",
));
}
Ok(())
}
/// Reads a [`Bundle`] from a v5 transaction format.
#[allow(clippy::redundant_closure)]
pub(crate) fn read_v5_bundle<R: Read>(mut reader: R) -> io::Result<Option<Bundle<Authorized>>> {
let sd_v5s = Vector::read(&mut reader, read_spend_v5)?;
let od_v5s = Vector::read(&mut reader, read_output_v5)?;
let n_spends = sd_v5s.len();
let n_outputs = od_v5s.len();
let value_balance = if n_spends > 0 || n_outputs > 0 {
Transaction::read_amount(&mut reader)?
} else {
Amount::zero()
};
let anchor = if n_spends > 0 {
Some(read_base(&mut reader, "anchor")?)
} else {
None
};
let v_spend_proofs = Array::read(&mut reader, n_spends, |r| read_zkproof(r))?;
let v_spend_auth_sigs = Array::read(&mut reader, n_spends, |r| read_spend_auth_sig(r))?;
let v_output_proofs = Array::read(&mut reader, n_outputs, |r| read_zkproof(r))?;
let binding_sig = if n_spends > 0 || n_outputs > 0 {
Some(redjubjub::Signature::read(&mut reader)?)
} else {
None
};
let shielded_spends = sd_v5s
.into_iter()
.zip(
v_spend_proofs
.into_iter()
.zip(v_spend_auth_sigs.into_iter()),
)
.map(|(sd_5, (zkproof, spend_auth_sig))| {
// the following `unwrap` is safe because we know n_spends > 0.
sd_5.into_spend_description(anchor.unwrap(), zkproof, spend_auth_sig)
})
.collect();
let shielded_outputs = od_v5s
.into_iter()
.zip(v_output_proofs.into_iter())
.map(|(od_5, zkproof)| od_5.into_output_description(zkproof))
.collect();
Ok(binding_sig.map(|binding_sig| {
Bundle::from_parts(
shielded_spends,
shielded_outputs,
value_balance,
Authorized { binding_sig },
)
}))
}
/// Writes a [`Bundle`] in the v5 transaction format.
pub(crate) fn write_v5_bundle<W: Write>(
mut writer: W,
sapling_bundle: Option<&Bundle<Authorized>>,
) -> io::Result<()> {
if let Some(bundle) = sapling_bundle {
Vector::write(&mut writer, bundle.shielded_spends(), |w, e| {
write_spend_v5_without_witness_data(w, e)
})?;
Vector::write(&mut writer, bundle.shielded_outputs(), |w, e| {
write_output_v5_without_proof(w, e)
})?;
if !(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty()) {
writer.write_all(&bundle.value_balance().to_i64_le_bytes())?;
}
if !bundle.shielded_spends().is_empty() {
writer.write_all(bundle.shielded_spends()[0].anchor().to_repr().as_ref())?;
}
Array::write(
&mut writer,
bundle.shielded_spends().iter().map(|s| &s.zkproof()[..]),
|w, e| w.write_all(e),
)?;
Array::write(
&mut writer,
bundle.shielded_spends().iter().map(|s| s.spend_auth_sig()),
|w, e| e.write(w),
)?;
Array::write(
&mut writer,
bundle.shielded_outputs().iter().map(|s| &s.zkproof()[..]),
|w, e| w.write_all(e),
)?;
if !(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty()) {
bundle.authorization().binding_sig.write(&mut writer)?;
}
} else {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
Ok(())
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;

View File

@ -13,22 +13,17 @@ mod tests;
use blake2b_simd::Hash as Blake2bHash;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use ff::PrimeField;
use memuse::DynamicUsage;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::Debug;
use std::io::{self, Read, Write};
use std::ops::Deref;
use zcash_encoding::{Array, CompactSize, Vector};
use zcash_encoding::{CompactSize, Vector};
use crate::{
consensus::{BlockHeight, BranchId},
sapling::{
self, builder as sapling_builder,
bundle::{OutputDescription, SpendDescription},
redjubjub,
},
sapling::{self, builder as sapling_builder, redjubjub},
};
use self::{
@ -602,18 +597,8 @@ impl Transaction {
0u32.into()
};
let (value_balance, shielded_spends, shielded_outputs) = if version.has_sapling() {
let vb = Self::read_amount(&mut reader)?;
#[allow(clippy::redundant_closure)]
let ss: Vec<SpendDescription<sapling::bundle::Authorized>> =
Vector::read(&mut reader, |r| sapling_serialization::read_spend_v4(r))?;
#[allow(clippy::redundant_closure)]
let so: Vec<OutputDescription<sapling::bundle::GrothProofBytes>> =
Vector::read(&mut reader, |r| sapling_serialization::read_output_v4(r))?;
(vb, ss, so)
} else {
(Amount::zero(), vec![], vec![])
};
let (value_balance, shielded_spends, shielded_outputs) =
sapling_serialization::read_v4_components(&mut reader, version.has_sapling())?;
let sprout_bundle = if version.has_sprout() {
let joinsplits = Vector::read(&mut reader, |r| {
@ -699,7 +684,7 @@ impl Transaction {
let (consensus_branch_id, lock_time, expiry_height) =
Self::read_v5_header_fragment(&mut reader)?;
let transparent_bundle = Self::read_transparent(&mut reader)?;
let sapling_bundle = Self::read_v5_sapling(&mut reader)?;
let sapling_bundle = sapling_serialization::read_v5_bundle(&mut reader)?;
let orchard_bundle = orchard_serialization::read_v5_bundle(&mut reader)?;
#[cfg(feature = "zfuture")]
@ -743,72 +728,7 @@ impl Transaction {
pub fn temporary_zcashd_read_v5_sapling<R: Read>(
reader: R,
) -> io::Result<Option<sapling::Bundle<sapling::bundle::Authorized>>> {
Self::read_v5_sapling(reader)
}
#[allow(clippy::redundant_closure)]
fn read_v5_sapling<R: Read>(
mut reader: R,
) -> io::Result<Option<sapling::Bundle<sapling::bundle::Authorized>>> {
let sd_v5s = Vector::read(&mut reader, sapling_serialization::read_spend_v5)?;
let od_v5s = Vector::read(&mut reader, sapling_serialization::read_output_v5)?;
let n_spends = sd_v5s.len();
let n_outputs = od_v5s.len();
let value_balance = if n_spends > 0 || n_outputs > 0 {
Self::read_amount(&mut reader)?
} else {
Amount::zero()
};
let anchor = if n_spends > 0 {
Some(sapling_serialization::read_base(&mut reader, "anchor")?)
} else {
None
};
let v_spend_proofs = Array::read(&mut reader, n_spends, |r| {
sapling_serialization::read_zkproof(r)
})?;
let v_spend_auth_sigs = Array::read(&mut reader, n_spends, |r| {
sapling_serialization::read_spend_auth_sig(r)
})?;
let v_output_proofs = Array::read(&mut reader, n_outputs, |r| {
sapling_serialization::read_zkproof(r)
})?;
let binding_sig = if n_spends > 0 || n_outputs > 0 {
Some(redjubjub::Signature::read(&mut reader)?)
} else {
None
};
let shielded_spends = sd_v5s
.into_iter()
.zip(
v_spend_proofs
.into_iter()
.zip(v_spend_auth_sigs.into_iter()),
)
.map(|(sd_5, (zkproof, spend_auth_sig))| {
// the following `unwrap` is safe because we know n_spends > 0.
sd_5.into_spend_description(anchor.unwrap(), zkproof, spend_auth_sig)
})
.collect();
let shielded_outputs = od_v5s
.into_iter()
.zip(v_output_proofs.into_iter())
.map(|(od_5, zkproof)| od_5.into_output_description(zkproof))
.collect();
Ok(binding_sig.map(|binding_sig| {
sapling::Bundle::from_parts(
shielded_spends,
shielded_outputs,
value_balance,
sapling::bundle::Authorized { binding_sig },
)
}))
sapling_serialization::read_v5_bundle(reader)
}
#[cfg(feature = "zfuture")]
@ -846,34 +766,11 @@ impl Transaction {
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
}
if self.version.has_sapling() {
writer.write_all(
&self
.sapling_bundle
.as_ref()
.map_or(Amount::zero(), |b| *b.value_balance())
.to_i64_le_bytes(),
)?;
Vector::write(
&mut writer,
self.sapling_bundle
.as_ref()
.map_or(&[], |b| b.shielded_spends()),
|w, e| sapling_serialization::write_spend_v4(w, e),
)?;
Vector::write(
&mut writer,
self.sapling_bundle
.as_ref()
.map_or(&[], |b| b.shielded_outputs()),
|w, e| sapling_serialization::write_output_v4(w, e),
)?;
} else if self.sapling_bundle.is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Sapling components may not be present if Sapling is not active.",
));
}
sapling_serialization::write_v4_components(
&mut writer,
self.sapling_bundle.as_ref(),
self.version.has_sapling(),
)?;
if self.version.has_sprout() {
if let Some(bundle) = self.sprout_bundle.as_ref() {
@ -942,59 +839,11 @@ impl Transaction {
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized>>,
writer: W,
) -> io::Result<()> {
Self::write_v5_sapling_inner(sapling_bundle, writer)
sapling_serialization::write_v5_bundle(writer, sapling_bundle)
}
pub fn write_v5_sapling<W: Write>(&self, writer: W) -> io::Result<()> {
Self::write_v5_sapling_inner(self.sapling_bundle.as_ref(), writer)
}
fn write_v5_sapling_inner<W: Write>(
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized>>,
mut writer: W,
) -> io::Result<()> {
if let Some(bundle) = sapling_bundle {
Vector::write(&mut writer, bundle.shielded_spends(), |w, e| {
sapling_serialization::write_spend_v5_without_witness_data(w, e)
})?;
Vector::write(&mut writer, bundle.shielded_outputs(), |w, e| {
sapling_serialization::write_output_v5_without_proof(w, e)
})?;
if !(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty()) {
writer.write_all(&bundle.value_balance().to_i64_le_bytes())?;
}
if !bundle.shielded_spends().is_empty() {
writer.write_all(bundle.shielded_spends()[0].anchor().to_repr().as_ref())?;
}
Array::write(
&mut writer,
bundle.shielded_spends().iter().map(|s| &s.zkproof()[..]),
|w, e| w.write_all(e),
)?;
Array::write(
&mut writer,
bundle.shielded_spends().iter().map(|s| s.spend_auth_sig()),
|w, e| e.write(w),
)?;
Array::write(
&mut writer,
bundle.shielded_outputs().iter().map(|s| &s.zkproof()[..]),
|w, e| w.write_all(e),
)?;
if !(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty()) {
bundle.authorization().binding_sig.write(&mut writer)?;
}
} else {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
Ok(())
sapling_serialization::write_v5_bundle(writer, self.sapling_bundle.as_ref())
}
#[cfg(feature = "zfuture")]