From 16988bea5d44e5752cc5c21e93ac4e29a020f048 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 11 May 2024 00:10:47 +0000 Subject: [PATCH] zcash_primitives: Extract bundle-specific parts of tx serialization --- zcash_primitives/src/transaction/builder.rs | 2 +- .../src/transaction/components.rs | 68 ++++++- .../src/transaction/components/orchard.rs | 10 +- .../src/transaction/components/sapling.rs | 69 ++++++- .../src/transaction/components/sprout.rs | 47 ++++- .../src/transaction/components/transparent.rs | 29 ++- .../src/transaction/components/tze.rs | 38 +++- zcash_primitives/src/transaction/mod.rs | 168 ++++-------------- zcash_primitives/src/transaction/tests.rs | 4 +- 9 files changed, 292 insertions(+), 143 deletions(-) diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 27bef6b643..3ad965c2ed 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -951,7 +951,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, }; diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 662988d1bd..36c11e46f5 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -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 TzePart for Tze { } /// The Transparent part of an authorized transaction. -pub trait AuthorizedTransparentPart: TransparentPart {} +pub trait AuthorizedTransparentPart: TransparentPart { + fn read_bundle(reader: R) -> io::Result>; + + fn write_bundle(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( + reader: R, + tx_has_sprout: bool, + use_groth: bool, + ) -> io::Result>; + + fn write_v4_bundle( + 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( + reader: R, + tx_has_sapling: bool, + ) -> io::Result; + + fn read_v4_binding_sig( + reader: R, + tx_has_sapling: bool, + components: Self::V4Components, + ) -> io::Result>; + + fn write_v4_components( + bundle: Option<&Self::Bundle>, + writer: W, + tx_has_sapling: bool, + ) -> io::Result<()>; + + fn write_v4_binding_sig( + bundle: Option<&Self::Bundle>, + writer: W, + tx_has_sapling: bool, + ) -> io::Result<()>; + + fn read_v5_bundle(reader: R) -> io::Result>; + + fn write_v5_bundle(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(reader: R) -> io::Result>; + + fn write_v5_bundle(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(reader: R, tx_has_tze: bool) -> io::Result>; + + fn write_bundle( + bundle: Option<&Self::Bundle>, + writer: W, + tx_has_tze: bool, + ) -> io::Result<()>; +} diff --git a/zcash_primitives/src/transaction/components/orchard.rs b/zcash_primitives/src/transaction/components/orchard.rs index bed830e9da..83c78d5714 100644 --- a/zcash_primitives/src/transaction/components/orchard.rs +++ b/zcash_primitives/src/transaction/components/orchard.rs @@ -44,7 +44,15 @@ impl MapAuth for () { } } -impl AuthorizedOrchardPart for Orchard {} +impl AuthorizedOrchardPart for Orchard { + fn read_v5_bundle(reader: R) -> io::Result> { + read_v5_bundle(reader) + } + + fn write_v5_bundle(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( diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 8b63f82bb0..3b70add7ec 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -84,7 +84,74 @@ impl MapAuth for () { } } -impl AuthorizedSaplingPart for Sapling {} +impl AuthorizedSaplingPart for Sapling { + type V4Components = ( + Amount, + Vec>, + Vec>, + ); + + fn read_v4_components( + reader: R, + tx_has_sapling: bool, + ) -> io::Result { + read_v4_components(reader, tx_has_sapling) + } + + fn read_v4_binding_sig( + mut reader: R, + tx_has_sapling: bool, + (value_balance, shielded_spends, shielded_outputs): Self::V4Components, + ) -> io::Result> { + 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( + 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( + 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(reader: R) -> io::Result> { + read_v5_bundle(reader) + } + + fn write_v5_bundle(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()> { + write_v5_bundle(writer, bundle) + } +} /// Consensus rules (§4.4) & (§4.5): /// - Canonical encoding is enforced here. diff --git a/zcash_primitives/src/transaction/components/sprout.rs b/zcash_primitives/src/transaction/components/sprout.rs index eb8cc70fcc..51fd9f954b 100644 --- a/zcash_primitives/src/transaction/components/sprout.rs +++ b/zcash_primitives/src/transaction/components/sprout.rs @@ -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( + mut reader: R, + tx_has_sprout: bool, + use_groth: bool, + ) -> io::Result> { + 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( + 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)] diff --git a/zcash_primitives/src/transaction/components/transparent.rs b/zcash_primitives/src/transaction/components/transparent.rs index dc6a2b666e..f48a4b29e4 100644 --- a/zcash_primitives/src/transaction/components/transparent.rs +++ b/zcash_primitives/src/transaction/components/transparent.rs @@ -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}; @@ -205,7 +206,33 @@ impl TxOut { } } -impl AuthorizedTransparentPart for Transparent {} +impl AuthorizedTransparentPart for Transparent { + fn read_bundle(mut reader: R) -> io::Result> { + 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(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 { diff --git a/zcash_primitives/src/transaction/components/tze.rs b/zcash_primitives/src/transaction/components/tze.rs index 5f1da37e89..4f57002863 100644 --- a/zcash_primitives/src/transaction/components/tze.rs +++ b/zcash_primitives/src/transaction/components/tze.rs @@ -211,7 +211,43 @@ impl TzeOut { } } -impl AuthorizedTzePart for Tze {} +impl AuthorizedTzePart for Tze { + fn read_bundle(mut reader: R, tx_has_tze: bool) -> io::Result> { + 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( + 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 { diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 6df6b54cad..cc7c489c3f 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -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 { - let transparent_bundle = Self::read_transparent(&mut reader)?; + let transparent_bundle = Transparent::read_bundle(&mut reader)?; let lock_time = reader.read_u32::()?; 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 sprout_bundle = if version.has_sprout() { - let joinsplits = Vector::read(&mut reader, |r| { - JsDescription::read(r, 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 components = Sapling::read_v4_components(&mut reader, version.has_sapling())?; - 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 sprout_bundle = + Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?; + + 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( - mut reader: R, - ) -> io::Result>> { - 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(mut reader: R) -> io::Result { let mut tmp = [0; 8]; reader.read_exact(&mut tmp)?; @@ -788,16 +735,12 @@ impl Transaction { fn read_v5(mut reader: R, version: TxVersion) -> io::Result { 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( reader: R, ) -> io::Result>> { - sapling_serialization::read_v5_bundle(reader) - } - - #[cfg(zcash_unstable = "zfuture")] - fn read_tze(mut reader: &mut R) -> io::Result>> { - 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(&self, writer: W) -> io::Result<()> { @@ -871,27 +799,23 @@ impl Transaction { writer.write_u32::(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(&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(&self, writer: W) -> io::Result<()> { + Transparent::write_bundle(self.transparent_bundle.as_ref(), writer) } pub fn write_v5(&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>, writer: W, ) -> io::Result<()> { - sapling_serialization::write_v5_bundle(writer, sapling_bundle) + Sapling::write_v5_bundle(sapling_bundle, writer) } pub fn write_v5_sapling(&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(&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(&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? diff --git a/zcash_primitives/src/transaction/tests.rs b/zcash_primitives/src/transaction/tests.rs index e181dd5e1e..251d858371 100644 --- a/zcash_primitives/src/transaction/tests.rs +++ b/zcash_primitives/src/transaction/tests.rs @@ -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")]