Skip to content

Commit

Permalink
Update Transaction definition. (#105)
Browse files Browse the repository at this point in the history
* Added a few more top-level fields for the Transaction struct

* Add a placeholder Script type.

This could alternately use bytes::Bytes to save some allocations
but I don't think this is important to get perfectly now.  In the future, we
will want to have all of the script handling code in the zebra-script crate,
but we will need to have a container type for an encoded script in zebra-chain,
because otherwise zebra-chain would depend on zebra-script and not the other
way around.

* Rename Transaction{Input,Output} -> Transparent{Input,Output}.

These are only *transparent* inputs and outputs, so by putting Transparent in
the name (instead of Transaction) it's more clear that a transaction's inputs
or outputs can also be shielded.

* Add a LockTime enum.

* First attempt at a Transaction enum.

This attempts to map the versioning and field presence rules into an ADT, so
that structurally invalid transactions (e.g., a BCTV14 proof in a Sapling
transaction) are unrepresentable.

* Update zebra-chain/src/transaction.rs

Co-Authored-By: Daira Hopwood <daira@jacaranda.org>

* Add fixme note on type refinement.

* Rename Transaction variants according to version.

* Split transaction.rs into submodules.

* Start filling in spend, output descriptions.

* Progress on JoinSplit data structures.

This has a lot of duplication and should really use generics to abstract over
Sprout-on-BCTV14 or Sprout-on-Groth16.

* Add data types for Bctv14 and Groth16 proofs.

This also adds a trait to abstract over them.

* Make JoinSplit descriptions generic over the proof system.

* Update zebra-chain/src/transaction/joinsplit.rs
  • Loading branch information
hdevalence authored and dconnolly committed Dec 5, 2019
1 parent 82e246d commit c013895
Show file tree
Hide file tree
Showing 12 changed files with 524 additions and 150 deletions.
1 change: 1 addition & 0 deletions zebra-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod sha256d_writer;
pub mod block;
pub mod equihash_solution;
pub mod note_commitment_tree;
pub mod proofs;
pub mod serialization;
pub mod transaction;
pub mod types;
22 changes: 22 additions & 0 deletions zebra-chain/src/proofs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! ZK proofs used in Zcash.

use std::fmt::Debug;

mod bctv14;
mod groth16;

pub use bctv14::Bctv14Proof;
pub use groth16::Groth16Proof;

/// A marker trait used to abstract over BCTV14 or Groth16 proofs.
pub trait ZkSnarkProof: Copy + Clone + Debug + PartialEq + Eq + private::Sealed {}
impl ZkSnarkProof for Bctv14Proof {}
impl ZkSnarkProof for Groth16Proof {}

mod private {
use super::*;

pub trait Sealed {}
impl Sealed for Bctv14Proof {}
impl Sealed for Groth16Proof {}
}
32 changes: 32 additions & 0 deletions zebra-chain/src/proofs/bctv14.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::fmt;

/// An encoding of a BCTV14 proof, as used in Zcash.
pub struct Bctv14Proof(pub [u8; 296]);

impl fmt::Debug for Bctv14Proof {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Bctv14Proof")
.field(&hex::encode(&self.0[..]))
.finish()
}
}

// These impls all only exist because of array length restrictions.

impl Copy for Bctv14Proof {}

impl Clone for Bctv14Proof {
fn clone(&self) -> Self {
let mut bytes = [0; 296];
bytes[..].copy_from_slice(&self.0[..]);
Self(bytes)
}
}

impl PartialEq for Bctv14Proof {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}

impl Eq for Bctv14Proof {}
32 changes: 32 additions & 0 deletions zebra-chain/src/proofs/groth16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::fmt;

/// An encoding of a Groth16 proof, as used in Zcash.
pub struct Groth16Proof(pub [u8; 192]);

impl fmt::Debug for Groth16Proof {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Groth16Proof")
.field(&hex::encode(&self.0[..]))
.finish()
}
}

// These impls all only exist because of array length restrictions.

impl Copy for Groth16Proof {}

