zcash_primitives: Extract bundle-specific parts of tx serialization

This commit is contained in:
Jack Grigg 2024-05-11 00:10:47 +00:00
parent c9c2486564
commit 7477ff1604
9 changed files with 291 additions and 142 deletions

View File

@ -942,7 +942,7 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
use crate::{
legacy::keys::{AccountPrivKey, IncomingViewingKey},
transaction::{builder::DEFAULT_TX_EXPIRY_DELTA, OutPoint, TxOut},
transaction::{builder::DEFAULT_TX_EXPIRY_DELTA, transparent::TxOut, OutPoint},
zip32::AccountId,
};

View File

@ -1,5 +1,6 @@
//! Types representing the components within Zcash transactions.
use std::io;
use std::marker::PhantomData;
use zcash_protocol::value::BalanceError;
@ -184,17 +185,74 @@ impl<A: tze::Authorization> TzePart for Tze<A> {
}
/// The Transparent part of an authorized transaction.
pub trait AuthorizedTransparentPart: TransparentPart {}
pub trait AuthorizedTransparentPart: TransparentPart {
fn read_bundle<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;
fn write_bundle<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
}
/// The Sprout part of an authorized transaction.
pub trait AuthorizedSproutPart: SproutPart {}
pub trait AuthorizedSproutPart: SproutPart {
fn read_v4_bundle<R: io::Read>(
reader: R,
tx_has_sprout: bool,
use_groth: bool,
) -> io::Result<Option<Self::Bundle>>;
fn write_v4_bundle<W: io::Write>(
bundle: Option<&Self::Bundle>,
writer: W,
tx_has_sprout: bool,
) -> io::Result<()>;
}
/// The Sapling part of an authorized transaction.
pub trait AuthorizedSaplingPart: SaplingPart {}
pub trait AuthorizedSaplingPart: SaplingPart {
type V4Components;
fn read_v4_components<R: io::Read>(
reader: R,
tx_has_sapling: bool,
) -> io::Result<Self::V4Components>;
fn read_v4_binding_sig<R: io::Read>(
reader: R,
tx_has_sapling: bool,
components: Self::V4Components,
) -> io::Result<Option<Self::Bundle>>;
fn write_v4_components<W: io::Write>(
bundle: Option<&Self::Bundle>,
writer: W,
tx_has_sapling: bool,
) -> io::Result<()>;
fn write_v4_binding_sig<W: io::Write>(
bundle: Option<&Self::Bundle>,
writer: W,
tx_has_sapling: bool,
) -> io::Result<()>;
fn read_v5_bundle<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;
fn write_v5_bundle<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
}
/// The Orchard part of an authorized transaction.
pub trait AuthorizedOrchardPart: OrchardPart {}
pub trait AuthorizedOrchardPart: OrchardPart {
fn read_v5_bundle<R: io::Read>(reader: R) -> io::Result<Option<Self::Bundle>>;
fn write_v5_bundle<W: io::Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>;
}
/// The TZE part of an authorized transaction.
#[cfg(zcash_unstable = "zfuture")]
pub trait AuthorizedTzePart: TzePart {}
pub trait AuthorizedTzePart: TzePart {
fn read_bundle<R: io::Read>(reader: R, tx_has_tze: bool) -> io::Result<Option<Self::Bundle>>;
fn write_bundle<W: io::Write>(
bundle: Option<&Self::Bundle>,
writer: W,
tx_has_tze: bool,
) -> io::Result<()>;
}

View File

