Add tests for transaction builder support.

This commit is contained in:
Kris Nuttycombe 2020-05-27 12:09:59 -06:00
parent ca8e24eef5
commit 4a954c7f8f
6 changed files with 232 additions and 63 deletions

View File

@ -11,3 +11,9 @@ edition = "2018"
[dependencies]
blake2b_simd = "0.5"
zcash_primitives = { version = "0.3.0", path = "../zcash_primitives" }
[dev-dependencies]
ff = { version = "0.7", path = "../ff" }
jubjub = { version = "0.4", path = "../jubjub" }
zcash_proofs = { path = "../zcash_proofs" }
rand_core = "0.5.1"

View File

@ -304,21 +304,28 @@ fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u
(hash_1, hash_2)
}
pub struct DemoBuilder<'a, B> {
txn_builder: &'a mut B,
extension_id: usize,
pub struct DemoBuilder<B> {
pub txn_builder: B,
pub extension_id: usize,
}
#[derive(Debug)]
pub enum DemoBuildError<E> {
BaseBuilderError(E),
ExpectedOpen,
ExpectedClose,
PrevoutParseFailure(Error),
TransferMismatch { expected: [u8; 32], actual: [u8; 32] },
CloseMismatch { expected: [u8; 32], actual: [u8; 32] },
TransferMismatch {
expected: [u8; 32],
actual: [u8; 32],
},
CloseMismatch {
expected: [u8; 32],
actual: [u8; 32],
},
}
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> {
pub fn demo_open(
&mut self,
value: Amount,
@ -343,15 +350,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
let (hash_1, hash_2) = builder_hashes(&preimage_1, &preimage_2);
// eagerly validate the relationship between prevout.1 and preimage_1
match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) {
Ok(Precondition::Open(hash)) =>
match Precondition::from_payload(
prevout.1.precondition.mode,
&prevout.1.precondition.payload,
) {
Ok(Precondition::Open(hash)) => {
if hash.0 != hash_1 {
Err(DemoBuildError::TransferMismatch { expected: hash.0, actual: hash_1})?
}
Ok(Precondition::Close(_)) =>
Err(DemoBuildError::ExpectedOpen)?,
Err(parse_failure) =>
Err(DemoBuildError::PrevoutParseFailure(parse_failure))?
Err(DemoBuildError::TransferMismatch {
expected: hash.0,
actual: hash_1,
})?
}
}
Ok(Precondition::Close(_)) => Err(DemoBuildError::ExpectedOpen)?,
Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure))?,
}
self.txn_builder
@ -360,12 +372,13 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
})
.map_err(DemoBuildError::BaseBuilderError)?;
self.txn_builder.add_tze_output(
self.extension_id,
transfer_amount, // can this be > prevout.1.value?
&Precondition::close(hash_2),
)
.map_err(DemoBuildError::BaseBuilderError)
self.txn_builder
.add_tze_output(
self.extension_id,
transfer_amount, // can this be > prevout.1.value?
&Precondition::close(hash_2),
)
.map_err(DemoBuildError::BaseBuilderError)
}
pub fn demo_close(
@ -380,15 +393,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
};
// eagerly validate the relationship between prevout.1 and preimage_2
match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) {
Ok(Precondition::Open(_)) =>
Err(DemoBuildError::ExpectedClose)?,
Ok(Precondition::Close(hash)) =>
match Precondition::from_payload(
prevout.1.precondition.mode,
&prevout.1.precondition.payload,
) {
Ok(Precondition::Open(_)) => Err(DemoBuildError::ExpectedClose)?,
Ok(Precondition::Close(hash)) => {
if hash.0 != hash_2 {
Err(DemoBuildError::CloseMismatch { expected: hash.0, actual: hash_2})?
}
Err(parse_failure) =>
Err(DemoBuildError::PrevoutParseFailure(parse_failure))?
Err(DemoBuildError::CloseMismatch {
expected: hash.0,
actual: hash_2,
})?
}
}
Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure))?,
}
self.txn_builder
@ -401,17 +419,33 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
#[cfg(test)]
mod tests {
use ff::{Field, PrimeField};
use blake2b_simd::Params;
use rand_core::OsRng;
use zcash_proofs::prover::LocalTxProver;
use super::{close, open, Context, Precondition, Program, Witness};
use zcash_primitives::{
consensus::{
BranchId,
TestNetwork,
},
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
legacy::TransparentAddress,
merkle_tree::{CommitmentTree, IncrementalWitness},
primitives::Rseed,
sapling::Node,
transaction::{
builder::Builder,
components::{Amount, OutPoint, TzeIn, TzeOut},
Transaction, TransactionData,
},
zip32::ExtendedSpendingKey,
};
use super::{close, open, Context, DemoBuilder, Precondition, Program, Witness};
#[test]
fn precondition_open_round_trip() {
let data = vec![7; 32];
@ -519,7 +553,7 @@ mod tests {
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
};
let mut mtx_a = TransactionData::nu4();
let mut mtx_a = TransactionData::future();
mtx_a.tze_outputs.push(out_a);
let tx_a = mtx_a.freeze().unwrap();
@ -535,7 +569,7 @@ mod tests {
value: Amount::from_u64(1).unwrap(),
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
};
let mut mtx_b = TransactionData::nu4();
let mut mtx_b = TransactionData::future();
mtx_b.tze_inputs.push(in_b);
mtx_b.tze_outputs.push(out_b);
let tx_b = mtx_b.freeze().unwrap();
@ -549,7 +583,7 @@ mod tests {
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
};
let mut mtx_c = TransactionData::nu4();
let mut mtx_c = TransactionData::future();
mtx_c.tze_inputs.push(in_c);
let tx_c = mtx_c.freeze().unwrap();
@ -579,4 +613,118 @@ mod tests {
);
}
}
#[test]
fn demo_builder_program() {
let preimage_1 = [1; 32];
let preimage_2 = [2; 32];
let prover = LocalTxProver::with_default_location().unwrap();
//
// Opening transaction
//
let mut rng = OsRng;
let mut builder_a: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0);
// create some inputs to spend
let extsk = ExtendedSpendingKey::master(&[]);
let to = extsk.default_address().unwrap().1;
let note1 = to
.create_note(110000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
.unwrap();
let cm1 = Node::new(note1.cmu().to_repr());
let mut tree = CommitmentTree::new();
// fake that the note appears in some previous
// shielded output
tree.append(cm1).unwrap();
let witness1 = IncrementalWitness::from_tree(&tree);
builder_a
.add_sapling_spend(
extsk.clone(),
*to.diversifier(),
note1.clone(),
witness1.path().unwrap(),
)
.unwrap();
let mut db_a = DemoBuilder {
txn_builder: &mut builder_a,
extension_id: 0,
};
let value = Amount::from_u64(100000).unwrap();
db_a.demo_open(value, preimage_1, preimage_2)
.map_err(|e| format!("open failure: {:?}", e))
.unwrap();
let (tx_a, _) = builder_a
.build(BranchId::Canopy, &prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
//
// Transfer
//
let mut builder_b: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0);
let mut db_b = DemoBuilder {
txn_builder: &mut builder_b,
extension_id: 0,
};
let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.data.tze_outputs[0].clone());
let value_xfr = Amount::from_u64(90000).unwrap();
db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, preimage_2)
.map_err(|e| format!("transfer failure: {:?}", e))
.unwrap();
let (tx_b, _) = builder_b
.build(BranchId::Canopy, &prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
//
// Closing transaction
//
let mut builder_c: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0);
let mut db_c = DemoBuilder {
txn_builder: &mut builder_c,
extension_id: 0,
};
let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.data.tze_outputs[0].clone());
db_c.demo_close(prevout_b, preimage_2)
.map_err(|e| format!("close failure: {:?}", e))
.unwrap();
builder_c.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::from_u64(80000).unwrap())
.unwrap();
let (tx_c, _) = builder_c
.build(BranchId::Canopy, &prover)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
// Verify tx_b
let ctx0 = Ctx { tx: &tx_b };
assert_eq!(
Program.verify(
&tx_a.data.tze_outputs[0].precondition,
&tx_b.data.tze_inputs[0].witness,
&ctx0
),
Ok(())
);
// Verify tx_c
let ctx1 = Ctx { tx: &tx_b };
assert_eq!(
Program.verify(
&tx_b.data.tze_outputs[0].precondition,
&tx_c.data.tze_inputs[0].witness,
&ctx1
),
Ok(())
);
}
}

