From b2ff29db78bceb1eb31c1eaed96ebbf0601471d0 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 1 May 2023 22:10:00 +0000 Subject: [PATCH] Refactor Sapling builder to separate out proof generation Closes zcash/librustzcash#741. --- zcash_client_backend/src/data_api/wallet.rs | 47 +- zcash_client_sqlite/src/testing.rs | 16 +- zcash_client_sqlite/src/wallet/sapling.rs | 7 +- zcash_extensions/src/transparent/demo.rs | 6 +- zcash_primitives/benches/note_decryption.rs | 12 +- zcash_primitives/src/sapling.rs | 27 +- zcash_primitives/src/sapling/value.rs | 17 +- zcash_primitives/src/sapling/value/sums.rs | 16 + zcash_primitives/src/transaction/builder.rs | 108 ++- .../src/transaction/components/sapling.rs | 10 +- .../transaction/components/sapling/builder.rs | 761 ++++++++++++------ zcash_primitives/src/transaction/mod.rs | 3 +- 12 files changed, 711 insertions(+), 319 deletions(-) diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index a5b9fef55..a8c5151ca 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -8,7 +8,7 @@ use zcash_primitives::{ sapling::{ self, note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey}, - prover::TxProver as SaplingProver, + prover::{OutputProver, SpendProver}, Node, }, transaction::{ @@ -191,7 +191,8 @@ where pub fn create_spend_to_address( wallet_db: &mut DbT, params: &ParamsT, - prover: impl SaplingProver, + spend_prover: &impl SpendProver, + output_prover: &impl OutputProver, usk: &UnifiedSpendingKey, to: &RecipientAddress, amount: NonNegativeAmount, @@ -231,7 +232,15 @@ where change_memo, )?; - create_proposed_transaction(wallet_db, params, prover, usk, ovk_policy, &proposal) + create_proposed_transaction( + wallet_db, + params, + spend_prover, + output_prover, + usk, + ovk_policy, + &proposal, + ) } /// Constructs a transaction that sends funds as specified by the `request` argument @@ -289,7 +298,8 @@ where pub fn spend( wallet_db: &mut DbT, params: &ParamsT, - prover: impl SaplingProver, + spend_prover: &impl SpendProver, + output_prover: &impl OutputProver, input_selector: &InputsT, usk: &UnifiedSpendingKey, request: zip321::TransactionRequest, @@ -324,7 +334,15 @@ where min_confirmations, )?; - create_proposed_transaction(wallet_db, params, prover, usk, ovk_policy, &proposal) + create_proposed_transaction( + wallet_db, + params, + spend_prover, + output_prover, + usk, + ovk_policy, + &proposal, + ) } /// Select transaction inputs, compute fees, and construct a proposal for a transaction @@ -475,7 +493,8 @@ where pub fn create_proposed_transaction( wallet_db: &mut DbT, params: &ParamsT, - prover: impl SaplingProver, + spend_prover: &impl SpendProver, + output_prover: &impl OutputProver, usk: &UnifiedSpendingKey, ovk_policy: OvkPolicy, proposal: &Proposal, @@ -663,7 +682,8 @@ where } // Build the transaction with the specified fee rule - let (tx, sapling_build_meta) = builder.build(&prover, proposal.fee_rule())?; + let (tx, sapling_build_meta) = + builder.build(spend_prover, output_prover, proposal.fee_rule())?; let internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal)); let sapling_outputs = @@ -768,7 +788,8 @@ where pub fn shield_transparent_funds( wallet_db: &mut DbT, params: &ParamsT, - prover: impl SaplingProver, + spend_prover: &impl SpendProver, + output_prover: &impl OutputProver, input_selector: &InputsT, shielding_threshold: NonNegativeAmount, usk: &UnifiedSpendingKey, @@ -798,7 +819,15 @@ where min_confirmations, )?; - create_proposed_transaction(wallet_db, params, prover, usk, OvkPolicy::Sender, &proposal) + create_proposed_transaction( + wallet_db, + params, + spend_prover, + output_prover, + usk, + OvkPolicy::Sender, + &proposal, + ) } #[allow(clippy::type_complexity)] diff --git a/zcash_client_sqlite/src/testing.rs b/zcash_client_sqlite/src/testing.rs index d2c6d9192..dd90a755b 100644 --- a/zcash_client_sqlite/src/testing.rs +++ b/zcash_client_sqlite/src/testing.rs @@ -448,10 +448,12 @@ impl TestState { >, > { let params = self.network(); + let prover = test_prover(); create_spend_to_address( &mut self.db_data, ¶ms, - test_prover(), + &prover, + &prover, usk, to, amount, @@ -484,10 +486,12 @@ impl TestState { InputsT: InputSelector>, { let params = self.network(); + let prover = test_prover(); spend( &mut self.db_data, ¶ms, - test_prover(), + &prover, + &prover, input_selector, usk, request, @@ -614,10 +618,12 @@ impl TestState { FeeRuleT: FeeRule, { let params = self.network(); + let prover = test_prover(); create_proposed_transaction( &mut self.db_data, ¶ms, - test_prover(), + &prover, + &prover, usk, ovk_policy, proposal, @@ -647,10 +653,12 @@ impl TestState { InputsT: InputSelector>, { let params = self.network(); + let prover = test_prover(); shield_transparent_funds( &mut self.db_data, ¶ms, - test_prover(), + &prover, + &prover, input_selector, shielding_threshold, usk, diff --git a/zcash_client_sqlite/src/wallet/sapling.rs b/zcash_client_sqlite/src/wallet/sapling.rs index 129c65f5d..01f3d39e7 100644 --- a/zcash_client_sqlite/src/wallet/sapling.rs +++ b/zcash_client_sqlite/src/wallet/sapling.rs @@ -451,8 +451,9 @@ pub(crate) mod tests { legacy::TransparentAddress, memo::{Memo, MemoBytes}, sapling::{ - note_encryption::try_sapling_output_recovery, prover::TxProver, Node, Note, - PaymentAddress, + note_encryption::try_sapling_output_recovery, + prover::{OutputProver, SpendProver}, + Node, Note, PaymentAddress, }, transaction::{ components::{amount::NonNegativeAmount, Amount}, @@ -497,7 +498,7 @@ pub(crate) mod tests { zcash_primitives::transaction::components::{OutPoint, TxOut}, }; - pub(crate) fn test_prover() -> impl TxProver { + pub(crate) fn test_prover() -> impl SpendProver + OutputProver { match LocalTxProver::with_default_location() { Some(tx_prover) => tx_prover, None => { diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index d7b85bf5d..8ceb816fa 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -839,7 +839,7 @@ mod tests { .unwrap(); let (tx_a, _) = builder_a .txn_builder - .build_zfuture(&prover, &fee_rule) + .build_zfuture(&prover, &prover, &fee_rule) .map_err(|e| format!("build failure: {:?}", e)) .unwrap(); let tze_a = tx_a.tze_bundle().unwrap(); @@ -857,7 +857,7 @@ mod tests { .unwrap(); let (tx_b, _) = builder_b .txn_builder - .build_zfuture(&prover, &fee_rule) + .build_zfuture(&prover, &prover, &fee_rule) .map_err(|e| format!("build failure: {:?}", e)) .unwrap(); let tze_b = tx_b.tze_bundle().unwrap(); @@ -882,7 +882,7 @@ mod tests { let (tx_c, _) = builder_c .txn_builder - .build_zfuture(&prover, &fee_rule) + .build_zfuture(&prover, &prover, &fee_rule) .map_err(|e| format!("build failure: {:?}", e)) .unwrap(); let tze_c = tx_c.tze_bundle().unwrap(); diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index 4827badce..66fbf5963 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -12,13 +12,11 @@ use zcash_primitives::{ try_sapling_compact_note_decryption, try_sapling_note_decryption, PreparedIncomingViewingKey, SaplingDomain, }, - prover::mock::MockTxProver, + prover::mock::{MockOutputProver, MockSpendProver}, value::NoteValue, Diversifier, SaplingIvk, }, - transaction::components::sapling::{ - builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription, - }, + transaction::components::sapling::{builder::SaplingBuilder, CompactOutputDescription}, }; #[cfg(unix)] @@ -32,7 +30,7 @@ fn bench_note_decryption(c: &mut Criterion) { let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); // Construct a Sapling output. - let output: OutputDescription = { + let output = { let diversifier = Diversifier([0; 11]); let pa = valid_ivk.to_payment_address(diversifier).unwrap(); @@ -46,8 +44,8 @@ fn bench_note_decryption(c: &mut Criterion) { MemoBytes::empty(), ) .unwrap(); - let bundle = builder - .build(&MockTxProver, &mut (), &mut rng, height, None) + let (bundle, _) = builder + .build::(&mut rng, height) .unwrap() .unwrap(); bundle.shielded_outputs()[0].clone() diff --git a/zcash_primitives/src/sapling.rs b/zcash_primitives/src/sapling.rs index 759826581..82c18e3bb 100644 --- a/zcash_primitives/src/sapling.rs +++ b/zcash_primitives/src/sapling.rs @@ -36,11 +36,11 @@ pub fn spend_sig( sighash: &[u8; 32], rng: &mut R, ) -> Signature { - spend_sig_internal(ask, ar, sighash, rng) + spend_sig_internal(&ask, ar, sighash, rng) } pub(crate) fn spend_sig_internal( - ask: PrivateKey, + ask: &PrivateKey, ar: jubjub::Fr, sighash: &[u8; 32], rng: &mut R, @@ -60,6 +60,29 @@ pub(crate) fn spend_sig_internal( rsk.sign(&data_to_be_signed, rng, SPENDING_KEY_GENERATOR) } +/// Verifies a spendAuthSig. +/// +/// This only exists because the RedJubjub implementation inside `zcash_primitives` does +/// not implement key prefixing (which was added in response to a Sapling audit). This +/// will be fixed by migrating to the redjubjub crate. +pub(crate) fn verify_spend_sig( + ak: &PublicKey, + alpha: jubjub::Fr, + sighash: &[u8; 32], + sig: &Signature, +) -> bool { + // We compute `rk` (needed for key prefixing) + let rk = ak.randomize(alpha, SPENDING_KEY_GENERATOR); + + // Compute the signature's message for rk/spend_auth_sig + let mut data_to_be_signed = [0u8; 64]; + data_to_be_signed[0..32].copy_from_slice(&rk.0.to_bytes()); + data_to_be_signed[32..64].copy_from_slice(&sighash[..]); + + // Do the verifying + rk.verify(&data_to_be_signed, sig, SPENDING_KEY_GENERATOR) +} + #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { pub use super::{ diff --git a/zcash_primitives/src/sapling/value.rs b/zcash_primitives/src/sapling/value.rs index ed68027df..7927305e0 100644 --- a/zcash_primitives/src/sapling/value.rs +++ b/zcash_primitives/src/sapling/value.rs @@ -38,7 +38,7 @@ //! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html use bitvec::{array::BitArray, order::Lsb0}; -use ff::Field; +use ff::{Field, PrimeField}; use group::GroupEncoding; use rand::RngCore; use subtle::CtOption; @@ -86,6 +86,21 @@ impl ValueCommitTrapdoor { ValueCommitTrapdoor(jubjub::Scalar::random(rng)) } + /// Constructs `ValueCommitTrapdoor` from the byte representation of a scalar. + /// + /// Returns a `None` [`CtOption`] if `bytes` is not a canonical representation of a + /// Jubjub scalar. + /// + /// This is a low-level API, requiring a detailed understanding of the + /// [use of value commitment trapdoors][saplingbalance] in the Zcash protocol + /// to use correctly and securely. It is intended to be used in combination + /// with [`ValueCommitment::derive`]. + /// + /// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance + pub fn from_bytes(bytes: [u8; 32]) -> CtOption { + jubjub::Scalar::from_repr(bytes).map(ValueCommitTrapdoor) + } + /// Returns the inner Jubjub scalar representing this trapdoor. /// /// This is public for access by `zcash_proofs`. diff --git a/zcash_primitives/src/sapling/value/sums.rs b/zcash_primitives/src/sapling/value/sums.rs index f1d8e1a7f..46cc92b05 100644 --- a/zcash_primitives/src/sapling/value/sums.rs +++ b/zcash_primitives/src/sapling/value/sums.rs @@ -123,6 +123,14 @@ impl Sub<&ValueCommitTrapdoor> for ValueCommitTrapdoor { } } +impl Sub for TrapdoorSum { + type Output = TrapdoorSum; + + fn sub(self, rhs: Self) -> Self::Output { + TrapdoorSum(self.0 - rhs.0) + } +} + impl SubAssign<&ValueCommitTrapdoor> for TrapdoorSum { fn sub_assign(&mut self, rhs: &ValueCommitTrapdoor) { self.0 -= rhs.0; @@ -207,6 +215,14 @@ impl SubAssign<&ValueCommitment> for CommitmentSum { } } +impl Sub for CommitmentSum { + type Output = CommitmentSum; + + fn sub(self, rhs: Self) -> Self::Output { + CommitmentSum(self.0 - rhs.0) + } +} + impl Sum for CommitmentSum { fn sum>(iter: I) -> Self { iter.fold(CommitmentSum::zero(), |acc, cv| acc + &cv) diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 21ed4c680..1041da07f 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -12,7 +12,11 @@ use crate::{ keys::OutgoingViewingKey, legacy::TransparentAddress, memo::MemoBytes, - sapling::{self, prover::TxProver, Diversifier, Note, PaymentAddress}, + sapling::{ + self, + prover::{OutputProver, SpendProver}, + redjubjub, Diversifier, Note, PaymentAddress, + }, transaction::{ components::{ amount::{Amount, BalanceError}, @@ -162,6 +166,7 @@ pub struct Builder<'a, P, R> { // `add_sapling_spend` or `add_orchard_spend`, we will build an unauthorized, unproven // transaction, and then the caller will be responsible for using the spending keys or their // derivatives for proving and signing to complete transaction creation. + sapling_asks: Vec, orchard_saks: Vec, #[cfg(feature = "zfuture")] tze_builder: TzeBuilder<'a, TransactionData>, @@ -266,6 +271,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { transparent_builder: TransparentBuilder::empty(), sapling_builder: SaplingBuilder::new(params, target_height), orchard_builder, + sapling_asks: vec![], orchard_saks: Vec::new(), #[cfg(feature = "zfuture")] tze_builder: TzeBuilder::empty(), @@ -329,7 +335,12 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { merkle_path: sapling::MerklePath, ) -> Result<(), sapling_builder::Error> { self.sapling_builder - .add_spend(&mut self.rng, extsk, diversifier, note, merkle_path) + .add_spend(&mut self.rng, &extsk, diversifier, note, merkle_path)?; + + self.sapling_asks + .push(redjubjub::PrivateKey(extsk.expsk.ask)); + + Ok(()) } /// Adds a Sapling address to send funds to. @@ -432,13 +443,14 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { /// /// Upon success, returns a tuple containing the final transaction, and the /// [`SaplingMetadata`] generated during the build process. - pub fn build( + pub fn build( self, - prover: &impl TxProver, + spend_prover: &SP, + output_prover: &OP, fee_rule: &FR, ) -> Result<(Transaction, SaplingMetadata), Error> { let fee = self.get_fee(fee_rule).map_err(Error::Fee)?; - self.build_internal(prover, fee.into()) + self.build_internal(spend_prover, output_prover, fee.into()) } /// Builds a transaction from the configured spends and outputs. @@ -446,9 +458,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { /// Upon success, returns a tuple containing the final transaction, and the /// [`SaplingMetadata`] generated during the build process. #[cfg(feature = "zfuture")] - pub fn build_zfuture( + pub fn build_zfuture( self, - prover: &impl TxProver, + spend_prover: &SP, + output_prover: &OP, fee_rule: &FR, ) -> Result<(Transaction, SaplingMetadata), Error> { let fee = fee_rule @@ -464,12 +477,13 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { ) .map_err(Error::Fee)?; - self.build_internal(prover, fee.into()) + self.build_internal(spend_prover, output_prover, fee.into()) } - fn build_internal( + fn build_internal( self, - prover: &impl TxProver, + spend_prover: &SP, + output_prover: &OP, fee: Amount, ) -> Result<(Transaction, SaplingMetadata), Error> { let consensus_branch_id = BranchId::for_height(&self.params, self.target_height); @@ -497,17 +511,27 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> 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 = self + let (sapling_bundle, tx_metadata) = match self .sapling_builder - .build( - prover, - &mut ctx, - &mut rng, - self.target_height, - self.progress_notifier.as_ref(), - ) - .map_err(Error::SaplingBuild)?; + .build::(&mut rng, self.target_height) + .map_err(Error::SaplingBuild)? + .map(|(bundle, tx_metadata)| { + // We need to create proofs before signatures, because we still support + // creating V4 transactions, which commit to the Sapling proofs in the + // transaction digest. + ( + bundle.create_proofs( + spend_prover, + output_prover, + &mut rng, + self.progress_notifier.as_ref(), + ), + tx_metadata, + ) + }) { + Some((bundle, meta)) => (Some(bundle), meta), + None => (None, SaplingMetadata::empty()), + }; let orchard_bundle: Option> = if let Some(builder) = self.orchard_builder { @@ -560,17 +584,17 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { let shielded_sig_commitment = signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts); - let (sapling_bundle, tx_metadata) = match unauthed_tx + let sapling_bundle = unauthed_tx .sapling_bundle .map(|b| { - b.apply_signatures(prover, &mut ctx, &mut rng, shielded_sig_commitment.as_ref()) + b.apply_signatures( + &mut rng, + *shielded_sig_commitment.as_ref(), + &self.sapling_asks, + ) }) .transpose() - .map_err(Error::SaplingBuild)? - { - Some((bundle, meta)) => (Some(bundle), meta), - None => (None, SaplingMetadata::empty()), - }; + .map_err(Error::SaplingBuild)?; let orchard_bundle = unauthed_tx .orchard_bundle @@ -648,7 +672,7 @@ mod testing { use super::{Builder, Error, SaplingMetadata}; use crate::{ consensus::{self, BlockHeight}, - sapling::prover::mock::MockTxProver, + sapling::prover::mock::{MockOutputProver, MockSpendProver}, transaction::fees::fixed, transaction::Transaction, }; @@ -693,7 +717,11 @@ mod testing { impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> { pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error> { #[allow(deprecated)] - self.build(&MockTxProver, &fixed::FeeRule::standard()) + self.build( + &MockSpendProver, + &MockOutputProver, + &fixed::FeeRule::standard(), + ) } } } @@ -710,10 +738,7 @@ mod tests { legacy::TransparentAddress, memo::MemoBytes, sapling::{self, Node, Rseed}, - transaction::components::{ - amount::{Amount, NonNegativeAmount}, - sapling::builder::{self as sapling_builder}, - }, + transaction::components::amount::{Amount, BalanceError, NonNegativeAmount}, zip32::ExtendedSpendingKey, }; @@ -759,6 +784,7 @@ mod tests { tze_builder: std::marker::PhantomData, progress_notifier: None, orchard_builder: None, + sapling_asks: vec![], orchard_saks: Vec::new(), }; @@ -828,12 +854,9 @@ mod tests { ) .unwrap(); - // Expect a binding signature error, because our inputs aren't valid, but this shows - // that a binding signature was attempted - assert_matches!( - builder.mock_build(), - Err(Error::SaplingBuild(sapling_builder::Error::BindingSig)) - ); + // A binding signature (and bundle) is present because there is a Sapling spend. + let (tx, _) = builder.mock_build().unwrap(); + assert!(tx.sapling_bundle().is_some()); } #[test] @@ -950,9 +973,6 @@ mod tests { // Succeeds if there is sufficient input // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in - // - // (Still fails because we are using a MockTxProver which doesn't correctly - // compute bindingSig.) { let mut builder = Builder::new(TEST_NETWORK, tx_height, None); builder @@ -982,8 +1002,8 @@ mod tests { .unwrap(); assert_matches!( builder.mock_build(), - Err(Error::SaplingBuild(sapling_builder::Error::BindingSig)) - ) + Ok((tx, _)) if tx.fee_paid(|_| Err(BalanceError::Overflow)).unwrap() == Amount::const_from_i64(10_000) + ); } } } diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 1c17eaf36..25f2c541e 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -34,19 +34,11 @@ pub trait Authorization: Debug { type AuthSig: Clone + Debug; } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Unproven; - -impl Authorization for Unproven { - type SpendProof = (); - type OutputProof = (); - type AuthSig = (); -} - /// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to /// the ledger. #[derive(Debug, Copy, Clone)] pub struct Authorized { + // TODO: Make this private. pub binding_sig: redjubjub::Signature, } diff --git a/zcash_primitives/src/transaction/components/sapling/builder.rs b/zcash_primitives/src/transaction/components/sapling/builder.rs index 8687ccb2b..c4ddd2e1c 100644 --- a/zcash_primitives/src/transaction/components/sapling/builder.rs +++ b/zcash_primitives/src/transaction/components/sapling/builder.rs @@ -1,32 +1,37 @@ //! Types and functions for building Sapling transaction components. use core::fmt; -use std::sync::mpsc::Sender; +use std::{marker::PhantomData, sync::mpsc::Sender}; use ff::Field; use rand::{seq::SliceRandom, RngCore}; +use rand_core::CryptoRng; use crate::{ consensus::{self, BlockHeight}, keys::OutgoingViewingKey, memo::MemoBytes, sapling::{ - keys::SaplingIvk, + self, + constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, note_encryption::sapling_note_encryption, - prover::TxProver, - redjubjub::{PrivateKey, Signature}, + prover::{OutputProver, SpendProver}, + redjubjub::{PrivateKey, PublicKey, Signature}, spend_sig_internal, util::generate_random_rseed_internal, - value::{NoteValue, ValueSum}, - Diversifier, MerklePath, Node, Note, PaymentAddress, + value::{ + CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum, + }, + verify_spend_sig, Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey, + SaplingIvk, }, transaction::{ builder::Progress, components::{ amount::{Amount, NonNegativeAmount}, sapling::{ - fees, Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, - SpendDescription, + fees, Authorization, Authorized, Bundle, GrothProofBytes, MapAuth, + OutputDescription, SpendDescription, }, }, }, @@ -41,8 +46,15 @@ const MIN_SHIELDED_OUTPUTS: usize = 2; pub enum Error { AnchorMismatch, BindingSig, + /// A signature is valid for more than one input. This should never happen if `alpha` + /// is sampled correctly, and indicates a critical failure in randomness generation. + DuplicateSignature, InvalidAddress, InvalidAmount, + /// External signature is not valid. + InvalidExternalSignature, + /// A bundle could not be built because required signatures were missing. + MissingSignatures, SpendProof, } @@ -53,8 +65,11 @@ impl fmt::Display for Error { write!(f, "Anchor mismatch (anchors for all spends must be equal)") } Error::BindingSig => write!(f, "Failed to create bindingSig"), + Error::DuplicateSignature => write!(f, "Signature valid for more than one input"), Error::InvalidAddress => write!(f, "Invalid address"), Error::InvalidAmount => write!(f, "Invalid amount"), + Error::InvalidExternalSignature => write!(f, "External signature was invalid"), + Error::MissingSignatures => write!(f, "Required signatures were missing during build"), Error::SpendProof => write!(f, "Failed to create Sapling spend proof"), } } @@ -62,11 +77,12 @@ impl fmt::Display for Error { #[derive(Debug, Clone)] pub struct SpendDescriptionInfo { - extsk: ExtendedSpendingKey, + proof_generation_key: ProofGenerationKey, diversifier: Diversifier, note: Note, alpha: jubjub::Fr, merkle_path: MerklePath, + rcv: ValueCommitTrapdoor, } impl fees::InputView<()> for SpendDescriptionInfo { @@ -81,6 +97,70 @@ impl fees::InputView<()> for SpendDescriptionInfo { } } +impl SpendDescriptionInfo { + fn new_internal( + mut rng: &mut R, + extsk: &ExtendedSpendingKey, + diversifier: Diversifier, + note: Note, + merkle_path: MerklePath, + ) -> Self { + SpendDescriptionInfo { + proof_generation_key: extsk.expsk.proof_generation_key(), + diversifier, + note, + alpha: jubjub::Fr::random(&mut rng), + merkle_path, + rcv: ValueCommitTrapdoor::random(rng), + } + } + + fn build( + self, + anchor: Option, + ) -> Result>, Error> { + let anchor = anchor.expect("Sapling anchor must be set if Sapling spends are present."); + + // Construct the value commitment. + let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone()); + + let ak = PublicKey(self.proof_generation_key.ak.into()); + + // This is the result of the re-randomization, we compute it for the caller + let rk = ak.randomize(self.alpha, SPENDING_KEY_GENERATOR); + + let nullifier = self.note.nf( + &self.proof_generation_key.to_viewing_key().nk, + u64::try_from(self.merkle_path.position()) + .expect("Sapling note commitment tree position must fit into a u64"), + ); + + let zkproof = Pr::prepare_circuit( + self.proof_generation_key, + self.diversifier, + *self.note.rseed(), + self.note.value(), + self.alpha, + self.rcv, + anchor, + self.merkle_path.clone(), + ) + .ok_or(Error::SpendProof)?; + + Ok(SpendDescription { + cv, + anchor, + nullifier, + rk, + zkproof, + spend_auth_sig: SigningParts { + ak, + alpha: self.alpha, + }, + }) + } +} + /// A struct containing the information required in order to construct a /// Sapling output to a transaction. #[derive(Clone)] @@ -89,9 +169,38 @@ struct SaplingOutputInfo { ovk: Option, note: Note, memo: MemoBytes, + rcv: ValueCommitTrapdoor, } impl SaplingOutputInfo { + fn dummy( + params: &P, + mut rng: &mut R, + target_height: BlockHeight, + ) -> Self { + // This is a dummy output + let dummy_to = { + let mut diversifier = Diversifier([0; 11]); + loop { + rng.fill_bytes(&mut diversifier.0); + let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); + if let Some(addr) = dummy_ivk.to_payment_address(diversifier) { + break addr; + } + } + }; + + Self::new_internal( + params, + rng, + target_height, + None, + dummy_to, + NoteValue::from_raw(0), + MemoBytes::empty(), + ) + } + fn new_internal( params: &P, rng: &mut R, @@ -105,24 +214,31 @@ impl SaplingOutputInfo { let note = Note::from_parts(to, value, rseed); - SaplingOutputInfo { ovk, note, memo } + SaplingOutputInfo { + ovk, + note, + memo, + rcv: ValueCommitTrapdoor::random(rng), + } } - fn build( + fn build( self, - prover: &Pr, - ctx: &mut Pr::SaplingProvingContext, rng: &mut R, - ) -> OutputDescription { + ) -> OutputDescription { let encryptor = sapling_note_encryption::(self.ovk, self.note.clone(), self.memo, rng); - let (zkproof, cv) = prover.output_proof( - ctx, + // Construct the value commitment. + let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone()); + + // Prepare the circuit that will be used to construct the proof. + let zkproof = Pr::prepare_circuit( encryptor.esk().0, self.note.recipient(), self.note.rcm(), - self.note.value().inner(), + self.note.value(), + self.rcv, ); let cmu = self.note.cmu(); @@ -197,23 +313,6 @@ 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 SpendProof = GrothProofBytes; - type OutputProof = GrothProofBytes; - type AuthSig = SpendDescriptionInfo; -} - impl

