diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index c612be7d7..6f144ad1d 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -23,8 +23,7 @@ type Error = Box; 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 { - 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, }) } diff --git a/zebra-chain/src/block/tests/generate.rs b/zebra-chain/src/block/tests/generate.rs index adc0c0da0..c01286e99 100644 --- a/zebra-chain/src/block/tests/generate.rs +++ b/zebra-chain/src/block/tests/generate.rs @@ -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(); diff --git a/zebra-chain/src/primitives.rs b/zebra-chain/src/primitives.rs index 012267272..94cfa1da1 100644 --- a/zebra-chain/src/primitives.rs +++ b/zebra-chain/src/primitives.rs @@ -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; diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index ea8f91837..a5273e01f 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -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, + inputs: Vec, /// The transparent outputs from the transaction. - outputs: Vec, + outputs: Vec, /// 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, + inputs: Vec, /// The transparent outputs from the transaction. - outputs: Vec, + outputs: Vec, /// 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, + inputs: Vec, /// The transparent outputs from the transaction. - outputs: Vec, + outputs: Vec, /// 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, + inputs: Vec, /// The transparent outputs from the transaction. - outputs: Vec, + outputs: Vec, /// 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 { .. }) ) } } diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index cdea32fb5..c3fd65dfb 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -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(mut reader: R) -> Result { 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(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.hash.0[..])?; - writer.write_u32::(self.index)?; - Ok(()) - } -} - -impl ZcashDeserialize for OutPoint { - fn zcash_deserialize(mut reader: R) -> Result { - Ok(OutPoint { - hash: TransactionHash(reader.read_32_bytes()?), - index: reader.read_u32::()?, - }) - } -} - -// 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, -) -> 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(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::(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::(h)?; - } else { - panic!("Invalid coinbase height"); - } - Ok(()) -} - -impl ZcashSerialize for TransparentInput { - fn zcash_serialize(&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::(*sequence)?; - } - TransparentInput::Coinbase { - height, - data, - sequence, - } => { - writer.write_all(&[0; 32][..])?; - writer.write_u32::(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::(*sequence)?; - } - } - Ok(()) - } -} - -impl ZcashDeserialize for TransparentInput { - fn zcash_deserialize(mut reader: R) -> Result { - // 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::()? != 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::()?; - Ok(TransparentInput::Coinbase { - height, - data, - sequence, - }) - } else { - Ok(TransparentInput::PrevOut { - outpoint: OutPoint { - hash: TransactionHash(bytes), - index: reader.read_u32::()?, - }, - unlock_script: Script::zcash_deserialize(&mut reader)?, - sequence: reader.read_u32::()?, - }) - } - } -} - -impl ZcashSerialize for TransparentOutput { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_u64::(self.value.into())?; - self.lock_script.zcash_serialize(&mut writer)?; - Ok(()) - } -} - -impl ZcashDeserialize for TransparentOutput { - fn zcash_deserialize(mut reader: R) -> Result { - Ok(TransparentOutput { - value: reader.read_u64::()?.try_into()?, - lock_script: Script::zcash_deserialize(&mut reader)?, - }) - } -} - impl ZcashSerialize for JoinSplit

{ fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { writer.write_u64::(self.vpub_old.into())?; diff --git a/zebra-chain/src/transaction/tests/arbitrary.rs b/zebra-chain/src/transaction/tests/arbitrary.rs index 38ae367fa..b6da5e25b 100644 --- a/zebra-chain/src/transaction/tests/arbitrary.rs +++ b/zebra-chain/src/transaction/tests/arbitrary.rs @@ -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 { ( - vec(any::(), 0..10), - vec(any::(), 0..10), + vec(any::(), 0..10), + vec(any::(), 0..10), any::(), ) .prop_map(|(inputs, outputs, lock_time)| Transaction::V1 { @@ -31,8 +30,8 @@ impl Transaction { pub fn v2_strategy() -> impl Strategy { ( - vec(any::(), 0..10), - vec(any::(), 0..10), + vec(any::(), 0..10), + vec(any::(), 0..10), any::(), option::of(any::>()), ) @@ -49,8 +48,8 @@ impl Transaction { pub fn v3_strategy() -> impl Strategy { ( - vec(any::(), 0..10), - vec(any::(), 0..10), + vec(any::(), 0..10), + vec(any::(), 0..10), any::(), any::(), option::of(any::>()), @@ -69,8 +68,8 @@ impl Transaction { pub fn v4_strategy() -> impl Strategy { ( - vec(any::(), 0..10), - vec(any::(), 0..10), + vec(any::(), 0..10), + vec(any::(), 0..10), any::(), any::(), any::(), @@ -311,37 +310,3 @@ impl Arbitrary for Transaction { type Strategy = BoxedStrategy; } - -impl Arbitrary for TransparentInput { - type Parameters = (); - - fn arbitrary_with(_args: ()) -> Self::Strategy { - prop_oneof![ - (any::(), any::