zebra_chain/sapling/
spend.rs

1//! Sapling spends for `V4` and `V5` `Transaction`s.
2//!
3//! Zebra uses a generic spend type for `V4` and `V5` transactions.
4//! The anchor change is handled using the `AnchorVariant` type trait.
5
6use std::{fmt, io};
7
8use derive_getters::Getters;
9
10use crate::{
11    block::MAX_BLOCK_BYTES,
12    primitives::{redjubjub::SpendAuth, Groth16Proof},
13    serialization::{
14        ReadZcashExt, SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize,
15        ZcashDeserializeInto, ZcashSerialize,
16    },
17};
18
19use super::{
20    commitment, keys::ValidatingKey, note, tree, AnchorVariant, FieldNotPresent, PerSpendAnchor,
21    SharedAnchor,
22};
23
24/// A _Spend Description_, as described in [protocol specification ยง7.3][ps].
25///
26/// # Differences between Transaction Versions
27///
28/// In `Transaction::V4`, each `Spend` has its own anchor. In `Transaction::V5`,
29/// there is a single `shared_anchor` for the entire transaction. This
30/// structural difference is modeled using the `AnchorVariant` type trait.
31///
32/// `V4` transactions serialize the fields of spends and outputs together.
33/// `V5` transactions split them into multiple arrays.
34///
35/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding
36#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Getters)]
37pub struct Spend<AnchorV: AnchorVariant> {
38    /// A value commitment to the value of the input note.
39    pub cv: commitment::ValueCommitment,
40    /// An anchor for this spend.
41    ///
42    /// The anchor is the root of the Sapling note commitment tree in a previous
43    /// block. This root should be in the best chain for a transaction to be
44    /// mined, and it must be in the relevant chain for a transaction to be
45    /// valid.
46    ///
47    /// Some transaction versions have a shared anchor, rather than a per-spend
48    /// anchor.
49    pub per_spend_anchor: AnchorV::PerSpend,
50    /// The nullifier of the input note.
51    pub nullifier: note::Nullifier,
52    /// The randomized public key for `spend_auth_sig`.
53    pub rk: ValidatingKey,
54    /// The ZK spend proof.
55    pub zkproof: Groth16Proof,
56    /// A signature authorizing this spend.
57    pub spend_auth_sig: redjubjub::Signature<SpendAuth>,
58}
59
60/// The serialization prefix fields of a `Spend` in Transaction V5.
61///
62/// In `V5` transactions, spends are split into multiple arrays, so the prefix,
63/// proof, and signature must be serialised and deserialized separately.
64///
65/// Serialized as `SpendDescriptionV5` in [protocol specification ยง7.3][ps].
66///
67/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding
68#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
69pub struct SpendPrefixInTransactionV5 {
70    /// A value commitment to the value of the input note.
71    pub cv: commitment::ValueCommitment,
72    /// The nullifier of the input note.
73    pub nullifier: note::Nullifier,
74    /// The randomized public key for `spend_auth_sig`.
75    pub rk: ValidatingKey,
76}
77
78// We can't derive Eq because `VerificationKey` does not implement it,
79// even if it is valid for it.
80impl<AnchorV: AnchorVariant + PartialEq> Eq for Spend<AnchorV> {}
81
82// We can't derive Eq because `VerificationKey` does not implement it,
83// even if it is valid for it.
84impl Eq for SpendPrefixInTransactionV5 {}
85
86impl<AnchorV> fmt::Display for Spend<AnchorV>
87where
88    AnchorV: AnchorVariant + Clone,
89{
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        let mut fmter = f
92            .debug_struct(format!("sapling::Spend<{}>", std::any::type_name::<AnchorV>()).as_str());
93
94        fmter.field("per_spend_anchor", &self.per_spend_anchor);
95        fmter.field("nullifier", &self.nullifier);
96
97        fmter.finish()
98    }
99}
100
101impl From<(Spend<SharedAnchor>, tree::Root)> for Spend<PerSpendAnchor> {
102    /// Convert a `Spend<SharedAnchor>` and its shared anchor, into a
103    /// `Spend<PerSpendAnchor>`.
104    fn from(shared_spend: (Spend<SharedAnchor>, tree::Root)) -> Self {
105        Spend::<PerSpendAnchor> {
106            per_spend_anchor: shared_spend.1,
107            cv: shared_spend.0.cv,
108            nullifier: shared_spend.0.nullifier,
109            rk: shared_spend.0.rk,
110            zkproof: shared_spend.0.zkproof,
111            spend_auth_sig: shared_spend.0.spend_auth_sig,
112        }
113    }
114}
115
116impl From<(Spend<PerSpendAnchor>, FieldNotPresent)> for Spend<PerSpendAnchor> {
117    /// Take the `Spend<PerSpendAnchor>` from a spend + anchor tuple.
118    fn from(per_spend: (Spend<PerSpendAnchor>, FieldNotPresent)) -> Self {
119        per_spend.0
120    }
121}
122
123impl Spend<SharedAnchor> {
124    /// Combine the prefix and non-prefix fields from V5 transaction
125    /// deserialization.
126    pub fn from_v5_parts(
127        prefix: SpendPrefixInTransactionV5,
128        zkproof: Groth16Proof,
129        spend_auth_sig: redjubjub::Signature<SpendAuth>,
130    ) -> Spend<SharedAnchor> {
131        Spend::<SharedAnchor> {
132            cv: prefix.cv,
133            per_spend_anchor: FieldNotPresent,
134            nullifier: prefix.nullifier,
135            rk: prefix.rk,
136            zkproof,
137            spend_auth_sig,
138        }
139    }
140
141    /// Split out the prefix and non-prefix fields for V5 transaction
142    /// serialization.
143    pub fn into_v5_parts(
144        self,
145    ) -> (
146        SpendPrefixInTransactionV5,
147        Groth16Proof,
148        redjubjub::Signature<SpendAuth>,
149    ) {
150        let prefix = SpendPrefixInTransactionV5 {
151            cv: self.cv,
152            nullifier: self.nullifier,
153            rk: self.rk,
154        };
155
156        (prefix, self.zkproof, self.spend_auth_sig)
157    }
158}
159
160impl ZcashSerialize for Spend<PerSpendAnchor> {
161    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
162        writer.write_all(&self.cv.0.to_bytes())?;
163        self.per_spend_anchor.zcash_serialize(&mut writer)?;
164        writer.write_32_bytes(&self.nullifier.into())?;
165        writer.write_all(&<[u8; 32]>::from(self.rk.clone())[..])?;
166        self.zkproof.zcash_serialize(&mut writer)?;
167        writer.write_all(&<[u8; 64]>::from(self.spend_auth_sig)[..])?;
168        Ok(())
169    }
170}
171
172impl ZcashDeserialize for Spend<PerSpendAnchor> {
173    /// # Consensus
174    ///
175    /// > The anchor of each Spend description MUST refer to some earlier
176    /// > blockโ€™s final Sapling treestate. The anchor is encoded separately in
177    /// > each Spend description for v4 transactions, or encoded once and shared
178    /// > between all Spend descriptions in a v5 transaction.
179    ///
180    /// <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
181    ///
182    /// This rule is also implemented in
183    /// `zebra_state::service::check::anchors` and
184    /// `crate::transaction::serialize`.
185    ///
186    /// The "anchor encoding for v4 transactions" is implemented here.
187    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
188        // # Consensus
189        //
190        // > Elements of a Spend description MUST be valid encodings of the types given above.
191        //
192        // https://zips.z.cash/protocol/protocol.pdf#spenddesc
193        //
194        // See comments below for each specific type.
195        //
196        // > LEOS2IP_{256}(anchorSapling), if present, MUST be less than ๐‘ž_๐•.
197        //
198        // https://zips.z.cash/protocol/protocol.pdf#spendencodingandconsensus
199        //
200        // Applies to `per_spend_anchor` below; validated in
201        // [`crate::sapling::tree::Root::zcash_deserialize`].
202        Ok(Spend {
203            // Type is `ValueCommit^{Sapling}.Output`, i.e. J
204            // https://zips.z.cash/protocol/protocol.pdf#abstractcommit
205            // See [`sapling_crypto::value::ValueCommitment::::zcash_deserialize`].
206            cv: commitment::ValueCommitment(
207                sapling_crypto::value::ValueCommitment::zcash_deserialize(&mut reader)?,
208            ),
209            // Type is `B^{[โ„“_{Sapling}_{Merkle}]}`, i.e. 32 bytes.
210            // But as mentioned above, we validate it further as an integer.
211            per_spend_anchor: (&mut reader).zcash_deserialize_into()?,
212            // Type is `B^Y^{[โ„“_{PRFnfSapling}/8]}`, i.e. 32 bytes
213            nullifier: note::Nullifier::from(reader.read_32_bytes()?),
214            // Type is `SpendAuthSig^{Sapling}.Public`, i.e. J
215            // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
216            // See [`ValidatingKey::try_from`].
217            rk: reader
218                .read_32_bytes()?
219                .try_into()
220                .map_err(SerializationError::Parse)?,
221            // Type is `ZKSpend.Proof`, described in
222            // https://zips.z.cash/protocol/protocol.pdf#grothencoding
223            // It is not enforced here; this just reads 192 bytes.
224            // The type is validated when validating the proof, see
225            // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
226            zkproof: Groth16Proof::zcash_deserialize(&mut reader)?,
227            // Type is SpendAuthSig^{Sapling}.Signature, i.e.
228            // B^Y^{[ceiling(โ„“_G/8) + ceiling(bitlength(๐‘Ÿ_G)/8)]} i.e. 64 bytes
229            // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
230            spend_auth_sig: reader.read_64_bytes()?.into(),
231        })
232    }
233}
234
235// zkproof and spend_auth_sig are deserialized separately, so we can only
236// deserialize Spend<SharedAnchor> in the context of a V5 transaction.
237//
238// Instead, implement serialization and deserialization for the
239// Spend<SharedAnchor> prefix fields, which are stored in the same array.
240
241impl ZcashSerialize for SpendPrefixInTransactionV5 {
242    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
243        writer.write_all(&self.cv.0.to_bytes())?;
244        writer.write_32_bytes(&self.nullifier.into())?;
245        writer.write_all(&<[u8; 32]>::from(self.rk.clone())[..])?;
246        Ok(())
247    }
248}
249
250impl ZcashDeserialize for SpendPrefixInTransactionV5 {
251    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
252        // # Consensus
253        //
254        // > Elements of a Spend description MUST be valid encodings of the types given above.
255        //
256        // https://zips.z.cash/protocol/protocol.pdf#spenddesc
257        //
258        // See comments below for each specific type.
259        Ok(SpendPrefixInTransactionV5 {
260            // Type is `ValueCommit^{Sapling}.Output`, i.e. J
261            // https://zips.z.cash/protocol/protocol.pdf#abstractcommit
262            // See [`sapling_crypto::value::ValueCommitment::zcash_deserialize`].
263            cv: commitment::ValueCommitment::zcash_deserialize(&mut reader)?,
264            // Type is `B^Y^{[โ„“_{PRFnfSapling}/8]}`, i.e. 32 bytes
265            nullifier: note::Nullifier::from(reader.read_32_bytes()?),
266            // Type is `SpendAuthSig^{Sapling}.Public`, i.e. J
267            // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
268            // See [`ValidatingKey::try_from`].
269            rk: reader
270                .read_32_bytes()?
271                .try_into()
272                .map_err(SerializationError::Parse)?,
273        })
274    }
275}
276
277/// In Transaction V5, SpendAuth signatures are serialized and deserialized in a
278/// separate array.
279impl ZcashSerialize for redjubjub::Signature<SpendAuth> {
280    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
281        writer.write_all(&<[u8; 64]>::from(*self)[..])?;
282        Ok(())
283    }
284}
285
286impl ZcashDeserialize for redjubjub::Signature<SpendAuth> {
287    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
288        Ok(reader.read_64_bytes()?.into())
289    }
290}
291
292/// The size of a spend with a per-spend anchor.
293pub(crate) const ANCHOR_PER_SPEND_SIZE: u64 = SHARED_ANCHOR_SPEND_SIZE + 32;
294
295/// The size of a spend with a shared anchor, without associated fields.
296///
297/// This is the size of spends in the initial array, there are another
298/// 2 arrays of zkproofs and spend_auth_sigs required in the transaction format.
299pub(crate) const SHARED_ANCHOR_SPEND_PREFIX_SIZE: u64 = 32 + 32 + 32;
300/// The size of a spend with a shared anchor, including associated fields.
301///
302/// A Spend contains: a 32 byte cv, a 32 byte anchor (transaction V4 only),
303/// a 32 byte nullifier, a 32 byte rk, a 192 byte zkproof (serialized separately
304/// in V5), and a 64 byte spendAuthSig (serialized separately in V5).
305///
306/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding
307pub(crate) const SHARED_ANCHOR_SPEND_SIZE: u64 = SHARED_ANCHOR_SPEND_PREFIX_SIZE + 192 + 64;
308
309/// The maximum number of sapling spends in a valid Zcash on-chain transaction V4.
310impl TrustedPreallocate for Spend<PerSpendAnchor> {
311    fn max_allocation() -> u64 {
312        const MAX: u64 = (MAX_BLOCK_BYTES - 1) / ANCHOR_PER_SPEND_SIZE;
313        // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
314        // https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
315        // This acts as nSpendsSapling and is therefore subject to the rule.
316        // The maximum value is actually smaller due to the block size limit,
317        // but we ensure the 2^16 limit with a static assertion.
318        // (The check is not required pre-NU5, but it doesn't cause problems.)
319        static_assertions::const_assert!(MAX < (1 << 16));
320        MAX
321    }
322}
323
324/// The maximum number of sapling spends in a valid Zcash on-chain transaction V5.
325///
326/// If a transaction contains more spends than can fit in maximally large block, it might be
327/// valid on the network and in the mempool, but it can never be mined into a block. So
328/// rejecting these large edge-case transactions can never break consensus.
329impl TrustedPreallocate for SpendPrefixInTransactionV5 {
330    fn max_allocation() -> u64 {
331        // Since a serialized Vec<Spend> uses at least one byte for its length,
332        // and the associated fields are required,
333        // a valid max allocation can never exceed this size
334        const MAX: u64 = (MAX_BLOCK_BYTES - 1) / SHARED_ANCHOR_SPEND_SIZE;
335        // # Consensus
336        //
337        // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
338        //
339        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
340        //
341        // This acts as nSpendsSapling and is therefore subject to the rule.
342        // The maximum value is actually smaller due to the block size limit,
343        // but we ensure the 2^16 limit with a static assertion.
344        // (The check is not required pre-NU5, but it doesn't cause problems.)
345        static_assertions::const_assert!(MAX < (1 << 16));
346        MAX
347    }
348}
349
350impl TrustedPreallocate for redjubjub::Signature<SpendAuth> {
351    fn max_allocation() -> u64 {
352        // Each associated field must have a corresponding spend prefix.
353        SpendPrefixInTransactionV5::max_allocation()
354    }
355}