Connect `pczt` crate to `zcash_primitives` tx builder

This commit is contained in:
Jack Grigg 2024-12-03 13:21:48 +00:00
parent ebacc0a8b8
commit 318254cc5c
5 changed files with 185 additions and 0 deletions

View File

@ -52,6 +52,7 @@ sapling = [
"dep:zcash_protocol",
]
transparent = ["dep:secp256k1", "dep:zcash_primitives", "dep:zcash_protocol"]
zcp-builder = ["dep:zcash_primitives", "dep:zcash_protocol"]
io-finalizer = ["orchard", "sapling"]
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
signer = ["dep:blake2b_simd", "dep:rand_core", "orchard", "sapling", "transparent"]

View File

@ -43,6 +43,8 @@ mod orchard;
mod sapling;
mod transparent;
#[cfg(feature = "zcp-builder")]
const SAPLING_TX_VERSION: u32 = 4;
const V5_TX_VERSION: u32 = 5;
const V5_VERSION_GROUP_ID: u32 = 0x26A7270A;

View File

@ -86,4 +86,79 @@ impl Creator {
},
}
}
/// Builds a PCZT from the output of a [`Builder`].
///
/// Returns `None` if the `TxVersion` is incompatible with PCZTs.
///
/// [`Builder`]: zcash_primitives::transaction::builder::Builder
#[cfg(feature = "zcp-builder")]
pub fn build_from_parts<P: zcash_protocol::consensus::Parameters>(
parts: zcash_primitives::transaction::builder::PcztParts<P>,
) -> Option<Pczt> {
use zcash_primitives::transaction::sighash::SighashType;
use zcash_protocol::consensus::NetworkConstants;
use crate::{common::FLAG_HAS_SIGHASH_SINGLE, SAPLING_TX_VERSION};
let tx_version = match parts.version {
zcash_primitives::transaction::TxVersion::Sprout(_)
| zcash_primitives::transaction::TxVersion::Overwinter => None,
zcash_primitives::transaction::TxVersion::Sapling => Some(SAPLING_TX_VERSION),
zcash_primitives::transaction::TxVersion::Zip225 => Some(V5_TX_VERSION),
}?;
// Spends and outputs not modifiable.
let mut tx_modifiable = 0b0000_0000;
// Check if any input is using `SIGHASH_SINGLE`.
if parts.transparent.as_ref().map_or(false, |bundle| {
bundle
.inputs()
.iter()
.any(|input| input.sighash_type() == &SighashType::SINGLE)
}) {
tx_modifiable |= FLAG_HAS_SIGHASH_SINGLE;
}
Some(Pczt {
global: crate::common::Global {
tx_version,
version_group_id: parts.version.version_group_id(),
consensus_branch_id: parts.consensus_branch_id.into(),
fallback_lock_time: Some(parts.lock_time),
expiry_height: parts.expiry_height.into(),
coin_type: parts.params.network_type().coin_type(),
tx_modifiable,
proprietary: BTreeMap::new(),
},
transparent: parts
.transparent
.map(crate::transparent::Bundle::serialize_from)
.unwrap_or_else(|| crate::transparent::Bundle {
inputs: vec![],
outputs: vec![],
}),
sapling: parts
.sapling
.map(crate::sapling::Bundle::serialize_from)
.unwrap_or_else(|| crate::sapling::Bundle {
spends: vec![],
outputs: vec![],
value_sum: 0,
anchor: sapling::Anchor::empty_tree().to_bytes(),
bsk: None,
}),
orchard: parts
.orchard
.map(crate::orchard::Bundle::serialize_from)
.unwrap_or_else(|| crate::orchard::Bundle {
actions: vec![],
flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
value_sum: (0, true),
anchor: orchard::Anchor::empty_tree().to_bytes(),
zkproof: None,
bsk: None,
}),
})
}
}

View File

