From 443855e9e9821e43dc9d937cc1516769d73fc675 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 +- zcash_extensions/src/transparent/demo.rs | 2 +- .../src/transaction/components.rs | 16 +- zcash_primitives/src/transaction/mod.rs | 156 ++++++++++++++---- zcash_primitives/src/transaction/txid.rs | 26 +-- 6 files changed, 146 insertions(+), 62 deletions(-) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 9fff7e3a3f..2394cc5686 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::{Transaction, TransactionData, TxVersion}, zip32::AccountId, }; @@ -1266,7 +1266,7 @@ mod tests { [], )?; - let tx = TransactionData::from_parts( + let tx: Transaction = TransactionData::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..08174aa0f5 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, + Transaction, TransactionData, TxVersion, }, }, }; @@ -413,7 +413,7 @@ mod tests { .unwrap(); // create a UTXO to spend - let tx = TransactionData::from_parts( + let tx: Transaction = TransactionData::from_parts( TxVersion::Sapling, BranchId::Canopy, 0, diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 40389cbb31..03d53b536a 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -673,7 +673,7 @@ mod tests { precondition: tze::Precondition::from(0, &Precondition::open(hash_1)), }; - let tx_a = TransactionData::from_parts_zfuture( + let tx_a: Transaction = TransactionData::from_parts_zfuture( TxVersion::ZFuture, BranchId::ZFuture, 0, diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 3d7b66f7b2..e732464662 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -264,9 +264,7 @@ impl TzePart for Tze { 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<()>; @@ -295,7 +293,7 @@ impl TransparentEnc for NoTransparent { } } -pub(super) trait SproutEnc: SproutPart { +pub trait SproutEnc: SproutPart { fn read_v4_bundle( reader: R, tx_has_sprout: bool, @@ -341,9 +339,7 @@ impl SproutEnc for NoSprout { } } -pub(super) trait SaplingEnc: - SaplingPart -{ +pub trait SaplingEnc: SaplingPart { type V4Components; fn read_v4_components( @@ -463,9 +459,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<()>; @@ -488,7 +482,7 @@ impl OrchardEnc for NoOrchard { } #[cfg(zcash_unstable = "zfuture")] -pub(super) trait TzeEnc: TzePart { +pub trait TzeEnc: TzePart { fn read_bundle(reader: R, tx_has_tze: bool) -> io::Result>; fn write_bundle( diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 87e606cf3f..e93ed0d57a 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -299,22 +299,69 @@ impl Authorization for Unauthorized { } /// A Zcash transaction. -#[derive(Debug)] -pub struct Transaction { +pub type Transaction = TransactionWith>; + +/// 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 + B: Bundles, + B::Transparent: TransparentPart, + B::Sapling: SaplingPart, + B::Orchard: OrchardPart, +{ txid: TxId, - data: TransactionData>, + data: TransactionData, } -impl Deref for Transaction { - type Target = TransactionData>; +impl fmt::Debug for TransactionWith +where + B: Bundles + fmt::Debug + FutureDebug, + B::Transparent: TransparentPart + fmt::Debug, + B::Sprout: SproutPart + fmt::Debug, + B::Sapling: SaplingPart + fmt::Debug, + B::Orchard: OrchardPart + fmt::Debug, + ::Bundle: fmt::Debug, + ::Bundle: fmt::Debug, + ::Bundle: fmt::Debug, + ::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() + } +} - fn deref(&self) -> &TransactionData> { +impl Deref for TransactionWith +where + B: Bundles, + B::Transparent: TransparentPart, + B::Sapling: SaplingPart, + B::Orchard: OrchardPart, +{ + type Target = TransactionData; + + fn deref(&self) -> &TransactionData { &self.data } } -impl PartialEq for Transaction { - fn eq(&self, other: &Transaction) -> bool { +impl PartialEq for TransactionWith +where + B: Bundles, + B::Transparent: TransparentPart, + B::Sapling: SaplingPart, + B::Orchard: OrchardPart, +{ + fn eq(&self, other: &Self) -> bool { self.txid == other.txid } } @@ -620,14 +667,47 @@ impl>> Tran } } -impl TransactionData> { - pub fn freeze(self) -> io::Result { - Transaction::from_data(self) +/// Trait marker for bounds that are not yet required by consensus rules. +#[cfg(not(zcash_unstable = "zfuture"))] +pub trait FutureEnc {} +#[cfg(not(zcash_unstable = "zfuture"))] +impl FutureEnc for B {} + +#[cfg(zcash_unstable = "zfuture")] +pub trait FutureEnc: txid::FutureBundles { + type FutureEncTze: TzeEnc; +} +#[cfg(zcash_unstable = "zfuture")] +impl FutureEnc for B +where + B: txid::FutureBundles, + B::FutureTze: TzeEnc, +{ + type FutureEncTze = B::FutureTze; +} + +impl TransactionData +where + B: Bundles + FutureEnc, + B::Transparent: TransparentEnc + txid::TransparentDigester, + B::Sprout: SproutEnc, + B::Sapling: SaplingEnc + txid::SaplingDigester, + B::Orchard: OrchardEnc + txid::OrchardDigester, +{ + pub fn freeze(self) -> io::Result> { + TransactionWith::from_data(self) } } -impl Transaction { - fn from_data(data: TransactionData>) -> io::Result { +impl TransactionWith +where + B: Bundles + FutureEnc, + B::Transparent: TransparentEnc + txid::TransparentDigester, + B::Sprout: SproutEnc, + B::Sapling: SaplingEnc + txid::SaplingDigester, + B::Orchard: OrchardEnc + txid::OrchardDigester, +{ + fn from_data(data: TransactionData) -> io::Result { match data.version { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { Self::from_data_v4(data) @@ -638,8 +718,8 @@ impl Transaction { } } - fn from_data_v4(data: TransactionData>) -> io::Result { - let mut tx = Transaction { + fn from_data_v4(data: TransactionData) -> io::Result { + let mut tx = TransactionWith { txid: TxId([0; 32]), data, }; @@ -649,17 +729,17 @@ impl Transaction { Ok(tx) } - fn from_data_v5(data: TransactionData>) -> 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) -> TransactionData> { + pub fn into_data(self) -> TransactionData { self.data } @@ -687,7 +767,7 @@ impl Transaction { version: TxVersion, consensus_branch_id: BranchId, ) -> io::Result { - let transparent_bundle = Transparent::read(&mut reader)?; + let transparent_bundle = B::Transparent::read(&mut reader)?; let lock_time = reader.read_u32::()?; let expiry_height: BlockHeight = if version.has_overwinter() { @@ -696,19 +776,19 @@ impl Transaction { 0u32.into() }; - let components = Sapling::read_v4_components(&mut reader, version.has_sapling())?; + let components = B::Sapling::read_v4_components(&mut reader, version.has_sapling())?; let sprout_bundle = - Sprout::read_v4_bundle(&mut reader, version.has_sprout(), version.has_sapling())?; + B::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)?; + B::Sapling::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, @@ -735,12 +815,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 = 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 = B::Transparent::read(&mut reader)?; + let sapling_bundle = B::Sapling::read_v5_bundle(&mut reader)?; + let orchard_bundle = B::Orchard::read_v5_bundle(&mut reader)?; #[cfg(zcash_unstable = "zfuture")] - let tze_bundle = Tze::read_bundle(&mut reader, version.has_tze())?; + let tze_bundle = B::Tze::read_bundle(&mut reader, version.has_tze())?; let data = TransactionData { version, @@ -799,19 +879,19 @@ impl Transaction { writer.write_u32::(u32::from(self.expiry_height))?; } - Sapling::write_v4_components( + B::Sapling::write_v4_components( self.sapling_bundle.as_ref(), &mut writer, self.version.has_sapling(), )?; - Sprout::write_v4_bundle( + B::Sprout::write_v4_bundle( self.sprout_bundle.as_ref(), &mut writer, self.version.has_sprout(), )?; - Sapling::write_v4_binding_sig( + B::Sapling::write_v4_binding_sig( self.sapling_bundle.as_ref(), &mut writer, self.version.has_sapling(), @@ -828,7 +908,7 @@ impl Transaction { } pub fn write_transparent(&self, writer: W) -> io::Result<()> { - Transparent::write(self.transparent_bundle.as_ref(), writer) + B::Transparent::write(self.transparent_bundle.as_ref(), writer) } pub fn write_v5(&self, mut writer: W) -> io::Result<()> { @@ -841,7 +921,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)?; + B::Orchard::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?; #[cfg(zcash_unstable = "zfuture")] self.write_tze(&mut writer)?; Ok(()) @@ -864,14 +944,22 @@ impl Transaction { } pub fn write_v5_sapling(&self, writer: W) -> io::Result<()> { - Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer) + B::Sapling::write_v5_bundle(self.sapling_bundle.as_ref(), writer) } #[cfg(zcash_unstable = "zfuture")] pub fn write_tze(&self, writer: W) -> io::Result<()> { - Tze::write_bundle(self.tze_bundle.as_ref(), writer, self.version.has_tze()) + B::Tze::write_bundle(self.tze_bundle.as_ref(), writer, self.version.has_tze()) } +} +impl TransactionWith +where + B: Bundles + txid::FutureWitnesses, + B::Transparent: txid::TransparentWitnessDigester, + B::Sapling: txid::SaplingWitnessDigester, + B::Orchard: txid::OrchardWitnessDigester, +{ // 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 f80863ab27..34d302446b 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -203,7 +203,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>; } @@ -235,7 +235,7 @@ fn transparent_digests( } #[cfg(zcash_unstable = "zfuture")] -trait TzeDigester: TzePart { +pub trait TzeDigester: TzePart { fn digest(tze_bundle: Option<&Self::Bundle>) -> Option>; } @@ -290,7 +290,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; } @@ -327,7 +327,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; } @@ -358,12 +358,12 @@ fn hash_tze_txid_data(tze_digests: Option<&TzeDigests>) -> Blake2bH /// Trait marker for bounds that are not yet required by consensus rules. #[cfg(not(zcash_unstable = "zfuture"))] -trait FutureBundles {} +pub trait FutureBundles {} #[cfg(not(zcash_unstable = "zfuture"))] impl FutureBundles for B {} #[cfg(zcash_unstable = "zfuture")] -trait FutureBundles: Bundles { +pub trait FutureBundles: Bundles { type FutureTze: TzeDigester; } #[cfg(zcash_unstable = "zfuture")] @@ -515,7 +515,9 @@ pub fn to_txid( TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap()) } -trait TransparentWitnessDigester: TransparentPart { +pub trait TransparentWitnessDigester: + TransparentPart +{ fn witness_digest(transparent_bundle: Option<&Self::Bundle>) -> Blake2bHash; } @@ -531,7 +533,7 @@ impl TransparentWitnessDigester for Transparent { } } -trait SaplingWitnessDigester: SaplingPart { +pub trait SaplingWitnessDigester: SaplingPart { fn witness_digest(sapling_bundle: Option<&Self::Bundle>) -> Blake2bHash; } @@ -559,7 +561,7 @@ impl SaplingWitnessDigester for Sapling { } } -trait OrchardWitnessDigester: OrchardPart { +pub trait OrchardWitnessDigester: OrchardPart { fn witness_digest(orchard_bundle: Option<&Self::Bundle>) -> Blake2bHash; } @@ -572,7 +574,7 @@ impl OrchardWitnessDigester for Orchard { } #[cfg(zcash_unstable = "zfuture")] -trait TzeWitnessDigester: TzePart { +pub trait TzeWitnessDigester: TzePart { fn witness_digest(tze_bundle: Option<&Self::Bundle>) -> Blake2bHash; } @@ -591,12 +593,12 @@ impl TzeWitnessDigester for Tze { /// Trait marker for bounds that are not yet required by consensus rules. #[cfg(not(zcash_unstable = "zfuture"))] -trait FutureWitnesses {} +pub trait FutureWitnesses {} #[cfg(not(zcash_unstable = "zfuture"))] impl FutureWitnesses for B {} #[cfg(zcash_unstable = "zfuture")] -trait FutureWitnesses: Bundles { +pub trait FutureWitnesses: Bundles { type FutureTze: TzeWitnessDigester; } #[cfg(zcash_unstable = "zfuture")]