Skip to main content

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