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 sprout_joinsplits(&self) -> Box<dyn Iterator<Item = sprout::GenericJoinSplit> + '_> {
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 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
627 Transaction::V4 {
629 joinsplit_data: Some(joinsplit_data),
630 ..
631 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
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 { .. } => Box::new(std::iter::empty()),
647 #[cfg(feature = "tx_v6")]
648 Transaction::V6 { .. } => Box::new(std::iter::empty()),
649 }
650 }
651
652 pub fn joinsplit_count(&self) -> usize {
654 match self {
655 Transaction::V2 {
657 joinsplit_data: Some(joinsplit_data),
658 ..
659 }
660 | Transaction::V3 {
661 joinsplit_data: Some(joinsplit_data),
662 ..
663 } => joinsplit_data.joinsplits().count(),
664 Transaction::V4 {
666 joinsplit_data: Some(joinsplit_data),
667 ..
668 } => joinsplit_data.joinsplits().count(),
669 Transaction::V1 { .. }
671 | Transaction::V2 {
672 joinsplit_data: None,
673 ..
674 }
675 | Transaction::V3 {
676 joinsplit_data: None,
677 ..
678 }
679 | Transaction::V4 {
680 joinsplit_data: None,
681 ..
682 }
683 | Transaction::V5 { .. } => 0,
684 #[cfg(feature = "tx_v6")]
685 Transaction::V6 { .. } => 0,
686 }
687 }
688
689 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
691 match self {
696 Transaction::V2 {
698 joinsplit_data: Some(joinsplit_data),
699 ..
700 }
701 | Transaction::V3 {
702 joinsplit_data: Some(joinsplit_data),
703 ..
704 } => Box::new(joinsplit_data.nullifiers()),
705 Transaction::V4 {
707 joinsplit_data: Some(joinsplit_data),
708 ..
709 } => Box::new(joinsplit_data.nullifiers()),
710 Transaction::V1 { .. }
712 | Transaction::V2 {
713 joinsplit_data: None,
714 ..
715 }
716 | Transaction::V3 {
717 joinsplit_data: None,
718 ..
719 }
720 | Transaction::V4 {
721 joinsplit_data: None,
722 ..
723 }
724 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
725 #[cfg(feature = "tx_v6")]
726 Transaction::V6 { .. } => Box::new(std::iter::empty()),
727 }
728 }
729
730 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
733 match self {
734 Transaction::V2 {
736 joinsplit_data: Some(joinsplit_data),
737 ..
738 }
739 | Transaction::V3 {
740 joinsplit_data: Some(joinsplit_data),
741 ..
742 } => Some(joinsplit_data.pub_key),
743 Transaction::V4 {
745 joinsplit_data: Some(joinsplit_data),
746 ..
747 } => Some(joinsplit_data.pub_key),
748 Transaction::V1 { .. }
750 | Transaction::V2 {
751 joinsplit_data: None,
752 ..
753 }
754 | Transaction::V3 {
755 joinsplit_data: None,
756 ..
757 }
758 | Transaction::V4 {
759 joinsplit_data: None,
760 ..
761 }
762 | Transaction::V5 { .. } => None,
763 #[cfg(feature = "tx_v6")]
764 Transaction::V6 { .. } => None,
765 }
766 }
767
768 pub fn has_sprout_joinsplit_data(&self) -> bool {
770 match self {
771 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
773 #[cfg(feature = "tx_v6")]
774 Transaction::V6 { .. } => false,
775
776 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
778 joinsplit_data.is_some()
779 }
780
781 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
783 }
784 }
785
786 pub fn sprout_note_commitments(
788 &self,
789 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
790 match self {
791 Transaction::V2 {
793 joinsplit_data: Some(joinsplit_data),
794 ..
795 }
796 | Transaction::V3 {
797 joinsplit_data: Some(joinsplit_data),
798 ..
799 } => Box::new(joinsplit_data.note_commitments()),
800
801 Transaction::V4 {
803 joinsplit_data: Some(joinsplit_data),
804 ..
805 } => Box::new(joinsplit_data.note_commitments()),
806
807 Transaction::V2 {
809 joinsplit_data: None,
810 ..
811 }
812 | Transaction::V3 {
813 joinsplit_data: None,
814 ..
815 }
816 | Transaction::V4 {
817 joinsplit_data: None,
818 ..
819 }
820 | Transaction::V1 { .. }
821 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
822 #[cfg(feature = "tx_v6")]
823 Transaction::V6 { .. } => Box::new(std::iter::empty()),
824 }
825 }
826
827 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
832 match self {
835 Transaction::V4 {
836 sapling_shielded_data: Some(sapling_shielded_data),
837 ..
838 } => Box::new(sapling_shielded_data.anchors()),
839
840 Transaction::V5 {
841 sapling_shielded_data: Some(sapling_shielded_data),
842 ..
843 } => Box::new(sapling_shielded_data.anchors()),
844
845 #[cfg(feature = "tx_v6")]
846 Transaction::V6 {
847 sapling_shielded_data: Some(sapling_shielded_data),
848 ..
849 } => Box::new(sapling_shielded_data.anchors()),
850
851 Transaction::V1 { .. }
853 | Transaction::V2 { .. }
854 | Transaction::V3 { .. }
855 | Transaction::V4 {
856 sapling_shielded_data: None,
857 ..
858 }
859 | Transaction::V5 {
860 sapling_shielded_data: None,
861 ..
862 } => Box::new(std::iter::empty()),
863 #[cfg(feature = "tx_v6")]
864 Transaction::V6 {
865 sapling_shielded_data: None,
866 ..
867 } => Box::new(std::iter::empty()),
868 }
869 }
870
871 pub fn sapling_spends_per_anchor(
882 &self,
883 ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
884 match self {
885 Transaction::V4 {
886 sapling_shielded_data: Some(sapling_shielded_data),
887 ..
888 } => Box::new(sapling_shielded_data.spends_per_anchor()),
889 Transaction::V5 {
890 sapling_shielded_data: Some(sapling_shielded_data),
891 ..
892 } => Box::new(sapling_shielded_data.spends_per_anchor()),
893 #[cfg(feature = "tx_v6")]
894 Transaction::V6 {
895 sapling_shielded_data: Some(sapling_shielded_data),
896 ..
897 } => Box::new(sapling_shielded_data.spends_per_anchor()),
898
899 Transaction::V1 { .. }
901 | Transaction::V2 { .. }
902 | Transaction::V3 { .. }
903 | Transaction::V4 {
904 sapling_shielded_data: None,
905 ..
906 }
907 | Transaction::V5 {
908 sapling_shielded_data: None,
909 ..
910 } => Box::new(std::iter::empty()),
911 #[cfg(feature = "tx_v6")]
912 Transaction::V6 {
913 sapling_shielded_data: None,
914 ..
915 } => Box::new(std::iter::empty()),
916 }
917 }
918
919 pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
922 match self {
923 Transaction::V4 {
924 sapling_shielded_data: Some(sapling_shielded_data),
925 ..
926 } => Box::new(sapling_shielded_data.outputs()),
927 Transaction::V5 {
928 sapling_shielded_data: Some(sapling_shielded_data),
929 ..
930 } => Box::new(sapling_shielded_data.outputs()),
931 #[cfg(feature = "tx_v6")]
932 Transaction::V6 {
933 sapling_shielded_data: Some(sapling_shielded_data),
934 ..
935 } => Box::new(sapling_shielded_data.outputs()),
936
937 Transaction::V1 { .. }
939 | Transaction::V2 { .. }
940 | Transaction::V3 { .. }
941 | Transaction::V4 {
942 sapling_shielded_data: None,
943 ..
944 }
945 | Transaction::V5 {
946 sapling_shielded_data: None,
947 ..
948 } => Box::new(std::iter::empty()),
949 #[cfg(feature = "tx_v6")]
950 Transaction::V6 {
951 sapling_shielded_data: None,
952 ..
953 } => Box::new(std::iter::empty()),
954 }
955 }
956
957 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
959 match self {
962 Transaction::V4 {
964 sapling_shielded_data: Some(sapling_shielded_data),
965 ..
966 } => Box::new(sapling_shielded_data.nullifiers()),
967 Transaction::V5 {
968 sapling_shielded_data: Some(sapling_shielded_data),
969 ..
970 } => Box::new(sapling_shielded_data.nullifiers()),
971 #[cfg(feature = "tx_v6")]
972 Transaction::V6 {
973 sapling_shielded_data: Some(sapling_shielded_data),
974 ..
975 } => Box::new(sapling_shielded_data.nullifiers()),
976
977 Transaction::V1 { .. }
979 | Transaction::V2 { .. }
980 | Transaction::V3 { .. }
981 | Transaction::V4 {
982 sapling_shielded_data: None,
983 ..
984 }
985 | Transaction::V5 {
986 sapling_shielded_data: None,
987 ..
988 } => Box::new(std::iter::empty()),
989 #[cfg(feature = "tx_v6")]
990 Transaction::V6 {
991 sapling_shielded_data: None,
992 ..
993 } => Box::new(std::iter::empty()),
994 }
995 }
996
997 pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
999 match self {
1002 Transaction::V4 {
1004 sapling_shielded_data: Some(sapling_shielded_data),
1005 ..
1006 } => Box::new(sapling_shielded_data.note_commitments()),
1007 Transaction::V5 {
1008 sapling_shielded_data: Some(sapling_shielded_data),
1009 ..
1010 } => Box::new(sapling_shielded_data.note_commitments()),
1011 #[cfg(feature = "tx_v6")]
1012 Transaction::V6 {
1013 sapling_shielded_data: Some(sapling_shielded_data),
1014 ..
1015 } => Box::new(sapling_shielded_data.note_commitments()),
1016
1017 Transaction::V1 { .. }
1019 | Transaction::V2 { .. }
1020 | Transaction::V3 { .. }
1021 | Transaction::V4 {
1022 sapling_shielded_data: None,
1023 ..
1024 }
1025 | Transaction::V5 {
1026 sapling_shielded_data: None,
1027 ..
1028 } => Box::new(std::iter::empty()),
1029 #[cfg(feature = "tx_v6")]
1030 Transaction::V6 {
1031 sapling_shielded_data: None,
1032 ..
1033 } => Box::new(std::iter::empty()),
1034 }
1035 }
1036
1037 pub fn has_sapling_shielded_data(&self) -> bool {
1039 match self {
1040 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1041 Transaction::V4 {
1042 sapling_shielded_data,
1043 ..
1044 } => sapling_shielded_data.is_some(),
1045 Transaction::V5 {
1046 sapling_shielded_data,
1047 ..
1048 } => sapling_shielded_data.is_some(),
1049 #[cfg(feature = "tx_v6")]
1050 Transaction::V6 {
1051 sapling_shielded_data,
1052 ..
1053 } => sapling_shielded_data.is_some(),
1054 }
1055 }
1056
1057 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1062 match self {
1063 Transaction::V5 {
1065 orchard_shielded_data,
1066 ..
1067 } => orchard_shielded_data.as_ref(),
1068 #[cfg(feature = "tx_v6")]
1069 Transaction::V6 {
1070 orchard_shielded_data,
1071 ..
1072 } => orchard_shielded_data.as_ref(),
1073
1074 Transaction::V1 { .. }
1076 | Transaction::V2 { .. }
1077 | Transaction::V3 { .. }
1078 | Transaction::V4 { .. } => None,
1079 }
1080 }
1081
1082 pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1085 self.orchard_shielded_data()
1086 .into_iter()
1087 .flat_map(orchard::ShieldedData::actions)
1088 }
1089
1090 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1093 self.orchard_shielded_data()
1094 .into_iter()
1095 .flat_map(orchard::ShieldedData::nullifiers)
1096 }
1097
1098 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1101 self.orchard_shielded_data()
1102 .into_iter()
1103 .flat_map(orchard::ShieldedData::note_commitments)
1104 }
1105
1106 pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1109 self.orchard_shielded_data()
1110 .map(|orchard_shielded_data| orchard_shielded_data.flags)
1111 }
1112
1113 pub fn has_orchard_shielded_data(&self) -> bool {
1116 self.orchard_shielded_data().is_some()
1117 }
1118
1119 #[allow(clippy::unwrap_in_result)]
1126 fn transparent_value_balance_from_outputs(
1127 &self,
1128 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1129 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1130 let input_value = self
1131 .inputs()
1132 .iter()
1133 .map(|i| i.value_from_outputs(outputs))
1134 .sum::<Result<Amount<NonNegative>, AmountError>>()
1135 .map_err(ValueBalanceError::Transparent)?
1136 .constrain()
1137 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1138
1139 let output_value = self
1140 .outputs()
1141 .iter()
1142 .map(|o| o.value())
1143 .sum::<Result<Amount<NonNegative>, AmountError>>()
1144 .map_err(ValueBalanceError::Transparent)?
1145 .constrain()
1146 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1147
1148 (input_value - output_value)
1149 .map(ValueBalance::from_transparent_amount)
1150 .map_err(ValueBalanceError::Transparent)
1151 }
1152
1153 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1159 match self {
1160 Transaction::V2 {
1162 joinsplit_data: Some(joinsplit_data),
1163 ..
1164 }
1165 | Transaction::V3 {
1166 joinsplit_data: Some(joinsplit_data),
1167 ..
1168 } => Box::new(
1169 joinsplit_data
1170 .joinsplits()
1171 .map(|joinsplit| &joinsplit.vpub_old),
1172 ),
1173 Transaction::V4 {
1175 joinsplit_data: Some(joinsplit_data),
1176 ..
1177 } => Box::new(
1178 joinsplit_data
1179 .joinsplits()
1180 .map(|joinsplit| &joinsplit.vpub_old),
1181 ),
1182 Transaction::V1 { .. }
1184 | Transaction::V2 {
1185 joinsplit_data: None,
1186 ..
1187 }
1188 | Transaction::V3 {
1189 joinsplit_data: None,
1190 ..
1191 }
1192 | Transaction::V4 {
1193 joinsplit_data: None,
1194 ..
1195 }
1196 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1197 #[cfg(feature = "tx_v6")]
1198 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1199 }
1200 }
1201
1202 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1208 match self {
1209 Transaction::V2 {
1211 joinsplit_data: Some(joinsplit_data),
1212 ..
1213 }
1214 | Transaction::V3 {
1215 joinsplit_data: Some(joinsplit_data),
1216 ..
1217 } => Box::new(
1218 joinsplit_data
1219 .joinsplits()
1220 .map(|joinsplit| &joinsplit.vpub_new),
1221 ),
1222 Transaction::V4 {
1224 joinsplit_data: Some(joinsplit_data),
1225 ..
1226 } => Box::new(
1227 joinsplit_data
1228 .joinsplits()
1229 .map(|joinsplit| &joinsplit.vpub_new),
1230 ),
1231 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(std::iter::empty()),
1246 #[cfg(feature = "tx_v6")]
1247 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1248 }
1249 }
1250
1251 fn sprout_joinsplit_value_balances(
1260 &self,
1261 ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1262 let joinsplit_value_balances = match self {
1263 Transaction::V2 {
1264 joinsplit_data: Some(joinsplit_data),
1265 ..
1266 }
1267 | Transaction::V3 {
1268 joinsplit_data: Some(joinsplit_data),
1269 ..
1270 } => joinsplit_data.joinsplit_value_balances(),
1271 Transaction::V4 {
1272 joinsplit_data: Some(joinsplit_data),
1273 ..
1274 } => joinsplit_data.joinsplit_value_balances(),
1275 Transaction::V1 { .. }
1276 | Transaction::V2 {
1277 joinsplit_data: None,
1278 ..
1279 }
1280 | Transaction::V3 {
1281 joinsplit_data: None,
1282 ..
1283 }
1284 | Transaction::V4 {
1285 joinsplit_data: None,
1286 ..
1287 }
1288 | Transaction::V5 { .. } => Box::new(iter::empty()),
1289 #[cfg(feature = "tx_v6")]
1290 Transaction::V6 { .. } => Box::new(iter::empty()),
1291 };
1292
1293 joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1294 }
1295
1296 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1308 self.sprout_joinsplit_value_balances().sum()
1309 }
1310
1311 pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1323 let sapling_value_balance = match self {
1324 Transaction::V4 {
1325 sapling_shielded_data: Some(sapling_shielded_data),
1326 ..
1327 } => sapling_shielded_data.value_balance,
1328 Transaction::V5 {
1329 sapling_shielded_data: Some(sapling_shielded_data),
1330 ..
1331 } => sapling_shielded_data.value_balance,
1332 #[cfg(feature = "tx_v6")]
1333 Transaction::V6 {
1334 sapling_shielded_data: Some(sapling_shielded_data),
1335 ..
1336 } => sapling_shielded_data.value_balance,
1337
1338 Transaction::V1 { .. }
1339 | Transaction::V2 { .. }
1340 | Transaction::V3 { .. }
1341 | Transaction::V4 {
1342 sapling_shielded_data: None,
1343 ..
1344 }
1345 | Transaction::V5 {
1346 sapling_shielded_data: None,
1347 ..
1348 } => Amount::zero(),
1349 #[cfg(feature = "tx_v6")]
1350 Transaction::V6 {
1351 sapling_shielded_data: None,
1352 ..
1353 } => Amount::zero(),
1354 };
1355
1356 ValueBalance::from_sapling_amount(sapling_value_balance)
1357 }
1358
1359 pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1364 match self {
1365 Transaction::V4 {
1366 sapling_shielded_data: Some(sapling_shielded_data),
1367 ..
1368 } => Some(sapling_shielded_data.binding_sig),
1369 Transaction::V5 {
1370 sapling_shielded_data: Some(sapling_shielded_data),
1371 ..
1372 } => Some(sapling_shielded_data.binding_sig),
1373 #[cfg(feature = "tx_v6")]
1374 Transaction::V6 {
1375 sapling_shielded_data: Some(sapling_shielded_data),
1376 ..
1377 } => Some(sapling_shielded_data.binding_sig),
1378 _ => None,
1379 }
1380 }
1381
1382 pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1390 match self {
1391 Transaction::V2 {
1392 joinsplit_data: Some(joinsplit_data),
1393 ..
1394 } => Some(joinsplit_data.pub_key),
1395 Transaction::V3 {
1396 joinsplit_data: Some(joinsplit_data),
1397 ..
1398 } => Some(joinsplit_data.pub_key),
1399 Transaction::V4 {
1400 joinsplit_data: Some(joinsplit_data),
1401 ..
1402 } => Some(joinsplit_data.pub_key),
1403 _ => None,
1404 }
1405 }
1406
1407 pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1415 match self {
1416 Transaction::V2 {
1417 joinsplit_data: Some(joinsplit_data),
1418 ..
1419 } => Some(joinsplit_data.sig),
1420 Transaction::V3 {
1421 joinsplit_data: Some(joinsplit_data),
1422 ..
1423 } => Some(joinsplit_data.sig),
1424 Transaction::V4 {
1425 joinsplit_data: Some(joinsplit_data),
1426 ..
1427 } => Some(joinsplit_data.sig),
1428 _ => None,
1429 }
1430 }
1431
1432 pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1444 let orchard_value_balance = self
1445 .orchard_shielded_data()
1446 .map(|shielded_data| shielded_data.value_balance)
1447 .unwrap_or_else(Amount::zero);
1448
1449 ValueBalance::from_orchard_amount(orchard_value_balance)
1450 }
1451
1452 pub(crate) fn value_balance_from_outputs(
1454 &self,
1455 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1456 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1457 self.transparent_value_balance_from_outputs(outputs)?
1458 + self.sprout_value_balance()?
1459 + self.sapling_value_balance()
1460 + self.orchard_value_balance()
1461 }
1462
1463 pub fn value_balance(
1484 &self,
1485 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1486 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1487 self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1488 }
1489
1490 pub(crate) fn to_librustzcash(
1496 &self,
1497 nu: NetworkUpgrade,
1498 ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1499 if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1500 return Err(crate::Error::InvalidConsensusBranchId);
1501 }
1502
1503 let Some(branch_id) = nu.branch_id() else {
1504 return Err(crate::Error::InvalidConsensusBranchId);
1505 };
1506
1507 let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1508 return Err(crate::Error::InvalidConsensusBranchId);
1509 };
1510
1511 Ok(zcash_primitives::transaction::Transaction::read(
1512 &self.zcash_serialize_to_vec()?[..],
1513 branch_id,
1514 )?)
1515 }
1516
1517 pub fn has_shielded_data(&self) -> bool {
1521 self.has_shielded_inputs() || self.has_shielded_outputs()
1522 }
1523
1524 pub fn version_group_id(&self) -> Option<u32> {
1526 match self {
1530 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1531 Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1532 Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1533 Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1534 #[cfg(feature = "tx_v6")]
1535 Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1536 }
1537 }
1538}
1539
1540#[cfg(any(test, feature = "proptest-impl"))]
1541impl Transaction {
1542 pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1548 match self {
1549 Transaction::V1 { .. }
1550 | Transaction::V2 { .. }
1551 | Transaction::V3 { .. }
1552 | Transaction::V4 { .. } => Err(
1553 "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1554 ),
1555 Transaction::V5 {
1556 ref mut network_upgrade,
1557 ..
1558 } => {
1559 *network_upgrade = nu;
1560 Ok(())
1561 }
1562 #[cfg(feature = "tx_v6")]
1563 Transaction::V6 {
1564 ref mut network_upgrade,
1565 ..
1566 } => {
1567 *network_upgrade = nu;
1568 Ok(())
1569 }
1570 }
1571 }
1572
1573 pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1579 match self {
1580 Transaction::V1 { .. } | Transaction::V2 { .. } => {
1581 panic!("v1 and v2 transactions are not supported")
1582 }
1583 Transaction::V3 {
1584 ref mut expiry_height,
1585 ..
1586 }
1587 | Transaction::V4 {
1588 ref mut expiry_height,
1589 ..
1590 }
1591 | Transaction::V5 {
1592 ref mut expiry_height,
1593 ..
1594 } => expiry_height,
1595 #[cfg(feature = "tx_v6")]
1596 Transaction::V6 {
1597 ref mut expiry_height,
1598 ..
1599 } => expiry_height,
1600 }
1601 }
1602
1603 pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1605 match self {
1606 Transaction::V1 { ref mut inputs, .. } => inputs,
1607 Transaction::V2 { ref mut inputs, .. } => inputs,
1608 Transaction::V3 { ref mut inputs, .. } => inputs,
1609 Transaction::V4 { ref mut inputs, .. } => inputs,
1610 Transaction::V5 { ref mut inputs, .. } => inputs,
1611 #[cfg(feature = "tx_v6")]
1612 Transaction::V6 { ref mut inputs, .. } => inputs,
1613 }
1614 }
1615
1616 pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1621 self.orchard_shielded_data_mut()
1622 .map(|shielded_data| &mut shielded_data.value_balance)
1623 }
1624
1625 pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1630 match self {
1631 Transaction::V4 {
1632 sapling_shielded_data: Some(sapling_shielded_data),
1633 ..
1634 } => Some(&mut sapling_shielded_data.value_balance),
1635 Transaction::V5 {
1636 sapling_shielded_data: Some(sapling_shielded_data),
1637 ..
1638 } => Some(&mut sapling_shielded_data.value_balance),
1639 #[cfg(feature = "tx_v6")]
1640 Transaction::V6 {
1641 sapling_shielded_data: Some(sapling_shielded_data),
1642 ..
1643 } => Some(&mut sapling_shielded_data.value_balance),
1644 Transaction::V1 { .. }
1645 | Transaction::V2 { .. }
1646 | Transaction::V3 { .. }
1647 | Transaction::V4 {
1648 sapling_shielded_data: None,
1649 ..
1650 }
1651 | Transaction::V5 {
1652 sapling_shielded_data: None,
1653 ..
1654 } => None,
1655 #[cfg(feature = "tx_v6")]
1656 Transaction::V6 {
1657 sapling_shielded_data: None,
1658 ..
1659 } => None,
1660 }
1661 }
1662
1663 pub fn input_values_from_sprout_mut(
1668 &mut self,
1669 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1670 match self {
1671 Transaction::V2 {
1673 joinsplit_data: Some(joinsplit_data),
1674 ..
1675 }
1676 | Transaction::V3 {
1677 joinsplit_data: Some(joinsplit_data),
1678 ..
1679 } => Box::new(
1680 joinsplit_data
1681 .joinsplits_mut()
1682 .map(|joinsplit| &mut joinsplit.vpub_new),
1683 ),
1684 Transaction::V4 {
1686 joinsplit_data: Some(joinsplit_data),
1687 ..
1688 } => Box::new(
1689 joinsplit_data
1690 .joinsplits_mut()
1691 .map(|joinsplit| &mut joinsplit.vpub_new),
1692 ),
1693 Transaction::V1 { .. }
1695 | Transaction::V2 {
1696 joinsplit_data: None,
1697 ..
1698 }
1699 | Transaction::V3 {
1700 joinsplit_data: None,
1701 ..
1702 }
1703 | Transaction::V4 {
1704 joinsplit_data: None,
1705 ..
1706 }
1707 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1708 #[cfg(feature = "tx_v6")]
1709 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1710 }
1711 }
1712
1713 pub fn output_values_to_sprout_mut(
1718 &mut self,
1719 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1720 match self {
1721 Transaction::V2 {
1723 joinsplit_data: Some(joinsplit_data),
1724 ..
1725 }
1726 | Transaction::V3 {
1727 joinsplit_data: Some(joinsplit_data),
1728 ..
1729 } => Box::new(
1730 joinsplit_data
1731 .joinsplits_mut()
1732 .map(|joinsplit| &mut joinsplit.vpub_old),
1733 ),
1734 Transaction::V4 {
1736 joinsplit_data: Some(joinsplit_data),
1737 ..
1738 } => Box::new(
1739 joinsplit_data
1740 .joinsplits_mut()
1741 .map(|joinsplit| &mut joinsplit.vpub_old),
1742 ),
1743 Transaction::V1 { .. }
1745 | Transaction::V2 {
1746 joinsplit_data: None,
1747 ..
1748 }
1749 | Transaction::V3 {
1750 joinsplit_data: None,
1751 ..
1752 }
1753 | Transaction::V4 {
1754 joinsplit_data: None,
1755 ..
1756 }
1757 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1758 #[cfg(feature = "tx_v6")]
1759 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1760 }
1761 }
1762
1763 pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1765 self.outputs_mut()
1766 .iter_mut()
1767 .map(|output| &mut output.value)
1768 }
1769
1770 pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1773 match self {
1774 Transaction::V5 {
1775 orchard_shielded_data: Some(orchard_shielded_data),
1776 ..
1777 } => Some(orchard_shielded_data),
1778 #[cfg(feature = "tx_v6")]
1779 Transaction::V6 {
1780 orchard_shielded_data: Some(orchard_shielded_data),
1781 ..
1782 } => Some(orchard_shielded_data),
1783
1784 Transaction::V1 { .. }
1785 | Transaction::V2 { .. }
1786 | Transaction::V3 { .. }
1787 | Transaction::V4 { .. }
1788 | Transaction::V5 {
1789 orchard_shielded_data: None,
1790 ..
1791 } => None,
1792 #[cfg(feature = "tx_v6")]
1793 Transaction::V6 {
1794 orchard_shielded_data: None,
1795 ..
1796 } => None,
1797 }
1798 }
1799
1800 pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1802 match self {
1803 Transaction::V1 {
1804 ref mut outputs, ..
1805 } => outputs,
1806 Transaction::V2 {
1807 ref mut outputs, ..
1808 } => outputs,
1809 Transaction::V3 {
1810 ref mut outputs, ..
1811 } => outputs,
1812 Transaction::V4 {
1813 ref mut outputs, ..
1814 } => outputs,
1815 Transaction::V5 {
1816 ref mut outputs, ..
1817 } => outputs,
1818 #[cfg(feature = "tx_v6")]
1819 Transaction::V6 {
1820 ref mut outputs, ..
1821 } => outputs,
1822 }
1823 }
1824}