diff --git a/zcash_client_backend/src/address.rs b/zcash_client_backend/src/address.rs index 8b144a3b7..f605d4637 100644 --- a/zcash_client_backend/src/address.rs +++ b/zcash_client_backend/src/address.rs @@ -8,6 +8,7 @@ use crate::encoding::{ }; /// An address that funds can be sent to. +// TODO: rename to ParsedAddress #[derive(Debug, PartialEq, Clone)] pub enum RecipientAddress { Shielded(PaymentAddress), diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index c8d598875..ea3c05f13 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -238,8 +238,7 @@ where }, RecipientAddress::Transparent(addr) => { let script = addr.script(); - tx.transparent_bundle - .as_ref() + tx.transparent_bundle() .and_then(|b| { b.vout .iter() diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 7374ffd15..418db6708 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -44,7 +44,7 @@ pub fn decrypt_transaction( ) -> Vec { let mut decrypted = vec![]; - if let Some(bundle) = tx.sapling_bundle.as_ref() { + if let Some(bundle) = tx.sapling_bundle() { for (account, extfvk) in extfvks.iter() { let ivk = extfvk.fvk.vk.ivk(); let ovk = extfvk.fvk.ovk; diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 52e0bc7bd..e7fdafc63 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -489,7 +489,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // // Assumes that create_spend_to_address() will never be called in parallel, which is a // reasonable assumption for a light client such as a mobile phone. - if let Some(bundle) = sent_tx.tx.sapling_bundle.as_ref() { + if let Some(bundle) = sent_tx.tx.sapling_bundle() { for spend in &bundle.shielded_spends { wallet::mark_spent(up, tx_ref, &spend.nullifier)?; } diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 73e7ffd23..402b91784 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -650,14 +650,14 @@ pub fn put_tx_data<'a, P>( if stmts .stmt_update_tx_data - .execute(params![u32::from(tx.expiry_height), raw_tx, txid,])? + .execute(params![u32::from(tx.expiry_height()), raw_tx, txid,])? == 0 { // It isn't there, so insert our transaction into the database. stmts.stmt_insert_tx_data.execute(params![ txid, created_at, - u32::from(tx.expiry_height), + u32::from(tx.expiry_height()), raw_tx ])?; diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index b4a737027..ac9dfcdeb 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -68,9 +68,9 @@ pub fn get_spendable_notes

