//! Transactions and transaction-related structures. use serde::{Deserialize, Serialize}; mod hash; mod joinsplit; mod lock_time; mod memo; mod serialize; mod shielded_data; mod sighash; #[cfg(any(test, feature = "proptest-impl"))] mod arbitrary; #[cfg(test)] mod tests; pub use hash::Hash; pub use joinsplit::JoinSplitData; pub use lock_time::LockTime; pub use memo::Memo; pub use shielded_data::ShieldedData; pub use sighash::HashType; use crate::{ amount::Amount, block, parameters::NetworkUpgrade, primitives::{Bctv14Proof, Groth16Proof}, sapling, sprout, transparent, }; /// A Zcash transaction. /// /// A transaction is an encoded data structure that facilitates the transfer of /// value between two public key addresses on the Zcash ecosystem. Everything is /// designed to ensure that transactions can created, propagated on the network, /// validated, and finally added to the global ledger of transactions (the /// blockchain). /// /// Zcash has a number of different transaction formats. They are represented /// internally by different enum variants. Because we checkpoint on Sapling /// activation, we do not validate any pre-Sapling transaction types. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] // XXX consider boxing the Optional fields of V4 and V5 txs #[allow(clippy::large_enum_variant)] pub enum Transaction { /// A fully transparent transaction (`version = 1`). V1 { /// The transparent inputs to the transaction. inputs: Vec, /// The transparent outputs from the transaction. outputs: Vec, /// The earliest time or block height that this transaction can be added to the /// chain. lock_time: LockTime, }, /// A Sprout transaction (`version = 2`). V2 { /// The transparent inputs to the transaction. inputs: Vec, /// The transparent outputs from the transaction. outputs: Vec, /// The earliest time or block height that this transaction can be added to the /// chain. lock_time: LockTime, /// The JoinSplit data for this transaction, if any. joinsplit_data: Option>, }, /// An Overwinter transaction (`version = 3`). V3 { /// The transparent inputs to the transaction. inputs: Vec, /// The transparent outputs from the transaction. outputs: Vec, /// The earliest time or block height that this transaction can be added to the /// chain. lock_time: LockTime, /// The latest block height that this transaction can be added to the chain. expiry_height: block::Height, /// The JoinSplit data for this transaction, if any. joinsplit_data: Option>, }, /// A Sapling transaction (`version = 4`). V4 { /// The transparent inputs to the transaction. inputs: Vec, /// The transparent outputs from the transaction. outputs: Vec, /// The earliest time or block height that this transaction can be added to the /// chain. lock_time: LockTime, /// The latest block height that this transaction can be added to the chain. expiry_height: block::Height, /// The net value of Sapling spend transfers minus output transfers. value_balance: Amount, /// The JoinSplit data for this transaction, if any. joinsplit_data: Option>, /// The shielded data for this transaction, if any. shielded_data: Option, }, /// A `version = 5` transaction, which supports `Sapling` and `Orchard`. // TODO: does this transaction type support `Sprout`? // Check for ZIP-225 updates after the decision on 2021-03-05. V5 { /// The earliest time or block height that this transaction can be added to the /// chain. lock_time: LockTime, /// The latest block height that this transaction can be added to the chain. expiry_height: block::Height, /// The transparent inputs to the transaction. inputs: Vec, /// The transparent outputs from the transaction. outputs: Vec, /// The rest of the transaction as bytes rest: Vec, }, } impl Transaction { /// Compute the hash of this transaction. pub fn hash(&self) -> Hash { Hash::from(self) } /// Access the transparent inputs of this transaction, regardless of version. pub fn inputs(&self) -> &[transparent::Input] { match self { Transaction::V1 { ref inputs, .. } => inputs, Transaction::V2 { ref inputs, .. } => inputs, Transaction::V3 { ref inputs, .. } => inputs, Transaction::V4 { ref inputs, .. } => inputs, Transaction::V5 { ref inputs, .. } => inputs, } } /// Access the transparent outputs of this transaction, regardless of version. pub fn outputs(&self) -> &[transparent::Output] { match self { Transaction::V1 { ref outputs, .. } => outputs, Transaction::V2 { ref outputs, .. } => outputs, Transaction::V3 { ref outputs, .. } => outputs, Transaction::V4 { ref outputs, .. } => outputs, Transaction::V5 { ref outputs, .. } => outputs, } } /// Get this transaction's lock time. pub fn lock_time(&self) -> LockTime { match self { Transaction::V1 { lock_time, .. } => *lock_time, Transaction::V2 { lock_time, .. } => *lock_time, Transaction::V3 { lock_time, .. } => *lock_time, Transaction::V4 { lock_time, .. } => *lock_time, Transaction::V5 { lock_time, .. } => *lock_time, } } /// Get this transaction's expiry height, if any. pub fn expiry_height(&self) -> Option { match self { Transaction::V1 { .. } => None, Transaction::V2 { .. } => None, Transaction::V3 { expiry_height, .. } => Some(*expiry_height), Transaction::V4 { expiry_height, .. } => Some(*expiry_height), Transaction::V5 { expiry_height, .. } => Some(*expiry_height), } } /// Access the sprout::Nullifiers in this transaction, regardless of version. pub fn sprout_nullifiers(&self) -> Box + '_> { // This function returns a boxed iterator because the different // transaction variants end up having different iterator types match self { // JoinSplits with Bctv14 Proofs Transaction::V2 { joinsplit_data: Some(joinsplit_data), .. } | Transaction::V3 { joinsplit_data: Some(joinsplit_data), .. } => Box::new( joinsplit_data .joinsplits() .flat_map(|joinsplit| joinsplit.nullifiers.iter()), ), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new( joinsplit_data .joinsplits() .flat_map(|joinsplit| joinsplit.nullifiers.iter()), ), // Maybe JoinSplits, maybe not, we're still deciding Transaction::V5 { .. } => { unimplemented!( "v5 transaction format as specified in ZIP-225 after decision on 2021-03-12" ) } // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } => Box::new(std::iter::empty()), } } /// Access the sapling::Nullifiers in this transaction, regardless of version. pub fn sapling_nullifiers(&self) -> Box + '_> { // This function returns a boxed iterator because the different // transaction variants end up having different iterator types match self { // JoinSplits with Groth Proofs Transaction::V4 { shielded_data: Some(shielded_data), .. } => Box::new(shielded_data.nullifiers()), Transaction::V5 { .. } => { unimplemented!("v5 transaction format as specified in ZIP-225") } // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { shielded_data: None, .. } => Box::new(std::iter::empty()), } } /// Returns `true` if transaction contains any coinbase inputs. pub fn contains_coinbase_input(&self) -> bool { self.inputs() .iter() .any(|input| matches!(input, transparent::Input::Coinbase { .. })) } /// Returns `true` if this transaction is a coinbase transaction. pub fn is_coinbase(&self) -> bool { self.inputs().len() == 1 && matches!( self.inputs().get(0), Some(transparent::Input::Coinbase { .. }) ) } /// Calculate the sighash for the current transaction /// /// # Details /// /// The `input` argument indicates the transparent Input for which we are /// producing a sighash. It is comprised of the index identifying the /// transparent::Input within the transaction and the transparent::Output /// representing the UTXO being spent by that input. /// /// # Panics /// /// - if passed in any NetworkUpgrade from before NetworkUpgrade::Overwinter /// - if called on a v1 or v2 transaction /// - if the input index points to a transparent::Input::CoinBase /// - if the input index is out of bounds for self.inputs() pub fn sighash( &self, network_upgrade: NetworkUpgrade, hash_type: sighash::HashType, input: Option<(u32, transparent::Output)>, ) -> blake2b_simd::Hash { sighash::SigHasher::new(self, hash_type, network_upgrade, input).sighash() } }