Add transaction-builder suport for TZE-bearing transactions.

This commit is contained in:
Jack Grigg 2020-05-07 11:52:03 +12:00 committed by Kris Nuttycombe
parent bf7f95b0e9
commit 961d251178
15 changed files with 420 additions and 161 deletions

View File

@ -3,7 +3,7 @@ members = [
"components/equihash",
"zcash_client_backend",
"zcash_client_sqlite",
"zcash_extensions_api",
"zcash_extensions",
"zcash_history",
"zcash_primitives",
"zcash_proofs",

View File

@ -1,11 +1,13 @@
[package]
name = "zcash_extensions_api"
name = "zcash_extensions"
description = "TBD"
version = "0.0.0"
authors = ["Jack Grigg <jack@z.cash>"]
authors = ["Jack Grigg <jack@z.cash>", "Kris Nuttycombe <kris@z.cash>"]
homepage = "https://github.com/zcash/librustzcash"
repository = "https://github.com/zcash/librustzcash"
license = "MIT OR Apache-2.0"
edition = "2018"
[dependencies]
blake2b_simd = "0.5"
zcash_primitives = { version = "0.3.0", path = "../zcash_primitives" }

View File

@ -1,11 +1,11 @@
//! Consensus logic for Transparent Zcash Extensions.
use std::convert::TryFrom;
use zcash_extensions_api::transparent::{Error, Extension, Precondition, Witness};
use zcash_primitives::extensions::transparent::{Error, Extension, Precondition, Witness};
use zcash_primitives::transaction::components::TzeOut;
use zcash_primitives::transaction::Transaction;
use crate::extensions::transparent::demo;
use crate::transaction::components::TzeOut;
use crate::transaction::Transaction;
use crate::transparent::demo;
/// The set of programs that have assigned type IDs within the Zcash consensus rules.
#[derive(Debug, Clone, Copy)]

View File

@ -0,0 +1,2 @@
pub mod consensus;
pub mod transparent;

View File

@ -0,0 +1,3 @@
//! Zcash transparent extensions.
pub mod demo;

View File

@ -21,9 +21,11 @@
use blake2b_simd::Params;
use std::convert::TryFrom;
use std::fmt;
use zcash_extensions_api::transparent::{Extension, FromPayload, ToPayload};
use crate::transaction::components::TzeOut;
use zcash_primitives::extensions::transparent::{
Extension, ExtensionTxBuilder, FromPayload, ToPayload,
};
use zcash_primitives::transaction::components::{amount::Amount, OutPoint, TzeOut};
mod open {
pub const MODE: usize = 0;
@ -273,6 +275,89 @@ impl<C: Context> Extension<C> for Program {
}
}
fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u8; 32]) {
let hash_2 = {
let mut hash = [0; 32];
hash.copy_from_slice(Params::new().hash_length(32).hash(preimage_2).as_bytes());
hash
};
let hash_1 = {
let mut hash = [0; 32];
hash.copy_from_slice(
Params::new()
.hash_length(32)
.to_state()
.update(preimage_1)
.update(&hash_2)
.finalize()
.as_bytes(),
);
hash
};
(hash_1, hash_2)
}
pub struct DemoBuilder<'a, B> {
txn_builder: &'a mut B,
extension_id: usize,
}
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
pub fn demo_open(
&mut self,
value: Amount,
preimage_1: [u8; 32],
preimage_2: [u8; 32],
) -> Result<(), B::BuildError> {
let (hash_1, _) = builder_hashes(&preimage_1, &preimage_2);
// Call through to the generic builder.
self.txn_builder
.add_tze_output(self.extension_id, value, &Precondition::open(hash_1))
}
pub fn demo_transfer_to_close(
&mut self,
prevout: (OutPoint, TzeOut),
transfer_amount: Amount,
preimage_1: [u8; 32],
preimage_2: [u8; 32],
) -> Result<(), B::BuildError> {
let (_, hash_2) = builder_hashes(&preimage_1, &preimage_2);
// should we eagerly validate the relationship between prevout.1 and preimage_1?
self.txn_builder
.add_tze_input(self.extension_id, prevout, move |_| {
Ok(Witness::open(preimage_1))
})?;
self.txn_builder.add_tze_output(
self.extension_id,
transfer_amount, // can this be > prevout.1.value?
&Precondition::close(hash_2),
)
}
pub fn demo_close(
&mut self,
prevout: (OutPoint, TzeOut),
preimage: [u8; 32],
) -> Result<(), B::BuildError> {
let hash_2 = {
let mut hash = [0; 32];
hash.copy_from_slice(Params::new().hash_length(32).hash(&preimage).as_bytes());
hash
};
self.txn_builder
.add_tze_input(self.extension_id, prevout, move |_| {
Ok(Witness::close(preimage_2))
})
}
}
#[cfg(test)]
mod tests {
use blake2b_simd::Params;
@ -382,29 +467,49 @@ mod tests {
hash
};
let mut mtx_a = TransactionData::nu4();
mtx_a.tze_outputs.push(TzeOut {
//
// Opening transaction
//
let out_a = TzeOut {
value: Amount::from_u64(1).unwrap(),
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
});
};
println!("{:x?}", precondition.payload);
let mut mtx_a = TransactionData::nu4();
mtx_a.tze_outputs.push(out_a);
let tx_a = mtx_a.freeze().unwrap();
let mut mtx_b = TransactionData::nu4();
mtx_b.tze_inputs.push(TzeIn {
//
// Transfer
//
let in_b = TzeIn {
prevout: OutPoint::new(tx_a.txid().0, 0),
witness: tze::Witness::from(0, &Witness::open(preimage_1)),
});
mtx_b.tze_outputs.push(TzeOut {
};
let out_b = TzeOut {
value: Amount::from_u64(1).unwrap(),
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
});
};
let mut mtx_b = TransactionData::nu4();
mtx_b.tze_inputs.push(in_b);
mtx_b.tze_outputs.push(out_b);
let tx_b = mtx_b.freeze().unwrap();
let mut mtx_c = TransactionData::nu4();
mtx_c.tze_inputs.push(TzeIn {
//
// Closing transaction
//
let in_c = TzeIn {
prevout: OutPoint::new(tx_b.txid().0, 0),
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
});
};
let mut mtx_c = TransactionData::nu4();
mtx_c.tze_inputs.push(in_c);
let tx_c = mtx_c.freeze().unwrap();
// Verify tx_b

View File

@ -1,107 +0,0 @@
//! Core traits and structs for Transparent Zcash Extensions.
use std::fmt;
pub trait FromPayload<E>: Sized {
/// Parses an extension type from a mode and payload.
fn from_payload(mode: usize, payload: &[u8]) -> Result<Self, E>;
}
pub trait ToPayload {
/// Returns a serialized payload and its corresponding mode.
fn to_payload(&self) -> (usize, Vec<u8>);
}
/// A condition that can be used to encumber transparent funds.
#[derive(Debug)]
pub struct Precondition {
pub extension_id: usize,
pub mode: usize,
pub payload: Vec<u8>,
}
impl Precondition {
pub fn from<P: ToPayload>(extension_id: usize, value: &P) -> Precondition {
let (mode, payload) = value.to_payload();
Precondition {
extension_id,
mode,
payload,
}
}
}
/// Data that satisfies the precondition for prior encumbered funds, enabling them to be
/// spent.
#[derive(Debug)]
pub struct Witness {
pub extension_id: usize,
pub mode: usize,
pub payload: Vec<u8>,
}
impl Witness {
pub fn from<P: ToPayload>(extension_id: usize, value: &P) -> Witness {
let (mode, payload) = value.to_payload();
Witness {
extension_id,
mode,
payload,
}
}
}
#[derive(Debug, PartialEq)]
pub enum Error<E> {
InvalidForEpoch(u32, usize),
InvalidExtensionId(usize),
ProgramError(E),
}
impl<E: fmt::Display> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidForEpoch(cid, ptype) => write!(
f,
"Program type {} is invalid for consensus branch id {}",
ptype, cid
),
Error::InvalidExtensionId(extension_id) => {
write!(f, "Unrecognized program type id {}", extension_id)
}
Error::ProgramError(err) => write!(f, "Program error: {}", err),
}
}
}
pub trait Extension<C> {
type P;
type W;
type Error;
fn verify_inner(
&self,
precondition: &Self::P,
witness: &Self::W,
context: &C,
) -> Result<(), Self::Error>;
fn verify(
&self,
precondition: &Precondition,
witness: &Witness,
context: &C,
) -> Result<(), Self::Error>
where
Self::P: FromPayload<Self::Error>,
Self::W: FromPayload<Self::Error>,
{
self.verify_inner(
&Self::P::from_payload(precondition.mode, &precondition.payload)?,
&Self::W::from_payload(witness.mode, &witness.payload)?,
&context,
)
}
}

