2018-11-20 05:37:21 -08:00
|
|
|
//! Structs for building transactions.
|
|
|
|
|
2020-07-01 13:26:54 -07:00
|
|
|
use crate::primitives::{Diversifier, Note, PaymentAddress};
|
2019-08-20 16:17:21 -07:00
|
|
|
use crate::zip32::ExtendedSpendingKey;
|
2019-08-15 09:39:55 -07:00
|
|
|
use ff::Field;
|
|
|
|
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
|
2019-03-08 19:20:32 -08:00
|
|
|
use std::error;
|
|
|
|
use std::fmt;
|
2020-08-05 20:36:02 -07:00
|
|
|
use std::marker::PhantomData;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
use crate::{
|
2020-08-05 13:08:58 -07:00
|
|
|
consensus::{self, BlockHeight},
|
2018-11-20 05:37:21 -08:00
|
|
|
keys::OutgoingViewingKey,
|
2019-05-24 05:32:55 -07:00
|
|
|
legacy::TransparentAddress,
|
2020-02-07 16:25:24 -08:00
|
|
|
merkle_tree::MerklePath,
|
2020-07-30 07:34:29 -07:00
|
|
|
note_encryption::{Memo, SaplingNoteEncryption},
|
2018-11-20 05:37:21 -08:00
|
|
|
prover::TxProver,
|
2019-05-04 09:47:18 -07:00
|
|
|
redjubjub::PrivateKey,
|
2018-11-20 05:37:21 -08:00
|
|
|
sapling::{spend_sig, Node},
|
|
|
|
transaction::{
|
2019-07-25 14:37:16 -07:00
|
|
|
components::{amount::DEFAULT_FEE, Amount, OutputDescription, SpendDescription, TxOut},
|
2018-11-20 05:37:21 -08:00
|
|
|
signature_hash_data, Transaction, TransactionData, SIGHASH_ALL,
|
|
|
|
},
|
2020-08-05 00:21:42 -07:00
|
|
|
util::generate_random_rseed,
|
2018-11-20 05:37:21 -08:00
|
|
|
};
|
|
|
|
|
2019-07-31 08:20:13 -07:00
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
use crate::{
|
|
|
|
legacy::Script,
|
|
|
|
transaction::components::{OutPoint, TxIn},
|
|
|
|
};
|
|
|
|
|
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
|
|
|
|
/// with dummy outputs if necessary. See https://github.com/zcash/zcash/issues/3615
|
|
|
|
const MIN_SHIELDED_OUTPUTS: usize = 2;
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
2019-08-13 07:10:57 -07:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
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::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"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl error::Error for Error {}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
struct SpendDescriptionInfo {
|
|
|
|
extsk: ExtendedSpendingKey,
|
|
|
|
diversifier: Diversifier,
|
2020-07-01 13:26:54 -07:00
|
|
|
note: Note,
|
|
|
|
alpha: jubjub::Fr,
|
2020-02-07 16:25:24 -08:00
|
|
|
merkle_path: MerklePath<Node>,
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SaplingOutput {
|
2020-08-28 08:12:37 -07:00
|
|
|
/// `None` represents the `ovk = ⊥` case.
|
|
|
|
ovk: Option<OutgoingViewingKey>,
|
2020-07-01 13:26:54 -07:00
|
|
|
to: PaymentAddress,
|
|
|
|
note: Note,
|
2018-11-20 05:37:21 -08:00
|
|
|
memo: Memo,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SaplingOutput {
|
2020-07-29 22:13:59 -07:00
|
|
|
pub fn new<R: RngCore + CryptoRng, P: consensus::Parameters>(
|
2020-08-05 13:27:40 -07:00
|
|
|
params: &P,
|
2020-08-05 13:08:58 -07:00
|
|
|
height: BlockHeight,
|
2018-11-20 05:37:21 -08:00
|
|
|
rng: &mut R,
|
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,
|
|
|
|
memo: Option<Memo>,
|
|
|
|
) -> Result<Self, Error> {
|
2020-07-01 13:26:54 -07:00
|
|
|
let g_d = match to.g_d() {
|
2018-11-20 05:37:21 -08:00
|
|
|
Some(g_d) => g_d,
|
2019-08-13 07:10:57 -07:00
|
|
|
None => return Err(Error::InvalidAddress),
|
2018-11-20 05:37:21 -08:00
|
|
|
};
|
2019-07-25 12:50:17 -07:00
|
|
|
if value.is_negative() {
|
2019-08-13 07:10:57 -07:00
|
|
|
return Err(Error::InvalidAmount);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-08-05 13:27:40 -07:00
|
|
|
let rseed = generate_random_rseed(params, height, rng);
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
let note = Note {
|
|
|
|
g_d,
|
2019-08-23 15:08:09 -07:00
|
|
|
pk_d: to.pk_d().clone(),
|
2019-07-25 14:37:16 -07:00
|
|
|
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_default(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-06-12 15:11:06 -07:00
|
|
|
pub fn build<P: TxProver, R: RngCore + CryptoRng>(
|
2018-11-20 05:37:21 -08:00
|
|
|
self,
|
|
|
|
prover: &P,
|
|
|
|
ctx: &mut P::SaplingProvingContext,
|
2019-06-12 15:11:06 -07:00
|
|
|
rng: &mut R,
|
2018-11-20 05:37:21 -08:00
|
|
|
) -> OutputDescription {
|
2020-08-28 08:12:37 -07:00
|
|
|
let mut encryptor = SaplingNoteEncryption::new(
|
2019-06-12 15:11:06 -07:00
|
|
|
self.ovk,
|
|
|
|
self.note.clone(),
|
|
|
|
self.to.clone(),
|
|
|
|
self.memo,
|
2020-08-05 21:47:35 -07:00
|
|
|
rng,
|
2019-06-12 15:11:06 -07:00
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
let (zkproof, cv) = prover.output_proof(
|
|
|
|
ctx,
|
|
|
|
encryptor.esk().clone(),
|
|
|
|
self.to,
|
2020-07-29 21:36:12 -07:00
|
|
|
self.note.rcm(),
|
2018-11-20 05:37:21 -08:00
|
|
|
self.note.value,
|
|
|
|
);
|
|
|
|
|
2020-08-21 10:33:22 -07:00
|
|
|
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);
|
|
|
|
|
|
|
|
let ephemeral_key = encryptor.epk().clone().into();
|
|
|
|
|
|
|
|
OutputDescription {
|
|
|
|
cv,
|
|
|
|
cmu,
|
|
|
|
ephemeral_key,
|
|
|
|
enc_ciphertext,
|
|
|
|
out_ciphertext,
|
|
|
|
zkproof,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-31 08:20:13 -07:00
|
|
|
#[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 {
|
2019-11-13 11:20:09 -08:00
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
fn push(
|
|
|
|
&mut self,
|
|
|
|
mtx: &mut TransactionData,
|
|
|
|
sk: secp256k1::SecretKey,
|
|
|
|
utxo: OutPoint,
|
|
|
|
coin: TxOut,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
if coin.value.is_negative() {
|
|
|
|
return Err(Error::InvalidAmount);
|
|
|
|
}
|
|
|
|
|
|
|
|
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};
|
|
|
|
|
|
|
|
if &hash[..] != &Ripemd160::digest(&Sha256::digest(&pubkey))[..] {
|
|
|
|
return Err(Error::InvalidAddress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => return Err(Error::InvalidAddress),
|
|
|
|
}
|
|
|
|
|
|
|
|
mtx.vin.push(TxIn::new(utxo));
|
|
|
|
self.inputs.push(TransparentInputInfo { sk, pubkey, coin });
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-11-13 11:21:47 -08:00
|
|
|
fn value_sum(&self) -> Amount {
|
2019-07-31 08:20:13 -07:00
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
{
|
|
|
|
self.inputs
|
|
|
|
.iter()
|
|
|
|
.map(|input| input.coin.value)
|
|
|
|
.sum::<Amount>()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "transparent-inputs"))]
|
|
|
|
{
|
|
|
|
Amount::zero()
|
|
|
|
}
|
|
|
|
}
|
2019-11-13 11:12:55 -08:00
|
|
|
|
|
|
|
#[cfg(feature = "transparent-inputs")]
|
2019-11-25 07:41:14 -08:00
|
|
|
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];
|
2019-11-13 11:12:55 -08:00
|
|
|
for (i, info) in self.inputs.iter().enumerate() {
|
|
|
|
sighash.copy_from_slice(&signature_hash_data(
|
|
|
|
mtx,
|
|
|
|
consensus_branch_id,
|
|
|
|
SIGHASH_ALL,
|
|
|
|
Some((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"))]
|
2019-11-25 07:41:14 -08:00
|
|
|
fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {}
|
2019-07-31 08:20:13 -07:00
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
/// Metadata about a transaction created by a [`Builder`].
|
2019-08-13 07:24:08 -07:00
|
|
|
#[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> {
|
2019-08-02 03:25:00 -07:00
|
|
|
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> {
|
2019-08-02 03:25:00 -07:00
|
|
|
self.output_indices.get(n).copied()
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates a [`Transaction`] from its inputs and outputs.
|
2020-08-05 20:36:02 -07:00
|
|
|
pub struct Builder<P: consensus::Parameters, R: RngCore + CryptoRng> {
|
2020-08-05 13:27:40 -07:00
|
|
|
params: P,
|
2019-06-12 15:11:51 -07:00
|
|
|
rng: R,
|
2020-08-05 13:08:58 -07:00
|
|
|
height: BlockHeight,
|
2018-11-20 05:37:21 -08:00
|
|
|
mtx: TransactionData,
|
|
|
|
fee: Amount,
|
2020-07-01 13:26:54 -07:00
|
|
|
anchor: Option<bls12_381::Scalar>,
|
2018-11-20 05:37:21 -08:00
|
|
|
spends: Vec<SpendDescriptionInfo>,
|
|
|
|
outputs: Vec<SaplingOutput>,
|
2019-11-13 11:21:47 -08:00
|
|
|
transparent_inputs: TransparentInputs,
|
2020-07-01 13:26:54 -07:00
|
|
|
change_address: Option<(OutgoingViewingKey, PaymentAddress)>,
|
2020-08-05 20:36:02 -07:00
|
|
|
phantom: PhantomData<P>,
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-08-06 02:33:33 -07:00
|
|
|
impl<P: consensus::Parameters> Builder<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).
|
2020-08-05 13:27:40 -07:00
|
|
|
pub fn new(params: P, height: BlockHeight) -> Self {
|
|
|
|
Builder::new_with_rng(params, height, OsRng)
|
2020-08-05 20:36:02 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
|
2019-06-12 15:11:51 -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).
|
2020-08-05 13:27:40 -07:00
|
|
|
pub fn new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder<P, R> {
|
2018-11-20 05:37:21 -08:00
|
|
|
let mut mtx = TransactionData::new();
|
|
|
|
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
|
|
|
|
|
|
|
|
Builder {
|
2020-08-05 13:27:40 -07:00
|
|
|
params,
|
2019-06-12 15:11:51 -07:00
|
|
|
rng,
|
2020-07-29 22:13:59 -07:00
|
|
|
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(),
|
2018-11-20 05:37:21 -08:00
|
|
|
change_address: None,
|
2020-08-05 20:36:02 -07:00
|
|
|
phantom: PhantomData,
|
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> {
|
|
|
|
// Consistency check: all anchors must equal the first one
|
2020-08-21 12:43:19 -07:00
|
|
|
let cmu = Node::new(note.cmu().into());
|
2018-11-20 05:37:21 -08:00
|
|
|
if let Some(anchor) = self.anchor {
|
2020-08-21 12:43:19 -07:00
|
|
|
let path_root: bls12_381::Scalar = merkle_path.root(cmu).into();
|
2020-02-07 16:25:24 -08:00
|
|
|
if path_root != anchor {
|
2019-08-13 07:10:57 -07:00
|
|
|
return Err(Error::AnchorMismatch);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
} else {
|
2020-08-21 12:43:19 -07:00
|
|
|
self.anchor = Some(merkle_path.root(cmu).into())
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2020-07-01 13:26:54 -07:00
|
|
|
let alpha = jubjub::Fr::random(&mut self.rng);
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2019-08-13 07:10:57 -07: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,
|
2020-02-07 16:25:24 -08:00
|
|
|
merkle_path,
|
2018-11-20 05:37:21 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
memo: Option<Memo>,
|
|
|
|
) -> Result<(), Error> {
|
2020-08-05 13:27:40 -07:00
|
|
|
let output = SaplingOutput::new(
|
|
|
|
&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(())
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
utxo: OutPoint,
|
|
|
|
coin: TxOut,
|
|
|
|
) -> Result<(), Error> {
|
2019-11-13 11:21:47 -08:00
|
|
|
self.transparent_inputs.push(&mut self.mtx, sk, utxo, coin)
|
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> {
|
2019-07-25 12:50:17 -07:00
|
|
|
if value.is_negative() {
|
2019-08-13 07:10:57 -07:00
|
|
|
return Err(Error::InvalidAmount);
|
2019-05-24 05:32:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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`]).
|
2020-07-01 13:26:54 -07:00
|
|
|
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(
|
|
|
|
mut self,
|
2019-11-25 07:41:14 -08:00
|
|
|
consensus_branch_id: consensus::BranchId,
|
2020-02-07 16:37:31 -08:00
|
|
|
prover: &impl TxProver,
|
2018-11-20 05:37:21 -08:00
|
|
|
) -> Result<(Transaction, TransactionMetadata), Error> {
|
|
|
|
let mut tx_metadata = TransactionMetadata::new();
|
|
|
|
|
|
|
|
//
|
|
|
|
// Consistency checks
|
|
|
|
//
|
|
|
|
|
|
|
|
// Valid change
|
2019-11-13 11:21:47 -08:00
|
|
|
let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum()
|
2019-05-24 05:32:55 -07:00
|
|
|
- self
|
|
|
|
.mtx
|
|
|
|
.vout
|
|
|
|
.iter()
|
2019-07-25 12:53:42 -07:00
|
|
|
.map(|output| output.value)
|
|
|
|
.sum::<Amount>();
|
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.
|
|
|
|
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,
|
2019-08-23 15:08:09 -07:00
|
|
|
PaymentAddress::from_parts(
|
|
|
|
self.spends[0].diversifier,
|
|
|
|
self.spends[0].note.pk_d.clone(),
|
|
|
|
)
|
|
|
|
.ok_or(Error::InvalidAddress)?,
|
2018-11-20 05:37:21 -08:00
|
|
|
)
|
|
|
|
} else {
|
2019-08-13 07:10:57 -07:00
|
|
|
return Err(Error::NoChangeAddress);
|
2018-11-20 05:37:21 -08:00
|
|
|
};
|
|
|
|
|
2020-08-28 08:12:37 -07: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();
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
// Create Sapling SpendDescriptions
|
2019-09-10 10:01:13 -07:00
|
|
|
if !spends.is_empty() {
|
|
|
|
let anchor = self.anchor.expect("anchor was set if spends were added");
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2019-09-10 10:01:13 -07:00
|
|
|
for (i, (pos, spend)) in spends.iter().enumerate() {
|
2020-07-01 13:26:54 -07:00
|
|
|
let proof_generation_key = spend.extsk.expsk.proof_generation_key();
|
2019-09-10 10:01:13 -07:00
|
|
|
|
|
|
|
let mut nullifier = [0u8; 32];
|
|
|
|
nullifier.copy_from_slice(&spend.note.nf(
|
2020-07-01 13:26:54 -07:00
|
|
|
&proof_generation_key.to_viewing_key(),
|
2020-02-07 16:25:24 -08:00
|
|
|
spend.merkle_path.position,
|
2019-09-10 10:01:13 -07:00
|
|
|
));
|
|
|
|
|
|
|
|
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,
|
2019-09-10 10:01:13 -07:00
|
|
|
spend.alpha,
|
|
|
|
spend.note.value,
|
|
|
|
anchor,
|
2020-02-07 16:25:24 -08:00
|
|
|
spend.merkle_path.clone(),
|
2019-09-10 10:01:13 -07:00
|
|
|
)
|
|
|
|
.map_err(|()| Error::SpendProof)?;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2019-09-10 10:01:13 -07:00
|
|
|
self.mtx.shielded_spends.push(SpendDescription {
|
|
|
|
cv,
|
2018-11-20 05:37:21 -08:00
|
|
|
anchor,
|
2019-09-10 10:01:13 -07:00
|
|
|
nullifier,
|
|
|
|
rk,
|
|
|
|
zkproof,
|
|
|
|
spend_auth_sig: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2020-02-07 16:37:31 -08:00
|
|
|
output.build(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);
|
2020-07-01 13:26:54 -07:00
|
|
|
if let Some(val) = diversifier.g_d() {
|
2018-11-20 05:37:21 -08:00
|
|
|
g_d = val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(diversifier, g_d)
|
|
|
|
};
|
|
|
|
|
2019-08-23 15:08:09 -07:00
|
|
|
let (pk_d, payment_address) = loop {
|
2020-07-01 13:26:54 -07:00
|
|
|
let dummy_ivk = jubjub::Fr::random(&mut self.rng);
|
|
|
|
let pk_d = g_d * dummy_ivk;
|
2019-08-23 15:08:09 -07:00
|
|
|
if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d.clone()) {
|
|
|
|
break (pk_d, addr);
|
|
|
|
}
|
2018-11-20 05:37:21 -08:00
|
|
|
};
|
|
|
|
|
2020-08-05 13:27:40 -07:00
|
|
|
let rseed = generate_random_rseed(&self.params, self.height, &mut self.rng);
|
2020-08-04 23:27:36 -07:00
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
(
|
2019-08-23 15:08:09 -07:00
|
|
|
payment_address,
|
2018-11-20 05:37:21 -08:00
|
|
|
Note {
|
|
|
|
g_d,
|
|
|
|
pk_d,
|
2020-08-04 23:27:36 -07:00
|
|
|
rseed,
|
2018-11-20 05:37:21 -08:00
|
|
|
value: 0,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2020-07-30 07:34:29 -07:00
|
|
|
let esk = dummy_note.generate_or_derive_esk(&mut self.rng);
|
2020-07-01 13:26:54 -07:00
|
|
|
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
|
|
|
|
2020-08-21 10:33:22 -07: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,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.mtx.shielded_outputs.push(output_desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Signatures
|
|
|
|
//
|
|
|
|
|
|
|
|
let mut sighash = [0u8; 32];
|
|
|
|
sighash.copy_from_slice(&signature_hash_data(
|
|
|
|
&self.mtx,
|
|
|
|
consensus_branch_id,
|
|
|
|
SIGHASH_ALL,
|
|
|
|
None,
|
|
|
|
));
|
|
|
|
|
|
|
|
// 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(
|
|
|
|
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
|
|
|
|
if binding_sig_needed {
|
|
|
|
self.mtx.binding_sig = Some(
|
|
|
|
prover
|
|
|
|
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
|
|
|
|
.map_err(|()| Error::BindingSig)?,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
self.mtx.binding_sig = None;
|
|
|
|
}
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2019-07-31 08:20:13 -07:00
|
|
|
// Transparent signatures
|
2019-11-13 11:21:47 -08:00
|
|
|
self.transparent_inputs
|
2019-11-13 11:12:55 -08:00
|
|
|
.apply_signatures(&mut self.mtx, consensus_branch_id);
|
2019-07-31 08:20:13 -07:00
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
Ok((
|
|
|
|
self.mtx.freeze().expect("Transaction should be complete"),
|
|
|
|
tx_metadata,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use ff::{Field, PrimeField};
|
2019-09-28 00:48:43 -07:00
|
|
|
use rand_core::OsRng;
|
2020-08-05 20:36:02 -07:00
|
|
|
use std::marker::PhantomData;
|
2019-08-06 02:46:40 -07:00
|
|
|
|
2019-08-13 07:10:57 -07:00
|
|
|
use super::{Builder, Error};
|
2018-11-20 05:37:21 -08:00
|
|
|
use crate::{
|
2020-08-05 13:27:40 -07:00
|
|
|
consensus::{self, Network::TestNetwork, H0},
|
2019-05-24 05:32:55 -07:00
|
|
|
legacy::TransparentAddress,
|
2018-11-20 05:37:21 -08:00
|
|
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
2020-07-29 21:36:12 -07:00
|
|
|
primitives::Rseed,
|
2018-11-20 05:37:21 -08:00
|
|
|
prover::mock::MockTxProver,
|
|
|
|
sapling::Node,
|
|
|
|
transaction::components::Amount,
|
|
|
|
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
|
|
|
};
|
|
|
|
|
|
|
|
#[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;
|
|
|
|
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2020-08-28 08:12:37 -07:00
|
|
|
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
|
2019-08-13 07:24:08 -07:00
|
|
|
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() {
|
2020-08-02 22:39:36 -07:00
|
|
|
use crate::consensus::{NetworkUpgrade, Parameters};
|
2020-04-03 12:13:39 -07:00
|
|
|
use crate::transaction::{
|
|
|
|
builder::{self, TransparentInputs},
|
|
|
|
TransactionData,
|
|
|
|
};
|
|
|
|
|
2020-08-05 13:27:40 -07:00
|
|
|
let sapling_activation_height = TestNetwork
|
|
|
|
.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 {
|
|
|
|
params: TestNetwork,
|
2020-04-03 12:13:39 -07:00
|
|
|
rng: OsRng,
|
2020-08-02 22:39:36 -07:00
|
|
|
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(),
|
|
|
|
change_address: None,
|
2020-08-05 20:36:02 -07:00
|
|
|
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
|
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());
|
2020-04-03 12:13:39 -07:00
|
|
|
let mut tree = CommitmentTree::new();
|
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);
|
|
|
|
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
2020-04-03 12:13:39 -07:00
|
|
|
|
|
|
|
// Create a tx with a sapling spend. binding_sig should be present
|
|
|
|
builder
|
|
|
|
.add_sapling_spend(
|
|
|
|
extsk.clone(),
|
|
|
|
*to.diversifier(),
|
|
|
|
note1.clone(),
|
|
|
|
witness1.path().unwrap(),
|
|
|
|
)
|
|
|
|
.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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
#[test]
|
|
|
|
fn fails_on_negative_transparent_output() {
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
|
|
|
builder.add_transparent_output(
|
|
|
|
&TransparentAddress::PublicKey([0; 20]),
|
|
|
|
Amount::from_i64(-1).unwrap(),
|
|
|
|
),
|
|
|
|
Err(Error::InvalidAmount)
|
|
|
|
);
|
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(&[]);
|
|
|
|
|
|
|
|
// Fails with no inputs or outputs
|
|
|
|
// 0.0001 t-ZEC fee
|
|
|
|
{
|
2020-08-05 13:27:40 -07:00
|
|
|
let builder = Builder::new(TestNetwork, H0);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2020-02-07 16:37:31 -08:00
|
|
|
builder.build(consensus::BranchId::Sapling, &MockTxProver),
|
2019-08-13 07:24:08 -07:00
|
|
|
Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap()))
|
|
|
|
);
|
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);
|
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.0001 t-ZEC fee
|
|
|
|
{
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
2018-11-20 05:37:21 -08:00
|
|
|
builder
|
2020-08-05 20:36:02 -07:00
|
|
|
.add_sapling_output(
|
2019-07-25 14:37:16 -07:00
|
|
|
ovk.clone(),
|
|
|
|
to.clone(),
|
|
|
|
Amount::from_u64(50000).unwrap(),
|
|
|
|
None,
|
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
2020-02-07 16:37:31 -08:00
|
|
|
builder.build(consensus::BranchId::Sapling, &MockTxProver),
|
2019-08-13 07:24:08 -07:00
|
|
|
Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap()))
|
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
// Fail if there is only a transparent output
|
|
|
|
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
|
|
|
|
{
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
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!(
|
2020-02-07 16:37:31 -08:00
|
|
|
builder.build(consensus::BranchId::Sapling, &MockTxProver),
|
2019-08-13 07:24:08 -07:00
|
|
|
Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap()))
|
|
|
|
);
|
2019-05-24 05:32:55 -07:00
|
|
|
}
|
|
|
|
|
2018-11-20 05:37:21 -08:00
|
|
|
let note1 = to
|
2020-07-01 13:26:54 -07:00
|
|
|
.create_note(59999, 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());
|
2018-11-20 05:37:21 -08:00
|
|
|
let mut tree = CommitmentTree::new();
|
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
|
|
|
|
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in
|
2018-11-20 05:37:21 -08:00
|
|
|
{
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
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
|
2020-08-05 20:36:02 -07:00
|
|
|
.add_sapling_output(
|
2019-07-25 14:37:16 -07:00
|
|
|
ovk.clone(),
|
|
|
|
to.clone(),
|
|
|
|
Amount::from_u64(30000).unwrap(),
|
|
|
|
None,
|
|
|
|
)
|
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!(
|
2020-02-07 16:37:31 -08:00
|
|
|
builder.build(consensus::BranchId::Sapling, &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.)
|
|
|
|
{
|
2020-08-05 13:27:40 -07:00
|
|
|
let mut builder = Builder::new(TestNetwork, H0);
|
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
|
2020-08-05 20:36:02 -07:00
|
|
|
.add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None)
|
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!(
|
2020-02-07 16:37:31 -08:00
|
|
|
builder.build(consensus::BranchId::Sapling, &MockTxProver),
|
2019-11-25 07:41:14 -08:00
|
|
|
Err(Error::BindingSig)
|
|
|
|
)
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|