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