Use rust feature flagging to allow conditionally enabling TZE-related code.

This also uses the test-dependencies feature flag for conditional
inclusion of proptest-related modules which may be used by
dependent crates for generation of samples in testing.
This commit is contained in:
Kris Nuttycombe 2020-09-23 10:53:48 -06:00 committed by Jack Grigg
parent debd5e6f5c
commit 790071953c
9 changed files with 316 additions and 157 deletions

View File

@ -10,7 +10,7 @@ edition = "2018"
[dependencies]
blake2b_simd = "0.5"
zcash_primitives = { version = "0.4.0", path = "../zcash_primitives" }
zcash_primitives = { version = "0.4.0", path = "../zcash_primitives", features = ["zfuture"] }
[dev-dependencies]
ff = "0.8"

View File

@ -47,6 +47,7 @@ rand_xorshift = "0.2"
[features]
transparent-inputs = ["ripemd160", "secp256k1"]
test-dependencies = ["proptest"]
zfuture = []
[[bench]]
name = "note_decryption"

View File

@ -164,6 +164,7 @@ impl Parameters for MainNetwork {
NetworkUpgrade::Blossom => Some(BlockHeight(653_600)),
NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)),
NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)),
#[cfg(feature = "zfuture")]
NetworkUpgrade::ZFuture => None,
}
}
@ -199,6 +200,7 @@ impl Parameters for TestNetwork {
NetworkUpgrade::Blossom => Some(BlockHeight(584_000)),
NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)),
NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)),
#[cfg(feature = "zfuture")]
NetworkUpgrade::ZFuture => None,
}
}
@ -294,6 +296,7 @@ pub enum NetworkUpgrade {
/// This upgrade is expected never to activate on mainnet;
/// it is intended for use in integration testing of functionality
/// that is a candidate for integration in a future network upgrade.
#[cfg(feature = "zfuture")]
ZFuture,
}
@ -305,6 +308,7 @@ impl fmt::Display for NetworkUpgrade {
NetworkUpgrade::Blossom => write!(f, "Blossom"),
NetworkUpgrade::Heartwood => write!(f, "Heartwood"),
NetworkUpgrade::Canopy => write!(f, "Canopy"),
#[cfg(feature = "zfuture")]
NetworkUpgrade::ZFuture => write!(f, "ZFUTURE"),
}
}
@ -318,6 +322,7 @@ impl NetworkUpgrade {
NetworkUpgrade::Blossom => BranchId::Blossom,
NetworkUpgrade::Heartwood => BranchId::Heartwood,
NetworkUpgrade::Canopy => BranchId::Canopy,
#[cfg(feature = "zfuture")]
NetworkUpgrade::ZFuture => BranchId::ZFuture,
}
}
@ -366,6 +371,7 @@ pub enum BranchId {
Canopy,
/// Candidates for future consensus rules; this branch will never
/// activate on mainnet.
#[cfg(feature = "zfuture")]
ZFuture,
}
@ -380,6 +386,7 @@ impl TryFrom<u32> for BranchId {
0x2bb4_0e60 => Ok(BranchId::Blossom),
0xf5b9_230b => Ok(BranchId::Heartwood),
0xe9ff_75a6 => Ok(BranchId::Canopy),
#[cfg(feature = "zfuture")]
0xffff_ffff => Ok(BranchId::ZFuture),
_ => Err("Unknown consensus branch ID"),
}
@ -395,6 +402,7 @@ impl From<BranchId> for u32 {
BranchId::Blossom => 0x2bb4_0e60,
BranchId::Heartwood => 0xf5b9_230b,
BranchId::Canopy => 0xe9ff_75a6,
#[cfg(feature = "zfuture")]
BranchId::ZFuture => 0xffff_ffff,
}
}

View File

@ -10,7 +10,6 @@
pub mod block;
pub mod consensus;
pub mod constants;
pub mod extensions;
pub mod group_hash;
pub mod keys;
pub mod legacy;
@ -26,5 +25,8 @@ pub mod transaction;
pub mod util;
pub mod zip32;
#[cfg(feature = "zfuture")]
pub mod extensions;
#[cfg(test)]
mod test_vectors;

