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