Merge pull request #318 from nuttycom/tx_version_enum

Add TxVersion enum to make interacting with transaction version values safer.
This commit is contained in:
Kris Nuttycombe 2021-02-04 12:47:12 -07:00 committed by GitHub
commit b37b5876f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 326 additions and 282 deletions

View File

@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 23a823e9da7e6ae62a79153ab97362dd9d81b8d9eafc396c87870dfa8aa7354c # shrinks to tx = Transaction { txid: TxId([67, 236, 122, 87, 159, 85, 97, 164, 42, 126, 150, 55, 173, 65, 86, 103, 39, 53, 166, 88, 190, 39, 82, 24, 24, 1, 247, 35, 186, 51, 22, 210]), data: TransactionData( version = Sprout(1), vin = [], vout = [], lock_time = 0, expiry_height = BlockHeight(0), value_balance = Amount(1), shielded_spends = [], shielded_outputs = [], joinsplits = [], joinsplit_pubkey = None, binding_sig = None) }

View File

@ -53,6 +53,103 @@ impl fmt::Display for TxId {
} }
} }
/// The set of defined transaction format versions.
///
/// This is serialized in the first four or eight bytes of the transaction format, and
/// represents valid combinations of the `(overwintered, version, version_group_id)`
/// transaction fields. Note that this is not dependent on epoch, only on transaction encoding.
/// For example, if a particular epoch defines a new transaction version but also allows the
/// previous version, then only the new version would be added to this enum.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TxVersion {
Sprout(u32),
Overwinter,
Sapling,
#[cfg(feature = "zfuture")]
ZFuture,
}
impl TxVersion {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let header = reader.read_u32::<LittleEndian>()?;
let overwintered = (header >> 31) == 1;
let version = header & 0x7FFFFFFF;
if overwintered {
match (version, reader.read_u32::<LittleEndian>()?) {
(OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
(SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
#[cfg(feature = "zfuture")]
(ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
)),
}
} else if version >= 1 {
Ok(TxVersion::Sprout(version))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
))
}
}
pub fn header(&self) -> u32 {
// After Sprout, the overwintered bit is always set.
let overwintered = match self {
TxVersion::Sprout(_) => 0,
_ => 1 << 31,
};
overwintered
| match self {
TxVersion::Sprout(v) => *v,
TxVersion::Overwinter => OVERWINTER_TX_VERSION,
TxVersion::Sapling => SAPLING_TX_VERSION,
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => ZFUTURE_TX_VERSION,
}
}
pub fn version_group_id(&self) -> u32 {
match self {
TxVersion::Sprout(_) => 0,
TxVersion::Overwinter => OVERWINTER_VERSION_GROUP_ID,
TxVersion::Sapling => SAPLING_VERSION_GROUP_ID,
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => ZFUTURE_VERSION_GROUP_ID,
}
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_u32::<LittleEndian>(self.header())?;
match self {
TxVersion::Sprout(_) => Ok(()),
_ => writer.write_u32::<LittleEndian>(self.version_group_id()),
}
}
pub fn has_sprout(&self) -> bool {
match self {
TxVersion::Sprout(v) => *v >= 2u32,
TxVersion::Overwinter | TxVersion::Sapling => true,
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => true,
}
}
pub fn uses_groth_proofs(&self) -> bool {
match self {
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
TxVersion::Sapling => true,
#[cfg(feature = "zfuture")]
TxVersion::ZFuture => true,
}
}
}
/// A Zcash transaction. /// A Zcash transaction.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Transaction { pub struct Transaction {
@ -76,9 +173,7 @@ impl PartialEq for Transaction {
#[derive(Clone)] #[derive(Clone)]
pub struct TransactionData { pub struct TransactionData {
pub overwintered: bool, pub version: TxVersion,
pub version: u32,
pub version_group_id: u32,
pub vin: Vec<TxIn>, pub vin: Vec<TxIn>,
pub vout: Vec<TxOut>, pub vout: Vec<TxOut>,
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
@ -101,9 +196,7 @@ impl std::fmt::Debug for TransactionData {
write!( write!(
f, f,
"TransactionData( "TransactionData(
overwintered = {:?},
version = {:?}, version = {:?},
version_group_id = {:?},
vin = {:?}, vin = {:?},
vout = {:?},{} vout = {:?},{}
lock_time = {:?}, lock_time = {:?},
@ -114,9 +207,7 @@ impl std::fmt::Debug for TransactionData {
joinsplits = {:?}, joinsplits = {:?},
joinsplit_pubkey = {:?}, joinsplit_pubkey = {:?},
binding_sig = {:?})", binding_sig = {:?})",
self.overwintered,
self.version, self.version,
self.version_group_id,
self.vin, self.vin,
self.vout, self.vout,
{ {
@ -153,9 +244,7 @@ impl Default for TransactionData {
impl TransactionData { impl TransactionData {
pub fn new() -> Self { pub fn new() -> Self {
TransactionData { TransactionData {
overwintered: true, version: TxVersion::Sapling,
version: SAPLING_TX_VERSION,
version_group_id: SAPLING_VERSION_GROUP_ID,
vin: vec![], vin: vec![],
vout: vec![], vout: vec![],
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
@ -177,9 +266,7 @@ impl TransactionData {
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
pub fn zfuture() -> Self { pub fn zfuture() -> Self {
TransactionData { TransactionData {
overwintered: true, version: TxVersion::ZFuture,
version: ZFUTURE_TX_VERSION,
version_group_id: ZFUTURE_VERSION_GROUP_ID,
vin: vec![], vin: vec![],
vout: vec![], vout: vec![],
tze_inputs: vec![], tze_inputs: vec![],
@ -196,14 +283,6 @@ impl TransactionData {
} }
} }
fn header(&self) -> u32 {
let mut header = self.version;
if self.overwintered {
header |= 1 << 31;
}
header
}
pub fn freeze(self) -> io::Result<Transaction> { pub fn freeze(self) -> io::Result<Transaction> {
Transaction::from_data(self) Transaction::from_data(self)
} }
@ -228,39 +307,18 @@ impl Transaction {
pub fn read<R: Read>(reader: R) -> io::Result<Self> { pub fn read<R: Read>(reader: R) -> io::Result<Self> {
let mut reader = HashReader::new(reader); let mut reader = HashReader::new(reader);
let header = reader.read_u32::<LittleEndian>()?; let version = TxVersion::read(&mut reader)?;
let overwintered = (header >> 31) == 1; let is_overwinter_v3 = version == TxVersion::Overwinter;
let version = header & 0x7FFFFFFF; let is_sapling_v4 = version == TxVersion::Sapling;
let version_group_id = if overwintered {
reader.read_u32::<LittleEndian>()?
} else {
0
};
let is_overwinter_v3 = overwintered
&& version_group_id == OVERWINTER_VERSION_GROUP_ID
&& version == OVERWINTER_TX_VERSION;
let is_sapling_v4 = overwintered
&& version_group_id == SAPLING_VERSION_GROUP_ID
&& version == SAPLING_TX_VERSION;
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
let has_tze = overwintered let has_tze = version == TxVersion::ZFuture;
&& version_group_id == ZFUTURE_VERSION_GROUP_ID
&& version == ZFUTURE_TX_VERSION;
#[cfg(not(feature = "zfuture"))] #[cfg(not(feature = "zfuture"))]
let has_tze = false; let has_tze = false;
if overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
));
}
let vin = Vector::read(&mut reader, TxIn::read)?; let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?; let vout = Vector::read(&mut reader, TxOut::read)?;
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
let (tze_inputs, tze_outputs) = if has_tze { let (tze_inputs, tze_outputs) = if has_tze {
let wi = Vector::read(&mut reader, TzeIn::read)?; let wi = Vector::read(&mut reader, TzeIn::read)?;
@ -291,9 +349,9 @@ impl Transaction {
(Amount::zero(), vec![], vec![]) (Amount::zero(), vec![], vec![])
}; };
let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version >= 2 { let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version.has_sprout() {
let jss = Vector::read(&mut reader, |r| { let jss = Vector::read(&mut reader, |r| {
JSDescription::read(r, overwintered && version >= SAPLING_TX_VERSION) JSDescription::read(r, version.uses_groth_proofs())
})?; })?;
let (pubkey, sig) = if !jss.is_empty() { let (pubkey, sig) = if !jss.is_empty() {
let mut joinsplit_pubkey = [0; 32]; let mut joinsplit_pubkey = [0; 32];
@ -323,9 +381,7 @@ impl Transaction {
Ok(Transaction { Ok(Transaction {
txid: TxId(txid), txid: TxId(txid),
data: TransactionData { data: TransactionData {
overwintered,
version, version,
version_group_id,
vin, vin,
vout, vout,
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
@ -346,32 +402,15 @@ impl Transaction {
} }
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> { pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_u32::<LittleEndian>(self.header())?; self.version.write(&mut writer)?;
if self.overwintered {
writer.write_u32::<LittleEndian>(self.version_group_id)?;
}
let is_overwinter_v3 = self.overwintered
&& self.version_group_id == OVERWINTER_VERSION_GROUP_ID
&& self.version == OVERWINTER_TX_VERSION;
let is_sapling_v4 = self.overwintered
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
&& self.version == SAPLING_TX_VERSION;
let is_overwinter_v3 = self.version == TxVersion::Overwinter;
let is_sapling_v4 = self.version == TxVersion::Sapling;
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
let has_tze = self.overwintered let has_tze = self.version == TxVersion::ZFuture;
&& self.version_group_id == ZFUTURE_VERSION_GROUP_ID
&& self.version == ZFUTURE_TX_VERSION;
#[cfg(not(feature = "zfuture"))] #[cfg(not(feature = "zfuture"))]
let has_tze = false; let has_tze = false;
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
));
}
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?; Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?; Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
@ -390,7 +429,7 @@ impl Transaction {
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?; Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
} }
if self.version >= 2 { if self.version.has_sprout() {
Vector::write(&mut writer, &self.joinsplits, |w, e| e.write(w))?; Vector::write(&mut writer, &self.joinsplits, |w, e| e.write(w))?;
if !self.joinsplits.is_empty() { if !self.joinsplits.is_empty() {
match self.joinsplit_pubkey { match self.joinsplit_pubkey {
@ -414,7 +453,7 @@ impl Transaction {
} }
} }
if self.version < 2 || self.joinsplits.is_empty() { if !self.version.has_sprout() || self.joinsplits.is_empty() {
if self.joinsplit_pubkey.is_some() { if self.joinsplit_pubkey.is_some() {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
@ -452,7 +491,7 @@ impl Transaction {
} }
} }
#[cfg(feature = "test-dependencies")] #[cfg(any(test, feature = "test-dependencies"))]
pub mod testing { pub mod testing {
use proptest::collection::vec; use proptest::collection::vec;
use proptest::prelude::*; use proptest::prelude::*;
@ -465,15 +504,11 @@ pub mod testing {
use super::{ use super::{
components::{amount::MAX_MONEY, Amount, OutPoint, TxIn, TxOut}, components::{amount::MAX_MONEY, Amount, OutPoint, TxIn, TxOut},
Transaction, TransactionData, OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID, Transaction, TransactionData, TxVersion,
SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID,
}; };
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
use super::{ use super::components::{TzeIn, TzeOut};
components::{TzeIn, TzeOut},
ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID,
};
pub const VALID_OPCODES: [u8; 8] = [ pub const VALID_OPCODES: [u8; 8] = [
0x00, // OP_FALSE, 0x00, // OP_FALSE,
@ -487,7 +522,7 @@ pub mod testing {
]; ];
prop_compose! { prop_compose! {
pub fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..(100 as u32)) -> OutPoint { pub fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..100u32) -> OutPoint {
OutPoint::new(hash, n) OutPoint::new(hash, n)
} }
} }
@ -518,7 +553,7 @@ pub mod testing {
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
prop_compose! { 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 { pub fn arb_witness()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::<u8>(), 32..256)) -> tze::Witness {
tze::Witness { extension_id, mode, payload } tze::Witness { extension_id, mode, payload }
} }
} }
@ -532,7 +567,7 @@ pub mod testing {
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
prop_compose! { 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 { pub fn arb_precondition()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::<u8>(), 32..256)) -> tze::Precondition {
tze::Precondition { extension_id, mode, payload } tze::Precondition { extension_id, mode, payload }
} }
} }
@ -544,22 +579,33 @@ pub mod testing {
} }
} }
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = (u32, u32)> { pub fn arb_branch_id() -> impl Strategy<Value = BranchId> {
match branch_id { select(vec![
BranchId::Sprout => (1..(2 as u32)).prop_map(|i| (i, 0)).boxed(), BranchId::Sprout,
BranchId::Overwinter => { BranchId::Overwinter,
Just((OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID)).boxed() BranchId::Sapling,
} BranchId::Blossom,
BranchId::Heartwood,
BranchId::Canopy,
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
BranchId::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID)).boxed(), BranchId::ZFuture,
_otherwise => Just((SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID)).boxed(), ])
}
fn tx_versions(branch_id: BranchId) -> impl Strategy<Value = TxVersion> {
match branch_id {
BranchId::Sprout => (1..=2u32).prop_map(TxVersion::Sprout).boxed(),
BranchId::Overwinter => Just(TxVersion::Overwinter).boxed(),
#[cfg(feature = "zfuture")]
BranchId::ZFuture => Just(TxVersion::ZFuture).boxed(),
_otherwise => Just(TxVersion::Sapling).boxed(),
} }
} }
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
prop_compose! { prop_compose! {
pub fn arb_txdata(branch_id: BranchId)( pub fn arb_txdata(branch_id: BranchId)(
(version, version_group_id) in tx_versions(branch_id), version in tx_versions(branch_id),
vin in vec(arb_txin(), 0..10), vin in vec(arb_txin(), 0..10),
vout in vec(arb_txout(), 0..10), vout in vec(arb_txout(), 0..10),
tze_inputs in vec(arb_tzein(), 0..10), tze_inputs in vec(arb_tzein(), 0..10),
@ -569,15 +615,45 @@ pub mod testing {
value_balance in arb_amount(), value_balance in arb_amount(),
) -> TransactionData { ) -> TransactionData {
TransactionData { TransactionData {
overwintered: branch_id != BranchId::Sprout,
version, version,
version_group_id,
vin, vout, vin, vout,
tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] }, tze_inputs: if branch_id == BranchId::ZFuture { tze_inputs } else { vec![] },
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] }, tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
lock_time, lock_time,
expiry_height: expiry_height.into(), expiry_height: expiry_height.into(),
value_balance, value_balance: match version {
TxVersion::Sprout(_) | TxVersion::Overwinter => Amount::zero(),
_ => 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(not(feature = "zfuture"))]
prop_compose! {
pub fn arb_txdata(branch_id: BranchId)(
version in tx_versions(branch_id),
vin in vec(arb_txin(), 0..10),
vout in vec(arb_txout(), 0..10),
lock_time in any::<u32>(),
expiry_height in any::<u32>(),
value_balance in arb_amount(),
) -> TransactionData {
TransactionData {
version,
vin, vout,
lock_time,
expiry_height: expiry_height.into(),
value_balance: match version {
TxVersion::Sprout(_) | TxVersion::Overwinter => Amount::zero(),
_ => value_balance,
},
shielded_spends: vec![], //FIXME shielded_spends: vec![], //FIXME
shielded_outputs: vec![], //FIXME shielded_outputs: vec![], //FIXME
joinsplits: vec![], //FIXME joinsplits: vec![], //FIXME
@ -588,7 +664,6 @@ pub mod testing {
} }
} }
#[cfg(feature = "zfuture")]
prop_compose! { prop_compose! {
pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction { pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
Transaction::from_data(tx_data).unwrap() Transaction::from_data(tx_data).unwrap()

View File

@ -16,15 +16,11 @@ use crate::{
use super::{ use super::{
components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut}, components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION, Transaction, TransactionData, TxVersion,
SAPLING_VERSION_GROUP_ID,
}; };
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
use super::{ use super::components::{TzeIn, TzeOut};
components::{TzeIn, TzeOut},
ZFUTURE_VERSION_GROUP_ID,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash"; const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash"; const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
@ -67,47 +63,27 @@ macro_rules! update_hash {
}; };
} }
/// This is a private enum; when `cfg(feature = "zfuture")` is not fn has_overwinter_components(version: &TxVersion) -> bool {
/// enabled, SigHashVersion::ZFuture is not constructable. match version {
#[derive(PartialEq)] TxVersion::Sprout(_) => false,
enum SigHashVersion { _ => true,
Sprout,
Overwinter,
Sapling,
#[cfg(feature = "zfuture")]
ZFuture,
}
impl SigHashVersion {
fn from_tx(tx: &TransactionData) -> Self {
if tx.overwintered {
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!(),
}
} else {
SigHashVersion::Sprout
} }
} }
fn has_sapling_components(&self) -> bool { fn has_sapling_components(version: &TxVersion) -> bool {
match self { match version {
SigHashVersion::Sprout | SigHashVersion::Overwinter => false, TxVersion::Sprout(_) | TxVersion::Overwinter => false,
_ => true, _ => true,
} }
} }
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
fn has_tze_components(&self) -> bool { fn has_tze_components(version: &TxVersion) -> bool {
match self { match version {
SigHashVersion::ZFuture => true, TxVersion::ZFuture => true,
_ => false, _ => false,
} }
} }
}
fn prevout_hash(vin: &[TxIn]) -> Blake2bHash { fn prevout_hash(vin: &[TxIn]) -> Blake2bHash {
let mut data = Vec::with_capacity(vin.len() * 36); let mut data = Vec::with_capacity(vin.len() * 36);
@ -154,16 +130,16 @@ fn single_output_hash(tx_out: &TxOut) -> Blake2bHash {
} }
fn joinsplits_hash( fn joinsplits_hash(
txversion: u32, txversion: TxVersion,
joinsplits: &[JSDescription], joinsplits: &[JSDescription],
joinsplit_pubkey: &[u8; 32], joinsplit_pubkey: &[u8; 32],
) -> Blake2bHash { ) -> Blake2bHash {
let mut data = Vec::with_capacity( let mut data = Vec::with_capacity(
joinsplits.len() joinsplits.len()
* if txversion < SAPLING_TX_VERSION { * if txversion.uses_groth_proofs() {
1802 // JSDescription with PHGR13 proof
} else {
1698 // JSDescription with Groth16 proof 1698 // JSDescription with Groth16 proof
} else {
1802 // JSDescription with PHGR13 proof
}, },
); );
for js in joinsplits { for js in joinsplits {
@ -266,16 +242,7 @@ pub fn signature_hash_data<'a>(
hash_type: u32, hash_type: u32,
signable_input: SignableInput<'a>, signable_input: SignableInput<'a>,
) -> Vec<u8> { ) -> Vec<u8> {
let sigversion = SigHashVersion::from_tx(tx); if has_overwinter_components(&tx.version) {
match sigversion {
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]; let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX); (&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..]) (&mut personal[12..])
@ -288,8 +255,8 @@ pub fn signature_hash_data<'a>(
.to_state(); .to_state();
let mut tmp = [0; 8]; let mut tmp = [0; 8];
update_u32!(h, tx.header(), tmp); update_u32!(h, tx.version.header(), tmp);
update_u32!(h, tx.version_group_id, tmp); update_u32!(h, tx.version.version_group_id(), tmp);
update_hash!( update_hash!(
h, h,
hash_type & SIGHASH_ANYONECANPAY == 0, hash_type & SIGHASH_ANYONECANPAY == 0,
@ -318,7 +285,7 @@ pub fn signature_hash_data<'a>(
h.update(&[0; 32]); h.update(&[0; 32]);
}; };
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
if sigversion.has_tze_components() { if has_tze_components(&tx.version) {
update_hash!( update_hash!(
h, h,
!tx.tze_inputs.is_empty(), !tx.tze_inputs.is_empty(),
@ -335,7 +302,7 @@ pub fn signature_hash_data<'a>(
!tx.joinsplits.is_empty(), !tx.joinsplits.is_empty(),
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap()) joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
); );
if sigversion.has_sapling_components() { if has_sapling_components(&tx.version) {
update_hash!( update_hash!(
h, h,
!tx.shielded_spends.is_empty(), !tx.shielded_spends.is_empty(),
@ -349,7 +316,7 @@ pub fn signature_hash_data<'a>(
} }
update_u32!(h, tx.lock_time, tmp); update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height.into(), tmp); update_u32!(h, tx.expiry_height.into(), tmp);
if sigversion.has_sapling_components() { if has_sapling_components(&tx.version) {
h.update(&tx.value_balance.to_i64_le_bytes()); h.update(&tx.value_balance.to_i64_le_bytes());
} }
update_u32!(h, hash_type, tmp); update_u32!(h, hash_type, tmp);
@ -361,7 +328,7 @@ pub fn signature_hash_data<'a>(
value, value,
} => { } => {
#[cfg(feature = "zfuture")] #[cfg(feature = "zfuture")]
let mut data = if sigversion.has_tze_components() { let mut data = if has_tze_components(&tx.version) {
// domain separation here is to avoid collision attacks // domain separation here is to avoid collision attacks
// between transparent and TZE inputs. // between transparent and TZE inputs.
ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec() ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec()
@ -386,7 +353,7 @@ pub fn signature_hash_data<'a>(
index, index,
precondition, precondition,
value, value,
} if sigversion.has_tze_components() => { } if has_tze_components(&tx.version) => {
// domain separation here is to avoid collision attacks // domain separation here is to avoid collision attacks
// between transparent and TZE inputs. // between transparent and TZE inputs.
let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec(); let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec();
@ -409,7 +376,8 @@ pub fn signature_hash_data<'a>(
} }
h.finalize().as_ref().to_vec() h.finalize().as_ref().to_vec()
} } else {
unimplemented!()
} }
} }

View File

@ -1,22 +1,17 @@
use ff::Field; use ff::Field;
use rand_core::OsRng; use rand_core::OsRng;
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
use proptest::prelude::*; use proptest::prelude::*;
use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey}; use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey};
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
use crate::consensus::BranchId;
use super::{ use super::{
components::Amount, components::Amount,
sighash::{signature_hash, SignableInput}, sighash::{signature_hash, SignableInput},
Transaction, TransactionData, Transaction, TransactionData,
}; };
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))] use super::testing::{arb_branch_id, arb_tx};
use super::testing::arb_tx;
#[test] #[test]
fn tx_read_write() { fn tx_read_write() {
@ -75,21 +70,20 @@ fn tx_write_rejects_unexpected_binding_sig() {
} }
} }
#[cfg(all(feature = "zfuture", feature = "test-dependencies"))]
proptest! { proptest! {
#[test] #[test]
fn test_tze_roundtrip(tx in arb_tx(BranchId::ZFuture)) { fn tx_serialization_roundtrip(tx in arb_branch_id().prop_flat_map(arb_tx)) {
let mut txn_bytes = vec![]; let mut txn_bytes = vec![];
tx.write(&mut txn_bytes).unwrap(); tx.write(&mut txn_bytes).unwrap();
let txo = Transaction::read(&txn_bytes[..]).unwrap(); let txo = Transaction::read(&txn_bytes[..]).unwrap();
assert_eq!(tx.overwintered, txo.overwintered);
assert_eq!(tx.version, txo.version); assert_eq!(tx.version, txo.version);
assert_eq!(tx.version_group_id, txo.version_group_id);
assert_eq!(tx.vin, txo.vin); assert_eq!(tx.vin, txo.vin);
assert_eq!(tx.vout, txo.vout); assert_eq!(tx.vout, txo.vout);
#[cfg(feature = "zfuture")]
assert_eq!(tx.tze_inputs, txo.tze_inputs); assert_eq!(tx.tze_inputs, txo.tze_inputs);
#[cfg(feature = "zfuture")]
assert_eq!(tx.tze_outputs, txo.tze_outputs); assert_eq!(tx.tze_outputs, txo.tze_outputs);
assert_eq!(tx.lock_time, txo.lock_time); assert_eq!(tx.lock_time, txo.lock_time);
assert_eq!(tx.value_balance, txo.value_balance); assert_eq!(tx.value_balance, txo.value_balance);