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 Ivk {
16 Orchard([u8; 64]),
20
21 Sapling([u8; 64]),
29
30 P2pkh([u8; 65]),
45
46 Unknown {
47 typecode: u32,
48 data: Vec<u8>,
49 },
50}
51
52impl fmt::Debug for Ivk {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Ivk::Orchard(_) => f.debug_tuple("Ivk::Orchard").field(&"...").finish(),
56 Ivk::Sapling(_) => f.debug_tuple("Ivk::Sapling").field(&"...").finish(),
57 Ivk::P2pkh(_) => f.debug_tuple("Ivk::P2pkh").field(&"...").finish(),
58 Ivk::Unknown { typecode, .. } => f
59 .debug_struct("Ivk::Unknown")
60 .field("typecode", typecode)
61 .field("data", &"...")
62 .finish(),
63 }
64 }
65}
66
67impl TryFrom<(u32, &[u8])> for Ivk {
68 type Error = ParseError;
69
70 fn try_from((typecode, data): (u32, &[u8])) -> Result<Self, Self::Error> {
71 let data = data.to_vec();
72 match typecode.try_into()? {
73 Typecode::P2pkh => data.try_into().map(Ivk::P2pkh),
74 Typecode::P2sh => Err(data),
75 Typecode::Sapling => data.try_into().map(Ivk::Sapling),
76 Typecode::Orchard => data.try_into().map(Ivk::Orchard),
77 Typecode::Unknown(_) => Ok(Ivk::Unknown { typecode, data }),
78 }
79 .map_err(|e| {
80 ParseError::InvalidEncoding(format!("Invalid ivk for typecode {}: {:?}", typecode, e))
81 })
82 }
83}
84
85impl SealedItem for Ivk {
86 fn typecode(&self) -> Typecode {
87 match self {
88 Ivk::P2pkh(_) => Typecode::P2pkh,
89 Ivk::Sapling(_) => Typecode::Sapling,
90 Ivk::Orchard(_) => Typecode::Orchard,
91 Ivk::Unknown { typecode, .. } => Typecode::Unknown(*typecode),
92 }
93 }
94
95 fn data(&self) -> &[u8] {
96 match self {
97 Ivk::P2pkh(data) => data,
98 Ivk::Sapling(data) => data,
99 Ivk::Orchard(data) => data,
100 Ivk::Unknown { data, .. } => data,
101 }
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Hash)]
133pub struct Uivk(pub(crate) Vec<Ivk>);
134
135impl Container for Uivk {
136 type Item = Ivk;
137
138 fn items_as_parsed(&self) -> &[Ivk] {
143 &self.0
144 }
145}
146
147impl Encoding for Uivk {}
148
149impl SealedContainer for Uivk {
150 const MAINNET: &'static str = constants::mainnet::HRP_UNIFIED_IVK;
156
157 const TESTNET: &'static str = constants::testnet::HRP_UNIFIED_IVK;
163
164 const REGTEST: &'static str = constants::regtest::HRP_UNIFIED_IVK;
166
167 fn from_inner(ivks: Vec<Self::Item>) -> Self {
168 Self(ivks)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use alloc::borrow::ToOwned;
175 use alloc::vec::Vec;
176
177 use assert_matches::assert_matches;
178
179 use proptest::{
180 array::{uniform1, uniform32},
181 prelude::*,
182 sample::select,
183 };
184
185 use super::{Ivk, ParseError, Typecode, Uivk};
186 use crate::kind::unified::{
187 private::{SealedContainer, SealedItem},
188 Container, Encoding,
189 };
190 use zcash_protocol::consensus::NetworkType;
191
192 prop_compose! {
193 fn uniform64()(a in uniform32(0u8..), b in uniform32(0u8..)) -> [u8; 64] {
194 let mut c = [0; 64];
195 c[..32].copy_from_slice(&a);
196 c[32..].copy_from_slice(&b);
197 c
198 }
199 }
200
201 prop_compose! {
202 fn uniform65()(a in uniform1(0u8..), b in uniform64()) -> [u8; 65] {
203 let mut c = [0; 65];
204 c[..1].copy_from_slice(&a);
205 c[1..].copy_from_slice(&b);
206 c
207 }
208 }
209
210 fn arb_shielded_ivk() -> impl Strategy<Value = Vec<Ivk>> {
211 prop_oneof![
212 vec![uniform64().prop_map(Ivk::Sapling)],
213 vec![uniform64().prop_map(Ivk::Orchard)],
214 vec![
215 uniform64().prop_map(Ivk::Sapling as fn([u8; 64]) -> Ivk),
216 uniform64().prop_map(Ivk::Orchard)
217 ],
218 ]
219 }
220
221 fn arb_transparent_ivk() -> impl Strategy<Value = Ivk> {
222 uniform65().prop_map(Ivk::P2pkh)
223 }
224
225 prop_compose! {
226 fn arb_unified_ivk()(
227 shielded in arb_shielded_ivk(),
228 transparent in prop::option::of(arb_transparent_ivk()),
229 ) -> Uivk {
230 let mut items: Vec<_> = transparent.into_iter().chain(shielded).collect();
231 items.sort_unstable_by(Ivk::encoding_order);
232 Uivk(items)
233 }
234 }
235
236 proptest! {
237 #[test]
238 fn uivk_roundtrip(
239 network in select(vec![NetworkType::Main, NetworkType::Test, NetworkType::Regtest]),
240 uivk in arb_unified_ivk(),
241 ) {
242 let encoded = uivk.encode(&network);
243 let decoded = Uivk::decode(&encoded);
244 prop_assert_eq!(decoded, Ok((network, uivk)));
245 }
246 }
247
248 #[test]
249 fn padding() {
250 let invalid_padding = [
254 0xba, 0xbc, 0xc0, 0x71, 0xcd, 0x3b, 0xfd, 0x9a, 0x32, 0x19, 0x7e, 0xeb, 0x8a, 0xa7,
255 0x6e, 0xd4, 0xac, 0xcb, 0x59, 0xc2, 0x54, 0x26, 0xc6, 0xab, 0x71, 0xc7, 0xc3, 0x72,
256 0xc, 0xa9, 0xad, 0xa4, 0xad, 0x8c, 0x9e, 0x35, 0x7b, 0x4c, 0x5d, 0xc7, 0x66, 0x12,
257 0x8a, 0xc5, 0x42, 0x89, 0xc1, 0x77, 0x32, 0xdc, 0xe8, 0x4b, 0x51, 0x31, 0x30, 0x3,
258 0x20, 0xe3, 0xb6, 0x8c, 0xbb, 0xab, 0xe8, 0x89, 0xf8, 0xed, 0xac, 0x6d, 0x8e, 0xb1,
259 0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
260 ];
261 assert_eq!(
262 Uivk::parse_internal(Uivk::MAINNET, &invalid_padding[..]),
263 Err(ParseError::InvalidEncoding(
264 "Invalid padding bytes".to_owned()
265 ))
266 );
267
268 let truncated_padding = [
270 0x96, 0x73, 0x6a, 0x56, 0xbc, 0x44, 0x38, 0xe2, 0x47, 0x41, 0x1c, 0x70, 0xe4, 0x6,
271 0x87, 0xbe, 0xb6, 0x90, 0xbd, 0xab, 0x1b, 0xd8, 0x27, 0x10, 0x0, 0x21, 0x30, 0x2, 0x77,
272 0x87, 0x0, 0x25, 0x96, 0x94, 0x8f, 0x1e, 0x39, 0xd2, 0xd8, 0x65, 0xb4, 0x3c, 0x72,
273 0xd8, 0xac, 0xec, 0x5b, 0xa2, 0x18, 0x62, 0x3f, 0xb, 0x88, 0xb4, 0x41, 0xf1, 0x55,
274 0x39, 0x53, 0xbf, 0x2a, 0xd6, 0xcf, 0xdd, 0x46, 0xb7, 0xd8, 0xc1, 0x39, 0x34, 0x4d,
275 0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
276 ];
277 assert_eq!(
278 Uivk::parse_internal(Uivk::MAINNET, &truncated_padding[..]),
279 Err(ParseError::InvalidEncoding(
280 "Invalid padding bytes".to_owned()
281 ))
282 );
283 }
284
285 #[test]
286 fn truncated() {
287 let truncated_sapling_data = [
293 0xce, 0xbc, 0xfe, 0xc5, 0xef, 0x2d, 0xe, 0x66, 0xc2, 0x8c, 0x34, 0xdc, 0x2e, 0x24,
294 0xd2, 0xc7, 0x4b, 0xac, 0x36, 0xe0, 0x43, 0x72, 0xa7, 0x33, 0xa4, 0xe, 0xe0, 0x52,
295 0x15, 0x64, 0x66, 0x92, 0x36, 0xa7, 0x60, 0x8e, 0x48, 0xe8, 0xb0, 0x30, 0x4d, 0xcb,
296 0xd, 0x6f, 0x5, 0xd4, 0xb8, 0x72, 0x6a, 0xdc, 0x6c, 0x5c, 0xa, 0xf8, 0xdf, 0x95, 0x5a,
297 0xba, 0xe1, 0xaa, 0x82, 0x51, 0xe2, 0x70, 0x8d, 0x13, 0x16, 0x88, 0x6a, 0xc0, 0xc1,
298 0x99, 0x3c, 0xaf, 0x2c, 0x16, 0x54, 0x80, 0x7e, 0xb, 0xad, 0x31, 0x29, 0x26, 0xdd,
299 0x7a, 0x55, 0x98, 0x1, 0x18, 0xb, 0x14, 0x94, 0xb2, 0x6b, 0x81, 0x67, 0x73, 0xa6, 0xd0,
300 0x20, 0x94, 0x17, 0x3a, 0xf9, 0x98, 0x43, 0x58, 0xd6, 0x1, 0x10, 0x73, 0x32, 0xb4,
301 0x99, 0xad, 0x6b, 0xfe, 0xc0, 0x97, 0xaf, 0xd2, 0xee, 0x8, 0xe5, 0x83, 0x6b, 0xb6,
302 0xd9, 0x0, 0xef, 0x84, 0xff, 0xe8, 0x58, 0xba, 0xe8, 0x10, 0xea, 0x2d, 0xee, 0x72,
303 0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
304 ];
305 assert_matches!(
306 Uivk::parse_internal(Uivk::MAINNET, &truncated_sapling_data[..]),
307 Err(ParseError::InvalidEncoding(_))
308 );
309
310 let truncated_after_sapling_typecode = [
312 0xf7, 0x3, 0xd8, 0xbe, 0x6a, 0x27, 0xfa, 0xa1, 0xd3, 0x11, 0xea, 0x25, 0x94, 0xe2, 0xb,
313 0xde, 0xed, 0x6a, 0xaa, 0x8, 0x46, 0x7d, 0xe4, 0xb1, 0xe, 0xf1, 0xde, 0x61, 0xd7, 0x95,
314 0xf7, 0x82, 0x62, 0x32, 0x7a, 0x73, 0x8c, 0x55, 0x93, 0xa1, 0x63, 0x75, 0xe2, 0xca,
315 0xcb, 0x73, 0xd5, 0xe5, 0xa3, 0xbd, 0xb3, 0xf2, 0x26, 0xfa, 0x1c, 0xa2, 0xad, 0xb6,
316 0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
317 ];
318 assert_matches!(
319 Uivk::parse_internal(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
320 Err(ParseError::InvalidEncoding(_))
321 );
322 }
323
324 #[test]
325 fn duplicate_typecode() {
326 let uivk = Uivk(vec![Ivk::Sapling([1; 64]), Ivk::Sapling([2; 64])]);
328 let encoded = uivk.encode(&NetworkType::Main);
329 assert_eq!(
330 Uivk::decode(&encoded),
331 Err(ParseError::DuplicateTypecode(Typecode::Sapling))
332 );
333 }
334
335 #[test]
336 fn only_transparent() {
337 let encoded = [
339 0x12, 0x51, 0x37, 0xc7, 0xac, 0x8c, 0xd, 0x13, 0x3a, 0x5f, 0xc6, 0x84, 0x53, 0x90,
340 0xf8, 0xe7, 0x23, 0x34, 0xfb, 0xda, 0x49, 0x3c, 0x87, 0x1c, 0x8f, 0x1a, 0xe1, 0x63,
341 0xba, 0xdf, 0x77, 0x64, 0x43, 0xcf, 0xdc, 0x37, 0x1f, 0xd2, 0x89, 0x60, 0xe3, 0x77,
342 0x20, 0xd0, 0x1c, 0x5, 0x40, 0xe5, 0x43, 0x55, 0xc4, 0xe5, 0xf8, 0xaa, 0xe, 0x7a, 0xe7,
343 0x8c, 0x53, 0x15, 0xb8, 0x8f, 0x90, 0x14, 0x33, 0x30, 0x52, 0x2b, 0x8, 0x89, 0x90,
344 0xbd, 0xfe, 0xa4, 0xb7, 0x47, 0x20, 0x92, 0x6, 0xf0, 0x0, 0xf9, 0x64,
345 ];
346
347 assert_eq!(
348 Uivk::parse_internal(Uivk::MAINNET, &encoded[..]),
349 Err(ParseError::OnlyTransparent)
350 );
351 }
352
353 #[test]
354 fn ivks_are_sorted() {
355 let uivk = Uivk(vec![
357 Ivk::P2pkh([0; 65]),
358 Ivk::Orchard([0; 64]),
359 Ivk::Unknown {
360 typecode: 0xff,
361 data: vec![],
362 },
363 Ivk::Sapling([0; 64]),
364 ]);
365
366 assert_eq!(
368 uivk.items(),
369 vec![
370 Ivk::Orchard([0; 64]),
371 Ivk::Sapling([0; 64]),
372 Ivk::P2pkh([0; 65]),
373 Ivk::Unknown {
374 typecode: 0xff,
375 data: vec![],
376 },
377 ]
378 )
379 }
380
381 #[test]
382 fn ivk_debug_redaction() {
383 assert_eq!(
384 format!("{:?}", Ivk::Orchard([0; 64])),
385 "Ivk::Orchard(\"...\")"
386 );
387 assert_eq!(
388 format!("{:?}", Ivk::Sapling([0; 64])),
389 "Ivk::Sapling(\"...\")"
390 );
391 assert_eq!(format!("{:?}", Ivk::P2pkh([0; 65])), "Ivk::P2pkh(\"...\")");
392 assert_eq!(
393 format!(
394 "{:?}",
395 Ivk::Unknown {
396 typecode: 31337,
397 data: vec![4, 5, 6],
398 }
399 ),
400 "Ivk::Unknown { typecode: 31337, data: \"...\" }"
401 );
402 }
403
404 #[test]
405 fn uivk_debug_redaction() {
406 let uivk = Uivk(vec![
407 Ivk::Sapling([0; 64]),
408 Ivk::Unknown {
409 typecode: 9,
410 data: vec![8, 8, 8],
411 },
412 ]);
413
414 assert_eq!(
415 format!("{:?}", uivk),
416 "Uivk([Ivk::Sapling(\"...\"), Ivk::Unknown { typecode: 9, data: \"...\" }])"
417 );
418 }
419}