View File

@ -36,7 +36,6 @@ ripemd160 = { version = "0.9", optional = true }
secp256k1 = { version = "0.19", optional = true }
sha2 = "0.9"
subtle = "2.2.1"
zcash_extensions_api = { version = "0.0", path = "../zcash_extensions_api" }
[dev-dependencies]
criterion = "0.3"

View File

@ -3,8 +3,6 @@
use std::convert::TryFrom;
use std::fmt;
pub mod extensions;
/// Zcash consensus parameters.
pub trait Parameters {
fn activation_height(nu: NetworkUpgrade) -> Option<u32>;

View File

@ -1,3 +0,0 @@
//! Consensus logic for Zcash extensions.
pub mod transparent;

View File

@ -1,3 +1 @@
//! Zcash extensions.
pub mod transparent;

View File

@ -1,3 +1,157 @@
//! Zcash transparent extensions.
//! Core traits and structs for Transparent Zcash Extensions.
pub mod demo;
use crate::transaction::components::{Amount, OutPoint, TzeOut};
use std::fmt;
pub trait FromPayload<E>: Sized {
/// Parses an extension type from a mode and payload.
fn from_payload(mode: usize, payload: &[u8]) -> Result<Self, E>;
}
pub trait ToPayload {
/// Returns a serialized payload and its corresponding mode.
fn to_payload(&self) -> (usize, Vec<u8>);
}
/// A condition that can be used to encumber transparent funds.
#[derive(Clone, Debug)]
pub struct Precondition {
pub extension_id: usize,
pub mode: usize,
pub payload: Vec<u8>,
}
impl Precondition {
pub fn from<P: ToPayload>(extension_id: usize, value: &P) -> Precondition {
let (mode, payload) = value.to_payload();
Precondition {
extension_id,
mode,
payload,
}
}
}
/// Data that satisfies the precondition for prior encumbered funds, enabling them to be
/// spent.
#[derive(Clone, Debug)]
pub struct Witness {
pub extension_id: usize,
pub mode: usize,
pub payload: Vec<u8>,
}
impl Witness {
pub fn from<P: ToPayload>(extension_id: usize, value: &P) -> Witness {
let (mode, payload) = value.to_payload();
Witness {
extension_id,
mode,
payload,
}
}
}
#[derive(Debug, PartialEq)]
pub enum Error<E> {
InvalidForEpoch(u32, usize),
InvalidExtensionId(usize),
ProgramError(E),
}
impl<E: fmt::Display> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidForEpoch(cid, ptype) => write!(
f,
"Program type {} is invalid for consensus branch id {}",
ptype, cid
),
Error::InvalidExtensionId(extension_id) => {
write!(f, "Unrecognized program type id {}", extension_id)
}
Error::ProgramError(err) => write!(f, "Program error: {}", err),
}
}
}
pub trait Extension<C> {
type P;
type W;
type Error;
fn verify_inner(
&self,
precondition: &Self::P,
witness: &Self::W,
context: &C,
) -> Result<(), Self::Error>;
fn verify(
&self,
precondition: &Precondition,
witness: &Witness,
context: &C,
) -> Result<(), Self::Error>
where
Self::P: FromPayload<Self::Error>,
Self::W: FromPayload<Self::Error>,
{
self.verify_inner(
&Self::P::from_payload(precondition.mode, &precondition.payload)?,
&Self::W::from_payload(witness.mode, &witness.payload)?,
&context,
)
}
}
// pub trait WitnessBuilder<BuildCtx> {
// type Error;
// type Witness: ToPayload;
//
// fn build_witness(ctx: BuildCtx) -> Result<Witness, Self::Error>;
// }
// This extension trait is satisfied by the transaction::builder::Builder type. It provides a
// minimal contract for interacting with the transaction builder, that extension library authors
// can use to add extension-specific builder traits that may be used to interact with the
// transaction builder. This may make it simpler for projects that include transaction-builder
// functionality to integrate with third-party extensions without those extensions being coupled to
// a particular transaction or builder representation.
pub trait ExtensionTxBuilder<'a> {
type BuildCtx;
type BuildError;
fn add_tze_input<WBuilder, W: ToPayload>(
&mut self,
extension_id: usize,
prevout: (OutPoint, TzeOut),
witness_builder: WBuilder,
) -> Result<(), Self::BuildError>
where
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, Self::BuildError>);
//where WBuilder: WitnessBuilder<Self::BuildCtx, Witness = W, Error = Self::BuildError>;
fn add_tze_output<P: ToPayload>(
&mut self,
extension_id: usize,
value: Amount,
guarded_by: &P,
) -> Result<(), Self::BuildError>;
}
pub trait Epoch<VerifyCtx> {
type VerifyError;
// Implementation of this method should check that the provided witness
// satisfies the specified precondition, given the context. This verification
// becomes part of the consensus rules.
fn verify(
&self,
precondition: &Precondition,
witness: &Witness,
ctx: &VerifyCtx,
) -> Result<(), Error<Self::VerifyError>>;
}

