Add transaction-builder suport for TZE-bearing transactions.
This commit is contained in:
parent
bf7f95b0e9
commit
961d251178
|
@ -3,7 +3,7 @@ members = [
|
|||
"components/equihash",
|
||||
"zcash_client_backend",
|
||||
"zcash_client_sqlite",
|
||||
"zcash_extensions_api",
|
||||
"zcash_extensions",
|
||||
"zcash_history",
|
||||
"zcash_primitives",
|
||||
"zcash_proofs",
|
||||
|
|
|
@ -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" }
|
|
@ -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)]
|
|
@ -0,0 +1,2 @@
|
|||
pub mod consensus;
|
||||
pub mod transparent;
|
|
@ -0,0 +1,3 @@
|
|||
//! Zcash transparent extensions.
|
||||
|
||||
pub mod demo;
|
|
@ -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
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
//! Consensus logic for Zcash extensions.
|
||||
|
||||
pub mod transparent;
|
|
@ -1,3 +1 @@
|
|||
//! Zcash extensions.
|
||||
|
||||
pub mod transparent;
|
||||
|
|
|
@ -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>>;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue