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