diff --git a/zebra-chain/src/transparent/serialize.rs b/zebra-chain/src/transparent/serialize.rs index 0a9d01915..11cd98964 100644 --- a/zebra-chain/src/transparent/serialize.rs +++ b/zebra-chain/src/transparent/serialize.rs @@ -1,3 +1,5 @@ +//! Serializes and deserializes transparent data. + use std::io; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; @@ -14,13 +16,24 @@ use crate::{ use super::{CoinbaseData, Input, OutPoint, Output, Script}; /// The maximum length of the coinbase data. +/// /// Includes the encoded coinbase height, if any. /// /// > The number of bytes in the coinbase script, up to a maximum of 100 bytes. /// -/// https://developer.bitcoin.org/reference/transactions.html#coinbase-input-the-input-of-the-first-transaction-in-a-block +/// pub const MAX_COINBASE_DATA_LEN: usize = 100; +/// The minimum length of the coinbase data. +/// +/// Includes the encoded coinbase height, if any. +/// +// TODO: Update the link below once the constant is documented in the +// protocol. +/// +/// +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 @@ -68,18 +81,19 @@ impl ZcashDeserialize for OutPoint { /// /// # 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 +/// > 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> { @@ -262,9 +276,14 @@ impl ZcashDeserialize for Input { } 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 has too much data")); + 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::()?;