Add property test for round-trip serialization of TZE-bearing transactions.
This commit is contained in:
parent
a5d5f87b48
commit
208208238d
|
@ -41,6 +41,7 @@ subtle = "2.2.3"
|
|||
criterion = "0.3"
|
||||
hex-literal = "0.2"
|
||||
rand_xorshift = "0.2"
|
||||
proptest = "0.10.1"
|
||||
|
||||
[features]
|
||||
transparent-inputs = ["ripemd160", "secp256k1"]
|
||||
|
|
|
@ -32,7 +32,7 @@ pub trait ToPayload {
|
|||
/// and extension-specific types. The payload field of this struct
|
||||
/// is treated as opaque to all but extension corresponding to the
|
||||
/// encapsulated extension_id value.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Precondition {
|
||||
pub extension_id: u32,
|
||||
pub mode: u32,
|
||||
|
@ -66,7 +66,7 @@ impl Precondition {
|
|||
/// and extension-specific types. The payload field of this struct
|
||||
/// is treated as opaque to all but extension corresponding to the
|
||||
/// encapsulated extension_id value.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Witness {
|
||||
pub extension_id: u32,
|
||||
pub mode: u32,
|
||||
|
|
|
@ -26,7 +26,7 @@ enum OpCode {
|
|||
}
|
||||
|
||||
/// A serialized script, used inside transparent inputs and outputs of a transaction.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Script(pub Vec<u8>);
|
||||
|
||||
impl Script {
|
||||
|
|
|
@ -36,7 +36,7 @@ pub struct Signature {
|
|||
|
||||
pub struct PrivateKey(pub jubjub::Fr);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PublicKey(pub ExtendedPoint);
|
||||
|
||||
impl Signature {
|
||||
|
|
|
@ -56,7 +56,7 @@ impl OutPoint {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TxIn {
|
||||
pub prevout: OutPoint,
|
||||
pub script_sig: Script,
|
||||
|
@ -93,7 +93,7 @@ impl TxIn {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TxOut {
|
||||
pub value: Amount,
|
||||
pub script_pubkey: Script,
|
||||
|
@ -121,7 +121,7 @@ impl TxOut {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TzeIn {
|
||||
pub prevout: OutPoint,
|
||||
pub witness: tze::Witness,
|
||||
|
@ -171,7 +171,7 @@ impl TzeIn {
|
|||
&mut writer,
|
||||
usize::try_from(self.witness.mode).map_err(|e| to_io_error(e))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.write_without_witness(&mut writer)?;
|
||||
|
@ -179,7 +179,7 @@ impl TzeIn {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TzeOut {
|
||||
pub value: Amount,
|
||||
pub precondition: tze::Precondition,
|
||||
|
@ -225,6 +225,7 @@ impl TzeOut {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SpendDescription {
|
||||
pub cv: jubjub::ExtendedPoint,
|
||||
pub anchor: bls12_381::Scalar,
|
||||
|
@ -314,6 +315,7 @@ impl SpendDescription {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OutputDescription {
|
||||
pub cv: jubjub::ExtendedPoint,
|
||||
pub cmu: bls12_381::Scalar,
|
||||
|
@ -405,6 +407,7 @@ impl OutputDescription {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum SproutProof {
|
||||
Groth([u8; GROTH_PROOF_SIZE]),
|
||||
PHGR([u8; PHGR_PROOF_SIZE]),
|
||||
|
@ -419,6 +422,7 @@ impl std::fmt::Debug for SproutProof {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JSDescription {
|
||||
vpub_old: Amount,
|
||||
vpub_new: Amount,
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::iter::Sum;
|
|||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
const COIN: i64 = 1_0000_0000;
|
||||
const MAX_MONEY: i64 = 21_000_000 * COIN;
|
||||
pub const MAX_MONEY: i64 = 21_000_000 * COIN;
|
||||
|
||||
pub const DEFAULT_FEE: Amount = Amount(10000);
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ impl fmt::Display for TxId {
|
|||
}
|
||||
|
||||
/// A Zcash transaction.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Transaction {
|
||||
txid: TxId,
|
||||
data: TransactionData,
|
||||
|
@ -62,6 +62,7 @@ impl PartialEq for Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TransactionData {
|
||||
pub overwintered: bool,
|
||||
pub version: u32,
|
||||
|
|
|
@ -1,14 +1,124 @@
|
|||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey};
|
||||
use proptest::collection::vec;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::{
|
||||
consensus::BranchId, constants::SPENDING_KEY_GENERATOR, extensions::transparent as tze,
|
||||
legacy::Script, redjubjub::PrivateKey,
|
||||
};
|
||||
|
||||
use super::{
|
||||
components::Amount,
|
||||
components::amount::MAX_MONEY,
|
||||
components::{Amount, OutPoint, TxIn, TxOut, TzeIn, TzeOut},
|
||||
sighash::{signature_hash, SignableInput},
|
||||
Transaction, TransactionData,
|
||||
Transaction, TransactionData, FUTURE_TX_VERSION, FUTURE_VERSION_GROUP_ID,
|
||||
OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
|
||||
SAPLING_VERSION_GROUP_ID,
|
||||
};
|
||||
|
||||
prop_compose! {
|
||||
fn arb_outpoint()(hash in prop::array::uniform32(1u8..), n in 1..(100 as u32)) -> OutPoint {
|
||||
OutPoint::new(hash, n)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: actually generate real possible script values?
|
||||
prop_compose! {
|
||||
fn arb_script()(v in vec(any::<u8>(), 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::Future => Just((FUTURE_TX_VERSION, FUTURE_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::Future { tze_inputs } else { vec![] },
|
||||
tze_outputs: if branch_id == BranchId::Future { tze_outputs } else { vec![] },
|
||||
lock_time,
|
||||
expiry_height,
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_read_write() {
|
||||
let data = &self::data::tx_read_write::TX_READ_WRITE;
|
||||
|
@ -66,6 +176,26 @@ fn tx_write_rejects_unexpected_binding_sig() {
|
|||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_tze_roundtrip(tx in arb_tx(BranchId::Future)) {
|
||||
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);
|
||||
assert_eq!(tx.tze_inputs, txo.tze_inputs);
|
||||
assert_eq!(tx.tze_outputs, txo.tze_outputs);
|
||||
assert_eq!(tx.lock_time, txo.lock_time);
|
||||
assert_eq!(tx.value_balance, txo.value_balance);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tze_tx_parse() {
|
||||
let txn_bytes = vec![
|
||||
|
|
Loading…
Reference in New Issue