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};
24pub use zcash_protocol::address::Revision;
25
26#[cfg(feature = "test-dependencies")]
27pub use address::testing;
28
29const PADDING_LEN: usize = 16;
30
31/// Typecodes for data items (receivers / viewing keys).
32///
33/// The derived ordering coincides with ascending typecode value order (the canonical
34/// encoding order of the corresponding items) for all values constructible by this
35/// crate's parsers, as an `Unknown` typecode never holds a known code's value.
36#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub enum DataTypecode {
38    /// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316].
39    ///
40    /// [ZIP 316]: https://zips.z.cash/zip-0316
41    P2pkh,
42    /// In an address, a transparent P2SH address as specified in [ZIP 316].
43    ///
44    /// In a Revision 2 viewing key, a P2SH viewing key item: the encoding of a [BIP 388]
45    /// wallet policy, whose descriptor template and key information vector together
46    /// determine the redeem script a P2SH receiver is derived from. Revision 0 assigns
47    /// this typecode no meaning in a viewing key.
48    ///
49    /// [ZIP 316]: https://zips.z.cash/zip-0316
50    /// [BIP 388]: https://github.com/bitcoin/bips/blob/master/bip-0388.mediawiki
51    P2sh,
52    /// A Sapling raw address, FVK, or IVK encoding as specified in [ZIP 316].
53    ///
54    /// [ZIP 316]: https://zips.z.cash/zip-0316
55    Sapling,
56    /// An Orchard raw address, FVK, or IVK encoding as specified in [ZIP 316].
57    ///
58    /// [ZIP 316]: https://zips.z.cash/zip-0316
59    Orchard,
60    /// An unknown data typecode.
61    Unknown(u32),
62}
63
64impl DataTypecode {
65    pub fn is_transparent(&self) -> bool {
66        matches!(self, DataTypecode::P2pkh | DataTypecode::P2sh)
67    }
68
69    pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
70        use DataTypecode::*;
71        match (a, b) {
72            (Orchard, Orchard) | (Sapling, Sapling) | (P2sh, P2sh) | (P2pkh, P2pkh) => {
73                cmp::Ordering::Equal
74            }
75
76            (Unknown(a), Unknown(b)) => b.cmp(a),
77
78            (Orchard, _) => cmp::Ordering::Less,
79            (_, Orchard) => cmp::Ordering::Greater,
80
81            (Sapling, _) => cmp::Ordering::Less,
82            (_, Sapling) => cmp::Ordering::Greater,
83
84            (P2sh, _) => cmp::Ordering::Less,
85            (_, P2sh) => cmp::Ordering::Greater,
86
87            (P2pkh, _) => cmp::Ordering::Less,
88            (_, P2pkh) => cmp::Ordering::Greater,
89        }
90    }
91}
92
93impl From<DataTypecode> for u32 {
94    fn from(t: DataTypecode) -> Self {
95        match t {
96            DataTypecode::P2pkh => 0x00,
97            DataTypecode::P2sh => 0x01,
98            DataTypecode::Sapling => 0x02,
99            DataTypecode::Orchard => 0x03,
100            DataTypecode::Unknown(tc) => tc,
101        }
102    }
103}
104
105/// Typecodes for metadata items (0xC0..=0xFC).
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107pub enum MetadataTypecode {
108    /// Expiry height (typecode 0xE0). 4-byte little-endian block height.
109    ExpiryHeight,
110    /// Expiry time (typecode 0xE1). 8-byte little-endian Unix timestamp.
111    ExpiryTime,
112    /// An unknown metadata typecode.
113    Unknown(u32),
114}
115
116impl From<MetadataTypecode> for u32 {
117    fn from(t: MetadataTypecode) -> Self {
118        match t {
119            MetadataTypecode::ExpiryHeight => 0xE0,
120            MetadataTypecode::ExpiryTime => 0xE1,
121            MetadataTypecode::Unknown(tc) => tc,
122        }
123    }
124}
125
126/// The known Receiver and Viewing Key types.
127///
128/// This typecode covers both data items (receivers, viewing keys) and metadata items
129/// as defined in [ZIP 316](https://zips.z.cash/zip-0316).
130///
131/// The typecodes `0xFFFA..=0xFFFF` reserved for experiments are currently not
132/// distinguished from unknown values, and will be parsed as [`Typecode::Data`]`(`[`DataTypecode::Unknown`]`)`.
133#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
134pub enum Typecode {
135    /// A data item (receiver or viewing key).
136    Data(DataTypecode),
137    /// A metadata item.
138    Metadata(MetadataTypecode),
139}
140
141// Convenience associated constants for backward compatibility.
142impl Typecode {
143    /// P2PKH data typecode.
144    pub const P2PKH: Typecode = Typecode::Data(DataTypecode::P2pkh);
145    /// P2SH data typecode.
146    pub const P2SH: Typecode = Typecode::Data(DataTypecode::P2sh);
147    /// Sapling data typecode.
148    pub const SAPLING: Typecode = Typecode::Data(DataTypecode::Sapling);
149    /// Orchard data typecode.
150    pub const ORCHARD: Typecode = Typecode::Data(DataTypecode::Orchard);
151}
152
153impl From<DataTypecode> for Typecode {
154    fn from(tc: DataTypecode) -> Self {
155        Typecode::Data(tc)
156    }
157}
158
159impl From<MetadataTypecode> for Typecode {
160    fn from(tc: MetadataTypecode) -> Self {
161        Typecode::Metadata(tc)
162    }
163}
164
165/// Boundary between "SHOULD-understand" (unknown) metadata and "MUST-understand" metadata.
166const MUST_UNDERSTAND_METADATA_MIN: u32 = 0xE0;
167/// Maximum metadata typecode value. Values >= 0xFD are reserved.
168const METADATA_TYPECODE_MAX: u32 = 0xFC;
169/// Minimum metadata typecode value.
170const METADATA_TYPECODE_MIN: u32 = 0xC0;
171
172impl Typecode {
173    /// Returns the numeric typecode value.
174    pub fn typecode_value(&self) -> u32 {
175        match self {
176            Typecode::Data(tc) => u32::from(*tc),
177            Typecode::Metadata(tc) => u32::from(*tc),
178        }
179    }
180
181    pub fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
182        use DataTypecode::*;
183        match (a, b) {
184            // Data items always have preference over metadata.
185            (Typecode::Data(_), Typecode::Metadata(_)) => cmp::Ordering::Less,
186            (Typecode::Metadata(_), Typecode::Data(_)) => cmp::Ordering::Greater,
187
188            // Metadata items: order by typecode.
189            (Typecode::Metadata(a), Typecode::Metadata(b)) => u32::from(*a).cmp(&u32::from(*b)),
190
191            // Data items: known items in priority order.
192            (Typecode::Data(a), Typecode::Data(b)) => match (a, b) {
193                (Orchard, Orchard) | (Sapling, Sapling) | (P2sh, P2sh) | (P2pkh, P2pkh) => {
194                    cmp::Ordering::Equal
195                }
196
197                (Unknown(a), Unknown(b)) => b.cmp(a),
198
199                (Orchard, _) => cmp::Ordering::Less,
200                (_, Orchard) => cmp::Ordering::Greater,
201
202                (Sapling, _) => cmp::Ordering::Less,
203                (_, Sapling) => cmp::Ordering::Greater,
204
205                (P2sh, _) => cmp::Ordering::Less,
206                (_, P2sh) => cmp::Ordering::Greater,
207
208                (P2pkh, _) => cmp::Ordering::Less,
209                (_, P2pkh) => cmp::Ordering::Greater,
210            },
211        }
212    }
213
214    pub fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
215        a.typecode_value().cmp(&b.typecode_value())
216    }
217
218    pub fn is_transparent_data(&self) -> bool {
219        match self {
220            Typecode::Data(tc) => tc.is_transparent(),
221            Typecode::Metadata(_) => false,
222        }
223    }
224}
225
226impl TryFrom<u32> for Typecode {
227    type Error = ParseError;
228
229    fn try_from(typecode: u32) -> Result<Self, Self::Error> {
230        match typecode {
231            0x00 => Ok(Typecode::Data(DataTypecode::P2pkh)),
232            0x01 => Ok(Typecode::Data(DataTypecode::P2sh)),
233            0x02 => Ok(Typecode::Data(DataTypecode::Sapling)),
234            0x03 => Ok(Typecode::Data(DataTypecode::Orchard)),
235            0x04..=0xBF => Ok(Typecode::Data(DataTypecode::Unknown(typecode))),
236            tc @ METADATA_TYPECODE_MIN..=METADATA_TYPECODE_MAX => {
237                match tc {
238                    0xE0 => Ok(Typecode::Metadata(MetadataTypecode::ExpiryHeight)),
239                    0xE1 => Ok(Typecode::Metadata(MetadataTypecode::ExpiryTime)),
240                    // 0xC0..=0xDF: unknown SHOULD-understand metadata
241                    // 0xE2..=0xFC: unknown MUST-understand metadata
242                    _ => Ok(Typecode::Metadata(MetadataTypecode::Unknown(tc))),
243                }
244            }
245            0xFD..=0x02000000 => Ok(Typecode::Data(DataTypecode::Unknown(typecode))),
246            0x02000001..=u32::MAX => Err(ParseError::InvalidTypecodeValue(u64::from(typecode))),
247        }
248    }
249}
250
251impl From<Typecode> for u32 {
252    fn from(t: Typecode) -> Self {
253        t.typecode_value()
254    }
255}
256
257impl TryFrom<Typecode> for usize {
258    type Error = TryFromIntError;
259    fn try_from(t: Typecode) -> Result<Self, Self::Error> {
260        u32::from(t).try_into()
261    }
262}
263
264/// A parsed metadata item.
265#[derive(Clone, Debug, PartialEq, Eq, Hash)]
266pub enum MetadataItem {
267    /// An expiry height, encoded as a 4-byte little-endian block height.
268    ExpiryHeight(u32),
269    /// An expiry time, encoded as an 8-byte little-endian Unix timestamp.
270    ExpiryTime(u64),
271    /// An unknown metadata item.
272    Unknown { typecode: u32, data: Vec<u8> },
273}
274
275impl MetadataItem {
276    /// Returns the typecode for this metadata item.
277    pub fn typecode(&self) -> MetadataTypecode {
278        match self {
279            MetadataItem::ExpiryHeight(_) => MetadataTypecode::ExpiryHeight,
280            MetadataItem::ExpiryTime(_) => MetadataTypecode::ExpiryTime,
281            MetadataItem::Unknown { typecode, .. } => MetadataTypecode::Unknown(*typecode),
282        }
283    }
284
285    /// Returns the raw data bytes for this metadata item.
286    pub fn data(&self) -> Vec<u8> {
287        match self {
288            MetadataItem::ExpiryHeight(h) => h.to_le_bytes().to_vec(),
289            MetadataItem::ExpiryTime(t) => t.to_le_bytes().to_vec(),
290            MetadataItem::Unknown { data, .. } => data.clone(),
291        }
292    }
293
294    /// Returns the combined typecode for this metadata item.
295    pub fn combined_typecode(&self) -> Typecode {
296        Typecode::Metadata(self.typecode())
297    }
298}
299
300/// An item within a unified container, which can be either a data item or a metadata item.
301#[derive(Clone, Debug, PartialEq, Eq, Hash)]
302pub enum Uitem<T> {
303    /// A data item (receiver or viewing key).
304    Data(T),
305    /// A metadata item.
306    Metadata(MetadataItem),
307}
308
309impl<T: private::SealedItem> Uitem<T> {
310    /// Compares items by the canonical encoding order of a unified container:
311    /// ascending typecode value, with ties broken by the raw item data.
312    pub(crate) fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
313        fn typecode_value<T: private::SealedItem>(item: &Uitem<T>) -> u32 {
314            match item {
315                Uitem::Data(d) => u32::from(d.typecode()),
316                Uitem::Metadata(m) => u32::from(m.typecode()),
317            }
318        }
319
320        typecode_value(a)
321            .cmp(&typecode_value(b))
322            .then_with(|| match (a, b) {
323                (Uitem::Data(a), Uitem::Data(b)) => a.data().cmp(b.data()),
324                (Uitem::Metadata(a), Uitem::Metadata(b)) => a.data().cmp(&b.data()),
325                // The typecode ranges for data and metadata items are disjoint, so
326                // items with equal typecodes are of the same kind.
327                _ => cmp::Ordering::Equal,
328            })
329    }
330}
331
332/// The kinds of unified viewing key container in which a P2SH viewing key item can
333/// occur, determining the multipath notation its descriptor template must use.
334#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
335pub enum P2shItemKind {
336    /// An item in a Unified Full Viewing Key, whose descriptor template must use the
337    /// `/**` multipath notation for each key placeholder.
338    FullViewing,
339    /// An item in a Unified Incoming Viewing Key, whose descriptor template must use the
340    /// `/*` notation for each key placeholder.
341    IncomingViewing,
342}
343
344/// Errors in the structure of a ZIP 316 Revision 2 P2SH viewing key item.
345///
346/// The item payload encodes a [BIP 388] wallet policy: a descriptor template followed by
347/// a vector of key information entries. Only the structure of the payload is validated;
348/// full validation of the descriptor template against the BIP 388 grammar is the
349/// responsibility of consumers that interpret the policy.
350///
351/// [BIP 388]: https://github.com/bitcoin/bips/blob/master/bip-0388.mediawiki
352#[non_exhaustive]
353#[derive(Clone, Copy, Debug, PartialEq, Eq)]
354pub enum P2shItemError {
355    /// The item payload is not a well-formed encoding of a descriptor template and key
356    /// information vector.
357    Malformed,
358    /// The descriptor template is not US-ASCII.
359    TemplateEncoding,
360    /// The key placeholders in the descriptor template do not correspond one-to-one
361    /// with the entries of the key information vector.
362    PlaceholderCount {
363        /// The number of `@N` key placeholders in the descriptor template.
364        placeholders: usize,
365        /// The number of entries in the key information vector.
366        keys: usize,
367    },
368    /// A key placeholder is not followed by the multipath notation required for the
369    /// containing key kind (`/**` in a UFVK, `/*` in a UIVK).
370    Multipath,
371}
372
373impl fmt::Display for P2shItemError {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        match self {
376            P2shItemError::Malformed => {
377                write!(f, "malformed wallet policy container encoding")
378            }
379            P2shItemError::TemplateEncoding => {
380                write!(f, "descriptor template is not US-ASCII")
381            }
382            P2shItemError::PlaceholderCount { placeholders, keys } => {
383                write!(
384                    f,
385                    "template has {placeholders} key placeholders but {keys} key entries"
386                )
387            }
388            P2shItemError::Multipath => {
389                write!(
390                    f,
391                    "a key placeholder does not use the required multipath notation"
392                )
393            }
394        }
395    }
396}
397
398#[cfg(feature = "std")]
399impl Error for P2shItemError {}
400
401/// An error while attempting to parse a string as a Zcash address.
402#[derive(Debug, PartialEq, Eq)]
403pub enum ParseError {
404    /// The unified container contains both P2PKH and P2SH items.
405    BothP2phkAndP2sh,
406    /// The unified container contains a duplicated typecode.
407    DuplicateTypecode(Typecode),
408    /// The parsed typecode exceeds the maximum allowed CompactSize value.
409    InvalidTypecodeValue(u64),
410    /// The string is an invalid encoding.
411    InvalidEncoding(String),
412    /// The items in the unified container are not in typecode order.
413    InvalidTypecodeOrder,
414    /// The unified container only contains transparent items.
415    OnlyTransparent,
416    /// The string is not Bech32m encoded, and so cannot be a unified address.
417    NotUnified,
418    /// The Bech32m string has an unrecognized human-readable prefix.
419    UnknownPrefix(String),
420    /// A MUST-understand metadata typecode was encountered that this implementation
421    /// does not recognize.
422    NotUnderstood(u32),
423    /// A transparent receiver was found in a Revision 2 Unified Address.
424    TransparentReceiverInR2Address,
425    /// The unified container has no data items.
426    NoDataItems,
427    /// A metadata item has an invalid length.
428    InvalidMetadataLength {
429        typecode: u32,
430        expected: usize,
431        actual: usize,
432    },
433    /// A P2SH viewing key item is structurally invalid.
434    InvalidP2shItem(P2shItemError),
435}
436
437impl fmt::Display for ParseError {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        match self {
440            ParseError::BothP2phkAndP2sh => write!(f, "UA contains both P2PKH and P2SH items"),
441            ParseError::DuplicateTypecode(c) => write!(f, "Duplicate typecode {}", u32::from(*c)),
442            ParseError::InvalidTypecodeValue(v) => write!(f, "Typecode value out of range {v}"),
443            ParseError::InvalidEncoding(msg) => write!(f, "Invalid encoding: {msg}"),
444            ParseError::InvalidTypecodeOrder => write!(f, "Items are out of order."),
445            ParseError::OnlyTransparent => write!(f, "UA only contains transparent items"),
446            ParseError::NotUnified => write!(f, "Address is not Bech32m encoded"),
447            ParseError::UnknownPrefix(s) => {
448                write!(f, "Unrecognized Bech32m human-readable prefix: {s}")
449            }
450            ParseError::NotUnderstood(tc) => {
451                write!(
452                    f,
453                    "MUST-understand metadata typecode 0x{tc:02X} not recognized"
454                )
455            }
456            ParseError::TransparentReceiverInR2Address => {
457                write!(
458                    f,
459                    "Transparent receivers are not permitted in Revision 2 Unified Addresses"
460                )
461            }
462            ParseError::NoDataItems => {
463                write!(f, "Unified container has no data items")
464            }
465            ParseError::InvalidMetadataLength {
466                typecode,
467                expected,
468                actual,
469            } => {
470                write!(
471                    f,
472                    "Metadata typecode 0x{typecode:02X} has invalid length: expected {expected}, got {actual}"
473                )
474            }
475            ParseError::InvalidP2shItem(e) => {
476                write!(f, "Invalid P2SH viewing key item: {e}")
477            }
478        }
479    }
480}
481
482#[cfg(feature = "std")]
483impl Error for ParseError {}
484
485pub(crate) mod private {
486    use alloc::borrow::ToOwned;
487    use alloc::vec::Vec;
488    use core::cmp;
489    use core::convert::{TryFrom, TryInto};
490    use corez::io::Write;
491
492    use super::{
493        MUST_UNDERSTAND_METADATA_MIN, MetadataItem, MetadataTypecode, PADDING_LEN, ParseError,
494        Typecode, Uitem,
495    };
496    use zcash_encoding::CompactSize;
497    use zcash_protocol::address::Revision;
498    use zcash_protocol::consensus::NetworkType;
499
500    /// A raw address or viewing key (data item).
501    pub trait SealedItem: Clone {
502        fn parse(typecode: super::DataTypecode, data: &[u8]) -> Result<Self, ParseError>;
503        fn typecode(&self) -> super::DataTypecode;
504        fn data(&self) -> &[u8];
505
506        fn preference_order(a: &Self, b: &Self) -> cmp::Ordering {
507            match super::DataTypecode::preference_order(&a.typecode(), &b.typecode()) {
508                cmp::Ordering::Equal => a.data().cmp(b.data()),
509                res => res,
510            }
511        }
512
513        fn encoding_order(a: &Self, b: &Self) -> cmp::Ordering {
514            match u32::from(a.typecode()).cmp(&u32::from(b.typecode())) {
515                cmp::Ordering::Equal => a.data().cmp(b.data()),
516                res => res,
517            }
518        }
519
520        fn write_raw_encoding<W: Write>(&self, mut writer: W) -> corez::io::Result<()> {
521            let data = self.data();
522            CompactSize::write(
523                &mut writer,
524                <u32>::from(self.typecode())
525                    .try_into()
526                    .expect("a typecode fits in a usize"),
527            )?;
528            CompactSize::write(&mut writer, data.len())?;
529            writer.write_all(data)
530        }
531    }
532
533    /// Write a metadata item's raw encoding.
534    fn write_metadata_raw_encoding<W: Write>(
535        item: &MetadataItem,
536        mut writer: W,
537    ) -> corez::io::Result<()> {
538        let tc_val: u32 = item.typecode().into();
539        let data = item.data();
540        CompactSize::write(
541            &mut writer,
542            tc_val.try_into().expect("a typecode fits in a usize"),
543        )?;
544        CompactSize::write(&mut writer, data.len())?;
545        writer.write_all(&data)
546    }
547
548    /// A Unified Container containing addresses or viewing keys.
549    pub trait SealedContainer: super::Container + core::marker::Sized {
550        const MAINNET: &'static str;
551        const TESTNET: &'static str;
552        const REGTEST: &'static str;
553
554        const MAINNET_R2: &'static str;
555        const TESTNET_R2: &'static str;
556        const REGTEST_R2: &'static str;
557
558        /// HRP constants for transparent-including R2 addresses.
559        /// For non-address containers (UVKs), these are set to the same values as
560        /// the R2 constants since the distinction does not apply.
561        const MAINNET_R2_TI: &'static str;
562        const TESTNET_R2_TI: &'static str;
563        const REGTEST_R2_TI: &'static str;
564
565        /// Whether this container type is an Address container (as opposed to a viewing key).
566        const IS_ADDRESS: bool;
567
568        /// Implementations of this method should act as unchecked constructors
569        /// of the container type; the caller is guaranteed to check the
570        /// general invariants that apply to all unified containers.
571        fn from_inner(revision: Revision, items: Vec<Uitem<Self::Item>>) -> Self;
572
573        fn network_hrp(
574            network: &NetworkType,
575            revision: Revision,
576            has_transparent: bool,
577        ) -> &'static str {
578            match (network, revision) {
579                (NetworkType::Main, Revision::R0) => Self::MAINNET,
580                (NetworkType::Test, Revision::R0) => Self::TESTNET,
581                (NetworkType::Regtest, Revision::R0) => Self::REGTEST,
582                (NetworkType::Main, Revision::R2) => {
583                    if Self::IS_ADDRESS && has_transparent {
584                        Self::MAINNET_R2_TI
585                    } else {
586                        Self::MAINNET_R2
587                    }
588                }
589                (NetworkType::Test, Revision::R2) => {
590                    if Self::IS_ADDRESS && has_transparent {
591                        Self::TESTNET_R2_TI
592                    } else {
593                        Self::TESTNET_R2
594                    }
595                }
596                (NetworkType::Regtest, Revision::R2) => {
597                    if Self::IS_ADDRESS && has_transparent {
598                        Self::REGTEST_R2_TI
599                    } else {
600                        Self::REGTEST_R2
601                    }
602                }
603            }
604        }
605
606        fn hrp_network(hrp: &str) -> Option<(NetworkType, Revision)> {
607            if hrp == Self::MAINNET {
608                Some((NetworkType::Main, Revision::R0))
609            } else if hrp == Self::TESTNET {
610                Some((NetworkType::Test, Revision::R0))
611            } else if hrp == Self::REGTEST {
612                Some((NetworkType::Regtest, Revision::R0))
613            } else if hrp == Self::MAINNET_R2 {
614                Some((NetworkType::Main, Revision::R2))
615            } else if hrp == Self::TESTNET_R2 {
616                Some((NetworkType::Test, Revision::R2))
617            } else if hrp == Self::REGTEST_R2 {
618                Some((NetworkType::Regtest, Revision::R2))
619            } else if hrp == Self::MAINNET_R2_TI {
620                Some((NetworkType::Main, Revision::R2))
621            } else if hrp == Self::TESTNET_R2_TI {
622                Some((NetworkType::Test, Revision::R2))
623            } else if hrp == Self::REGTEST_R2_TI {
624                Some((NetworkType::Regtest, Revision::R2))
625            } else {
626                None
627            }
628        }
629
630        fn write_raw_encoding<W: Write>(&self, mut writer: W) -> corez::io::Result<()> {
631            for item in self.items_as_parsed() {
632                match item {
633                    Uitem::Data(data_item) => data_item.write_raw_encoding(&mut writer)?,
634                    Uitem::Metadata(meta_item) => {
635                        write_metadata_raw_encoding(meta_item, &mut writer)?;
636                    }
637                }
638            }
639            Ok(())
640        }
641
642        /// Returns the jumbled padded raw encoding of this Unified Address or viewing key.
643        fn to_jumbled_bytes(&self, hrp: &str) -> Vec<u8> {
644            assert!(hrp.len() <= PADDING_LEN);
645
646            let mut padded = Vec::new();
647            self.write_raw_encoding(&mut padded)
648                .expect("writing to a Vec cannot fail");
649
650            let mut padding = [0u8; PADDING_LEN];
651            padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
652            padded
653                .write_all(&padding)
654                .expect("writing to a Vec cannot fail");
655
656            f4jumble::f4jumble(&padded)
657                .unwrap_or_else(|e| panic!("f4jumble failed on {:?}: {}", padded, e))
658        }
659
660        /// Parse the items of the unified container, returning both data and metadata items.
661        fn parse_items<T: Into<Vec<u8>>>(
662            hrp: &str,
663            buf: T,
664            revision: Revision,
665        ) -> Result<Vec<Uitem<Self::Item>>, ParseError> {
666            fn read_raw_item(
667                mut cursor: &mut corez::io::Cursor<&[u8]>,
668            ) -> Result<(u32, Vec<u8>), ParseError> {
669                let typecode = CompactSize::read(&mut cursor)
670                    .map(|v| u32::try_from(v).expect("CompactSize::read enforces MAX_SIZE limit"))
671                    .map_err(|e| {
672                        ParseError::InvalidEncoding(format!(
673                            "Failed to deserialize CompactSize-encoded typecode {e}"
674                        ))
675                    })?;
676                let length = CompactSize::read(&mut cursor).map_err(|e| {
677                    ParseError::InvalidEncoding(format!(
678                        "Failed to deserialize CompactSize-encoded length {e}"
679                    ))
680                })?;
681                let addr_end = cursor.position().checked_add(length).ok_or_else(|| {
682                    ParseError::InvalidEncoding(format!(
683                        "Length value {length} caused an overflow error"
684                    ))
685                })?;
686                let buf = cursor.get_ref();
687                if (buf.len() as u64) < addr_end {
688                    return Err(ParseError::InvalidEncoding(format!(
689                        "Truncated: unable to read {length} bytes of item data"
690                    )));
691                }
692                let data = buf[cursor.position() as usize..addr_end as usize].to_vec();
693                cursor.set_position(addr_end);
694                Ok((typecode, data))
695            }
696
697            // Here we allocate if necessary to get a mutable Vec<u8> to unjumble.
698            let mut encoded = buf.into();
699            f4jumble::f4jumble_inv_mut(&mut encoded[..]).map_err(|e| {
700                ParseError::InvalidEncoding(format!("F4Jumble decoding failed: {e}"))
701            })?;
702
703            // Validate and strip trailing padding bytes.
704            if hrp.len() > 16 {
705                return Err(ParseError::InvalidEncoding(
706                    "Invalid human-readable part".to_owned(),
707                ));
708            }
709            let mut expected_padding = [0; PADDING_LEN];
710            expected_padding[0..hrp.len()].copy_from_slice(hrp.as_bytes());
711            let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) {
712                (encoded, tail) if tail == expected_padding => Ok(encoded),
713                _ => Err(ParseError::InvalidEncoding(
714                    "Invalid padding bytes".to_owned(),
715                )),
716            }?;
717
718            let mut cursor = corez::io::Cursor::new(encoded);
719            let mut result = vec![];
720            while cursor.position() < encoded.len().try_into().unwrap() {
721                let (tc_val, data) = read_raw_item(&mut cursor)?;
722
723                match Typecode::try_from(tc_val)? {
724                    Typecode::Data(dtc) => {
725                        // ZIP 316 gives Typecode 0x01 no meaning in a Revision 0 viewing
726                        // key, so a consumer must treat it as unrecognised instead of as
727                        // a P2SH viewing key item. In an address, 0x01 is a P2SH receiver
728                        // in every revision.
729                        let dtc = if revision == Revision::R0
730                            && !Self::IS_ADDRESS
731                            && dtc == super::DataTypecode::P2sh
732                        {
733                            super::DataTypecode::Unknown(u32::from(dtc))
734                        } else {
735                            dtc
736                        };
737                        result.push(Uitem::Data(Self::Item::parse(dtc, &data)?));
738                    }
739                    Typecode::Metadata(mtc) => {
740                        result.push(Uitem::Metadata(parse_metadata_item(revision, mtc, data)?));
741                    }
742                }
743            }
744            assert_eq!(cursor.position(), encoded.len().try_into().unwrap());
745
746            Ok(result)
747        }
748
749        /// A private function that constructs a unified container with the
750        /// specified items, which must be in ascending typecode order.
751        fn try_from_items_internal(
752            revision: Revision,
753            items: Vec<Uitem<Self::Item>>,
754        ) -> Result<Self, ParseError> {
755            assert!(u32::from(Typecode::P2SH) == u32::from(Typecode::P2PKH) + 1);
756
757            let mut has_data_item = false;
758            let mut only_transparent = true;
759            let mut prev_code: Option<u32> = None;
760            for item in &items {
761                let t = match item {
762                    Uitem::Data(d) => Typecode::Data(d.typecode()),
763                    Uitem::Metadata(m) => m.combined_typecode(),
764                };
765                let t_code = Some(t.typecode_value());
766
767                if t_code < prev_code {
768                    return Err(ParseError::InvalidTypecodeOrder);
769                } else if t_code == prev_code {
770                    return Err(ParseError::DuplicateTypecode(t));
771                }
772
773                if let Uitem::Data(d) = item {
774                    has_data_item = true;
775                    let dt = d.typecode();
776                    if dt == super::DataTypecode::P2sh
777                        && prev_code == Some(u32::from(super::DataTypecode::P2pkh))
778                    {
779                        return Err(ParseError::BothP2phkAndP2sh);
780                    }
781
782                    if !dt.is_transparent() {
783                        only_transparent = false;
784                    }
785                }
786
787                prev_code = t_code;
788            }
789
790            // A container of any revision must contain at least one data item; one that
791            // holds only metadata items is rejected.
792            if !has_data_item {
793                return Err(ParseError::NoDataItems);
794            }
795
796            // A Revision 0 container must also contain at least one shielded item. This
797            // requirement is dropped for Revision 2, where the `zu`/`tu` HRP-content
798            // rules apply instead; those are enforced in `parse_internal` for decoding,
799            // and in `encode` for encoding.
800            if revision == Revision::R0 && only_transparent {
801                return Err(ParseError::OnlyTransparent);
802            }
803
804            Ok(Self::from_inner(revision, items))
805        }
806
807        fn parse_internal<T: Into<Vec<u8>>>(
808            hrp: &str,
809            buf: T,
810            revision: Revision,
811        ) -> Result<Self, ParseError> {
812            let result = Self::parse_items(hrp, buf, revision)
813                .and_then(|items| Self::try_from_items_internal(revision, items))?;
814
815            // Enforce HRP-content consistency for R2 addresses.
816            if revision == Revision::R2 && Self::IS_ADDRESS {
817                let is_ti_hrp = hrp == Self::MAINNET_R2_TI
818                    || hrp == Self::TESTNET_R2_TI
819                    || hrp == Self::REGTEST_R2_TI;
820
821                let has_transparent = result
822                    .items_as_parsed()
823                    .iter()
824                    .any(|item| matches!(item, Uitem::Data(d) if d.typecode().is_transparent()));
825
826                // A `zu` address must not contain any transparent receiver. ZIP 316
827                // imposes no further constraint on its contents: a `zu` address whose
828                // only data item is of an unrecognised type is well-formed. A `tu`
829                // address has no additional constraints beyond those enforced by
830                // `try_from_items_internal`.
831                if !is_ti_hrp && has_transparent {
832                    return Err(ParseError::TransparentReceiverInR2Address);
833                }
834            }
835
836            Ok(result)
837        }
838    }
839
840    /// Validates the structure of a ZIP 316 Revision 2 P2SH viewing key item payload: a
841    /// [BIP 388] wallet policy consisting of a descriptor template and a key information
842    /// vector.
843    ///
844    /// This checks the container framing, that the template is US-ASCII, that the `@N`
845    /// key placeholders correspond one-to-one with the key information entries, and that
846    /// each placeholder is followed by the multipath prefix `kind` requires (`/**` for a
847    /// UFVK, `/*` for a UIVK). Only that prefix is checked; what follows it is not
848    /// validated as a BIP 388 path component. This function does not validate the
849    /// template against the full BIP 388 descriptor grammar, nor the key material
850    /// itself; that is the responsibility of consumers that interpret the policy.
851    ///
852    /// [BIP 388]: https://github.com/bitcoin/bips/blob/master/bip-0388.mediawiki
853    pub(crate) fn validate_p2sh_item(
854        kind: super::P2shItemKind,
855        data: &[u8],
856    ) -> Result<(), super::P2shItemError> {
857        use super::{P2shItemError, P2shItemKind};
858
859        /// The length of one key information entry: a 32-byte BIP 32 chain code followed
860        /// by a 33-byte SEC1 compressed public key.
861        const KEY_INFO_LEN: usize = 65;
862
863        let mut cursor = corez::io::Cursor::new(data);
864        let template_len = CompactSize::read(&mut cursor)
865            .ok()
866            .and_then(|n| usize::try_from(n).ok())
867            .ok_or(P2shItemError::Malformed)?;
868        let template_start = usize::try_from(cursor.position()).expect("cursor fits in usize");
869        let template_end = template_start
870            .checked_add(template_len)
871            .filter(|end| *end <= data.len())
872            .ok_or(P2shItemError::Malformed)?;
873        let template = &data[template_start..template_end];
874        cursor.set_position(template_end as u64);
875
876        let n_keys = CompactSize::read(&mut cursor)
877            .ok()
878            .and_then(|n| usize::try_from(n).ok())
879            .ok_or(P2shItemError::Malformed)?;
880        let keys_start = usize::try_from(cursor.position()).expect("cursor fits in usize");
881        if n_keys
882            .checked_mul(KEY_INFO_LEN)
883            .and_then(|len| keys_start.checked_add(len))
884            != Some(data.len())
885        {
886            return Err(P2shItemError::Malformed);
887        }
888
889        if !template.is_ascii() {
890            return Err(P2shItemError::TemplateEncoding);
891        }
892
893        // Check that the `@N` key placeholders reference each key information entry
894        // exactly once, and that each uses the required multipath notation.
895        let mut placeholders = 0usize;
896        let mut seen = alloc::vec![false; n_keys];
897        let mut i = 0;
898        while i < template.len() {
899            if template[i] != b'@' {
900                i += 1;
901                continue;
902            }
903            let digits_start = i + 1;
904            let digits_end = template[digits_start..]
905                .iter()
906                .position(|b| !b.is_ascii_digit())
907                .map(|n| digits_start + n)
908                .unwrap_or(template.len());
909            let index: usize = core::str::from_utf8(&template[digits_start..digits_end])
910                .ok()
911                .filter(|s| !s.is_empty())
912                .and_then(|s| s.parse().ok())
913                .ok_or(P2shItemError::Malformed)?;
914            if index >= n_keys || seen[index] {
915                return Err(P2shItemError::PlaceholderCount {
916                    placeholders: placeholders + 1,
917                    keys: n_keys,
918                });
919            }
920            seen[index] = true;
921            placeholders += 1;
922
923            let suffix = &template[digits_end..];
924            let multipath_ok = match kind {
925                P2shItemKind::FullViewing => suffix.starts_with(b"/**"),
926                P2shItemKind::IncomingViewing => {
927                    suffix.starts_with(b"/*") && !suffix.starts_with(b"/**")
928                }
929            };
930            if !multipath_ok {
931                return Err(P2shItemError::Multipath);
932            }
933            i = digits_end;
934        }
935        if placeholders != n_keys {
936            return Err(P2shItemError::PlaceholderCount {
937                placeholders,
938                keys: n_keys,
939            });
940        }
941
942        Ok(())
943    }
944
945    /// Parses a metadata item, enforcing the MUST-understand rules for the container's
946    /// revision.
947    fn parse_metadata_item(
948        revision: Revision,
949        typecode: MetadataTypecode,
950        data: Vec<u8>,
951    ) -> Result<MetadataItem, ParseError> {
952        let tc_val = u32::from(typecode);
953        // A Revision 0 container must not contain any metadata item in the
954        // MUST-understand range.
955        if revision == Revision::R0 && tc_val >= MUST_UNDERSTAND_METADATA_MIN {
956            return Err(ParseError::NotUnderstood(tc_val));
957        }
958        match typecode {
959            MetadataTypecode::ExpiryHeight => {
960                let data: [u8; 4] =
961                    data.as_slice()
962                        .try_into()
963                        .map_err(|_| ParseError::InvalidMetadataLength {
964                            typecode: tc_val,
965                            expected: 4,
966                            actual: data.len(),
967                        })?;
968                Ok(MetadataItem::ExpiryHeight(u32::from_le_bytes(data)))
969            }
970            MetadataTypecode::ExpiryTime => {
971                let data: [u8; 8] =
972                    data.as_slice()
973                        .try_into()
974                        .map_err(|_| ParseError::InvalidMetadataLength {
975                            typecode: tc_val,
976                            expected: 8,
977                            actual: data.len(),
978                        })?;
979                Ok(MetadataItem::ExpiryTime(u64::from_le_bytes(data)))
980            }
981            MetadataTypecode::Unknown(tc) if tc >= MUST_UNDERSTAND_METADATA_MIN => {
982                // An item in the MUST-understand range that this implementation does not
983                // recognize renders the whole container unsupported.
984                Err(ParseError::NotUnderstood(tc))
985            }
986            MetadataTypecode::Unknown(tc) => Ok(MetadataItem::Unknown { typecode: tc, data }),
987        }
988    }
989}
990
991use private::SealedItem;
992
993/// The bech32m checksum algorithm, defined in [BIP-350], extended to allow all lengths
994/// supported by [ZIP 316].
995///
996/// [BIP-350]: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
997/// [ZIP 316]: https://zips.z.cash/zip-0316#solution
998#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
999pub enum Bech32mZip316 {}
1000impl Checksum for Bech32mZip316 {
1001    type MidstateRepr = <Bech32m as Checksum>::MidstateRepr;
1002    // l^MAX from ZIP 316.
1003    const CODE_LENGTH: usize = 4194368;
1004    const CHECKSUM_LENGTH: usize = Bech32m::CHECKSUM_LENGTH;
1005    const GENERATOR_SH: [u32; 5] = Bech32m::GENERATOR_SH;
1006    const TARGET_RESIDUE: u32 = Bech32m::TARGET_RESIDUE;
1007}
1008
1009/// Trait providing common encoding and decoding logic for Unified containers.
1010pub trait Encoding: private::SealedContainer {
1011    /// Constructs a value of a unified container type from a vector of items,
1012    /// sorted according to typecode as specified in ZIP 316.
1013    ///
1014    /// This function will return an error if ZIP 316 invariants are violated.
1015    fn try_from_items(
1016        revision: Revision,
1017        mut items: Vec<Uitem<Self::Item>>,
1018    ) -> Result<Self, ParseError> {
1019        items.sort_unstable_by(Uitem::encoding_order);
1020        Self::try_from_items_internal(revision, items)
1021    }
1022
1023    /// Decodes a unified container from its string representation, preserving
1024    /// the order of its components so that it correctly obeys round-trip
1025    /// serialization invariants.
1026    fn decode(s: &str) -> Result<(NetworkType, Revision, Self), ParseError> {
1027        if let Ok(parsed) = CheckedHrpstring::new::<Bech32mZip316>(s) {
1028            let hrp = parsed.hrp();
1029            let hrp = hrp.as_str();
1030            // validate that the HRP corresponds to a known network.
1031            let (net, revision) =
1032                Self::hrp_network(hrp).ok_or_else(|| ParseError::UnknownPrefix(hrp.to_string()))?;
1033
1034            let data = parsed.byte_iter().collect::<Vec<_>>();
1035
1036            Self::parse_internal(hrp, data, revision).map(|value| (net, revision, value))
1037        } else {
1038            Err(ParseError::NotUnified)
1039        }
1040    }
1041
1042    /// Encodes the contents of the unified container to its string representation
1043    /// using the correct constants for the specified network, preserving the
1044    /// ordering of the contained items such that it correctly obeys round-trip
1045    /// serialization invariants.
1046    fn encode(&self, network: &NetworkType) -> String {
1047        let has_transparent = Self::IS_ADDRESS
1048            && self
1049                .items_as_parsed()
1050                .iter()
1051                .any(|item| matches!(item, Uitem::Data(d) if d.typecode().is_transparent()));
1052        let hrp = Self::network_hrp(network, self.revision(), has_transparent);
1053        bech32::encode::<Bech32mZip316>(Hrp::parse_unchecked(hrp), &self.to_jumbled_bytes(hrp))
1054            .expect("F4Jumble ensures length is short enough by construction")
1055    }
1056}
1057
1058/// Trait for Unified containers, that exposes the items within them.
1059pub trait Container {
1060    /// The type of data item in this unified container.
1061    type Item: Item;
1062
1063    /// Returns the revision of the unified encoding.
1064    fn revision(&self) -> Revision;
1065
1066    /// Returns the data items contained within this container, sorted in preference order.
1067    fn items(&self) -> Vec<Self::Item> {
1068        let mut items: Vec<_> = self
1069            .items_as_parsed()
1070            .iter()
1071            .filter_map(|item| match item {
1072                Uitem::Data(d) => Some(d.clone()),
1073                Uitem::Metadata(_) => None,
1074            })
1075            .collect();
1076        items.sort_unstable_by(Self::Item::preference_order);
1077        items
1078    }
1079
1080    /// Returns all items (data and metadata) in the order they were parsed from the
1081    /// string encoding.
1082    fn items_as_parsed(&self) -> &[Uitem<Self::Item>];
1083
1084    /// Returns just the metadata items from this container.
1085    fn metadata_items(&self) -> Vec<&MetadataItem> {
1086        self.items_as_parsed()
1087            .iter()
1088            .filter_map(|item| match item {
1089                Uitem::Metadata(m) => Some(m),
1090                Uitem::Data(_) => None,
1091            })
1092            .collect()
1093    }
1094}
1095
1096/// Trait for unified items, exposing specific methods on them.
1097pub trait Item: SealedItem {
1098    /// Returns the opaque typed encoding of this item.
1099    ///
1100    /// This is the same encoding used internally by [`Encoding::encode`].
1101    /// This API is for advanced usage; in most cases you should not depend
1102    /// on the typed encoding of items.
1103    fn typed_encoding(&self) -> Vec<u8> {
1104        let mut ret = vec![];
1105        self.write_raw_encoding(&mut ret)
1106            .expect("writing to a Vec cannot fail");
1107        ret
1108    }
1109}
1110
1111impl<T: SealedItem> Item for T {}