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(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
999        // This function returns a boxed iterator because the different
1000        // transaction variants end up having different iterator types
1001        match self {
1002            // Spends with Groth16 Proofs
1003            Transaction::V4 {
1004                sapling_shielded_data: Some(sapling_shielded_data),
1005                ..
1006            } => Box::new(sapling_shielded_data.note_commitments()),
1007            Transaction::V5 {
1008                sapling_shielded_data: Some(sapling_shielded_data),
1009                ..
1010            } => Box::new(sapling_shielded_data.note_commitments()),
1011            #[cfg(feature = "tx_v6")]
1012            Transaction::V6 {
1013                sapling_shielded_data: Some(sapling_shielded_data),
1014                ..
1015            } => Box::new(sapling_shielded_data.note_commitments()),
1016
1017            // No Spends
1018            Transaction::V1 { .. }
1019            | Transaction::V2 { .. }
1020            | Transaction::V3 { .. }
1021            | Transaction::V4 {
1022                sapling_shielded_data: None,
1023                ..
1024            }
1025            | Transaction::V5 {
1026                sapling_shielded_data: None,
1027                ..
1028            } => Box::new(std::iter::empty()),
1029            #[cfg(feature = "tx_v6")]
1030            Transaction::V6 {
1031                sapling_shielded_data: None,
1032                ..
1033            } => Box::new(std::iter::empty()),
1034        }
1035    }
1036
1037    /// Returns `true` if the transaction has any Sapling shielded data.
1038    pub fn has_sapling_shielded_data(&self) -> bool {
1039        match self {
1040            Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1041            Transaction::V4 {
1042                sapling_shielded_data,
1043                ..
1044            } => sapling_shielded_data.is_some(),
1045            Transaction::V5 {
1046                sapling_shielded_data,
1047                ..
1048            } => sapling_shielded_data.is_some(),
1049            #[cfg(feature = "tx_v6")]
1050            Transaction::V6 {
1051                sapling_shielded_data,
1052                ..
1053            } => sapling_shielded_data.is_some(),
1054        }
1055    }
1056
1057    // orchard
1058
1059    /// Access the [`orchard::ShieldedData`] in this transaction,
1060    /// regardless of version.
1061    pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1062        match self {
1063            // Maybe Orchard shielded data
1064            Transaction::V5 {
1065                orchard_shielded_data,
1066                ..
1067            } => orchard_shielded_data.as_ref(),
1068            #[cfg(feature = "tx_v6")]
1069            Transaction::V6 {
1070                orchard_shielded_data,
1071                ..
1072            } => orchard_shielded_data.as_ref(),
1073
1074            // No Orchard shielded data
1075            Transaction::V1 { .. }
1076            | Transaction::V2 { .. }
1077            | Transaction::V3 { .. }
1078            | Transaction::V4 { .. } => None,
1079        }
1080    }
1081
1082    /// Iterate over the [`orchard::Action`]s in this transaction, if there are any,
1083    /// regardless of version.
1084    pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1085        self.orchard_shielded_data()
1086            .into_iter()
1087            .flat_map(orchard::ShieldedData::actions)
1088    }
1089
1090    /// Access the [`orchard::Nullifier`]s in this transaction, if there are any,
1091    /// regardless of version.
1092    pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1093        self.orchard_shielded_data()
1094            .into_iter()
1095            .flat_map(orchard::ShieldedData::nullifiers)
1096    }
1097
1098    /// Access the note commitments in this transaction, if there are any,
1099    /// regardless of version.
1100    pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1101        self.orchard_shielded_data()
1102            .into_iter()
1103            .flat_map(orchard::ShieldedData::note_commitments)
1104    }
1105
1106    /// Access the [`orchard::Flags`] in this transaction, if there is any,
1107    /// regardless of version.
1108    pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1109        self.orchard_shielded_data()
1110            .map(|orchard_shielded_data| orchard_shielded_data.flags)
1111    }
1112
1113    /// Return if the transaction has any Orchard shielded data,
1114    /// regardless of version.
1115    pub fn has_orchard_shielded_data(&self) -> bool {
1116        self.orchard_shielded_data().is_some()
1117    }
1118
1119    // value balances
1120
1121    /// Return the transparent value balance,
1122    /// using the outputs spent by this transaction.
1123    ///
1124    /// See `transparent_value_balance` for details.
1125    #[allow(clippy::unwrap_in_result)]
1126    fn transparent_value_balance_from_outputs(
1127        &self,
1128        outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1129    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1130        let input_value = self
1131            .inputs()
1132            .iter()
1133            .map(|i| i.value_from_outputs(outputs))
1134            .sum::<Result<Amount<NonNegative>, AmountError>>()
1135            .map_err(ValueBalanceError::Transparent)?
1136            .constrain()
1137            .expect("conversion from NonNegative to NegativeAllowed is always valid");
1138
1139        let output_value = self
1140            .outputs()
1141            .iter()
1142            .map(|o| o.value())
1143            .sum::<Result<Amount<NonNegative>, AmountError>>()
1144            .map_err(ValueBalanceError::Transparent)?
1145            .constrain()
1146            .expect("conversion from NonNegative to NegativeAllowed is always valid");
1147
1148        (input_value - output_value)
1149            .map(ValueBalance::from_transparent_amount)
1150            .map_err(ValueBalanceError::Transparent)
1151    }
1152
1153    /// Returns the `vpub_old` fields from `JoinSplit`s in this transaction,
1154    /// regardless of version, in the order they appear in the transaction.
1155    ///
1156    /// These values are added to the sprout chain value pool,
1157    /// and removed from the value pool of this transaction.
1158    pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1159        match self {
1160            // JoinSplits with Bctv14 Proofs
1161            Transaction::V2 {
1162                joinsplit_data: Some(joinsplit_data),
1163                ..
1164            }
1165            | Transaction::V3 {
1166                joinsplit_data: Some(joinsplit_data),
1167                ..
1168            } => Box::new(
1169                joinsplit_data
1170                    .joinsplits()
1171                    .map(|joinsplit| &joinsplit.vpub_old),
1172            ),
1173            // JoinSplits with Groth Proofs
1174            Transaction::V4 {
1175                joinsplit_data: Some(joinsplit_data),
1176                ..
1177            } => Box::new(
1178                joinsplit_data
1179                    .joinsplits()
1180                    .map(|joinsplit| &joinsplit.vpub_old),
1181            ),
1182            // No JoinSplits
1183            Transaction::V1 { .. }
1184            | Transaction::V2 {
1185                joinsplit_data: None,
1186                ..
1187            }
1188            | Transaction::V3 {
1189                joinsplit_data: None,
1190                ..
1191            }
1192            | Transaction::V4 {
1193                joinsplit_data: None,
1194                ..
1195            }
1196            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1197            #[cfg(feature = "tx_v6")]
1198            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1199        }
1200    }
1201
1202    /// Returns the `vpub_new` fields from `JoinSplit`s in this transaction,
1203    /// regardless of version, in the order they appear in the transaction.
1204    ///
1205    /// These values are removed from the value pool of this transaction.
1206    /// and added to the sprout chain value pool.
1207    pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1208        match self {
1209            // JoinSplits with Bctv14 Proofs
1210            Transaction::V2 {
1211                joinsplit_data: Some(joinsplit_data),
1212                ..
1213            }
1214            | Transaction::V3 {
1215                joinsplit_data: Some(joinsplit_data),
1216                ..
1217            } => Box::new(
1218                joinsplit_data
1219                    .joinsplits()
1220                    .map(|joinsplit| &joinsplit.vpub_new),
1221            ),
1222            // JoinSplits with Groth Proofs
1223            Transaction::V4 {
1224                joinsplit_data: Some(joinsplit_data),
1225                ..
1226            } => Box::new(
1227                joinsplit_data
1228                    .joinsplits()
1229                    .map(|joinsplit| &joinsplit.vpub_new),
1230            ),
1231            // No JoinSplits
1232            Transaction::V1 { .. }
1233            | Transaction::V2 {
1234                joinsplit_data: None,
1235                ..
1236            }
1237            | Transaction::V3 {
1238                joinsplit_data: None,
1239                ..
1240            }
1241            | Transaction::V4 {
1242                joinsplit_data: None,
1243                ..
1244            }
1245            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1246            #[cfg(feature = "tx_v6")]
1247            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1248        }
1249    }
1250
1251    /// Return a list of sprout value balances,
1252    /// the changes in the transaction value pool due to each sprout `JoinSplit`.
1253    ///
1254    /// Each value balance is the sprout `vpub_new` field, minus the `vpub_old` field.
1255    ///
1256    /// See [`sprout_value_balance`][svb] for details.
1257    ///
1258    /// [svb]: crate::transaction::Transaction::sprout_value_balance
1259    fn sprout_joinsplit_value_balances(
1260        &self,
1261    ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1262        let joinsplit_value_balances = match self {
1263            Transaction::V2 {
1264                joinsplit_data: Some(joinsplit_data),
1265                ..
1266            }
1267            | Transaction::V3 {
1268                joinsplit_data: Some(joinsplit_data),
1269                ..
1270            } => joinsplit_data.joinsplit_value_balances(),
1271            Transaction::V4 {
1272                joinsplit_data: Some(joinsplit_data),
1273                ..
1274            } => joinsplit_data.joinsplit_value_balances(),
1275            Transaction::V1 { .. }
1276            | Transaction::V2 {
1277                joinsplit_data: None,
1278                ..
1279            }
1280            | Transaction::V3 {
1281                joinsplit_data: None,
1282                ..
1283            }
1284            | Transaction::V4 {
1285                joinsplit_data: None,
1286                ..
1287            }
1288            | Transaction::V5 { .. } => Box::new(iter::empty()),
1289            #[cfg(feature = "tx_v6")]
1290            Transaction::V6 { .. } => Box::new(iter::empty()),
1291        };
1292
1293        joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1294    }
1295
1296    /// Return the sprout value balance,
1297    /// the change in the transaction value pool due to sprout `JoinSplit`s.
1298    ///
1299    /// The sum of all sprout `vpub_new` fields, minus the sum of all `vpub_old` fields.
1300    ///
1301    /// Positive values are added to this transaction's value pool,
1302    /// and removed from the sprout chain value pool.
1303    /// Negative values are removed from this transaction,
1304    /// and added to the sprout pool.
1305    ///
1306    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1307    fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1308        self.sprout_joinsplit_value_balances().sum()
1309    }
1310
1311    /// Return the sapling value balance,
1312    /// the change in the transaction value pool due to sapling `Spend`s and `Output`s.
1313    ///
1314    /// Returns the `valueBalanceSapling` field in this transaction.
1315    ///
1316    /// Positive values are added to this transaction's value pool,
1317    /// and removed from the sapling chain value pool.
1318    /// Negative values are removed from this transaction,
1319    /// and added to sapling pool.
1320    ///
1321    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1322    pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1323        let sapling_value_balance = match self {
1324            Transaction::V4 {
1325                sapling_shielded_data: Some(sapling_shielded_data),
1326                ..
1327            } => sapling_shielded_data.value_balance,
1328            Transaction::V5 {
1329                sapling_shielded_data: Some(sapling_shielded_data),
1330                ..
1331            } => sapling_shielded_data.value_balance,
1332            #[cfg(feature = "tx_v6")]
1333            Transaction::V6 {
1334                sapling_shielded_data: Some(sapling_shielded_data),
1335                ..
1336            } => sapling_shielded_data.value_balance,
1337
1338            Transaction::V1 { .. }
1339            | Transaction::V2 { .. }
1340            | Transaction::V3 { .. }
1341            | Transaction::V4 {
1342                sapling_shielded_data: None,
1343                ..
1344            }
1345            | Transaction::V5 {
1346                sapling_shielded_data: None,
1347                ..
1348            } => Amount::zero(),
1349            #[cfg(feature = "tx_v6")]
1350            Transaction::V6 {
1351                sapling_shielded_data: None,
1352                ..
1353            } => Amount::zero(),
1354        };
1355
1356        ValueBalance::from_sapling_amount(sapling_value_balance)
1357    }
1358
1359    /// Returns the Sapling binding signature for this transaction.
1360    ///
1361    /// Returns `Some(binding_sig)` for transactions that contain Sapling shielded
1362    /// data (V4+), or `None` for transactions without Sapling components.
1363    pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1364        match self {
1365            Transaction::V4 {
1366                sapling_shielded_data: Some(sapling_shielded_data),
1367                ..
1368            } => Some(sapling_shielded_data.binding_sig),
1369            Transaction::V5 {
1370                sapling_shielded_data: Some(sapling_shielded_data),
1371                ..
1372            } => Some(sapling_shielded_data.binding_sig),
1373            #[cfg(feature = "tx_v6")]
1374            Transaction::V6 {
1375                sapling_shielded_data: Some(sapling_shielded_data),
1376                ..
1377            } => Some(sapling_shielded_data.binding_sig),
1378            _ => None,
1379        }
1380    }
1381
1382    /// Returns the JoinSplit public key for this transaction.
1383    ///
1384    /// Returns `Some(pub_key)` for transactions that contain JoinSplit data (V2-V4),
1385    /// or `None` for transactions without JoinSplit components or unsupported versions.
1386    ///
1387    /// ## Note
1388    /// JoinSplits are deprecated in favor of Sapling and Orchard
1389    pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1390        match self {
1391            Transaction::V2 {
1392                joinsplit_data: Some(joinsplit_data),
1393                ..
1394            } => Some(joinsplit_data.pub_key),
1395            Transaction::V3 {
1396                joinsplit_data: Some(joinsplit_data),
1397                ..
1398            } => Some(joinsplit_data.pub_key),
1399            Transaction::V4 {
1400                joinsplit_data: Some(joinsplit_data),
1401                ..
1402            } => Some(joinsplit_data.pub_key),
1403            _ => None,
1404        }
1405    }
1406
1407    /// Returns the JoinSplit signature this for transaction.
1408    ///
1409    /// Returns `Some(signature)` for transactions that contain JoinSplit data (V2-V4),
1410    /// or `None` for transactions without JoinSplit components or unsupported versions.
1411    ///
1412    /// ## Note
1413    /// JoinSplits are deprecated in favor of Sapling and Orchard
1414    pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1415        match self {
1416            Transaction::V2 {
1417                joinsplit_data: Some(joinsplit_data),
1418                ..
1419            } => Some(joinsplit_data.sig),
1420            Transaction::V3 {
1421                joinsplit_data: Some(joinsplit_data),
1422                ..
1423            } => Some(joinsplit_data.sig),
1424            Transaction::V4 {
1425                joinsplit_data: Some(joinsplit_data),
1426                ..
1427            } => Some(joinsplit_data.sig),
1428            _ => None,
1429        }
1430    }
1431
1432    /// Return the orchard value balance, the change in the transaction value
1433    /// pool due to [`orchard::Action`]s.
1434    ///
1435    /// Returns the `valueBalanceOrchard` field in this transaction.
1436    ///
1437    /// Positive values are added to this transaction's value pool,
1438    /// and removed from the orchard chain value pool.
1439    /// Negative values are removed from this transaction,
1440    /// and added to orchard pool.
1441    ///
1442    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1443    pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1444        let orchard_value_balance = self
1445            .orchard_shielded_data()
1446            .map(|shielded_data| shielded_data.value_balance)
1447            .unwrap_or_else(Amount::zero);
1448
1449        ValueBalance::from_orchard_amount(orchard_value_balance)
1450    }
1451
1452    /// Returns the value balances for this transaction using the provided transparent outputs.
1453    pub(crate) fn value_balance_from_outputs(
1454        &self,
1455        outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1456    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1457        self.transparent_value_balance_from_outputs(outputs)?
1458            + self.sprout_value_balance()?
1459            + self.sapling_value_balance()
1460            + self.orchard_value_balance()
1461    }
1462
1463    /// Returns the value balances for this transaction.
1464    ///
1465    /// These are the changes in the transaction value pool, split up into transparent, Sprout,
1466    /// Sapling, and Orchard values.
1467    ///
1468    /// Calculated as the sum of the inputs and outputs from each pool, or the sum of the value
1469    /// balances from each pool.
1470    ///
1471    /// Positive values are added to this transaction's value pool, and removed from the
1472    /// corresponding chain value pool. Negative values are removed from this transaction, and added
1473    /// to the corresponding pool.
1474    ///
1475    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
1476    ///
1477    /// `utxos` must contain the utxos of every input in the transaction, including UTXOs created by
1478    /// earlier transactions in this block.
1479    ///
1480    /// ## Note
1481    ///
1482    /// The chain value pool has the opposite sign to the transaction value pool.
1483    pub fn value_balance(
1484        &self,
1485        utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1486    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1487        self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1488    }
1489
1490    /// Converts [`Transaction`] to [`zcash_primitives::transaction::Transaction`].
1491    ///
1492    /// If the tx contains a network upgrade, this network upgrade must match the passed `nu`. The
1493    /// passed `nu` must also contain a consensus branch id convertible to its `librustzcash`
1494    /// equivalent.
1495    pub(crate) fn to_librustzcash(
1496        &self,
1497        nu: NetworkUpgrade,
1498    ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1499        if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1500            return Err(crate::Error::InvalidConsensusBranchId);
1501        }
1502
1503        let Some(branch_id) = nu.branch_id() else {
1504            return Err(crate::Error::InvalidConsensusBranchId);
1505        };
1506
1507        let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1508            return Err(crate::Error::InvalidConsensusBranchId);
1509        };
1510
1511        Ok(zcash_primitives::transaction::Transaction::read(
1512            &self.zcash_serialize_to_vec()?[..],
1513            branch_id,
1514        )?)
1515    }
1516
1517    // Common Sapling & Orchard Properties
1518
1519    /// Does this transaction have shielded inputs or outputs?
1520    pub fn has_shielded_data(&self) -> bool {
1521        self.has_shielded_inputs() || self.has_shielded_outputs()
1522    }
1523
1524    /// Get the version group ID for this transaction, if any.
1525    pub fn version_group_id(&self) -> Option<u32> {
1526        // We could store the parsed version group ID and return that,
1527        // but since the consensus rules constraint it, we can just return
1528        // the value that must have been parsed.
1529        match self {
1530            Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1531            Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1532            Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1533            Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1534            #[cfg(feature = "tx_v6")]
1535            Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1536        }
1537    }
1538}
1539
1540#[cfg(any(test, feature = "proptest-impl"))]
1541impl Transaction {
1542    /// Updates the [`NetworkUpgrade`] for this transaction.
1543    ///
1544    /// ## Notes
1545    ///
1546    /// - Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.
1547    pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1548        match self {
1549            Transaction::V1 { .. }
1550            | Transaction::V2 { .. }
1551            | Transaction::V3 { .. }
1552            | Transaction::V4 { .. } => Err(
1553                "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1554            ),
1555            Transaction::V5 {
1556                ref mut network_upgrade,
1557                ..
1558            } => {
1559                *network_upgrade = nu;
1560                Ok(())
1561            }
1562            #[cfg(feature = "tx_v6")]
1563            Transaction::V6 {
1564                ref mut network_upgrade,
1565                ..
1566            } => {
1567                *network_upgrade = nu;
1568                Ok(())
1569            }
1570        }
1571    }
1572
1573    /// Modify the expiry height of this transaction.
1574    ///
1575    /// # Panics
1576    ///
1577    /// - if called on a v1 or v2 transaction
1578    pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1579        match self {
1580            Transaction::V1 { .. } | Transaction::V2 { .. } => {
1581                panic!("v1 and v2 transactions are not supported")
1582            }
1583            Transaction::V3 {
1584                ref mut expiry_height,
1585                ..
1586            }
1587            | Transaction::V4 {
1588                ref mut expiry_height,
1589                ..
1590            }
1591            | Transaction::V5 {
1592                ref mut expiry_height,
1593                ..
1594            } => expiry_height,
1595            #[cfg(feature = "tx_v6")]
1596            Transaction::V6 {
1597                ref mut expiry_height,
1598                ..
1599            } => expiry_height,
1600        }
1601    }
1602
1603    /// Modify the transparent inputs of this transaction, regardless of version.
1604    pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1605        match self {
1606            Transaction::V1 { ref mut inputs, .. } => inputs,
1607            Transaction::V2 { ref mut inputs, .. } => inputs,
1608            Transaction::V3 { ref mut inputs, .. } => inputs,
1609            Transaction::V4 { ref mut inputs, .. } => inputs,
1610            Transaction::V5 { ref mut inputs, .. } => inputs,
1611            #[cfg(feature = "tx_v6")]
1612            Transaction::V6 { ref mut inputs, .. } => inputs,
1613        }
1614    }
1615
1616    /// Modify the `value_balance` field from the `orchard::ShieldedData` in this transaction,
1617    /// regardless of version.
1618    ///
1619    /// See `orchard_value_balance` for details.
1620    pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1621        self.orchard_shielded_data_mut()
1622            .map(|shielded_data| &mut shielded_data.value_balance)
1623    }
1624
1625    /// Modify the `value_balance` field from the `sapling::ShieldedData` in this transaction,
1626    /// regardless of version.
1627    ///
1628    /// See `sapling_value_balance` for details.
1629    pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1630        match self {
1631            Transaction::V4 {
1632                sapling_shielded_data: Some(sapling_shielded_data),
1633                ..
1634            } => Some(&mut sapling_shielded_data.value_balance),
1635            Transaction::V5 {
1636                sapling_shielded_data: Some(sapling_shielded_data),
1637                ..
1638            } => Some(&mut sapling_shielded_data.value_balance),
1639            #[cfg(feature = "tx_v6")]
1640            Transaction::V6 {
1641                sapling_shielded_data: Some(sapling_shielded_data),
1642                ..
1643            } => Some(&mut sapling_shielded_data.value_balance),
1644            Transaction::V1 { .. }
1645            | Transaction::V2 { .. }
1646            | Transaction::V3 { .. }
1647            | Transaction::V4 {
1648                sapling_shielded_data: None,
1649                ..
1650            }
1651            | Transaction::V5 {
1652                sapling_shielded_data: None,
1653                ..
1654            } => None,
1655            #[cfg(feature = "tx_v6")]
1656            Transaction::V6 {
1657                sapling_shielded_data: None,
1658                ..
1659            } => None,
1660        }
1661    }
1662
1663    /// Modify the `vpub_new` fields from `JoinSplit`s in this transaction,
1664    /// regardless of version, in the order they appear in the transaction.
1665    ///
1666    /// See `input_values_from_sprout` for details.
1667    pub fn input_values_from_sprout_mut(
1668        &mut self,
1669    ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1670        match self {
1671            // JoinSplits with Bctv14 Proofs
1672            Transaction::V2 {
1673                joinsplit_data: Some(joinsplit_data),
1674                ..
1675            }
1676            | Transaction::V3 {
1677                joinsplit_data: Some(joinsplit_data),
1678                ..
1679            } => Box::new(
1680                joinsplit_data
1681                    .joinsplits_mut()
1682                    .map(|joinsplit| &mut joinsplit.vpub_new),
1683            ),
1684            // JoinSplits with Groth Proofs
1685            Transaction::V4 {
1686                joinsplit_data: Some(joinsplit_data),
1687                ..
1688            } => Box::new(
1689                joinsplit_data
1690                    .joinsplits_mut()
1691                    .map(|joinsplit| &mut joinsplit.vpub_new),
1692            ),
1693            // No JoinSplits
1694            Transaction::V1 { .. }
1695            | Transaction::V2 {
1696                joinsplit_data: None,
1697                ..
1698            }
1699            | Transaction::V3 {
1700                joinsplit_data: None,
1701                ..
1702            }
1703            | Transaction::V4 {
1704                joinsplit_data: None,
1705                ..
1706            }
1707            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1708            #[cfg(feature = "tx_v6")]
1709            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1710        }
1711    }
1712
1713    /// Modify the `vpub_old` fields from `JoinSplit`s in this transaction,
1714    /// regardless of version, in the order they appear in the transaction.
1715    ///
1716    /// See `output_values_to_sprout` for details.
1717    pub fn output_values_to_sprout_mut(
1718        &mut self,
1719    ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1720        match self {
1721            // JoinSplits with Bctv14 Proofs
1722            Transaction::V2 {
1723                joinsplit_data: Some(joinsplit_data),
1724                ..
1725            }
1726            | Transaction::V3 {
1727                joinsplit_data: Some(joinsplit_data),
1728                ..
1729            } => Box::new(
1730                joinsplit_data
1731                    .joinsplits_mut()
1732                    .map(|joinsplit| &mut joinsplit.vpub_old),
1733            ),
1734            // JoinSplits with Groth16 Proofs
1735            Transaction::V4 {
1736                joinsplit_data: Some(joinsplit_data),
1737                ..
1738            } => Box::new(
1739                joinsplit_data
1740                    .joinsplits_mut()
1741                    .map(|joinsplit| &mut joinsplit.vpub_old),
1742            ),
1743            // No JoinSplits
1744            Transaction::V1 { .. }
1745            | Transaction::V2 {
1746                joinsplit_data: None,
1747                ..
1748            }
1749            | Transaction::V3 {
1750                joinsplit_data: None,
1751                ..
1752            }
1753            | Transaction::V4 {
1754                joinsplit_data: None,
1755                ..
1756            }
1757            | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1758            #[cfg(feature = "tx_v6")]
1759            Transaction::V6 { .. } => Box::new(std::iter::empty()),
1760        }
1761    }
1762
1763    /// Modify the transparent output values of this transaction, regardless of version.
1764    pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1765        self.outputs_mut()
1766            .iter_mut()
1767            .map(|output| &mut output.value)
1768    }
1769
1770    /// Modify the [`orchard::ShieldedData`] in this transaction,
1771    /// regardless of version.
1772    pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1773        match self {
1774            Transaction::V5 {
1775                orchard_shielded_data: Some(orchard_shielded_data),
1776                ..
1777            } => Some(orchard_shielded_data),
1778            #[cfg(feature = "tx_v6")]
1779            Transaction::V6 {
1780                orchard_shielded_data: Some(orchard_shielded_data),
1781                ..
1782            } => Some(orchard_shielded_data),
1783
1784            Transaction::V1 { .. }
1785            | Transaction::V2 { .. }
1786            | Transaction::V3 { .. }
1787            | Transaction::V4 { .. }
1788            | Transaction::V5 {
1789                orchard_shielded_data: None,
1790                ..
1791            } => None,
1792            #[cfg(feature = "tx_v6")]
1793            Transaction::V6 {
1794                orchard_shielded_data: None,
1795                ..
1796            } => None,
1797        }
1798    }
1799
1800    /// Modify the transparent outputs of this transaction, regardless of version.
1801    pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1802        match self {
1803            Transaction::V1 {
1804                ref mut outputs, ..
1805            } => outputs,
1806            Transaction::V2 {
1807                ref mut outputs, ..
1808            } => outputs,
1809            Transaction::V3 {
1810                ref mut outputs, ..
1811            } => outputs,
1812            Transaction::V4 {
1813                ref mut outputs, ..
1814            } => outputs,
1815            Transaction::V5 {
1816                ref mut outputs, ..
1817            } => outputs,
1818            #[cfg(feature = "tx_v6")]
1819            Transaction::V6 {
1820                ref mut outputs, ..
1821            } => outputs,
1822        }
1823    }
1824}