2018-11-20 05:37:21 -08:00
|
|
|
//! Structs for building transactions.
|
|
|
|
|
2019-08-20 16:17:21 -07:00
|
|
|
use crate::zip32::ExtendedSpendingKey;
|
2019-08-06 02:46:40 -07:00
|
|
|
use crate::{
|
2018-11-20 05:37:21 -08:00
|
|
|
jubjub::fs::Fs,
|
|
|
|
primitives::{Diversifier, Note, PaymentAddress},
|
|
|
|
};
|
2019-08-15 09:39:55 -07:00
|
|
|
use ff::Field;
|
|
|
|
use pairing::bls12_381::{Bls12, Fr};
|
|
|
|
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
keys::OutgoingViewingKey,
|
2019-05-24 05:32:55 -07:00
|
|
|
legacy::TransparentAddress,
|
2018-11-20 05:37:21 -08:00
|
|
|
merkle_tree::{CommitmentTreeWitness, IncrementalWitness},
|
|
|
|
note_encryption::{generate_esk, Memo, SaplingNoteEncryption},
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
JUBJUB,
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
InvalidWitness,
|
|
|
|
NoChangeAddress,
|
|
|
|
SpendProof,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct SpendDescriptionInfo {
|
|
|
|
extsk: ExtendedSpendingKey,
|
|
|
|
diversifier: Diversifier,
|
|
|
|
note: Note<Bls12>,
|
|
|
|
alpha: Fs,
|
|
|
|
witness: CommitmentTreeWitness<Node>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SaplingOutput {
|
|
|
|
ovk: OutgoingViewingKey,
|
|
|
|
to: PaymentAddress<Bls12>,
|
|
|
|
note: Note<Bls12>,
|
|
|
|
memo: Memo,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SaplingOutput {
|
|
|
|
pub fn new<R: RngCore + CryptoRng>(
|
|
|
|
rng: &mut R,
|
|
|
|
ovk: OutgoingViewingKey,
|
|
|
|
to: PaymentAddress<Bls12>,
|
|
|
|
value: Amount,
|
|
|
|
memo: Option<Memo>,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
let g_d = match to.g_d(&JUBJUB) {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
let rcm = Fs::random(rng);
|
|
|
|
|
|
|
|
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(),
|
2018-11-20 05:37:21 -08:00
|
|
|
r: rcm,
|
|
|
|
};
|
|
|
|
|
|
|
|
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 {
|
2019-06-12 15:11:06 -07:00
|
|
|
let encryptor = SaplingNoteEncryption::new(
|
|
|
|
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,
|
|
|
|
encryptor.esk().clone(),
|
|
|
|
self.to,
|
|
|
|
self.note.r,
|
|
|
|
self.note.value,
|
|
|
|
);
|
|
|
|
|
|
|
|
let cmu = self.note.cm(&JUBJUB);
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2019-06-12 15:11:51 -07:00
|
|
|
pub struct Builder<R: RngCore + CryptoRng> {
|
|
|
|
rng: R,
|
2018-11-20 05:37:21 -08:00
|
|
|
mtx: TransactionData,
|
|
|
|
fee: Amount,
|
|
|
|
anchor: Option<Fr>,
|
|
|
|
spends: Vec<SpendDescriptionInfo>,
|
|
|
|
outputs: Vec<SaplingOutput>,
|
|
|
|
change_address: Option<(OutgoingViewingKey, PaymentAddress<Bls12>)>,
|
|
|
|
}
|
|
|
|
|
2019-06-12 15:11:51 -07:00
|
|
|
impl Builder<OsRng> {
|
2018-11-20 05:37:21 -08:00
|
|
|
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
|
2019-06-12 15:11:51 -07:00
|
|
|
/// using default values for general transaction fields and the default OS random.
|
2018-11-20 05:37:21 -08: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).
|
2019-06-12 15:11:51 -07:00
|
|
|
pub fn new(height: u32) -> Self {
|
|
|
|
Builder::new_with_rng(height, OsRng)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<R: RngCore + CryptoRng> Builder<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).
|
|
|
|
pub fn new_with_rng(height: u32, rng: R) -> Builder<R> {
|
2018-11-20 05:37:21 -08:00
|
|
|
let mut mtx = TransactionData::new();
|
|
|
|
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
|
|
|
|
|
|
|
|
Builder {
|
2019-06-12 15:11:51 -07:00
|
|
|
rng,
|
2018-11-20 05:37:21 -08:00
|
|
|
mtx,
|
|
|
|
fee: DEFAULT_FEE,
|
|
|
|
anchor: None,
|
|
|
|
spends: vec![],
|
|
|
|
outputs: vec![],
|
|
|
|
change_address: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a Sapling note to be spent in this transaction.
|
|
|
|
///
|
|
|
|
/// Returns an error if the given witness does not have the same anchor as previous
|
|
|
|
/// witnesses, or has no path.
|
|
|
|
pub fn add_sapling_spend(
|
|
|
|
&mut self,
|
|
|
|
extsk: ExtendedSpendingKey,
|
|
|
|
diversifier: Diversifier,
|
|
|
|
note: Note<Bls12>,
|
|
|
|
witness: IncrementalWitness<Node>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Consistency check: all anchors must equal the first one
|
|
|
|
if let Some(anchor) = self.anchor {
|
|
|
|
let witness_root: Fr = witness.root().into();
|
|
|
|
if witness_root != anchor {
|
2019-08-13 07:10:57 -07:00
|
|
|
return Err(Error::AnchorMismatch);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.anchor = Some(witness.root().into())
|
|
|
|
}
|
2019-08-13 07:10:57 -07:00
|
|
|
let witness = witness.path().ok_or(Error::InvalidWitness)?;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
let alpha = Fs::random(&mut self.rng);
|
|
|
|
|
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,
|
|
|
|
witness,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a Sapling address to send funds to.
|
|
|
|
pub fn add_sapling_output(
|
|
|
|
&mut self,
|
|
|
|
ovk: OutgoingViewingKey,
|
|
|
|
to: PaymentAddress<Bls12>,
|
|
|
|
value: Amount,
|
|
|
|
memo: Option<Memo>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let output = SaplingOutput::new(&mut self.rng, ovk, to, value, memo)?;
|
|
|
|
|
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-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`]).
|
|
|
|
pub fn send_change_to(&mut self, ovk: OutgoingViewingKey, to: PaymentAddress<Bls12>) {
|
|
|
|
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,
|
|
|
|
consensus_branch_id: u32,
|
|
|
|
prover: impl TxProver,
|
|
|
|
) -> Result<(Transaction, TransactionMetadata), Error> {
|
|
|
|
let mut tx_metadata = TransactionMetadata::new();
|
|
|
|
|
|
|
|
//
|
|
|
|
// Consistency checks
|
|
|
|
//
|
|
|
|
|
|
|
|
// Valid change
|
2019-07-25 12:53:42 -07:00
|
|
|
let change = self.mtx.value_balance
|
|
|
|
- self.fee
|
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
|
|
|
};
|
|
|
|
|
2019-07-25 12:53:42 -07:00
|
|
|
self.add_sapling_output(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();
|
|
|
|
let anchor = self.anchor.expect("anchor was set if spends were added");
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
// Create Sapling SpendDescriptions
|
|
|
|
for (i, (pos, spend)) in spends.iter().enumerate() {
|
|
|
|
let proof_generation_key = spend.extsk.expsk.proof_generation_key(&JUBJUB);
|
|
|
|
|
|
|
|
let mut nullifier = [0u8; 32];
|
|
|
|
nullifier.copy_from_slice(&spend.note.nf(
|
2019-08-02 04:00:15 -07:00
|
|
|
&proof_generation_key.to_viewing_key(&JUBJUB),
|
2018-11-20 05:37:21 -08:00
|
|
|
spend.witness.position,
|
|
|
|
&JUBJUB,
|
|
|
|
));
|
|
|
|
|
|
|
|
let (zkproof, cv, rk) = prover
|
|
|
|
.spend_proof(
|
|
|
|
&mut ctx,
|
|
|
|
proof_generation_key,
|
|
|
|
spend.diversifier,
|
|
|
|
spend.note.r,
|
|
|
|
spend.alpha,
|
|
|
|
spend.note.value,
|
|
|
|
anchor,
|
|
|
|
spend.witness.clone(),
|
|
|
|
)
|
2019-08-13 07:10:57 -07:00
|
|
|
.map_err(|()| Error::SpendProof)?;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
|
|
|
self.mtx.shielded_spends.push(SpendDescription {
|
|
|
|
cv,
|
2019-08-02 03:25:00 -07:00
|
|
|
anchor,
|
2018-11-20 05:37:21 -08:00
|
|
|
nullifier,
|
|
|
|
rk,
|
|
|
|
zkproof,
|
|
|
|
spend_auth_sig: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Record the post-randomized spend location
|
|
|
|
tx_metadata.spend_indices[*pos] = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2019-06-12 15:11:06 -07: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);
|
|
|
|
if let Some(val) = diversifier.g_d::<Bls12>(&JUBJUB) {
|
|
|
|
g_d = val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(diversifier, g_d)
|
|
|
|
};
|
|
|
|
|
2019-08-23 15:08:09 -07:00
|
|
|
let (pk_d, payment_address) = loop {
|
2018-11-20 05:37:21 -08:00
|
|
|
let dummy_ivk = Fs::random(&mut self.rng);
|
2019-08-23 15:08:09 -07:00
|
|
|
let pk_d = g_d.mul(dummy_ivk, &JUBJUB);
|
|
|
|
if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d.clone()) {
|
|
|
|
break (pk_d, addr);
|
|
|
|
}
|
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,
|
|
|
|
r: Fs::random(&mut self.rng),
|
|
|
|
value: 0,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2019-06-12 15:11:06 -07:00
|
|
|
let esk = generate_esk(&mut self.rng);
|
2018-11-20 05:37:21 -08:00
|
|
|
let epk = dummy_note.g_d.mul(esk, &JUBJUB);
|
|
|
|
|
|
|
|
let (zkproof, cv) =
|
|
|
|
prover.output_proof(&mut ctx, esk, dummy_to, dummy_note.r, dummy_note.value);
|
|
|
|
|
|
|
|
let cmu = dummy_note.cm(&JUBJUB);
|
|
|
|
|
|
|
|
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
|
|
|
&JUBJUB,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
self.mtx.binding_sig = Some(
|
|
|
|
prover
|
2019-07-25 14:37:16 -07:00
|
|
|
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
|
2019-08-13 07:10:57 -07:00
|
|
|
.map_err(|()| Error::BindingSig)?,
|
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;
|
2019-08-06 02:46:40 -07:00
|
|
|
|
|
|
|
use crate::jubjub::fs::Fs;
|
2018-11-20 05:37:21 -08:00
|
|
|
|
2019-08-13 07:10:57 -07:00
|
|
|
use super::{Builder, Error};
|
2018-11-20 05:37:21 -08:00
|
|
|
use crate::{
|
2019-05-24 05:32:55 -07:00
|
|
|
legacy::TransparentAddress,
|
2018-11-20 05:37:21 -08:00
|
|
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
|
|
|
prover::mock::MockTxProver,
|
|
|
|
sapling::Node,
|
|
|
|
transaction::components::Amount,
|
|
|
|
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
|
|
|
JUBJUB,
|
|
|
|
};
|
|
|
|
|
|
|
|
#[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(0);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
|
|
|
builder.add_sapling_output(ovk, to, Amount::from_i64(-1).unwrap(), None),
|
|
|
|
Err(Error::InvalidAmount)
|
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
2019-05-24 05:32:55 -07:00
|
|
|
#[test]
|
|
|
|
fn fails_on_negative_transparent_output() {
|
|
|
|
let mut builder = Builder::new(0);
|
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
|
|
|
|
{
|
|
|
|
let builder = Builder::new(0);
|
2019-08-13 07:24:08 -07:00
|
|
|
assert_eq!(
|
|
|
|
builder.build(1, MockTxProver),
|
|
|
|
Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap()))
|
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
|
|
|
let ovk = extfvk.fvk.ovk;
|
|
|
|
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
|
|
|
|
{
|
|
|
|
let mut builder = Builder::new(0);
|
|
|
|
builder
|
2019-07-25 14:37:16 -07:00
|
|
|
.add_sapling_output(
|
|
|
|
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!(
|
|
|
|
builder.build(1, MockTxProver),
|
|
|
|
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
|
|
|
|
{
|
|
|
|
let mut builder = Builder::new(0);
|
|
|
|
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!(
|
|
|
|
builder.build(1, MockTxProver),
|
|
|
|
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
|
|
|
|
.create_note(59999, Fs::random(&mut rng), &JUBJUB)
|
|
|
|
.unwrap();
|
|
|
|
let cm1 = Node::new(note1.cm(&JUBJUB).into_repr());
|
|
|
|
let mut tree = CommitmentTree::new();
|
|
|
|
tree.append(cm1).unwrap();
|
|
|
|
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
|
|
|
{
|
|
|
|
let mut builder = Builder::new(0);
|
|
|
|
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(),
|
|
|
|
witness1.clone(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
builder
|
2019-07-25 14:37:16 -07:00
|
|
|
.add_sapling_output(
|
|
|
|
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!(
|
|
|
|
builder.build(1, MockTxProver),
|
|
|
|
Err(Error::ChangeIsNegative(Amount::from_i64(-1).unwrap()))
|
|
|
|
);
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let note2 = to.create_note(1, Fs::random(&mut rng), &JUBJUB).unwrap();
|
|
|
|
let cm2 = Node::new(note2.cm(&JUBJUB).into_repr());
|
|
|
|
tree.append(cm2).unwrap();
|
|
|
|
witness1.append(cm2).unwrap();
|
|
|
|
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.)
|
|
|
|
{
|
|
|
|
let mut builder = Builder::new(0);
|
|
|
|
builder
|
2019-08-23 15:08:09 -07:00
|
|
|
.add_sapling_spend(extsk.clone(), *to.diversifier(), note1, witness1)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
|
|
|
builder
|
2019-08-23 15:08:09 -07:00
|
|
|
.add_sapling_spend(extsk, *to.diversifier(), note2, witness2)
|
2018-11-20 05:37:21 -08:00
|
|
|
.unwrap();
|
|
|
|
builder
|
2019-07-25 14:37:16 -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-08-13 07:24:08 -07:00
|
|
|
assert_eq!(builder.build(1, MockTxProver), Err(Error::BindingSig))
|
2018-11-20 05:37:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|