//! Serializes and deserializes transparent data. use std::io; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::{ block::{self, Height}, serialization::{ zcash_serialize_bytes, FakeWriter, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, }, transaction, }; use super::{CoinbaseData, Input, OutPoint, Output, Script}; /// The maximum length of the coinbase data. /// /// Includes the encoded coinbase height, if any. /// /// # Consensus /// /// > A coinbase transaction script MUST have length in {2 .. 100} bytes. /// /// pub const MAX_COINBASE_DATA_LEN: usize = 100; /// The maximum length of the encoded coinbase height. /// /// # Consensus /// /// > The length of heightBytes MUST be in the range {1 .. 5}. Then the encoding is the length /// > of heightBytes encoded as one byte, followed by heightBytes itself. /// /// pub const MAX_COINBASE_HEIGHT_DATA_LEN: usize = 6; /// The minimum length of the coinbase data. /// /// Includes the encoded coinbase height, if any. /// /// # Consensus /// /// > A coinbase transaction script MUST have length in {2 .. 100} bytes. /// /// pub const MIN_COINBASE_DATA_LEN: usize = 2; /// The coinbase data for a genesis block. /// /// Zcash uses the same coinbase data for the Mainnet, Testnet, and Regtest /// genesis blocks. pub 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(&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: transaction::Hash(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. // Starting at Network Upgrade 5, coinbase transactions also encode the block // height in the expiry height field. But Zebra does not use this field to // determine the coinbase height, because it is not present in older network // upgrades. /// Split `data` into a block height and remaining miner-controlled coinbase data. /// /// The height may consume `0..=5` bytes at the stat of the coinbase data. /// The genesis block does not include an encoded coinbase height. /// /// # Consensus /// /// > A coinbase transaction for a *block* at *block height* greater than 0 MUST have /// > a script that, as its first item, encodes the *block height* `height` as follows. /// > For `height` in the range {1..16}, the encoding is a single byte of value /// > `0x50` + `height`. Otherwise, let `heightBytes` be the signed little-endian /// > representation of `height`, using the minimum nonzero number of bytes such that /// > the most significant byte is < `0x80`. /// > The length of `heightBytes` MUST be in the range {1..5}. /// > Then the encoding is the length of `heightBytes` encoded as one byte, /// > followed by `heightBytes` itself. This matches the encoding used by Bitcoin in the /// > implementation of [BIP-34] (but the description here is to be considered normative). /// /// /// pub(crate) fn parse_coinbase_height( mut data: Vec, ) -> Result<(block::Height, CoinbaseData), SerializationError> { match (data.first(), 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 128 exclusive encode block height with the `0x01` opcode. // The Bitcoin encoding requires that the most significant byte is below 0x80. (Some(0x01), len) if len >= 2 && data[1] < 0x80 => { let h = data[1] as u32; if (17..128).contains(&h) { Ok((Height(h), CoinbaseData(data.split_off(2)))) } else { Err(SerializationError::Parse("Invalid block height")) } } // Blocks 128 through 32768 exclusive encode block height with the `0x02` opcode. // The Bitcoin encoding requires that the most significant byte is below 0x80. (Some(0x02), len) if len >= 3 && data[2] < 0x80 => { let h = data[1] as u32 + ((data[2] as u32) << 8); if (128..32_768).contains(&h) { Ok((Height(h), CoinbaseData(data.split_off(3)))) } else { Err(SerializationError::Parse("Invalid block height")) } } // Blocks 32768 through 2**23 exclusive encode block height with the `0x03` opcode. // The Bitcoin encoding requires that the most significant byte is below 0x80. (Some(0x03), len) if len >= 4 && data[3] < 0x80 => { let h = data[1] as u32 + ((data[2] as u32) << 8) + ((data[3] as u32) << 16); if (32_768..8_388_608).contains(&h) { Ok((Height(h), CoinbaseData(data.split_off(4)))) } else { Err(SerializationError::Parse("Invalid block height")) } } // 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. // // In the far future, Zcash might reach this height, and the miner might use the // same coinbase data as the genesis block. So we need an updated consensus rule // to handle this edge case. // // TODO: update this check based on the consensus rule changes in // https://github.com/zcash/zips/issues/540 (Some(0x04), _) if data[..] == GENESIS_COINBASE_DATA[..] => { Ok((Height(0), CoinbaseData(data))) } // As noted above, this is included for completeness. // The Bitcoin encoding requires that the most significant byte is below 0x80. (Some(0x04), len) if len >= 5 && data[4] < 0x80 => { let h = data[1] as u32 + ((data[2] as u32) << 8) + ((data[3] as u32) << 16) + ((data[4] as u32) << 24); if (8_388_608..=Height::MAX.0).contains(&h) { 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", )), } } /// Encode `height` into a block height, as a prefix of the coinbase data. /// Does not write `coinbase_data`. /// /// The height may produce `0..=5` initial bytes of coinbase data. /// /// # Errors /// /// Returns an error if the coinbase height is zero, /// and the `coinbase_data` does not match the Zcash mainnet and testnet genesis coinbase data. /// (They are identical.) /// /// This check is required, because the genesis block does not include an encoded /// coinbase height, pub(crate) fn write_coinbase_height( height: block::Height, coinbase_data: &CoinbaseData, mut w: W, ) -> Result<(), io::Error> { // We can't write this as a match statement on stable until exclusive range // guards are stabilized. // The Bitcoin encoding requires that the most significant byte is below 0x80, // so the ranges run up to 2^{n-1} rather than 2^n. if let 0 = height.0 { // The genesis block's coinbase data does not have a height prefix. // So we return an error if the entire coinbase data doesn't match genesis. // (If we don't do this check, then deserialization will fail.) // // TODO: update this check based on the consensus rule changes in // https://github.com/zcash/zips/issues/540 if coinbase_data.0 != GENESIS_COINBASE_DATA { return Err(io::Error::new( io::ErrorKind::Other, "invalid genesis coinbase data", )); } } else if let h @ 1..=16 = height.0 { w.write_u8(0x50 + (h as u8))?; } else if let h @ 17..=127 = height.0 { w.write_u8(0x01)?; w.write_u8(h as u8)?; } else if let h @ 128..=32_767 = height.0 { w.write_u8(0x02)?; w.write_u16::(h as u16)?; } else if let h @ 32_768..=8_388_607 = 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 @ 8_388_608..=block::Height::MAX_AS_U32 = height.0 { w.write_u8(0x04)?; w.write_u32::(h)?; } else { panic!("Invalid coinbase height"); } Ok(()) } impl Height { /// Get the size of `Height` when serialized into a coinbase input script. pub fn coinbase_zcash_serialized_size(&self) -> usize { let mut writer = FakeWriter(0); let empty_data = CoinbaseData(Vec::new()); write_coinbase_height(*self, &empty_data, &mut writer).expect("writer should never fail"); writer.0 } } impl ZcashSerialize for Input { /// Serialize this transparent input. /// /// # Errors /// /// Returns an error if the coinbase height is zero, /// and the coinbase data does not match the Zcash mainnet and testnet genesis coinbase data. /// (They are identical.) /// /// This check is required, because the genesis block does not include an encoded /// coinbase height, fn zcash_serialize(&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::(*sequence)?; } Input::Coinbase { height, data, sequence, } => { writer.write_all(&[0; 32][..])?; writer.write_u32::(0xffff_ffff)?; let mut height_and_data = Vec::new(); write_coinbase_height(*height, data, &mut height_and_data)?; height_and_data.extend(&data.0); zcash_serialize_bytes(&height_and_data, &mut writer)?; writer.write_u32::(*sequence)?; } } Ok(()) } } impl ZcashDeserialize for Input { 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 data: Vec = (&mut reader).zcash_deserialize_into()?; // Check the coinbase data length. if data.len() > MAX_COINBASE_DATA_LEN { return Err(SerializationError::Parse("coinbase data is too long")); } else if data.len() < MIN_COINBASE_DATA_LEN { return Err(SerializationError::Parse("coinbase data is too short")); } let (height, data) = parse_coinbase_height(data)?; let sequence = reader.read_u32::()?; Ok(Input::Coinbase { height, data, sequence, }) } else { Ok(Input::PrevOut { outpoint: OutPoint { hash: transaction::Hash(bytes), index: reader.read_u32::()?, }, unlock_script: Script::zcash_deserialize(&mut reader)?, sequence: reader.read_u32::()?, }) } } } impl ZcashSerialize for Output { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { self.value.zcash_serialize(&mut writer)?; self.lock_script.zcash_serialize(&mut writer)?; Ok(()) } } impl ZcashDeserialize for Output { fn zcash_deserialize(mut reader: R) -> Result { let reader = &mut reader; Ok(Output { value: reader.zcash_deserialize_into()?, lock_script: Script::zcash_deserialize(reader)?, }) } }