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:
commit
b37b5876f2
|
@ -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) }
|
|
@ -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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Transaction {
|
||||
|
@ -76,9 +173,7 @@ impl PartialEq for Transaction {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct TransactionData {
|
||||
pub overwintered: bool,
|
||||
pub version: u32,
|
||||
pub version_group_id: u32,
|
||||
pub version: TxVersion,
|
||||
pub vin: Vec<TxIn>,
|
||||
pub vout: Vec<TxOut>,
|
||||
#[cfg(feature = "zfuture")]
|
||||
|
@ -101,9 +196,7 @@ impl std::fmt::Debug for TransactionData {
|
|||
write!(
|
||||
f,
|
||||
"TransactionData(
|
||||
overwintered = {:?},
|
||||
version = {:?},
|
||||
version_group_id = {:?},
|
||||
vin = {:?},
|
||||
vout = {:?},{}
|
||||
lock_time = {:?},
|
||||
|
@ -114,9 +207,7 @@ impl std::fmt::Debug for TransactionData {
|
|||
joinsplits = {:?},
|
||||
joinsplit_pubkey = {:?},
|
||||
binding_sig = {:?})",
|
||||
self.overwintered,
|
||||
self.version,
|
||||
self.version_group_id,
|
||||
self.vin,
|
||||
self.vout,
|
||||
{
|
||||
|
@ -153,9 +244,7 @@ impl Default for TransactionData {
|
|||
impl TransactionData {
|
||||
pub fn new() -> Self {
|
||||
TransactionData {
|
||||
overwintered: true,
|
||||
version: SAPLING_TX_VERSION,
|
||||
version_group_id: SAPLING_VERSION_GROUP_ID,
|
||||
version: TxVersion::Sapling,
|
||||
vin: vec![],
|
||||
vout: vec![],
|
||||
#[cfg(feature = "zfuture")]
|
||||
|
@ -177,9 +266,7 @@ impl TransactionData {
|
|||
#[cfg(feature = "zfuture")]
|
||||
pub fn zfuture() -> Self {
|
||||
TransactionData {
|
||||
overwintered: true,
|
||||
version: ZFUTURE_TX_VERSION,
|
||||
version_group_id: ZFUTURE_VERSION_GROUP_ID,
|
||||
version: TxVersion::ZFuture,
|
||||
vin: vec![],
|
||||
vout: 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> {
|
||||
Transaction::from_data(self)
|
||||
}
|
||||
|
@ -228,39 +307,18 @@ impl Transaction {
|
|||
pub fn read<R: Read>(reader: R) -> io::Result<Self> {
|
||||
let mut reader = HashReader::new(reader);
|
||||
|
||||
let header = reader.read_u32::<LittleEndian>()?;
|
||||
let overwintered = (header >> 31) == 1;
|
||||
let version = header & 0x7FFFFFFF;
|
||||
|
||||
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;
|
||||
let version = TxVersion::read(&mut reader)?;
|
||||
let is_overwinter_v3 = version == TxVersion::Overwinter;
|
||||
let is_sapling_v4 = version == TxVersion::Sapling;
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
let has_tze = overwintered
|
||||
&& version_group_id == ZFUTURE_VERSION_GROUP_ID
|
||||
&& version == ZFUTURE_TX_VERSION;
|
||||
let has_tze = version == TxVersion::ZFuture;
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
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 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)?;
|
||||
|
@ -291,9 +349,9 @@ impl Transaction {
|
|||
(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| {
|
||||
JSDescription::read(r, overwintered && version >= SAPLING_TX_VERSION)
|
||||
JSDescription::read(r, version.uses_groth_proofs())
|
||||
})?;
|
||||
let (pubkey, sig) = if !jss.is_empty() {
|
||||
let mut joinsplit_pubkey = [0; 32];
|
||||
|
@ -323,9 +381,7 @@ impl Transaction {
|
|||
Ok(Transaction {
|
||||
txid: TxId(txid),
|
||||
data: TransactionData {
|
||||
overwintered,
|
||||
version,
|
||||
version_group_id,
|
||||
vin,
|
||||
vout,
|
||||
#[cfg(feature = "zfuture")]
|
||||
|
@ -346,32 +402,15 @@ impl Transaction {
|
|||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_u32::<LittleEndian>(self.header())?;
|
||||
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;
|
||||
self.version.write(&mut writer)?;
|
||||
|
||||
let is_overwinter_v3 = self.version == TxVersion::Overwinter;
|
||||
let is_sapling_v4 = self.version == TxVersion::Sapling;
|
||||
#[cfg(feature = "zfuture")]
|
||||
let has_tze = self.overwintered
|
||||
&& self.version_group_id == ZFUTURE_VERSION_GROUP_ID
|
||||
&& self.version == ZFUTURE_TX_VERSION;
|
||||
let has_tze = self.version == TxVersion::ZFuture;
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
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.vout, |w, e| e.write(w))?;
|
||||
#[cfg(feature = "zfuture")]
|
||||
|
@ -390,7 +429,7 @@ impl Transaction {
|
|||
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))?;
|
||||
if !self.joinsplits.is_empty() {
|
||||
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() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
|
@ -452,7 +491,7 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-dependencies")]
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
|
@ -465,15 +504,11 @@ pub mod testing {
|
|||
|
||||
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,
|
||||
Transaction, TransactionData, TxVersion,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::{
|
||||
components::{TzeIn, TzeOut},
|
||||
ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID,
|
||||
};
|
||||
use super::components::{TzeIn, TzeOut};
|
||||
|
||||
pub const VALID_OPCODES: [u8; 8] = [
|
||||
0x00, // OP_FALSE,
|
||||
|
@ -487,7 +522,7 @@ pub mod testing {
|
|||
];
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -518,7 +553,7 @@ pub mod testing {
|
|||
|
||||
#[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 {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
@ -532,7 +567,7 @@ pub mod testing {
|
|||
|
||||
#[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 {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
@ -544,22 +579,33 @@ pub mod testing {
|
|||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
pub fn arb_branch_id() -> impl Strategy<Value = BranchId> {
|
||||
select(vec![
|
||||
BranchId::Sprout,
|
||||
BranchId::Overwinter,
|
||||
BranchId::Sapling,
|
||||
BranchId::Blossom,
|
||||
BranchId::Heartwood,
|
||||
BranchId::Canopy,
|
||||
#[cfg(feature = "zfuture")]
|
||||
BranchId::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID)).boxed(),
|
||||
_otherwise => Just((SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID)).boxed(),
|
||||
BranchId::ZFuture,
|
||||
])
|
||||
}
|
||||
|
||||
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")]
|
||||
prop_compose! {
|
||||
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),
|
||||
vout in vec(arb_txout(), 0..10),
|
||||
tze_inputs in vec(arb_tzein(), 0..10),
|
||||
|
@ -569,15 +615,45 @@ pub mod testing {
|
|||
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,
|
||||
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_outputs: vec![], //FIXME
|
||||
joinsplits: vec![], //FIXME
|
||||
|
@ -588,7 +664,6 @@ pub mod testing {
|
|||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
|
|
|
@ -16,15 +16,11 @@ use crate::{
|
|||
|
||||
use super::{
|
||||
components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut},
|
||||
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
|
||||
SAPLING_VERSION_GROUP_ID,
|
||||
Transaction, TransactionData, TxVersion,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
use super::{
|
||||
components::{TzeIn, TzeOut},
|
||||
ZFUTURE_VERSION_GROUP_ID,
|
||||
};
|
||||
use super::components::{TzeIn, TzeOut};
|
||||
|
||||
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
|
||||
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
|
||||
|
@ -67,45 +63,25 @@ 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,
|
||||
fn has_overwinter_components(version: &TxVersion) -> bool {
|
||||
match version {
|
||||
TxVersion::Sprout(_) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
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(version: &TxVersion) -> bool {
|
||||
match version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_sapling_components(&self) -> bool {
|
||||
match self {
|
||||
SigHashVersion::Sprout | SigHashVersion::Overwinter => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn has_tze_components(&self) -> bool {
|
||||
match self {
|
||||
SigHashVersion::ZFuture => true,
|
||||
_ => false,
|
||||
}
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn has_tze_components(version: &TxVersion) -> bool {
|
||||
match version {
|
||||
TxVersion::ZFuture => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,16 +130,16 @@ fn single_output_hash(tx_out: &TxOut) -> Blake2bHash {
|
|||
}
|
||||
|
||||
fn joinsplits_hash(
|
||||
txversion: u32,
|
||||
txversion: TxVersion,
|
||||
joinsplits: &[JSDescription],
|
||||
joinsplit_pubkey: &[u8; 32],
|
||||
) -> Blake2bHash {
|
||||
let mut data = Vec::with_capacity(
|
||||
joinsplits.len()
|
||||
* if txversion < SAPLING_TX_VERSION {
|
||||
1802 // JSDescription with PHGR13 proof
|
||||
} else {
|
||||
* if txversion.uses_groth_proofs() {
|
||||
1698 // JSDescription with Groth16 proof
|
||||
} else {
|
||||
1802 // JSDescription with PHGR13 proof
|
||||
},
|
||||
);
|
||||
for js in joinsplits {
|
||||
|
@ -266,150 +242,142 @@ pub fn signature_hash_data<'a>(
|
|||
hash_type: u32,
|
||||
signable_input: SignableInput<'a>,
|
||||
) -> Vec<u8> {
|
||||
let sigversion = SigHashVersion::from_tx(tx);
|
||||
match sigversion {
|
||||
SigHashVersion::Sprout => unimplemented!(),
|
||||
if has_overwinter_components(&tx.version) {
|
||||
let mut personal = [0; 16];
|
||||
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
|
||||
(&mut personal[12..])
|
||||
.write_u32::<LittleEndian>(consensus_branch_id.into())
|
||||
.unwrap();
|
||||
|
||||
// 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..])
|
||||
.write_u32::<LittleEndian>(consensus_branch_id.into())
|
||||
.unwrap();
|
||||
let mut h = Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
.personal(&personal)
|
||||
.to_state();
|
||||
let mut tmp = [0; 8];
|
||||
|
||||
let mut h = Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
.personal(&personal)
|
||||
.to_state();
|
||||
let mut tmp = [0; 8];
|
||||
|
||||
update_u32!(h, tx.header(), tmp);
|
||||
update_u32!(h, tx.version_group_id, tmp);
|
||||
update_hash!(
|
||||
h,
|
||||
hash_type & SIGHASH_ANYONECANPAY == 0,
|
||||
prevout_hash(&tx.vin)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
hash_type & SIGHASH_ANYONECANPAY == 0
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
|
||||
sequence_hash(&tx.vin)
|
||||
);
|
||||
|
||||
if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
|
||||
{
|
||||
h.update(outputs_hash(&tx.vout).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]);
|
||||
};
|
||||
#[cfg(feature = "zfuture")]
|
||||
if sigversion.has_tze_components() {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_inputs.is_empty(),
|
||||
tze_inputs_hash(&tx.tze_inputs)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_outputs.is_empty(),
|
||||
tze_outputs_hash(&tx.tze_outputs)
|
||||
);
|
||||
}
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.joinsplits.is_empty(),
|
||||
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
|
||||
);
|
||||
if sigversion.has_sapling_components() {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_spends.is_empty(),
|
||||
shielded_spends_hash(&tx.shielded_spends)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_outputs.is_empty(),
|
||||
shielded_outputs_hash(&tx.shielded_outputs)
|
||||
);
|
||||
}
|
||||
update_u32!(h, tx.lock_time, tmp);
|
||||
update_u32!(h, tx.expiry_height.into(), tmp);
|
||||
if sigversion.has_sapling_components() {
|
||||
h.update(&tx.value_balance.to_i64_le_bytes());
|
||||
}
|
||||
update_u32!(h, hash_type, tmp);
|
||||
update_u32!(h, tx.version.header(), tmp);
|
||||
update_u32!(h, tx.version.version_group_id(), tmp);
|
||||
update_hash!(
|
||||
h,
|
||||
hash_type & SIGHASH_ANYONECANPAY == 0,
|
||||
prevout_hash(&tx.vin)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
hash_type & SIGHASH_ANYONECANPAY == 0
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
|
||||
sequence_hash(&tx.vin)
|
||||
);
|
||||
|
||||
if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
|
||||
{
|
||||
h.update(outputs_hash(&tx.vout).as_ref());
|
||||
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
|
||||
match signable_input {
|
||||
SignableInput::Transparent {
|
||||
index,
|
||||
script_code,
|
||||
value,
|
||||
} => {
|
||||
#[cfg(feature = "zfuture")]
|
||||
let mut data = if sigversion.has_tze_components() {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec()
|
||||
} else {
|
||||
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());
|
||||
(&mut data)
|
||||
.write_u32::<LittleEndian>(tx.vin[index].sequence)
|
||||
.unwrap();
|
||||
h.update(&data);
|
||||
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]);
|
||||
};
|
||||
#[cfg(feature = "zfuture")]
|
||||
if has_tze_components(&tx.version) {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_inputs.is_empty(),
|
||||
tze_inputs_hash(&tx.tze_inputs)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.tze_outputs.is_empty(),
|
||||
tze_outputs_hash(&tx.tze_outputs)
|
||||
);
|
||||
}
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.joinsplits.is_empty(),
|
||||
joinsplits_hash(tx.version, &tx.joinsplits, &tx.joinsplit_pubkey.unwrap())
|
||||
);
|
||||
if has_sapling_components(&tx.version) {
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_spends.is_empty(),
|
||||
shielded_spends_hash(&tx.shielded_spends)
|
||||
);
|
||||
update_hash!(
|
||||
h,
|
||||
!tx.shielded_outputs.is_empty(),
|
||||
shielded_outputs_hash(&tx.shielded_outputs)
|
||||
);
|
||||
}
|
||||
update_u32!(h, tx.lock_time, tmp);
|
||||
update_u32!(h, tx.expiry_height.into(), tmp);
|
||||
if has_sapling_components(&tx.version) {
|
||||
h.update(&tx.value_balance.to_i64_le_bytes());
|
||||
}
|
||||
update_u32!(h, hash_type, tmp);
|
||||
|
||||
match signable_input {
|
||||
SignableInput::Transparent {
|
||||
index,
|
||||
script_code,
|
||||
value,
|
||||
} => {
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze {
|
||||
index,
|
||||
precondition,
|
||||
value,
|
||||
} if sigversion.has_tze_components() => {
|
||||
let mut data = if has_tze_components(&tx.version) {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec();
|
||||
ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
tx.tze_inputs[index].prevout.write(&mut data).unwrap();
|
||||
CompactSize::write(&mut data, precondition.extension_id.try_into().unwrap())
|
||||
.unwrap();
|
||||
CompactSize::write(&mut data, precondition.mode.try_into().unwrap()).unwrap();
|
||||
Vector::write(&mut data, &precondition.payload, |w, e| w.write_u8(*e)).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
h.update(&data);
|
||||
}
|
||||
#[cfg(not(feature = "zfuture"))]
|
||||
let mut data = vec![];
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze { .. } => {
|
||||
panic!("A request has been made to sign a TZE input, but the signature hash version is not ZFuture");
|
||||
}
|
||||
|
||||
_ => (),
|
||||
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::<LittleEndian>(tx.vin[index].sequence)
|
||||
.unwrap();
|
||||
h.update(&data);
|
||||
}
|
||||
|
||||
h.finalize().as_ref().to_vec()
|
||||
#[cfg(feature = "zfuture")]
|
||||
SignableInput::Tze {
|
||||
index,
|
||||
precondition,
|
||||
value,
|
||||
} if has_tze_components(&tx.version) => {
|
||||
// domain separation here is to avoid collision attacks
|
||||
// between transparent and TZE inputs.
|
||||
let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec();
|
||||
|
||||
tx.tze_inputs[index].prevout.write(&mut data).unwrap();
|
||||
CompactSize::write(&mut data, precondition.extension_id.try_into().unwrap())
|
||||
.unwrap();
|
||||
CompactSize::write(&mut data, precondition.mode.try_into().unwrap()).unwrap();
|
||||
Vector::write(&mut data, &precondition.payload, |w, e| w.write_u8(*e)).unwrap();
|
||||
data.extend_from_slice(&value.to_i64_le_bytes());
|
||||
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");
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
h.finalize().as_ref().to_vec()
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey};
|
||||
|
||||
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
|
||||
use crate::consensus::BranchId;
|
||||
|
||||
use super::{
|
||||
components::Amount,
|
||||
sighash::{signature_hash, SignableInput},
|
||||
Transaction, TransactionData,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "test-dependencies", feature = "zfuture"))]
|
||||
use super::testing::arb_tx;
|
||||
use super::testing::{arb_branch_id, arb_tx};
|
||||
|
||||
#[test]
|
||||
fn tx_read_write() {
|
||||
|
@ -75,21 +70,20 @@ 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)) {
|
||||
fn tx_serialization_roundtrip(tx in arb_branch_id().prop_flat_map(arb_tx)) {
|
||||
let mut txn_bytes = vec![];
|
||||
tx.write(&mut 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_group_id, txo.version_group_id);
|
||||
assert_eq!(tx.vin, txo.vin);
|
||||
assert_eq!(tx.vout, txo.vout);
|
||||
#[cfg(feature = "zfuture")]
|
||||
assert_eq!(tx.tze_inputs, txo.tze_inputs);
|
||||
#[cfg(feature = "zfuture")]
|
||||
assert_eq!(tx.tze_outputs, txo.tze_outputs);
|
||||
assert_eq!(tx.lock_time, txo.lock_time);
|
||||
assert_eq!(tx.value_balance, txo.value_balance);
|
||||
|
|
Loading…
Reference in New Issue