1use std::{collections::HashMap, fmt, iter, sync::Arc};
4
5use halo2::pasta::pallas;
6
7mod auth_digest;
8mod hash;
9mod joinsplit;
10mod lock_time;
11mod memo;
12mod serialize;
13mod sighash;
14mod txid;
15mod unmined;
16
17pub mod builder;
18
19#[cfg(any(test, feature = "proptest-impl"))]
20#[allow(clippy::unwrap_in_result)]
21pub mod arbitrary;
22#[cfg(test)]
23mod tests;
24
25pub use auth_digest::AuthDigest;
26pub use hash::{Hash, WtxId};
27pub use joinsplit::JoinSplitData;
28pub use lock_time::LockTime;
29pub use memo::Memo;
30use redjubjub::{Binding, Signature};
31pub use sapling::FieldNotPresent;
32pub use serialize::{
33 SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE,
34 MIN_TRANSPARENT_TX_V5_SIZE,
35};
36pub use sighash::{HashType, SigHash, SigHasher};
37pub use unmined::{
38 zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
39};
40use zcash_protocol::consensus;
41
42#[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
43use crate::parameters::TX_V6_VERSION_GROUP_ID;
44use crate::{
45 amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
46 block, orchard,
47 parameters::{
48 Network, NetworkUpgrade, OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID,
49 TX_V5_VERSION_GROUP_ID,
50 },
51 primitives::{ed25519, Bctv14Proof, Groth16Proof},
52 sapling,
53 serialization::ZcashSerialize,
54 sprout,
55 transparent::{
56 self, outputs_from_utxos,
57 CoinbaseSpendRestriction::{self, *},
58 },
59 value_balance::{ValueBalance, ValueBalanceError},
60 Error,
61};
62
63#[derive(Clone, Debug, PartialEq, Eq)]
75#[cfg_attr(
76 any(test, feature = "proptest-impl", feature = "elasticsearch"),
77 derive(Serialize)
78)]
79pub enum Transaction {
80 V1 {
82 inputs: Vec<transparent::Input>,
84 outputs: Vec<transparent::Output>,
86 lock_time: LockTime,
89 },
90 V2 {
92 inputs: Vec<transparent::Input>,
94 outputs: Vec<transparent::Output>,
96 lock_time: LockTime,
99 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
101 },
102 V3 {
104 inputs: Vec<transparent::Input>,
106 outputs: Vec<transparent::Output>,
108 lock_time: LockTime,
111 expiry_height: block::Height,
113 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
115 },
116 V4 {
118 inputs: Vec<transparent::Input>,
120 outputs: Vec<transparent::Output>,
122 lock_time: LockTime,
125 expiry_height: block::Height,
127 joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
129 sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
131 },
132 V5 {
134 network_upgrade: NetworkUpgrade,
138 lock_time: LockTime,
141 expiry_height: block::Height,
143 inputs: Vec<transparent::Input>,
145 outputs: Vec<transparent::Output>,
147 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
149 orchard_shielded_data: Option<orchard::ShieldedData>,
151 },
152 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
154 V6 {
155 network_upgrade: NetworkUpgrade,
159 lock_time: LockTime,
162 expiry_height: block::Height,
164 zip233_amount: Amount<NonNegative>,
166 inputs: Vec<transparent::Input>,
168 outputs: Vec<transparent::Output>,
170 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
172 orchard_shielded_data: Option<orchard::ShieldedData>,
174 },
175}
176
177impl fmt::Display for Transaction {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 let mut fmter = f.debug_struct("Transaction");
180
181 fmter.field("version", &self.version());
182
183 if let Some(network_upgrade) = self.network_upgrade() {
184 fmter.field("network_upgrade", &network_upgrade);
185 }
186
187 if let Some(lock_time) = self.lock_time() {
188 fmter.field("lock_time", &lock_time);
189 }
190
191 if let Some(expiry_height) = self.expiry_height() {
192 fmter.field("expiry_height", &expiry_height);
193 }
194 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
195 fmter.field("zip233_amount", &self.zip233_amount());
196
197 fmter.field("transparent_inputs", &self.inputs().len());
198 fmter.field("transparent_outputs", &self.outputs().len());
199 fmter.field("sprout_joinsplits", &self.joinsplit_count());
200 fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
201 fmter.field("sapling_outputs", &self.sapling_outputs().count());
202 fmter.field("orchard_actions", &self.orchard_actions().count());
203
204 fmter.field("unmined_id", &self.unmined_id());
205
206 fmter.finish()
207 }
208}
209
210impl Transaction {
211 pub fn hash(&self) -> Hash {
218 Hash::from(self)
219 }
220
221 pub fn unmined_id(&self) -> UnminedTxId {
226 UnminedTxId::from(self)
227 }
228
229 pub fn sighash(
255 &self,
256 nu: NetworkUpgrade,
257 hash_type: sighash::HashType,
258 all_previous_outputs: Arc<Vec<transparent::Output>>,
259 input_index_script_code: Option<(usize, Vec<u8>)>,
260 ) -> Result<SigHash, Error> {
261 Ok(sighash::SigHasher::new(self, nu, all_previous_outputs)?
262 .sighash(hash_type, input_index_script_code))
263 }
264
265 pub fn sighasher(
267 &self,
268 nu: NetworkUpgrade,
269 all_previous_outputs: Arc<Vec<transparent::Output>>,
270 ) -> Result<sighash::SigHasher, Error> {
271 sighash::SigHasher::new(self, nu, all_previous_outputs)
272 }
273
274 pub fn auth_digest(&self) -> Option<AuthDigest> {
281 match self {
282 Transaction::V1 { .. }
283 | Transaction::V2 { .. }
284 | Transaction::V3 { .. }
285 | Transaction::V4 { .. } => None,
286 Transaction::V5 { .. } => Some(AuthDigest::from(self)),
287 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
288 Transaction::V6 { .. } => Some(AuthDigest::from(self)),
289 }
290 }
291
292 pub fn has_transparent_inputs(&self) -> bool {
296 !self.inputs().is_empty()
297 }
298
299 pub fn has_transparent_outputs(&self) -> bool {
301 !self.outputs().is_empty()
302 }
303
304 pub fn has_transparent_inputs_or_outputs(&self) -> bool {
306 self.has_transparent_inputs() || self.has_transparent_outputs()
307 }
308
309 pub fn has_transparent_or_shielded_inputs(&self) -> bool {
311 self.has_transparent_inputs() || self.has_shielded_inputs()
312 }
313
314 pub fn has_shielded_inputs(&self) -> bool {
318 self.joinsplit_count() > 0
319 || self.sapling_spends_per_anchor().count() > 0
320 || (self.orchard_actions().count() > 0
321 && self
322 .orchard_flags()
323 .unwrap_or_else(orchard::Flags::empty)
324 .contains(orchard::Flags::ENABLE_SPENDS))
325 }
326
327 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
329 pub fn has_zip233_amount(&self) -> bool {
330 self.zip233_amount() > Amount::<NonNegative>::zero()
331 }
332 pub fn has_shielded_outputs(&self) -> bool {
336 self.joinsplit_count() > 0
337 || self.sapling_outputs().count() > 0
338 || (self.orchard_actions().count() > 0
339 && self
340 .orchard_flags()
341 .unwrap_or_else(orchard::Flags::empty)
342 .contains(orchard::Flags::ENABLE_OUTPUTS))
343 }
344
345 pub fn has_transparent_or_shielded_outputs(&self) -> bool {
347 self.has_transparent_outputs() || self.has_shielded_outputs()
348 }
349
350 pub fn has_enough_orchard_flags(&self) -> bool {
352 if self.version() < 5 || self.orchard_actions().count() == 0 {
353 return true;
354 }
355 self.orchard_flags()
356 .unwrap_or_else(orchard::Flags::empty)
357 .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
358 }
359
360 pub fn coinbase_spend_restriction(
363 &self,
364 network: &Network,
365 spend_height: block::Height,
366 ) -> CoinbaseSpendRestriction {
367 if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
368 CheckCoinbaseMaturity { spend_height }
371 } else {
372 DisallowCoinbaseSpend
373 }
374 }
375
376 pub fn is_overwintered(&self) -> bool {
380 match self {
381 Transaction::V1 { .. } | Transaction::V2 { .. } => false,
382 Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
383 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
384 Transaction::V6 { .. } => true,
385 }
386 }
387
388 pub fn version(&self) -> u32 {
400 match self {
401 Transaction::V1 { .. } => 1,
402 Transaction::V2 { .. } => 2,
403 Transaction::V3 { .. } => 3,
404 Transaction::V4 { .. } => 4,
405 Transaction::V5 { .. } => 5,
406 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
407 Transaction::V6 { .. } => 6,
408 }
409 }
410
411 pub fn lock_time(&self) -> Option<LockTime> {
413 let lock_time = match self {
414 Transaction::V1 { lock_time, .. }
415 | Transaction::V2 { lock_time, .. }
416 | Transaction::V3 { lock_time, .. }
417 | Transaction::V4 { lock_time, .. }
418 | Transaction::V5 { lock_time, .. } => *lock_time,
419 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
420 Transaction::V6 { lock_time, .. } => *lock_time,
421 };
422
423 if lock_time == LockTime::unlocked() {
430 return None;
431 }
432
433 let has_sequence_number_enabling_lock_time = self
448 .inputs()
449 .iter()
450 .map(transparent::Input::sequence)
451 .any(|sequence_number| sequence_number != u32::MAX);
452
453 if has_sequence_number_enabling_lock_time {
454 Some(lock_time)
455 } else {
456 None
457 }
458 }
459
460 pub fn raw_lock_time(&self) -> u32 {
462 let lock_time = match self {
463 Transaction::V1 { lock_time, .. }
464 | Transaction::V2 { lock_time, .. }
465 | Transaction::V3 { lock_time, .. }
466 | Transaction::V4 { lock_time, .. }
467 | Transaction::V5 { lock_time, .. } => *lock_time,
468 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
469 Transaction::V6 { lock_time, .. } => *lock_time,
470 };
471 let mut lock_time_bytes = Vec::new();
472 lock_time
473 .zcash_serialize(&mut lock_time_bytes)
474 .expect("lock_time should serialize");
475 u32::from_le_bytes(
476 lock_time_bytes
477 .try_into()
478 .expect("should serialize as 4 bytes"),
479 )
480 }
481
482 pub fn lock_time_is_time(&self) -> bool {
486 if let Some(lock_time) = self.lock_time() {
487 return lock_time.is_time();
488 }
489
490 false
491 }
492
493 pub fn expiry_height(&self) -> Option<block::Height> {
495 match self {
496 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
497 Transaction::V3 { expiry_height, .. }
498 | Transaction::V4 { expiry_height, .. }
499 | Transaction::V5 { expiry_height, .. } => match expiry_height {
500 block::Height(0) => None,
504 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
505 },
506 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
507 Transaction::V6 { expiry_height, .. } => match expiry_height {
508 block::Height(0) => None,
513 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
514 },
515 }
516 }
517
518 pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
523 match self {
524 Transaction::V1 { .. }
525 | Transaction::V2 { .. }
526 | Transaction::V3 { .. }
527 | Transaction::V4 { .. } => None,
528 Transaction::V5 {
529 network_upgrade, ..
530 } => Some(*network_upgrade),
531 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
532 Transaction::V6 {
533 network_upgrade, ..
534 } => Some(*network_upgrade),
535 }
536 }
537
538 pub fn inputs(&self) -> &[transparent::Input] {
542 match self {
543 Transaction::V1 { ref inputs, .. } => inputs,
544 Transaction::V2 { ref inputs, .. } => inputs,
545 Transaction::V3 { ref inputs, .. } => inputs,
546 Transaction::V4 { ref inputs, .. } => inputs,
547 Transaction::V5 { ref inputs, .. } => inputs,
548 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
549 Transaction::V6 { ref inputs, .. } => inputs,
550 }
551 }
552
553 pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
555 self.inputs()
556 .iter()
557 .filter_map(transparent::Input::outpoint)
558 }
559
560 pub fn outputs(&self) -> &[transparent::Output] {
562 match self {
563 Transaction::V1 { ref outputs, .. } => outputs,
564 Transaction::V2 { ref outputs, .. } => outputs,
565 Transaction::V3 { ref outputs, .. } => outputs,
566 Transaction::V4 { ref outputs, .. } => outputs,
567 Transaction::V5 { ref outputs, .. } => outputs,
568 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
569 Transaction::V6 { ref outputs, .. } => outputs,
570 }
571 }
572
573 pub fn is_coinbase(&self) -> bool {
577 self.inputs().len() == 1
578 && matches!(
579 self.inputs().first(),
580 Some(transparent::Input::Coinbase { .. })
581 )
582 }
583
584 pub fn is_valid_non_coinbase(&self) -> bool {
591 self.inputs()
592 .iter()
593 .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
594 }
595
596 pub fn sprout_groth16_joinsplits(
600 &self,
601 ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
602 match self {
603 Transaction::V4 {
605 joinsplit_data: Some(joinsplit_data),
606 ..
607 } => Box::new(joinsplit_data.joinsplits()),
608
609 Transaction::V1 { .. }
611 | Transaction::V2 { .. }
612 | Transaction::V3 { .. }
613 | Transaction::V4 {
614 joinsplit_data: None,
615 ..
616 }
617 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
618 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
619 Transaction::V6 { .. } => Box::new(std::iter::empty()),
620 }
621 }
622
623 pub fn sprout_joinsplits(&self) -> Box<dyn Iterator<Item = sprout::GenericJoinSplit> + '_> {
625 match self {
626 Transaction::V2 {
628 joinsplit_data: Some(joinsplit_data),
629 ..
630 }
631 | Transaction::V3 {
632 joinsplit_data: Some(joinsplit_data),
633 ..
634 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
635 Transaction::V4 {
637 joinsplit_data: Some(joinsplit_data),
638 ..
639 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
640 Transaction::V1 { .. }
642 | Transaction::V2 {
643 joinsplit_data: None,
644 ..
645 }
646 | Transaction::V3 {
647 joinsplit_data: None,
648 ..
649 }
650 | Transaction::V4 {
651 joinsplit_data: None,
652 ..
653 }
654 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
655 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
656 Transaction::V6 { .. } => Box::new(std::iter::empty()),
657 }
658 }
659
660 pub fn joinsplit_count(&self) -> usize {
662 match self {
663 Transaction::V2 {
665 joinsplit_data: Some(joinsplit_data),
666 ..
667 }
668 | Transaction::V3 {
669 joinsplit_data: Some(joinsplit_data),
670 ..
671 } => joinsplit_data.joinsplits().count(),
672 Transaction::V4 {
674 joinsplit_data: Some(joinsplit_data),
675 ..
676 } => joinsplit_data.joinsplits().count(),
677 Transaction::V1 { .. }
679 | Transaction::V2 {
680 joinsplit_data: None,
681 ..
682 }
683 | Transaction::V3 {
684 joinsplit_data: None,
685 ..
686 }
687 | Transaction::V4 {
688 joinsplit_data: None,
689 ..
690 }
691 | Transaction::V5 { .. } => 0,
692 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
693 Transaction::V6 { .. } => 0,
694 }
695 }
696
697 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
699 match self {
704 Transaction::V2 {
706 joinsplit_data: Some(joinsplit_data),
707 ..
708 }
709 | Transaction::V3 {
710 joinsplit_data: Some(joinsplit_data),
711 ..
712 } => Box::new(joinsplit_data.nullifiers()),
713 Transaction::V4 {
715 joinsplit_data: Some(joinsplit_data),
716 ..
717 } => Box::new(joinsplit_data.nullifiers()),
718 Transaction::V1 { .. }
720 | Transaction::V2 {
721 joinsplit_data: None,
722 ..
723 }
724 | Transaction::V3 {
725 joinsplit_data: None,
726 ..
727 }
728 | Transaction::V4 {
729 joinsplit_data: None,
730 ..
731 }
732 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
733 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
734 Transaction::V6 { .. } => Box::new(std::iter::empty()),
735 }
736 }
737
738 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
741 match self {
742 Transaction::V2 {
744 joinsplit_data: Some(joinsplit_data),
745 ..
746 }
747 | Transaction::V3 {
748 joinsplit_data: Some(joinsplit_data),
749 ..
750 } => Some(joinsplit_data.pub_key),
751 Transaction::V4 {
753 joinsplit_data: Some(joinsplit_data),
754 ..
755 } => Some(joinsplit_data.pub_key),
756 Transaction::V1 { .. }
758 | Transaction::V2 {
759 joinsplit_data: None,
760 ..
761 }
762 | Transaction::V3 {
763 joinsplit_data: None,
764 ..
765 }
766 | Transaction::V4 {
767 joinsplit_data: None,
768 ..
769 }
770 | Transaction::V5 { .. } => None,
771 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
772 Transaction::V6 { .. } => None,
773 }
774 }
775
776 pub fn has_sprout_joinsplit_data(&self) -> bool {
778 match self {
779 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
781 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
782 Transaction::V6 { .. } => false,
783
784 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
786 joinsplit_data.is_some()
787 }
788
789 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
791 }
792 }
793
794 pub fn sprout_note_commitments(
796 &self,
797 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
798 match self {
799 Transaction::V2 {
801 joinsplit_data: Some(joinsplit_data),
802 ..
803 }
804 | Transaction::V3 {
805 joinsplit_data: Some(joinsplit_data),
806 ..
807 } => Box::new(joinsplit_data.note_commitments()),
808
809 Transaction::V4 {
811 joinsplit_data: Some(joinsplit_data),
812 ..
813 } => Box::new(joinsplit_data.note_commitments()),
814
815 Transaction::V2 {
817 joinsplit_data: None,
818 ..
819 }
820 | Transaction::V3 {
821 joinsplit_data: None,
822 ..
823 }
824 | Transaction::V4 {
825 joinsplit_data: None,
826 ..
827 }
828 | Transaction::V1 { .. }
829 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
830 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
831 Transaction::V6 { .. } => Box::new(std::iter::empty()),
832 }
833 }
834
835 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
840 match self {
843 Transaction::V4 {
844 sapling_shielded_data: Some(sapling_shielded_data),
845 ..
846 } => Box::new(sapling_shielded_data.anchors()),
847
848 Transaction::V5 {
849 sapling_shielded_data: Some(sapling_shielded_data),
850 ..
851 } => Box::new(sapling_shielded_data.anchors()),
852
853 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
854 Transaction::V6 {
855 sapling_shielded_data: Some(sapling_shielded_data),
856 ..
857 } => Box::new(sapling_shielded_data.anchors()),
858
859 Transaction::V1 { .. }
861 | Transaction::V2 { .. }
862 | Transaction::V3 { .. }
863 | Transaction::V4 {
864 sapling_shielded_data: None,
865 ..
866 }
867 | Transaction::V5 {
868 sapling_shielded_data: None,
869 ..
870 } => Box::new(std::iter::empty()),
871 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
872 Transaction::V6 {
873 sapling_shielded_data: None,
874 ..
875 } => Box::new(std::iter::empty()),
876 }
877 }
878
879 pub fn sapling_spends_per_anchor(
890 &self,
891 ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
892 match self {
893 Transaction::V4 {
894 sapling_shielded_data: Some(sapling_shielded_data),
895 ..
896 } => Box::new(sapling_shielded_data.spends_per_anchor()),
897 Transaction::V5 {
898 sapling_shielded_data: Some(sapling_shielded_data),
899 ..
900 } => Box::new(sapling_shielded_data.spends_per_anchor()),
901 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
902 Transaction::V6 {
903 sapling_shielded_data: Some(sapling_shielded_data),
904 ..
905 } => Box::new(sapling_shielded_data.spends_per_anchor()),
906
907 Transaction::V1 { .. }
909 | Transaction::V2 { .. }
910 | Transaction::V3 { .. }
911 | Transaction::V4 {
912 sapling_shielded_data: None,
913 ..
914 }
915 | Transaction::V5 {
916 sapling_shielded_data: None,
917 ..
918 } => Box::new(std::iter::empty()),
919 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
920 Transaction::V6 {
921 sapling_shielded_data: None,
922 ..
923 } => Box::new(std::iter::empty()),
924 }
925 }
926
927 pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
930 match self {
931 Transaction::V4 {
932 sapling_shielded_data: Some(sapling_shielded_data),
933 ..
934 } => Box::new(sapling_shielded_data.outputs()),
935 Transaction::V5 {
936 sapling_shielded_data: Some(sapling_shielded_data),
937 ..
938 } => Box::new(sapling_shielded_data.outputs()),
939 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
940 Transaction::V6 {
941 sapling_shielded_data: Some(sapling_shielded_data),
942 ..
943 } => Box::new(sapling_shielded_data.outputs()),
944
945 Transaction::V1 { .. }
947 | Transaction::V2 { .. }
948 | Transaction::V3 { .. }
949 | Transaction::V4 {
950 sapling_shielded_data: None,
951 ..
952 }
953 | Transaction::V5 {
954 sapling_shielded_data: None,
955 ..
956 } => Box::new(std::iter::empty()),
957 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
958 Transaction::V6 {
959 sapling_shielded_data: None,
960 ..
961 } => Box::new(std::iter::empty()),
962 }
963 }
964
965 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
967 match self {
970 Transaction::V4 {
972 sapling_shielded_data: Some(sapling_shielded_data),
973 ..
974 } => Box::new(sapling_shielded_data.nullifiers()),
975 Transaction::V5 {
976 sapling_shielded_data: Some(sapling_shielded_data),
977 ..
978 } => Box::new(sapling_shielded_data.nullifiers()),
979 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
980 Transaction::V6 {
981 sapling_shielded_data: Some(sapling_shielded_data),
982 ..
983 } => Box::new(sapling_shielded_data.nullifiers()),
984
985 Transaction::V1 { .. }
987 | Transaction::V2 { .. }
988 | Transaction::V3 { .. }
989 | Transaction::V4 {
990 sapling_shielded_data: None,
991 ..
992 }
993 | Transaction::V5 {
994 sapling_shielded_data: None,
995 ..
996 } => Box::new(std::iter::empty()),
997 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
998 Transaction::V6 {
999 sapling_shielded_data: None,
1000 ..
1001 } => Box::new(std::iter::empty()),
1002 }
1003 }
1004
1005 pub fn sapling_note_commitments(
1007 &self,
1008 ) -> Box<dyn Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> + '_> {
1009 match self {
1012 Transaction::V4 {
1014 sapling_shielded_data: Some(sapling_shielded_data),
1015 ..
1016 } => Box::new(sapling_shielded_data.note_commitments()),
1017 Transaction::V5 {
1018 sapling_shielded_data: Some(sapling_shielded_data),
1019 ..
1020 } => Box::new(sapling_shielded_data.note_commitments()),
1021 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1022 Transaction::V6 {
1023 sapling_shielded_data: Some(sapling_shielded_data),
1024 ..
1025 } => Box::new(sapling_shielded_data.note_commitments()),
1026
1027 Transaction::V1 { .. }
1029 | Transaction::V2 { .. }
1030 | Transaction::V3 { .. }
1031 | Transaction::V4 {
1032 sapling_shielded_data: None,
1033 ..
1034 }
1035 | Transaction::V5 {
1036 sapling_shielded_data: None,
1037 ..
1038 } => Box::new(std::iter::empty()),
1039 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1040 Transaction::V6 {
1041 sapling_shielded_data: None,
1042 ..
1043 } => Box::new(std::iter::empty()),
1044 }
1045 }
1046
1047 pub fn has_sapling_shielded_data(&self) -> bool {
1049 match self {
1050 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1051 Transaction::V4 {
1052 sapling_shielded_data,
1053 ..
1054 } => sapling_shielded_data.is_some(),
1055 Transaction::V5 {
1056 sapling_shielded_data,
1057 ..
1058 } => sapling_shielded_data.is_some(),
1059 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1060 Transaction::V6 {
1061 sapling_shielded_data,
1062 ..
1063 } => sapling_shielded_data.is_some(),
1064 }
1065 }
1066
1067 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1072 match self {
1073 Transaction::V5 {
1075 orchard_shielded_data,
1076 ..
1077 } => orchard_shielded_data.as_ref(),
1078 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1079 Transaction::V6 {
1080 orchard_shielded_data,
1081 ..
1082 } => orchard_shielded_data.as_ref(),
1083
1084 Transaction::V1 { .. }
1086 | Transaction::V2 { .. }
1087 | Transaction::V3 { .. }
1088 | Transaction::V4 { .. } => None,
1089 }
1090 }
1091
1092 pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1095 self.orchard_shielded_data()
1096 .into_iter()
1097 .flat_map(orchard::ShieldedData::actions)
1098 }
1099
1100 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1103 self.orchard_shielded_data()
1104 .into_iter()
1105 .flat_map(orchard::ShieldedData::nullifiers)
1106 }
1107
1108 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1111 self.orchard_shielded_data()
1112 .into_iter()
1113 .flat_map(orchard::ShieldedData::note_commitments)
1114 }
1115
1116 pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1119 self.orchard_shielded_data()
1120 .map(|orchard_shielded_data| orchard_shielded_data.flags)
1121 }
1122
1123 pub fn has_orchard_shielded_data(&self) -> bool {
1126 self.orchard_shielded_data().is_some()
1127 }
1128
1129 #[allow(clippy::unwrap_in_result)]
1136 fn transparent_value_balance_from_outputs(
1137 &self,
1138 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1139 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1140 let input_value = self
1141 .inputs()
1142 .iter()
1143 .map(|i| i.value_from_outputs(outputs))
1144 .sum::<Result<Amount<NonNegative>, AmountError>>()
1145 .map_err(ValueBalanceError::Transparent)?
1146 .constrain()
1147 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1148
1149 let output_value = self
1150 .outputs()
1151 .iter()
1152 .map(|o| o.value())
1153 .sum::<Result<Amount<NonNegative>, AmountError>>()
1154 .map_err(ValueBalanceError::Transparent)?
1155 .constrain()
1156 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1157
1158 (input_value - output_value)
1159 .map(ValueBalance::from_transparent_amount)
1160 .map_err(ValueBalanceError::Transparent)
1161 }
1162
1163 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1169 match self {
1170 Transaction::V2 {
1172 joinsplit_data: Some(joinsplit_data),
1173 ..
1174 }
1175 | Transaction::V3 {
1176 joinsplit_data: Some(joinsplit_data),
1177 ..
1178 } => Box::new(
1179 joinsplit_data
1180 .joinsplits()
1181 .map(|joinsplit| &joinsplit.vpub_old),
1182 ),
1183 Transaction::V4 {
1185 joinsplit_data: Some(joinsplit_data),
1186 ..
1187 } => Box::new(
1188 joinsplit_data
1189 .joinsplits()
1190 .map(|joinsplit| &joinsplit.vpub_old),
1191 ),
1192 Transaction::V1 { .. }
1194 | Transaction::V2 {
1195 joinsplit_data: None,
1196 ..
1197 }
1198 | Transaction::V3 {
1199 joinsplit_data: None,
1200 ..
1201 }
1202 | Transaction::V4 {
1203 joinsplit_data: None,
1204 ..
1205 }
1206 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1207 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1208 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1209 }
1210 }
1211
1212 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1218 match self {
1219 Transaction::V2 {
1221 joinsplit_data: Some(joinsplit_data),
1222 ..
1223 }
1224 | Transaction::V3 {
1225 joinsplit_data: Some(joinsplit_data),
1226 ..
1227 } => Box::new(
1228 joinsplit_data
1229 .joinsplits()
1230 .map(|joinsplit| &joinsplit.vpub_new),
1231 ),
1232 Transaction::V4 {
1234 joinsplit_data: Some(joinsplit_data),
1235 ..
1236 } => Box::new(
1237 joinsplit_data
1238 .joinsplits()
1239 .map(|joinsplit| &joinsplit.vpub_new),
1240 ),
1241 Transaction::V1 { .. }
1243 | Transaction::V2 {
1244 joinsplit_data: None,
1245 ..
1246 }
1247 | Transaction::V3 {
1248 joinsplit_data: None,
1249 ..
1250 }
1251 | Transaction::V4 {
1252 joinsplit_data: None,
1253 ..
1254 }
1255 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1256 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1257 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1258 }
1259 }
1260
1261 fn sprout_joinsplit_value_balances(
1270 &self,
1271 ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1272 let joinsplit_value_balances = match self {
1273 Transaction::V2 {
1274 joinsplit_data: Some(joinsplit_data),
1275 ..
1276 }
1277 | Transaction::V3 {
1278 joinsplit_data: Some(joinsplit_data),
1279 ..
1280 } => joinsplit_data.joinsplit_value_balances(),
1281 Transaction::V4 {
1282 joinsplit_data: Some(joinsplit_data),
1283 ..
1284 } => joinsplit_data.joinsplit_value_balances(),
1285 Transaction::V1 { .. }
1286 | Transaction::V2 {
1287 joinsplit_data: None,
1288 ..
1289 }
1290 | Transaction::V3 {
1291 joinsplit_data: None,
1292 ..
1293 }
1294 | Transaction::V4 {
1295 joinsplit_data: None,
1296 ..
1297 }
1298 | Transaction::V5 { .. } => Box::new(iter::empty()),
1299 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1300 Transaction::V6 { .. } => Box::new(iter::empty()),
1301 };
1302
1303 joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1304 }
1305
1306 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1318 self.sprout_joinsplit_value_balances().sum()
1319 }
1320
1321 pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1333 let sapling_value_balance = match self {
1334 Transaction::V4 {
1335 sapling_shielded_data: Some(sapling_shielded_data),
1336 ..
1337 } => sapling_shielded_data.value_balance,
1338 Transaction::V5 {
1339 sapling_shielded_data: Some(sapling_shielded_data),
1340 ..
1341 } => sapling_shielded_data.value_balance,
1342 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1343 Transaction::V6 {
1344 sapling_shielded_data: Some(sapling_shielded_data),
1345 ..
1346 } => sapling_shielded_data.value_balance,
1347
1348 Transaction::V1 { .. }
1349 | Transaction::V2 { .. }
1350 | Transaction::V3 { .. }
1351 | Transaction::V4 {
1352 sapling_shielded_data: None,
1353 ..
1354 }
1355 | Transaction::V5 {
1356 sapling_shielded_data: None,
1357 ..
1358 } => Amount::zero(),
1359 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1360 Transaction::V6 {
1361 sapling_shielded_data: None,
1362 ..
1363 } => Amount::zero(),
1364 };
1365
1366 ValueBalance::from_sapling_amount(sapling_value_balance)
1367 }
1368
1369 pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1374 match self {
1375 Transaction::V4 {
1376 sapling_shielded_data: Some(sapling_shielded_data),
1377 ..
1378 } => Some(sapling_shielded_data.binding_sig),
1379 Transaction::V5 {
1380 sapling_shielded_data: Some(sapling_shielded_data),
1381 ..
1382 } => Some(sapling_shielded_data.binding_sig),
1383 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1384 Transaction::V6 {
1385 sapling_shielded_data: Some(sapling_shielded_data),
1386 ..
1387 } => Some(sapling_shielded_data.binding_sig),
1388 _ => None,
1389 }
1390 }
1391
1392 pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1400 match self {
1401 Transaction::V2 {
1402 joinsplit_data: Some(joinsplit_data),
1403 ..
1404 } => Some(joinsplit_data.pub_key),
1405 Transaction::V3 {
1406 joinsplit_data: Some(joinsplit_data),
1407 ..
1408 } => Some(joinsplit_data.pub_key),
1409 Transaction::V4 {
1410 joinsplit_data: Some(joinsplit_data),
1411 ..
1412 } => Some(joinsplit_data.pub_key),
1413 _ => None,
1414 }
1415 }
1416
1417 pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1425 match self {
1426 Transaction::V2 {
1427 joinsplit_data: Some(joinsplit_data),
1428 ..
1429 } => Some(joinsplit_data.sig),
1430 Transaction::V3 {
1431 joinsplit_data: Some(joinsplit_data),
1432 ..
1433 } => Some(joinsplit_data.sig),
1434 Transaction::V4 {
1435 joinsplit_data: Some(joinsplit_data),
1436 ..
1437 } => Some(joinsplit_data.sig),
1438 _ => None,
1439 }
1440 }
1441
1442 pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1454 let orchard_value_balance = self
1455 .orchard_shielded_data()
1456 .map(|shielded_data| shielded_data.value_balance)
1457 .unwrap_or_else(Amount::zero);
1458
1459 ValueBalance::from_orchard_amount(orchard_value_balance)
1460 }
1461
1462 pub(crate) fn value_balance_from_outputs(
1464 &self,
1465 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1466 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1467 self.transparent_value_balance_from_outputs(outputs)?
1468 + self.sprout_value_balance()?
1469 + self.sapling_value_balance()
1470 + self.orchard_value_balance()
1471 }
1472
1473 pub fn value_balance(
1494 &self,
1495 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1496 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1497 self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1498 }
1499
1500 pub(crate) fn to_librustzcash(
1506 &self,
1507 nu: NetworkUpgrade,
1508 ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1509 if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1510 return Err(crate::Error::InvalidConsensusBranchId);
1511 }
1512
1513 let Some(branch_id) = nu.branch_id() else {
1514 return Err(crate::Error::InvalidConsensusBranchId);
1515 };
1516
1517 let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1518 return Err(crate::Error::InvalidConsensusBranchId);
1519 };
1520
1521 Ok(zcash_primitives::transaction::Transaction::read(
1522 &self.zcash_serialize_to_vec()?[..],
1523 branch_id,
1524 )?)
1525 }
1526
1527 pub fn has_shielded_data(&self) -> bool {
1531 self.has_shielded_inputs() || self.has_shielded_outputs()
1532 }
1533
1534 pub fn version_group_id(&self) -> Option<u32> {
1536 match self {
1540 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1541 Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1542 Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1543 Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1544 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1545 Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1546 }
1547 }
1548
1549 pub fn zip233_amount(&self) -> Amount<NonNegative> {
1551 match self {
1552 Transaction::V1 { .. }
1553 | Transaction::V2 { .. }
1554 | Transaction::V3 { .. }
1555 | Transaction::V4 { .. }
1556 | Transaction::V5 { .. } => Amount::zero(),
1557 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1558 Transaction::V6 { zip233_amount, .. } => *zip233_amount,
1559 }
1560 }
1561}
1562
1563#[cfg(any(test, feature = "proptest-impl"))]
1564impl Transaction {
1565 pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1571 match self {
1572 Transaction::V1 { .. }
1573 | Transaction::V2 { .. }
1574 | Transaction::V3 { .. }
1575 | Transaction::V4 { .. } => Err(
1576 "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1577 ),
1578 Transaction::V5 {
1579 ref mut network_upgrade,
1580 ..
1581 } => {
1582 *network_upgrade = nu;
1583 Ok(())
1584 }
1585 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1586 Transaction::V6 {
1587 ref mut network_upgrade,
1588 ..
1589 } => {
1590 *network_upgrade = nu;
1591 Ok(())
1592 }
1593 }
1594 }
1595
1596 pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1602 match self {
1603 Transaction::V1 { .. } | Transaction::V2 { .. } => {
1604 panic!("v1 and v2 transactions are not supported")
1605 }
1606 Transaction::V3 {
1607 ref mut expiry_height,
1608 ..
1609 }
1610 | Transaction::V4 {
1611 ref mut expiry_height,
1612 ..
1613 }
1614 | Transaction::V5 {
1615 ref mut expiry_height,
1616 ..
1617 } => expiry_height,
1618 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1619 Transaction::V6 {
1620 ref mut expiry_height,
1621 ..
1622 } => expiry_height,
1623 }
1624 }
1625
1626 pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1628 match self {
1629 Transaction::V1 { ref mut inputs, .. } => inputs,
1630 Transaction::V2 { ref mut inputs, .. } => inputs,
1631 Transaction::V3 { ref mut inputs, .. } => inputs,
1632 Transaction::V4 { ref mut inputs, .. } => inputs,
1633 Transaction::V5 { ref mut inputs, .. } => inputs,
1634 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1635 Transaction::V6 { ref mut inputs, .. } => inputs,
1636 }
1637 }
1638
1639 pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1644 self.orchard_shielded_data_mut()
1645 .map(|shielded_data| &mut shielded_data.value_balance)
1646 }
1647
1648 pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1653 match self {
1654 Transaction::V4 {
1655 sapling_shielded_data: Some(sapling_shielded_data),
1656 ..
1657 } => Some(&mut sapling_shielded_data.value_balance),
1658 Transaction::V5 {
1659 sapling_shielded_data: Some(sapling_shielded_data),
1660 ..
1661 } => Some(&mut sapling_shielded_data.value_balance),
1662 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1663 Transaction::V6 {
1664 sapling_shielded_data: Some(sapling_shielded_data),
1665 ..
1666 } => Some(&mut sapling_shielded_data.value_balance),
1667 Transaction::V1 { .. }
1668 | Transaction::V2 { .. }
1669 | Transaction::V3 { .. }
1670 | Transaction::V4 {
1671 sapling_shielded_data: None,
1672 ..
1673 }
1674 | Transaction::V5 {
1675 sapling_shielded_data: None,
1676 ..
1677 } => None,
1678 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1679 Transaction::V6 {
1680 sapling_shielded_data: None,
1681 ..
1682 } => None,
1683 }
1684 }
1685
1686 pub fn input_values_from_sprout_mut(
1691 &mut self,
1692 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1693 match self {
1694 Transaction::V2 {
1696 joinsplit_data: Some(joinsplit_data),
1697 ..
1698 }
1699 | Transaction::V3 {
1700 joinsplit_data: Some(joinsplit_data),
1701 ..
1702 } => Box::new(
1703 joinsplit_data
1704 .joinsplits_mut()
1705 .map(|joinsplit| &mut joinsplit.vpub_new),
1706 ),
1707 Transaction::V4 {
1709 joinsplit_data: Some(joinsplit_data),
1710 ..
1711 } => Box::new(
1712 joinsplit_data
1713 .joinsplits_mut()
1714 .map(|joinsplit| &mut joinsplit.vpub_new),
1715 ),
1716 Transaction::V1 { .. }
1718 | Transaction::V2 {
1719 joinsplit_data: None,
1720 ..
1721 }
1722 | Transaction::V3 {
1723 joinsplit_data: None,
1724 ..
1725 }
1726 | Transaction::V4 {
1727 joinsplit_data: None,
1728 ..
1729 }
1730 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1731 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1732 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1733 }
1734 }
1735
1736 pub fn output_values_to_sprout_mut(
1741 &mut self,
1742 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1743 match self {
1744 Transaction::V2 {
1746 joinsplit_data: Some(joinsplit_data),
1747 ..
1748 }
1749 | Transaction::V3 {
1750 joinsplit_data: Some(joinsplit_data),
1751 ..
1752 } => Box::new(
1753 joinsplit_data
1754 .joinsplits_mut()
1755 .map(|joinsplit| &mut joinsplit.vpub_old),
1756 ),
1757 Transaction::V4 {
1759 joinsplit_data: Some(joinsplit_data),
1760 ..
1761 } => Box::new(
1762 joinsplit_data
1763 .joinsplits_mut()
1764 .map(|joinsplit| &mut joinsplit.vpub_old),
1765 ),
1766 Transaction::V1 { .. }
1768 | Transaction::V2 {
1769 joinsplit_data: None,
1770 ..
1771 }
1772 | Transaction::V3 {
1773 joinsplit_data: None,
1774 ..
1775 }
1776 | Transaction::V4 {
1777 joinsplit_data: None,
1778 ..
1779 }
1780 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1781 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1782 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1783 }
1784 }
1785
1786 pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1788 self.outputs_mut()
1789 .iter_mut()
1790 .map(|output| &mut output.value)
1791 }
1792
1793 pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1796 match self {
1797 Transaction::V5 {
1798 orchard_shielded_data: Some(orchard_shielded_data),
1799 ..
1800 } => Some(orchard_shielded_data),
1801 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1802 Transaction::V6 {
1803 orchard_shielded_data: Some(orchard_shielded_data),
1804 ..
1805 } => Some(orchard_shielded_data),
1806
1807 Transaction::V1 { .. }
1808 | Transaction::V2 { .. }
1809 | Transaction::V3 { .. }
1810 | Transaction::V4 { .. }
1811 | Transaction::V5 {
1812 orchard_shielded_data: None,
1813 ..
1814 } => None,
1815 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1816 Transaction::V6 {
1817 orchard_shielded_data: None,
1818 ..
1819 } => None,
1820 }
1821 }
1822
1823 pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1825 match self {
1826 Transaction::V1 {
1827 ref mut outputs, ..
1828 } => outputs,
1829 Transaction::V2 {
1830 ref mut outputs, ..
1831 } => outputs,
1832 Transaction::V3 {
1833 ref mut outputs, ..
1834 } => outputs,
1835 Transaction::V4 {
1836 ref mut outputs, ..
1837 } => outputs,
1838 Transaction::V5 {
1839 ref mut outputs, ..
1840 } => outputs,
1841 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1842 Transaction::V6 {
1843 ref mut outputs, ..
1844 } => outputs,
1845 }
1846 }
1847}