impl Clone for Groth16Proof {
fn clone(&self) -> Self {
let mut bytes = [0; 192];
bytes[..].copy_from_slice(&self.0[..]);
Self(bytes)
}
}

impl PartialEq for Groth16Proof {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}

impl Eq for Groth16Proof {}
228 changes: 79 additions & 149 deletions zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,158 +1,88 @@
//! Transaction types.

use std::{fmt, io};
mod hash;
mod joinsplit;
mod serialize;
mod shielded_data;
mod transparent;

use hex;

use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
use crate::sha256d_writer::Sha256dWriter;

/// A hash of a `Transaction`
///
/// TODO: I'm pretty sure this is also a SHA256d hash but I haven't
/// confirmed it yet.
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct TransactionHash(pub [u8; 32]);

impl fmt::Debug for TransactionHash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("TransactionHash")
.field(&hex::encode(&self.0))
.finish()
}
}

impl From<Transaction> for TransactionHash {
fn from(transaction: Transaction) -> Self {
let mut hash_writer = Sha256dWriter::default();
transaction
.zcash_serialize(&mut hash_writer)
.expect("Transactions must serialize into the hash.");
Self(hash_writer.finish())
}
}

/// OutPoint
///
/// A particular transaction output reference.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct OutPoint {
/// References the transaction that contains the UTXO being spent.
pub hash: TransactionHash,

/// Identifies which UTXO from that transaction is referenced; the
/// first output is 0, etc.
pub index: u32,
}

/// Transaction Input
// `Copy` cannot be implemented for `Vec<u8>`
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TransactionInput {
/// The previous output transaction reference.
pub previous_output: OutPoint,
#[cfg(test)]
mod tests;

/// Computational Script for confirming transaction authorization.
// XXX pzec uses their own `Bytes` type that wraps a `Vec<u8>`
// with some extra methods.
pub signature_script: Vec<u8>,
pub use hash::TransactionHash;
pub use joinsplit::{JoinSplit, JoinSplitData, SproutInputNoteData, SproutOutputNoteData};
pub use shielded_data::{OutputDescription, ShieldedData, SpendDescription};
pub use transparent::{OutPoint, TransparentInput, TransparentOutput};

/// Transaction version as defined by the sender. Intended for
/// "replacement" of transactions when information is updated
/// before inclusion into a block.
pub sequence: u32,
}
use crate::proofs::{Bctv14Proof, Groth16Proof};
use crate::types::{BlockHeight, LockTime};

/// Transaction Output
///
/// The most fundamental building block of a transaction is a
/// transaction output -- the ZEC you own in your "wallet" is in
/// fact a subset of unspent transaction outputs (or "UTXO"s) of the
/// global UTXO set.
/// A Zcash transaction.
///
/// UTXOs are indivisible, discrete units of value which can only be
/// consumed in their entirety. Thus, if I want to send you 1 ZEC and
/// I only own one UTXO worth 2 ZEC, I would construct a transaction
/// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me
/// (just like receiving change).
// `Copy` cannot be implemented for `Vec<u8>`
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TransactionOutput {
/// Transaction value.
// At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
pub value: u64,

/// Usually contains the public key as a Bitcoin script setting up
/// conditions to claim this output.
pub pk_script: Vec<u8>,
}

/// Transaction
/// A transaction is an encoded data structure that facilitates the transfer of
/// value between two public key addresses on the Zcash ecosystem. Everything is
/// designed to ensure that transactions can created, propagated on the network,
/// validated, and finally added to the global ledger of transactions (the
/// blockchain).
///
/// A transaction is an encoded data structure that facilitates the
/// transfer of value between two public key addresses on the Zcash
/// ecosystem. Everything is designed to ensure that transactions can
/// created, propagated on the network, validated, and finally added
/// to the global ledger of transactions (the blockchain).
// This is not up to date with the data included in the Zcash
// transaction format: https://zips.z.cash/protocol/protocol.pdf
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Transaction {
/// Transaction data format version (note, this is signed).
pub version: i32,

/// A list of 1 or more transaction inputs or sources for coins.
pub tx_in: Vec<TransactionInput>,

/// A list of 1 or more transaction outputs or destinations for coins.
pub tx_out: Vec<TransactionOutput>,

/// The block number or timestamp at which this transaction is unlocked:
///
/// |Value |Description |
/// |------------|----------------------------------------------------|
/// |0 |Not locked (default) |
/// |< 500000000 |Block number at which this transaction is unlocked |
/// |>= 500000000|UNIX timestamp at which this transaction is unlocked|
///
/// If all `TransactionInput`s have final (0xffffffff) sequence
/// numbers, then lock_time is irrelevant. Otherwise, the
/// transaction may not be added to a block until after `lock_time`.
pub lock_time: u32,
}

