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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::parameters::Network;
|
use crate::{parameters::Network, transaction::Transaction, transparent};
|
||||||
use crate::transaction::Transaction;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use proptest_derive::Arbitrary;
|
use proptest_derive::Arbitrary;
|
||||||
|
@ -42,12 +41,11 @@ pub struct Block {
|
||||||
impl Block {
|
impl Block {
|
||||||
/// Return the block height reported in the coinbase transaction, if any.
|
/// Return the block height reported in the coinbase transaction, if any.
|
||||||
pub fn coinbase_height(&self) -> Option<Height> {
|
pub fn coinbase_height(&self) -> Option<Height> {
|
||||||
use crate::transaction::TransparentInput;
|
|
||||||
self.transactions
|
self.transactions
|
||||||
.get(0)
|
.get(0)
|
||||||
.and_then(|tx| tx.inputs().get(0))
|
.and_then(|tx| tx.inputs().get(0))
|
||||||
.and_then(|input| match input {
|
.and_then(|input| match input {
|
||||||
TransparentInput::Coinbase { ref height, .. } => Some(*height),
|
transparent::Input::Coinbase { ref height, .. } => Some(*height),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
serialization::{ZcashDeserialize, ZcashSerialize},
|
serialization::{ZcashDeserialize, ZcashSerialize},
|
||||||
transaction::{LockTime, Transaction, TransparentInput, TransparentOutput},
|
transaction::{LockTime, Transaction},
|
||||||
|
transparent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{serialize::MAX_BLOCK_BYTES, Block, Header};
|
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 {
|
fn single_transaction_block(oversized: bool) -> Block {
|
||||||
// Dummy input and output
|
// Dummy input and output
|
||||||
let input =
|
let input =
|
||||||
TransparentInput::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
|
transparent::Input::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
|
||||||
let output =
|
let output =
|
||||||
TransparentOutput::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
|
transparent::Output::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
|
||||||
|
|
||||||
// A block header
|
// A block header
|
||||||
let header = block_header();
|
let header = block_header();
|
||||||
|
|
|
@ -5,11 +5,9 @@
|
||||||
//! whose functionality is implemented elsewhere.
|
//! whose functionality is implemented elsewhere.
|
||||||
|
|
||||||
mod proofs;
|
mod proofs;
|
||||||
mod script;
|
|
||||||
|
|
||||||
pub use ed25519_zebra as ed25519;
|
pub use ed25519_zebra as ed25519;
|
||||||
pub use redjubjub;
|
pub use redjubjub;
|
||||||
pub use x25519_dalek as x25519;
|
pub use x25519_dalek as x25519;
|
||||||
|
|
||||||
pub use proofs::{Bctv14Proof, Groth16Proof, ZkSnarkProof};
|
pub use proofs::{Bctv14Proof, Groth16Proof, ZkSnarkProof};
|
||||||
pub use script::Script;
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ mod lock_time;
|
||||||
mod memo;
|
mod memo;
|
||||||
mod serialize;
|
mod serialize;
|
||||||
mod shielded_data;
|
mod shielded_data;
|
||||||
mod transparent;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -18,11 +17,13 @@ pub use joinsplit::{JoinSplit, JoinSplitData};
|
||||||
pub use lock_time::LockTime;
|
pub use lock_time::LockTime;
|
||||||
pub use memo::Memo;
|
pub use memo::Memo;
|
||||||
pub use shielded_data::{Output, ShieldedData, Spend};
|
pub use shielded_data::{Output, ShieldedData, Spend};
|
||||||
pub use transparent::{CoinbaseData, OutPoint, TransparentInput, TransparentOutput};
|
|
||||||
|
|
||||||
use crate::amount::Amount;
|
use crate::{
|
||||||
use crate::block;
|
amount::Amount,
|
||||||
use crate::primitives::{Bctv14Proof, Groth16Proof};
|
block,
|
||||||
|
primitives::{Bctv14Proof, Groth16Proof},
|
||||||
|
transparent,
|
||||||
|
};
|
||||||
|
|
||||||
/// A Zcash transaction.
|
/// A Zcash transaction.
|
||||||
///
|
///
|
||||||
|
@ -42,9 +43,9 @@ pub enum Transaction {
|
||||||
/// A fully transparent transaction (`version = 1`).
|
/// A fully transparent transaction (`version = 1`).
|
||||||
V1 {
|
V1 {
|
||||||
/// The transparent inputs to the transaction.
|
/// The transparent inputs to the transaction.
|
||||||
inputs: Vec<TransparentInput>,
|
inputs: Vec<transparent::Input>,
|
||||||
/// The transparent outputs from the transaction.
|
/// 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
|
/// The earliest time or block height that this transaction can be added to the
|
||||||
/// chain.
|
/// chain.
|
||||||
lock_time: LockTime,
|
lock_time: LockTime,
|
||||||
|
@ -52,9 +53,9 @@ pub enum Transaction {
|
||||||
/// A Sprout transaction (`version = 2`).
|
/// A Sprout transaction (`version = 2`).
|
||||||
V2 {
|
V2 {
|
||||||
/// The transparent inputs to the transaction.
|
/// The transparent inputs to the transaction.
|
||||||
inputs: Vec<TransparentInput>,
|
inputs: Vec<transparent::Input>,
|
||||||
/// The transparent outputs from the transaction.
|
/// 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
|
/// The earliest time or block height that this transaction can be added to the
|
||||||
/// chain.
|
/// chain.
|
||||||
lock_time: LockTime,
|
lock_time: LockTime,
|
||||||
|
@ -64,9 +65,9 @@ pub enum Transaction {
|
||||||
/// An Overwinter transaction (`version = 3`).
|
/// An Overwinter transaction (`version = 3`).
|
||||||
V3 {
|
V3 {
|
||||||
/// The transparent inputs to the transaction.
|
/// The transparent inputs to the transaction.
|
||||||
inputs: Vec<TransparentInput>,
|
inputs: Vec<transparent::Input>,
|
||||||
/// The transparent outputs from the transaction.
|
/// 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
|
/// The earliest time or block height that this transaction can be added to the
|
||||||
/// chain.
|
/// chain.
|
||||||
lock_time: LockTime,
|
lock_time: LockTime,
|
||||||
|
@ -78,9 +79,9 @@ pub enum Transaction {
|
||||||
/// A Sapling transaction (`version = 4`).
|
/// A Sapling transaction (`version = 4`).
|
||||||
V4 {
|
V4 {
|
||||||
/// The transparent inputs to the transaction.
|
/// The transparent inputs to the transaction.
|
||||||
inputs: Vec<TransparentInput>,
|
inputs: Vec<transparent::Input>,
|
||||||
/// The transparent outputs from the transaction.
|
/// 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
|
/// The earliest time or block height that this transaction can be added to the
|
||||||
/// chain.
|
/// chain.
|
||||||
lock_time: LockTime,
|
lock_time: LockTime,
|
||||||
|
@ -97,7 +98,7 @@ pub enum Transaction {
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
/// Access the transparent inputs of this transaction, regardless of version.
|
/// Access the transparent inputs of this transaction, regardless of version.
|
||||||
pub fn inputs(&self) -> &[TransparentInput] {
|
pub fn inputs(&self) -> &[transparent::Input] {
|
||||||
match self {
|
match self {
|
||||||
Transaction::V1 { ref inputs, .. } => inputs,
|
Transaction::V1 { ref inputs, .. } => inputs,
|
||||||
Transaction::V2 { ref inputs, .. } => inputs,
|
Transaction::V2 { ref inputs, .. } => inputs,
|
||||||
|
@ -107,7 +108,7 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the transparent outputs of this transaction, regardless of version.
|
/// Access the transparent outputs of this transaction, regardless of version.
|
||||||
pub fn outputs(&self) -> &[TransparentOutput] {
|
pub fn outputs(&self) -> &[transparent::Output] {
|
||||||
match self {
|
match self {
|
||||||
Transaction::V1 { ref outputs, .. } => outputs,
|
Transaction::V1 { ref outputs, .. } => outputs,
|
||||||
Transaction::V2 { ref outputs, .. } => outputs,
|
Transaction::V2 { ref outputs, .. } => outputs,
|
||||||
|
@ -140,7 +141,7 @@ impl Transaction {
|
||||||
pub fn contains_coinbase_input(&self) -> bool {
|
pub fn contains_coinbase_input(&self) -> bool {
|
||||||
self.inputs()
|
self.inputs()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|input| matches!(input, TransparentInput::Coinbase { .. }))
|
.any(|input| matches!(input, transparent::Input::Coinbase { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this transaction is a coinbase transaction.
|
/// Returns `true` if this transaction is a coinbase transaction.
|
||||||
|
@ -148,7 +149,7 @@ impl Transaction {
|
||||||
self.inputs().len() == 1
|
self.inputs().len() == 1
|
||||||
&& matches!(
|
&& matches!(
|
||||||
self.inputs().get(0),
|
self.inputs().get(0),
|
||||||
Some(TransparentInput::Coinbase { .. })
|
Some(transparent::Input::Coinbase { .. })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
//! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the
|
//! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the
|
||||||
//! transaction types, so that all of the serialization logic is in one place.
|
//! 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 byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use std::{
|
|
||||||
convert::TryInto,
|
|
||||||
io::{self, Read},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::{Script, ZkSnarkProof},
|
primitives::ZkSnarkProof,
|
||||||
serialization::{
|
serialization::{
|
||||||
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
|
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
|
||||||
},
|
},
|
||||||
|
@ -21,17 +18,6 @@ use super::*;
|
||||||
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
|
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
|
||||||
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085;
|
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 {
|
impl ZcashDeserialize for jubjub::Fq {
|
||||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
|
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> {
|
impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
|
||||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
writer.write_u64::<LittleEndian>(self.vpub_old.into())?;
|
writer.write_u64::<LittleEndian>(self.vpub_old.into())?;
|
||||||
|
|
|
@ -5,20 +5,19 @@ use proptest::{arbitrary::any, array, collection::vec, option, prelude::*};
|
||||||
use crate::{
|
use crate::{
|
||||||
amount::{Amount, NonNegative},
|
amount::{Amount, NonNegative},
|
||||||
block,
|
block,
|
||||||
primitives::{Bctv14Proof, Groth16Proof, Script, ZkSnarkProof},
|
primitives::{Bctv14Proof, Groth16Proof, ZkSnarkProof},
|
||||||
sapling, sprout,
|
sapling, sprout, transparent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{
|
use super::super::{
|
||||||
CoinbaseData, JoinSplit, JoinSplitData, LockTime, Memo, OutPoint, Output, ShieldedData, Spend,
|
JoinSplit, JoinSplitData, LockTime, Memo, Output, ShieldedData, Spend, Transaction,
|
||||||
Transaction, TransparentInput, TransparentOutput,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
pub fn v1_strategy() -> impl Strategy<Value = Self> {
|
pub fn v1_strategy() -> impl Strategy<Value = Self> {
|
||||||
(
|
(
|
||||||
vec(any::<TransparentInput>(), 0..10),
|
vec(any::<transparent::Input>(), 0..10),
|
||||||
vec(any::<TransparentOutput>(), 0..10),
|
vec(any::<transparent::Output>(), 0..10),
|
||||||
any::<LockTime>(),
|
any::<LockTime>(),
|
||||||
)
|
)
|
||||||
.prop_map(|(inputs, outputs, lock_time)| Transaction::V1 {
|
.prop_map(|(inputs, outputs, lock_time)| Transaction::V1 {
|
||||||
|
@ -31,8 +30,8 @@ impl Transaction {
|
||||||
|
|
||||||
pub fn v2_strategy() -> impl Strategy<Value = Self> {
|
pub fn v2_strategy() -> impl Strategy<Value = Self> {
|
||||||
(
|
(
|
||||||
vec(any::<TransparentInput>(), 0..10),
|
vec(any::<transparent::Input>(), 0..10),
|
||||||
vec(any::<TransparentOutput>(), 0..10),
|
vec(any::<transparent::Output>(), 0..10),
|
||||||
any::<LockTime>(),
|
any::<LockTime>(),
|
||||||
option::of(any::<JoinSplitData<Bctv14Proof>>()),
|
option::of(any::<JoinSplitData<Bctv14Proof>>()),
|
||||||
)
|
)
|
||||||
|
@ -49,8 +48,8 @@ impl Transaction {
|
||||||
|
|
||||||
pub fn v3_strategy() -> impl Strategy<Value = Self> {
|
pub fn v3_strategy() -> impl Strategy<Value = Self> {
|
||||||
(
|
(
|
||||||
vec(any::<TransparentInput>(), 0..10),
|
vec(any::<transparent::Input>(), 0..10),
|
||||||
vec(any::<TransparentOutput>(), 0..10),
|
vec(any::<transparent::Output>(), 0..10),
|
||||||
any::<LockTime>(),
|
any::<LockTime>(),
|
||||||
any::<block::Height>(),
|
any::<block::Height>(),
|
||||||
option::of(any::<JoinSplitData<Bctv14Proof>>()),
|
option::of(any::<JoinSplitData<Bctv14Proof>>()),
|
||||||
|
@ -69,8 +68,8 @@ impl Transaction {
|
||||||
|
|
||||||
pub fn v4_strategy() -> impl Strategy<Value = Self> {
|
pub fn v4_strategy() -> impl Strategy<Value = Self> {
|
||||||
(
|
(
|
||||||
vec(any::<TransparentInput>(), 0..10),
|
vec(any::<transparent::Input>(), 0..10),
|
||||||
vec(any::<TransparentOutput>(), 0..10),
|
vec(any::<transparent::Output>(), 0..10),
|
||||||
any::<LockTime>(),
|
any::<LockTime>(),
|
||||||
any::<block::Height>(),
|
any::<block::Height>(),
|
||||||
any::<Amount>(),
|
any::<Amount>(),
|
||||||
|
@ -311,37 +310,3 @@ impl Arbitrary for Transaction {
|
||||||
|
|
||||||
type Strategy = BoxedStrategy<Self>;
|
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.
|
//! Transparent-related (Bitcoin-inherited) functionality.
|
||||||
|
#![allow(clippy::unit_arg)]
|
||||||
|
|
||||||
mod address;
|
mod address;
|
||||||
mod keys;
|
mod keys;
|
||||||
|
mod script;
|
||||||
|
mod serialize;
|
||||||
|
|
||||||
pub use address::TransparentAddress;
|
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::{
|
use crate::{
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
primitives::Script,
|
|
||||||
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
|
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::Script;
|
||||||
|
|
||||||
/// Magic numbers used to identify what networks Transparent Addresses
|
/// Magic numbers used to identify what networks Transparent Addresses
|
||||||
/// are associated with.
|
/// are associated with.
|
||||||
mod magics {
|
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