Merge pull request #286 from nuttycom/zip-tzes

Implementation for ZIP-222 Transparent Zcash Extensions
This commit is contained in:
Kris Nuttycombe 2020-10-13 14:56:44 -06:00 committed by GitHub
commit 72b6de39eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1898 additions and 104 deletions

View File

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

View File

@ -20,14 +20,14 @@ group = "0.8"
hex = "0.4"
jubjub = "0.5.1"
protobuf = "2.15"
subtle = "2"
subtle = "2.2.3"
zcash_primitives = { version = "0.4", path = "../zcash_primitives" }
[build-dependencies]
protobuf-codegen-pure = "2.15"
[dev-dependencies]
rand_core = "0.5"
rand_core = "0.5.1"
rand_xorshift = "0.2"
[badges]

View File

@ -0,0 +1,19 @@
[package]
name = "zcash_extensions"
description = "Zcash Extension implementations & consensus node integration layer."
version = "0.0.0"
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.4.0", path = "../zcash_primitives" }
[dev-dependencies]
ff = "0.8"
jubjub = "0.5.1"
rand_core = "0.5.1"
zcash_proofs = { version = "0.4.0", path = "../zcash_proofs" }

View File

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

View File

@ -0,0 +1,122 @@
//! Consensus logic for Transparent Zcash Extensions.
use std::convert::TryFrom;
use zcash_primitives::consensus::{BlockHeight, BranchId};
use zcash_primitives::extensions::transparent::{Error, Extension, Precondition, Witness};
use zcash_primitives::transaction::{components::TzeOut, Transaction};
use crate::transparent::demo;
/// Wire value for the demo extension identifier.
pub const EXTENSION_DEMO: u32 = 0;
/// The set of programs that have assigned type IDs within the Zcash consensus rules.
#[derive(Debug, Clone, Copy)]
pub enum ExtensionId {
Demo,
}
pub struct InvalidExtId(u32);
impl TryFrom<u32> for ExtensionId {
type Error = InvalidExtId;
fn try_from(t: u32) -> Result<Self, Self::Error> {
match t {
EXTENSION_DEMO => Ok(ExtensionId::Demo),
n => Err(InvalidExtId(n)),
}
}
}
impl From<ExtensionId> for u32 {
fn from(type_id: ExtensionId) -> u32 {
match type_id {
ExtensionId::Demo => EXTENSION_DEMO,
}
}
}
/// The complete set of context data that is available to any extension having
/// an assigned extension type ID. This type may be modified in the future if
/// additional context information is required by newly integrated TZEs.
pub struct Context<'a> {
pub height: BlockHeight,
pub tx: &'a Transaction,
}
impl<'a> Context<'a> {
pub fn new(height: BlockHeight, tx: &'a Transaction) -> Self {
Context { height, tx }
}
}
/// Implementations of this trait provide complete extension validation rules
/// for a specific epoch, and handle dispatch of verification to individual
/// TZEs based upon extension ID and mode.
pub trait Epoch {
type Error;
/// For a specific epoch, if the extension ID and mode of the supplied
/// witness matches that of the supplied precondition, these values will
/// be passed to the associated extension for verification, along with
/// whatever that extension requires of the provided [`Context`].
///
/// Successful validation is indicated by the returned Result containing
/// no errors.
fn verify<'a>(
&self,
precondition: &Precondition,
witness: &Witness,
ctx: &Context<'a>,
) -> Result<(), Error<Self::Error>>;
}
/// Implementation of required operations for the demo extension, as satisfied
/// by the context.
impl<'a> demo::Context for Context<'a> {
fn is_tze_only(&self) -> bool {
self.tx.vin.is_empty()
&& self.tx.vout.is_empty()
&& self.tx.shielded_spends.is_empty()
&& self.tx.shielded_outputs.is_empty()
&& self.tx.joinsplits.is_empty()
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
&self.tx.tze_outputs
}
}
/// Identifier for the set of TZEs associated with the ZFUTURE network upgrade.
/// This epoch is intended only for use on private test networks.
struct EpochVTest;
impl Epoch for EpochVTest {
type Error = String;
fn verify<'a>(
&self,
precondition: &Precondition,
witness: &Witness,
ctx: &Context<'a>,
) -> Result<(), Error<Self::Error>> {
let ext_id = ExtensionId::try_from(precondition.extension_id)
.map_err(|InvalidExtId(id)| Error::InvalidExtensionId(id))?;
// This epoch recognizes the following set of extensions:
match ext_id {
ExtensionId::Demo => demo::Program
.verify(precondition, witness, ctx)
.map_err(|e| Error::ProgramError(format!("Epoch vTest program error: {}", e))),
}
}
}
pub fn epoch_for_branch(branch_id: BranchId) -> Option<Box<dyn Epoch<Error = String>>> {
// Map from consensus branch IDs to epochs.
match branch_id {
BranchId::ZFuture => Some(Box::new(EpochVTest)),
_ => None,
}
}

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

