diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index b98342353..e79786907 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, }, @@ -695,6 +695,7 @@ mod tests { Some(Bundle { vin: vec![], vout: vec![out_a], + authorization: Authorized, }), ) .freeze() @@ -725,6 +726,7 @@ mod tests { Some(Bundle { vin: vec![in_b], vout: vec![out_b], + authorization: Authorized, }), ) .freeze() @@ -751,6 +753,7 @@ mod tests { Some(Bundle { vin: vec![in_c], vout: vec![], + authorization: Authorized, }), ) .freeze() diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 149828cab..3a152dfcd 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -41,7 +41,7 @@ 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}, @@ -333,7 +333,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { .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 { version, @@ -384,9 +384,11 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { .map_err(Error::SaplingBuild)?; #[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(( @@ -395,7 +397,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { sapling_sigs, transparent_bundle, #[cfg(feature = "zfuture")] - tze_witnesses, + tze_bundle, ) .expect("An IO error occurred applying signatures."), tx_metadata, @@ -406,7 +408,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { unauthed_tx: TransactionData, sapling_sigs: Option<(Vec, redjubjub::Signature)>, transparent_bundle: Option>, - #[cfg(feature = "zfuture")] tze_witnesses: Option>, + #[cfg(feature = "zfuture")] tze_bundle: Option>, ) -> io::Result { let signed_sapling_bundle = match (unauthed_tx.sapling_bundle, sapling_sigs) { (Some(bundle), Some((spend_sigs, binding_sig))) => { @@ -418,17 +420,6 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { } }; - #[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 authorized_tx = TransactionData { version: unauthed_tx.version, consensus_branch_id: unauthed_tx.consensus_branch_id, @@ -439,7 +430,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> { sapling_bundle: signed_sapling_bundle, orchard_bundle: None, #[cfg(feature = "zfuture")] - tze_bundle: signed_tze_bundle, + tze_bundle, }; authorized_tx.freeze() diff --git a/zcash_primitives/src/transaction/components/tze.rs b/zcash_primitives/src/transaction/components/tze.rs index 4df728775..20321ced5 100644 --- a/zcash_primitives/src/transaction/components/tze.rs +++ b/zcash_primitives/src/transaction/components/tze.rs @@ -25,13 +25,6 @@ pub trait Authorization: Debug { type Witness: Debug + Clone + PartialEq; } -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Unauthorized; - -impl Authorization for Unauthorized { - type Witness = (); -} - #[derive(Debug, Copy, Clone, PartialEq)] pub struct Authorized; @@ -41,30 +34,9 @@ impl Authorization for Authorized { #[derive(Debug, Clone, PartialEq)] pub struct Bundle { - pub vin: Vec>, + pub vin: Vec>, pub vout: Vec, -} - -impl Bundle { - pub fn apply_signatures(self, witnesses: Vec) -> Bundle { - assert!(self.vin.len() == witnesses.len()); - Bundle { - vin: self - .vin - .into_iter() - .zip(witnesses.into_iter()) - .map(|(tzein, payload)| TzeIn { - prevout: tzein.prevout, - witness: tze::Witness { - extension_id: tzein.witness.extension_id, - mode: tzein.witness.mode, - payload, - }, - }) - .collect(), - vout: self.vout, - } - } + pub authorization: A, } #[derive(Clone, Debug, PartialEq)] @@ -99,12 +71,12 @@ impl OutPoint { } #[derive(Clone, Debug, PartialEq)] -pub struct TzeIn { +pub struct TzeIn { pub prevout: OutPoint, - pub witness: tze::Witness, + pub witness: tze::Witness, } -impl TzeIn { +impl TzeIn { /// Write without witness data (for signature hashing) /// /// This is also used as the prefix for the encoded form used @@ -127,7 +99,7 @@ impl TzeIn { /// Transaction encoding and decoding functions conforming to [ZIP 222]. /// /// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions -impl TzeIn { +impl TzeIn<()> { /// Convenience constructor pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self { TzeIn { @@ -141,7 +113,7 @@ impl TzeIn { } } -impl TzeIn { +impl TzeIn<::Witness> { /// Read witness metadata & payload /// /// Used to decode the encoded form used within a serialized @@ -249,7 +221,7 @@ pub mod testing { } prop_compose! { - pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn { + pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn { TzeIn { prevout, witness } } } @@ -274,7 +246,7 @@ pub mod testing { if branch_id != BranchId::ZFuture || (vin.is_empty() && vout.is_empty()) { None } else { - Some(Bundle { vin, vout }) + Some(Bundle { vin, vout, authorization: Authorized }) } } } diff --git a/zcash_primitives/src/transaction/components/tze/builder.rs b/zcash_primitives/src/transaction/components/tze/builder.rs index b17784cea..11b7a0242 100644 --- a/zcash_primitives/src/transaction/components/tze/builder.rs +++ b/zcash_primitives/src/transaction/components/tze/builder.rs @@ -5,9 +5,12 @@ use std::fmt; use crate::{ extensions::transparent::{self as tze, ToPayload}, - transaction::components::{ - amount::Amount, - tze::{Bundle, OutPoint, TzeIn, TzeOut, Unauthorized}, + transaction::{ + self as tx, + components::{ + amount::Amount, + tze::{Authorization, Authorized, Bundle, OutPoint, TzeIn, TzeOut}, + }, }, }; @@ -28,17 +31,24 @@ impl fmt::Display for Error { } #[allow(clippy::type_complexity)] -struct TzeSigner<'a, BuildCtx> { +pub struct TzeSigner<'a, BuildCtx> { prevout: TzeOut, builder: Box Result<(u32, Vec), Error> + 'a>, } pub struct TzeBuilder<'a, BuildCtx> { signers: Vec>, - vin: Vec>, + vin: Vec>, vout: Vec, } +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Unauthorized; + +impl Authorization for Unauthorized { + type Witness = (); +} + impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> { pub fn empty() -> Self { TzeBuilder { @@ -99,43 +109,63 @@ impl<'a, BuildCtx> TzeBuilder<'a, BuildCtx> { .sum::>()? } - pub fn build(&self) -> Option> { + pub fn build(self) -> (Option>, Vec>) { if self.vin.is_empty() && self.vout.is_empty() { - None + (None, vec![]) } else { - Some(Bundle { - vin: self.vin.clone(), - vout: self.vout.clone(), - }) - } - } - - pub fn create_witnesses(self, mtx: &BuildCtx) -> Result>, Error> { - // Create TZE input witnesses - if self.vin.is_empty() && self.vout.is_empty() { - Ok(None) - } else { - // Create TZE input witnesses - let payloads = self - .signers - .into_iter() - .zip(self.vin.into_iter()) - .into_iter() - .map(|(signer, tzein)| { - // 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. - let (mode, payload) = (signer.builder)(&mtx)?; - let input_mode = tzein.witness.mode; - if mode != input_mode { - return Err(Error::WitnessModeMismatch(input_mode, mode)); - } - - Ok(tze::AuthData(payload)) - }) - .collect::, Error>>()?; - - Ok(Some(payloads)) + ( + Some(Bundle { + vin: self.vin.clone(), + vout: self.vout.clone(), + authorization: Unauthorized, + }), + self.signers, + ) } } } + +impl Bundle { + pub fn into_authorized( + self, + unauthed_tx: &tx::TransactionData, + signers: Vec>>, + ) -> Result, Error> { + // Create TZE input witnesses + let payloads = signers + .into_iter() + .zip(self.vin.iter()) + .into_iter() + .map(|(signer, tzein)| { + // 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. + let (mode, payload) = (signer.builder)(unauthed_tx)?; + let input_mode = tzein.witness.mode; + if mode != input_mode { + return Err(Error::WitnessModeMismatch(input_mode, mode)); + } + + Ok(tze::AuthData(payload)) + }) + .collect::, Error>>()?; + + Ok(Bundle { + vin: self + .vin + .into_iter() + .zip(payloads.into_iter()) + .map(|(tzein, payload)| TzeIn { + prevout: tzein.prevout, + witness: tze::Witness { + extension_id: tzein.witness.extension_id, + mode: tzein.witness.mode, + payload, + }, + }) + .collect(), + vout: self.vout, + authorization: Authorized, + }) + } +} diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 1fbe429ea..3a08a6612 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -261,7 +261,7 @@ impl Authorization for Unauthorized { type OrchardAuth = orchard::builder::Unauthorized; #[cfg(feature = "zfuture")] - type TzeAuth = tze::Unauthorized; + type TzeAuth = tze::builder::Unauthorized; } /// A Zcash transaction. @@ -795,7 +795,11 @@ impl Transaction { Ok(if vin.is_empty() && vout.is_empty() { None } else { - Some(tze::Bundle { vin, vout }) + Some(tze::Bundle { + vin, + vout, + authorization: tze::Authorized, + }) }) } diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index 498e0bb79..de8c74584 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -116,7 +116,7 @@ pub(crate) fn transparent_outputs_hash>(vout: &[T]) -> Blake2bH /// In the case that no inputs are provided, this produces a default /// hash from just the personalization string. #[cfg(feature = "zfuture")] -pub(crate) fn hash_tze_inputs(tze_inputs: &[TzeIn]) -> Blake2bHash { +pub(crate) fn hash_tze_inputs(tze_inputs: &[TzeIn]) -> Blake2bHash { let mut h = hasher(ZCASH_TZE_INPUTS_HASH_PERSONALIZATION); for tzein in tze_inputs { tzein.write_without_witness(&mut h).unwrap();