2018-11-20 05:37:21 -08:00
|
|
|
//! Structs for building transactions.
|
|
|
|
|
2019-03-08 19:20:32 -08:00
|
|
|
use std::error;
|
|
|
|
use std::fmt;
|
2021-04-26 10:14:01 -07:00
|
|
|
use std::sync::mpsc::Sender;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2021-05-12 23:16:40 -07:00
|
|
|
#[cfg(not(feature = "zfuture"))]
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
|
|
|
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
2021-03-30 15:07:52 -07:00
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
use crate::{
|
2021-06-02 12:45:29 -07:00
|
|
|
consensus::{self, BlockHeight, BranchId},
|
2022-01-27 07:54:27 -08:00
|
|
|
keys::OutgoingViewingKey,
|
2019-05-24 05:32:55 -07:00
|
|
|
legacy::TransparentAddress,
|
2020-10-29 09:48:26 -07:00
|
|
|
memo::MemoBytes,
|
2020-02-07 16:25:24 -08:00
|
|
|
merkle_tree::MerklePath,
|
2022-01-27 08:15:10 -08:00
|
|
|
sapling::{prover::TxProver, Diversifier, Node, Note, PaymentAddress},
|
2018-11-20 05:37:21 -08:00
|
|
|
transaction::{
|
2020-05-06 16:52:03 -07:00
|
|
|
components::{
|
2021-03-04 13:40:45 -08:00
|
|
|
amount::{Amount, DEFAULT_FEE},
|
2022-05-19 04:41:25 -07:00
|
|
|
orchard::builder::{WithOrchard, WithoutOrchard},
|
2021-04-07 14:58:30 -07:00
|
|
|
sapling::{
|
|
|
|
self,
|
|
|
|
builder::{SaplingBuilder, SaplingMetadata},
|
|
|
|
},
|
2021-04-12 17:55:16 -07:00
|
|
|
transparent::{self, builder::TransparentBuilder},
|
2020-05-06 16:52:03 -07:00
|
|
|
},
|
2022-01-06 11:56:12 -08:00
|
|
|
sighash::{signature_hash, SignableInput},
|
2021-05-12 23:16:40 -07:00
|
|
|
txid::TxIdDigester,
|
2021-05-12 16:01:17 -07:00
|
|
|
Transaction, TransactionData, TxVersion, Unauthorized,
|
2018-11-20 05:37:21 -08:00
|
|
|
},
|
2020-05-06 16:52:03 -07:00
|
|
|
zip32::ExtendedSpendingKey,
|
2018-11-20 05:37:21 -08:00
|
|
|
};
|
|
|
|
|
2019-07-31 08:20:13 -07:00
|
|
|
#[cfg(feature = "transparent-inputs")]
|
2021-06-03 16:27:15 -07:00
|
|
|
use crate::transaction::components::transparent::TxOut;
|
2019-07-31 08:20:13 -07:00
|
|
|
|
2020-09-23 09:53:48 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
|
|
|
use crate::{
|
2021-06-03 17:25:41 -07:00
|
|
|
extensions::transparent::{ExtensionTxBuilder, ToPayload},
|
2021-04-01 17:10:01 -07:00
|
|
|
transaction::components::{
|
2021-04-16 11:38:13 -07:00
|
|
|
tze::builder::TzeBuilder,
|
|
|
|
tze::{self, TzeOut},
|
2021-04-01 17:10:01 -07:00
|
|
|
},
|
2020-09-23 09:53:48 -07:00
|
|
|
};
|
|
|
|
|
2020-10-21 11:37:21 -07:00
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
2021-03-04 09:48:04 -08:00
|
|
|
use crate::sapling::prover::mock::MockTxProver;
|
2020-10-21 11:37:21 -07:00
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
|
|
|
|
|
2022-05-19 04:41:25 -07:00
|
|
|
#[derive(Debug)]
|
2019-08-13 07:10:57 -07:00
|
|
|
pub enum Error {
|
2019-07-25 12:53:42 -07:00
|
|
|
ChangeIsNegative(Amount),
|
2018-11-20 05:37:21 -08:00
|
|
|
InvalidAmount,
|
|
|
|
NoChangeAddress,
|
2021-04-07 14:58:30 -07:00
|
|
|
TransparentBuild(transparent::builder::Error),
|
|
|
|
SaplingBuild(sapling::builder::Error),
|
2022-05-19 04:41:25 -07:00
|
|
|
OrchardBuild(orchard::builder::Error),
|
2021-04-01 17:10:01 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-04-07 14:58:30 -07:00
|
|
|
TzeBuild(tze::builder::Error),
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2019-03-08 19:20:32 -08:00
|
|
|
impl fmt::Display for Error {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Error::ChangeIsNegative(amount) => {
|
|
|
|
write!(f, "Change is negative ({:?} zatoshis)", amount)
|
|
|
|
}
|
|
|
|
Error::InvalidAmount => write!(f, "Invalid amount"),
|
|
|
|
Error::NoChangeAddress => write!(f, "No change address specified or discoverable"),
|
2021-06-02 15:48:55 -07:00
|
|
|
Error::TransparentBuild(err) => err.fmt(f),
|
|
|
|
Error::SaplingBuild(err) => err.fmt(f),
|
2022-05-19 04:41:25 -07:00
|
|
|
Error::OrchardBuild(err) => write!(f, "{:?}", err),
|
2021-04-01 17:10:01 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-06-02 15:48:55 -07:00
|
|
|
Error::TzeBuild(err) => err.fmt(f),
|
2019-03-08 19:20:32 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl error::Error for Error {}
|
|
|
|
|
2021-05-18 05:45:48 -07:00
|
|
|
/// Reports on the progress made by the builder towards building a transaction.
|
2021-04-26 10:14:01 -07:00
|
|
|
pub struct Progress {
|
2021-05-18 05:45:48 -07:00
|
|
|
/// The number of steps completed.
|
2021-04-26 10:14:01 -07:00
|
|
|
cur: u32,
|
2021-05-18 05:45:48 -07:00
|
|
|
/// The expected total number of steps (as of this progress update), if known.
|
2021-04-26 10:14:01 -07:00
|
|
|
end: Option<u32>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Progress {
|
2021-06-01 07:53:53 -07:00
|
|
|
pub fn new(cur: u32, end: Option<u32>) -> Self {
|
2021-04-26 10:14:01 -07:00
|
|
|
Self { cur, end }
|
|
|
|
}
|
|
|
|
|
2021-05-18 05:45:48 -07:00
|
|
|
/// Returns the number of steps completed so far while building the transaction.
|
|
|
|
///
|
|
|
|
/// Note that each step may not be of the same complexity/duration.
|
2021-04-26 10:14:01 -07:00
|
|
|
pub fn cur(&self) -> u32 {
|
|
|
|
self.cur
|
|
|
|
}
|
|
|
|
|
2021-05-18 05:45:48 -07:00
|
|
|
/// Returns the total expected number of steps before this transaction will be ready,
|
|
|
|
/// or `None` if the end is unknown as of this progress update.
|
|
|
|
///
|
|
|
|
/// Note that each step may not be of the same complexity/duration.
|
2021-04-26 10:14:01 -07:00
|
|
|
pub fn end(&self) -> Option<u32> {
|
|
|
|
self.end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 11:35:18 -07:00
|
|
|
enum ChangeAddress {
|
2021-06-02 12:45:29 -07:00
|
|
|
SaplingChangeAddress(OutgoingViewingKey, PaymentAddress),
|
2021-06-02 11:35:18 -07:00
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
/// Generates a [`Transaction`] from its inputs and outputs.
|
2022-05-19 04:41:25 -07:00
|
|
|
pub struct Builder<'a, P, R, O = WithoutOrchard> {
|
2020-08-05 13:27:40 -07:00
|
|
|
params: P,
|
2019-06-12 15:11:51 -07:00
|
|
|
rng: R,
|
2021-04-01 13:08:17 -07:00
|
|
|
target_height: BlockHeight,
|
|
|
|
expiry_height: BlockHeight,
|
2018-11-20 05:37:21 -08:00
|
|
|
fee: Amount,
|
2021-04-01 13:08:17 -07:00
|
|
|
transparent_builder: TransparentBuilder,
|
|
|
|
sapling_builder: SaplingBuilder<P>,
|
2022-05-19 04:41:25 -07:00
|
|
|
orchard_builder: O,
|
2021-06-02 11:35:18 -07:00
|
|
|
change_address: Option<ChangeAddress>,
|
2020-09-23 09:53:48 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-03-30 15:07:52 -07:00
|
|
|
tze_builder: TzeBuilder<'a, TransactionData<Unauthorized>>,
|
2021-04-01 13:08:17 -07:00
|
|
|
#[cfg(not(feature = "zfuture"))]
|
|
|
|
tze_builder: PhantomData<&'a ()>,
|
2021-05-26 13:58:08 -07:00
|
|
|
progress_notifier: Option<Sender<Progress>>,
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-05-06 16:52:03 -07:00
|
|
|
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
|
2020-08-05 20:36:02 -07:00
|
|
|
/// 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).
|
2021-04-01 13:08:17 -07:00
|
|
|
pub fn new(params: P, target_height: BlockHeight) -> Self {
|
|
|
|
Builder::new_with_rng(params, target_height, OsRng)
|
2020-05-27 11:09:59 -07:00
|
|
|
}
|
2020-08-05 20:36:02 -07:00
|
|
|
}
|
|
|
|
|
2022-05-19 04:41:25 -07:00
|
|
|
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),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 16:52:03 -07:00
|
|
|
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
2020-09-08 15:53:27 -07:00
|
|
|
/// Creates a new `Builder` targeted for inclusion in the block with the given height
|
|
|
|
/// and randomness source, using default values for general transaction fields.
|
|
|
|
///
|
|
|
|
/// # 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).
|
2021-04-01 13:08:17 -07:00
|
|
|
pub fn new_with_rng(params: P, target_height: BlockHeight, rng: R) -> Builder<'a, P, R> {
|
2022-05-19 04:41:25 -07:00
|
|
|
Self::new_internal(params, target_height, rng, WithoutOrchard)
|
2020-09-08 15:53:27 -07:00
|
|
|
}
|
2020-10-21 11:37:21 -07:00
|
|
|
}
|
2020-09-08 15:53:27 -07:00
|
|
|
|
2022-05-19 04:41:25 -07:00
|
|
|
impl<'a, P: consensus::Parameters, R: RngCore, O> Builder<'a, P, R, O> {
|
2020-09-08 15:53:27 -07:00
|
|
|
/// Common utility function for builder construction.
|
2020-10-21 11:37:21 -07:00
|
|
|
///
|
2022-05-19 04:41:25 -07:00
|
|
|
/// 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 {
|
2018-11-20 05:37:21 -08:00
|
|
|
Builder {
|
2021-06-02 15:57:27 -07:00
|
|
|
params: params.clone(),
|
2019-06-12 15:11:51 -07:00
|
|
|
rng,
|
2021-04-01 13:08:17 -07:00
|
|
|
target_height,
|
|
|
|
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
|
2018-11-20 05:37:21 -08:00
|
|
|
fee: DEFAULT_FEE,
|
2021-05-11 13:17:54 -07:00
|
|
|
transparent_builder: TransparentBuilder::empty(),
|
2021-04-07 14:58:30 -07:00
|
|
|
sapling_builder: SaplingBuilder::new(params, target_height),
|
2022-05-19 04:41:25 -07:00
|
|
|
orchard_builder,
|
2021-06-02 11:35:18 -07:00
|
|
|
change_address: None,
|
2020-09-23 09:53:48 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-04-01 17:10:01 -07:00
|
|
|
tze_builder: TzeBuilder::empty(),
|
2021-04-01 13:08:17 -07:00
|
|
|
#[cfg(not(feature = "zfuture"))]
|
|
|
|
tze_builder: PhantomData,
|
2021-05-26 13:58:08 -07:00
|
|
|
progress_notifier: None,
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
}
|
2022-05-19 04:41:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2022-05-19 04:41:25 -07:00
|
|
|
impl<'a, P: consensus::Parameters, R: RngCore, O> Builder<'a, P, R, O> {
|
2018-11-20 05:37:21 -08:00
|
|
|
/// Adds a Sapling note to be spent in this transaction.
|
|
|
|
///
|
2020-02-07 16:25:24 -08:00
|
|
|
/// Returns an error if the given Merkle path does not have the same anchor as the
|
|
|
|
/// paths for previous Sapling notes.
|
2018-11-20 05:37:21 -08:00
|
|
|
pub fn add_sapling_spend(
|
|
|
|
&mut self,
|
|
|
|
extsk: ExtendedSpendingKey,
|
|
|
|
diversifier: Diversifier,
|
2020-07-01 13:26:54 -07:00
|
|
|
note: Note,
|
2020-02-07 16:25:24 -08:00
|
|
|
merkle_path: MerklePath<Node>,
|
2018-11-20 05:37:21 -08:00
|
|
|
) -> Result<(), Error> {
|
2021-04-01 13:08:17 -07:00
|
|
|
self.sapling_builder
|
|
|
|
.add_spend(&mut self.rng, extsk, diversifier, note, merkle_path)
|
2021-06-02 15:48:55 -07:00
|
|
|
.map_err(Error::SaplingBuild)
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a Sapling address to send funds to.
|
2020-08-05 20:36:02 -07:00
|
|
|
pub fn add_sapling_output(
|
2018-11-20 05:37:21 -08:00
|
|
|
&mut self,
|
2020-08-28 08:12:37 -07:00
|
|
|
ovk: Option<OutgoingViewingKey>,
|
2020-07-01 13:26:54 -07:00
|
|
|
to: PaymentAddress,
|
2018-11-20 05:37:21 -08:00
|
|
|
value: Amount,
|
2021-03-26 17:39:43 -07:00
|
|
|
memo: MemoBytes,
|
2018-11-20 05:37:21 -08:00
|
|
|
) -> Result<(), Error> {
|
2021-04-01 16:35:32 -07:00
|
|
|
self.sapling_builder
|
2021-06-02 15:57:27 -07:00
|
|
|
.add_output(&mut self.rng, ovk, to, value, memo)
|
2021-06-02 15:48:55 -07:00
|
|
|
.map_err(Error::SaplingBuild)
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2019-07-31 08:20:13 -07:00
|
|
|
/// Adds a transparent coin to be spent in this transaction.
|
|
|
|
#[cfg(feature = "transparent-inputs")]
|
2020-08-22 22:56:03 -07:00
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))]
|
2019-07-31 08:20:13 -07:00
|
|
|
pub fn add_transparent_input(
|
|
|
|
&mut self,
|
|
|
|
sk: secp256k1::SecretKey,
|
2021-04-07 14:58:30 -07:00
|
|
|
utxo: transparent::OutPoint,
|
2019-07-31 08:20:13 -07:00
|
|
|
coin: TxOut,
|
|
|
|
) -> Result<(), Error> {
|
2021-05-11 13:17:54 -07:00
|
|
|
self.transparent_builder
|
|
|
|
.add_input(sk, utxo, coin)
|
2021-06-02 15:48:55 -07:00
|
|
|
.map_err(Error::TransparentBuild)
|
2019-07-31 08:20:13 -07:00
|
|
|
}
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
/// Adds a transparent address to send funds to.
|
|
|
|
pub fn add_transparent_output(
|
|
|
|
&mut self,
|
|
|
|
to: &TransparentAddress,
|
|
|
|
value: Amount,
|
|
|
|
) -> Result<(), Error> {
|
2021-05-11 13:17:54 -07:00
|
|
|
self.transparent_builder
|
|
|
|
.add_output(to, value)
|
2021-06-02 15:48:55 -07:00
|
|
|
.map_err(Error::TransparentBuild)
|
2019-05-24 05:32:55 -07:00
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
/// Sets the Sapling address to which any change will be sent.
|
|
|
|
///
|
|
|
|
/// By default, change is sent to the Sapling address corresponding to the first note
|
|
|
|
/// being spent (i.e. the first call to [`Builder::add_sapling_spend`]).
|
2020-07-01 13:26:54 -07:00
|
|
|
pub fn send_change_to(&mut self, ovk: OutgoingViewingKey, to: PaymentAddress) {
|
2021-06-02 11:35:18 -07:00
|
|
|
self.change_address = Some(ChangeAddress::SaplingChangeAddress(ovk, to))
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2021-05-26 13:58:08 -07:00
|
|
|
/// Sets the notifier channel, where progress of building the transaction is sent.
|
2018-11-20 05:37:21 -08:00
|
|
|
///
|
2021-05-26 13:58:08 -07:00
|
|
|
/// An update is sent after every Spend or Output is computed, and the `u32` sent
|
|
|
|
/// represents the total steps completed so far. It will eventually send number of
|
|
|
|
/// spends + outputs. If there's an error building the transaction, the channel is
|
|
|
|
/// closed.
|
|
|
|
pub fn with_progress_notifier(&mut self, progress_notifier: Sender<Progress>) {
|
|
|
|
self.progress_notifier = Some(progress_notifier);
|
2021-04-26 10:14:01 -07:00
|
|
|
}
|
|
|
|
|
2021-06-02 16:03:07 -07:00
|
|
|
/// Returns the sum of the transparent, Sapling, and TZE value balances.
|
|
|
|
fn value_balance(&self) -> Result<Amount, Error> {
|
2021-06-02 12:45:29 -07:00
|
|
|
let value_balances = [
|
|
|
|
self.transparent_builder
|
|
|
|
.value_balance()
|
|
|
|
.ok_or(Error::InvalidAmount)?,
|
|
|
|
self.sapling_builder.value_balance(),
|
|
|
|
#[cfg(feature = "zfuture")]
|
|
|
|
self.tze_builder
|
|
|
|
.value_balance()
|
|
|
|
.ok_or(Error::InvalidAmount)?,
|
|
|
|
];
|
|
|
|
|
2022-02-01 13:02:07 -08:00
|
|
|
IntoIterator::into_iter(&value_balances)
|
2021-06-02 12:45:29 -07:00
|
|
|
.sum::<Option<_>>()
|
|
|
|
.ok_or(Error::InvalidAmount)
|
|
|
|
}
|
|
|
|
|
2021-04-26 10:14:01 -07:00
|
|
|
/// Builds a transaction from the configured spends and outputs.
|
|
|
|
///
|
|
|
|
/// Upon success, returns a tuple containing the final transaction, and the
|
2021-04-01 13:08:17 -07:00
|
|
|
/// [`SaplingMetadata`] generated during the build process.
|
2021-05-26 13:58:08 -07:00
|
|
|
pub fn build(
|
2018-11-20 05:37:21 -08:00
|
|
|
mut self,
|
2020-02-07 16:37:31 -08:00
|
|
|
prover: &impl TxProver,
|
2021-04-01 13:08:17 -07:00
|
|
|
) -> Result<(Transaction, SaplingMetadata), Error> {
|
2021-06-02 12:45:29 -07:00
|
|
|
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
|
|
|
|
|
|
|
|
// determine transaction version
|
|
|
|
let version = TxVersion::suggested_for_branch(consensus_branch_id);
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
//
|
|
|
|
// Consistency checks
|
|
|
|
//
|
|
|
|
|
|
|
|
// Valid change
|
2021-06-02 12:45:29 -07:00
|
|
|
let change = (self.value_balance()? - self.fee).ok_or(Error::InvalidAmount)?;
|
2020-05-06 16:52:03 -07:00
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
if change.is_negative() {
|
2019-08-13 07:10:57 -07:00
|
|
|
return Err(Error::ChangeIsNegative(change));
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Change output
|
|
|
|
//
|
|
|
|
|
|
|
|
if change.is_positive() {
|
|
|
|
// Send change to the specified change address. If no change address
|
|
|
|
// was set, send change to the first Sapling address given as input.
|
2021-06-02 11:35:18 -07:00
|
|
|
match self.change_address.take() {
|
|
|
|
Some(ChangeAddress::SaplingChangeAddress(ovk, addr)) => {
|
2021-08-12 10:21:30 -07:00
|
|
|
self.add_sapling_output(Some(ovk), addr, change, MemoBytes::empty())?;
|
2021-06-02 11:35:18 -07:00
|
|
|
}
|
|
|
|
None => {
|
2021-06-02 12:45:29 -07:00
|
|
|
let (ovk, addr) = self
|
|
|
|
.sapling_builder
|
|
|
|
.get_candidate_change_address()
|
2021-06-02 15:48:55 -07:00
|
|
|
.ok_or(Error::NoChangeAddress)?;
|
2021-08-12 10:21:30 -07:00
|
|
|
self.add_sapling_output(Some(ovk), addr, change, MemoBytes::empty())?;
|
2021-06-02 11:35:18 -07:00
|
|
|
}
|
|
|
|
}
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2021-04-12 17:55:16 -07:00
|
|
|
let transparent_bundle = self.transparent_builder.build();
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2021-06-03 19:21:10 -07:00
|
|
|
let mut rng = self.rng;
|
2018-11-20 05:37:21 -08:00
|
|
|
let mut ctx = prover.new_sapling_proving_context();
|
2021-06-03 19:21:10 -07:00
|
|
|
let sapling_bundle = self
|
2021-04-01 16:35:32 -07:00
|
|
|
.sapling_builder
|
|
|
|
.build(
|
|
|
|
prover,
|
|
|
|
&mut ctx,
|
2021-06-03 19:21:10 -07:00
|
|
|
&mut rng,
|
2021-04-01 16:35:32 -07:00
|
|
|
self.target_height,
|
2021-06-01 07:53:53 -07:00
|
|
|
self.progress_notifier.as_ref(),
|
2021-04-01 16:35:32 -07:00
|
|
|
)
|
2021-06-02 15:48:55 -07:00
|
|
|
.map_err(Error::SaplingBuild)?;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2022-05-19 04:41:25 -07:00
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2021-04-01 13:08:17 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-06-03 17:25:41 -07:00
|
|
|
let (tze_bundle, tze_signers) = self.tze_builder.build();
|
2021-04-26 10:14:01 -07:00
|
|
|
|
2021-06-03 19:21:10 -07:00
|
|
|
let unauthed_tx: TransactionData<Unauthorized> = TransactionData {
|
2021-04-01 13:08:17 -07:00
|
|
|
version,
|
2021-05-12 23:16:40 -07:00
|
|
|
consensus_branch_id: BranchId::for_height(&self.params, self.target_height),
|
2021-04-01 13:08:17 -07:00
|
|
|
lock_time: 0,
|
|
|
|
expiry_height: self.expiry_height,
|
2021-04-12 17:55:16 -07:00
|
|
|
transparent_bundle,
|
2021-04-08 16:53:38 -07:00
|
|
|
sprout_bundle: None,
|
2021-04-07 14:58:30 -07:00
|
|
|
sapling_bundle,
|
2022-05-19 04:41:25 -07:00
|
|
|
orchard_bundle,
|
2021-04-16 11:38:13 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
|
|
|
tze_bundle,
|
2021-04-01 13:08:17 -07:00
|
|
|
};
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
//
|
2020-10-09 15:19:44 -07:00
|
|
|
// Signatures -- everything but the signatures must already have been added.
|
2018-11-20 05:37:21 -08:00
|
|
|
//
|
2021-05-12 23:16:40 -07:00
|
|
|
let txid_parts = unauthed_tx.digest(TxIdDigester);
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2021-06-03 16:27:15 -07:00
|
|
|
let transparent_bundle = unauthed_tx.transparent_bundle.clone().map(|b| {
|
|
|
|
b.apply_signatures(
|
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
&unauthed_tx,
|
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
&txid_parts,
|
|
|
|
)
|
|
|
|
});
|
2021-05-12 23:16:40 -07:00
|
|
|
|
2021-06-03 19:21:10 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
|
|
|
let tze_bundle = unauthed_tx
|
|
|
|
.tze_bundle
|
|
|
|
.clone()
|
|
|
|
.map(|b| b.into_authorized(&unauthed_tx, tze_signers))
|
|
|
|
.transpose()
|
|
|
|
.map_err(Error::TzeBuild)?;
|
|
|
|
|
2021-05-12 23:16:40 -07:00
|
|
|
// the commitment being signed is shared across all Sapling inputs; once
|
|
|
|
// V4 transactions are deprecated this should just be the txid, but
|
|
|
|
// for now we need to continue to compute it here.
|
2022-01-06 11:56:12 -08:00
|
|
|
let shielded_sig_commitment =
|
|
|
|
signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts);
|
2021-03-30 15:07:52 -07:00
|
|
|
|
2021-06-03 19:21:10 -07:00
|
|
|
let (sapling_bundle, tx_metadata) = match unauthed_tx
|
|
|
|
.sapling_bundle
|
|
|
|
.map(|b| {
|
|
|
|
b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref())
|
|
|
|
})
|
2021-06-03 17:25:41 -07:00
|
|
|
.transpose()
|
2021-06-03 19:21:10 -07:00
|
|
|
.map_err(Error::SaplingBuild)?
|
|
|
|
{
|
|
|
|
Some((bundle, meta)) => (Some(bundle), meta),
|
|
|
|
None => (None, SaplingMetadata::empty()),
|
2021-04-07 14:58:30 -07:00
|
|
|
};
|
|
|
|
|
2022-05-19 04:41:25 -07:00
|
|
|
// 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)?;
|
|
|
|
|
2021-04-16 11:38:13 -07:00
|
|
|
let authorized_tx = TransactionData {
|
2021-04-07 14:58:30 -07:00
|
|
|
version: unauthed_tx.version,
|
2021-05-12 16:01:17 -07:00
|
|
|
consensus_branch_id: unauthed_tx.consensus_branch_id,
|
2021-04-07 14:58:30 -07:00
|
|
|
lock_time: unauthed_tx.lock_time,
|
|
|
|
expiry_height: unauthed_tx.expiry_height,
|
2021-04-12 17:55:16 -07:00
|
|
|
transparent_bundle,
|
2021-04-08 16:53:38 -07:00
|
|
|
sprout_bundle: unauthed_tx.sprout_bundle,
|
2021-06-03 19:21:10 -07:00
|
|
|
sapling_bundle,
|
2022-05-19 04:41:25 -07:00
|
|
|
orchard_bundle,
|
2021-04-16 11:38:13 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-06-03 17:25:41 -07:00
|
|
|
tze_bundle,
|
2021-04-07 14:58:30 -07:00
|
|
|
};
|
|
|
|
|
2021-06-03 19:21:10 -07:00
|
|
|
// The unwrap() here is safe because the txid hashing
|
|
|
|
// of freeze() should be infalliable.
|
|
|
|
Ok((authorized_tx.freeze().unwrap(), tx_metadata))
|
2021-04-07 14:58:30 -07:00
|
|
|
}
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-09-23 09:53:48 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2020-05-06 16:52:03 -07:00
|
|
|
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
|
|
|
|
for Builder<'a, P, R>
|
|
|
|
{
|
2021-03-30 15:07:52 -07:00
|
|
|
type BuildCtx = TransactionData<Unauthorized>;
|
2021-04-07 14:58:30 -07:00
|
|
|
type BuildError = tze::builder::Error;
|
2020-05-06 16:52:03 -07:00
|
|
|
|
|
|
|
fn add_tze_input<WBuilder, W: ToPayload>(
|
|
|
|
&mut self,
|
2020-06-03 19:38:05 -07:00
|
|
|
extension_id: u32,
|
2020-09-09 09:54:18 -07:00
|
|
|
mode: u32,
|
2021-04-16 11:38:13 -07:00
|
|
|
prevout: (tze::OutPoint, TzeOut),
|
2020-05-06 16:52:03 -07:00
|
|
|
witness_builder: WBuilder,
|
|
|
|
) -> Result<(), Self::BuildError>
|
|
|
|
where
|
2021-04-07 14:58:30 -07:00
|
|
|
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, tze::builder::Error>),
|
2020-05-06 16:52:03 -07:00
|
|
|
{
|
2021-04-01 13:08:17 -07:00
|
|
|
self.tze_builder
|
|
|
|
.add_input(extension_id, mode, prevout, witness_builder);
|
|
|
|
|
2020-05-06 16:52:03 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_tze_output<G: ToPayload>(
|
|
|
|
&mut self,
|
2020-06-03 19:38:05 -07:00
|
|
|
extension_id: u32,
|
2020-05-06 16:52:03 -07:00
|
|
|
value: Amount,
|
|
|
|
guarded_by: &G,
|
|
|
|
) -> Result<(), Self::BuildError> {
|
2021-04-01 13:08:17 -07:00
|
|
|
self.tze_builder.add_output(extension_id, value, guarded_by)
|
2020-05-06 16:52:03 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-21 11:37:21 -07:00
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
|
|
|
impl<'a, P: consensus::Parameters, R: RngCore> 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.
|
|
|
|
///
|
|
|
|
/// # 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).
|
|
|
|
///
|
|
|
|
/// WARNING: DO NOT USE IN PRODUCTION
|
|
|
|
pub fn test_only_new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
|
2021-04-01 13:08:17 -07:00
|
|
|
Self::new_internal(params, height, rng)
|
2020-10-21 11:37:21 -07:00
|
|
|
}
|
|
|
|
|
2021-06-02 12:45:29 -07:00
|
|
|
pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error> {
|
|
|
|
self.build(&MockTxProver)
|
2020-10-21 11:37:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use ff::{Field, PrimeField};
|
2019-09-28 00:48:43 -07:00
|
|
|
use rand_core::OsRng;
|
2019-08-06 02:46:40 -07:00
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
use crate::{
|
2021-06-02 20:07:29 -07:00
|
|
|
consensus::{NetworkUpgrade, Parameters, TEST_NETWORK},
|
2019-05-24 05:32:55 -07:00
|
|
|
legacy::TransparentAddress,
|
2021-03-26 17:39:43 -07:00
|
|
|
memo::MemoBytes,
|
2018-11-20 05:37:21 -08:00
|
|
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
2021-03-04 13:26:39 -08:00
|
|
|
sapling::{prover::mock::MockTxProver, Node, Rseed},
|
2021-06-02 12:45:29 -07:00
|
|
|
transaction::components::{
|
|
|
|
amount::{Amount, DEFAULT_FEE},
|
2021-04-07 14:58:30 -07:00
|
|
|
sapling::builder::{self as build_s},
|
2021-05-12 23:16:40 -07:00
|
|
|
transparent::builder::{self as build_t},
|
2021-04-01 13:08:17 -07:00
|
|
|
},
|
2018-11-20 05:37:21 -08:00
|
|
|
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
|
|
|
};
|
|
|
|
|
2021-04-01 13:08:17 -07:00
|
|
|
use super::{Builder, Error, SaplingBuilder, DEFAULT_TX_EXPIRY_DELTA};
|
2020-09-23 09:53:48 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "zfuture")]
|
2021-04-01 13:08:17 -07:00
|
|
|
use super::TzeBuilder;
|
2020-05-18 19:27:19 -07:00
|
|
|
|
2021-05-12 23:16:40 -07:00
|
|
|
#[cfg(not(feature = "zfuture"))]
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
#[test]
|
|
|
|
fn fails_on_negative_output() {
|
|
|
|
let extsk = ExtendedSpendingKey::master(&[]);
|
|
|
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
|
|
|
let ovk = extfvk.fvk.ovk;
|
2021-10-11 12:42:01 -07:00
|
|
|
let to = extfvk.default_address().1;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2021-06-02 20:07:29 -07:00
|
|
|
let sapling_activation_height = TEST_NETWORK
|
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let mut builder = Builder::new(TEST_NETWORK, sapling_activation_height);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2021-03-26 17:39:43 -07:00
|
|
|
builder.add_sapling_output(
|
|
|
|
Some(ovk),
|
|
|
|
to,
|
|
|
|
Amount::from_i64(-1).unwrap(),
|
|
|
|
MemoBytes::empty()
|
|
|
|
),
|
2021-04-07 14:58:30 -07:00
|
|
|
Err(Error::SaplingBuild(build_s::Error::InvalidAmount))
|
2019-08-13 07:24:08 -07:00
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-04-03 12:13:39 -07:00
|
|
|
#[test]
|
|
|
|
fn binding_sig_absent_if_no_shielded_spend_or_output() {
|
2020-09-17 10:55:39 -07:00
|
|
|
use crate::consensus::NetworkUpgrade;
|
2021-04-01 13:08:17 -07:00
|
|
|
use crate::transaction::builder::{self, TransparentBuilder};
|
2020-04-03 12:13:39 -07:00
|
|
|
|
2020-09-17 10:55:39 -07:00
|
|
|
let sapling_activation_height = TEST_NETWORK
|
2020-08-05 13:27:40 -07:00
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap();
|
2020-08-02 22:39:36 -07:00
|
|
|
|
2020-04-03 12:13:39 -07:00
|
|
|
// Create a builder with 0 fee, so we can construct t outputs
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = builder::Builder {
|
2020-09-17 10:55:39 -07:00
|
|
|
params: TEST_NETWORK,
|
2020-04-03 12:13:39 -07:00
|
|
|
rng: OsRng,
|
2021-04-01 13:08:17 -07:00
|
|
|
target_height: sapling_activation_height,
|
|
|
|
expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA,
|
2020-04-03 12:13:39 -07:00
|
|
|
fee: Amount::zero(),
|
2021-05-11 13:17:54 -07:00
|
|
|
transparent_builder: TransparentBuilder::empty(),
|
2021-04-07 14:58:30 -07:00
|
|
|
sapling_builder: SaplingBuilder::new(TEST_NETWORK, sapling_activation_height),
|
2022-05-19 04:41:25 -07:00
|
|
|
orchard_builder: None,
|
2021-06-02 11:35:18 -07:00
|
|
|
change_address: None,
|
2020-09-23 09:53:48 -07:00
|
|
|
#[cfg(feature = "zfuture")]
|
2021-04-01 17:10:01 -07:00
|
|
|
tze_builder: TzeBuilder::empty(),
|
2021-04-01 16:35:32 -07:00
|
|
|
#[cfg(not(feature = "zfuture"))]
|
|
|
|
tze_builder: PhantomData,
|
2021-05-26 13:58:08 -07:00
|
|
|
progress_notifier: None,
|
2020-04-03 12:13:39 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// Create a tx with only t output. No binding_sig should be present
|
|
|
|
builder
|
|
|
|
.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::zero())
|
|
|
|
.unwrap();
|
|
|
|
|
2021-06-02 12:45:29 -07:00
|
|
|
let (tx, _) = builder.build(&MockTxProver).unwrap();
|
2020-04-03 12:13:39 -07:00
|
|
|
// No binding signature, because only t input and outputs
|
2021-04-07 14:58:30 -07:00
|
|
|
assert!(tx.sapling_bundle.is_none());
|
2020-04-03 12:13:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_sig_present_if_shielded_spend() {
|
|
|
|
let extsk = ExtendedSpendingKey::master(&[]);
|
|
|
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
2021-10-11 12:42:01 -07:00
|
|
|
let to = extfvk.default_address().1;
|
2020-04-03 12:13:39 -07:00
|
|
|
|
|
|
|
let mut rng = OsRng;
|
|
|
|
|
|
|
|
let note1 = to
|
2020-07-01 13:26:54 -07:00
|
|
|
.create_note(50000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
|
2020-04-03 12:13:39 -07:00
|
|
|
.unwrap();
|
2020-08-21 10:33:22 -07:00
|
|
|
let cmu1 = Node::new(note1.cmu().to_repr());
|
2021-02-04 06:34:41 -08:00
|
|
|
let mut tree = CommitmentTree::empty();
|
2020-08-21 10:33:22 -07:00
|
|
|
tree.append(cmu1).unwrap();
|
2020-04-03 12:13:39 -07:00
|
|
|
let witness1 = IncrementalWitness::from_tree(&tree);
|
|
|
|
|
2021-06-02 20:07:29 -07:00
|
|
|
let tx_height = TEST_NETWORK
|
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap();
|
|
|
|
let mut builder = Builder::new(TEST_NETWORK, tx_height);
|
2020-04-03 12:13:39 -07:00
|
|
|
|
|
|
|
// Create a tx with a sapling spend. binding_sig should be present
|
|
|
|
builder
|
2020-10-30 06:27:49 -07:00
|
|
|
.add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap())
|
2020-04-03 12:13:39 -07:00
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
builder
|
|
|
|
.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::zero())
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// Expect a binding signature error, because our inputs aren't valid, but this shows
|
|
|
|
// that a binding signature was attempted
|
|
|
|
assert_eq!(
|
2021-06-02 12:45:29 -07:00
|
|
|
builder.build(&MockTxProver),
|
2021-04-07 14:58:30 -07:00
|
|
|
Err(Error::SaplingBuild(build_s::Error::BindingSig))
|
2020-04-03 12:13:39 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
#[test]
|
|
|
|
fn fails_on_negative_transparent_output() {
|
2021-06-02 20:07:29 -07:00
|
|
|
let tx_height = TEST_NETWORK
|
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap();
|
|
|
|
let mut builder = Builder::new(TEST_NETWORK, tx_height);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
|
|
|
builder.add_transparent_output(
|
|
|
|
&TransparentAddress::PublicKey([0; 20]),
|
|
|
|
Amount::from_i64(-1).unwrap(),
|
|
|
|
),
|
2021-05-12 23:16:40 -07:00
|
|
|
Err(Error::TransparentBuild(build_t::Error::InvalidAmount))
|
2019-08-13 07:24:08 -07:00
|
|
|
);
|
2019-05-24 05:32:55 -07:00
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
#[test]
|
|
|
|
fn fails_on_negative_change() {
|
|
|
|
let mut rng = OsRng;
|
|
|
|
|
|
|
|
// Just use the master key as the ExtendedSpendingKey for this test
|
|
|
|
let extsk = ExtendedSpendingKey::master(&[]);
|
2021-06-02 20:07:29 -07:00
|
|
|
let tx_height = TEST_NETWORK
|
|
|
|
.activation_height(NetworkUpgrade::Sapling)
|
|
|
|
.unwrap();
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
// Fails with no inputs or outputs
|
|
|
|
// 0.0001 t-ZEC fee
|
|
|
|
{
|
2021-06-02 20:07:29 -07:00
|
|
|
let builder = Builder::new(TEST_NETWORK, tx_height);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2021-06-02 12:45:29 -07:00
|
|
|
builder.build(&MockTxProver),
|
2021-05-11 08:57:28 -07:00
|
|
|
Err(Error::ChangeIsNegative(
|
|
|
|
(Amount::zero() - DEFAULT_FEE).unwrap()
|
|
|
|
))
|
2019-08-13 07:24:08 -07:00
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
2020-08-28 08:12:37 -07:00
|
|
|
let ovk = Some(extfvk.fvk.ovk);
|
2021-10-11 12:42:01 -07:00
|
|
|
let to = extfvk.default_address().1;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
// Fail if there is only a Sapling output
|
2020-11-23 09:41:30 -08:00
|
|
|
// 0.0005 z-ZEC out, 0.00001 t-ZEC fee
|
2018-11-20 05:37:21 -08:00
|
|
|
{
|
2021-06-02 20:07:29 -07:00
|
|
|
let mut builder = Builder::new(TEST_NETWORK, tx_height);
|
2018-11-20 05:37:21 -08:00
|
|
|
builder
|
2021-03-26 17:39:43 -07:00
|
|
|
.add_sapling_output(
|
|
|
|
ovk,
|
|
|
|
to.clone(),
|
|
|
|
Amount::from_u64(50000).unwrap(),
|
|
|
|
MemoBytes::empty(),
|
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2021-06-02 12:45:29 -07:00
|
|
|
builder.build(&MockTxProver),
|
2020-11-23 09:41:30 -08:00
|
|
|
Err(Error::ChangeIsNegative(
|
2021-05-11 08:57:28 -07:00
|
|
|
(Amount::from_i64(-50000).unwrap() - DEFAULT_FEE).unwrap()
|
2020-11-23 09:41:30 -08:00
|
|
|
))
|
2019-08-13 07:24:08 -07:00
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
// Fail if there is only a transparent output
|
2020-11-23 09:41:30 -08:00
|
|
|
// 0.0005 t-ZEC out, 0.00001 t-ZEC fee
|
2019-05-24 05:32:55 -07:00
|
|
|
{
|
2021-06-02 20:07:29 -07:00
|
|
|
let mut builder = Builder::new(TEST_NETWORK, tx_height);
|
2019-05-24 05:32:55 -07:00
|
|
|
builder
|
2019-07-25 14:37:16 -07:00
|
|
|
.add_transparent_output(
|
|
|
|
&TransparentAddress::PublicKey([0; 20]),
|
|
|
|
Amount::from_u64(50000).unwrap(),
|
|
|
|
)
|
2019-05-24 05:32:55 -07:00
|
|
|
.unwrap();
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2021-06-02 12:45:29 -07:00
|
|
|
builder.build(&MockTxProver),
|
2020-11-23 09:41:30 -08:00
|
|
|
Err(Error::ChangeIsNegative(
|
2021-05-11 08:57:28 -07:00
|
|
|
(Amount::from_i64(-50000).unwrap() - DEFAULT_FEE).unwrap()
|
2020-11-23 09:41:30 -08:00
|
|
|
))
|
2019-08-13 07:24:08 -07:00
|
|
|
);
|
2019-05-24 05:32:55 -07:00
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
let note1 = to
|
2020-11-23 09:41:30 -08:00
|
|
|
.create_note(50999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
2020-08-21 10:33:22 -07:00
|
|
|
let cmu1 = Node::new(note1.cmu().to_repr());
|
2021-02-04 06:34:41 -08:00
|
|
|
let mut tree = CommitmentTree::empty();
|
2020-08-21 10:33:22 -07:00
|
|
|
tree.append(cmu1).unwrap();
|
2018-11-20 05:37:21 -08:00
|
|
|
let mut witness1 = IncrementalWitness::from_tree(&tree);
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
// Fail if there is insufficient input
|
2020-11-23 09:41:30 -08:00
|
|
|
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.00001 t-ZEC fee, 0.00050999 z-ZEC in
|
2018-11-20 05:37:21 -08:00
|
|
|
{
|
2021-06-02 20:07:29 -07:00
|
|
|
let mut builder = Builder::new(TEST_NETWORK, tx_height);
|
2018-11-20 05:37:21 -08:00
|
|
|
builder
|
|
|
|
.add_sapling_spend(
|
|
|
|
extsk.clone(),
|
2019-08-23 15:08:09 -07:00
|
|
|
*to.diversifier(),
|
2018-11-20 05:37:21 -08:00
|
|
|
note1.clone(),
|
2020-02-07 09:31:38 -08:00
|
|
|
witness1.path().unwrap(),
|
2018-11-20 05:37:21 -08:00
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
builder
|
2021-03-26 17:39:43 -07:00
|
|
|
.add_sapling_output(
|
|
|
|
ovk,
|
|
|
|
to.clone(),
|
|
|
|
Amount::from_u64(30000).unwrap(),
|
|
|
|
MemoBytes::empty(),
|
|
|
|
)
|
2019-05-24 05:32:55 -07:00
|
|
|
.unwrap();
|
|
|
|
builder
|
2019-07-25 14:37:16 -07:00
|
|
|
.add_transparent_output(
|
|
|
|
&TransparentAddress::PublicKey([0; 20]),
|
|
|
|
Amount::from_u64(20000).unwrap(),
|
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2021-06-02 12:45:29 -07:00
|
|
|
builder.build(&MockTxProver),
|
2019-08-13 07:24:08 -07:00
|
|
|
Err(Error::ChangeIsNegative(Amount::from_i64(-1).unwrap()))
|
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-07-29 21:36:12 -07:00
|
|
|
let note2 = to
|
2020-07-01 13:26:54 -07:00
|
|
|
.create_note(1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
|
2020-07-29 21:36:12 -07:00
|
|
|
.unwrap();
|
2020-08-21 10:33:22 -07:00
|
|
|
let cmu2 = Node::new(note2.cmu().to_repr());
|
|
|
|
tree.append(cmu2).unwrap();
|
|
|
|
witness1.append(cmu2).unwrap();
|
2018-11-20 05:37:21 -08:00
|
|
|
let witness2 = IncrementalWitness::from_tree(&tree);
|
|
|
|
|
|
|
|
// Succeeds if there is sufficient input
|
2019-05-24 05:32:55 -07:00
|
|
|
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in
|
2018-11-20 05:37:21 -08:00
|
|
|
//
|
|
|
|
// (Still fails because we are using a MockTxProver which doesn't correctly
|
|
|
|
// compute bindingSig.)
|
|
|
|
{
|
2021-06-02 20:07:29 -07:00
|
|
|
let mut builder = Builder::new(TEST_NETWORK, tx_height);
|
2018-11-20 05:37:21 -08:00
|
|
|
builder
|
2020-02-07 09:31:38 -08:00
|
|
|
.add_sapling_spend(
|
|
|
|
extsk.clone(),
|
|
|
|
*to.diversifier(),
|
|
|
|
note1,
|
|
|
|
witness1.path().unwrap(),
|
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
|
|
|
builder
|
2020-02-07 09:31:38 -08:00
|
|
|
.add_sapling_spend(extsk, *to.diversifier(), note2, witness2.path().unwrap())
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
|
|
|
builder
|
2021-03-26 17:39:43 -07:00
|
|
|
.add_sapling_output(
|
|
|
|
ovk,
|
|
|
|
to,
|
|
|
|
Amount::from_u64(30000).unwrap(),
|
|
|
|
MemoBytes::empty(),
|
|
|
|
)
|
2019-05-24 05:32:55 -07:00
|
|
|
.unwrap();
|
|
|
|
builder
|
2019-07-25 14:37:16 -07:00
|
|
|
.add_transparent_output(
|
|
|
|
&TransparentAddress::PublicKey([0; 20]),
|
|
|
|
Amount::from_u64(20000).unwrap(),
|
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
2019-11-25 07:41:14 -08:00
|
|
|
assert_eq!(
|
2021-06-02 12:45:29 -07:00
|
|
|
builder.build(&MockTxProver),
|
2021-04-07 14:58:30 -07:00
|
|
|
Err(Error::SaplingBuild(build_s::Error::BindingSig))
|
2019-11-25 07:41:14 -08:00
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|