From c013895cd78474e0deb0a37e2514ac1aeff99617 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Thu, 5 Dec 2019 12:56:58 -0800 Subject: [PATCH] Update Transaction definition. (#105) * 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 * 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 --- zebra-chain/src/lib.rs | 1 + zebra-chain/src/proofs.rs | 22 ++ zebra-chain/src/proofs/bctv14.rs | 32 +++ zebra-chain/src/proofs/groth16.rs | 32 +++ zebra-chain/src/transaction.rs | 228 +++++++------------ zebra-chain/src/transaction/hash.rs | 55 +++++ zebra-chain/src/transaction/joinsplit.rs | 94 ++++++++ zebra-chain/src/transaction/serialize.rs | 20 ++ zebra-chain/src/transaction/shielded_data.rs | 80 +++++++ zebra-chain/src/transaction/tests.rs | 1 + zebra-chain/src/transaction/transparent.rs | 57 +++++ zebra-chain/src/types.rs | 52 ++++- 12 files changed, 524 insertions(+), 150 deletions(-) create mode 100644 zebra-chain/src/proofs.rs create mode 100644 zebra-chain/src/proofs/bctv14.rs create mode 100644 zebra-chain/src/proofs/groth16.rs create mode 100644 zebra-chain/src/transaction/hash.rs create mode 100644 zebra-chain/src/transaction/joinsplit.rs create mode 100644 zebra-chain/src/transaction/serialize.rs create mode 100644 zebra-chain/src/transaction/shielded_data.rs create mode 100644 zebra-chain/src/transaction/tests.rs create mode 100644 zebra-chain/src/transaction/transparent.rs diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index d97b71ed658..76c79a6fdb3 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -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; diff --git a/zebra-chain/src/proofs.rs b/zebra-chain/src/proofs.rs new file mode 100644 index 00000000000..b93815fb680 --- /dev/null +++ b/zebra-chain/src/proofs.rs @@ -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 {} +} diff --git a/zebra-chain/src/proofs/bctv14.rs b/zebra-chain/src/proofs/bctv14.rs new file mode 100644 index 00000000000..f10ebf4a86e --- /dev/null +++ b/zebra-chain/src/proofs/bctv14.rs @@ -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 {} diff --git a/zebra-chain/src/proofs/groth16.rs b/zebra-chain/src/proofs/groth16.rs new file mode 100644 index 00000000000..a2007e09640 --- /dev/null +++ b/zebra-chain/src/proofs/groth16.rs @@ -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 {} diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index dbd7b1b1587..264ee6e84e0 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -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 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` -#[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` - // with some extra methods. - pub signature_script: Vec, +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` -#[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, -} - -/// 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, - - /// A list of 1 or more transaction outputs or destinations for coins. - pub tx_out: Vec, - - /// 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(&self, _writer: W) -> Result<(), SerializationError> { - unimplemented!(); - } -} - -impl ZcashDeserialize for Transaction { - fn zcash_deserialize(_reader: R) -> Result { - 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, + /// The transparent outputs from the transaction. + outputs: Vec, + /// 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, + /// The transparent outputs from the transaction. + outputs: Vec, + /// 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>, + }, + /// An Overwinter transaction (`version = 3`). + V3 { + /// The transparent inputs to the transaction. + inputs: Vec, + /// The transparent outputs from the transaction. + outputs: Vec, + /// 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>, + }, + /// A Sapling transaction (`version = 4`). + V4 { + /// The transparent inputs to the transaction. + inputs: Vec, + /// The transparent outputs from the transaction. + outputs: Vec, + /// 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, + /// The JoinSplit data for this transaction, if any. + joinsplit_data: Option>, + }, } diff --git a/zebra-chain/src/transaction/hash.rs b/zebra-chain/src/transaction/hash.rs new file mode 100644 index 00000000000..2c0137344b9 --- /dev/null +++ b/zebra-chain/src/transaction/hash.rs @@ -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 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\")" + ); + } +} diff --git a/zebra-chain/src/transaction/joinsplit.rs b/zebra-chain/src/transaction/joinsplit.rs new file mode 100644 index 00000000000..0ac9618e070 --- /dev/null +++ b/zebra-chain/src/transaction/joinsplit.rs @@ -0,0 +1,94 @@ +use crate::proofs::ZkSnarkProof; + +/// Describes input notes to a Sprout transaction. +/// +/// The [protocol specification §7.2][ps] describes these fields as being encoded +/// separately into two arrays of the same length. Instead, by bundling them +/// together into one structure, we can ensure that it's not possible to create a +/// JoinSplit description with mismatched array lengths. This means we do not +/// need to maintain any invariants about equal array lengths. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencoding +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SproutInputNoteData { + /// A nullifier for the input note. + /// + /// XXX refine type + pub nullifier: [u8; 32], + /// A message authentication tag. + /// + /// XXX refine type + pub vmac: [u8; 32], +} + +/// Describes output notes from a Sprout transaction. +/// +/// The [protocol specification §7.2][ps] describes these fields as being encoded +/// separately into two arrays of the same length. Instead, by bundling them +/// together into one structure, we can ensure that it's not possible to create a +/// JoinSplit description with mismatched array lengths. This means we do not +/// need to maintain any invariants about equal array lengths. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencoding +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SproutOutputNoteData { + /// A note commitment for this output note. + /// + /// XXX refine type + pub commitment: [u8; 32], + /// A ciphertext component for this output note. + /// + /// XXX refine type + /// XXX this should be a [u8; 601] but we need trait impls. + pub enc_ciphertext: Vec, +} + +/// A _JoinSplit Description_, as described in [protocol specification §7.2][ps]. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencoding +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct JoinSplit { + /// A value that the JoinSplit transfer removes from the transparent value + /// pool. + /// + /// XXX refine to an Amount + vpub_old: u64, + /// A value that the JoinSplit transfer inserts into the transparent value + /// pool. + /// + /// XXX refine to an Amount + vpub_new: u64, + /// A root of the Sprout note commitment tree at some block height in the + /// past, or the root produced by a previous JoinSplit transfer in this + /// transaction. + /// + /// XXX refine type + anchor: [u8; 32], + /// An X25519 public key. + /// + /// XXX refine to an x25519-dalek type? + ephemeral_key: [u8; 32], + /// A 256-bit seed that must be chosen independently at random for each + /// JoinSplit description. + random_seed: [u8; 32], + /// A sequence of input notes for this transaction. + input_notes: Vec, + /// A sequence of output notes for this transaction. + output_notes: Vec, + /// A ZK JoinSplit proof, either a [`Groth16Proof`] or a [`Bctv14Proof`]. + zkproof: P, +} + +/// A bundle of JoinSplit descriptions and signature data. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct JoinSplitData { + /// A sequence of JoinSplit descriptions using proofs of type `P`. + pub joinsplits: Vec>, + /// The public key for the JoinSplit signature. + // XXX refine to a Zcash-flavored Ed25519 pubkey. + pub pub_key: [u8; 32], + /// The JoinSplit signature. + // XXX refine to a Zcash-flavored Ed25519 signature. + // for now it's [u64; 8] rather than [u8; 64] to get trait impls + pub sig: [u64; 8], +} diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs new file mode 100644 index 00000000000..02c245b88c1 --- /dev/null +++ b/zebra-chain/src/transaction/serialize.rs @@ -0,0 +1,20 @@ +//! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the +//! transaction types, so that all of the serialization logic is in one place. + +use std::io; + +use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}; + +use super::Transaction; + +impl ZcashSerialize for Transaction { + fn zcash_serialize(&self, _writer: W) -> Result<(), SerializationError> { + unimplemented!(); + } +} + +impl ZcashDeserialize for Transaction { + fn zcash_deserialize(_reader: R) -> Result { + unimplemented!(); + } +} diff --git a/zebra-chain/src/transaction/shielded_data.rs b/zebra-chain/src/transaction/shielded_data.rs new file mode 100644 index 00000000000..e00e56f4085 --- /dev/null +++ b/zebra-chain/src/transaction/shielded_data.rs @@ -0,0 +1,80 @@ +// XXX this name seems too long? +use crate::note_commitment_tree::SaplingNoteTreeRootHash; + +/// A _Spend Description_, as described in [protocol specification §7.3][ps]. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpendDescription { + /// A value commitment to the value of the input note. + /// + /// XXX refine to a specific type. + pub cv: [u8; 32], + /// A root of the Sapling note commitment tree at some block height in the past. + pub anchor: SaplingNoteTreeRootHash, + /// The nullifier of the input note. + /// + /// XXX refine to a specific type. + pub nullifier: [u8; 32], + /// The randomized public key for `spend_auth_sig`. + /// + /// XXX refine to a specific type. + pub rk: [u8; 32], + /// The ZK spend proof. + /// + /// XXX add proof types. + /// XXX for now it's [u64; 24] instead of [u8; 192] to get trait impls + pub zkproof: [u64; 24], + /// A signature authorizing this spend. + /// + /// XXX refine to a specific type: redjubjub signature? + /// XXX for now it's [u64; 8] instead of [u8; 64] to get trait impls + pub spend_auth_sig: [u64; 8], +} + +/// A _Output Description_, as described in [protocol specification §7.4][ps]. +/// +/// https://zips.z.cash/protocol/protocol.pdf#outputencoding +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OutputDescription { + /// A value commitment to the value of the input note. + /// + /// XXX refine to a specific type. + pub cv: [u8; 32], + /// The u-coordinate of the note commitment for the output note. + /// + /// XXX refine to a specific type. + pub cmu: [u8; 32], + /// An encoding of an ephemeral Jubjub public key. + /// + /// XXX refine to a specific type. + pub ephemeral_key: [u8; 32], + /// A ciphertext component for the encrypted output note. + /// + /// XXX refine to a specific type. + /// XXX this is a Vec rather than a [u8; 580] to get trait impls + pub enc_ciphertext: Vec, + /// A ciphertext component for the encrypted output note. + /// + /// XXX refine to a specific type. + /// XXX this is a [u64; 10] rather than a [u8; 80] to get trait impls + pub out_ciphertext: [u64; 10], + /// The ZK output proof. + /// + /// XXX add proof types. + /// XXX for now it's [u64; 24] instead of [u8; 192] to get trait impls + pub zkproof: [u64; 24], +} + +/// Sapling-on-Groth16 spend and output descriptions. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ShieldedData { + /// A sequence of [`SpendDescription`]s for this transaction. + pub shielded_spends: Vec, + /// A sequence of shielded outputs for this transaction. + pub shielded_outputs: Vec, + /// A signature on the transaction hash. + // XXX refine this type to a RedJubjub signature. + // for now it's [u64; 8] rather than [u8; 64] to get trait impls + pub binding_sig: [u64; 8], +} diff --git a/zebra-chain/src/transaction/tests.rs b/zebra-chain/src/transaction/tests.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/zebra-chain/src/transaction/tests.rs @@ -0,0 +1 @@ + diff --git a/zebra-chain/src/transaction/transparent.rs b/zebra-chain/src/transaction/transparent.rs new file mode 100644 index 00000000000..9cfa7b1503f --- /dev/null +++ b/zebra-chain/src/transaction/transparent.rs @@ -0,0 +1,57 @@ +//! Transaction types. + +use crate::types::Script; + +use super::TransactionHash; + +/// 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, +} + +/// A transparent input to a transaction. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TransparentInput { + /// The previous output transaction reference. + pub previous_output: OutPoint, + + /// Computational Script for confirming transaction authorization. + pub signature_script: Script, + + /// Transaction version as defined by the sender. Intended for + /// "replacement" of transactions when information is updated + /// before inclusion into a block. + pub sequence: u32, +} + +/// A transparent output from a transaction. +/// +/// 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. +/// +/// 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). +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TransparentOutput { + /// Transaction value. + // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64. + // XXX refine to Amount ? + pub value: u64, + + /// Usually contains the public key as a Bitcoin script setting up + /// conditions to claim this output. + pub pk_script: Script, +} diff --git a/zebra-chain/src/types.rs b/zebra-chain/src/types.rs index ca0b4634c0e..4ba0e37508e 100644 --- a/zebra-chain/src/types.rs +++ b/zebra-chain/src/types.rs @@ -1,7 +1,12 @@ //! Newtype wrappers for primitive data types with semantic meaning. +use std::{fmt, io}; + +use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; +use chrono::{DateTime, TimeZone, Utc}; use hex; -use std::fmt; + +use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}; /// A 4-byte checksum using truncated double-SHA256 (two rounds of SHA256). #[derive(Copy, Clone, Eq, PartialEq)] @@ -30,6 +35,51 @@ impl fmt::Debug for Sha256dChecksum { #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct BlockHeight(pub u32); +/// A Bitcoin-style `locktime`, representing either a block height or an epoch +/// time. +/// +/// # Invariants +/// +/// Users should not construct a `LockTime` with `BlockHeight` greater than or +/// equal to `500_000_000` or a timestamp before 4 November 1985 (Unix timestamp +/// less than `500_000_000`). +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum LockTime { + /// Unlock at a particular block height. + Height(BlockHeight), + /// Unlock at a particular time. + Time(DateTime), +} + +impl ZcashSerialize for LockTime { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + // This implementation does not check the invariants on `LockTime` so that the + // serialization is fallible only if the underlying writer is. This ensures that + // we can always compute a hash of a transaction object. + use LockTime::*; + match self { + Height(BlockHeight(n)) => writer.write_u32::(*n)?, + Time(t) => writer.write_u32::(t.timestamp() as u32)?, + } + Ok(()) + } +} + +impl ZcashDeserialize for LockTime { + fn zcash_deserialize(mut reader: R) -> Result { + let n = reader.read_u32::()?; + if n < 500_000_000 { + Ok(LockTime::Height(BlockHeight(n))) + } else { + Ok(LockTime::Time(Utc.timestamp(n as i64, 0))) + } + } +} + +/// An encoding of a Bitcoin script. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Script(pub Vec); + #[cfg(test)] mod tests { use super::*;