librustzcash/zcash_primitives/src/transaction/builder.rs

1276 lines
42 KiB
Rust
Raw Normal View History

2018-11-20 05:37:21 -08:00
//! Structs for building transactions.
#[cfg(feature = "zfuture")]
use std::boxed::Box;
use std::error;
use std::fmt;
use std::marker::PhantomData;
2021-04-26 10:14:01 -07:00
use std::sync::mpsc::Sender;
2018-11-20 05:37:21 -08:00
use ff::Field;
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
2018-11-20 05:37:21 -08:00
use crate::{
2020-08-05 13:08:58 -07:00
consensus::{self, BlockHeight},
legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::OutgoingViewingKey, note_encryption::sapling_note_encryption, prover::TxProver,
redjubjub::PrivateKey, spend_sig_internal, util::generate_random_rseed_internal,
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},
OutputDescription, SpendDescription, TxOut,
},
2020-06-03 19:39:43 -07:00
signature_hash_data, SignableInput, Transaction, TransactionData, SIGHASH_ALL,
2018-11-20 05:37:21 -08:00
},
zip32::ExtendedSpendingKey,
2018-11-20 05:37:21 -08:00
};
#[cfg(feature = "transparent-inputs")]
use crate::{
legacy::Script,
transaction::components::{OutPoint, TxIn},
};
#[cfg(feature = "zfuture")]
use crate::{
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
transaction::components::{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;
/// If there are any shielded inputs, always have at least two shielded outputs, padding
2021-03-25 13:49:27 -07:00
/// with dummy outputs if necessary. See <https://github.com/zcash/zcash/issues/3615>.
2018-11-20 05:37:21 -08:00
const MIN_SHIELDED_OUTPUTS: usize = 2;
#[derive(Debug, PartialEq)]
pub enum Error {
2018-11-20 05:37:21 -08:00
AnchorMismatch,
BindingSig,
2019-07-25 12:53:42 -07:00
ChangeIsNegative(Amount),
2018-11-20 05:37:21 -08:00
InvalidAddress,
InvalidAmount,
NoChangeAddress,
SpendProof,
TzeWitnessModeMismatch(u32, u32),
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::AnchorMismatch => {
write!(f, "Anchor mismatch (anchors for all spends must be equal)")
}
Error::BindingSig => write!(f, "Failed to create bindingSig"),
Error::ChangeIsNegative(amount) => {
write!(f, "Change is negative ({:?} zatoshis)", amount)
}
Error::InvalidAddress => write!(f, "Invalid address"),
Error::InvalidAmount => write!(f, "Invalid amount"),
Error::NoChangeAddress => write!(f, "No change address specified or discoverable"),
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
Error::TzeWitnessModeMismatch(expected, actual) =>
write!(f, "TZE witness builder returned a mode that did not match the mode with which the input was initially constructed: expected = {:?}, actual = {:?}", expected, actual),
}
}
}
impl error::Error for Error {}
2018-11-20 05:37:21 -08:00
struct SpendDescriptionInfo {
extsk: ExtendedSpendingKey,
diversifier: Diversifier,
note: Note,
alpha: jubjub::Fr,
merkle_path: MerklePath<Node>,
2018-11-20 05:37:21 -08:00
}
pub struct SaplingOutput<P: consensus::Parameters> {
/// `None` represents the `ovk = ⊥` case.
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
note: Note,
memo: MemoBytes,
_params: PhantomData<P>,
2018-11-20 05:37:21 -08:00
}
impl<P: consensus::Parameters> SaplingOutput<P> {
pub fn new<R: RngCore + CryptoRng>(
params: &P,
2020-08-05 13:08:58 -07:00
height: BlockHeight,
2018-11-20 05:37:21 -08:00
rng: &mut R,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
2018-11-20 05:37:21 -08:00
value: Amount,
memo: Option<MemoBytes>,
) -> Result<Self, Error> {
Self::new_internal(params, height, rng, ovk, to, value, memo)
}
fn new_internal<R: RngCore>(
params: &P,
height: BlockHeight,
rng: &mut R,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: Amount,
memo: Option<MemoBytes>,
2018-11-20 05:37:21 -08:00
) -> Result<Self, Error> {
let g_d = to.g_d().ok_or(Error::InvalidAddress)?;
if value.is_negative() {
return Err(Error::InvalidAmount);
2018-11-20 05:37:21 -08:00
}
let rseed = generate_random_rseed_internal(params, height, rng);
2018-11-20 05:37:21 -08:00
let note = Note {
g_d,
2020-10-30 06:27:49 -07:00
pk_d: *to.pk_d(),
value: value.into(),
2020-07-30 08:07:33 -07:00
rseed,
2018-11-20 05:37:21 -08:00
};
Ok(SaplingOutput {
ovk,
to,
note,
memo: memo.unwrap_or_else(MemoBytes::empty),
_params: PhantomData::default(),
2018-11-20 05:37:21 -08:00
})
}
pub fn build<Pr: TxProver, R: RngCore + CryptoRng>(
2018-11-20 05:37:21 -08:00
self,
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
2018-11-20 05:37:21 -08:00
) -> OutputDescription {
self.build_internal(prover, ctx, rng)
}
fn build_internal<Pr: TxProver, R: RngCore>(
self,
prover: &Pr,
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
) -> OutputDescription {
2021-04-08 09:08:00 -07:00
let encryptor = sapling_note_encryption::<R, P>(
self.ovk,
self.note.clone(),
self.to.clone(),
self.memo,
rng,
);
2018-11-20 05:37:21 -08:00
let (zkproof, cv) = prover.output_proof(
ctx,
2020-10-30 06:27:49 -07:00
*encryptor.esk(),
2018-11-20 05:37:21 -08:00
self.to,
2020-07-29 21:36:12 -07:00
self.note.rcm(),
2018-11-20 05:37:21 -08:00
self.note.value,
);
let cmu = self.note.cmu();
2018-11-20 05:37:21 -08:00
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
let ephemeral_key = *encryptor.epk();
2018-11-20 05:37:21 -08:00
OutputDescription {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
}
}
}
#[cfg(feature = "transparent-inputs")]
struct TransparentInputInfo {
sk: secp256k1::SecretKey,
pubkey: [u8; secp256k1::constants::PUBLIC_KEY_SIZE],
coin: TxOut,
}
#[cfg(feature = "transparent-inputs")]
struct TransparentInputs {
secp: secp256k1::Secp256k1<secp256k1::SignOnly>,
inputs: Vec<TransparentInputInfo>,
}
#[cfg(feature = "transparent-inputs")]
impl Default for TransparentInputs {
fn default() -> Self {
TransparentInputs {
secp: secp256k1::Secp256k1::gen_new(),
inputs: Default::default(),
}
}
}
#[cfg(not(feature = "transparent-inputs"))]
#[derive(Default)]
struct TransparentInputs;
impl TransparentInputs {
#[cfg(feature = "transparent-inputs")]
fn push(&mut self, sk: secp256k1::SecretKey, coin: TxOut) -> Result<(), Error> {
if coin.value.is_negative() {
return Err(Error::InvalidAmount);
}
// Ensure that the RIPEMD-160 digest of the public key associated with the
// provided secret key matches that of the address to which the provided
// output may be spent.
let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk).serialize();
match coin.script_pubkey.address() {
Some(TransparentAddress::PublicKey(hash)) => {
use ripemd160::Ripemd160;
use sha2::{Digest, Sha256};
2020-10-30 06:40:26 -07:00
if hash[..] != Ripemd160::digest(&Sha256::digest(&pubkey))[..] {
return Err(Error::InvalidAddress);
}
}
_ => return Err(Error::InvalidAddress),
}
self.inputs.push(TransparentInputInfo { sk, pubkey, coin });
Ok(())
}
2019-11-13 11:21:47 -08:00
fn value_sum(&self) -> Amount {
#[cfg(feature = "transparent-inputs")]
{
self.inputs
.iter()
.map(|input| input.coin.value)
.sum::<Amount>()
}
#[cfg(not(feature = "transparent-inputs"))]
{
Amount::zero()
}
}
#[cfg(feature = "transparent-inputs")]
fn apply_signatures(
&self,
mtx: &mut TransactionData,
consensus_branch_id: consensus::BranchId,
) {
2019-11-14 09:54:07 -08:00
let mut sighash = [0u8; 32];
for (i, info) in self.inputs.iter().enumerate() {
sighash.copy_from_slice(&signature_hash_data(
mtx,
consensus_branch_id,
SIGHASH_ALL,
2020-06-03 19:39:43 -07:00
SignableInput::transparent(i, &info.coin.script_pubkey, info.coin.value),
));
let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes");
let sig = self.secp.sign(&msg, &info.sk);
// Signature has to have "SIGHASH_ALL" appended to it
let mut sig_bytes: Vec<u8> = sig.serialize_der()[..].to_vec();
sig_bytes.extend(&[SIGHASH_ALL as u8]);
// P2PKH scriptSig
mtx.vin[i].script_sig = Script::default() << &sig_bytes[..] << &info.pubkey[..];
}
}
#[cfg(not(feature = "transparent-inputs"))]
fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {}
}
#[cfg(feature = "zfuture")]
2021-02-03 11:46:47 -08:00
#[allow(clippy::type_complexity)]
struct TzeInputInfo<'a, BuildCtx> {
prevout: TzeOut,
builder: Box<dyn FnOnce(&BuildCtx) -> Result<(u32, Vec<u8>), Error> + 'a>,
}
#[cfg(feature = "zfuture")]
struct TzeInputs<'a, BuildCtx> {
builders: Vec<TzeInputInfo<'a, BuildCtx>>,
}
#[cfg(feature = "zfuture")]
impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> {
fn default() -> Self {
TzeInputs { builders: vec![] }
}
fn push<WBuilder, W: ToPayload>(&mut self, tzeout: TzeOut, builder: WBuilder)
where
WBuilder: 'a + FnOnce(&BuildCtx) -> Result<W, Error>,
{
self.builders.push(TzeInputInfo {
prevout: tzeout,
builder: Box::new(move |ctx| builder(&ctx).map(|x| x.to_payload())),
});
}
}
2018-11-20 05:37:21 -08:00
/// Metadata about a transaction created by a [`Builder`].
#[derive(Debug, PartialEq)]
2018-11-20 05:37:21 -08:00
pub struct TransactionMetadata {
spend_indices: Vec<usize>,
output_indices: Vec<usize>,
}
impl TransactionMetadata {
fn new() -> Self {
TransactionMetadata {
spend_indices: vec![],
output_indices: vec![],
}
}
/// Returns the index within the transaction of the [`SpendDescription`] corresponding
/// to the `n`-th call to [`Builder::add_sapling_spend`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first spend
/// they added (via the first call to [`Builder::add_sapling_spend`]) is the first
/// [`SpendDescription`] in the transaction.
pub fn spend_index(&self, n: usize) -> Option<usize> {
self.spend_indices.get(n).copied()
2018-11-20 05:37:21 -08:00
}
/// Returns the index within the transaction of the [`OutputDescription`] corresponding
/// to the `n`-th call to [`Builder::add_sapling_output`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first output
/// they added (via the first call to [`Builder::add_sapling_output`]) is the first
/// [`OutputDescription`] in the transaction.
pub fn output_index(&self, n: usize) -> Option<usize> {
self.output_indices.get(n).copied()
2018-11-20 05:37:21 -08:00
}
}
2021-04-26 10:14:01 -07:00
/// Reports on the progress made by the builder towards building the transaction.
/// `cur` is the number of steps completed, and `end` is the total number of steps
/// expected. Note that each step may not be of the same complexity/duration
pub struct Progress {
cur: u32,
end: Option<u32>,
}
impl Progress {
fn new(cur: u32, end: Option<u32>) -> Self {
Self { cur, end }
}
/// Returns the amount of progress made so far building the Tx
pub fn cur(&self) -> u32 {
self.cur
}
/// Returns the total expected number of steps before this Tx is ready
pub fn end(&self) -> Option<u32> {
self.end
}
}
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,
2020-08-05 13:08:58 -07:00
height: BlockHeight,
2018-11-20 05:37:21 -08:00
mtx: TransactionData,
fee: Amount,
anchor: Option<bls12_381::Scalar>,
2018-11-20 05:37:21 -08:00
spends: Vec<SpendDescriptionInfo>,
outputs: Vec<SaplingOutput<P>>,
2019-11-13 11:21:47 -08:00
transparent_inputs: TransparentInputs,
#[cfg(feature = "zfuture")]
tze_inputs: TzeInputs<'a, TransactionData>,
change_address: Option<(OutgoingViewingKey, PaymentAddress)>,
_phantom: &'a PhantomData<P>,
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, height: BlockHeight) -> Self {
Builder::new_with_rng(params, height, OsRng)
}
2020-09-08 15:53:27 -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,
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
2020-09-08 15:53:27 -07:00
///
/// # 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.
#[cfg(feature = "zfuture")]
2020-09-23 12:11:47 -07:00
pub fn new_zfuture(params: P, height: BlockHeight) -> Self {
Builder::new_with_rng_zfuture(params, 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, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(params, height, rng, TransactionData::new())
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
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
2020-09-08 15:53:27 -07:00
///
/// # 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.
#[cfg(feature = "zfuture")]
2020-09-23 12:11:47 -07:00
pub fn new_with_rng_zfuture(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(params, height, rng, TransactionData::zfuture())
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_with_mtx(
params: P,
height: BlockHeight,
rng: R,
mut mtx: TransactionData,
) -> Builder<'a, P, R> {
2018-11-20 05:37:21 -08:00
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
Builder {
params,
rng,
height,
2018-11-20 05:37:21 -08:00
mtx,
fee: DEFAULT_FEE,
anchor: None,
spends: vec![],
outputs: vec![],
2019-11-13 11:21:47 -08:00
transparent_inputs: TransparentInputs::default(),
#[cfg(feature = "zfuture")]
tze_inputs: TzeInputs::default(),
2018-11-20 05:37:21 -08:00
change_address: None,
_phantom: &PhantomData,
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> {
// Consistency check: all anchors must equal the first one
let cmu = Node::new(note.cmu().into());
2018-11-20 05:37:21 -08:00
if let Some(anchor) = self.anchor {
let path_root: bls12_381::Scalar = merkle_path.root(cmu).into();
if path_root != anchor {
return Err(Error::AnchorMismatch);
2018-11-20 05:37:21 -08:00
}
} else {
self.anchor = Some(merkle_path.root(cmu).into())
2018-11-20 05:37:21 -08:00
}
let alpha = jubjub::Fr::random(&mut self.rng);
2018-11-20 05:37:21 -08:00
self.mtx.value_balance += Amount::from_u64(note.value).map_err(|_| Error::InvalidAmount)?;
2018-11-20 05:37:21 -08:00
self.spends.push(SpendDescriptionInfo {
extsk,
diversifier,
note,
alpha,
merkle_path,
2018-11-20 05:37:21 -08:00
});
Ok(())
}
/// 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> {
let output = SaplingOutput::new_internal(
&self.params,
self.height,
&mut self.rng,
ovk,
to,
value,
memo,
)?;
2018-11-20 05:37:21 -08:00
2019-07-25 12:53:42 -07:00
self.mtx.value_balance -= value;
2018-11-20 05:37:21 -08:00
self.outputs.push(output);
Ok(())
}
/// 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: OutPoint,
coin: TxOut,
) -> Result<(), Error> {
self.transparent_inputs.push(sk, coin)?;
self.mtx.vin.push(TxIn::new(utxo));
Ok(())
}
/// Adds a transparent address to send funds to.
pub fn add_transparent_output(
&mut self,
to: &TransparentAddress,
value: Amount,
) -> Result<(), Error> {
if value.is_negative() {
return Err(Error::InvalidAmount);
}
self.mtx.vout.push(TxOut {
value,
script_pubkey: to.script(),
});
Ok(())
}
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) {
2018-11-20 05:37:21 -08:00
self.change_address = Some((ovk, to));
}
/// Builds a transaction from the configured spends and outputs.
///
/// Upon success, returns a tuple containing the final transaction, and the
/// [`TransactionMetadata`] generated during the build process.
///
/// `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(
2021-04-26 10:14:01 -07:00
self,
consensus_branch_id: consensus::BranchId,
prover: &impl TxProver,
) -> Result<(Transaction, TransactionMetadata), Error> {
self.build_with_progress_notifier(consensus_branch_id, prover, None)
}
/// Builds a transaction from the configured spends and outputs.
///
/// Upon success, returns a tuple containing the final transaction, and the
/// [`TransactionMetadata`] generated during the build process.
///
/// `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.
///
/// `progress_notifier` sets the notifier channel, where progress of building the transaction is sent.
/// It sends an update 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 build_with_progress_notifier(
2018-11-20 05:37:21 -08:00
mut self,
consensus_branch_id: consensus::BranchId,
prover: &impl TxProver,
2021-04-26 10:14:01 -07:00
progress_notifier: Option<Sender<Progress>>,
2018-11-20 05:37:21 -08:00
) -> Result<(Transaction, TransactionMetadata), Error> {
let mut tx_metadata = TransactionMetadata::new();
//
// Consistency checks
//
// Valid change
let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum()
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>();
#[cfg(feature = "zfuture")]
let change = change
+ self
.tze_inputs
.builders
.iter()
.map(|ein| ein.prevout.value)
.sum::<Amount>()
- self
.mtx
.tze_outputs
.iter()
.map(|tzo| tzo.value)
2019-07-25 12:53:42 -07:00
.sum::<Amount>();
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() {
// Send change to the specified change address. If no change address
// was set, send change to the first Sapling address given as input.
let change_address = if let Some(change_address) = self.change_address.take() {
change_address
} else if !self.spends.is_empty() {
(
self.spends[0].extsk.expsk.ovk,
PaymentAddress::from_parts(
self.spends[0].diversifier,
2020-10-30 06:27:49 -07:00
self.spends[0].note.pk_d,
)
.ok_or(Error::InvalidAddress)?,
2018-11-20 05:37:21 -08:00
)
} else {
return Err(Error::NoChangeAddress);
2018-11-20 05:37:21 -08:00
};
self.add_sapling_output(Some(change_address.0), change_address.1, change, None)?;
2018-11-20 05:37:21 -08:00
}
//
// Record initial positions of spends and outputs
//
let mut spends: Vec<_> = self.spends.into_iter().enumerate().collect();
let mut outputs: Vec<_> = self
.outputs
.into_iter()
.enumerate()
.map(|(i, o)| Some((i, o)))
.collect();
//
// Sapling spends and outputs
//
let mut ctx = prover.new_sapling_proving_context();
// Pad Sapling outputs
let orig_outputs_len = outputs.len();
if !spends.is_empty() {
while outputs.len() < MIN_SHIELDED_OUTPUTS {
outputs.push(None);
}
}
// Randomize order of inputs and outputs
spends.shuffle(&mut self.rng);
outputs.shuffle(&mut self.rng);
tx_metadata.spend_indices.resize(spends.len(), 0);
tx_metadata.output_indices.resize(orig_outputs_len, 0);
2020-04-03 12:13:39 -07:00
// Record if we'll need a binding signature
let binding_sig_needed = !spends.is_empty() || !outputs.is_empty();
2021-04-26 10:14:01 -07:00
// Keep track of the total number of steps computed
let total_progress = spends.len() as u32 + outputs.len() as u32;
let mut progress = 0u32;
2018-11-20 05:37:21 -08:00
// Create Sapling SpendDescriptions
if !spends.is_empty() {
let anchor = self.anchor.expect("anchor was set if spends were added");
2018-11-20 05:37:21 -08:00
for (i, (pos, spend)) in spends.iter().enumerate() {
let proof_generation_key = spend.extsk.expsk.proof_generation_key();
let nullifier = spend.note.nf(
&proof_generation_key.to_viewing_key(),
spend.merkle_path.position,
);
let (zkproof, cv, rk) = prover
.spend_proof(
&mut ctx,
proof_generation_key,
spend.diversifier,
2020-08-03 20:23:03 -07:00
spend.note.rseed,
spend.alpha,
spend.note.value,
anchor,
spend.merkle_path.clone(),
)
.map_err(|()| Error::SpendProof)?;
2018-11-20 05:37:21 -08:00
self.mtx.shielded_spends.push(SpendDescription {
cv,
2018-11-20 05:37:21 -08:00
anchor,
nullifier,
rk,
zkproof,
spend_auth_sig: None,
});
2021-04-26 10:14:01 -07:00
// Update progress and send a notification on the channel
progress += 1;
progress_notifier
.as_ref()
.map(|tx| tx.send(Progress::new(progress, Some(total_progress))));
// Record the post-randomized spend location
tx_metadata.spend_indices[*pos] = i;
}
2018-11-20 05:37:21 -08:00
}
// Create Sapling OutputDescriptions
for (i, output) in outputs.into_iter().enumerate() {
let output_desc = if let Some((pos, output)) = output {
// Record the post-randomized output location
tx_metadata.output_indices[pos] = i;
output.build_internal(prover, &mut ctx, &mut self.rng)
2018-11-20 05:37:21 -08:00
} else {
// This is a dummy output
let (dummy_to, dummy_note) = {
let (diversifier, g_d) = {
let mut diversifier;
let g_d;
loop {
let mut d = [0; 11];
self.rng.fill_bytes(&mut d);
diversifier = Diversifier(d);
if let Some(val) = diversifier.g_d() {
2018-11-20 05:37:21 -08:00
g_d = val;
break;
}
}
(diversifier, g_d)
};
let (pk_d, payment_address) = loop {
let dummy_ivk = jubjub::Fr::random(&mut self.rng);
let pk_d = g_d * dummy_ivk;
2020-10-30 06:27:49 -07:00
if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d) {
break (pk_d, addr);
}
2018-11-20 05:37:21 -08:00
};
let rseed =
generate_random_rseed_internal(&self.params, self.height, &mut self.rng);
2018-11-20 05:37:21 -08:00
(
payment_address,
2018-11-20 05:37:21 -08:00
Note {
g_d,
pk_d,
rseed,
2018-11-20 05:37:21 -08:00
value: 0,
},
)
};
let esk = dummy_note.generate_or_derive_esk_internal(&mut self.rng);
let epk = dummy_note.g_d * esk;
2018-11-20 05:37:21 -08:00
2020-07-29 21:36:12 -07:00
let (zkproof, cv) = prover.output_proof(
&mut ctx,
esk,
dummy_to,
dummy_note.rcm(),
dummy_note.value,
);
2018-11-20 05:37:21 -08:00
let cmu = dummy_note.cmu();
2018-11-20 05:37:21 -08:00
let mut enc_ciphertext = [0u8; 580];
let mut out_ciphertext = [0u8; 80];
self.rng.fill_bytes(&mut enc_ciphertext[..]);
self.rng.fill_bytes(&mut out_ciphertext[..]);
OutputDescription {
cv,
cmu,
ephemeral_key: epk.into(),
enc_ciphertext,
out_ciphertext,
zkproof,
}
};
2021-04-26 10:14:01 -07:00
// Update progress and send a notification on the channel
progress += 1;
progress_notifier
.as_ref()
.map(|tx| tx.send(Progress::new(progress, Some(total_progress))));
2018-11-20 05:37:21 -08:00
self.mtx.shielded_outputs.push(output_desc);
}
//
// 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(
&self.mtx,
consensus_branch_id,
SIGHASH_ALL,
2020-06-03 19:39:43 -07:00
SignableInput::Shielded,
2018-11-20 05:37:21 -08:00
));
// Create Sapling spendAuth and binding signatures
for (i, (_, spend)) in spends.into_iter().enumerate() {
self.mtx.shielded_spends[i].spend_auth_sig = Some(spend_sig_internal(
2018-11-20 05:37:21 -08:00
PrivateKey(spend.extsk.expsk.ask),
spend.alpha,
&sighash,
2019-06-12 15:12:55 -07:00
&mut self.rng,
2018-11-20 05:37:21 -08:00
));
}
2020-04-03 12:13:39 -07:00
// Add a binding signature if needed
self.mtx.binding_sig = if binding_sig_needed {
Some(
2020-04-03 12:13:39 -07:00
prover
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
.map_err(|_| Error::BindingSig)?,
)
2020-04-03 12:13:39 -07:00
} else {
None
};
2018-11-20 05:37:21 -08:00
// Create TZE input witnesses
#[cfg(feature = "zfuture")]
for (i, tze_in) in self.tze_inputs.builders.into_iter().enumerate() {
// The witness builder function should have cached/closed over whatever data was necessary for the
// witness to commit to at the time it was added to the transaction builder; here, it then computes those
// commitments.
let (mode, payload) = (tze_in.builder)(&self.mtx)?;
let mut current = self.mtx.tze_inputs.get_mut(i).unwrap();
if mode != current.witness.mode {
return Err(Error::TzeWitnessModeMismatch(current.witness.mode, mode));
}
current.witness.payload = payload;
}
// Transparent signatures
2019-11-13 11:21:47 -08:00
self.transparent_inputs
.apply_signatures(&mut self.mtx, consensus_branch_id);
2018-11-20 05:37:21 -08:00
Ok((
self.mtx.freeze().expect("Transaction should be complete"),
tx_metadata,
))
}
}
#[cfg(feature = "zfuture")]
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
for Builder<'a, P, R>
{
type BuildCtx = TransactionData;
type BuildError = Error;
fn add_tze_input<WBuilder, W: ToPayload>(
&mut self,
extension_id: u32,
mode: u32,
(outpoint, prevout): (TzeOutPoint, TzeOut),
witness_builder: WBuilder,
) -> Result<(), Self::BuildError>
where
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, Self::BuildError>),
{
self.mtx
.tze_inputs
.push(TzeIn::new(outpoint, extension_id, mode));
self.tze_inputs.push(prevout, witness_builder);
Ok(())
}
fn add_tze_output<G: ToPayload>(
&mut self,
extension_id: u32,
value: Amount,
guarded_by: &G,
) -> Result<(), Self::BuildError> {
if value.is_negative() {
return Err(Error::InvalidAmount);
}
let (mode, payload) = guarded_by.to_payload();
self.mtx.tze_outputs.push(TzeOut {
value,
precondition: tze::Precondition {
extension_id,
mode,
payload,
},
});
Ok(())
}
}
#[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_with_mtx(params, height, rng, TransactionData::new())
}
/// 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_with_mtx(params, height, rng, TransactionData::zfuture())
}
pub fn mock_build(
self,
consensus_branch_id: consensus::BranchId,
) -> Result<(Transaction, TransactionMetadata), Error> {
self.build(consensus_branch_id, &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;
use std::marker::PhantomData;
2018-11-20 05:37:21 -08:00
use crate::{
consensus::{self, Parameters, H0, 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, amount::DEFAULT_FEE},
2018-11-20 05:37:21 -08:00
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use super::{Builder, Error};
#[cfg(feature = "zfuture")]
use super::TzeInputs;
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 mut builder = Builder::new(TEST_NETWORK, H0);
assert_eq!(
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
Err(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;
2020-04-03 12:13:39 -07:00
use crate::transaction::{
builder::{self, TransparentInputs},
TransactionData,
};
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,
height: sapling_activation_height,
2020-04-03 12:13:39 -07:00
mtx: TransactionData::new(),
fee: Amount::zero(),
anchor: None,
spends: vec![],
outputs: vec![],
transparent_inputs: TransparentInputs::default(),
#[cfg(feature = "zfuture")]
tze_inputs: TzeInputs::default(),
2020-04-03 12:13:39 -07:00
change_address: None,
_phantom: &PhantomData,
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(consensus::BranchId::Sapling, &MockTxProver)
.unwrap();
// No binding signature, because only t input and outputs
assert!(tx.binding_sig.is_none());
}
#[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 mut builder = Builder::new(TEST_NETWORK, H0);
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(consensus::BranchId::Sapling, &MockTxProver),
Err(Error::BindingSig)
);
}
#[test]
fn fails_on_negative_transparent_output() {
let mut builder = Builder::new(TEST_NETWORK, H0);
assert_eq!(
builder.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_i64(-1).unwrap(),
),
Err(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(&[]);
// Fails with no inputs or outputs
// 0.0001 t-ZEC fee
{
let builder = Builder::new(TEST_NETWORK, H0);
assert_eq!(
builder.build(consensus::BranchId::Sapling, &MockTxProver),
Err(Error::ChangeIsNegative(Amount::zero() - DEFAULT_FEE))
);
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, H0);
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(consensus::BranchId::Sapling, &MockTxProver),
Err(Error::ChangeIsNegative(
Amount::from_i64(-50000).unwrap() - DEFAULT_FEE
))
);
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, H0);
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(50000).unwrap(),
)
.unwrap();
assert_eq!(
builder.build(consensus::BranchId::Sapling, &MockTxProver),
Err(Error::ChangeIsNegative(
Amount::from_i64(-50000).unwrap() - DEFAULT_FEE
))
);
}
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, H0);
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(consensus::BranchId::Sapling, &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, H0);
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(consensus::BranchId::Sapling, &MockTxProver),
Err(Error::BindingSig)
)
2018-11-20 05:37:21 -08:00
}
}
}