1pub mod builder;
3pub mod components;
4pub mod sighash;
5pub mod sighash_v4;
6pub mod sighash_v5;
7pub mod txid;
8pub mod util;
9
10#[cfg(test)]
11mod tests;
12
13use blake2b_simd::Hash as Blake2bHash;
14use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
15use ff::PrimeField;
16use std::convert::TryFrom;
17use std::fmt;
18use std::fmt::Debug;
19use std::io::{self, Read, Write};
20use std::ops::Deref;
21use zcash_encoding::{Array, CompactSize, Vector};
22
23use crate::{
24 consensus::{BlockHeight, BranchId},
25 sapling::redjubjub,
26};
27
28use self::{
29 components::{
30 amount::Amount,
31 orchard as orchard_serialization,
32 sapling::{
33 self, OutputDescription, OutputDescriptionV5, SpendDescription, SpendDescriptionV5,
34 },
35 sprout::{self, JsDescription},
36 transparent::{self, TxIn, TxOut},
37 },
38 txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester},
39 util::sha256d::{HashReader, HashWriter},
40};
41
42#[cfg(feature = "zfuture")]
43use self::components::tze::{self, TzeIn, TzeOut};
44
45const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
46const OVERWINTER_TX_VERSION: u32 = 3;
47const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
48const SAPLING_TX_VERSION: u32 = 4;
49
50const V5_TX_VERSION: u32 = 5;
51const V5_VERSION_GROUP_ID: u32 = 0x26A7270A;
52
53#[cfg(feature = "zfuture")]
60const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF;
61#[cfg(feature = "zfuture")]
62const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF;
63
64#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
65pub struct TxId([u8; 32]);
66
67impl fmt::Debug for TxId {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 let txid_str = self.to_string();
72 f.debug_tuple("TxId").field(&txid_str).finish()
73 }
74}
75
76impl fmt::Display for TxId {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 let mut data = self.0;
79 data.reverse();
80 formatter.write_str(&hex::encode(data))
81 }
82}
83
84impl AsRef<[u8; 32]> for TxId {
85 fn as_ref(&self) -> &[u8; 32] {
86 &self.0
87 }
88}
89
90impl TxId {
91 pub fn from_bytes(bytes: [u8; 32]) -> Self {
92 TxId(bytes)
93 }
94
95 pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
96 let mut hash = [0u8; 32];
97 reader.read_exact(&mut hash)?;
98 Ok(TxId::from_bytes(hash))
99 }
100
101 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
102 writer.write_all(&self.0)?;
103 Ok(())
104 }
105}
106
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum TxVersion {
116 Sprout(u32),
117 Overwinter,
118 Sapling,
119 Zip225,
120 #[cfg(feature = "zfuture")]
121 ZFuture,
122}
123
124impl TxVersion {
125 pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
126 let header = reader.read_u32::<LittleEndian>()?;
127 let overwintered = (header >> 31) == 1;
128 let version = header & 0x7FFFFFFF;
129
130 if overwintered {
131 match (version, reader.read_u32::<LittleEndian>()?) {
132 (OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
133 (SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
134 (V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::Zip225),
135 #[cfg(feature = "zfuture")]
136 (ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
137 _ => Err(io::Error::new(
138 io::ErrorKind::InvalidInput,
139 "Unknown transaction format",
140 )),
141 }
142 } else if version >= 1 {
143 Ok(TxVersion::Sprout(version))
144 } else {
145 Err(io::Error::new(
146 io::ErrorKind::InvalidInput,
147 "Unknown transaction format",
148 ))
149 }
150 }
151
152 pub fn header(&self) -> u32 {
153 let overwintered = match self {
155 TxVersion::Sprout(_) => 0,
156 _ => 1 << 31,
157 };
158
159 overwintered
160 | match self {
161 TxVersion::Sprout(v) => *v,
162 TxVersion::Overwinter => OVERWINTER_TX_VERSION,
163 TxVersion::Sapling => SAPLING_TX_VERSION,
164 TxVersion::Zip225 => V5_TX_VERSION,
165 #[cfg(feature = "zfuture")]
166 TxVersion::ZFuture => ZFUTURE_TX_VERSION,
167 }
168 }
169
170 pub fn version_group_id(&self) -> u32 {
171 match self {
172 TxVersion::Sprout(_) => 0,
173 TxVersion::Overwinter => OVERWINTER_VERSION_GROUP_ID,
174 TxVersion::Sapling => SAPLING_VERSION_GROUP_ID,
175 TxVersion::Zip225 => V5_VERSION_GROUP_ID,
176 #[cfg(feature = "zfuture")]
177 TxVersion::ZFuture => ZFUTURE_VERSION_GROUP_ID,
178 }
179 }
180
181 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
182 writer.write_u32::<LittleEndian>(self.header())?;
183 match self {
184 TxVersion::Sprout(_) => Ok(()),
185 _ => writer.write_u32::<LittleEndian>(self.version_group_id()),
186 }
187 }
188
189 pub fn has_sprout(&self) -> bool {
190 match self {
191 TxVersion::Sprout(v) => *v >= 2u32,
192 TxVersion::Overwinter | TxVersion::Sapling => true,
193 TxVersion::Zip225 => false,
194 #[cfg(feature = "zfuture")]
195 TxVersion::ZFuture => true,
196 }
197 }
198
199 pub fn has_overwinter(&self) -> bool {
200 !matches!(self, TxVersion::Sprout(_))
201 }
202
203 pub fn has_sapling(&self) -> bool {
204 match self {
205 TxVersion::Sprout(_) | TxVersion::Overwinter => false,
206 TxVersion::Sapling => true,
207 TxVersion::Zip225 => true,
208 #[cfg(feature = "zfuture")]
209 TxVersion::ZFuture => true,
210 }
211 }
212
213 pub fn has_orchard(&self) -> bool {
214 match self {
215 TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => false,
216 TxVersion::Zip225 => true,
217 #[cfg(feature = "zfuture")]
218 TxVersion::ZFuture => true,
219 }
220 }
221
222 #[cfg(feature = "zfuture")]
223 pub fn has_tze(&self) -> bool {
224 matches!(self, TxVersion::ZFuture)
225 }
226
227 pub fn suggested_for_branch(consensus_branch_id: BranchId) -> Self {
228 match consensus_branch_id {
229 BranchId::Sprout => TxVersion::Sprout(2),
230 BranchId::Overwinter => TxVersion::Overwinter,
231 BranchId::Sapling | BranchId::Blossom | BranchId::Heartwood | BranchId::Canopy => {
232 TxVersion::Sapling
233 }
234 BranchId::Nu5 => TxVersion::Zip225,
235 #[cfg(feature = "zfuture")]
236 BranchId::ZFuture => TxVersion::ZFuture,
237 }
238 }
239}
240
241pub trait Authorization {
243 type TransparentAuth: transparent::Authorization;
244 type SaplingAuth: sapling::Authorization;
245 type OrchardAuth: orchard::bundle::Authorization;
246
247 #[cfg(feature = "zfuture")]
248 type TzeAuth: tze::Authorization;
249}
250
251#[derive(Debug)]
252pub struct Authorized;
253
254impl Authorization for Authorized {
255 type TransparentAuth = transparent::Authorized;
256 type SaplingAuth = sapling::Authorized;
257 type OrchardAuth = orchard::bundle::Authorized;
258
259 #[cfg(feature = "zfuture")]
260 type TzeAuth = tze::Authorized;
261}
262
263pub struct Unauthorized;
264
265impl Authorization for Unauthorized {
266 type TransparentAuth = transparent::builder::Unauthorized;
267 type SaplingAuth = sapling::builder::Unauthorized;
268 type OrchardAuth = orchard_serialization::Unauthorized;
269
270 #[cfg(feature = "zfuture")]
271 type TzeAuth = tze::builder::Unauthorized;
272}
273
274#[derive(Debug)]
276pub struct Transaction {
277 txid: TxId,
278 data: TransactionData<Authorized>,
279}
280
281impl Deref for Transaction {
282 type Target = TransactionData<Authorized>;
283
284 fn deref(&self) -> &TransactionData<Authorized> {
285 &self.data
286 }
287}
288
289impl PartialEq for Transaction {
290 fn eq(&self, other: &Transaction) -> bool {
291 self.txid == other.txid
292 }
293}
294
295#[derive(Debug)]
296pub struct TransactionData<A: Authorization> {
297 version: TxVersion,
298 consensus_branch_id: BranchId,
299 lock_time: u32,
300 expiry_height: BlockHeight,
301 transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
302 sprout_bundle: Option<sprout::Bundle>,
303 sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
304 orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
305 #[cfg(feature = "zfuture")]
306 tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
307}
308
309impl<A: Authorization> TransactionData<A> {
310 #[allow(clippy::too_many_arguments)]
311 pub fn from_parts(
312 version: TxVersion,
313 consensus_branch_id: BranchId,
314 lock_time: u32,
315 expiry_height: BlockHeight,
316 transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
317 sprout_bundle: Option<sprout::Bundle>,
318 sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
319 orchard_bundle: Option<orchard::Bundle<A::OrchardAuth, Amount>>,
320 #[cfg(feature = "zfuture")] tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
321 ) -> Self {
322 TransactionData {
323 version,
324 consensus_branch_id,
325 lock_time,
326 expiry_height,
327 transparent_bundle,
328 sprout_bundle,
329 sapling_bundle,
330 orchard_bundle,
331 #[cfg(feature = "zfuture")]
332 tze_bundle,
333 }
334 }
335
336 pub fn version(&self) -> TxVersion {
337 self.version
338 }
339
340 pub fn consensus_branch_id(&self) -> BranchId {
341 self.consensus_branch_id
342 }
343
344 pub fn lock_time(&self) -> u32 {
345 self.lock_time
346 }
347
348 pub fn expiry_height(&self) -> BlockHeight {
349 self.expiry_height
350 }
351
352 pub fn transparent_bundle(&self) -> Option<&transparent::Bundle<A::TransparentAuth>> {
353 self.transparent_bundle.as_ref()
354 }
355
356 pub fn sprout_bundle(&self) -> Option<&sprout::Bundle> {
357 self.sprout_bundle.as_ref()
358 }
359
360 pub fn sapling_bundle(&self) -> Option<&sapling::Bundle<A::SaplingAuth>> {
361 self.sapling_bundle.as_ref()
362 }
363
364 pub fn orchard_bundle(&self) -> Option<&orchard::Bundle<A::OrchardAuth, Amount>> {
365 self.orchard_bundle.as_ref()
366 }
367
368 #[cfg(feature = "zfuture")]
369 pub fn tze_bundle(&self) -> Option<&tze::Bundle<A::TzeAuth>> {
370 self.tze_bundle.as_ref()
371 }
372
373 pub fn digest<D: TransactionDigest<A>>(&self, digester: D) -> D::Digest {
374 digester.combine(
375 digester.digest_header(
376 self.version,
377 self.consensus_branch_id,
378 self.lock_time,
379 self.expiry_height,
380 ),
381 digester.digest_transparent(self.transparent_bundle.as_ref()),
382 digester.digest_sapling(self.sapling_bundle.as_ref()),
383 digester.digest_orchard(self.orchard_bundle.as_ref()),
384 #[cfg(feature = "zfuture")]
385 digester.digest_tze(self.tze_bundle.as_ref()),
386 )
387 }
388
389 pub fn map_bundles<B: Authorization>(
394 self,
395 f_transparent: impl FnOnce(
396 Option<transparent::Bundle<A::TransparentAuth>>,
397 ) -> Option<transparent::Bundle<B::TransparentAuth>>,
398 f_sapling: impl FnOnce(
399 Option<sapling::Bundle<A::SaplingAuth>>,
400 ) -> Option<sapling::Bundle<B::SaplingAuth>>,
401 f_orchard: impl FnOnce(
402 Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
403 ) -> Option<orchard::bundle::Bundle<B::OrchardAuth, Amount>>,
404 #[cfg(feature = "zfuture")] f_tze: impl FnOnce(
405 Option<tze::Bundle<A::TzeAuth>>,
406 ) -> Option<tze::Bundle<B::TzeAuth>>,
407 ) -> TransactionData<B> {
408 TransactionData {
409 version: self.version,
410 consensus_branch_id: self.consensus_branch_id,
411 lock_time: self.lock_time,
412 expiry_height: self.expiry_height,
413 transparent_bundle: f_transparent(self.transparent_bundle),
414 sprout_bundle: self.sprout_bundle,
415 sapling_bundle: f_sapling(self.sapling_bundle),
416 orchard_bundle: f_orchard(self.orchard_bundle),
417 #[cfg(feature = "zfuture")]
418 tze_bundle: f_tze(self.tze_bundle),
419 }
420 }
421
422 pub fn map_authorization<B: Authorization>(
423 self,
424 f_transparent: impl transparent::MapAuth<A::TransparentAuth, B::TransparentAuth>,
425 f_sapling: impl sapling::MapAuth<A::SaplingAuth, B::SaplingAuth>,
426 mut f_orchard: impl orchard_serialization::MapAuth<A::OrchardAuth, B::OrchardAuth>,
427 #[cfg(feature = "zfuture")] f_tze: impl tze::MapAuth<A::TzeAuth, B::TzeAuth>,
428 ) -> TransactionData<B> {
429 TransactionData {
430 version: self.version,
431 consensus_branch_id: self.consensus_branch_id,
432 lock_time: self.lock_time,
433 expiry_height: self.expiry_height,
434 transparent_bundle: self
435 .transparent_bundle
436 .map(|b| b.map_authorization(f_transparent)),
437 sprout_bundle: self.sprout_bundle,
438 sapling_bundle: self.sapling_bundle.map(|b| b.map_authorization(f_sapling)),
439 orchard_bundle: self.orchard_bundle.map(|b| {
440 b.map_authorization(
441 &mut f_orchard,
442 |f, _, s| f.map_spend_auth(s),
443 |f, a| f.map_authorization(a),
444 )
445 }),
446 #[cfg(feature = "zfuture")]
447 tze_bundle: self.tze_bundle.map(|b| b.map_authorization(f_tze)),
448 }
449 }
450}
451
452impl<A: Authorization> TransactionData<A> {
453 pub fn sapling_value_balance(&self) -> Amount {
454 self.sapling_bundle
455 .as_ref()
456 .map_or(Amount::zero(), |b| b.value_balance)
457 }
458}
459
460impl TransactionData<Authorized> {
461 pub fn freeze(self) -> io::Result<Transaction> {
462 Transaction::from_data(self)
463 }
464}
465
466impl Transaction {
467 fn from_data(data: TransactionData<Authorized>) -> io::Result<Self> {
468 match data.version {
469 TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
470 Self::from_data_v4(data)
471 }
472 TxVersion::Zip225 => Ok(Self::from_data_v5(data)),
473 #[cfg(feature = "zfuture")]
474 TxVersion::ZFuture => Ok(Self::from_data_v5(data)),
475 }
476 }
477
478 fn from_data_v4(data: TransactionData<Authorized>) -> io::Result<Self> {
479 let mut tx = Transaction {
480 txid: TxId([0; 32]),
481 data,
482 };
483 let mut writer = HashWriter::default();
484 tx.write(&mut writer)?;
485 tx.txid.0.copy_from_slice(&writer.into_hash());
486 Ok(tx)
487 }
488
489 fn from_data_v5(data: TransactionData<Authorized>) -> Self {
490 let txid = to_txid(
491 data.version,
492 data.consensus_branch_id,
493 &data.digest(TxIdDigester),
494 );
495
496 Transaction { txid, data }
497 }
498
499 pub fn into_data(self) -> TransactionData<Authorized> {
500 self.data
501 }
502
503 pub fn txid(&self) -> TxId {
504 self.txid
505 }
506
507 pub fn read<R: Read>(reader: R, consensus_branch_id: BranchId) -> io::Result<Self> {
508 let mut reader = HashReader::new(reader);
509
510 let version = TxVersion::read(&mut reader)?;
511 match version {
512 TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
513 Self::read_v4(reader, version, consensus_branch_id)
514 }
515 TxVersion::Zip225 => Self::read_v5(reader.into_base_reader(), version),
516 #[cfg(feature = "zfuture")]
517 TxVersion::ZFuture => Self::read_v5(reader.into_base_reader(), version),
518 }
519 }
520
521 #[allow(clippy::redundant_closure)]
522 fn read_v4<R: Read>(
523 mut reader: HashReader<R>,
524 version: TxVersion,
525 consensus_branch_id: BranchId,
526 ) -> io::Result<Self> {
527 let transparent_bundle = Self::read_transparent(&mut reader)?;
528
529 let lock_time = reader.read_u32::<LittleEndian>()?;
530 let expiry_height: BlockHeight = if version.has_overwinter() {
531 reader.read_u32::<LittleEndian>()?.into()
532 } else {
533 0u32.into()
534 };
535
536 let (value_balance, shielded_spends, shielded_outputs) = if version.has_sapling() {
537 let vb = Self::read_amount(&mut reader)?;
538 #[allow(clippy::redundant_closure)]
539 let ss: Vec<SpendDescription<sapling::Authorized>> =
540 Vector::read(&mut reader, |r| SpendDescription::read(r))?;
541 #[allow(clippy::redundant_closure)]
542 let so: Vec<OutputDescription<sapling::GrothProofBytes>> =
543 Vector::read(&mut reader, |r| OutputDescription::read(r))?;
544 (vb, ss, so)
545 } else {
546 (Amount::zero(), vec![], vec![])
547 };
548
549 let sprout_bundle = if version.has_sprout() {
550 let joinsplits = Vector::read(&mut reader, |r| {
551 JsDescription::read(r, version.has_sapling())
552 })?;
553
554 if !joinsplits.is_empty() {
555 let mut bundle = sprout::Bundle {
556 joinsplits,
557 joinsplit_pubkey: [0; 32],
558 joinsplit_sig: [0; 64],
559 };
560 reader.read_exact(&mut bundle.joinsplit_pubkey)?;
561 reader.read_exact(&mut bundle.joinsplit_sig)?;
562 Some(bundle)
563 } else {
564 None
565 }
566 } else {
567 None
568 };
569
570 let binding_sig = if version.has_sapling()
571 && !(shielded_spends.is_empty() && shielded_outputs.is_empty())
572 {
573 Some(redjubjub::Signature::read(&mut reader)?)
574 } else {
575 None
576 };
577
578 let mut txid = [0; 32];
579 let hash_bytes = reader.into_hash();
580 txid.copy_from_slice(&hash_bytes);
581
582 Ok(Transaction {
583 txid: TxId(txid),
584 data: TransactionData {
585 version,
586 consensus_branch_id,
587 lock_time,
588 expiry_height,
589 transparent_bundle,
590 sprout_bundle,
591 sapling_bundle: binding_sig.map(|binding_sig| sapling::Bundle {
592 value_balance,
593 shielded_spends,
594 shielded_outputs,
595 authorization: sapling::Authorized { binding_sig },
596 }),
597 orchard_bundle: None,
598 #[cfg(feature = "zfuture")]
599 tze_bundle: None,
600 },
601 })
602 }
603
604 fn read_transparent<R: Read>(
605 mut reader: R,
606 ) -> io::Result<Option<transparent::Bundle<transparent::Authorized>>> {
607 let vin = Vector::read(&mut reader, TxIn::read)?;
608 let vout = Vector::read(&mut reader, TxOut::read)?;
609 Ok(if vin.is_empty() && vout.is_empty() {
610 None
611 } else {
612 Some(transparent::Bundle {
613 vin,
614 vout,
615 authorization: transparent::Authorized,
616 })
617 })
618 }
619
620 fn read_amount<R: Read>(mut reader: R) -> io::Result<Amount> {
621 let mut tmp = [0; 8];
622 reader.read_exact(&mut tmp)?;
623 Amount::from_i64_le_bytes(tmp)
624 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))
625 }
626
627 fn read_v5<R: Read>(mut reader: R, version: TxVersion) -> io::Result<Self> {
628 let (consensus_branch_id, lock_time, expiry_height) =
629 Self::read_v5_header_fragment(&mut reader)?;
630 let transparent_bundle = Self::read_transparent(&mut reader)?;
631 let sapling_bundle = Self::read_v5_sapling(&mut reader)?;
632 let orchard_bundle = orchard_serialization::read_v5_bundle(&mut reader)?;
633
634 #[cfg(feature = "zfuture")]
635 let tze_bundle = if version.has_tze() {
636 Self::read_tze(&mut reader)?
637 } else {
638 None
639 };
640
641 let data = TransactionData {
642 version,
643 consensus_branch_id,
644 lock_time,
645 expiry_height,
646 transparent_bundle,
647 sprout_bundle: None,
648 sapling_bundle,
649 orchard_bundle,
650 #[cfg(feature = "zfuture")]
651 tze_bundle,
652 };
653
654 Ok(Self::from_data_v5(data))
655 }
656
657 fn read_v5_header_fragment<R: Read>(mut reader: R) -> io::Result<(BranchId, u32, BlockHeight)> {
658 let consensus_branch_id = reader.read_u32::<LittleEndian>().and_then(|value| {
659 BranchId::try_from(value).map_err(|e| {
660 io::Error::new(
661 io::ErrorKind::InvalidInput,
662 "invalid consensus branch id: ".to_owned() + e,
663 )
664 })
665 })?;
666 let lock_time = reader.read_u32::<LittleEndian>()?;
667 let expiry_height: BlockHeight = reader.read_u32::<LittleEndian>()?.into();
668 Ok((consensus_branch_id, lock_time, expiry_height))
669 }
670
671 #[allow(clippy::redundant_closure)]
672 fn read_v5_sapling<R: Read>(
673 mut reader: R,
674 ) -> io::Result<Option<sapling::Bundle<sapling::Authorized>>> {
675 let sd_v5s = Vector::read(&mut reader, SpendDescriptionV5::read)?;
676 let od_v5s = Vector::read(&mut reader, OutputDescriptionV5::read)?;
677 let n_spends = sd_v5s.len();
678 let n_outputs = od_v5s.len();
679 let value_balance = if n_spends > 0 || n_outputs > 0 {
680 Self::read_amount(&mut reader)?
681 } else {
682 Amount::zero()
683 };
684
685 let anchor = if n_spends > 0 {
686 Some(sapling::read_base(&mut reader, "anchor")?)
687 } else {
688 None
689 };
690
691 let v_spend_proofs = Array::read(&mut reader, n_spends, |r| sapling::read_zkproof(r))?;
692 let v_spend_auth_sigs = Array::read(&mut reader, n_spends, |r| {
693 SpendDescription::read_spend_auth_sig(r)
694 })?;
695 let v_output_proofs = Array::read(&mut reader, n_outputs, |r| sapling::read_zkproof(r))?;
696
697 let binding_sig = if n_spends > 0 || n_outputs > 0 {
698 Some(redjubjub::Signature::read(&mut reader)?)
699 } else {
700 None
701 };
702
703 let shielded_spends = sd_v5s
704 .into_iter()
705 .zip(
706 v_spend_proofs
707 .into_iter()
708 .zip(v_spend_auth_sigs.into_iter()),
709 )
710 .map(|(sd_5, (zkproof, spend_auth_sig))| {
711 sd_5.into_spend_description(anchor.unwrap(), zkproof, spend_auth_sig)
713 })
714 .collect();
715
716 let shielded_outputs = od_v5s
717 .into_iter()
718 .zip(v_output_proofs.into_iter())
719 .map(|(od_5, zkproof)| od_5.into_output_description(zkproof))
720 .collect();
721
722 Ok(binding_sig.map(|binding_sig| sapling::Bundle {
723 value_balance,
724 shielded_spends,
725 shielded_outputs,
726 authorization: sapling::Authorized { binding_sig },
727 }))
728 }
729
730 #[cfg(feature = "zfuture")]
731 fn read_tze<R: Read>(mut reader: &mut R) -> io::Result<Option<tze::Bundle<tze::Authorized>>> {
732 let vin = Vector::read(&mut reader, TzeIn::read)?;
733 let vout = Vector::read(&mut reader, TzeOut::read)?;
734 Ok(if vin.is_empty() && vout.is_empty() {
735 None
736 } else {
737 Some(tze::Bundle {
738 vin,
739 vout,
740 authorization: tze::Authorized,
741 })
742 })
743 }
744
745 pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
746 match self.version {
747 TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
748 self.write_v4(writer)
749 }
750 TxVersion::Zip225 => self.write_v5(writer),
751 #[cfg(feature = "zfuture")]
752 TxVersion::ZFuture => self.write_v5(writer),
753 }
754 }
755
756 pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
757 self.version.write(&mut writer)?;
758
759 self.write_transparent(&mut writer)?;
760 writer.write_u32::<LittleEndian>(self.lock_time)?;
761 if self.version.has_overwinter() {
762 writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
763 }
764
765 if self.version.has_sapling() {
766 writer.write_all(
767 &self
768 .sapling_bundle
769 .as_ref()
770 .map_or(Amount::zero(), |b| b.value_balance)
771 .to_i64_le_bytes(),
772 )?;
773 Vector::write(
774 &mut writer,
775 self.sapling_bundle
776 .as_ref()
777 .map_or(&[], |b| &b.shielded_spends),
778 |w, e| e.write_v4(w),
779 )?;
780 Vector::write(
781 &mut writer,
782 self.sapling_bundle
783 .as_ref()
784 .map_or(&[], |b| &b.shielded_outputs),
785 |w, e| e.write_v4(w),
786 )?;
787 } else if self.sapling_bundle.is_some() {
788 return Err(io::Error::new(
789 io::ErrorKind::InvalidInput,
790 "Sapling components may not be present if Sapling is not active.",
791 ));
792 }
793
794 if self.version.has_sprout() {
795 if let Some(bundle) = self.sprout_bundle.as_ref() {
796 Vector::write(&mut writer, &bundle.joinsplits, |w, e| e.write(w))?;
797 writer.write_all(&bundle.joinsplit_pubkey)?;
798 writer.write_all(&bundle.joinsplit_sig)?;
799 } else {
800 CompactSize::write(&mut writer, 0)?;
801 }
802 }
803
804 if self.version.has_sapling() {
805 if let Some(bundle) = self.sapling_bundle.as_ref() {
806 bundle.authorization.binding_sig.write(&mut writer)?;
807 }
808 }
809
810 if self.orchard_bundle.is_some() {
811 return Err(io::Error::new(
812 io::ErrorKind::InvalidInput,
813 "Orchard components cannot be present when serializing to the V4 transaction format."
814 ));
815 }
816
817 Ok(())
818 }
819
820 pub fn write_transparent<W: Write>(&self, mut writer: W) -> io::Result<()> {
821 if let Some(bundle) = &self.transparent_bundle {
822 Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
823 Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
824 } else {
825 CompactSize::write(&mut writer, 0)?;
826 CompactSize::write(&mut writer, 0)?;
827 }
828
829 Ok(())
830 }
831
832 pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
833 if self.sprout_bundle.is_some() {
834 return Err(io::Error::new(
835 io::ErrorKind::InvalidInput,
836 "Sprout components cannot be present when serializing to the V5 transaction format.",
837 ));
838 }
839 self.write_v5_header(&mut writer)?;
840 self.write_transparent(&mut writer)?;
841 self.write_v5_sapling(&mut writer)?;
842 orchard_serialization::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
843 #[cfg(feature = "zfuture")]
844 self.write_tze(&mut writer)?;
845 Ok(())
846 }
847
848 pub fn write_v5_header<W: Write>(&self, mut writer: W) -> io::Result<()> {
849 self.version.write(&mut writer)?;
850 writer.write_u32::<LittleEndian>(u32::from(self.consensus_branch_id))?;
851 writer.write_u32::<LittleEndian>(self.lock_time)?;
852 writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
853 Ok(())
854 }
855
856 pub fn write_v5_sapling<W: Write>(&self, mut writer: W) -> io::Result<()> {
857 if let Some(bundle) = &self.sapling_bundle {
858 Vector::write(&mut writer, &bundle.shielded_spends, |w, e| {
859 e.write_v5_without_witness_data(w)
860 })?;
861
862 Vector::write(&mut writer, &bundle.shielded_outputs, |w, e| {
863 e.write_v5_without_proof(w)
864 })?;
865
866 if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
867 writer.write_all(&bundle.value_balance.to_i64_le_bytes())?;
868 }
869 if !bundle.shielded_spends.is_empty() {
870 writer.write_all(bundle.shielded_spends[0].anchor.to_repr().as_ref())?;
871 }
872
873 Array::write(
874 &mut writer,
875 bundle.shielded_spends.iter().map(|s| s.zkproof),
876 |w, e| w.write_all(e),
877 )?;
878 Array::write(
879 &mut writer,
880 bundle.shielded_spends.iter().map(|s| s.spend_auth_sig),
881 |w, e| e.write(w),
882 )?;
883
884 Array::write(
885 &mut writer,
886 bundle.shielded_outputs.iter().map(|s| s.zkproof),
887 |w, e| w.write_all(e),
888 )?;
889
890 if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
891 bundle.authorization.binding_sig.write(&mut writer)?;
892 }
893 } else {
894 CompactSize::write(&mut writer, 0)?;
895 CompactSize::write(&mut writer, 0)?;
896 }
897
898 Ok(())
899 }
900
901 #[cfg(feature = "zfuture")]
902 pub fn write_tze<W: Write>(&self, mut writer: W) -> io::Result<()> {
903 if let Some(bundle) = &self.tze_bundle {
904 Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
905 Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
906 } else {
907 CompactSize::write(&mut writer, 0)?;
908 CompactSize::write(&mut writer, 0)?;
909 }
910
911 Ok(())
912 }
913
914 pub fn auth_commitment(&self) -> Blake2bHash {
916 self.data.digest(BlockTxCommitmentDigester)
917 }
918}
919
920#[derive(Clone, Debug)]
921pub struct TransparentDigests<A> {
922 pub prevouts_digest: A,
923 pub sequence_digest: A,
924 pub outputs_digest: A,
925}
926
927#[derive(Clone, Debug)]
928pub struct TzeDigests<A> {
929 pub inputs_digest: A,
930 pub outputs_digest: A,
931 pub per_input_digest: Option<A>,
932}
933
934#[derive(Clone, Debug)]
935pub struct TxDigests<A> {
936 pub header_digest: A,
937 pub transparent_digests: Option<TransparentDigests<A>>,
938 pub sapling_digest: Option<A>,
939 pub orchard_digest: Option<A>,
940 #[cfg(feature = "zfuture")]
941 pub tze_digests: Option<TzeDigests<A>>,
942}
943
944pub trait TransactionDigest<A: Authorization> {
945 type HeaderDigest;
946 type TransparentDigest;
947 type SaplingDigest;
948 type OrchardDigest;
949
950 #[cfg(feature = "zfuture")]
951 type TzeDigest;
952
953 type Digest;
954
955 fn digest_header(
956 &self,
957 version: TxVersion,
958 consensus_branch_id: BranchId,
959 lock_time: u32,
960 expiry_height: BlockHeight,
961 ) -> Self::HeaderDigest;
962
963 fn digest_transparent(
964 &self,
965 transparent_bundle: Option<&transparent::Bundle<A::TransparentAuth>>,
966 ) -> Self::TransparentDigest;
967
968 fn digest_sapling(
969 &self,
970 sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth>>,
971 ) -> Self::SaplingDigest;
972
973 fn digest_orchard(
974 &self,
975 orchard_bundle: Option<&orchard::Bundle<A::OrchardAuth, Amount>>,
976 ) -> Self::OrchardDigest;
977
978 #[cfg(feature = "zfuture")]
979 fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<A::TzeAuth>>) -> Self::TzeDigest;
980
981 fn combine(
982 &self,
983 header_digest: Self::HeaderDigest,
984 transparent_digest: Self::TransparentDigest,
985 sapling_digest: Self::SaplingDigest,
986 orchard_digest: Self::OrchardDigest,
987 #[cfg(feature = "zfuture")] tze_digest: Self::TzeDigest,
988 ) -> Self::Digest;
989}
990
991pub enum DigestError {
992 NotSigned,
993}
994
995#[cfg(any(test, feature = "test-dependencies"))]
996pub mod testing {
997 use proptest::prelude::*;
998
999 use crate::consensus::BranchId;
1000
1001 use super::{
1002 components::{
1003 orchard::testing::{self as orchard},
1004 sapling::testing::{self as sapling},
1005 transparent::testing::{self as transparent},
1006 },
1007 Authorized, Transaction, TransactionData, TxId, TxVersion,
1008 };
1009
1010 #[cfg(feature = "zfuture")]
1011 use super::components::tze::testing::{self as tze};
1012
1013 pub fn arb_txid() -> impl Strategy<Value = TxId> {
1014 prop::array::uniform32(any::<u8>()).prop_map(TxId::from_bytes)
1015 }
1016
1017 pub fn arb_tx_version(branch_id: BranchId) -> impl Strategy<Value = TxVersion> {
1018 match branch_id {
1019 BranchId::Sprout => (1..=2u32).prop_map(TxVersion::Sprout).boxed(),
1020 BranchId::Overwinter => Just(TxVersion::Overwinter).boxed(),
1021 BranchId::Sapling | BranchId::Blossom | BranchId::Heartwood | BranchId::Canopy => {
1022 Just(TxVersion::Sapling).boxed()
1023 }
1024 BranchId::Nu5 => Just(TxVersion::Zip225).boxed(),
1025 #[cfg(feature = "zfuture")]
1026 BranchId::ZFuture => Just(TxVersion::ZFuture).boxed(),
1027 }
1028 }
1029
1030 #[cfg(not(feature = "zfuture"))]
1031 prop_compose! {
1032 pub fn arb_txdata(consensus_branch_id: BranchId)(
1033 version in arb_tx_version(consensus_branch_id),
1034 )(
1035 lock_time in any::<u32>(),
1036 expiry_height in any::<u32>(),
1037 transparent_bundle in transparent::arb_bundle(),
1038 sapling_bundle in sapling::arb_bundle_for_version(version),
1039 orchard_bundle in orchard::arb_bundle_for_version(version),
1040 version in Just(version)
1041 ) -> TransactionData<Authorized> {
1042 TransactionData {
1043 version,
1044 consensus_branch_id,
1045 lock_time,
1046 expiry_height: expiry_height.into(),
1047 transparent_bundle,
1048 sprout_bundle: None,
1049 sapling_bundle,
1050 orchard_bundle
1051 }
1052 }
1053 }
1054
1055 #[cfg(feature = "zfuture")]
1056 prop_compose! {
1057 pub fn arb_txdata(consensus_branch_id: BranchId)(
1058 version in arb_tx_version(consensus_branch_id),
1059 )(
1060 lock_time in any::<u32>(),
1061 expiry_height in any::<u32>(),
1062 transparent_bundle in transparent::arb_bundle(),
1063 sapling_bundle in sapling::arb_bundle_for_version(version),
1064 orchard_bundle in orchard::arb_bundle_for_version(version),
1065 tze_bundle in tze::arb_bundle(consensus_branch_id),
1066 version in Just(version)
1067 ) -> TransactionData<Authorized> {
1068 TransactionData {
1069 version,
1070 consensus_branch_id,
1071 lock_time,
1072 expiry_height: expiry_height.into(),
1073 transparent_bundle,
1074 sprout_bundle: None,
1075 sapling_bundle,
1076 orchard_bundle,
1077 tze_bundle
1078 }
1079 }
1080 }
1081
1082 prop_compose! {
1083 pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
1084 Transaction::from_data(tx_data).unwrap()
1085 }
1086 }
1087}