librustzcash/zcash_primitives/src/transaction/builder.rs

823 lines
28 KiB
Rust
Raw Normal View History

2018-11-20 05:37:21 -08:00
//! Structs for building transactions.
use core::array;
use std::error;
use std::fmt;
use std::io;
2021-04-26 10:14:01 -07:00
use std::sync::mpsc::Sender;
2018-11-20 05:37:21 -08:00
use rand::{rngs::OsRng, CryptoRng, RngCore};
use orchard::bundle::{self as orchard};
2018-11-20 05:37:21 -08:00
use crate::{
consensus::{self, BlockHeight, BranchId},
legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::OutgoingViewingKey, prover::TxProver, redjubjub, Diversifier, Node, Note,
PaymentAddress,
},
2018-11-20 05:37:21 -08:00
transaction::{
components::{
2021-03-04 13:40:45 -08:00
amount::{Amount, DEFAULT_FEE},
sapling::{
self,
builder::{SaplingBuilder, SaplingMetadata},
},
transparent::{self, builder::TransparentBuilder, TxIn},
},
signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized,
SIGHASH_ALL,
2018-11-20 05:37:21 -08:00
},
zip32::ExtendedSpendingKey,
2018-11-20 05:37:21 -08:00
};
#[cfg(not(feature = "zfuture"))]
use std::marker::PhantomData;
#[cfg(feature = "transparent-inputs")]
use crate::{legacy::Script, transaction::components::transparent::TxOut};
#[cfg(feature = "zfuture")]
use crate::{
extensions::transparent::{ExtensionTxBuilder, ToPayload},
transaction::components::{
tze::builder::{TzeBuilder, WitnessData},
tze::{self, TzeIn, TzeOut, TzeOutPoint},
},
};
#[cfg(any(test, feature = "test-dependencies"))]
use crate::sapling::prover::mock::MockTxProver;
2018-11-20 05:37:21 -08:00
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
#[derive(Debug, PartialEq)]
pub enum Error {
2019-07-25 12:53:42 -07:00
ChangeIsNegative(Amount),
2018-11-20 05:37:21 -08:00
InvalidAmount,
NoChangeAddress,
TransparentBuild(transparent::builder::Error),
SaplingBuild(sapling::builder::Error),
#[cfg(feature = "zfuture")]
TzeBuild(tze::builder::Error),
2018-11-20 05:37:21 -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),
#[cfg(feature = "zfuture")]
2021-06-02 15:48:55 -07:00
Error::TzeBuild(err) => err.fmt(f),
}
}
}
impl error::Error for Error {}
/// Reports on the progress made by the builder towards building a transaction.
2021-04-26 10:14:01 -07:00
pub struct Progress {
/// The number of steps completed.
2021-04-26 10:14:01 -07:00
cur: u32,
/// 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 }
}
/// 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
}
/// 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
}
}
enum ChangeAddress {
SaplingChangeAddress(OutgoingViewingKey, PaymentAddress),
}
2018-11-20 05:37:21 -08:00
/// Generates a [`Transaction`] from its inputs and outputs.
pub struct Builder<'a, P: consensus::Parameters, R: RngCore> {
params: P,
rng: R,
target_height: BlockHeight,
expiry_height: BlockHeight,
2018-11-20 05:37:21 -08:00
fee: Amount,
transparent_builder: TransparentBuilder,
sapling_builder: SaplingBuilder<P>,
change_address: Option<ChangeAddress>,
#[cfg(feature = "zfuture")]
tze_builder: TzeBuilder<'a, TransactionData<Unauthorized>>,
#[cfg(not(feature = "zfuture"))]
tze_builder: PhantomData<&'a ()>,
progress_notifier: Option<Sender<Progress>>,
2018-11-20 05:37:21 -08:00
}
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
/// 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 new(params: P, target_height: BlockHeight) -> Self {
Builder::new_with_rng(params, target_height, OsRng)
}
}
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).
pub fn new_with_rng(params: P, target_height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_internal(params, target_height, rng)
2020-09-08 15:53:27 -07:00
}
}
2020-09-08 15:53:27 -07:00
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
2020-09-08 15:53:27 -07:00
/// 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> {
2018-11-20 05:37:21 -08:00
Builder {
params: params.clone(),
rng,
target_height,
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
2018-11-20 05:37:21 -08:00
fee: DEFAULT_FEE,
transparent_builder: TransparentBuilder::empty(),
sapling_builder: SaplingBuilder::new(params, target_height),
change_address: None,
#[cfg(feature = "zfuture")]
tze_builder: TzeBuilder::empty(),
#[cfg(not(feature = "zfuture"))]
tze_builder: PhantomData,
progress_notifier: None,
2018-11-20 05:37:21 -08:00
}
}
/// 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
/// paths for previous Sapling notes.
2018-11-20 05:37:21 -08:00
pub fn add_sapling_spend(
&mut self,
extsk: ExtendedSpendingKey,
diversifier: Diversifier,
note: Note,
merkle_path: MerklePath<Node>,
2018-11-20 05:37:21 -08:00
) -> Result<(), Error> {
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.
pub fn add_sapling_output(
2018-11-20 05:37:21 -08:00
&mut self,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
2018-11-20 05:37:21 -08:00
value: Amount,
memo: Option<MemoBytes>,
2018-11-20 05:37:21 -08:00
) -> Result<(), Error> {
self.sapling_builder
.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
}
/// Adds a transparent coin to be spent in this transaction.
#[cfg(feature = "transparent-inputs")]
#[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))]
pub fn add_transparent_input(
&mut self,
sk: secp256k1::SecretKey,
utxo: transparent::OutPoint,
coin: TxOut,
) -> Result<(), Error> {
self.transparent_builder
.add_input(sk, utxo, coin)
2021-06-02 15:48:55 -07:00
.map_err(Error::TransparentBuild)
}
/// Adds a transparent address to send funds to.
pub fn add_transparent_output(
&mut self,
to: &TransparentAddress,
value: Amount,
) -> Result<(), Error> {
self.transparent_builder
.add_output(to, value)
2021-06-02 15:48:55 -07:00
.map_err(Error::TransparentBuild)
}
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`]).
pub fn send_change_to(&mut self, ovk: OutgoingViewingKey, to: PaymentAddress) {
self.change_address = Some(ChangeAddress::SaplingChangeAddress(ovk, to))
2018-11-20 05:37:21 -08:00
}
/// Sets the notifier channel, where progress of building the transaction is sent.
2018-11-20 05:37:21 -08: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> {
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)?,
];
array::IntoIter::new(value_balances)
.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
/// [`SaplingMetadata`] generated during the build process.
2021-04-26 10:14:01 -07:00
///
/// `consensus_branch_id` must be valid for the block height that this transaction is
/// targeting. An invalid `consensus_branch_id` will *not* result in an error from
/// this function, and instead will generate a transaction that will be rejected by
/// the network.
pub fn build(
2018-11-20 05:37:21 -08:00
mut self,
prover: &impl TxProver,
) -> Result<(Transaction, SaplingMetadata), Error> {
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
let change = (self.value_balance()? - self.fee).ok_or(Error::InvalidAmount)?;
2018-11-20 05:37:21 -08:00
if change.is_negative() {
return Err(Error::ChangeIsNegative(change));
2018-11-20 05:37:21 -08:00
}
//
// Change output
//
if change.is_positive() {
match self.change_address.take() {
Some(ChangeAddress::SaplingChangeAddress(ovk, addr)) => {
self.add_sapling_output(Some(ovk), addr, change, None)?;
}
None => {
let (ovk, addr) = self
.sapling_builder
.get_candidate_change_address()
2021-06-02 15:48:55 -07:00
.ok_or(Error::NoChangeAddress)?;
self.add_sapling_output(Some(ovk), addr, change, None)?;
}
}
2018-11-20 05:37:21 -08:00
}
let (vin, vout) = self.transparent_builder.build();
2018-11-20 05:37:21 -08:00
let mut ctx = prover.new_sapling_proving_context();
let (sapling_bundle, tx_metadata) = self
.sapling_builder
.build(
prover,
&mut ctx,
&mut self.rng,
self.target_height,
2021-06-01 07:53:53 -07:00
self.progress_notifier.as_ref(),
)
2021-06-02 15:48:55 -07:00
.map_err(Error::SaplingBuild)?;
2018-11-20 05:37:21 -08:00
#[cfg(feature = "zfuture")]
let (tze_inputs, tze_outputs) = self.tze_builder.build();
2021-04-26 10:14:01 -07:00
let unauthed_tx = TransactionData {
version,
vin,
vout,
#[cfg(feature = "zfuture")]
tze_inputs,
#[cfg(feature = "zfuture")]
tze_outputs,
lock_time: 0,
expiry_height: self.expiry_height,
joinsplits: vec![],
joinsplit_pubkey: None,
joinsplit_sig: None,
sapling_bundle,
orchard_bundle: None,
};
2018-11-20 05:37:21 -08:00
//
// Signatures -- everything but the signatures must already have been added.
2018-11-20 05:37:21 -08:00
//
let mut sighash = [0u8; 32];
sighash.copy_from_slice(&signature_hash_data(
&unauthed_tx,
2018-11-20 05:37:21 -08:00
consensus_branch_id,
SIGHASH_ALL,
2020-06-03 19:39:43 -07:00
SignableInput::Shielded,
2018-11-20 05:37:21 -08:00
));
#[cfg(feature = "transparent-inputs")]
let transparent_sigs = Some(
self.transparent_builder
.create_signatures(&unauthed_tx, consensus_branch_id),
);
let sapling_sigs = self
.sapling_builder
.create_signatures(prover, &mut ctx, &mut self.rng, &sighash, &tx_metadata)
2021-06-02 15:48:55 -07:00
.map_err(Error::SaplingBuild)?;
2020-04-03 12:13:39 -07:00
#[cfg(feature = "zfuture")]
let tze_witnesses = Some(
self.tze_builder
.create_witnesses(&unauthed_tx)
.map_err(Error::TzeBuild)?,
);
Ok((
Self::apply_signatures(
consensus_branch_id,
unauthed_tx,
#[cfg(feature = "transparent-inputs")]
transparent_sigs,
sapling_sigs,
None,
#[cfg(feature = "zfuture")]
tze_witnesses,
)
.expect("An IO error occurred applying signatures."),
tx_metadata,
))
}
pub fn apply_signatures(
consensus_branch_id: consensus::BranchId,
unauthed_tx: TransactionData<Unauthorized>,
#[cfg(feature = "transparent-inputs")] transparent_sigs: Option<Vec<Script>>,
sapling_sigs: Option<(Vec<redjubjub::Signature>, redjubjub::Signature)>,
_orchard_auth: Option<(
Vec<<orchard::Authorized as orchard::Authorization>::SpendAuth>,
orchard::Authorized,
)>,
#[cfg(feature = "zfuture")] tze_witnesses: Option<Vec<WitnessData>>,
) -> io::Result<Transaction> {
let signed_sapling_bundle = match (unauthed_tx.sapling_bundle, sapling_sigs) {
(Some(bundle), Some((spend_sigs, binding_sig))) => {
Some(bundle.apply_signatures(spend_sigs, binding_sig))
}
(None, None) => None,
_ => {
panic!("Mismatch between sapling bundle and signatures.");
}
};
let mut authorized_tx = TransactionData {
version: unauthed_tx.version,
vin: unauthed_tx.vin,
vout: unauthed_tx.vout,
#[cfg(feature = "zfuture")]
tze_inputs: unauthed_tx.tze_inputs,
#[cfg(feature = "zfuture")]
tze_outputs: unauthed_tx.tze_outputs,
lock_time: unauthed_tx.lock_time,
expiry_height: unauthed_tx.expiry_height,
joinsplits: unauthed_tx.joinsplits,
joinsplit_pubkey: unauthed_tx.joinsplit_pubkey,
joinsplit_sig: unauthed_tx.joinsplit_sig,
sapling_bundle: signed_sapling_bundle,
orchard_bundle: None,
};
#[cfg(feature = "transparent-inputs")]
Self::apply_transparent_sigs(&mut authorized_tx.vin, transparent_sigs);
#[cfg(feature = "zfuture")]
Self::apply_tze_sigs(&mut authorized_tx.tze_inputs, tze_witnesses);
authorized_tx.freeze(consensus_branch_id)
}
#[cfg(feature = "transparent-inputs")]
pub fn apply_transparent_sigs(vin: &mut [TxIn], transparent_sigs: Option<Vec<Script>>) {
if let Some(script_sigs) = transparent_sigs {
assert!(vin.len() == script_sigs.len());
for (i, sig) in script_sigs.into_iter().enumerate() {
vin[i].script_sig = sig;
}
}
}
#[cfg(feature = "zfuture")]
pub fn apply_tze_sigs(vtzein: &mut [TzeIn], tze_witnesses: Option<Vec<WitnessData>>) {
if let Some(tze_witnesses) = tze_witnesses {
assert!(vtzein.len() == tze_witnesses.len());
for (i, payload) in tze_witnesses.into_iter().enumerate() {
vtzein[i].witness.payload = payload.0;
}
}
2018-11-20 05:37:21 -08:00
}
}
#[cfg(feature = "zfuture")]
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
for Builder<'a, P, R>
{
type BuildCtx = TransactionData<Unauthorized>;
type BuildError = tze::builder::Error;
fn add_tze_input<WBuilder, W: ToPayload>(
&mut self,
extension_id: u32,
mode: u32,
prevout: (TzeOutPoint, TzeOut),
witness_builder: WBuilder,
) -> Result<(), Self::BuildError>
where
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, tze::builder::Error>),
{
self.tze_builder
.add_input(extension_id, mode, prevout, witness_builder);
Ok(())
}
fn add_tze_output<G: ToPayload>(
&mut self,
extension_id: u32,
value: Amount,
guarded_by: &G,
) -> Result<(), Self::BuildError> {
self.tze_builder.add_output(extension_id, value, guarded_by)
}
}
#[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> {
Self::new_internal(params, height, rng)
}
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
/// and randomness source, using default values for general transaction fields
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
///
/// # 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).
///
/// The transaction will be constructed and serialized according to the
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
/// integration testing of new features.
///
/// WARNING: DO NOT USE IN PRODUCTION
#[cfg(feature = "zfuture")]
pub fn test_only_new_with_rng_zfuture(
params: P,
height: BlockHeight,
rng: R,
) -> Builder<'a, P, R> {
Self::new_internal(params, height, rng)
}
pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error> {
self.build(&MockTxProver)
}
}
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;
2018-11-20 05:37:21 -08:00
use crate::{
consensus::{NetworkUpgrade, Parameters, TEST_NETWORK},
legacy::TransparentAddress,
2018-11-20 05:37:21 -08:00
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{prover::mock::MockTxProver, Node, Rseed},
transaction::components::{
amount::{Amount, DEFAULT_FEE},
sapling::builder::{self as build_s},
transparent::builder::{self as transparent},
},
2018-11-20 05:37:21 -08:00
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use super::{Builder, Error, SaplingBuilder, DEFAULT_TX_EXPIRY_DELTA};
#[cfg(not(feature = "zfuture"))]
use std::marker::PhantomData;
#[cfg(feature = "zfuture")]
use super::TzeBuilder;
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;
let to = extfvk.default_address().unwrap().1;
let sapling_activation_height = TEST_NETWORK
.activation_height(NetworkUpgrade::Sapling)
.unwrap();
let mut builder = Builder::new(TEST_NETWORK, sapling_activation_height);
assert_eq!(
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
Err(Error::SaplingBuild(build_s::Error::InvalidAmount))
);
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() {
use crate::consensus::NetworkUpgrade;
use crate::transaction::builder::{self, TransparentBuilder};
2020-04-03 12:13:39 -07:00
let sapling_activation_height = TEST_NETWORK
.activation_height(NetworkUpgrade::Sapling)
.unwrap();
2020-04-03 12:13:39 -07:00
// Create a builder with 0 fee, so we can construct t outputs
let mut builder = builder::Builder {
params: TEST_NETWORK,
2020-04-03 12:13:39 -07:00
rng: OsRng,
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(),
transparent_builder: TransparentBuilder::empty(),
sapling_builder: SaplingBuilder::new(TEST_NETWORK, sapling_activation_height),
change_address: None,
#[cfg(feature = "zfuture")]
tze_builder: TzeBuilder::empty(),
#[cfg(not(feature = "zfuture"))]
tze_builder: PhantomData,
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();
let (tx, _) = builder.build(&MockTxProver).unwrap();
2020-04-03 12:13:39 -07:00
// No binding signature, because only t input and outputs
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);
let to = extfvk.default_address().unwrap().1;
let mut rng = OsRng;
let note1 = to
.create_note(50000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
2020-04-03 12:13:39 -07:00
.unwrap();
let cmu1 = Node::new(note1.cmu().to_repr());
let mut tree = CommitmentTree::empty();
tree.append(cmu1).unwrap();
2020-04-03 12:13:39 -07:00
let witness1 = IncrementalWitness::from_tree(&tree);
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!(
builder.build(&MockTxProver),
Err(Error::SaplingBuild(build_s::Error::BindingSig))
2020-04-03 12:13:39 -07:00
);
}
#[test]
fn fails_on_negative_transparent_output() {
let tx_height = TEST_NETWORK
.activation_height(NetworkUpgrade::Sapling)
.unwrap();
let mut builder = Builder::new(TEST_NETWORK, tx_height);
assert_eq!(
builder.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_i64(-1).unwrap(),
),
2021-06-02 16:03:07 -07:00
Err(Error::TransparentBuild(transparent::Error::InvalidAmount))
);
}
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(&[]);
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
{
let builder = Builder::new(TEST_NETWORK, tx_height);
assert_eq!(
builder.build(&MockTxProver),
Err(Error::ChangeIsNegative(
(Amount::zero() - DEFAULT_FEE).unwrap()
))
);
2018-11-20 05:37:21 -08:00
}
let extfvk = ExtendedFullViewingKey::from(&extsk);
let ovk = Some(extfvk.fvk.ovk);
2018-11-20 05:37:21 -08:00
let to = extfvk.default_address().unwrap().1;
// Fail if there is only a Sapling output
// 0.0005 z-ZEC out, 0.00001 t-ZEC fee
2018-11-20 05:37:21 -08:00
{
let mut builder = Builder::new(TEST_NETWORK, tx_height);
2018-11-20 05:37:21 -08:00
builder
2020-10-30 06:27:49 -07:00
.add_sapling_output(ovk, to.clone(), Amount::from_u64(50000).unwrap(), None)
2018-11-20 05:37:21 -08:00
.unwrap();
assert_eq!(
builder.build(&MockTxProver),
Err(Error::ChangeIsNegative(
(Amount::from_i64(-50000).unwrap() - DEFAULT_FEE).unwrap()
))
);
2018-11-20 05:37:21 -08:00
}
// Fail if there is only a transparent output
// 0.0005 t-ZEC out, 0.00001 t-ZEC fee
{
let mut builder = Builder::new(TEST_NETWORK, tx_height);
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(50000).unwrap(),
)
.unwrap();
assert_eq!(
builder.build(&MockTxProver),
Err(Error::ChangeIsNegative(
(Amount::from_i64(-50000).unwrap() - DEFAULT_FEE).unwrap()
))
);
}
2018-11-20 05:37:21 -08:00
let note1 = to
.create_note(50999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
2018-11-20 05:37:21 -08:00
.unwrap();
let cmu1 = Node::new(note1.cmu().to_repr());
let mut tree = CommitmentTree::empty();
tree.append(cmu1).unwrap();
2018-11-20 05:37:21 -08:00
let mut witness1 = IncrementalWitness::from_tree(&tree);
// Fail if there is insufficient input
// 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
{
let mut builder = Builder::new(TEST_NETWORK, tx_height);
2018-11-20 05:37:21 -08:00
builder
.add_sapling_spend(
extsk.clone(),
*to.diversifier(),
2018-11-20 05:37:21 -08:00
note1.clone(),
witness1.path().unwrap(),
2018-11-20 05:37:21 -08:00
)
.unwrap();
builder
2020-10-30 06:27:49 -07:00
.add_sapling_output(ovk, to.clone(), Amount::from_u64(30000).unwrap(), None)
.unwrap();
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(20000).unwrap(),
)
2018-11-20 05:37:21 -08:00
.unwrap();
assert_eq!(
builder.build(&MockTxProver),
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
.create_note(1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
2020-07-29 21:36:12 -07:00
.unwrap();
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
// 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.)
{
let mut builder = Builder::new(TEST_NETWORK, tx_height);
2018-11-20 05:37:21 -08:00
builder
.add_sapling_spend(
extsk.clone(),
*to.diversifier(),
note1,
witness1.path().unwrap(),
)
2018-11-20 05:37:21 -08:00
.unwrap();
builder
.add_sapling_spend(extsk, *to.diversifier(), note2, witness2.path().unwrap())
2018-11-20 05:37:21 -08:00
.unwrap();
builder
.add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None)
.unwrap();
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(20000).unwrap(),
)
2018-11-20 05:37:21 -08:00
.unwrap();
assert_eq!(
builder.build(&MockTxProver),
Err(Error::SaplingBuild(build_s::Error::BindingSig))
)
2018-11-20 05:37:21 -08:00
}
}
}