Skip to main content

zcash_address/kind/
unified.rs

1//! Implementation of [ZIP 316](https://zips.z.cash/zip-0316) Unified Addresses and Viewing Keys.
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use core::cmp;
6use core::convert::{TryFrom, TryInto};
7use core::fmt;
8use core::num::TryFromIntError;
9
10#[cfg(feature = "std")]
11use std::error::Error;
12
13use bech32::{Bech32m, Checksum, Hrp, primitives::decode::CheckedHrpstring};
14
15use zcash_protocol::consensus::NetworkType;
16
17pub(crate) mod address;
18pub(crate) mod fvk;
19pub(crate) mod ivk;
20
21pub use address::{Address, Receiver};
22pub use fvk::{Fvk, Ufvk};
23pub use ivk::{Ivk, Uivk};
24
25const PADDING_LEN: usize = 16;
26
27/// The known Receiver and Viewing Key types.
28///
29/// The typecodes `0xFFFA..=0xFFFF` reserved for experiments are currently not
30/// distinguished from unknown values, and will be parsed as [`Typecode::Unknown`].
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub enum Typecode {
33    /// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
34    P2pkh,
35    /// A transparent P2SH address.
36    ///
37    /// This typecode cannot occur in a [`Ufvk`] or [`Uivk`].
38    P2sh,
39    /// A Sapling raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
40    Sapling,
41    /// An Orchard raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
42    Orchard,
43    /// An unknown or experimental typecode.
44    Unknown(u32),
45}
46
47impl Typecode {
48    pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
49        match (a, b) {
50            // Trivial equality checks.
51            (Self::Orchard, Self::Orchard)
52            | (Self::Sapling, Self::Sapling)
53            | (Self::P2sh, Self::P2sh)
54            | (Self::P2pkh, Self::P2pkh) => cmp::Ordering::Equal,
55
56            // We don't know for certain the preference order of unknown items, but it
57            // is likely that the higher typecode has higher preference. The exact order
58            // doesn't really matter, as unknown items have lower preference than
59            // known items.
60            (Self::Unknown(a), Self::Unknown(b)) => b.cmp(a),
61
62            // For the remaining cases, we rely on `match` always choosing the first arm
63            // with a matching pattern. Patterns below are listed in priority order:
64            (Self::Orchard, _) => cmp::Ordering::Less,
65            (_, Self::Orchard) => cmp::Ordering::Greater,
66
67            (Self::Sapling, _) => cmp::Ordering::Less,
68            (_, Self::Sapling) => cmp::Ordering::Greater,
69
70            (Self::P2sh, _) => cmp::Ordering::Less,
71            (_, Self::P2sh) => cmp::Ordering::Greater,
72
73            (Self::P2pkh, _) => cmp::Ordering::Less,
74            (_, Self::P2pkh) => cmp::Ordering::Greater,
75        }
76    }
77
78    pub fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
79        u32::from(*a).cmp(&u32::from(*b))
80    }
81}
82
83impl TryFrom<u32> for Typecode {
84    type Error = ParseError;
85
86    fn try_from(typecode: u32) -> Result<Self, Self::Error> {
87        match typecode {
88            0x00 => Ok(Typecode::P2pkh),
89            0x01 => Ok(Typecode::P2sh),
90            0x02 => Ok(Typecode::Sapling),
91            0x03 => Ok(Typecode::Orchard),
92            0x04..=0x02000000 => Ok(Typecode::Unknown(typecode)),
93            0x02000001..=u32::MAX => Err(ParseError::InvalidTypecodeValue(u64::from(typecode))),
94        }
95    }
96}
97
98impl From<Typecode> for u32 {
99    fn from(t: Typecode) -> Self {
100        match t {
101            Typecode::P2pkh => 0x00,
102            Typecode::P2sh => 0x01,
103            Typecode::Sapling => 0x02,
104            Typecode::Orchard => 0x03,
105            Typecode::Unknown(typecode) => typecode,
106        }
107    }
108}
109
110impl TryFrom<Typecode> for usize {
111    type Error = TryFromIntError;
112    fn try_from(t: Typecode) -> Result<Self, Self::Error> {
113        u32::from(t).try_into()
114    }
115}
116
117impl Typecode {
118    fn is_transparent(&self) -> bool {
119        // Unknown typecodes are treated as not transparent for the purpose of disallowing
120        // only-transparent UAs, which can be represented with existing address encodings.
121        matches!(self, Typecode::P2pkh | Typecode::P2sh)
122    }
123}
124
125/// An error while attempting to parse a string as a Zcash address.
126#[derive(Debug, PartialEq, Eq)]
127pub enum ParseError {
128    /// The unified container contains both P2PKH and P2SH items.
129    BothP2phkAndP2sh,
130    /// The unified container contains a duplicated typecode.
131    DuplicateTypecode(Typecode),
132    /// The parsed typecode exceeds the maximum allowed CompactSize value.
133    InvalidTypecodeValue(u64),
134    /// The string is an invalid encoding.
135    InvalidEncoding(String),
136    /// The items in the unified container are not in typecode order.
137    InvalidTypecodeOrder,
138    /// The unified container only contains transparent items.
139    OnlyTransparent,
140    /// The string is not Bech32m encoded, and so cannot be a unified address.
141    NotUnified,
142    /// The Bech32m string has an unrecognized human-readable prefix.
143    UnknownPrefix(String),
144}
145
146impl fmt::Display for ParseError {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match self {
149            ParseError::BothP2phkAndP2sh => write!(f, "UA contains both P2PKH and P2SH items"),
150            ParseError::DuplicateTypecode(c) => write!(f, "Duplicate typecode {}", u32::from(*c)),
151            ParseError::InvalidTypecodeValue(v) => write!(f, "Typecode value out of range {v}"),
152            ParseError::InvalidEncoding(msg) => write!(f, "Invalid encoding: {msg}"),
153            ParseError::InvalidTypecodeOrder => write!(f, "Items are out of order."),
154            ParseError::OnlyTransparent => write!(f, "UA only contains transparent items"),
155            ParseError::NotUnified => write!(f, "Address is not Bech32m encoded"),
156            ParseError::UnknownPrefix(s) => {
157                write!(f, "Unrecognized Bech32m human-readable prefix: {s}")
158            }
159        }
160    }
161}
162
163#[cfg(feature = "std")]
164impl Error for ParseError {}
165
166pub(crate) mod private {
167    use alloc::borrow::ToOwned;
168    use alloc::vec::Vec;
169    use core::cmp;
170    use core::convert::{TryFrom, TryInto};
171    use corez::io::Write;
172
173    use super::{PADDING_LEN, ParseError, Typecode};
174    use zcash_encoding::CompactSize;
175    use zcash_protocol::consensus::NetworkType;
176
177    /// A raw address or viewing key.
178    pub trait SealedItem: for<'a> TryFrom<(u32, &'a [u8]), Error = ParseError> + Clone {
179        fn typecode(&self) -> Typecode;
180        fn data(&self) -> &[u8];
181
182        fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
183            match Typecode::preference_order(&a.typecode(), &b.typecode()) {
184                cmp::Ordering::Equal => a.data().cmp(b.data()),
185                res => res,
186            }
187        }
188
189        fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
190            match Typecode::encoding_order(&a.typecode(), &b.typecode()) {
191                cmp::Ordering::Equal => a.data().cmp(b.data()),
192                res => res,
193            }
194        }
195
196        fn write_raw_encoding<W: Write>(&self, mut writer: W) {
197            let data = self.data();
198            CompactSize::write(
199                &mut writer,
200                <u32>::from(self.typecode()).try_into().unwrap(),
201            )
202            .unwrap();
203            CompactSize::write(&mut writer, data.len()).unwrap();
204            writer.write_all(data).unwrap();
205        }
206    }
207
208    /// A Unified Container containing addresses or viewing keys.
209    pub trait SealedContainer: super::Container + core::marker::Sized {
210        const MAINNET: &'static str;
211        const TESTNET: &'static str;
212        const REGTEST: &'static str;
213
214        /// Implementations of this method should act as unchecked constructors
215        /// of the container type; the caller is guaranteed to check the
216        /// general invariants that apply to all unified containers.
217        fn from_inner(items: Vec<Self::Item>) -> Self;
218
219        fn network_hrp(network: &NetworkType) -> &'static str {
220            match network {
221                NetworkType::Main => Self::MAINNET,
222                NetworkType::Test => Self::TESTNET,
223                NetworkType::Regtest => Self::REGTEST,
224            }
225        }
226
227        fn hrp_network(hrp: &str) -> Option<NetworkType> {
228            if hrp == Self::MAINNET {
229                Some(NetworkType::Main)
230            } else if hrp == Self::TESTNET {
231                Some(NetworkType::Test)
232            } else if hrp == Self::REGTEST {
233                Some(NetworkType::Regtest)
234            } else {
235                None
236            }
237        }
238
239        fn write_raw_encoding<W: Write>(&self, mut writer: W) {
240            for item in self.items_as_parsed() {
241                item.write_raw_encoding(&mut writer);
242            }
243        }
244
245        /// Returns the jumbled padded raw encoding of this Unified Address or viewing key.
246        fn to_jumbled_bytes(&self, hrp: &str) -> Vec<u8> {
247            assert!(hrp.len() <= PADDING_LEN);
248
249            let mut padded = Vec::new();
250            self.write_raw_encoding(&mut padded);
251
252            let mut padding = [0u8; PADDING_LEN];
253            padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
254            padded.write_all(&padding).unwrap();
255
256            f4jumble::f4jumble(&padded)
257                .unwrap_or_else(|e| panic!("f4jumble failed on {:?}: {}", padded, e))
258        }
259
260        /// Parse the items of the unified container.
261        fn parse_items<T: Into<Vec<u8>>>(hrp: &str, buf: T) -> Result<Vec<Self::Item>, ParseError> {
262            fn read_receiver<R: SealedItem>(
263                mut cursor: &mut corez::io::Cursor<&[u8]>,
264            ) -> Result<R, ParseError> {
265                let typecode = CompactSize::read(&mut cursor)
266                    .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit"))
267                    .map_err(|e| {
268                        ParseError::InvalidEncoding(format!(
269                            "Failed to deserialize CompactSize-encoded typecode {e}"
270                        ))
271                    })?;
272                let length = CompactSize::read(&mut cursor).map_err(|e| {
273                    ParseError::InvalidEncoding(format!(
274                        "Failed to deserialize CompactSize-encoded length {e}"
275                    ))
276                })?;
277                let addr_end = cursor.position().checked_add(length).ok_or_else(|| {
278                    ParseError::InvalidEncoding(format!(
279                        "Length value {length} caused an overflow error"
280                    ))
281                })?;
282                let buf = cursor.get_ref();
283                if (buf.len() as u64) < addr_end {
284                    return Err(ParseError::InvalidEncoding(format!(
285                        "Truncated: unable to read {length} bytes of item data"
286                    )));
287                }
288                let result = R::try_from((
289                    typecode,
290                    &buf[cursor.position() as usize..addr_end as usize],
291                ));
292                cursor.set_position(addr_end);
293                result
294            }
295
296            // Here we allocate if necessary to get a mutable Vec<u8> to unjumble.
297            let mut encoded = buf.into();
298            f4jumble::f4jumble_inv_mut(&mut encoded[..]).map_err(|e| {
299                ParseError::InvalidEncoding(format!("F4Jumble decoding failed: {e}"))
300            })?;
301
302            // Validate and strip trailing padding bytes.
303            if hrp.len() > 16 {
304                return Err(ParseError::InvalidEncoding(
305                    "Invalid human-readable part".to_owned(),
306                ));
307            }
308            let mut expected_padding = [0; PADDING_LEN];
309            expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
310            let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) {
311                (encoded, tail) if tail == expected_padding => Ok(encoded),
312                _ => Err(ParseError::InvalidEncoding(
313                    "Invalid padding bytes".to_owned(),
314                )),
315            }?;
316
317            let mut cursor = corez::io::Cursor::new(encoded);
318            let mut result = vec![];
319            while cursor.position() < encoded.len().try_into().unwrap() {
320                result.push(read_receiver(&mut cursor)?);
321            }
322            assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
323
324            Ok(result)
325        }
326
327        /// A private function that constructs a unified container with the
328        /// specified items, which must be in ascending typecode order.
329        fn try_from_items_internal(items: Vec<Self::Item>) -> Result<Self, ParseError> {
330            assert!(u32::from(Typecode::P2sh) == u32::from(Typecode::P2pkh) + 1);
331
332            let mut only_transparent = true;
333            let mut prev_code = None; // less than any Some
334            for item in &items {
335                let t = item.typecode();
336                let t_code = Some(u32::from(t));
337                if t_code < prev_code {
338                    return Err(ParseError::InvalidTypecodeOrder);
339                } else if t_code == prev_code {
340                    return Err(ParseError::DuplicateTypecode(t));
341                } else if t == Typecode::P2sh && prev_code == Some(u32::from(Typecode::P2pkh)) {
342                    // P2pkh and P2sh can only be in that order and next to each other,
343                    // otherwise we would detect an out-of-order or duplicate typecode.
344                    return Err(ParseError::BothP2phkAndP2sh);
345                } else {
346                    prev_code = t_code;
347                    only_transparent = only_transparent && t.is_transparent();
348                }
349            }
350
351            if only_transparent {
352                Err(ParseError::OnlyTransparent)
353            } else {
354                // All checks pass!
355                Ok(Self::from_inner(items))
356            }
357        }
358
359        fn parse_internal<T: Into<Vec<u8>>>(hrp: &str, buf: T) -> Result<Self, ParseError> {
360            Self::parse_items(hrp, buf).and_then(Self::try_from_items_internal)
361        }
362    }
363}
364
365use private::SealedItem;
366
367/// The bech32m checksum algorithm, defined in [BIP-350], extended to allow all lengths
368/// supported by [ZIP 316].
369///
370/// [BIP-350]: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
371/// [ZIP 316]: https://zips.z.cash/zip-0316#solution
372#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
373pub enum Bech32mZip316 {}
374impl Checksum for Bech32mZip316 {
375    type MidstateRepr = <Bech32m as Checksum>::MidstateRepr;
376    // l^MAX from ZIP 316.
377    const CODE_LENGTH: usize = 4194368;
378    const CHECKSUM_LENGTH: usize = Bech32m::CHECKSUM_LENGTH;
379    const GENERATOR_SH: [u32; 5] = Bech32m::GENERATOR_SH;
380    const TARGET_RESIDUE: u32 = Bech32m::TARGET_RESIDUE;
381}
382
383/// Trait providing common encoding and decoding logic for Unified containers.
384pub trait Encoding: private::SealedContainer {
385    /// Constructs a value of a unified container type from a vector
386    /// of container items, sorted according to typecode as specified
387    /// in ZIP 316.
388    ///
389    /// This function will return an error in the case that the following ZIP 316
390    /// invariants concerning the composition of a unified container are
391    /// violated:
392    /// * the item list may not contain two items having the same typecode
393    /// * the item list may not contain only transparent items (or no items)
394    /// * the item list may not contain both P2PKH and P2SH items.
395    fn try_from_items(mut items: Vec<Self::Item>) -> Result<Self, ParseError> {
396        items.sort_unstable_by(Self::Item::encoding_order);
397        Self::try_from_items_internal(items)
398    }
399
400    /// Decodes a unified container from its string representation, preserving
401    /// the order of its components so that it correctly obeys round-trip
402    /// serialization invariants.
403    fn decode(s: &str) -> Result<(NetworkType, Self), ParseError> {
404        if let Ok(parsed) = CheckedHrpstring::new::<Bech32mZip316>(s) {
405            let hrp = parsed.hrp();
406            let hrp = hrp.as_str();
407            // validate that the HRP corresponds to a known network.
408            let net =
409                Self::hrp_network(hrp).ok_or_else(|| ParseError::UnknownPrefix(hrp.to_string()))?;
410
411            let data = parsed.byte_iter().collect::<Vec<_>>();
412
413            Self::parse_internal(hrp, data).map(|value| (net, value))
414        } else {
415            Err(ParseError::NotUnified)
416        }
417    }
418
419    /// Encodes the contents of the unified container to its string representation
420    /// using the correct constants for the specified network, preserving the
421    /// ordering of the contained items such that it correctly obeys round-trip
422    /// serialization invariants.
423    fn encode(&self, network: &NetworkType) -> String {
424        let hrp = Self::network_hrp(network);
425        bech32::encode::<Bech32mZip316>(Hrp::parse_unchecked(hrp), &self.to_jumbled_bytes(hrp))
426            .expect("F4Jumble ensures length is short enough by construction")
427    }
428}
429
430/// Trait for Unified containers, that exposes the items within them.
431pub trait Container {
432    /// The type of item in this unified container.
433    type Item: Item;
434
435    /// Returns the items contained within this container, sorted in preference order.
436    fn items(&self) -> Vec<Self::Item> {
437        let mut items = self.items_as_parsed().to_vec();
438        // Unstable sorting is fine, because all items are guaranteed by construction
439        // to have distinct typecodes.
440        items.sort_unstable_by(Self::Item::preference_order);
441        items
442    }
443
444    /// Returns the items in the order they were parsed from the string encoding.
445    ///
446    /// This API is for advanced usage; in most cases you should use `Self::items`.
447    fn items_as_parsed(&self) -> &[Self::Item];
448}
449
450/// Trait for unified items, exposing specific methods on them.
451pub trait Item: SealedItem {
452    /// Returns the opaque typed encoding of this item.
453    ///
454    /// This is the same encoding used internally by [`Encoding::encode`].
455    /// This API is for advanced usage; in most cases you should not depend
456    /// on the typed encoding of items.
457    fn typed_encoding(&self) -> Vec<u8> {
458        let mut ret = vec![];
459        self.write_raw_encoding(&mut ret);
460        ret
461    }
462}
463
464impl<T: SealedItem> Item for T {}