impl ZcashSerialize for Transaction {
fn zcash_serialize<W: io::Write>(&self, _writer: W) -> Result<(), SerializationError> {
unimplemented!();
}
}

impl ZcashDeserialize for Transaction {
fn zcash_deserialize<R: io::Read>(_reader: R) -> Result<Self, SerializationError> {
unimplemented!();
}
}

#[cfg(test)]
mod tests {

use std::io::Write;

use super::TransactionHash;

use crate::sha256d_writer::Sha256dWriter;

#[test]
fn transactionhash_debug() {
let preimage = b"foo bar baz";
let mut sha_writer = Sha256dWriter::default();
let _ = sha_writer.write_all(preimage);

let hash = TransactionHash(sha_writer.finish());

assert_eq!(
format!("{:?}", hash),
"TransactionHash(\"bf46b4b5030752fedac6f884976162bbfb29a9398f104a280b3e34d51b416631\")"
);
}
/// Zcash has a number of different transaction formats. They are represented
/// internally by different enum variants. Because we checkpoint on Sapling
/// activation, we do not parse any pre-Sapling transaction types.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Transaction {
/// A fully transparent transaction (`version = 1`).
V1 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
},
/// A Sprout transaction (`version = 2`).
V2 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
/// The JoinSplit data for this transaction, if any.
joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
},
/// An Overwinter transaction (`version = 3`).
V3 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
/// The latest block height that this transaction can be added to the chain.
expiry_height: BlockHeight,
/// The JoinSplit data for this transaction, if any.
joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
},
/// A Sapling transaction (`version = 4`).
V4 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
/// The latest block height that this transaction can be added to the chain.
expiry_height: BlockHeight,
/// The net value of Sapling spend transfers minus output transfers.
// XXX refine this to an Amount type.
value_balance: i64,
/// The shielded data for this transaction, if any.
shielded_data: Option<ShieldedData>,
/// The JoinSplit data for this transaction, if any.
joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
},
}
55 changes: 55 additions & 0 deletions zebra-chain/src/transaction/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::fmt;

use hex;

use crate::{serialization::ZcashSerialize, sha256d_writer::Sha256dWriter};

use super::Transaction;

/// A hash of a `Transaction`
///
/// TODO: I'm pretty sure this is also a SHA256d hash but I haven't
/// confirmed it yet.
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct TransactionHash(pub [u8; 32]);

impl From<Transaction> for TransactionHash {
fn from(transaction: Transaction) -> Self {
let mut hash_writer = Sha256dWriter::default();
transaction
.zcash_serialize(&mut hash_writer)
.expect("Transactions must serialize into the hash.");
Self(hash_writer.finish())
}
}

impl fmt::Debug for TransactionHash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("TransactionHash")
.field(&hex::encode(&self.0))
.finish()
}
}

#[cfg(test)]
mod tests {
use std::io::Write;

use crate::sha256d_writer::Sha256dWriter;

use super::*;

#[test]
fn transactionhash_debug() {
let preimage = b"foo bar baz";
let mut sha_writer = Sha256dWriter::default();
let _ = sha_writer.write_all(preimage);

let hash = TransactionHash(sha_writer.finish());

assert_eq!(
format!("{:?}", hash),
"TransactionHash(\"bf46b4b5030752fedac6f884976162bbfb29a9398f104a280b3e34d51b416631\")"
);
}
}
Loading

0 comments on commit c013895

Please sign in to comment.