@ -0,0 +1,799 @@
//! Demo implementation of TZE consensus rules.
//!
//! The demo program implements a dual-hash-lock encumbrance with the following form:
//!
//! > `hash = BLAKE2b_256(preimage_1 || BLAKE2b_256(preimage_2))`
//!
//! The two preimages are revealed in sequential transactions, demonstrating how TZEs can
//! impose constraints on how program modes are chained together.
//!
//! The demo program has two modes:
//!
//! - Mode 0: `hash_1 = BLAKE2b_256(preimage_1 || hash_2)`
//! - Mode 1: `hash_2 = BLAKE2b_256(preimage_2)`
//!
//! and uses the following transaction formats:
//!
//! - `tx_a`: `[ [any input types...] ----> TzeOut(value, hash_1) ]`
//! - `tx_b`: `[ TzeIn(tx_a, preimage_1) -> TzeOut(value, hash_2) ]`
//! - `tx_c`: `[ TzeIn(tx_b, preimage_2) -> [any output types...] ]`
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fmt;
use blake2b_simd::Params;
use zcash_primitives::{
extensions::transparent::{Extension, ExtensionTxBuilder, FromPayload, ToPayload},
transaction::components::{amount::Amount, OutPoint, TzeOut},
};
/// Types and constants used for Mode 0 (open a channel)
mod open {
pub const MODE: u32 = 0;
#[derive(Debug, PartialEq)]
pub struct Precondition(pub [u8; 32]);
#[derive(Debug, PartialEq)]
pub struct Witness(pub [u8; 32]);
}
/// Types and constants used for Mode 1 (close a channel)
mod close {
pub const MODE: u32 = 1;
#[derive(Debug, PartialEq)]
pub struct Precondition(pub [u8; 32]);
#[derive(Debug, PartialEq)]
pub struct Witness(pub [u8; 32]);
}
/// The precondition type for the demo extension.
#[derive(Debug, PartialEq)]
pub enum Precondition {
Open(open::Precondition),
Close(close::Precondition),
}
impl Precondition {
/// Convenience constructor for opening precondition values.
pub fn open(hash: [u8; 32]) -> Self {
Precondition::Open(open::Precondition(hash))
}
/// Convenience constructor for closing precondition values.
pub fn close(hash: [u8; 32]) -> Self {
Precondition::Close(close::Precondition(hash))
}
}
/// Errors that may be produced during parsing and verification of demo preconditions and
/// witnesses.
#[derive(Debug, PartialEq)]
pub enum Error {
/// Parse error indicating that the payload of the condition or the witness was
/// not 32 bytes.
IllegalPayloadLength(usize),
/// Verification error indicating that the specified mode was not recognized by
/// the extension.
ModeInvalid(u32),
/// Verification error indicating that the transaction provided in the verification
/// context was missing required TZE inputs or outputs.
NonTzeTxn,
/// Verification error indicating that the witness being verified did not satisfy the
/// precondition under inspection.
HashMismatch,
/// Verification error indicating that the mode requested by the witness value did not
/// conform to that of the precondition under inspection.
ModeMismatch,
/// Verification error indicating that an `Open`-mode precondition was encountered
/// when a `Close` was expected.
ExpectedClose,
/// Verification error indicating that an unexpected number of TZE outputs (more than
/// one) was encountered in the transaction under inspection, in violation of
/// the extension's invariants.
InvalidOutputQty(usize),
}
impl fmt::Display for Error {
fn fmt<'a>(&self, f: &mut fmt::Formatter<'a>) -> fmt::Result {
match self {
Error::IllegalPayloadLength(sz) => write!(f, "Illegal payload length for demo: {}", sz),
Error::ModeInvalid(m) => write!(f, "Invalid TZE mode for demo program: {}", m),
Error::NonTzeTxn => write!(f, "Transaction has non-TZE inputs."),
Error::HashMismatch => write!(f, "Hash mismatch"),
Error::ModeMismatch => write!(f, "Extension operation mode mismatch."),
Error::ExpectedClose => write!(f, "Got open, expected close."),
Error::InvalidOutputQty(qty) => write!(f, "Incorrect number of outputs: {}", qty),
}
}
}
impl TryFrom<(u32, Precondition)> for Precondition {
type Error = Error;
fn try_from(from: (u32, Self)) -> Result<Self, Self::Error> {
match from {
(open::MODE, Precondition::Open(p)) => Ok(Precondition::Open(p)),
(close::MODE, Precondition::Close(p)) => Ok(Precondition::Close(p)),
_ => Err(Error::ModeInvalid(from.0)),
}
}
}
impl FromPayload for Precondition {
type Error = Error;
fn from_payload(mode: u32, payload: &[u8]) -> Result<Self, Self::Error> {
match mode {
open::MODE => payload
.try_into()
.map_err(|_| Error::IllegalPayloadLength(payload.len()))
.map(Precondition::open),
close::MODE => payload
.try_into()
.map_err(|_| Error::IllegalPayloadLength(payload.len()))
.map(Precondition::close),
_ => Err(Error::ModeInvalid(mode)),
}
}
}
impl ToPayload for Precondition {
fn to_payload(&self) -> (u32, Vec<u8>) {
match self {
Precondition::Open(p) => (open::MODE, p.0.to_vec()),
Precondition::Close(p) => (close::MODE, p.0.to_vec()),
}
}
}
/// The witness type for the demo extension.
#[derive(Debug, PartialEq)]
pub enum Witness {
Open(open::Witness),
Close(close::Witness),
}
impl Witness {
pub fn open(preimage: [u8; 32]) -> Self {
Witness::Open(open::Witness(preimage))
}
pub fn close(preimage: [u8; 32]) -> Self {
Witness::Close(close::Witness(preimage))
}
}
impl TryFrom<(u32, Witness)> for Witness {
type Error = Error;
fn try_from(from: (u32, Self)) -> Result<Self, Self::Error> {
match from {
(open::MODE, Witness::Open(p)) => Ok(Witness::Open(p)),
(close::MODE, Witness::Close(p)) => Ok(Witness::Close(p)),
_ => Err(Error::ModeInvalid(from.0)),
}
}
}
impl FromPayload for Witness {
type Error = Error;
fn from_payload(mode: u32, payload: &[u8]) -> Result<Self, Self::Error> {
match mode {
open::MODE => payload
.try_into()
.map_err(|_| Error::IllegalPayloadLength(payload.len()))
.map(Witness::open),
close::MODE => payload
.try_into()
.map_err(|_| Error::IllegalPayloadLength(payload.len()))
.map(Witness::close),
_ => Err(Error::ModeInvalid(mode)),
}
}
}
impl ToPayload for Witness {
fn to_payload(&self) -> (u32, Vec<u8>) {
match self {
Witness::Open(w) => (open::MODE, w.0.to_vec()),
Witness::Close(w) => (close::MODE, w.0.to_vec()),
}
}
}
/// This trait defines the context information that the demo extension requires
/// be made available to it by a consensus node integrating this extension.
///
/// This context type provides accessors to information relevant to a single
/// transaction being validated by the extension.
pub trait Context {
/// Predicate used to determine whether this transaction has only TZE
/// inputs and outputs. The demo extension does not support verification
/// of transactions which have either shielded or transparent inputs and
/// outputs.
fn is_tze_only(&self) -> bool;
/// List of all TZE outputs in the transaction being validate by the extension.
fn tx_tze_outputs(&self) -> &[TzeOut];
}
/// Marker type for the demo extension.
///
/// A value of this type will be used as the receiver for
/// `zcash_primitives::extensions::transparent::Extension` method invocations.
pub struct Program;
impl<C: Context> Extension<C> for Program {
type Precondition = Precondition;
type Witness = Witness;
type Error = Error;
/// Runs the program against the given precondition, witness, and context.
///
/// At this point the precondition and witness have been parsed and validated
/// non-contextually, and are guaranteed to both be for this program. All subsequent
/// validation is this function's responsibility.
fn verify_inner(
&self,
precondition: &Precondition,
witness: &Witness,
context: &C,
) -> Result<(), Error> {
// This match statement is selecting the mode that the program is operating in,
// based on the enums defined in the parser.
match (precondition, witness) {
(Precondition::Open(p_open), Witness::Open(w_open)) => {
// In OPEN mode, we enforce that the transaction must only contain inputs
// and outputs from this program. The consensus rules enforce that if a
// transaction contains both TZE inputs and TZE outputs, they must all be
// of the same program type. Therefore we only need to check that the
// transaction does not contain any other type of input or output.
if !context.is_tze_only() {
return Err(Error::NonTzeTxn);
}
// Next, check that there is only a single TZE output of the correct type.
let outputs = context.tx_tze_outputs();
match outputs {
[tze_out] => match Precondition::from_payload(
tze_out.precondition.mode,
&tze_out.precondition.payload,
) {
Ok(Precondition::Close(p_close)) => {
// Finally, check the precondition:
// precondition_open = BLAKE2b_256(witness_open || precondition_close)
let hash = Params::new()
.hash_length(32)
.personal(b"demo_pc_h1_perso")
.to_state()
.update(&w_open.0)
.update(&p_close.0)
.finalize();
if hash.as_bytes() == p_open.0 {
Ok(())
} else {
Err(Error::HashMismatch)
}
}
Ok(Precondition::Open(_)) => Err(Error::ExpectedClose),
Err(e) => Err(e),
},
_ => Err(Error::InvalidOutputQty(outputs.len())),
}
}
(Precondition::Close(p), Witness::Close(w)) => {
// In CLOSE mode, we only require that the precondition is satisfied:
// precondition_close = BLAKE2b_256(witness_close)
let hash = Params::new()
.hash_length(32)
.personal(b"demo_pc_h2_perso")
.hash(&w.0);
if hash.as_bytes() == p.0 {
Ok(())
} else {
Err(Error::HashMismatch)
}
}
_ => Err(Error::ModeMismatch),
}
}
}
fn hash_1(preimage_1: &[u8; 32], hash_2: &[u8; 32]) -> [u8; 32] {
let mut hash = [0; 32];
hash.copy_from_slice(
Params::new()
.hash_length(32)
.personal(b"demo_pc_h1_perso")
.to_state()
.update(preimage_1)
.update(hash_2)
.finalize()
.as_bytes(),
);
hash
}
/// Wrapper for [`zcash_primitives::transaction::builder::Builder`] that simplifies
/// constructing transactions that utilize the features of the demo extension.
pub struct DemoBuilder<B> {
/// The wrapped transaction builder.
pub txn_builder: B,
/// The assigned identifier for this extension. This is necessary as the author
/// of the demo extension will not know ahead of time what identifier will be
/// assigned to it at the time of inclusion in the Zcash consensus rules.
pub extension_id: u32,
}
/// Errors that can occur in construction of transactions using `DemoBuilder`.
#[derive(Debug)]
pub enum DemoBuildError<E> {
/// Wrapper for errors returned from the underlying `Builder`
BaseBuilderError(E),
ExpectedOpen,
ExpectedClose,
PrevoutParseFailure(Error),
TransferMismatch {
expected: [u8; 32],
actual: [u8; 32],
},
CloseMismatch {
expected: [u8; 32],
actual: [u8; 32],
},
}
/// Convenience methods for use with [`zcash_primitives::transaction::builder::Builder`]
/// for constructing transactions that utilize the features of the demo extension.
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> {
/// Add a channel-opening precondition to the outputs of the transaction under
/// construction.
pub fn demo_open(
&mut self,
value: Amount,
hash_1: [u8; 32],
) -> Result<(), DemoBuildError<B::BuildError>> {
// Call through to the generic builder.
self.txn_builder
.add_tze_output(self.extension_id, value, &Precondition::open(hash_1))
.map_err(DemoBuildError::BaseBuilderError)
}
/// Add a witness to a previous channel-opening precondition and a new channel-closing
/// precondition to the transaction under construction.
pub fn demo_transfer_to_close(
&mut self,
prevout: (OutPoint, TzeOut),
transfer_amount: Amount,
preimage_1: [u8; 32],
hash_2: [u8; 32],
) -> Result<(), DemoBuildError<B::BuildError>> {
let h1 = hash_1(&preimage_1, &hash_2);
// eagerly validate the relationship between prevout.1 and preimage_1
match Precondition::from_payload(
prevout.1.precondition.mode,
&prevout.1.precondition.payload,
) {
Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure)),
Ok(Precondition::Close(_)) => Err(DemoBuildError::ExpectedOpen),
Ok(Precondition::Open(hash)) if hash.0 != h1 => Err(DemoBuildError::TransferMismatch {
expected: hash.0,
actual: h1,
}),
Ok(Precondition::Open(_)) => {
self.txn_builder
.add_tze_input(self.extension_id, open::MODE, prevout, move |_| {
Ok(Witness::open(preimage_1))
})
.map_err(DemoBuildError::BaseBuilderError)?;
self.txn_builder
.add_tze_output(
self.extension_id,
transfer_amount,
&Precondition::close(hash_2),
)
.map_err(DemoBuildError::BaseBuilderError)
}
}
}
/// Add a channel-closing witness to the transaction under construction.
pub fn demo_close(
&mut self,
prevout: (OutPoint, TzeOut),
preimage_2: [u8; 32],
) -> Result<(), DemoBuildError<B::BuildError>> {
let hash_2 = {
let mut hash = [0; 32];
hash.copy_from_slice(
Params::new()
.hash_length(32)
.personal(b"demo_pc_h2_perso")
.hash(&preimage_2)
.as_bytes(),
);
hash
};
// eagerly validate the relationship between prevout.1 and preimage_2
match Precondition::from_payload(
prevout.1.precondition.mode,
&prevout.1.precondition.payload,
) {
Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure)),
Ok(Precondition::Open(_)) => Err(DemoBuildError::ExpectedClose),
Ok(Precondition::Close(hash)) if hash.0 != hash_2 => {
Err(DemoBuildError::CloseMismatch {
expected: hash.0,
actual: hash_2,
})
}
Ok(Precondition::Close(_)) => self
.txn_builder
.add_tze_input(self.extension_id, close::MODE, prevout, move |_| {
Ok(Witness::close(preimage_2))
})
.map_err(DemoBuildError::BaseBuilderError),
}
}
}
#[cfg(test)]
mod tests {
use blake2b_simd::Params;
use ff::{Field, PrimeField};
use rand_core::OsRng;
use zcash_proofs::prover::LocalTxProver;
use zcash_primitives::{
consensus::{BranchId, H0, TEST_NETWORK},
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, hash_1, open, Context, DemoBuilder, Precondition, Program, Witness};
fn demo_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)
.personal(b"demo_pc_h2_perso")
.hash(preimage_2)
.as_bytes(),
);
hash
};
(hash_1(preimage_1, &hash_2), hash_2)
}
#[test]
fn precondition_open_round_trip() {
let data = vec![7; 32];
let p = Precondition::from_payload(open::MODE, &data).unwrap();
assert_eq!(p, Precondition::Open(open::Precondition([7; 32])));
assert_eq!(p.to_payload(), (open::MODE, data));
}
#[test]
fn precondition_close_round_trip() {
let data = vec![7; 32];
let p = Precondition::from_payload(close::MODE, &data).unwrap();
assert_eq!(p, Precondition::Close(close::Precondition([7; 32])));
assert_eq!(p.to_payload(), (close::MODE, data));
}
#[test]
fn precondition_rejects_invalid_mode_or_length() {
for mode in 0..3 {
for len in &[31, 33] {
let p = Precondition::from_payload(mode, &vec![7; *len]);
assert!(p.is_err());
}
}
}
#[test]
fn witness_open_round_trip() {
let data = vec![7; 32];
let w = Witness::from_payload(open::MODE, &data).unwrap();
assert_eq!(w, Witness::open([7; 32]));
assert_eq!(w.to_payload(), (open::MODE, data));
}
#[test]
fn witness_close_round_trip() {
let data = vec![7; 32];
let p = Witness::from_payload(close::MODE, &data).unwrap();
assert_eq!(p, Witness::close([7; 32]));
assert_eq!(p.to_payload(), (close::MODE, data));
}
#[test]
fn witness_rejects_invalid_mode_or_length() {
for mode in 0..3 {
for len in &[31, 33] {
let p = Witness::from_payload(mode, &vec![7; *len]);
assert!(p.is_err());
}
}
}
/// Dummy context
pub struct Ctx<'a> {
pub tx: &'a Transaction,
}
/// Implementation of required operations for the demo extension, as satisfied
/// by the context.
impl<'a> Context for Ctx<'a> {
fn is_tze_only(&self) -> bool {
self.tx.vin.is_empty()
&& self.tx.vout.is_empty()
&& self.tx.shielded_spends.is_empty()
&& self.tx.shielded_outputs.is_empty()
&& self.tx.joinsplits.is_empty()
}
fn tx_tze_outputs(&self) -> &[TzeOut] {
&self.tx.tze_outputs
}
}
#[test]
fn demo_program() {
let preimage_1 = [1; 32];
let preimage_2 = [2; 32];
let hash_2 = {
let mut hash = [0; 32];
hash.copy_from_slice(
Params::new()
.hash_length(32)
.personal(b"demo_pc_h2_perso")
.hash(&preimage_2)
.as_bytes(),
);
hash
};
let hash_1 = {
let mut hash = [0; 32];
hash.copy_from_slice(
Params::new()
.hash_length(32)
.personal(b"demo_pc_h1_perso")
.to_state()
.update(&preimage_1)
.update(&hash_2)
.finalize()
.as_bytes(),
);
hash
};
//
// Opening transaction
//
let out_a = TzeOut {
value: Amount::from_u64(1).unwrap(),
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
};
let mut mtx_a = TransactionData::zfuture();
mtx_a.tze_outputs.push(out_a);
let tx_a = mtx_a.freeze().unwrap();
//
// Transfer
//
let in_b = TzeIn {
prevout: OutPoint::new(tx_a.txid().0, 0),
witness: tze::Witness::from(0, &Witness::open(preimage_1)),
};
let out_b = TzeOut {
value: Amount::from_u64(1).unwrap(),
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
};
let mut mtx_b = TransactionData::zfuture();
mtx_b.tze_inputs.push(in_b);
mtx_b.tze_outputs.push(out_b);
let tx_b = mtx_b.freeze().unwrap();
//
// 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::zfuture();
mtx_c.tze_inputs.push(in_c);
let tx_c = mtx_c.freeze().unwrap();
// Verify tx_b
{
let ctx = Ctx { tx: &tx_b };
assert_eq!(
Program.verify(
&tx_a.tze_outputs[0].precondition,
&tx_b.tze_inputs[0].witness,
&ctx
),
Ok(())
);
}
// Verify tx_c
{
let ctx = Ctx { tx: &tx_c };
assert_eq!(
Program.verify(
&tx_b.tze_outputs[0].precondition,
&tx_c.tze_inputs[0].witness,
&ctx
),
Ok(())
);
}
}
#[test]
fn demo_builder_program() {
let preimage_1 = [1; 32];
let preimage_2 = [2; 32];
// Only run the test if we have the prover parameters.
let prover = match LocalTxProver::with_default_location() {
Some(prover) => prover,
None => return,
};
//
// Opening transaction
//
let mut rng = OsRng;
let mut builder_a = Builder::new_with_rng_zfuture(TEST_NETWORK, H0, rng);
// 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();
let (h1, h2) = demo_hashes(&preimage_1, &preimage_2);
db_a.demo_open(value, h1)
.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::new_with_rng_zfuture(TEST_NETWORK, H0, rng);
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.tze_outputs[0].clone());
let value_xfr = Amount::from_u64(90000).unwrap();
db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, h2)
.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::new_with_rng_zfuture(TEST_NETWORK, H0, rng);
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.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.tze_outputs[0].precondition,
&tx_b.tze_inputs[0].witness,
&ctx0
),
Ok(())
);
// Verify tx_c
let ctx1 = Ctx { tx: &tx_c };
assert_eq!(
Program.verify(
&tx_b.tze_outputs[0].precondition,
&tx_c.tze_inputs[0].witness,
&ctx1
),
Ok(())
);
}
}

