zebra_chain/sapling/
shielded_data.rs

1//! Sapling shielded data for `V4` and `V5` `Transaction`s.
2//!
3//! Zebra uses a generic shielded data type for `V4` and `V5` transactions.
4//! The `value_balance` change is handled using the default zero value.
5//! The anchor change is handled using the `AnchorVariant` type trait.
6
7use std::{
8    cmp::{max, Eq, PartialEq},
9    fmt::{self, Debug},
10};
11
12use derive_getters::Getters;
13use itertools::Itertools;
14#[cfg(any(test, feature = "proptest-impl"))]
15use proptest_derive::Arbitrary;
16use serde::{de::DeserializeOwned, Serialize};
17
18use crate::{
19    amount::Amount,
20    primitives::{
21        redjubjub::{Binding, Signature},
22        Groth16Proof,
23    },
24    sapling::{
25        output::OutputPrefixInTransactionV5, spend::SpendPrefixInTransactionV5, tree, Nullifier,
26        Output, Spend, ValueCommitment,
27    },
28    serialization::{AtLeastOne, TrustedPreallocate},
29};
30
31/// Per-Spend Sapling anchors, used in Transaction V4 and the
32/// `spends_per_anchor` method.
33#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
34pub struct PerSpendAnchor {}
35
36/// Shared Sapling anchors, used in Transaction V5.
37#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
38pub struct SharedAnchor {}
39
40/// This field is not present in this transaction version.
41#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
42#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
43pub struct FieldNotPresent;
44
45impl AnchorVariant for PerSpendAnchor {
46    type Shared = FieldNotPresent;
47    type PerSpend = tree::Root;
48}
49
50impl AnchorVariant for SharedAnchor {
51    type Shared = tree::Root;
52    type PerSpend = FieldNotPresent;
53}
54
55/// A type trait to handle structural differences between V4 and V5 Sapling
56/// Transaction anchors.
57///
58/// In Transaction V4, anchors are per-Spend. In Transaction V5, there is a
59/// single transaction anchor for all Spends in a transaction.
60pub trait AnchorVariant {
61    /// The type of the shared anchor.
62    type Shared: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
63
64    /// The type of the per-spend anchor.
65    type PerSpend: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
66}
67
68/// A bundle of [`Spend`] and [`Output`] descriptions and signature data.
69///
70/// Spend and Output descriptions are optional, but Zcash transactions must
71/// include a binding signature if and only if there is at least one Spend *or*
72/// Output description. This wrapper type bundles at least one Spend or Output
73/// description with the required signature data, so that an
74/// `Option<ShieldedData>` correctly models the presence or absence of any
75/// shielded data.
76///
77/// # Differences between Transaction Versions
78///
79/// The Sapling `value_balance` field is optional in `Transaction::V5`, but
80/// required in `Transaction::V4`. In both cases, if there is no `ShieldedData`,
81/// then the field value must be zero. Therefore, we only need to store
82/// `value_balance` when there is some Sapling `ShieldedData`.
83///
84/// In `Transaction::V4`, each `Spend` has its own anchor. In `Transaction::V5`,
85/// there is a single `shared_anchor` for the entire transaction, which is only
86/// present when there is at least one spend. These structural differences are
87/// modeled using the `AnchorVariant` type trait and `TransferData` enum.
88#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Getters)]
89pub struct ShieldedData<AnchorV>
90where
91    AnchorV: AnchorVariant + Clone,
92{
93    /// The net value of Sapling spend transfers minus output transfers.
94    /// Denoted as `valueBalanceSapling` in the spec.
95    pub value_balance: Amount,
96
97    /// A bundle of spends and outputs, containing at least one spend or
98    /// output, in the order they appear in the transaction.
99    ///
100    /// In V5 transactions, also contains a shared anchor, if there are any
101    /// spends.
102    pub transfers: TransferData<AnchorV>,
103
104    /// A signature on the transaction hash.
105    /// Denoted as `bindingSigSapling` in the spec.
106    pub binding_sig: Signature<Binding>,
107}
108
109/// A bundle of [`Spend`] and [`Output`] descriptions, and a shared anchor.
110///
111/// This wrapper type bundles at least one Spend or Output description with
112/// the required anchor data, so that an `Option<ShieldedData>` (which contains
113/// this type) correctly models the presence or absence of any spends and
114/// shielded data, across both V4 and V5 transactions.
115///
116/// Specifically, TransferData ensures that:
117/// * there is at least one spend or output, and
118/// * the shared anchor is only present when there are spends.
119#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
120pub enum TransferData<AnchorV>
121where
122    AnchorV: AnchorVariant + Clone,
123{
124    /// A bundle containing at least one spend, and the shared spend anchor.
125    /// There can also be zero or more outputs.
126    ///
127    /// In Transaction::V5, if there are any spends, there must also be a shared
128    /// spend anchor.
129    SpendsAndMaybeOutputs {
130        /// The shared anchor for all `Spend`s in this transaction.
131        ///
132        /// The anchor is the root of the Sapling note commitment tree in a previous
133        /// block. This root should be in the best chain for a transaction to be
134        /// mined, and it must be in the relevant chain for a transaction to be
135        /// valid.
136        ///
137        /// Some transaction versions have a per-spend anchor, rather than a shared
138        /// anchor.
139        ///
140        /// Use the `shared_anchor` method to access this field.
141        shared_anchor: AnchorV::Shared,
142
143        /// At least one spend.
144        ///
145        /// Use the [`ShieldedData::spends`] method to get an iterator over the
146        /// [`Spend`]s in this `TransferData`.
147        spends: AtLeastOne<Spend<AnchorV>>,
148
149        /// Maybe some outputs (can be empty), in the order they appear in the
150        /// transaction.
151        ///
152        /// Use the [`ShieldedData::outputs`] method to get an iterator over the
153        /// [`Output`]s in this `TransferData`.
154        maybe_outputs: Vec<Output>,
155    },
156
157    /// A bundle containing at least one output, with no spends and no shared
158    /// spend anchor.
159    ///
160    /// In Transaction::V5, if there are no spends, there must not be a shared
161    /// anchor.
162    JustOutputs {
163        /// At least one output, in the order they appear in the transaction.
164        ///
165        /// Use the [`ShieldedData::outputs`] method to get an iterator over the
166        /// [`Output`]s in this `TransferData`.
167        outputs: AtLeastOne<Output>,
168    },
169}
170
171impl<AnchorV> fmt::Display for ShieldedData<AnchorV>
172where
173    AnchorV: AnchorVariant + Clone,
174{
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        let mut fmter = f.debug_struct(
177            format!(
178                "sapling::ShieldedData<{}>",
179                std::any::type_name::<AnchorV>()
180            )
181            .as_str(),
182        );
183
184        fmter.field("spends", &self.transfers.spends().count());
185        fmter.field("outputs", &self.transfers.outputs().count());
186        fmter.field("value_balance", &self.value_balance);
187
188        fmter.finish()
189    }
190}
191
192impl<AnchorV> ShieldedData<AnchorV>
193where
194    AnchorV: AnchorVariant + Clone,
195    Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
196{
197    /// Iterate over the [`Spend`]s for this transaction, returning deduplicated
198    /// [`tree::Root`]s, regardless of the underlying transaction version.
199    pub fn anchors(&self) -> impl Iterator<Item = tree::Root> + '_ {
200        // TODO: use TransferData::shared_anchor to improve performance for V5 transactions
201        self.spends_per_anchor()
202            .map(|spend| spend.per_spend_anchor)
203            .unique_by(|raw| raw.0.to_bytes())
204    }
205
206    /// Iterate over the [`Spend`]s for this transaction, returning
207    /// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
208    ///
209    /// # Correctness
210    ///
211    /// Do not use this function for serialization.
212    pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
213        self.transfers.spends_per_anchor()
214    }
215}
216
217impl<AnchorV> ShieldedData<AnchorV>
218where
219    AnchorV: AnchorVariant + Clone,
220{
221    /// Iterate over the [`Spend`]s for this transaction, returning them as
222    /// their generic type.
223    ///
224    /// # Correctness
225    ///
226    /// Use this function for serialization.
227    pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
228        self.transfers.spends()
229    }
230
231    /// Iterate over the [`Output`]s for this transaction, in the order they
232    /// appear in it.
233    pub fn outputs(&self) -> impl Iterator<Item = &Output> {
234        self.transfers.outputs()
235    }
236
237    /// Provide the shared anchor for this transaction, if present.
238    ///
239    /// The shared anchor is only present if:
240    /// * there is at least one spend, and
241    /// * this is a `V5` transaction.
242    pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
243        self.transfers.shared_anchor()
244    }
245
246    /// Collect the [`Nullifier`]s for this transaction, if it contains
247    /// [`Spend`]s.
248    pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
249        self.spends().map(|spend| &spend.nullifier)
250    }
251
252    /// Collect the cm_u's for this transaction, if it contains [`Output`]s,
253    /// in the order they appear in the transaction.
254    pub fn note_commitments(&self) -> impl Iterator<Item = &jubjub::Fq> {
255        self.outputs().map(|output| &output.cm_u)
256    }
257
258    /// Calculate the Spend/Output binding verification key.
259    ///
260    /// Getting the binding signature validating key from the Spend and Output
261    /// description value commitments and the balancing value implicitly checks
262    /// that the balancing value is consistent with the value transferred in the
263    /// Spend and Output descriptions but also proves that the signer knew the
264    /// randomness used for the Spend and Output value commitments, which
265    /// prevents replays of Output descriptions.
266    ///
267    /// The net value of Spend transfers minus Output transfers in a transaction
268    /// is called the balancing value, measured in zatoshi as a signed integer
269    /// v_balance.
270    ///
271    /// Consistency of v_balance with the value commitments in Spend
272    /// descriptions and Output descriptions is enforced by the binding
273    /// signature.
274    ///
275    /// Instead of generating a key pair at random, we generate it as a function
276    /// of the value commitments in the Spend descriptions and Output
277    /// descriptions of the transaction, and the balancing value.
278    ///
279    /// <https://zips.z.cash/protocol/protocol.pdf#saplingbalance>
280    pub fn binding_verification_key(&self) -> redjubjub::VerificationKeyBytes<Binding> {
281        let cv_old: ValueCommitment = self.spends().map(|spend| spend.cv.into()).sum();
282        let cv_new: ValueCommitment = self.outputs().map(|output| output.cv.into()).sum();
283        let cv_balance: ValueCommitment =
284            ValueCommitment::new(jubjub::Fr::zero(), self.value_balance);
285
286        let key_bytes: [u8; 32] = (cv_old - cv_new - cv_balance).into();
287
288        key_bytes.into()
289    }
290}
291
292impl<AnchorV> TransferData<AnchorV>
293where
294    AnchorV: AnchorVariant + Clone,
295    Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
296{
297    /// Iterate over the [`Spend`]s for this transaction, returning
298    /// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
299    ///
300    /// Allows generic operations over V4 and V5 transactions, including:
301    /// * spend verification, and
302    /// * non-malleable transaction ID generation.
303    ///
304    /// # Correctness
305    ///
306    /// Do not use this function for serialization.
307    pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
308        self.spends().cloned().map(move |spend| {
309            Spend::<PerSpendAnchor>::from((
310                spend,
311                self.shared_anchor()
312                    .expect("shared anchor must be Some if there are any spends"),
313            ))
314        })
315    }
316}
317
318impl<AnchorV> TransferData<AnchorV>
319where
320    AnchorV: AnchorVariant + Clone,
321{
322    /// Iterate over the [`Spend`]s for this transaction, returning them as
323    /// their generic type.
324    pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
325        use TransferData::*;
326
327        let spends = match self {
328            SpendsAndMaybeOutputs { spends, .. } => Some(spends.iter()),
329            JustOutputs { .. } => None,
330        };
331
332        // this awkward construction avoids returning a newtype struct or
333        // type-erased boxed iterator
334        spends.into_iter().flatten()
335    }
336
337    /// Iterate over the [`Output`]s for this transaction.
338    pub fn outputs(&self) -> impl Iterator<Item = &Output> {
339        use TransferData::*;
340
341        match self {
342            SpendsAndMaybeOutputs { maybe_outputs, .. } => maybe_outputs,
343            JustOutputs { outputs, .. } => outputs.as_vec(),
344        }
345        .iter()
346    }
347
348    /// Provide the shared anchor for this transaction, if present.
349    ///
350    /// The shared anchor is only present if:
351    /// * there is at least one spend, and
352    /// * this is a `V5` transaction.
353    pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
354        use TransferData::*;
355
356        match self {
357            SpendsAndMaybeOutputs { shared_anchor, .. } => Some(shared_anchor.clone()),
358            JustOutputs { .. } => None,
359        }
360    }
361}
362
363impl TrustedPreallocate for Groth16Proof {
364    fn max_allocation() -> u64 {
365        // Each V5 transaction proof array entry must have a corresponding
366        // spend or output prefix. We use the larger limit, so we don't reject
367        // any valid large blocks.
368        //
369        // TODO: put a separate limit on proofs in spends and outputs
370        max(
371            SpendPrefixInTransactionV5::max_allocation(),
372            OutputPrefixInTransactionV5::max_allocation(),
373        )
374    }
375}