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