zebra_chain/
transaction.rs

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