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