zcash_address/kind/unified/
ivk.rs

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/// The set of known IVKs for Unified IVKs.
11#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Ivk {
13    /// The raw encoding of an Orchard Incoming Viewing Key.
14    ///
15    /// `(dk, ivk)` each 32 bytes.
16    Orchard([u8; 64]),
17
18    /// Data contained within the Sapling component of a Unified Incoming Viewing Key.
19    ///
20    /// In order to ensure that Unified Addresses can always be derived from UIVKs, we
21    /// store more data here than was specified to be part of a Sapling IVK. Specifically,
22    /// we store the same data here as we do for Orchard.
23    ///
24    /// `(dk, ivk)` each 32 bytes.
25    Sapling([u8; 64]),
26
27    /// A pruned version of the extended public key for the BIP 44 account corresponding to the
28    /// transparent address subtree from which transparent addresses are derived,
29    /// at the external `change` BIP 44 path, i.e. `m/44'/133'/<account_id>'/0`. This
30    /// includes just the chain code (32 bytes) and the compressed public key (33 bytes), and excludes
31    /// the depth of in the derivation tree, the parent key fingerprint, and the child key
32    /// number (which would reveal the wallet account number for which this UFVK was generated).
33    ///
34    /// Transparent addresses don't have "viewing keys" - the addresses themselves serve
35    /// that purpose. However, we want the ability to derive diversified Unified Addresses
36    /// from Unified Viewing Keys, and to not break the unlinkability property when they
37    /// include transparent receivers. To achieve this, we treat the last hardened node in
38    /// the BIP 44 derivation path as the "transparent viewing key"; all addresses derived
39    /// from this node use non-hardened derivation, and can thus be derived just from this
40    /// pruned extended public key.
41    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/// A Unified Incoming Viewing Key.
88///
89/// # Examples
90///
91/// ```
92/// use zcash_address::unified::{self, Container, Encoding};
93///
94/// # #[cfg(not(feature = "std"))]
95/// # fn main() {}
96/// # #[cfg(feature = "std")]
97/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
98/// # let uivk_from_user = || "uivk1djetqg3fws7y7qu5tekynvcdhz69gsyq07ewvppmzxdqhpfzdgmx8urnkqzv7ylz78ez43ux266pqjhecd59fzhn7wpe6zarnzh804hjtkyad25ryqla5pnc8p5wdl3phj9fczhz64zprun3ux7y9jc08567xryumuz59rjmg4uuflpjqwnq0j0tzce0x74t4tv3gfjq7nczkawxy6y7hse733ae3vw7qfjd0ss0pytvezxp42p6rrpzeh6t2zrz7zpjk0xhngcm6gwdppxs58jkx56gsfflugehf5vjlmu7vj3393gj6u37wenavtqyhdvcdeaj86s6jczl4zq";
99/// let example_uivk: &str = uivk_from_user();
100///
101/// let (network, uivk) = unified::Uivk::decode(example_uivk)?;
102///
103/// // We can obtain the pool-specific Incoming Viewing Keys for the UIVK in
104/// // preference order (the order in which wallets should prefer to use their
105/// // corresponding address receivers):
106/// let ivks: Vec<unified::Ivk> = uivk.items();
107///
108/// // And we can create the UIVK from a list of IVKs:
109/// let new_uivk = unified::Uivk::try_from_items(ivks)?;
110/// assert_eq!(new_uivk, uivk);
111/// # Ok(())
112/// # }
113/// ```
114#[derive(Clone, Debug, PartialEq, Eq, Hash)]
115pub struct Uivk(pub(crate) Vec<Ivk>);
116
117impl Container for Uivk {
118    type Item = Ivk;
119
120    /// Returns the IVKs contained within this UIVK, in the order they were
121    /// parsed from the string encoding.
122    ///
123    /// This API is for advanced usage; in most cases you should use `Uivk::items`.
124    fn items_as_parsed(&self) -> &[Ivk] {
125        &self.0
126    }
127}
128
129impl Encoding for Uivk {}
130
131impl SealedContainer for Uivk {
132    /// The HRP for a Bech32m-encoded mainnet Unified IVK.
133    ///
134    /// Defined in [ZIP 316][zip-0316].
135    ///
136    /// [zip-0316]: https://zips.z.cash/zip-0316
137    const MAINNET: &'static str = constants::mainnet::HRP_UNIFIED_IVK;
138
139    /// The HRP for a Bech32m-encoded testnet Unified IVK.
140    ///
141    /// Defined in [ZIP 316][zip-0316].
142    ///
143    /// [zip-0316]: https://zips.z.cash/zip-0316
144    const TESTNET: &'static str = constants::testnet::HRP_UNIFIED_IVK;
145
146    /// The HRP for a Bech32m-encoded regtest Unified IVK.
147    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        // The test cases below use `Uivk(vec![Ivk::Orchard([1; 64])])` as base.
233
234        // Invalid padding ([0xff; 16] instead of [b'u', 0x00, 0x00, 0x00...])
235        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        // Short padding (padded to 15 bytes instead of 16)
251        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        // The test cases below start from an encoding of
270        //     `Uivk(vec![Ivk::Orchard([1; 64]), Ivk::Sapling([2; 64])])`
271        // with the ivk data truncated, but valid padding.
272
273        // - Missing the last data byte of the Sapling ivk.
274        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        // - Truncated after the typecode of the Sapling ivk.
293        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        // Construct and serialize an invalid UIVK.
309        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        // Raw Encoding of `Uivk(vec![Ivk::P2pkh([0; 65])])`.
320        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        // Construct a UIVK with ivks in an unsorted order.
338        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        // `Uivk::items` sorts the ivks in priority order.
349        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}