Skip to main content

zebra_chain/
transaction.rs

1//! Transactions and transaction-related structures.
2
3use std::{collections::HashMap, fmt, iter, sync::Arc};
4
5use halo2::pasta::pallas;
6
7mod auth_digest;
8mod hash;
9mod joinsplit;
10mod lock_time;
11mod memo;
12mod serialize;
13mod sighash;
14mod txid;
15mod unmined;
16
17#[cfg(any(test, feature = "proptest-impl"))]
18#[allow(clippy::unwrap_in_result)]
19pub mod arbitrary;
20#[cfg(test)]
21mod tests;
22
23pub use auth_digest::AuthDigest;
24pub use hash::{Hash, WtxId};
25pub use joinsplit::JoinSplitData;
26pub use lock_time::LockTime;
27pub use memo::Memo;
28use redjubjub::{Binding, Signature};
29pub use sapling::FieldNotPresent;
30pub use serialize::{
31    SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE,
32    MIN_TRANSPARENT_TX_V5_SIZE,
33};
34pub use sighash::{HashType, SigHash, SigHasher};
35pub use unmined::{
36    zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
37};
38use zcash_protocol::consensus;
39
40#[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
41use crate::parameters::TX_V6_VERSION_GROUP_ID;
42use crate::{
43    amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
44    block, orchard,
45    parameters::{
46        Network, NetworkUpgrade, OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID,
47        TX_V5_VERSION_GROUP_ID,
48    },
49    primitives::{ed25519, Bctv14Proof, Groth16Proof},
50    sapling,
51    serialization::ZcashSerialize,
52    sprout,
53    transparent::{
54        self, outputs_from_utxos,
55        CoinbaseSpendRestriction::{self, *},
56    },
57    value_balance::{ValueBalance, ValueBalanceError},
58    Error,
59};
60
61/// A Zcash transaction.
62///
63/// A transaction is an encoded data structure that facilitates the transfer of
64/// value between two public key addresses on the Zcash ecosystem. Everything is
65/// designed to ensure that transactions can be created, propagated on the
66/// network, validated, and finally added to the global ledger of transactions
67/// (the blockchain).
68///
69/// Zcash has a number of different transaction formats. They are represented
70/// internally by different enum variants. Because we checkpoint on Canopy
71/// activation, we do not validate any pre-Sapling transaction types.
72#[derive(Clone, Debug, PartialEq, Eq)]
73#[cfg_attr(
74    any(test, feature = "proptest-impl", feature = "elasticsearch"),
75    derive(Serialize)
76)]
77pub enum Transaction {
78    /// A fully transparent transaction (`version = 1`).
79    V1 {
80        /// The transparent inputs to the transaction.
81        inputs: Vec<transparent::Input>,
82        /// The transparent outputs from the transaction.
83        outputs: Vec<transparent::Output>,
84        /// The earliest time or block height that this transaction can be added to the
85        /// chain.
86        lock_time: LockTime,
87    },
88    /// A Sprout transaction (`version = 2`).
89    V2 {
90        /// The transparent inputs to the transaction.
91        inputs: Vec<transparent::Input>,
92        /// The transparent outputs from the transaction.
93        outputs: Vec<transparent::Output>,
94        /// The earliest time or block height that this transaction can be added to the
95        /// chain.
96        lock_time: LockTime,
97        /// The JoinSplit data for this transaction, if any.
98        joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
99    },
100    /// An Overwinter transaction (`version = 3`).
101    V3 {
102        /// The transparent inputs to the transaction.
103        inputs: Vec<transparent::Input>,
104        /// The transparent outputs from the transaction.
105        outputs: Vec<transparent::Output>,
106        /// The earliest time or block height that this transaction can be added to the
107        /// chain.
108        lock_time: LockTime,
109        /// The latest block height that this transaction can be added to the chain.
110        expiry_height: block::Height,
111        /// The JoinSplit data for this transaction, if any.
112        joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
113    },
114    /// A Sapling transaction (`version = 4`).
115    V4 {
116        /// The transparent inputs to the transaction.
117        inputs: Vec<transparent::Input>,
118        /// The transparent outputs from the transaction.
119        outputs: Vec<transparent::Output>,
120        /// The earliest time or block height that this transaction can be added to the
121        /// chain.
122        lock_time: LockTime,
123        /// The latest block height that this transaction can be added to the chain.
124        expiry_height: block::Height,
125        /// The JoinSplit data for this transaction, if any.
126        joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
127        /// The sapling shielded data for this transaction, if any.
128        sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
129    },
130    /// A `version = 5` transaction , which supports Orchard, Sapling, and transparent, but not Sprout.
131    V5 {
132        /// The Network Upgrade for this transaction.
133        ///
134        /// Derived from the ConsensusBranchId field.
135        network_upgrade: NetworkUpgrade,
136        /// The earliest time or block height that this transaction can be added to the
137        /// chain.
138        lock_time: LockTime,
139        /// The latest block height that this transaction can be added to the chain.
140        expiry_height: block::Height,
141        /// The transparent inputs to the transaction.
142        inputs: Vec<transparent::Input>,
143        /// The transparent outputs from the transaction.
144        outputs: Vec<transparent::Output>,
145        /// The sapling shielded data for this transaction, if any.
146        sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
147        /// The orchard data for this transaction, if any.
148        orchard_shielded_data: Option<orchard::ShieldedData>,
149    },
150    /// A `version = 6` transaction, which is reserved for current development.
151    #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
152    V6 {
153        /// The Network Upgrade for this transaction.
154        ///
155        /// Derived from the ConsensusBranchId field.
156        network_upgrade: NetworkUpgrade,
157        /// The earliest time or block height that this transaction can be added to the
158        /// chain.
159        lock_time: LockTime,
160        /// The latest block height that this transaction can be added to the chain.
161        expiry_height: block::Height,
162        /// The burn amount for this transaction, if any.
163        zip233_amount: Amount<NonNegative>,
164        /// The transparent inputs to the transaction.
165        inputs: Vec<transparent::Input>,
166        /// The transparent outputs from the transaction.
167        outputs: Vec<transparent::Output>,
168        /// The sapling shielded data for this transaction, if any.
169        sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
170        /// The orchard data for this transaction, if any.
171        orchard_shielded_data: Option<orchard::ShieldedData>,
172    },
173}
174
175impl fmt::Display for Transaction {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        let mut fmter = f.debug_struct("Transaction");
178
179        fmter.field("version", &self.version());
180
181        if let Some(network_upgrade) = self.network_upgrade() {
182            fmter.field("network_upgrade", &network_upgrade);
183        }
184
185        if let Some(lock_time) = self.lock_time() {
186            fmter.field("lock_time", &lock_time);
187        }
188
189        if let Some(expiry_height) = self.expiry_height() {
190            fmter.field("expiry_height", &expiry_height);
191        }
192        #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
193        fmter.field("zip233_amount", &self.zip233_amount());
194
195        fmter.field("transparent_inputs", &self.inputs().len());
196        fmter.field("transparent_outputs", &self.outputs().len());
197        fmter.field("sprout_joinsplits", &self.joinsplit_count());
198        fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
199        fmter.field("sapling_outputs", &self.sapling_outputs().count());
200        fmter.field("orchard_actions", &self.orchard_actions().count());
201
202        fmter.field("unmined_id", &self.unmined_id());
203
204        fmter.finish()
205    }
206}
207
208impl Transaction {
209    // identifiers and hashes
210
211    /// Compute the hash (mined transaction ID) of this transaction.
212    ///
213    /// The hash uniquely identifies mined v5 transactions,
214    /// and all v1-v4 transactions, whether mined or unmined.
215    pub fn hash(&self) -> Hash {
216        Hash::from(self)
217    }
218
219    /// Compute the unmined transaction ID of this transaction.
220    ///
221    /// This ID uniquely identifies unmined transactions,
222    /// regardless of version.
223    pub fn unmined_id(&self) -> UnminedTxId {
224        UnminedTxId::from(self)
225    }
226
227    /// Calculate the sighash for the current transaction.
228    ///
229    /// If you need to compute multiple sighashes for the same transactions,
230    /// it's more efficient to use [`Transaction::sighasher()`].
231    ///
232    /// # Details
233    ///
234    /// `all_previous_outputs` represents the UTXOs being spent by each input
235    /// in the transaction.
236    ///
237    /// The `input_index_script_code` tuple indicates the index of the
238    /// transparent Input for which we are producing a sighash and the
239    /// respective script code being validated, or None if it's a shielded
240    /// input.
241    ///
242    /// # Panics
243    ///
244    /// - if passed in any NetworkUpgrade from before NetworkUpgrade::Overwinter
245    /// - if called on a v1 or v2 transaction
246    /// - if the input index points to a transparent::Input::CoinBase
247    /// - if the input index is out of bounds for self.inputs()
248    /// - if the tx contains `nConsensusBranchId` field and `nu` doesn't match it
249    /// - if the tx is not convertible to its `librustzcash` equivalent
250    /// - if `nu` doesn't contain a consensus branch id convertible to its `librustzcash`
251    ///   equivalent
252    pub fn sighash(
253        &self,
254        nu: NetworkUpgrade,
255        hash_type: sighash::HashType,
256        all_previous_outputs: Arc<Vec<transparent::Output>>,
257        input_index_script_code: Option<(usize, Vec<u8>)>,
258    ) -> Result<SigHash, Error> {
259        Ok(sighash::SigHasher::new(self, nu, all_previous_outputs)?
260            .sighash(hash_type, input_index_script_code))
261    }
262
263    /// Return a [`SigHasher`] for this transaction.
264    pub fn sighasher(
265        &self,
266        nu: NetworkUpgrade,
267        all_previous_outputs: Arc<Vec<transparent::Output>>,
268    ) -> Result<sighash::SigHasher, Error> {
269        sighash::SigHasher::new(self, nu, all_previous_outputs)
270    }
271
272    /// Compute the authorizing data commitment of this transaction as specified
273    /// in [ZIP-244].
274    ///
275    /// Returns None for pre-v5 transactions.
276    ///
277    /// [ZIP-244]: https://zips.z.cash/zip-0244.
278    pub fn auth_digest(&self) -> Option<AuthDigest> {
279        match self {
280            Transaction::V1 { .. }
281            | Transaction::V2 { .. }
282            | Transaction::V3 { .. }
283            | Transaction::V4 { .. } => None,
284            Transaction::V5 { .. } => Some(AuthDigest::from(self)),
285            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
286            Transaction::V6 { .. } => Some(AuthDigest::from(self)),
287        }
288    }
289
290    // other properties
291
292    /// Does this transaction have transparent inputs?
293    pub fn has_transparent_inputs(&self) -> bool {
294        !self.inputs().is_empty()
295    }
296
297    /// Does this transaction have transparent outputs?
298    pub fn has_transparent_outputs(&self) -> bool {
299        !self.outputs().is_empty()
300    }
301
302    /// Does this transaction have transparent inputs or outputs?
303    pub fn has_transparent_inputs_or_outputs(&self) -> bool {
304        self.has_transparent_inputs() || self.has_transparent_outputs()
305    }
306
307    /// Does this transaction have transparent or shielded inputs?
308    pub fn has_transparent_or_shielded_inputs(&self) -> bool {
309        self.has_transparent_inputs() || self.has_shielded_inputs()
310    }
311
312    /// Does this transaction have shielded inputs?
313    ///
314    /// See [`Self::has_transparent_or_shielded_inputs`] for details.
315    pub fn has_shielded_inputs(&self) -> bool {
316        self.joinsplit_count() > 0
317            || self.sapling_spends_per_anchor().count() > 0
318            || (self.orchard_actions().count() > 0
319                && self
320                    .orchard_flags()
321                    .unwrap_or_else(orchard::Flags::empty)
322                    .contains(orchard::Flags::ENABLE_SPENDS))
323    }
324
325    /// Does this transaction have zip233_amount output?
326    #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
327    pub fn has_zip233_amount(&self) -> bool {
328        self.zip233_amount() > Amount::<NonNegative>::zero()
329    }
330    /// Does this transaction have shielded outputs?
331    ///
332    /// See [`Self::has_transparent_or_shielded_outputs`] for details.
333    pub fn has_shielded_outputs(&self) -> bool {
334        self.joinsplit_count() > 0
335            || self.sapling_outputs().count() > 0
336            || (self.orchard_actions().count() > 0
337                && self
338                    .orchard_flags()
339                    .unwrap_or_else(orchard::Flags::empty)
340                    .contains(orchard::Flags::ENABLE_OUTPUTS))
341    }
342
343    /// Does this transaction have transparent or shielded outputs?
344    pub fn has_transparent_or_shielded_outputs(&self) -> bool {
345        self.has_transparent_outputs() || self.has_shielded_outputs()
346    }
347
348    /// Does this transaction has at least one flag when we have at least one orchard action?
349    pub fn has_enough_orchard_flags(&self) -> bool {
350        if self.version() < 5 || self.orchard_actions().count() == 0 {
351            return true;
352        }
353        self.orchard_flags()
354            .unwrap_or_else(orchard::Flags::empty)
355            .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
356    }
357
358    /// Returns the [`CoinbaseSpendRestriction`] for this transaction,
359    /// assuming it is mined at `spend_height`.
360    pub fn coinbase_spend_restriction(
361        &self,
362        network: &Network,
363        spend_height: block::Height,
364    ) -> CoinbaseSpendRestriction {
365        if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
366            // we know this transaction must have shielded outputs if it has no
367            // transparent outputs, because of other consensus rules.
368            CheckCoinbaseMaturity { spend_height }
369        } else {
370            DisallowCoinbaseSpend
371        }
372    }
373
374    // header
375
376    /// Return if the `fOverwintered` flag of this transaction is set.
377    pub fn is_overwintered(&self) -> bool {
378        match self {
379            Transaction::V1 { .. } | Transaction::V2 { .. } => false,
380            Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
381            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
382            Transaction::V6 { .. } => true,
383        }
384    }
385
386    /// Returns the version of this transaction.
387    ///
388    /// Note that the returned version is equal to `effectiveVersion`, described in [§ 7.1
389    /// Transaction Encoding and Consensus]:
390    ///
391    /// > `effectiveVersion` [...] is equal to `min(2, version)` when `fOverwintered = 0` and to
392    /// > `version` otherwise.
393    ///
394    /// Zebra handles the `fOverwintered` flag via the [`Self::is_overwintered`] method.
395    ///
396    /// [§ 7.1 Transaction Encoding and Consensus]: <https://zips.z.cash/protocol/protocol.pdf#txnencoding>
397    pub fn version(&self) -> u32 {
398        match self {
399            Transaction::V1 { .. } => 1,
400            Transaction::V2 { .. } => 2,
401            Transaction::V3 { .. } => 3,
402            Transaction::V4 { .. } => 4,
403            Transaction::V5 { .. } => 5,
404            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
405            Transaction::V6 { .. } => 6,
406        }
407    }
408
409    /// Get this transaction's lock time.
410    pub fn lock_time(&self) -> Option<LockTime> {
411        let lock_time = match self {
412            Transaction::V1 { lock_time, .. }
413            | Transaction::V2 { lock_time, .. }
414            | Transaction::V3 { lock_time, .. }
415            | Transaction::V4 { lock_time, .. }
416            | Transaction::V5 { lock_time, .. } => *lock_time,
417            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
418            Transaction::V6 { lock_time, .. } => *lock_time,
419        };
420
421        // `zcashd` checks that the block height is greater than the lock height.
422        // This check allows the genesis block transaction, which would otherwise be invalid.
423        // (Or have to use a lock time.)
424        //
425        // It matches the `zcashd` check here:
426        // https://github.com/zcash/zcash/blob/1a7c2a3b04bcad6549be6d571bfdff8af9a2c814/src/main.cpp#L720
427        if lock_time == LockTime::unlocked() {
428            return None;
429        }
430
431        // Consensus rule:
432        //
433        // > The transaction must be finalized: either its locktime must be in the past (or less
434        // > than or equal to the current block height), or all of its sequence numbers must be
435        // > 0xffffffff.
436        //
437        // In `zcashd`, this rule applies to both coinbase and prevout input sequence numbers.
438        //
439        // Unlike Bitcoin, Zcash allows transactions with no transparent inputs. These transactions
440        // only have shielded inputs. Surprisingly, the `zcashd` implementation ignores the lock
441        // time in these transactions. `zcashd` only checks the lock time when it finds a
442        // transparent input sequence number that is not `u32::MAX`.
443        //
444        // https://developer.bitcoin.org/devguide/transactions.html#non-standard-transactions
445        let has_sequence_number_enabling_lock_time = self
446            .inputs()
447            .iter()
448            .map(transparent::Input::sequence)
449            .any(|sequence_number| sequence_number != u32::MAX);
450
451        if has_sequence_number_enabling_lock_time {
452            Some(lock_time)
453        } else {
454            None
455        }
456    }
457
458    /// Get the raw lock time value.
459    pub fn raw_lock_time(&self) -> u32 {
460        let lock_time = match self {
461            Transaction::V1 { lock_time, .. }
462            | Transaction::V2 { lock_time, .. }
463            | Transaction::V3 { lock_time, .. }
464            | Transaction::V4 { lock_time, .. }
465            | Transaction::V5 { lock_time, .. } => *lock_time,
466            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
467            Transaction::V6 { lock_time, .. } => *lock_time,
468        };
469        let mut lock_time_bytes = Vec::new();
470        lock_time
471            .zcash_serialize(&mut lock_time_bytes)
472            .expect("lock_time should serialize");
473        u32::from_le_bytes(
474            lock_time_bytes
475                .try_into()
476                .expect("should serialize as 4 bytes"),
477        )
478    }
479
480    /// Returns `true` if this transaction's `lock_time` is a [`LockTime::Time`].
481    /// Returns `false` if it is a [`LockTime::Height`] (locked or unlocked), is unlocked,
482    /// or if the transparent input sequence numbers have disabled lock times.
483    pub fn lock_time_is_time(&self) -> bool {
484        if let Some(lock_time) = self.lock_time() {
485            return lock_time.is_time();
486        }
487
488        false
489    }
490
491    /// Get this transaction's expiry height, if any.
492    pub fn expiry_height(&self) -> Option<block::Height> {
493        match self {
494            Transaction::V1 { .. } | Transaction::V2 { .. } => None,
495            Transaction::V3 { expiry_height, .. }
496            | Transaction::V4 { expiry_height, .. }
497            | Transaction::V5 { expiry_height, .. } => match expiry_height {
498                // Consensus rule:
499                // > No limit: To set no limit on transactions (so that they do not expire), nExpiryHeight should be set to 0.
500                // https://zips.z.cash/zip-0203#specification
501                block::Height(0) => None,
502                block::Height(expiry_height) => Some(block::Height(*expiry_height)),
503            },
504            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
505            Transaction::V6 { expiry_height, .. } => match expiry_height {
506                // # Consensus
507                //
508                // > No limit: To set no limit on transactions (so that they do not expire), nExpiryHeight should be set to 0.
509                // https://zips.z.cash/zip-0203#specification
510                block::Height(0) => None,
511                block::Height(expiry_height) => Some(block::Height(*expiry_height)),
512            },
513        }
514    }
515
516    /// Get this transaction's network upgrade field, if any.
517    /// This field is serialized as `nConsensusBranchId` ([7.1]).
518    ///
519    /// [7.1]: https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
520    pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
521        match self {
522            Transaction::V1 { .. }
523            | Transaction::V2 { .. }
524            | Transaction::V3 { .. }
525            | Transaction::V4 { .. } => None,
526            Transaction::V5 {
527                network_upgrade, ..
528            } => Some(*network_upgrade),
529            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
530            Transaction::V6 {
531                network_upgrade, ..
532            } => Some(*network_upgrade),
533        }
534    }
535
536    // transparent
537
538    /// Access the transparent inputs of this transaction, regardless of version.
539    pub fn inputs(&self) -> &[transparent::Input] {
540        match self {
541            Transaction::V1 { ref inputs, .. } => inputs,
542            Transaction::V2 { ref inputs, .. } => inputs,
543            Transaction::V3 { ref inputs, .. } => inputs,
544            Transaction::V4 { ref inputs, .. } => inputs,
545            Transaction::V5 { ref inputs, .. } => inputs,
546            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
547            Transaction::V6 { ref inputs, .. } => inputs,
548        }
549    }
550
551    /// Access the [`transparent::OutPoint`]s spent by this transaction's [`transparent::Input`]s.
552    pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
553        self.inputs()
554            .iter()
555            .filter_map(transparent::Input::outpoint)
556    }
557
558    /// Access the transparent outputs of this transaction, regardless of version.
559    pub fn outputs(&self) -> &[transparent::Output] {
560        match self {
561            Transaction::V1 { ref outputs, .. } => outputs,
562            Transaction::V2 { ref outputs, .. } => outputs,
563            Transaction::V3 { ref outputs, .. } => outputs,
564            Transaction::V4 { ref outputs, .. } => outputs,
565            Transaction::V5 { ref outputs, .. } => outputs,
566            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
567            Transaction::V6 { ref outputs, .. } => outputs,
568        }
569    }
570
571    /// Returns `true` if this transaction has valid inputs for a coinbase
572    /// transaction, that is, has a single input and it is a coinbase input
573    /// (null prevout).
574    pub fn is_coinbase(&self) -> bool {
575        self.inputs().len() == 1
576            && matches!(
577                self.inputs().first(),
578                Some(transparent::Input::Coinbase { .. })
579            )
580    }
581
582    /// Returns `true` if this transaction has valid inputs for a non-coinbase
583    /// transaction, that is, does not have any coinbase input (non-null prevouts).
584    ///
585    /// Note that it's possible for a transaction return false in both
586    /// [`Transaction::is_coinbase`] and [`Transaction::is_valid_non_coinbase`],
587    /// though those transactions will be rejected.
588    pub fn is_valid_non_coinbase(&self) -> bool {
589        self.inputs()
590            .iter()
591            .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
592    }
593
594    // sprout
595
596    /// Returns the Sprout `JoinSplit<Groth16Proof>`s in this transaction, regardless of version.
597    pub fn sprout_groth16_joinsplits(
598        &self,
599    ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
600        match self {
601            // JoinSplits with Groth16 Proofs
602            Transaction::V4 {
603                joinsplit_data: Some(joinsplit_data),
604                ..
605            } => Box::new(joinsplit_data.joinsplits()),
606
607            // No JoinSplits / JoinSplits with BCTV14 proofs
608            Transaction::V1 { .. }
609            | Transaction::V2 { .. }
610            | Transaction::V3 { .. }
611            | Transaction::V4 {
612                joinsplit_data: None,
613                ..
614            }
615            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
616            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
617            Transaction::V6 { .. } => Box::new(std::iter::empty()),
618        }
619    }
620
621    /// Returns the Sprout `GenericJoinSplit`s in this transaction, regardless of version.
622    pub fn sprout_joinsplits(&self) -> Box<dyn Iterator<Item = sprout::GenericJoinSplit> + '_> {
623        match self {
624            // JoinSplits with Bctv14 Proofs
625            Transaction::V2 {
626                joinsplit_data: Some(joinsplit_data),
627                ..
628            }
629            | Transaction::V3 {
630                joinsplit_data: Some(joinsplit_data),
631                ..
632            } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
633            // JoinSplits with Groth Proofs
634            Transaction::V4 {
635                joinsplit_data: Some(joinsplit_data),
636                ..
637            } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
638            // No JoinSplits
639            Transaction::V1 { .. }
640            | Transaction::V2 {
641                joinsplit_data: None,
642                ..
643            }
644            | Transaction::V3 {
645                joinsplit_data: None,
646                ..
647            }
648            | Transaction::V4 {
649                joinsplit_data: None,
650                ..
651            }
652            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
653            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
654            Transaction::V6 { .. } => Box::new(std::iter::empty()),
655        }
656    }
657
658    /// Returns the number of `JoinSplit`s in this transaction, regardless of version.
659    pub fn joinsplit_count(&self) -> usize {
660        match self {
661            // JoinSplits with Bctv14 Proofs
662            Transaction::V2 {
663                joinsplit_data: Some(joinsplit_data),
664                ..
665            }
666            | Transaction::V3 {
667                joinsplit_data: Some(joinsplit_data),
668                ..
669            } => joinsplit_data.joinsplits().count(),
670            // JoinSplits with Groth Proofs
671            Transaction::V4 {
672                joinsplit_data: Some(joinsplit_data),
673                ..
674            } => joinsplit_data.joinsplits().count(),
675            // No JoinSplits
676            Transaction::V1 { .. }
677            | Transaction::V2 {
678                joinsplit_data: None,
679                ..
680            }
681            | Transaction::V3 {
682                joinsplit_data: None,
683                ..
684            }
685            | Transaction::V4 {
686                joinsplit_data: None,
687                ..
688            }
689            | Transaction::V5 { .. } => 0,
690            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
691            Transaction::V6 { .. } => 0,
692        }
693    }
694
695    /// Access the sprout::Nullifiers in this transaction, regardless of version.
696    pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
697        // This function returns a boxed iterator because the different
698        // transaction variants end up having different iterator types
699        // (we could extract bctv and groth as separate iterators, then chain
700        // them together, but that would be much harder to read and maintain)
701        match self {
702            // JoinSplits with Bctv14 Proofs
703            Transaction::V2 {
704                joinsplit_data: Some(joinsplit_data),
705                ..
706            }
707            | Transaction::V3 {
708                joinsplit_data: Some(joinsplit_data),
709                ..
710            } => Box::new(joinsplit_data.nullifiers()),
711            // JoinSplits with Groth Proofs
712            Transaction::V4 {
713                joinsplit_data: Some(joinsplit_data),
714                ..
715            } => Box::new(joinsplit_data.nullifiers()),
716            // No JoinSplits
717            Transaction::V1 { .. }
718            | Transaction::V2 {
719                joinsplit_data: None,
720                ..
721            }
722            | Transaction::V3 {
723                joinsplit_data: None,
724                ..
725            }
726            | Transaction::V4 {
727                joinsplit_data: None,
728                ..
729            }
730            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
731            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
732            Transaction::V6 { .. } => Box::new(std::iter::empty()),
733        }
734    }
735
736    /// Access the JoinSplit public validating key in this transaction,
737    /// regardless of version, if any.
738    pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
739        match self {
740            // JoinSplits with Bctv14 Proofs
741            Transaction::V2 {
742                joinsplit_data: Some(joinsplit_data),
743                ..
744            }
745            | Transaction::V3 {
746                joinsplit_data: Some(joinsplit_data),
747                ..
748            } => Some(joinsplit_data.pub_key),
749            // JoinSplits with Groth Proofs
750            Transaction::V4 {
751                joinsplit_data: Some(joinsplit_data),
752                ..
753            } => Some(joinsplit_data.pub_key),
754            // No JoinSplits
755            Transaction::V1 { .. }
756            | Transaction::V2 {
757                joinsplit_data: None,
758                ..
759            }
760            | Transaction::V3 {
761                joinsplit_data: None,
762                ..
763            }
764            | Transaction::V4 {
765                joinsplit_data: None,
766                ..
767            }
768            | Transaction::V5 { .. } => None,
769            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
770            Transaction::V6 { .. } => None,
771        }
772    }
773
774    /// Return if the transaction has any Sprout JoinSplit data.
775    pub fn has_sprout_joinsplit_data(&self) -> bool {
776        match self {
777            // No JoinSplits
778            Transaction::V1 { .. } | Transaction::V5 { .. } => false,
779            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
780            Transaction::V6 { .. } => false,
781
782            // JoinSplits-on-BCTV14
783            Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
784                joinsplit_data.is_some()
785            }
786
787            // JoinSplits-on-Groth16
788            Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
789        }
790    }
791
792    /// Returns the Sprout note commitments in this transaction.
793    pub fn sprout_note_commitments(
794        &self,
795    ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
796        match self {
797            // Return [`NoteCommitment`]s with [`Bctv14Proof`]s.
798            Transaction::V2 {
799                joinsplit_data: Some(joinsplit_data),
800                ..
801            }
802            | Transaction::V3 {
803                joinsplit_data: Some(joinsplit_data),
804                ..
805            } => Box::new(joinsplit_data.note_commitments()),
806
807            // Return [`NoteCommitment`]s with [`Groth16Proof`]s.
808            Transaction::V4 {
809                joinsplit_data: Some(joinsplit_data),
810                ..
811            } => Box::new(joinsplit_data.note_commitments()),
812
813            // Return an empty iterator.
814            Transaction::V2 {
815                joinsplit_data: None,
816                ..
817            }
818            | Transaction::V3 {
819                joinsplit_data: None,
820                ..
821            }
822            | Transaction::V4 {
823                joinsplit_data: None,
824                ..
825            }
826            | Transaction::V1 { .. }
827            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
828            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
829            Transaction::V6 { .. } => Box::new(std::iter::empty()),
830        }
831    }
832
833    // sapling
834
835    /// Access the deduplicated [`sapling::tree::Root`]s in this transaction,
836    /// regardless of version.
837    pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
838        // This function returns a boxed iterator because the different
839        // transaction variants end up having different iterator types
840        match self {
841            Transaction::V4 {
842                sapling_shielded_data: Some(sapling_shielded_data),
843                ..
844            } => Box::new(sapling_shielded_data.anchors()),
845
846            Transaction::V5 {
847                sapling_shielded_data: Some(sapling_shielded_data),
848                ..
849            } => Box::new(sapling_shielded_data.anchors()),
850
851            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
852            Transaction::V6 {
853                sapling_shielded_data: Some(sapling_shielded_data),
854                ..
855            } => Box::new(sapling_shielded_data.anchors()),
856
857            // No Spends
858            Transaction::V1 { .. }
859            | Transaction::V2 { .. }
860            | Transaction::V3 { .. }
861            | Transaction::V4 {
862                sapling_shielded_data: None,
863                ..
864            }
865            | Transaction::V5 {
866                sapling_shielded_data: None,
867                ..
868            } => Box::new(std::iter::empty()),
869            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
870            Transaction::V6 {
871                sapling_shielded_data: None,
872                ..
873            } => Box::new(std::iter::empty()),
874        }
875    }
876
877    /// Iterate over the sapling [`Spend`](sapling::Spend)s for this transaction,
878    /// returning `Spend<PerSpendAnchor>` regardless of the underlying
879    /// transaction version.
880    ///
881    /// Shared anchors in V5 transactions are copied into each sapling spend.
882    /// This allows the same code to validate spends from V4 and V5 transactions.
883    ///
884    /// # Correctness
885    ///
886    /// Do not use this function for serialization.
887    pub fn sapling_spends_per_anchor(
888        &self,
889    ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
890        match self {
891            Transaction::V4 {
892                sapling_shielded_data: Some(sapling_shielded_data),
893                ..
894            } => Box::new(sapling_shielded_data.spends_per_anchor()),
895            Transaction::V5 {
896                sapling_shielded_data: Some(sapling_shielded_data),
897                ..
898            } => Box::new(sapling_shielded_data.spends_per_anchor()),
899            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
900            Transaction::V6 {
901                sapling_shielded_data: Some(sapling_shielded_data),
902                ..
903            } => Box::new(sapling_shielded_data.spends_per_anchor()),
904
905            // No Spends
906            Transaction::V1 { .. }
907            | Transaction::V2 { .. }
908            | Transaction::V3 { .. }
909            | Transaction::V4 {
910                sapling_shielded_data: None,
911                ..
912            }
913            | Transaction::V5 {
914                sapling_shielded_data: None,
915                ..
916            } => Box::new(std::iter::empty()),
917            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
918            Transaction::V6 {
919                sapling_shielded_data: None,
920                ..
921            } => Box::new(std::iter::empty()),
922        }
923    }
924
925    /// Iterate over the sapling [`Output`](sapling::Output)s for this
926    /// transaction
927    pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
928        match self {
929            Transaction::V4 {
930                sapling_shielded_data: Some(sapling_shielded_data),
931                ..
932            } => Box::new(sapling_shielded_data.outputs()),
933            Transaction::V5 {
934                sapling_shielded_data: Some(sapling_shielded_data),
935                ..
936            } => Box::new(sapling_shielded_data.outputs()),
937            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
938            Transaction::V6 {
939                sapling_shielded_data: Some(sapling_shielded_data),
940                ..
941            } => Box::new(sapling_shielded_data.outputs()),
942
943            // No Outputs
944            Transaction::V1 { .. }
945            | Transaction::V2 { .. }
946            | Transaction::V3 { .. }
947            | Transaction::V4 {
948                sapling_shielded_data: None,
949                ..
950            }
951            | Transaction::V5 {
952                sapling_shielded_data: None,
953                ..
954            } => Box::new(std::iter::empty()),
955            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
956            Transaction::V6 {
957                sapling_shielded_data: None,
958                ..
959            } => Box::new(std::iter::empty()),
960        }
961    }
962
963    /// Access the sapling::Nullifiers in this transaction, regardless of version.
964    pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
965        // This function returns a boxed iterator because the different
966        // transaction variants end up having different iterator types
967        match self {
968            // Spends with Groth Proofs
969            Transaction::V4 {
970                sapling_shielded_data: Some(sapling_shielded_data),
971                ..
972            } => Box::new(sapling_shielded_data.nullifiers()),
973            Transaction::V5 {
974                sapling_shielded_data: Some(sapling_shielded_data),
975                ..
976            } => Box::new(sapling_shielded_data.nullifiers()),
977            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
978            Transaction::V6 {
979                sapling_shielded_data: Some(sapling_shielded_data),
980                ..
981            } => Box::new(sapling_shielded_data.nullifiers()),
982
983            // No Spends
984            Transaction::V1 { .. }
985            | Transaction::V2 { .. }
986            | Transaction::V3 { .. }
987            | Transaction::V4 {
988                sapling_shielded_data: None,
989                ..
990            }
991            | Transaction::V5 {
992                sapling_shielded_data: None,
993                ..
994            } => Box::new(std::iter::empty()),
995            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
996            Transaction::V6 {
997                sapling_shielded_data: None,
998                ..
999            } => Box::new(std::iter::empty()),
1000        }
1001    }
1002
1003    /// Returns the Sapling note commitments in this transaction, regardless of version.
1004    pub fn sapling_note_commitments(
1005        &self,
1006    ) -> Box<dyn Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> + '_> {
1007        // This function returns a boxed iterator because the different
1008        // transaction variants end up having different iterator types
1009        match self {
1010            // Spends with Groth16 Proofs
1011            Transaction::V4 {
1012                sapling_shielded_data: Some(sapling_shielded_data),
1013                ..
1014            } => Box::new(sapling_shielded_data.note_commitments()),
1015            Transaction::V5 {
1016                sapling_shielded_data: Some(sapling_shielded_data),
1017                ..
1018            } => Box::new(sapling_shielded_data.note_commitments()),
1019            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1020            Transaction::V6 {
1021                sapling_shielded_data: Some(sapling_shielded_data),
1022                ..
1023            } => Box::new(sapling_shielded_data.note_commitments()),
1024
1025            // No Spends
1026            Transaction::V1 { .. }
1027            | Transaction::V2 { .. }
1028            | Transaction::V3 { .. }
1029            | Transaction::V4 {
1030                sapling_shielded_data: None,
1031                ..
1032            }
1033            | Transaction::V5 {
1034                sapling_shielded_data: None,
1035                ..
1036            } => Box::new(std::iter::empty()),
1037            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1038            Transaction::V6 {
1039                sapling_shielded_data: None,
1040                ..
1041            } => Box::new(std::iter::empty()),
1042        }
1043    }
1044
1045    /// Returns `true` if the transaction has any Sapling shielded data.
1046    pub fn has_sapling_shielded_data(&self) -> bool {
1047        match self {
1048            Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1049            Transaction::V4 {
1050                sapling_shielded_data,
1051                ..
1052            } => sapling_shielded_data.is_some(),
1053            Transaction::V5 {
1054                sapling_shielded_data,
1055                ..
1056            } => sapling_shielded_data.is_some(),
1057            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1058            Transaction::V6 {
1059                sapling_shielded_data,
1060                ..
1061            } => sapling_shielded_data.is_some(),
1062        }
1063    }
1064
1065    // orchard
1066
1067    /// Access the [`orchard::ShieldedData`] in this transaction,
1068    /// regardless of version.
1069    pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1070        match self {
1071            // Maybe Orchard shielded data
1072            Transaction::V5 {
1073                orchard_shielded_data,
1074                ..
1075            } => orchard_shielded_data.as_ref(),
1076            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1077            Transaction::V6 {
1078                orchard_shielded_data,
1079                ..
1080            } => orchard_shielded_data.as_ref(),
1081
1082            // No Orchard shielded data
1083            Transaction::V1 { .. }
1084            | Transaction::V2 { .. }
1085            | Transaction::V3 { .. }
1086            | Transaction::V4 { .. } => None,
1087        }
1088    }
1089
1090    /// Iterate over the [`orchard::Action`]s in this transaction, if there are any,
1091    /// regardless of version.
1092    pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1093        self.orchard_shielded_data()
1094            .into_iter()
1095            .flat_map(orchard::ShieldedData::actions)
1096    }
1097
1098    /// Access the [`orchard::Nullifier`]s in this transaction, if there are any,
1099    /// regardless of version.
1100    pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1101        self.orchard_shielded_data()
1102            .into_iter()
1103            .flat_map(orchard::ShieldedData::nullifiers)
1104    }
1105
1106    /// Access the note commitments in this transaction, if there are any,
1107    /// regardless of version.
1108    pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1109        self.orchard_shielded_data()
1110            .into_iter()
1111            .flat_map(orchard::ShieldedData::note_commitments)
1112    }
1113
1114    /// Access the [`orchard::Flags`] in this transaction, if there is any,
1115    /// regardless of version.
1116    pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1117        self.orchard_shielded_data()
1118            .map(|orchard_shielded_data| orchard_shielded_data.flags)
1119    }
1120
1121    /// Return if the transaction has any Orchard shielded data,
1122    /// regardless of version.
1123    pub fn has_orchard_shielded_data(&self) -> bool {
1124        self.orchard_shielded_data().is_some()
1125    }
1126
1127    // value balances
1128
1129    /// Return the transparent value balance,
1130    /// using the outputs spent by this transaction.
1131    ///
1132    /// See `transparent_value_balance` for details.
1133    #[allow(clippy::unwrap_in_result)]
1134    fn transparent_value_balance_from_outputs(
1135        &self,
1136        outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1137    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1138        let input_value = self
1139            .inputs()
1140            .iter()
1141            .map(|i| i.value_from_outputs(outputs))
1142            .sum::<Result<Amount<NonNegative>, AmountError>>()
1143            .map_err(ValueBalanceError::Transparent)?
1144            .constrain()
1145            .expect("conversion from NonNegative to NegativeAllowed is always valid");
1146
1147        let output_value = self
1148            .outputs()
1149            .iter()
1150            .map(|o| o.value())
1151            .sum::<Result<Amount<NonNegative>, AmountError>>()
1152            .map_err(ValueBalanceError::Transparent)?
1153            .constrain()
1154            .expect("conversion from NonNegative to NegativeAllowed is always valid");
1155
1156        (input_value - output_value)
1157            .map(ValueBalance::from_transparent_amount)
1158            .map_err(ValueBalanceError::Transparent)
1159    }
1160
1161    /// Returns the `vpub_old` fields from `JoinSplit`s in this transaction,
1162    /// regardless of version, in the order they appear in the transaction.
1163    ///
1164    /// These values are added to the sprout chain value pool,
1165    /// and removed from the value pool of this transaction.
1166    pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1167        match self {
1168            // JoinSplits with Bctv14 Proofs
1169            Transaction::V2 {
1170                joinsplit_data: Some(joinsplit_data),
1171                ..
1172            }
1173            | Transaction::V3 {
1174                joinsplit_data: Some(joinsplit_data),
1175                ..
1176            } => Box::new(
1177                joinsplit_data
1178                    .joinsplits()
1179                    .map(|joinsplit| &joinsplit.vpub_old),
1180            ),
1181            // JoinSplits with Groth Proofs
1182            Transaction::V4 {
1183                joinsplit_data: Some(joinsplit_data),
1184                ..
1185            } => Box::new(
1186                joinsplit_data
1187                    .joinsplits()
1188                    .map(|joinsplit| &joinsplit.vpub_old),
1189            ),
1190            // No JoinSplits
1191            Transaction::V1 { .. }
1192            | Transaction::V2 {
1193                joinsplit_data: None,
1194                ..
1195            }
1196            | Transaction::V3 {
1197                joinsplit_data: None,
1198                ..
1199            }
1200            | Transaction::V4 {
1201                joinsplit_data: None,
1202                ..
1203            }
1204            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1205            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1206            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1207        }
1208    }
1209
1210    /// Returns the `vpub_new` fields from `JoinSplit`s in this transaction,
1211    /// regardless of version, in the order they appear in the transaction.
1212    ///
1213    /// These values are removed from the value pool of this transaction.
1214    /// and added to the sprout chain value pool.
1215    pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1216        match self {
1217            // JoinSplits with Bctv14 Proofs
1218            Transaction::V2 {
1219                joinsplit_data: Some(joinsplit_data),
1220                ..
1221            }
1222            | Transaction::V3 {
1223                joinsplit_data: Some(joinsplit_data),
1224                ..
1225            } => Box::new(
1226                joinsplit_data
1227                    .joinsplits()
1228                    .map(|joinsplit| &joinsplit.vpub_new),
1229            ),
1230            // JoinSplits with Groth Proofs
1231            Transaction::V4 {
1232                joinsplit_data: Some(joinsplit_data),
1233                ..
1234            } => Box::new(
1235                joinsplit_data
1236                    .joinsplits()
1237                    .map(|joinsplit| &joinsplit.vpub_new),
1238            ),
1239            // No JoinSplits
1240            Transaction::V1 { .. }
1241            | Transaction::V2 {
1242                joinsplit_data: None,
1243                ..
1244            }
1245            | Transaction::V3 {
1246                joinsplit_data: None,
1247                ..
1248            }
1249            | Transaction::V4 {
1250                joinsplit_data: None,
1251                ..
1252            }
1253            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1254            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1255            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1256        }
1257    }
1258
1259    /// Return a list of sprout value balances,
1260    /// the changes in the transaction value pool due to each sprout `JoinSplit`.
1261    ///
1262    /// Each value balance is the sprout `vpub_new` field, minus the `vpub_old` field.
1263    ///
1264    /// See [`sprout_value_balance`][svb] for details.
1265    ///
1266    /// [svb]: crate::transaction::Transaction::sprout_value_balance
1267    fn sprout_joinsplit_value_balances(
1268        &self,
1269    ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1270        let joinsplit_value_balances = match self {
1271            Transaction::V2 {
1272                joinsplit_data: Some(joinsplit_data),
1273                ..
1274            }
1275            | Transaction::V3 {
1276                joinsplit_data: Some(joinsplit_data),
1277                ..
1278            } => joinsplit_data.joinsplit_value_balances(),
1279            Transaction::V4 {
1280                joinsplit_data: Some(joinsplit_data),
1281                ..
1282            } => joinsplit_data.joinsplit_value_balances(),
1283            Transaction::V1 { .. }
1284            | Transaction::V2 {
1285                joinsplit_data: None,
1286                ..
1287            }
1288            | Transaction::V3 {
1289                joinsplit_data: None,
1290                ..
1291            }
1292            | Transaction::V4 {
1293                joinsplit_data: None,
1294                ..
1295            }
1296            | Transaction::V5 { .. } => Box::new(iter::empty()),
1297            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1298            Transaction::V6 { .. } => Box::new(iter::empty()),
1299        };
1300
1301        joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1302    }
1303
1304    /// Return the sprout value balance,
1305    /// the change in the transaction value pool due to sprout `JoinSplit`s.
1306    ///
1307    /// The sum of all sprout `vpub_new` fields, minus the sum of all `vpub_old` fields.
1308    ///
1309    /// Positive values are added to this transaction's value pool,
1310    /// and removed from the sprout chain value pool.
1311    /// Negative values are removed from this transaction,
1312    /// and added to the sprout pool.
1313    ///
1314    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1315    fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1316        self.sprout_joinsplit_value_balances().sum()
1317    }
1318
1319    /// Return the sapling value balance,
1320    /// the change in the transaction value pool due to sapling `Spend`s and `Output`s.
1321    ///
1322    /// Returns the `valueBalanceSapling` field in this transaction.
1323    ///
1324    /// Positive values are added to this transaction's value pool,
1325    /// and removed from the sapling chain value pool.
1326    /// Negative values are removed from this transaction,
1327    /// and added to sapling pool.
1328    ///
1329    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1330    pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1331        let sapling_value_balance = match self {
1332            Transaction::V4 {
1333                sapling_shielded_data: Some(sapling_shielded_data),
1334                ..
1335            } => sapling_shielded_data.value_balance,
1336            Transaction::V5 {
1337                sapling_shielded_data: Some(sapling_shielded_data),
1338                ..
1339            } => sapling_shielded_data.value_balance,
1340            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1341            Transaction::V6 {
1342                sapling_shielded_data: Some(sapling_shielded_data),
1343                ..
1344            } => sapling_shielded_data.value_balance,
1345
1346            Transaction::V1 { .. }
1347            | Transaction::V2 { .. }
1348            | Transaction::V3 { .. }
1349            | Transaction::V4 {
1350                sapling_shielded_data: None,
1351                ..
1352            }
1353            | Transaction::V5 {
1354                sapling_shielded_data: None,
1355                ..
1356            } => Amount::zero(),
1357            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1358            Transaction::V6 {
1359                sapling_shielded_data: None,
1360                ..
1361            } => Amount::zero(),
1362        };
1363
1364        ValueBalance::from_sapling_amount(sapling_value_balance)
1365    }
1366
1367    /// Returns the Sapling binding signature for this transaction.
1368    ///
1369    /// Returns `Some(binding_sig)` for transactions that contain Sapling shielded
1370    /// data (V4+), or `None` for transactions without Sapling components.
1371    pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1372        match self {
1373            Transaction::V4 {
1374                sapling_shielded_data: Some(sapling_shielded_data),
1375                ..
1376            } => Some(sapling_shielded_data.binding_sig),
1377            Transaction::V5 {
1378                sapling_shielded_data: Some(sapling_shielded_data),
1379                ..
1380            } => Some(sapling_shielded_data.binding_sig),
1381            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1382            Transaction::V6 {
1383                sapling_shielded_data: Some(sapling_shielded_data),
1384                ..
1385            } => Some(sapling_shielded_data.binding_sig),
1386            _ => None,
1387        }
1388    }
1389
1390    /// Returns the JoinSplit public key for this transaction.
1391    ///
1392    /// Returns `Some(pub_key)` for transactions that contain JoinSplit data (V2-V4),
1393    /// or `None` for transactions without JoinSplit components or unsupported versions.
1394    ///
1395    /// ## Note
1396    /// JoinSplits are deprecated in favor of Sapling and Orchard
1397    pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1398        match self {
1399            Transaction::V2 {
1400                joinsplit_data: Some(joinsplit_data),
1401                ..
1402            } => Some(joinsplit_data.pub_key),
1403            Transaction::V3 {
1404                joinsplit_data: Some(joinsplit_data),
1405                ..
1406            } => Some(joinsplit_data.pub_key),
1407            Transaction::V4 {
1408                joinsplit_data: Some(joinsplit_data),
1409                ..
1410            } => Some(joinsplit_data.pub_key),
1411            _ => None,
1412        }
1413    }
1414
1415    /// Returns the JoinSplit signature this for transaction.
1416    ///
1417    /// Returns `Some(signature)` for transactions that contain JoinSplit data (V2-V4),
1418    /// or `None` for transactions without JoinSplit components or unsupported versions.
1419    ///
1420    /// ## Note
1421    /// JoinSplits are deprecated in favor of Sapling and Orchard
1422    pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1423        match self {
1424            Transaction::V2 {
1425                joinsplit_data: Some(joinsplit_data),
1426                ..
1427            } => Some(joinsplit_data.sig),
1428            Transaction::V3 {
1429                joinsplit_data: Some(joinsplit_data),
1430                ..
1431            } => Some(joinsplit_data.sig),
1432            Transaction::V4 {
1433                joinsplit_data: Some(joinsplit_data),
1434                ..
1435            } => Some(joinsplit_data.sig),
1436            _ => None,
1437        }
1438    }
1439
1440    /// Return the orchard value balance, the change in the transaction value
1441    /// pool due to [`orchard::Action`]s.
1442    ///
1443    /// Returns the `valueBalanceOrchard` field in this transaction.
1444    ///
1445    /// Positive values are added to this transaction's value pool,
1446    /// and removed from the orchard chain value pool.
1447    /// Negative values are removed from this transaction,
1448    /// and added to orchard pool.
1449    ///
1450    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1451    pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1452        let orchard_value_balance = self
1453            .orchard_shielded_data()
1454            .map(|shielded_data| shielded_data.value_balance)
1455            .unwrap_or_else(Amount::zero);
1456
1457        ValueBalance::from_orchard_amount(orchard_value_balance)
1458    }
1459
1460    /// Returns the value balances for this transaction using the provided transparent outputs.
1461    pub(crate) fn value_balance_from_outputs(
1462        &self,
1463        outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1464    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1465        self.transparent_value_balance_from_outputs(outputs)?
1466            + self.sprout_value_balance()?
1467            + self.sapling_value_balance()
1468            + self.orchard_value_balance()
1469    }
1470
1471    /// Returns the value balances for this transaction.
1472    ///
1473    /// These are the changes in the transaction value pool, split up into transparent, Sprout,
1474    /// Sapling, and Orchard values.
1475    ///
1476    /// Calculated as the sum of the inputs and outputs from each pool, or the sum of the value
1477    /// balances from each pool.
1478    ///
1479    /// Positive values are added to this transaction's value pool, and removed from the
1480    /// corresponding chain value pool. Negative values are removed from this transaction, and added
1481    /// to the corresponding pool.
1482    ///
1483    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1484    ///
1485    /// `utxos` must contain the utxos of every input in the transaction, including UTXOs created by
1486    /// earlier transactions in this block.
1487    ///
1488    /// ## Note
1489    ///
1490    /// The chain value pool has the opposite sign to the transaction value pool.
1491    pub fn value_balance(
1492        &self,
1493        utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1494    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1495        self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1496    }
1497
1498    /// Converts [`Transaction`] to [`zcash_primitives::transaction::Transaction`].
1499    ///
1500    /// If the tx contains a network upgrade, this network upgrade must match the passed `nu`. The
1501    /// passed `nu` must also contain a consensus branch id convertible to its `librustzcash`
1502    /// equivalent.
1503    pub(crate) fn to_librustzcash(
1504        &self,
1505        nu: NetworkUpgrade,
1506    ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1507        if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1508            return Err(crate::Error::InvalidConsensusBranchId);
1509        }
1510
1511        let Some(branch_id) = nu.branch_id() else {
1512            return Err(crate::Error::InvalidConsensusBranchId);
1513        };
1514
1515        let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1516            return Err(crate::Error::InvalidConsensusBranchId);
1517        };
1518
1519        Ok(zcash_primitives::transaction::Transaction::read(
1520            &self.zcash_serialize_to_vec()?[..],
1521            branch_id,
1522        )?)
1523    }
1524
1525    // Common Sapling & Orchard Properties
1526
1527    /// Does this transaction have shielded inputs or outputs?
1528    pub fn has_shielded_data(&self) -> bool {
1529        self.has_shielded_inputs() || self.has_shielded_outputs()
1530    }
1531
1532    /// Get the version group ID for this transaction, if any.
1533    pub fn version_group_id(&self) -> Option<u32> {
1534        // We could store the parsed version group ID and return that,
1535        // but since the consensus rules constraint it, we can just return
1536        // the value that must have been parsed.
1537        match self {
1538            Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1539            Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1540            Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1541            Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1542            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1543            Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1544        }
1545    }
1546
1547    /// Access the zip233 amount field of this transaction, regardless of version.
1548    pub fn zip233_amount(&self) -> Amount<NonNegative> {
1549        match self {
1550            Transaction::V1 { .. }
1551            | Transaction::V2 { .. }
1552            | Transaction::V3 { .. }
1553            | Transaction::V4 { .. }
1554            | Transaction::V5 { .. } => Amount::zero(),
1555            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1556            Transaction::V6 { zip233_amount, .. } => *zip233_amount,
1557        }
1558    }
1559}
1560
1561#[cfg(any(test, feature = "proptest-impl"))]
1562impl Transaction {
1563    /// Updates the [`NetworkUpgrade`] for this transaction.
1564    ///
1565    /// ## Notes
1566    ///
1567    /// - Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.
1568    pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1569        match self {
1570            Transaction::V1 { .. }
1571            | Transaction::V2 { .. }
1572            | Transaction::V3 { .. }
1573            | Transaction::V4 { .. } => Err(
1574                "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1575            ),
1576            Transaction::V5 {
1577                ref mut network_upgrade,
1578                ..
1579            } => {
1580                *network_upgrade = nu;
1581                Ok(())
1582            }
1583            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1584            Transaction::V6 {
1585                ref mut network_upgrade,
1586                ..
1587            } => {
1588                *network_upgrade = nu;
1589                Ok(())
1590            }
1591        }
1592    }
1593
1594    /// Modify the expiry height of this transaction.
1595    ///
1596    /// # Panics
1597    ///
1598    /// - if called on a v1 or v2 transaction
1599    pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1600        match self {
1601            Transaction::V1 { .. } | Transaction::V2 { .. } => {
1602                panic!("v1 and v2 transactions are not supported")
1603            }
1604            Transaction::V3 {
1605                ref mut expiry_height,
1606                ..
1607            }
1608            | Transaction::V4 {
1609                ref mut expiry_height,
1610                ..
1611            }
1612            | Transaction::V5 {
1613                ref mut expiry_height,
1614                ..
1615            } => expiry_height,
1616            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1617            Transaction::V6 {
1618                ref mut expiry_height,
1619                ..
1620            } => expiry_height,
1621        }
1622    }
1623
1624    /// Modify the transparent inputs of this transaction, regardless of version.
1625    pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1626        match self {
1627            Transaction::V1 { ref mut inputs, .. } => inputs,
1628            Transaction::V2 { ref mut inputs, .. } => inputs,
1629            Transaction::V3 { ref mut inputs, .. } => inputs,
1630            Transaction::V4 { ref mut inputs, .. } => inputs,
1631            Transaction::V5 { ref mut inputs, .. } => inputs,
1632            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1633            Transaction::V6 { ref mut inputs, .. } => inputs,
1634        }
1635    }
1636
1637    /// Modify the `value_balance` field from the `orchard::ShieldedData` in this transaction,
1638    /// regardless of version.
1639    ///
1640    /// See `orchard_value_balance` for details.
1641    pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1642        self.orchard_shielded_data_mut()
1643            .map(|shielded_data| &mut shielded_data.value_balance)
1644    }
1645
1646    /// Modify the `value_balance` field from the `sapling::ShieldedData` in this transaction,
1647    /// regardless of version.
1648    ///
1649    /// See `sapling_value_balance` for details.
1650    pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1651        match self {
1652            Transaction::V4 {
1653                sapling_shielded_data: Some(sapling_shielded_data),
1654                ..
1655            } => Some(&mut sapling_shielded_data.value_balance),
1656            Transaction::V5 {
1657                sapling_shielded_data: Some(sapling_shielded_data),
1658                ..
1659            } => Some(&mut sapling_shielded_data.value_balance),
1660            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1661            Transaction::V6 {
1662                sapling_shielded_data: Some(sapling_shielded_data),
1663                ..
1664            } => Some(&mut sapling_shielded_data.value_balance),
1665            Transaction::V1 { .. }
1666            | Transaction::V2 { .. }
1667            | Transaction::V3 { .. }
1668            | Transaction::V4 {
1669                sapling_shielded_data: None,
1670                ..
1671            }
1672            | Transaction::V5 {
1673                sapling_shielded_data: None,
1674                ..
1675            } => None,
1676            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1677            Transaction::V6 {
1678                sapling_shielded_data: None,
1679                ..
1680            } => None,
1681        }
1682    }
1683
1684    /// Modify the `vpub_new` fields from `JoinSplit`s in this transaction,
1685    /// regardless of version, in the order they appear in the transaction.
1686    ///
1687    /// See `input_values_from_sprout` for details.
1688    pub fn input_values_from_sprout_mut(
1689        &mut self,
1690    ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1691        match self {
1692            // JoinSplits with Bctv14 Proofs
1693            Transaction::V2 {
1694                joinsplit_data: Some(joinsplit_data),
1695                ..
1696            }
1697            | Transaction::V3 {
1698                joinsplit_data: Some(joinsplit_data),
1699                ..
1700            } => Box::new(
1701                joinsplit_data
1702                    .joinsplits_mut()
1703                    .map(|joinsplit| &mut joinsplit.vpub_new),
1704            ),
1705            // JoinSplits with Groth Proofs
1706            Transaction::V4 {
1707                joinsplit_data: Some(joinsplit_data),
1708                ..
1709            } => Box::new(
1710                joinsplit_data
1711                    .joinsplits_mut()
1712                    .map(|joinsplit| &mut joinsplit.vpub_new),
1713            ),
1714            // No JoinSplits
1715            Transaction::V1 { .. }
1716            | Transaction::V2 {
1717                joinsplit_data: None,
1718                ..
1719            }
1720            | Transaction::V3 {
1721                joinsplit_data: None,
1722                ..
1723            }
1724            | Transaction::V4 {
1725                joinsplit_data: None,
1726                ..
1727            }
1728            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1729            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1730            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1731        }
1732    }
1733
1734    /// Modify the `vpub_old` fields from `JoinSplit`s in this transaction,
1735    /// regardless of version, in the order they appear in the transaction.
1736    ///
1737    /// See `output_values_to_sprout` for details.
1738    pub fn output_values_to_sprout_mut(
1739        &mut self,
1740    ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1741        match self {
1742            // JoinSplits with Bctv14 Proofs
1743            Transaction::V2 {
1744                joinsplit_data: Some(joinsplit_data),
1745                ..
1746            }
1747            | Transaction::V3 {
1748                joinsplit_data: Some(joinsplit_data),
1749                ..
1750            } => Box::new(
1751                joinsplit_data
1752                    .joinsplits_mut()
1753                    .map(|joinsplit| &mut joinsplit.vpub_old),
1754            ),
1755            // JoinSplits with Groth16 Proofs
1756            Transaction::V4 {
1757                joinsplit_data: Some(joinsplit_data),
1758                ..
1759            } => Box::new(
1760                joinsplit_data
1761                    .joinsplits_mut()
1762                    .map(|joinsplit| &mut joinsplit.vpub_old),
1763            ),
1764            // No JoinSplits
1765            Transaction::V1 { .. }
1766            | Transaction::V2 {
1767                joinsplit_data: None,
1768                ..
1769            }
1770            | Transaction::V3 {
1771                joinsplit_data: None,
1772                ..
1773            }
1774            | Transaction::V4 {
1775                joinsplit_data: None,
1776                ..
1777            }
1778            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1779            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1780            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1781        }
1782    }
1783
1784    /// Modify the transparent output values of this transaction, regardless of version.
1785    pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1786        self.outputs_mut()
1787            .iter_mut()
1788            .map(|output| &mut output.value)
1789    }
1790
1791    /// Modify the [`orchard::ShieldedData`] in this transaction,
1792    /// regardless of version.
1793    pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1794        match self {
1795            Transaction::V5 {
1796                orchard_shielded_data: Some(orchard_shielded_data),
1797                ..
1798            } => Some(orchard_shielded_data),
1799            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1800            Transaction::V6 {
1801                orchard_shielded_data: Some(orchard_shielded_data),
1802                ..
1803            } => Some(orchard_shielded_data),
1804
1805            Transaction::V1 { .. }
1806            | Transaction::V2 { .. }
1807            | Transaction::V3 { .. }
1808            | Transaction::V4 { .. }
1809            | Transaction::V5 {
1810                orchard_shielded_data: None,
1811                ..
1812            } => None,
1813            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1814            Transaction::V6 {
1815                orchard_shielded_data: None,
1816                ..
1817            } => None,
1818        }
1819    }
1820
1821    /// Modify the transparent outputs of this transaction, regardless of version.
1822    pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1823        match self {
1824            Transaction::V1 {
1825                ref mut outputs, ..
1826            } => outputs,
1827            Transaction::V2 {
1828                ref mut outputs, ..
1829            } => outputs,
1830            Transaction::V3 {
1831                ref mut outputs, ..
1832            } => outputs,
1833            Transaction::V4 {
1834                ref mut outputs, ..
1835            } => outputs,
1836            Transaction::V5 {
1837                ref mut outputs, ..
1838            } => outputs,
1839            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1840            Transaction::V6 {
1841                ref mut outputs, ..
1842            } => outputs,
1843        }
1844    }
1845}