View File

@ -35,12 +35,13 @@ rand_core = "0.5.1"
ripemd160 = { version = "0.9", optional = true }
secp256k1 = { version = "0.19", optional = true }
sha2 = "0.9"
subtle = "2.2.1"
subtle = "2.2.3"
[dev-dependencies]
criterion = "0.3"
hex-literal = "0.2"
rand_xorshift = "0.2"
proptest = "0.10.1"
[features]
transparent-inputs = ["ripemd160", "secp256k1"]

View File

@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use rand_core::OsRng;
use zcash_primitives::{
consensus::{NetworkUpgrade::Canopy, Parameters, TestNetwork},
consensus::{NetworkUpgrade::Canopy, Parameters, TEST_NETWORK},
note_encryption::{try_sapling_note_decryption, Memo, SaplingNoteEncryption},
primitives::{Diversifier, PaymentAddress, ValueCommitment},
transaction::components::{OutputDescription, GROTH_PROOF_SIZE},
@ -10,9 +10,8 @@ use zcash_primitives::{
};
fn bench_note_decryption(c: &mut Criterion) {
let params = TestNetwork;
let mut rng = OsRng;
let height = params.activation_height(Canopy).unwrap();
let height = TEST_NETWORK.activation_height(Canopy).unwrap();
let valid_ivk = jubjub::Fr::random(&mut rng);
let invalid_ivk = jubjub::Fr::random(&mut rng);
@ -23,7 +22,7 @@ fn bench_note_decryption(c: &mut Criterion) {
let pk_d = diversifier.g_d().unwrap() * valid_ivk;
let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
let rseed = generate_random_rseed(&params, height, &mut rng);
let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
// Construct the value commitment for the proof instance
let value = 100;
@ -56,7 +55,7 @@ fn bench_note_decryption(c: &mut Criterion) {
group.bench_function("valid", |b| {
b.iter(|| {
try_sapling_note_decryption(
&TestNetwork,
&TEST_NETWORK,
height,
&valid_ivk,
&output.ephemeral_key,
@ -70,7 +69,7 @@ fn bench_note_decryption(c: &mut Criterion) {
group.bench_function("invalid", |b| {
b.iter(|| {
try_sapling_note_decryption(
&TestNetwork,
&TEST_NETWORK,
height,
&invalid_ivk,
&output.ephemeral_key,

View File

@ -1,4 +1,4 @@
//! Consensus parameters.
//! Consensus logic and parameters.
use std::cmp::{Ord, Ordering};
use std::convert::TryFrom;
@ -164,6 +164,7 @@ impl Parameters for MainNetwork {
NetworkUpgrade::Blossom => Some(BlockHeight(653_600)),
NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)),
NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)),
NetworkUpgrade::ZFuture => None,
}
}
@ -198,6 +199,7 @@ impl Parameters for TestNetwork {
NetworkUpgrade::Blossom => Some(BlockHeight(584_000)),
NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)),
NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)),
NetworkUpgrade::ZFuture => None,
}
}
@ -287,6 +289,12 @@ pub enum NetworkUpgrade {
///
/// [Canopy]: https://z.cash/upgrade/canopy/
Canopy,
/// The [ZFUTURE] network upgrade.
///
/// This upgrade is expected never to activate on mainnet;
/// it is intended for use in integration testing of functionality
/// that is a candidate for integration in a future network upgrade.
ZFuture,
}
impl fmt::Display for NetworkUpgrade {
@ -297,6 +305,7 @@ impl fmt::Display for NetworkUpgrade {
NetworkUpgrade::Blossom => write!(f, "Blossom"),
NetworkUpgrade::Heartwood => write!(f, "Heartwood"),
NetworkUpgrade::Canopy => write!(f, "Canopy"),
NetworkUpgrade::ZFuture => write!(f, "ZFUTURE"),
}
}
}
@ -309,6 +318,7 @@ impl NetworkUpgrade {
NetworkUpgrade::Blossom => BranchId::Blossom,
NetworkUpgrade::Heartwood => BranchId::Heartwood,
NetworkUpgrade::Canopy => BranchId::Canopy,
NetworkUpgrade::ZFuture => BranchId::ZFuture,
}
}
}
@ -354,6 +364,9 @@ pub enum BranchId {
Heartwood,
/// The consensus rules deployed by [`NetworkUpgrade::Canopy`].
Canopy,
/// Candidates for future consensus rules; this branch will never
/// activate on mainnet.
ZFuture,
}
impl TryFrom<u32> for BranchId {
@ -367,6 +380,7 @@ impl TryFrom<u32> for BranchId {
0x2bb4_0e60 => Ok(BranchId::Blossom),
0xf5b9_230b => Ok(BranchId::Heartwood),
0xe9ff_75a6 => Ok(BranchId::Canopy),
0xffff_ffff => Ok(BranchId::ZFuture),
_ => Err("Unknown consensus branch ID"),
}
}
@ -381,6 +395,7 @@ impl From<BranchId> for u32 {
BranchId::Blossom => 0x2bb4_0e60,
BranchId::Heartwood => 0xf5b9_230b,
BranchId::Canopy => 0xe9ff_75a6,
BranchId::ZFuture => 0xffff_ffff,
}
}
}

