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