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