View File

@ -1,6 +1,8 @@
//! Structs for building transactions.
#[cfg(feature = "zfuture")]
use std::boxed::Box;
use std::error;
use std::fmt;
use std::marker::PhantomData;
@ -10,7 +12,6 @@ use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
use crate::{
consensus::{self, BlockHeight},
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
keys::OutgoingViewingKey,
legacy::TransparentAddress,
merkle_tree::MerklePath,
@ -21,8 +22,7 @@ use crate::{
sapling::{spend_sig, Node},
transaction::{
components::{
amount::Amount, amount::DEFAULT_FEE, OutPoint, OutputDescription, SpendDescription,
TxOut, TzeIn, TzeOut,
amount::Amount, amount::DEFAULT_FEE, OutputDescription, SpendDescription, TxOut,
},
signature_hash_data, SignableInput, Transaction, TransactionData, SIGHASH_ALL,
},
@ -33,6 +33,15 @@ use crate::{
#[cfg(feature = "transparent-inputs")]
use crate::{legacy::Script, transaction::components::TxIn};
#[cfg(feature = "zfuture")]
use crate::{
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
transaction::components::{TzeIn, TzeOut},
};
#[cfg(any(feature = "transparent-inputs", feature = "zfuture"))]
use crate::transaction::components::OutPoint;
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
/// If there are any shielded inputs, always have at least two shielded outputs, padding
@ -265,15 +274,18 @@ impl TransparentInputs {
fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {}
}
#[cfg(feature = "zfuture")]
struct TzeInputInfo<'a, BuildCtx> {
prevout: TzeOut,
builder: Box<dyn FnOnce(&BuildCtx) -> Result<(u32, Vec<u8>), Error> + 'a>,
}
#[cfg(feature = "zfuture")]
struct TzeInputs<'a, BuildCtx> {
builders: Vec<TzeInputInfo<'a, BuildCtx>>,
}
#[cfg(feature = "zfuture")]
impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> {
fn default() -> Self {
TzeInputs { builders: vec![] }
@ -339,9 +351,10 @@ pub struct Builder<'a, P: consensus::Parameters, R: RngCore + CryptoRng> {
spends: Vec<SpendDescriptionInfo>,
outputs: Vec<SaplingOutput>,
transparent_inputs: TransparentInputs,
#[cfg(feature = "zfuture")]
tze_inputs: TzeInputs<'a, TransactionData>,
change_address: Option<(OutgoingViewingKey, PaymentAddress)>,
phantom: PhantomData<P>,
_phantom: &'a PhantomData<P>,
}
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
@ -372,6 +385,7 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
/// The transaction will be constructed and serialized according to the
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
/// integration testing of new features.
#[cfg(feature = "zfuture")]
pub fn new_zfuture(params: P, height: BlockHeight) -> Self {
Builder::new_with_rng_zfuture(params, height, OsRng)
}
@ -405,6 +419,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
/// The transaction will be constructed and serialized according to the
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
/// integration testing of new features.
#[cfg(feature = "zfuture")]
pub fn new_with_rng_zfuture(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(params, height, rng, TransactionData::zfuture())
}
@ -428,9 +443,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
spends: vec![],
outputs: vec![],
transparent_inputs: TransparentInputs::default(),
#[cfg(feature = "zfuture")]
tze_inputs: TzeInputs::default(),
change_address: None,
phantom: PhantomData,
_phantom: &PhantomData,
}
}
@ -558,7 +574,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
// Valid change
let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum()
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>()
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>();
#[cfg(feature = "zfuture")]
let change = change
+ self
.tze_inputs
.builders
@ -786,6 +805,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
};
// Create TZE input witnesses
#[cfg(feature = "zfuture")]
for (i, tze_in) in self.tze_inputs.builders.into_iter().enumerate() {
// The witness builder function should have cached/closed over whatever data was necessary for the
// witness to commit to at the time it was added to the transaction builder; here, it then computes those
@ -810,6 +830,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
}
}
#[cfg(feature = "zfuture")]
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
for Builder<'a, P, R>
{
@ -874,7 +895,10 @@ mod tests {
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use super::{Builder, Error, TzeInputs};
use super::{Builder, Error};
#[cfg(feature = "zfuture")]
use super::TzeInputs;
#[test]
fn fails_on_negative_output() {
@ -913,9 +937,10 @@ mod tests {
spends: vec![],
outputs: vec![],
transparent_inputs: TransparentInputs::default(),
#[cfg(feature = "zfuture")]
tze_inputs: TzeInputs::default(),
change_address: None,
phantom: PhantomData,
_phantom: &PhantomData,
};
// Create a tx with only t output. No binding_sig should be present

View File

@ -5,13 +5,21 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use std::convert::TryFrom;
use std::io::{self, Read, Write};
use crate::extensions::transparent as tze;
use crate::legacy::Script;
use crate::redjubjub::{PublicKey, Signature};
use crate::serialize::{CompactSize, Vector};
#[cfg(feature = "zfuture")]
use std::convert::TryFrom;
use crate::{
legacy::Script,
redjubjub::{PublicKey, Signature},
};
#[cfg(feature = "zfuture")]
use crate::{
extensions::transparent as tze,
serialize::{CompactSize, Vector},
};
pub mod amount;
pub use self::amount::Amount;
@ -121,19 +129,22 @@ impl TxOut {
}
}
#[cfg(feature = "zfuture")]
fn to_io_error(_: std::num::TryFromIntError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "value out of range")
}
#[derive(Clone, Debug, PartialEq)]
#[cfg(feature = "zfuture")]
pub struct TzeIn {
pub prevout: OutPoint,
pub witness: tze::Witness,
}
fn to_io_error(_: std::num::TryFromIntError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "value out of range")
}
/// Transaction encoding and decoding functions conforming to ZIP-222
///
/// https://zips.z.cash/zip-0222#encoding-in-transactions
#[cfg(feature = "zfuture")]
impl TzeIn {
/// Convenience constructor
pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self {
@ -198,11 +209,13 @@ impl TzeIn {
}
#[derive(Clone, Debug, PartialEq)]
#[cfg(feature = "zfuture")]
pub struct TzeOut {
pub value: Amount,
pub precondition: tze::Precondition,
}
#[cfg(feature = "zfuture")]
impl TzeOut {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let value = {

View File

@ -21,9 +21,10 @@ mod tests;
pub use self::sighash::{signature_hash, signature_hash_data, SignableInput, SIGHASH_ALL};
use self::components::{
Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut,
};
use self::components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut};
#[cfg(feature = "zfuture")]
use self::components::{TzeIn, TzeOut};
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
const OVERWINTER_TX_VERSION: u32 = 3;
@ -36,7 +37,9 @@ const SAPLING_TX_VERSION: u32 = 4;
/// using these constants should be inspected, and use of these constants
/// should be removed as appropriate in favor of the new consensus
/// transaction version and group.
#[cfg(feature = "zfuture")]
const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF;
#[cfg(feature = "zfuture")]
const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF;
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
@ -78,7 +81,9 @@ pub struct TransactionData {
pub version_group_id: u32,
pub vin: Vec<TxIn>,
pub vout: Vec<TxOut>,
#[cfg(feature = "zfuture")]
pub tze_inputs: Vec<TzeIn>,
#[cfg(feature = "zfuture")]
pub tze_outputs: Vec<TzeOut>,
pub lock_time: u32,
pub expiry_height: BlockHeight,
@ -100,9 +105,7 @@ impl std::fmt::Debug for TransactionData {
version = {:?},
version_group_id = {:?},
vin = {:?},
vout = {:?},
tze_inputs = {:?},
tze_outputs = {:?},
vout = {:?},{}
lock_time = {:?},
expiry_height = {:?},
value_balance = {:?},
@ -116,8 +119,19 @@ impl std::fmt::Debug for TransactionData {
self.version_group_id,
self.vin,
self.vout,
self.tze_inputs,
self.tze_outputs,
{
#[cfg(feature = "zfuture")]
{
format!(
"
tze_inputs = {:?},
tze_outputs = {:?},",
self.tze_inputs, self.tze_outputs
)
}
#[cfg(not(feature = "zfuture"))]
""
},
self.lock_time,
self.expiry_height,
self.value_balance,
@ -144,7 +158,9 @@ impl TransactionData {
version_group_id: SAPLING_VERSION_GROUP_ID,
vin: vec![],
vout: vec![],
#[cfg(feature = "zfuture")]
tze_inputs: vec![],
#[cfg(feature = "zfuture")]
tze_outputs: vec![],
lock_time: 0,
expiry_height: 0u32.into(),
@ -158,6 +174,7 @@ impl TransactionData {
}
}
#[cfg(feature = "zfuture")]
pub fn zfuture() -> Self {
TransactionData {
overwintered: true,
@ -227,9 +244,13 @@ impl Transaction {
let is_sapling_v4 = overwintered
&& version_group_id == SAPLING_VERSION_GROUP_ID
&& version == SAPLING_TX_VERSION;
#[cfg(feature = "zfuture")]
let has_tze = overwintered
&& version_group_id == ZFUTURE_VERSION_GROUP_ID
&& version == ZFUTURE_TX_VERSION;
#[cfg(not(feature = "zfuture"))]
let has_tze = false;
if overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
@ -240,6 +261,7 @@ impl Transaction {
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
#[cfg(feature = "zfuture")]
let (tze_inputs, tze_outputs) = if has_tze {
let wi = Vector::read(&mut reader, TzeIn::read)?;
let wo = Vector::read(&mut reader, TzeOut::read)?;
@ -306,7 +328,9 @@ impl Transaction {
version_group_id,
vin,
vout,
#[cfg(feature = "zfuture")]
tze_inputs,
#[cfg(feature = "zfuture")]
tze_outputs,
lock_time,
expiry_height,
@ -333,9 +357,13 @@ impl Transaction {
let is_sapling_v4 = self.overwintered
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
&& self.version == SAPLING_TX_VERSION;
#[cfg(feature = "zfuture")]
let has_tze = self.overwintered
&& self.version_group_id == ZFUTURE_VERSION_GROUP_ID
&& self.version == ZFUTURE_TX_VERSION;
#[cfg(not(feature = "zfuture"))]
let has_tze = false;
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
@ -346,6 +374,7 @@ impl Transaction {
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
#[cfg(feature = "zfuture")]
if has_tze {
Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.tze_outputs, |w, e| e.write(w))?;
@ -422,3 +451,147 @@ impl Transaction {
Ok(())
}
}
#[cfg(feature = "test-dependencies")]
pub mod testing {
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::select;
use crate::{consensus::BranchId, legacy::Script};
#[cfg(feature = "zfuture")]
use crate::extensions::transparent as tze;
use super::{
components::{amount::MAX_MONEY, Amount, OutPoint, TxIn, TxOut},
Transaction, TransactionData, OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID,
SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID,
};
#[cfg(feature = "zfuture")]
use super::{
components::{TzeIn, TzeOut},
ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID,
};
pub const VALID_OPCODES: [u8; 8] = [
0x00, // OP_FALSE,
0x51, // OP_1,
0x52, // OP_2,
0x53, // OP_3,
0xac, // OP_CHECKSIG,
0x63, // OP_IF,
0x65, // OP_VERIF,
0x6a, // OP_RETURN,
];
prop_compose! {
pub fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..(100 as u32)) -> OutPoint {
OutPoint::new(hash, n)
}
}
prop_compose! {
pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
Script(v)
}
}
prop_compose! {
pub fn arb_txin()(prevout in arb_outpoint(), script_sig in arb_script(), sequence in any::<u32>()) -> TxIn {
TxIn { prevout, script_sig, sequence }
}
}
prop_compose! {
pub fn arb_amount()(value in 0..MAX_MONEY) -> Amount {
Amount::from_i64(value).unwrap()
}
}
prop_compose! {
pub fn arb_txout()(value in arb_amount(), script_pubkey in arb_script()) -> TxOut {
TxOut { value, script_pubkey }
}
}
#[cfg(feature = "zfuture")]
prop_compose! {
pub fn arb_witness()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Witness {
tze::Witness { extension_id, mode, payload }
}
}
#[cfg(feature = "zfuture")]
prop_compose! {
pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn {
TzeIn { prevout, witness }
}
}
#[cfg(feature = "zfuture")]
prop_compose! {
pub fn arb_precondition()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Precondition {
tze::Precondition { extension_id, mode, payload }
}
}
#[cfg(feature = "zfuture")]
prop_compose! {
fn arb_tzeout()(value in arb_amount(), precondition in arb_precondition()) -> TzeOut {
TzeOut { value, precondition }
}
}
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = (u32, u32)> {
match branch_id {
BranchId::Sprout => (1..(2 as u32)).prop_map(|i| (i, 0)).boxed(),
BranchId::Overwinter => {
Just((OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID)).boxed()
}
#[cfg(feature = "zfuture")]
BranchId::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID)).boxed(),
_otherwise => Just((SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID)).boxed(),
}
}
#[cfg(feature = "zfuture")]
prop_compose! {
pub fn arb_txdata(branch_id: BranchId)(
(version, version_group_id) in tx_versions(branch_id),
vin in vec(arb_txin(), 0..10),
vout in vec(arb_txout(), 0..10),
tze_inputs in vec(arb_tzein(), 0..10),
tze_outputs in vec(arb_tzeout(), 0..10),
lock_time in any::<u32>(),
expiry_height in any::<u32>(),
value_balance in arb_amount(),
) -> TransactionData {
TransactionData {
overwintered: branch_id != BranchId::Sprout,
version,
version_group_id,
vin, vout,
tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] },
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
lock_time,
expiry_height: expiry_height.into(),
value_balance,
shielded_spends: vec![], //FIXME
shielded_outputs: vec![], //FIXME
joinsplits: vec![], //FIXME
joinsplit_pubkey: None, //FIXME
joinsplit_sig: None, //FIXME
binding_sig: None, //FIXME
}
}
}
#[cfg(feature = "zfuture")]
prop_compose! {
pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
Transaction::from_data(tx_data).unwrap()
}
}
}

View File

@ -1,3 +1,4 @@
#[cfg(feature = "zfuture")]
use std::convert::TryInto;
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
@ -5,19 +6,24 @@ use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use crate::{consensus, legacy::Script};
#[cfg(feature = "zfuture")]
use crate::{
consensus,
extensions::transparent::Precondition,
legacy::Script,
serialize::{CompactSize, Vector},
};
use super::{
components::{
Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut,
},
components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
SAPLING_VERSION_GROUP_ID, ZFUTURE_VERSION_GROUP_ID,
SAPLING_VERSION_GROUP_ID,
};
#[cfg(feature = "zfuture")]
use super::{
components::{TzeIn, TzeOut},
ZFUTURE_VERSION_GROUP_ID,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
@ -27,10 +33,15 @@ 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";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash_TzeInsHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashTzeOutsHash";
#[cfg(feature = "zfuture")]
const ZCASH_TZE_SIGNED_INPUT_TAG: &[u8; 1] = &[0x00];
#[cfg(feature = "zfuture")]
const ZCASH_TRANSPARENT_SIGNED_INPUT_TAG: &[u8; 1] = &[0x01];
pub const SIGHASH_ALL: u32 = 1;
@ -56,11 +67,14 @@ macro_rules! update_hash {
};
}
/// This is a private enum; when `cfg(feature = "zfuture")` is not
/// enabled, SigHashVersion::ZFuture is not constructable.
#[derive(PartialEq)]
enum SigHashVersion {
Sprout,
Overwinter,
Sapling,
#[cfg(feature = "zfuture")]
ZFuture,
}
@ -70,6 +84,7 @@ impl SigHashVersion {
match tx.version_group_id {
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
#[cfg(feature = "zfuture")]
ZFUTURE_VERSION_GROUP_ID => SigHashVersion::ZFuture,
_ => unimplemented!(),
}
@ -172,6 +187,7 @@ fn shielded_outputs_hash(shielded_outputs: &[OutputDescription]) -> Blake2bHash
.hash(&data)
}
#[cfg(feature = "zfuture")]
fn tze_inputs_hash(tze_inputs: &[TzeIn]) -> Blake2bHash {
let mut data = vec![];
for tzein in tze_inputs {
@ -183,6 +199,7 @@ fn tze_inputs_hash(tze_inputs: &[TzeIn]) -> Blake2bHash {
.hash(&data)
}
#[cfg(feature = "zfuture")]
fn tze_outputs_hash(tze_outputs: &[TzeOut]) -> Blake2bHash {
let mut data = vec![];
for tzeout in tze_outputs {
@ -201,6 +218,7 @@ pub enum SignableInput<'a> {
script_code: &'a Script,
value: Amount,
},
#[cfg(feature = "zfuture")]
Tze {
index: usize,
precondition: &'a Precondition,
@ -217,6 +235,7 @@ impl<'a> SignableInput<'a> {
}
}
#[cfg(feature = "zfuture")]
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
SignableInput::Tze {
index,
@ -234,7 +253,14 @@ pub fn signature_hash_data<'a>(
) -> Vec<u8> {
let sigversion = SigHashVersion::from_tx(tx);
match sigversion {
SigHashVersion::Overwinter | SigHashVersion::Sapling | SigHashVersion::ZFuture => {
SigHashVersion::Sprout => unimplemented!(),
// ZIP-243 is implemented as a patch to ZIP-143; ZFuture is temporarily added
// as an additional patch to ZIP-243 but that match will likely be removed
// due to the need for transaction malleability fixes for TZE deployment.
//
// SigHashVersion::Overwinter | SigHashVersion::Sapling | SigHashVersion::ZFuture => {
_ => {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
@ -276,6 +302,7 @@ pub fn signature_hash_data<'a>(
} else {
h.update(&[0; 32]);
};
#[cfg(feature = "zfuture")]
if sigversion == SigHashVersion::ZFuture {
update_hash!(
h,
@ -293,7 +320,14 @@ pub fn signature_hash_data<'a>(
!tx.joinsplits.is_empty(),
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
);
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::ZFuture {
if sigversion == SigHashVersion::Sapling || {
#[cfg(feature = "zfuture")]
{
sigversion == SigHashVersion::ZFuture
}
#[cfg(not(feature = "zfuture"))]
false
} {
update_hash!(
h,
!tx.shielded_spends.is_empty(),
@ -307,7 +341,14 @@ pub fn signature_hash_data<'a>(
}
update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height.into(), tmp);
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::ZFuture {
if sigversion == SigHashVersion::Sapling || {
#[cfg(feature = "zfuture")]
{
sigversion == SigHashVersion::ZFuture
}
#[cfg(not(feature = "zfuture"))]
false
} {
h.update(&tx.value_balance.to_i64_le_bytes());
}
update_u32!(h, hash_type, tmp);
@ -318,6 +359,7 @@ pub fn signature_hash_data<'a>(
script_code,
value,
} => {
#[cfg(feature = "zfuture")]
let mut data = if sigversion == SigHashVersion::ZFuture {
// domain separation here is to avoid collision attacks
// between transparent and TZE inputs.
@ -326,6 +368,9 @@ pub fn signature_hash_data<'a>(
vec![]
};
#[cfg(not(feature = "zfuture"))]
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());
@ -335,6 +380,7 @@ pub fn signature_hash_data<'a>(
h.update(&data);
}
#[cfg(feature = "zfuture")]
SignableInput::Tze {
index,
precondition,
@ -353,6 +399,7 @@ pub fn signature_hash_data<'a>(
h.update(&data);
}
#[cfg(feature = "zfuture")]
SignableInput::Tze { .. } => {
panic!("A request has been made to sign a TZE input, but the signature hash version is not ZFuture");
}
@ -362,7 +409,6 @@ pub fn signature_hash_data<'a>(
h.finalize().as_ref().to_vec()
}
SigHashVersion::Sprout => unimplemented!(),
}
}

View File

@ -1,133 +1,22 @@
use ff::Field;
use rand_core::OsRng;
use proptest::collection::vec;
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
use proptest::prelude::*;
use proptest::sample::select;
use crate::{
consensus::BranchId, constants::SPENDING_KEY_GENERATOR, extensions::transparent as tze,
legacy::Script, redjubjub::PrivateKey,
};
use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey};
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
use crate::consensus::BranchId;
use super::{
components::amount::MAX_MONEY,
components::{Amount, OutPoint, TxIn, TxOut, TzeIn, TzeOut},
components::Amount,
sighash::{signature_hash, SignableInput},
Transaction, TransactionData, OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID,
SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID, ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID,
Transaction, TransactionData,
};
prop_compose! {
fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..(100 as u32)) -> OutPoint {
OutPoint::new(hash, n)
}
}
const VALID_OPCODES: [u8; 8] = [
0x00, // OP_FALSE,
0x51, // OP_1,
0x52, // OP_2,
0x53, // OP_3,
0xac, // OP_CHECKSIG,
0x63, // OP_IF,
0x65, // OP_VERIF,
0x6a, // OP_RETURN,
];
prop_compose! {
fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
Script(v)
}
}
prop_compose! {
fn arb_txin()(prevout in arb_outpoint(), script_sig in arb_script(), sequence in any::<u32>()) -> TxIn {
TxIn { prevout, script_sig, sequence }
}
}
prop_compose! {
fn arb_amount()(value in 0..MAX_MONEY) -> Amount {
Amount::from_i64(value).unwrap()
}
}
prop_compose! {
fn arb_txout()(value in arb_amount(), script_pubkey in arb_script()) -> TxOut {
TxOut { value, script_pubkey }
}
}
prop_compose! {
fn arb_witness()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Witness {
tze::Witness { extension_id, mode, payload }
}
}
prop_compose! {
fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn {
TzeIn { prevout, witness }
}
}
prop_compose! {
fn arb_precondition()(extension_id in 0..(100 as u32), mode in (0..100 as u32), payload in vec(any::<u8>(), 32..256)) -> tze::Precondition {
tze::Precondition { extension_id, mode, payload }
}
}
prop_compose! {
fn arb_tzeout()(value in arb_amount(), precondition in arb_precondition()) -> TzeOut {
TzeOut { value, precondition }
}
}
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = (u32, u32)> {
match branch_id {
BranchId::Sprout => (1..(2 as u32)).prop_map(|i| (i, 0)).boxed(),
BranchId::Overwinter => Just((OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID)).boxed(),
BranchId::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID)).boxed(),
_otherwise => Just((SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID)).boxed(),
}
}
prop_compose! {
fn arb_txdata(branch_id: BranchId)(
(version, version_group_id) in tx_versions(branch_id),
vin in vec(arb_txin(), 0..10),
vout in vec(arb_txout(), 0..10),
tze_inputs in vec(arb_tzein(), 0..10),
tze_outputs in vec(arb_tzeout(), 0..10),
lock_time in any::<u32>(),
expiry_height in any::<u32>(),
value_balance in arb_amount(),
) -> TransactionData {
TransactionData {
overwintered: branch_id != BranchId::Sprout,
version,
version_group_id,
vin, vout,
tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] },
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
lock_time,
expiry_height: expiry_height.into(),
value_balance,
shielded_spends: vec![], //FIXME
shielded_outputs: vec![], //FIXME
joinsplits: vec![], //FIXME
joinsplit_pubkey: None, //FIXME
joinsplit_sig: None, //FIXME
binding_sig: None, //FIXME
}
}
}
prop_compose! {
fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
Transaction::from_data(tx_data).unwrap()
}
}
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
use super::testing::arb_tx;
#[test]
fn tx_read_write() {
@ -186,6 +75,7 @@ fn tx_write_rejects_unexpected_binding_sig() {
}
}
#[cfg(all(feature = "zfuture", feature = "test-dependencies"))]
proptest! {
#[test]
fn test_tze_roundtrip(tx in arb_tx(BranchId::ZFuture)) {
@ -207,6 +97,7 @@ proptest! {
}
#[test]
#[cfg(feature = "zfuture")]
fn test_tze_tx_parse() {
let txn_bytes = vec![
0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x52, 0x52, 0x52, 0x52,