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