pczt/
lib.rs

1//! The Partially Created Zcash Transaction (PCZT) format.
2//!
3//! This format enables splitting up the logical steps of creating a Zcash transaction
4//! across distinct entities. The entity roles roughly match those specified in
5//! [BIP 174: Partially Signed Bitcoin Transaction Format] and [BIP 370: PSBT Version 2],
6//! with additional Zcash-specific roles.
7//!
8//! [BIP 174: Partially Signed Bitcoin Transaction Format]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
9//! [BIP 370: PSBT Version 2]: https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki
10//!
11#![cfg_attr(feature = "std", doc = "## Feature flags")]
12#![cfg_attr(feature = "std", doc = document_features::document_features!())]
13//!
14
15#![no_std]
16#![cfg_attr(docsrs, feature(doc_cfg))]
17#![cfg_attr(docsrs, feature(doc_auto_cfg))]
18// Catch documentation errors caused by code changes.
19#![deny(rustdoc::broken_intra_doc_links)]
20
21#[macro_use]
22extern crate alloc;
23
24use alloc::vec::Vec;
25
26use getset::Getters;
27use serde::{Deserialize, Serialize};
28
29#[cfg(feature = "signer")]
30use {roles::signer::EffectsOnly, zcash_primitives::transaction::TransactionData};
31
32pub mod roles;
33
34pub mod common;
35pub mod orchard;
36pub mod sapling;
37pub mod transparent;
38
39const MAGIC_BYTES: &[u8] = b"PCZT";
40const PCZT_VERSION_1: u32 = 1;
41
42/// A partially-created Zcash transaction.
43#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
44pub struct Pczt {
45    /// Global fields that are relevant to the transaction as a whole.
46    #[getset(get = "pub")]
47    global: common::Global,
48
49    //
50    // Protocol-specific fields.
51    //
52    // Unlike the `TransactionData` type in `zcash_primitives`, these are not optional.
53    // This is because a PCZT does not always contain a semantically-valid transaction,
54    // and there may be phases where we need to store protocol-specific metadata before
55    // it has been determined whether there are protocol-specific inputs or outputs.
56    //
57    #[getset(get = "pub")]
58    transparent: transparent::Bundle,
59    #[getset(get = "pub")]
60    sapling: sapling::Bundle,
61    #[getset(get = "pub")]
62    orchard: orchard::Bundle,
63}
64
65impl Pczt {
66    /// Parses a PCZT from its encoding.
67    pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
68        if bytes.len() < 8 {
69            return Err(ParseError::TooShort);
70        }
71        if &bytes[..4] != MAGIC_BYTES {
72            return Err(ParseError::NotPczt);
73        }
74        let version = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
75        if version != PCZT_VERSION_1 {
76            return Err(ParseError::UnknownVersion(version));
77        }
78
79        // This is a v1 PCZT.
80        postcard::from_bytes(&bytes[8..]).map_err(ParseError::Invalid)
81    }
82
83    /// Serializes this PCZT.
84    pub fn serialize(&self) -> Vec<u8> {
85        let mut bytes = vec![];
86        bytes.extend_from_slice(MAGIC_BYTES);
87        bytes.extend_from_slice(&PCZT_VERSION_1.to_le_bytes());
88        postcard::to_extend(self, bytes).expect("can serialize into memory")
89    }
90
91    /// Gets the effects of this transaction.
92    #[cfg(feature = "signer")]
93    pub fn into_effects(self) -> Option<TransactionData<EffectsOnly>> {
94        let Self {
95            global,
96            transparent,
97            sapling,
98            orchard,
99        } = self;
100
101        let transparent = transparent.into_parsed().ok()?;
102        let sapling = sapling.into_parsed().ok()?;
103        let orchard = orchard.into_parsed().ok()?;
104
105        roles::signer::pczt_to_tx_data(&global, &transparent, &sapling, &orchard).ok()
106    }
107}
108
109/// Errors that can occur while parsing a PCZT.
110#[derive(Debug)]
111pub enum ParseError {
112    /// The bytes do not contain a PCZT.
113    NotPczt,
114    /// The PCZT encoding was invalid.
115    Invalid(postcard::Error),
116    /// The bytes are too short to contain a PCZT.
117    TooShort,
118    /// The PCZT has an unknown version.
119    UnknownVersion(u32),
120}