@ -10,6 +10,8 @@ and this library adheres to Rust's notion of
### Added
- `zcash_primitives::transaction`
- `TransactionData::try_map_bundles`
- `builder::{PcztResult, PcztParts}`
- `builder::Builder::build_for_pczt`
- `components::transparent`:
- `pczt` module.
- `EffectsOnly`

View File

@ -6,6 +6,7 @@ use std::fmt;
use std::sync::mpsc::Sender;
use rand::{CryptoRng, RngCore};
use zcash_protocol::consensus::Parameters;
use crate::{
consensus::{self, BlockHeight, BranchId, NetworkUpgrade},
@ -272,6 +273,30 @@ impl BuildResult {
}
}
/// The result of [`Builder::build_for_pczt`].
///
/// It includes the PCZT components along with metadata describing how spends and outputs
/// were shuffled in creating the transaction's shielded bundles.
#[derive(Debug)]
pub struct PcztResult<P: Parameters> {
pub pczt_parts: PcztParts<P>,
pub sapling_meta: SaplingMetadata,
pub orchard_meta: orchard::builder::BundleMetadata,
}
/// The components of a PCZT.
#[derive(Debug)]
pub struct PcztParts<P: Parameters> {
pub params: P,
pub version: TxVersion,
pub consensus_branch_id: BranchId,
pub lock_time: u32,
pub expiry_height: BlockHeight,
pub transparent: Option<transparent::pczt::Bundle>,
pub sapling: Option<sapling::pczt::Bundle>,
pub orchard: Option<orchard::pczt::Bundle>,
}
/// Generates a [`Transaction`] from its inputs and outputs.
pub struct Builder<'a, P, U: sapling::builder::ProverProgress> {
params: P,
@ -835,6 +860,86 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<
orchard_meta,
})
}
/// Builds a PCZT from the configured spends and outputs.
///
/// Upon success, returns a struct containing the PCZT components, and the
/// [`SaplingMetadata`] and [`orchard::builder::BundleMetadata`] generated during the
/// build process.
pub fn build_for_pczt<R: RngCore + CryptoRng, FR: FeeRule>(
self,
mut rng: R,
fee_rule: &FR,
) -> Result<PcztResult<P>, Error<FR::Error>> {
let fee = self.get_fee(fee_rule).map_err(Error::Fee)?;
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
// determine transaction version
let version = TxVersion::suggested_for_branch(consensus_branch_id);
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
//
// Consistency checks
//
// After fees are accounted for, the value balance of the transaction must be zero.
let balance_after_fees =
(self.value_balance()? - fee.into()).ok_or(BalanceError::Underflow)?;
match balance_after_fees.cmp(&Amount::zero()) {
Ordering::Less => {
return Err(Error::InsufficientFunds(-balance_after_fees));
}
Ordering::Greater => {
return Err(Error::ChangeRequired(balance_after_fees));
}
Ordering::Equal => (),
};
let transparent_bundle = self.transparent_builder.build_for_pczt();
let (sapling_bundle, sapling_meta) = match self
.sapling_builder
.map(|builder| {
builder
.build_for_pczt(&mut rng)
.map_err(Error::SaplingBuild)
})
.transpose()?
{
Some((bundle, meta)) => (Some(bundle), meta),
None => (None, SaplingMetadata::empty()),
};
let (orchard_bundle, orchard_meta) = match self
.orchard_builder
.map(|builder| {
builder
.build_for_pczt(&mut rng)
.map_err(Error::OrchardBuild)
})
.transpose()?
{
Some((bundle, meta)) => (Some(bundle), meta),
None => (None, orchard::builder::BundleMetadata::empty()),
};
Ok(PcztResult {
pczt_parts: PcztParts {
params: self.params,
version,
consensus_branch_id,
lock_time: 0,
expiry_height: self.expiry_height,
transparent: transparent_bundle,
sapling: sapling_bundle,
orchard: orchard_bundle,
},
sapling_meta,
orchard_meta,
})
}
}
#[cfg(zcash_unstable = "zfuture")]