zcash_primitives: Add Orchard to the transaction builder
Closes zcash/librustzcash#406.
This commit is contained in:
parent
37fc28634e
commit
4bbcd8325a
|
@ -19,6 +19,7 @@ use crate::{
|
|||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, DEFAULT_FEE},
|
||||
orchard::builder::{WithOrchard, WithoutOrchard},
|
||||
sapling::{
|
||||
self,
|
||||
builder::{SaplingBuilder, SaplingMetadata},
|
||||
|
@ -49,13 +50,14 @@ use crate::sapling::prover::mock::MockTxProver;
|
|||
|
||||
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ChangeIsNegative(Amount),
|
||||
InvalidAmount,
|
||||
NoChangeAddress,
|
||||
TransparentBuild(transparent::builder::Error),
|
||||
SaplingBuild(sapling::builder::Error),
|
||||
OrchardBuild(orchard::builder::Error),
|
||||
#[cfg(feature = "zfuture")]
|
||||
TzeBuild(tze::builder::Error),
|
||||
}
|
||||
|
@ -70,6 +72,7 @@ impl fmt::Display for Error {
|
|||
Error::NoChangeAddress => write!(f, "No change address specified or discoverable"),
|
||||
Error::TransparentBuild(err) => err.fmt(f),
|
||||
Error::SaplingBuild(err) => err.fmt(f),
|
||||
Error::OrchardBuild(err) => write!(f, "{:?}", err),
|
||||
#[cfg(feature = "zfuture")]
|
||||
Error::TzeBuild(err) => err.fmt(f),
|
||||
}
|
||||
|
@ -112,7 +115,7 @@ enum ChangeAddress {
|
|||
}
|
||||
|
||||
/// Generates a [`Transaction`] from its inputs and outputs.
|
||||
pub struct Builder<'a, P, R> {
|
||||
pub struct Builder<'a, P, R, O = WithoutOrchard> {
|
||||
params: P,
|
||||
rng: R,
|
||||
target_height: BlockHeight,
|
||||
|
@ -120,6 +123,7 @@ pub struct Builder<'a, P, R> {
|
|||
fee: Amount,
|
||||
transparent_builder: TransparentBuilder,
|
||||
sapling_builder: SaplingBuilder<P>,
|
||||
orchard_builder: O,
|
||||
change_address: Option<ChangeAddress>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder<'a, TransactionData<Unauthorized>>,
|
||||
|
@ -143,6 +147,30 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng, WithOrchard> {
|
||||
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
|
||||
/// using default values for general transaction fields and the default OS random.
|
||||
///
|
||||
/// # Default values
|
||||
///
|
||||
/// The expiry height will be set to the given height plus the default transaction
|
||||
/// expiry delta (20 blocks).
|
||||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
pub fn with_orchard_anchor(
|
||||
params: P,
|
||||
target_height: BlockHeight,
|
||||
anchor: orchard::tree::Anchor,
|
||||
) -> Self {
|
||||
Builder::new_internal(
|
||||
params,
|
||||
target_height,
|
||||
OsRng,
|
||||
WithOrchard::new(params, target_height, anchor),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||
/// Creates a new `Builder` targeted for inclusion in the block with the given height
|
||||
/// and randomness source, using default values for general transaction fields.
|
||||
|
@ -154,16 +182,21 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
pub fn new_with_rng(params: P, target_height: BlockHeight, rng: R) -> Builder<'a, P, R> {
|
||||
Self::new_internal(params, target_height, rng)
|
||||
Self::new_internal(params, target_height, rng, WithoutOrchard)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
||||
impl<'a, P: consensus::Parameters, R: RngCore, O> Builder<'a, P, R, O> {
|
||||
/// Common utility function for builder construction.
|
||||
///
|
||||
/// WARNING: THIS MUST REMAIN PRIVATE AS IT ALLOWS CONSTRUCTION
|
||||
/// OF BUILDERS WITH NON-CryptoRng RNGs
|
||||
fn new_internal(params: P, target_height: BlockHeight, rng: R) -> Builder<'a, P, R> {
|
||||
/// WARNING: THIS MUST REMAIN PRIVATE AS IT ALLOWS CONSTRUCTION OF BUILDERS WITH
|
||||
/// NON-CryptoRng RNGs. WE RELY ON THIS BEING PRIVATE BELOW IN `ThisIsAReallyBadIdea`.
|
||||
fn new_internal(
|
||||
params: P,
|
||||
target_height: BlockHeight,
|
||||
rng: R,
|
||||
orchard_builder: O,
|
||||
) -> Self {
|
||||
Builder {
|
||||
params: params.clone(),
|
||||
rng,
|
||||
|
@ -172,6 +205,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
fee: DEFAULT_FEE,
|
||||
transparent_builder: TransparentBuilder::empty(),
|
||||
sapling_builder: SaplingBuilder::new(params, target_height),
|
||||
orchard_builder,
|
||||
change_address: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder::empty(),
|
||||
|
@ -180,7 +214,46 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
progress_notifier: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R, WithOrchard> {
|
||||
// TODO: Change the way the builder is constructed so we don't even expose these
|
||||
// functions if you don't provide an Orchard anchor.
|
||||
/// Adds a note to be spent in this bundle.
|
||||
///
|
||||
/// Returns an error if the given Merkle path does not have the required anchor for
|
||||
/// the given note.
|
||||
pub fn add_orchard_spend(
|
||||
&mut self,
|
||||
fvk: orchard::keys::FullViewingKey,
|
||||
note: orchard::Note,
|
||||
merkle_path: orchard::tree::MerklePath,
|
||||
) -> Result<(), Error> {
|
||||
self.orchard_builder
|
||||
.add_spend(fvk, note, merkle_path)
|
||||
.map_err(Error::OrchardBuild)
|
||||
}
|
||||
|
||||
/// Adds an Orchard address which will receive funds in this transaction.
|
||||
pub fn add_orchard_output(
|
||||
&mut self,
|
||||
ovk: Option<orchard::keys::OutgoingViewingKey>,
|
||||
recipient: orchard::Address,
|
||||
value: Amount,
|
||||
memo: MemoBytes,
|
||||
) -> Result<(), Error> {
|
||||
self.orchard_builder
|
||||
.add_recipient(
|
||||
ovk,
|
||||
recipient,
|
||||
orchard::value::NoteValue::from(value),
|
||||
Some(*memo.as_array()),
|
||||
)
|
||||
.map_err(Error::OrchardBuild)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters, R: RngCore, O> Builder<'a, P, R, O> {
|
||||
/// Adds a Sapling note to be spent in this transaction.
|
||||
///
|
||||
/// Returns an error if the given Merkle path does not have the same anchor as the
|
||||
|
@ -331,6 +404,13 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
)
|
||||
.map_err(Error::SaplingBuild)?;
|
||||
|
||||
let orchard_bundle: Option<orchard::Bundle<_, Amount>> =
|
||||
if let Some(builder) = self.orchard_builder {
|
||||
Some(builder.build(&mut rng).map_err(Error::OrchardBuild)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let (tze_bundle, tze_signers) = self.tze_builder.build();
|
||||
|
||||
|
@ -342,7 +422,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle,
|
||||
orchard_bundle: None,
|
||||
orchard_bundle,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle,
|
||||
};
|
||||
|
@ -387,6 +467,41 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
None => (None, SaplingMetadata::empty()),
|
||||
};
|
||||
|
||||
// This is only safe because Builder::new_internal is a crate-private constructor.
|
||||
struct ThisIsAReallyBadIdea<'a, R: RngCore>(&'a mut R);
|
||||
impl<'a, R: RngCore> CryptoRng for ThisIsAReallyBadIdea<'a, R> {}
|
||||
impl<'a, R: RngCore> RngCore for ThisIsAReallyBadIdea<'a, R> {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.0.next_u64()
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
self.0.fill_bytes(dest)
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||
self.0.try_fill_bytes(dest)
|
||||
}
|
||||
}
|
||||
|
||||
let orchard_bundle = unauthed_tx
|
||||
.orchard_bundle
|
||||
.map(|b| {
|
||||
b.create_proof(pk, &mut rng).and_then(|b| {
|
||||
b.apply_signatures(
|
||||
ThisIsAReallyBadIdea(&mut rng),
|
||||
*shielded_sig_commitment.as_ref(),
|
||||
orchard_sk,
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
.map_err(Error::OrchardBuild)?;
|
||||
|
||||
let authorized_tx = TransactionData {
|
||||
version: unauthed_tx.version,
|
||||
consensus_branch_id: unauthed_tx.consensus_branch_id,
|
||||
|
@ -395,7 +510,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
|
|||
transparent_bundle,
|
||||
sprout_bundle: unauthed_tx.sprout_bundle,
|
||||
sapling_bundle,
|
||||
orchard_bundle: None,
|
||||
orchard_bundle,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_bundle,
|
||||
};
|
||||
|
@ -529,6 +644,7 @@ mod tests {
|
|||
fee: Amount::zero(),
|
||||
transparent_builder: TransparentBuilder::empty(),
|
||||
sapling_builder: SaplingBuilder::new(TEST_NETWORK, sapling_activation_height),
|
||||
orchard_builder: None,
|
||||
change_address: None,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_builder: TzeBuilder::empty(),
|
||||
|
|
|
@ -16,17 +16,14 @@ use zcash_encoding::{Array, CompactSize, Vector};
|
|||
use super::Amount;
|
||||
use crate::transaction::Transaction;
|
||||
|
||||
pub mod builder;
|
||||
|
||||
pub const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
|
||||
pub const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
|
||||
pub const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
|
||||
|
||||
/// Marker for a bundle with no proofs or signatures.
|
||||
#[derive(Debug)]
|
||||
pub struct Unauthorized;
|
||||
|
||||
impl Authorization for Unauthorized {
|
||||
type SpendAuth = ();
|
||||
}
|
||||
pub type Unauthorized = orchard::builder::InProgress<orchard::builder::Unproven, orchard::builder::Unauthorized>;
|
||||
|
||||
pub trait MapAuth<A: Authorization, B: Authorization> {
|
||||
fn map_spend_auth(&self, s: A::SpendAuth) -> B::SpendAuth;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
use crate::consensus::{self, BlockHeight, NetworkUpgrade};
|
||||
|
||||
pub struct WithoutOrchard;
|
||||
|
||||
pub struct WithOrchard(Option<orchard::builder::Builder>);
|
||||
|
||||
impl WithOrchard {
|
||||
pub(crate) fn new<P: consensus::Parameters>(
|
||||
params: P,
|
||||
target_height: BlockHeight,
|
||||
anchor: orchard::tree::Anchor,
|
||||
) -> Self {
|
||||
let orchard_builder = if params.is_nu_active(NetworkUpgrade::Nu5, target_height) {
|
||||
Some(orchard::builder::Builder::new(
|
||||
orchard::bundle::Flags::from_parts(true, true),
|
||||
anchor,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self(orchard_builder)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue