Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 125 additions & 9 deletions zcash_primitives/src/transaction/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::{
transaction::{
components::{
amount::{Amount, DEFAULT_FEE},
orchard::builder::{WithOrchard, WithoutOrchard},
sapling::{
self,
builder::{SaplingBuilder, SaplingMetadata},
Expand Down Expand Up @@ -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),
}
Expand All @@ -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),
}
Expand Down Expand Up @@ -112,14 +115,15 @@ 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,
expiry_height: BlockHeight,
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>>,
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -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(),
Expand All @@ -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
Expand Down Expand Up @@ -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();

Expand All @@ -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,
};
Expand Down Expand Up @@ -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,
Expand All @@ -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,
};
Expand Down Expand Up @@ -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(),
Expand Down
9 changes: 3 additions & 6 deletions zcash_primitives/src/transaction/components/orchard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions zcash_primitives/src/transaction/components/orchard/builder.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}