View File

@ -60,7 +60,7 @@ pub trait TxProver {
}
#[cfg(test)]
pub(crate) mod mock {
pub mod mock {
use ff::Field;
use rand_core::OsRng;

View File

@ -371,20 +371,14 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
pub fn new(height: u32) -> Self {
Builder::new_with_rng(height, OsRng)
}
pub fn new_future(height: u32) -> Self {
Builder::new_with_rng_future(height, OsRng)
}
}
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
/// Creates a new `Builder` targeted for inclusion in the block with the given height
/// and randomness source, using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
///
/// The fee will be set to the default fee (0.0001 ZEC).
pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, P, R> {
let mut mtx = TransactionData::new();
fn new_with_mtx(height: u32, rng: R, mut mtx: TransactionData) -> Builder<'a, P, R> {
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
Builder {
@ -402,6 +396,25 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
}
}
/// Creates a new `Builder` targeted for inclusion in the block with the given height
/// and randomness source, using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
///
/// The fee will be set to the default fee (0.0001 ZEC).
pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, P, R> {
let mtx = TransactionData::new();
Self::new_with_mtx(height, rng, mtx)
}
pub fn new_with_rng_future(height: u32, rng: R) -> Builder<'a, P, R> {
let mtx = TransactionData::future();
Self::new_with_mtx(height, rng, mtx)
}
/// Adds a Sapling note to be spent in this transaction.
///
/// Returns an error if the given Merkle path does not have the same anchor as the
@ -508,7 +521,6 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
mut self,
consensus_branch_id: consensus::BranchId,
prover: &impl TxProver,
// epoch: &Epoch<TransactionData>
) -> Result<(Transaction, TransactionMetadata), Error> {
let mut tx_metadata = TransactionMetadata::new();
@ -517,7 +529,9 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
//
// Valid change
let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum()
let change = self.mtx.value_balance
- self.fee
+ self.transparent_inputs.value_sum()
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>()
+ self
.tze_inputs
@ -735,15 +749,15 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
}
// Add a binding signature if needed
if binding_sig_needed {
self.mtx.binding_sig = Some(
self.mtx.binding_sig = if binding_sig_needed {
Some(
prover
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
.map_err(|()| Error::BindingSig)?,
);
.map_err(|_| Error::BindingSig)?,
)
} else {
self.mtx.binding_sig = None;
}
None
};
// // Create TZE input witnesses
for tze_in in self.tze_inputs.builders {
@ -784,7 +798,6 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a
where
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, Self::BuildError>),
{
// where WBuilder: WitnessBuilder<Self::BuildCtx, Witness = impl ToPayload, Error = Self::BuildError> {
self.tze_inputs.push(extension_id, prevout, witness_builder);
Ok(())
}

