diff --git a/book/src/dev/rfcs/0010-v5-transaction.md b/book/src/dev/rfcs/0010-v5-transaction.md index a707dfa0a51..eb538911aa7 100644 --- a/book/src/dev/rfcs/0010-v5-transaction.md +++ b/book/src/dev/rfcs/0010-v5-transaction.md @@ -53,7 +53,7 @@ Orchard uses `Halo2Proof`s with corresponding signature type changes. Each Orcha [other-transaction-v5-changes]: #other-transaction-v5-changes V5 transactions split `Spend`s, `Output`s, and `AuthorizedAction`s into multiple arrays, -with a single `compactsize` count before the first array. We add new +with a single `CompactSize` count before the first array. We add new `zcash_deserialize_external_count` and `zcash_serialize_external_count` utility functions, which make it easier to serialize and deserialize these arrays correctly. diff --git a/zebra-chain/src/serialization.rs b/zebra-chain/src/serialization.rs index 99aedf99657..baeb0a82b67 100644 --- a/zebra-chain/src/serialization.rs +++ b/zebra-chain/src/serialization.rs @@ -6,6 +6,7 @@ //! `ReadZcashExt`, extension traits for `io::Read` and `io::Write` with utility functions //! for reading and writing data (e.g., the Bitcoin variable-integer format). +mod compact_size; mod constraint; mod date_time; mod error; @@ -14,13 +15,17 @@ mod write_zcash; mod zcash_deserialize; mod zcash_serialize; -pub(crate) mod serde_helpers; - pub mod sha256d; +pub(crate) mod serde_helpers; + #[cfg(any(test, feature = "proptest-impl"))] pub mod arbitrary; +#[cfg(test)] +pub mod tests; + +pub use compact_size::{CompactSize64, CompactSizeMessage}; pub use constraint::AtLeastOne; pub use date_time::{DateTime32, Duration32}; pub use error::SerializationError; @@ -31,9 +36,6 @@ pub use zcash_deserialize::{ ZcashDeserialize, ZcashDeserializeInto, }; pub use zcash_serialize::{ - zcash_serialize_bytes, zcash_serialize_bytes_external_count, zcash_serialize_external_count, - FakeWriter, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN, + zcash_serialize_bytes, zcash_serialize_bytes_external_count, zcash_serialize_empty_list, + zcash_serialize_external_count, FakeWriter, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN, }; - -#[cfg(test)] -pub mod tests; diff --git a/zebra-chain/src/serialization/arbitrary.rs b/zebra-chain/src/serialization/arbitrary.rs index 8e396284996..bee67f27304 100644 --- a/zebra-chain/src/serialization/arbitrary.rs +++ b/zebra-chain/src/serialization/arbitrary.rs @@ -1,9 +1,13 @@ //! Arbitrary data generation for serialization proptests -use super::{read_zcash::canonical_socket_addr, DateTime32}; +use std::{convert::TryInto, net::SocketAddr}; + use chrono::{TimeZone, Utc, MAX_DATETIME, MIN_DATETIME}; use proptest::{arbitrary::any, prelude::*}; -use std::net::SocketAddr; + +use super::{ + read_zcash::canonical_socket_addr, CompactSizeMessage, DateTime32, MAX_PROTOCOL_MESSAGE_LEN, +}; impl Arbitrary for DateTime32 { type Parameters = (); @@ -62,3 +66,18 @@ pub fn datetime_u32() -> impl Strategy> { pub fn canonical_socket_addr_strategy() -> impl Strategy { any::().prop_map(canonical_socket_addr) } + +impl Arbitrary for CompactSizeMessage { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (0..=MAX_PROTOCOL_MESSAGE_LEN) + .prop_map(|size| { + size.try_into() + .expect("MAX_PROTOCOL_MESSAGE_LEN fits in CompactSizeMessage") + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/serialization/compact_size.rs b/zebra-chain/src/serialization/compact_size.rs new file mode 100644 index 00000000000..b682565d2de --- /dev/null +++ b/zebra-chain/src/serialization/compact_size.rs @@ -0,0 +1,363 @@ +//! Types and serialization for the Bitcoin `CompactSize` format. +//! +//! Zebra decodes `CompactSize` fields into two different types, +//! depending on the consensus rules that apply to that type: +//! - [`CompactSizeMessage`] for sizes that must be less than the network message limit, and +//! - [`CompactSize64`] for flags, arbitrary counts, and sizes that span multiple blocks. + +use std::convert::{TryFrom, TryInto}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use crate::serialization::{ + SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, + MAX_PROTOCOL_MESSAGE_LEN, +}; + +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + +/// A CompactSize-encoded field that is limited to [`MAX_PROTOCOL_MESSAGE_LEN`]. +/// Used for sizes or counts of objects that are sent in network messages. +/// +/// # Security +/// +/// Deserialized sizes must be validated before being used to allocate memory. +/// +/// Preallocating vectors using untrusted `CompactSize`s allows memory +/// denial of service attacks. Valid sizes must be less than +/// `MAX_PROTOCOL_MESSAGE_LEN / min_serialized_item_bytes` (or a lower limit +/// specified by the Zcash consensus rules or Bitcoin network protocol). +/// +/// As a defence-in-depth for memory preallocation attacks, +/// Zebra rejects sizes greater than the protocol message length limit. +/// (These sizes should be impossible, because each array items takes at +/// least one byte.) +/// +/// # Serialization Examples +/// +/// ``` +/// use zebra_chain::serialization::{CompactSizeMessage, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN}; +/// use std::convert::{TryFrom, TryInto}; +/// +/// let size = CompactSizeMessage::try_from(0x12).unwrap(); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\x12"); +/// +/// let size = CompactSizeMessage::try_from(0xfd).unwrap(); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfd\xfd\x00"); +/// +/// let size = CompactSizeMessage::try_from(0xaafd).unwrap(); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfd\xfd\xaa"); +/// +/// let max_size: usize = MAX_PROTOCOL_MESSAGE_LEN.try_into().unwrap(); +/// let size = CompactSizeMessage::try_from(max_size).unwrap(); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfe\x00\x00\x20\x00"); +/// ``` +/// +/// [`CompactSizeMessage`]s greater than the maximum network message length +/// can not be constructed: +/// ``` +/// # use zebra_chain::serialization::{CompactSizeMessage, MAX_PROTOCOL_MESSAGE_LEN}; +/// # use std::convert::{TryFrom, TryInto}; +/// # let max_size: usize = MAX_PROTOCOL_MESSAGE_LEN.try_into().unwrap(); +/// assert!(CompactSizeMessage::try_from(max_size + 1).is_err()); +/// +/// assert!(CompactSizeMessage::try_from(0xbbaafd).is_err()); +/// +/// assert!(CompactSizeMessage::try_from(0x22ccbbaafd).is_err()); +/// ``` +/// +/// # Deserialization Examples +/// +/// ``` +/// use zebra_chain::serialization::{CompactSizeMessage, ZcashDeserializeInto, MAX_PROTOCOL_MESSAGE_LEN}; +/// use std::{convert::{TryFrom, TryInto}, io::Cursor}; +/// +/// assert_eq!( +/// CompactSizeMessage::try_from(0x12).unwrap(), +/// Cursor::new(b"\x12").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSizeMessage::try_from(0xfd).unwrap(), +/// Cursor::new(b"\xfd\xfd\x00").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSizeMessage::try_from(0xaafd).unwrap(), +/// Cursor::new(b"\xfd\xfd\xaa").zcash_deserialize_into().unwrap(), +/// ); +/// +/// let max_size: usize = MAX_PROTOCOL_MESSAGE_LEN.try_into().unwrap(); +/// assert_eq!( +/// CompactSizeMessage::try_from(max_size).unwrap(), +/// Cursor::new(b"\xfe\x00\x00\x20\x00").zcash_deserialize_into().unwrap(), +/// ); +/// ``` +/// +/// [`CompactSizeMessage`]s greater than the maximum network message length are invalid. +/// They return a [`SerializationError::Parse`]: +/// ``` +/// # use zebra_chain::serialization::{CompactSizeMessage, ZcashDeserialize, MAX_PROTOCOL_MESSAGE_LEN}; +/// # use std::{convert::TryFrom, io::Cursor}; +/// let max_size_plus_one = Cursor::new(b"\xfe\x01\x00\x20\x00"); +/// assert!(CompactSizeMessage::zcash_deserialize(max_size_plus_one).is_err()); +/// +/// let bytes = Cursor::new(b"\xfe\xfd\xaa\xbb\x00"); +/// assert!(CompactSizeMessage::zcash_deserialize(bytes).is_err()); +/// +/// let bytes = Cursor::new(b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00"); +/// assert!(CompactSizeMessage::zcash_deserialize(bytes).is_err()); +/// ``` +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct CompactSizeMessage( + /// The number of items in a Zcash message. + /// + /// This field is private to enforce the [`MAX_PROTOCOL_MESSAGE_LEN`] limit. + u32, +); + +/// An arbitrary CompactSize-encoded field. +/// Used for flags, arbitrary counts, and sizes that span multiple blocks. +/// +/// # Security +/// +/// Deserialized sizes must be validated before being used to allocate memory. +/// +/// Most allocation sizes should use [`CompactSizeMessage`], +/// because it is limited to [`MAX_PROTOCOL_MESSAGE_LEN`]. +/// +/// # Serialization Examples +/// +/// ``` +/// use zebra_chain::serialization::{CompactSize64, ZcashSerialize, MAX_PROTOCOL_MESSAGE_LEN}; +/// use std::convert::TryFrom; +/// +/// let size = CompactSize64::from(0x12); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\x12"); +/// +/// let size = CompactSize64::from(0xfd); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfd\xfd\x00"); +/// +/// let size = CompactSize64::from(0xaafd); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfd\xfd\xaa"); +/// +/// let max_size = u64::try_from(MAX_PROTOCOL_MESSAGE_LEN).unwrap(); +/// let size = CompactSize64::from(max_size); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfe\x00\x00\x20\x00"); +/// +/// let size = CompactSize64::from(max_size + 1); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfe\x01\x00\x20\x00"); +/// +/// let size = CompactSize64::from(0xbbaafd); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xfe\xfd\xaa\xbb\x00"); +/// +/// let size = CompactSize64::from(0x22ccbbaafd); +/// let buf = size.zcash_serialize_to_vec().unwrap(); +/// assert_eq!(buf, b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00"); +/// ``` +/// +/// # Deserialization Examples +/// +/// ``` +/// use zebra_chain::serialization::{CompactSize64, ZcashDeserializeInto, MAX_PROTOCOL_MESSAGE_LEN}; +/// use std::{convert::TryFrom, io::Cursor}; +/// +/// assert_eq!( +/// CompactSize64::from(0x12), +/// Cursor::new(b"\x12").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSize64::from(0xfd), +/// Cursor::new(b"\xfd\xfd\x00").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSize64::from(0xaafd), +/// Cursor::new(b"\xfd\xfd\xaa").zcash_deserialize_into().unwrap(), +/// ); +/// +/// let max_size = u64::try_from(MAX_PROTOCOL_MESSAGE_LEN).unwrap(); +/// assert_eq!( +/// CompactSize64::from(max_size), +/// Cursor::new(b"\xfe\x00\x00\x20\x00").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSize64::from(max_size + 1), +/// Cursor::new(b"\xfe\x01\x00\x20\x00").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSize64::from(0xbbaafd), +/// Cursor::new(b"\xfe\xfd\xaa\xbb\x00").zcash_deserialize_into().unwrap(), +/// ); +/// +/// assert_eq!( +/// CompactSize64::from(0x22ccbbaafd), +/// Cursor::new(b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00").zcash_deserialize_into().unwrap(), +/// ); +///``` +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +pub struct CompactSize64( + /// A numeric value. + /// + /// This field is private for consistency with [`CompactSizeMessage`]. + u64, +); + +// `CompactSizeMessage` are item counts, so we expect them to be used with `usize` +// `CompactSize64` are arbitrary integers, so we expect it to be used with `u64` +// +// We don't implement conversions between CompactSizeMessage and CompactSize64, +// because we want to distinguish fields with different numeric constraints. +// +// We don't implement From for u16 or u8, +// because we want all values to go through the same code paths. +// (And we don't want to accidentally truncate using `as`.) +// It would also make integer literal type inference fail. +// +// We don't implement `PartialEq` or `Ord` with integers, +// because it makes type inference fail. + +impl TryFrom for CompactSizeMessage { + type Error = SerializationError; + + #[inline] + fn try_from(size: usize) -> Result { + use SerializationError::Parse; + + let size: u32 = size.try_into()?; + + // # Security + // Defence-in-depth for memory DoS via preallocation. + if size + > MAX_PROTOCOL_MESSAGE_LEN + .try_into() + .expect("MAX_PROTOCOL_MESSAGE_LEN fits in u32") + { + // This could be invalid data from peers, so we return a parse error. + Err(Parse("CompactSize larger than protocol message limit"))?; + } + + Ok(CompactSizeMessage(size)) + } +} + +impl From for usize { + #[inline] + fn from(size: CompactSizeMessage) -> Self { + size.0.try_into().expect("u32 fits in usize") + } +} + +impl From for u64 { + #[inline] + fn from(size: CompactSize64) -> Self { + size.0 + } +} + +impl From for CompactSize64 { + #[inline] + fn from(size: u64) -> Self { + CompactSize64(size) + } +} + +impl ZcashSerialize for CompactSizeMessage { + /// Serialize a CompactSizeMessage into the Zcash consensus-critical format. + /// + /// # Panics + /// + /// If the value exceeds `MAX_PROTOCOL_MESSAGE_LEN`. + #[inline] + fn zcash_serialize(&self, writer: W) -> Result<(), std::io::Error> { + // # Security + // Defence-in-depth for memory DoS via preallocation. + // + // This is validated data inside Zebra, so we panic. + assert!( + self.0 + <= MAX_PROTOCOL_MESSAGE_LEN + .try_into() + .expect("usize fits in u64"), + "CompactSize larger than protocol message limit" + ); + + // Use the same serialization format as CompactSize64. + let size: u64 = self.0.into(); + CompactSize64(size).zcash_serialize(writer) + } +} + +impl ZcashDeserialize for CompactSizeMessage { + #[inline] + fn zcash_deserialize(reader: R) -> Result { + // Use the same deserialization format as CompactSize64. + let size: CompactSize64 = reader.zcash_deserialize_into()?; + + let size: usize = size.0.try_into()?; + size.try_into() + } +} + +impl ZcashSerialize for CompactSize64 { + #[inline] + fn zcash_serialize(&self, mut writer: W) -> Result<(), std::io::Error> { + let n = self.0; + match n { + 0x00..=0xfc => writer.write_u8(n as u8), + 0x00fd..=0xffff => { + writer.write_u8(0xfd)?; + writer.write_u16::(n as u16) + } + 0x0001_0000..=0xffff_ffff => { + writer.write_u8(0xfe)?; + writer.write_u32::(n as u32) + } + _ => { + writer.write_u8(0xff)?; + writer.write_u64::(n) + } + } + } +} + +impl ZcashDeserialize for CompactSize64 { + #[inline] + fn zcash_deserialize(mut reader: R) -> Result { + use SerializationError::Parse; + + let flag_byte = reader.read_u8()?; + let size = match flag_byte { + n @ 0x00..=0xfc => Ok(n as u64), + 0xfd => match reader.read_u16::()? { + n @ 0x00fd..=u16::MAX => Ok(n as u64), + _ => Err(Parse("non-canonical CompactSize")), + }, + 0xfe => match reader.read_u32::()? { + n @ 0x0001_0000..=u32::MAX => Ok(n as u64), + _ => Err(Parse("non-canonical CompactSize")), + }, + 0xff => match reader.read_u64::()? { + n @ 0x0000_0001_0000_0000..=u64::MAX => Ok(n), + _ => Err(Parse("non-canonical CompactSize")), + }, + }?; + + Ok(CompactSize64(size)) + } +} diff --git a/zebra-chain/src/serialization/error.rs b/zebra-chain/src/serialization/error.rs index dcbfb5c6ee9..5d42b333f94 100644 --- a/zebra-chain/src/serialization/error.rs +++ b/zebra-chain/src/serialization/error.rs @@ -26,7 +26,7 @@ pub enum SerializationError { TryFromSliceError(#[from] TryFromSliceError), /// The length of a vec is too large to convert to a usize (and thus, too large to allocate on this platform) - #[error("compactsize too large: {0}")] + #[error("CompactSize too large: {0}")] TryFromIntError(#[from] TryFromIntError), /// An error caused when validating a zatoshi `Amount` diff --git a/zebra-chain/src/serialization/read_zcash.rs b/zebra-chain/src/serialization/read_zcash.rs index 23ba352c024..6f5f7767632 100644 --- a/zebra-chain/src/serialization/read_zcash.rs +++ b/zebra-chain/src/serialization/read_zcash.rs @@ -1,6 +1,6 @@ -use std::io; use std::{ convert::TryInto, + io, net::{IpAddr, Ipv6Addr, SocketAddr}, }; @@ -67,15 +67,15 @@ pub trait ReadZcashExt: io::Read { n @ 0x00..=0xfc => Ok(n as u64), 0xfd => match self.read_u16::()? { n @ 0x0000_00fd..=0x0000_ffff => Ok(n as u64), - _ => Err(Parse("non-canonical compactsize")), + _ => Err(Parse("non-canonical CompactSize")), }, 0xfe => match self.read_u32::()? { n @ 0x0001_0000..=0xffff_ffff => Ok(n as u64), - _ => Err(Parse("non-canonical compactsize")), + _ => Err(Parse("non-canonical CompactSize")), }, 0xff => match self.read_u64::()? { n @ 0x1_0000_0000..=0xffff_ffff_ffff_ffff => Ok(n), - _ => Err(Parse("non-canonical compactsize")), + _ => Err(Parse("non-canonical CompactSize")), }, }?; @@ -86,7 +86,7 @@ pub trait ReadZcashExt: io::Read { .try_into() .expect("usize fits in u64") { - Err(Parse("compactsize larger than protocol message limit"))?; + Err(Parse("CompactSize larger than protocol message limit"))?; } Ok(size) diff --git a/zebra-chain/src/serialization/tests/prop.rs b/zebra-chain/src/serialization/tests/prop.rs index 3ff5d2a88b2..bd1a1def48a 100644 --- a/zebra-chain/src/serialization/tests/prop.rs +++ b/zebra-chain/src/serialization/tests/prop.rs @@ -14,7 +14,7 @@ proptest! { fn compactsize_write_then_read_round_trip(s in 0u64..0x2_0000u64) { zebra_test::init(); - // Maximum encoding size of a compactsize is 9 bytes. + // Maximum encoding size of a CompactSize is 9 bytes. let mut buf = [0u8; 8+1]; Cursor::new(&mut buf[..]).write_compactsize(s).unwrap(); let expect_s = Cursor::new(&buf[..]).read_compactsize().unwrap(); @@ -27,7 +27,7 @@ proptest! { // Only do the test if the bytes were valid. if let Ok(s) = Cursor::new(&bytes[..]).read_compactsize() { - // The compactsize encoding is variable-length, so we may not even + // The CompactSize encoding is variable-length, so we may not even // read all of the input bytes, and therefore we can't expect that // the encoding will reproduce bytes that were never read. Instead, // copy the input bytes, and overwrite them with the encoding of s, diff --git a/zebra-chain/src/serialization/write_zcash.rs b/zebra-chain/src/serialization/write_zcash.rs index 40013eaf644..4cb218e2064 100644 --- a/zebra-chain/src/serialization/write_zcash.rs +++ b/zebra-chain/src/serialization/write_zcash.rs @@ -1,12 +1,13 @@ -use std::io; use std::{ convert::TryInto, + io, net::{IpAddr, SocketAddr}, }; -use super::MAX_PROTOCOL_MESSAGE_LEN; use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; +use super::MAX_PROTOCOL_MESSAGE_LEN; + /// Extends [`Write`] with methods for writing Zcash/Bitcoin types. /// /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html @@ -51,12 +52,12 @@ pub trait WriteZcashExt: io::Write { // # Security // Defence-in-depth for memory DoS via preallocation. // - if n > MAX_PROTOCOL_MESSAGE_LEN - .try_into() - .expect("usize fits in u64") - { - panic!("compactsize larger than protocol message limit"); - } + assert!( + n <= MAX_PROTOCOL_MESSAGE_LEN + .try_into() + .expect("usize fits in u64"), + "CompactSize larger than protocol message limit" + ); match n { 0x0000_0000..=0x0000_00fc => self.write_u8(n as u8), diff --git a/zebra-chain/src/serialization/zcash_deserialize.rs b/zebra-chain/src/serialization/zcash_deserialize.rs index ac0e543b632..1e2552f1077 100644 --- a/zebra-chain/src/serialization/zcash_deserialize.rs +++ b/zebra-chain/src/serialization/zcash_deserialize.rs @@ -3,7 +3,7 @@ use std::{ io, }; -use super::{AtLeastOne, ReadZcashExt, SerializationError, MAX_PROTOCOL_MESSAGE_LEN}; +use super::{AtLeastOne, CompactSizeMessage, SerializationError, MAX_PROTOCOL_MESSAGE_LEN}; /// Consensus-critical serialization for Zcash. /// @@ -20,20 +20,20 @@ pub trait ZcashDeserialize: Sized { fn zcash_deserialize(reader: R) -> Result; } -/// Deserialize a `Vec`, where the number of items is set by a compactsize +/// Deserialize a `Vec`, where the number of items is set by a CompactSize /// prefix in the data. This is the most common format in Zcash. /// /// See `zcash_deserialize_external_count` for more details, and usage /// information. impl ZcashDeserialize for Vec { fn zcash_deserialize(mut reader: R) -> Result { - let len = reader.read_compactsize()?.try_into()?; - zcash_deserialize_external_count(len, reader) + let len: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?; + zcash_deserialize_external_count(len.into(), reader) } } /// Deserialize an `AtLeastOne` vector, where the number of items is set by a -/// compactsize prefix in the data. This is the most common format in Zcash. +/// CompactSize prefix in the data. This is the most common format in Zcash. impl ZcashDeserialize for AtLeastOne { fn zcash_deserialize(mut reader: R) -> Result { let v: Vec = (&mut reader).zcash_deserialize_into()?; @@ -48,16 +48,16 @@ impl ZcashDeserialize for AtLeastOne { fn zcash_deserialize(mut reader: R) -> Result { - let len = reader.read_compactsize()?.try_into()?; - zcash_deserialize_bytes_external_count(len, reader) + let len: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?; + zcash_deserialize_bytes_external_count(len.into(), reader) } } /// Deserialize a `Vec` containing `external_count` items. /// -/// In Zcash, most arrays are stored as a compactsize, followed by that number +/// In Zcash, most arrays are stored as a CompactSize, followed by that number /// of items of type `T`. But in `Transaction::V5`, some types are serialized as -/// multiple arrays in different locations, with a single compactsize before the +/// multiple arrays in different locations, with a single CompactSize before the /// first array. /// /// ## Usage @@ -65,7 +65,7 @@ impl ZcashDeserialize for Vec { /// Use `zcash_deserialize_external_count` when the array count is determined by /// other data, or a consensus rule. /// -/// Use `Vec::zcash_deserialize` for data that contains compactsize count, +/// Use `Vec::zcash_deserialize` for data that contains CompactSize count, /// followed by the data array. /// /// For example, when a single count applies to multiple arrays: @@ -159,7 +159,7 @@ pub trait TrustedPreallocate { /// The length of the longest valid `Vec` that can be received over the network /// -/// It takes 5 bytes to encode a compactsize representing any number netween 2^16 and (2^32 - 1) +/// It takes 5 bytes to encode a CompactSize representing any number netween 2^16 and (2^32 - 1) /// MAX_PROTOCOL_MESSAGE_LEN is ~2^21, so the largest Vec that can be received from an honest peer is /// (MAX_PROTOCOL_MESSAGE_LEN - 5); pub(crate) const MAX_U8_ALLOCATION: usize = MAX_PROTOCOL_MESSAGE_LEN - 5; diff --git a/zebra-chain/src/serialization/zcash_serialize.rs b/zebra-chain/src/serialization/zcash_serialize.rs index 7af6c4966e6..4fd17d77614 100644 --- a/zebra-chain/src/serialization/zcash_serialize.rs +++ b/zebra-chain/src/serialization/zcash_serialize.rs @@ -1,6 +1,11 @@ -use std::io; +use std::{convert::TryInto, io}; -use super::{AtLeastOne, WriteZcashExt}; +use super::{AtLeastOne, CompactSizeMessage}; + +/// The maximum length of a Zcash message, in bytes. +/// +/// This value is used to calculate safe preallocation limits for some types +pub const MAX_PROTOCOL_MESSAGE_LEN: usize = 2 * 1024 * 1024; /// Consensus-critical serialization for Zcash. /// @@ -52,18 +57,31 @@ impl std::io::Write for FakeWriter { } } -/// Serialize a `Vec` as a compactsize number of items, then the items. This is +/// Serialize a `Vec` as a CompactSize number of items, then the items. This is /// the most common format in Zcash. /// /// See `zcash_serialize_external_count` for more details, and usage information. impl ZcashSerialize for Vec { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_compactsize(self.len() as u64)?; + let len: CompactSizeMessage = self + .len() + .try_into() + .expect("len fits in MAX_PROTOCOL_MESSAGE_LEN"); + len.zcash_serialize(&mut writer)?; + zcash_serialize_external_count(self, writer) } } -/// Serialize a byte vector as a compactsize number of items, then the items. +/// Serialize an `AtLeastOne` vector as a CompactSize number of items, then the +/// items. This is the most common format in Zcash. +impl ZcashSerialize for AtLeastOne { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + self.as_vec().zcash_serialize(&mut writer) + } +} + +/// Serialize a byte vector as a CompactSize number of items, then the items. /// /// # Correctness /// @@ -75,24 +93,28 @@ impl ZcashSerialize for Vec { // we specifically want to serialize `Vec`s here, rather than generic slices #[allow(clippy::ptr_arg)] pub fn zcash_serialize_bytes(vec: &Vec, mut writer: W) -> Result<(), io::Error> { - writer.write_compactsize(vec.len() as u64)?; + let len: CompactSizeMessage = vec + .len() + .try_into() + .expect("len fits in MAX_PROTOCOL_MESSAGE_LEN"); + len.zcash_serialize(&mut writer)?; + zcash_serialize_bytes_external_count(vec, writer) } -/// Serialize an `AtLeastOne` vector as a compactsize number of items, then the -/// items. This is the most common format in Zcash. -impl ZcashSerialize for AtLeastOne { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - self.as_vec().zcash_serialize(&mut writer) - } +/// Serialize an empty list of items, by writing a zero CompactSize length. +/// (And no items.) +pub fn zcash_serialize_empty_list(writer: W) -> Result<(), io::Error> { + let len: CompactSizeMessage = 0.try_into().expect("zero fits in MAX_PROTOCOL_MESSAGE_LEN"); + len.zcash_serialize(writer) } /// Serialize a typed `Vec` **without** writing the number of items as a -/// compactsize. +/// CompactSize. /// -/// In Zcash, most arrays are stored as a compactsize, followed by that number +/// In Zcash, most arrays are stored as a CompactSize, followed by that number /// of items of type `T`. But in `Transaction::V5`, some types are serialized as -/// multiple arrays in different locations, with a single compactsize before the +/// multiple arrays in different locations, with a single CompactSize before the /// first array. /// /// ## Usage @@ -100,7 +122,7 @@ impl ZcashSerialize for AtLeastOne { /// Use `zcash_serialize_external_count` when the array count is determined by /// other data, or a consensus rule. /// -/// Use `Vec::zcash_serialize` for data that contains compactsize count, +/// Use `Vec::zcash_serialize` for data that contains CompactSize count, /// followed by the data array. /// /// For example, when a single count applies to multiple arrays: @@ -125,7 +147,7 @@ pub fn zcash_serialize_external_count( } /// Serialize a raw byte `Vec` **without** writing the number of items as a -/// compactsize. +/// CompactSize. /// /// This is a convenience alias for `writer.write_all(&vec)`. // @@ -152,8 +174,3 @@ impl ZcashSerialize for String { self.as_str().zcash_serialize(&mut writer) } } - -/// The maximum length of a Zcash message, in bytes. -/// -/// This value is used to calculate safe preallocation limits for some types -pub const MAX_PROTOCOL_MESSAGE_LEN: usize = 2 * 1024 * 1024;