@ -44,7 +44,15 @@ impl MapAuth<Authorized, Authorized> for () {
}
}
impl AuthorizedOrchardPart for Orchard<orchard::bundle::Authorized> {}
impl AuthorizedOrchardPart for Orchard<orchard::bundle::Authorized> {
fn read_v5_bundle<R: Read>(reader: R) -> io::Result<Option<Self::Bundle>> {
read_v5_bundle(reader)
}
fn write_v5_bundle<W: Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()> {
write_v5_bundle(bundle, writer)
}
}
/// Reads an [`orchard::Bundle`] from a v5 transaction format.
pub fn read_v5_bundle<R: Read>(

View File

@ -84,7 +84,74 @@ impl MapAuth<Authorized, Authorized> for () {
}
}
impl AuthorizedSaplingPart for Sapling<sapling::bundle::Authorized> {}
impl AuthorizedSaplingPart for Sapling<sapling::bundle::Authorized> {
type V4Components = (
Amount,
Vec<sapling::bundle::SpendDescription<sapling::bundle::Authorized>>,
Vec<sapling::bundle::OutputDescription<sapling::bundle::GrothProofBytes>>,
);
fn read_v4_components<R: Read>(
reader: R,
tx_has_sapling: bool,
) -> io::Result<Self::V4Components> {
read_v4_components(reader, tx_has_sapling)
}
fn read_v4_binding_sig<R: Read>(
mut reader: R,
tx_has_sapling: bool,
(value_balance, shielded_spends, shielded_outputs): Self::V4Components,
) -> io::Result<Option<Self::Bundle>> {
let binding_sig =
if tx_has_sapling && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
let mut sig = [0; 64];
reader.read_exact(&mut sig)?;
Some(redjubjub::Signature::from(sig))
} else {
None
};
Ok(binding_sig.and_then(|binding_sig| {
sapling::Bundle::from_parts(
shielded_spends,
shielded_outputs,
value_balance,
sapling::bundle::Authorized { binding_sig },
)
}))
}
fn write_v4_components<W: Write>(
bundle: Option<&Self::Bundle>,
writer: W,
tx_has_sapling: bool,
) -> io::Result<()> {
write_v4_components(writer, bundle, tx_has_sapling)
}
fn write_v4_binding_sig<W: Write>(
bundle: Option<&Self::Bundle>,
mut writer: W,
tx_has_sapling: bool,
) -> io::Result<()> {
if tx_has_sapling {
if let Some(bundle) = bundle {
writer.write_all(&<[u8; 64]>::from(bundle.authorization().binding_sig))?;
}
}
Ok(())
}
fn read_v5_bundle<R: Read>(reader: R) -> io::Result<Option<Self::Bundle>> {
read_v5_bundle(reader)
}
fn write_v5_bundle<W: Write>(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()> {
write_v5_bundle(writer, bundle)
}
}
/// Consensus rules (§4.4) & (§4.5):
/// - Canonical encoding is enforced here.

View File