View File

@ -44,8 +44,8 @@ impl fmt::Display for TxId {
/// A Zcash transaction.
#[derive(Debug)]
pub struct Transaction {
txid: TxId,
data: TransactionData,
pub txid: TxId,
pub data: TransactionData,
}
impl Deref for Transaction {
@ -142,7 +142,7 @@ impl TransactionData {
}
}
pub fn nu4() -> Self {
pub fn future() -> Self {
TransactionData {
overwintered: true,
version: FUTURE_TX_VERSION,
@ -310,10 +310,11 @@ impl Transaction {
let is_sapling_v4 = self.overwintered
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
&& self.version == SAPLING_TX_VERSION;
let is_nu4_v5 = self.overwintered
let has_tze = self.overwintered
&& self.version_group_id == FUTURE_VERSION_GROUP_ID
&& self.version == FUTURE_TX_VERSION;
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || is_nu4_v5) {
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
@ -322,16 +323,16 @@ impl Transaction {
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
if is_nu4_v5 {
if has_tze {
Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.tze_outputs, |w, e| e.write(w))?;
}
writer.write_u32::<LittleEndian>(self.lock_time)?;
if is_overwinter_v3 || is_sapling_v4 || is_nu4_v5 {
if is_overwinter_v3 || is_sapling_v4 || has_tze {
writer.write_u32::<LittleEndian>(self.expiry_height)?;
}
if is_sapling_v4 || is_nu4_v5 {
if is_sapling_v4 || has_tze {
writer.write_all(&self.value_balance.to_i64_le_bytes())?;
Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?;
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
@ -376,7 +377,7 @@ impl Transaction {
}
}
if (is_sapling_v4 || is_nu4_v5)
if (is_sapling_v4 || has_tze)
&& !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty())
{
match self.binding_sig {

View File

@ -6,7 +6,7 @@ use group::GroupEncoding;
use super::{
components::{Amount, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
SAPLING_VERSION_GROUP_ID,
SAPLING_VERSION_GROUP_ID, FUTURE_VERSION_GROUP_ID,
};
use crate::{consensus, legacy::Script};
@ -54,6 +54,7 @@ impl SigHashVersion {
match tx.version_group_id {
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
FUTURE_VERSION_GROUP_ID => SigHashVersion::Sapling, //FIXME
_ => unimplemented!(),
}
} else {