Merge pull request #286 from nuttycom/zip-tzes
Implementation for ZIP-222 Transparent Zcash Extensions
This commit is contained in:
commit
72b6de39eb
|
@ -3,6 +3,7 @@ members = [
|
|||
"components/equihash",
|
||||
"zcash_client_backend",
|
||||
"zcash_client_sqlite",
|
||||
"zcash_extensions",
|
||||
"zcash_history",
|
||||
"zcash_primitives",
|
||||
"zcash_proofs",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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" }
|
|
@ -0,0 +1 @@
|
|||
pub mod transparent;
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod consensus;
|
||||
pub mod transparent;
|
|
@ -0,0 +1,3 @@
|
|||
//! Zcash transparent extensions.
|
||||
|
||||
pub mod demo;
|
|
@ -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(())
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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"]
|
||||
|
|
|
@ -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(¶ms, 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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pub mod transparent;
|
|
@ -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>;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -60,7 +60,7 @@ pub trait TxProver {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mock {
|
||||
pub mod mock {
|
||||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue