Add property test for round-trip serialization of TZE-bearing transactions.

This commit is contained in:
Kris Nuttycombe 2020-09-09 17:22:29 -06:00
parent a5d5f87b48
commit 208208238d
8 changed files with 150 additions and 14 deletions

View File

@ -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"]

View File

@ -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,

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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![