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 Sprout `GenericJoinSplit`s in this transaction, regardless of version.
616    pub fn sprout_joinsplits(&self) -> Box<dyn Iterator<Item = sprout::GenericJoinSplit> + '_> {
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            } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
627            // JoinSplits with Groth Proofs
628            Transaction::V4 {
629                joinsplit_data: Some(joinsplit_data),
630                ..
631            } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
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 { .. } => Box::new(std::iter::empty()),
647            #[cfg(feature = "tx_v6")]
648            Transaction::V6 { .. } => Box::new(std::iter::empty()),
649        }
650    }
651
652    /// Returns the number of `JoinSplit`s in this transaction, regardless of version.
653    pub fn joinsplit_count(&self) -> usize {
654        match self {
655            // JoinSplits with Bctv14 Proofs
656            Transaction::V2 {
657                joinsplit_data: Some(joinsplit_data),
658                ..
659            }
660            | Transaction::V3 {
661                joinsplit_data: Some(joinsplit_data),
662                ..
663            } => joinsplit_data.joinsplits().count(),
664            // JoinSplits with Groth Proofs
665            Transaction::V4 {
666                joinsplit_data: Some(joinsplit_data),
667                ..
668            } => joinsplit_data.joinsplits().count(),
669            // No JoinSplits
670            Transaction::V1 { .. }
671            | Transaction::V2 {
672                joinsplit_data: None,
673                ..
674            }
675            | Transaction::V3 {
676                joinsplit_data: None,
677                ..
678            }
679            | Transaction::V4 {
680                joinsplit_data: None,
681                ..
682            }
683            | Transaction::V5 { .. } => 0,
684            #[cfg(feature = "tx_v6")]
685            Transaction::V6 { .. } => 0,
686        }
687    }
688
689    /// Access the sprout::Nullifiers in this transaction, regardless of version.
690    pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
691        // This function returns a boxed iterator because the different
692        // transaction variants end up having different iterator types
693        // (we could extract bctv and groth as separate iterators, then chain
694        // them together, but that would be much harder to read and maintain)
695        match self {
696            // JoinSplits with Bctv14 Proofs
697            Transaction::V2 {
698                joinsplit_data: Some(joinsplit_data),
699                ..
700            }
701            | Transaction::V3 {
702                joinsplit_data: Some(joinsplit_data),
703                ..
704            } => Box::new(joinsplit_data.nullifiers()),
705            // JoinSplits with Groth Proofs
706            Transaction::V4 {
707                joinsplit_data: Some(joinsplit_data),
708                ..
709            } => Box::new(joinsplit_data.nullifiers()),
710            // No JoinSplits
711            Transaction::V1 { .. }
712            | Transaction::V2 {
713                joinsplit_data: None,
714                ..
715            }
716            | Transaction::V3 {
717                joinsplit_data: None,
718                ..
719            }
720            | Transaction::V4 {
721                joinsplit_data: None,
722                ..
723            }
724            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
725            #[cfg(feature = "tx_v6")]
726            Transaction::V6 { .. } => Box::new(std::iter::empty()),
727        }
728    }
729
730    /// Access the JoinSplit public validating key in this transaction,
731    /// regardless of version, if any.
732    pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
733        match self {
734            // JoinSplits with Bctv14 Proofs
735            Transaction::V2 {
736                joinsplit_data: Some(joinsplit_data),
737                ..
738            }
739            | Transaction::V3 {
740                joinsplit_data: Some(joinsplit_data),
741                ..
742            } => Some(joinsplit_data.pub_key),
743            // JoinSplits with Groth Proofs
744            Transaction::V4 {
745                joinsplit_data: Some(joinsplit_data),
746                ..
747            } => Some(joinsplit_data.pub_key),
748            // No JoinSplits
749            Transaction::V1 { .. }
750            | Transaction::V2 {
751                joinsplit_data: None,
752                ..
753            }
754            | Transaction::V3 {
755                joinsplit_data: None,
756                ..
757            }
758            | Transaction::V4 {
759                joinsplit_data: None,
760                ..
761            }
762            | Transaction::V5 { .. } => None,
763            #[cfg(feature = "tx_v6")]
764            Transaction::V6 { .. } => None,
765        }
766    }
767
768    /// Return if the transaction has any Sprout JoinSplit data.
769    pub fn has_sprout_joinsplit_data(&self) -> bool {
770        match self {
771            // No JoinSplits
772            Transaction::V1 { .. } | Transaction::V5 { .. } => false,
773            #[cfg(feature = "tx_v6")]
774            Transaction::V6 { .. } => false,
775
776            // JoinSplits-on-BCTV14
777            Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
778                joinsplit_data.is_some()
779            }
780
781            // JoinSplits-on-Groth16
782            Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
783        }
784    }
785
786    /// Returns the Sprout note commitments in this transaction.
787    pub fn sprout_note_commitments(
788        &self,
789    ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
790        match self {
791            // Return [`NoteCommitment`]s with [`Bctv14Proof`]s.
792            Transaction::V2 {
793                joinsplit_data: Some(joinsplit_data),
794                ..
795            }
796            | Transaction::V3 {
797                joinsplit_data: Some(joinsplit_data),
798                ..
799            } => Box::new(joinsplit_data.note_commitments()),
800
801            // Return [`NoteCommitment`]s with [`Groth16Proof`]s.
802            Transaction::V4 {
803                joinsplit_data: Some(joinsplit_data),
804                ..
805            } => Box::new(joinsplit_data.note_commitments()),
806
807            // Return an empty iterator.
808            Transaction::V2 {
809                joinsplit_data: None,
810                ..
811            }
812            | Transaction::V3 {
813                joinsplit_data: None,
814                ..
815            }
816            | Transaction::V4 {
817                joinsplit_data: None,
818                ..
819            }
820            | Transaction::V1 { .. }
821            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
822            #[cfg(feature = "tx_v6")]
823            Transaction::V6 { .. } => Box::new(std::iter::empty()),
824        }
825    }
826
827    // sapling
828
829    /// Access the deduplicated [`sapling::tree::Root`]s in this transaction,
830    /// regardless of version.
831    pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
832        // This function returns a boxed iterator because the different
833        // transaction variants end up having different iterator types
834        match self {
835            Transaction::V4 {
836                sapling_shielded_data: Some(sapling_shielded_data),
837                ..
838            } => Box::new(sapling_shielded_data.anchors()),
839
840            Transaction::V5 {
841                sapling_shielded_data: Some(sapling_shielded_data),
842                ..
843            } => Box::new(sapling_shielded_data.anchors()),
844
845            #[cfg(feature = "tx_v6")]
846            Transaction::V6 {
847                sapling_shielded_data: Some(sapling_shielded_data),
848                ..
849            } => Box::new(sapling_shielded_data.anchors()),
850
851            // No Spends
852            Transaction::V1 { .. }
853            | Transaction::V2 { .. }
854            | Transaction::V3 { .. }
855            | Transaction::V4 {
856                sapling_shielded_data: None,
857                ..
858            }
859            | Transaction::V5 {
860                sapling_shielded_data: None,
861                ..
862            } => Box::new(std::iter::empty()),
863            #[cfg(feature = "tx_v6")]
864            Transaction::V6 {
865                sapling_shielded_data: None,
866                ..
867            } => Box::new(std::iter::empty()),
868        }
869    }
870
871    /// Iterate over the sapling [`Spend`](sapling::Spend)s for this transaction,
872    /// returning `Spend<PerSpendAnchor>` regardless of the underlying
873    /// transaction version.
874    ///
875    /// Shared anchors in V5 transactions are copied into each sapling spend.
876    /// This allows the same code to validate spends from V4 and V5 transactions.
877    ///
878    /// # Correctness
879    ///
880    /// Do not use this function for serialization.
881    pub fn sapling_spends_per_anchor(
882        &self,
883    ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
884        match self {
885            Transaction::V4 {
886                sapling_shielded_data: Some(sapling_shielded_data),
887                ..
888            } => Box::new(sapling_shielded_data.spends_per_anchor()),
889            Transaction::V5 {
890                sapling_shielded_data: Some(sapling_shielded_data),
891                ..
892            } => Box::new(sapling_shielded_data.spends_per_anchor()),
893            #[cfg(feature = "tx_v6")]
894            Transaction::V6 {
895                sapling_shielded_data: Some(sapling_shielded_data),
896                ..
897            } => Box::new(sapling_shielded_data.spends_per_anchor()),
898
899            // No Spends
900            Transaction::V1 { .. }
901            | Transaction::V2 { .. }
902            | Transaction::V3 { .. }
903            | Transaction::V4 {
904                sapling_shielded_data: None,
905                ..
906            }
907            | Transaction::V5 {
908                sapling_shielded_data: None,
909                ..
910            } => Box::new(std::iter::empty()),
911            #[cfg(feature = "tx_v6")]
912            Transaction::V6 {
913                sapling_shielded_data: None,
914                ..
915            } => Box::new(std::iter::empty()),
916        }
917    }
918
919    /// Iterate over the sapling [`Output`](sapling::Output)s for this
920    /// transaction
921    pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
922        match self {
923            Transaction::V4 {
924                sapling_shielded_data: Some(sapling_shielded_data),
925                ..
926            } => Box::new(sapling_shielded_data.outputs()),
927            Transaction::V5 {
928                sapling_shielded_data: Some(sapling_shielded_data),
929                ..
930            } => Box::new(sapling_shielded_data.outputs()),
931            #[cfg(feature = "tx_v6")]
932            Transaction::V6 {
933                sapling_shielded_data: Some(sapling_shielded_data),
934                ..
935            } => Box::new(sapling_shielded_data.outputs()),
936
937            // No Outputs
938            Transaction::V1 { .. }
939            | Transaction::V2 { .. }
940            | Transaction::V3 { .. }
941            | Transaction::V4 {
942                sapling_shielded_data: None,
943                ..
944            }
945            | Transaction::V5 {
946                sapling_shielded_data: None,
947                ..
948            } => Box::new(std::iter::empty()),
949            #[cfg(feature = "tx_v6")]
950            Transaction::V6 {
951                sapling_shielded_data: None,
952                ..
953            } => Box::new(std::iter::empty()),
954        }
955    }
956
957    /// Access the sapling::Nullifiers in this transaction, regardless of version.
958    pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
959        // This function returns a boxed iterator because the different
960        // transaction variants end up having different iterator types
961        match self {
962            // Spends with Groth Proofs
963            Transaction::V4 {
964                sapling_shielded_data: Some(sapling_shielded_data),
965                ..
966            } => Box::new(sapling_shielded_data.nullifiers()),
967            Transaction::V5 {
968                sapling_shielded_data: Some(sapling_shielded_data),
969                ..
970            } => Box::new(sapling_shielded_data.nullifiers()),
971            #[cfg(feature = "tx_v6")]
972            Transaction::V6 {
973                sapling_shielded_data: Some(sapling_shielded_data),
974                ..
975            } => Box::new(sapling_shielded_data.nullifiers()),
976
977            // No Spends
978            Transaction::V1 { .. }
979            | Transaction::V2 { .. }
980            | Transaction::V3 { .. }
981            | Transaction::V4 {
982                sapling_shielded_data: None,
983                ..
984            }
985            | Transaction::V5 {
986                sapling_shielded_data: None,
987                ..
988            } => Box::new(std::iter::empty()),
989            #[cfg(feature = "tx_v6")]
990            Transaction::V6 {
991                sapling_shielded_data: None,
992                ..
993            } => Box::new(std::iter::empty()),
994        }
995    }
996
997    /// Returns the Sapling note commitments in this transaction, regardless of version.
998    pub fn sapling_note_commitments(
999        &self,
1000    ) -> Box<dyn Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> + '_> {
1001        // This function returns a boxed iterator because the different
1002        // transaction variants end up having different iterator types
1003        match self {
1004            // Spends with Groth16 Proofs
1005            Transaction::V4 {
1006                sapling_shielded_data: Some(sapling_shielded_data),
1007                ..
1008            } => Box::new(sapling_shielded_data.note_commitments()),
1009            Transaction::V5 {
1010                sapling_shielded_data: Some(sapling_shielded_data),
1011                ..
1012            } => Box::new(sapling_shielded_data.note_commitments()),
1013            #[cfg(feature = "tx_v6")]
1014            Transaction::V6 {
1015                sapling_shielded_data: Some(sapling_shielded_data),
1016                ..
1017            } => Box::new(sapling_shielded_data.note_commitments()),
1018
1019            // No Spends
1020            Transaction::V1 { .. }
1021            | Transaction::V2 { .. }
1022            | Transaction::V3 { .. }
1023            | Transaction::V4 {
1024                sapling_shielded_data: None,
1025                ..
1026            }
1027            | Transaction::V5 {
1028                sapling_shielded_data: None,
1029                ..
1030            } => Box::new(std::iter::empty()),
1031            #[cfg(feature = "tx_v6")]
1032            Transaction::V6 {
1033                sapling_shielded_data: None,
1034                ..
1035            } => Box::new(std::iter::empty()),
1036        }
1037    }
1038
1039    /// Returns `true` if the transaction has any Sapling shielded data.
1040    pub fn has_sapling_shielded_data(&self) -> bool {
1041        match self {
1042            Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1043            Transaction::V4 {
1044                sapling_shielded_data,
1045                ..
1046            } => sapling_shielded_data.is_some(),
1047            Transaction::V5 {
1048                sapling_shielded_data,
1049                ..
1050            } => sapling_shielded_data.is_some(),
1051            #[cfg(feature = "tx_v6")]
1052            Transaction::V6 {
1053                sapling_shielded_data,
1054                ..
1055            } => sapling_shielded_data.is_some(),
1056        }
1057    }
1058
1059    // orchard
1060
1061    /// Access the [`orchard::ShieldedData`] in this transaction,
1062    /// regardless of version.
1063    pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1064        match self {
1065            // Maybe Orchard shielded data
1066            Transaction::V5 {
1067                orchard_shielded_data,
1068                ..
1069            } => orchard_shielded_data.as_ref(),
1070            #[cfg(feature = "tx_v6")]
1071            Transaction::V6 {
1072                orchard_shielded_data,
1073                ..
1074            } => orchard_shielded_data.as_ref(),
1075
1076            // No Orchard shielded data
1077            Transaction::V1 { .. }
1078            | Transaction::V2 { .. }
1079            | Transaction::V3 { .. }
1080            | Transaction::V4 { .. } => None,
1081        }
1082    }
1083
1084    /// Iterate over the [`orchard::Action`]s in this transaction, if there are any,
1085    /// regardless of version.
1086    pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1087        self.orchard_shielded_data()
1088            .into_iter()
1089            .flat_map(orchard::ShieldedData::actions)
1090    }
1091
1092    /// Access the [`orchard::Nullifier`]s in this transaction, if there are any,
1093    /// regardless of version.
1094    pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1095        self.orchard_shielded_data()
1096            .into_iter()
1097            .flat_map(orchard::ShieldedData::nullifiers)
1098    }
1099
1100    /// Access the note commitments in this transaction, if there are any,
1101    /// regardless of version.
1102    pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1103        self.orchard_shielded_data()
1104            .into_iter()
1105            .flat_map(orchard::ShieldedData::note_commitments)
1106    }
1107
1108    /// Access the [`orchard::Flags`] in this transaction, if there is any,
1109    /// regardless of version.
1110    pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1111        self.orchard_shielded_data()
1112            .map(|orchard_shielded_data| orchard_shielded_data.flags)
1113    }
1114
1115    /// Return if the transaction has any Orchard shielded data,
1116    /// regardless of version.
1117    pub fn has_orchard_shielded_data(&self) -> bool {
1118        self.orchard_shielded_data().is_some()
1119    }
1120
1121    // value balances
1122
1123    /// Return the transparent value balance,
1124    /// using the outputs spent by this transaction.
1125    ///
1126    /// See `transparent_value_balance` for details.
1127    #[allow(clippy::unwrap_in_result)]
1128    fn transparent_value_balance_from_outputs(
1129        &self,
1130        outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1131    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1132        let input_value = self
1133            .inputs()
1134            .iter()
1135            .map(|i| i.value_from_outputs(outputs))
1136            .sum::<Result<Amount<NonNegative>, AmountError>>()
1137            .map_err(ValueBalanceError::Transparent)?
1138            .constrain()
1139            .expect("conversion from NonNegative to NegativeAllowed is always valid");
1140
1141        let output_value = self
1142            .outputs()
1143            .iter()
1144            .map(|o| o.value())
1145            .sum::<Result<Amount<NonNegative>, AmountError>>()
1146            .map_err(ValueBalanceError::Transparent)?
1147            .constrain()
1148            .expect("conversion from NonNegative to NegativeAllowed is always valid");
1149
1150        (input_value - output_value)
1151            .map(ValueBalance::from_transparent_amount)
1152            .map_err(ValueBalanceError::Transparent)
1153    }
1154
1155    /// Returns the `vpub_old` fields from `JoinSplit`s in this transaction,
1156    /// regardless of version, in the order they appear in the transaction.
1157    ///
1158    /// These values are added to the sprout chain value pool,
1159    /// and removed from the value pool of this transaction.
1160    pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1161        match self {
1162            // JoinSplits with Bctv14 Proofs
1163            Transaction::V2 {
1164                joinsplit_data: Some(joinsplit_data),
1165                ..
1166            }
1167            | Transaction::V3 {
1168                joinsplit_data: Some(joinsplit_data),
1169                ..
1170            } => Box::new(
1171                joinsplit_data
1172                    .joinsplits()
1173                    .map(|joinsplit| &joinsplit.vpub_old),
1174            ),
1175            // JoinSplits with Groth Proofs
1176            Transaction::V4 {
1177                joinsplit_data: Some(joinsplit_data),
1178                ..
1179            } => Box::new(
1180                joinsplit_data
1181                    .joinsplits()
1182                    .map(|joinsplit| &joinsplit.vpub_old),
1183            ),
1184            // No JoinSplits
1185            Transaction::V1 { .. }
1186            | Transaction::V2 {
1187                joinsplit_data: None,
1188                ..
1189            }
1190            | Transaction::V3 {
1191                joinsplit_data: None,
1192                ..
1193            }
1194            | Transaction::V4 {
1195                joinsplit_data: None,
1196                ..
1197            }
1198            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1199            #[cfg(feature = "tx_v6")]
1200            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1201        }
1202    }
1203
1204    /// Returns the `vpub_new` fields from `JoinSplit`s in this transaction,
1205    /// regardless of version, in the order they appear in the transaction.
1206    ///
1207    /// These values are removed from the value pool of this transaction.
1208    /// and added to the sprout chain value pool.
1209    pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1210        match self {
1211            // JoinSplits with Bctv14 Proofs
1212            Transaction::V2 {
1213                joinsplit_data: Some(joinsplit_data),
1214                ..
1215            }
1216            | Transaction::V3 {
1217                joinsplit_data: Some(joinsplit_data),
1218                ..
1219            } => Box::new(
1220                joinsplit_data
1221                    .joinsplits()
1222                    .map(|joinsplit| &joinsplit.vpub_new),
1223            ),
1224            // JoinSplits with Groth Proofs
1225            Transaction::V4 {
1226                joinsplit_data: Some(joinsplit_data),
1227                ..
1228            } => Box::new(
1229                joinsplit_data
1230                    .joinsplits()
1231                    .map(|joinsplit| &joinsplit.vpub_new),
1232            ),
1233            // No JoinSplits
1234            Transaction::V1 { .. }
1235            | Transaction::V2 {
1236                joinsplit_data: None,
1237                ..
1238            }
1239            | Transaction::V3 {
1240                joinsplit_data: None,
1241                ..
1242            }
1243            | Transaction::V4 {
1244                joinsplit_data: None,
1245                ..
1246            }
1247            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1248            #[cfg(feature = "tx_v6")]
1249            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1250        }
1251    }
1252
1253    /// Return a list of sprout value balances,
1254    /// the changes in the transaction value pool due to each sprout `JoinSplit`.
1255    ///
1256    /// Each value balance is the sprout `vpub_new` field, minus the `vpub_old` field.
1257    ///
1258    /// See [`sprout_value_balance`][svb] for details.
1259    ///
1260    /// [svb]: crate::transaction::Transaction::sprout_value_balance
1261    fn sprout_joinsplit_value_balances(
1262        &self,
1263    ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1264        let joinsplit_value_balances = match self {
1265            Transaction::V2 {
1266                joinsplit_data: Some(joinsplit_data),
1267                ..
1268            }
1269            | Transaction::V3 {
1270                joinsplit_data: Some(joinsplit_data),
1271                ..
1272            } => joinsplit_data.joinsplit_value_balances(),
1273            Transaction::V4 {
1274                joinsplit_data: Some(joinsplit_data),
1275                ..
1276            } => joinsplit_data.joinsplit_value_balances(),
1277            Transaction::V1 { .. }
1278            | Transaction::V2 {
1279                joinsplit_data: None,
1280                ..
1281            }
1282            | Transaction::V3 {
1283                joinsplit_data: None,
1284                ..
1285            }
1286            | Transaction::V4 {
1287                joinsplit_data: None,
1288                ..
1289            }
1290            | Transaction::V5 { .. } => Box::new(iter::empty()),
1291            #[cfg(feature = "tx_v6")]
1292            Transaction::V6 { .. } => Box::new(iter::empty()),
1293        };
1294
1295        joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1296    }
1297
1298    /// Return the sprout value balance,
1299    /// the change in the transaction value pool due to sprout `JoinSplit`s.
1300    ///
1301    /// The sum of all sprout `vpub_new` fields, minus the sum of all `vpub_old` fields.
1302    ///
1303    /// Positive values are added to this transaction's value pool,
1304    /// and removed from the sprout chain value pool.
1305    /// Negative values are removed from this transaction,
1306    /// and added to the sprout pool.
1307    ///
1308    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1309    fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1310        self.sprout_joinsplit_value_balances().sum()
1311    }
1312
1313    /// Return the sapling value balance,
1314    /// the change in the transaction value pool due to sapling `Spend`s and `Output`s.
1315    ///
1316    /// Returns the `valueBalanceSapling` field in this transaction.
1317    ///
1318    /// Positive values are added to this transaction's value pool,
1319    /// and removed from the sapling chain value pool.
1320    /// Negative values are removed from this transaction,
1321    /// and added to sapling pool.
1322    ///
1323    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1324    pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1325        let sapling_value_balance = match self {
1326            Transaction::V4 {
1327                sapling_shielded_data: Some(sapling_shielded_data),
1328                ..
1329            } => sapling_shielded_data.value_balance,
1330            Transaction::V5 {
1331                sapling_shielded_data: Some(sapling_shielded_data),
1332                ..
1333            } => sapling_shielded_data.value_balance,
1334            #[cfg(feature = "tx_v6")]
1335            Transaction::V6 {
1336                sapling_shielded_data: Some(sapling_shielded_data),
1337                ..
1338            } => sapling_shielded_data.value_balance,
1339
1340            Transaction::V1 { .. }
1341            | Transaction::V2 { .. }
1342            | Transaction::V3 { .. }
1343            | Transaction::V4 {
1344                sapling_shielded_data: None,
1345                ..
1346            }
1347            | Transaction::V5 {
1348                sapling_shielded_data: None,
1349                ..
1350            } => Amount::zero(),
1351            #[cfg(feature = "tx_v6")]
1352            Transaction::V6 {
1353                sapling_shielded_data: None,
1354                ..
1355            } => Amount::zero(),
1356        };
1357
1358        ValueBalance::from_sapling_amount(sapling_value_balance)
1359    }
1360
1361    /// Returns the Sapling binding signature for this transaction.
1362    ///
1363    /// Returns `Some(binding_sig)` for transactions that contain Sapling shielded
1364    /// data (V4+), or `None` for transactions without Sapling components.
1365    pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1366        match self {
1367            Transaction::V4 {
1368                sapling_shielded_data: Some(sapling_shielded_data),
1369                ..
1370            } => Some(sapling_shielded_data.binding_sig),
1371            Transaction::V5 {
1372                sapling_shielded_data: Some(sapling_shielded_data),
1373                ..
1374            } => Some(sapling_shielded_data.binding_sig),
1375            #[cfg(feature = "tx_v6")]
1376            Transaction::V6 {
1377                sapling_shielded_data: Some(sapling_shielded_data),
1378                ..
1379            } => Some(sapling_shielded_data.binding_sig),
1380            _ => None,
1381        }
1382    }
1383
1384    /// Returns the JoinSplit public key for this transaction.
1385    ///
1386    /// Returns `Some(pub_key)` for transactions that contain JoinSplit data (V2-V4),
1387    /// or `None` for transactions without JoinSplit components or unsupported versions.
1388    ///
1389    /// ## Note
1390    /// JoinSplits are deprecated in favor of Sapling and Orchard
1391    pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1392        match self {
1393            Transaction::V2 {
1394                joinsplit_data: Some(joinsplit_data),
1395                ..
1396            } => Some(joinsplit_data.pub_key),
1397            Transaction::V3 {
1398                joinsplit_data: Some(joinsplit_data),
1399                ..
1400            } => Some(joinsplit_data.pub_key),
1401            Transaction::V4 {
1402                joinsplit_data: Some(joinsplit_data),
1403                ..
1404            } => Some(joinsplit_data.pub_key),
1405            _ => None,
1406        }
1407    }
1408
1409    /// Returns the JoinSplit signature this for transaction.
1410    ///
1411    /// Returns `Some(signature)` for transactions that contain JoinSplit data (V2-V4),
1412    /// or `None` for transactions without JoinSplit components or unsupported versions.
1413    ///
1414    /// ## Note
1415    /// JoinSplits are deprecated in favor of Sapling and Orchard
1416    pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1417        match self {
1418            Transaction::V2 {
1419                joinsplit_data: Some(joinsplit_data),
1420                ..
1421            } => Some(joinsplit_data.sig),
1422            Transaction::V3 {
1423                joinsplit_data: Some(joinsplit_data),
1424                ..
1425            } => Some(joinsplit_data.sig),
1426            Transaction::V4 {
1427                joinsplit_data: Some(joinsplit_data),
1428                ..
1429            } => Some(joinsplit_data.sig),
1430            _ => None,
1431        }
1432    }
1433
1434    /// Return the orchard value balance, the change in the transaction value
1435    /// pool due to [`orchard::Action`]s.
1436    ///
1437    /// Returns the `valueBalanceOrchard` field in this transaction.
1438    ///
1439    /// Positive values are added to this transaction's value pool,
1440    /// and removed from the orchard chain value pool.
1441    /// Negative values are removed from this transaction,
1442    /// and added to orchard pool.
1443    ///
1444    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1445    pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1446        let orchard_value_balance = self
1447            .orchard_shielded_data()
1448            .map(|shielded_data| shielded_data.value_balance)
1449            .unwrap_or_else(Amount::zero);
1450
1451        ValueBalance::from_orchard_amount(orchard_value_balance)
1452    }
1453
1454    /// Returns the value balances for this transaction using the provided transparent outputs.
1455    pub(crate) fn value_balance_from_outputs(
1456        &self,
1457        outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1458    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1459        self.transparent_value_balance_from_outputs(outputs)?
1460            + self.sprout_value_balance()?
1461            + self.sapling_value_balance()
1462            + self.orchard_value_balance()
1463    }
1464
1465    /// Returns the value balances for this transaction.
1466    ///
1467    /// These are the changes in the transaction value pool, split up into transparent, Sprout,
1468    /// Sapling, and Orchard values.
1469    ///
1470    /// Calculated as the sum of the inputs and outputs from each pool, or the sum of the value
1471    /// balances from each pool.
1472    ///
1473    /// Positive values are added to this transaction's value pool, and removed from the
1474    /// corresponding chain value pool. Negative values are removed from this transaction, and added
1475    /// to the corresponding pool.
1476    ///
1477    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1478    ///
1479    /// `utxos` must contain the utxos of every input in the transaction, including UTXOs created by
1480    /// earlier transactions in this block.
1481    ///
1482    /// ## Note
1483    ///
1484    /// The chain value pool has the opposite sign to the transaction value pool.
1485    pub fn value_balance(
1486        &self,
1487        utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1488    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1489        self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1490    }
1491
1492    /// Converts [`Transaction`] to [`zcash_primitives::transaction::Transaction`].
1493    ///
1494    /// If the tx contains a network upgrade, this network upgrade must match the passed `nu`. The
1495    /// passed `nu` must also contain a consensus branch id convertible to its `librustzcash`
1496    /// equivalent.
1497    pub(crate) fn to_librustzcash(
1498        &self,
1499        nu: NetworkUpgrade,
1500    ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1501        if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1502            return Err(crate::Error::InvalidConsensusBranchId);
1503        }
1504
1505        let Some(branch_id) = nu.branch_id() else {
1506            return Err(crate::Error::InvalidConsensusBranchId);
1507        };
1508
1509        let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1510            return Err(crate::Error::InvalidConsensusBranchId);
1511        };
1512
1513        Ok(zcash_primitives::transaction::Transaction::read(
1514            &self.zcash_serialize_to_vec()?[..],
1515            branch_id,
1516        )?)
1517    }
1518
1519    // Common Sapling & Orchard Properties
1520
1521    /// Does this transaction have shielded inputs or outputs?
1522    pub fn has_shielded_data(&self) -> bool {
1523        self.has_shielded_inputs() || self.has_shielded_outputs()
1524    }
1525
1526    /// Get the version group ID for this transaction, if any.
1527    pub fn version_group_id(&self) -> Option<u32> {
1528        // We could store the parsed version group ID and return that,
1529        // but since the consensus rules constraint it, we can just return
1530        // the value that must have been parsed.
1531        match self {
1532            Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1533            Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1534            Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1535            Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1536            #[cfg(feature = "tx_v6")]
1537            Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1538        }
1539    }
1540}
1541
1542#[cfg(any(test, feature = "proptest-impl"))]
1543impl Transaction {
1544    /// Updates the [`NetworkUpgrade`] for this transaction.
1545    ///
1546    /// ## Notes
1547    ///
1548    /// - Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.
1549    pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1550        match self {
1551            Transaction::V1 { .. }
1552            | Transaction::V2 { .. }
1553            | Transaction::V3 { .. }
1554            | Transaction::V4 { .. } => Err(
1555                "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1556            ),
1557            Transaction::V5 {
1558                ref mut network_upgrade,
1559                ..
1560            } => {
1561                *network_upgrade = nu;
1562                Ok(())
1563            }
1564            #[cfg(feature = "tx_v6")]
1565            Transaction::V6 {
1566                ref mut network_upgrade,
1567                ..
1568            } => {
1569                *network_upgrade = nu;
1570                Ok(())
1571            }
1572        }
1573    }
1574
1575    /// Modify the expiry height of this transaction.
1576    ///
1577    /// # Panics
1578    ///
1579    /// - if called on a v1 or v2 transaction
1580    pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1581        match self {
1582            Transaction::V1 { .. } | Transaction::V2 { .. } => {
1583                panic!("v1 and v2 transactions are not supported")
1584            }
1585            Transaction::V3 {
1586                ref mut expiry_height,
1587                ..
1588            }
1589            | Transaction::V4 {
1590                ref mut expiry_height,
1591                ..
1592            }
1593            | Transaction::V5 {
1594                ref mut expiry_height,
1595                ..
1596            } => expiry_height,
1597            #[cfg(feature = "tx_v6")]
1598            Transaction::V6 {
1599                ref mut expiry_height,
1600                ..
1601            } => expiry_height,
1602        }
1603    }
1604
1605    /// Modify the transparent inputs of this transaction, regardless of version.
1606    pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1607        match self {
1608            Transaction::V1 { ref mut inputs, .. } => inputs,
1609            Transaction::V2 { ref mut inputs, .. } => inputs,
1610            Transaction::V3 { ref mut inputs, .. } => inputs,
1611            Transaction::V4 { ref mut inputs, .. } => inputs,
1612            Transaction::V5 { ref mut inputs, .. } => inputs,
1613            #[cfg(feature = "tx_v6")]
1614            Transaction::V6 { ref mut inputs, .. } => inputs,
1615        }
1616    }
1617
1618    /// Modify the `value_balance` field from the `orchard::ShieldedData` in this transaction,
1619    /// regardless of version.
1620    ///
1621    /// See `orchard_value_balance` for details.
1622    pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1623        self.orchard_shielded_data_mut()
1624            .map(|shielded_data| &mut shielded_data.value_balance)
1625    }
1626
1627    /// Modify the `value_balance` field from the `sapling::ShieldedData` in this transaction,
1628    /// regardless of version.
1629    ///
1630    /// See `sapling_value_balance` for details.
1631    pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1632        match self {
1633            Transaction::V4 {
1634                sapling_shielded_data: Some(sapling_shielded_data),
1635                ..
1636            } => Some(&mut sapling_shielded_data.value_balance),
1637            Transaction::V5 {
1638                sapling_shielded_data: Some(sapling_shielded_data),
1639                ..
1640            } => Some(&mut sapling_shielded_data.value_balance),
1641            #[cfg(feature = "tx_v6")]
1642            Transaction::V6 {
1643                sapling_shielded_data: Some(sapling_shielded_data),
1644                ..
1645            } => Some(&mut sapling_shielded_data.value_balance),
1646            Transaction::V1 { .. }
1647            | Transaction::V2 { .. }
1648            | Transaction::V3 { .. }
1649            | Transaction::V4 {
1650                sapling_shielded_data: None,
1651                ..
1652            }
1653            | Transaction::V5 {
1654                sapling_shielded_data: None,
1655                ..
1656            } => None,
1657            #[cfg(feature = "tx_v6")]
1658            Transaction::V6 {
1659                sapling_shielded_data: None,
1660                ..
1661            } => None,
1662        }
1663    }
1664
1665    /// Modify the `vpub_new` fields from `JoinSplit`s in this transaction,
1666    /// regardless of version, in the order they appear in the transaction.
1667    ///
1668    /// See `input_values_from_sprout` for details.
1669    pub fn input_values_from_sprout_mut(
1670        &mut self,
1671    ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1672        match self {
1673            // JoinSplits with Bctv14 Proofs
1674            Transaction::V2 {
1675                joinsplit_data: Some(joinsplit_data),
1676                ..
1677            }
1678            | Transaction::V3 {
1679                joinsplit_data: Some(joinsplit_data),
1680                ..
1681            } => Box::new(
1682                joinsplit_data
1683                    .joinsplits_mut()
1684                    .map(|joinsplit| &mut joinsplit.vpub_new),
1685            ),
1686            // JoinSplits with Groth Proofs
1687            Transaction::V4 {
1688                joinsplit_data: Some(joinsplit_data),
1689                ..
1690            } => Box::new(
1691                joinsplit_data
1692                    .joinsplits_mut()
1693                    .map(|joinsplit| &mut joinsplit.vpub_new),
1694            ),
1695            // No JoinSplits
1696            Transaction::V1 { .. }
1697            | Transaction::V2 {
1698                joinsplit_data: None,
1699                ..
1700            }
1701            | Transaction::V3 {
1702                joinsplit_data: None,
1703                ..
1704            }
1705            | Transaction::V4 {
1706                joinsplit_data: None,
1707                ..
1708            }
1709            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1710            #[cfg(feature = "tx_v6")]
1711            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1712        }
1713    }
1714
1715    /// Modify the `vpub_old` fields from `JoinSplit`s in this transaction,
1716    /// regardless of version, in the order they appear in the transaction.
1717    ///
1718    /// See `output_values_to_sprout` for details.
1719    pub fn output_values_to_sprout_mut(
1720        &mut self,
1721    ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1722        match self {
1723            // JoinSplits with Bctv14 Proofs
1724            Transaction::V2 {
1725                joinsplit_data: Some(joinsplit_data),
1726                ..
1727            }
1728            | Transaction::V3 {
1729                joinsplit_data: Some(joinsplit_data),
1730                ..
1731            } => Box::new(
1732                joinsplit_data
1733                    .joinsplits_mut()
1734                    .map(|joinsplit| &mut joinsplit.vpub_old),
1735            ),
1736            // JoinSplits with Groth16 Proofs
1737            Transaction::V4 {
1738                joinsplit_data: Some(joinsplit_data),
1739                ..
1740            } => Box::new(
1741                joinsplit_data
1742                    .joinsplits_mut()
1743                    .map(|joinsplit| &mut joinsplit.vpub_old),
1744            ),
1745            // No JoinSplits
1746            Transaction::V1 { .. }
1747            | Transaction::V2 {
1748                joinsplit_data: None,
1749                ..
1750            }
1751            | Transaction::V3 {
1752                joinsplit_data: None,
1753                ..
1754            }
1755            | Transaction::V4 {
1756                joinsplit_data: None,
1757                ..
1758            }
1759            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1760            #[cfg(feature = "tx_v6")]
1761            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1762        }
1763    }
1764
1765    /// Modify the transparent output values of this transaction, regardless of version.
1766    pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1767        self.outputs_mut()
1768            .iter_mut()
1769            .map(|output| &mut output.value)
1770    }
1771
1772    /// Modify the [`orchard::ShieldedData`] in this transaction,
1773    /// regardless of version.
1774    pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1775        match self {
1776            Transaction::V5 {
1777                orchard_shielded_data: Some(orchard_shielded_data),
1778                ..
1779            } => Some(orchard_shielded_data),
1780            #[cfg(feature = "tx_v6")]
1781            Transaction::V6 {
1782                orchard_shielded_data: Some(orchard_shielded_data),
1783                ..
1784            } => Some(orchard_shielded_data),
1785
1786            Transaction::V1 { .. }
1787            | Transaction::V2 { .. }
1788            | Transaction::V3 { .. }
1789            | Transaction::V4 { .. }
1790            | Transaction::V5 {
1791                orchard_shielded_data: None,
1792                ..
1793            } => None,
1794            #[cfg(feature = "tx_v6")]
1795            Transaction::V6 {
1796                orchard_shielded_data: None,
1797                ..
1798            } => None,
1799        }
1800    }
1801
1802    /// Modify the transparent outputs of this transaction, regardless of version.
1803    pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1804        match self {
1805            Transaction::V1 {
1806                ref mut outputs, ..
1807            } => outputs,
1808            Transaction::V2 {
1809                ref mut outputs, ..
1810            } => outputs,
1811            Transaction::V3 {
1812                ref mut outputs, ..
1813            } => outputs,
1814            Transaction::V4 {
1815                ref mut outputs, ..
1816            } => outputs,
1817            Transaction::V5 {
1818                ref mut outputs, ..
1819            } => outputs,
1820            #[cfg(feature = "tx_v6")]
1821            Transaction::V6 {
1822                ref mut outputs, ..
1823            } => outputs,
1824        }
1825    }
1826}