diff --git a/zebra-chain/src/serialization.rs b/zebra-chain/src/serialization.rs index d8e67d3b..493af7cf 100644 --- a/zebra-chain/src/serialization.rs +++ b/zebra-chain/src/serialization.rs @@ -6,370 +6,17 @@ //! `ReadZcashExt`, extension traits for `io::Read` and `io::Write` with utility functions //! for reading and writing data (e.g., the Bitcoin variable-integer format). -use std::io; -use std::net::{IpAddr, SocketAddr}; +mod error; +mod read_zcash; +mod write_zcash; +mod zcash_deserialize; +mod zcash_serialize; -use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; -use thiserror::Error; - -/// A serialization error. -// XXX refine error types -- better to use boxed errors? -#[derive(Error, Debug)] -pub enum SerializationError { - /// An io error that prevented deserialization - #[error("unable to deserialize type")] - Io(#[from] io::Error), - /// The data to be deserialized was malformed. - // XXX refine errors - #[error("parse error: {0}")] - Parse(&'static str), - /// An error caused when validating a zatoshi `Amount` - #[error("input couldn't be parsed as a zatoshi `Amount`")] - Amount { - /// The source error indicating how the num failed to validate - #[from] - source: crate::types::amount::Error, - }, -} - -/// Consensus-critical serialization for Zcash. -/// -/// This trait provides a generic serialization for consensus-critical -/// formats, such as network messages, transactions, blocks, etc. It is intended -/// for use only in consensus-critical contexts; in other contexts, such as -/// internal storage, it would be preferable to use Serde. -pub trait ZcashSerialize: Sized { - /// Write `self` to the given `writer` using the canonical format. - /// - /// This function has a `zcash_` prefix to alert the reader that the - /// serialization in use is consensus-critical serialization, rather than - /// some other kind of serialization. - /// - /// Notice that the error type is [`std::io::Error`]; this indicates that - /// serialization MUST be infallible up to errors in the underlying writer. - /// In other words, any type implementing `ZcashSerialize` must make illegal - /// states unrepresentable. - fn zcash_serialize(&self, writer: W) -> Result<(), io::Error>; - - /// Helper function to construct a vec to serialize the current struct into - fn zcash_serialize_to_vec(&self) -> Result, io::Error> { - let mut data = Vec::new(); - self.zcash_serialize(&mut data)?; - Ok(data) - } -} - -/// Consensus-critical serialization for Zcash. -/// -/// This trait provides a generic deserialization for consensus-critical -/// formats, such as network messages, transactions, blocks, etc. It is intended -/// for use only in consensus-critical contexts; in other contexts, such as -/// internal storage, it would be preferable to use Serde. -pub trait ZcashDeserialize: Sized { - /// Try to read `self` from the given `reader`. - /// - /// This function has a `zcash_` prefix to alert the reader that the - /// serialization in use is consensus-critical serialization, rather than - /// some other kind of serialization. - fn zcash_deserialize(reader: R) -> Result; -} - -impl ZcashSerialize for Vec { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_compactsize(self.len() as u64)?; - for x in self { - x.zcash_serialize(&mut writer)?; - } - Ok(()) - } -} - -impl ZcashDeserialize for Vec { - fn zcash_deserialize(mut reader: R) -> Result { - let len = reader.read_compactsize()?; - // We're given len, so we could preallocate. But blindly preallocating - // without a size bound can allow DOS attacks, and there's no way to - // pass a size bound in a ZcashDeserialize impl, so instead we allocate - // as we read from the reader. (The maximum block and transaction sizes - // limit the eventual size of these allocations.) - let mut vec = Vec::new(); - for _ in 0..len { - vec.push(T::zcash_deserialize(&mut reader)?); - } - Ok(vec) - } -} - -/// Extends [`Write`] with methods for writing Zcash/Bitcoin types. -/// -/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html -pub trait WriteZcashExt: io::Write { - /// Writes a `u64` using the Bitcoin `CompactSize` encoding. - /// - /// # Examples - /// - /// ```rust - /// use zebra_chain::serialization::WriteZcashExt; - /// - /// let mut buf = Vec::new(); - /// buf.write_compactsize(0x12).unwrap(); - /// assert_eq!(buf, b"\x12"); - /// - /// let mut buf = Vec::new(); - /// buf.write_compactsize(0xfd).unwrap(); - /// assert_eq!(buf, b"\xfd\xfd\x00"); - /// - /// let mut buf = Vec::new(); - /// buf.write_compactsize(0xaafd).unwrap(); - /// assert_eq!(buf, b"\xfd\xfd\xaa"); - /// - /// let mut buf = Vec::new(); - /// buf.write_compactsize(0xbbaafd).unwrap(); - /// assert_eq!(buf, b"\xfe\xfd\xaa\xbb\x00"); - /// - /// let mut buf = Vec::new(); - /// buf.write_compactsize(0x22ccbbaafd).unwrap(); - /// assert_eq!(buf, b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00"); - /// ``` - #[inline] - fn write_compactsize(&mut self, n: u64) -> io::Result<()> { - match n { - 0x0000_0000..=0x0000_00fc => self.write_u8(n as u8), - 0x0000_00fd..=0x0000_ffff => { - self.write_u8(0xfd)?; - self.write_u16::(n as u16) - } - 0x0001_0000..=0xffff_ffff => { - self.write_u8(0xfe)?; - self.write_u32::(n as u32) - } - _ => { - self.write_u8(0xff)?; - self.write_u64::(n) - } - } - } - - /// Write an `IpAddr` in Bitcoin format. - #[inline] - fn write_ip_addr(&mut self, addr: IpAddr) -> io::Result<()> { - use std::net::IpAddr::*; - let v6_addr = match addr { - V4(ref v4) => v4.to_ipv6_mapped(), - V6(v6) => v6, - }; - self.write_all(&v6_addr.octets()) - } - - /// Write a `SocketAddr` in Bitcoin format. - #[inline] - fn write_socket_addr(&mut self, addr: SocketAddr) -> io::Result<()> { - self.write_ip_addr(addr.ip())?; - self.write_u16::(addr.port()) - } - - /// Write a string in Bitcoin format. - #[inline] - fn write_string(&mut self, string: &str) -> io::Result<()> { - self.write_compactsize(string.len() as u64)?; - self.write_all(string.as_bytes()) - } - - /// Convenience method to write exactly 32 u8's. - #[inline] - fn write_32_bytes(&mut self, bytes: &[u8; 32]) -> io::Result<()> { - self.write_all(bytes) - } - - /// Convenience method to write exactly 64 u8's. - #[inline] - fn write_64_bytes(&mut self, bytes: &[u8; 64]) -> io::Result<()> { - self.write_all(bytes) - } -} - -/// Mark all types implementing `Write` as implementing the extension. -impl WriteZcashExt for W {} - -/// Extends [`Read`] with methods for writing Zcash/Bitcoin types. -/// -/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html -pub trait ReadZcashExt: io::Read { - /// Reads a `u64` using the Bitcoin `CompactSize` encoding. - /// - /// # Examples - /// - /// ```rust - /// use zebra_chain::serialization::ReadZcashExt; - /// - /// use std::io::Cursor; - /// assert_eq!( - /// 0x12, - /// Cursor::new(b"\x12") - /// .read_compactsize().unwrap() - /// ); - /// assert_eq!( - /// 0xfd, - /// Cursor::new(b"\xfd\xfd\x00") - /// .read_compactsize().unwrap() - /// ); - /// assert_eq!( - /// 0xaafd, - /// Cursor::new(b"\xfd\xfd\xaa") - /// .read_compactsize().unwrap() - /// ); - /// assert_eq!( - /// 0xbbaafd, - /// Cursor::new(b"\xfe\xfd\xaa\xbb\x00") - /// .read_compactsize().unwrap() - /// ); - /// assert_eq!( - /// 0x22ccbbaafd, - /// Cursor::new(b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00") - /// .read_compactsize().unwrap() - /// ); - /// ``` - #[inline] - fn read_compactsize(&mut self) -> Result { - use SerializationError::Parse; - let flag_byte = self.read_u8()?; - match flag_byte { - 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")), - }, - 0xfe => match self.read_u32::()? { - n @ 0x0001_0000..=0xffff_ffff => Ok(n as u64), - _ => 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")), - }, - } - } - - /// Read an IP address in Bitcoin format. - #[inline] - fn read_ip_addr(&mut self) -> io::Result { - use std::net::{IpAddr::*, Ipv6Addr}; - - let mut octets = [0u8; 16]; - self.read_exact(&mut octets)?; - let v6_addr = Ipv6Addr::from(octets); - - match v6_addr.to_ipv4() { - Some(v4_addr) => Ok(V4(v4_addr)), - None => Ok(V6(v6_addr)), - } - } - - /// Read a Bitcoin-encoded `SocketAddr`. - #[inline] - fn read_socket_addr(&mut self) -> io::Result { - let ip_addr = self.read_ip_addr()?; - let port = self.read_u16::()?; - Ok(SocketAddr::new(ip_addr, port)) - } - - /// Read a Bitcoin-encoded UTF-8 string. - #[inline] - fn read_string(&mut self) -> Result { - let len = self.read_compactsize()?; - let mut buf = vec![0; len as usize]; - self.read_exact(&mut buf)?; - String::from_utf8(buf).map_err(|_| SerializationError::Parse("invalid utf-8")) - } - - /// Convenience method to read a `[u8; 4]`. - #[inline] - fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> { - let mut bytes = [0; 4]; - self.read_exact(&mut bytes)?; - Ok(bytes) - } - - /// Convenience method to read a `[u8; 12]`. - #[inline] - fn read_12_bytes(&mut self) -> io::Result<[u8; 12]> { - let mut bytes = [0; 12]; - self.read_exact(&mut bytes)?; - Ok(bytes) - } - - /// Convenience method to read a `[u8; 32]`. - #[inline] - fn read_32_bytes(&mut self) -> io::Result<[u8; 32]> { - let mut bytes = [0; 32]; - self.read_exact(&mut bytes)?; - Ok(bytes) - } - - /// Convenience method to read a `[u8; 64]`. - #[inline] - fn read_64_bytes(&mut self) -> io::Result<[u8; 64]> { - let mut bytes = [0; 64]; - self.read_exact(&mut bytes)?; - Ok(bytes) - } -} - -/// Mark all types implementing `Read` as implementing the extension. -impl ReadZcashExt for R {} - -/// Helper for deserializing more succinctly via type inference -pub trait ZcashDeserializeInto { - /// Deserialize based on type inference - fn zcash_deserialize_into(self) -> Result - where - T: ZcashDeserialize; -} - -impl ZcashDeserializeInto for R { - fn zcash_deserialize_into(self) -> Result - where - T: ZcashDeserialize, - { - T::zcash_deserialize(self) - } -} +pub use error::SerializationError; +pub use read_zcash::ReadZcashExt; +pub use write_zcash::WriteZcashExt; +pub use zcash_deserialize::{ZcashDeserialize, ZcashDeserializeInto}; +pub use zcash_serialize::ZcashSerialize; #[cfg(test)] -#[allow(clippy::unnecessary_operation)] -mod tests { - use super::*; - use proptest::prelude::*; - - use std::io::Cursor; - - proptest! { - // The tests below are cheap so we can run them a lot. - #![proptest_config(ProptestConfig::with_cases(100_000))] - - #[test] - fn compactsize_write_then_read_round_trip(s in 0u64..0x2_0000u64) { - // 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(); - prop_assert_eq!(s, expect_s); - } - - #[test] - fn compactsize_read_then_write_round_trip(bytes in prop::array::uniform9(0u8..)) { - // 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 - // 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, - // so that if the encoding is different, we'll catch it on the part - // that's written. - let mut expect_bytes = bytes; - Cursor::new(&mut expect_bytes[..]).write_compactsize(s).unwrap(); - prop_assert_eq!(bytes, expect_bytes); - } - } - } -} +mod proptests; diff --git a/zebra-chain/src/serialization/error.rs b/zebra-chain/src/serialization/error.rs new file mode 100644 index 00000000..d744bfec --- /dev/null +++ b/zebra-chain/src/serialization/error.rs @@ -0,0 +1,23 @@ +use std::io; + +use thiserror::Error; + +/// A serialization error. +// XXX refine error types -- better to use boxed errors? +#[derive(Error, Debug)] +pub enum SerializationError { + /// An io error that prevented deserialization + #[error("unable to deserialize type")] + Io(#[from] io::Error), + /// The data to be deserialized was malformed. + // XXX refine errors + #[error("parse error: {0}")] + Parse(&'static str), + /// An error caused when validating a zatoshi `Amount` + #[error("input couldn't be parsed as a zatoshi `Amount`")] + Amount { + /// The source error indicating how the num failed to validate + #[from] + source: crate::types::amount::Error, + }, +} diff --git a/zebra-chain/src/serialization/proptests.rs b/zebra-chain/src/serialization/proptests.rs new file mode 100644 index 00000000..dda4b763 --- /dev/null +++ b/zebra-chain/src/serialization/proptests.rs @@ -0,0 +1,36 @@ +//! Property-based tests for basic serialization primitives. + +use super::*; +use proptest::prelude::*; + +use std::io::Cursor; + +proptest! { + // The tests below are cheap so we can run them a lot. + #![proptest_config(ProptestConfig::with_cases(100_000))] + + #[test] + fn compactsize_write_then_read_round_trip(s in 0u64..0x2_0000u64) { + // 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(); + prop_assert_eq!(s, expect_s); + } + + #[test] + fn compactsize_read_then_write_round_trip(bytes in prop::array::uniform9(0u8..)) { + // 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 + // 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, + // so that if the encoding is different, we'll catch it on the part + // that's written. + let mut expect_bytes = bytes; + Cursor::new(&mut expect_bytes[..]).write_compactsize(s).unwrap(); + prop_assert_eq!(bytes, expect_bytes); + } + } +} diff --git a/zebra-chain/src/serialization/read_zcash.rs b/zebra-chain/src/serialization/read_zcash.rs new file mode 100644 index 00000000..8a06b14a --- /dev/null +++ b/zebra-chain/src/serialization/read_zcash.rs @@ -0,0 +1,133 @@ +use std::io; +use std::net::{IpAddr, SocketAddr}; + +use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; + +use super::SerializationError; + +/// Extends [`Read`] with methods for writing Zcash/Bitcoin types. +/// +/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html +pub trait ReadZcashExt: io::Read { + /// Reads a `u64` using the Bitcoin `CompactSize` encoding. + /// + /// # Examples + /// + /// ```rust + /// use zebra_chain::serialization::ReadZcashExt; + /// + /// use std::io::Cursor; + /// assert_eq!( + /// 0x12, + /// Cursor::new(b"\x12") + /// .read_compactsize().unwrap() + /// ); + /// assert_eq!( + /// 0xfd, + /// Cursor::new(b"\xfd\xfd\x00") + /// .read_compactsize().unwrap() + /// ); + /// assert_eq!( + /// 0xaafd, + /// Cursor::new(b"\xfd\xfd\xaa") + /// .read_compactsize().unwrap() + /// ); + /// assert_eq!( + /// 0xbbaafd, + /// Cursor::new(b"\xfe\xfd\xaa\xbb\x00") + /// .read_compactsize().unwrap() + /// ); + /// assert_eq!( + /// 0x22ccbbaafd, + /// Cursor::new(b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00") + /// .read_compactsize().unwrap() + /// ); + /// ``` + #[inline] + fn read_compactsize(&mut self) -> Result { + use SerializationError::Parse; + let flag_byte = self.read_u8()?; + match flag_byte { + 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")), + }, + 0xfe => match self.read_u32::()? { + n @ 0x0001_0000..=0xffff_ffff => Ok(n as u64), + _ => 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")), + }, + } + } + + /// Read an IP address in Bitcoin format. + #[inline] + fn read_ip_addr(&mut self) -> io::Result { + use std::net::{IpAddr::*, Ipv6Addr}; + + let mut octets = [0u8; 16]; + self.read_exact(&mut octets)?; + let v6_addr = Ipv6Addr::from(octets); + + match v6_addr.to_ipv4() { + Some(v4_addr) => Ok(V4(v4_addr)), + None => Ok(V6(v6_addr)), + } + } + + /// Read a Bitcoin-encoded `SocketAddr`. + #[inline] + fn read_socket_addr(&mut self) -> io::Result { + let ip_addr = self.read_ip_addr()?; + let port = self.read_u16::()?; + Ok(SocketAddr::new(ip_addr, port)) + } + + /// Read a Bitcoin-encoded UTF-8 string. + #[inline] + fn read_string(&mut self) -> Result { + let len = self.read_compactsize()?; + let mut buf = vec![0; len as usize]; + self.read_exact(&mut buf)?; + String::from_utf8(buf).map_err(|_| SerializationError::Parse("invalid utf-8")) + } + + /// Convenience method to read a `[u8; 4]`. + #[inline] + fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> { + let mut bytes = [0; 4]; + self.read_exact(&mut bytes)?; + Ok(bytes) + } + + /// Convenience method to read a `[u8; 12]`. + #[inline] + fn read_12_bytes(&mut self) -> io::Result<[u8; 12]> { + let mut bytes = [0; 12]; + self.read_exact(&mut bytes)?; + Ok(bytes) + } + + /// Convenience method to read a `[u8; 32]`. + #[inline] + fn read_32_bytes(&mut self) -> io::Result<[u8; 32]> { + let mut bytes = [0; 32]; + self.read_exact(&mut bytes)?; + Ok(bytes) + } + + /// Convenience method to read a `[u8; 64]`. + #[inline] + fn read_64_bytes(&mut self) -> io::Result<[u8; 64]> { + let mut bytes = [0; 64]; + self.read_exact(&mut bytes)?; + Ok(bytes) + } +} + +/// Mark all types implementing `Read` as implementing the extension. +impl ReadZcashExt for R {} diff --git a/zebra-chain/src/serialization/write_zcash.rs b/zebra-chain/src/serialization/write_zcash.rs new file mode 100644 index 00000000..ea6c7158 --- /dev/null +++ b/zebra-chain/src/serialization/write_zcash.rs @@ -0,0 +1,95 @@ +use std::io; +use std::net::{IpAddr, SocketAddr}; + +use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; + +/// Extends [`Write`] with methods for writing Zcash/Bitcoin types. +/// +/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html +pub trait WriteZcashExt: io::Write { + /// Writes a `u64` using the Bitcoin `CompactSize` encoding. + /// + /// # Examples + /// + /// ```rust + /// use zebra_chain::serialization::WriteZcashExt; + /// + /// let mut buf = Vec::new(); + /// buf.write_compactsize(0x12).unwrap(); + /// assert_eq!(buf, b"\x12"); + /// + /// let mut buf = Vec::new(); + /// buf.write_compactsize(0xfd).unwrap(); + /// assert_eq!(buf, b"\xfd\xfd\x00"); + /// + /// let mut buf = Vec::new(); + /// buf.write_compactsize(0xaafd).unwrap(); + /// assert_eq!(buf, b"\xfd\xfd\xaa"); + /// + /// let mut buf = Vec::new(); + /// buf.write_compactsize(0xbbaafd).unwrap(); + /// assert_eq!(buf, b"\xfe\xfd\xaa\xbb\x00"); + /// + /// let mut buf = Vec::new(); + /// buf.write_compactsize(0x22ccbbaafd).unwrap(); + /// assert_eq!(buf, b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00"); + /// ``` + #[inline] + fn write_compactsize(&mut self, n: u64) -> io::Result<()> { + match n { + 0x0000_0000..=0x0000_00fc => self.write_u8(n as u8), + 0x0000_00fd..=0x0000_ffff => { + self.write_u8(0xfd)?; + self.write_u16::(n as u16) + } + 0x0001_0000..=0xffff_ffff => { + self.write_u8(0xfe)?; + self.write_u32::(n as u32) + } + _ => { + self.write_u8(0xff)?; + self.write_u64::(n) + } + } + } + + /// Write an `IpAddr` in Bitcoin format. + #[inline] + fn write_ip_addr(&mut self, addr: IpAddr) -> io::Result<()> { + use std::net::IpAddr::*; + let v6_addr = match addr { + V4(ref v4) => v4.to_ipv6_mapped(), + V6(v6) => v6, + }; + self.write_all(&v6_addr.octets()) + } + + /// Write a `SocketAddr` in Bitcoin format. + #[inline] + fn write_socket_addr(&mut self, addr: SocketAddr) -> io::Result<()> { + self.write_ip_addr(addr.ip())?; + self.write_u16::(addr.port()) + } + + /// Write a string in Bitcoin format. + #[inline] + fn write_string(&mut self, string: &str) -> io::Result<()> { + self.write_compactsize(string.len() as u64)?; + self.write_all(string.as_bytes()) + } + + /// Convenience method to write exactly 32 u8's. + #[inline] + fn write_32_bytes(&mut self, bytes: &[u8; 32]) -> io::Result<()> { + self.write_all(bytes) + } + + /// Convenience method to write exactly 64 u8's. + #[inline] + fn write_64_bytes(&mut self, bytes: &[u8; 64]) -> io::Result<()> { + self.write_all(bytes) + } +} + +/// Mark all types implementing `Write` as implementing the extension. +impl WriteZcashExt for W {} diff --git a/zebra-chain/src/serialization/zcash_deserialize.rs b/zebra-chain/src/serialization/zcash_deserialize.rs new file mode 100644 index 00000000..a7ac07d7 --- /dev/null +++ b/zebra-chain/src/serialization/zcash_deserialize.rs @@ -0,0 +1,51 @@ +use std::io; + +use super::{ReadZcashExt, SerializationError}; + +/// Consensus-critical serialization for Zcash. +/// +/// This trait provides a generic deserialization for consensus-critical +/// formats, such as network messages, transactions, blocks, etc. It is intended +/// for use only in consensus-critical contexts; in other contexts, such as +/// internal storage, it would be preferable to use Serde. +pub trait ZcashDeserialize: Sized { + /// Try to read `self` from the given `reader`. + /// + /// This function has a `zcash_` prefix to alert the reader that the + /// serialization in use is consensus-critical serialization, rather than + /// some other kind of serialization. + fn zcash_deserialize(reader: R) -> Result; +} + +impl ZcashDeserialize for Vec { + fn zcash_deserialize(mut reader: R) -> Result { + let len = reader.read_compactsize()?; + // We're given len, so we could preallocate. But blindly preallocating + // without a size bound can allow DOS attacks, and there's no way to + // pass a size bound in a ZcashDeserialize impl, so instead we allocate + // as we read from the reader. (The maximum block and transaction sizes + // limit the eventual size of these allocations.) + let mut vec = Vec::new(); + for _ in 0..len { + vec.push(T::zcash_deserialize(&mut reader)?); + } + Ok(vec) + } +} + +/// Helper for deserializing more succinctly via type inference +pub trait ZcashDeserializeInto { + /// Deserialize based on type inference + fn zcash_deserialize_into(self) -> Result + where + T: ZcashDeserialize; +} + +impl ZcashDeserializeInto for R { + fn zcash_deserialize_into(self) -> Result + where + T: ZcashDeserialize, + { + T::zcash_deserialize(self) + } +} diff --git a/zebra-chain/src/serialization/zcash_serialize.rs b/zebra-chain/src/serialization/zcash_serialize.rs new file mode 100644 index 00000000..1e66bb36 --- /dev/null +++ b/zebra-chain/src/serialization/zcash_serialize.rs @@ -0,0 +1,40 @@ +use std::io; + +use super::WriteZcashExt; + +/// Consensus-critical serialization for Zcash. +/// +/// This trait provides a generic serialization for consensus-critical +/// formats, such as network messages, transactions, blocks, etc. It is intended +/// for use only in consensus-critical contexts; in other contexts, such as +/// internal storage, it would be preferable to use Serde. +pub trait ZcashSerialize: Sized { + /// Write `self` to the given `writer` using the canonical format. + /// + /// This function has a `zcash_` prefix to alert the reader that the + /// serialization in use is consensus-critical serialization, rather than + /// some other kind of serialization. + /// + /// Notice that the error type is [`std::io::Error`]; this indicates that + /// serialization MUST be infallible up to errors in the underlying writer. + /// In other words, any type implementing `ZcashSerialize` must make illegal + /// states unrepresentable. + fn zcash_serialize(&self, writer: W) -> Result<(), io::Error>; + + /// Helper function to construct a vec to serialize the current struct into + fn zcash_serialize_to_vec(&self) -> Result, io::Error> { + let mut data = Vec::new(); + self.zcash_serialize(&mut data)?; + Ok(data) + } +} + +impl ZcashSerialize for Vec { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_compactsize(self.len() as u64)?; + for x in self { + x.zcash_serialize(&mut writer)?; + } + Ok(()) + } +}