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;
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#[derive(Clone, Debug, PartialEq, Eq)]
69#[cfg_attr(
70 any(test, feature = "proptest-impl", feature = "elasticsearch"),
71 derive(Serialize)
72)]
73pub enum Transaction {
74 V1 {
76 inputs: Vec<transparent::Input>,
78 outputs: Vec<transparent::Output>,
80 lock_time: LockTime,
83 },
84 V2 {
86 inputs: Vec<transparent::Input>,
88 outputs: Vec<transparent::Output>,
90 lock_time: LockTime,
93 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
95 },
96 V3 {
98 inputs: Vec<transparent::Input>,
100 outputs: Vec<transparent::Output>,
102 lock_time: LockTime,
105 expiry_height: block::Height,
107 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
109 },
110 V4 {
112 inputs: Vec<transparent::Input>,
114 outputs: Vec<transparent::Output>,
116 lock_time: LockTime,
119 expiry_height: block::Height,
121 joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
123 sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
125 },
126 V5 {
128 network_upgrade: NetworkUpgrade,
132 lock_time: LockTime,
135 expiry_height: block::Height,
137 inputs: Vec<transparent::Input>,
139 outputs: Vec<transparent::Output>,
141 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
143 orchard_shielded_data: Option<orchard::ShieldedData>,
145 },
146 #[cfg(feature = "tx_v6")]
148 V6 {
149 network_upgrade: NetworkUpgrade,
153 lock_time: LockTime,
156 expiry_height: block::Height,
158 inputs: Vec<transparent::Input>,
160 outputs: Vec<transparent::Output>,
162 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
164 orchard_shielded_data: Option<orchard::ShieldedData>,
166 },
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 pub fn hash(&self) -> Hash {
209 Hash::from(self)
210 }
211
212 pub fn unmined_id(&self) -> UnminedTxId {
217 UnminedTxId::from(self)
218 }
219
220 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 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 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 pub fn has_transparent_inputs(&self) -> bool {
287 !self.inputs().is_empty()
288 }
289
290 pub fn has_transparent_outputs(&self) -> bool {
292 !self.outputs().is_empty()
293 }
294
295 pub fn has_transparent_inputs_or_outputs(&self) -> bool {
297 self.has_transparent_inputs() || self.has_transparent_outputs()
298 }
299
300 pub fn has_transparent_or_shielded_inputs(&self) -> bool {
302 self.has_transparent_inputs() || self.has_shielded_inputs()
303 }
304
305 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 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 pub fn has_transparent_or_shielded_outputs(&self) -> bool {
333 self.has_transparent_outputs() || self.has_shielded_outputs()
334 }
335
336 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 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 CheckCoinbaseMaturity { spend_height }
357 } else {
358 DisallowCoinbaseSpend
359 }
360 }
361
362 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 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 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 if lock_time == LockTime::unlocked() {
416 return None;
417 }
418
419 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 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 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 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 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 block::Height(0) => None,
499 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
500 },
501 }
502 }
503
504 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 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 pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
541 self.inputs()
542 .iter()
543 .filter_map(transparent::Input::outpoint)
544 }
545
546 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 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 pub fn is_valid_non_coinbase(&self) -> bool {
577 self.inputs()
578 .iter()
579 .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
580 }
581
582 pub fn sprout_groth16_joinsplits(
586 &self,
587 ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
588 match self {
589 Transaction::V4 {
591 joinsplit_data: Some(joinsplit_data),
592 ..
593 } => Box::new(joinsplit_data.joinsplits()),
594
595 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 pub fn joinsplit_count(&self) -> usize {
611 match self {
612 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 Transaction::V4 {
623 joinsplit_data: Some(joinsplit_data),
624 ..
625 } => joinsplit_data.joinsplits().count(),
626 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 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
648 match self {
653 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 Transaction::V4 {
664 joinsplit_data: Some(joinsplit_data),
665 ..
666 } => Box::new(joinsplit_data.nullifiers()),
667 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 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
690 match self {
691 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 Transaction::V4 {
702 joinsplit_data: Some(joinsplit_data),
703 ..
704 } => Some(joinsplit_data.pub_key),
705 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 pub fn has_sprout_joinsplit_data(&self) -> bool {
727 match self {
728 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
730 #[cfg(feature = "tx_v6")]
731 Transaction::V6 { .. } => false,
732
733 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
735 joinsplit_data.is_some()
736 }
737
738 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
740 }
741 }
742
743 pub fn sprout_note_commitments(
745 &self,
746 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
747 match self {
748 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 Transaction::V4 {
760 joinsplit_data: Some(joinsplit_data),
761 ..
762 } => Box::new(joinsplit_data.note_commitments()),
763
764 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 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
789 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 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 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 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 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 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 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
916 match self {
919 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 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 pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
956 match self {
959 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 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 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 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1019 match self {
1020 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 Transaction::V1 { .. }
1033 | Transaction::V2 { .. }
1034 | Transaction::V3 { .. }
1035 | Transaction::V4 { .. } => None,
1036 }
1037 }
1038
1039 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 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 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 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 pub fn has_orchard_shielded_data(&self) -> bool {
1073 self.orchard_shielded_data().is_some()
1074 }
1075
1076 #[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 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1116 match self {
1117 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 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 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 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1165 match self {
1166 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 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 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 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 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1265 self.sprout_joinsplit_value_balances().sum()
1266 }
1267
1268 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 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 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 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 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 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 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 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 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 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 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 pub fn input_values_from_sprout_mut(
1537 &mut self,
1538 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1539 match self {
1540 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 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 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 pub fn output_values_to_sprout_mut(
1587 &mut self,
1588 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1589 match self {
1590 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 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 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 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 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 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}