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}