View File

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

View File

@ -0,0 +1,195 @@
//! Core traits and structs for Transparent Zcash Extensions.
use crate::transaction::components::{Amount, OutPoint, TzeOut};
use std::fmt;
/// Binary parsing capability for TZE preconditions & witnesses.
///
/// Serialization formats interpreted by implementations of this trait become consensus-critical
/// upon activation of of the extension that uses them.
pub trait FromPayload: Sized {
type Error;
/// Parses an extension-specific witness or precondition from a mode and payload.
fn from_payload(mode: u32, payload: &[u8]) -> Result<Self, Self::Error>;
}
/// Binary serialization capability for TZE preconditions & witnesses.
///
/// Serialization formats used by implementations of this trait become consensus-critical upon
/// activation of of the extension that uses them.
pub trait ToPayload {
/// Returns a serialized payload and its corresponding mode.
fn to_payload(&self) -> (u32, Vec<u8>);
}
/// A condition that can be used to encumber transparent funds.
///
/// This struct is an intermediate representation between the serialized binary format which is
/// used inside of a transaction, and extension-specific types. The payload field of this struct is
/// treated as opaque to all but the extension corresponding to the encapsulated `extension_id`
/// value.
#[derive(Clone, Debug, PartialEq)]
pub struct Precondition {
pub extension_id: u32,
pub mode: u32,
pub payload: Vec<u8>,
}
impl Precondition {
/// Produce the intermediate format for an extension-specific precondition
/// type.
pub fn from<P: ToPayload>(extension_id: u32, value: &P) -> Precondition {
let (mode, payload) = value.to_payload();
Precondition {
extension_id,
mode,
payload,
}
}
/// Attempt to parse an extension-specific precondition value from the
/// intermediate representation.
pub fn try_to<P: FromPayload>(&self) -> Result<P, P::Error> {
P::from_payload(self.mode, &self.payload)
}
}
/// Data that satisfies the precondition for prior encumbered funds, enabling them to be spent.
///
/// This struct is an intermediate representation between the serialized binary format which is
/// used inside of a transaction, and extension-specific types. The payload field of this struct is
/// treated as opaque to all but the extension corresponding to the encapsulated `extension_id`
/// value.
#[derive(Clone, Debug, PartialEq)]
pub struct Witness {
pub extension_id: u32,
pub mode: u32,
pub payload: Vec<u8>,
}
impl Witness {
/// Produce the intermediate format for an extension-specific witness
/// type.
pub fn from<P: ToPayload>(extension_id: u32, value: &P) -> Witness {
let (mode, payload) = value.to_payload();
Witness {
extension_id,
mode,
payload,
}
}
/// Attempt to parse an extension-specific witness value from the
/// intermediate representation.
pub fn try_to<P: FromPayload>(&self) -> Result<P, P::Error> {
P::from_payload(self.mode, &self.payload)
}
}
#[derive(Debug, PartialEq)]
pub enum Error<E> {
InvalidExtensionId(u32),
ProgramError(E),
}
impl<E: fmt::Display> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidExtensionId(extension_id) => {
write!(f, "Unrecognized program type id {}", extension_id)
}
Error::ProgramError(err) => write!(f, "Program error: {}", err),
}
}
}
/// This is the primary trait which must be implemented by an extension type for that type to be
/// eligible for inclusion in Zcash consensus rules.
pub trait Extension<C> {
/// Extension-specific precondition type. The extension will need to implement
/// [`FromPayload<Error = Self::Error>`] for this type in order for their extension to be
/// eligible for integration into consensus rules.
type Precondition;
/// Extension-specific witness type. The extension will need to implement [`FromPayload<Error =
/// Self::Error>`] for this type in order for their extension to be eligible for integration
/// into consensus rules.
type Witness;
/// Extension-specific error type. This should encompass both parsing and verification errors.
type Error;
/// This is the primary method that an extension must implement. Implementations should return
/// [`Ok(())`] if verification of the witness succeeds against the supplied precondition, and
/// an error in any other case.
fn verify_inner(
&self,
precondition: &Self::Precondition,
witness: &Self::Witness,
context: &C,
) -> Result<(), Self::Error>;
/// This is a convenience method intended for use by consensus nodes at the integration point
/// to provide easy interoperation with the opaque, cross-extension `Precondition` and
/// `Witness` types.
fn verify(
&self,
precondition: &Precondition,
witness: &Witness,
context: &C,
) -> Result<(), Self::Error>
where
Self::Precondition: FromPayload<Error = Self::Error>,
Self::Witness: FromPayload<Error = Self::Error>,
{
self.verify_inner(
&Self::Precondition::from_payload(precondition.mode, &precondition.payload)?,
&Self::Witness::from_payload(witness.mode, &witness.payload)?,
&context,
)
}
}
/// An interface for transaction builders which support addition of TZE inputs and outputs.
///
/// This extension trait is satisfied by [`transaction::builder::Builder`]. 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.
///
/// [`transaction::builder::Builder`]: crate::transaction::builder::Builder
pub trait ExtensionTxBuilder<'a> {
type BuildCtx;
type BuildError;
/// Adds a TZE input to the transaction by providing a witness to a precondition identified by a
/// prior outpoint.
///
/// The `witness_builder` function allows the transaction builder to provide extra contextual
/// information from the transaction under construction to be used in the production of this
/// witness (for example, so that the witness may internally make commitments based upon this
/// information.) For the standard transaction builder, the value provided here is the
/// transaction under construction.
fn add_tze_input<WBuilder, W: ToPayload>(
&mut self,
extension_id: u32,
mode: u32,
prevout: (OutPoint, TzeOut),
witness_builder: WBuilder,
) -> Result<(), Self::BuildError>
where
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, Self::BuildError>);
/// Adds a TZE precondition to the transaction which must be satisfied by a future transaction's
/// witness in order to spend the specified `amount`.
fn add_tze_output<Precondition: ToPayload>(
&mut self,
extension_id: u32,
value: Amount,
guarded_by: &Precondition,
) -> Result<(), Self::BuildError>;
}

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

