1use alloc::vec::Vec;
2use core::convert::{TryFrom, TryInto};
3use zcash_protocol::constants;
4
5use super::{
6 private::{SealedContainer, SealedItem},
7 Container, Encoding, ParseError, Typecode,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Fvk {
13 Orchard([u8; 96]),
17
18 Sapling([u8; 128]),
22
23 P2pkh([u8; 65]),
37
38 Unknown {
39 typecode: u32,
40 data: Vec<u8>,
41 },
42}
43
44impl TryFrom<(u32, &[u8])> for Fvk {
45 type Error = ParseError;
46
47 fn try_from((typecode, data): (u32, &[u8])) -> Result<Self, Self::Error> {
48 let data = data.to_vec();
49 match typecode.try_into()? {
50 Typecode::P2pkh => data.try_into().map(Fvk::P2pkh),
51 Typecode::P2sh => Err(data),
52 Typecode::Sapling => data.try_into().map(Fvk::Sapling),
53 Typecode::Orchard => data.try_into().map(Fvk::Orchard),
54 Typecode::Unknown(_) => Ok(Fvk::Unknown { typecode, data }),
55 }
56 .map_err(|e| {
57 ParseError::InvalidEncoding(format!("Invalid fvk for typecode {}: {:?}", typecode, e))
58 })
59 }
60}
61
62impl SealedItem for Fvk {
63 fn typecode(&self) -> Typecode {
64 match self {
65 Fvk::P2pkh(_) => Typecode::P2pkh,
66 Fvk::Sapling(_) => Typecode::Sapling,
67 Fvk::Orchard(_) => Typecode::Orchard,
68 Fvk::Unknown { typecode, .. } => Typecode::Unknown(*typecode),
69 }
70 }
71
72 fn data(&self) -> &[u8] {
73 match self {
74 Fvk::P2pkh(data) => data,
75 Fvk::Sapling(data) => data,
76 Fvk::Orchard(data) => data,
77 Fvk::Unknown { data, .. } => data,
78 }
79 }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, Hash)]
110pub struct Ufvk(pub(crate) Vec<Fvk>);
111
112impl Container for Ufvk {
113 type Item = Fvk;
114
115 fn items_as_parsed(&self) -> &[Fvk] {
120 &self.0
121 }
122}
123
124impl Encoding for Ufvk {}
125
126impl SealedContainer for Ufvk {
127 const MAINNET: &'static str = constants::mainnet::HRP_UNIFIED_FVK;
133
134 const TESTNET: &'static str = constants::testnet::HRP_UNIFIED_FVK;
140
141 const REGTEST: &'static str = constants::regtest::HRP_UNIFIED_FVK;
147
148 fn from_inner(fvks: Vec<Self::Item>) -> Self {
149 Self(fvks)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use alloc::borrow::ToOwned;
156 use alloc::vec::Vec;
157
158 use assert_matches::assert_matches;
159
160 use proptest::{array::uniform1, array::uniform32, prelude::*, sample::select};
161
162 use super::{Fvk, ParseError, Typecode, Ufvk};
163 use crate::kind::unified::{
164 private::{SealedContainer, SealedItem},
165 Container, Encoding,
166 };
167 use zcash_protocol::consensus::NetworkType;
168
169 prop_compose! {
170 fn uniform128()(a in uniform96(), b in uniform32(0u8..)) -> [u8; 128] {
171 let mut fvk = [0; 128];
172 fvk[..96].copy_from_slice(&a);
173 fvk[96..].copy_from_slice(&b);
174 fvk
175 }
176 }
177
178 prop_compose! {
179 fn uniform96()(a in uniform32(0u8..), b in uniform32(0u8..), c in uniform32(0u8..)) -> [u8; 96] {
180 let mut fvk = [0; 96];
181 fvk[..32].copy_from_slice(&a);
182 fvk[32..64].copy_from_slice(&b);
183 fvk[64..].copy_from_slice(&c);
184 fvk
185 }
186 }
187
188 prop_compose! {
189 fn uniform65()(a in uniform32(0u8..), b in uniform32(0u8..), c in uniform1(0u8..)) -> [u8; 65] {
190 let mut fvk = [0; 65];
191 fvk[..32].copy_from_slice(&a);
192 fvk[32..64].copy_from_slice(&b);
193 fvk[64..].copy_from_slice(&c);
194 fvk
195 }
196 }
197
198 pub fn arb_orchard_fvk() -> impl Strategy<Value = Fvk> {
199 uniform96().prop_map(Fvk::Orchard)
200 }
201
202 pub fn arb_sapling_fvk() -> impl Strategy<Value = Fvk> {
203 uniform128().prop_map(Fvk::Sapling)
204 }
205
206 fn arb_shielded_fvk() -> impl Strategy<Value = Vec<Fvk>> {
207 prop_oneof![
208 vec![arb_sapling_fvk().boxed()],
209 vec![arb_orchard_fvk().boxed()],
210 vec![arb_sapling_fvk().boxed(), arb_orchard_fvk().boxed()],
211 ]
212 }
213
214 fn arb_transparent_fvk() -> BoxedStrategy<Fvk> {
215 uniform65().prop_map(Fvk::P2pkh).boxed()
216 }
217
218 prop_compose! {
219 fn arb_unified_fvk()(
220 shielded in arb_shielded_fvk(),
221 transparent in prop::option::of(arb_transparent_fvk()),
222 ) -> Ufvk {
223 let mut items: Vec<_> = transparent.into_iter().chain(shielded).collect();
224 items.sort_unstable_by(Fvk::encoding_order);
225 Ufvk(items)
226 }
227 }
228
229 proptest! {
230 #[test]
231 fn ufvk_roundtrip(
232 network in select(vec![NetworkType::Main, NetworkType::Test, NetworkType::Regtest]),
233 ufvk in arb_unified_fvk(),
234 ) {
235 let encoded = ufvk.encode(&network);
236 let decoded = Ufvk::decode(&encoded);
237 prop_assert_eq!(decoded, Ok((network, ufvk)));
238 }
239 }
240
241 #[test]
242 fn padding() {
243 let invalid_padding = [
247 0x6b, 0x32, 0x44, 0xf1, 0xb, 0x67, 0xe9, 0x8f, 0x6, 0x57, 0xe3, 0x5, 0x17, 0xa0, 0x7,
248 0x5c, 0xb0, 0xc9, 0x23, 0xcc, 0xb7, 0x54, 0xac, 0x55, 0x6a, 0x65, 0x99, 0x95, 0x32,
249 0x97, 0xd5, 0x34, 0xa7, 0xc8, 0x6f, 0xc, 0xd7, 0x3b, 0xe0, 0x88, 0x19, 0xf3, 0x3e,
250 0x26, 0x19, 0xd6, 0x5f, 0x9a, 0x62, 0xc9, 0x6f, 0xad, 0x3b, 0xe5, 0xdd, 0xf1, 0xff,
251 0x5b, 0x4a, 0x13, 0x61, 0xc0, 0xd5, 0xa5, 0x87, 0xc5, 0x69, 0x48, 0xdb, 0x7e, 0xc6,
252 0x4e, 0xb0, 0x55, 0x41, 0x3f, 0xc0, 0x53, 0xbb, 0x79, 0x8b, 0x24, 0xa0, 0xfa, 0xd1,
253 0x6e, 0xea, 0x9, 0xea, 0xb3, 0xaf, 0x0, 0x7d, 0x86, 0x47, 0xdb, 0x8b, 0x38, 0xdd, 0x7b,
254 0xdf, 0x63, 0xe7, 0xef, 0x65, 0x6b, 0x18, 0x23, 0xf7, 0x3e, 0x35, 0x7c, 0xf3, 0xc4,
255 ];
256 assert_eq!(
257 Ufvk::parse_internal(Ufvk::MAINNET, &invalid_padding[..]),
258 Err(ParseError::InvalidEncoding(
259 "Invalid padding bytes".to_owned()
260 ))
261 );
262
263 let truncated_padding = [
265 0xdf, 0xea, 0x84, 0x55, 0xc3, 0x4a, 0x7c, 0x6e, 0x9f, 0x83, 0x3, 0x21, 0x14, 0xb0,
266 0xcf, 0xb0, 0x60, 0x84, 0x75, 0x3a, 0xdc, 0xb9, 0x93, 0x16, 0xc0, 0x8f, 0x28, 0x5f,
267 0x61, 0x5e, 0xf0, 0x8e, 0x44, 0xae, 0xa6, 0x74, 0xc5, 0x64, 0xad, 0xfa, 0xdc, 0x7d,
268 0x64, 0x2a, 0x9, 0x47, 0x16, 0xf6, 0x5d, 0x8e, 0x46, 0xc4, 0xf0, 0x54, 0xfa, 0x5, 0x28,
269 0x1e, 0x3d, 0x7d, 0x37, 0xa5, 0x9f, 0x8b, 0x62, 0x78, 0xf6, 0x50, 0x18, 0x63, 0xe4,
270 0x51, 0x14, 0xae, 0x89, 0x41, 0x86, 0xd4, 0x9f, 0x10, 0x4b, 0x66, 0x2b, 0xf9, 0x46,
271 0x9c, 0xeb, 0xe8, 0x90, 0x8, 0xad, 0xd9, 0x6c, 0x6a, 0xf1, 0xed, 0xeb, 0x72, 0x44,
272 0x43, 0x8e, 0xc0, 0x3e, 0x9f, 0xf4, 0xf1, 0x80, 0x32, 0xcf, 0x2f, 0x7e, 0x7f, 0x91,
273 ];
274 assert_eq!(
275 Ufvk::parse_internal(Ufvk::MAINNET, &truncated_padding[..]),
276 Err(ParseError::InvalidEncoding(
277 "Invalid padding bytes".to_owned()
278 ))
279 );
280 }
281
282 #[test]
283 fn truncated() {
284 let truncated_sapling_data = vec![
290 0x43, 0xbf, 0x17, 0xa2, 0xb7, 0x85, 0xe7, 0x8e, 0xa4, 0x6d, 0x36, 0xa5, 0xf1, 0x1d,
291 0x74, 0xd1, 0x40, 0x6e, 0xed, 0xbd, 0x6b, 0x51, 0x6a, 0x36, 0x9c, 0xb3, 0x28, 0xd,
292 0x90, 0xa1, 0x1e, 0x3a, 0x67, 0xa2, 0x15, 0xc5, 0xfb, 0x82, 0x96, 0xf4, 0x35, 0x57,
293 0x71, 0x5d, 0xbb, 0xac, 0x30, 0x1d, 0x1, 0x6d, 0xdd, 0x2e, 0xf, 0x8, 0x4b, 0xcf, 0x5,
294 0xfe, 0x86, 0xd7, 0xa0, 0x9d, 0x94, 0x9f, 0x16, 0x5e, 0xa0, 0x3, 0x58, 0x81, 0x71,
295 0x40, 0xe4, 0xb8, 0xfc, 0x64, 0x75, 0x80, 0x46, 0x4f, 0x51, 0x2d, 0xb2, 0x51, 0xf,
296 0x22, 0x49, 0x53, 0x95, 0xbd, 0x7b, 0x66, 0xd9, 0x17, 0xda, 0x15, 0x62, 0xe0, 0xc6,
297 0xf8, 0x5c, 0xdf, 0x75, 0x6d, 0x7, 0xb, 0xf7, 0xab, 0xfc, 0x20, 0x61, 0xd0, 0xf4, 0x79,
298 0xfa, 0x4, 0xd3, 0xac, 0x8b, 0xf, 0x3c, 0x30, 0x23, 0x32, 0x37, 0x51, 0xc5, 0xfc, 0x66,
299 0x7e, 0xe1, 0x9c, 0xa8, 0xec, 0x52, 0x57, 0x7e, 0xc0, 0x31, 0x83, 0x1c, 0x31, 0x5,
300 0x1b, 0xc3, 0x70, 0xd3, 0x44, 0x74, 0xd2, 0x8a, 0xda, 0x32, 0x4, 0x93, 0xd2, 0xbf,
301 0xb4, 0xbb, 0xa, 0x9e, 0x8c, 0xe9, 0x8f, 0xe7, 0x8a, 0x95, 0xc8, 0x21, 0xfa, 0x12,
302 0x41, 0x2e, 0x69, 0x54, 0xf0, 0x7a, 0x9e, 0x20, 0x94, 0xa3, 0xaa, 0xc3, 0x50, 0x43,
303 0xc5, 0xe2, 0x32, 0x8b, 0x2e, 0x4f, 0xbb, 0xb4, 0xc0, 0x7f, 0x47, 0x35, 0xab, 0x89,
304 0x8c, 0x7a, 0xbf, 0x7b, 0x9a, 0xdd, 0xee, 0x18, 0x2c, 0x2d, 0xc2, 0xfc,
305 ];
306 assert_matches!(
307 Ufvk::parse_internal(Ufvk::MAINNET, &truncated_sapling_data[..]),
308 Err(ParseError::InvalidEncoding(_))
309 );
310
311 let truncated_after_sapling_typecode = [
313 0xac, 0x26, 0x5b, 0x19, 0x8f, 0x88, 0xb0, 0x7, 0xb3, 0x0, 0x91, 0x19, 0x52, 0xe1, 0x73,
314 0x48, 0xff, 0x66, 0x7a, 0xef, 0xcf, 0x57, 0x9c, 0x65, 0xe4, 0x6a, 0x7a, 0x1d, 0x19,
315 0x75, 0x6b, 0x43, 0xdd, 0xcf, 0xb9, 0x9a, 0xf3, 0x7a, 0xf8, 0xb, 0x23, 0x96, 0x64,
316 0x8c, 0x57, 0x56, 0x67, 0x9, 0x40, 0x35, 0xcb, 0xb1, 0xa4, 0x91, 0x4f, 0xdc, 0x39, 0x0,
317 0x98, 0x56, 0xa8, 0xf7, 0x25, 0x1a, 0xc8, 0xbc, 0xd7, 0xb3, 0xb0, 0xfa, 0x78, 0x6,
318 0xe8, 0x50, 0xfe, 0x92, 0xec, 0x5b, 0x1f, 0x74, 0xb9, 0xcf, 0x1f, 0x2e, 0x3b, 0x41,
319 0x54, 0xd1, 0x9e, 0xec, 0x8b, 0xef, 0x35, 0xb8, 0x44, 0xdd, 0xab, 0x9a, 0x8d,
320 ];
321 assert_matches!(
322 Ufvk::parse_internal(Ufvk::MAINNET, &truncated_after_sapling_typecode[..]),
323 Err(ParseError::InvalidEncoding(_))
324 );
325 }
326
327 #[test]
328 fn duplicate_typecode() {
329 let ufvk = Ufvk(vec![Fvk::Sapling([1; 128]), Fvk::Sapling([2; 128])]);
332 let encoded = ufvk.to_jumbled_bytes(Ufvk::MAINNET);
333 assert_eq!(
334 Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
335 Err(ParseError::DuplicateTypecode(Typecode::Sapling))
336 );
337 }
338
339 #[test]
340 fn only_transparent() {
341 let encoded = [
343 0xc4, 0x70, 0xc8, 0x7a, 0xcc, 0xe6, 0x6b, 0x1a, 0x62, 0xc7, 0xcd, 0x5f, 0x76, 0xd8,
344 0xcc, 0x9c, 0x50, 0xbd, 0xce, 0x85, 0x80, 0xd7, 0x78, 0x25, 0x3e, 0x47, 0x9, 0x57,
345 0x7d, 0x6a, 0xdb, 0x10, 0xb4, 0x11, 0x80, 0x13, 0x4c, 0x83, 0x76, 0xb4, 0x6b, 0xbd,
346 0xef, 0x83, 0x5c, 0xa7, 0x68, 0xe6, 0xba, 0x41, 0x12, 0xbd, 0x43, 0x24, 0xf5, 0xaa,
347 0xa0, 0xf5, 0xf8, 0xe1, 0x59, 0xa0, 0x95, 0x85, 0x86, 0xf1, 0x9e, 0xcf, 0x8f, 0x94,
348 0xf4, 0xf5, 0x16, 0xef, 0x5c, 0xe0, 0x26, 0xbc, 0x23, 0x73, 0x76, 0x3f, 0x4b,
349 ];
350
351 assert_eq!(
352 Ufvk::parse_internal(Ufvk::MAINNET, &encoded[..]),
353 Err(ParseError::OnlyTransparent)
354 );
355 }
356
357 #[test]
358 fn fvks_are_sorted() {
359 let ufvk = Ufvk(vec![
361 Fvk::P2pkh([0; 65]),
362 Fvk::Orchard([0; 96]),
363 Fvk::Unknown {
364 typecode: 0xff,
365 data: vec![],
366 },
367 Fvk::Sapling([0; 128]),
368 ]);
369
370 assert_eq!(
372 ufvk.items(),
373 vec![
374 Fvk::Orchard([0; 96]),
375 Fvk::Sapling([0; 128]),
376 Fvk::P2pkh([0; 65]),
377 Fvk::Unknown {
378 typecode: 0xff,
379 data: vec![],
380 },
381 ]
382 )
383 }
384}