@ -2,6 +2,8 @@
use std::io::{self, Read, Write};
use zcash_encoding::{CompactSize, Vector};
use super::{amount::Amount, AuthorizedSproutPart, Sprout, GROTH_PROOF_SIZE};
// π_A + π_A' + π_B + π_B' + π_C + π_C' + π_K + π_H
@ -29,7 +31,50 @@ impl Bundle {
}
}
impl AuthorizedSproutPart for Sprout {}
impl AuthorizedSproutPart for Sprout {
fn read_v4_bundle<R: io::Read>(
mut reader: R,
tx_has_sprout: bool,
use_groth: bool,
) -> io::Result<Option<Self::Bundle>> {
if tx_has_sprout {
let joinsplits = Vector::read(&mut reader, |r| JsDescription::read(r, use_groth))?;
if !joinsplits.is_empty() {
let mut bundle = Bundle {
joinsplits,
joinsplit_pubkey: [0; 32],
joinsplit_sig: [0; 64],
};
reader.read_exact(&mut bundle.joinsplit_pubkey)?;
reader.read_exact(&mut bundle.joinsplit_sig)?;
Ok(Some(bundle))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn write_v4_bundle<W: io::Write>(
bundle: Option<&Self::Bundle>,
mut writer: W,
tx_has_sprout: bool,
) -> io::Result<()> {
if tx_has_sprout {
if let Some(bundle) = bundle {
Vector::write(&mut writer, &bundle.joinsplits, |w, e| e.write(w))?;
writer.write_all(&bundle.joinsplit_pubkey)?;
writer.write_all(&bundle.joinsplit_sig)?;
} else {
CompactSize::write(&mut writer, 0)?;
}
}
Ok(())
}
}
#[derive(Clone)]
#[allow(clippy::upper_case_acronyms)]

View File

@ -1,6 +1,7 @@
//! Structs representing the components within Zcash transactions.
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use zcash_encoding::{CompactSize, Vector};
use std::fmt::Debug;
use std::io::{self, Read, Write};
@ -192,7 +193,33 @@ impl TxOut {
}
}
impl AuthorizedTransparentPart for Transparent<Authorized> {}
impl AuthorizedTransparentPart for Transparent<Authorized> {
fn read_bundle<R: Read>(mut reader: R) -> io::Result<Option<Self::Bundle>> {
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
Ok(if vin.is_empty() && vout.is_empty() {
None
} else {
Some(Bundle {
vin,
vout,
authorization: Authorized,
})
})
}
fn write_bundle<W: Write>(bundle: Option<&Self::Bundle>, mut writer: W) -> io::Result<()> {
if let Some(bundle) = bundle {
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
} else {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
Ok(())
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {

View File

@ -211,7 +211,43 @@ impl TzeOut {
}
}
impl AuthorizedTzePart for Tze<Authorized> {}
impl AuthorizedTzePart for Tze<Authorized> {
fn read_bundle<R: Read>(mut reader: R, tx_has_tze: bool) -> io::Result<Option<Self::Bundle>> {
if tx_has_tze {
let vin = Vector::read(&mut reader, TzeIn::read)?;
let vout = Vector::read(&mut reader, TzeOut::read)?;
Ok(if vin.is_empty() && vout.is_empty() {
None
} else {
Some(Bundle {
vin,
vout,
authorization: Authorized,
})
})
} else {
Ok(None)
}
}
fn write_bundle<W: Write>(
bundle: Option<&Self::Bundle>,
mut writer: W,
tx_has_tze: bool,
) -> io::Result<()> {
if tx_has_tze {
if let Some(bundle) = bundle {
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
} else {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
}
Ok(())
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {

View File

@ -19,31 +19,27 @@ use std::fmt;
use std::fmt::Debug;
use std::io::{self, Read, Write};
use std::ops::Deref;
use zcash_encoding::{CompactSize, Vector};
use crate::{
consensus::{BlockHeight, BranchId},
sapling::{self, builder as sapling_builder},
};
use self::components::AuthorizedSproutPart;
use self::{
components::{
amount::{Amount, BalanceError},
orchard as orchard_serialization, sapling as sapling_serialization,
sprout::{self, JsDescription},
transparent::{self, TxIn, TxOut},
AllBundles, Bundles, Orchard, OrchardPart, OutPoint, Sapling, SaplingPart, ShieldedBundle,
Sprout, SproutPart, Transparent, TransparentPart,
orchard as orchard_serialization, sapling as sapling_serialization, sprout, transparent,
AllBundles, AuthorizedOrchardPart, AuthorizedSaplingPart, AuthorizedTransparentPart,
Bundles, Orchard, OrchardPart, OutPoint, Sapling, SaplingPart, ShieldedBundle, Sprout,
SproutPart, Transparent, TransparentPart,
},
txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester},
util::sha256d::{HashReader, HashWriter},
};
#[cfg(zcash_unstable = "zfuture")]
use self::components::{
tze::{self, TzeIn, TzeOut},
Tze, TzePart,
};
use self::components::{tze, AuthorizedTzePart, Tze, TzePart};
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
const OVERWINTER_TX_VERSION: u32 = 3;
@ -691,7 +687,7 @@ impl Transaction {
version: TxVersion,
consensus_branch_id: BranchId,
) -> io::Result<Self> {
let transparent_bundle = Self::read_transparent(&mut reader)?;
let transparent_bundle = Transparent::read_bundle(&mut reader)?;
let lock_time = reader.read_u32::<LittleEndian>()?;
let expiry_height: BlockHeight = if version.has_overwinter() {
@ -700,39 +696,13 @@ impl Transaction {
0u32.into()
};
let (value_balance, shielded_spends, shielded_outputs) =
sapling_serialization::read_v4_components(&mut reader, version.has_sapling())?;
let components = Sapling::read_v4_components(&mut reader, version.has_sapling())?;
let sprout_bundle = if version.has_sprout() {
let joinsplits = Vector::read(&mut reader, |r| {
JsDescription::read(r, version.has_sapling())
})?;
let sprout_bundle =
Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?;
if !joinsplits.is_empty() {
let mut bundle = sprout::Bundle {
joinsplits,
joinsplit_pubkey: [0; 32],
joinsplit_sig: [0; 64],
};
reader.read_exact(&mut bundle.joinsplit_pubkey)?;
reader.read_exact(&mut bundle.joinsplit_sig)?;
Some(bundle)
} else {
None
}
} else {
None
};
let binding_sig = if version.has_sapling()
&& !(shielded_spends.is_empty() && shielded_outputs.is_empty())
{
let mut sig = [0; 64];
reader.read_exact(&mut sig)?;
Some(redjubjub::Signature::from(sig))
} else {
None
};
let sapling_bundle =
Sapling::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?;
let mut txid = [0; 32];
let hash_bytes = reader.into_hash();
@ -747,14 +717,7 @@ impl Transaction {
expiry_height,
transparent_bundle,
sprout_bundle,
sapling_bundle: binding_sig.and_then(|binding_sig| {
sapling::Bundle::from_parts(
shielded_spends,
shielded_outputs,
value_balance,
sapling::bundle::Authorized { binding_sig },
)
}),
sapling_bundle,
orchard_bundle: None,
#[cfg(zcash_unstable = "zfuture")]
tze_bundle: None,
@ -762,22 +725,6 @@ impl Transaction {
})
}
fn read_transparent<R: Read>(
mut reader: R,
) -> io::Result<Option<transparent::Bundle<transparent::Authorized>>> {
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
Ok(if vin.is_empty() && vout.is_empty() {
None
} else {
Some(transparent::Bundle {
vin,
vout,
authorization: transparent::Authorized,
})
})
}
fn read_amount<R: Read>(mut reader: R) -> io::Result<Amount> {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
@ -788,16 +735,12 @@ impl Transaction {
fn read_v5<R: Read>(mut reader: R, version: TxVersion) -> io::Result<Self> {
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 = sapling_serialization::read_v5_bundle(&mut reader)?;
let orchard_bundle = orchard_serialization::read_v5_bundle(&mut reader)?;
let transparent_bundle = Transparent::read_bundle(&mut reader)?;
let sapling_bundle = Sapling::read_v5_bundle(&mut reader)?;
let orchard_bundle = Orchard::read_v5_bundle(&mut reader)?;
#[cfg(zcash_unstable = "zfuture")]
let tze_bundle = if version.has_tze() {
Self::read_tze(&mut reader)?
} else {
None
};
let tze_bundle = Tze::read_bundle(&mut reader, version.has_tze())?;
let data = TransactionData {
version,
@ -833,22 +776,7 @@ impl Transaction {
pub fn temporary_zcashd_read_v5_sapling<R: Read>(
reader: R,
) -> io::Result<Option<sapling::Bundle<sapling::bundle::Authorized, Amount>>> {
sapling_serialization::read_v5_bundle(reader)
}
#[cfg(zcash_unstable = "zfuture")]
fn read_tze<R: Read>(mut reader: &mut R) -> io::Result<Option<tze::Bundle<tze::Authorized>>> {
let vin = Vector::read(&mut reader, TzeIn::read)?;
let vout = Vector::read(&mut reader, TzeOut::read)?;
Ok(if vin.is_empty() && vout.is_empty() {
None
} else {
Some(tze::Bundle {
vin,
vout,
authorization: tze::Authorized,
})
})
Sapling::read_v5_bundle(reader)
}
pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
@ -871,27 +799,23 @@ impl Transaction {
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
}
sapling_serialization::write_v4_components(
&mut writer,
Sapling::write_v4_components(
self.sapling_bundle.as_ref(),
&mut writer,
self.version.has_sapling(),
)?;
if self.version.has_sprout() {
if let Some(bundle) = self.sprout_bundle.as_ref() {
Vector::write(&mut writer, &bundle.joinsplits, |w, e| e.write(w))?;
writer.write_all(&bundle.joinsplit_pubkey)?;
writer.write_all(&bundle.joinsplit_sig)?;
} else {
CompactSize::write(&mut writer, 0)?;
}
}
Sprout::write_v4_bundle(
self.sprout_bundle.as_ref(),
&mut writer,
self.version.has_sprout(),
)?;
if self.version.has_sapling() {
if let Some(bundle) = self.sapling_bundle.as_ref() {
writer.write_all(&<[u8; 64]>::from(bundle.authorization().binding_sig))?;
}
}
Sapling::write_v4_binding_sig(
self.sapling_bundle.as_ref(),
&mut writer,
self.version.has_sapling(),
)?;
if self.orchard_bundle.is_some() {
return Err(io::Error::new(
@ -903,16 +827,8 @@ impl Transaction {
Ok(())
}
pub fn write_transparent<W: Write>(&self, mut writer: W) -> io::Result<()> {
if let Some(bundle) = &self.transparent_bundle {
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
} else {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
Ok(())
pub fn write_transparent<W: Write>(&self, writer: W) -> io::Result<()> {
Transparent::write_bundle(self.transparent_bundle.as_ref(), writer)
}
pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
@ -925,7 +841,7 @@ impl Transaction {
self.write_v5_header(&mut writer)?;
self.write_transparent(&mut writer)?;
self.write_v5_sapling(&mut writer)?;
orchard_serialization::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
#[cfg(zcash_unstable = "zfuture")]
self.write_tze(&mut writer)?;
Ok(())
@ -944,24 +860,16 @@ impl Transaction {
sapling_bundle: Option<&sapling::Bundle<sapling::bundle::Authorized, Amount>>,
writer: W,
) -> io::Result<()> {
sapling_serialization::write_v5_bundle(writer, sapling_bundle)
Sapling::write_v5_bundle(sapling_bundle, writer)
}
pub fn write_v5_sapling<W: Write>(&self, writer: W) -> io::Result<()> {
sapling_serialization::write_v5_bundle(writer, self.sapling_bundle.as_ref())
Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer)
}
#[cfg(zcash_unstable = "zfuture")]
pub fn write_tze<W: Write>(&self, mut writer: W) -> io::Result<()> {
if let Some(bundle) = &self.tze_bundle {
Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
} else {
CompactSize::write(&mut writer, 0)?;
CompactSize::write(&mut writer, 0)?;
}
Ok(())
pub fn write_tze<W: Write>(&self, writer: W) -> io::Result<()> {
Tze::write_bundle(self.tze_bundle.as_ref(), writer, self.version.has_tze())
}
// TODO: should this be moved to `from_data` and stored?

View File

@ -16,9 +16,9 @@ use super::{
sighash_v4::v4_signature_hash,
sighash_v5::v5_signature_hash,
testing::arb_tx,
transparent::{self},
transparent::{self, TxIn},
txid::TxIdDigester,
AllBundles, Authorization, Transaction, TransactionData, TxDigests, TxIn,
AllBundles, Authorization, Transaction, TransactionData, TxDigests,
};
#[cfg(zcash_unstable = "zfuture")]