Skip to main content

zebra_chain/serialization/
zcash_serialize.rs

1//! Converting Zcash consensus-critical data structures into bytes.
2
3use byteorder::WriteBytesExt;
4use std::{io, net::Ipv6Addr};
5
6use super::{AtLeastOne, CompactSizeMessage};
7
8/// The maximum length of a Zcash message, in bytes.
9///
10/// This value is used to calculate safe preallocation limits for some types
11pub const MAX_PROTOCOL_MESSAGE_LEN: usize = 2 * 1024 * 1024;
12
13/// The maximum number of block headers in a single `headers` protocol message.
14///
15/// <https://zips.z.cash/protocol/protocol.pdf#page=108>
16pub const MAX_HEADERS_PER_MESSAGE: usize = 160;
17
18/// Consensus-critical serialization for Zcash.
19///
20/// This trait provides a generic serialization for consensus-critical
21/// formats, such as network messages, transactions, blocks, etc.
22///
23/// It is intended for use only for consensus-critical formats.
24/// Internal serialization can freely use `serde`, or any other format.
25pub trait ZcashSerialize: Sized {
26    /// Write `self` to the given `writer` using the canonical format.
27    ///
28    /// This function has a `zcash_` prefix to alert the reader that the
29    /// serialization in use is consensus-critical serialization, rather than
30    /// some other kind of serialization.
31    ///
32    /// Notice that the error type is [`std::io::Error`]; this indicates that
33    /// serialization MUST be infallible up to errors in the underlying writer.
34    /// In other words, any type implementing `ZcashSerialize` must make illegal
35    /// states unrepresentable.
36    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error>;
37
38    /// Helper function to construct a vec to serialize the current struct into
39    fn zcash_serialize_to_vec(&self) -> Result<Vec<u8>, io::Error> {
40        let mut data = Vec::new();
41        self.zcash_serialize(&mut data)?;
42        Ok(data)
43    }
44
45    /// Get the size of `self` by using a fake writer.
46    fn zcash_serialized_size(&self) -> usize {
47        let mut writer = FakeWriter(0);
48        self.zcash_serialize(&mut writer)
49            .expect("writer should never fail");
50        writer.0
51    }
52}
53
54impl ZcashSerialize for u8 {
55    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
56        writer.write_u8(*self)
57    }
58}
59
60/// A fake writer helper used to get object lengths without allocating RAM.
61pub struct FakeWriter(pub usize);
62
63impl std::io::Write for FakeWriter {
64    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
65        self.0 += buf.len();
66
67        Ok(buf.len())
68    }
69
70    fn flush(&mut self) -> std::io::Result<()> {
71        Ok(())
72    }
73}
74
75/// Serialize a `Vec` as a CompactSize number of items, then the items. This is
76/// the most common format in Zcash.
77///
78/// See `zcash_serialize_external_count` for more details, and usage information.
79impl<T: ZcashSerialize> ZcashSerialize for Vec<T> {
80    #[allow(clippy::unwrap_in_result)]
81    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
82        let len: CompactSizeMessage = self
83            .len()
84            .try_into()
85            .expect("len fits in MAX_PROTOCOL_MESSAGE_LEN");
86        len.zcash_serialize(&mut writer)?;
87
88        zcash_serialize_external_count(self, writer)
89    }
90}
91
92/// Serialize an `AtLeastOne` vector as a CompactSize number of items, then the
93/// items. This is the most common format in Zcash.
94impl<T: ZcashSerialize> ZcashSerialize for AtLeastOne<T> {
95    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
96        self.as_vec().zcash_serialize(&mut writer)
97    }
98}
99
100/// Serialize a byte vector as a CompactSize number of items, then the items.
101///
102/// # Correctness
103///
104/// Most Zcash types have specific rules about serialization of `Vec<u8>`s.
105/// Check the spec and consensus rules before using this function.
106///
107/// See `zcash_serialize_bytes_external_count` for more details, and usage information.
108//
109// we specifically want to serialize `Vec`s here, rather than generic slices
110#[allow(clippy::ptr_arg)]
111pub fn zcash_serialize_bytes<W: io::Write>(vec: &Vec<u8>, mut writer: W) -> Result<(), io::Error> {
112    CompactSizeMessage::try_from(vec.len())?.zcash_serialize(&mut writer)?;
113
114    zcash_serialize_bytes_external_count(vec, writer)
115}
116
117/// Serialize an empty list of items, by writing a zero CompactSize length.
118/// (And no items.)
119pub fn zcash_serialize_empty_list<W: io::Write>(writer: W) -> Result<(), io::Error> {
120    CompactSizeMessage::default().zcash_serialize(writer)
121}
122
123/// Serialize a typed `Vec` **without** writing the number of items as a
124/// CompactSize.
125///
126/// In Zcash, most arrays are stored as a CompactSize, followed by that number
127/// of items of type `T`. But in `Transaction::V5`, some types are serialized as
128/// multiple arrays in different locations, with a single CompactSize before the
129/// first array.
130///
131/// ## Usage
132///
133/// Use `zcash_serialize_external_count` when the array count is determined by
134/// other data, or a consensus rule.
135///
136/// Use `Vec::zcash_serialize` for data that contains CompactSize count,
137/// followed by the data array.
138///
139/// For example, when a single count applies to multiple arrays:
140/// 1. Use `Vec::zcash_serialize` for the array that has a data count.
141/// 2. Use `zcash_serialize_external_count` for the arrays with no count in the
142///    data, passing the length of the first array.
143///
144/// This function has a `zcash_` prefix to alert the reader that the
145/// serialization in use is consensus-critical serialization, rather than
146/// some other kind of serialization.
147//
148// we specifically want to serialize `Vec`s here, rather than generic slices
149#[allow(clippy::ptr_arg)]
150pub fn zcash_serialize_external_count<W: io::Write, T: ZcashSerialize>(
151    vec: &Vec<T>,
152    mut writer: W,
153) -> Result<(), io::Error> {
154    for x in vec {
155        x.zcash_serialize(&mut writer)?;
156    }
157    Ok(())
158}
159
160/// Serialize a raw byte `Vec` **without** writing the number of items as a
161/// CompactSize.
162///
163/// This is a convenience alias for `writer.write_all(&vec)`.
164//
165// we specifically want to serialize `Vec`s here, rather than generic slices
166#[allow(clippy::ptr_arg)]
167pub fn zcash_serialize_bytes_external_count<W: io::Write>(
168    vec: &Vec<u8>,
169    mut writer: W,
170) -> Result<(), io::Error> {
171    writer.write_all(vec)
172}
173
174/// Write a Bitcoin-encoded UTF-8 `&str`.
175impl ZcashSerialize for &str {
176    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
177        let str_bytes = self.as_bytes().to_vec();
178        zcash_serialize_bytes(&str_bytes, writer)
179    }
180}
181
182/// Write a Bitcoin-encoded UTF-8 `String`.
183impl ZcashSerialize for String {
184    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
185        self.as_str().zcash_serialize(&mut writer)
186    }
187}
188
189// We don't impl ZcashSerialize for Ipv4Addr or SocketAddrs,
190// because the IPv4 and port formats are different in addr (v1) and addrv2 messages.
191
192/// Write a Bitcoin-encoded IPv6 address.
193impl ZcashSerialize for Ipv6Addr {
194    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
195        writer.write_all(&self.octets())
196    }
197}