Connect `pczt` crate to `zcash_primitives` tx builder
This commit is contained in:
parent
ebacc0a8b8
commit
318254cc5c
|
@ -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"]
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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")]
|
||||
|
|
Loading…
Reference in New Issue