chain: move Transparent{Input,Output} to transparent
Also bring the `Script` type there too.
This commit is contained in:
parent
25f63518f4
commit
d945cd28e8
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 { .. })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())?;
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
mod arbitrary;
|
|
@ -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>;
|
||||
}
|
Loading…
Reference in New Issue