zebra_chain/transaction/joinsplit.rs
1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 amount::{self, Amount, NegativeAllowed},
7 fmt::HexDebug,
8 primitives::{ed25519, ZkSnarkProof},
9 sprout::{self, JoinSplit, Nullifier},
10};
11
12/// A bundle of [`JoinSplit`] descriptions and signature data.
13///
14/// JoinSplit descriptions are optional, but Zcash transactions must include a
15/// JoinSplit signature and verification key if and only if there is at least one
16/// JoinSplit description. This wrapper type bundles at least one JoinSplit
17/// description with the required signature data, so that an
18/// `Option<JoinSplitData>` correctly models the presence or absence of any
19/// JoinSplit data.
20#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct JoinSplitData<P: ZkSnarkProof> {
22 /// The first JoinSplit description in the transaction,
23 /// using proofs of type `P`.
24 ///
25 /// Storing this separately from `rest` ensures that it is impossible
26 /// to construct an invalid `JoinSplitData` with no `JoinSplit`s.
27 ///
28 /// However, it's not necessary to access or process `first` and `rest`
29 /// separately, as the [`JoinSplitData::joinsplits`] method provides an
30 /// iterator over all of the `JoinSplit`s.
31 #[serde(bound(
32 serialize = "JoinSplit<P>: Serialize",
33 deserialize = "JoinSplit<P>: Deserialize<'de>"
34 ))]
35 pub first: JoinSplit<P>,
36 /// The rest of the JoinSplit descriptions, using proofs of type `P`,
37 /// in the order they appear in the transaction.
38 ///
39 /// The [`JoinSplitData::joinsplits`] method provides an iterator over
40 /// all `JoinSplit`s.
41 #[serde(bound(
42 serialize = "JoinSplit<P>: Serialize",
43 deserialize = "JoinSplit<P>: Deserialize<'de>"
44 ))]
45 pub rest: Vec<JoinSplit<P>>,
46 /// The public key for the JoinSplit signature, denoted as `joinSplitPubKey` in the spec.
47 pub pub_key: ed25519::VerificationKeyBytes,
48 /// The JoinSplit signature, denoted as `joinSplitSig` in the spec.
49 pub sig: ed25519::Signature,
50}
51
52impl<P: ZkSnarkProof> fmt::Debug for JoinSplitData<P> {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.debug_struct("JoinSplitData")
55 .field("first", &self.first)
56 .field("rest", &self.rest)
57 .field("pub_key", &self.pub_key)
58 .field("sig", &HexDebug(&self.sig.to_bytes()))
59 .finish()
60 }
61}
62
63impl<P: ZkSnarkProof> fmt::Display for JoinSplitData<P> {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let mut fmter =
66 f.debug_struct(format!("JoinSplitData<{}>", std::any::type_name::<P>()).as_str());
67
68 fmter.field("joinsplits", &self.joinsplits().count());
69 fmter.field("value_balance", &self.value_balance());
70
71 fmter.finish()
72 }
73}
74
75impl<P: ZkSnarkProof> JoinSplitData<P> {
76 /// Iterate over the [`JoinSplit`]s in `self`, in the order they appear
77 /// in the transaction.
78 pub fn joinsplits(&self) -> impl Iterator<Item = &JoinSplit<P>> {
79 std::iter::once(&self.first).chain(self.rest.iter())
80 }
81
82 /// Modify the [`JoinSplit`]s in `self`,
83 /// in the order they appear in the transaction.
84 #[cfg(any(test, feature = "proptest-impl"))]
85 pub fn joinsplits_mut(&mut self) -> impl Iterator<Item = &mut JoinSplit<P>> {
86 std::iter::once(&mut self.first).chain(self.rest.iter_mut())
87 }
88
89 /// Iterate over the [`Nullifier`]s in `self`.
90 pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
91 self.joinsplits()
92 .flat_map(|joinsplit| joinsplit.nullifiers.iter())
93 }
94
95 /// Return the sprout value balance,
96 /// the change in the transaction value pool due to sprout [`JoinSplit`]s.
97 ///
98 /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
99 ///
100 /// See [`sprout_value_balance`][svb] for details.
101 ///
102 /// [svb]: crate::transaction::Transaction::sprout_value_balance
103 pub fn value_balance(&self) -> Result<Amount<NegativeAllowed>, amount::Error> {
104 self.joinsplit_value_balances().sum()
105 }
106
107 /// Return a list of sprout value balances,
108 /// the changes in the transaction value pool due to each sprout [`JoinSplit`].
109 ///
110 /// See [`sprout_value_balance`][svb] for details.
111 ///
112 /// [svb]: crate::transaction::Transaction::sprout_value_balance
113 pub fn joinsplit_value_balances(
114 &self,
115 ) -> Box<dyn Iterator<Item = Amount<NegativeAllowed>> + '_> {
116 Box::new(self.joinsplits().map(JoinSplit::value_balance))
117 }
118
119 /// Collect the Sprout note commitments for this transaction, if it contains `Output`s,
120 /// in the order they appear in the transaction.
121 pub fn note_commitments(&self) -> impl Iterator<Item = &sprout::commitment::NoteCommitment> {
122 self.joinsplits()
123 .flat_map(|joinsplit| &joinsplit.commitments)
124 }
125}