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",
|
"dep:zcash_protocol",
|
||||||
]
|
]
|
||||||
transparent = ["dep:secp256k1", "dep:zcash_primitives", "dep:zcash_protocol"]
|
transparent = ["dep:secp256k1", "dep:zcash_primitives", "dep:zcash_protocol"]
|
||||||
|
zcp-builder = ["dep:zcash_primitives", "dep:zcash_protocol"]
|
||||||
io-finalizer = ["orchard", "sapling"]
|
io-finalizer = ["orchard", "sapling"]
|
||||||
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
|
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
|
||||||
signer = ["dep:blake2b_simd", "dep:rand_core", "orchard", "sapling", "transparent"]
|
signer = ["dep:blake2b_simd", "dep:rand_core", "orchard", "sapling", "transparent"]
|
||||||
|
|
|
@ -43,6 +43,8 @@ mod orchard;
|
||||||
mod sapling;
|
mod sapling;
|
||||||
mod transparent;
|
mod transparent;
|
||||||
|
|
||||||
|
#[cfg(feature = "zcp-builder")]
|
||||||
|
const SAPLING_TX_VERSION: u32 = 4;
|
||||||
const V5_TX_VERSION: u32 = 5;
|
const V5_TX_VERSION: u32 = 5;
|
||||||
const V5_VERSION_GROUP_ID: u32 = 0x26A7270A;
|
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
|
### Added
|
||||||
- `zcash_primitives::transaction`
|
- `zcash_primitives::transaction`
|
||||||
- `TransactionData::try_map_bundles`
|
- `TransactionData::try_map_bundles`
|
||||||
|
- `builder::{PcztResult, PcztParts}`
|
||||||
|
- `builder::Builder::build_for_pczt`
|
||||||
- `components::transparent`:
|
- `components::transparent`:
|
||||||
- `pczt` module.
|
- `pczt` module.
|
||||||
- `EffectsOnly`
|
- `EffectsOnly`
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::fmt;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
|
use zcash_protocol::consensus::Parameters;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus::{self, BlockHeight, BranchId, NetworkUpgrade},
|
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.
|
/// Generates a [`Transaction`] from its inputs and outputs.
|
||||||
pub struct Builder<'a, P, U: sapling::builder::ProverProgress> {
|
pub struct Builder<'a, P, U: sapling::builder::ProverProgress> {
|
||||||
params: P,
|
params: P,
|
||||||
|
@ -835,6 +860,86 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<
|
||||||
orchard_meta,
|
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")]
|
#[cfg(zcash_unstable = "zfuture")]
|
||||||
|
|
Loading…
Reference in New Issue