#![cfg_attr(feature = "std", doc = "## Feature flags")]
#![cfg_attr(feature = "std", doc = document_features::document_features!())]
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#![deny(rustdoc::broken_intra_doc_links)]
#[macro_use]
extern crate alloc;
use alloc::vec::Vec;
use getset::Getters;
use serde::{Deserialize, Serialize};
#[cfg(all(
any(feature = "io-finalizer", feature = "signer", feature = "tx-extractor"),
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233",
))]
use zcash_protocol::value::Zatoshis;
#[cfg(any(feature = "io-finalizer", feature = "signer", feature = "tx-extractor"))]
use {
common::{Global, determine_lock_time},
zcash_primitives::transaction::{Authorization, TransactionData, TxVersion},
zcash_protocol::consensus::BranchId,
zcash_protocol::constants::{V5_TX_VERSION, V5_VERSION_GROUP_ID},
};
#[cfg(any(feature = "io-finalizer", feature = "signer"))]
use {
blake2b_simd::Hash as Blake2bHash,
zcash_primitives::transaction::{
TxDigests, sighash::SignableInput, sighash_v5::v5_signature_hash,
},
};
pub mod roles;
pub mod common;
pub mod orchard;
pub mod sapling;
pub mod transparent;
const MAGIC_BYTES: &[u8] = b"PCZT";
const PCZT_VERSION_1: u32 = 1;
#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
pub struct Pczt {
#[getset(get = "pub")]
global: common::Global,
#[getset(get = "pub")]
transparent: transparent::Bundle,
#[getset(get = "pub")]
sapling: sapling::Bundle,
#[getset(get = "pub")]
orchard: orchard::Bundle,
}
impl Pczt {
pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
if bytes.len() < 8 {
return Err(ParseError::TooShort);
}
if &bytes[..4] != MAGIC_BYTES {
return Err(ParseError::NotPczt);
}
let version = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
if version != PCZT_VERSION_1 {
return Err(ParseError::UnknownVersion(version));
}
postcard::from_bytes(&bytes[8..]).map_err(ParseError::Invalid)
}
pub fn serialize(&self) -> Vec<u8> {
let mut bytes = vec![];
bytes.extend_from_slice(MAGIC_BYTES);
bytes.extend_from_slice(&PCZT_VERSION_1.to_le_bytes());
postcard::to_extend(self, bytes).expect("can serialize into memory")
}
#[cfg(any(feature = "io-finalizer", feature = "signer", feature = "tx-extractor"))]
pub(crate) fn extract_tx_data<A, E>(
self,
extract_transparent: impl FnOnce(
&::transparent::pczt::Bundle,
) -> Result<
Option<::transparent::bundle::Bundle<A::TransparentAuth>>,
E,
>,
extract_sapling: impl FnOnce(
&::sapling::pczt::Bundle,
) -> Result<
Option<::sapling::Bundle<A::SaplingAuth, zcash_protocol::value::ZatBalance>>,
E,
>,
extract_orchard: impl FnOnce(
&::orchard::pczt::Bundle,
) -> Result<
Option<::orchard::Bundle<A::OrchardAuth, zcash_protocol::value::ZatBalance>>,
E,
>,
) -> Result<ParsedPczt<A>, E>
where
A: Authorization,
E: From<ExtractError>,
{
let Pczt {
global,
transparent,
sapling,
orchard,
} = self;
let transparent = transparent
.into_parsed()
.map_err(ExtractError::TransparentParse)?;
let sapling = sapling.into_parsed().map_err(ExtractError::SaplingParse)?;
let orchard = orchard.into_parsed().map_err(ExtractError::OrchardParse)?;
let version = match (global.tx_version, global.version_group_id) {
(V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::V5),
(version, version_group_id) => Err(ExtractError::UnsupportedTxVersion {
version,
version_group_id,
}),
}?;
let consensus_branch_id = BranchId::try_from(global.consensus_branch_id)
.map_err(|_| ExtractError::UnknownConsensusBranchId)?;
let lock_time = determine_lock_time(&global, transparent.inputs())
.ok_or(ExtractError::IncompatibleLockTimes)?;
let transparent_bundle = extract_transparent(&transparent)?;
let sapling_bundle = extract_sapling(&sapling)?;
let orchard_bundle = extract_orchard(&orchard)?;
let tx_data = TransactionData::from_parts(
version,
consensus_branch_id,
lock_time,
global.expiry_height.into(),
#[cfg(all(
any(zcash_unstable = "nu7", zcash_unstable = "zfuture"),
feature = "zip-233"
))]
Zatoshis::ZERO,
transparent_bundle,
None,
sapling_bundle,
orchard_bundle,
);
Ok(ParsedPczt {
global,
transparent,
sapling,
orchard,
tx_data,
})
}
#[cfg(any(feature = "io-finalizer", feature = "signer"))]
pub fn into_effects(self) -> Result<TransactionData<EffectsOnly>, ExtractError> {
self.extract_tx_data(
|t| {
t.extract_effects()
.map_err(ExtractError::TransparentExtract)
},
|s| s.extract_effects().map_err(ExtractError::SaplingExtract),
|o| o.extract_effects().map_err(ExtractError::OrchardExtract),
)
.map(|parsed| parsed.tx_data)
}
}
#[cfg(any(feature = "io-finalizer", feature = "signer", feature = "tx-extractor"))]
#[cfg_attr(
not(any(feature = "io-finalizer", feature = "signer")),
allow(dead_code)
)]
pub(crate) struct ParsedPczt<A: Authorization> {
pub(crate) global: Global,
pub(crate) transparent: ::transparent::pczt::Bundle,
pub(crate) sapling: ::sapling::pczt::Bundle,
pub(crate) orchard: ::orchard::pczt::Bundle,
pub(crate) tx_data: TransactionData<A>,
}
#[cfg(any(feature = "io-finalizer", feature = "signer"))]
pub struct EffectsOnly;
#[cfg(any(feature = "io-finalizer", feature = "signer"))]
impl Authorization for EffectsOnly {
type TransparentAuth = ::transparent::bundle::EffectsOnly;
type SaplingAuth = ::sapling::bundle::EffectsOnly;
type OrchardAuth = ::orchard::bundle::EffectsOnly;
#[cfg(zcash_unstable = "zfuture")]
type TzeAuth = core::convert::Infallible;
}
#[cfg(any(feature = "io-finalizer", feature = "signer"))]
pub(crate) fn sighash(
tx_data: &TransactionData<EffectsOnly>,
signable_input: &SignableInput,
txid_parts: &TxDigests<Blake2bHash>,
) -> [u8; 32] {
v5_signature_hash(tx_data, signable_input, txid_parts)
.as_ref()
.try_into()
.expect("correct length")
}
#[cfg(any(feature = "io-finalizer", feature = "signer", feature = "tx-extractor"))]
#[derive(Debug)]
#[non_exhaustive]
pub enum ExtractError {
IncompatibleLockTimes,
OrchardExtract(::orchard::pczt::TxExtractorError),
OrchardParse(::orchard::pczt::ParseError),
SaplingExtract(::sapling::pczt::TxExtractorError),
SaplingParse(::sapling::pczt::ParseError),
TransparentExtract(::transparent::pczt::TxExtractorError),
TransparentParse(::transparent::pczt::ParseError),
UnknownConsensusBranchId,
UnsupportedTxVersion { version: u32, version_group_id: u32 },
}
#[derive(Debug)]
pub enum ParseError {
NotPczt,
Invalid(postcard::Error),
TooShort,
UnknownVersion(u32),
}