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