zcash_address/kind/unified/
fvk.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 FVKs for Unified FVKs.
11#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Fvk {
13    /// The raw encoding of an Orchard Full Viewing Key.
14    ///
15    /// `(ak, nk, rivk)` each 32 bytes.
16    Orchard([u8; 96]),
17
18    /// Data contained within the Sapling component of a Unified Full Viewing Key
19    ///
20    /// `(ak, nk, ovk, dk)` each 32 bytes.
21    Sapling([u8; 128]),
22
23    /// A pruned version of the extended public key for the BIP 44 account corresponding to the
24    /// transparent address subtree from which transparent addresses are derived. This
25    /// includes just the chain code (32 bytes) and the compressed public key (33 bytes), and excludes
26    /// the depth of in the derivation tree, the parent key fingerprint, and the child key
27    /// number (which would reveal the wallet account number for which this UFVK was generated).
28    ///
29    /// Transparent addresses don't have "viewing keys" - the addresses themselves serve
30    /// that purpose. However, we want the ability to derive diversified Unified Addresses
31    /// from Unified Viewing Keys, and to not break the unlinkability property when they
32    /// include transparent receivers. To achieve this, we treat the last hardened node in
33    /// the BIP 44 derivation path as the "transparent viewing key"; all addresses derived
34    /// from this node use non-hardened derivation, and can thus be derived just from this
35    /// pruned extended public key.
36    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/// A Unified Full Viewing Key.
83///
84/// # Examples
85///
86/// ```
87/// use zcash_address::unified::{self, Container, Encoding};
88///
89/// # #[cfg(not(feature = "std"))]
90/// # fn main() {}
91/// # #[cfg(feature = "std")]
92/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
93/// # let ufvk_from_user = || "uview1cgrqnry478ckvpr0f580t6fsahp0a5mj2e9xl7hv2d2jd4ldzy449mwwk2l9yeuts85wjls6hjtghdsy5vhhvmjdw3jxl3cxhrg3vs296a3czazrycrr5cywjhwc5c3ztfyjdhmz0exvzzeyejamyp0cr9z8f9wj0953fzht0m4lenk94t70ruwgjxag2tvp63wn9ftzhtkh20gyre3w5s24f6wlgqxnjh40gd2lxe75sf3z8h5y2x0atpxcyf9t3em4h0evvsftluruqne6w4sm066sw0qe5y8qg423grple5fftxrqyy7xmqmatv7nzd7tcjadu8f7mqz4l83jsyxy4t8pkayytyk7nrp467ds85knekdkvnd7hqkfer8mnqd7pv";
94/// let example_ufvk: &str = ufvk_from_user();
95///
96/// let (network, ufvk) = unified::Ufvk::decode(example_ufvk)?;
97///
98/// // We can obtain the pool-specific Full Viewing Keys for the UFVK in preference
99/// // order (the order in which wallets should prefer to use their corresponding
100/// // address receivers):
101/// let fvks: Vec<unified::Fvk> = ufvk.items();
102///
103/// // And we can create the UFVK from a list of FVKs:
104/// let new_ufvk = unified::Ufvk::try_from_items(fvks)?;
105/// assert_eq!(new_ufvk, ufvk);
106/// # Ok(())
107/// # }
108/// ```
109#[derive(Clone, Debug, PartialEq, Eq, Hash)]
110pub struct Ufvk(pub(crate) Vec<Fvk>);
111
112impl Container for Ufvk {
113    type Item = Fvk;
114
115    /// Returns the FVKs contained within this UFVK, in the order they were
116    /// parsed from the string encoding.
117    ///
118    /// This API is for advanced usage; in most cases you should use `Ufvk::receivers`.
119    fn items_as_parsed(&self) -> &[Fvk] {
120        &self.0
121    }
122}
123
124impl Encoding for Ufvk {}
125
126impl SealedContainer for Ufvk {
127    /// The HRP for a Bech32m-encoded mainnet Unified FVK.
128    ///
129    /// Defined in [ZIP 316][zip-0316].
130    ///
131    /// [zip-0316]: https://zips.z.cash/zip-0316
132    const MAINNET: &'static str = constants::mainnet::HRP_UNIFIED_FVK;
133
134    /// The HRP for a Bech32m-encoded testnet Unified FVK.
135    ///
136    /// Defined in [ZIP 316][zip-0316].
137    ///
138    /// [zip-0316]: https://zips.z.cash/zip-0316
139    const TESTNET: &'static str = constants::testnet::HRP_UNIFIED_FVK;
140
141    /// The HRP for a Bech32m-encoded regtest Unified FVK.
142    ///
143    /// Defined in [ZIP 316][zip-0316].
144    ///
145    /// [zip-0316]: https://zips.z.cash/zip-0316
146    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        // The test cases below use `Ufvk(vec![Fvk::Orchard([1; 96])])` as base.
244
245        // Invalid padding ([0xff; 16] instead of [b'u', 0x00, 0x00, 0x00...])
246        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        // Short padding (padded to 15 bytes instead of 16)
264        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        // The test cases below start from an encoding of
285        //     `Ufvk(vec![Fvk::Orchard([1; 96]), Fvk::Sapling([2; 96])])`
286        // with the fvk data truncated, but valid padding.
287
288        // - Missing the last data byte of the Sapling fvk.
289        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        // - Truncated after the typecode of the Sapling fvk.
312        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        // Construct and serialize an invalid Ufvk. This must be done using private
330        // methods, as the public API does not permit construction of such invalid values.
331        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        // Raw encoding of `Ufvk(vec![Fvk::P2pkh([0; 65])])`.
342        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        // Construct a UFVK with fvks in an unsorted order.
360        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        // `Ufvk::items` sorts the fvks in priority order.
371        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}