2018-11-20 05:37:21 -08:00
//! Structs for building transactions.
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-06 16:52:03 -07:00
use std ::boxed ::Box ;
2020-09-23 09:53:48 -07:00
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
2020-05-06 16:52:03 -07: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 } ,
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 } ,
2020-05-06 16:52:03 -07:00
primitives ::{ Diversifier , Note , PaymentAddress } ,
2018-11-20 05:37:21 -08:00
prover ::TxProver ,
2019-05-04 09:47:18 -07:00
redjubjub ::PrivateKey ,
2020-10-21 11:37:21 -07:00
sapling ::{ spend_sig_internal , Node } ,
2018-11-20 05:37:21 -08:00
transaction ::{
2020-05-06 16:52:03 -07:00
components ::{
2020-09-23 09:53:48 -07:00
amount ::Amount , amount ::DEFAULT_FEE , OutputDescription , SpendDescription , TxOut ,
2020-05-06 16:52:03 -07:00
} ,
2020-06-03 19:39:43 -07:00
signature_hash_data , SignableInput , Transaction , TransactionData , SIGHASH_ALL ,
2018-11-20 05:37:21 -08:00
} ,
2020-10-21 11:37:21 -07:00
util ::generate_random_rseed_internal ,
2020-05-06 16:52:03 -07:00
zip32 ::ExtendedSpendingKey ,
2018-11-20 05:37:21 -08:00
} ;
2019-07-31 08:20:13 -07:00
#[ cfg(feature = " transparent-inputs " ) ]
2020-10-15 06:03:40 -07:00
use crate ::{ legacy ::Script , transaction ::components ::TxIn } ;
2019-07-31 08:20:13 -07:00
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
use crate ::{
extensions ::transparent ::{ self as tze , ExtensionTxBuilder , ToPayload } ,
transaction ::components ::{ TzeIn , TzeOut } ,
} ;
#[ cfg(any(feature = " transparent-inputs " , feature = " zfuture " )) ]
use crate ::transaction ::components ::OutPoint ;
2020-10-21 11:37:21 -07:00
#[ cfg(any(test, feature = " test-dependencies " )) ]
use crate ::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
/// 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 ,
2020-09-09 09:54:18 -07:00
TzeWitnessModeMismatch ( u32 , u32 ) ,
2018-11-20 05:37:21 -08:00
}
2019-03-08 19:20:32 -08:00
impl fmt ::Display for Error {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
Error ::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 " ) ,
2020-09-09 09:54:18 -07:00
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 ) ,
2019-03-08 19:20:32 -08:00
}
}
}
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 > ,
2020-10-21 11:37:21 -07:00
) -> Result < Self , Error > {
Self ::new_internal ( params , height , rng , ovk , to , value , memo )
}
fn new_internal < R : RngCore , P : consensus ::Parameters > (
params : & P ,
height : BlockHeight ,
rng : & mut R ,
ovk : Option < OutgoingViewingKey > ,
to : PaymentAddress ,
value : Amount ,
memo : Option < Memo > ,
2018-11-20 05:37:21 -08:00
) -> Result < Self , Error > {
2021-01-28 12:40:07 -08:00
let g_d = to . g_d ( ) . ok_or ( Error ::InvalidAddress ) ? ;
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-10-21 11:37:21 -07: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 ( ) ,
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-10-21 11:37:21 -07:00
self . build_internal ( prover , ctx , rng )
}
fn build_internal < P : TxProver , R : RngCore > (
self ,
prover : & P ,
ctx : & mut P ::SaplingProvingContext ,
rng : & mut R ,
) -> OutputDescription {
let mut encryptor = SaplingNoteEncryption ::new_internal (
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 ,
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 ,
) ;
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 " ) ]
2020-05-06 16:52:03 -07:00
fn push ( & mut self , sk : secp256k1 ::SecretKey , coin : TxOut ) -> Result < ( ) , Error > {
2019-11-13 11:20:09 -08:00
if coin . value . is_negative ( ) {
return Err ( Error ::InvalidAmount ) ;
}
2020-10-09 15:19:44 -07:00
// Ensure that the RIPEMD-160 digest of the public key associated with the
2020-05-06 16:52:03 -07:00
// provided secret key matches that of the address to which the provided
2020-10-09 15:19:44 -07:00
// output may be spent.
2019-11-13 11:20:09 -08:00
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 ) ) [ .. ] {
2019-11-13 11:20:09 -08:00
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 {
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 = [ 0 u8 ; 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 ,
2020-06-03 19:39:43 -07:00
SignableInput ::transparent ( i , & info . coin . script_pubkey , info . coin . value ) ,
2019-11-13 11:12:55 -08:00
) ) ;
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
}
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2021-02-03 11:46:47 -08:00
#[ allow(clippy::type_complexity) ]
2020-05-06 16:52:03 -07:00
struct TzeInputInfo < ' a , BuildCtx > {
prevout : TzeOut ,
2020-09-09 09:54:18 -07:00
builder : Box < dyn FnOnce ( & BuildCtx ) -> Result < ( u32 , Vec < u8 > ) , Error > + ' a > ,
2020-05-06 16:52:03 -07:00
}
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-06 16:52:03 -07:00
struct TzeInputs < ' a , BuildCtx > {
builders : Vec < TzeInputInfo < ' a , BuildCtx > > ,
}
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-06 16:52:03 -07:00
impl < ' a , BuildCtx > TzeInputs < ' a , BuildCtx > {
2020-05-18 19:27:19 -07:00
fn default ( ) -> Self {
TzeInputs { builders : vec ! [ ] }
}
2020-09-09 09:54:18 -07:00
fn push < WBuilder , W : ToPayload > ( & mut self , tzeout : TzeOut , builder : WBuilder )
where
2020-05-06 16:52:03 -07:00
WBuilder : ' a + FnOnce ( & BuildCtx ) -> Result < W , Error > ,
{
self . builders . push ( TzeInputInfo {
prevout : tzeout ,
2020-09-09 09:54:18 -07:00
builder : Box ::new ( move | ctx | builder ( & ctx ) . map ( | x | x . to_payload ( ) ) ) ,
2020-05-06 16:52:03 -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-10-21 11:37:21 -07:00
pub struct Builder < ' a , P : consensus ::Parameters , R : RngCore > {
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-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-06 16:52:03 -07:00
tze_inputs : TzeInputs < ' a , TransactionData > ,
2020-07-01 13:26:54 -07:00
change_address : Option < ( OutgoingViewingKey , PaymentAddress ) > ,
2020-09-23 09:53:48 -07:00
_phantom : & ' a PhantomData < P > ,
2018-11-20 05:37:21 -08:00
}
2020-05-06 16:52:03 -07:00
impl < ' a , P : consensus ::Parameters > Builder < ' a , P , OsRng > {
2020-08-05 20:36:02 -07:00
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
/// using default values for general transaction fields and the default OS random.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
///
/// The fee will be set to the default fee (0.0001 ZEC).
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
}
2020-05-27 11:09:59 -07:00
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,
2020-09-22 09:06:54 -07:00
/// 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).
///
2020-09-22 09:06:54 -07:00
/// 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.
2020-09-23 09:53:48 -07:00
#[ 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 )
2020-05-27 11:09:59 -07:00
}
2020-08-05 20:36:02 -07:00
}
2020-05-06 16:52:03 -07:00
impl < ' a , P : consensus ::Parameters , R : RngCore + CryptoRng > Builder < ' a , P , R > {
2020-09-08 15:53:27 -07:00
/// Creates a new `Builder` targeted for inclusion in the block with the given height
/// and randomness source, using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
///
/// The fee will be set to the default fee (0.0001 ZEC).
2020-09-21 08:57:50 -07:00
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
2020-09-22 09:06:54 -07:00
/// 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).
///
2020-09-22 09:06:54 -07:00
/// 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.
2020-09-23 09:53:48 -07:00
#[ 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-10-21 11:37:21 -07:00
}
2020-09-08 15:53:27 -07:00
2020-10-21 11:37:21 -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.
2020-10-21 11:37:21 -07:00
///
/// WARNING: THIS MUST REMAIN PRIVATE AS IT ALLOWS CONSTRUCTION
/// OF BUILDERS WITH NON-CryptoRng RNGs
2020-09-21 08:57:50 -07:00
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 {
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 ( ) ,
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-18 19:27:19 -07:00
tze_inputs : TzeInputs ::default ( ) ,
2018-11-20 05:37:21 -08:00
change_address : None ,
2020-09-23 09:53:48 -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-10-21 11:37:21 -07:00
let output = SaplingOutput ::new_internal (
2020-08-05 13:27:40 -07:00
& 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 > {
2020-06-02 15:20:44 -07:00
self . transparent_inputs . push ( sk , coin ) ? ;
2020-05-06 16:52:03 -07:00
self . mtx . vin . push ( TxIn ::new ( utxo ) ) ;
2020-10-15 06:03:40 -07:00
Ok ( ( ) )
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
2020-06-02 15:20:44 -07:00
let change = self . mtx . value_balance - self . fee + self . transparent_inputs . value_sum ( )
2020-09-23 09:53:48 -07:00
- self . mtx . vout . iter ( ) . map ( | vo | vo . value ) . sum ::< Amount > ( ) ;
#[ cfg(feature = " zfuture " ) ]
let change = change
2020-05-06 16:52:03 -07:00
+ self
. tze_inputs
. builders
. iter ( )
. map ( | ein | ein . prevout . value )
. sum ::< Amount > ( )
2019-05-24 05:32:55 -07:00
- self
. mtx
2020-05-06 16:52:03 -07:00
. tze_outputs
2019-05-24 05:32:55 -07:00
. iter ( )
2020-05-06 16:52:03 -07:00
. map ( | tzo | tzo . value )
2019-07-25 12:53:42 -07:00
. sum ::< Amount > ( ) ;
2020-05-06 16:52:03 -07:00
2018-11-20 05:37:21 -08:00
if change . is_negative ( ) {
2019-08-13 07:10:57 -07:00
return Err ( Error ::ChangeIsNegative ( change ) ) ;
2018-11-20 05:37:21 -08:00
}
//
// Change output
//
if change . is_positive ( ) {
// Send change to the specified change address. If no change address
// was set, send change to the first Sapling address given as input.
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 ,
2020-10-30 06:27:49 -07:00
self . spends [ 0 ] . note . pk_d ,
2019-08-23 15:08:09 -07:00
)
. 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
2021-01-08 20:49:38 -08:00
let nullifier = 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 ,
2021-01-08 20:49:38 -08:00
) ;
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-10-21 11:37:21 -07:00
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 ) ;
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 ;
2020-10-30 06:27:49 -07:00
if let Some ( addr ) = PaymentAddress ::from_parts ( diversifier , pk_d ) {
2019-08-23 15:08:09 -07:00
break ( pk_d , addr ) ;
}
2018-11-20 05:37:21 -08:00
} ;
2020-10-21 11:37:21 -07:00
let rseed =
generate_random_rseed_internal ( & 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-10-21 11:37:21 -07:00
let esk = dummy_note . generate_or_derive_esk_internal ( & 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 = [ 0 u8 ; 580 ] ;
let mut out_ciphertext = [ 0 u8 ; 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 ) ;
}
//
2020-10-09 15:19:44 -07:00
// Signatures -- everything but the signatures must already have been added.
2018-11-20 05:37:21 -08:00
//
let mut sighash = [ 0 u8 ; 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 ( ) {
2020-10-21 11:37:21 -07:00
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
2020-05-27 11:09:59 -07:00
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 )
2020-05-27 11:09:59 -07:00
. map_err ( | _ | Error ::BindingSig ) ? ,
)
2020-04-03 12:13:39 -07:00
} else {
2020-05-27 11:09:59 -07:00
None
} ;
2018-11-20 05:37:21 -08:00
2020-09-23 12:58:21 -07:00
// Create TZE input witnesses
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-09-09 09:54:18 -07:00
for ( i , tze_in ) in self . tze_inputs . builders . into_iter ( ) . enumerate ( ) {
2020-10-13 13:28:54 -07:00
// 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.
2020-09-09 09:54:18 -07:00
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 ;
2020-05-06 16:52:03 -07: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 ,
) )
}
}
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-06 16:52:03 -07:00
impl < ' a , P : consensus ::Parameters , R : RngCore + CryptoRng > ExtensionTxBuilder < ' a >
for Builder < ' a , P , R >
{
type BuildCtx = TransactionData ;
type BuildError = Error ;
fn add_tze_input < WBuilder , W : ToPayload > (
& mut self ,
2020-06-03 19:38:05 -07:00
extension_id : u32 ,
2020-09-09 09:54:18 -07:00
mode : u32 ,
( outpoint , prevout ) : ( OutPoint , TzeOut ) ,
2020-05-06 16:52:03 -07:00
witness_builder : WBuilder ,
) -> Result < ( ) , Self ::BuildError >
where
WBuilder : ' a + ( FnOnce ( & Self ::BuildCtx ) -> Result < W , Self ::BuildError > ) ,
{
2020-09-09 09:54:18 -07:00
self . mtx
. tze_inputs
. push ( TzeIn ::new ( outpoint , extension_id , mode ) ) ;
self . tze_inputs . push ( prevout , witness_builder ) ;
2020-05-06 16:52:03 -07:00
Ok ( ( ) )
}
fn add_tze_output < G : ToPayload > (
& mut self ,
2020-06-03 19:38:05 -07:00
extension_id : u32 ,
2020-05-06 16:52:03 -07:00
value : Amount ,
guarded_by : & G ,
) -> Result < ( ) , Self ::BuildError > {
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 ( ( ) )
}
}
2020-10-21 11:37:21 -07:00
#[ cfg(any(test, feature = " test-dependencies " )) ]
impl < ' a , P : consensus ::Parameters , R : RngCore > Builder < ' a , P , R > {
/// Creates a new `Builder` targeted for inclusion in the block with the given height
/// and randomness source, using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
///
/// The fee will be set to the default fee (0.0001 ZEC).
///
/// WARNING: DO NOT USE IN PRODUCTION
pub fn test_only_new_with_rng ( params : P , height : BlockHeight , rng : R ) -> Builder < ' a , P , R > {
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 ;
2020-08-05 20:36:02 -07:00
use std ::marker ::PhantomData ;
2019-08-06 02:46:40 -07:00
2018-11-20 05:37:21 -08:00
use crate ::{
2020-09-17 10:55:39 -07:00
consensus ::{ self , Parameters , H0 , TEST_NETWORK } ,
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 ,
2020-11-23 09:41:30 -08:00
transaction ::components ::{ amount ::Amount , amount ::DEFAULT_FEE } ,
2018-11-20 05:37:21 -08:00
zip32 ::{ ExtendedFullViewingKey , ExtendedSpendingKey } ,
} ;
2020-09-23 09:53:48 -07:00
use super ::{ Builder , Error } ;
#[ cfg(feature = " zfuture " ) ]
use super ::TzeInputs ;
2020-05-18 19:27:19 -07:00
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 ;
2020-09-17 10:55:39 -07:00
let mut builder = Builder ::new ( TEST_NETWORK , 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-09-17 10:55:39 -07:00
use crate ::consensus ::NetworkUpgrade ;
2020-04-03 12:13:39 -07:00
use crate ::transaction ::{
builder ::{ self , TransparentInputs } ,
TransactionData ,
} ;
2020-09-17 10:55:39 -07:00
let sapling_activation_height = TEST_NETWORK
2020-08-05 13:27:40 -07:00
. activation_height ( NetworkUpgrade ::Sapling )
. unwrap ( ) ;
2020-08-02 22:39:36 -07:00
2020-04-03 12:13:39 -07:00
// Create a builder with 0 fee, so we can construct t outputs
2020-08-05 13:27:40 -07:00
let mut builder = builder ::Builder {
2020-09-17 10:55:39 -07:00
params : TEST_NETWORK ,
2020-04-03 12:13:39 -07:00
rng : OsRng ,
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 ( ) ,
2020-09-23 09:53:48 -07:00
#[ cfg(feature = " zfuture " ) ]
2020-05-18 19:27:19 -07:00
tze_inputs : TzeInputs ::default ( ) ,
2020-04-03 12:13:39 -07:00
change_address : None ,
2020-09-23 09:53:48 -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-09-17 10:55:39 -07:00
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 )
) ;
}
2019-05-24 05:32:55 -07:00
#[ test ]
fn fails_on_negative_transparent_output ( ) {
2020-09-17 10:55:39 -07:00
let mut builder = Builder ::new ( TEST_NETWORK , 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-09-17 10:55:39 -07:00
let builder = Builder ::new ( TEST_NETWORK , 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 ) ,
2020-11-23 09:41:30 -08:00
Err ( Error ::ChangeIsNegative ( Amount ::zero ( ) - DEFAULT_FEE ) )
2019-08-13 07:24:08 -07:00
) ;
2018-11-20 05:37:21 -08:00
}
let extfvk = ExtendedFullViewingKey ::from ( & extsk ) ;
2020-08-28 08:12:37 -07:00
let ovk = Some ( extfvk . fvk . ovk ) ;
2018-11-20 05:37:21 -08:00
let to = extfvk . default_address ( ) . unwrap ( ) . 1 ;
// Fail if there is only a Sapling output
2020-11-23 09:41:30 -08:00
// 0.0005 z-ZEC out, 0.00001 t-ZEC fee
2018-11-20 05:37:21 -08:00
{
2020-09-17 10:55:39 -07: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 ( ) ;
2019-08-13 07:24:08 -07:00
assert_eq! (
2020-02-07 16:37:31 -08:00
builder . build ( consensus ::BranchId ::Sapling , & MockTxProver ) ,
2020-11-23 09:41:30 -08:00
Err ( Error ::ChangeIsNegative (
Amount ::from_i64 ( - 50000 ) . unwrap ( ) - DEFAULT_FEE
) )
2019-08-13 07:24:08 -07:00
) ;
2018-11-20 05:37:21 -08:00
}
2019-05-24 05:32:55 -07:00
// Fail if there is only a transparent output
2020-11-23 09:41:30 -08:00
// 0.0005 t-ZEC out, 0.00001 t-ZEC fee
2019-05-24 05:32:55 -07:00
{
2020-09-17 10:55:39 -07:00
let mut builder = Builder ::new ( TEST_NETWORK , 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 ) ,
2020-11-23 09:41:30 -08:00
Err ( Error ::ChangeIsNegative (
Amount ::from_i64 ( - 50000 ) . unwrap ( ) - DEFAULT_FEE
) )
2019-08-13 07:24:08 -07:00
) ;
2019-05-24 05:32:55 -07:00
}
2018-11-20 05:37:21 -08:00
let note1 = to
2020-11-23 09:41:30 -08:00
. create_note ( 50999 , Rseed ::BeforeZip212 ( jubjub ::Fr ::random ( & mut rng ) ) )
2018-11-20 05:37:21 -08:00
. unwrap ( ) ;
2020-08-21 10:33:22 -07:00
let cmu1 = Node ::new ( note1 . cmu ( ) . to_repr ( ) ) ;
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
2020-11-23 09:41:30 -08:00
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.00001 t-ZEC fee, 0.00050999 z-ZEC in
2018-11-20 05:37:21 -08:00
{
2020-09-17 10:55:39 -07:00
let mut builder = Builder ::new ( TEST_NETWORK , 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-10-30 06:27:49 -07:00
. add_sapling_output ( ovk , 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-09-17 10:55:39 -07:00
let mut builder = Builder ::new ( TEST_NETWORK , 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
}
}
}