From ee79b52bfdb5e3d616aaf36edbc9507e6eb9d2cd Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 May 2020 11:52:03 +1200 Subject: [PATCH 01/14] Add transaction-builder suport for TZE-bearing transactions. --- Cargo.toml | 2 +- .../Cargo.toml | 6 +- .../src/consensus.rs | 0 .../src/consensus}/transparent.rs | 8 +- zcash_extensions/src/lib.rs | 3 + zcash_extensions/src/transparent.rs | 3 + .../src}/transparent/demo.rs | 135 +++++++++++++-- zcash_extensions_api/src/transparent.rs | 107 ------------ zcash_primitives/Cargo.toml | 1 - zcash_primitives/src/consensus.rs | 2 - zcash_primitives/src/consensus/extensions.rs | 3 - zcash_primitives/src/extensions.rs | 2 - .../src/extensions/transparent.rs | 159 +++++++++++++++++- zcash_primitives/src/lib.rs | 2 +- zcash_primitives/src/transaction/builder.rs | 136 +++++++++++++-- .../src/transaction/components.rs | 10 +- 16 files changed, 419 insertions(+), 160 deletions(-) rename {zcash_extensions_api => zcash_extensions}/Cargo.toml (53%) rename zcash_extensions_api/src/lib.rs => zcash_extensions/src/consensus.rs (100%) rename {zcash_primitives/src/consensus/extensions => zcash_extensions/src/consensus}/transparent.rs (92%) create mode 100644 zcash_extensions/src/lib.rs create mode 100644 zcash_extensions/src/transparent.rs rename {zcash_primitives/src/extensions => zcash_extensions/src}/transparent/demo.rs (81%) delete mode 100644 zcash_extensions_api/src/transparent.rs delete mode 100644 zcash_primitives/src/consensus/extensions.rs diff --git a/Cargo.toml b/Cargo.toml index 3bfd0677f2..a66aba1d7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "group", "pairing", "zcash_client_backend", - "zcash_extensions_api", + "zcash_extensions", "zcash_history", "zcash_primitives", "zcash_proofs", diff --git a/zcash_extensions_api/Cargo.toml b/zcash_extensions/Cargo.toml similarity index 53% rename from zcash_extensions_api/Cargo.toml rename to zcash_extensions/Cargo.toml index 2deb5dadf4..5cb922ba14 100644 --- a/zcash_extensions_api/Cargo.toml +++ b/zcash_extensions/Cargo.toml @@ -1,11 +1,13 @@ [package] -name = "zcash_extensions_api" +name = "zcash_extensions" description = "TBD" version = "0.0.0" -authors = ["Jack Grigg "] +authors = ["Jack Grigg ", "Kris Nuttycombe "] homepage = "https://github.com/zcash/librustzcash" repository = "https://github.com/zcash/librustzcash" license = "MIT OR Apache-2.0" edition = "2018" [dependencies] +blake2b_simd = "0.5" +zcash_primitives = { version = "0.2", path = "../zcash_primitives" } diff --git a/zcash_extensions_api/src/lib.rs b/zcash_extensions/src/consensus.rs similarity index 100% rename from zcash_extensions_api/src/lib.rs rename to zcash_extensions/src/consensus.rs diff --git a/zcash_primitives/src/consensus/extensions/transparent.rs b/zcash_extensions/src/consensus/transparent.rs similarity index 92% rename from zcash_primitives/src/consensus/extensions/transparent.rs rename to zcash_extensions/src/consensus/transparent.rs index 2c3e21745d..c0f1ab20af 100644 --- a/zcash_primitives/src/consensus/extensions/transparent.rs +++ b/zcash_extensions/src/consensus/transparent.rs @@ -1,11 +1,11 @@ //! Consensus logic for Transparent Zcash Extensions. use std::convert::TryFrom; -use zcash_extensions_api::transparent::{Error, Extension, Precondition, Witness}; +use zcash_primitives::transaction::components::TzeOut; +use zcash_primitives::transaction::Transaction; +use zcash_primitives::extensions::transparent::{Error, Extension, Precondition, Witness}; -use crate::extensions::transparent::demo; -use crate::transaction::components::TzeOut; -use crate::transaction::Transaction; +use crate::transparent::demo; /// The set of programs that have assigned type IDs within the Zcash consensus rules. #[derive(Debug, Clone, Copy)] diff --git a/zcash_extensions/src/lib.rs b/zcash_extensions/src/lib.rs new file mode 100644 index 0000000000..19f72ffcaf --- /dev/null +++ b/zcash_extensions/src/lib.rs @@ -0,0 +1,3 @@ +pub mod transparent; +pub mod consensus; + diff --git a/zcash_extensions/src/transparent.rs b/zcash_extensions/src/transparent.rs new file mode 100644 index 0000000000..48aca679f4 --- /dev/null +++ b/zcash_extensions/src/transparent.rs @@ -0,0 +1,3 @@ +//! Zcash transparent extensions. + +pub mod demo; diff --git a/zcash_primitives/src/extensions/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs similarity index 81% rename from zcash_primitives/src/extensions/transparent/demo.rs rename to zcash_extensions/src/transparent/demo.rs index cadd020dfd..168c62a2d8 100644 --- a/zcash_primitives/src/extensions/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -21,9 +21,9 @@ use blake2b_simd::Params; use std::convert::TryFrom; use std::fmt; -use zcash_extensions_api::transparent::{Extension, FromPayload, ToPayload}; -use crate::transaction::components::TzeOut; +use zcash_primitives::transaction::components::{OutPoint, TzeOut, amount::Amount}; +use zcash_primitives::extensions::transparent::{Extension, FromPayload, ToPayload, ExtensionTxBuilder}; mod open { pub const MODE: usize = 0; @@ -273,6 +273,94 @@ impl Extension for Program { } } +fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u8; 32]) { + let hash_2 = { + let mut hash = [0; 32]; + hash.copy_from_slice(Params::new().hash_length(32).hash(preimage_2).as_bytes()); + hash + }; + + let hash_1 = { + let mut hash = [0; 32]; + hash.copy_from_slice( + Params::new() + .hash_length(32) + .to_state() + .update(preimage_1) + .update(&hash_2) + .finalize() + .as_bytes(), + ); + hash + }; + + (hash_1, hash_2) +} + +pub struct DemoBuilder<'a, B> { + txn_builder: &'a mut B, + extension_id: usize, +} + +impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { + pub fn demo_open( + &mut self, + value: Amount, + preimage_1: [u8; 32], + preimage_2: [u8; 32] + ) -> Result<(), B::BuildError> { + let (hash_1, _) = builder_hashes(&preimage_1, &preimage_2); + + // Call through to the generic builder. + self.txn_builder.add_tze_output( + self.extension_id, + value, + &Precondition::open(hash_1), + ) + } + + pub fn demo_transfer_to_close( + &mut self, + prevout: (OutPoint, TzeOut), + transfer_amount: Amount, + preimage_1: [u8; 32], + preimage_2: [u8; 32] + ) -> Result<(), B::BuildError> { + let (_, hash_2) = builder_hashes(&preimage_1, &preimage_2); + + // should we eagerly validate the relationship between prevout.1 and preimage_1? + self.txn_builder.add_tze_input( + self.extension_id, + prevout, + move |_| Ok(Witness::open(preimage_1)) + )?; + + self.txn_builder.add_tze_output( + self.extension_id, + transfer_amount, // can this be > prevout.1.value? + &Precondition::close(hash_2) + ) + } + + pub fn demo_close( + &mut self, + prevout: (OutPoint, TzeOut), + preimage: [u8; 32] + ) -> Result<(), B::BuildError> { + let hash_2 = { + let mut hash = [0; 32]; + hash.copy_from_slice(Params::new().hash_length(32).hash(&preimage).as_bytes()); + hash + }; + + self.txn_builder.add_tze_input( + self.extension_id, + prevout, + move |_| Ok(Witness::close(hash_2)) + ) + } +} + #[cfg(test)] mod tests { use blake2b_simd::Params; @@ -382,31 +470,52 @@ mod tests { hash }; - let mut mtx_a = TransactionData::nu4(); - mtx_a.tze_outputs.push(TzeOut { + // + // Opening transaction + // + + let out_a = TzeOut { value: Amount::from_u64(1).unwrap(), precondition: tze::Precondition::from(0, &Precondition::open(hash_1)), - }); + }; + + println!("{:x?}", precondition.payload); + + let mut mtx_a = TransactionData::nu4(); + mtx_a.tze_outputs.push(out_a); let tx_a = mtx_a.freeze().unwrap(); - let mut mtx_b = TransactionData::nu4(); - mtx_b.tze_inputs.push(TzeIn { + // + // Transfer + // + + let in_b = TzeIn { prevout: OutPoint::new(tx_a.txid().0, 0), witness: tze::Witness::from(0, &Witness::open(preimage_1)), - }); - mtx_b.tze_outputs.push(TzeOut { + }; + let out_b = TzeOut { value: Amount::from_u64(1).unwrap(), precondition: tze::Precondition::from(0, &Precondition::close(hash_2)), - }); + }; + let mut mtx_b = TransactionData::nu4(); + mtx_b.tze_inputs.push(in_b); + mtx_b.tze_outputs.push(out_b); let tx_b = mtx_b.freeze().unwrap(); - let mut mtx_c = TransactionData::nu4(); - mtx_c.tze_inputs.push(TzeIn { + // + // Closing transaction + // + + let in_c = TzeIn { prevout: OutPoint::new(tx_b.txid().0, 0), witness: tze::Witness::from(0, &Witness::close(preimage_2)), - }); + }; + + let mut mtx_c = TransactionData::nu4(); + mtx_c.tze_inputs.push(in_c); let tx_c = mtx_c.freeze().unwrap(); + // Verify tx_b { let ctx = Ctx { tx: &tx_b }; diff --git a/zcash_extensions_api/src/transparent.rs b/zcash_extensions_api/src/transparent.rs deleted file mode 100644 index 028075f192..0000000000 --- a/zcash_extensions_api/src/transparent.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Core traits and structs for Transparent Zcash Extensions. - -use std::fmt; - -pub trait FromPayload: Sized { - /// Parses an extension type from a mode and payload. - fn from_payload(mode: usize, payload: &[u8]) -> Result; -} - -pub trait ToPayload { - /// Returns a serialized payload and its corresponding mode. - fn to_payload(&self) -> (usize, Vec); -} - -/// A condition that can be used to encumber transparent funds. -#[derive(Debug)] -pub struct Precondition { - pub extension_id: usize, - pub mode: usize, - pub payload: Vec, -} - -impl Precondition { - pub fn from(extension_id: usize, value: &P) -> Precondition { - let (mode, payload) = value.to_payload(); - Precondition { - extension_id, - mode, - payload, - } - } -} - -/// Data that satisfies the precondition for prior encumbered funds, enabling them to be -/// spent. -#[derive(Debug)] -pub struct Witness { - pub extension_id: usize, - pub mode: usize, - pub payload: Vec, -} - -impl Witness { - pub fn from(extension_id: usize, value: &P) -> Witness { - let (mode, payload) = value.to_payload(); - Witness { - extension_id, - mode, - payload, - } - } -} - -#[derive(Debug, PartialEq)] -pub enum Error { - InvalidForEpoch(u32, usize), - InvalidExtensionId(usize), - ProgramError(E), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::InvalidForEpoch(cid, ptype) => write!( - f, - "Program type {} is invalid for consensus branch id {}", - ptype, cid - ), - - Error::InvalidExtensionId(extension_id) => { - write!(f, "Unrecognized program type id {}", extension_id) - } - - Error::ProgramError(err) => write!(f, "Program error: {}", err), - } - } -} - -pub trait Extension { - type P; - type W; - type Error; - - fn verify_inner( - &self, - precondition: &Self::P, - witness: &Self::W, - context: &C, - ) -> Result<(), Self::Error>; - - fn verify( - &self, - precondition: &Precondition, - witness: &Witness, - context: &C, - ) -> Result<(), Self::Error> - where - Self::P: FromPayload, - Self::W: FromPayload, - { - self.verify_inner( - &Self::P::from_payload(precondition.mode, &precondition.payload)?, - &Self::W::from_payload(witness.mode, &witness.payload)?, - &context, - ) - } -} diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 11a00c8b80..b40744db01 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -29,7 +29,6 @@ ripemd160 = { version = "0.8", optional = true } secp256k1 = { version = "=0.15.0", optional = true } sha2 = "0.8" subtle = "2.2.1" -zcash_extensions_api = { version = "0.0", path = "../zcash_extensions_api" } [dev-dependencies] criterion = "0.3" diff --git a/zcash_primitives/src/consensus.rs b/zcash_primitives/src/consensus.rs index 42657c35c9..67d328e589 100644 --- a/zcash_primitives/src/consensus.rs +++ b/zcash_primitives/src/consensus.rs @@ -3,8 +3,6 @@ use std::convert::TryFrom; use std::fmt; -pub mod extensions; - /// Zcash consensus parameters. pub trait Parameters { fn activation_height(nu: NetworkUpgrade) -> Option; diff --git a/zcash_primitives/src/consensus/extensions.rs b/zcash_primitives/src/consensus/extensions.rs deleted file mode 100644 index 6210d3fcdd..0000000000 --- a/zcash_primitives/src/consensus/extensions.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Consensus logic for Zcash extensions. - -pub mod transparent; diff --git a/zcash_primitives/src/extensions.rs b/zcash_primitives/src/extensions.rs index 1aa5804f34..7d540a8bac 100644 --- a/zcash_primitives/src/extensions.rs +++ b/zcash_primitives/src/extensions.rs @@ -1,3 +1 @@ -//! Zcash extensions. - pub mod transparent; diff --git a/zcash_primitives/src/extensions/transparent.rs b/zcash_primitives/src/extensions/transparent.rs index 48aca679f4..bf83dc26f3 100644 --- a/zcash_primitives/src/extensions/transparent.rs +++ b/zcash_primitives/src/extensions/transparent.rs @@ -1,3 +1,158 @@ -//! Zcash transparent extensions. +//! Core traits and structs for Transparent Zcash Extensions. + +use std::fmt; +use crate::transaction::components::{Amount, OutPoint, TzeOut}; + +pub trait FromPayload: Sized { + /// Parses an extension type from a mode and payload. + fn from_payload(mode: usize, payload: &[u8]) -> Result; +} + +pub trait ToPayload { + /// Returns a serialized payload and its corresponding mode. + fn to_payload(&self) -> (usize, Vec); +} + +/// A condition that can be used to encumber transparent funds. +#[derive(Clone, Debug)] +pub struct Precondition { + pub extension_id: usize, + pub mode: usize, + pub payload: Vec, +} + +impl Precondition { + pub fn from(extension_id: usize, value: &P) -> Precondition { + let (mode, payload) = value.to_payload(); + Precondition { + extension_id, + mode, + payload, + } + } +} + +/// Data that satisfies the precondition for prior encumbered funds, enabling them to be +/// spent. +#[derive(Clone, Debug)] +pub struct Witness { + pub extension_id: usize, + pub mode: usize, + pub payload: Vec, +} + +impl Witness { + pub fn from(extension_id: usize, value: &P) -> Witness { + let (mode, payload) = value.to_payload(); + Witness { + extension_id, + mode, + payload, + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Error { + InvalidForEpoch(u32, usize), + InvalidExtensionId(usize), + ProgramError(E), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidForEpoch(cid, ptype) => write!( + f, + "Program type {} is invalid for consensus branch id {}", + ptype, cid + ), + + Error::InvalidExtensionId(extension_id) => { + write!(f, "Unrecognized program type id {}", extension_id) + } + + Error::ProgramError(err) => write!(f, "Program error: {}", err), + } + } +} + +pub trait Extension { + type P; + type W; + type Error; + + fn verify_inner( + &self, + precondition: &Self::P, + witness: &Self::W, + context: &C, + ) -> Result<(), Self::Error>; + + fn verify( + &self, + precondition: &Precondition, + witness: &Witness, + context: &C, + ) -> Result<(), Self::Error> + where + Self::P: FromPayload, + Self::W: FromPayload, + { + self.verify_inner( + &Self::P::from_payload(precondition.mode, &precondition.payload)?, + &Self::W::from_payload(witness.mode, &witness.payload)?, + &context, + ) + } +} + +// pub trait WitnessBuilder { +// type Error; +// type Witness: ToPayload; +// +// fn build_witness(ctx: BuildCtx) -> Result; +// } + +// This extension trait is satisfied by the transaction::builder::Builder type. It provides a +// minimal contract for interacting with the transaction builder, that extension library authors +// can use to add extension-specific builder traits that may be used to interact with the +// transaction builder. This may make it simpler for projects that include transaction-builder +// functionality to integrate with third-party extensions without those extensions being coupled to +// a particular transaction or builder representation. +pub trait ExtensionTxBuilder<'a> { + type BuildCtx; + type BuildError; + + fn add_tze_input( + &mut self, + extension_id: usize, + prevout: (OutPoint, TzeOut), + witness_builder: WBuilder + ) -> Result<(), Self::BuildError> + where WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result); + //where WBuilder: WitnessBuilder; + + fn add_tze_output( + &mut self, + extension_id: usize, + value: Amount, + guarded_by: &P, + ) -> Result<(), Self::BuildError>; +} + +pub trait Epoch { + type VerifyError; + + // Implementation of this method should check that the provided witness + // satisfies the specified precondition, given the context. This verification + // becomes part of the consensus rules. + fn verify( + &self, + precondition: &Precondition, + witness: &Witness, + ctx: &VerifyCtx + ) -> Result<(), Error>; +} + -pub mod demo; diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index d45c546e10..16b961c296 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -9,8 +9,8 @@ use lazy_static::lazy_static; pub mod block; -pub mod consensus; pub mod constants; +pub mod consensus; pub mod extensions; pub mod group_hash; pub mod jubjub; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 9acb996d41..cd07c8d9e3 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -1,9 +1,11 @@ //! Structs for building transactions. +use std::boxed::Box; use crate::zip32::ExtendedSpendingKey; use crate::{ jubjub::fs::Fs, primitives::{Diversifier, Note, PaymentAddress}, + extensions::transparent::ExtensionTxBuilder, }; use ff::Field; use pairing::bls12_381::{Bls12, Fr}; @@ -12,6 +14,7 @@ use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore}; use crate::{ consensus, keys::OutgoingViewingKey, + extensions::transparent::{self as tze, ToPayload}, legacy::TransparentAddress, merkle_tree::MerklePath, note_encryption::{generate_esk, Memo, SaplingNoteEncryption}, @@ -19,7 +22,10 @@ use crate::{ redjubjub::PrivateKey, sapling::{spend_sig, Node}, transaction::{ - components::{amount::DEFAULT_FEE, Amount, OutputDescription, SpendDescription, TxOut}, + components::{ + amount::DEFAULT_FEE, amount::Amount, + OutputDescription, SpendDescription, TxOut, TzeIn, TzeOut, OutPoint + }, signature_hash_data, Transaction, TransactionData, SIGHASH_ALL, }, JUBJUB, @@ -167,15 +173,16 @@ impl TransparentInputs { #[cfg(feature = "transparent-inputs")] fn push( &mut self, - mtx: &mut TransactionData, sk: secp256k1::SecretKey, - utxo: OutPoint, coin: TxOut, ) -> Result<(), Error> { if coin.value.is_negative() { return Err(Error::InvalidAmount); } + // ensure that the ripemd160 digest of the public key associated with the + // provided secret key matches that of the address to which the provided + // output may be spent let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk).serialize(); match coin.script_pubkey.address() { Some(TransparentAddress::PublicKey(hash)) => { @@ -189,7 +196,6 @@ impl TransparentInputs { _ => return Err(Error::InvalidAddress), } - mtx.vin.push(TxIn::new(utxo)); self.inputs.push(TransparentInputInfo { sk, pubkey, coin }); Ok(()) @@ -223,6 +229,7 @@ impl TransparentInputs { consensus_branch_id, SIGHASH_ALL, Some((i, &info.coin.script_pubkey, info.coin.value)), + // tze equivalent is ??? )); let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes"); @@ -241,6 +248,38 @@ impl TransparentInputs { fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {} } + +struct TzeInputInfo<'a, BuildCtx> { + prevout: TzeOut, + builder: Box Result + 'a>, +} + +struct TzeInputs<'a, BuildCtx> { + builders: Vec>, +} + +impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> { + fn push( + &mut self, + extension_id: usize, + prevout: (OutPoint, TzeOut), + builder: WBuilder) + where WBuilder: 'a + FnOnce(&BuildCtx) -> Result { + let (outpoint, tzeout) = prevout; + self.builders.push(TzeInputInfo { + prevout: tzeout, + builder: Box::new( + move |ctx| { + let (mode, payload) = builder(&ctx).map(|x| x.to_payload())?; + Ok(TzeIn { + prevout: outpoint, + witness: tze::Witness { extension_id, mode, payload } + }) + }) + }); + } +} + /// Metadata about a transaction created by a [`Builder`]. #[derive(Debug, PartialEq)] pub struct TransactionMetadata { @@ -280,7 +319,7 @@ impl TransactionMetadata { } /// Generates a [`Transaction`] from its inputs and outputs. -pub struct Builder { +pub struct Builder<'a, R: RngCore + CryptoRng> { rng: R, mtx: TransactionData, fee: Amount, @@ -288,10 +327,11 @@ pub struct Builder { spends: Vec, outputs: Vec, transparent_inputs: TransparentInputs, + tze_inputs: TzeInputs<'a, TransactionData>, change_address: Option<(OutgoingViewingKey, PaymentAddress)>, } -impl Builder { +impl Builder<'_, OsRng> { /// Creates a new `Builder` targeted for inclusion in the block with the given height, /// using default values for general transaction fields and the default OS random. /// @@ -306,7 +346,7 @@ impl Builder { } } -impl Builder { +impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { /// Creates a new `Builder` targeted for inclusion in the block with the given height /// and randomness source, using default values for general transaction fields. /// @@ -316,7 +356,7 @@ impl Builder { /// expiry delta (20 blocks). /// /// The fee will be set to the default fee (0.0001 ZEC). - pub fn new_with_rng(height: u32, rng: R) -> Builder { + pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, R> { let mut mtx = TransactionData::new(); mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA; @@ -328,6 +368,7 @@ impl Builder { spends: vec![], outputs: vec![], transparent_inputs: TransparentInputs::default(), + tze_inputs: TzeInputs { builders: vec![] }, change_address: None, } } @@ -394,7 +435,8 @@ impl Builder { utxo: OutPoint, coin: TxOut, ) -> Result<(), Error> { - self.transparent_inputs.push(&mut self.mtx, sk, utxo, coin) + self.mtx.vin.push(TxIn::new(utxo)); + self.transparent_inputs.push(sk, coin) } /// Adds a transparent address to send funds to. @@ -436,7 +478,8 @@ impl Builder { mut self, consensus_branch_id: consensus::BranchId, prover: &impl TxProver, - ) -> Result<(Transaction, TransactionMetadata), Error> { + // epoch: &Epoch + ) -> Result<(Transaction, TransactionMetadata), Error> { let mut tx_metadata = TransactionMetadata::new(); // @@ -444,13 +487,14 @@ impl Builder { // // Valid change - let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum() - - self - .mtx - .vout - .iter() - .map(|output| output.value) - .sum::(); + let change = + self.mtx.value_balance + - self.fee + + self.transparent_inputs.value_sum() + - self.mtx.vout.iter().map(|vo| vo.value).sum::() + + self.tze_inputs.builders.iter().map(|ein| ein.prevout.value).sum::() + - self.mtx.tze_outputs.iter().map(|tzo| tzo.value).sum::(); + if change.is_negative() { return Err(Error::ChangeIsNegative(change)); } @@ -623,7 +667,7 @@ impl Builder { } // - // Signatures + // Signatures -- all effects must have been applied. // let mut sighash = [0u8; 32]; @@ -650,6 +694,19 @@ impl Builder { .map_err(|()| Error::BindingSig)?, ); + // // Create TZE input witnesses + for tze_in in self.tze_inputs.builders { + // Need to enable witness to commit to the amount. + // - So hardware wallets "know" the amount without having to be sent all the + // prior TZE outputs to which this witness gives evidence. + // + // The witness is expected to commit to the precommitment internally? + // (Or make it part of the sighash?) + // - TODO: Check whether transparent inputs committing to script_pubkey was + // only so that hardware wallets "knew" what address was being spent from. + self.mtx.tze_inputs.push((tze_in.builder)(&self.mtx)?); + } + // Transparent signatures self.transparent_inputs .apply_signatures(&mut self.mtx, consensus_branch_id); @@ -661,6 +718,49 @@ impl Builder { } } + + +impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { + type BuildCtx = TransactionData; + type BuildError = Error; + + fn add_tze_input( + &mut self, + extension_id: usize, + prevout: (OutPoint, TzeOut), + witness_builder: WBuilder + ) -> Result<(), Self::BuildError> + where WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result) { + // where WBuilder: WitnessBuilder { + self.tze_inputs.push(extension_id, prevout, witness_builder); + Ok(()) + } + + fn add_tze_output( + &mut self, + extension_id: usize, + value: Amount, + guarded_by: &P + ) -> Result<(), Self::BuildError> { + if value.is_negative() { + return Err(Error::InvalidAmount); + } + + let (mode, payload) = guarded_by.to_payload(); + self.mtx.tze_outputs.push(TzeOut { + value, + precondition: tze::Precondition { + extension_id, + mode, + payload, + }, + }); + + Ok(()) + } +} + + #[cfg(test)] mod tests { use ff::{Field, PrimeField}; diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index cfc9aa2285..ff8ad53918 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -5,8 +5,8 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{PrimeField, PrimeFieldRepr}; use pairing::bls12_381::{Bls12, Fr, FrRepr}; use std::io::{self, Read, Write}; -use zcash_extensions_api::transparent as tze; +use crate::extensions::transparent as tze; use crate::legacy::Script; use crate::redjubjub::{PublicKey, Signature}; use crate::serialize::{CompactSize, Vector}; @@ -111,7 +111,7 @@ impl TxOut { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct TzeIn { pub prevout: OutPoint, pub witness: tze::Witness, @@ -144,7 +144,7 @@ impl TzeIn { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct TzeOut { pub value: Amount, pub precondition: tze::Precondition, @@ -178,7 +178,9 @@ impl TzeOut { CompactSize::write(&mut writer, self.precondition.extension_id)?; CompactSize::write(&mut writer, self.precondition.mode)?; - Vector::write(&mut writer, &self.precondition.payload, |w, b| w.write_u8(*b)) + Vector::write(&mut writer, &self.precondition.payload, |w, b| { + w.write_u8(*b) + }) } } From 2f643cb12eccda2b37fb2802413c1483757c6aab Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 18 May 2020 20:27:19 -0600 Subject: [PATCH 02/14] Make error a type member of FromPayload trait rather than a type variable. --- zcash_extensions/src/transparent/demo.rs | 14 +++++++++----- zcash_primitives/src/extensions/transparent.rs | 16 ++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 168c62a2d8..e63e808c30 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -22,8 +22,8 @@ use blake2b_simd::Params; use std::convert::TryFrom; use std::fmt; -use zcash_primitives::transaction::components::{OutPoint, TzeOut, amount::Amount}; use zcash_primitives::extensions::transparent::{Extension, FromPayload, ToPayload, ExtensionTxBuilder}; +use zcash_primitives::transaction::components::{OutPoint, TzeOut, amount::Amount}; mod open { pub const MODE: usize = 0; @@ -98,8 +98,10 @@ impl TryFrom<(usize, Precondition)> for Precondition { } } -impl FromPayload for Precondition { - fn from_payload(mode: usize, payload: &[u8]) -> Result { +impl FromPayload for Precondition { + type Error = Error; + + fn from_payload(mode: usize, payload: &[u8]) -> Result { match mode { open::MODE => { if payload.len() == 32 { @@ -161,8 +163,10 @@ impl TryFrom<(usize, Witness)> for Witness { } } -impl FromPayload for Witness { - fn from_payload(mode: usize, payload: &[u8]) -> Result { +impl FromPayload for Witness { + type Error = Error; + + fn from_payload(mode: usize, payload: &[u8]) -> Result { match mode { open::MODE => { if payload.len() == 32 { diff --git a/zcash_primitives/src/extensions/transparent.rs b/zcash_primitives/src/extensions/transparent.rs index bf83dc26f3..21b2d1d205 100644 --- a/zcash_primitives/src/extensions/transparent.rs +++ b/zcash_primitives/src/extensions/transparent.rs @@ -3,9 +3,11 @@ use std::fmt; use crate::transaction::components::{Amount, OutPoint, TzeOut}; -pub trait FromPayload: Sized { +pub trait FromPayload: Sized { + type Error; + /// Parses an extension type from a mode and payload. - fn from_payload(mode: usize, payload: &[u8]) -> Result; + fn from_payload(mode: usize, payload: &[u8]) -> Result; } pub trait ToPayload { @@ -30,6 +32,10 @@ impl Precondition { payload, } } + + pub fn try_to(&self) -> Result { + P::from_payload(self.mode, &self.payload) + } } /// Data that satisfies the precondition for prior encumbered funds, enabling them to be @@ -96,8 +102,8 @@ pub trait Extension { context: &C, ) -> Result<(), Self::Error> where - Self::P: FromPayload, - Self::W: FromPayload, + Self::P: FromPayload, + Self::W: FromPayload, { self.verify_inner( &Self::P::from_payload(precondition.mode, &precondition.payload)?, @@ -154,5 +160,3 @@ pub trait Epoch { ctx: &VerifyCtx ) -> Result<(), Error>; } - - From 9e337d08893090384b489f40fed9b03ce85b01d6 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 19 May 2020 11:08:22 -0600 Subject: [PATCH 03/14] Fix demo test compilation errors. --- zcash_extensions/src/transparent/demo.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index e63e808c30..79c490cd57 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -368,12 +368,14 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { #[cfg(test)] mod tests { use blake2b_simd::Params; - use zcash_extensions_api::transparent::{self as tze, Extension, FromPayload, ToPayload}; use super::{close, open, Context, Precondition, Program, Witness}; - use crate::transaction::{ - components::{Amount, OutPoint, TzeIn, TzeOut}, - Transaction, TransactionData, + use zcash_primitives::{ + extensions::transparent::{self as tze, Extension, FromPayload, ToPayload}, + transaction::{ + components::{Amount, OutPoint, TzeIn, TzeOut}, + Transaction, TransactionData, + }, }; #[test] @@ -483,7 +485,7 @@ mod tests { precondition: tze::Precondition::from(0, &Precondition::open(hash_1)), }; - println!("{:x?}", precondition.payload); + // println!("{:x?}", precondition.payload); let mut mtx_a = TransactionData::nu4(); mtx_a.tze_outputs.push(out_a); From 17ce3eac81b1f1eaac871df4c828feb3d15c4543 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 19 May 2020 13:22:44 -0600 Subject: [PATCH 04/14] rustfmt --- zcash_extensions/src/consensus/transparent.rs | 2 +- zcash_extensions/src/lib.rs | 3 +- zcash_extensions/src/transparent/demo.rs | 64 +++++++------- .../src/extensions/transparent.rs | 15 ++-- zcash_primitives/src/lib.rs | 2 +- zcash_primitives/src/transaction/builder.rs | 88 ++++++++++--------- 6 files changed, 88 insertions(+), 86 deletions(-) diff --git a/zcash_extensions/src/consensus/transparent.rs b/zcash_extensions/src/consensus/transparent.rs index c0f1ab20af..07769f368d 100644 --- a/zcash_extensions/src/consensus/transparent.rs +++ b/zcash_extensions/src/consensus/transparent.rs @@ -1,9 +1,9 @@ //! Consensus logic for Transparent Zcash Extensions. use std::convert::TryFrom; +use zcash_primitives::extensions::transparent::{Error, Extension, Precondition, Witness}; use zcash_primitives::transaction::components::TzeOut; use zcash_primitives::transaction::Transaction; -use zcash_primitives::extensions::transparent::{Error, Extension, Precondition, Witness}; use crate::transparent::demo; diff --git a/zcash_extensions/src/lib.rs b/zcash_extensions/src/lib.rs index 19f72ffcaf..3b6ce6424b 100644 --- a/zcash_extensions/src/lib.rs +++ b/zcash_extensions/src/lib.rs @@ -1,3 +1,2 @@ -pub mod transparent; pub mod consensus; - +pub mod transparent; diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 79c490cd57..537f3f71c3 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -22,8 +22,10 @@ use blake2b_simd::Params; use std::convert::TryFrom; use std::fmt; -use zcash_primitives::extensions::transparent::{Extension, FromPayload, ToPayload, ExtensionTxBuilder}; -use zcash_primitives::transaction::components::{OutPoint, TzeOut, amount::Amount}; +use zcash_primitives::extensions::transparent::{ + Extension, ExtensionTxBuilder, FromPayload, ToPayload, +}; +use zcash_primitives::transaction::components::{amount::Amount, OutPoint, TzeOut}; mod open { pub const MODE: usize = 0; @@ -297,59 +299,55 @@ fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u ); hash }; - + (hash_1, hash_2) } -pub struct DemoBuilder<'a, B> { +pub struct DemoBuilder<'a, B> { txn_builder: &'a mut B, extension_id: usize, } impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { pub fn demo_open( - &mut self, - value: Amount, - preimage_1: [u8; 32], - preimage_2: [u8; 32] + &mut self, + value: Amount, + preimage_1: [u8; 32], + preimage_2: [u8; 32], ) -> Result<(), B::BuildError> { let (hash_1, _) = builder_hashes(&preimage_1, &preimage_2); // Call through to the generic builder. - self.txn_builder.add_tze_output( - self.extension_id, - value, - &Precondition::open(hash_1), - ) + self.txn_builder + .add_tze_output(self.extension_id, value, &Precondition::open(hash_1)) } pub fn demo_transfer_to_close( - &mut self, + &mut self, prevout: (OutPoint, TzeOut), transfer_amount: Amount, - preimage_1: [u8; 32], - preimage_2: [u8; 32] + preimage_1: [u8; 32], + preimage_2: [u8; 32], ) -> Result<(), B::BuildError> { let (_, hash_2) = builder_hashes(&preimage_1, &preimage_2); // should we eagerly validate the relationship between prevout.1 and preimage_1? - self.txn_builder.add_tze_input( - self.extension_id, - prevout, - move |_| Ok(Witness::open(preimage_1)) - )?; + self.txn_builder + .add_tze_input(self.extension_id, prevout, move |_| { + Ok(Witness::open(preimage_1)) + })?; self.txn_builder.add_tze_output( - self.extension_id, + self.extension_id, transfer_amount, // can this be > prevout.1.value? - &Precondition::close(hash_2) + &Precondition::close(hash_2), ) } pub fn demo_close( - &mut self, - prevout: (OutPoint, TzeOut), - preimage: [u8; 32] + &mut self, + prevout: (OutPoint, TzeOut), + preimage: [u8; 32], ) -> Result<(), B::BuildError> { let hash_2 = { let mut hash = [0; 32]; @@ -357,11 +355,10 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { hash }; - self.txn_builder.add_tze_input( - self.extension_id, - prevout, - move |_| Ok(Witness::close(hash_2)) - ) + self.txn_builder + .add_tze_input(self.extension_id, prevout, move |_| { + Ok(Witness::close(hash_2)) + }) } } @@ -491,7 +488,7 @@ mod tests { mtx_a.tze_outputs.push(out_a); let tx_a = mtx_a.freeze().unwrap(); - // + // // Transfer // @@ -508,7 +505,7 @@ mod tests { mtx_b.tze_outputs.push(out_b); let tx_b = mtx_b.freeze().unwrap(); - // + // // Closing transaction // @@ -521,7 +518,6 @@ mod tests { mtx_c.tze_inputs.push(in_c); let tx_c = mtx_c.freeze().unwrap(); - // Verify tx_b { let ctx = Ctx { tx: &tx_b }; diff --git a/zcash_primitives/src/extensions/transparent.rs b/zcash_primitives/src/extensions/transparent.rs index 21b2d1d205..ee4b75d305 100644 --- a/zcash_primitives/src/extensions/transparent.rs +++ b/zcash_primitives/src/extensions/transparent.rs @@ -1,7 +1,7 @@ //! Core traits and structs for Transparent Zcash Extensions. -use std::fmt; use crate::transaction::components::{Amount, OutPoint, TzeOut}; +use std::fmt; pub trait FromPayload: Sized { type Error; @@ -90,7 +90,7 @@ pub trait Extension { fn verify_inner( &self, - precondition: &Self::P, + precondition: &Self::P, witness: &Self::W, context: &C, ) -> Result<(), Self::Error>; @@ -116,7 +116,7 @@ pub trait Extension { // pub trait WitnessBuilder { // type Error; // type Witness: ToPayload; -// +// // fn build_witness(ctx: BuildCtx) -> Result; // } @@ -131,12 +131,13 @@ pub trait ExtensionTxBuilder<'a> { type BuildError; fn add_tze_input( - &mut self, + &mut self, extension_id: usize, prevout: (OutPoint, TzeOut), - witness_builder: WBuilder + witness_builder: WBuilder, ) -> Result<(), Self::BuildError> - where WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result); + where + WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result); //where WBuilder: WitnessBuilder; fn add_tze_output( @@ -157,6 +158,6 @@ pub trait Epoch { &self, precondition: &Precondition, witness: &Witness, - ctx: &VerifyCtx + ctx: &VerifyCtx, ) -> Result<(), Error>; } diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index 16b961c296..d45c546e10 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -9,8 +9,8 @@ use lazy_static::lazy_static; pub mod block; -pub mod constants; pub mod consensus; +pub mod constants; pub mod extensions; pub mod group_hash; pub mod jubjub; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index cd07c8d9e3..33f54dd9b3 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -1,20 +1,20 @@ //! Structs for building transactions. -use std::boxed::Box; use crate::zip32::ExtendedSpendingKey; use crate::{ + extensions::transparent::ExtensionTxBuilder, jubjub::fs::Fs, primitives::{Diversifier, Note, PaymentAddress}, - extensions::transparent::ExtensionTxBuilder, }; use ff::Field; use pairing::bls12_381::{Bls12, Fr}; use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore}; +use std::boxed::Box; use crate::{ consensus, - keys::OutgoingViewingKey, extensions::transparent::{self as tze, ToPayload}, + keys::OutgoingViewingKey, legacy::TransparentAddress, merkle_tree::MerklePath, note_encryption::{generate_esk, Memo, SaplingNoteEncryption}, @@ -23,8 +23,8 @@ use crate::{ sapling::{spend_sig, Node}, transaction::{ components::{ - amount::DEFAULT_FEE, amount::Amount, - OutputDescription, SpendDescription, TxOut, TzeIn, TzeOut, OutPoint + amount::Amount, amount::DEFAULT_FEE, OutPoint, OutputDescription, SpendDescription, + TxOut, TzeIn, TzeOut, }, signature_hash_data, Transaction, TransactionData, SIGHASH_ALL, }, @@ -171,11 +171,7 @@ struct TransparentInputs; impl TransparentInputs { #[cfg(feature = "transparent-inputs")] - fn push( - &mut self, - sk: secp256k1::SecretKey, - coin: TxOut, - ) -> Result<(), Error> { + fn push(&mut self, sk: secp256k1::SecretKey, coin: TxOut) -> Result<(), Error> { if coin.value.is_negative() { return Err(Error::InvalidAmount); } @@ -248,7 +244,6 @@ impl TransparentInputs { fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {} } - struct TzeInputInfo<'a, BuildCtx> { prevout: TzeOut, builder: Box Result + 'a>, @@ -260,22 +255,27 @@ struct TzeInputs<'a, BuildCtx> { impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> { fn push( - &mut self, - extension_id: usize, - prevout: (OutPoint, TzeOut), - builder: WBuilder) - where WBuilder: 'a + FnOnce(&BuildCtx) -> Result { + &mut self, + extension_id: usize, + prevout: (OutPoint, TzeOut), + builder: WBuilder, + ) where + WBuilder: 'a + FnOnce(&BuildCtx) -> Result, + { let (outpoint, tzeout) = prevout; - self.builders.push(TzeInputInfo { - prevout: tzeout, - builder: Box::new( - move |ctx| { - let (mode, payload) = builder(&ctx).map(|x| x.to_payload())?; - Ok(TzeIn { - prevout: outpoint, - witness: tze::Witness { extension_id, mode, payload } - }) + self.builders.push(TzeInputInfo { + prevout: tzeout, + builder: Box::new(move |ctx| { + let (mode, payload) = builder(&ctx).map(|x| x.to_payload())?; + Ok(TzeIn { + prevout: outpoint, + witness: tze::Witness { + extension_id, + mode, + payload, + }, }) + }), }); } } @@ -479,7 +479,7 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { consensus_branch_id: consensus::BranchId, prover: &impl TxProver, // epoch: &Epoch - ) -> Result<(Transaction, TransactionMetadata), Error> { + ) -> Result<(Transaction, TransactionMetadata), Error> { let mut tx_metadata = TransactionMetadata::new(); // @@ -487,13 +487,20 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { // // Valid change - let change = - self.mtx.value_balance - - self.fee - + self.transparent_inputs.value_sum() + let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum() - self.mtx.vout.iter().map(|vo| vo.value).sum::() - + self.tze_inputs.builders.iter().map(|ein| ein.prevout.value).sum::() - - self.mtx.tze_outputs.iter().map(|tzo| tzo.value).sum::(); + + self + .tze_inputs + .builders + .iter() + .map(|ein| ein.prevout.value) + .sum::() + - self + .mtx + .tze_outputs + .iter() + .map(|tzo| tzo.value) + .sum::(); if change.is_negative() { return Err(Error::ChangeIsNegative(change)); @@ -718,20 +725,20 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { } } - - impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { type BuildCtx = TransactionData; type BuildError = Error; fn add_tze_input( - &mut self, + &mut self, extension_id: usize, prevout: (OutPoint, TzeOut), - witness_builder: WBuilder - ) -> Result<(), Self::BuildError> - where WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result) { - // where WBuilder: WitnessBuilder { + witness_builder: WBuilder, + ) -> Result<(), Self::BuildError> + where + WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result), + { + // where WBuilder: WitnessBuilder { self.tze_inputs.push(extension_id, prevout, witness_builder); Ok(()) } @@ -740,7 +747,7 @@ impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { &mut self, extension_id: usize, value: Amount, - guarded_by: &P + guarded_by: &P, ) -> Result<(), Self::BuildError> { if value.is_negative() { return Err(Error::InvalidAmount); @@ -760,7 +767,6 @@ impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { } } - #[cfg(test)] mod tests { use ff::{Field, PrimeField}; From 324e351c63f1acf4b69e8ebdab931a652a2c3a44 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 26 May 2020 14:49:02 -0600 Subject: [PATCH 05/14] Add validation to demo transaction builder. --- zcash_extensions/src/transparent/demo.rs | 35 +++++++++++++++---- .../src/extensions/transparent.rs | 8 ----- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 537f3f71c3..f0e55001d2 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -308,18 +308,26 @@ pub struct DemoBuilder<'a, B> { extension_id: usize, } +pub enum DemoBuildError { + BaseBuilderError(E), + ExpectedOpen, + PrevoutParseFailure(Error), + TransferCloseMismatch { expected: [u8; 32], actual: [u8; 32] }, +} + impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { pub fn demo_open( &mut self, value: Amount, preimage_1: [u8; 32], preimage_2: [u8; 32], - ) -> Result<(), B::BuildError> { + ) -> Result<(), DemoBuildError> { let (hash_1, _) = builder_hashes(&preimage_1, &preimage_2); // Call through to the generic builder. self.txn_builder .add_tze_output(self.extension_id, value, &Precondition::open(hash_1)) + .map_err(DemoBuildError::BaseBuilderError) } pub fn demo_transfer_to_close( @@ -328,30 +336,43 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { transfer_amount: Amount, preimage_1: [u8; 32], preimage_2: [u8; 32], - ) -> Result<(), B::BuildError> { - let (_, hash_2) = builder_hashes(&preimage_1, &preimage_2); + ) -> Result<(), DemoBuildError> { + let (hash_1, hash_2) = builder_hashes(&preimage_1, &preimage_2); + + // eagerly validate the relationship between prevout.1 and preimage_1? + match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) { + Ok(Precondition::Open(hash)) => + if hash.0 != hash_1 { + Err(DemoBuildError::TransferCloseMismatch { expected: hash.0, actual: hash_1})? + } + Ok(Precondition::Close(_)) => + Err(DemoBuildError::ExpectedOpen)?, + Err(parse_failure) => + Err(DemoBuildError::PrevoutParseFailure(parse_failure))? + } - // should we eagerly validate the relationship between prevout.1 and preimage_1? self.txn_builder .add_tze_input(self.extension_id, prevout, move |_| { Ok(Witness::open(preimage_1)) - })?; + }) + .map_err(DemoBuildError::BaseBuilderError)?; self.txn_builder.add_tze_output( self.extension_id, transfer_amount, // can this be > prevout.1.value? &Precondition::close(hash_2), ) + .map_err(DemoBuildError::BaseBuilderError) } pub fn demo_close( &mut self, prevout: (OutPoint, TzeOut), - preimage: [u8; 32], + preimage_2: [u8; 32], ) -> Result<(), B::BuildError> { let hash_2 = { let mut hash = [0; 32]; - hash.copy_from_slice(Params::new().hash_length(32).hash(&preimage).as_bytes()); + hash.copy_from_slice(Params::new().hash_length(32).hash(&preimage_2).as_bytes()); hash }; diff --git a/zcash_primitives/src/extensions/transparent.rs b/zcash_primitives/src/extensions/transparent.rs index ee4b75d305..12ed3c5191 100644 --- a/zcash_primitives/src/extensions/transparent.rs +++ b/zcash_primitives/src/extensions/transparent.rs @@ -113,13 +113,6 @@ pub trait Extension { } } -// pub trait WitnessBuilder { -// type Error; -// type Witness: ToPayload; -// -// fn build_witness(ctx: BuildCtx) -> Result; -// } - // This extension trait is satisfied by the transaction::builder::Builder type. It provides a // minimal contract for interacting with the transaction builder, that extension library authors // can use to add extension-specific builder traits that may be used to interact with the @@ -138,7 +131,6 @@ pub trait ExtensionTxBuilder<'a> { ) -> Result<(), Self::BuildError> where WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result); - //where WBuilder: WitnessBuilder; fn add_tze_output( &mut self, From f2f66efb5cc3925247e50cc9bc3bfc4aeb6c6754 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 26 May 2020 17:20:39 -0600 Subject: [PATCH 06/14] Validate closing transaction in demo builder. --- zcash_extensions/src/transparent/demo.rs | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index f0e55001d2..15a7fcd580 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -311,8 +311,10 @@ pub struct DemoBuilder<'a, B> { pub enum DemoBuildError { BaseBuilderError(E), ExpectedOpen, + ExpectedClose, PrevoutParseFailure(Error), - TransferCloseMismatch { expected: [u8; 32], actual: [u8; 32] }, + TransferMismatch { expected: [u8; 32], actual: [u8; 32] }, + CloseMismatch { expected: [u8; 32], actual: [u8; 32] }, } impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { @@ -339,11 +341,11 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { ) -> Result<(), DemoBuildError> { let (hash_1, hash_2) = builder_hashes(&preimage_1, &preimage_2); - // eagerly validate the relationship between prevout.1 and preimage_1? + // eagerly validate the relationship between prevout.1 and preimage_1 match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) { Ok(Precondition::Open(hash)) => if hash.0 != hash_1 { - Err(DemoBuildError::TransferCloseMismatch { expected: hash.0, actual: hash_1})? + Err(DemoBuildError::TransferMismatch { expected: hash.0, actual: hash_1})? } Ok(Precondition::Close(_)) => Err(DemoBuildError::ExpectedOpen)?, @@ -369,17 +371,30 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { &mut self, prevout: (OutPoint, TzeOut), preimage_2: [u8; 32], - ) -> Result<(), B::BuildError> { + ) -> Result<(), DemoBuildError> { let hash_2 = { let mut hash = [0; 32]; hash.copy_from_slice(Params::new().hash_length(32).hash(&preimage_2).as_bytes()); hash }; + // eagerly validate the relationship between prevout.1 and preimage_2 + match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) { + Ok(Precondition::Open(_)) => + Err(DemoBuildError::ExpectedClose)?, + Ok(Precondition::Close(hash)) => + if hash.0 != hash_2 { + Err(DemoBuildError::CloseMismatch { expected: hash.0, actual: hash_2})? + } + Err(parse_failure) => + Err(DemoBuildError::PrevoutParseFailure(parse_failure))? + } + self.txn_builder .add_tze_input(self.extension_id, prevout, move |_| { Ok(Witness::close(hash_2)) }) + .map_err(DemoBuildError::BaseBuilderError) } } @@ -503,8 +518,6 @@ mod tests { precondition: tze::Precondition::from(0, &Precondition::open(hash_1)), }; - // println!("{:x?}", precondition.payload); - let mut mtx_a = TransactionData::nu4(); mtx_a.tze_outputs.push(out_a); let tx_a = mtx_a.freeze().unwrap(); From 12ba9ab9cd8223c74c1dc0194ee0fb64bdde7c8f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 27 May 2020 09:30:34 -0600 Subject: [PATCH 07/14] Fix incorrect closing witness. --- zcash_extensions/src/transparent/demo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 15a7fcd580..3667915477 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -392,7 +392,7 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { self.txn_builder .add_tze_input(self.extension_id, prevout, move |_| { - Ok(Witness::close(hash_2)) + Ok(Witness::close(preimage_2)) }) .map_err(DemoBuildError::BaseBuilderError) } From 75903fa38702b04f46c38096d233adbd25e1689d Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 27 May 2020 12:09:59 -0600 Subject: [PATCH 08/14] Before prover test deps. --- zcash_extensions/src/transparent/demo.rs | 97 +++++++++++++++++++-- zcash_primitives/src/consensus.rs | 12 +++ zcash_primitives/src/transaction/builder.rs | 36 +++++--- zcash_primitives/src/transaction/mod.rs | 4 +- 4 files changed, 130 insertions(+), 19 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 3667915477..692f98b1ec 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -304,10 +304,12 @@ fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u } pub struct DemoBuilder<'a, B> { - txn_builder: &'a mut B, - extension_id: usize, + pub txn_builder: &'a mut B, + pub extension_id: usize, } + +#[derive(Debug)] pub enum DemoBuildError { BaseBuilderError(E), ExpectedOpen, @@ -402,12 +404,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { mod tests { use blake2b_simd::Params; - use super::{close, open, Context, Precondition, Program, Witness}; + use super::{close, open, Context, Precondition, Program, Witness, DemoBuilder}; use zcash_primitives::{ - extensions::transparent::{self as tze, Extension, FromPayload, ToPayload}, + consensus::{BranchId}, + extensions::transparent::{ + self as tze, + Extension, + FromPayload, + ToPayload, + ExtensionTxBuilder, + }, transaction::{ + builder::Builder, components::{Amount, OutPoint, TzeIn, TzeOut}, - Transaction, TransactionData, + Transaction, TransactionData, }, }; @@ -578,4 +588,81 @@ mod tests { ); } } + + #[test] + fn demo_builder_program() -> Result<(), String> { + let preimage_1 = [1; 32]; + let preimage_2 = [2; 32]; + + // + // Opening transaction + // + + let builder_a = DemoBuilder { + txn_builder: &mut Builder::new_nu4(0), + extension_id: 0 + }; + + let open_value = Amount::from_u64(1).unwrap(); + builder_a.demo_open(open_value, preimage_1, preimage_2) + .map_err(|e| format!("open failure: {:?}", e))?; + let (tx_a, _) = builder_a.txn_builder.build(BranchId::Canopy, MockTxProver) + .map_err(|e| format!("build failure: {:?}", e))?; + + // + // Transfer + // + + let builder_b = DemoBuilder { + txn_builder: &mut Builder::new_nu4(0), + extension_id: 0 + }; + let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.data.tze_outputs[0]); + builder_b.demo_transfer_to_close(prevout_a, open_value, preimage_1, preimage_2) + .map_err(|e| format!("transfer failure: {:?}", e))?; + let (tx_b, _) = builder_b.txn_builder.build(BranchId::Canopy, MockTxProver) + .map_err(|e| format!("build failure: {:?}", e))?; + + // + // Closing transaction + // + + let builder_c = DemoBuilder { + txn_builder: &mut Builder::new_nu4(0), + extension_id: 0 + }; + let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.data.tze_outputs[0]); + builder_c.demo_close(prevout_b, preimage_2) + .map_err(|e| format!("close failure: {:?}", e))?; + let (tx_c, _) = builder_b.txn_builder.build(BranchId::Canopy, MockTxProver) + .map_err(|e| format!("build failure: {:?}", e))?; + + // Verify tx_b + { + let ctx = Ctx { tx: &tx_b }; + assert_eq!( + Program.verify( + &tx_a.data.tze_outputs[0].precondition, + &tx_b.data.tze_inputs[0].witness, + &ctx + ), + Ok(()) + ); + } + + // Verify tx_c + { + let ctx = Ctx { tx: &tx_b }; + assert_eq!( + Program.verify( + &tx_b.data.tze_outputs[0].precondition, + &tx_c.data.tze_inputs[0].witness, + &ctx + ), + Ok(()) + ); + } + + Ok(()) + } } diff --git a/zcash_primitives/src/consensus.rs b/zcash_primitives/src/consensus.rs index 67d328e589..dd61c05f98 100644 --- a/zcash_primitives/src/consensus.rs +++ b/zcash_primitives/src/consensus.rs @@ -26,6 +26,7 @@ impl Parameters for MainNetwork { NetworkUpgrade::Sapling => Some(419_200), NetworkUpgrade::Blossom => Some(653_600), NetworkUpgrade::Heartwood => None, + NetworkUpgrade::Canopy => None, } } } @@ -41,6 +42,7 @@ impl Parameters for TestNetwork { NetworkUpgrade::Sapling => Some(280_000), NetworkUpgrade::Blossom => Some(584_000), NetworkUpgrade::Heartwood => None, + NetworkUpgrade::Canopy => None, } } } @@ -67,6 +69,10 @@ pub enum NetworkUpgrade { /// /// [Heartwood]: https://z.cash/upgrade/heartwood/ Heartwood, + /// The [Canopy] network upgrade. + /// + /// [Canopy]: https://z.cash/upgrade/canopy/ + Canopy, } impl fmt::Display for NetworkUpgrade { @@ -76,6 +82,7 @@ impl fmt::Display for NetworkUpgrade { NetworkUpgrade::Sapling => write!(f, "Sapling"), NetworkUpgrade::Blossom => write!(f, "Blossom"), NetworkUpgrade::Heartwood => write!(f, "Heartwood"), + NetworkUpgrade::Canopy => write!(f, "Canopy"), } } } @@ -87,6 +94,7 @@ impl NetworkUpgrade { NetworkUpgrade::Sapling => BranchId::Sapling, NetworkUpgrade::Blossom => BranchId::Blossom, NetworkUpgrade::Heartwood => BranchId::Heartwood, + NetworkUpgrade::Canopy => BranchId::Canopy, } } } @@ -127,6 +135,8 @@ pub enum BranchId { Blossom, /// The consensus rules deployed by [`NetworkUpgrade::Heartwood`]. Heartwood, + /// The consensus rules deployed by [`NetworkUpgrade::Canopy`]. + Canopy, } impl TryFrom for BranchId { @@ -139,6 +149,7 @@ impl TryFrom for BranchId { 0x76b8_09bb => Ok(BranchId::Sapling), 0x2bb4_0e60 => Ok(BranchId::Blossom), 0xf5b9_230b => Ok(BranchId::Heartwood), + 0x1111_1111 => Ok(BranchId::Canopy), //FIXME _ => Err("Unknown consensus branch ID"), } } @@ -152,6 +163,7 @@ impl From for u32 { BranchId::Sapling => 0x76b8_09bb, BranchId::Blossom => 0x2bb4_0e60, BranchId::Heartwood => 0xf5b9_230b, + BranchId::Canopy => 0x1111_1111, //FIXME } } } diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 33f54dd9b3..2d3fe032ce 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -344,20 +344,14 @@ impl Builder<'_, OsRng> { pub fn new(height: u32) -> Self { Builder::new_with_rng(height, OsRng) } + + pub fn new_nu4(height: u32) -> Self { + Builder::new_with_rng_nu4(height, OsRng) + } } impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { - /// Creates a new `Builder` targeted for inclusion in the block with the given height - /// and randomness source, using default values for general transaction fields. - /// - /// # Default values - /// - /// The expiry height will be set to the given height plus the default transaction - /// expiry delta (20 blocks). - /// - /// The fee will be set to the default fee (0.0001 ZEC). - pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, R> { - let mut mtx = TransactionData::new(); + fn new_with_mtx(height: u32, rng: R, mut mtx: TransactionData) -> Builder<'a, R> { mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA; Builder { @@ -373,6 +367,25 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { } } + /// Creates a new `Builder` targeted for inclusion in the block with the given height + /// and randomness source, using default values for general transaction fields. + /// + /// # Default values + /// + /// The expiry height will be set to the given height plus the default transaction + /// expiry delta (20 blocks). + /// + /// The fee will be set to the default fee (0.0001 ZEC). + pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, R> { + let mtx = TransactionData::new(); + Self::new_with_mtx(height, rng, mtx) + } + + pub fn new_with_rng_nu4(height: u32, rng: R) -> Builder<'a, R> { + let mtx = TransactionData::nu4(); + Self::new_with_mtx(height, rng, mtx) + } + /// Adds a Sapling note to be spent in this transaction. /// /// Returns an error if the given Merkle path does not have the same anchor as the @@ -478,7 +491,6 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { mut self, consensus_branch_id: consensus::BranchId, prover: &impl TxProver, - // epoch: &Epoch ) -> Result<(Transaction, TransactionMetadata), Error> { let mut tx_metadata = TransactionMetadata::new(); diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index fc48a84874..55ede339da 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -44,8 +44,8 @@ impl fmt::Display for TxId { /// A Zcash transaction. #[derive(Debug)] pub struct Transaction { - txid: TxId, - data: TransactionData, + pub txid: TxId, + pub data: TransactionData, } impl Deref for Transaction { From dbdaa6721cbb87dfecdc469f448ff011f393bf6c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 28 May 2020 10:35:24 -0600 Subject: [PATCH 09/14] Add tests for transaction builder support. --- zcash_extensions/Cargo.toml | 6 + zcash_extensions/src/transparent/demo.rs | 238 ++++++++++++-------- zcash_primitives/src/transaction/builder.rs | 17 +- zcash_primitives/src/transaction/sighash.rs | 3 +- 4 files changed, 165 insertions(+), 99 deletions(-) diff --git a/zcash_extensions/Cargo.toml b/zcash_extensions/Cargo.toml index 5cb922ba14..4014934802 100644 --- a/zcash_extensions/Cargo.toml +++ b/zcash_extensions/Cargo.toml @@ -11,3 +11,9 @@ edition = "2018" [dependencies] blake2b_simd = "0.5" zcash_primitives = { version = "0.2", path = "../zcash_primitives" } + +[dev-dependencies] +ff = { version = "0.6", path = "../ff" } +pairing = { version = "0.16", path = "../pairing" } +zcash_proofs = { path = "../zcash_proofs" } +rand_core = "0.5.1" diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 692f98b1ec..6736c30cd3 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -303,23 +303,28 @@ fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u (hash_1, hash_2) } -pub struct DemoBuilder<'a, B> { - pub txn_builder: &'a mut B, +pub struct DemoBuilder { + pub txn_builder: B, pub extension_id: usize, } - #[derive(Debug)] pub enum DemoBuildError { BaseBuilderError(E), ExpectedOpen, ExpectedClose, PrevoutParseFailure(Error), - TransferMismatch { expected: [u8; 32], actual: [u8; 32] }, - CloseMismatch { expected: [u8; 32], actual: [u8; 32] }, + TransferMismatch { + expected: [u8; 32], + actual: [u8; 32], + }, + CloseMismatch { + expected: [u8; 32], + actual: [u8; 32], + }, } -impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { +impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> { pub fn demo_open( &mut self, value: Amount, @@ -344,15 +349,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { let (hash_1, hash_2) = builder_hashes(&preimage_1, &preimage_2); // eagerly validate the relationship between prevout.1 and preimage_1 - match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) { - Ok(Precondition::Open(hash)) => + match Precondition::from_payload( + prevout.1.precondition.mode, + &prevout.1.precondition.payload, + ) { + Ok(Precondition::Open(hash)) => { if hash.0 != hash_1 { - Err(DemoBuildError::TransferMismatch { expected: hash.0, actual: hash_1})? - } - Ok(Precondition::Close(_)) => - Err(DemoBuildError::ExpectedOpen)?, - Err(parse_failure) => - Err(DemoBuildError::PrevoutParseFailure(parse_failure))? + Err(DemoBuildError::TransferMismatch { + expected: hash.0, + actual: hash_1, + })? + } + } + Ok(Precondition::Close(_)) => Err(DemoBuildError::ExpectedOpen)?, + Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure))?, } self.txn_builder @@ -361,12 +371,13 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { }) .map_err(DemoBuildError::BaseBuilderError)?; - self.txn_builder.add_tze_output( - self.extension_id, - transfer_amount, // can this be > prevout.1.value? - &Precondition::close(hash_2), - ) - .map_err(DemoBuildError::BaseBuilderError) + self.txn_builder + .add_tze_output( + self.extension_id, + transfer_amount, // can this be > prevout.1.value? + &Precondition::close(hash_2), + ) + .map_err(DemoBuildError::BaseBuilderError) } pub fn demo_close( @@ -381,15 +392,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { }; // eagerly validate the relationship between prevout.1 and preimage_2 - match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) { - Ok(Precondition::Open(_)) => - Err(DemoBuildError::ExpectedClose)?, - Ok(Precondition::Close(hash)) => + match Precondition::from_payload( + prevout.1.precondition.mode, + &prevout.1.precondition.payload, + ) { + Ok(Precondition::Open(_)) => Err(DemoBuildError::ExpectedClose)?, + Ok(Precondition::Close(hash)) => { if hash.0 != hash_2 { - Err(DemoBuildError::CloseMismatch { expected: hash.0, actual: hash_2})? - } - Err(parse_failure) => - Err(DemoBuildError::PrevoutParseFailure(parse_failure))? + Err(DemoBuildError::CloseMismatch { + expected: hash.0, + actual: hash_2, + })? + } + } + Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure))?, } self.txn_builder @@ -403,24 +419,29 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> { #[cfg(test)] mod tests { use blake2b_simd::Params; + use ff::PrimeField; - use super::{close, open, Context, Precondition, Program, Witness, DemoBuilder}; + use super::{close, open, Context, DemoBuilder, Precondition, Program, Witness}; + use zcash_proofs::prover::LocalTxProver; use zcash_primitives::{ - consensus::{BranchId}, - extensions::transparent::{ - self as tze, - Extension, - FromPayload, - ToPayload, - ExtensionTxBuilder, - }, + consensus::BranchId, + extensions::transparent::{self as tze, Extension, FromPayload, ToPayload}, + legacy::TransparentAddress, + jubjub::{fs::Fs}, + merkle_tree::{CommitmentTree, IncrementalWitness}, + sapling::Node, transaction::{ builder::Builder, components::{Amount, OutPoint, TzeIn, TzeOut}, - Transaction, TransactionData, + Transaction, TransactionData, }, + zip32::ExtendedSpendingKey, + JUBJUB, }; + use ff::Field; + use rand_core::OsRng; + #[test] fn precondition_open_round_trip() { let data = vec![7; 32]; @@ -590,79 +611,114 @@ mod tests { } #[test] - fn demo_builder_program() -> Result<(), String> { + fn demo_builder_program() { let preimage_1 = [1; 32]; let preimage_2 = [2; 32]; + let prover = LocalTxProver::with_default_location().unwrap(); + // // Opening transaction // - - let builder_a = DemoBuilder { - txn_builder: &mut Builder::new_nu4(0), - extension_id: 0 + + let mut rng = OsRng; + let mut builder_a = Builder::new_nu4(0); + + // create some inputs to spend + let extsk = ExtendedSpendingKey::master(&[]); + let to = extsk.default_address().unwrap().1; + let note1 = to + .create_note(110000, Fs::random(&mut rng), &JUBJUB) + .unwrap(); + let cm1 = Node::new(note1.cm(&JUBJUB).into_repr()); + let mut tree = CommitmentTree::new(); + // fake that the note appears in some previous + // shielded output + tree.append(cm1).unwrap(); + let witness1 = IncrementalWitness::from_tree(&tree); + builder_a.add_sapling_spend( + extsk.clone(), + *to.diversifier(), + note1.clone(), + witness1.path().unwrap(), + ) + .unwrap(); + + let mut db_a = DemoBuilder { + txn_builder: &mut builder_a, + extension_id: 0, }; - let open_value = Amount::from_u64(1).unwrap(); - builder_a.demo_open(open_value, preimage_1, preimage_2) - .map_err(|e| format!("open failure: {:?}", e))?; - let (tx_a, _) = builder_a.txn_builder.build(BranchId::Canopy, MockTxProver) - .map_err(|e| format!("build failure: {:?}", e))?; + let value = Amount::from_u64(100000).unwrap(); + db_a.demo_open(value, preimage_1, preimage_2) + .map_err(|e| format!("open failure: {:?}", e)) + .unwrap(); + let (tx_a, _) = builder_a + .build(BranchId::Canopy, &prover) + .map_err(|e| format!("build failure: {:?}", e)) + .unwrap(); // // Transfer // - - let builder_b = DemoBuilder { - txn_builder: &mut Builder::new_nu4(0), - extension_id: 0 + + let mut builder_b = Builder::new_nu4(0); + let mut db_b = DemoBuilder { + txn_builder: &mut builder_b, + extension_id: 0, }; - let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.data.tze_outputs[0]); - builder_b.demo_transfer_to_close(prevout_a, open_value, preimage_1, preimage_2) - .map_err(|e| format!("transfer failure: {:?}", e))?; - let (tx_b, _) = builder_b.txn_builder.build(BranchId::Canopy, MockTxProver) - .map_err(|e| format!("build failure: {:?}", e))?; - + let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.data.tze_outputs[0].clone()); + let value_xfr = Amount::from_u64(90000).unwrap(); + db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, preimage_2) + .map_err(|e| format!("transfer failure: {:?}", e)) + .unwrap(); + let (tx_b, _) = builder_b + .build(BranchId::Canopy, &prover) + .map_err(|e| format!("build failure: {:?}", e)) + .unwrap(); + // // Closing transaction // - let builder_c = DemoBuilder { - txn_builder: &mut Builder::new_nu4(0), - extension_id: 0 + let mut builder_c = Builder::new_nu4(0); + let mut db_c = DemoBuilder { + txn_builder: &mut builder_c, + extension_id: 0, }; - let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.data.tze_outputs[0]); - builder_c.demo_close(prevout_b, preimage_2) - .map_err(|e| format!("close failure: {:?}", e))?; - let (tx_c, _) = builder_b.txn_builder.build(BranchId::Canopy, MockTxProver) - .map_err(|e| format!("build failure: {:?}", e))?; - + let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.data.tze_outputs[0].clone()); + db_c.demo_close(prevout_b, preimage_2) + .map_err(|e| format!("close failure: {:?}", e)) + .unwrap(); + + builder_c.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::from_u64(80000).unwrap()) + .unwrap(); + + let (tx_c, _) = builder_c + .build(BranchId::Canopy, &prover) + .map_err(|e| format!("build failure: {:?}", e)) + .unwrap(); + // Verify tx_b - { - let ctx = Ctx { tx: &tx_b }; - assert_eq!( - Program.verify( - &tx_a.data.tze_outputs[0].precondition, - &tx_b.data.tze_inputs[0].witness, - &ctx - ), - Ok(()) - ); - } + let ctx0 = Ctx { tx: &tx_b }; + assert_eq!( + Program.verify( + &tx_a.data.tze_outputs[0].precondition, + &tx_b.data.tze_inputs[0].witness, + &ctx0 + ), + Ok(()) + ); // Verify tx_c - { - let ctx = Ctx { tx: &tx_b }; - assert_eq!( - Program.verify( - &tx_b.data.tze_outputs[0].precondition, - &tx_c.data.tze_inputs[0].witness, - &ctx - ), - Ok(()) - ); - } - - Ok(()) + let ctx1 = Ctx { tx: &tx_b }; + assert_eq!( + Program.verify( + &tx_b.data.tze_outputs[0].precondition, + &tx_c.data.tze_inputs[0].witness, + &ctx1 + ), + Ok(()) + ); } } diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 2d3fe032ce..5732165dae 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -499,7 +499,9 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { // // Valid change - let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum() + let change = self.mtx.value_balance + - self.fee + + self.transparent_inputs.value_sum() - self.mtx.vout.iter().map(|vo| vo.value).sum::() + self .tze_inputs @@ -707,11 +709,13 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { &JUBJUB, )); } - self.mtx.binding_sig = Some( - prover - .binding_sig(&mut ctx, self.mtx.value_balance, &sighash) - .map_err(|()| Error::BindingSig)?, - ); + if !self.mtx.shielded_spends.is_empty() { + self.mtx.binding_sig = Some( + prover + .binding_sig(&mut ctx, self.mtx.value_balance, &sighash) + .map_err(|_| Error::BindingSig)?, + ); + } // // Create TZE input witnesses for tze_in in self.tze_inputs.builders { @@ -750,7 +754,6 @@ impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { where WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result), { - // where WBuilder: WitnessBuilder { self.tze_inputs.push(extension_id, prevout, witness_builder); Ok(()) } diff --git a/zcash_primitives/src/transaction/sighash.rs b/zcash_primitives/src/transaction/sighash.rs index 4319f41752..5d6b8bb767 100644 --- a/zcash_primitives/src/transaction/sighash.rs +++ b/zcash_primitives/src/transaction/sighash.rs @@ -5,7 +5,7 @@ use ff::{PrimeField, PrimeFieldRepr}; use super::{ components::{Amount, TxOut}, Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION, - SAPLING_VERSION_GROUP_ID, + SAPLING_VERSION_GROUP_ID, NU4_VERSION_GROUP_ID, }; use crate::{consensus, legacy::Script}; @@ -53,6 +53,7 @@ impl SigHashVersion { match tx.version_group_id { OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter, SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling, + NU4_VERSION_GROUP_ID => SigHashVersion::Sapling, //FIXME _ => unimplemented!(), } } else { From e5a582d9e125af00fa7d66b92bcb8b709ed1cb96 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 2 Jun 2020 16:20:44 -0600 Subject: [PATCH 10/14] Check transparent input for correctness before modifying vin. --- zcash_extensions/src/transparent/demo.rs | 37 +++++++++++++-------- zcash_primitives/src/transaction/builder.rs | 7 ++-- zcash_primitives/src/transaction/sighash.rs | 4 +-- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 6736c30cd3..635d775f84 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -422,12 +422,11 @@ mod tests { use ff::PrimeField; use super::{close, open, Context, DemoBuilder, Precondition, Program, Witness}; - use zcash_proofs::prover::LocalTxProver; use zcash_primitives::{ consensus::BranchId, extensions::transparent::{self as tze, Extension, FromPayload, ToPayload}, + jubjub::fs::Fs, legacy::TransparentAddress, - jubjub::{fs::Fs}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::Node, transaction::{ @@ -438,6 +437,7 @@ mod tests { zip32::ExtendedSpendingKey, JUBJUB, }; + use zcash_proofs::prover::LocalTxProver; use ff::Field; use rand_core::OsRng; @@ -636,13 +636,14 @@ mod tests { // shielded output tree.append(cm1).unwrap(); let witness1 = IncrementalWitness::from_tree(&tree); - builder_a.add_sapling_spend( - extsk.clone(), - *to.diversifier(), - note1.clone(), - witness1.path().unwrap(), - ) - .unwrap(); + builder_a + .add_sapling_spend( + extsk.clone(), + *to.diversifier(), + note1.clone(), + witness1.path().unwrap(), + ) + .unwrap(); let mut db_a = DemoBuilder { txn_builder: &mut builder_a, @@ -667,7 +668,10 @@ mod tests { txn_builder: &mut builder_b, extension_id: 0, }; - let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.data.tze_outputs[0].clone()); + let prevout_a = ( + OutPoint::new(tx_a.txid().0, 0), + tx_a.data.tze_outputs[0].clone(), + ); let value_xfr = Amount::from_u64(90000).unwrap(); db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, preimage_2) .map_err(|e| format!("transfer failure: {:?}", e)) @@ -686,13 +690,20 @@ mod tests { txn_builder: &mut builder_c, extension_id: 0, }; - let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.data.tze_outputs[0].clone()); + let prevout_b = ( + OutPoint::new(tx_a.txid().0, 0), + tx_b.data.tze_outputs[0].clone(), + ); db_c.demo_close(prevout_b, preimage_2) .map_err(|e| format!("close failure: {:?}", e)) .unwrap(); - builder_c.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::from_u64(80000).unwrap()) - .unwrap(); + builder_c + .add_transparent_output( + &TransparentAddress::PublicKey([0; 20]), + Amount::from_u64(80000).unwrap(), + ) + .unwrap(); let (tx_c, _) = builder_c .build(BranchId::Canopy, &prover) diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 5732165dae..d83eb02f19 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -448,8 +448,9 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { utxo: OutPoint, coin: TxOut, ) -> Result<(), Error> { + self.transparent_inputs.push(sk, coin)?; self.mtx.vin.push(TxIn::new(utxo)); - self.transparent_inputs.push(sk, coin) + Ok(()); } /// Adds a transparent address to send funds to. @@ -499,9 +500,7 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { // // Valid change - let change = self.mtx.value_balance - - self.fee - + self.transparent_inputs.value_sum() + let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum() - self.mtx.vout.iter().map(|vo| vo.value).sum::() + self .tze_inputs diff --git a/zcash_primitives/src/transaction/sighash.rs b/zcash_primitives/src/transaction/sighash.rs index 5d6b8bb767..f7f954a40c 100644 --- a/zcash_primitives/src/transaction/sighash.rs +++ b/zcash_primitives/src/transaction/sighash.rs @@ -4,8 +4,8 @@ use ff::{PrimeField, PrimeFieldRepr}; use super::{ components::{Amount, TxOut}, - Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION, - SAPLING_VERSION_GROUP_ID, NU4_VERSION_GROUP_ID, + Transaction, TransactionData, NU4_VERSION_GROUP_ID, OVERWINTER_VERSION_GROUP_ID, + SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID, }; use crate::{consensus, legacy::Script}; From daaa77b7e907a1db4717a68a0f95685903d6f962 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 3 Jun 2020 16:05:55 -0600 Subject: [PATCH 11/14] Keep transaction details private. --- zcash_extensions/src/transparent/demo.rs | 12 ++++++------ zcash_primitives/src/transaction/mod.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 635d775f84..ec9c0d8622 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -670,7 +670,7 @@ mod tests { }; let prevout_a = ( OutPoint::new(tx_a.txid().0, 0), - tx_a.data.tze_outputs[0].clone(), + tx_a.tze_outputs[0].clone(), ); let value_xfr = Amount::from_u64(90000).unwrap(); db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, preimage_2) @@ -692,7 +692,7 @@ mod tests { }; let prevout_b = ( OutPoint::new(tx_a.txid().0, 0), - tx_b.data.tze_outputs[0].clone(), + tx_b.tze_outputs[0].clone(), ); db_c.demo_close(prevout_b, preimage_2) .map_err(|e| format!("close failure: {:?}", e)) @@ -714,8 +714,8 @@ mod tests { let ctx0 = Ctx { tx: &tx_b }; assert_eq!( Program.verify( - &tx_a.data.tze_outputs[0].precondition, - &tx_b.data.tze_inputs[0].witness, + &tx_a.tze_outputs[0].precondition, + &tx_b.tze_inputs[0].witness, &ctx0 ), Ok(()) @@ -725,8 +725,8 @@ mod tests { let ctx1 = Ctx { tx: &tx_b }; assert_eq!( Program.verify( - &tx_b.data.tze_outputs[0].precondition, - &tx_c.data.tze_inputs[0].witness, + &tx_b.tze_outputs[0].precondition, + &tx_c.tze_inputs[0].witness, &ctx1 ), Ok(()) diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 55ede339da..fc48a84874 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -44,8 +44,8 @@ impl fmt::Display for TxId { /// A Zcash transaction. #[derive(Debug)] pub struct Transaction { - pub txid: TxId, - pub data: TransactionData, + txid: TxId, + data: TransactionData, } impl Deref for Transaction { From 2a83cc24bf8663e9af538cb2fa2f633f0699f34f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 3 Jun 2020 20:38:05 -0600 Subject: [PATCH 12/14] Change extension_id and mode types to u32 --- zcash_extensions/src/consensus/transparent.rs | 10 +++--- zcash_extensions/src/transparent/demo.rs | 34 ++++++++----------- .../src/extensions/transparent.rs | 24 ++++++------- zcash_primitives/src/transaction/builder.rs | 6 ++-- .../src/transaction/components.rs | 33 +++++++++++++----- 5 files changed, 59 insertions(+), 48 deletions(-) diff --git a/zcash_extensions/src/consensus/transparent.rs b/zcash_extensions/src/consensus/transparent.rs index 07769f368d..a4a3ed073b 100644 --- a/zcash_extensions/src/consensus/transparent.rs +++ b/zcash_extensions/src/consensus/transparent.rs @@ -13,12 +13,12 @@ pub enum ExtensionId { Demo, } -pub struct InvalidExtId(usize); +pub struct InvalidExtId(u32); -impl TryFrom for ExtensionId { +impl TryFrom for ExtensionId { type Error = InvalidExtId; - fn try_from(t: usize) -> Result { + fn try_from(t: u32) -> Result { match t { 0 => Ok(ExtensionId::Demo), n => Err(InvalidExtId(n)), @@ -26,8 +26,8 @@ impl TryFrom for ExtensionId { } } -impl From for usize { - fn from(type_id: ExtensionId) -> usize { +impl From for u32 { + fn from(type_id: ExtensionId) -> u32 { match type_id { ExtensionId::Demo => 0, } diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index ec9c0d8622..ad1e1aeb88 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -28,7 +28,7 @@ use zcash_primitives::extensions::transparent::{ use zcash_primitives::transaction::components::{amount::Amount, OutPoint, TzeOut}; mod open { - pub const MODE: usize = 0; + pub const MODE: u32 = 0; #[derive(Debug, PartialEq)] pub struct Precondition(pub [u8; 32]); @@ -38,7 +38,7 @@ mod open { } mod close { - pub const MODE: usize = 1; + pub const MODE: u32 = 1; #[derive(Debug, PartialEq)] pub struct Precondition(pub [u8; 32]); @@ -66,7 +66,7 @@ impl Precondition { #[derive(Debug, PartialEq)] pub enum Error { IllegalPayloadLength(usize), - ModeInvalid(usize), + ModeInvalid(u32), NonTzeTxn, HashMismatch, // include hashes? ModeMismatch, @@ -88,10 +88,10 @@ impl fmt::Display for Error { } } -impl TryFrom<(usize, Precondition)> for Precondition { +impl TryFrom<(u32, Precondition)> for Precondition { type Error = Error; - fn try_from(from: (usize, Self)) -> Result { + fn try_from(from: (u32, Self)) -> Result { match from { (open::MODE, Precondition::Open(p)) => Ok(Precondition::Open(p)), (close::MODE, Precondition::Close(p)) => Ok(Precondition::Close(p)), @@ -103,7 +103,7 @@ impl TryFrom<(usize, Precondition)> for Precondition { impl FromPayload for Precondition { type Error = Error; - fn from_payload(mode: usize, payload: &[u8]) -> Result { + fn from_payload(mode: u32, payload: &[u8]) -> Result { match mode { open::MODE => { if payload.len() == 32 { @@ -129,7 +129,7 @@ impl FromPayload for Precondition { } impl ToPayload for Precondition { - fn to_payload(&self) -> (usize, Vec) { + fn to_payload(&self) -> (u32, Vec) { match self { Precondition::Open(p) => (open::MODE, p.0.to_vec()), Precondition::Close(p) => (close::MODE, p.0.to_vec()), @@ -153,10 +153,10 @@ impl Witness { } } -impl TryFrom<(usize, Witness)> for Witness { +impl TryFrom<(u32, Witness)> for Witness { type Error = Error; - fn try_from(from: (usize, Self)) -> Result { + fn try_from(from: (u32, Self)) -> Result { match from { (open::MODE, Witness::Open(p)) => Ok(Witness::Open(p)), (close::MODE, Witness::Close(p)) => Ok(Witness::Close(p)), @@ -168,7 +168,7 @@ impl TryFrom<(usize, Witness)> for Witness { impl FromPayload for Witness { type Error = Error; - fn from_payload(mode: usize, payload: &[u8]) -> Result { + fn from_payload(mode: u32, payload: &[u8]) -> Result { match mode { open::MODE => { if payload.len() == 32 { @@ -194,7 +194,7 @@ impl FromPayload for Witness { } impl ToPayload for Witness { - fn to_payload(&self) -> (usize, Vec) { + fn to_payload(&self) -> (u32, Vec) { match self { Witness::Open(w) => (open::MODE, w.0.to_vec()), Witness::Close(w) => (close::MODE, w.0.to_vec()), @@ -305,7 +305,7 @@ fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u pub struct DemoBuilder { pub txn_builder: B, - pub extension_id: usize, + pub extension_id: u32, } #[derive(Debug)] @@ -668,10 +668,7 @@ mod tests { txn_builder: &mut builder_b, extension_id: 0, }; - let prevout_a = ( - OutPoint::new(tx_a.txid().0, 0), - tx_a.tze_outputs[0].clone(), - ); + let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.tze_outputs[0].clone()); let value_xfr = Amount::from_u64(90000).unwrap(); db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, preimage_2) .map_err(|e| format!("transfer failure: {:?}", e)) @@ -690,10 +687,7 @@ mod tests { txn_builder: &mut builder_c, extension_id: 0, }; - let prevout_b = ( - OutPoint::new(tx_a.txid().0, 0), - tx_b.tze_outputs[0].clone(), - ); + let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.tze_outputs[0].clone()); db_c.demo_close(prevout_b, preimage_2) .map_err(|e| format!("close failure: {:?}", e)) .unwrap(); diff --git a/zcash_primitives/src/extensions/transparent.rs b/zcash_primitives/src/extensions/transparent.rs index 12ed3c5191..577fad011a 100644 --- a/zcash_primitives/src/extensions/transparent.rs +++ b/zcash_primitives/src/extensions/transparent.rs @@ -7,24 +7,24 @@ pub trait FromPayload: Sized { type Error; /// Parses an extension type from a mode and payload. - fn from_payload(mode: usize, payload: &[u8]) -> Result; + fn from_payload(mode: u32, payload: &[u8]) -> Result; } pub trait ToPayload { /// Returns a serialized payload and its corresponding mode. - fn to_payload(&self) -> (usize, Vec); + fn to_payload(&self) -> (u32, Vec); } /// A condition that can be used to encumber transparent funds. #[derive(Clone, Debug)] pub struct Precondition { - pub extension_id: usize, - pub mode: usize, + pub extension_id: u32, + pub mode: u32, pub payload: Vec, } impl Precondition { - pub fn from(extension_id: usize, value: &P) -> Precondition { + pub fn from(extension_id: u32, value: &P) -> Precondition { let (mode, payload) = value.to_payload(); Precondition { extension_id, @@ -42,13 +42,13 @@ impl Precondition { /// spent. #[derive(Clone, Debug)] pub struct Witness { - pub extension_id: usize, - pub mode: usize, + pub extension_id: u32, + pub mode: u32, pub payload: Vec, } impl Witness { - pub fn from(extension_id: usize, value: &P) -> Witness { + pub fn from(extension_id: u32, value: &P) -> Witness { let (mode, payload) = value.to_payload(); Witness { extension_id, @@ -60,8 +60,8 @@ impl Witness { #[derive(Debug, PartialEq)] pub enum Error { - InvalidForEpoch(u32, usize), - InvalidExtensionId(usize), + InvalidForEpoch(u32, u32), + InvalidExtensionId(u32), ProgramError(E), } @@ -125,7 +125,7 @@ pub trait ExtensionTxBuilder<'a> { fn add_tze_input( &mut self, - extension_id: usize, + extension_id: u32, prevout: (OutPoint, TzeOut), witness_builder: WBuilder, ) -> Result<(), Self::BuildError> @@ -134,7 +134,7 @@ pub trait ExtensionTxBuilder<'a> { fn add_tze_output( &mut self, - extension_id: usize, + extension_id: u32, value: Amount, guarded_by: &P, ) -> Result<(), Self::BuildError>; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index d83eb02f19..25433bff37 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -256,7 +256,7 @@ struct TzeInputs<'a, BuildCtx> { impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> { fn push( &mut self, - extension_id: usize, + extension_id: u32, prevout: (OutPoint, TzeOut), builder: WBuilder, ) where @@ -746,7 +746,7 @@ impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { fn add_tze_input( &mut self, - extension_id: usize, + extension_id: u32, prevout: (OutPoint, TzeOut), witness_builder: WBuilder, ) -> Result<(), Self::BuildError> @@ -759,7 +759,7 @@ impl<'a, R: RngCore + CryptoRng> ExtensionTxBuilder<'a> for Builder<'a, R> { fn add_tze_output( &mut self, - extension_id: usize, + extension_id: u32, value: Amount, guarded_by: &P, ) -> Result<(), Self::BuildError> { diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index ff8ad53918..b5c3d49a07 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -4,6 +4,7 @@ use crate::jubjub::{edwards, Unknown}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ff::{PrimeField, PrimeFieldRepr}; use pairing::bls12_381::{Bls12, Fr, FrRepr}; +use std::convert::TryFrom; use std::io::{self, Read, Write}; use crate::extensions::transparent as tze; @@ -117,6 +118,10 @@ pub struct TzeIn { pub witness: tze::Witness, } +fn to_io_error(_: std::num::TryFromIntError) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, "value out of range") +} + impl TzeIn { pub fn read(mut reader: &mut R) -> io::Result { let prevout = OutPoint::read(&mut reader)?; @@ -128,8 +133,8 @@ impl TzeIn { Ok(TzeIn { prevout, witness: tze::Witness { - extension_id, - mode, + extension_id: u32::try_from(extension_id).map_err(|e| to_io_error(e))?, + mode: u32::try_from(mode).map_err(|e| to_io_error(e))?, payload, }, }) @@ -138,8 +143,14 @@ impl TzeIn { pub fn write(&self, mut writer: W) -> io::Result<()> { self.prevout.write(&mut writer)?; - CompactSize::write(&mut writer, self.witness.extension_id)?; - CompactSize::write(&mut writer, self.witness.mode)?; + CompactSize::write( + &mut writer, + usize::try_from(self.witness.extension_id).map_err(|e| to_io_error(e))?, + )?; + CompactSize::write( + &mut writer, + usize::try_from(self.witness.mode).map_err(|e| to_io_error(e))?, + )?; Vector::write(&mut writer, &self.witness.payload, |w, b| w.write_u8(*b)) } } @@ -166,8 +177,8 @@ impl TzeOut { Ok(TzeOut { value, precondition: tze::Precondition { - extension_id, - mode, + extension_id: u32::try_from(extension_id).map_err(|e| to_io_error(e))?, + mode: u32::try_from(mode).map_err(|e| to_io_error(e))?, payload, }, }) @@ -176,8 +187,14 @@ impl TzeOut { pub fn write(&self, mut writer: W) -> io::Result<()> { writer.write_all(&self.value.to_i64_le_bytes())?; - CompactSize::write(&mut writer, self.precondition.extension_id)?; - CompactSize::write(&mut writer, self.precondition.mode)?; + CompactSize::write( + &mut writer, + usize::try_from(self.precondition.extension_id).map_err(|e| to_io_error(e))?, + )?; + CompactSize::write( + &mut writer, + usize::try_from(self.precondition.mode).map_err(|e| to_io_error(e))?, + )?; Vector::write(&mut writer, &self.precondition.payload, |w, b| { w.write_u8(*b) }) From 4be71efff59c1389866147d8ccab764e2ed1208c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 3 Jun 2020 20:39:43 -0600 Subject: [PATCH 13/14] Add signature hash over TZE data. --- zcash_primitives/src/transaction/builder.rs | 7 +- zcash_primitives/src/transaction/mod.rs | 2 +- zcash_primitives/src/transaction/sighash.rs | 100 +++++++++++++++----- zcash_primitives/src/transaction/tests.rs | 28 +++--- 4 files changed, 99 insertions(+), 38 deletions(-) diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 25433bff37..6be7ea83a5 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -26,7 +26,7 @@ use crate::{ amount::Amount, amount::DEFAULT_FEE, OutPoint, OutputDescription, SpendDescription, TxOut, TzeIn, TzeOut, }, - signature_hash_data, Transaction, TransactionData, SIGHASH_ALL, + signature_hash_data, SignableInput, Transaction, TransactionData, SIGHASH_ALL, }, JUBJUB, }; @@ -224,8 +224,7 @@ impl TransparentInputs { mtx, consensus_branch_id, SIGHASH_ALL, - Some((i, &info.coin.script_pubkey, info.coin.value)), - // tze equivalent is ??? + SignableInput::transparent(i, &info.coin.script_pubkey, info.coin.value), )); let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes"); @@ -695,7 +694,7 @@ impl<'a, R: RngCore + CryptoRng> Builder<'a, R> { &self.mtx, consensus_branch_id, SIGHASH_ALL, - None, + SignableInput::Shielded, )); // Create Sapling spendAuth and binding signatures diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index fc48a84874..f7e135bb55 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -17,7 +17,7 @@ mod sighash; #[cfg(test)] mod tests; -pub use self::sighash::{signature_hash, signature_hash_data, SIGHASH_ALL}; +pub use self::sighash::{signature_hash, signature_hash_data, SignableInput, SIGHASH_ALL}; use self::components::{ Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut, diff --git a/zcash_primitives/src/transaction/sighash.rs b/zcash_primitives/src/transaction/sighash.rs index f7f954a40c..51f5850975 100644 --- a/zcash_primitives/src/transaction/sighash.rs +++ b/zcash_primitives/src/transaction/sighash.rs @@ -7,7 +7,7 @@ use super::{ Transaction, TransactionData, NU4_VERSION_GROUP_ID, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID, }; -use crate::{consensus, legacy::Script}; +use crate::{consensus, extensions::transparent::Precondition, legacy::Script}; const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash"; const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash"; @@ -16,6 +16,7 @@ const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash"; const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash"; const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash"; const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash"; +const ZCASH_TZE_SIGNED_INPUT_DOMAIN_SEPARATOR: &[u8; 16] = b"ZcashTZE_SigHash"; pub const SIGHASH_ALL: u32 = 1; const SIGHASH_NONE: u32 = 2; @@ -151,11 +152,43 @@ fn shielded_outputs_hash(tx: &TransactionData) -> Blake2bHash { .hash(&data) } -pub fn signature_hash_data( +pub enum SignableInput<'a> { + Shielded, + Transparent { + index: usize, + script_code: &'a Script, + value: Amount, + }, + Tze { + index: usize, + precondition: &'a Precondition, + value: Amount, + }, +} + +impl<'a> SignableInput<'a> { + pub fn transparent(index: usize, script_code: &'a Script, value: Amount) -> Self { + SignableInput::Transparent { + index, + script_code, + value, + } + } + + pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self { + SignableInput::Tze { + index, + precondition, + value, + } + } +} + +pub fn signature_hash_data<'a>( tx: &TransactionData, consensus_branch_id: consensus::BranchId, hash_type: u32, - transparent_input: Option<(usize, &Script, Amount)>, + signable_input: SignableInput<'a>, ) -> Vec { let sigversion = SigHashVersion::from_tx(tx); match sigversion { @@ -182,17 +215,18 @@ pub fn signature_hash_data( && (hash_type & SIGHASH_MASK) != SIGHASH_NONE, sequence_hash(tx) ); + if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE && (hash_type & SIGHASH_MASK) != SIGHASH_NONE { h.update(outputs_hash(tx).as_ref()); - } else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE - && transparent_input.is_some() - && transparent_input.as_ref().unwrap().0 < tx.vout.len() - { - h.update( - single_output_hash(&tx.vout[transparent_input.as_ref().unwrap().0]).as_ref(), - ); + } else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE { + match signable_input { + SignableInput::Transparent { index, .. } if index < tx.vout.len() => { + h.update(single_output_hash(&tx.vout[index]).as_ref()) + } + _ => h.update(&[0; 32]), + }; } else { h.update(&[0; 32]); }; @@ -212,15 +246,37 @@ pub fn signature_hash_data( } update_u32!(h, hash_type, tmp); - if let Some((n, script_code, amount)) = transparent_input { - let mut data = vec![]; - tx.vin[n].prevout.write(&mut data).unwrap(); - script_code.write(&mut data).unwrap(); - data.extend_from_slice(&amount.to_i64_le_bytes()); - (&mut data) - .write_u32::(tx.vin[n].sequence) - .unwrap(); - h.update(&data); + match signable_input { + SignableInput::Transparent { + index, + script_code, + value, + } => { + let mut data = vec![]; + tx.vin[index].prevout.write(&mut data).unwrap(); + script_code.write(&mut data).unwrap(); + data.extend_from_slice(&value.to_i64_le_bytes()); + (&mut data) + .write_u32::(tx.vin[index].sequence) + .unwrap(); + h.update(&data); + } + SignableInput::Tze { + index, + precondition, + value, + } => { + let mut data = ZCASH_TZE_SIGNED_INPUT_DOMAIN_SEPARATOR.to_vec(); + + tx.tze_inputs[index].prevout.write(&mut data).unwrap(); + data.write_u32::(precondition.extension_id) + .unwrap(); + data.write_u32::(precondition.mode).unwrap(); + data.extend(&precondition.payload); + data.extend_from_slice(&value.to_i64_le_bytes()); + h.update(&data); + } + _ => (), } h.finalize().as_ref().to_vec() @@ -229,11 +285,11 @@ pub fn signature_hash_data( } } -pub fn signature_hash( +pub fn signature_hash<'a>( tx: &Transaction, consensus_branch_id: consensus::BranchId, hash_type: u32, - transparent_input: Option<(usize, &Script, Amount)>, + signable_input: SignableInput<'a>, ) -> Vec { - signature_hash_data(tx, consensus_branch_id, hash_type, transparent_input) + signature_hash_data(tx, consensus_branch_id, hash_type, signable_input) } diff --git a/zcash_primitives/src/transaction/tests.rs b/zcash_primitives/src/transaction/tests.rs index 2fc267c6f2..97a99a0a08 100644 --- a/zcash_primitives/src/transaction/tests.rs +++ b/zcash_primitives/src/transaction/tests.rs @@ -4,7 +4,11 @@ use rand_core::OsRng; use crate::jubjub::{fs::Fs, FixedGenerators}; -use super::{components::Amount, sighash::signature_hash, Transaction, TransactionData}; +use super::{ + components::Amount, + sighash::{signature_hash, SignableInput}, + Transaction, TransactionData, +}; use crate::redjubjub::PrivateKey; use crate::JUBJUB; @@ -75,16 +79,17 @@ mod data; fn zip_0143() { for tv in self::data::zip_0143::make_test_vectors() { let tx = Transaction::read(&tv.tx[..]).unwrap(); - let transparent_input = tv.transparent_input.map(|n| { - ( + let signable_input = match tv.transparent_input { + Some(n) => SignableInput::transparent( n as usize, &tv.script_code, Amount::from_nonnegative_i64(tv.amount).unwrap(), - ) - }); + ), + _ => SignableInput::Shielded, + }; assert_eq!( - signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, transparent_input), + signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input), tv.sighash ); } @@ -94,16 +99,17 @@ fn zip_0143() { fn zip_0243() { for tv in self::data::zip_0243::make_test_vectors() { let tx = Transaction::read(&tv.tx[..]).unwrap(); - let transparent_input = tv.transparent_input.map(|n| { - ( + let signable_input = match tv.transparent_input { + Some(n) => SignableInput::transparent( n as usize, &tv.script_code, Amount::from_nonnegative_i64(tv.amount).unwrap(), - ) - }); + ), + _ => SignableInput::Shielded, + }; assert_eq!( - signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, transparent_input), + signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input), tv.sighash ); } From 70e66c53662b1d4eaf0ed96801535ea3d01fbc48 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 5 Jun 2020 00:30:04 +1200 Subject: [PATCH 14/14] zcash_extensions: Only run demo builder test if we have parameters --- zcash_extensions/src/transparent/demo.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index ad1e1aeb88..d59b87abd0 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -615,7 +615,11 @@ mod tests { let preimage_1 = [1; 32]; let preimage_2 = [2; 32]; - let prover = LocalTxProver::with_default_location().unwrap(); + // Only run the test if we have the prover parameters. + let prover = match LocalTxProver::with_default_location() { + Some(prover) => prover, + None => return, + }; // // Opening transaction