View File

@ -1,27 +1,33 @@
//! Structs for building transactions.
use crate::primitives::{Diversifier, Note, PaymentAddress};
use crate::zip32::ExtendedSpendingKey;
use ff::Field;
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
use std::boxed::Box;
use std::error;
use std::fmt;
use std::marker::PhantomData;
use ff::Field;
use rand::{rngs::OsRng, seq::SliceRandom, CryptoRng, RngCore};
use crate::{
consensus,
extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload},
keys::OutgoingViewingKey,
legacy::TransparentAddress,
merkle_tree::MerklePath,
note_encryption::{Memo, SaplingNoteEncryption},
primitives::{Diversifier, Note, PaymentAddress},
prover::TxProver,
redjubjub::PrivateKey,
sapling::{spend_sig, Node},
transaction::{
components::{amount::DEFAULT_FEE, Amount, OutputDescription, SpendDescription, TxOut},
components::{
amount::Amount, amount::DEFAULT_FEE, OutPoint, OutputDescription, SpendDescription,
TxOut, TzeIn, TzeOut,
},
signature_hash_data, Transaction, TransactionData, SIGHASH_ALL,
},
util::generate_random_rseed,
zip32::ExtendedSpendingKey,
};
#[cfg(feature = "transparent-inputs")]
@ -186,17 +192,14 @@ struct TransparentInputs;
impl TransparentInputs {
#[cfg(feature = "transparent-inputs")]
fn push(
&mut self,
mtx: &mut TransactionData,
sk: secp256k1::SecretKey,
utxo: OutPoint,
coin: TxOut,
) -> Result<(), Error> {
fn push(&mut self, sk: secp256k1::SecretKey, coin: TxOut) -> Result<(), Error> {
if coin.value.is_negative() {
return Err(Error::InvalidAmount);
}
// ensure that the ripemd160 digest of the public key associated with the
// provided secret key matches that of the address to which the provided
// output may be spent
let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk).serialize();
match coin.script_pubkey.address() {
Some(TransparentAddress::PublicKey(hash)) => {
@ -210,7 +213,6 @@ impl TransparentInputs {
_ => return Err(Error::InvalidAddress),
}
mtx.vin.push(TxIn::new(utxo));
self.inputs.push(TransparentInputInfo { sk, pubkey, coin });
Ok(())
@ -244,6 +246,7 @@ impl TransparentInputs {
consensus_branch_id,
SIGHASH_ALL,
Some((i, &info.coin.script_pubkey, info.coin.value)),
// tze equivalent is ???
));
let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes");
@ -262,6 +265,42 @@ impl TransparentInputs {
fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {}
}
struct TzeInputInfo<'a, BuildCtx> {
prevout: TzeOut,
builder: Box<dyn FnOnce(&BuildCtx) -> Result<TzeIn, Error> + 'a>,
}
struct TzeInputs<'a, BuildCtx> {
builders: Vec<TzeInputInfo<'a, BuildCtx>>,
}
impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> {
fn push<WBuilder, W: ToPayload>(
&mut self,
extension_id: usize,
prevout: (OutPoint, TzeOut),
builder: WBuilder,
) where
WBuilder: 'a + FnOnce(&BuildCtx) -> Result<W, Error>,
{
let (outpoint, tzeout) = prevout;
self.builders.push(TzeInputInfo {
prevout: tzeout,
builder: Box::new(move |ctx| {
let (mode, payload) = builder(&ctx).map(|x| x.to_payload())?;
Ok(TzeIn {
prevout: outpoint,
witness: tze::Witness {
extension_id,
mode,
payload,
},
})
}),
});
}
}
/// Metadata about a transaction created by a [`Builder`].
#[derive(Debug, PartialEq)]
pub struct TransactionMetadata {
@ -301,7 +340,7 @@ impl TransactionMetadata {
}
/// Generates a [`Transaction`] from its inputs and outputs.
pub struct Builder<P: consensus::Parameters, R: RngCore + CryptoRng> {
pub struct Builder<'a, P: consensus::Parameters, R: RngCore + CryptoRng> {
rng: R,
height: u32,
mtx: TransactionData,
@ -310,11 +349,12 @@ pub struct Builder<P: consensus::Parameters, R: RngCore + CryptoRng> {
spends: Vec<SpendDescriptionInfo>,
outputs: Vec<SaplingOutput>,
transparent_inputs: TransparentInputs,
tze_inputs: TzeInputs<'a, TransactionData>,
change_address: Option<(OutgoingViewingKey, PaymentAddress)>,
phantom: PhantomData<P>,
}
impl<P: consensus::Parameters> Builder<P, OsRng> {
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
/// using default values for general transaction fields and the default OS random.
///
@ -329,7 +369,7 @@ impl<P: consensus::Parameters> Builder<P, OsRng> {
}
}
impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
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.
///
@ -339,7 +379,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
/// 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<P, R> {
pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, P, R> {
let mut mtx = TransactionData::new();
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
@ -352,6 +392,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
spends: vec![],
outputs: vec![],
transparent_inputs: TransparentInputs::default(),
tze_inputs: TzeInputs { builders: vec![] },
change_address: None,
phantom: PhantomData,
}
@ -420,7 +461,8 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
utxo: OutPoint,
coin: TxOut,
) -> Result<(), Error> {
self.transparent_inputs.push(&mut self.mtx, sk, utxo, coin)
self.mtx.vin.push(TxIn::new(utxo));
self.transparent_inputs.push(sk, coin)
}
/// Adds a transparent address to send funds to.
@ -462,6 +504,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<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();
@ -471,12 +514,20 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
// Valid change
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
.builders
.iter()
.map(|ein| ein.prevout.value)
.sum::<Amount>()
- self
.mtx
.vout
.tze_outputs
.iter()
.map(|output| output.value)
.map(|tzo| tzo.value)
.sum::<Amount>();
if change.is_negative() {
return Err(Error::ChangeIsNegative(change));
}
@ -658,7 +709,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
}
//
// Signatures
// Signatures -- all effects must have been applied.
//
let mut sighash = [0u8; 32];
@ -690,6 +741,19 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
self.mtx.binding_sig = None;
}
// // Create TZE input witnesses
for tze_in in self.tze_inputs.builders {
// Need to enable witness to commit to the amount.
// - So hardware wallets "know" the amount without having to be sent all the
// prior TZE outputs to which this witness gives evidence.
//
// The witness is expected to commit to the precommitment internally?
// (Or make it part of the sighash?)
// - TODO: Check whether transparent inputs committing to script_pubkey was
// only so that hardware wallets "knew" what address was being spent from.
self.mtx.tze_inputs.push((tze_in.builder)(&self.mtx)?);
}
// Transparent signatures
self.transparent_inputs
.apply_signatures(&mut self.mtx, consensus_branch_id);
@ -701,6 +765,50 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
}
}
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a>
for Builder<'a, P, R>
{
type BuildCtx = TransactionData;
type BuildError = Error;
fn add_tze_input<WBuilder, W: ToPayload>(
&mut self,
extension_id: usize,
prevout: (OutPoint, TzeOut),
witness_builder: WBuilder,
) -> Result<(), Self::BuildError>
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(())
}
fn add_tze_output<G: ToPayload>(
&mut self,
extension_id: usize,
value: Amount,
guarded_by: &G,
) -> Result<(), Self::BuildError> {
if value.is_negative() {
return Err(Error::InvalidAmount);
}
let (mode, payload) = guarded_by.to_payload();
self.mtx.tze_outputs.push(TzeOut {
value,
precondition: tze::Precondition {
extension_id,
mode,
payload,
},
});
Ok(())
}
}
#[cfg(test)]
mod tests {
use ff::{Field, PrimeField};

View File

@ -4,8 +4,8 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use std::io::{self, Read, Write};
use zcash_extensions_api::transparent as tze;
use crate::extensions::transparent as tze;
use crate::legacy::Script;
use crate::redjubjub::{PublicKey, Signature};
use crate::serialize::{CompactSize, Vector};
@ -118,7 +118,7 @@ impl TxOut {
}
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct TzeIn {
pub prevout: OutPoint,
pub witness: tze::Witness,
@ -151,7 +151,7 @@ impl TzeIn {
}
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct TzeOut {
pub value: Amount,
pub precondition: tze::Precondition,