( "SELECT diversifier, value, rcm, witness FROM received_notes INNER JOIN transactions ON transactions.id_tx = received_notes.tx - INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note - WHERE account = :account - AND spent IS NULL + INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note + WHERE account = :account + AND spent IS NULL AND transactions.block <= :anchor_height AND sapling_witnesses.block = :anchor_height", )?; @@ -153,7 +153,7 @@ mod tests { use zcash_primitives::{ block::BlockHash, - consensus::BlockHeight, + consensus::{BlockHeight, BranchId}, legacy::TransparentAddress, sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver}, transaction::{components::Amount, Transaction}, @@ -617,7 +617,7 @@ mod tests { |row| row.get(0), ) .unwrap(); - let tx = Transaction::read(&raw_tx[..]).unwrap(); + let tx = Transaction::read(&raw_tx[..], BranchId::Canopy).unwrap(); // Fetch the output index from the database let output_index: i64 = db_write @@ -631,8 +631,7 @@ mod tests { ) .unwrap(); - let output = - &tx.sapling_bundle.as_ref().unwrap().shielded_outputs[output_index as usize]; + let output = &tx.sapling_bundle().unwrap().shielded_outputs[output_index as usize]; try_sapling_output_recovery( &network, diff --git a/zcash_extensions/Cargo.toml b/zcash_extensions/Cargo.toml index 4280a8efc..a34144977 100644 --- a/zcash_extensions/Cargo.toml +++ b/zcash_extensions/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies] blake2b_simd = "0.5" -zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["zfuture"] } +zcash_primitives = { version = "0.5", path = "../zcash_primitives", features = ["zfuture" ] } [dev-dependencies] ff = "0.10" diff --git a/zcash_extensions/src/consensus/transparent.rs b/zcash_extensions/src/consensus/transparent.rs index c9bde5d21..6c5a4b455 100644 --- a/zcash_extensions/src/consensus/transparent.rs +++ b/zcash_extensions/src/consensus/transparent.rs @@ -78,14 +78,14 @@ pub trait Epoch { /// by the context. impl<'a> demo::Context for Context<'a> { fn is_tze_only(&self) -> bool { - self.tx.transparent_bundle.is_none() - && self.tx.sapling_bundle.is_none() - && self.tx.sprout_bundle.is_none() - && self.tx.orchard_bundle.is_none() + self.tx.transparent_bundle().is_none() + && self.tx.sapling_bundle().is_none() + && self.tx.sprout_bundle().is_none() + && self.tx.orchard_bundle().is_none() } fn tx_tze_outputs(&self) -> &[TzeOut] { - if let Some(bundle) = &self.tx.tze_bundle { + if let Some(bundle) = self.tx.tze_bundle() { &bundle.vout } else { &[] diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index fcd493b6b..18f2b3a17 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -495,7 +495,7 @@ mod tests { builder::Builder, components::{ amount::{Amount, DEFAULT_FEE}, - tze::{Bundle, OutPoint, TzeIn, TzeOut}, + tze::{Authorized, Bundle, OutPoint, TzeIn, TzeOut}, }, Transaction, TransactionData, TxVersion, }, @@ -621,14 +621,14 @@ mod tests { /// by the context. impl<'a> Context for Ctx<'a> { fn is_tze_only(&self) -> bool { - self.tx.transparent_bundle.is_none() - && self.tx.sprout_bundle.is_none() - && self.tx.sapling_bundle.is_none() - && self.tx.orchard_bundle.is_none() + self.tx.transparent_bundle().is_none() + && self.tx.sapling_bundle().is_none() + && self.tx.sprout_bundle().is_none() + && self.tx.orchard_bundle().is_none() } fn tx_tze_outputs(&self) -> &[TzeOut] { - match &self.tx.tze_bundle { + match self.tx.tze_bundle() { Some(b) => &b.vout, None => &[], } @@ -683,20 +683,22 @@ mod tests { precondition: tze::Precondition::from(0, &Precondition::open(hash_1)), }; - let tx_a = TransactionData { - version: TxVersion::ZFuture, - lock_time: 0, - expiry_height: 0u32.into(), - transparent_bundle: None, - sprout_bundle: None, - sapling_bundle: None, - orchard_bundle: None, - tze_bundle: Some(Bundle { + let tx_a = TransactionData::from_parts( + TxVersion::ZFuture, + BranchId::ZFuture, + 0, + 0u32.into(), + None, + None, + None, + None, + Some(Bundle { vin: vec![], vout: vec![out_a], + authorization: Authorized, }), - } - .freeze(BranchId::ZFuture) + ) + .freeze() .unwrap(); // @@ -712,20 +714,22 @@ mod tests { precondition: tze::Precondition::from(0, &Precondition::close(hash_2)), }; - let tx_b = TransactionData { - version: TxVersion::ZFuture, - lock_time: 0, - expiry_height: 0u32.into(), - transparent_bundle: None, - sprout_bundle: None, - sapling_bundle: None, - orchard_bundle: None, - tze_bundle: Some(Bundle { + let tx_b = TransactionData::from_parts( + TxVersion::ZFuture, + BranchId::ZFuture, + 0, + 0u32.into(), + None, + None, + None, + None, + Some(Bundle { vin: vec![in_b], vout: vec![out_b], + authorization: Authorized, }), - } - .freeze(BranchId::ZFuture) + ) + .freeze() .unwrap(); // @@ -737,20 +741,22 @@ mod tests { witness: tze::Witness::from(0, &Witness::close(preimage_2)), }; - let tx_c = TransactionData { - version: TxVersion::ZFuture, - lock_time: 0, - expiry_height: 0u32.into(), - transparent_bundle: None, - sprout_bundle: None, - sapling_bundle: None, - orchard_bundle: None, - tze_bundle: Some(Bundle { + let tx_c = TransactionData::from_parts( + TxVersion::ZFuture, + BranchId::ZFuture, + 0, + 0u32.into(), + None, + None, + None, + None, + Some(Bundle { vin: vec![in_c], vout: vec![], + authorization: Authorized, }), - } - .freeze(BranchId::ZFuture) + ) + .freeze() .unwrap(); // Verify tx_b @@ -758,8 +764,8 @@ mod tests { let ctx = Ctx { tx: &tx_b }; assert_eq!( Program.verify( - &tx_a.tze_bundle.as_ref().unwrap().vout[0].precondition, - &tx_b.tze_bundle.as_ref().unwrap().vin[0].witness, + &tx_a.tze_bundle().unwrap().vout[0].precondition, + &tx_b.tze_bundle().unwrap().vin[0].witness, &ctx ), Ok(()) @@ -771,8 +777,8 @@ mod tests { let ctx = Ctx { tx: &tx_c }; assert_eq!( Program.verify( - &tx_b.tze_bundle.as_ref().unwrap().vout[0].precondition, - &tx_c.tze_bundle.as_ref().unwrap().vin[0].witness, + &tx_b.tze_bundle().unwrap().vout[0].precondition, + &tx_c.tze_bundle().unwrap().vin[0].witness, &ctx ), Ok(()) @@ -830,7 +836,7 @@ mod tests { .build(&prover) .map_err(|e| format!("build failure: {:?}", e)) .unwrap(); - let tze_a = tx_a.tze_bundle.as_ref().unwrap(); + let tze_a = tx_a.tze_bundle().unwrap(); // // Transfer @@ -848,7 +854,7 @@ mod tests { .build(&prover) .map_err(|e| format!("build failure: {:?}", e)) .unwrap(); - let tze_b = tx_b.tze_bundle.as_ref().unwrap(); + let tze_b = tx_b.tze_bundle().unwrap(); // // Closing transaction @@ -873,7 +879,7 @@ mod tests { .build(&prover) .map_err(|e| format!("build failure: {:?}", e)) .unwrap(); - let tze_c = tx_c.tze_bundle.as_ref().unwrap(); + let tze_c = tx_c.tze_bundle().unwrap(); // Verify tx_b let ctx0 = Ctx { tx: &tx_b }; diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index e4ea979f3..b74cc3c34 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -10,7 +10,7 @@ use zcash_primitives::{ Diversifier, PaymentAddress, SaplingIvk, ValueCommitment, }, transaction::components::{ - sapling::{Authorized, OutputDescription}, + sapling::{GrothProofBytes, OutputDescription}, GROTH_PROOF_SIZE, }, }; @@ -23,7 +23,7 @@ fn bench_note_decryption(c: &mut Criterion) { let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); // Construct a fake Sapling output as if we had just deserialized a transaction. - let output: OutputDescription = { + let output: OutputDescription = { let diversifier = Diversifier([0; 11]); let pk_d = diversifier.g_d().unwrap() * valid_ivk.0; let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap(); diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index cedb8c786..5589985a6 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -386,7 +386,7 @@ pub fn try_sapling_output_recovery_with_ock( params: &P, height: BlockHeight, ock: &OutgoingCipherKey, - output: &OutputDescription, + output: &OutputDescription, ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { params: params.clone(), @@ -408,7 +408,7 @@ pub fn try_sapling_output_recovery( params: &P, height: BlockHeight, ovk: &OutgoingViewingKey, - output: &OutputDescription, + output: &OutputDescription, ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { params: params.clone(), @@ -465,7 +465,7 @@ mod tests { OutgoingViewingKey, OutgoingCipherKey, SaplingIvk, - OutputDescription, + OutputDescription, ) { let ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); @@ -498,7 +498,7 @@ mod tests { ) -> ( OutgoingViewingKey, OutgoingCipherKey, - OutputDescription, + OutputDescription, ) { let diversifier = Diversifier([0; 11]); let pk_d = diversifier.g_d().unwrap() * ivk.0; diff --git a/zcash_primitives/src/serialize.rs b/zcash_primitives/src/serialize.rs index e09fc44d1..267c5c1f5 100644 --- a/zcash_primitives/src/serialize.rs +++ b/zcash_primitives/src/serialize.rs @@ -1,4 +1,5 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use nonempty::NonEmpty; use std::io::{self, Read, Write}; const MAX_SIZE: usize = 0x02000000; @@ -70,7 +71,7 @@ impl Vector { F: Fn(&mut R) -> io::Result, { let count = CompactSize::read(&mut reader)?; - (0..count).map(|_| func(&mut reader)).collect() + Array::read(reader, count, func) } pub fn write(mut writer: W, vec: &[E], func: F) -> io::Result<()> @@ -80,6 +81,40 @@ impl Vector { CompactSize::write(&mut writer, vec.len())?; vec.iter().try_for_each(|e| func(&mut writer, e)) } + + pub fn write_nonempty( + mut writer: W, + vec: &NonEmpty, + func: F, + ) -> io::Result<()> + where + F: Fn(&mut W, &E) -> io::Result<()>, + { + CompactSize::write(&mut writer, vec.len())?; + vec.iter().try_for_each(|e| func(&mut writer, e)) + } +} + +pub struct Array; + +impl Array { + pub fn read(mut reader: R, count: usize, func: F) -> io::Result> + where + F: Fn(&mut R) -> io::Result, + { + (0..count).map(|_| func(&mut reader)).collect() + } + + pub fn write, F>( + mut writer: W, + vec: I, + func: F, + ) -> io::Result<()> + where + F: Fn(&mut W, &E) -> io::Result<()>, + { + vec.into_iter().try_for_each(|e| func(&mut writer, &e)) + } } pub struct Optional; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index b7987d203..b3f251763 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -1,13 +1,14 @@ //! Structs for building transactions. -use rand::{rngs::OsRng, CryptoRng, RngCore}; use std::array; use std::error; use std::fmt; -use std::io; use std::sync::mpsc::Sender; -use orchard::bundle::{self as orchard}; +#[cfg(not(feature = "zfuture"))] +use std::marker::PhantomData; + +use rand::{rngs::OsRng, CryptoRng, RngCore}; use crate::{ consensus::{self, BlockHeight, BranchId}, @@ -15,8 +16,7 @@ use crate::{ memo::MemoBytes, merkle_tree::MerklePath, sapling::{ - keys::OutgoingViewingKey, prover::TxProver, redjubjub, Diversifier, Node, Note, - PaymentAddress, + keys::OutgoingViewingKey, prover::TxProver, Diversifier, Node, Note, PaymentAddress, }, transaction::{ components::{ @@ -27,21 +27,19 @@ use crate::{ }, transparent::{self, builder::TransparentBuilder}, }, - signature_hash_data, SignableInput, Transaction, TransactionData, TxVersion, Unauthorized, - SIGHASH_ALL, + sighash::{signature_hash, SignableInput, SIGHASH_ALL}, + txid::TxIdDigester, + Transaction, TransactionData, TxVersion, Unauthorized, }, zip32::ExtendedSpendingKey, }; -#[cfg(not(feature = "zfuture"))] -use std::marker::PhantomData; - #[cfg(feature = "transparent-inputs")] -use crate::{legacy::Script, transaction::components::transparent::TxOut}; +use crate::transaction::components::transparent::TxOut; #[cfg(feature = "zfuture")] use crate::{ - extensions::transparent::{AuthData, ExtensionTxBuilder, ToPayload}, + extensions::transparent::{ExtensionTxBuilder, ToPayload}, transaction::components::{ tze::builder::TzeBuilder, tze::{self, TzeOut}, @@ -279,11 +277,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { /// /// Upon success, returns a tuple containing the final transaction, and the /// [`SaplingMetadata`] 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, prover: &impl TxProver, @@ -325,23 +318,25 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { let transparent_bundle = self.transparent_builder.build(); + let mut rng = self.rng; let mut ctx = prover.new_sapling_proving_context(); - let (sapling_bundle, tx_metadata) = self + let sapling_bundle = self .sapling_builder .build( prover, &mut ctx, - &mut self.rng, + &mut rng, self.target_height, self.progress_notifier.as_ref(), ) .map_err(Error::SaplingBuild)?; #[cfg(feature = "zfuture")] - let tze_bundle = self.tze_builder.build(); + let (tze_bundle, tze_signers) = self.tze_builder.build(); - let unauthed_tx = TransactionData { + let unauthed_tx: TransactionData = TransactionData { version, + consensus_branch_id: BranchId::for_height(&self.params, self.target_height), lock_time: 0, expiry_height: self.expiry_height, transparent_bundle, @@ -355,110 +350,63 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { // // Signatures -- everything but the signatures must already have been added. // + let txid_parts = unauthed_tx.digest(TxIdDigester); - let mut sighash = [0u8; 32]; - sighash.copy_from_slice(&signature_hash_data( - &unauthed_tx, - consensus_branch_id, - SIGHASH_ALL, - SignableInput::Shielded, - )); - - #[cfg(feature = "transparent-inputs")] - let transparent_sigs = self - .transparent_builder - .create_signatures(&unauthed_tx, consensus_branch_id); - - let sapling_sigs = self - .sapling_builder - .create_signatures(prover, &mut ctx, &mut self.rng, &sighash, &tx_metadata) - .map_err(Error::SaplingBuild)?; + let transparent_bundle = unauthed_tx.transparent_bundle.clone().map(|b| { + b.apply_signatures( + #[cfg(feature = "transparent-inputs")] + &unauthed_tx, + #[cfg(feature = "transparent-inputs")] + &txid_parts, + ) + }); #[cfg(feature = "zfuture")] - let tze_witnesses = self - .tze_builder - .create_witnesses(&unauthed_tx) + let tze_bundle = unauthed_tx + .tze_bundle + .clone() + .map(|b| b.into_authorized(&unauthed_tx, tze_signers)) + .transpose() .map_err(Error::TzeBuild)?; - Ok(( - Self::apply_signatures( - consensus_branch_id, - unauthed_tx, - #[cfg(feature = "transparent-inputs")] - transparent_sigs, - sapling_sigs, - None, - #[cfg(feature = "zfuture")] - tze_witnesses, - ) - .expect("An IO error occurred applying signatures."), - tx_metadata, - )) - } + // the commitment being signed is shared across all Sapling inputs; once + // V4 transactions are deprecated this should just be the txid, but + // for now we need to continue to compute it here. + let shielded_sig_commitment = signature_hash( + &unauthed_tx, + SIGHASH_ALL, + &SignableInput::Shielded, + &txid_parts, + ); - fn apply_signatures( - consensus_branch_id: consensus::BranchId, - unauthed_tx: TransactionData, - #[cfg(feature = "transparent-inputs")] transparent_sigs: Option>, - sapling_sigs: Option<(Vec, redjubjub::Signature)>, - _orchard_auth: Option<( - Vec<::SpendAuth>, - orchard::Authorized, - )>, - #[cfg(feature = "zfuture")] tze_witnesses: Option>, - ) -> io::Result { - #[cfg(feature = "transparent-inputs")] - let transparent_bundle = match (unauthed_tx.transparent_bundle, transparent_sigs) { - (Some(bundle), Some(script_sigs)) => Some(bundle.apply_signatures(script_sigs)), - (None, None) => None, - (b, s) => { - panic!( - "Mismatch between transparent bundle ({}) and signatures ({}).", - b.is_some(), - s.is_some() - ); - } - }; - - #[cfg(not(feature = "transparent-inputs"))] - let transparent_bundle = unauthed_tx - .transparent_bundle - .map(|b| b.apply_signatures(vec![])); - - let signed_sapling_bundle = match (unauthed_tx.sapling_bundle, sapling_sigs) { - (Some(bundle), Some((spend_sigs, binding_sig))) => { - Some(bundle.apply_signatures(spend_sigs, binding_sig)) - } - (None, None) => None, - _ => { - panic!("Mismatch between sapling bundle and signatures."); - } - }; - - #[cfg(feature = "zfuture")] - let signed_tze_bundle = match (unauthed_tx.tze_bundle, tze_witnesses) { - (Some(bundle), Some(witness_payloads)) => { - Some(bundle.apply_signatures(witness_payloads)) - } - (None, None) => None, - _ => { - panic!("Mismatch between TZE bundle and witnesses.") - } + let (sapling_bundle, tx_metadata) = match unauthed_tx + .sapling_bundle + .map(|b| { + b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref()) + }) + .transpose() + .map_err(Error::SaplingBuild)? + { + Some((bundle, meta)) => (Some(bundle), meta), + None => (None, SaplingMetadata::empty()), }; let authorized_tx = TransactionData { version: unauthed_tx.version, + consensus_branch_id: unauthed_tx.consensus_branch_id, lock_time: unauthed_tx.lock_time, expiry_height: unauthed_tx.expiry_height, transparent_bundle, sprout_bundle: unauthed_tx.sprout_bundle, - sapling_bundle: signed_sapling_bundle, + sapling_bundle, orchard_bundle: None, #[cfg(feature = "zfuture")] - tze_bundle: signed_tze_bundle, + tze_bundle, }; - authorized_tx.freeze(consensus_branch_id) + // The unwrap() here is safe because the txid hashing + // of freeze() should be infalliable. + Ok((authorized_tx.freeze().unwrap(), tx_metadata)) } } @@ -530,19 +478,19 @@ mod tests { transaction::components::{ amount::{Amount, DEFAULT_FEE}, sapling::builder::{self as build_s}, - transparent::builder::{self as transparent}, + transparent::builder::{self as build_t}, }, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; use super::{Builder, Error, SaplingBuilder, DEFAULT_TX_EXPIRY_DELTA}; - #[cfg(not(feature = "zfuture"))] - use std::marker::PhantomData; - #[cfg(feature = "zfuture")] use super::TzeBuilder; + #[cfg(not(feature = "zfuture"))] + use std::marker::PhantomData; + #[test] fn fails_on_negative_output() { let extsk = ExtendedSpendingKey::master(&[]); @@ -646,7 +594,7 @@ mod tests { &TransparentAddress::PublicKey([0; 20]), Amount::from_i64(-1).unwrap(), ), - Err(Error::TransparentBuild(transparent::Error::InvalidAmount)) + Err(Error::TransparentBuild(build_t::Error::InvalidAmount)) ); } diff --git a/zcash_primitives/src/transaction/components/amount.rs b/zcash_primitives/src/transaction/components/amount.rs index 1da978d76..3ea1c9bab 100644 --- a/zcash_primitives/src/transaction/components/amount.rs +++ b/zcash_primitives/src/transaction/components/amount.rs @@ -2,6 +2,8 @@ use std::convert::TryFrom; use std::iter::Sum; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; +use orchard::value as orchard; + pub const COIN: i64 = 1_0000_0000; pub const MAX_MONEY: i64 = 21_000_000 * COIN; @@ -180,6 +182,14 @@ impl Neg for Amount { } } +impl TryFrom for Amount { + type Error = (); + + fn try_from(v: orchard::ValueSum) -> Result { + i64::try_from(v).map_err(|_| ()).and_then(Amount::try_from) + } +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::prelude::prop_compose; diff --git a/zcash_primitives/src/transaction/components/orchard.rs b/zcash_primitives/src/transaction/components/orchard.rs index 9b7357f62..a6d274f05 100644 --- a/zcash_primitives/src/transaction/components/orchard.rs +++ b/zcash_primitives/src/transaction/components/orchard.rs @@ -179,3 +179,39 @@ pub fn write_flags(mut writer: W, flags: &Flags) -> io::Result<()> { pub fn write_anchor(mut writer: W, anchor: &Anchor) -> io::Result<()> { writer.write_all(&anchor.0) } + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::prelude::*; + + use orchard::bundle::{ + testing::{self as t_orch}, + Authorized, Bundle, + }; + + use crate::transaction::{ + components::amount::{testing::arb_amount, Amount}, + TxVersion, + }; + + prop_compose! { + pub fn arb_bundle()( + orchard_value_balance in arb_amount(), + bundle in t_orch::arb_bundle() + ) -> Bundle { + // overwrite the value balance, as we can't guarantee that the + // value doesn't exceed the MAX_MONEY bounds. + bundle.try_map_value_balance::<_, (), _>(|_| Ok(orchard_value_balance)).unwrap() + } + } + + pub fn arb_bundle_for_version( + v: TxVersion, + ) -> impl Strategy>> { + if v.has_orchard() { + Strategy::boxed(prop::option::of(arb_bundle())) + } else { + Strategy::boxed(Just(None)) + } + } +} diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 745bfee1c..9866d4458 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -33,14 +33,6 @@ impl Authorization for Unproven { type AuthSig = (); } -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Unauthorized; - -impl Authorization for Unauthorized { - type Proof = GrothProofBytes; - type AuthSig = (); -} - #[derive(Debug, Copy, Clone)] pub struct Authorized { pub binding_sig: redjubjub::Signature, @@ -54,36 +46,11 @@ impl Authorization for Authorized { #[derive(Debug, Clone)] pub struct Bundle { pub shielded_spends: Vec>, - pub shielded_outputs: Vec>, + pub shielded_outputs: Vec>, pub value_balance: Amount, pub authorization: A, } -impl Bundle { - pub fn apply_signatures( - self, - spend_auth_sigs: Vec, - binding_sig: Signature, - ) -> Bundle { - assert!(self.shielded_spends.len() == spend_auth_sigs.len()); - Bundle { - shielded_spends: self - .shielded_spends - .iter() - .zip(spend_auth_sigs.iter()) - .map(|(spend, sig)| spend.apply_signature(*sig)) - .collect(), - shielded_outputs: self - .shielded_outputs - .into_iter() - .map(|o| o.into_authorized()) - .collect(), //TODO, is there a zero-cost way to do this? - value_balance: self.value_balance, - authorization: Authorized { binding_sig }, - } - } -} - #[derive(Clone)] pub struct SpendDescription { pub cv: jubjub::ExtendedPoint, @@ -94,19 +61,6 @@ pub struct SpendDescription { pub spend_auth_sig: A::AuthSig, } -impl SpendDescription { - pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription { - SpendDescription { - cv: self.cv, - anchor: self.anchor, - nullifier: self.nullifier, - rk: self.rk.clone(), - zkproof: self.zkproof, - spend_auth_sig, - } - } -} - impl std::fmt::Debug for SpendDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( @@ -206,7 +160,7 @@ impl SpendDescription { }) } - pub fn write(&self, mut writer: W) -> io::Result<()> { + pub fn write_v4(&self, mut writer: W) -> io::Result<()> { writer.write_all(&self.cv.to_bytes())?; writer.write_all(self.anchor.to_repr().as_ref())?; writer.write_all(&self.nullifier.0)?; @@ -214,21 +168,58 @@ impl SpendDescription { writer.write_all(&self.zkproof)?; self.spend_auth_sig.write(&mut writer) } + + pub fn write_v5_without_witness_data(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes())?; + writer.write_all(&self.nullifier.0)?; + self.rk.write(&mut writer) + } } #[derive(Clone)] -pub struct OutputDescription { +pub struct SpendDescriptionV5 { + pub cv: jubjub::ExtendedPoint, + pub nullifier: Nullifier, + pub rk: PublicKey, +} + +impl SpendDescriptionV5 { + pub fn read(mut reader: &mut R) -> io::Result { + let cv = read_point(&mut reader, "cv")?; + let nullifier = SpendDescription::read_nullifier(&mut reader)?; + let rk = SpendDescription::read_rk(&mut reader)?; + + Ok(SpendDescriptionV5 { cv, nullifier, rk }) + } + + pub fn into_spend_description( + self, + anchor: bls12_381::Scalar, + zkproof: GrothProofBytes, + spend_auth_sig: Signature, + ) -> SpendDescription { + SpendDescription { + cv: self.cv, + anchor, + nullifier: self.nullifier, + rk: self.rk, + zkproof, + spend_auth_sig, + } + } +} + +#[derive(Clone)] +pub struct OutputDescription { pub cv: jubjub::ExtendedPoint, pub cmu: bls12_381::Scalar, pub ephemeral_key: jubjub::ExtendedPoint, pub enc_ciphertext: [u8; 580], pub out_ciphertext: [u8; 80], - pub zkproof: A::Proof, + pub zkproof: Proof, } -impl ShieldedOutput> - for OutputDescription -{ +impl ShieldedOutput> for OutputDescription { fn epk(&self) -> &jubjub::ExtendedPoint { &self.ephemeral_key } @@ -242,7 +233,7 @@ impl ShieldedOutput } } -impl std::fmt::Debug for OutputDescription { +impl std::fmt::Debug for OutputDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( f, @@ -252,57 +243,28 @@ impl std::fmt::Debug for OutputDescription { } } -impl OutputDescription { - pub fn read(reader: &mut R) -> io::Result { +impl OutputDescription { + pub fn read(mut reader: &mut R) -> io::Result { // Consensus rules (§4.5): // - Canonical encoding is enforced here. // - "Not small order" is enforced in SaplingVerificationContext::check_output() // (located in zcash_proofs::sapling::verifier). - let cv = { - let mut bytes = [0u8; 32]; - reader.read_exact(&mut bytes)?; - let cv = jubjub::ExtendedPoint::from_bytes(&bytes); - if cv.is_none().into() { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv")); - } - cv.unwrap() - }; + let cv = read_point(&mut reader, "cv")?; // Consensus rule (§7.4): Canonical encoding is enforced here - let cmu = { - let mut f = [0u8; 32]; - reader.read_exact(&mut f)?; - bls12_381::Scalar::from_repr(f) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "cmu not in field"))? - }; + let cmu = read_base(&mut reader, "cmu")?; // Consensus rules (§4.5): // - Canonical encoding is enforced here. // - "Not small order" is enforced in SaplingVerificationContext::check_output() - let ephemeral_key = { - let mut bytes = [0u8; 32]; - reader.read_exact(&mut bytes)?; - let ephemeral_key = jubjub::ExtendedPoint::from_bytes(&bytes); - if ephemeral_key.is_none().into() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid ephemeral_key", - )); - } - ephemeral_key.unwrap() - }; + let ephemeral_key = read_point(&mut reader, "ephemeral key")?; let mut enc_ciphertext = [0u8; 580]; let mut out_ciphertext = [0u8; 80]; reader.read_exact(&mut enc_ciphertext)?; reader.read_exact(&mut out_ciphertext)?; - // Consensus rules (§4.5): - // - Canonical encoding is enforced by the API of SaplingVerificationContext::check_output() - // due to the need to parse this into a bellman::groth16::Proof. - // - Proof validity is enforced in SaplingVerificationContext::check_output() - let mut zkproof = [0u8; GROTH_PROOF_SIZE]; - reader.read_exact(&mut zkproof)?; + let zkproof = read_zkproof(&mut reader)?; Ok(OutputDescription { cv, @@ -313,10 +275,8 @@ impl OutputDescription { zkproof, }) } -} -impl> OutputDescription { - pub fn write(&self, mut writer: W) -> io::Result<()> { + pub fn write_v4(&self, mut writer: W) -> io::Result<()> { writer.write_all(&self.cv.to_bytes())?; writer.write_all(self.cmu.to_repr().as_ref())?; writer.write_all(&self.ephemeral_key.to_bytes())?; @@ -324,17 +284,56 @@ impl> OutputDescription { writer.write_all(&self.out_ciphertext)?; writer.write_all(&self.zkproof) } + + pub fn write_v5_without_proof(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes())?; + writer.write_all(self.cmu.to_repr().as_ref())?; + writer.write_all(&self.ephemeral_key.to_bytes())?; + writer.write_all(&self.enc_ciphertext)?; + writer.write_all(&self.out_ciphertext) + } } -impl OutputDescription { - pub fn into_authorized(self) -> OutputDescription { +#[derive(Clone)] +pub struct OutputDescriptionV5 { + pub cv: jubjub::ExtendedPoint, + pub cmu: bls12_381::Scalar, + pub ephemeral_key: jubjub::ExtendedPoint, + pub enc_ciphertext: [u8; 580], + pub out_ciphertext: [u8; 80], +} + +impl OutputDescriptionV5 { + pub fn read(mut reader: &mut R) -> io::Result { + let cv = read_point(&mut reader, "cv")?; + let cmu = read_base(&mut reader, "cmu")?; + let ephemeral_key = read_point(&mut reader, "ephemeral key")?; + + let mut enc_ciphertext = [0u8; 580]; + let mut out_ciphertext = [0u8; 80]; + reader.read_exact(&mut enc_ciphertext)?; + reader.read_exact(&mut out_ciphertext)?; + + Ok(OutputDescriptionV5 { + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + }) + } + + pub fn into_output_description( + self, + zkproof: GrothProofBytes, + ) -> OutputDescription { OutputDescription { cv: self.cv, cmu: self.cmu, ephemeral_key: self.ephemeral_key, enc_ciphertext: self.enc_ciphertext, out_ciphertext: self.out_ciphertext, - zkproof: self.zkproof, + zkproof, } } } @@ -345,7 +344,7 @@ pub struct CompactOutputDescription { pub enc_ciphertext: Vec, } -impl From> for CompactOutputDescription { +impl From> for CompactOutputDescription { fn from(out: OutputDescription) -> CompactOutputDescription { CompactOutputDescription { epk: out.ephemeral_key, @@ -368,3 +367,127 @@ impl ShieldedOutput> for CompactOutpu &self.enc_ciphertext } } + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use ff::Field; + use group::Group; + use proptest::collection::vec; + use proptest::prelude::*; + use rand::{rngs::StdRng, SeedableRng}; + use std::convert::TryFrom; + + use crate::{ + constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + sapling::{ + redjubjub::{PrivateKey, PublicKey}, + Nullifier, + }, + transaction::{ + components::{amount::testing::arb_amount, GROTH_PROOF_SIZE}, + TxVersion, + }, + }; + + use super::{Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription}; + + prop_compose! { + fn arb_extended_point()(rng_seed in prop::array::uniform32(any::())) -> jubjub::ExtendedPoint { + let mut rng = StdRng::from_seed(rng_seed); + let scalar = jubjub::Scalar::random(&mut rng); + jubjub::ExtendedPoint::generator() * scalar + } + } + + prop_compose! { + /// produce a spend description with invalid data (useful only for serialization + /// roundtrip testing). + fn arb_spend_description()( + cv in arb_extended_point(), + anchor in vec(any::(), 64) + .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap()) + .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), + nullifier in prop::array::uniform32(any::()) + .prop_map(|v| Nullifier::from_slice(&v).unwrap()), + zkproof in vec(any::(), GROTH_PROOF_SIZE) + .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()), + rng_seed in prop::array::uniform32(prop::num::u8::ANY), + fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY), + ) -> SpendDescription { + let mut rng = StdRng::from_seed(rng_seed); + let sk1 = PrivateKey(jubjub::Fr::random(&mut rng)); + let rk = PublicKey::from_private(&sk1, SPENDING_KEY_GENERATOR); + SpendDescription { + cv, + anchor, + nullifier, + rk, + zkproof, + spend_auth_sig: sk1.sign(&fake_sighash_bytes, &mut rng, SPENDING_KEY_GENERATOR), + } + } + } + + prop_compose! { + /// produce an output description with invalid data (useful only for serialization + /// roundtrip testing). + pub fn arb_output_description()( + cv in arb_extended_point(), + cmu in vec(any::(), 64) + .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap()) + .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)), + enc_ciphertext in vec(any::(), 580) + .prop_map(|v| <[u8;580]>::try_from(v.as_slice()).unwrap()), + ephemeral_key in arb_extended_point(), + out_ciphertext in vec(any::(), 80) + .prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()), + zkproof in vec(any::(), GROTH_PROOF_SIZE) + .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()), + ) -> OutputDescription { + OutputDescription { + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + zkproof, + } + } + } + + prop_compose! { + pub fn arb_bundle()( + shielded_spends in vec(arb_spend_description(), 0..30), + shielded_outputs in vec(arb_output_description(), 0..30), + value_balance in arb_amount(), + rng_seed in prop::array::uniform32(prop::num::u8::ANY), + fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY), + ) -> Option> { + if shielded_spends.is_empty() && shielded_outputs.is_empty() { + None + } else { + let mut rng = StdRng::from_seed(rng_seed); + let bsk = PrivateKey(jubjub::Fr::random(&mut rng)); + + Some( + Bundle { + shielded_spends, + shielded_outputs, + value_balance, + authorization: Authorized { binding_sig: bsk.sign(&fake_bvk_bytes, &mut rng, VALUE_COMMITMENT_RANDOMNESS_GENERATOR) }, + } + ) + } + } + } + + pub fn arb_bundle_for_version( + v: TxVersion, + ) -> impl Strategy>> { + if v.has_sapling() { + Strategy::boxed(arb_bundle()) + } else { + Strategy::boxed(Just(None)) + } + } +} diff --git a/zcash_primitives/src/transaction/components/sapling/builder.rs b/zcash_primitives/src/transaction/components/sapling/builder.rs index fafb387b5..28f039154 100644 --- a/zcash_primitives/src/transaction/components/sapling/builder.rs +++ b/zcash_primitives/src/transaction/components/sapling/builder.rs @@ -23,7 +23,10 @@ use crate::{ builder::Progress, components::{ amount::Amount, - sapling::{Bundle, OutputDescription, SpendDescription, Unauthorized}, + sapling::{ + Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, + SpendDescription, + }, }, }, zip32::ExtendedSpendingKey, @@ -56,7 +59,8 @@ impl fmt::Display for Error { } } -struct SpendDescriptionInfo { +#[derive(Debug, Clone)] +pub struct SpendDescriptionInfo { extsk: ExtendedSpendingKey, diversifier: Diversifier, note: Note, @@ -110,7 +114,7 @@ impl SaplingOutput { prover: &Pr, ctx: &mut Pr::SaplingProvingContext, rng: &mut R, - ) -> OutputDescription { + ) -> OutputDescription { let encryptor = sapling_note_encryption::( self.ovk, self.note.clone(), @@ -146,7 +150,7 @@ impl SaplingOutput { } /// Metadata about a transaction created by a [`SaplingBuilder`]. -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct SaplingMetadata { spend_indices: Vec, output_indices: Vec, @@ -192,6 +196,22 @@ pub struct SaplingBuilder

{ outputs: Vec, } +#[derive(Clone)] +pub struct Unauthorized { + tx_metadata: SaplingMetadata, +} + +impl std::fmt::Debug for Unauthorized { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "Unauthorized") + } +} + +impl Authorization for Unauthorized { + type Proof = GrothProofBytes; + type AuthSig = SpendDescriptionInfo; +} + impl SaplingBuilder

{ pub fn new(params: P, target_height: BlockHeight) -> Self { SaplingBuilder { @@ -284,15 +304,16 @@ impl SaplingBuilder

{ } pub fn build( - &self, + self, prover: &Pr, ctx: &mut Pr::SaplingProvingContext, mut rng: R, target_height: BlockHeight, progress_notifier: Option<&Sender>, - ) -> Result<(Option>, SaplingMetadata), Error> { + ) -> Result>, Error> { // Record initial positions of spends and outputs - let mut indexed_spends: Vec<_> = self.spends.iter().enumerate().collect(); + let params = self.params; + let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); let mut indexed_outputs: Vec<_> = self .outputs .iter() @@ -328,7 +349,7 @@ impl SaplingBuilder

{ .expect("Sapling anchor must be set if Sapling spends are present."); indexed_spends - .iter() + .into_iter() .enumerate() .map(|(i, (pos, spend))| { let proof_generation_key = spend.extsk.expsk.proof_generation_key(); @@ -352,7 +373,7 @@ impl SaplingBuilder

{ .map_err(|_| Error::SpendProof)?; // Record the post-randomized spend location - tx_metadata.spend_indices[*pos] = i; + tx_metadata.spend_indices[pos] = i; // Update progress and send a notification on the channel progress += 1; @@ -369,7 +390,7 @@ impl SaplingBuilder

{ nullifier, rk, zkproof, - spend_auth_sig: (), + spend_auth_sig: spend, }) }) .collect::, Error>>()? @@ -378,7 +399,7 @@ impl SaplingBuilder

{ }; // Create Sapling OutputDescriptions - let shielded_outputs: Vec> = indexed_outputs + let shielded_outputs: Vec> = indexed_outputs .into_iter() .enumerate() .map(|(i, output)| { @@ -404,7 +425,6 @@ impl SaplingBuilder

{ } (diversifier, g_d) }; - let (pk_d, payment_address) = loop { let dummy_ivk = jubjub::Fr::random(&mut rng); let pk_d = g_d * dummy_ivk; @@ -414,7 +434,7 @@ impl SaplingBuilder

{ }; let rseed = - generate_random_rseed_internal(&self.params, target_height, &mut rng); + generate_random_rseed_internal(¶ms, target_height, &mut rng); ( payment_address, @@ -470,47 +490,59 @@ impl SaplingBuilder

{ shielded_spends, shielded_outputs, value_balance: self.value_balance, - authorization: Unauthorized, + authorization: Unauthorized { tx_metadata }, }) }; - Ok((bundle, tx_metadata)) + Ok(bundle) } +} - pub fn create_signatures( +impl SpendDescription { + pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription { + SpendDescription { + cv: self.cv, + anchor: self.anchor, + nullifier: self.nullifier, + rk: self.rk.clone(), + zkproof: self.zkproof, + spend_auth_sig, + } + } +} + +impl Bundle { + pub fn apply_signatures( self, prover: &Pr, ctx: &mut Pr::SaplingProvingContext, rng: &mut R, sighash_bytes: &[u8; 32], - tx_metadata: &SaplingMetadata, - ) -> Result, Signature)>, Error> { - // Create Sapling spendAuth signatures. These must be properly ordered with respect to the - // shuffle that is described by tx_metadata. - let mut spend_sigs = vec![None; self.spends.len()]; - for (i, spend) in self.spends.into_iter().enumerate() { - spend_sigs[tx_metadata.spend_indices[i]] = Some(spend_sig_internal( - PrivateKey(spend.extsk.expsk.ask), - spend.alpha, - sighash_bytes, - rng, - )); - } + ) -> Result<(Bundle, SaplingMetadata), Error> { + let binding_sig = prover + .binding_sig(ctx, self.value_balance, sighash_bytes) + .map_err(|_| Error::BindingSig)?; - if tx_metadata.spend_indices.is_empty() && tx_metadata.output_indices.is_empty() { - Ok(None) - } else { - let spend_sigs = spend_sigs - .into_iter() - .collect::>>() - .unwrap_or_default(); - - let binding_sig = prover - .binding_sig(ctx, self.value_balance, sighash_bytes) - .map_err(|_| Error::BindingSig)?; - - Ok(Some((spend_sigs, binding_sig))) - } + Ok(( + Bundle { + shielded_spends: self + .shielded_spends + .iter() + .map(|spend| { + spend.apply_signature(spend_sig_internal( + PrivateKey(spend.spend_auth_sig.extsk.expsk.ask), + spend.spend_auth_sig.alpha, + sighash_bytes, + rng, + )) + }) + .collect(), + shielded_outputs: self.shielded_outputs, + value_balance: self.value_balance, + authorization: Authorized { binding_sig }, + }, + self.authorization.tx_metadata, + )) } } @@ -574,23 +606,22 @@ pub mod testing { let prover = MockTxProver; let mut ctx = prover.new_sapling_proving_context(); - let (bundle, meta) = builder.build( + let bundle = builder.build( &prover, &mut ctx, &mut rng, target_height.unwrap(), None - ).unwrap(); + ).unwrap().unwrap(); - let (spend_auth_sigs, binding_sig) = builder.create_signatures( + let (bundle, _) = bundle.apply_signatures( &prover, &mut ctx, &mut rng, &fake_sighash_bytes, - &meta - ).unwrap().unwrap(); + ).unwrap(); - bundle.unwrap().apply_signatures(spend_auth_sigs, binding_sig) + bundle } } } diff --git a/zcash_primitives/src/transaction/components/transparent.rs b/zcash_primitives/src/transaction/components/transparent.rs index 6b1261be7..59059dbb9 100644 --- a/zcash_primitives/src/transaction/components/transparent.rs +++ b/zcash_primitives/src/transaction/components/transparent.rs @@ -15,13 +15,6 @@ pub trait Authorization: Debug { type ScriptSig: Debug + Clone + PartialEq; } -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Unauthorized; - -impl Authorization for Unauthorized { - type ScriptSig = (); -} - #[derive(Debug, Copy, Clone, PartialEq)] pub struct Authorized; @@ -33,25 +26,7 @@ impl Authorization for Authorized { pub struct Bundle { pub vin: Vec>, pub vout: Vec, -} - -impl Bundle { - pub fn apply_signatures(self, script_sigs: Vec