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.
|
/// 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()
|
||||||
|
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue