From 56ee59e8c98804cefda86c179b0f0b3e06105800 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 10 May 2024 22:25:32 +0000 Subject: [PATCH] zcash_primitives: Generalise `TransactionData` protocol-specific bundles --- devtools/src/bin/inspect/transaction.rs | 6 +- .../init/migrations/tx_retrieval_queue.rs | 4 +- zcash_primitives/src/transaction/builder.rs | 9 +- .../src/transaction/components.rs | 29 +++ .../components/transparent/builder.rs | 11 +- .../src/transaction/components/tze/builder.rs | 4 +- zcash_primitives/src/transaction/mod.rs | 201 ++++++++++++------ zcash_primitives/src/transaction/sighash.rs | 29 ++- .../src/transaction/sighash_v4.rs | 31 +-- .../src/transaction/sighash_v5.rs | 35 ++- zcash_primitives/src/transaction/tests.rs | 6 +- zcash_primitives/src/transaction/txid.rs | 94 ++++++-- 12 files changed, 320 insertions(+), 139 deletions(-) diff --git a/devtools/src/bin/inspect/transaction.rs b/devtools/src/bin/inspect/transaction.rs index 3521430765..b27b7fdbb7 100644 --- a/devtools/src/bin/inspect/transaction.rs +++ b/devtools/src/bin/inspect/transaction.rs @@ -19,7 +19,9 @@ use zcash_primitives::{ legacy::{keys::pubkey_to_address, Script, TransparentAddress}, memo::{Memo, MemoBytes}, transaction::{ - components::{amount::NonNegativeAmount, sapling as sapling_serialization, transparent}, + components::{ + amount::NonNegativeAmount, sapling as sapling_serialization, transparent, AllBundles, + }, sighash::{signature_hash, SignableInput, TransparentAuthorizingContext}, txid::TxIdDigester, Authorization, Transaction, TransactionData, TxId, TxVersion, @@ -207,7 +209,7 @@ pub(crate) fn inspect( tx.write(&mut buf).unwrap(); let tx = Transaction::read(&buf[..], tx.consensus_branch_id()).unwrap(); - let tx: TransactionData = tx.into_data().map_authorization( + let tx: TransactionData> = tx.into_data().map_authorization( f_transparent, (), (), diff --git a/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs b/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs index 5c94abbc2c..f91e8c6d53 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs @@ -149,7 +149,7 @@ mod tests { use tempfile::NamedTempFile; use zcash_primitives::{ legacy::{Script, TransparentAddress}, - transaction::{components::transparent, Authorized, TransactionData, TxVersion}, + transaction::{components::{transparent, AllBundles}, Authorized, TransactionData, TxVersion}, }; use zcash_protocol::{ consensus::{BranchId, Network}, @@ -185,7 +185,7 @@ mod tests { .unwrap(); // Add transactions to the wallet that exercise the data migration. - let add_tx_to_wallet = |tx: TransactionData| { + let add_tx_to_wallet = |tx: TransactionData>| { let tx = tx.freeze().unwrap(); let txid = tx.txid(); let mut raw_tx = vec![]; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index b40655dc55..6edd9bc938 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -21,6 +21,7 @@ use crate::{ components::{ amount::{Amount, BalanceError}, transparent::{self, builder::TransparentBuilder, TxOut}, + AllBundles, }, fees::{ transparent::{InputView, OutputView}, @@ -288,7 +289,7 @@ pub struct Builder<'a, P, U: sapling::builder::ProverProgress> { sapling_asks: Vec, orchard_saks: Vec, #[cfg(zcash_unstable = "zfuture")] - tze_builder: TzeBuilder<'a, TransactionData>, + tze_builder: TzeBuilder<'a, TransactionData>>, #[cfg(not(zcash_unstable = "zfuture"))] tze_builder: std::marker::PhantomData<&'a ()>, progress_notifier: U, @@ -746,7 +747,7 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder< #[cfg(zcash_unstable = "zfuture")] let (tze_bundle, tze_signers) = self.tze_builder.build(); - let unauthed_tx: TransactionData = TransactionData { + let unauthed_tx: TransactionData> = TransactionData { version, consensus_branch_id: BranchId::for_height(&self.params, self.target_height), lock_time: 0, @@ -765,7 +766,7 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder< let txid_parts = unauthed_tx.digest(TxIdDigester); let transparent_bundle = unauthed_tx.transparent_bundle.clone().map(|b| { - b.apply_signatures( + b.apply_signatures::<_, _, AllBundles>( #[cfg(feature = "transparent-inputs")] &unauthed_tx, #[cfg(feature = "transparent-inputs")] @@ -841,7 +842,7 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder< impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> ExtensionTxBuilder<'a> for Builder<'a, P, U> { - type BuildCtx = TransactionData; + type BuildCtx = TransactionData>; type BuildError = tze::builder::Error; fn add_tze_input( diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 802923e115..799edc8e2f 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -34,9 +34,38 @@ pub use crate::sapling::bundle::{OutputDescription, SpendDescription}; #[cfg(zcash_unstable = "zfuture")] pub use self::tze::{TzeIn, TzeOut}; +use super::Authorization; + // π_A + π_B + π_C pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; +/// The protocol-specific bundles of data within a transaction. +pub trait Bundles { + type Transparent: TransparentPart; + type Sprout: SproutPart; + type Sapling: SaplingPart; + type Orchard: OrchardPart; + + #[cfg(zcash_unstable = "zfuture")] + type Tze: TzePart; +} + +/// Marker type for a transaction that may contain payments within any Zcash protocol. +#[derive(Debug)] +pub struct AllBundles { + _auth: PhantomData, +} + +impl Bundles for AllBundles { + type Transparent = Transparent; + type Sprout = Sprout; + type Sapling = Sapling; + type Orchard = Orchard; + + #[cfg(zcash_unstable = "zfuture")] + type Tze = Tze; +} + /// The protocol-agnostic parts of a shielded bundle. /// /// The trait methods can be implemented without any knowledge of protocol-specific diff --git a/zcash_primitives/src/transaction/components/transparent/builder.rs b/zcash_primitives/src/transaction/components/transparent/builder.rs index c275065fe1..5954201cdc 100644 --- a/zcash_primitives/src/transaction/components/transparent/builder.rs +++ b/zcash_primitives/src/transaction/components/transparent/builder.rs @@ -5,6 +5,7 @@ use std::fmt; use crate::{ legacy::{Script, TransparentAddress}, transaction::{ + self as tx, components::{ amount::{Amount, BalanceError, NonNegativeAmount}, transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut}, @@ -16,7 +17,6 @@ use crate::{ #[cfg(feature = "transparent-inputs")] use { crate::transaction::{ - self as tx, components::transparent::OutPoint, sighash::{signature_hash, SignableInput, SIGHASH_ALL}, TransactionData, TxDigests, @@ -239,9 +239,14 @@ impl TransparentAuthorizingContext for Unauthorized { } impl Bundle { - pub fn apply_signatures( + pub fn apply_signatures< + Sp: tx::sighash_v4::SproutSigDigester, + Sa: tx::sighash_v4::SaplingSigDigester, + B: tx::Bundles, Sprout = Sp, Sapling = Sa> + + tx::sighash_v5::FutureBundles, + >( self, - #[cfg(feature = "transparent-inputs")] mtx: &TransactionData, + #[cfg(feature = "transparent-inputs")] mtx: &TransactionData, #[cfg(feature = "transparent-inputs")] txid_parts_cache: &TxDigests, ) -> Bundle { #[cfg(feature = "transparent-inputs")] diff --git a/zcash_primitives/src/transaction/components/tze/builder.rs b/zcash_primitives/src/transaction/components/tze/builder.rs index 974dac9ce8..2eb8243019 100644 --- a/zcash_primitives/src/transaction/components/tze/builder.rs +++ b/zcash_primitives/src/transaction/components/tze/builder.rs @@ -157,8 +157,8 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> { impl Bundle { pub fn into_authorized( self, - unauthed_tx: &tx::TransactionData, - signers: Vec>>, + unauthed_tx: &tx::TransactionData>, + signers: Vec>>>, ) -> Result, Error> { // Create TZE input witnesses let payloads = signers diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index adf1dffdf1..3337b5774e 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -32,14 +32,18 @@ use self::{ orchard as orchard_serialization, sapling as sapling_serialization, sprout::{self, JsDescription}, transparent::{self, TxIn, TxOut}, - OutPoint, + AllBundles, 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}; +use self::components::{ + tze::{self, TzeIn, TzeOut}, + Tze, TzePart, +}; const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270; const OVERWINTER_TX_VERSION: u32 = 3; @@ -301,13 +305,13 @@ impl Authorization for Unauthorized { #[derive(Debug)] pub struct Transaction { txid: TxId, - data: TransactionData, + data: TransactionData>, } impl Deref for Transaction { - type Target = TransactionData; + type Target = TransactionData>; - fn deref(&self) -> &TransactionData { + fn deref(&self) -> &TransactionData> { &self.data } } @@ -319,21 +323,70 @@ impl PartialEq for Transaction { } /// The information contained in a Zcash transaction. -#[derive(Debug)] -pub struct TransactionData { +pub struct TransactionData { version: TxVersion, consensus_branch_id: BranchId, lock_time: u32, expiry_height: BlockHeight, - transparent_bundle: Option>, - sprout_bundle: Option, - sapling_bundle: Option>, - orchard_bundle: Option>, + transparent_bundle: Option<::Bundle>, + sprout_bundle: Option<::Bundle>, + sapling_bundle: Option<::Bundle>, + orchard_bundle: Option<::Bundle>, #[cfg(zcash_unstable = "zfuture")] - tze_bundle: Option>, + tze_bundle: Option<::Bundle>, +} + +/// Trait marker for bounds that are not yet required by consensus rules. +#[cfg(not(zcash_unstable = "zfuture"))] +trait FutureDebug {} +#[cfg(not(zcash_unstable = "zfuture"))] +impl FutureDebug for B {} + +#[cfg(zcash_unstable = "zfuture")] +trait FutureDebug: Bundles { + type FutureTze: TzePart + fmt::Debug; + type FutureTzeBundle: fmt::Debug; +} +#[cfg(zcash_unstable = "zfuture")] +impl FutureDebug for B +where + B: Bundles, + B::Tze: TzePart + fmt::Debug, + ::Bundle: fmt::Debug, +{ + type FutureTze = B::Tze; + type FutureTzeBundle = ::Bundle; +} + +impl fmt::Debug for TransactionData +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 { + let mut s = f.debug_struct("TransactionData"); + s.field("version", &self.version) + .field("consensus_branch_id", &self.consensus_branch_id) + .field("lock_time", &self.lock_time) + .field("expiry_height", &self.expiry_height) + .field("transparent_bundle", &self.transparent_bundle) + .field("sprout_bundle", &self.sprout_bundle) + .field("sapling_bundle", &self.sapling_bundle) + .field("orchard_bundle", &self.orchard_bundle); + #[cfg(zcash_unstable = "zfuture")] + s.field("tze_bundle", &self.tze_bundle); + s.finish() + } } -impl TransactionData { +impl TransactionData { /// Constructs a `TransactionData` from its constituent parts. #[allow(clippy::too_many_arguments)] pub fn from_parts( @@ -341,10 +394,10 @@ impl TransactionData { consensus_branch_id: BranchId, lock_time: u32, expiry_height: BlockHeight, - transparent_bundle: Option>, - sprout_bundle: Option, - sapling_bundle: Option>, - orchard_bundle: Option>, + transparent_bundle: Option<::Bundle>, + sprout_bundle: Option<::Bundle>, + sapling_bundle: Option<::Bundle>, + orchard_bundle: Option<::Bundle>, ) -> Self { TransactionData { version, @@ -369,11 +422,11 @@ impl TransactionData { consensus_branch_id: BranchId, lock_time: u32, expiry_height: BlockHeight, - transparent_bundle: Option>, - sprout_bundle: Option, - sapling_bundle: Option>, - orchard_bundle: Option>, - tze_bundle: Option>, + transparent_bundle: Option<::Bundle>, + sprout_bundle: Option<::Bundle>, + sapling_bundle: Option<::Bundle>, + orchard_bundle: Option<::Bundle>, + tze_bundle: Option<::Bundle>, ) -> Self { TransactionData { version, @@ -405,28 +458,40 @@ impl TransactionData { pub fn expiry_height(&self) -> BlockHeight { self.expiry_height } +} - pub fn transparent_bundle(&self) -> Option<&transparent::Bundle> { +impl>> TransactionData { + pub fn transparent_bundle(&self) -> Option<&transparent::Bundle> { self.transparent_bundle.as_ref() } +} +impl> TransactionData { pub fn sprout_bundle(&self) -> Option<&sprout::Bundle> { self.sprout_bundle.as_ref() } +} - pub fn sapling_bundle(&self) -> Option<&sapling::Bundle> { +impl>> TransactionData { + pub fn sapling_bundle(&self) -> Option<&sapling::Bundle> { self.sapling_bundle.as_ref() } +} - pub fn orchard_bundle(&self) -> Option<&orchard::Bundle> { +impl>> TransactionData { + pub fn orchard_bundle(&self) -> Option<&orchard::Bundle> { self.orchard_bundle.as_ref() } +} - #[cfg(zcash_unstable = "zfuture")] - pub fn tze_bundle(&self) -> Option<&tze::Bundle> { +#[cfg(zcash_unstable = "zfuture")] +impl>> TransactionData { + pub fn tze_bundle(&self) -> Option<&tze::Bundle> { self.tze_bundle.as_ref() } +} +impl TransactionData { /// Returns the total fees paid by the transaction, given a function that can be used to /// retrieve the value of previous transactions' transparent outputs that are being spent in /// this transaction. @@ -436,19 +501,19 @@ impl TransactionData { F: FnMut(&OutPoint) -> Result, { let value_balances = [ - self.transparent_bundle - .as_ref() - .map_or_else(|| Ok(Amount::zero()), |b| b.value_balance(get_prevout))?, - self.sprout_bundle.as_ref().map_or_else( + self.transparent_bundle.as_ref().map_or_else( || Ok(Amount::zero()), - |b| b.value_balance().ok_or(BalanceError::Overflow), + |b| B::Transparent::value_balance(b, get_prevout), )?, + self.sprout_bundle + .as_ref() + .map_or_else(Amount::zero, |b| b.value_balance()), self.sapling_bundle .as_ref() - .map_or_else(Amount::zero, |b| *b.value_balance()), + .map_or_else(Amount::zero, |b| b.value_balance()), self.orchard_bundle .as_ref() - .map_or_else(Amount::zero, |b| *b.value_balance()), + .map_or_else(Amount::zero, |b| b.value_balance()), ]; value_balances @@ -457,7 +522,7 @@ impl TransactionData { .ok_or_else(|| BalanceError::Overflow.into()) } - pub fn digest>(&self, digester: D) -> D::Digest { + pub fn digest>(&self, digester: D) -> D::Digest { digester.combine( digester.digest_header( self.version, @@ -477,43 +542,48 @@ impl TransactionData { /// /// This shouldn't be necessary for most use cases; it is provided for handling the /// cross-FFI builder logic in `zcashd`. - pub fn map_bundles( + pub fn map_bundles( self, f_transparent: impl FnOnce( - Option>, - ) -> Option>, + Option<::Bundle>, + ) -> Option<::Bundle>, + f_sprout: impl FnOnce( + Option<::Bundle>, + ) -> Option<::Bundle>, f_sapling: impl FnOnce( - Option>, - ) -> Option>, + Option<::Bundle>, + ) -> Option<::Bundle>, f_orchard: impl FnOnce( - Option>, - ) -> Option>, + Option<::Bundle>, + ) -> Option<::Bundle>, #[cfg(zcash_unstable = "zfuture")] f_tze: impl FnOnce( - Option>, + Option<::Bundle>, ) - -> Option>, - ) -> TransactionData { + -> Option<::Bundle>, + ) -> TransactionData { TransactionData { version: self.version, consensus_branch_id: self.consensus_branch_id, lock_time: self.lock_time, expiry_height: self.expiry_height, transparent_bundle: f_transparent(self.transparent_bundle), - sprout_bundle: self.sprout_bundle, + sprout_bundle: f_sprout(self.sprout_bundle), sapling_bundle: f_sapling(self.sapling_bundle), orchard_bundle: f_orchard(self.orchard_bundle), #[cfg(zcash_unstable = "zfuture")] tze_bundle: f_tze(self.tze_bundle), } } +} - pub fn map_authorization( +impl TransactionData> { + pub fn map_authorization( self, - f_transparent: impl transparent::MapAuth, - mut f_sapling: impl sapling_serialization::MapAuth, - mut f_orchard: impl orchard_serialization::MapAuth, - #[cfg(zcash_unstable = "zfuture")] f_tze: impl tze::MapAuth, - ) -> TransactionData { + f_transparent: impl transparent::MapAuth, + mut f_sapling: impl sapling_serialization::MapAuth, + mut f_orchard: impl orchard_serialization::MapAuth, + #[cfg(zcash_unstable = "zfuture")] f_tze: impl tze::MapAuth, + ) -> TransactionData> { TransactionData { version: self.version, consensus_branch_id: self.consensus_branch_id, @@ -545,7 +615,7 @@ impl TransactionData { } } -impl TransactionData { +impl>> TransactionData { pub fn sapling_value_balance(&self) -> Amount { self.sapling_bundle .as_ref() @@ -553,14 +623,14 @@ impl TransactionData { } } -impl TransactionData { +impl TransactionData> { pub fn freeze(self) -> io::Result { Transaction::from_data(self) } } impl Transaction { - fn from_data(data: TransactionData) -> io::Result { + fn from_data(data: TransactionData>) -> io::Result { match data.version { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { Self::from_data_v4(data) @@ -571,7 +641,7 @@ impl Transaction { } } - fn from_data_v4(data: TransactionData) -> io::Result { + fn from_data_v4(data: TransactionData>) -> io::Result { let mut tx = Transaction { txid: TxId([0; 32]), data, @@ -582,7 +652,7 @@ 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, @@ -592,7 +662,7 @@ impl Transaction { Transaction { txid, data } } - pub fn into_data(self) -> TransactionData { + pub fn into_data(self) -> TransactionData> { self.data } @@ -923,7 +993,7 @@ pub struct TxDigests { pub tze_digests: Option>, } -pub trait TransactionDigest { +pub trait TransactionDigest { type HeaderDigest; type TransparentDigest; type SaplingDigest; @@ -944,21 +1014,21 @@ pub trait TransactionDigest { fn digest_transparent( &self, - transparent_bundle: Option<&transparent::Bundle>, + transparent_bundle: Option<&::Bundle>, ) -> Self::TransparentDigest; fn digest_sapling( &self, - sapling_bundle: Option<&sapling::Bundle>, + sapling_bundle: Option<&::Bundle>, ) -> Self::SaplingDigest; fn digest_orchard( &self, - orchard_bundle: Option<&orchard::Bundle>, + orchard_bundle: Option<&::Bundle>, ) -> Self::OrchardDigest; #[cfg(zcash_unstable = "zfuture")] - fn digest_tze(&self, tze_bundle: Option<&tze::Bundle>) -> Self::TzeDigest; + fn digest_tze(&self, tze_bundle: Option<&::Bundle>) -> Self::TzeDigest; fn combine( &self, @@ -985,6 +1055,7 @@ pub mod testing { orchard::testing::{self as orchard}, sapling::testing::{self as sapling}, transparent::testing::{self as transparent}, + AllBundles, }, Authorized, Transaction, TransactionData, TxId, TxVersion, }; @@ -1021,7 +1092,7 @@ pub mod testing { sapling_bundle in sapling::arb_bundle_for_version(version), orchard_bundle in orchard::arb_bundle_for_version(version), version in Just(version) - ) -> TransactionData { + ) -> TransactionData> { TransactionData { version, consensus_branch_id, @@ -1047,7 +1118,7 @@ pub mod testing { orchard_bundle in orchard::arb_bundle_for_version(version), tze_bundle in tze::arb_bundle(consensus_branch_id), version in Just(version) - ) -> TransactionData { + ) -> TransactionData> { TransactionData { version, consensus_branch_id, diff --git a/zcash_primitives/src/transaction/sighash.rs b/zcash_primitives/src/transaction/sighash.rs index 4fd1c3e988..88b7e32545 100644 --- a/zcash_primitives/src/transaction/sighash.rs +++ b/zcash_primitives/src/transaction/sighash.rs @@ -1,15 +1,12 @@ use blake2b_simd::Hash as Blake2bHash; use super::{ - components::{amount::NonNegativeAmount, transparent}, - sighash_v4::v4_signature_hash, - sighash_v5::v5_signature_hash, - Authorization, TransactionData, TxDigests, TxVersion, -}; -use crate::{ - legacy::Script, - sapling::{self, bundle::GrothProofBytes}, + components::{amount::NonNegativeAmount, transparent, Bundles}, + sighash_v4::{self, v4_signature_hash}, + sighash_v5::{self, v5_signature_hash}, + TransactionData, TxDigests, TxVersion, }; +use crate::legacy::Script; #[cfg(zcash_unstable = "zfuture")] use {super::components::Amount, crate::extensions::transparent::Precondition}; @@ -75,15 +72,17 @@ pub trait TransparentAuthorizingContext: transparent::Authorization { /// the full data of the transaction, the input being signed, and the /// set of precomputed hashes produced in the construction of the /// transaction ID. -pub fn signature_hash< - TA: TransparentAuthorizingContext, - SA: sapling::bundle::Authorization, - A: Authorization, ->( - tx: &TransactionData, +pub fn signature_hash( + tx: &TransactionData, signable_input: &SignableInput, txid_parts: &TxDigests, -) -> SignatureHash { +) -> SignatureHash +where + B: Bundles + sighash_v5::FutureBundles, + B::Transparent: sighash_v4::TransparentSigDigester + sighash_v5::TransparentSigDigester, + B::Sprout: sighash_v4::SproutSigDigester, + B::Sapling: sighash_v4::SaplingSigDigester, +{ SignatureHash(match tx.version { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { v4_signature_hash(tx, signable_input) diff --git a/zcash_primitives/src/transaction/sighash_v4.rs b/zcash_primitives/src/transaction/sighash_v4.rs index bccd3aa89c..5ca806dc2e 100644 --- a/zcash_primitives/src/transaction/sighash_v4.rs +++ b/zcash_primitives/src/transaction/sighash_v4.rs @@ -15,10 +15,11 @@ use super::{ sapling as sapling_serialization, sprout::JsDescription, transparent::{self, TxIn, TxOut}, - Sapling, SaplingPart, ShieldedBundle, Sprout, SproutPart, Transparent, TransparentPart, + Bundles, Sapling, SaplingPart, ShieldedBundle, Sprout, SproutPart, Transparent, + TransparentPart, }, sighash::{SignableInput, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, SIGHASH_SINGLE}, - Authorization, TransactionData, + TransactionData, }; const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash"; @@ -279,13 +280,15 @@ fn shielded_outputs_hash(shielded_outputs: &[OutputDescription] .hash(&data) } -pub fn v4_signature_hash( - tx: &TransactionData, +pub fn v4_signature_hash( + tx: &TransactionData, signable_input: &SignableInput<'_>, ) -> Blake2bHash where - SA: sapling::bundle::Authorization, - A: Authorization, + B: Bundles, + B::Transparent: TransparentSigDigester, + B::Sprout: SproutSigDigester, + B::Sapling: SaplingSigDigester, { let hash_type = signable_input.hash_type(); if tx.version.has_overwinter() { @@ -303,22 +306,22 @@ where update_hash!( h, hash_type & SIGHASH_ANYONECANPAY == 0, - Transparent::digest_prevout(tx.transparent_bundle.as_ref()) + B::Transparent::digest_prevout(tx.transparent_bundle.as_ref()) ); update_hash!( h, (hash_type & SIGHASH_ANYONECANPAY) == 0 && (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE && (hash_type & SIGHASH_MASK) != SIGHASH_NONE, - Transparent::digest_sequence(tx.transparent_bundle.as_ref()) + B::Transparent::digest_sequence(tx.transparent_bundle.as_ref()) ); if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE && (hash_type & SIGHASH_MASK) != SIGHASH_NONE { - h.update(Transparent::digest_outputs(tx.transparent_bundle.as_ref()).as_bytes()); + h.update(B::Transparent::digest_outputs(tx.transparent_bundle.as_ref()).as_bytes()); } else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE { - h.update(&Transparent::digest_single_output( + h.update(&B::Transparent::digest_single_output( tx.transparent_bundle.as_ref(), signable_input, )); @@ -326,14 +329,14 @@ where h.update(&[0; 32]); }; - h.update(&Sprout::digest_joinsplits( + h.update(&B::Sprout::digest_joinsplits( tx.consensus_branch_id, tx.sprout_bundle.as_ref(), )); if tx.version.has_sapling() { - h.update(&Sapling::digest_spends(tx.sapling_bundle.as_ref())); - h.update(&Sapling::digest_outputs(tx.sapling_bundle.as_ref())); + h.update(&B::Sapling::digest_spends(tx.sapling_bundle.as_ref())); + h.update(&B::Sapling::digest_outputs(tx.sapling_bundle.as_ref())); } h.update(&tx.lock_time.to_le_bytes()); h.update(&u32::from(tx.expiry_height).to_le_bytes()); @@ -350,7 +353,7 @@ where match signable_input { SignableInput::Shielded => (), SignableInput::Transparent { .. } => { - h.update(&Transparent::digest_signable_input( + h.update(&B::Transparent::digest_signable_input( tx.transparent_bundle.as_ref(), signable_input, )); diff --git a/zcash_primitives/src/transaction/sighash_v5.rs b/zcash_primitives/src/transaction/sighash_v5.rs index 8bd0450eb4..990f0d2d8f 100644 --- a/zcash_primitives/src/transaction/sighash_v5.rs +++ b/zcash_primitives/src/transaction/sighash_v5.rs @@ -6,7 +6,7 @@ use zcash_encoding::Array; use crate::transaction::{ components::{ transparent::{self, TxOut}, - Transparent, TransparentPart, + Bundles, Transparent, TransparentPart, }, sighash::{ SignableInput, TransparentAuthorizingContext, SIGHASH_ANYONECANPAY, SIGHASH_MASK, @@ -16,7 +16,7 @@ use crate::transaction::{ hash_transparent_txid_data, to_hash, transparent_outputs_hash, transparent_prevout_hash, transparent_sequence_hash, ZCASH_TRANSPARENT_HASH_PERSONALIZATION, }, - Authorization, TransactionData, TransparentDigests, TxDigests, + TransactionData, TransparentDigests, TxDigests, }; #[cfg(zcash_unstable = "zfuture")] @@ -210,15 +210,34 @@ fn tze_input_sigdigests( } } +/// Trait marker for bounds that are not yet required by consensus rules. +#[cfg(not(zcash_unstable = "zfuture"))] +pub trait FutureBundles {} +#[cfg(not(zcash_unstable = "zfuture"))] +impl FutureBundles for B {} + +#[cfg(zcash_unstable = "zfuture")] +pub trait FutureBundles: Bundles { + type FutureTze: TzeSigDigester; +} +#[cfg(zcash_unstable = "zfuture")] +impl FutureBundles for B +where + B: Bundles, + B::Tze: TzeSigDigester, +{ + type FutureTze = B::Tze; +} + /// Implements the [Signature Digest section of ZIP 244](https://zips.z.cash/zip-0244#signature-digest) -pub fn v5_signature_hash( - tx: &TransactionData, +pub fn v5_signature_hash( + tx: &TransactionData, signable_input: &SignableInput<'_>, txid_parts: &TxDigests, ) -> Blake2bHash where - TA: TransparentAuthorizingContext, - A: Authorization, + B: Bundles + FutureBundles, + B::Transparent: TransparentSigDigester, { // The caller must provide the transparent digests if and only if the transaction has a // transparent component. @@ -231,7 +250,7 @@ where tx.version, tx.consensus_branch_id, txid_parts.header_digest, - Transparent::digest( + B::Transparent::digest( tx.transparent_bundle .as_ref() .zip(txid_parts.transparent_digests.as_ref()), @@ -240,7 +259,7 @@ where txid_parts.sapling_digest, txid_parts.orchard_digest, #[cfg(zcash_unstable = "zfuture")] - Tze::digest( + B::Tze::digest( tx.tze_bundle.as_ref().zip(txid_parts.tze_digests.as_ref()), signable_input, ) diff --git a/zcash_primitives/src/transaction/tests.rs b/zcash_primitives/src/transaction/tests.rs index 6aa63b29c8..e181dd5e1e 100644 --- a/zcash_primitives/src/transaction/tests.rs +++ b/zcash_primitives/src/transaction/tests.rs @@ -18,7 +18,7 @@ use super::{ testing::arb_tx, transparent::{self}, txid::TxIdDigester, - Authorization, Transaction, TransactionData, TxDigests, TxIn, + AllBundles, Authorization, Transaction, TransactionData, TxDigests, TxIn, }; #[cfg(zcash_unstable = "zfuture")] @@ -206,11 +206,13 @@ impl Authorization for TestUnauthorized { type TzeAuth = tze::Authorized; } +type TestUnauthorizedTransactionData = TransactionData>; + #[test] fn zip_0244() { fn to_test_txdata( tv: &self::data::zip_0244::TestVector, - ) -> (TransactionData, TxDigests) { + ) -> (TestUnauthorizedTransactionData, TxDigests) { let tx = Transaction::read(&tv.tx[..], BranchId::Nu5).unwrap(); assert_eq!(tx.txid.as_ref(), &tv.txid); diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index 262f906650..e94e730c9f 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -19,10 +19,10 @@ use super::{ components::{ amount::Amount, transparent::{self, TxIn, TxOut}, - AuthorizedOrchardPart, AuthorizedSaplingPart, AuthorizedTransparentPart, Orchard, + AuthorizedOrchardPart, AuthorizedSaplingPart, AuthorizedTransparentPart, Bundles, Orchard, OrchardPart, Sapling, SaplingPart, Transparent, TransparentPart, }, - Authorization, Authorized, TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion, + TransactionDigest, TransparentDigests, TxDigests, TxId, TxVersion, }; #[cfg(zcash_unstable = "zfuture")] @@ -338,6 +338,25 @@ fn hash_tze_txid_data(tze_digests: Option<&TzeDigests>) -> Blake2bH h.finalize() } +/// Trait marker for bounds that are not yet required by consensus rules. +#[cfg(not(zcash_unstable = "zfuture"))] +pub trait FutureBundles {} +#[cfg(not(zcash_unstable = "zfuture"))] +impl FutureBundles for B {} + +#[cfg(zcash_unstable = "zfuture")] +pub trait FutureBundles: Bundles { + type FutureTze: TzeDigester; +} +#[cfg(zcash_unstable = "zfuture")] +impl FutureBundles for B +where + B: Bundles, + B::Tze: TzeDigester, +{ + type FutureTze = B::Tze; +} + /// A TransactionDigest implementation that commits to all of the effecting /// data of a transaction to produce a nonmalleable transaction identifier. /// @@ -347,7 +366,13 @@ fn hash_tze_txid_data(tze_digests: Option<&TzeDigests>) -> Blake2bH /// This implements the [TxId Digest section of ZIP 244](https://zips.z.cash/zip-0244#txid-digest) pub struct TxIdDigester; -impl TransactionDigest for TxIdDigester { +impl TransactionDigest for TxIdDigester +where + B: Bundles + FutureBundles, + B::Transparent: TransparentDigester, + B::Sapling: SaplingDigester, + B::Orchard: OrchardDigester, +{ type HeaderDigest = Blake2bHash; type TransparentDigest = Option>; type SaplingDigest = Option; @@ -370,28 +395,28 @@ impl TransactionDigest for TxIdDigester { fn digest_transparent( &self, - transparent_bundle: Option<&transparent::Bundle>, + transparent_bundle: Option<&::Bundle>, ) -> Self::TransparentDigest { - Transparent::digest(transparent_bundle) + B::Transparent::digest(transparent_bundle) } fn digest_sapling( &self, - sapling_bundle: Option<&sapling::Bundle>, + sapling_bundle: Option<&::Bundle>, ) -> Self::SaplingDigest { - Sapling::digest(sapling_bundle) + B::Sapling::digest(sapling_bundle) } fn digest_orchard( &self, - orchard_bundle: Option<&orchard::Bundle>, + orchard_bundle: Option<&::Bundle>, ) -> Self::OrchardDigest { - Orchard::digest(orchard_bundle) + B::Orchard::digest(orchard_bundle) } #[cfg(zcash_unstable = "zfuture")] - fn digest_tze(&self, tze_bundle: Option<&tze::Bundle>) -> Self::TzeDigest { - Tze::digest(tze_bundle) + fn digest_tze(&self, tze_bundle: Option<&::Bundle>) -> Self::TzeDigest { + B::Tze::digest(tze_bundle) } fn combine( @@ -546,13 +571,38 @@ impl TzeWitnessDigester for Tze { } } +/// Trait marker for bounds that are not yet required by consensus rules. +#[cfg(not(zcash_unstable = "zfuture"))] +pub trait FutureWitnesses {} +#[cfg(not(zcash_unstable = "zfuture"))] +impl FutureWitnesses for B {} + +#[cfg(zcash_unstable = "zfuture")] +pub trait FutureWitnesses: Bundles { + type FutureTze: TzeWitnessDigester; +} +#[cfg(zcash_unstable = "zfuture")] +impl FutureWitnesses for B +where + B: Bundles, + B::Tze: TzeWitnessDigester, +{ + type FutureTze = B::Tze; +} + /// Digester which constructs a digest of only the witness data. /// This does not internally commit to the txid, so if that is /// desired it should be done using the result of this digest /// function. pub struct BlockTxCommitmentDigester; -impl TransactionDigest for BlockTxCommitmentDigester { +impl TransactionDigest for BlockTxCommitmentDigester +where + B: Bundles + FutureWitnesses, + B::Transparent: TransparentWitnessDigester, + B::Sapling: SaplingWitnessDigester, + B::Orchard: OrchardWitnessDigester, +{ /// We use the header digest to pass the transaction ID into /// where it needs to be used for personalization string construction. type HeaderDigest = BranchId; @@ -577,28 +627,28 @@ impl TransactionDigest for BlockTxCommitmentDigester { fn digest_transparent( &self, - transparent_bundle: Option<&transparent::Bundle>, - ) -> Blake2bHash { - Transparent::witness_digest(transparent_bundle) + transparent_bundle: Option<&::Bundle>, + ) -> Self::TransparentDigest { + B::Transparent::witness_digest(transparent_bundle) } fn digest_sapling( &self, - sapling_bundle: Option<&sapling::Bundle>, - ) -> Blake2bHash { - Sapling::witness_digest(sapling_bundle) + sapling_bundle: Option<&::Bundle>, + ) -> Self::SaplingDigest { + B::Sapling::witness_digest(sapling_bundle) } fn digest_orchard( &self, - orchard_bundle: Option<&orchard::Bundle>, + orchard_bundle: Option<&::Bundle>, ) -> Self::OrchardDigest { - Orchard::witness_digest(orchard_bundle) + B::Orchard::witness_digest(orchard_bundle) } #[cfg(zcash_unstable = "zfuture")] - fn digest_tze(&self, tze_bundle: Option<&tze::Bundle>) -> Blake2bHash { - Tze::witness_digest(tze_bundle) + fn digest_tze(&self, tze_bundle: Option<&::Bundle>) -> Blake2bHash { + B::Tze::witness_digest(tze_bundle) } fn combine(