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}