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 Ivk {
13 Orchard([u8; 64]),
17
18 Sapling([u8; 64]),
26
27 P2pkh([u8; 65]),
42
43 Unknown {
44 typecode: u32,
45 data: Vec<u8>,
46 },
47}
48
49impl TryFrom<(u32, &[u8])> for Ivk {
50 type Error = ParseError;
51
52 fn try_from((typecode, data): (u32, &[u8])) -> Result<Self, Self::Error> {
53 let data = data.to_vec();
54 match typecode.try_into()? {
55 Typecode::P2pkh => data.try_into().map(Ivk::P2pkh),
56 Typecode::P2sh => Err(data),
57 Typecode::Sapling => data.try_into().map(Ivk::Sapling),
58 Typecode::Orchard => data.try_into().map(Ivk::Orchard),
59 Typecode::Unknown(_) => Ok(Ivk::Unknown { typecode, data }),
60 }
61 .map_err(|e| {
62 ParseError::InvalidEncoding(format!("Invalid ivk for typecode {}: {:?}", typecode, e))
63 })
64 }
65}
66
67impl SealedItem for Ivk {
68 fn typecode(&self) -> Typecode {
69 match self {
70 Ivk::P2pkh(_) => Typecode::P2pkh,
71 Ivk::Sapling(_) => Typecode::Sapling,
72 Ivk::Orchard(_) => Typecode::Orchard,
73 Ivk::Unknown { typecode, .. } => Typecode::Unknown(*typecode),
74 }
75 }
76
77 fn data(&self) -> &[u8] {
78 match self {
79 Ivk::P2pkh(data) => data,
80 Ivk::Sapling(data) => data,
81 Ivk::Orchard(data) => data,
82 Ivk::Unknown { data, .. } => data,
83 }
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq, Hash)]
115pub struct Uivk(pub(crate) Vec<Ivk>);
116
117impl Container for Uivk {
118 type Item = Ivk;
119
120 fn items_as_parsed(&self) -> &[Ivk] {
125 &self.0
126 }
127}
128
129impl Encoding for Uivk {}
130
131impl SealedContainer for Uivk {
132 const MAINNET: &'static str = constants::mainnet::HRP_UNIFIED_IVK;
138
139 const TESTNET: &'static str = constants::testnet::HRP_UNIFIED_IVK;
145
146 const REGTEST: &'static str = constants::regtest::HRP_UNIFIED_IVK;
148
149 fn from_inner(ivks: Vec<Self::Item>) -> Self {
150 Self(ivks)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use alloc::borrow::ToOwned;
157 use alloc::vec::Vec;
158
159 use assert_matches::assert_matches;
160
161 use proptest::{
162 array::{uniform1, uniform32},
163 prelude::*,
164 sample::select,
165 };
166
167 use super::{Ivk, ParseError, Typecode, Uivk};
168 use crate::kind::unified::{
169 private::{SealedContainer, SealedItem},
170 Container, Encoding,
171 };
172 use zcash_protocol::consensus::NetworkType;
173
174 prop_compose! {
175 fn uniform64()(a in uniform32(0u8..), b in uniform32(0u8..)) -> [u8; 64] {
176 let mut c = [0; 64];
177 c[..32].copy_from_slice(&a);
178 c[32..].copy_from_slice(&b);
179 c
180 }
181 }
182
183 prop_compose! {
184 fn uniform65()(a in uniform1(0u8..), b in uniform64()) -> [u8; 65] {
185 let mut c = [0; 65];
186 c[..1].copy_from_slice(&a);
187 c[1..].copy_from_slice(&b);
188 c
189 }
190 }
191
192 fn arb_shielded_ivk() -> impl Strategy<Value = Vec<Ivk>> {
193 prop_oneof![
194 vec![uniform64().prop_map(Ivk::Sapling)],
195 vec![uniform64().prop_map(Ivk::Orchard)],
196 vec![
197 uniform64().prop_map(Ivk::Sapling as fn([u8; 64]) -> Ivk),
198 uniform64().prop_map(Ivk::Orchard)
199 ],
200 ]
201 }
202
203 fn arb_transparent_ivk() -> impl Strategy<Value = Ivk> {
204 uniform65().prop_map(Ivk::P2pkh)
205 }
206
207 prop_compose! {
208 fn arb_unified_ivk()(
209 shielded in arb_shielded_ivk(),
210 transparent in prop::option::of(arb_transparent_ivk()),
211 ) -> Uivk {
212 let mut items: Vec<_> = transparent.into_iter().chain(shielded).collect();
213 items.sort_unstable_by(Ivk::encoding_order);
214 Uivk(items)
215 }
216 }
217
218 proptest! {
219 #[test]
220 fn uivk_roundtrip(
221 network in select(vec![NetworkType::Main, NetworkType::Test, NetworkType::Regtest]),
222 uivk in arb_unified_ivk(),
223 ) {
224 let encoded = uivk.encode(&network);
225 let decoded = Uivk::decode(&encoded);
226 prop_assert_eq!(decoded, Ok((network, uivk)));
227 }
228 }
229
230 #[test]
231 fn padding() {
232 let invalid_padding = [
236 0xba, 0xbc, 0xc0, 0x71, 0xcd, 0x3b, 0xfd, 0x9a, 0x32, 0x19, 0x7e, 0xeb, 0x8a, 0xa7,
237 0x6e, 0xd4, 0xac, 0xcb, 0x59, 0xc2, 0x54, 0x26, 0xc6, 0xab, 0x71, 0xc7, 0xc3, 0x72,
238 0xc, 0xa9, 0xad, 0xa4, 0xad, 0x8c, 0x9e, 0x35, 0x7b, 0x4c, 0x5d, 0xc7, 0x66, 0x12,
239 0x8a, 0xc5, 0x42, 0x89, 0xc1, 0x77, 0x32, 0xdc, 0xe8, 0x4b, 0x51, 0x31, 0x30, 0x3,
240 0x20, 0xe3, 0xb6, 0x8c, 0xbb, 0xab, 0xe8, 0x89, 0xf8, 0xed, 0xac, 0x6d, 0x8e, 0xb1,
241 0x83, 0xe8, 0x92, 0x18, 0x28, 0x70, 0x1e, 0x81, 0x76, 0x56, 0xb6, 0x15,
242 ];
243 assert_eq!(
244 Uivk::parse_internal(Uivk::MAINNET, &invalid_padding[..]),
245 Err(ParseError::InvalidEncoding(
246 "Invalid padding bytes".to_owned()
247 ))
248 );
249
250 let truncated_padding = [
252 0x96, 0x73, 0x6a, 0x56, 0xbc, 0x44, 0x38, 0xe2, 0x47, 0x41, 0x1c, 0x70, 0xe4, 0x6,
253 0x87, 0xbe, 0xb6, 0x90, 0xbd, 0xab, 0x1b, 0xd8, 0x27, 0x10, 0x0, 0x21, 0x30, 0x2, 0x77,
254 0x87, 0x0, 0x25, 0x96, 0x94, 0x8f, 0x1e, 0x39, 0xd2, 0xd8, 0x65, 0xb4, 0x3c, 0x72,
255 0xd8, 0xac, 0xec, 0x5b, 0xa2, 0x18, 0x62, 0x3f, 0xb, 0x88, 0xb4, 0x41, 0xf1, 0x55,
256 0x39, 0x53, 0xbf, 0x2a, 0xd6, 0xcf, 0xdd, 0x46, 0xb7, 0xd8, 0xc1, 0x39, 0x34, 0x4d,
257 0xf9, 0x65, 0x49, 0x14, 0xab, 0x7c, 0x55, 0x7b, 0x39, 0x47,
258 ];
259 assert_eq!(
260 Uivk::parse_internal(Uivk::MAINNET, &truncated_padding[..]),
261 Err(ParseError::InvalidEncoding(
262 "Invalid padding bytes".to_owned()
263 ))
264 );
265 }
266
267 #[test]
268 fn truncated() {
269 let truncated_sapling_data = [
275 0xce, 0xbc, 0xfe, 0xc5, 0xef, 0x2d, 0xe, 0x66, 0xc2, 0x8c, 0x34, 0xdc, 0x2e, 0x24,
276 0xd2, 0xc7, 0x4b, 0xac, 0x36, 0xe0, 0x43, 0x72, 0xa7, 0x33, 0xa4, 0xe, 0xe0, 0x52,
277 0x15, 0x64, 0x66, 0x92, 0x36, 0xa7, 0x60, 0x8e, 0x48, 0xe8, 0xb0, 0x30, 0x4d, 0xcb,
278 0xd, 0x6f, 0x5, 0xd4, 0xb8, 0x72, 0x6a, 0xdc, 0x6c, 0x5c, 0xa, 0xf8, 0xdf, 0x95, 0x5a,
279 0xba, 0xe1, 0xaa, 0x82, 0x51, 0xe2, 0x70, 0x8d, 0x13, 0x16, 0x88, 0x6a, 0xc0, 0xc1,
280 0x99, 0x3c, 0xaf, 0x2c, 0x16, 0x54, 0x80, 0x7e, 0xb, 0xad, 0x31, 0x29, 0x26, 0xdd,
281 0x7a, 0x55, 0x98, 0x1, 0x18, 0xb, 0x14, 0x94, 0xb2, 0x6b, 0x81, 0x67, 0x73, 0xa6, 0xd0,
282 0x20, 0x94, 0x17, 0x3a, 0xf9, 0x98, 0x43, 0x58, 0xd6, 0x1, 0x10, 0x73, 0x32, 0xb4,
283 0x99, 0xad, 0x6b, 0xfe, 0xc0, 0x97, 0xaf, 0xd2, 0xee, 0x8, 0xe5, 0x83, 0x6b, 0xb6,
284 0xd9, 0x0, 0xef, 0x84, 0xff, 0xe8, 0x58, 0xba, 0xe8, 0x10, 0xea, 0x2d, 0xee, 0x72,
285 0xf5, 0xd5, 0x8a, 0xb5, 0x1a,
286 ];
287 assert_matches!(
288 Uivk::parse_internal(Uivk::MAINNET, &truncated_sapling_data[..]),
289 Err(ParseError::InvalidEncoding(_))
290 );
291
292 let truncated_after_sapling_typecode = [
294 0xf7, 0x3, 0xd8, 0xbe, 0x6a, 0x27, 0xfa, 0xa1, 0xd3, 0x11, 0xea, 0x25, 0x94, 0xe2, 0xb,
295 0xde, 0xed, 0x6a, 0xaa, 0x8, 0x46, 0x7d, 0xe4, 0xb1, 0xe, 0xf1, 0xde, 0x61, 0xd7, 0x95,
296 0xf7, 0x82, 0x62, 0x32, 0x7a, 0x73, 0x8c, 0x55, 0x93, 0xa1, 0x63, 0x75, 0xe2, 0xca,
297 0xcb, 0x73, 0xd5, 0xe5, 0xa3, 0xbd, 0xb3, 0xf2, 0x26, 0xfa, 0x1c, 0xa2, 0xad, 0xb6,
298 0xd8, 0x21, 0x5e, 0x8, 0xa, 0x82, 0x95, 0x21, 0x74,
299 ];
300 assert_matches!(
301 Uivk::parse_internal(Uivk::MAINNET, &truncated_after_sapling_typecode[..]),
302 Err(ParseError::InvalidEncoding(_))
303 );
304 }
305
306 #[test]
307 fn duplicate_typecode() {
308 let uivk = Uivk(vec![Ivk::Sapling([1; 64]), Ivk::Sapling([2; 64])]);
310 let encoded = uivk.encode(&NetworkType::Main);
311 assert_eq!(
312 Uivk::decode(&encoded),
313 Err(ParseError::DuplicateTypecode(Typecode::Sapling))
314 );
315 }
316
317 #[test]
318 fn only_transparent() {
319 let encoded = [
321 0x12, 0x51, 0x37, 0xc7, 0xac, 0x8c, 0xd, 0x13, 0x3a, 0x5f, 0xc6, 0x84, 0x53, 0x90,
322 0xf8, 0xe7, 0x23, 0x34, 0xfb, 0xda, 0x49, 0x3c, 0x87, 0x1c, 0x8f, 0x1a, 0xe1, 0x63,
323 0xba, 0xdf, 0x77, 0x64, 0x43, 0xcf, 0xdc, 0x37, 0x1f, 0xd2, 0x89, 0x60, 0xe3, 0x77,
324 0x20, 0xd0, 0x1c, 0x5, 0x40, 0xe5, 0x43, 0x55, 0xc4, 0xe5, 0xf8, 0xaa, 0xe, 0x7a, 0xe7,
325 0x8c, 0x53, 0x15, 0xb8, 0x8f, 0x90, 0x14, 0x33, 0x30, 0x52, 0x2b, 0x8, 0x89, 0x90,
326 0xbd, 0xfe, 0xa4, 0xb7, 0x47, 0x20, 0x92, 0x6, 0xf0, 0x0, 0xf9, 0x64,
327 ];
328
329 assert_eq!(
330 Uivk::parse_internal(Uivk::MAINNET, &encoded[..]),
331 Err(ParseError::OnlyTransparent)
332 );
333 }
334
335 #[test]
336 fn ivks_are_sorted() {
337 let uivk = Uivk(vec![
339 Ivk::P2pkh([0; 65]),
340 Ivk::Orchard([0; 64]),
341 Ivk::Unknown {
342 typecode: 0xff,
343 data: vec![],
344 },
345 Ivk::Sapling([0; 64]),
346 ]);
347
348 assert_eq!(
350 uivk.items(),
351 vec![
352 Ivk::Orchard([0; 64]),
353 Ivk::Sapling([0; 64]),
354 Ivk::P2pkh([0; 65]),
355 Ivk::Unknown {
356 typecode: 0xff,
357 data: vec![],
358 },
359 ]
360 )
361 }
362}