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(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(feature = "tx_v6")]
154 V6 {
155 network_upgrade: NetworkUpgrade,
159 lock_time: LockTime,
162 expiry_height: block::Height,
164 inputs: Vec<transparent::Input>,
166 outputs: Vec<transparent::Output>,
168 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
170 orchard_shielded_data: Option<orchard::ShieldedData>,
172 },
174}
175
176impl fmt::Display for Transaction {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 let mut fmter = f.debug_struct("Transaction");
179
180 fmter.field("version", &self.version());
181
182 if let Some(network_upgrade) = self.network_upgrade() {
183 fmter.field("network_upgrade", &network_upgrade);
184 }
185
186 if let Some(lock_time) = self.lock_time() {
187 fmter.field("lock_time", &lock_time);
188 }
189
190 if let Some(expiry_height) = self.expiry_height() {
191 fmter.field("expiry_height", &expiry_height);
192 }
193
194 fmter.field("transparent_inputs", &self.inputs().len());
195 fmter.field("transparent_outputs", &self.outputs().len());
196 fmter.field("sprout_joinsplits", &self.joinsplit_count());
197 fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
198 fmter.field("sapling_outputs", &self.sapling_outputs().count());
199 fmter.field("orchard_actions", &self.orchard_actions().count());
200
201 fmter.field("unmined_id", &self.unmined_id());
202
203 fmter.finish()
204 }
205}
206
207impl Transaction {
208 pub fn hash(&self) -> Hash {
215 Hash::from(self)
216 }
217
218 pub fn unmined_id(&self) -> UnminedTxId {
223 UnminedTxId::from(self)
224 }
225
226 pub fn sighash(
252 &self,
253 nu: NetworkUpgrade,
254 hash_type: sighash::HashType,
255 all_previous_outputs: Arc<Vec<transparent::Output>>,
256 input_index_script_code: Option<(usize, Vec<u8>)>,
257 ) -> Result<SigHash, Error> {
258 Ok(sighash::SigHasher::new(self, nu, all_previous_outputs)?
259 .sighash(hash_type, input_index_script_code))
260 }
261
262 pub fn sighasher(
264 &self,
265 nu: NetworkUpgrade,
266 all_previous_outputs: Arc<Vec<transparent::Output>>,
267 ) -> Result<sighash::SigHasher, Error> {
268 sighash::SigHasher::new(self, nu, all_previous_outputs)
269 }
270
271 pub fn auth_digest(&self) -> Option<AuthDigest> {
278 match self {
279 Transaction::V1 { .. }
280 | Transaction::V2 { .. }
281 | Transaction::V3 { .. }
282 | Transaction::V4 { .. } => None,
283 Transaction::V5 { .. } => Some(AuthDigest::from(self)),
284 #[cfg(feature = "tx_v6")]
285 Transaction::V6 { .. } => Some(AuthDigest::from(self)),
286 }
287 }
288
289 pub fn has_transparent_inputs(&self) -> bool {
293 !self.inputs().is_empty()
294 }
295
296 pub fn has_transparent_outputs(&self) -> bool {
298 !self.outputs().is_empty()
299 }
300
301 pub fn has_transparent_inputs_or_outputs(&self) -> bool {
303 self.has_transparent_inputs() || self.has_transparent_outputs()
304 }
305
306 pub fn has_transparent_or_shielded_inputs(&self) -> bool {
308 self.has_transparent_inputs() || self.has_shielded_inputs()
309 }
310
311 pub fn has_shielded_inputs(&self) -> bool {
315 self.joinsplit_count() > 0
316 || self.sapling_spends_per_anchor().count() > 0
317 || (self.orchard_actions().count() > 0
318 && self
319 .orchard_flags()
320 .unwrap_or_else(orchard::Flags::empty)
321 .contains(orchard::Flags::ENABLE_SPENDS))
322 }
323
324 pub fn has_shielded_outputs(&self) -> bool {
328 self.joinsplit_count() > 0
329 || self.sapling_outputs().count() > 0
330 || (self.orchard_actions().count() > 0
331 && self
332 .orchard_flags()
333 .unwrap_or_else(orchard::Flags::empty)
334 .contains(orchard::Flags::ENABLE_OUTPUTS))
335 }
336
337 pub fn has_transparent_or_shielded_outputs(&self) -> bool {
339 self.has_transparent_outputs() || self.has_shielded_outputs()
340 }
341
342 pub fn has_enough_orchard_flags(&self) -> bool {
344 if self.version() < 5 || self.orchard_actions().count() == 0 {
345 return true;
346 }
347 self.orchard_flags()
348 .unwrap_or_else(orchard::Flags::empty)
349 .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
350 }
351
352 pub fn coinbase_spend_restriction(
355 &self,
356 network: &Network,
357 spend_height: block::Height,
358 ) -> CoinbaseSpendRestriction {
359 if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
360 CheckCoinbaseMaturity { spend_height }
363 } else {
364 DisallowCoinbaseSpend
365 }
366 }
367
368 pub fn is_overwintered(&self) -> bool {
372 match self {
373 Transaction::V1 { .. } | Transaction::V2 { .. } => false,
374 Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
375 #[cfg(feature = "tx_v6")]
376 Transaction::V6 { .. } => true,
377 }
378 }
379
380 pub fn version(&self) -> u32 {
392 match self {
393 Transaction::V1 { .. } => 1,
394 Transaction::V2 { .. } => 2,
395 Transaction::V3 { .. } => 3,
396 Transaction::V4 { .. } => 4,
397 Transaction::V5 { .. } => 5,
398 #[cfg(feature = "tx_v6")]
399 Transaction::V6 { .. } => 6,
400 }
401 }
402
403 pub fn lock_time(&self) -> Option<LockTime> {
405 let lock_time = match self {
406 Transaction::V1 { lock_time, .. }
407 | Transaction::V2 { lock_time, .. }
408 | Transaction::V3 { lock_time, .. }
409 | Transaction::V4 { lock_time, .. }
410 | Transaction::V5 { lock_time, .. } => *lock_time,
411 #[cfg(feature = "tx_v6")]
412 Transaction::V6 { lock_time, .. } => *lock_time,
413 };
414
415 if lock_time == LockTime::unlocked() {
422 return None;
423 }
424
425 let has_sequence_number_enabling_lock_time = self
440 .inputs()
441 .iter()
442 .map(transparent::Input::sequence)
443 .any(|sequence_number| sequence_number != u32::MAX);
444
445 if has_sequence_number_enabling_lock_time {
446 Some(lock_time)
447 } else {
448 None
449 }
450 }
451
452 pub fn raw_lock_time(&self) -> u32 {
454 let lock_time = match self {
455 Transaction::V1 { lock_time, .. }
456 | Transaction::V2 { lock_time, .. }
457 | Transaction::V3 { lock_time, .. }
458 | Transaction::V4 { lock_time, .. }
459 | Transaction::V5 { lock_time, .. } => *lock_time,
460 #[cfg(feature = "tx_v6")]
461 Transaction::V6 { lock_time, .. } => *lock_time,
462 };
463 let mut lock_time_bytes = Vec::new();
464 lock_time
465 .zcash_serialize(&mut lock_time_bytes)
466 .expect("lock_time should serialize");
467 u32::from_le_bytes(
468 lock_time_bytes
469 .try_into()
470 .expect("should serialize as 4 bytes"),
471 )
472 }
473
474 pub fn lock_time_is_time(&self) -> bool {
478 if let Some(lock_time) = self.lock_time() {
479 return lock_time.is_time();
480 }
481
482 false
483 }
484
485 pub fn expiry_height(&self) -> Option<block::Height> {
487 match self {
488 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
489 Transaction::V3 { expiry_height, .. }
490 | Transaction::V4 { expiry_height, .. }
491 | Transaction::V5 { expiry_height, .. } => match expiry_height {
492 block::Height(0) => None,
496 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
497 },
498 #[cfg(feature = "tx_v6")]
499 Transaction::V6 { expiry_height, .. } => match expiry_height {
500 block::Height(0) => None,
505 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
506 },
507 }
508 }
509
510 pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
515 match self {
516 Transaction::V1 { .. }
517 | Transaction::V2 { .. }
518 | Transaction::V3 { .. }
519 | Transaction::V4 { .. } => None,
520 Transaction::V5 {
521 network_upgrade, ..
522 } => Some(*network_upgrade),
523 #[cfg(feature = "tx_v6")]
524 Transaction::V6 {
525 network_upgrade, ..
526 } => Some(*network_upgrade),
527 }
528 }
529
530 pub fn inputs(&self) -> &[transparent::Input] {
534 match self {
535 Transaction::V1 { ref inputs, .. } => inputs,
536 Transaction::V2 { ref inputs, .. } => inputs,
537 Transaction::V3 { ref inputs, .. } => inputs,
538 Transaction::V4 { ref inputs, .. } => inputs,
539 Transaction::V5 { ref inputs, .. } => inputs,
540 #[cfg(feature = "tx_v6")]
541 Transaction::V6 { ref inputs, .. } => inputs,
542 }
543 }
544
545 pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
547 self.inputs()
548 .iter()
549 .filter_map(transparent::Input::outpoint)
550 }
551
552 pub fn outputs(&self) -> &[transparent::Output] {
554 match self {
555 Transaction::V1 { ref outputs, .. } => outputs,
556 Transaction::V2 { ref outputs, .. } => outputs,
557 Transaction::V3 { ref outputs, .. } => outputs,
558 Transaction::V4 { ref outputs, .. } => outputs,
559 Transaction::V5 { ref outputs, .. } => outputs,
560 #[cfg(feature = "tx_v6")]
561 Transaction::V6 { ref outputs, .. } => outputs,
562 }
563 }
564
565 pub fn is_coinbase(&self) -> bool {
569 self.inputs().len() == 1
570 && matches!(
571 self.inputs().first(),
572 Some(transparent::Input::Coinbase { .. })
573 )
574 }
575
576 pub fn is_valid_non_coinbase(&self) -> bool {
583 self.inputs()
584 .iter()
585 .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
586 }
587
588 pub fn sprout_groth16_joinsplits(
592 &self,
593 ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
594 match self {
595 Transaction::V4 {
597 joinsplit_data: Some(joinsplit_data),
598 ..
599 } => Box::new(joinsplit_data.joinsplits()),
600
601 Transaction::V1 { .. }
603 | Transaction::V2 { .. }
604 | Transaction::V3 { .. }
605 | Transaction::V4 {
606 joinsplit_data: None,
607 ..
608 }
609 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
610 #[cfg(feature = "tx_v6")]
611 Transaction::V6 { .. } => Box::new(std::iter::empty()),
612 }
613 }
614
615 pub fn joinsplit_count(&self) -> usize {
617 match self {
618 Transaction::V2 {
620 joinsplit_data: Some(joinsplit_data),
621 ..
622 }
623 | Transaction::V3 {
624 joinsplit_data: Some(joinsplit_data),
625 ..
626 } => joinsplit_data.joinsplits().count(),
627 Transaction::V4 {
629 joinsplit_data: Some(joinsplit_data),
630 ..
631 } => joinsplit_data.joinsplits().count(),
632 Transaction::V1 { .. }
634 | Transaction::V2 {
635 joinsplit_data: None,
636 ..
637 }
638 | Transaction::V3 {
639 joinsplit_data: None,
640 ..
641 }
642 | Transaction::V4 {
643 joinsplit_data: None,
644 ..
645 }
646 | Transaction::V5 { .. } => 0,
647 #[cfg(feature = "tx_v6")]
648 Transaction::V6 { .. } => 0,
649 }
650 }
651
652 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
654 match self {
659 Transaction::V2 {
661 joinsplit_data: Some(joinsplit_data),
662 ..
663 }
664 | Transaction::V3 {
665 joinsplit_data: Some(joinsplit_data),
666 ..
667 } => Box::new(joinsplit_data.nullifiers()),
668 Transaction::V4 {
670 joinsplit_data: Some(joinsplit_data),
671 ..
672 } => Box::new(joinsplit_data.nullifiers()),
673 Transaction::V1 { .. }
675 | Transaction::V2 {
676 joinsplit_data: None,
677 ..
678 }
679 | Transaction::V3 {
680 joinsplit_data: None,
681 ..
682 }
683 | Transaction::V4 {
684 joinsplit_data: None,
685 ..
686 }
687 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
688 #[cfg(feature = "tx_v6")]
689 Transaction::V6 { .. } => Box::new(std::iter::empty()),
690 }
691 }
692
693 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
696 match self {
697 Transaction::V2 {
699 joinsplit_data: Some(joinsplit_data),
700 ..
701 }
702 | Transaction::V3 {
703 joinsplit_data: Some(joinsplit_data),
704 ..
705 } => Some(joinsplit_data.pub_key),
706 Transaction::V4 {
708 joinsplit_data: Some(joinsplit_data),
709 ..
710 } => Some(joinsplit_data.pub_key),
711 Transaction::V1 { .. }
713 | Transaction::V2 {
714 joinsplit_data: None,
715 ..
716 }
717 | Transaction::V3 {
718 joinsplit_data: None,
719 ..
720 }
721 | Transaction::V4 {
722 joinsplit_data: None,
723 ..
724 }
725 | Transaction::V5 { .. } => None,
726 #[cfg(feature = "tx_v6")]
727 Transaction::V6 { .. } => None,
728 }
729 }
730
731 pub fn has_sprout_joinsplit_data(&self) -> bool {
733 match self {
734 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
736 #[cfg(feature = "tx_v6")]
737 Transaction::V6 { .. } => false,
738
739 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
741 joinsplit_data.is_some()
742 }
743
744 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
746 }
747 }
748
749 pub fn sprout_note_commitments(
751 &self,
752 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
753 match self {
754 Transaction::V2 {
756 joinsplit_data: Some(joinsplit_data),
757 ..
758 }
759 | Transaction::V3 {
760 joinsplit_data: Some(joinsplit_data),
761 ..
762 } => Box::new(joinsplit_data.note_commitments()),
763
764 Transaction::V4 {
766 joinsplit_data: Some(joinsplit_data),
767 ..
768 } => Box::new(joinsplit_data.note_commitments()),
769
770 Transaction::V2 {
772 joinsplit_data: None,
773 ..
774 }
775 | Transaction::V3 {
776 joinsplit_data: None,
777 ..
778 }
779 | Transaction::V4 {
780 joinsplit_data: None,
781 ..
782 }
783 | Transaction::V1 { .. }
784 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
785 #[cfg(feature = "tx_v6")]
786 Transaction::V6 { .. } => Box::new(std::iter::empty()),
787 }
788 }
789
790 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
795 match self {
798 Transaction::V4 {
799 sapling_shielded_data: Some(sapling_shielded_data),
800 ..
801 } => Box::new(sapling_shielded_data.anchors()),
802
803 Transaction::V5 {
804 sapling_shielded_data: Some(sapling_shielded_data),
805 ..
806 } => Box::new(sapling_shielded_data.anchors()),
807
808 #[cfg(feature = "tx_v6")]
809 Transaction::V6 {
810 sapling_shielded_data: Some(sapling_shielded_data),
811 ..
812 } => Box::new(sapling_shielded_data.anchors()),
813
814 Transaction::V1 { .. }
816 | Transaction::V2 { .. }
817 | Transaction::V3 { .. }
818 | Transaction::V4 {
819 sapling_shielded_data: None,
820 ..
821 }
822 | Transaction::V5 {
823 sapling_shielded_data: None,
824 ..
825 } => Box::new(std::iter::empty()),
826 #[cfg(feature = "tx_v6")]
827 Transaction::V6 {
828 sapling_shielded_data: None,
829 ..
830 } => Box::new(std::iter::empty()),
831 }
832 }
833
834 pub fn sapling_spends_per_anchor(
845 &self,
846 ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
847 match self {
848 Transaction::V4 {
849 sapling_shielded_data: Some(sapling_shielded_data),
850 ..
851 } => Box::new(sapling_shielded_data.spends_per_anchor()),
852 Transaction::V5 {
853 sapling_shielded_data: Some(sapling_shielded_data),
854 ..
855 } => Box::new(sapling_shielded_data.spends_per_anchor()),
856 #[cfg(feature = "tx_v6")]
857 Transaction::V6 {
858 sapling_shielded_data: Some(sapling_shielded_data),
859 ..
860 } => Box::new(sapling_shielded_data.spends_per_anchor()),
861
862 Transaction::V1 { .. }
864 | Transaction::V2 { .. }
865 | Transaction::V3 { .. }
866 | Transaction::V4 {
867 sapling_shielded_data: None,
868 ..
869 }
870 | Transaction::V5 {
871 sapling_shielded_data: None,
872 ..
873 } => Box::new(std::iter::empty()),
874 #[cfg(feature = "tx_v6")]
875 Transaction::V6 {
876 sapling_shielded_data: None,
877 ..
878 } => Box::new(std::iter::empty()),
879 }
880 }
881
882 pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
885 match self {
886 Transaction::V4 {
887 sapling_shielded_data: Some(sapling_shielded_data),
888 ..
889 } => Box::new(sapling_shielded_data.outputs()),
890 Transaction::V5 {
891 sapling_shielded_data: Some(sapling_shielded_data),
892 ..
893 } => Box::new(sapling_shielded_data.outputs()),
894 #[cfg(feature = "tx_v6")]
895 Transaction::V6 {
896 sapling_shielded_data: Some(sapling_shielded_data),
897 ..
898 } => Box::new(sapling_shielded_data.outputs()),
899
900 Transaction::V1 { .. }
902 | Transaction::V2 { .. }
903 | Transaction::V3 { .. }
904 | Transaction::V4 {
905 sapling_shielded_data: None,
906 ..
907 }
908 | Transaction::V5 {
909 sapling_shielded_data: None,
910 ..
911 } => Box::new(std::iter::empty()),
912 #[cfg(feature = "tx_v6")]
913 Transaction::V6 {
914 sapling_shielded_data: None,
915 ..
916 } => Box::new(std::iter::empty()),
917 }
918 }
919
920 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
922 match self {
925 Transaction::V4 {
927 sapling_shielded_data: Some(sapling_shielded_data),
928 ..
929 } => Box::new(sapling_shielded_data.nullifiers()),
930 Transaction::V5 {
931 sapling_shielded_data: Some(sapling_shielded_data),
932 ..
933 } => Box::new(sapling_shielded_data.nullifiers()),
934 #[cfg(feature = "tx_v6")]
935 Transaction::V6 {
936 sapling_shielded_data: Some(sapling_shielded_data),
937 ..
938 } => Box::new(sapling_shielded_data.nullifiers()),
939
940 Transaction::V1 { .. }
942 | Transaction::V2 { .. }
943 | Transaction::V3 { .. }
944 | Transaction::V4 {
945 sapling_shielded_data: None,
946 ..
947 }
948 | Transaction::V5 {
949 sapling_shielded_data: None,
950 ..
951 } => Box::new(std::iter::empty()),
952 #[cfg(feature = "tx_v6")]
953 Transaction::V6 {
954 sapling_shielded_data: None,
955 ..
956 } => Box::new(std::iter::empty()),
957 }
958 }
959
960 pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
962 match self {
965 Transaction::V4 {
967 sapling_shielded_data: Some(sapling_shielded_data),
968 ..
969 } => Box::new(sapling_shielded_data.note_commitments()),
970 Transaction::V5 {
971 sapling_shielded_data: Some(sapling_shielded_data),
972 ..
973 } => Box::new(sapling_shielded_data.note_commitments()),
974 #[cfg(feature = "tx_v6")]
975 Transaction::V6 {
976 sapling_shielded_data: Some(sapling_shielded_data),
977 ..
978 } => Box::new(sapling_shielded_data.note_commitments()),
979
980 Transaction::V1 { .. }
982 | Transaction::V2 { .. }
983 | Transaction::V3 { .. }
984 | Transaction::V4 {
985 sapling_shielded_data: None,
986 ..
987 }
988 | Transaction::V5 {
989 sapling_shielded_data: None,
990 ..
991 } => Box::new(std::iter::empty()),
992 #[cfg(feature = "tx_v6")]
993 Transaction::V6 {
994 sapling_shielded_data: None,
995 ..
996 } => Box::new(std::iter::empty()),
997 }
998 }
999
1000 pub fn has_sapling_shielded_data(&self) -> bool {
1002 match self {
1003 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1004 Transaction::V4 {
1005 sapling_shielded_data,
1006 ..
1007 } => sapling_shielded_data.is_some(),
1008 Transaction::V5 {
1009 sapling_shielded_data,
1010 ..
1011 } => sapling_shielded_data.is_some(),
1012 #[cfg(feature = "tx_v6")]
1013 Transaction::V6 {
1014 sapling_shielded_data,
1015 ..
1016 } => sapling_shielded_data.is_some(),
1017 }
1018 }
1019
1020 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1025 match self {
1026 Transaction::V5 {
1028 orchard_shielded_data,
1029 ..
1030 } => orchard_shielded_data.as_ref(),
1031 #[cfg(feature = "tx_v6")]
1032 Transaction::V6 {
1033 orchard_shielded_data,
1034 ..
1035 } => orchard_shielded_data.as_ref(),
1036
1037 Transaction::V1 { .. }
1039 | Transaction::V2 { .. }
1040 | Transaction::V3 { .. }
1041 | Transaction::V4 { .. } => None,
1042 }
1043 }
1044
1045 pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1048 self.orchard_shielded_data()
1049 .into_iter()
1050 .flat_map(orchard::ShieldedData::actions)
1051 }
1052
1053 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1056 self.orchard_shielded_data()
1057 .into_iter()
1058 .flat_map(orchard::ShieldedData::nullifiers)
1059 }
1060
1061 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1064 self.orchard_shielded_data()
1065 .into_iter()
1066 .flat_map(orchard::ShieldedData::note_commitments)
1067 }
1068
1069 pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1072 self.orchard_shielded_data()
1073 .map(|orchard_shielded_data| orchard_shielded_data.flags)
1074 }
1075
1076 pub fn has_orchard_shielded_data(&self) -> bool {
1079 self.orchard_shielded_data().is_some()
1080 }
1081
1082 #[allow(clippy::unwrap_in_result)]
1089 fn transparent_value_balance_from_outputs(
1090 &self,
1091 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1092 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1093 let input_value = self
1094 .inputs()
1095 .iter()
1096 .map(|i| i.value_from_outputs(outputs))
1097 .sum::<Result<Amount<NonNegative>, AmountError>>()
1098 .map_err(ValueBalanceError::Transparent)?
1099 .constrain()
1100 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1101
1102 let output_value = self
1103 .outputs()
1104 .iter()
1105 .map(|o| o.value())
1106 .sum::<Result<Amount<NonNegative>, AmountError>>()
1107 .map_err(ValueBalanceError::Transparent)?
1108 .constrain()
1109 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1110
1111 (input_value - output_value)
1112 .map(ValueBalance::from_transparent_amount)
1113 .map_err(ValueBalanceError::Transparent)
1114 }
1115
1116 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1122 match self {
1123 Transaction::V2 {
1125 joinsplit_data: Some(joinsplit_data),
1126 ..
1127 }
1128 | Transaction::V3 {
1129 joinsplit_data: Some(joinsplit_data),
1130 ..
1131 } => Box::new(
1132 joinsplit_data
1133 .joinsplits()
1134 .map(|joinsplit| &joinsplit.vpub_old),
1135 ),
1136 Transaction::V4 {
1138 joinsplit_data: Some(joinsplit_data),
1139 ..
1140 } => Box::new(
1141 joinsplit_data
1142 .joinsplits()
1143 .map(|joinsplit| &joinsplit.vpub_old),
1144 ),
1145 Transaction::V1 { .. }
1147 | Transaction::V2 {
1148 joinsplit_data: None,
1149 ..
1150 }
1151 | Transaction::V3 {
1152 joinsplit_data: None,
1153 ..
1154 }
1155 | Transaction::V4 {
1156 joinsplit_data: None,
1157 ..
1158 }
1159 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1160 #[cfg(feature = "tx_v6")]
1161 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1162 }
1163 }
1164
1165 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1171 match self {
1172 Transaction::V2 {
1174 joinsplit_data: Some(joinsplit_data),
1175 ..
1176 }
1177 | Transaction::V3 {
1178 joinsplit_data: Some(joinsplit_data),
1179 ..
1180 } => Box::new(
1181 joinsplit_data
1182 .joinsplits()
1183 .map(|joinsplit| &joinsplit.vpub_new),
1184 ),
1185 Transaction::V4 {
1187 joinsplit_data: Some(joinsplit_data),
1188 ..
1189 } => Box::new(
1190 joinsplit_data
1191 .joinsplits()
1192 .map(|joinsplit| &joinsplit.vpub_new),
1193 ),
1194 Transaction::V1 { .. }
1196 | Transaction::V2 {
1197 joinsplit_data: None,
1198 ..
1199 }
1200 | Transaction::V3 {
1201 joinsplit_data: None,
1202 ..
1203 }
1204 | Transaction::V4 {
1205 joinsplit_data: None,
1206 ..
1207 }
1208 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1209 #[cfg(feature = "tx_v6")]
1210 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1211 }
1212 }
1213
1214 fn sprout_joinsplit_value_balances(
1223 &self,
1224 ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1225 let joinsplit_value_balances = match self {
1226 Transaction::V2 {
1227 joinsplit_data: Some(joinsplit_data),
1228 ..
1229 }
1230 | Transaction::V3 {
1231 joinsplit_data: Some(joinsplit_data),
1232 ..
1233 } => joinsplit_data.joinsplit_value_balances(),
1234 Transaction::V4 {
1235 joinsplit_data: Some(joinsplit_data),
1236 ..
1237 } => joinsplit_data.joinsplit_value_balances(),
1238 Transaction::V1 { .. }
1239 | Transaction::V2 {
1240 joinsplit_data: None,
1241 ..
1242 }
1243 | Transaction::V3 {
1244 joinsplit_data: None,
1245 ..
1246 }
1247 | Transaction::V4 {
1248 joinsplit_data: None,
1249 ..
1250 }
1251 | Transaction::V5 { .. } => Box::new(iter::empty()),
1252 #[cfg(feature = "tx_v6")]
1253 Transaction::V6 { .. } => Box::new(iter::empty()),
1254 };
1255
1256 joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1257 }
1258
1259 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1271 self.sprout_joinsplit_value_balances().sum()
1272 }
1273
1274 pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1286 let sapling_value_balance = match self {
1287 Transaction::V4 {
1288 sapling_shielded_data: Some(sapling_shielded_data),
1289 ..
1290 } => sapling_shielded_data.value_balance,
1291 Transaction::V5 {
1292 sapling_shielded_data: Some(sapling_shielded_data),
1293 ..
1294 } => sapling_shielded_data.value_balance,
1295 #[cfg(feature = "tx_v6")]
1296 Transaction::V6 {
1297 sapling_shielded_data: Some(sapling_shielded_data),
1298 ..
1299 } => sapling_shielded_data.value_balance,
1300
1301 Transaction::V1 { .. }
1302 | Transaction::V2 { .. }
1303 | Transaction::V3 { .. }
1304 | Transaction::V4 {
1305 sapling_shielded_data: None,
1306 ..
1307 }
1308 | Transaction::V5 {
1309 sapling_shielded_data: None,
1310 ..
1311 } => Amount::zero(),
1312 #[cfg(feature = "tx_v6")]
1313 Transaction::V6 {
1314 sapling_shielded_data: None,
1315 ..
1316 } => Amount::zero(),
1317 };
1318
1319 ValueBalance::from_sapling_amount(sapling_value_balance)
1320 }
1321
1322 pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1327 match self {
1328 Transaction::V4 {
1329 sapling_shielded_data: Some(sapling_shielded_data),
1330 ..
1331 } => Some(sapling_shielded_data.binding_sig),
1332 Transaction::V5 {
1333 sapling_shielded_data: Some(sapling_shielded_data),
1334 ..
1335 } => Some(sapling_shielded_data.binding_sig),
1336 #[cfg(feature = "tx_v6")]
1337 Transaction::V6 {
1338 sapling_shielded_data: Some(sapling_shielded_data),
1339 ..
1340 } => Some(sapling_shielded_data.binding_sig),
1341 _ => None,
1342 }
1343 }
1344
1345 pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1353 match self {
1354 Transaction::V2 {
1355 joinsplit_data: Some(joinsplit_data),
1356 ..
1357 } => Some(joinsplit_data.pub_key),
1358 Transaction::V3 {
1359 joinsplit_data: Some(joinsplit_data),
1360 ..
1361 } => Some(joinsplit_data.pub_key),
1362 Transaction::V4 {
1363 joinsplit_data: Some(joinsplit_data),
1364 ..
1365 } => Some(joinsplit_data.pub_key),
1366 _ => None,
1367 }
1368 }
1369
1370 pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1378 match self {
1379 Transaction::V2 {
1380 joinsplit_data: Some(joinsplit_data),
1381 ..
1382 } => Some(joinsplit_data.sig),
1383 Transaction::V3 {
1384 joinsplit_data: Some(joinsplit_data),
1385 ..
1386 } => Some(joinsplit_data.sig),
1387 Transaction::V4 {
1388 joinsplit_data: Some(joinsplit_data),
1389 ..
1390 } => Some(joinsplit_data.sig),
1391 _ => None,
1392 }
1393 }
1394
1395 pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1407 let orchard_value_balance = self
1408 .orchard_shielded_data()
1409 .map(|shielded_data| shielded_data.value_balance)
1410 .unwrap_or_else(Amount::zero);
1411
1412 ValueBalance::from_orchard_amount(orchard_value_balance)
1413 }
1414
1415 pub(crate) fn value_balance_from_outputs(
1417 &self,
1418 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1419 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1420 self.transparent_value_balance_from_outputs(outputs)?
1421 + self.sprout_value_balance()?
1422 + self.sapling_value_balance()
1423 + self.orchard_value_balance()
1424 }
1425
1426 pub fn value_balance(
1447 &self,
1448 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1449 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1450 self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1451 }
1452
1453 pub(crate) fn to_librustzcash(
1459 &self,
1460 nu: NetworkUpgrade,
1461 ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1462 if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1463 return Err(crate::Error::InvalidConsensusBranchId);
1464 }
1465
1466 let Some(branch_id) = nu.branch_id() else {
1467 return Err(crate::Error::InvalidConsensusBranchId);
1468 };
1469
1470 let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1471 return Err(crate::Error::InvalidConsensusBranchId);
1472 };
1473
1474 Ok(zcash_primitives::transaction::Transaction::read(
1475 &self.zcash_serialize_to_vec()?[..],
1476 branch_id,
1477 )?)
1478 }
1479
1480 pub fn has_shielded_data(&self) -> bool {
1484 self.has_shielded_inputs() || self.has_shielded_outputs()
1485 }
1486
1487 pub fn version_group_id(&self) -> Option<u32> {
1489 match self {
1493 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1494 Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1495 Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1496 Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1497 #[cfg(feature = "tx_v6")]
1498 Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1499 }
1500 }
1501}
1502
1503#[cfg(any(test, feature = "proptest-impl"))]
1504impl Transaction {
1505 pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1511 match self {
1512 Transaction::V1 { .. }
1513 | Transaction::V2 { .. }
1514 | Transaction::V3 { .. }
1515 | Transaction::V4 { .. } => Err(
1516 "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1517 ),
1518 Transaction::V5 {
1519 ref mut network_upgrade,
1520 ..
1521 } => {
1522 *network_upgrade = nu;
1523 Ok(())
1524 }
1525 #[cfg(feature = "tx_v6")]
1526 Transaction::V6 {
1527 ref mut network_upgrade,
1528 ..
1529 } => {
1530 *network_upgrade = nu;
1531 Ok(())
1532 }
1533 }
1534 }
1535
1536 pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1542 match self {
1543 Transaction::V1 { .. } | Transaction::V2 { .. } => {
1544 panic!("v1 and v2 transactions are not supported")
1545 }
1546 Transaction::V3 {
1547 ref mut expiry_height,
1548 ..
1549 }
1550 | Transaction::V4 {
1551 ref mut expiry_height,
1552 ..
1553 }
1554 | Transaction::V5 {
1555 ref mut expiry_height,
1556 ..
1557 } => expiry_height,
1558 #[cfg(feature = "tx_v6")]
1559 Transaction::V6 {
1560 ref mut expiry_height,
1561 ..
1562 } => expiry_height,
1563 }
1564 }
1565
1566 pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1568 match self {
1569 Transaction::V1 { ref mut inputs, .. } => inputs,
1570 Transaction::V2 { ref mut inputs, .. } => inputs,
1571 Transaction::V3 { ref mut inputs, .. } => inputs,
1572 Transaction::V4 { ref mut inputs, .. } => inputs,
1573 Transaction::V5 { ref mut inputs, .. } => inputs,
1574 #[cfg(feature = "tx_v6")]
1575 Transaction::V6 { ref mut inputs, .. } => inputs,
1576 }
1577 }
1578
1579 pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1584 self.orchard_shielded_data_mut()
1585 .map(|shielded_data| &mut shielded_data.value_balance)
1586 }
1587
1588 pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1593 match self {
1594 Transaction::V4 {
1595 sapling_shielded_data: Some(sapling_shielded_data),
1596 ..
1597 } => Some(&mut sapling_shielded_data.value_balance),
1598 Transaction::V5 {
1599 sapling_shielded_data: Some(sapling_shielded_data),
1600 ..
1601 } => Some(&mut sapling_shielded_data.value_balance),
1602 #[cfg(feature = "tx_v6")]
1603 Transaction::V6 {
1604 sapling_shielded_data: Some(sapling_shielded_data),
1605 ..
1606 } => Some(&mut sapling_shielded_data.value_balance),
1607 Transaction::V1 { .. }
1608 | Transaction::V2 { .. }
1609 | Transaction::V3 { .. }
1610 | Transaction::V4 {
1611 sapling_shielded_data: None,
1612 ..
1613 }
1614 | Transaction::V5 {
1615 sapling_shielded_data: None,
1616 ..
1617 } => None,
1618 #[cfg(feature = "tx_v6")]
1619 Transaction::V6 {
1620 sapling_shielded_data: None,
1621 ..
1622 } => None,
1623 }
1624 }
1625
1626 pub fn input_values_from_sprout_mut(
1631 &mut self,
1632 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1633 match self {
1634 Transaction::V2 {
1636 joinsplit_data: Some(joinsplit_data),
1637 ..
1638 }
1639 | Transaction::V3 {
1640 joinsplit_data: Some(joinsplit_data),
1641 ..
1642 } => Box::new(
1643 joinsplit_data
1644 .joinsplits_mut()
1645 .map(|joinsplit| &mut joinsplit.vpub_new),
1646 ),
1647 Transaction::V4 {
1649 joinsplit_data: Some(joinsplit_data),
1650 ..
1651 } => Box::new(
1652 joinsplit_data
1653 .joinsplits_mut()
1654 .map(|joinsplit| &mut joinsplit.vpub_new),
1655 ),
1656 Transaction::V1 { .. }
1658 | Transaction::V2 {
1659 joinsplit_data: None,
1660 ..
1661 }
1662 | Transaction::V3 {
1663 joinsplit_data: None,
1664 ..
1665 }
1666 | Transaction::V4 {
1667 joinsplit_data: None,
1668 ..
1669 }
1670 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1671 #[cfg(feature = "tx_v6")]
1672 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1673 }
1674 }
1675
1676 pub fn output_values_to_sprout_mut(
1681 &mut self,
1682 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1683 match self {
1684 Transaction::V2 {
1686 joinsplit_data: Some(joinsplit_data),
1687 ..
1688 }
1689 | Transaction::V3 {
1690 joinsplit_data: Some(joinsplit_data),
1691 ..
1692 } => Box::new(
1693 joinsplit_data
1694 .joinsplits_mut()
1695 .map(|joinsplit| &mut joinsplit.vpub_old),
1696 ),
1697 Transaction::V4 {
1699 joinsplit_data: Some(joinsplit_data),
1700 ..
1701 } => Box::new(
1702 joinsplit_data
1703 .joinsplits_mut()
1704 .map(|joinsplit| &mut joinsplit.vpub_old),
1705 ),
1706 Transaction::V1 { .. }
1708 | Transaction::V2 {
1709 joinsplit_data: None,
1710 ..
1711 }
1712 | Transaction::V3 {
1713 joinsplit_data: None,
1714 ..
1715 }
1716 | Transaction::V4 {
1717 joinsplit_data: None,
1718 ..
1719 }
1720 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1721 #[cfg(feature = "tx_v6")]
1722 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1723 }
1724 }
1725
1726 pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1728 self.outputs_mut()
1729 .iter_mut()
1730 .map(|output| &mut output.value)
1731 }
1732
1733 pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1736 match self {
1737 Transaction::V5 {
1738 orchard_shielded_data: Some(orchard_shielded_data),
1739 ..
1740 } => Some(orchard_shielded_data),
1741 #[cfg(feature = "tx_v6")]
1742 Transaction::V6 {
1743 orchard_shielded_data: Some(orchard_shielded_data),
1744 ..
1745 } => Some(orchard_shielded_data),
1746
1747 Transaction::V1 { .. }
1748 | Transaction::V2 { .. }
1749 | Transaction::V3 { .. }
1750 | Transaction::V4 { .. }
1751 | Transaction::V5 {
1752 orchard_shielded_data: None,
1753 ..
1754 } => None,
1755 #[cfg(feature = "tx_v6")]
1756 Transaction::V6 {
1757 orchard_shielded_data: None,
1758 ..
1759 } => None,
1760 }
1761 }
1762
1763 pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1765 match self {
1766 Transaction::V1 {
1767 ref mut outputs, ..
1768 } => outputs,
1769 Transaction::V2 {
1770 ref mut outputs, ..
1771 } => outputs,
1772 Transaction::V3 {
1773 ref mut outputs, ..
1774 } => outputs,
1775 Transaction::V4 {
1776 ref mut outputs, ..
1777 } => outputs,
1778 Transaction::V5 {
1779 ref mut outputs, ..
1780 } => outputs,
1781 #[cfg(feature = "tx_v6")]
1782 Transaction::V6 {
1783 ref mut outputs, ..
1784 } => outputs,
1785 }
1786 }
1787}