//! Transactions and transaction-related structures. use std::{collections::HashMap, fmt, iter}; use halo2::pasta::pallas; mod auth_digest; mod hash; mod joinsplit; mod lock_time; mod memo; mod serialize; mod sighash; mod txid; mod unmined; #[cfg(feature = "getblocktemplate-rpcs")] pub mod builder; #[cfg(any(test, feature = "proptest-impl"))] #[allow(clippy::unwrap_in_result)] pub mod arbitrary; #[cfg(test)] mod tests; pub use auth_digest::AuthDigest; pub use hash::{Hash, WtxId}; pub use joinsplit::JoinSplitData; pub use lock_time::LockTime; pub use memo::Memo; pub use sapling::FieldNotPresent; pub use serialize::{ SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE, MIN_TRANSPARENT_TX_V5_SIZE, }; pub use sighash::{HashType, SigHash}; pub use unmined::{ zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD, }; use crate::{ amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative}, block, orchard, parameters::NetworkUpgrade, primitives::{ed25519, Bctv14Proof, Groth16Proof}, sapling, sprout, transparent::{ self, outputs_from_utxos, CoinbaseSpendRestriction::{self, *}, }, value_balance::{ValueBalance, ValueBalanceError}, }; /// 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 be 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 Canopy /// activation, we do not validate any pre-Sapling transaction types. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))] 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 JoinSplit data for this transaction, if any. joinsplit_data: Option>, /// The sapling shielded data for this transaction, if any. sapling_shielded_data: Option>, }, /// A `version = 5` transaction , which supports Orchard, Sapling, and transparent, but not Sprout. V5 { /// The Network Upgrade for this transaction. /// /// Derived from the ConsensusBranchId field. network_upgrade: NetworkUpgrade, /// 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 sapling shielded data for this transaction, if any. sapling_shielded_data: Option>, /// The orchard data for this transaction, if any. orchard_shielded_data: Option, }, } impl fmt::Display for Transaction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fmter = f.debug_struct("Transaction"); fmter.field("version", &self.version()); if let Some(network_upgrade) = self.network_upgrade() { fmter.field("network_upgrade", &network_upgrade); } if let Some(lock_time) = self.lock_time() { fmter.field("lock_time", &lock_time); } if let Some(expiry_height) = self.expiry_height() { fmter.field("expiry_height", &expiry_height); } fmter.field("transparent_inputs", &self.inputs().len()); fmter.field("transparent_outputs", &self.outputs().len()); fmter.field("sprout_joinsplits", &self.joinsplit_count()); fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count()); fmter.field("sapling_outputs", &self.sapling_outputs().count()); fmter.field("orchard_actions", &self.orchard_actions().count()); fmter.field("unmined_id", &self.unmined_id()); fmter.finish() } } impl Transaction { // identifiers and hashes /// Compute the hash (mined transaction ID) of this transaction. /// /// The hash uniquely identifies mined v5 transactions, /// and all v1-v4 transactions, whether mined or unmined. pub fn hash(&self) -> Hash { Hash::from(self) } /// Compute the unmined transaction ID of this transaction. /// /// This ID uniquely identifies unmined transactions, /// regardless of version. pub fn unmined_id(&self) -> UnminedTxId { UnminedTxId::from(self) } /// 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, all_previous_outputs: &[transparent::Output], input: Option, ) -> SigHash { sighash::SigHasher::new( self, hash_type, network_upgrade, all_previous_outputs, input, ) .sighash() } /// Compute the authorizing data commitment of this transaction as specified /// in [ZIP-244]. /// /// Returns None for pre-v5 transactions. /// /// [ZIP-244]: https://zips.z.cash/zip-0244. pub fn auth_digest(&self) -> Option { match self { Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { .. } => None, Transaction::V5 { .. } => Some(AuthDigest::from(self)), } } // other properties /// Does this transaction have transparent or shielded inputs? pub fn has_transparent_or_shielded_inputs(&self) -> bool { !self.inputs().is_empty() || self.has_shielded_inputs() } /// Does this transaction have shielded inputs? /// /// See [`Self::has_transparent_or_shielded_inputs`] for details. pub fn has_shielded_inputs(&self) -> bool { self.joinsplit_count() > 0 || self.sapling_spends_per_anchor().count() > 0 || (self.orchard_actions().count() > 0 && self .orchard_flags() .unwrap_or_else(orchard::Flags::empty) .contains(orchard::Flags::ENABLE_SPENDS)) } /// Does this transaction have transparent or shielded outputs? pub fn has_transparent_or_shielded_outputs(&self) -> bool { !self.outputs().is_empty() || self.has_shielded_outputs() } /// Does this transaction have shielded outputs? /// /// See [`Self::has_transparent_or_shielded_outputs`] for details. pub fn has_shielded_outputs(&self) -> bool { self.joinsplit_count() > 0 || self.sapling_outputs().count() > 0 || (self.orchard_actions().count() > 0 && self .orchard_flags() .unwrap_or_else(orchard::Flags::empty) .contains(orchard::Flags::ENABLE_OUTPUTS)) } /// Does this transaction has at least one flag when we have at least one orchard action? pub fn has_enough_orchard_flags(&self) -> bool { if self.version() < 5 || self.orchard_actions().count() == 0 { return true; } self.orchard_flags() .unwrap_or_else(orchard::Flags::empty) .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS) } /// Returns the [`CoinbaseSpendRestriction`] for this transaction, /// assuming it is mined at `spend_height`. pub fn coinbase_spend_restriction( &self, spend_height: block::Height, ) -> CoinbaseSpendRestriction { if self.outputs().is_empty() { // we know this transaction must have shielded outputs, // because of other consensus rules OnlyShieldedOutputs { spend_height } } else { SomeTransparentOutputs } } // header /// Return if the `fOverwintered` flag of this transaction is set. pub fn is_overwintered(&self) -> bool { match self { Transaction::V1 { .. } | Transaction::V2 { .. } => false, Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true, } } /// Return the version of this transaction. pub fn version(&self) -> u32 { match self { Transaction::V1 { .. } => 1, Transaction::V2 { .. } => 2, Transaction::V3 { .. } => 3, Transaction::V4 { .. } => 4, Transaction::V5 { .. } => 5, } } /// Get this transaction's lock time. pub fn lock_time(&self) -> Option { let lock_time = match self { Transaction::V1 { lock_time, .. } | Transaction::V2 { lock_time, .. } | Transaction::V3 { lock_time, .. } | Transaction::V4 { lock_time, .. } | Transaction::V5 { lock_time, .. } => *lock_time, }; // `zcashd` checks that the block height is greater than the lock height. // This check allows the genesis block transaction, which would otherwise be invalid. // (Or have to use a lock time.) // // It matches the `zcashd` check here: // https://github.com/zcash/zcash/blob/1a7c2a3b04bcad6549be6d571bfdff8af9a2c814/src/main.cpp#L720 if lock_time == LockTime::unlocked() { return None; } // Consensus rule: // // > The transaction must be finalized: either its locktime must be in the past (or less // > than or equal to the current block height), or all of its sequence numbers must be // > 0xffffffff. // // In `zcashd`, this rule applies to both coinbase and prevout input sequence numbers. // // Unlike Bitcoin, Zcash allows transactions with no transparent inputs. These transactions // only have shielded inputs. Surprisingly, the `zcashd` implementation ignores the lock // time in these transactions. `zcashd` only checks the lock time when it finds a // transparent input sequence number that is not `u32::MAX`. // // https://developer.bitcoin.org/devguide/transactions.html#non-standard-transactions let has_sequence_number_enabling_lock_time = self .inputs() .iter() .map(transparent::Input::sequence) .any(|sequence_number| sequence_number != u32::MAX); if has_sequence_number_enabling_lock_time { Some(lock_time) } else { None } } /// Returns `true` if this transaction's `lock_time` is a [`LockTime::Time`]. /// Returns `false` if it is a [`LockTime::Height`] (locked or unlocked), is unlocked, /// or if the transparent input sequence numbers have disabled lock times. pub fn lock_time_is_time(&self) -> bool { if let Some(lock_time) = self.lock_time() { return lock_time.is_time(); } false } /// Get this transaction's expiry height, if any. pub fn expiry_height(&self) -> Option { match self { Transaction::V1 { .. } | Transaction::V2 { .. } => None, Transaction::V3 { expiry_height, .. } | Transaction::V4 { expiry_height, .. } | Transaction::V5 { expiry_height, .. } => match expiry_height { // Consensus rule: // > No limit: To set no limit on transactions (so that they do not expire), nExpiryHeight should be set to 0. // https://zips.z.cash/zip-0203#specification block::Height(0) => None, block::Height(expiry_height) => Some(block::Height(*expiry_height)), }, } } /// Modify the expiry height of this transaction. /// /// # Panics /// /// - if called on a v1 or v2 transaction #[cfg(any(test, feature = "proptest-impl"))] pub fn expiry_height_mut(&mut self) -> &mut block::Height { match self { Transaction::V1 { .. } | Transaction::V2 { .. } => { panic!("v1 and v2 transactions are not supported") } Transaction::V3 { ref mut expiry_height, .. } | Transaction::V4 { ref mut expiry_height, .. } | Transaction::V5 { ref mut expiry_height, .. } => expiry_height, } } /// Get this transaction's network upgrade field, if any. /// This field is serialized as `nConsensusBranchId` ([7.1]). /// /// [7.1]: https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus pub fn network_upgrade(&self) -> Option { match self { Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { .. } => None, Transaction::V5 { network_upgrade, .. } => Some(*network_upgrade), } } // transparent /// 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, } } /// Modify the transparent inputs of this transaction, regardless of version. #[cfg(any(test, feature = "proptest-impl"))] pub fn inputs_mut(&mut self) -> &mut Vec { match self { Transaction::V1 { ref mut inputs, .. } => inputs, Transaction::V2 { ref mut inputs, .. } => inputs, Transaction::V3 { ref mut inputs, .. } => inputs, Transaction::V4 { ref mut inputs, .. } => inputs, Transaction::V5 { ref mut inputs, .. } => inputs, } } /// Access the [`transparent::OutPoint`]s spent by this transaction's [`transparent::Input`]s. pub fn spent_outpoints(&self) -> impl Iterator + '_ { self.inputs() .iter() .filter_map(transparent::Input::outpoint) } /// 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, } } /// Modify the transparent outputs of this transaction, regardless of version. #[cfg(any(test, feature = "proptest-impl"))] pub fn outputs_mut(&mut self) -> &mut Vec { match self { Transaction::V1 { ref mut outputs, .. } => outputs, Transaction::V2 { ref mut outputs, .. } => outputs, Transaction::V3 { ref mut outputs, .. } => outputs, Transaction::V4 { ref mut outputs, .. } => outputs, Transaction::V5 { ref mut outputs, .. } => outputs, } } /// Returns `true` if this transaction has valid inputs for a coinbase /// transaction, that is, has a single input and it is a coinbase input /// (null prevout). pub fn is_coinbase(&self) -> bool { self.inputs().len() == 1 && matches!( self.inputs().get(0), Some(transparent::Input::Coinbase { .. }) ) } /// Returns `true` if this transaction has valid inputs for a non-coinbase /// transaction, that is, does not have any coinbase input (non-null prevouts). /// /// Note that it's possible for a transaction return false in both /// [`Transaction::is_coinbase`] and [`Transaction::is_valid_non_coinbase`], /// though those transactions will be rejected. pub fn is_valid_non_coinbase(&self) -> bool { self.inputs() .iter() .all(|input| matches!(input, transparent::Input::PrevOut { .. })) } // sprout /// Returns the Sprout `JoinSplit`s in this transaction, regardless of version. pub fn sprout_groth16_joinsplits( &self, ) -> Box> + '_> { match self { // JoinSplits with Groth16 Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new(joinsplit_data.joinsplits()), // No JoinSplits / JoinSplits with BCTV14 proofs Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } /// Returns the number of `JoinSplit`s in this transaction, regardless of version. pub fn joinsplit_count(&self) -> usize { match self { // JoinSplits with Bctv14 Proofs Transaction::V2 { joinsplit_data: Some(joinsplit_data), .. } | Transaction::V3 { joinsplit_data: Some(joinsplit_data), .. } => joinsplit_data.joinsplits().count(), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => joinsplit_data.joinsplits().count(), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => 0, } } /// 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 // (we could extract bctv and groth as separate iterators, then chain // them together, but that would be much harder to read and maintain) match self { // JoinSplits with Bctv14 Proofs Transaction::V2 { joinsplit_data: Some(joinsplit_data), .. } | Transaction::V3 { joinsplit_data: Some(joinsplit_data), .. } => Box::new(joinsplit_data.nullifiers()), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new(joinsplit_data.nullifiers()), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } /// Access the JoinSplit public validating key in this transaction, /// regardless of version, if any. pub fn sprout_joinsplit_pub_key(&self) -> Option { match self { // JoinSplits with Bctv14 Proofs Transaction::V2 { joinsplit_data: Some(joinsplit_data), .. } | Transaction::V3 { joinsplit_data: Some(joinsplit_data), .. } => Some(joinsplit_data.pub_key), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Some(joinsplit_data.pub_key), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => None, } } /// Return if the transaction has any Sprout JoinSplit data. pub fn has_sprout_joinsplit_data(&self) -> bool { match self { // No JoinSplits Transaction::V1 { .. } | Transaction::V5 { .. } => false, // JoinSplits-on-BCTV14 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => { joinsplit_data.is_some() } // JoinSplits-on-Groth16 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(), } } /// Returns the Sprout note commitments in this transaction. pub fn sprout_note_commitments( &self, ) -> Box + '_> { match self { // Return [`NoteCommitment`]s with [`Bctv14Proof`]s. Transaction::V2 { joinsplit_data: Some(joinsplit_data), .. } | Transaction::V3 { joinsplit_data: Some(joinsplit_data), .. } => Box::new(joinsplit_data.note_commitments()), // Return [`NoteCommitment`]s with [`Groth16Proof`]s. Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new(joinsplit_data.note_commitments()), // Return an empty iterator. Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V1 { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } // sapling /// Access the deduplicated [`sapling::tree::Root`]s in this transaction, /// regardless of version. pub fn sapling_anchors(&self) -> Box + '_> { // This function returns a boxed iterator because the different // transaction variants end up having different iterator types match self { Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.anchors()), Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.anchors()), // No Spends Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), } } /// Iterate over the sapling [`Spend`](sapling::Spend)s for this transaction, /// returning `Spend` regardless of the underlying /// transaction version. /// /// Shared anchors in V5 transactions are copied into each sapling spend. /// This allows the same code to validate spends from V4 and V5 transactions. /// /// # Correctness /// /// Do not use this function for serialization. pub fn sapling_spends_per_anchor( &self, ) -> Box> + '_> { match self { Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.spends_per_anchor()), Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.spends_per_anchor()), // No Spends Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), } } /// Iterate over the sapling [`Output`](sapling::Output)s for this /// transaction pub fn sapling_outputs(&self) -> Box + '_> { match self { Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.outputs()), Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.outputs()), // No Outputs Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_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 { // Spends with Groth Proofs Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.nullifiers()), Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.nullifiers()), // No Spends Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), } } /// Returns the Sapling note commitments in this transaction, regardless of version. pub fn sapling_note_commitments(&self) -> Box + '_> { // This function returns a boxed iterator because the different // transaction variants end up having different iterator types match self { // Spends with Groth16 Proofs Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.note_commitments()), Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.note_commitments()), // No Spends Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), } } /// Return if the transaction has any Sapling shielded data. pub fn has_sapling_shielded_data(&self) -> bool { match self { Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false, Transaction::V4 { sapling_shielded_data, .. } => sapling_shielded_data.is_some(), Transaction::V5 { sapling_shielded_data, .. } => sapling_shielded_data.is_some(), } } // orchard /// Access the [`orchard::ShieldedData`] in this transaction, /// regardless of version. pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> { match self { // Maybe Orchard shielded data Transaction::V5 { orchard_shielded_data, .. } => orchard_shielded_data.as_ref(), // No Orchard shielded data Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { .. } => None, } } /// Modify the [`orchard::ShieldedData`] in this transaction, /// regardless of version. #[cfg(any(test, feature = "proptest-impl"))] pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> { match self { Transaction::V5 { orchard_shielded_data: Some(orchard_shielded_data), .. } => Some(orchard_shielded_data), Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { orchard_shielded_data: None, .. } => None, } } /// Iterate over the [`orchard::Action`]s in this transaction, if there are any, /// regardless of version. pub fn orchard_actions(&self) -> impl Iterator { self.orchard_shielded_data() .into_iter() .flat_map(orchard::ShieldedData::actions) } /// Access the [`orchard::Nullifier`]s in this transaction, if there are any, /// regardless of version. pub fn orchard_nullifiers(&self) -> impl Iterator { self.orchard_shielded_data() .into_iter() .flat_map(orchard::ShieldedData::nullifiers) } /// Access the note commitments in this transaction, if there are any, /// regardless of version. pub fn orchard_note_commitments(&self) -> impl Iterator { self.orchard_shielded_data() .into_iter() .flat_map(orchard::ShieldedData::note_commitments) } /// Access the [`orchard::Flags`] in this transaction, if there is any, /// regardless of version. pub fn orchard_flags(&self) -> Option { self.orchard_shielded_data() .map(|orchard_shielded_data| orchard_shielded_data.flags) } /// Return if the transaction has any Orchard shielded data, /// regardless of version. pub fn has_orchard_shielded_data(&self) -> bool { self.orchard_shielded_data().is_some() } // value balances /// Return the transparent value balance, /// using the outputs spent by this transaction. /// /// See `transparent_value_balance` for details. #[allow(clippy::unwrap_in_result)] fn transparent_value_balance_from_outputs( &self, outputs: &HashMap, ) -> Result, ValueBalanceError> { let input_value = self .inputs() .iter() .map(|i| i.value_from_outputs(outputs)) .sum::, AmountError>>() .map_err(ValueBalanceError::Transparent)? .constrain() .expect("conversion from NonNegative to NegativeAllowed is always valid"); let output_value = self .outputs() .iter() .map(|o| o.value()) .sum::, AmountError>>() .map_err(ValueBalanceError::Transparent)? .constrain() .expect("conversion from NonNegative to NegativeAllowed is always valid"); (input_value - output_value) .map(ValueBalance::from_transparent_amount) .map_err(ValueBalanceError::Transparent) } /// Modify the transparent output values of this transaction, regardless of version. #[cfg(any(test, feature = "proptest-impl"))] pub fn output_values_mut(&mut self) -> impl Iterator> { self.outputs_mut() .iter_mut() .map(|output| &mut output.value) } /// Returns the `vpub_old` fields from `JoinSplit`s in this transaction, /// regardless of version, in the order they appear in the transaction. /// /// These values are added to the sprout chain value pool, /// and removed from the value pool of this transaction. pub fn output_values_to_sprout(&self) -> Box> + '_> { 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() .map(|joinsplit| &joinsplit.vpub_old), ), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new( joinsplit_data .joinsplits() .map(|joinsplit| &joinsplit.vpub_old), ), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } /// Modify the `vpub_old` fields from `JoinSplit`s in this transaction, /// regardless of version, in the order they appear in the transaction. /// /// See `output_values_to_sprout` for details. #[cfg(any(test, feature = "proptest-impl"))] pub fn output_values_to_sprout_mut( &mut self, ) -> Box> + '_> { 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_mut() .map(|joinsplit| &mut joinsplit.vpub_old), ), // JoinSplits with Groth16 Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new( joinsplit_data .joinsplits_mut() .map(|joinsplit| &mut joinsplit.vpub_old), ), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } /// Returns the `vpub_new` fields from `JoinSplit`s in this transaction, /// regardless of version, in the order they appear in the transaction. /// /// These values are removed from the value pool of this transaction. /// and added to the sprout chain value pool. pub fn input_values_from_sprout(&self) -> Box> + '_> { 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() .map(|joinsplit| &joinsplit.vpub_new), ), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new( joinsplit_data .joinsplits() .map(|joinsplit| &joinsplit.vpub_new), ), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } /// Modify the `vpub_new` fields from `JoinSplit`s in this transaction, /// regardless of version, in the order they appear in the transaction. /// /// See `input_values_from_sprout` for details. #[cfg(any(test, feature = "proptest-impl"))] pub fn input_values_from_sprout_mut( &mut self, ) -> Box> + '_> { 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_mut() .map(|joinsplit| &mut joinsplit.vpub_new), ), // JoinSplits with Groth Proofs Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => Box::new( joinsplit_data .joinsplits_mut() .map(|joinsplit| &mut joinsplit.vpub_new), ), // No JoinSplits Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), } } /// Return a list of sprout value balances, /// the changes in the transaction value pool due to each sprout `JoinSplit`. /// /// Each value balance is the sprout `vpub_new` field, minus the `vpub_old` field. /// /// See [`sprout_value_balance`][svb] for details. /// /// [svb]: crate::transaction::Transaction::sprout_value_balance fn sprout_joinsplit_value_balances( &self, ) -> impl Iterator> + '_ { let joinsplit_value_balances = match self { Transaction::V2 { joinsplit_data: Some(joinsplit_data), .. } | Transaction::V3 { joinsplit_data: Some(joinsplit_data), .. } => joinsplit_data.joinsplit_value_balances(), Transaction::V4 { joinsplit_data: Some(joinsplit_data), .. } => joinsplit_data.joinsplit_value_balances(), Transaction::V1 { .. } | Transaction::V2 { joinsplit_data: None, .. } | Transaction::V3 { joinsplit_data: None, .. } | Transaction::V4 { joinsplit_data: None, .. } | Transaction::V5 { .. } => Box::new(iter::empty()), }; joinsplit_value_balances.map(ValueBalance::from_sprout_amount) } /// Return the sprout value balance, /// the change in the transaction value pool due to sprout `JoinSplit`s. /// /// The sum of all sprout `vpub_new` fields, minus the sum of all `vpub_old` fields. /// /// Positive values are added to this transaction's value pool, /// and removed from the sprout chain value pool. /// Negative values are removed from this transaction, /// and added to the sprout pool. /// /// fn sprout_value_balance(&self) -> Result, ValueBalanceError> { self.sprout_joinsplit_value_balances().sum() } /// Return the sapling value balance, /// the change in the transaction value pool due to sapling `Spend`s and `Output`s. /// /// Returns the `valueBalanceSapling` field in this transaction. /// /// Positive values are added to this transaction's value pool, /// and removed from the sapling chain value pool. /// Negative values are removed from this transaction, /// and added to sapling pool. /// /// pub fn sapling_value_balance(&self) -> ValueBalance { let sapling_value_balance = match self { Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => sapling_shielded_data.value_balance, Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => sapling_shielded_data.value_balance, Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_data: None, .. } => Amount::zero(), }; ValueBalance::from_sapling_amount(sapling_value_balance) } /// Modify the `value_balance` field from the `sapling::ShieldedData` in this transaction, /// regardless of version. /// /// See `sapling_value_balance` for details. #[cfg(any(test, feature = "proptest-impl"))] pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount> { match self { Transaction::V4 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Some(&mut sapling_shielded_data.value_balance), Transaction::V5 { sapling_shielded_data: Some(sapling_shielded_data), .. } => Some(&mut sapling_shielded_data.value_balance), Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { sapling_shielded_data: None, .. } | Transaction::V5 { sapling_shielded_data: None, .. } => None, } } /// Return the orchard value balance, the change in the transaction value /// pool due to [`orchard::Action`]s. /// /// Returns the `valueBalanceOrchard` field in this transaction. /// /// Positive values are added to this transaction's value pool, /// and removed from the orchard chain value pool. /// Negative values are removed from this transaction, /// and added to orchard pool. /// /// pub fn orchard_value_balance(&self) -> ValueBalance { let orchard_value_balance = self .orchard_shielded_data() .map(|shielded_data| shielded_data.value_balance) .unwrap_or_else(Amount::zero); ValueBalance::from_orchard_amount(orchard_value_balance) } /// Modify the `value_balance` field from the `orchard::ShieldedData` in this transaction, /// regardless of version. /// /// See `orchard_value_balance` for details. #[cfg(any(test, feature = "proptest-impl"))] pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount> { self.orchard_shielded_data_mut() .map(|shielded_data| &mut shielded_data.value_balance) } /// Get the value balances for this transaction, /// using the transparent outputs spent in this transaction. /// /// See `value_balance` for details. pub(crate) fn value_balance_from_outputs( &self, outputs: &HashMap, ) -> Result, ValueBalanceError> { self.transparent_value_balance_from_outputs(outputs)? + self.sprout_value_balance()? + self.sapling_value_balance() + self.orchard_value_balance() } /// Get the value balances for this transaction. /// These are the changes in the transaction value pool, /// split up into transparent, sprout, sapling, and orchard values. /// /// Calculated as the sum of the inputs and outputs from each pool, /// or the sum of the value balances from each pool. /// /// Positive values are added to this transaction's value pool, /// and removed from the corresponding chain value pool. /// Negative values are removed from this transaction, /// and added to the corresponding pool. /// /// /// /// `utxos` must contain the utxos of every input in the transaction, /// including UTXOs created by earlier transactions in this block. /// /// Note: the chain value pool has the opposite sign to the transaction /// value pool. pub fn value_balance( &self, utxos: &HashMap, ) -> Result, ValueBalanceError> { self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone())) } }