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