From 5596785e3233ecd3fc6562f7bc8cf70a5815f809 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 11 May 2024 01:24:42 +0000 Subject: [PATCH] zcash_primitives: Allow tx serialization with omitted bundles --- zcash_client_sqlite/src/wallet/init.rs | 4 +- .../init/migrations/add_transaction_views.rs | 4 +- .../src/transaction/components.rs | 12 +- zcash_primitives/src/transaction/mod.rs | 123 +++++++++++++----- zcash_primitives/src/transaction/txid.rs | 6 +- 5 files changed, 103 insertions(+), 46 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 9fff7e3a3f..2825dd0520 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -340,7 +340,7 @@ mod tests { consensus::{ self, BlockHeight, BranchId, Network, NetworkConstants, NetworkUpgrade, Parameters, }, - transaction::{TransactionData, TxVersion}, + transaction::{AuthorizedTransactionData, TxVersion}, zip32::AccountId, }; @@ -1266,7 +1266,7 @@ mod tests { [], )?; - let tx = TransactionData::from_parts( + let tx = AuthorizedTransactionData::from_parts( TxVersion::Sapling, BranchId::Canopy, 0, diff --git a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs index a84f99a05b..e87ffa16ce 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs @@ -295,7 +295,7 @@ mod tests { transparent::{self, Authorized, OutPoint}, Amount, TxIn, TxOut, }, - TransactionData, TxVersion, + AuthorizedTransactionData, TxVersion, }, }, }; @@ -413,7 +413,7 @@ mod tests { .unwrap(); // create a UTXO to spend - let tx = TransactionData::from_parts( + let tx = AuthorizedTransactionData::from_parts( TxVersion::Sapling, BranchId::Canopy, 0, diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index b7fdf3d45b..920cfd2a0e 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -156,9 +156,7 @@ impl OrchardPart for Orchard { type Authorization = A; } -pub(super) trait TransparentEnc: - TransparentPart -{ +pub trait TransparentEnc: TransparentPart { fn read(reader: R) -> io::Result>; fn write(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>; @@ -187,9 +185,7 @@ impl TransparentEnc for NoTransparent { } } -pub(super) trait SaplingEnc: - SaplingPart -{ +pub trait SaplingEnc: SaplingPart { type V4Components; fn read_v4_components( @@ -309,9 +305,7 @@ impl SaplingEnc for NoSapling { } } -pub(super) trait OrchardEnc: - OrchardPart -{ +pub trait OrchardEnc: OrchardPart { fn read_v5_bundle(reader: R) -> io::Result>; fn write_v5_bundle(bundle: Option<&Self::Bundle>, writer: W) -> io::Result<()>; diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index db61b33d85..efc6105c19 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -299,22 +299,67 @@ impl Authorization for Unauthorized { } /// A Zcash transaction. -#[derive(Debug)] -pub struct Transaction { +pub type Transaction = TransactionWith< + Transparent, + Sapling, + Orchard, +>; + +/// An authorized Zcash transaction with configurable support in the type system for the +/// various transparent and shielded pools. +/// +/// If a particular pool is disabled (by using e.g. [`components::NoTransparent`]): +/// - The pool-specific APIs will be inaccessible. +/// - The serialization APIs will encode an empty bundle for that pool. +/// - The parsing APIs will return an error when parsing a transaction with spends or +/// outputs in that pool. +pub struct TransactionWith +where + T: TransparentPart, + S: SaplingPart, + O: OrchardPart, +{ txid: TxId, - data: AuthorizedTransactionData, + data: TransactionData, } -impl Deref for Transaction { - type Target = AuthorizedTransactionData; +impl fmt::Debug for TransactionWith +where + T: TransparentPart + fmt::Debug, + S: SaplingPart + fmt::Debug, + O: OrchardPart + fmt::Debug, + T::Bundle: fmt::Debug, + S::Bundle: fmt::Debug, + O::Bundle: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TransactionWith") + .field("txid", &self.txid) + .field("data", &self.data) + .finish() + } +} + +impl Deref for TransactionWith +where + T: TransparentPart, + S: SaplingPart, + O: OrchardPart, +{ + type Target = TransactionData; - fn deref(&self) -> &AuthorizedTransactionData { + fn deref(&self) -> &TransactionData { &self.data } } -impl PartialEq for Transaction { - fn eq(&self, other: &Transaction) -> bool { +impl PartialEq for TransactionWith +where + T: TransparentPart, + S: SaplingPart, + O: OrchardPart, +{ + fn eq(&self, other: &Self) -> bool { self.txid == other.txid } } @@ -592,14 +637,24 @@ impl } } -impl AuthorizedTransactionData { - pub fn freeze(self) -> io::Result { - Transaction::from_data(self) +impl TransactionData +where + T: TransparentEnc + txid::TransparentDigester, + S: SaplingEnc + txid::SaplingDigester, + O: OrchardEnc + txid::OrchardDigester, +{ + pub fn freeze(self) -> io::Result> { + TransactionWith::from_data(self) } } -impl Transaction { - fn from_data(data: AuthorizedTransactionData) -> io::Result { +impl TransactionWith +where + T: TransparentEnc + txid::TransparentDigester, + S: SaplingEnc + txid::SaplingDigester, + O: OrchardEnc + txid::OrchardDigester, +{ + fn from_data(data: TransactionData) -> io::Result { match data.version { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { Self::from_data_v4(data) @@ -610,8 +665,8 @@ impl Transaction { } } - fn from_data_v4(data: AuthorizedTransactionData) -> io::Result { - let mut tx = Transaction { + fn from_data_v4(data: TransactionData) -> io::Result { + let mut tx = TransactionWith { txid: TxId([0; 32]), data, }; @@ -621,17 +676,17 @@ impl Transaction { Ok(tx) } - fn from_data_v5(data: AuthorizedTransactionData) -> Self { + fn from_data_v5(data: TransactionData) -> Self { let txid = to_txid( data.version, data.consensus_branch_id, &data.digest(TxIdDigester), ); - Transaction { txid, data } + TransactionWith { txid, data } } - pub fn into_data(self) -> AuthorizedTransactionData { + pub fn into_data(self) -> TransactionData { self.data } @@ -659,7 +714,7 @@ impl Transaction { version: TxVersion, consensus_branch_id: BranchId, ) -> io::Result { - let transparent_bundle = Transparent::read(&mut reader)?; + let transparent_bundle = T::read(&mut reader)?; let lock_time = reader.read_u32::()?; let expiry_height: BlockHeight = if version.has_overwinter() { @@ -668,7 +723,7 @@ impl Transaction { 0u32.into() }; - let components = Sapling::read_v4_components(&mut reader, version.has_sapling())?; + let components = S::read_v4_components(&mut reader, version.has_sapling())?; let sprout_bundle = if version.has_sprout() { let joinsplits = Vector::read(&mut reader, |r| { @@ -692,13 +747,13 @@ impl Transaction { }; let sapling_bundle = - Sapling::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?; + S::read_v4_binding_sig(&mut reader, version.has_sapling(), components)?; let mut txid = [0; 32]; let hash_bytes = reader.into_hash(); txid.copy_from_slice(&hash_bytes); - Ok(Transaction { + Ok(TransactionWith { txid: TxId(txid), data: TransactionData { version, @@ -725,9 +780,9 @@ 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 = Transparent::read(&mut reader)?; - let sapling_bundle = Sapling::read_v5_bundle(&mut reader)?; - let orchard_bundle = Orchard::read_v5_bundle(&mut reader)?; + let transparent_bundle = T::read(&mut reader)?; + let sapling_bundle = S::read_v5_bundle(&mut reader)?; + let orchard_bundle = O::read_v5_bundle(&mut reader)?; #[cfg(zcash_unstable = "zfuture")] let tze_bundle = if version.has_tze() { @@ -808,7 +863,7 @@ impl Transaction { writer.write_u32::(u32::from(self.expiry_height))?; } - Sapling::write_v4_components( + S::write_v4_components( self.sapling_bundle.as_ref(), &mut writer, self.version.has_sapling(), @@ -824,7 +879,7 @@ impl Transaction { } } - Sapling::write_v4_binding_sig( + S::write_v4_binding_sig( self.sapling_bundle.as_ref(), &mut writer, self.version.has_sapling(), @@ -841,7 +896,7 @@ impl Transaction { } pub fn write_transparent(&self, writer: W) -> io::Result<()> { - Transparent::write(self.transparent_bundle.as_ref(), writer) + T::write(self.transparent_bundle.as_ref(), writer) } pub fn write_v5(&self, mut writer: W) -> io::Result<()> { @@ -854,7 +909,7 @@ impl Transaction { self.write_v5_header(&mut writer)?; self.write_transparent(&mut writer)?; self.write_v5_sapling(&mut writer)?; - Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?; + O::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?; #[cfg(zcash_unstable = "zfuture")] self.write_tze(&mut writer)?; Ok(()) @@ -877,7 +932,7 @@ impl Transaction { } pub fn write_v5_sapling(&self, writer: W) -> io::Result<()> { - Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer) + S::write_v5_bundle(self.sapling_bundle.as_ref(), writer) } #[cfg(zcash_unstable = "zfuture")] @@ -892,7 +947,15 @@ impl Transaction { Ok(()) } +} +impl + TransactionWith< + Transparent, + Sapling, + Orchard, + > +{ // TODO: should this be moved to `from_data` and stored? pub fn auth_commitment(&self) -> Blake2bHash { self.data.digest(BlockTxCommitmentDigester) diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index 8fbded85cc..71ae51934b 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -200,7 +200,7 @@ pub(crate) fn hash_sapling_outputs(shielded_outputs: &[OutputDescription]) h.finalize() } -trait TransparentDigester: TransparentPart { +pub trait TransparentDigester: TransparentPart { fn digest(transparent_bundle: Option<&Self::Bundle>) -> Option>; } @@ -275,7 +275,7 @@ pub(crate) fn hash_transparent_txid_data( h.finalize() } -trait SaplingDigester: SaplingPart { +pub trait SaplingDigester: SaplingPart { fn digest(sapling_bundle: Option<&Self::Bundle>) -> Option; } @@ -312,7 +312,7 @@ fn hash_sapling_txid_empty() -> Blake2bHash { hasher(ZCASH_SAPLING_HASH_PERSONALIZATION).finalize() } -trait OrchardDigester: OrchardPart { +pub trait OrchardDigester: OrchardPart { fn digest(orchard_bundle: Option<&Self::Bundle>) -> Option; }