1use std::sync::Arc;
4
5use crate::methods::arrayhex;
6use chrono::{DateTime, Utc};
7use derive_getters::Getters;
8use derive_new::new;
9use hex::ToHex;
10
11use serde_with::serde_as;
12use zebra_chain::{
13 amount::{self, Amount, NegativeOrZero, NonNegative},
14 block::{self, merkle::AUTH_DIGEST_PLACEHOLDER, Height},
15 orchard,
16 parameters::Network,
17 primitives::ed25519,
18 sapling::ValueCommitment,
19 serialization::ZcashSerialize,
20 transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
21 transparent::Script,
22};
23use zebra_consensus::groth16::Description;
24use zebra_script::Sigops;
25use zebra_state::IntoDisk;
26
27use super::super::opthex;
28use super::zec::Zec;
29
30#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
32#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
33pub struct TransactionTemplate<FeeConstraint>
34where
35 FeeConstraint: amount::Constraint + Clone + Copy,
36{
37 #[serde(with = "hex")]
39 pub(crate) data: SerializedTransaction,
40
41 #[serde(with = "hex")]
43 #[getter(copy)]
44 pub(crate) hash: transaction::Hash,
45
46 #[serde(rename = "authdigest")]
48 #[serde(with = "hex")]
49 #[getter(copy)]
50 pub(crate) auth_digest: transaction::AuthDigest,
51
52 pub(crate) depends: Vec<u16>,
59
60 #[getter(copy)]
66 pub(crate) fee: Amount<FeeConstraint>,
67
68 pub(crate) sigops: u32,
70
71 pub(crate) required: bool,
75}
76
77impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
79 fn from(tx: &VerifiedUnminedTx) -> Self {
80 assert!(
81 !tx.transaction.transaction.is_coinbase(),
82 "unexpected coinbase transaction in mempool"
83 );
84
85 Self {
86 data: tx.transaction.transaction.as_ref().into(),
87 hash: tx.transaction.id.mined_id(),
88 auth_digest: tx
89 .transaction
90 .id
91 .auth_digest()
92 .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
93
94 depends: Vec::new(),
96
97 fee: tx.miner_fee,
98
99 sigops: tx.sigops,
100
101 required: false,
103 }
104 }
105}
106
107impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
108 fn from(tx: VerifiedUnminedTx) -> Self {
109 Self::from(&tx)
110 }
111}
112
113impl TransactionTemplate<NegativeOrZero> {
114 pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
120 assert!(
121 tx.transaction.is_coinbase(),
122 "invalid generated coinbase transaction: \
123 must have exactly one input, which must be a coinbase input",
124 );
125
126 let miner_fee = (-miner_fee)
127 .constrain()
128 .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
129
130 Self {
131 data: tx.transaction.as_ref().into(),
132 hash: tx.id.mined_id(),
133 auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
134
135 depends: Vec::new(),
137
138 fee: miner_fee,
139
140 sigops: tx.sigops().expect("sigops count should be valid"),
141
142 required: true,
144 }
145 }
146}
147
148#[allow(clippy::too_many_arguments)]
151#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
152pub struct TransactionObject {
153 #[serde(skip_serializing_if = "Option::is_none")]
156 #[getter(copy)]
157 pub(crate) in_active_chain: Option<bool>,
158 #[serde(with = "hex")]
160 pub(crate) hex: SerializedTransaction,
161 #[serde(skip_serializing_if = "Option::is_none")]
164 #[getter(copy)]
165 pub(crate) height: Option<u32>,
166 #[serde(skip_serializing_if = "Option::is_none")]
169 #[getter(copy)]
170 pub(crate) confirmations: Option<u32>,
171
172 #[serde(rename = "vin")]
174 pub(crate) inputs: Vec<Input>,
175
176 #[serde(rename = "vout")]
178 pub(crate) outputs: Vec<Output>,
179
180 #[serde(rename = "vShieldedSpend")]
182 pub(crate) shielded_spends: Vec<ShieldedSpend>,
183
184 #[serde(rename = "vShieldedOutput")]
186 pub(crate) shielded_outputs: Vec<ShieldedOutput>,
187
188 #[serde(rename = "vjoinsplit")]
190 pub(crate) joinsplits: Vec<JoinSplit>,
191
192 #[serde(
194 skip_serializing_if = "Option::is_none",
195 with = "opthex",
196 default,
197 rename = "bindingSig"
198 )]
199 #[getter(copy)]
200 pub(crate) binding_sig: Option<[u8; 64]>,
201
202 #[serde(
204 skip_serializing_if = "Option::is_none",
205 with = "opthex",
206 default,
207 rename = "joinSplitPubKey"
208 )]
209 #[getter(copy)]
210 pub(crate) joinsplit_pub_key: Option<[u8; 32]>,
211
212 #[serde(
214 skip_serializing_if = "Option::is_none",
215 with = "opthex",
216 default,
217 rename = "joinSplitSig"
218 )]
219 #[getter(copy)]
220 pub(crate) joinsplit_sig: Option<[u8; ed25519::Signature::BYTE_SIZE]>,
221
222 #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
224 pub(crate) orchard: Option<Orchard>,
225
226 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
228 #[getter(copy)]
229 pub(crate) value_balance: Option<f64>,
230
231 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
233 #[getter(copy)]
234 pub(crate) value_balance_zat: Option<i64>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
238 #[getter(copy)]
239 pub(crate) size: Option<i64>,
240
241 #[serde(skip_serializing_if = "Option::is_none")]
243 #[getter(copy)]
244 pub(crate) time: Option<i64>,
245
246 #[serde(with = "hex")]
248 #[getter(copy)]
249 pub txid: transaction::Hash,
250
251 #[serde(
254 rename = "authdigest",
255 with = "opthex",
256 skip_serializing_if = "Option::is_none",
257 default
258 )]
259 #[getter(copy)]
260 pub(crate) auth_digest: Option<transaction::AuthDigest>,
261
262 pub(crate) overwintered: bool,
264
265 pub(crate) version: u32,
267
268 #[serde(
270 rename = "versiongroupid",
271 with = "opthex",
272 skip_serializing_if = "Option::is_none",
273 default
274 )]
275 pub(crate) version_group_id: Option<Vec<u8>>,
276
277 #[serde(rename = "locktime")]
279 pub(crate) lock_time: u32,
280
281 #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
283 #[getter(copy)]
284 pub(crate) expiry_height: Option<Height>,
285
286 #[serde(
288 rename = "blockhash",
289 with = "opthex",
290 skip_serializing_if = "Option::is_none",
291 default
292 )]
293 #[getter(copy)]
294 pub(crate) block_hash: Option<block::Hash>,
295
296 #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
298 #[getter(copy)]
299 pub(crate) block_time: Option<i64>,
300}
301
302#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
304#[serde(untagged)]
305pub enum Input {
306 Coinbase {
308 #[serde(with = "hex")]
310 coinbase: Vec<u8>,
311 sequence: u32,
313 },
314 NonCoinbase {
316 txid: String,
318 vout: u32,
320 #[serde(rename = "scriptSig")]
322 script_sig: ScriptSig,
323 sequence: u32,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 value: Option<f64>,
328 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
330 value_zat: Option<i64>,
331 #[serde(skip_serializing_if = "Option::is_none")]
333 address: Option<String>,
334 },
335}
336
337#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
339pub struct Output {
340 value: f64,
342 #[serde(rename = "valueZat")]
344 value_zat: i64,
345 n: u32,
347 #[serde(rename = "scriptPubKey")]
349 script_pub_key: ScriptPubKey,
350}
351
352#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
354pub struct ScriptPubKey {
355 asm: String,
358 #[serde(with = "hex")]
360 hex: Script,
361 #[serde(rename = "reqSigs")]
363 #[serde(default)]
364 #[serde(skip_serializing_if = "Option::is_none")]
365 #[getter(copy)]
366 req_sigs: Option<u32>,
367 r#type: String,
370 #[serde(default)]
372 #[serde(skip_serializing_if = "Option::is_none")]
373 addresses: Option<Vec<String>>,
374}
375
376#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
378pub struct ScriptSig {
379 asm: String,
382 hex: Script,
384}
385
386#[allow(clippy::too_many_arguments)]
388#[serde_with::serde_as]
389#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
390pub struct JoinSplit {
391 #[serde(rename = "vpub_old")]
393 old_public_value: f64,
394 #[serde(rename = "vpub_oldZat")]
396 old_public_value_zat: i64,
397 #[serde(rename = "vpub_new")]
399 new_public_value: f64,
400 #[serde(rename = "vpub_newZat")]
402 new_public_value_zat: i64,
403 #[serde(with = "hex")]
405 #[getter(copy)]
406 anchor: [u8; 32],
407 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
409 nullifiers: Vec<[u8; 32]>,
410 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
412 commitments: Vec<[u8; 32]>,
413 #[serde(rename = "onetimePubKey")]
415 #[serde(with = "hex")]
416 #[getter(copy)]
417 one_time_pubkey: [u8; 32],
418 #[serde(rename = "randomSeed")]
420 #[serde(with = "hex")]
421 #[getter(copy)]
422 random_seed: [u8; 32],
423 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
425 macs: Vec<[u8; 32]>,
426 #[serde(with = "hex")]
428 proof: Vec<u8>,
429 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
431 ciphertexts: Vec<Vec<u8>>,
432}
433
434#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
436pub struct ShieldedSpend {
437 #[serde(with = "hex")]
439 #[getter(skip)]
440 cv: ValueCommitment,
441 #[serde(with = "hex")]
443 #[getter(copy)]
444 anchor: [u8; 32],
445 #[serde(with = "hex")]
447 #[getter(copy)]
448 nullifier: [u8; 32],
449 #[serde(with = "hex")]
451 #[getter(copy)]
452 rk: [u8; 32],
453 #[serde(with = "hex")]
455 #[getter(copy)]
456 proof: [u8; 192],
457 #[serde(rename = "spendAuthSig", with = "hex")]
459 #[getter(copy)]
460 spend_auth_sig: [u8; 64],
461}
462
463impl ShieldedSpend {
465 pub fn cv(&self) -> ValueCommitment {
467 self.cv.clone()
468 }
469}
470
471#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
473pub struct ShieldedOutput {
474 #[serde(with = "hex")]
476 #[getter(skip)]
477 cv: ValueCommitment,
478 #[serde(rename = "cmu", with = "hex")]
480 cm_u: [u8; 32],
481 #[serde(rename = "ephemeralKey", with = "hex")]
483 ephemeral_key: [u8; 32],
484 #[serde(rename = "encCiphertext", with = "arrayhex")]
486 enc_ciphertext: [u8; 580],
487 #[serde(rename = "outCiphertext", with = "hex")]
489 out_ciphertext: [u8; 80],
490 #[serde(with = "hex")]
492 proof: [u8; 192],
493}
494
495impl ShieldedOutput {
497 pub fn cv(&self) -> ValueCommitment {
499 self.cv.clone()
500 }
501}
502
503#[serde_with::serde_as]
505#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
506pub struct Orchard {
507 actions: Vec<OrchardAction>,
509 #[serde(rename = "valueBalance")]
511 value_balance: f64,
512 #[serde(rename = "valueBalanceZat")]
514 value_balance_zat: i64,
515 #[serde(skip_serializing_if = "Option::is_none")]
517 flags: Option<OrchardFlags>,
518 #[serde_as(as = "Option<serde_with::hex::Hex>")]
520 #[serde(skip_serializing_if = "Option::is_none")]
521 #[getter(copy)]
522 anchor: Option<[u8; 32]>,
523 #[serde_as(as = "Option<serde_with::hex::Hex>")]
525 #[serde(skip_serializing_if = "Option::is_none")]
526 proof: Option<Vec<u8>>,
527 #[serde(rename = "bindingSig")]
529 #[serde(skip_serializing_if = "Option::is_none")]
530 #[serde_as(as = "Option<serde_with::hex::Hex>")]
531 #[getter(copy)]
532 binding_sig: Option<[u8; 64]>,
533}
534
535#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
537pub struct OrchardFlags {
538 #[serde(rename = "enableOutputs")]
540 enable_outputs: bool,
541 #[serde(rename = "enableSpends")]
543 enable_spends: bool,
544}
545
546#[allow(clippy::too_many_arguments)]
548#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
549pub struct OrchardAction {
550 #[serde(with = "hex")]
552 cv: [u8; 32],
553 #[serde(with = "hex")]
555 nullifier: [u8; 32],
556 #[serde(with = "hex")]
558 rk: [u8; 32],
559 #[serde(rename = "cmx", with = "hex")]
561 cm_x: [u8; 32],
562 #[serde(rename = "ephemeralKey", with = "hex")]
564 ephemeral_key: [u8; 32],
565 #[serde(rename = "encCiphertext", with = "arrayhex")]
567 enc_ciphertext: [u8; 580],
568 #[serde(rename = "spendAuthSig", with = "hex")]
570 spend_auth_sig: [u8; 64],
571 #[serde(rename = "outCiphertext", with = "hex")]
573 out_ciphertext: [u8; 80],
574}
575
576impl Default for TransactionObject {
577 fn default() -> Self {
578 Self {
579 hex: SerializedTransaction::from(
580 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
581 ),
582 height: Option::default(),
583 confirmations: Option::default(),
584 inputs: Vec::new(),
585 outputs: Vec::new(),
586 shielded_spends: Vec::new(),
587 shielded_outputs: Vec::new(),
588 joinsplits: Vec::new(),
589 orchard: None,
590 binding_sig: None,
591 joinsplit_pub_key: None,
592 joinsplit_sig: None,
593 value_balance: None,
594 value_balance_zat: None,
595 size: None,
596 time: None,
597 txid: transaction::Hash::from([0u8; 32]),
598 in_active_chain: None,
599 auth_digest: None,
600 overwintered: false,
601 version: 4,
602 version_group_id: None,
603 lock_time: 0,
604 expiry_height: None,
605 block_hash: None,
606 block_time: None,
607 }
608 }
609}
610
611impl TransactionObject {
612 #[allow(clippy::unwrap_in_result)]
614 #[allow(clippy::too_many_arguments)]
615 pub fn from_transaction(
616 tx: Arc<Transaction>,
617 height: Option<block::Height>,
618 confirmations: Option<u32>,
619 network: &Network,
620 block_time: Option<DateTime<Utc>>,
621 block_hash: Option<block::Hash>,
622 in_active_chain: Option<bool>,
623 txid: transaction::Hash,
624 ) -> Self {
625 let block_time = block_time.map(|bt| bt.timestamp());
626 Self {
627 hex: tx.clone().into(),
628 height: height.map(|height| height.0),
629 confirmations,
630 inputs: tx
631 .inputs()
632 .iter()
633 .map(|input| match input {
634 zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
635 coinbase: input
636 .coinbase_script()
637 .expect("we know it is a valid coinbase script"),
638 sequence: *sequence,
639 },
640 zebra_chain::transparent::Input::PrevOut {
641 sequence,
642 unlock_script,
643 outpoint,
644 } => Input::NonCoinbase {
645 txid: outpoint.hash.encode_hex(),
646 vout: outpoint.index,
647 script_sig: ScriptSig {
648 asm: "".to_string(),
649 hex: unlock_script.clone(),
650 },
651 sequence: *sequence,
652 value: None,
653 value_zat: None,
654 address: None,
655 },
656 })
657 .collect(),
658 outputs: tx
659 .outputs()
660 .iter()
661 .enumerate()
662 .map(|output| {
663 let (addresses, req_sigs) = output
665 .1
666 .address(network)
667 .map(|address| (vec![address.to_string()], 1))
668 .unzip();
669
670 Output {
671 value: Zec::from(output.1.value).lossy_zec(),
672 value_zat: output.1.value.zatoshis(),
673 n: output.0 as u32,
674 script_pub_key: ScriptPubKey {
675 asm: "".to_string(),
677 hex: output.1.lock_script.clone(),
678 req_sigs,
679 r#type: "".to_string(),
681 addresses,
682 },
683 }
684 })
685 .collect(),
686 shielded_spends: tx
687 .sapling_spends_per_anchor()
688 .map(|spend| {
689 let mut anchor = spend.per_spend_anchor.as_bytes();
690 anchor.reverse();
691
692 let mut nullifier = spend.nullifier.as_bytes();
693 nullifier.reverse();
694
695 let mut rk: [u8; 32] = spend.clone().rk.into();
696 rk.reverse();
697
698 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
699
700 ShieldedSpend {
701 cv: spend.cv.clone(),
702 anchor,
703 nullifier,
704 rk,
705 proof: spend.proof().0,
706 spend_auth_sig,
707 }
708 })
709 .collect(),
710 shielded_outputs: tx
711 .sapling_outputs()
712 .map(|output| {
713 let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
714 cm_u.reverse();
715 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
716 ephemeral_key.reverse();
717 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
718 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
719
720 ShieldedOutput {
721 cv: output.cv.clone(),
722 cm_u,
723 ephemeral_key,
724 enc_ciphertext,
725 out_ciphertext,
726 proof: output.proof().0,
727 }
728 })
729 .collect(),
730 joinsplits: tx
731 .sprout_joinsplits()
732 .map(|joinsplit| {
733 let mut ephemeral_key_bytes: [u8; 32] = joinsplit.ephemeral_key.to_bytes();
734 ephemeral_key_bytes.reverse();
735
736 JoinSplit {
737 old_public_value: Zec::from(joinsplit.vpub_old).lossy_zec(),
738 old_public_value_zat: joinsplit.vpub_old.zatoshis(),
739 new_public_value: Zec::from(joinsplit.vpub_new).lossy_zec(),
740 new_public_value_zat: joinsplit.vpub_new.zatoshis(),
741 anchor: joinsplit.anchor.bytes_in_display_order(),
742 nullifiers: joinsplit
743 .nullifiers
744 .iter()
745 .map(|n| n.bytes_in_display_order())
746 .collect(),
747 commitments: joinsplit
748 .commitments
749 .iter()
750 .map(|c| c.bytes_in_display_order())
751 .collect(),
752 one_time_pubkey: ephemeral_key_bytes,
753 random_seed: joinsplit.random_seed.bytes_in_display_order(),
754 macs: joinsplit
755 .vmacs
756 .iter()
757 .map(|m| m.bytes_in_display_order())
758 .collect(),
759 proof: joinsplit.zkproof.unwrap_or_default(),
760 ciphertexts: joinsplit
761 .enc_ciphertexts
762 .iter()
763 .map(|c| c.zcash_serialize_to_vec().unwrap_or_default())
764 .collect(),
765 }
766 })
767 .collect(),
768 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
769 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
770 orchard: Some(Orchard {
771 actions: tx
772 .orchard_actions()
773 .collect::<Vec<_>>()
774 .iter()
775 .map(|action| {
776 let spend_auth_sig: [u8; 64] = tx
777 .orchard_shielded_data()
778 .and_then(|shielded_data| {
779 shielded_data
780 .actions
781 .iter()
782 .find(|authorized_action| authorized_action.action == **action)
783 .map(|authorized_action| {
784 authorized_action.spend_auth_sig.into()
785 })
786 })
787 .unwrap_or([0; 64]);
788
789 let cv: [u8; 32] = action.cv.into();
790 let nullifier: [u8; 32] = action.nullifier.into();
791 let rk: [u8; 32] = action.rk.into();
792 let cm_x: [u8; 32] = action.cm_x.into();
793 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
794 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
795 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
796
797 OrchardAction {
798 cv,
799 nullifier,
800 rk,
801 cm_x,
802 ephemeral_key,
803 enc_ciphertext,
804 spend_auth_sig,
805 out_ciphertext,
806 }
807 })
808 .collect(),
809 value_balance: Zec::from(tx.orchard_value_balance().orchard_amount()).lossy_zec(),
810 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
811 flags: tx.orchard_shielded_data().map(|data| {
812 OrchardFlags::new(
813 data.flags.contains(orchard::Flags::ENABLE_OUTPUTS),
814 data.flags.contains(orchard::Flags::ENABLE_SPENDS),
815 )
816 }),
817 anchor: tx
818 .orchard_shielded_data()
819 .map(|data| data.shared_anchor.bytes_in_display_order()),
820 proof: tx
821 .orchard_shielded_data()
822 .map(|data| data.proof.bytes_in_display_order()),
823 binding_sig: tx
824 .orchard_shielded_data()
825 .map(|data| data.binding_sig.into()),
826 }),
827 binding_sig: tx.sapling_binding_sig().map(|raw_sig| raw_sig.into()),
828 joinsplit_pub_key: tx.joinsplit_pub_key().map(|raw_key| {
829 let mut key: [u8; 32] = raw_key.into();
831 key.reverse();
832 key
833 }),
834 joinsplit_sig: tx.joinsplit_sig().map(|raw_sig| raw_sig.into()),
835 size: tx.as_bytes().len().try_into().ok(),
836 time: block_time,
837 txid,
838 in_active_chain,
839 auth_digest: tx.auth_digest(),
840 overwintered: tx.is_overwintered(),
841 version: tx.version(),
842 version_group_id: tx.version_group_id().map(|id| id.to_be_bytes().to_vec()),
843 lock_time: tx.raw_lock_time(),
844 expiry_height: tx.expiry_height(),
845 block_hash,
846 block_time,
847 }
848 }
849}