1#![warn(missing_docs)]
9use std::io::{self, Read, Write};
10
11use zcash_address::unified::{Address, Container, Encoding, Receiver};
12use zcash_client_backend::address::UnifiedAddress;
13use zcash_encoding::{CompactSize, Vector};
14
15#[derive(Debug)]
23pub enum ParsedMemo {
24 Version0 {
26 uas: Vec<UnifiedAddress>,
28 },
29 Version1 {
31 uas: Vec<UnifiedAddress>,
33 rejection_address_indexes: Vec<u32>,
35 },
36}
37
38#[deprecated(note = "prefer version 1")]
43pub fn create_wallet_internal_memo_version_0(uas: &[UnifiedAddress]) -> io::Result<[u8; 511]> {
44 let mut version_and_data = Vec::new();
45 CompactSize::write(&mut version_and_data, 0usize)?;
46 Vector::write(&mut version_and_data, uas, |w, ua| {
47 write_unified_address_to_raw_encoding(ua, w)
48 })?;
49 let mut uas_bytes = [0u8; 511];
50 if version_and_data.len() > 511 {
51 Err(io::Error::new(
52 io::ErrorKind::InvalidData,
53 "Too many uas to fit in memo field",
54 ))
55 } else {
56 uas_bytes[..version_and_data.len()].copy_from_slice(version_and_data.as_slice());
57 Ok(uas_bytes)
58 }
59}
60
61pub fn create_wallet_internal_memo_version_1(
68 uas: &[UnifiedAddress],
69 refund_address_indexes: &[u32],
70) -> io::Result<[u8; 511]> {
71 let mut memo_bytes_vec = Vec::new();
72 CompactSize::write(&mut memo_bytes_vec, 1usize)?;
73 Vector::write(&mut memo_bytes_vec, uas, |w, ua| {
74 write_unified_address_to_raw_encoding(ua, w)
75 })?;
76 Vector::write(
77 &mut memo_bytes_vec,
78 refund_address_indexes,
79 |w, ea_index| CompactSize::write(w, *ea_index as usize),
80 )?;
81 let mut memo_bytes = [0u8; 511];
82 if memo_bytes_vec.len() > 511 {
83 Err(io::Error::new(
84 io::ErrorKind::InvalidData,
85 "Too many addresses to fit in memo field",
86 ))
87 } else {
88 memo_bytes[..memo_bytes_vec.len()].copy_from_slice(memo_bytes_vec.as_slice());
89 Ok(memo_bytes)
90 }
91}
92
93pub fn parse_zingo_memo(memo: [u8; 511]) -> io::Result<ParsedMemo> {
95 let mut reader: &[u8] = &memo;
96 match CompactSize::read(&mut reader)? {
97 0 => Ok(ParsedMemo::Version0 {
98 uas: Vector::read(&mut reader, |r| read_unified_address_from_raw_encoding(r))?,
99 }),
100 1 => Ok(ParsedMemo::Version1 {
101 uas: Vector::read(&mut reader, |r| read_unified_address_from_raw_encoding(r))?,
102 rejection_address_indexes: Vector::read(&mut reader, |r| CompactSize::read_t(r))?,
103 }),
104 _ => Err(io::Error::new(
105 io::ErrorKind::InvalidData,
106 "Received memo from a future version of this protocol.\n\
107 Please ensure your software is up-to-date",
108 )),
109 }
110}
111
112pub fn write_unified_address_to_raw_encoding<W: Write>(
116 ua: &UnifiedAddress,
117 writer: W,
118) -> io::Result<()> {
119 let mainnet_encoded_ua = ua.encode(&zcash_primitives::consensus::MAIN_NETWORK);
120 let (_mainnet, address) =
121 Address::decode(&mainnet_encoded_ua).expect("Freshly encoded ua to decode!");
122 let receivers = address.items();
123 Vector::write(writer, &receivers, |mut w, receiver| {
124 let (typecode, data): (u32, &[u8]) = match receiver {
125 Receiver::Orchard(data) => (3, data),
126 Receiver::Sapling(data) => (2, data),
127 Receiver::P2sh(data) => (1, data),
128 Receiver::P2pkh(data) => (0, data),
129 Receiver::Unknown { typecode, data } => (*typecode, data.as_slice()),
130 };
131 CompactSize::write(&mut w, typecode as usize)?;
132 CompactSize::write(&mut w, data.len())?;
133 w.write_all(data)
134 })
135}
136
137pub fn read_unified_address_from_raw_encoding<R: Read>(reader: R) -> io::Result<UnifiedAddress> {
141 let receivers = Vector::read(reader, |mut r| {
142 let typecode: usize = CompactSize::read_t(&mut r)?;
143 let addr_len: usize = CompactSize::read_t(&mut r)?;
144 let mut receiver_bytes = vec![0; addr_len];
145 r.read_exact(&mut receiver_bytes)?;
146 decode_receiver(typecode, receiver_bytes)
147 })?;
148 let address = Address::try_from_items(receivers)
149 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
150 UnifiedAddress::try_from(address).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
151}
152
153fn decode_receiver(typecode: usize, data: Vec<u8>) -> io::Result<Receiver> {
154 Ok(match typecode {
155 0 => Receiver::P2pkh(<[u8; 20]>::try_from(data).map_err(|e| {
156 io::Error::new(
157 io::ErrorKind::InvalidData,
158 format!(
159 "Typecode {typecode} (P2pkh) indicates 20 bytes, found length of {}",
160 e.len()
161 ),
162 )
163 })?),
164 1 => Receiver::P2sh(<[u8; 20]>::try_from(data).map_err(|e| {
165 io::Error::new(
166 io::ErrorKind::InvalidData,
167 format!(
168 "Typecode {typecode} (P2sh) indicates 20 bytes, found length of {}",
169 e.len()
170 ),
171 )
172 })?),
173 2 => Receiver::Sapling(<[u8; 43]>::try_from(data).map_err(|e| {
174 io::Error::new(
175 io::ErrorKind::InvalidData,
176 format!(
177 "Typecode {typecode} (Sapling) indicates 43 bytes, found length of {}",
178 e.len()
179 ),
180 )
181 })?),
182 3 => Receiver::Orchard(<[u8; 43]>::try_from(data).map_err(|e| {
183 io::Error::new(
184 io::ErrorKind::InvalidData,
185 format!(
186 "Typecode {typecode} (Orchard) indicates 43 bytes, found length of {}",
187 e.len()
188 ),
189 )
190 })?),
191 _ => Receiver::Unknown {
192 typecode: typecode as u32,
193 data,
194 },
195 })
196}
197
198#[cfg(test)]
199mod test_vectors;
200
201#[cfg(test)]
202mod tests {
203 use super::test_vectors as zingomemo_vectors;
204 use super::*;
205 use rand::{self, Rng};
206 use test_vectors::TestVector;
207 use zcash_primitives::consensus::MAIN_NETWORK;
208
209 fn get_some_number_of_ephemeral_indexes() -> Vec<u32> {
210 let count = rand::thread_rng().gen_range(0..=10);
212
213 (0..count).collect::<Vec<u32>>()
215 }
216 fn get_serialiazed_ua(test_vector: &TestVector) -> (UnifiedAddress, Vec<u8>) {
217 let zcash_keys::address::Address::Unified(ua) =
218 zcash_keys::address::Address::decode(&MAIN_NETWORK, test_vector.unified_addr).unwrap()
219 else {
220 panic!("Couldn't decode test_vector UA")
221 };
222 let mut serialized_ua = Vec::new();
223 write_unified_address_to_raw_encoding(&ua, &mut serialized_ua).unwrap();
224 (ua, serialized_ua)
225 }
226 #[test]
227 fn parse_zingo_memo_version_n() {
228 for test_vector in zingomemo_vectors::UA_TEST_VECTORS {
229 let (ua, _serialized_ua) = get_serialiazed_ua(test_vector);
230 #[allow(deprecated)]
232 let version0_bytes = create_wallet_internal_memo_version_0(&[ua.clone()]).unwrap();
233 let success_parse = parse_zingo_memo(version0_bytes).expect("To succeed in parse.");
234 if let ParsedMemo::Version0 { uas } = success_parse {
235 assert_eq!(uas[0], ua);
236 };
237 let random_rejection_indexes = get_some_number_of_ephemeral_indexes();
239 let version1_bytes =
240 create_wallet_internal_memo_version_1(&[ua.clone()], &random_rejection_indexes)
241 .expect("To create version 1 bytes");
242 let success_parse = parse_zingo_memo(version1_bytes).expect("To succeed in parse.");
243 if let ParsedMemo::Version1 {
244 uas,
245 rejection_address_indexes,
246 } = success_parse
247 {
248 assert_eq!(uas[0], ua);
249 assert_eq!(rejection_address_indexes, random_rejection_indexes);
250 };
251 }
252 }
253 #[test]
254 fn round_trip_ser_deser() {
255 for test_vector in zingomemo_vectors::UA_TEST_VECTORS {
256 let (ua, serialized_ua) = get_serialiazed_ua(test_vector);
257 assert_eq!(
258 ua,
259 read_unified_address_from_raw_encoding(&*serialized_ua).unwrap()
260 );
261 }
262 }
263}