zebra_chain/transaction/serialize.rs
1//! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the
2//! transaction types, so that all of the serialization logic is in one place.
3
4use std::{borrow::Borrow, io, sync::Arc};
5
6use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
7use halo2::pasta::group::ff::PrimeField;
8use hex::FromHex;
9use reddsa::{orchard::Binding, orchard::SpendAuth, Signature};
10
11use crate::{
12 amount,
13 block::MAX_BLOCK_BYTES,
14 parameters::{OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID},
15 primitives::{Halo2Proof, ZkSnarkProof},
16 serialization::{
17 zcash_deserialize_external_count, zcash_serialize_empty_list,
18 zcash_serialize_external_count, AtLeastOne, CompactSizeMessage, ReadZcashExt,
19 SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashDeserializeInto,
20 ZcashSerialize,
21 },
22};
23
24use crate::parameters::TX_V6_VERSION_GROUP_ID;
25
26use super::*;
27use crate::sapling;
28
29impl ZcashDeserialize for jubjub::Fq {
30 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
31 let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
32
33 if possible_scalar.is_some().into() {
34 Ok(possible_scalar.unwrap())
35 } else {
36 Err(SerializationError::Parse(
37 "Invalid jubjub::Fq, input not canonical",
38 ))
39 }
40 }
41}
42
43impl ZcashDeserialize for pallas::Scalar {
44 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
45 let possible_scalar = pallas::Scalar::from_repr(reader.read_32_bytes()?);
46
47 if possible_scalar.is_some().into() {
48 Ok(possible_scalar.unwrap())
49 } else {
50 Err(SerializationError::Parse(
51 "Invalid pallas::Scalar, input not canonical",
52 ))
53 }
54 }
55}
56
57impl ZcashDeserialize for pallas::Base {
58 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
59 let possible_field_element = pallas::Base::from_repr(reader.read_32_bytes()?);
60
61 if possible_field_element.is_some().into() {
62 Ok(possible_field_element.unwrap())
63 } else {
64 Err(SerializationError::Parse(
65 "Invalid pallas::Base, input not canonical",
66 ))
67 }
68 }
69}
70
71impl<P: ZkSnarkProof> ZcashSerialize for JoinSplitData<P> {
72 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
73 // Denoted as `nJoinSplit` and `vJoinSplit` in the spec.
74 let joinsplits: Vec<_> = self.joinsplits().cloned().collect();
75 joinsplits.zcash_serialize(&mut writer)?;
76
77 // Denoted as `joinSplitPubKey` in the spec.
78 writer.write_all(&<[u8; 32]>::from(self.pub_key)[..])?;
79
80 // Denoted as `joinSplitSig` in the spec.
81 writer.write_all(&<[u8; 64]>::from(self.sig)[..])?;
82 Ok(())
83 }
84}
85
86impl<P> ZcashDeserialize for Option<JoinSplitData<P>>
87where
88 P: ZkSnarkProof,
89 sprout::JoinSplit<P>: TrustedPreallocate,
90{
91 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
92 // Denoted as `nJoinSplit` and `vJoinSplit` in the spec.
93 let joinsplits: Vec<sprout::JoinSplit<P>> = (&mut reader).zcash_deserialize_into()?;
94 match joinsplits.split_first() {
95 None => Ok(None),
96 Some((first, rest)) => {
97 // Denoted as `joinSplitPubKey` in the spec.
98 let pub_key = reader.read_32_bytes()?.into();
99 // Denoted as `joinSplitSig` in the spec.
100 let sig = reader.read_64_bytes()?.into();
101 Ok(Some(JoinSplitData {
102 first: first.clone(),
103 rest: rest.to_vec(),
104 pub_key,
105 sig,
106 }))
107 }
108 }
109 }
110}
111
112// Transaction::V5 serializes sapling ShieldedData in a single continuous byte
113// range, so we can implement its serialization and deserialization separately.
114// (Unlike V4, where it must be serialized as part of the transaction.)
115
116impl ZcashSerialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
117 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
118 match self {
119 None => {
120 // Denoted as `nSpendsSapling` in the spec.
121 zcash_serialize_empty_list(&mut writer)?;
122 // Denoted as `nOutputsSapling` in the spec.
123 zcash_serialize_empty_list(&mut writer)?;
124 }
125 Some(sapling_shielded_data) => {
126 sapling_shielded_data.zcash_serialize(&mut writer)?;
127 }
128 }
129 Ok(())
130 }
131}
132
133impl ZcashSerialize for sapling::ShieldedData<sapling::SharedAnchor> {
134 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
135 // Collect arrays for Spends
136 // There's no unzip3, so we have to unzip twice.
137 let (spend_prefixes, spend_proofs_sigs): (Vec<_>, Vec<_>) = self
138 .spends()
139 .cloned()
140 .map(sapling::Spend::<sapling::SharedAnchor>::into_v5_parts)
141 .map(|(prefix, proof, sig)| (prefix, (proof, sig)))
142 .unzip();
143 let (spend_proofs, spend_sigs) = spend_proofs_sigs.into_iter().unzip();
144
145 // Collect arrays for Outputs
146 let (output_prefixes, output_proofs): (Vec<_>, _) = self
147 .outputs()
148 .cloned()
149 .map(sapling::Output::into_v5_parts)
150 .unzip();
151
152 // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
153 spend_prefixes.zcash_serialize(&mut writer)?;
154 // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
155 output_prefixes.zcash_serialize(&mut writer)?;
156
157 // Denoted as `valueBalanceSapling` in the spec.
158 self.value_balance.zcash_serialize(&mut writer)?;
159
160 // Denoted as `anchorSapling` in the spec.
161 // `TransferData` ensures this field is only present when there is at
162 // least one spend.
163 if let Some(shared_anchor) = self.shared_anchor() {
164 writer.write_all(&<[u8; 32]>::from(shared_anchor)[..])?;
165 }
166
167 // Denoted as `vSpendProofsSapling` in the spec.
168 zcash_serialize_external_count(&spend_proofs, &mut writer)?;
169 // Denoted as `vSpendAuthSigsSapling` in the spec.
170 zcash_serialize_external_count(&spend_sigs, &mut writer)?;
171
172 // Denoted as `vOutputProofsSapling` in the spec.
173 zcash_serialize_external_count(&output_proofs, &mut writer)?;
174
175 // Denoted as `bindingSigSapling` in the spec.
176 writer.write_all(&<[u8; 64]>::from(self.binding_sig)[..])?;
177
178 Ok(())
179 }
180}
181
182// we can't split ShieldedData out of Option<ShieldedData> deserialization,
183// because the counts are read along with the arrays.
184impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
185 #[allow(clippy::unwrap_in_result)]
186 fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
187 deserialize_v5_sapling_shielded_data(reader, false)
188 }
189}
190
191/// Deserialize V5/V6 Sapling shielded data with an optional early coinbase
192/// rejection.
193///
194/// When `is_coinbase` is true, a non-zero `nSpendsSapling` count is rejected
195/// **before** allocating the spend vector, closing the late-validation gap
196/// described in GHSA-rgwx-8r98-p34c.
197#[allow(clippy::unwrap_in_result)]
198fn deserialize_v5_sapling_shielded_data<R: io::Read>(
199 mut reader: R,
200 is_coinbase: bool,
201) -> Result<Option<sapling::ShieldedData<sapling::SharedAnchor>>, SerializationError> {
202 // Denoted as `nSpendsSapling` in the spec โ read count before allocating.
203 let spend_count: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
204 let spend_count: usize = spend_count.into();
205
206 // # Consensus
207 //
208 // > A coinbase transaction MUST NOT have any Spend descriptions.
209 //
210 // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
211 //
212 // Reject before allocating to prevent a peer from forcing thousands of
213 // spend-prefix allocations for a transaction that will always be invalid.
214 if is_coinbase && spend_count > 0 {
215 return Err(SerializationError::Parse(
216 "coinbase transaction must not have Sapling spends",
217 ));
218 }
219
220 // Denoted as `vSpendsSapling` in the spec.
221 let spend_prefixes: Vec<sapling::SpendPrefixInTransactionV5> =
222 zcash_deserialize_external_count(spend_count, &mut reader)?;
223
224 // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
225 let output_prefixes: Vec<_> = (&mut reader).zcash_deserialize_into()?;
226
227 // nSpendsSapling and nOutputsSapling as variables
228 let spends_count = spend_prefixes.len();
229 let outputs_count = output_prefixes.len();
230
231 // All the other fields depend on having spends or outputs
232 if spend_prefixes.is_empty() && output_prefixes.is_empty() {
233 return Ok(None);
234 }
235
236 // Denoted as `valueBalanceSapling` in the spec.
237 let value_balance = (&mut reader).zcash_deserialize_into()?;
238
239 // Denoted as `anchorSapling` in the spec.
240 //
241 // # Consensus
242 //
243 // > Elements of a Spend description MUST be valid encodings of the types given above.
244 //
245 // https://zips.z.cash/protocol/protocol.pdf#spenddesc
246 //
247 // Type is `B^{[โ_{Sapling}_{Merkle}]}`, i.e. 32 bytes
248 //
249 // > LEOS2IP_{256}(anchorSapling), if present, MUST be less than ๐_๐.
250 //
251 // https://zips.z.cash/protocol/protocol.pdf#spendencodingandconsensus
252 //
253 // Validated in [`crate::sapling::tree::Root::zcash_deserialize`].
254 let shared_anchor = if spends_count > 0 {
255 Some((&mut reader).zcash_deserialize_into()?)
256 } else {
257 None
258 };
259
260 // Denoted as `vSpendProofsSapling` in the spec.
261 //
262 // # Consensus
263 //
264 // > Elements of a Spend description MUST be valid encodings of the types given above.
265 //
266 // https://zips.z.cash/protocol/protocol.pdf#spenddesc
267 //
268 // Type is `ZKSpend.Proof`, described in
269 // https://zips.z.cash/protocol/protocol.pdf#grothencoding
270 // It is not enforced here; this just reads 192 bytes.
271 // The type is validated when validating the proof, see
272 // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
273 let spend_proofs = zcash_deserialize_external_count(spends_count, &mut reader)?;
274
275 // Denoted as `vSpendAuthSigsSapling` in the spec.
276 //
277 // # Consensus
278 //
279 // > Elements of a Spend description MUST be valid encodings of the types given above.
280 //
281 // https://zips.z.cash/protocol/protocol.pdf#spenddesc
282 //
283 // Type is SpendAuthSig^{Sapling}.Signature, i.e.
284 // B^Y^{[ceiling(โ_G/8) + ceiling(bitlength(๐_G)/8)]} i.e. 64 bytes
285 // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
286 // See [`redjubjub::Signature<SpendAuth>::zcash_deserialize`].
287 let spend_sigs = zcash_deserialize_external_count(spends_count, &mut reader)?;
288
289 // Denoted as `vOutputProofsSapling` in the spec.
290 //
291 // # Consensus
292 //
293 // > Elements of an Output description MUST be valid encodings of the types given above.
294 //
295 // https://zips.z.cash/protocol/protocol.pdf#outputdesc
296 //
297 // Type is `ZKOutput.Proof`, described in
298 // https://zips.z.cash/protocol/protocol.pdf#grothencoding
299 // It is not enforced here; this just reads 192 bytes.
300 // The type is validated when validating the proof, see
301 // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
302 let output_proofs = zcash_deserialize_external_count(outputs_count, &mut reader)?;
303
304 // Denoted as `bindingSigSapling` in the spec.
305 let binding_sig = reader.read_64_bytes()?.into();
306
307 // Create shielded spends from deserialized parts
308 let spends: Vec<_> = spend_prefixes
309 .into_iter()
310 .zip(spend_proofs)
311 .zip(spend_sigs)
312 .map(|((prefix, proof), sig)| {
313 sapling::Spend::<sapling::SharedAnchor>::from_v5_parts(prefix, proof, sig)
314 })
315 .collect();
316
317 // Create shielded outputs from deserialized parts
318 let outputs = output_prefixes
319 .into_iter()
320 .zip(output_proofs)
321 .map(|(prefix, proof)| sapling::Output::from_v5_parts(prefix, proof))
322 .collect();
323
324 // Create transfers
325 //
326 // # Consensus
327 //
328 // > The anchor of each Spend description MUST refer to some earlier
329 // > blockโs final Sapling treestate. The anchor is encoded separately
330 // > in each Spend description for v4 transactions, or encoded once and
331 // > shared between all Spend descriptions in a v5 transaction.
332 //
333 // <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
334 //
335 // This rule is also implemented in
336 // [`zebra_state::service::check::anchor`] and
337 // [`zebra_chain::sapling::spend`].
338 //
339 // The "anchor encoding for v5 transactions" is implemented here.
340 let transfers = match shared_anchor {
341 Some(shared_anchor) => sapling::TransferData::SpendsAndMaybeOutputs {
342 shared_anchor,
343 spends: spends
344 .try_into()
345 .expect("checked spends when parsing shared anchor"),
346 maybe_outputs: outputs,
347 },
348 None => sapling::TransferData::JustOutputs {
349 outputs: outputs
350 .try_into()
351 .expect("checked spends or outputs and returned early"),
352 },
353 };
354
355 Ok(Some(sapling::ShieldedData {
356 value_balance,
357 transfers,
358 binding_sig,
359 }))
360}
361
362/// Serializes an optional Orchard-protocol bundle (v5 Orchard, v6 Orchard, or Ironwood).
363///
364/// All three share the same wire encoding: an empty action list (`nActions = 0`) when the bundle is
365/// absent, otherwise the bundle's fields.
366///
367/// "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard, proofsOrchard,
368/// and bindingSigOrchard are present if and only if nActionsOrchard > 0." โ `ยง` note of the second
369/// table of <https://zips.z.cash/protocol/protocol.pdf#txnencoding>
370fn zcash_serialize_optional_orchard_bundle<W: io::Write>(
371 shielded_data: Option<&orchard::ShieldedData>,
372 mut writer: W,
373) -> Result<(), io::Error> {
374 match shielded_data {
375 // Denoted as `nActionsOrchard` in the spec.
376 None => zcash_serialize_empty_list(&mut writer),
377 Some(shielded_data) => shielded_data.zcash_serialize(&mut writer),
378 }
379}
380
381impl ZcashSerialize for Option<orchard::ShieldedData> {
382 fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
383 zcash_serialize_optional_orchard_bundle(self.as_ref(), writer)
384 }
385}
386
387impl ZcashSerialize for orchard::ShieldedData {
388 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
389 // Split the AuthorizedAction
390 let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
391 .actions
392 .iter()
393 .cloned()
394 .map(orchard::AuthorizedAction::into_parts)
395 .unzip();
396
397 // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
398 actions.zcash_serialize(&mut writer)?;
399
400 // Denoted as `flagsOrchard` in the spec.
401 self.flags.zcash_serialize(&mut writer)?;
402
403 // Denoted as `valueBalanceOrchard` in the spec.
404 self.value_balance.zcash_serialize(&mut writer)?;
405
406 // Denoted as `anchorOrchard` in the spec.
407 self.shared_anchor.zcash_serialize(&mut writer)?;
408
409 // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
410 self.proof.zcash_serialize(&mut writer)?;
411
412 // Denoted as `vSpendAuthSigsOrchard` in the spec.
413 zcash_serialize_external_count(&sigs, &mut writer)?;
414
415 // Denoted as `bindingSigOrchard` in the spec.
416 self.binding_sig.zcash_serialize(&mut writer)?;
417
418 Ok(())
419 }
420}
421
422/// The `flagsOrchard` codec a v6 Orchard-protocol bundle newtype uses on deserialization.
423///
424/// A v6 Orchard or Ironwood bundle encodes identically on the wire to a v5 Orchard bundle (the flag
425/// byte is written as-is); the pools differ only in the reserved-bit rule applied to `flagsOrchard`.
426/// The `enableCrossAddress` bit (bit 2) is permitted only for the Ironwood pool, and is reserved
427/// (MUST be 0) for the Orchard pool regardless of tx version โ matching
428/// `orchard::bundle::Flags::from_byte`, which rejects bit 2 for `ValuePool::Orchard`. Tying the
429/// codec to the bundle type lets the (de)serializers below imply it instead of naming it explicitly.
430trait V6FlagCodec {
431 /// The flag codec: `orchard::Flags` reserves bit 2, `orchard::FlagsV6` permits it.
432 type Codec: ZcashDeserialize + Into<orchard::Flags>;
433}
434
435impl V6FlagCodec for orchard::ShieldedDataV6 {
436 // The v6 Orchard bundle parses with the pre-NU6.3 codec, exactly like v5.
437 type Codec = orchard::Flags;
438}
439
440impl V6FlagCodec for ironwood::ShieldedData {
441 // Only the Ironwood bundle permits `enableCrossAddress`.
442 type Codec = orchard::FlagsV6;
443}
444
445/// Deserializes the shared Orchard-protocol bundle body of a v6 bundle newtype `T`, using the flag
446/// codec [implied by `T`](V6FlagCodec) rather than one named at the call site.
447fn deserialize_v6_orchard_shielded_data<R, T>(
448 reader: R,
449) -> Result<Option<orchard::ShieldedData>, SerializationError>
450where
451 R: io::Read,
452 T: V6FlagCodec,
453{
454 deserialize_orchard_shielded_data::<R, T::Codec>(reader)
455}
456
457impl ZcashDeserialize for Option<orchard::ShieldedDataV6> {
458 fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
459 Ok(
460 deserialize_v6_orchard_shielded_data::<R, orchard::ShieldedDataV6>(reader)?
461 .map(orchard::ShieldedDataV6::new),
462 )
463 }
464}
465
466impl ZcashSerialize for Option<orchard::ShieldedDataV6> {
467 fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
468 zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
469 }
470}
471
472impl ZcashDeserialize for Option<ironwood::ShieldedData> {
473 fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
474 Ok(
475 deserialize_v6_orchard_shielded_data::<R, ironwood::ShieldedData>(reader)?
476 .map(orchard::ShieldedDataV6::new)
477 .map(ironwood::ShieldedData::new),
478 )
479 }
480}
481
482impl ZcashSerialize for Option<ironwood::ShieldedData> {
483 fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
484 zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
485 }
486}
487
488// we can't split ShieldedData out of Option<ShieldedData> deserialization,
489// because the counts are read along with the arrays.
490impl ZcashDeserialize for Option<orchard::ShieldedData> {
491 fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
492 // The bare `Option<orchard::ShieldedData>` codec is the v5 Orchard bundle, which uses the
493 // pre-NU6.3 flag-byte format (`orchard::Flags`). v6 Orchard and Ironwood bundles use the
494 // `orchard::FlagsV6` newtype.
495 deserialize_orchard_shielded_data::<R, orchard::Flags>(reader)
496 }
497}
498
499/// Deserializes an `Option<orchard::ShieldedData>`, parsing the flags byte via the flag type `F`.
500///
501/// v5 Orchard bundles pass `F = orchard::Flags` (pre-NU6.3 format); v6 Orchard and Ironwood bundles
502/// pass `F = orchard::FlagsV6` (the NU6.3 format, which permits the `enableCrossAddress` flag).
503pub(crate) fn deserialize_orchard_shielded_data<R, F>(
504 mut reader: R,
505) -> Result<Option<orchard::ShieldedData>, SerializationError>
506where
507 R: io::Read,
508 F: ZcashDeserialize + Into<orchard::Flags>,
509{
510 // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
511 let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
512
513 // "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
514 // proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
515 // `ยง` note of the second table of https://zips.z.cash/protocol/protocol.pdf#txnencoding
516 if actions.is_empty() {
517 return Ok(None);
518 }
519
520 // # Consensus
521 //
522 // > Elements of an Action description MUST be canonical encodings of the types given above.
523 //
524 // https://zips.z.cash/protocol/protocol.pdf#actiondesc
525 //
526 // Some Action elements are validated in this function; they are described below.
527
528 // Denoted as `flagsOrchard` in the spec. The flag type `F` selects the reserved-bit rule
529 // (pre-NU6.3 reserves bits 2..7; NU6.3 reserves bits 3..7).
530 let flags: orchard::Flags = F::zcash_deserialize(&mut reader)?.into();
531
532 // Denoted as `valueBalanceOrchard` in the spec.
533 let value_balance: amount::Amount = (&mut reader).zcash_deserialize_into()?;
534
535 // Denoted as `anchorOrchard` in the spec.
536 // Consensus: type is `{0 .. ๐_โ โ 1}`. See [`orchard::tree::Root::zcash_deserialize`].
537 let shared_anchor: orchard::tree::Root = (&mut reader).zcash_deserialize_into()?;
538
539 // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
540 // Consensus: type is `ZKAction.Proof`, i.e. a byte sequence.
541 // https://zips.z.cash/protocol/protocol.pdf#halo2encoding
542 let proof: Halo2Proof = (&mut reader).zcash_deserialize_into()?;
543
544 // Denoted as `vSpendAuthSigsOrchard` in the spec.
545 // Consensus: this validates the `spendAuthSig` elements, whose type is
546 // SpendAuthSig^{Orchard}.Signature, i.e.
547 // B^Y^{[ceiling(โ_G/8) + ceiling(bitlength(๐_G)/8)]} i.e. 64 bytes
548 // See [`Signature::zcash_deserialize`].
549 let sigs: Vec<Signature<SpendAuth>> =
550 zcash_deserialize_external_count(actions.len(), &mut reader)?;
551
552 // Denoted as `bindingSigOrchard` in the spec.
553 let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
554
555 // Create the AuthorizedAction from deserialized parts
556 let authorized_actions: Vec<orchard::AuthorizedAction> = actions
557 .into_iter()
558 .zip(sigs)
559 .map(|(action, spend_auth_sig)| {
560 orchard::AuthorizedAction::from_parts(action, spend_auth_sig)
561 })
562 .collect();
563
564 let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
565
566 Ok(Some(orchard::ShieldedData {
567 flags,
568 value_balance,
569 shared_anchor,
570 proof,
571 actions,
572 binding_sig,
573 }))
574}
575
576impl<T: reddsa::SigType> ZcashSerialize for reddsa::Signature<T> {
577 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
578 writer.write_all(&<[u8; 64]>::from(*self)[..])?;
579 Ok(())
580 }
581}
582
583impl<T: reddsa::SigType> ZcashDeserialize for reddsa::Signature<T> {
584 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
585 Ok(reader.read_64_bytes()?.into())
586 }
587}
588
589impl ZcashSerialize for Transaction {
590 #[allow(clippy::unwrap_in_result)]
591 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
592 // Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES.
593 // (Strictly, the maximum transaction size is about 1.5 kB less,
594 // because blocks also include a block header.)
595 //
596 // Currently, all transaction structs are parsed as part of a
597 // block. So we don't need to check transaction size here, until
598 // we start parsing mempool transactions, or generating our own
599 // transactions (see #483).
600 //
601 // Since we checkpoint on Canopy activation, we won't ever need
602 // to check the smaller pre-Sapling transaction size limit.
603
604 // Denoted as `header` in the spec, contains the `fOverwintered` flag and the `version` field.
605 // Write `version` and set the `fOverwintered` bit if necessary
606 let overwintered_flag = if self.is_overwintered() { 1 << 31 } else { 0 };
607 let version = overwintered_flag | self.version();
608
609 writer.write_u32::<LittleEndian>(version)?;
610
611 match self {
612 Transaction::V1 {
613 inputs,
614 outputs,
615 lock_time,
616 } => {
617 // Denoted as `tx_in_count` and `tx_in` in the spec.
618 inputs.zcash_serialize(&mut writer)?;
619
620 // Denoted as `tx_out_count` and `tx_out` in the spec.
621 outputs.zcash_serialize(&mut writer)?;
622
623 // Denoted as `lock_time` in the spec.
624 lock_time.zcash_serialize(&mut writer)?;
625 }
626 Transaction::V2 {
627 inputs,
628 outputs,
629 lock_time,
630 joinsplit_data,
631 } => {
632 // Denoted as `tx_in_count` and `tx_in` in the spec.
633 inputs.zcash_serialize(&mut writer)?;
634
635 // Denoted as `tx_out_count` and `tx_out` in the spec.
636 outputs.zcash_serialize(&mut writer)?;
637
638 // Denoted as `lock_time` in the spec.
639 lock_time.zcash_serialize(&mut writer)?;
640
641 // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
642 // `joinSplitPubKey` and `joinSplitSig`.
643 match joinsplit_data {
644 // Write 0 for nJoinSplits to signal no JoinSplitData.
645 None => zcash_serialize_empty_list(writer)?,
646 Some(jsd) => jsd.zcash_serialize(&mut writer)?,
647 }
648 }
649 Transaction::V3 {
650 inputs,
651 outputs,
652 lock_time,
653 expiry_height,
654 joinsplit_data,
655 } => {
656 // Denoted as `nVersionGroupId` in the spec.
657 writer.write_u32::<LittleEndian>(OVERWINTER_VERSION_GROUP_ID)?;
658
659 // Denoted as `tx_in_count` and `tx_in` in the spec.
660 inputs.zcash_serialize(&mut writer)?;
661
662 // Denoted as `tx_out_count` and `tx_out` in the spec.
663 outputs.zcash_serialize(&mut writer)?;
664
665 // Denoted as `lock_time` in the spec.
666 lock_time.zcash_serialize(&mut writer)?;
667
668 writer.write_u32::<LittleEndian>(expiry_height.0)?;
669
670 // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
671 // `joinSplitPubKey` and `joinSplitSig`.
672 match joinsplit_data {
673 // Write 0 for nJoinSplits to signal no JoinSplitData.
674 None => zcash_serialize_empty_list(writer)?,
675 Some(jsd) => jsd.zcash_serialize(&mut writer)?,
676 }
677 }
678 Transaction::V4 {
679 inputs,
680 outputs,
681 lock_time,
682 expiry_height,
683 sapling_shielded_data,
684 joinsplit_data,
685 } => {
686 // Transaction V4 spec:
687 // https://zips.z.cash/protocol/protocol.pdf#txnencoding
688
689 // Denoted as `nVersionGroupId` in the spec.
690 writer.write_u32::<LittleEndian>(SAPLING_VERSION_GROUP_ID)?;
691
692 // Denoted as `tx_in_count` and `tx_in` in the spec.
693 inputs.zcash_serialize(&mut writer)?;
694
695 // Denoted as `tx_out_count` and `tx_out` in the spec.
696 outputs.zcash_serialize(&mut writer)?;
697
698 // Denoted as `lock_time` in the spec.
699 lock_time.zcash_serialize(&mut writer)?;
700
701 // Denoted as `nExpiryHeight` in the spec.
702 writer.write_u32::<LittleEndian>(expiry_height.0)?;
703
704 // The previous match arms serialize in one go, because the
705 // internal structure happens to nicely line up with the
706 // serialized structure. However, this is not possible for
707 // version 4 transactions, as the binding_sig for the
708 // ShieldedData is placed at the end of the transaction. So
709 // instead we have to interleave serialization of the
710 // ShieldedData and the JoinSplitData.
711
712 match sapling_shielded_data {
713 None => {
714 // Signal no value balance.
715 writer.write_i64::<LittleEndian>(0)?;
716 // Signal no shielded spends and no shielded outputs.
717 zcash_serialize_empty_list(&mut writer)?;
718 zcash_serialize_empty_list(&mut writer)?;
719 }
720 Some(sapling_shielded_data) => {
721 // Denoted as `valueBalanceSapling` in the spec.
722 sapling_shielded_data
723 .value_balance
724 .zcash_serialize(&mut writer)?;
725
726 // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
727 let spends: Vec<_> = sapling_shielded_data.spends().cloned().collect();
728 spends.zcash_serialize(&mut writer)?;
729
730 // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
731 let outputs: Vec<_> = sapling_shielded_data
732 .outputs()
733 .cloned()
734 .map(sapling::OutputInTransactionV4)
735 .collect();
736 outputs.zcash_serialize(&mut writer)?;
737 }
738 }
739
740 // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
741 // `joinSplitPubKey` and `joinSplitSig`.
742 match joinsplit_data {
743 None => zcash_serialize_empty_list(&mut writer)?,
744 Some(jsd) => jsd.zcash_serialize(&mut writer)?,
745 }
746
747 // Denoted as `bindingSigSapling` in the spec.
748 if let Some(shielded_data) = sapling_shielded_data {
749 writer.write_all(&<[u8; 64]>::from(shielded_data.binding_sig)[..])?;
750 }
751 }
752
753 Transaction::V5 {
754 network_upgrade,
755 lock_time,
756 expiry_height,
757 inputs,
758 outputs,
759 sapling_shielded_data,
760 orchard_shielded_data,
761 } => {
762 // Transaction V5 spec:
763 // https://zips.z.cash/protocol/protocol.pdf#txnencoding
764
765 // Denoted as `nVersionGroupId` in the spec.
766 writer.write_u32::<LittleEndian>(TX_V5_VERSION_GROUP_ID)?;
767
768 // Denoted as `nConsensusBranchId` in the spec.
769 writer.write_u32::<LittleEndian>(u32::from(
770 network_upgrade
771 .branch_id()
772 .expect("valid transactions must have a network upgrade with a branch id"),
773 ))?;
774
775 // Denoted as `lock_time` in the spec.
776 lock_time.zcash_serialize(&mut writer)?;
777
778 // Denoted as `nExpiryHeight` in the spec.
779 writer.write_u32::<LittleEndian>(expiry_height.0)?;
780
781 // Denoted as `tx_in_count` and `tx_in` in the spec.
782 inputs.zcash_serialize(&mut writer)?;
783
784 // Denoted as `tx_out_count` and `tx_out` in the spec.
785 outputs.zcash_serialize(&mut writer)?;
786
787 // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
788 // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
789 // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
790 // `bindingSigSapling`.
791 sapling_shielded_data.zcash_serialize(&mut writer)?;
792
793 // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
794 // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
795 // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
796 orchard_shielded_data.zcash_serialize(&mut writer)?;
797 }
798
799 Transaction::V6 {
800 network_upgrade,
801 lock_time,
802 expiry_height,
803 inputs,
804 outputs,
805 sapling_shielded_data,
806 orchard_shielded_data,
807 ironwood_shielded_data,
808 } => {
809 // Transaction V6 (Ironwood / NU6.3) spec:
810 // https://github.com/zcash/zips/pull/1301
811
812 // Denoted as `nVersionGroupId` in the spec.
813 writer.write_u32::<LittleEndian>(TX_V6_VERSION_GROUP_ID)?;
814
815 // Denoted as `nConsensusBranchId` in the spec.
816 writer.write_u32::<LittleEndian>(u32::from(
817 network_upgrade
818 .branch_id()
819 .expect("valid transactions must have a network upgrade with a branch id"),
820 ))?;
821
822 // Denoted as `lock_time` in the spec.
823 lock_time.zcash_serialize(&mut writer)?;
824
825 // Denoted as `nExpiryHeight` in the spec.
826 writer.write_u32::<LittleEndian>(expiry_height.0)?;
827
828 // Denoted as `tx_in_count` and `tx_in` in the spec.
829 inputs.zcash_serialize(&mut writer)?;
830
831 // Denoted as `tx_out_count` and `tx_out` in the spec.
832 outputs.zcash_serialize(&mut writer)?;
833
834 // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
835 // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
836 // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
837 // `bindingSigSapling`.
838 sapling_shielded_data.zcash_serialize(&mut writer)?;
839
840 // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
841 // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
842 // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
843 orchard_shielded_data.zcash_serialize(&mut writer)?;
844
845 // The Ironwood bundle: the same field layout as the Orchard bundle above, denoted
846 // `nActionsIronwood`, `vActionsIronwood`, `flagsIronwood`, `valueBalanceIronwood`,
847 // `anchorIronwood`, `sizeProofsIronwood`, `proofsIronwood`, `vSpendAuthSigsIronwood`,
848 // and `bindingSigIronwood`.
849 ironwood_shielded_data.zcash_serialize(&mut writer)?;
850 }
851 }
852 Ok(())
853 }
854}
855
856impl ZcashDeserialize for Transaction {
857 #[allow(clippy::unwrap_in_result)]
858 fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
859 // # Consensus
860 //
861 // > [Pre-Sapling] The encoded size of the transaction MUST be less than or
862 // > equal to 100000 bytes.
863 //
864 // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
865 //
866 // Zebra does not verify this rule because we checkpoint up to Canopy blocks, but:
867 // Since transactions must get mined into a block to be useful,
868 // we reject transactions that are larger than blocks.
869 //
870 // If the limit is reached, we'll get an UnexpectedEof error.
871 let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
872
873 let (version, overwintered) = {
874 const LOW_31_BITS: u32 = (1 << 31) - 1;
875 // Denoted as `header` in the spec, contains the `fOverwintered` flag and the `version` field.
876 let header = limited_reader.read_u32::<LittleEndian>()?;
877 (header & LOW_31_BITS, header >> 31 != 0)
878 };
879
880 // # Consensus
881 //
882 // The next rules apply for different transaction versions as follows:
883 //
884 // [Pre-Overwinter]: Transactions version 1 and 2.
885 // [Overwinter onward]: Transactions version 3 and above.
886 // [Overwinter only, pre-Sapling]: Transactions version 3.
887 // [Sapling to Canopy inclusive, pre-NU5]: Transactions version 4.
888 // [NU5 onward]: Transactions version 4 and above.
889 //
890 // > The transaction version number MUST be greater than or equal to 1.
891 //
892 // > [Pre-Overwinter] The fOverwintered fag MUST NOT be set.
893 //
894 // > [Overwinter onward] The version group ID MUST be recognized.
895 //
896 // > [Overwinter onward] The fOverwintered flag MUST be set.
897 //
898 // > [Overwinter only, pre-Sapling] The transaction version number MUST be 3,
899 // > and the version group ID MUST be 0x03C48270.
900 //
901 // > [Sapling to Canopy inclusive, pre-NU5] The transaction version number MUST be 4,
902 // > and the version group ID MUST be 0x892F2085.
903 //
904 // > [NU5 onward] The transaction version number MUST be 4 or 5.
905 // > If the transaction version number is 4 then the version group ID MUST be 0x892F2085.
906 // > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
907 //
908 // Note: Zebra checkpoints until Canopy blocks, this means only transactions versions
909 // 4 and 5 get fully verified. This satisfies "The transaction version number MUST be 4"
910 // and "The transaction version number MUST be 4 or 5" from the last two rules above.
911 // This is done in the zebra-consensus crate, in the transactions checks.
912 //
913 // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
914 match (version, overwintered) {
915 (1, false) => Ok(Transaction::V1 {
916 // Denoted as `tx_in_count` and `tx_in` in the spec.
917 inputs: Vec::zcash_deserialize(&mut limited_reader)?,
918 // Denoted as `tx_out_count` and `tx_out` in the spec.
919 outputs: Vec::zcash_deserialize(&mut limited_reader)?,
920 // Denoted as `lock_time` in the spec.
921 lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
922 }),
923 (2, false) => {
924 // Version 2 transactions use Sprout-on-BCTV14.
925 type OptV2Jsd = Option<JoinSplitData<Bctv14Proof>>;
926 Ok(Transaction::V2 {
927 // Denoted as `tx_in_count` and `tx_in` in the spec.
928 inputs: Vec::zcash_deserialize(&mut limited_reader)?,
929 // Denoted as `tx_out_count` and `tx_out` in the spec.
930 outputs: Vec::zcash_deserialize(&mut limited_reader)?,
931 // Denoted as `lock_time` in the spec.
932 lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
933 // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
934 // `joinSplitPubKey` and `joinSplitSig`.
935 joinsplit_data: OptV2Jsd::zcash_deserialize(&mut limited_reader)?,
936 })
937 }
938 (3, true) => {
939 // Denoted as `nVersionGroupId` in the spec.
940 let id = limited_reader.read_u32::<LittleEndian>()?;
941 if id != OVERWINTER_VERSION_GROUP_ID {
942 return Err(SerializationError::Parse(
943 "expected OVERWINTER_VERSION_GROUP_ID",
944 ));
945 }
946 // Version 3 transactions use Sprout-on-BCTV14.
947 type OptV3Jsd = Option<JoinSplitData<Bctv14Proof>>;
948 Ok(Transaction::V3 {
949 // Denoted as `tx_in_count` and `tx_in` in the spec.
950 inputs: Vec::zcash_deserialize(&mut limited_reader)?,
951 // Denoted as `tx_out_count` and `tx_out` in the spec.
952 outputs: Vec::zcash_deserialize(&mut limited_reader)?,
953 // Denoted as `lock_time` in the spec.
954 lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
955 // Denoted as `nExpiryHeight` in the spec.
956 expiry_height: block::Height(limited_reader.read_u32::<LittleEndian>()?),
957 // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
958 // `joinSplitPubKey` and `joinSplitSig`.
959 joinsplit_data: OptV3Jsd::zcash_deserialize(&mut limited_reader)?,
960 })
961 }
962 (4, true) => {
963 // Transaction V4 spec:
964 // https://zips.z.cash/protocol/protocol.pdf#txnencoding
965
966 // Denoted as `nVersionGroupId` in the spec.
967 let id = limited_reader.read_u32::<LittleEndian>()?;
968 if id != SAPLING_VERSION_GROUP_ID {
969 return Err(SerializationError::Parse(
970 "expected SAPLING_VERSION_GROUP_ID",
971 ));
972 }
973 // Version 4 transactions use Sprout-on-Groth16.
974 type OptV4Jsd = Option<JoinSplitData<Groth16Proof>>;
975
976 // The previous match arms deserialize in one go, because the
977 // internal structure happens to nicely line up with the
978 // serialized structure. However, this is not possible for
979 // version 4 transactions, as the binding_sig for the
980 // ShieldedData is placed at the end of the transaction. So
981 // instead we have to pull the component parts out manually and
982 // then assemble them.
983
984 // Denoted as `tx_in_count` and `tx_in` in the spec.
985 let inputs: Vec<transparent::Input> = Vec::zcash_deserialize(&mut limited_reader)?;
986
987 // Denoted as `tx_out_count` and `tx_out` in the spec.
988 let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
989
990 let is_coinbase = inputs.len() == 1
991 && matches!(inputs.first(), Some(transparent::Input::Coinbase { .. }));
992
993 // Denoted as `lock_time` in the spec.
994 let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
995
996 // Denoted as `nExpiryHeight` in the spec.
997 let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
998
999 // Denoted as `valueBalanceSapling` in the spec.
1000 let value_balance = (&mut limited_reader).zcash_deserialize_into()?;
1001
1002 // Denoted as `nSpendsSapling` โ read count before allocating.
1003 let spend_count: CompactSizeMessage =
1004 (&mut limited_reader).zcash_deserialize_into()?;
1005 let spend_count: usize = spend_count.into();
1006
1007 // # Consensus
1008 //
1009 // > A coinbase transaction MUST NOT have any Spend descriptions.
1010 //
1011 // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
1012 if is_coinbase && spend_count > 0 {
1013 return Err(SerializationError::Parse(
1014 "coinbase transaction must not have Sapling spends",
1015 ));
1016 }
1017
1018 // Denoted as `vSpendsSapling` in the spec.
1019 let shielded_spends: Vec<sapling::Spend<sapling::PerSpendAnchor>> =
1020 zcash_deserialize_external_count(spend_count, &mut limited_reader)?;
1021
1022 // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
1023 let shielded_outputs =
1024 Vec::<sapling::OutputInTransactionV4>::zcash_deserialize(&mut limited_reader)?
1025 .into_iter()
1026 .map(sapling::Output::from_v4)
1027 .collect();
1028
1029 // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
1030 // `joinSplitPubKey` and `joinSplitSig`.
1031 let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut limited_reader)?;
1032
1033 let sapling_transfers = if !shielded_spends.is_empty() {
1034 Some(sapling::TransferData::SpendsAndMaybeOutputs {
1035 shared_anchor: FieldNotPresent,
1036 spends: shielded_spends.try_into().expect("checked for spends"),
1037 maybe_outputs: shielded_outputs,
1038 })
1039 } else if !shielded_outputs.is_empty() {
1040 Some(sapling::TransferData::JustOutputs {
1041 outputs: shielded_outputs.try_into().expect("checked for outputs"),
1042 })
1043 } else {
1044 // # Consensus
1045 //
1046 // > [Sapling onward] If effectiveVersion = 4 and there are no Spend
1047 // > descriptions or Output descriptions, then valueBalanceSapling MUST be 0.
1048 //
1049 // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
1050 if value_balance != 0 {
1051 return Err(SerializationError::BadTransactionBalance);
1052 }
1053 None
1054 };
1055
1056 let sapling_shielded_data = match sapling_transfers {
1057 Some(transfers) => Some(sapling::ShieldedData {
1058 value_balance,
1059 transfers,
1060 // Denoted as `bindingSigSapling` in the spec.
1061 binding_sig: limited_reader.read_64_bytes()?.into(),
1062 }),
1063 None => None,
1064 };
1065
1066 Ok(Transaction::V4 {
1067 inputs,
1068 outputs,
1069 lock_time,
1070 expiry_height,
1071 sapling_shielded_data,
1072 joinsplit_data,
1073 })
1074 }
1075 (5, true) => {
1076 // Transaction V5 spec:
1077 // https://zips.z.cash/protocol/protocol.pdf#txnencoding
1078
1079 // Denoted as `nVersionGroupId` in the spec.
1080 let id = limited_reader.read_u32::<LittleEndian>()?;
1081 if id != TX_V5_VERSION_GROUP_ID {
1082 return Err(SerializationError::Parse("expected TX_V5_VERSION_GROUP_ID"));
1083 }
1084 // Denoted as `nConsensusBranchId` in the spec.
1085 // Convert it to a NetworkUpgrade
1086 let network_upgrade =
1087 NetworkUpgrade::try_from(limited_reader.read_u32::<LittleEndian>()?)?;
1088
1089 // # Consensus
1090 //
1091 // > [NU5 onward] The transaction version number MUST be 4 or 5.
1092 //
1093 // V5 transactions are only valid from NU5 onward, so reject
1094 // transactions with pre-NU5 consensus branch IDs.
1095 if network_upgrade < NetworkUpgrade::Nu5 {
1096 return Err(SerializationError::Parse(
1097 "v5 transaction must have NU5 or later consensus branch ID",
1098 ));
1099 }
1100
1101 // Denoted as `lock_time` in the spec.
1102 let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
1103
1104 // Denoted as `nExpiryHeight` in the spec.
1105 let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
1106
1107 // Denoted as `tx_in_count` and `tx_in` in the spec.
1108 let inputs: Vec<transparent::Input> = Vec::zcash_deserialize(&mut limited_reader)?;
1109
1110 // Denoted as `tx_out_count` and `tx_out` in the spec.
1111 let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
1112
1113 let is_coinbase = inputs.len() == 1
1114 && matches!(inputs.first(), Some(transparent::Input::Coinbase { .. }));
1115
1116 // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
1117 // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
1118 // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
1119 // `bindingSigSapling`.
1120 let sapling_shielded_data =
1121 deserialize_v5_sapling_shielded_data(&mut limited_reader, is_coinbase)?;
1122
1123 // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
1124 // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
1125 // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
1126 let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
1127
1128 let tx = Transaction::V5 {
1129 network_upgrade,
1130 lock_time,
1131 expiry_height,
1132 inputs,
1133 outputs,
1134 sapling_shielded_data,
1135 orchard_shielded_data,
1136 };
1137
1138 tx.to_librustzcash(network_upgrade)?;
1139
1140 Ok(tx)
1141 }
1142 (6, true) => {
1143 // Denoted as `nVersionGroupId` in the spec.
1144 let id = limited_reader.read_u32::<LittleEndian>()?;
1145 if id != TX_V6_VERSION_GROUP_ID {
1146 return Err(SerializationError::Parse("expected TX_V6_VERSION_GROUP_ID"));
1147 }
1148 // Denoted as `nConsensusBranchId` in the spec.
1149 // Convert it to a NetworkUpgrade
1150 let network_upgrade =
1151 NetworkUpgrade::try_from(limited_reader.read_u32::<LittleEndian>()?)?;
1152 // v6 transactions are only valid from NU6.3 onward, so reject transactions with
1153 // pre-NU6.3 consensus branch IDs at the wire layer. (The exact tx-vs-block network
1154 // upgrade match is also re-checked during verification by `consensus_branch_id`.)
1155 if network_upgrade < NetworkUpgrade::Nu6_3 {
1156 return Err(SerializationError::Parse(
1157 "v6 transaction must have a NU6.3 or later consensus branch ID",
1158 ));
1159 }
1160 // Denoted as `lock_time` in the spec.
1161 let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
1162
1163 // Denoted as `nExpiryHeight` in the spec.
1164 let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
1165
1166 // Denoted as `tx_in_count` and `tx_in` in the spec.
1167 let inputs: Vec<transparent::Input> = Vec::zcash_deserialize(&mut limited_reader)?;
1168
1169 // Denoted as `tx_out_count` and `tx_out` in the spec.
1170 let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
1171
1172 let is_coinbase = inputs.len() == 1
1173 && matches!(inputs.first(), Some(transparent::Input::Coinbase { .. }));
1174
1175 // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
1176 // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
1177 // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
1178 // `bindingSigSapling`.
1179 //
1180 // The v6 Sapling bundle has the same *wire* format as v5: the NU6.3 changes (moving
1181 // `anchorSapling` from the txid digest to the auth digest, and the `..._v6`
1182 // personalizations) only affect digest computation, which Zebra delegates to
1183 // librustzcash via `to_librustzcash`. So the v5 Sapling codec is reused here.
1184 let sapling_shielded_data =
1185 deserialize_v5_sapling_shielded_data(&mut limited_reader, is_coinbase)?;
1186
1187 // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
1188 // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
1189 // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`. The v6 Orchard
1190 // bundle reserves the `enableCrossAddress` bit (like v5); only the Ironwood bundle
1191 // below permits it.
1192 let orchard_shielded_data = (&mut limited_reader)
1193 .zcash_deserialize_into::<Option<orchard::ShieldedDataV6>>()?;
1194
1195 // The Ironwood bundle: the same field layout and NU6.3 flag format as the Orchard
1196 // bundle above (`nActionsIronwood` .. `bindingSigIronwood`).
1197 let ironwood_shielded_data = (&mut limited_reader)
1198 .zcash_deserialize_into::<Option<ironwood::ShieldedData>>()?;
1199
1200 let tx = Transaction::V6 {
1201 network_upgrade,
1202 lock_time,
1203 expiry_height,
1204 inputs,
1205 outputs,
1206 sapling_shielded_data,
1207 orchard_shielded_data,
1208 ironwood_shielded_data,
1209 };
1210
1211 // Validate that the whole transaction round-trips to the librustzcash format, as the
1212 // v5 arm above does. Zebra's and librustzcash's parsers are not identical, so this
1213 // fails closed at the wire layer for any divergence, ensuring an incompatibility can
1214 // never reach the `expect(...)` in the txid/auth-digest path (`Hash::from`), which
1215 // would otherwise abort the node on attacker-supplied input.
1216 tx.to_librustzcash(network_upgrade)?;
1217
1218 Ok(tx)
1219 }
1220 (_, _) => Err(SerializationError::Parse("bad tx header")),
1221 }
1222 }
1223}
1224
1225impl<T> ZcashDeserialize for Arc<T>
1226where
1227 T: ZcashDeserialize,
1228{
1229 fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
1230 Ok(Arc::new(T::zcash_deserialize(reader)?))
1231 }
1232}
1233
1234impl<T> ZcashSerialize for Arc<T>
1235where
1236 T: ZcashSerialize,
1237{
1238 fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
1239 T::zcash_serialize(self, writer)
1240 }
1241}
1242
1243/// A Tx Input must have an Outpoint (32 byte hash + 4 byte index), a 4 byte sequence number,
1244/// and a signature script, which always takes a min of 1 byte (for a length 0 script).
1245pub(crate) const MIN_TRANSPARENT_INPUT_SIZE: u64 = 32 + 4 + 4 + 1;
1246
1247/// A Transparent output has an 8 byte value and script which takes a min of 1 byte.
1248pub(crate) const MIN_TRANSPARENT_OUTPUT_SIZE: u64 = 8 + 1;
1249
1250/// All txs must have at least one input, a 4 byte locktime, and at least one output.
1251///
1252/// Shielded transfers are much larger than transparent transfers,
1253/// so this is the minimum transaction size.
1254pub const MIN_TRANSPARENT_TX_SIZE: u64 =
1255 MIN_TRANSPARENT_INPUT_SIZE + 4 + MIN_TRANSPARENT_OUTPUT_SIZE;
1256
1257/// The minimum transaction size for v4 transactions.
1258///
1259/// v4 transactions also have an expiry height.
1260pub const MIN_TRANSPARENT_TX_V4_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4;
1261
1262/// The minimum transaction size for v5 transactions.
1263///
1264/// v5 transactions also have an expiry height and a consensus branch ID.
1265pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4;
1266
1267/// No valid Zcash message contains more transactions than can fit in a single block
1268///
1269/// `tx` messages contain a single transaction, and `block` messages are limited to the maximum
1270/// block size.
1271impl TrustedPreallocate for Transaction {
1272 fn max_allocation() -> u64 {
1273 // A transparent transaction is the smallest transaction variant
1274 MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE
1275 }
1276}
1277
1278/// The maximum number of inputs in a valid Zcash on-chain transaction.
1279///
1280/// If a transaction contains more inputs than can fit in maximally large block, it might be
1281/// valid on the network and in the mempool, but it can never be mined into a block. So
1282/// rejecting these large edge-case transactions can never break consensus.
1283impl TrustedPreallocate for transparent::Input {
1284 fn max_allocation() -> u64 {
1285 MAX_BLOCK_BYTES / MIN_TRANSPARENT_INPUT_SIZE
1286 }
1287}
1288
1289/// The maximum number of outputs in a valid Zcash on-chain transaction.
1290///
1291/// If a transaction contains more outputs than can fit in maximally large block, it might be
1292/// valid on the network and in the mempool, but it can never be mined into a block. So
1293/// rejecting these large edge-case transactions can never break consensus.
1294impl TrustedPreallocate for transparent::Output {
1295 fn max_allocation() -> u64 {
1296 MAX_BLOCK_BYTES / MIN_TRANSPARENT_OUTPUT_SIZE
1297 }
1298}
1299
1300/// A serialized transaction.
1301///
1302/// Stores bytes that are guaranteed to be deserializable into a [`Transaction`].
1303///
1304/// Sorts in lexicographic order of the transaction's serialized data.
1305#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1306pub struct SerializedTransaction {
1307 bytes: Vec<u8>,
1308}
1309
1310impl fmt::Display for SerializedTransaction {
1311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1312 f.write_str(&hex::encode(&self.bytes))
1313 }
1314}
1315
1316impl fmt::Debug for SerializedTransaction {
1317 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1318 // A transaction with a lot of transfers can be extremely long in logs.
1319 let mut data_truncated = hex::encode(&self.bytes);
1320 if data_truncated.len() > 1003 {
1321 let end = data_truncated.len() - 500;
1322 // Replace the middle bytes with "...", but leave 500 bytes on either side.
1323 // The data is hex, so this replacement won't panic.
1324 data_truncated.replace_range(500..=end, "...");
1325 }
1326
1327 f.debug_tuple("SerializedTransaction")
1328 .field(&data_truncated)
1329 .finish()
1330 }
1331}
1332
1333/// Build a [`SerializedTransaction`] by serializing a block.
1334impl<B: Borrow<Transaction>> From<B> for SerializedTransaction {
1335 fn from(tx: B) -> Self {
1336 SerializedTransaction {
1337 bytes: tx
1338 .borrow()
1339 .zcash_serialize_to_vec()
1340 .expect("Writing to a `Vec` should never fail"),
1341 }
1342 }
1343}
1344
1345/// Access the serialized bytes of a [`SerializedTransaction`].
1346impl AsRef<[u8]> for SerializedTransaction {
1347 fn as_ref(&self) -> &[u8] {
1348 self.bytes.as_ref()
1349 }
1350}
1351
1352impl From<Vec<u8>> for SerializedTransaction {
1353 fn from(bytes: Vec<u8>) -> Self {
1354 Self { bytes }
1355 }
1356}
1357
1358impl FromHex for SerializedTransaction {
1359 type Error = <Vec<u8> as FromHex>::Error;
1360
1361 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
1362 let bytes = <Vec<u8>>::from_hex(hex)?;
1363
1364 Ok(bytes.into())
1365 }
1366}