chain: move Transparent{Input,Output} to transparent

Also bring the `Script` type there too.
This commit is contained in:
Henry de Valence 2020-08-17 01:24:33 -07:00
parent 25f63518f4
commit d945cd28e8
13 changed files with 404 additions and 386 deletions

View File

@ -23,8 +23,7 @@ type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
use serde::{Deserialize, Serialize};
use crate::parameters::Network;
use crate::transaction::Transaction;
use crate::{parameters::Network, transaction::Transaction, transparent};
#[cfg(test)]
use proptest_derive::Arbitrary;
@ -42,12 +41,11 @@ pub struct Block {
impl Block {
/// Return the block height reported in the coinbase transaction, if any.
pub fn coinbase_height(&self) -> Option<Height> {
use crate::transaction::TransparentInput;
self.transactions
.get(0)
.and_then(|tx| tx.inputs().get(0))
.and_then(|input| match input {
TransparentInput::Coinbase { ref height, .. } => Some(*height),
transparent::Input::Coinbase { ref height, .. } => Some(*height),
_ => None,
})
}

View File

@ -4,7 +4,8 @@ use std::sync::Arc;
use crate::{
serialization::{ZcashDeserialize, ZcashSerialize},
transaction::{LockTime, Transaction, TransparentInput, TransparentOutput},
transaction::{LockTime, Transaction},
transparent,
};
use super::super::{serialize::MAX_BLOCK_BYTES, Block, Header};
@ -71,9 +72,9 @@ fn multi_transaction_block(oversized: bool) -> Block {
fn single_transaction_block(oversized: bool) -> Block {
// Dummy input and output
let input =
TransparentInput::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
transparent::Input::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
let output =
TransparentOutput::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
transparent::Output::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
// A block header
let header = block_header();

View File

@ -5,11 +5,9 @@
//! whose functionality is implemented elsewhere.
mod proofs;
mod script;
pub use ed25519_zebra as ed25519;
pub use redjubjub;
pub use x25519_dalek as x25519;
pub use proofs::{Bctv14Proof, Groth16Proof, ZkSnarkProof};
pub use script::Script;

View File

@ -8,7 +8,6 @@ mod lock_time;
mod memo;
mod serialize;
mod shielded_data;
mod transparent;
#[cfg(test)]
mod tests;
@ -18,11 +17,13 @@ pub use joinsplit::{JoinSplit, JoinSplitData};
pub use lock_time::LockTime;
pub use memo::Memo;
pub use shielded_data::{Output, ShieldedData, Spend};
pub use transparent::{CoinbaseData, OutPoint, TransparentInput, TransparentOutput};
use crate::amount::Amount;
use crate::block;
use crate::primitives::{Bctv14Proof, Groth16Proof};
use crate::{
amount::Amount,
block,
primitives::{Bctv14Proof, Groth16Proof},
transparent,
};
/// A Zcash transaction.
///
@ -42,9 +43,9 @@ pub enum Transaction {
/// A fully transparent transaction (`version = 1`).
V1 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
inputs: Vec<transparent::Input>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
outputs: Vec<transparent::Output>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
@ -52,9 +53,9 @@ pub enum Transaction {
/// A Sprout transaction (`version = 2`).
V2 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
inputs: Vec<transparent::Input>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
outputs: Vec<transparent::Output>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
@ -64,9 +65,9 @@ pub enum Transaction {
/// An Overwinter transaction (`version = 3`).
V3 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
inputs: Vec<transparent::Input>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
outputs: Vec<transparent::Output>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
@ -78,9 +79,9 @@ pub enum Transaction {
/// A Sapling transaction (`version = 4`).
V4 {
/// The transparent inputs to the transaction.
inputs: Vec<TransparentInput>,
inputs: Vec<transparent::Input>,
/// The transparent outputs from the transaction.
outputs: Vec<TransparentOutput>,
outputs: Vec<transparent::Output>,
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
@ -97,7 +98,7 @@ pub enum Transaction {
impl Transaction {
/// Access the transparent inputs of this transaction, regardless of version.
pub fn inputs(&self) -> &[TransparentInput] {
pub fn inputs(&self) -> &[transparent::Input] {
match self {
Transaction::V1 { ref inputs, .. } => inputs,
Transaction::V2 { ref inputs, .. } => inputs,
@ -107,7 +108,7 @@ impl Transaction {
}
/// Access the transparent outputs of this transaction, regardless of version.
pub fn outputs(&self) -> &[TransparentOutput] {
pub fn outputs(&self) -> &[transparent::Output] {
match self {
Transaction::V1 { ref outputs, .. } => outputs,
Transaction::V2 { ref outputs, .. } => outputs,
@ -140,7 +141,7 @@ impl Transaction {
pub fn contains_coinbase_input(&self) -> bool {
self.inputs()
.iter()
.any(|input| matches!(input, TransparentInput::Coinbase { .. }))
.any(|input| matches!(input, transparent::Input::Coinbase { .. }))
}
/// Returns `true` if this transaction is a coinbase transaction.
@ -148,7 +149,7 @@ impl Transaction {
self.inputs().len() == 1
&& matches!(
self.inputs().get(0),
Some(TransparentInput::Coinbase { .. })
Some(transparent::Input::Coinbase { .. })
)
}
}

View File

@ -1,15 +1,12 @@
//! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the
//! transaction types, so that all of the serialization logic is in one place.
use std::{convert::TryInto, io, sync::Arc};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::{
convert::TryInto,
io::{self, Read},
sync::Arc,
};
use crate::{
primitives::{Script, ZkSnarkProof},
primitives::ZkSnarkProof,
serialization::{
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
},
@ -21,17 +18,6 @@ use super::*;
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085;
/// The coinbase data for a genesis block.
///
/// Zcash uses the same coinbase data for the Mainnet, Testnet, and Regtest
/// genesis blocks.
const GENESIS_COINBASE_DATA: [u8; 77] = [
4, 255, 255, 7, 31, 1, 4, 69, 90, 99, 97, 115, 104, 48, 98, 57, 99, 52, 101, 101, 102, 56, 98,
55, 99, 99, 52, 49, 55, 101, 101, 53, 48, 48, 49, 101, 51, 53, 48, 48, 57, 56, 52, 98, 54, 102,
101, 97, 51, 53, 54, 56, 51, 97, 55, 99, 97, 99, 49, 52, 49, 97, 48, 52, 51, 99, 52, 50, 48,
54, 52, 56, 51, 53, 100, 51, 52,
];
impl ZcashDeserialize for jubjub::Fq {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
@ -46,211 +32,6 @@ impl ZcashDeserialize for jubjub::Fq {
}
}
impl ZcashSerialize for OutPoint {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.hash.0[..])?;
writer.write_u32::<LittleEndian>(self.index)?;
Ok(())
}
}
impl ZcashDeserialize for OutPoint {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(OutPoint {
hash: TransactionHash(reader.read_32_bytes()?),
index: reader.read_u32::<LittleEndian>()?,
})
}
}
// Coinbase inputs include block heights (BIP34). These are not encoded
// directly, but as a Bitcoin script that pushes the block height to the stack
// when executed. The script data is otherwise unused. Because we want to
// *parse* transactions into an internal representation where illegal states are
// unrepresentable, we need just enough parsing of Bitcoin scripts to parse the
// coinbase height and split off the rest of the (inert) coinbase data.
fn parse_coinbase_height(
mut data: Vec<u8>,
) -> Result<(block::Height, CoinbaseData), SerializationError> {
use block::Height;
match (data.get(0), data.len()) {
// Blocks 1 through 16 inclusive encode block height with OP_N opcodes.
(Some(op_n @ 0x51..=0x60), len) if len >= 1 => Ok((
Height((op_n - 0x50) as u32),
CoinbaseData(data.split_off(1)),
)),
// Blocks 17 through 256 exclusive encode block height with the `0x01` opcode.
(Some(0x01), len) if len >= 2 => {
Ok((Height(data[1] as u32), CoinbaseData(data.split_off(2))))
}
// Blocks 256 through 65536 exclusive encode block height with the `0x02` opcode.
(Some(0x02), len) if len >= 3 => Ok((
Height(data[1] as u32 + ((data[2] as u32) << 8)),
CoinbaseData(data.split_off(3)),
)),
// Blocks 65536 through 2**24 exclusive encode block height with the `0x03` opcode.
(Some(0x03), len) if len >= 4 => Ok((
Height(data[1] as u32 + ((data[2] as u32) << 8) + ((data[3] as u32) << 16)),
CoinbaseData(data.split_off(4)),
)),
// The genesis block does not encode the block height by mistake; special case it.
// The first five bytes are [4, 255, 255, 7, 31], the little-endian encoding of
// 520_617_983. This is lucky because it means we can special-case the genesis block
// while remaining below the maximum `block::Height` of 500_000_000 forced by `LockTime`.
// While it's unlikely this code will ever process a block height that high, this means
// we don't need to maintain a cascade of different invariants for allowable heights.
(Some(0x04), _) if data[..] == GENESIS_COINBASE_DATA[..] => {
Ok((Height(0), CoinbaseData(data)))
}
// As noted above, this is included for completeness.
(Some(0x04), len) if len >= 5 => {
let h = data[1] as u32
+ ((data[2] as u32) << 8)
+ ((data[3] as u32) << 16)
+ ((data[4] as u32) << 24);
if h <= Height::MAX.0 {
Ok((Height(h), CoinbaseData(data.split_off(5))))
} else {
Err(SerializationError::Parse("Invalid block height"))
}
}
_ => Err(SerializationError::Parse(
"Could not parse BIP34 height in coinbase data",
)),
}
}
fn coinbase_height_len(height: block::Height) -> usize {
// We can't write this as a match statement on stable until exclusive range
// guards are stabilized.
if let 0 = height.0 {
0
} else if let _h @ 1..=16 = height.0 {
1
} else if let _h @ 17..=255 = height.0 {
2
} else if let _h @ 256..=65535 = height.0 {
3
} else if let _h @ 65536..=16_777_215 = height.0 {
4
} else if let _h @ 16_777_216..=block::Height::MAX_AS_U32 = height.0 {
5
} else {
panic!("Invalid coinbase height");
}
}
fn write_coinbase_height<W: io::Write>(height: block::Height, mut w: W) -> Result<(), io::Error> {
// We can't write this as a match statement on stable until exclusive range
// guards are stabilized.
if let 0 = height.0 {
// Genesis block does not include height.
} else if let h @ 1..=16 = height.0 {
w.write_u8(0x50 + (h as u8))?;
} else if let h @ 17..=255 = height.0 {
w.write_u8(0x01)?;
w.write_u8(h as u8)?;
} else if let h @ 256..=65535 = height.0 {
w.write_u8(0x02)?;
w.write_u16::<LittleEndian>(h as u16)?;
} else if let h @ 65536..=16_777_215 = height.0 {
w.write_u8(0x03)?;
w.write_u8(h as u8)?;
w.write_u8((h >> 8) as u8)?;
w.write_u8((h >> 16) as u8)?;
} else if let h @ 16_777_216..=block::Height::MAX_AS_U32 = height.0 {
w.write_u8(0x04)?;
w.write_u32::<LittleEndian>(h)?;
} else {
panic!("Invalid coinbase height");
}
Ok(())
}
impl ZcashSerialize for TransparentInput {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
TransparentInput::PrevOut {
outpoint,
unlock_script,
sequence,
} => {
outpoint.zcash_serialize(&mut writer)?;
unlock_script.zcash_serialize(&mut writer)?;
writer.write_u32::<LittleEndian>(*sequence)?;
}
TransparentInput::Coinbase {
height,
data,
sequence,
} => {
writer.write_all(&[0; 32][..])?;
writer.write_u32::<LittleEndian>(0xffff_ffff)?;
let height_len = coinbase_height_len(*height);
let total_len = height_len + data.as_ref().len();
writer.write_compactsize(total_len as u64)?;
write_coinbase_height(*height, &mut writer)?;
writer.write_all(&data.as_ref()[..])?;
writer.write_u32::<LittleEndian>(*sequence)?;
}
}
Ok(())
}
}
impl ZcashDeserialize for TransparentInput {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
// This inlines the OutPoint deserialization to peek at the hash value
// and detect whether we have a coinbase input.
let bytes = reader.read_32_bytes()?;
if bytes == [0; 32] {
if reader.read_u32::<LittleEndian>()? != 0xffff_ffff {
return Err(SerializationError::Parse("wrong index in coinbase"));
}
let len = reader.read_compactsize()?;
if len > 100 {
return Err(SerializationError::Parse("coinbase has too much data"));
}
let mut data = Vec::with_capacity(len as usize);
(&mut reader).take(len).read_to_end(&mut data)?;
let (height, data) = parse_coinbase_height(data)?;
let sequence = reader.read_u32::<LittleEndian>()?;
Ok(TransparentInput::Coinbase {
height,
data,
sequence,
})
} else {
Ok(TransparentInput::PrevOut {
outpoint: OutPoint {
hash: TransactionHash(bytes),
index: reader.read_u32::<LittleEndian>()?,
},
unlock_script: Script::zcash_deserialize(&mut reader)?,
sequence: reader.read_u32::<LittleEndian>()?,
})
}
}
}
impl ZcashSerialize for TransparentOutput {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_u64::<LittleEndian>(self.value.into())?;
self.lock_script.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for TransparentOutput {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(TransparentOutput {
value: reader.read_u64::<LittleEndian>()?.try_into()?,
lock_script: Script::zcash_deserialize(&mut reader)?,
})
}
}
impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_u64::<LittleEndian>(self.vpub_old.into())?;

View File

@ -5,20 +5,19 @@ use proptest::{arbitrary::any, array, collection::vec, option, prelude::*};
use crate::{
amount::{Amount, NonNegative},
block,
primitives::{Bctv14Proof, Groth16Proof, Script, ZkSnarkProof},
sapling, sprout,
primitives::{Bctv14Proof, Groth16Proof, ZkSnarkProof},
sapling, sprout, transparent,
};
use super::super::{
CoinbaseData, JoinSplit, JoinSplitData, LockTime, Memo, OutPoint, Output, ShieldedData, Spend,
Transaction, TransparentInput, TransparentOutput,
JoinSplit, JoinSplitData, LockTime, Memo, Output, ShieldedData, Spend, Transaction,
};
impl Transaction {
pub fn v1_strategy() -> impl Strategy<Value = Self> {
(
vec(any::<TransparentInput>(), 0..10),
vec(any::<TransparentOutput>(), 0..10),
vec(any::<transparent::Input>(), 0..10),
vec(any::<transparent::Output>(), 0..10),
any::<LockTime>(),
)
.prop_map(|(inputs, outputs, lock_time)| Transaction::V1 {
@ -31,8 +30,8 @@ impl Transaction {
pub fn v2_strategy() -> impl Strategy<Value = Self> {
(
vec(any::<TransparentInput>(), 0..10),
vec(any::<TransparentOutput>(), 0..10),
vec(any::<transparent::Input>(), 0..10),
vec(any::<transparent::Output>(), 0..10),
any::<LockTime>(),
option::of(any::<JoinSplitData<Bctv14Proof>>()),
)
@ -49,8 +48,8 @@ impl Transaction {
pub fn v3_strategy() -> impl Strategy<Value = Self> {
(
vec(any::<TransparentInput>(), 0..10),
vec(any::<TransparentOutput>(), 0..10),
vec(any::<transparent::Input>(), 0..10),
vec(any::<transparent::Output>(), 0..10),
any::<LockTime>(),
any::<block::Height>(),
option::of(any::<JoinSplitData<Bctv14Proof>>()),
@ -69,8 +68,8 @@ impl Transaction {
pub fn v4_strategy() -> impl Strategy<Value = Self> {
(
vec(any::<TransparentInput>(), 0..10),
vec(any::<TransparentOutput>(), 0..10),
vec(any::<transparent::Input>(), 0..10),
vec(any::<transparent::Output>(), 0..10),
any::<LockTime>(),
any::<block::Height>(),
any::<Amount>(),
@ -311,37 +310,3 @@ impl Arbitrary for Transaction {
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for TransparentInput {
type Parameters = ();
fn arbitrary_with(_args: ()) -> Self::Strategy {
prop_oneof![
(any::<OutPoint>(), any::<Script>(), any::<u32>())
.prop_map(|(outpoint, unlock_script, sequence)| {
TransparentInput::PrevOut {
outpoint,
unlock_script,
sequence,
}
})
.boxed(),
(
any::<block::Height>(),
vec(any::<u8>(), 0..95),
any::<u32>()
)
.prop_map(|(height, data, sequence)| {
TransparentInput::Coinbase {
height,
data: CoinbaseData(data),
sequence,
}
})
.boxed(),
]
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}

View File

@ -1,91 +0,0 @@
//! Transaction types.
#![allow(clippy::unit_arg)]
#[cfg(test)]
use proptest_derive::Arbitrary;
use crate::{
amount::{Amount, NonNegative},
block,
primitives::Script,
};
use super::TransactionHash;
/// Arbitrary data inserted by miners into a coinbase transaction.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct CoinbaseData(
/// Invariant: this vec, together with the coinbase height, must be less than
/// 100 bytes. We enforce this by only constructing CoinbaseData fields by
/// parsing blocks with 100-byte data fields. When we implement block
/// creation, we should provide a constructor for the coinbase data field
/// that restricts it to 95 = 100 -1 -4 bytes (safe for any block height up
/// to 500_000_000).
pub(super) Vec<u8>,
);
impl AsRef<[u8]> for CoinbaseData {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// OutPoint
///
/// A particular transaction output reference.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(test, derive(Arbitrary))]
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, Serialize, Deserialize)]
pub enum TransparentInput {
/// A reference to an output of a previous transaction.
PrevOut {
/// The previous output transaction reference.
outpoint: OutPoint,
/// The script that authorizes spending `outpoint`.
unlock_script: Script,
/// The sequence number for the output.
sequence: u32,
},
/// New coins created by the block reward.
Coinbase {
/// The height of this block.
height: block::Height,
/// Free data inserted by miners after the block height.
data: CoinbaseData,
/// The sequence number for the output.
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, Serialize, Deserialize)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct TransparentOutput {
/// Transaction value.
// At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
pub value: Amount<NonNegative>,
/// The lock script defines the conditions under which this output can be spent.
pub lock_script: Script,
}

View File

@ -1,6 +1,98 @@
//! Transparent-related (Bitcoin-inherited) functionality.
#![allow(clippy::unit_arg)]
mod address;
mod keys;
mod script;
mod serialize;
pub use address::TransparentAddress;
pub use script::Script;
#[cfg(test)]
mod tests;
#[cfg(test)]
use proptest_derive::Arbitrary;
use crate::{
amount::{Amount, NonNegative},
block, transaction,
};
/// Arbitrary data inserted by miners into a coinbase transaction.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct CoinbaseData(
/// Invariant: this vec, together with the coinbase height, must be less than
/// 100 bytes. We enforce this by only constructing CoinbaseData fields by
/// parsing blocks with 100-byte data fields. When we implement block
/// creation, we should provide a constructor for the coinbase data field
/// that restricts it to 95 = 100 -1 -4 bytes (safe for any block height up
/// to 500_000_000).
pub(super) Vec<u8>,
);
impl AsRef<[u8]> for CoinbaseData {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// OutPoint
///
/// A particular transaction output reference.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct OutPoint {
/// References the transaction that contains the UTXO being spent.
pub hash: transaction::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, Serialize, Deserialize)]
pub enum Input {
/// A reference to an output of a previous transaction.
PrevOut {
/// The previous output transaction reference.
outpoint: OutPoint,
/// The script that authorizes spending `outpoint`.
unlock_script: Script,
/// The sequence number for the output.
sequence: u32,
},
/// New coins created by the block reward.
Coinbase {
/// The height of this block.
height: block::Height,
/// Free data inserted by miners after the block height.
data: CoinbaseData,
/// The sequence number for the output.
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, Serialize, Deserialize)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct Output {
/// Transaction value.
// At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
pub value: Amount<NonNegative>,
/// The lock script defines the conditions under which this output can be spent.
pub lock_script: Script,
}

View File

@ -11,10 +11,11 @@ use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
use crate::{
parameters::Network,
primitives::Script,
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
};
use super::Script;
/// Magic numbers used to identify what networks Transparent Addresses
/// are associated with.
mod magics {

View File

@ -0,0 +1,232 @@
use std::{
convert::TryInto,
io::{self, Read},
};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crate::{
block,
serialization::{
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
},
transaction,
};
use super::{CoinbaseData, Input, OutPoint, Output, Script};
/// The coinbase data for a genesis block.
///
/// Zcash uses the same coinbase data for the Mainnet, Testnet, and Regtest
/// genesis blocks.
const GENESIS_COINBASE_DATA: [u8; 77] = [
4, 255, 255, 7, 31, 1, 4, 69, 90, 99, 97, 115, 104, 48, 98, 57, 99, 52, 101, 101, 102, 56, 98,
55, 99, 99, 52, 49, 55, 101, 101, 53, 48, 48, 49, 101, 51, 53, 48, 48, 57, 56, 52, 98, 54, 102,
101, 97, 51, 53, 54, 56, 51, 97, 55, 99, 97, 99, 49, 52, 49, 97, 48, 52, 51, 99, 52, 50, 48,
54, 52, 56, 51, 53, 100, 51, 52,
];
impl ZcashSerialize for OutPoint {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.hash.0[..])?;
writer.write_u32::<LittleEndian>(self.index)?;
Ok(())
}
}
impl ZcashDeserialize for OutPoint {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(OutPoint {
hash: transaction::TransactionHash(reader.read_32_bytes()?),
index: reader.read_u32::<LittleEndian>()?,
})
}
}
// Coinbase inputs include block heights (BIP34). These are not encoded
// directly, but as a Bitcoin script that pushes the block height to the stack
// when executed. The script data is otherwise unused. Because we want to
// *parse* transactions into an internal representation where illegal states are
// unrepresentable, we need just enough parsing of Bitcoin scripts to parse the
// coinbase height and split off the rest of the (inert) coinbase data.
fn parse_coinbase_height(
mut data: Vec<u8>,
) -> Result<(block::Height, CoinbaseData), SerializationError> {
use block::Height;
match (data.get(0), data.len()) {
// Blocks 1 through 16 inclusive encode block height with OP_N opcodes.
(Some(op_n @ 0x51..=0x60), len) if len >= 1 => Ok((
Height((op_n - 0x50) as u32),
CoinbaseData(data.split_off(1)),
)),
// Blocks 17 through 256 exclusive encode block height with the `0x01` opcode.
(Some(0x01), len) if len >= 2 => {
Ok((Height(data[1] as u32), CoinbaseData(data.split_off(2))))
}
// Blocks 256 through 65536 exclusive encode block height with the `0x02` opcode.
(Some(0x02), len) if len >= 3 => Ok((
Height(data[1] as u32 + ((data[2] as u32) << 8)),
CoinbaseData(data.split_off(3)),
)),
// Blocks 65536 through 2**24 exclusive encode block height with the `0x03` opcode.
(Some(0x03), len) if len >= 4 => Ok((
Height(data[1] as u32 + ((data[2] as u32) << 8) + ((data[3] as u32) << 16)),
CoinbaseData(data.split_off(4)),
)),
// The genesis block does not encode the block height by mistake; special case it.
// The first five bytes are [4, 255, 255, 7, 31], the little-endian encoding of
// 520_617_983. This is lucky because it means we can special-case the genesis block
// while remaining below the maximum `block::Height` of 500_000_000 forced by `LockTime`.
// While it's unlikely this code will ever process a block height that high, this means
// we don't need to maintain a cascade of different invariants for allowable heights.
(Some(0x04), _) if data[..] == GENESIS_COINBASE_DATA[..] => {
Ok((Height(0), CoinbaseData(data)))
}
// As noted above, this is included for completeness.
(Some(0x04), len) if len >= 5 => {
let h = data[1] as u32
+ ((data[2] as u32) << 8)
+ ((data[3] as u32) << 16)
+ ((data[4] as u32) << 24);
if h <= Height::MAX.0 {
Ok((Height(h), CoinbaseData(data.split_off(5))))
} else {
Err(SerializationError::Parse("Invalid block height"))
}
}
_ => Err(SerializationError::Parse(
"Could not parse BIP34 height in coinbase data",
)),
}
}
fn coinbase_height_len(height: block::Height) -> usize {
// We can't write this as a match statement on stable until exclusive range
// guards are stabilized.
if let 0 = height.0 {
0
} else if let _h @ 1..=16 = height.0 {
1
} else if let _h @ 17..=255 = height.0 {
2
} else if let _h @ 256..=65535 = height.0 {
3
} else if let _h @ 65536..=16_777_215 = height.0 {
4
} else if let _h @ 16_777_216..=block::Height::MAX_AS_U32 = height.0 {
5
} else {
panic!("Invalid coinbase height");
}
}
fn write_coinbase_height<W: io::Write>(height: block::Height, mut w: W) -> Result<(), io::Error> {
// We can't write this as a match statement on stable until exclusive range
// guards are stabilized.
if let 0 = height.0 {
// Genesis block does not include height.
} else if let h @ 1..=16 = height.0 {
w.write_u8(0x50 + (h as u8))?;
} else if let h @ 17..=255 = height.0 {
w.write_u8(0x01)?;
w.write_u8(h as u8)?;
} else if let h @ 256..=65535 = height.0 {
w.write_u8(0x02)?;
w.write_u16::<LittleEndian>(h as u16)?;
} else if let h @ 65536..=16_777_215 = height.0 {
w.write_u8(0x03)?;
w.write_u8(h as u8)?;
w.write_u8((h >> 8) as u8)?;
w.write_u8((h >> 16) as u8)?;
} else if let h @ 16_777_216..=block::Height::MAX_AS_U32 = height.0 {
w.write_u8(0x04)?;
w.write_u32::<LittleEndian>(h)?;
} else {
panic!("Invalid coinbase height");
}
Ok(())
}
impl ZcashSerialize for Input {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
Input::PrevOut {
outpoint,
unlock_script,
sequence,
} => {
outpoint.zcash_serialize(&mut writer)?;
unlock_script.zcash_serialize(&mut writer)?;
writer.write_u32::<LittleEndian>(*sequence)?;
}
Input::Coinbase {
height,
data,
sequence,
} => {
writer.write_all(&[0; 32][..])?;
writer.write_u32::<LittleEndian>(0xffff_ffff)?;
let height_len = coinbase_height_len(*height);
let total_len = height_len + data.as_ref().len();
writer.write_compactsize(total_len as u64)?;
write_coinbase_height(*height, &mut writer)?;
writer.write_all(&data.as_ref()[..])?;
writer.write_u32::<LittleEndian>(*sequence)?;
}
}
Ok(())
}
}
impl ZcashDeserialize for Input {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
// This inlines the OutPoint deserialization to peek at the hash value
// and detect whether we have a coinbase input.
let bytes = reader.read_32_bytes()?;
if bytes == [0; 32] {
if reader.read_u32::<LittleEndian>()? != 0xffff_ffff {
return Err(SerializationError::Parse("wrong index in coinbase"));
}
let len = reader.read_compactsize()?;
if len > 100 {
return Err(SerializationError::Parse("coinbase has too much data"));
}
let mut data = Vec::with_capacity(len as usize);
(&mut reader).take(len).read_to_end(&mut data)?;
let (height, data) = parse_coinbase_height(data)?;
let sequence = reader.read_u32::<LittleEndian>()?;
Ok(Input::Coinbase {
height,
data,
sequence,
})
} else {
Ok(Input::PrevOut {
outpoint: OutPoint {
hash: transaction::TransactionHash(bytes),
index: reader.read_u32::<LittleEndian>()?,
},
unlock_script: Script::zcash_deserialize(&mut reader)?,
sequence: reader.read_u32::<LittleEndian>()?,
})
}
}
}
impl ZcashSerialize for Output {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_u64::<LittleEndian>(self.value.into())?;
self.lock_script.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for Output {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(Output {
value: reader.read_u64::<LittleEndian>()?.try_into()?,
lock_script: Script::zcash_deserialize(&mut reader)?,
})
}
}

View File

@ -0,0 +1 @@
mod arbitrary;

View File

@ -0,0 +1,39 @@
use proptest::{arbitrary::any, collection::vec, prelude::*};
use crate::block;
use super::super::{CoinbaseData, Input, OutPoint, Script};
impl Arbitrary for Input {
type Parameters = ();
fn arbitrary_with(_args: ()) -> Self::Strategy {
prop_oneof![
(any::<OutPoint>(), any::<Script>(), any::<u32>())
.prop_map(|(outpoint, unlock_script, sequence)| {
Input::PrevOut {
outpoint,
unlock_script,
sequence,
}
})
.boxed(),
(
any::<block::Height>(),
vec(any::<u8>(), 0..95),
any::<u32>()
)
.prop_map(|(height, data, sequence)| {
Input::Coinbase {
height,
data: CoinbaseData(data),
sequence,
}
})
.boxed(),
]
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}