2019-09-15 06:37:16 -07:00
|
|
|
//! Serialization for Zcash.
|
|
|
|
//!
|
|
|
|
//! This module contains four traits: `ZcashSerialize` and `ZcashDeserialize`,
|
|
|
|
//! analogs of the Serde `Serialize` and `Deserialize` traits but intended for
|
|
|
|
//! consensus-critical Zcash serialization formats, and `WriteZcashExt` and
|
|
|
|
//! `ReadZcashExt`, extension traits for `io::Read` and `io::Write` with utility functions
|
|
|
|
//! for reading and writing data (e.g., the Bitcoin variable-integer format).
|
2019-09-14 07:00:36 -07:00
|
|
|
|
|
|
|
use std::io;
|
2019-09-15 06:37:16 -07:00
|
|
|
use std::net::{IpAddr, SocketAddr};
|
2019-09-14 07:00:36 -07:00
|
|
|
|
2019-09-15 06:37:16 -07:00
|
|
|
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
|
2019-10-15 20:38:26 -07:00
|
|
|
use thiserror::Error;
|
2019-09-14 07:00:36 -07:00
|
|
|
|
|
|
|
/// A serialization error.
|
|
|
|
// XXX refine error types -- better to use boxed errors?
|
2019-10-15 20:38:26 -07:00
|
|
|
#[derive(Error, Debug)]
|
2019-09-14 07:00:36 -07:00
|
|
|
pub enum SerializationError {
|
|
|
|
/// An underlying IO error.
|
2019-10-15 20:38:26 -07:00
|
|
|
#[error("io error")]
|
|
|
|
Io(#[from] io::Error),
|
2019-09-14 07:00:36 -07:00
|
|
|
/// The data to be deserialized was malformed.
|
2019-09-15 06:37:16 -07:00
|
|
|
// XXX refine errors
|
2019-10-15 20:38:26 -07:00
|
|
|
#[error("parse error: {0}")]
|
|
|
|
Parse(&'static str),
|
2019-09-14 07:00:36 -07:00
|
|
|
}
|
|
|
|
|
2019-09-15 06:37:16 -07:00
|
|
|
/// 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.
|
|
|
|
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), 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<R: io::Read>(reader: R) -> Result<Self, SerializationError>;
|
|
|
|
}
|
|
|
|
|
2019-09-14 07:00:36 -07:00
|
|
|
/// 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
|
2019-09-15 06:37:16 -07:00
|
|
|
/// use zebra_chain::serialization::WriteZcashExt;
|
2019-09-14 07:00:36 -07:00
|
|
|
///
|
|
|
|
/// 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..=0x0001_0000 => {
|
|
|
|
self.write_u8(0xfd)?;
|
|
|
|
self.write_u16::<LittleEndian>(n as u16)
|
|
|
|
}
|
|
|
|
0x0001_0000..=0xffff_ffff => {
|
|
|
|
self.write_u8(0xfe)?;
|
|
|
|
self.write_u32::<LittleEndian>(n as u32)
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
self.write_u8(0xff)?;
|
|
|
|
self.write_u64::<LittleEndian>(n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-15 06:37:16 -07:00
|
|
|
|
|
|
|
/// 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::<BigEndian>(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())
|
|
|
|
}
|
2019-09-14 07:00:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Mark all types implementing `Write` as implementing the extension.
|
|
|
|
impl<W: io::Write + ?Sized> 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
|
2019-09-15 06:37:16 -07:00
|
|
|
/// use zebra_chain::serialization::ReadZcashExt;
|
2019-09-14 07:00:36 -07:00
|
|
|
///
|
|
|
|
/// 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]
|
2019-09-15 06:37:16 -07:00
|
|
|
fn read_compactsize(&mut self) -> io::Result<u64> {
|
2019-09-14 07:00:36 -07:00
|
|
|
let flag_byte = self.read_u8()?;
|
|
|
|
match flag_byte {
|
2019-09-15 06:37:16 -07:00
|
|
|
0xff => self.read_u64::<LittleEndian>(),
|
2019-09-14 07:00:36 -07:00
|
|
|
0xfe => Ok(self.read_u32::<LittleEndian>()? as u64),
|
|
|
|
0xfd => Ok(self.read_u16::<LittleEndian>()? as u64),
|
|
|
|
n => Ok(n as u64),
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 08:57:08 -07:00
|
|
|
|
2019-09-15 06:37:16 -07:00
|
|
|
/// Read an IP address in Bitcoin format.
|
|
|
|
#[inline]
|
|
|
|
fn read_ip_addr(&mut self) -> io::Result<IpAddr> {
|
2019-09-14 09:31:45 -07:00
|
|
|
use std::net::{IpAddr::*, Ipv6Addr};
|
|
|
|
|
|
|
|
let mut octets = [0u8; 16];
|
2019-09-15 06:37:16 -07:00
|
|
|
self.read_exact(&mut octets)?;
|
2019-09-25 13:33:16 -07:00
|
|
|
let v6_addr = Ipv6Addr::from(octets);
|
2019-09-14 09:31:45 -07:00
|
|
|
|
|
|
|
match v6_addr.to_ipv4() {
|
|
|
|
Some(v4_addr) => Ok(V4(v4_addr)),
|
|
|
|
None => Ok(V6(v6_addr)),
|
|
|
|
}
|
2019-09-14 08:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-09-15 06:37:16 -07:00
|
|
|
/// Read a Bitcoin-encoded `SocketAddr`.
|
|
|
|
#[inline]
|
|
|
|
fn read_socket_addr(&mut self) -> io::Result<SocketAddr> {
|
|
|
|
let ip_addr = self.read_ip_addr()?;
|
|
|
|
let port = self.read_u16::<BigEndian>()?;
|
|
|
|
Ok(SocketAddr::new(ip_addr, port))
|
2019-09-14 08:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-09-15 06:37:16 -07:00
|
|
|
/// Read a Bitcoin-encoded UTF-8 string.
|
|
|
|
#[inline]
|
|
|
|
fn read_string(&mut self) -> io::Result<String> {
|
|
|
|
let len = self.read_compactsize()?;
|
2019-09-14 09:32:14 -07:00
|
|
|
let mut buf = vec![0; len as usize];
|
2019-09-15 06:37:16 -07:00
|
|
|
self.read_exact(&mut buf)?;
|
|
|
|
String::from_utf8(buf).map_err(|_| io::ErrorKind::InvalidData.into())
|
2019-09-14 08:57:08 -07:00
|
|
|
}
|
2019-09-19 06:30:20 -07:00
|
|
|
|
|
|
|
/// 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)
|
|
|
|
}
|
2019-09-25 14:05:30 -07:00
|
|
|
|
|
|
|
/// 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)
|
|
|
|
}
|
2019-10-08 15:21:53 -07:00
|
|
|
|
2019-10-09 19:12:03 -07:00
|
|
|
/// Convenience method to read a `Vec<T>` with a leading count in
|
|
|
|
/// a safer manner.
|
|
|
|
///
|
|
|
|
/// This method preallocates a buffer, performing a single
|
|
|
|
/// allocation in the honest case. It's possible for someone to
|
|
|
|
/// send a short message with a large count field, so if we
|
|
|
|
/// naively trust the count field we could be tricked into
|
|
|
|
/// preallocating a large buffer. Instead, we rely on the passed
|
|
|
|
/// maximum count for a valid message and select the min of the
|
|
|
|
/// two values.
|
2019-10-08 15:21:53 -07:00
|
|
|
#[inline]
|
|
|
|
fn read_list<T: ZcashDeserialize>(
|
|
|
|
&mut self,
|
|
|
|
max_count: usize,
|
|
|
|
) -> Result<Vec<T>, SerializationError> {
|
|
|
|
// This prevents the inferred type for zcash_deserialize from
|
|
|
|
// taking ownership of &mut self. This wouldn't really be an
|
|
|
|
// issue if the target impl's `Copy`, but we need to own it.
|
|
|
|
let mut self2 = self;
|
|
|
|
|
|
|
|
let count = self2.read_compactsize()? as usize;
|
|
|
|
|
|
|
|
let mut items = Vec::with_capacity(std::cmp::min(count, max_count));
|
|
|
|
|
|
|
|
for _ in 0..count {
|
|
|
|
items.push(T::zcash_deserialize(&mut self2)?);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(items);
|
|
|
|
}
|
2019-09-15 06:37:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Mark all types implementing `Read` as implementing the extension.
|
|
|
|
impl<R: io::Read + ?Sized> ReadZcashExt for R {}
|
2019-11-19 16:43:30 -08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use std::io::Cursor;
|
|
|
|
|
|
|
|
proptest! {
|
|
|
|
// The test below is cheap so we can run it a lot.
|
|
|
|
#![proptest_config(ProptestConfig::with_cases(100_000))]
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn compactsize_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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|