@ -10,6 +10,7 @@
pub mod block;
pub mod consensus;
pub mod constants;
pub mod extensions;
pub mod group_hash;
pub mod keys;
pub mod legacy;

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

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

@ -3,10 +3,10 @@ use std::io::{self, Read, Write};
const MAX_SIZE: usize = 0x02000000;
struct CompactSize;
pub(crate) struct CompactSize;
impl CompactSize {
fn read<R: Read>(mut reader: R) -> io::Result<usize> {
pub(crate) fn read<R: Read>(mut reader: R) -> io::Result<usize> {
let flag = reader.read_u8()?;
match if flag < 253 {
Ok(flag as usize)
@ -43,7 +43,7 @@ impl CompactSize {
}
}
fn write<W: Write>(mut writer: W, size: usize) -> io::Result<()> {
pub(crate) fn write<W: Write>(mut writer: W, size: usize) -> io::Result<()> {
match size {
s if s < 253 => writer.write_u8(s as u8),
s if s <= 0xFFFF => {

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::{self, BlockHeight},
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},
signature_hash_data, Transaction, TransactionData, SIGHASH_ALL,
components::{
amount::Amount, amount::DEFAULT_FEE, OutPoint, OutputDescription, SpendDescription,
TxOut, TzeIn, TzeOut,
},
signature_hash_data, SignableInput, Transaction, TransactionData, SIGHASH_ALL,
},
util::generate_random_rseed,
zip32::ExtendedSpendingKey,
};
#[cfg(feature = "transparent-inputs")]
@ -45,6 +51,7 @@ pub enum Error {
InvalidAmount,
NoChangeAddress,
SpendProof,
TzeWitnessModeMismatch(u32, u32),
}
impl fmt::Display for Error {
@ -61,6 +68,8 @@ impl fmt::Display for Error {
Error::InvalidAmount => write!(f, "Invalid amount"),
Error::NoChangeAddress => write!(f, "No change address specified or discoverable"),
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
Error::TzeWitnessModeMismatch(expected, actual) =>
write!(f, "TZE witness builder returned a mode that did not match the mode with which the input was initially constructed: expected = {:?}, actual = {:?}", expected, actual),
}
}
}
@ -187,17 +196,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 RIPEMD-160 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)) => {
@ -211,7 +217,6 @@ impl TransparentInputs {
_ => return Err(Error::InvalidAddress),
}
mtx.vin.push(TxIn::new(utxo));
self.inputs.push(TransparentInputInfo { sk, pubkey, coin });
Ok(())
@ -244,7 +249,7 @@ impl TransparentInputs {
mtx,
consensus_branch_id,
SIGHASH_ALL,
Some((i, &info.coin.script_pubkey, info.coin.value)),
SignableInput::transparent(i, &info.coin.script_pubkey, info.coin.value),
));
let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes");
@ -263,6 +268,31 @@ impl TransparentInputs {
fn apply_signatures(&self, _: &mut TransactionData, _: consensus::BranchId) {}
}
struct TzeInputInfo<'a, BuildCtx> {
prevout: TzeOut,
builder: Box<dyn FnOnce(&BuildCtx) -> Result<(u32, Vec<u8>), Error> + 'a>,
}
struct TzeInputs<'a, BuildCtx> {
builders: Vec<TzeInputInfo<'a, BuildCtx>>,
}
impl<'a, BuildCtx> TzeInputs<'a, BuildCtx> {
fn default() -> Self {
TzeInputs { builders: vec![] }
}
fn push<WBuilder, W: ToPayload>(&mut self, tzeout: TzeOut, builder: WBuilder)
where
WBuilder: 'a + FnOnce(&BuildCtx) -> Result<W, Error>,
{
self.builders.push(TzeInputInfo {
prevout: tzeout,
builder: Box::new(move |ctx| builder(&ctx).map(|x| x.to_payload())),
});
}
}
/// Metadata about a transaction created by a [`Builder`].
#[derive(Debug, PartialEq)]
pub struct TransactionMetadata {
@ -302,7 +332,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> {
params: P,
rng: R,
height: BlockHeight,
@ -312,11 +342,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,9 +360,27 @@ impl<P: consensus::Parameters> Builder<P, OsRng> {
pub fn new(params: P, height: BlockHeight) -> Self {
Builder::new_with_rng(params, height, 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,
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
///
/// # 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).
///
/// The transaction will be constructed and serialized according to the
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
/// integration testing of new features.
pub fn new_zfuture(params: P, height: BlockHeight) -> Self {
Builder::new_with_rng_zfuture(params, height, 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.
///
@ -341,8 +390,35 @@ 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(params: P, height: BlockHeight, rng: R) -> Builder<P, R> {
let mut mtx = TransactionData::new();
pub fn new_with_rng(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(params, height, rng, TransactionData::new())
}
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
/// and randomness source, using default values for general transaction fields
/// and the `ZFUTURE_TX_VERSION` and `ZFUTURE_VERSION_GROUP_ID` version identifiers.
///
/// # 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).
///
/// The transaction will be constructed and serialized according to the
/// NetworkUpgrade::ZFuture rules. This is intended only for use in
/// integration testing of new features.
pub fn new_with_rng_zfuture(params: P, height: BlockHeight, rng: R) -> Builder<'a, P, R> {
Self::new_with_mtx(params, height, rng, TransactionData::zfuture())
}
/// Common utility function for builder construction.
fn new_with_mtx(
params: P,
height: BlockHeight,
rng: R,
mut mtx: TransactionData,
) -> Builder<'a, P, R> {
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
Builder {
@ -355,6 +431,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
spends: vec![],
outputs: vec![],
transparent_inputs: TransparentInputs::default(),
tze_inputs: TzeInputs::default(),
change_address: None,
phantom: PhantomData,
}
@ -431,7 +508,9 @@ 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.transparent_inputs.push(sk, coin)?;
self.mtx.vin.push(TxIn::new(utxo));
Ok(());
}
/// Adds a transparent address to send funds to.
@ -482,12 +561,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));
}
@ -669,7 +756,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
}
//
// Signatures
// Signatures -- everything but the signatures must already have been added.
//
let mut sighash = [0u8; 32];
@ -677,7 +764,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
&self.mtx,
consensus_branch_id,
SIGHASH_ALL,
None,
SignableInput::Shielded,
));
// Create Sapling spendAuth and binding signatures
@ -691,14 +778,28 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<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 (i, tze_in) in self.tze_inputs.builders.into_iter().enumerate() {
// The witness builder function should have cached/closed over whatever data was necessary for the
// witness to commit to at the time it was added to the transaction builder; here, it then computes those
// commitments.
let (mode, payload) = (tze_in.builder)(&self.mtx)?;
let mut current = self.mtx.tze_inputs.get_mut(i).unwrap();
if mode != current.witness.mode {
return Err(Error::TzeWitnessModeMismatch(current.witness.mode, mode));
}
current.witness.payload = payload;
}
// Transparent signatures
@ -712,13 +813,59 @@ 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: u32,
mode: u32,
(outpoint, prevout): (OutPoint, TzeOut),
witness_builder: WBuilder,
) -> Result<(), Self::BuildError>
where
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, Self::BuildError>),
{
self.mtx
.tze_inputs
.push(TzeIn::new(outpoint, extension_id, mode));
self.tze_inputs.push(prevout, witness_builder);
Ok(())
}
fn add_tze_output<G: ToPayload>(
&mut self,
extension_id: u32,
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};
use rand_core::OsRng;
use std::marker::PhantomData;
use super::{Builder, Error};
use crate::{
consensus::{self, Parameters, H0, TEST_NETWORK},
legacy::TransparentAddress,
@ -730,6 +877,8 @@ mod tests {
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
use super::{Builder, Error, TzeInputs};
#[test]
fn fails_on_negative_output() {
let extsk = ExtendedSpendingKey::master(&[]);
@ -767,6 +916,7 @@ mod tests {
spends: vec![],
outputs: vec![],
transparent_inputs: TransparentInputs::default(),
tze_inputs: TzeInputs::default(),
change_address: None,
phantom: PhantomData,
};

View File

@ -1,12 +1,17 @@
//! Structs representing the components within Zcash transactions.
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use std::convert::TryFrom;
use std::io::{self, Read, Write};
use crate::extensions::transparent as tze;
use crate::legacy::Script;
use crate::redjubjub::{PublicKey, Signature};
use crate::serialize::{CompactSize, Vector};
pub mod amount;
pub use self::amount::Amount;
@ -51,7 +56,7 @@ impl OutPoint {
}
}
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq)]
pub struct TxIn {
pub prevout: OutPoint,
pub script_sig: Script,
@ -88,7 +93,7 @@ impl TxIn {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct TxOut {
pub value: Amount,
pub script_pubkey: Script,
@ -116,6 +121,129 @@ impl TxOut {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TzeIn {
pub prevout: OutPoint,
pub witness: tze::Witness,
}
fn to_io_error(_: std::num::TryFromIntError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, "value out of range")
}
/// Transaction encoding and decoding functions conforming to ZIP-222
///
/// https://zips.z.cash/zip-0222#encoding-in-transactions
impl TzeIn {
/// Convenience constructor
pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self {
TzeIn {
prevout,
witness: tze::Witness {
extension_id,
mode,
payload: vec![],
},
}
}
/// Read witness metadata & payload
///
/// Used to decode the encoded form used within a serialized
/// transaction.
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let prevout = OutPoint::read(&mut reader)?;
let extension_id = CompactSize::read(&mut reader)?;
let mode = CompactSize::read(&mut reader)?;
let payload = Vector::read(&mut reader, |r| r.read_u8())?;
Ok(TzeIn {
prevout,
witness: tze::Witness {
extension_id: u32::try_from(extension_id).map_err(|e| to_io_error(e))?,
mode: u32::try_from(mode).map_err(|e| to_io_error(e))?,
payload,
},
})
}
/// Write without witness data (for signature hashing)
///
/// This is also used as the prefix for the encoded form used
/// within a serialized transaction.
pub fn write_without_witness<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.prevout.write(&mut writer)?;
CompactSize::write(
&mut writer,
usize::try_from(self.witness.extension_id).map_err(|e| to_io_error(e))?,
)?;
CompactSize::write(
&mut writer,
usize::try_from(self.witness.mode).map_err(|e| to_io_error(e))?,
)
}
/// Write prevout, extension, and mode followed by witness data.
///
/// This calls [`write_without_witness`] to serialize witness metadata,
/// then appends the witness bytes themselves. This is the encoded
/// form that is used in a serialized transaction.
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.write_without_witness(&mut writer)?;
Vector::write(&mut writer, &self.witness.payload, |w, b| w.write_u8(*b))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TzeOut {
pub value: Amount,
pub precondition: tze::Precondition,
}
impl TzeOut {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let value = {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
Amount::from_nonnegative_i64_le_bytes(tmp)
}
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
let extension_id = CompactSize::read(&mut reader)?;
let mode = CompactSize::read(&mut reader)?;
let payload = Vector::read(&mut reader, |r| r.read_u8())?;
Ok(TzeOut {
value,
precondition: tze::Precondition {
extension_id: u32::try_from(extension_id).map_err(|e| to_io_error(e))?,
mode: u32::try_from(mode).map_err(|e| to_io_error(e))?,
payload,
},
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.value.to_i64_le_bytes())?;
CompactSize::write(
&mut writer,
usize::try_from(self.precondition.extension_id).map_err(|e| to_io_error(e))?,
)?;
CompactSize::write(
&mut writer,
usize::try_from(self.precondition.mode).map_err(|e| to_io_error(e))?,
)?;
Vector::write(&mut writer, &self.precondition.payload, |w, b| {
w.write_u8(*b)
})
}
}
#[derive(Clone)]
pub struct SpendDescription {
pub cv: jubjub::ExtendedPoint,
pub anchor: bls12_381::Scalar,
@ -205,6 +333,7 @@ impl SpendDescription {
}
}
#[derive(Clone)]
pub struct OutputDescription {
pub cv: jubjub::ExtendedPoint,
pub cmu: bls12_381::Scalar,
@ -296,6 +425,7 @@ impl OutputDescription {
}
}
#[derive(Clone)]
enum SproutProof {
Groth([u8; GROTH_PROOF_SIZE]),
PHGR([u8; PHGR_PROOF_SIZE]),
@ -310,6 +440,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

@ -16,15 +16,26 @@ mod sighash;
#[cfg(test)]
mod tests;
pub use self::sighash::{signature_hash, signature_hash_data, SIGHASH_ALL};
pub use self::sighash::{signature_hash, signature_hash_data, SignableInput, SIGHASH_ALL};
use self::components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut};
use self::components::{
Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut, TzeIn, TzeOut,
};
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
const OVERWINTER_TX_VERSION: u32 = 3;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
const SAPLING_TX_VERSION: u32 = 4;
/// These versions are used exclusively for in-development transaction
/// serialization, and will never be active under the consensus rules.
/// When new consensus transaction versions are added, all call sites
/// using these constants should be inspected, and use of these constants
/// should be removed as appropriate in favor of the new consensus
/// transaction version and group.
const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF;
const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF;
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct TxId(pub [u8; 32]);
@ -37,7 +48,7 @@ impl fmt::Display for TxId {
}
/// A Zcash transaction.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Transaction {
txid: TxId,
data: TransactionData,
@ -57,12 +68,15 @@ impl PartialEq for Transaction {
}
}
#[derive(Clone)]
pub struct TransactionData {
pub overwintered: bool,
pub version: u32,
pub version_group_id: u32,
pub vin: Vec<TxIn>,
pub vout: Vec<TxOut>,
pub tze_inputs: Vec<TzeIn>,
pub tze_outputs: Vec<TzeOut>,
pub lock_time: u32,
pub expiry_height: BlockHeight,
pub value_balance: Amount,
@ -84,6 +98,8 @@ impl std::fmt::Debug for TransactionData {
version_group_id = {:?},
vin = {:?},
vout = {:?},
tze_inputs = {:?},
tze_outputs = {:?},
lock_time = {:?},
expiry_height = {:?},
value_balance = {:?},
@ -97,6 +113,8 @@ impl std::fmt::Debug for TransactionData {
self.version_group_id,
self.vin,
self.vout,
self.tze_inputs,
self.tze_outputs,
self.lock_time,
self.expiry_height,
self.value_balance,
@ -117,6 +135,29 @@ impl TransactionData {
version_group_id: SAPLING_VERSION_GROUP_ID,
vin: vec![],
vout: vec![],
tze_inputs: vec![],
tze_outputs: vec![],
lock_time: 0,
expiry_height: 0u32.into(),
value_balance: Amount::zero(),
shielded_spends: vec![],
shielded_outputs: vec![],
joinsplits: vec![],
joinsplit_pubkey: None,
joinsplit_sig: None,
binding_sig: None,
}
}
pub fn zfuture() -> Self {
TransactionData {
overwintered: true,
version: ZFUTURE_TX_VERSION,
version_group_id: ZFUTURE_VERSION_GROUP_ID,
vin: vec![],
vout: vec![],
tze_inputs: vec![],
tze_outputs: vec![],
lock_time: 0,
expiry_height: 0u32.into(),
value_balance: Amount::zero(),
@ -177,7 +218,11 @@ impl Transaction {
let is_sapling_v4 = overwintered
&& version_group_id == SAPLING_VERSION_GROUP_ID
&& version == SAPLING_TX_VERSION;
if overwintered && !(is_overwinter_v3 || is_sapling_v4) {
let has_tze = overwintered
&& version_group_id == ZFUTURE_VERSION_GROUP_ID
&& version == ZFUTURE_TX_VERSION;
if overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
@ -186,14 +231,22 @@ impl Transaction {
let vin = Vector::read(&mut reader, TxIn::read)?;
let vout = Vector::read(&mut reader, TxOut::read)?;
let (tze_inputs, tze_outputs) = if has_tze {
let wi = Vector::read(&mut reader, TzeIn::read)?;
let wo = Vector::read(&mut reader, TzeOut::read)?;
(wi, wo)
} else {
(vec![], vec![])
};
let lock_time = reader.read_u32::<LittleEndian>()?;
let expiry_height: BlockHeight = if is_overwinter_v3 || is_sapling_v4 {
let expiry_height: BlockHeight = if is_overwinter_v3 || is_sapling_v4 || has_tze {
reader.read_u32::<LittleEndian>()?.into()
} else {
0u32.into()
};
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 {
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 || has_tze {
let vb = {
let mut tmp = [0; 8];
reader.read_exact(&mut tmp)?;
@ -225,12 +278,13 @@ impl Transaction {
(vec![], None, None)
};
let binding_sig =
if is_sapling_v4 && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
Some(Signature::read(&mut reader)?)
} else {
None
};
let binding_sig = if (is_sapling_v4 || has_tze)
&& !(shielded_spends.is_empty() && shielded_outputs.is_empty())
{
Some(Signature::read(&mut reader)?)
} else {
None
};
Transaction::from_data(TransactionData {
overwintered,
@ -238,6 +292,8 @@ impl Transaction {
version_group_id,
vin,
vout,
tze_inputs,
tze_outputs,
lock_time,
expiry_height,
value_balance,
@ -262,7 +318,11 @@ impl Transaction {
let is_sapling_v4 = self.overwintered
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
&& self.version == SAPLING_TX_VERSION;
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4) {
let has_tze = self.overwintered
&& self.version_group_id == ZFUTURE_VERSION_GROUP_ID
&& self.version == ZFUTURE_TX_VERSION;
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Unknown transaction format",
@ -271,12 +331,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 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 {
if is_overwinter_v3 || is_sapling_v4 || has_tze {
writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
}
if is_sapling_v4 {
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))?;
@ -321,7 +385,9 @@ impl Transaction {
}
}
if is_sapling_v4 && !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty()) {
if (is_sapling_v4 || has_tze)
&& !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty())
{
match self.binding_sig {
Some(sig) => sig.write(&mut writer)?,
None => {

View File

@ -1,14 +1,22 @@
use std::convert::TryInto;
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use crate::{
consensus,
extensions::transparent::Precondition,
legacy::Script,
serialize::{CompactSize, Vector},
};
use super::{
components::{Amount, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
SAPLING_VERSION_GROUP_ID,
SAPLING_VERSION_GROUP_ID, ZFUTURE_VERSION_GROUP_ID,
};
use crate::{consensus, legacy::Script};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
@ -17,6 +25,11 @@ const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";
const ZCASH_TZE_INPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"Zcash_TzeInsHash";
const ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashTzeOutsHash";
const ZCASH_TZE_SIGNED_INPUT_TAG: &[u8; 1] = &[0x00];
const ZCASH_TRANSPARENT_SIGNED_INPUT_TAG: &[u8; 1] = &[0x01];
pub const SIGHASH_ALL: u32 = 1;
const SIGHASH_NONE: u32 = 2;
@ -46,6 +59,7 @@ enum SigHashVersion {
Sprout,
Overwinter,
Sapling,
ZFuture,
}
impl SigHashVersion {
@ -54,6 +68,7 @@ impl SigHashVersion {
match tx.version_group_id {
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
ZFUTURE_VERSION_GROUP_ID => SigHashVersion::ZFuture,
_ => unimplemented!(),
}
} else {
@ -151,15 +166,69 @@ fn shielded_outputs_hash(tx: &TransactionData) -> Blake2bHash {
.hash(&data)
}
pub fn signature_hash_data(
fn tze_inputs_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = vec![];
for tzein in &tx.tze_inputs {
tzein.write_without_witness(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_TZE_INPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn tze_outputs_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = vec![];
for tzeout in &tx.tze_outputs {
tzeout.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_TZE_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
pub enum SignableInput<'a> {
Shielded,
Transparent {
index: usize,
script_code: &'a Script,
value: Amount,
},
Tze {
index: usize,
precondition: &'a Precondition,
value: Amount,
},
}
impl<'a> SignableInput<'a> {
pub fn transparent(index: usize, script_code: &'a Script, value: Amount) -> Self {
SignableInput::Transparent {
index,
script_code,
value,
}
}
pub fn tze(index: usize, precondition: &'a Precondition, value: Amount) -> Self {
SignableInput::Tze {
index,
precondition,
value,
}
}
}
pub fn signature_hash_data<'a>(
tx: &TransactionData,
consensus_branch_id: consensus::BranchId,
hash_type: u32,
transparent_input: Option<(usize, &Script, Amount)>,
signable_input: SignableInput<'a>,
) -> Vec<u8> {
let sigversion = SigHashVersion::from_tx(tx);
match sigversion {
SigHashVersion::Overwinter | SigHashVersion::Sapling => {
SigHashVersion::Overwinter | SigHashVersion::Sapling | SigHashVersion::ZFuture => {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
@ -182,22 +251,27 @@ pub fn signature_hash_data(
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
sequence_hash(tx)
);
if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
{
h.update(outputs_hash(tx).as_ref());
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE
&& transparent_input.is_some()
&& transparent_input.as_ref().unwrap().0 < tx.vout.len()
{
h.update(
single_output_hash(&tx.vout[transparent_input.as_ref().unwrap().0]).as_ref(),
);
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE {
match signable_input {
SignableInput::Transparent { index, .. } if index < tx.vout.len() => {
h.update(single_output_hash(&tx.vout[index]).as_ref())
}
_ => h.update(&[0; 32]),
};
} else {
h.update(&[0; 32]);
};
if sigversion == SigHashVersion::ZFuture {
update_hash!(h, !tx.tze_inputs.is_empty(), tze_inputs_hash(tx));
update_hash!(h, !tx.tze_outputs.is_empty(), tze_outputs_hash(tx));
}
update_hash!(h, !tx.joinsplits.is_empty(), joinsplits_hash(tx));
if sigversion == SigHashVersion::Sapling {
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::ZFuture {
update_hash!(h, !tx.shielded_spends.is_empty(), shielded_spends_hash(tx));
update_hash!(
h,
@ -207,20 +281,57 @@ pub fn signature_hash_data(
}
update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height.into(), tmp);
if sigversion == SigHashVersion::Sapling {
if sigversion == SigHashVersion::Sapling || sigversion == SigHashVersion::ZFuture {
h.update(&tx.value_balance.to_i64_le_bytes());
}
update_u32!(h, hash_type, tmp);
if let Some((n, script_code, amount)) = transparent_input {
let mut data = vec![];
tx.vin[n].prevout.write(&mut data).unwrap();
script_code.write(&mut data).unwrap();
data.extend_from_slice(&amount.to_i64_le_bytes());
(&mut data)
.write_u32::<LittleEndian>(tx.vin[n].sequence)
.unwrap();
h.update(&data);
match signable_input {
SignableInput::Transparent {
index,
script_code,
value,
} => {
let mut data = if sigversion == SigHashVersion::ZFuture {
// domain separation here is to avoid collision attacks
// between transparent and TZE inputs.
ZCASH_TRANSPARENT_SIGNED_INPUT_TAG.to_vec()
} else {
vec![]
};
tx.vin[index].prevout.write(&mut data).unwrap();
script_code.write(&mut data).unwrap();
data.extend_from_slice(&value.to_i64_le_bytes());
(&mut data)
.write_u32::<LittleEndian>(tx.vin[index].sequence)
.unwrap();
h.update(&data);
}
SignableInput::Tze {
index,
precondition,
value,
} if sigversion == SigHashVersion::ZFuture => {
// domain separation here is to avoid collision attacks
// between transparent and TZE inputs.
let mut data = ZCASH_TZE_SIGNED_INPUT_TAG.to_vec();
tx.tze_inputs[index].prevout.write(&mut data).unwrap();
CompactSize::write(&mut data, precondition.extension_id.try_into().unwrap())
.unwrap();
CompactSize::write(&mut data, precondition.mode.try_into().unwrap()).unwrap();
Vector::write(&mut data, &precondition.payload, |w, e| w.write_u8(*e)).unwrap();
data.extend_from_slice(&value.to_i64_le_bytes());
h.update(&data);
}
SignableInput::Tze { .. } => {
panic!("A request has been made to sign a TZE input, but the signature hash version is not ZFuture");
}
_ => (),
}
h.finalize().as_ref().to_vec()
@ -229,11 +340,11 @@ pub fn signature_hash_data(
}
}
pub fn signature_hash(
pub fn signature_hash<'a>(
tx: &Transaction,
consensus_branch_id: consensus::BranchId,
hash_type: u32,
transparent_input: Option<(usize, &Script, Amount)>,
signable_input: SignableInput<'a>,
) -> Vec<u8> {
signature_hash_data(tx, consensus_branch_id, hash_type, transparent_input)
signature_hash_data(tx, consensus_branch_id, hash_type, signable_input)
}

View File

@ -1,8 +1,133 @@
use ff::Field;
use rand_core::OsRng;
use super::{components::Amount, sighash::signature_hash, Transaction, TransactionData};
use crate::{constants::SPENDING_KEY_GENERATOR, redjubjub::PrivateKey};
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::select;
use crate::{
consensus::BranchId, constants::SPENDING_KEY_GENERATOR, extensions::transparent as tze,
legacy::Script, redjubjub::PrivateKey,
};
use super::{
components::amount::MAX_MONEY,
components::{Amount, OutPoint, TxIn, TxOut, TzeIn, TzeOut},
sighash::{signature_hash, SignableInput},
Transaction, TransactionData, OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID,
SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID, ZFUTURE_TX_VERSION, ZFUTURE_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)
}
}
const VALID_OPCODES: [u8; 8] = [
0x00, // OP_FALSE,
0x51, // OP_1,
0x52, // OP_2,
0x53, // OP_3,
0xac, // OP_CHECKSIG,
0x63, // OP_IF,
0x65, // OP_VERIF,
0x6a, // OP_RETURN,
];
prop_compose! {
fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 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::ZFuture => Just((ZFUTURE_TX_VERSION, ZFUTURE_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::ZFuture { tze_inputs } else { vec![] },
tze_outputs: if branch_id == BranchId::ZFuture { tze_outputs } else { vec![] },
lock_time,
expiry_height: expiry_height.into(),
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() {
@ -61,21 +186,72 @@ fn tx_write_rejects_unexpected_binding_sig() {
}
}
proptest! {
#[test]
fn test_tze_roundtrip(tx in arb_tx(BranchId::ZFuture)) {
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![
0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x52, 0x52, 0x52, 0x52,
0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52,
0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x30, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x20, 0xd9, 0x81, 0x80, 0x87, 0xde, 0x72, 0x44, 0xab, 0xc1, 0xb5, 0xfc,
0xf2, 0x8e, 0x55, 0xe4, 0x2c, 0x7f, 0xf9, 0xc6, 0x78, 0xc0, 0x60, 0x51, 0x81, 0xf3, 0x7a,
0xc5, 0xd7, 0x41, 0x4a, 0x7b, 0x95, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let tx = Transaction::read(&txn_bytes[..]);
match tx {
Ok(tx) => assert!(!tx.tze_inputs.is_empty()),
Err(e) => assert!(
false,
format!(
"An error occurred parsing a serialized TZE transaction: {}",
e
)
),
}
}
mod data;
#[test]
fn zip_0143() {
for tv in self::data::zip_0143::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let transparent_input = tv.transparent_input.map(|n| {
(
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::transparent(
n as usize,
&tv.script_code,
Amount::from_nonnegative_i64(tv.amount).unwrap(),
)
});
),
_ => SignableInput::Shielded,
};
assert_eq!(
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, transparent_input),
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input),
tv.sighash
);
}
@ -85,16 +261,17 @@ fn zip_0143() {
fn zip_0243() {
for tv in self::data::zip_0243::make_test_vectors() {
let tx = Transaction::read(&tv.tx[..]).unwrap();
let transparent_input = tv.transparent_input.map(|n| {
(
let signable_input = match tv.transparent_input {
Some(n) => SignableInput::transparent(
n as usize,
&tv.script_code,
Amount::from_nonnegative_i64(tv.amount).unwrap(),
)
});
),
_ => SignableInput::Shielded,
};
assert_eq!(
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, transparent_input),
signature_hash(&tx, tv.consensus_branch_id, tv.hash_type, signable_input),
tv.sighash
);
}