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