SaplingBuilder

{ pub fn new(params: P, target_height: BlockHeight) -> Self { SaplingBuilder { @@ -276,7 +375,7 @@ impl SaplingBuilder

{ pub fn add_spend( &mut self, mut rng: R, - extsk: ExtendedSpendingKey, + extsk: &ExtendedSpendingKey, diversifier: Diversifier, note: Note, merkle_path: MerklePath, @@ -292,18 +391,13 @@ impl SaplingBuilder

{ self.anchor = Some(merkle_path.root(node).into()) } - let alpha = jubjub::Fr::random(&mut rng); - self.value_balance = (self.value_balance + note.value()).ok_or(Error::InvalidAmount)?; self.try_value_balance()?; - self.spends.push(SpendDescriptionInfo { - extsk, - diversifier, - note, - alpha, - merkle_path, - }); + let spend = + SpendDescriptionInfo::new_internal(&mut rng, extsk, diversifier, note, merkle_path); + + self.spends.push(spend); Ok(()) } @@ -336,22 +430,18 @@ impl SaplingBuilder

{ Ok(()) } - pub fn build( + pub fn build( self, - prover: &Pr, - ctx: &mut Pr::SaplingProvingContext, mut rng: R, target_height: BlockHeight, - progress_notifier: Option<&Sender>, - ) -> Result>, Error> { + ) -> Result, Error> { let value_balance = self.try_value_balance()?; // Record initial positions of spends and outputs - let params = self.params; let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); let mut indexed_outputs: Vec<_> = self .outputs - .iter() + .into_iter() .enumerate() .map(|(i, o)| Some((i, o))) .collect(); @@ -373,204 +463,403 @@ impl SaplingBuilder

{ indexed_spends.shuffle(&mut rng); indexed_outputs.shuffle(&mut rng); - // Keep track of the total number of steps computed - let total_progress = indexed_spends.len() as u32 + indexed_outputs.len() as u32; - let mut progress = 0u32; + // Record the transaction metadata and create dummy outputs. + let spend_infos = indexed_spends + .into_iter() + .enumerate() + .map(|(i, (pos, spend))| { + // Record the post-randomized spend location + tx_metadata.spend_indices[pos] = i; - // Create Sapling SpendDescriptions - let shielded_spends: Vec> = if !indexed_spends.is_empty() { - let anchor = self - .anchor - .expect("Sapling anchor must be set if Sapling spends are present."); - - indexed_spends - .into_iter() - .enumerate() - .map(|(i, (pos, spend))| { - let proof_generation_key = spend.extsk.expsk.proof_generation_key(); - - let nullifier = spend.note.nf( - &proof_generation_key.to_viewing_key().nk, - u64::try_from(spend.merkle_path.position()) - .expect("Sapling note commitment tree position must fit into a u64"), - ); - - let (zkproof, cv, rk) = prover - .spend_proof( - ctx, - proof_generation_key, - spend.diversifier, - *spend.note.rseed(), - spend.alpha, - spend.note.value().inner(), - anchor, - spend.merkle_path.clone(), - ) - .map_err(|_| Error::SpendProof)?; - - // Record the post-randomized spend location - tx_metadata.spend_indices[pos] = i; - - // Update progress and send a notification on the channel - progress += 1; - if let Some(sender) = progress_notifier { - // If the send fails, we should ignore the error, not crash. - sender - .send(Progress::new(progress, Some(total_progress))) - .unwrap_or(()); - } - - Ok(SpendDescription { - cv, - anchor, - nullifier, - rk, - zkproof, - spend_auth_sig: spend, - }) - }) - .collect::, Error>>()? - } else { - vec![] - }; - - // Create Sapling OutputDescriptions - let shielded_outputs: Vec> = indexed_outputs + spend + }) + .collect::>(); + let output_infos = indexed_outputs .into_iter() .enumerate() .map(|(i, output)| { - let result = if let Some((pos, output)) = output { + if let Some((pos, output)) = output { // Record the post-randomized output location tx_metadata.output_indices[pos] = i; - output.clone().build::(prover, ctx, &mut rng) + output } else { // This is a dummy output - let dummy_note = { - let payment_address = { - let mut diversifier = Diversifier([0; 11]); - loop { - rng.fill_bytes(&mut diversifier.0); - let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); - if let Some(addr) = dummy_ivk.to_payment_address(diversifier) { - break addr; - } - } - }; - - let rseed = - generate_random_rseed_internal(¶ms, target_height, &mut rng); - - Note::from_parts(payment_address, NoteValue::from_raw(0), rseed) - }; - - let esk = dummy_note.generate_or_derive_esk_internal(&mut rng); - let epk = esk.derive_public( - dummy_note - .recipient() - .diversifier() - .g_d() - .expect("checked at construction") - .into(), - ); - - let (zkproof, cv) = prover.output_proof( - ctx, - esk.0, - dummy_note.recipient(), - dummy_note.rcm(), - dummy_note.value().inner(), - ); - - let cmu = dummy_note.cmu(); - - let mut enc_ciphertext = [0u8; 580]; - let mut out_ciphertext = [0u8; 80]; - rng.fill_bytes(&mut enc_ciphertext[..]); - rng.fill_bytes(&mut out_ciphertext[..]); - - OutputDescription { - cv, - cmu, - ephemeral_key: epk.to_bytes(), - enc_ciphertext, - out_ciphertext, - zkproof, - } - }; - - // Update progress and send a notification on the channel - progress += 1; - if let Some(sender) = progress_notifier { - // If the send fails, we should ignore the error, not crash. - sender - .send(Progress::new(progress, Some(total_progress))) - .unwrap_or(()); + SaplingOutputInfo::dummy(&self.params, &mut rng, target_height) } - - result }) - .collect(); + .collect::>(); + + // Compute the transaction binding signing key. + let bsk = { + let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum(); + let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum(); + (spends - outputs).into_bsk() + }; + + // Create the unauthorized Spend and Output descriptions. + let shielded_spends = spend_infos + .into_iter() + .map(|a| a.build::(self.anchor)) + .collect::, _>>()?; + let shielded_outputs = output_infos + .into_iter() + .map(|a| a.build::(&mut rng)) + .collect::>(); + + // Verify that bsk and bvk are consistent. + let bvk = { + let spends = shielded_spends + .iter() + .map(|spend| spend.cv()) + .sum::(); + let outputs = shielded_outputs + .iter() + .map(|output| output.cv()) + .sum::(); + (spends - outputs) + .into_bvk(i64::try_from(self.value_balance).map_err(|_| Error::InvalidAmount)?) + }; + assert_eq!( + PublicKey::from_private(&bsk, VALUE_COMMITMENT_RANDOMNESS_GENERATOR).0, + bvk.0, + ); let bundle = if shielded_spends.is_empty() && shielded_outputs.is_empty() { None } else { - Some(Bundle { - shielded_spends, - shielded_outputs, - value_balance, - authorization: Unauthorized { tx_metadata }, - }) + Some(( + Bundle { + shielded_spends, + shielded_outputs, + value_balance, + authorization: InProgress { + sigs: Unsigned { bsk }, + _proof_state: PhantomData::default(), + }, + }, + tx_metadata, + )) }; Ok(bundle) } } -impl SpendDescription { - pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription { - SpendDescription { - cv: self.cv.clone(), - anchor: self.anchor, - nullifier: self.nullifier, - rk: self.rk.clone(), - zkproof: self.zkproof, - spend_auth_sig, +/// Type alias for an in-progress bundle that has no proofs or signatures. +/// +/// This is returned by [`SaplingBuilder::build`]. +pub type UnauthorizedBundle = Bundle>; + +/// Marker trait representing bundle proofs in the process of being created. +pub trait InProgressProofs: fmt::Debug { + /// The proof type of a Sapling spend in the process of being proven. + type SpendProof: Clone + fmt::Debug; + /// The proof type of a Sapling output in the process of being proven. + type OutputProof: Clone + fmt::Debug; +} + +/// Marker trait representing bundle signatures in the process of being created. +pub trait InProgressSignatures: fmt::Debug { + /// The authorization type of a Sapling spend or output in the process of being + /// authorized. + type AuthSig: Clone + fmt::Debug; +} + +/// Marker for a bundle in the process of being built. +#[derive(Clone, Debug)] +pub struct InProgress { + sigs: S, + _proof_state: PhantomData

, +} + +impl Authorization for InProgress { + type SpendProof = P::SpendProof; + type OutputProof = P::OutputProof; + type AuthSig = S::AuthSig; +} + +/// Marker for a [`Bundle`] without proofs. +/// +/// The [`SpendDescription`]s and [`OutputDescription`]s within the bundle contain the +/// private data needed to create proofs. +#[derive(Debug)] +pub struct Unproven; + +impl InProgressProofs for Unproven { + type SpendProof = sapling::circuit::Spend; + type OutputProof = sapling::circuit::Output; +} + +/// Marker for a [`Bundle`] with proofs. +#[derive(Debug)] +pub struct Proven; + +impl InProgressProofs for Proven { + type SpendProof = GrothProofBytes; + type OutputProof = GrothProofBytes; +} + +struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore> { + spend_prover: &'a SP, + output_prover: &'a OP, + rng: R, + progress_notifier: Option<&'a Sender>, + total_progress: u32, + progress: u32, +} + +impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore> CreateProofs<'a, SP, OP, R> { + fn new( + spend_prover: &'a SP, + output_prover: &'a OP, + rng: R, + progress_notifier: Option<&'a Sender>, + total_progress: u32, + ) -> Self { + // Keep track of the total number of steps computed + Self { + spend_prover, + output_prover, + rng, + progress_notifier, + total_progress, + progress: 0u32, + } + } + + fn update_progress(&mut self) { + // Update progress and send a notification on the channel + self.progress += 1; + if let Some(sender) = self.progress_notifier { + // If the send fails, we should ignore the error, not crash. + sender + .send(Progress::new(self.progress, Some(self.total_progress))) + .unwrap_or(()); } } } -impl Bundle { - pub fn apply_signatures( - self, - prover: &Pr, - ctx: &mut Pr::SaplingProvingContext, - rng: &mut R, - sighash_bytes: &[u8; 32], - ) -> Result<(Bundle, SaplingMetadata), Error> { - let binding_sig = prover - .binding_sig(ctx, self.value_balance, sighash_bytes) - .map_err(|_| Error::BindingSig)?; +impl<'a, S: InProgressSignatures, SP: SpendProver, OP: OutputProver, R: RngCore> + MapAuth, InProgress> for CreateProofs<'a, SP, OP, R> +{ + fn map_spend_proof(&mut self, spend: sapling::circuit::Spend) -> GrothProofBytes { + let proof = self.spend_prover.create_proof(spend, &mut self.rng); + self.update_progress(); + SP::encode_proof(proof) + } - 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 }, + fn map_output_proof(&mut self, output: sapling::circuit::Output) -> GrothProofBytes { + let proof = self.output_prover.create_proof(output, &mut self.rng); + self.update_progress(); + OP::encode_proof(proof) + } + + fn map_auth_sig(&mut self, s: S::AuthSig) -> S::AuthSig { + s + } + + fn map_authorization(&mut self, a: InProgress) -> InProgress { + InProgress { + sigs: a.sigs, + _proof_state: PhantomData::default(), + } + } +} + +impl Bundle> { + /// Creates the proofs for this bundle. + pub fn create_proofs( + self, + spend_prover: &SP, + output_prover: &OP, + rng: impl RngCore, + progress_notifier: Option<&Sender>, + ) -> Bundle> { + let total_progress = self.shielded_spends.len() as u32 + self.shielded_outputs.len() as u32; + self.map_authorization(CreateProofs::new( + spend_prover, + output_prover, + rng, + progress_notifier, + total_progress, + )) + } +} + +/// Marker for an unauthorized bundle with no signatures. +pub struct Unsigned { + bsk: PrivateKey, +} + +impl fmt::Debug for Unsigned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Unsigned").finish_non_exhaustive() + } +} + +impl InProgressSignatures for Unsigned { + type AuthSig = SigningParts; +} + +/// The parts needed to sign a [`SpendDescription`]. +#[derive(Clone, Debug)] +pub struct SigningParts { + /// The spend validating key for this spend description. Used to match spend + /// authorizing keys to spend descriptions they can create signatures for. + ak: PublicKey, + /// The randomization needed to derive the actual signing key for this note. + alpha: jubjub::Scalar, +} + +/// Marker for a partially-authorized bundle, in the process of being signed. +#[derive(Debug)] +pub struct PartiallyAuthorized { + binding_signature: Signature, + sighash: [u8; 32], +} + +impl InProgressSignatures for PartiallyAuthorized { + type AuthSig = MaybeSigned; +} + +/// A heisen[`Signature`] for a particular [`SpendDescription`]. +#[derive(Clone, Debug)] +pub enum MaybeSigned { + /// The information needed to sign this [`SpendDescription`]. + SigningMetadata(SigningParts), + /// The signature for this [`SpendDescription`]. + Signature(Signature), +} + +impl MaybeSigned { + fn finalize(self) -> Result { + match self { + Self::Signature(sig) => Ok(sig), + _ => Err(Error::MissingSignatures), + } + } +} + +impl Bundle> { + /// Loads the sighash into this bundle, preparing it for signing. + /// + /// This API ensures that all signatures are created over the same sighash. + pub fn prepare( + self, + mut rng: R, + sighash: [u8; 32], + ) -> Bundle> { + self.map_authorization(( + |proof| proof, + |proof| proof, + MaybeSigned::SigningMetadata, + |auth: InProgress| InProgress { + sigs: PartiallyAuthorized { + binding_signature: auth.sigs.bsk.sign( + &sighash, + &mut rng, + VALUE_COMMITMENT_RANDOMNESS_GENERATOR, + ), + sighash, + }, + _proof_state: PhantomData::default(), + }, + )) + } +} + +impl Bundle> { + /// Applies signatures to this bundle, in order to authorize it. + /// + /// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and + /// [`Bundle::finalize`]. + pub fn apply_signatures( + self, + mut rng: R, + sighash: [u8; 32], + signing_keys: &[PrivateKey], + ) -> Result, Error> { + signing_keys + .iter() + .fold(self.prepare(&mut rng, sighash), |partial, ask| { + partial.sign(&mut rng, ask) + }) + .finalize() + } +} + +impl Bundle> { + /// Signs this bundle with the given [`PrivateKey`]. + /// + /// This will apply signatures for all notes controlled by this spending key. + pub fn sign(self, mut rng: R, ask: &PrivateKey) -> Self { + let expected_ak = PublicKey::from_private(ask, SPENDING_KEY_GENERATOR); + let sighash = self.authorization.sigs.sighash; + self.map_authorization(( + |proof| proof, + |proof| proof, + |maybe| match maybe { + MaybeSigned::SigningMetadata(parts) if parts.ak.0 == expected_ak.0 => { + MaybeSigned::Signature(spend_sig_internal(ask, parts.alpha, &sighash, &mut rng)) + } + s => s, + }, + |partial| partial, + )) + } + + /// Appends externally computed [`Signature`]s. + /// + /// Each signature will be applied to the one input for which it is valid. An error + /// will be returned if the signature is not valid for any inputs, or if it is valid + /// for more than one input. + pub fn append_signatures(self, signatures: &[Signature]) -> Result { + signatures.iter().try_fold(self, Self::append_signature) + } + + fn append_signature(self, signature: &Signature) -> Result { + let sighash = self.authorization.sigs.sighash; + let mut signature_valid_for = 0usize; + let bundle = self.map_authorization(( + |proof| proof, + |proof| proof, + |maybe| match maybe { + MaybeSigned::SigningMetadata(parts) => { + if verify_spend_sig(&parts.ak, parts.alpha, &sighash, signature) { + signature_valid_for += 1; + MaybeSigned::Signature(*signature) + } else { + // Signature isn't for this input. + MaybeSigned::SigningMetadata(parts) + } + } + s => s, + }, + |partial| partial, + )); + match signature_valid_for { + 0 => Err(Error::InvalidExternalSignature), + 1 => Ok(bundle), + _ => Err(Error::DuplicateSignature), + } + } +} + +impl Bundle> { + /// Finalizes this bundle, enabling it to be included in a transaction. + /// + /// Returns an error if any signatures are missing. + pub fn finalize(self) -> Result, Error> { + self.try_map_authorization(( + Ok, + Ok, + |maybe: MaybeSigned| maybe.finalize(), + |partial: InProgress| { + Ok(Authorized { + binding_sig: partial.sigs.binding_signature, + }) }, - self.authorization.tx_metadata, )) } } @@ -587,7 +876,8 @@ pub mod testing { TEST_NETWORK, }, sapling::{ - prover::mock::MockTxProver, + prover::mock::{MockOutputProver, MockSpendProver}, + redjubjub::PrivateKey, testing::{arb_node, arb_note}, value::testing::arb_positive_note_value, Diversifier, @@ -628,31 +918,30 @@ pub mod testing { for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) { builder.add_spend( &mut rng, - extsk.clone(), + &extsk, diversifier, note, path ).unwrap(); } - let prover = MockTxProver; - - let bundle = builder.build( - &prover, - &mut (), + let (bundle, _) = builder.build::( &mut rng, target_height.unwrap(), - None ).unwrap().unwrap(); - let (bundle, _) = bundle.apply_signatures( - &prover, - &mut (), + let bundle = bundle.create_proofs( + &MockSpendProver, + &MockOutputProver, &mut rng, - &fake_sighash_bytes, - ).unwrap(); + None, + ); - bundle + bundle.apply_signatures( + &mut rng, + fake_sighash_bytes, + &[PrivateKey(extsk.expsk.ask)], + ).unwrap() } } } diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index f44a3849d..e07a14349 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -275,7 +275,8 @@ pub struct Unauthorized; impl Authorization for Unauthorized { type TransparentAuth = transparent::builder::Unauthorized; - type SaplingAuth = sapling::builder::Unauthorized; + type SaplingAuth = + sapling::builder::InProgress; type OrchardAuth = orchard::builder::InProgress;