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::{primitives::decode::CheckedHrpstring, Bech32m, Checksum, Hrp};
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(typecode as u64)),
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 core2::io::Write;
172
173    use super::{ParseError, Typecode, PADDING_LEN};
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 core2::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 {}",
270                            e
271                        ))
272                    })?;
273                let length = CompactSize::read(&mut cursor).map_err(|e| {
274                    ParseError::InvalidEncoding(format!(
275                        "Failed to deserialize CompactSize-encoded length {}",
276                        e
277                    ))
278                })?;
279                let addr_end = cursor.position().checked_add(length).ok_or_else(|| {
280                    ParseError::InvalidEncoding(format!(
281                        "Length value {} caused an overflow error",
282                        length
283                    ))
284                })?;
285                let buf = cursor.get_ref();
286                if (buf.len() as u64) < addr_end {
287                    return Err(ParseError::InvalidEncoding(format!(
288                        "Truncated: unable to read {} bytes of item data",
289                        length
290                    )));
291                }
292                let result = R::try_from((
293                    typecode,
294                    &buf[cursor.position() as usize..addr_end as usize],
295                ));
296                cursor.set_position(addr_end);
297                result
298            }
299
300            // Here we allocate if necessary to get a mutable Vec<u8> to unjumble.
301            let mut encoded = buf.into();
302            f4jumble::f4jumble_inv_mut(&mut encoded[..]).map_err(|e| {
303                ParseError::InvalidEncoding(format!("F4Jumble decoding failed: {}", e))
304            })?;
305
306            // Validate and strip trailing padding bytes.
307            if hrp.len() > 16 {
308                return Err(ParseError::InvalidEncoding(
309                    "Invalid human-readable part".to_owned(),
310                ));
311            }
312            let mut expected_padding = [0; PADDING_LEN];
313            expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
314            let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) {
315                (encoded, tail) if tail == expected_padding => Ok(encoded),
316                _ => Err(ParseError::InvalidEncoding(
317                    "Invalid padding bytes".to_owned(),
318                )),
319            }?;
320
321            let mut cursor = core2::io::Cursor::new(encoded);
322            let mut result = vec![];
323            while cursor.position() < encoded.len().try_into().unwrap() {
324                result.push(read_receiver(&mut cursor)?);
325            }
326            assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
327
328            Ok(result)
329        }
330
331        /// A private function that constructs a unified container with the
332        /// specified items, which must be in ascending typecode order.
333        fn try_from_items_internal(items: Vec<Self::Item>) -> Result<Self, ParseError> {
334            assert!(u32::from(Typecode::P2sh) == u32::from(Typecode::P2pkh) + 1);
335
336            let mut only_transparent = true;
337            let mut prev_code = None; // less than any Some
338            for item in &items {
339                let t = item.typecode();
340                let t_code = Some(u32::from(t));
341                if t_code < prev_code {
342                    return Err(ParseError::InvalidTypecodeOrder);
343                } else if t_code == prev_code {
344                    return Err(ParseError::DuplicateTypecode(t));
345                } else if t == Typecode::P2sh && prev_code == Some(u32::from(Typecode::P2pkh)) {
346                    // P2pkh and P2sh can only be in that order and next to each other,
347                    // otherwise we would detect an out-of-order or duplicate typecode.
348                    return Err(ParseError::BothP2phkAndP2sh);
349                } else {
350                    prev_code = t_code;
351                    only_transparent = only_transparent && t.is_transparent();
352                }
353            }
354
355            if only_transparent {
356                Err(ParseError::OnlyTransparent)
357            } else {
358                // All checks pass!
359                Ok(Self::from_inner(items))
360            }
361        }
362
363        fn parse_internal<T: Into<Vec<u8>>>(hrp: &str, buf: T) -> Result<Self, ParseError> {
364            Self::parse_items(hrp, buf).and_then(Self::try_from_items_internal)
365        }
366    }
367}
368
369use private::SealedItem;
370
371/// The bech32m checksum algorithm, defined in [BIP-350], extended to allow all lengths
372/// supported by [ZIP 316].
373///
374/// [BIP-350]: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
375/// [ZIP 316]: https://zips.z.cash/zip-0316#solution
376#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
377pub enum Bech32mZip316 {}
378impl Checksum for Bech32mZip316 {
379    type MidstateRepr = <Bech32m as Checksum>::MidstateRepr;
380    // l^MAX from ZIP 316.
381    const CODE_LENGTH: usize = 4194368;
382    const CHECKSUM_LENGTH: usize = Bech32m::CHECKSUM_LENGTH;
383    const GENERATOR_SH: [u32; 5] = Bech32m::GENERATOR_SH;
384    const TARGET_RESIDUE: u32 = Bech32m::TARGET_RESIDUE;
385}
386
387/// Trait providing common encoding and decoding logic for Unified containers.
388pub trait Encoding: private::SealedContainer {
389    /// Constructs a value of a unified container type from a vector
390    /// of container items, sorted according to typecode as specified
391    /// in ZIP 316.
392    ///
393    /// This function will return an error in the case that the following ZIP 316
394    /// invariants concerning the composition of a unified container are
395    /// violated:
396    /// * the item list may not contain two items having the same typecode
397    /// * the item list may not contain only transparent items (or no items)
398    /// * the item list may not contain both P2PKH and P2SH items.
399    fn try_from_items(mut items: Vec<Self::Item>) -> Result<Self, ParseError> {
400        items.sort_unstable_by(Self::Item::encoding_order);
401        Self::try_from_items_internal(items)
402    }
403
404    /// Decodes a unified container from its string representation, preserving
405    /// the order of its components so that it correctly obeys round-trip
406    /// serialization invariants.
407    fn decode(s: &str) -> Result<(NetworkType, Self), ParseError> {
408        if let Ok(parsed) = CheckedHrpstring::new::<Bech32mZip316>(s) {
409            let hrp = parsed.hrp();
410            let hrp = hrp.as_str();
411            // validate that the HRP corresponds to a known network.
412            let net =
413                Self::hrp_network(hrp).ok_or_else(|| ParseError::UnknownPrefix(hrp.to_string()))?;
414
415            let data = parsed.byte_iter().collect::<Vec<_>>();
416
417            Self::parse_internal(hrp, data).map(|value| (net, value))
418        } else {
419            Err(ParseError::NotUnified)
420        }
421    }
422
423    /// Encodes the contents of the unified container to its string representation
424    /// using the correct constants for the specified network, preserving the
425    /// ordering of the contained items such that it correctly obeys round-trip
426    /// serialization invariants.
427    fn encode(&self, network: &NetworkType) -> String {
428        let hrp = Self::network_hrp(network);
429        bech32::encode::<Bech32mZip316>(Hrp::parse_unchecked(hrp), &self.to_jumbled_bytes(hrp))
430            .expect("F4Jumble ensures length is short enough by construction")
431    }
432}
433
434/// Trait for Unified containers, that exposes the items within them.
435pub trait Container {
436    /// The type of item in this unified container.
437    type Item: Item;
438
439    /// Returns the items contained within this container, sorted in preference order.
440    fn items(&self) -> Vec<Self::Item> {
441        let mut items = self.items_as_parsed().to_vec();
442        // Unstable sorting is fine, because all items are guaranteed by construction
443        // to have distinct typecodes.
444        items.sort_unstable_by(Self::Item::preference_order);
445        items
446    }
447
448    /// Returns the items in the order they were parsed from the string encoding.
449    ///
450    /// This API is for advanced usage; in most cases you should use `Self::items`.
451    fn items_as_parsed(&self) -> &[Self::Item];
452}
453
454/// Trait for unified items, exposing specific methods on them.
455pub trait Item: SealedItem {
456    /// Returns the opaque typed encoding of this item.
457    ///
458    /// This is the same encoding used internally by [`Encoding::encode`].
459    /// This API is for advanced usage; in most cases you should not depend
460    /// on the typed encoding of items.
461    fn typed_encoding(&self) -> Vec<u8> {
462        let mut ret = vec![];
463        self.write_raw_encoding(&mut ret);
464        ret
465    }
466}
467
468impl<T: SealedItem> Item for T {}