Skip to main content

zcash_keys/
address.rs

1//! Structs for handling supported address types.
2
3use alloc::{
4    boxed::Box,
5    string::{String, ToString},
6    vec::Vec,
7};
8use core::{convert::Infallible, fmt};
9
10use transparent::address::TransparentAddress;
11use zcash_address::{
12    ConversionError, ToAddress, TryFromAddress, ZcashAddress,
13    unified::{
14        self, Container, Encoding, MetadataItem, MetadataTypecode, Revision, Typecode, Uitem,
15    },
16};
17use zcash_protocol::{
18    PoolType, ShieldedPool,
19    consensus::{self, NetworkType},
20};
21
22use crate::encoding::{UnifiedDecodingError, UnifiedEncodingError};
23
24#[cfg(feature = "sapling")]
25use sapling::PaymentAddress;
26
27/// An error that prevents pruning a [`UnifiedAddress`] to a set of item types.
28#[derive(Debug, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum PruneError {
31    /// The address holds a MUST-understand metadata item of this type, and the
32    /// requested item types do not include it.
33    MustUnderstandMetadata(MetadataTypecode),
34    /// The pruned address has no encoding at any revision. For example, an address whose
35    /// only receiver is a short unknown item is below the minimum encoded length.
36    NotRepresentable(unified::ParseError),
37}
38
39impl fmt::Display for PruneError {
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        match self {
42            PruneError::MustUnderstandMetadata(typecode) => write!(
43                f,
44                "Cannot remove MUST-understand metadata item with typecode {:#04X}",
45                u32::from(*typecode)
46            ),
47            PruneError::NotRepresentable(cause) => {
48                write!(f, "The pruned unified address has no encoding: {cause}")
49            }
50        }
51    }
52}
53
54#[cfg(feature = "std")]
55impl std::error::Error for PruneError {}
56
57/// A Unified Address.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct UnifiedAddress {
60    #[cfg(feature = "orchard")]
61    orchard: Option<orchard::Address>,
62    #[cfg(feature = "sapling")]
63    sapling: Option<PaymentAddress>,
64    transparent: Option<TransparentAddress>,
65    unknown: Vec<(u32, Vec<u8>)>,
66    expiry_height: Option<consensus::BlockHeight>,
67    expiry_time: Option<u64>,
68    unknown_metadata: Vec<(u32, Vec<u8>)>,
69}
70
71impl TryFrom<unified::Address> for UnifiedAddress {
72    type Error = &'static str;
73
74    fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
75        Self::from_container(ua).map_err(|typecode| match typecode {
76            Typecode::ORCHARD => "Invalid Orchard receiver in Unified Address",
77            _ => "Invalid Sapling receiver in Unified Address",
78        })
79    }
80}
81
82impl UnifiedAddress {
83    /// Constructs a [`UnifiedAddress`] from a parsed unified container.
84    ///
85    /// On error, returns the typecode of the first receiver whose data is not a valid
86    /// receiver of its type.
87    fn from_container(ua: unified::Address) -> Result<Self, Typecode> {
88        #[cfg(feature = "orchard")]
89        let mut orchard = None;
90        #[cfg(feature = "sapling")]
91        let mut sapling = None;
92        let mut transparent = None;
93
94        let mut unknown: Vec<(u32, Vec<u8>)> = vec![];
95        let mut expiry_height = None;
96        let mut expiry_time = None;
97        let mut unknown_metadata: Vec<(u32, Vec<u8>)> = vec![];
98
99        // We can use as-parsed order here for efficiency, because we're breaking out the
100        // receivers we support from the unknown receivers.
101        for item in ua.items_as_parsed() {
102            match item {
103                Uitem::Data(unified::Receiver::Orchard(data)) => {
104                    #[cfg(feature = "orchard")]
105                    {
106                        orchard = Some(
107                            Option::from(orchard::Address::from_raw_address_bytes(data))
108                                .ok_or(Typecode::ORCHARD)?,
109                        );
110                    }
111                    #[cfg(not(feature = "orchard"))]
112                    {
113                        unknown.push((Typecode::ORCHARD.into(), data.to_vec()));
114                    }
115                }
116
117                Uitem::Data(unified::Receiver::Sapling(data)) => {
118                    #[cfg(feature = "sapling")]
119                    {
120                        sapling = Some(PaymentAddress::from_bytes(data).ok_or(Typecode::SAPLING)?);
121                    }
122                    #[cfg(not(feature = "sapling"))]
123                    {
124                        unknown.push((Typecode::SAPLING.into(), data.to_vec()));
125                    }
126                }
127
128                Uitem::Data(unified::Receiver::P2pkh(data)) => {
129                    transparent = Some(TransparentAddress::PublicKeyHash(*data));
130                }
131
132                Uitem::Data(unified::Receiver::P2sh(data)) => {
133                    transparent = Some(TransparentAddress::ScriptHash(*data));
134                }
135
136                Uitem::Data(unified::Receiver::Unknown { typecode, data }) => {
137                    unknown.push((*typecode, data.clone()));
138                }
139
140                Uitem::Metadata(MetadataItem::ExpiryHeight(h)) => {
141                    expiry_height = Some(consensus::BlockHeight::from_u32(*h));
142                }
143
144                Uitem::Metadata(MetadataItem::ExpiryTime(t)) => {
145                    expiry_time = Some(*t);
146                }
147
148                Uitem::Metadata(MetadataItem::Unknown { typecode, data }) => {
149                    unknown_metadata.push((*typecode, data.clone()));
150                }
151            }
152        }
153
154        Ok(Self {
155            #[cfg(feature = "orchard")]
156            orchard,
157            #[cfg(feature = "sapling")]
158            sapling,
159            transparent,
160            unknown,
161            expiry_height,
162            expiry_time,
163            unknown_metadata,
164        })
165    }
166
167    /// Constructs a Unified Address from a given set of receivers.
168    ///
169    /// Returns `None` if the receivers would produce an invalid Unified Address (namely,
170    /// if no receiver at all is provided). At least one receiver (transparent or shielded)
171    /// must be present.
172    ///
173    /// An address whose only receiver is transparent has an encoding only at [ZIP 316]
174    /// Revision 2, as a transparent-including (`tu`) address.
175    ///
176    /// [ZIP 316]: https://zips.z.cash/zip-0316
177    pub fn from_receivers(
178        #[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
179        #[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
180        transparent: Option<TransparentAddress>,
181        expiry_height: Option<consensus::BlockHeight>,
182        expiry_time: Option<u64>,
183    ) -> Option<Self> {
184        #[cfg(feature = "orchard")]
185        let has_orchard = orchard.is_some();
186        #[cfg(not(feature = "orchard"))]
187        let has_orchard = false;
188
189        #[cfg(feature = "sapling")]
190        let has_sapling = sapling.is_some();
191        #[cfg(not(feature = "sapling"))]
192        let has_sapling = false;
193
194        if has_orchard || has_sapling || transparent.is_some() {
195            Some(Self {
196                #[cfg(feature = "orchard")]
197                orchard,
198                #[cfg(feature = "sapling")]
199                sapling,
200                transparent,
201                unknown: vec![],
202                expiry_height,
203                expiry_time,
204                unknown_metadata: vec![],
205            })
206        } else {
207            None
208        }
209    }
210
211    /// Returns whether this address has an Orchard receiver.
212    ///
213    /// This method is available irrespective of whether the `orchard` feature flag is enabled.
214    pub fn has_orchard(&self) -> bool {
215        #[cfg(not(feature = "orchard"))]
216        return false;
217        #[cfg(feature = "orchard")]
218        return self.orchard.is_some();
219    }
220
221    /// Returns the Orchard receiver within this Unified Address, if any.
222    #[cfg(feature = "orchard")]
223    pub fn orchard(&self) -> Option<&orchard::Address> {
224        self.orchard.as_ref()
225    }
226
227    /// Returns whether this address has a Sapling receiver.
228    pub fn has_sapling(&self) -> bool {
229        #[cfg(not(feature = "sapling"))]
230        return false;
231
232        #[cfg(feature = "sapling")]
233        return self.sapling.is_some();
234    }
235
236    /// Returns the Sapling receiver within this Unified Address, if any.
237    #[cfg(feature = "sapling")]
238    pub fn sapling(&self) -> Option<&PaymentAddress> {
239        self.sapling.as_ref()
240    }
241
242    /// Returns whether this address has a Transparent receiver.
243    pub fn has_transparent(&self) -> bool {
244        self.transparent.is_some()
245    }
246
247    /// Returns the transparent receiver within this Unified Address, if any.
248    pub fn transparent(&self) -> Option<&TransparentAddress> {
249        self.transparent.as_ref()
250    }
251
252    /// Returns the set of unknown receivers of the unified address.
253    pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
254        &self.unknown
255    }
256
257    /// Returns the expiry height metadata for this address, if present.
258    pub fn expiry_height(&self) -> Option<consensus::BlockHeight> {
259        self.expiry_height
260    }
261
262    /// Returns the expiry time metadata for this address, if present.
263    pub fn expiry_time(&self) -> Option<u64> {
264        self.expiry_time
265    }
266
267    /// Returns a copy of this address that holds only the items whose types are in
268    /// `item_types`.
269    ///
270    /// The result omits each receiver and each SHOULD-understand metadata item whose type
271    /// is not in `item_types`. Use this to share an address with fewer receivers, for
272    /// example one without its transparent receiver.
273    ///
274    /// Returns `Ok(None)` if no receiver of this address has a type in `item_types`,
275    /// because a unified address must hold at least one receiver.
276    ///
277    /// Returns [`PruneError::MustUnderstandMetadata`] if this address holds a [ZIP 316]
278    /// MUST-understand metadata item, such as an expiry height or an expiry time, whose
279    /// type is not in `item_types`. Removal of such an item changes the meaning of the
280    /// address.
281    ///
282    /// Returns [`PruneError::NotRepresentable`] if the pruned address has no encoding at
283    /// any revision.
284    ///
285    /// [ZIP 316]: https://zips.z.cash/zip-0316
286    pub fn prune_retaining(&self, item_types: &[Typecode]) -> Result<Option<Self>, PruneError> {
287        let is_requested = |typecode: u32| {
288            item_types
289                .iter()
290                .any(|requested| requested.typecode_value() == typecode)
291        };
292
293        let must_understand_omitted = self
294            .expiry_height
295            .map(|_| MetadataTypecode::ExpiryHeight)
296            .into_iter()
297            .chain(self.expiry_time.map(|_| MetadataTypecode::ExpiryTime))
298            .chain(
299                self.unknown_metadata
300                    .iter()
301                    .map(|(typecode, _)| MetadataTypecode::Unknown(*typecode)),
302            )
303            .find(|typecode| typecode.is_must_understand() && !is_requested(u32::from(*typecode)));
304        if let Some(typecode) = must_understand_omitted {
305            return Err(PruneError::MustUnderstandMetadata(typecode));
306        }
307
308        let pruned = Self {
309            #[cfg(feature = "orchard")]
310            orchard: self
311                .orchard
312                .filter(|_| is_requested(Typecode::ORCHARD.into())),
313            #[cfg(feature = "sapling")]
314            sapling: self
315                .sapling
316                .filter(|_| is_requested(Typecode::SAPLING.into())),
317            transparent: self.transparent.filter(|taddr| {
318                is_requested(
319                    match taddr {
320                        TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
321                        TransparentAddress::ScriptHash(_) => Typecode::P2SH,
322                    }
323                    .into(),
324                )
325            }),
326            unknown: self
327                .unknown
328                .iter()
329                .filter(|(typecode, _)| is_requested(*typecode))
330                .cloned()
331                .collect(),
332            expiry_height: self
333                .expiry_height
334                .filter(|_| is_requested(MetadataTypecode::ExpiryHeight.into())),
335            expiry_time: self
336                .expiry_time
337                .filter(|_| is_requested(MetadataTypecode::ExpiryTime.into())),
338            unknown_metadata: self
339                .unknown_metadata
340                .iter()
341                .filter(|(typecode, _)| is_requested(*typecode))
342                .cloned()
343                .collect(),
344        };
345
346        let has_receiver = pruned.has_orchard()
347            || pruned.has_sapling()
348            || pruned.has_transparent()
349            || !pruned.unknown.is_empty();
350        if !has_receiver {
351            return Ok(None);
352        }
353
354        // Revision 2 can encode every address that any revision can encode.
355        pruned
356            .to_container(Revision::R2)
357            .map_err(PruneError::NotRepresentable)?;
358        Ok(Some(pruned))
359    }
360
361    /// Parses a [`UnifiedAddress`] from its [ZIP 316] string encoding at any revision.
362    ///
363    /// # Errors
364    ///
365    /// - [`UnifiedDecodingError::Parse`] if the string is not a valid unified
366    ///   address.
367    /// - [`UnifiedDecodingError::NetworkMismatch`] if the address is for a network
368    ///   other than that of `params`.
369    /// - [`UnifiedDecodingError::InvalidItem`] if a receiver's data is not a valid
370    ///   receiver of its type.
371    ///
372    /// [ZIP 316]: https://zips.z.cash/zip-0316
373    pub fn decode<P: consensus::Parameters>(
374        params: &P,
375        address: &str,
376    ) -> Result<Self, UnifiedDecodingError> {
377        let (actual, _revision, ua) =
378            unified::Address::decode(address).map_err(UnifiedDecodingError::Parse)?;
379        let expected = params.network_type();
380        if actual != expected {
381            return Err(UnifiedDecodingError::NetworkMismatch { expected, actual });
382        }
383        Self::from_container(ua).map_err(UnifiedDecodingError::InvalidItem)
384    }
385
386    /// Serializes this [`UnifiedAddress`] as a [`ZcashAddress`] for the given network.
387    ///
388    /// The encoding contains every receiver and metadata item of this address. It uses
389    /// [ZIP 316] [`Revision::R0`] if that revision can represent the address, and
390    /// [`Revision::R2`] otherwise. At [`Revision::R2`], an address with a transparent
391    /// receiver uses the transparent-including (`tu`) form, and an address without one
392    /// uses the shielded-only (`zu`) form. To encode only some of the receivers, first
393    /// call [`UnifiedAddress::prune_retaining`].
394    ///
395    /// [ZIP 316]: https://zips.z.cash/zip-0316
396    pub fn to_zcash_address(&self, net: NetworkType) -> ZcashAddress {
397        self.to_zcash_address_revision(net, Revision::R0)
398            .or_else(|_| self.to_zcash_address_revision(net, Revision::R2))
399            // Every `UnifiedAddress` has at least one data item, and every unknown item
400            // or metadata item it holds was accepted by a decoder. `prune_retaining`
401            // returns an address only if Revision 2 can encode it. So Revision 2 can
402            // always encode it.
403            .expect("Revision 2 can encode every UnifiedAddress")
404    }
405
406    /// Serializes this [`UnifiedAddress`] as a [`ZcashAddress`] for the given network at
407    /// the given [ZIP 316] revision.
408    ///
409    /// The encoding contains every receiver and metadata item of this address. At
410    /// [`Revision::R2`], an address with a transparent receiver uses the
411    /// transparent-including (`tu`) form, and an address without one uses the
412    /// shielded-only (`zu`) form.
413    ///
414    /// Returns [`UnifiedEncodingError::NotRepresentable`] if `revision` cannot encode
415    /// every receiver and metadata item of this address. [`Revision::R0`] cannot encode
416    /// expiry metadata or an address whose only receiver is transparent; every address
417    /// has a [`Revision::R2`] encoding.
418    ///
419    /// [ZIP 316]: https://zips.z.cash/zip-0316
420    pub fn to_zcash_address_revision(
421        &self,
422        net: NetworkType,
423        revision: Revision,
424    ) -> Result<ZcashAddress, UnifiedEncodingError> {
425        self.to_container(revision)
426            .map(|ua| ZcashAddress::from_unified(net, ua))
427            .map_err(|cause| UnifiedEncodingError::NotRepresentable { revision, cause })
428    }
429
430    /// Builds the unified container that holds every item of this address at `revision`.
431    fn to_container(&self, revision: Revision) -> Result<unified::Address, unified::ParseError> {
432        let items: Vec<Uitem<unified::Receiver>> = core::iter::empty()
433            .chain(self.unknown.iter().map(|(typecode, data)| {
434                Uitem::Data(unified::Receiver::Unknown {
435                    typecode: *typecode,
436                    data: data.clone(),
437                })
438            }))
439            .chain({
440                #[cfg(feature = "orchard")]
441                {
442                    self.orchard
443                        .as_ref()
444                        .map(|addr| {
445                            Uitem::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()))
446                        })
447                        .into_iter()
448                }
449                #[cfg(not(feature = "orchard"))]
450                {
451                    core::iter::empty()
452                }
453            })
454            .chain({
455                #[cfg(feature = "sapling")]
456                {
457                    self.sapling
458                        .as_ref()
459                        .map(|pa| Uitem::Data(unified::Receiver::Sapling(pa.to_bytes())))
460                        .into_iter()
461                }
462                #[cfg(not(feature = "sapling"))]
463                {
464                    core::iter::empty()
465                }
466            })
467            .chain(self.transparent.as_ref().map(|taddr| match taddr {
468                TransparentAddress::PublicKeyHash(data) => {
469                    Uitem::Data(unified::Receiver::P2pkh(*data))
470                }
471                TransparentAddress::ScriptHash(data) => Uitem::Data(unified::Receiver::P2sh(*data)),
472            }))
473            .chain(
474                self.expiry_height
475                    .map(|h| Uitem::Metadata(MetadataItem::ExpiryHeight(h.into()))),
476            )
477            .chain(
478                self.expiry_time
479                    .map(|t| Uitem::Metadata(MetadataItem::ExpiryTime(t))),
480            )
481            .chain(self.unknown_metadata.iter().map(|(typecode, data)| {
482                Uitem::Metadata(MetadataItem::Unknown {
483                    typecode: *typecode,
484                    data: data.clone(),
485                })
486            }))
487            .collect();
488
489        unified::Address::try_from_items(revision, items)
490    }
491
492    /// Returns the string encoding of this [`UnifiedAddress`] for the given network.
493    ///
494    /// The encoding contains every receiver and metadata item of this address. It uses
495    /// [ZIP 316] [`Revision::R0`] if that revision can represent the address, and
496    /// [`Revision::R2`] otherwise. At [`Revision::R2`], an address with a transparent
497    /// receiver uses the transparent-including (`tu`) form, and an address without one
498    /// uses the shielded-only (`zu`) form.
499    ///
500    /// [ZIP 316]: https://zips.z.cash/zip-0316
501    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
502        self.to_zcash_address(params.network_type()).to_string()
503    }
504
505    /// Returns the string encoding of this [`UnifiedAddress`] for the given network at the
506    /// given [ZIP 316] revision.
507    ///
508    /// The encoding contains every receiver and metadata item of this address. At
509    /// [`Revision::R2`], an address with a transparent receiver uses the
510    /// transparent-including (`tu`) form, and an address without one uses the
511    /// shielded-only (`zu`) form.
512    ///
513    /// Returns [`UnifiedEncodingError::NotRepresentable`] if `revision` cannot encode
514    /// every receiver and metadata item of this address. [`Revision::R0`] cannot encode
515    /// expiry metadata or an address whose only receiver is transparent; every address
516    /// has a [`Revision::R2`] encoding.
517    ///
518    /// [ZIP 316]: https://zips.z.cash/zip-0316
519    pub fn encode_revision<P: consensus::Parameters>(
520        &self,
521        params: &P,
522        revision: Revision,
523    ) -> Result<String, UnifiedEncodingError> {
524        self.to_zcash_address_revision(params.network_type(), revision)
525            .map(|addr| addr.to_string())
526    }
527
528    /// Returns the set of receiver typecodes.
529    pub fn receiver_types(&self) -> Vec<Typecode> {
530        let result = core::iter::empty();
531        #[cfg(feature = "orchard")]
532        let result = result.chain(self.orchard.map(|_| Typecode::ORCHARD));
533        #[cfg(feature = "sapling")]
534        let result = result.chain(self.sapling.map(|_| Typecode::SAPLING));
535        let result = result.chain(self.transparent.map(|taddr| match taddr {
536            TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
537            TransparentAddress::ScriptHash(_) => Typecode::P2SH,
538        }));
539        let result = result.chain(
540            self.unknown()
541                .iter()
542                .filter_map(|(typecode, _)| Typecode::try_from(*typecode).ok()),
543        );
544        result.collect()
545    }
546
547    /// Returns the set of receivers in the unified address, excluding unknown receiver types.
548    pub fn as_understood_receivers(&self) -> Vec<Receiver> {
549        let result = core::iter::empty();
550        #[cfg(feature = "orchard")]
551        let result = result.chain(self.orchard.map(Receiver::Orchard));
552        #[cfg(feature = "sapling")]
553        let result = result.chain(self.sapling.map(Receiver::Sapling));
554        let result = result.chain(self.transparent.map(Receiver::Transparent));
555        result.collect()
556    }
557}
558
559/// An enumeration of protocol-level receiver types.
560///
561/// While these correspond to unified address receiver types, this is a distinct type because it is
562/// used to represent the protocol-level recipient of a transfer, instead of a part of an encoded
563/// address.
564pub enum Receiver {
565    #[cfg(feature = "orchard")]
566    Orchard(orchard::Address),
567    #[cfg(feature = "sapling")]
568    Sapling(PaymentAddress),
569    Transparent(TransparentAddress),
570}
571
572impl Receiver {
573    /// Converts this receiver to a [`ZcashAddress`] for the given network, using the
574    /// least-capable address format that can carry it.
575    ///
576    /// An Orchard receiver becomes a Unified Address encoded at the given [ZIP 316]
577    /// revision, a Sapling receiver becomes a Sapling address, and a transparent receiver
578    /// becomes a transparent address. Every revision can encode a Unified Address with a
579    /// single Orchard receiver.
580    ///
581    /// [ZIP 316]: https://zips.z.cash/zip-0316
582    #[cfg_attr(not(feature = "orchard"), allow(unused_variables))]
583    pub fn to_zcash_address_revision(
584        &self,
585        net: NetworkType,
586        ua_revision: Revision,
587    ) -> ZcashAddress {
588        match self {
589            #[cfg(feature = "orchard")]
590            Receiver::Orchard(addr) => {
591                let receiver = Uitem::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()));
592                let ua = unified::Address::try_from_items(ua_revision, vec![receiver]).expect(
593                    "Every revision permits a unified address with a single Orchard receiver.",
594                );
595                ZcashAddress::from_unified(net, ua)
596            }
597            #[cfg(feature = "sapling")]
598            Receiver::Sapling(addr) => ZcashAddress::from_sapling(net, addr.to_bytes()),
599            Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
600                ZcashAddress::from_transparent_p2pkh(net, *data)
601            }
602            Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
603                ZcashAddress::from_transparent_p2sh(net, *data)
604            }
605        }
606    }
607
608    /// Converts this receiver to a [`ZcashAddress`] for the given network, using the
609    /// least-capable address format that can carry it.
610    ///
611    /// An Orchard receiver becomes a Unified Address encoded at [ZIP 316]
612    /// [`Revision::R0`], a Sapling receiver becomes a Sapling address, and a transparent
613    /// receiver becomes a transparent address.
614    ///
615    /// [ZIP 316]: https://zips.z.cash/zip-0316
616    pub fn to_zcash_address(&self, net: NetworkType) -> ZcashAddress {
617        self.to_zcash_address_revision(net, Revision::R0)
618    }
619
620    /// Returns whether or not this receiver corresponds to `addr`, or is contained
621    /// in `addr` when the latter is a Unified Address.
622    pub fn corresponds(&self, addr: &ZcashAddress) -> bool {
623        addr.matches_receiver(&match self {
624            #[cfg(feature = "orchard")]
625            Receiver::Orchard(addr) => unified::Receiver::Orchard(addr.to_raw_address_bytes()),
626            #[cfg(feature = "sapling")]
627            Receiver::Sapling(addr) => unified::Receiver::Sapling(addr.to_bytes()),
628            Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
629                unified::Receiver::P2pkh(*data)
630            }
631            Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
632                unified::Receiver::P2sh(*data)
633            }
634        })
635    }
636}
637
638/// An address that funds can be sent to.
639#[derive(Debug, PartialEq, Eq, Clone)]
640pub enum Address {
641    /// A Sapling payment address.
642    #[cfg(feature = "sapling")]
643    Sapling(Box<PaymentAddress>),
644
645    /// A transparent address corresponding to either a public key hash or a script hash.
646    Transparent(TransparentAddress),
647
648    /// A [ZIP 316] Unified Address.
649    ///
650    /// [ZIP 316]: https://zips.z.cash/zip-0316
651    Unified(Box<UnifiedAddress>),
652
653    /// A [ZIP 320] transparent-source-only P2PKH address, or "TEX address".
654    ///
655    /// [ZIP 320]: https://zips.z.cash/zip-0320
656    Tex([u8; 20]),
657}
658
659#[cfg(feature = "sapling")]
660impl From<PaymentAddress> for Address {
661    fn from(addr: PaymentAddress) -> Self {
662        Address::Sapling(Box::new(addr))
663    }
664}
665
666impl From<TransparentAddress> for Address {
667    fn from(addr: TransparentAddress) -> Self {
668        Address::Transparent(addr)
669    }
670}
671
672impl From<UnifiedAddress> for Address {
673    fn from(addr: UnifiedAddress) -> Self {
674        Address::Unified(Box::new(addr))
675    }
676}
677
678impl TryFromAddress for Address {
679    type Error = &'static str;
680
681    #[cfg(feature = "sapling")]
682    fn try_from_sapling(
683        _net: NetworkType,
684        data: [u8; 43],
685    ) -> Result<Self, ConversionError<Self::Error>> {
686        let pa = PaymentAddress::from_bytes(&data).ok_or("Invalid Sapling payment address")?;
687        Ok(pa.into())
688    }
689
690    fn try_from_unified(
691        _net: NetworkType,
692        ua: zcash_address::unified::Address,
693    ) -> Result<Self, ConversionError<Self::Error>> {
694        UnifiedAddress::try_from(ua)
695            .map_err(ConversionError::User)
696            .map(Address::from)
697    }
698
699    fn try_from_transparent_p2pkh(
700        _net: NetworkType,
701        data: [u8; 20],
702    ) -> Result<Self, ConversionError<Self::Error>> {
703        Ok(TransparentAddress::PublicKeyHash(data).into())
704    }
705
706    fn try_from_transparent_p2sh(
707        _net: NetworkType,
708        data: [u8; 20],
709    ) -> Result<Self, ConversionError<Self::Error>> {
710        Ok(TransparentAddress::ScriptHash(data).into())
711    }
712
713    fn try_from_tex(
714        _net: NetworkType,
715        data: [u8; 20],
716    ) -> Result<Self, ConversionError<Self::Error>> {
717        Ok(Address::Tex(data))
718    }
719}
720
721impl Address {
722    /// Attempts to decode an [`Address`] value from its [`ZcashAddress`] encoded representation.
723    ///
724    /// Returns `None` if any error is encountered in decoding. Use
725    /// [`Self::try_from_zcash_address`] passing in `s.parse()?` if you need detailed
726    /// error information.
727    pub fn decode<P: consensus::Parameters>(params: &P, s: &str) -> Option<Self> {
728        Self::try_from_zcash_address(params, s.parse::<ZcashAddress>().ok()?).ok()
729    }
730
731    /// Attempts to decode an [`Address`] value from its [`ZcashAddress`] encoded representation.
732    pub fn try_from_zcash_address<P: consensus::Parameters>(
733        params: &P,
734        zaddr: ZcashAddress,
735    ) -> Result<Self, ConversionError<&'static str>> {
736        zaddr.convert_if_network(params.network_type())
737    }
738
739    /// Converts this [`Address`] to a [`ZcashAddress`].
740    ///
741    /// A unified address is encoded with every receiver and metadata item, at the given
742    /// [ZIP 316] revision. Sapling, transparent, and TEX addresses have a single encoding
743    /// and ignore `ua_revision`.
744    ///
745    /// Returns [`UnifiedEncodingError::NotRepresentable`] if this is a unified address
746    /// that `ua_revision` cannot encode in full. [`Revision::R0`] cannot encode expiry
747    /// metadata or an address whose only receiver is transparent; every address has a
748    /// [`Revision::R2`] encoding.
749    ///
750    /// [ZIP 316]: https://zips.z.cash/zip-0316
751    pub fn to_zcash_address_revision<P: consensus::Parameters>(
752        &self,
753        params: &P,
754        ua_revision: Revision,
755    ) -> Result<ZcashAddress, UnifiedEncodingError> {
756        self.to_zcash_address_with(params.network_type(), |ua, net| {
757            ua.to_zcash_address_revision(net, ua_revision)
758        })
759    }
760
761    /// Converts this [`Address`] to its encoded [`ZcashAddress`] representation, using
762    /// `encode_unified` to encode a unified address.
763    fn to_zcash_address_with<E>(
764        &self,
765        net: NetworkType,
766        encode_unified: impl FnOnce(&UnifiedAddress, NetworkType) -> Result<ZcashAddress, E>,
767    ) -> Result<ZcashAddress, E> {
768        Ok(match self {
769            #[cfg(feature = "sapling")]
770            Address::Sapling(pa) => ZcashAddress::from_sapling(net, pa.to_bytes()),
771            Address::Transparent(addr) => match addr {
772                TransparentAddress::PublicKeyHash(data) => {
773                    ZcashAddress::from_transparent_p2pkh(net, *data)
774                }
775                TransparentAddress::ScriptHash(data) => {
776                    ZcashAddress::from_transparent_p2sh(net, *data)
777                }
778            },
779            Address::Unified(ua) => encode_unified(ua, net)?,
780            Address::Tex(data) => ZcashAddress::from_tex(net, *data),
781        })
782    }
783
784    /// Converts this [`Address`] to a [`ZcashAddress`].
785    ///
786    /// A unified address is encoded with every receiver and metadata item, at [ZIP 316]
787    /// [`Revision::R0`] if that revision can represent it, and at [`Revision::R2`]
788    /// otherwise. Sapling, transparent, and TEX addresses have a single encoding.
789    ///
790    /// [ZIP 316]: https://zips.z.cash/zip-0316
791    pub fn to_zcash_address<P: consensus::Parameters>(&self, params: &P) -> ZcashAddress {
792        let result: Result<_, Infallible> =
793            self.to_zcash_address_with(params.network_type(), |ua, net| {
794                Ok(ua.to_zcash_address(net))
795            });
796        match result {
797            Ok(addr) => addr,
798            Err(e) => match e {},
799        }
800    }
801
802    /// Returns the string encoding of this [`Address`].
803    ///
804    /// A unified address is encoded with every receiver and metadata item, at [ZIP 316]
805    /// [`Revision::R0`] if that revision can represent it, and at [`Revision::R2`]
806    /// otherwise. Sapling, transparent, and TEX addresses have a single encoding.
807    ///
808    /// [ZIP 316]: https://zips.z.cash/zip-0316
809    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
810        self.to_zcash_address(params).to_string()
811    }
812
813    /// Returns the string encoding of this [`Address`].
814    ///
815    /// A unified address is encoded with every receiver and metadata item, at the given
816    /// [ZIP 316] revision. Sapling, transparent, and TEX addresses have a single encoding
817    /// and ignore `ua_revision`.
818    ///
819    /// Returns [`UnifiedEncodingError::NotRepresentable`] if this is a unified address
820    /// that `ua_revision` cannot encode in full. [`Revision::R0`] cannot encode expiry
821    /// metadata or an address whose only receiver is transparent; every address has a
822    /// [`Revision::R2`] encoding.
823    ///
824    /// [ZIP 316]: https://zips.z.cash/zip-0316
825    pub fn encode_revision<P: consensus::Parameters>(
826        &self,
827        params: &P,
828        ua_revision: Revision,
829    ) -> Result<String, UnifiedEncodingError> {
830        self.to_zcash_address_revision(params, ua_revision)
831            .map(|addr| addr.to_string())
832    }
833
834    /// Returns whether or not this [`Address`] can receive funds in the specified pool.
835    pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
836        match self {
837            #[cfg(feature = "sapling")]
838            Address::Sapling(_) => {
839                matches!(pool_type, PoolType::Shielded(ShieldedPool::Sapling))
840            }
841            Address::Transparent(_) | Address::Tex(_) => {
842                matches!(pool_type, PoolType::Transparent)
843            }
844            Address::Unified(ua) => match pool_type {
845                PoolType::Transparent => ua.has_transparent(),
846                PoolType::Shielded(ShieldedPool::Sapling) => ua.has_sapling(),
847                // Ironwood shares the Orchard receiver.
848                PoolType::Shielded(ShieldedPool::Orchard | ShieldedPool::Ironwood) => {
849                    ua.has_orchard()
850                }
851            },
852        }
853    }
854
855    /// Returns the transparent address corresponding to this address, if it is a transparent
856    /// address, a Unified address with a transparent receiver, or ZIP 320 (TEX) address.
857    pub fn to_transparent_address(&self) -> Option<TransparentAddress> {
858        match self {
859            #[cfg(feature = "sapling")]
860            Address::Sapling(_) => None,
861            Address::Transparent(addr) => Some(*addr),
862            Address::Unified(ua) => ua.transparent().copied(),
863            Address::Tex(addr_bytes) => Some(TransparentAddress::PublicKeyHash(*addr_bytes)),
864        }
865    }
866
867    /// Returns the Sapling address corresponding to this address, if it is a ZIP 32-encoded
868    /// Sapling address, or a Unified address with a Sapling receiver.
869    #[cfg(feature = "sapling")]
870    pub fn to_sapling_address(&self) -> Option<PaymentAddress> {
871        match self {
872            Address::Sapling(addr) => Some(**addr),
873            Address::Transparent(_) => None,
874            Address::Unified(ua) => ua.sapling().copied(),
875            Address::Tex(_) => None,
876        }
877    }
878
879    /// Returns the protocol-typed unified [`Receiver`]s of this address as a vector, ignoring the
880    /// original encoding of the address.
881    ///
882    /// In the case that the underlying address is the [`Address::Unified`] variant, this is
883    /// equivalent to [`UnifiedAddress::as_understood_receivers`] in that it does not return
884    /// unknown receiver data.
885    ///
886    /// Note that this method eliminates the distinction between transparent addresses and the
887    /// transparent receiving address for a TEX address; as such, it should only be used in cases
888    /// where address uses are being detected in inspection of chain data, and NOT in any situation
889    /// where a transaction sending to this address is being constructed.
890    pub fn as_understood_unified_receivers(&self) -> Vec<Receiver> {
891        match self {
892            #[cfg(feature = "sapling")]
893            Address::Sapling(addr) => vec![Receiver::Sapling(**addr)],
894            Address::Transparent(addr) => vec![Receiver::Transparent(*addr)],
895            Address::Unified(ua) => ua.as_understood_receivers(),
896            Address::Tex(addr) => vec![Receiver::Transparent(TransparentAddress::PublicKeyHash(
897                *addr,
898            ))],
899        }
900    }
901}
902
903#[cfg(all(
904    any(
905        feature = "orchard",
906        feature = "sapling",
907        feature = "transparent-inputs"
908    ),
909    any(test, feature = "test-dependencies")
910))]
911pub mod testing {
912    use proptest::prelude::*;
913    use zcash_protocol::consensus::Network;
914
915    use crate::keys::{UnifiedAddressRequest, testing::arb_unified_spending_key};
916
917    use super::{Address, UnifiedAddress};
918
919    #[cfg(feature = "sapling")]
920    use sapling::testing::arb_payment_address;
921    use transparent::address::testing::arb_transparent_addr;
922
923    pub fn arb_unified_addr(
924        params: Network,
925        request: UnifiedAddressRequest,
926    ) -> impl Strategy<Value = UnifiedAddress> {
927        arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).0)
928    }
929
930    #[cfg(feature = "sapling")]
931    pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
932        prop_oneof![
933            arb_payment_address().prop_map(Address::from),
934            arb_transparent_addr().prop_map(Address::Transparent),
935            arb_unified_addr(Network::TestNetwork, request).prop_map(Address::from),
936            proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
937        ]
938    }
939
940    #[cfg(not(feature = "sapling"))]
941    pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
942        prop_oneof![
943            arb_transparent_addr().prop_map(Address::Transparent),
944            arb_unified_addr(Network::TestNetwork, request).prop_map(Address::from),
945            proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
946        ]
947    }
948}
949
950#[cfg(test)]
951mod tests {
952    use assert_matches::assert_matches;
953    use transparent::address::TransparentAddress;
954    use zcash_address::{
955        test_vectors,
956        unified::{self, Encoding, MetadataItem, MetadataTypecode, ParseError, Revision, Uitem},
957    };
958    use zcash_protocol::consensus::{BlockHeight, MAIN_NETWORK, NetworkType, TEST_NETWORK};
959
960    use super::{Address, PruneError, UnifiedAddress};
961    use crate::encoding::{UnifiedDecodingError, UnifiedEncodingError};
962
963    #[cfg(feature = "sapling")]
964    use crate::keys::sapling;
965
966    use zcash_address::unified::Typecode;
967
968    #[cfg(any(feature = "orchard", feature = "sapling"))]
969    use zip32::AccountId;
970
971    #[test]
972    #[cfg(any(feature = "orchard", feature = "sapling"))]
973    fn ua_round_trip() {
974        #[cfg(feature = "orchard")]
975        let orchard = {
976            let sk =
977                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
978            let fvk = orchard::keys::FullViewingKey::from(&sk);
979            Some(fvk.address_at(0u32, orchard::keys::Scope::External))
980        };
981
982        #[cfg(feature = "sapling")]
983        let sapling = {
984            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO)
985                .expect("the derivation path yields a valid key");
986            let dfvk = extsk.to_diversifiable_full_viewing_key();
987            Some(dfvk.default_address().1)
988        };
989
990        // Every encoding must retain the transparent receiver alongside the shielded ones.
991        for transparent in [None, Some(TransparentAddress::PublicKeyHash([0; 20]))] {
992            #[cfg(all(feature = "orchard", feature = "sapling"))]
993            let ua =
994                UnifiedAddress::from_receivers(orchard, sapling, transparent, None, None).unwrap();
995
996            #[cfg(all(not(feature = "orchard"), feature = "sapling"))]
997            let ua = UnifiedAddress::from_receivers(sapling, transparent, None, None).unwrap();
998
999            #[cfg(all(feature = "orchard", not(feature = "sapling")))]
1000            let ua = UnifiedAddress::from_receivers(orchard, transparent, None, None).unwrap();
1001
1002            let addr = Address::from(ua);
1003            assert_eq!(
1004                Address::decode(&MAIN_NETWORK, &addr.encode(&MAIN_NETWORK)),
1005                Some(addr.clone())
1006            );
1007            for revision in [Revision::R0, Revision::R2] {
1008                let addr_str = addr.encode_revision(&MAIN_NETWORK, revision).expect(
1009                    "a shielded address without metadata has an encoding at every revision",
1010                );
1011                assert_eq!(
1012                    Address::decode(&MAIN_NETWORK, &addr_str),
1013                    Some(addr.clone())
1014                );
1015            }
1016        }
1017    }
1018
1019    #[test]
1020    #[cfg(not(any(feature = "orchard", feature = "sapling")))]
1021    fn ua_round_trip() {
1022        let transparent = None;
1023        assert_eq!(
1024            UnifiedAddress::from_receivers(transparent, None, None),
1025            None
1026        )
1027    }
1028
1029    #[test]
1030    fn ua_parsing() {
1031        for tv in test_vectors::UNIFIED {
1032            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
1033                Some(Address::Unified(ua)) => {
1034                    assert_eq!(
1035                        ua.has_transparent(),
1036                        tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some()
1037                    );
1038                    #[cfg(feature = "sapling")]
1039                    assert_eq!(ua.has_sapling(), tv.sapling_raw_addr.is_some());
1040                    #[cfg(feature = "orchard")]
1041                    assert_eq!(ua.has_orchard(), tv.orchard_raw_addr.is_some());
1042                }
1043                Some(_) => {
1044                    panic!(
1045                        "{} did not decode to a unified address value.",
1046                        tv.unified_addr
1047                    );
1048                }
1049                None => {
1050                    panic!(
1051                        "Failed to decode unified address from test vector: {}",
1052                        tv.unified_addr
1053                    );
1054                }
1055            }
1056        }
1057    }
1058
1059    #[test]
1060    fn r0_test_vectors_reencode_identically() {
1061        for tv in test_vectors::UNIFIED {
1062            let ua = UnifiedAddress::decode(&MAIN_NETWORK, tv.unified_addr)
1063                .expect("test vector is a valid unified address");
1064            assert_eq!(
1065                ua.encode_revision(&MAIN_NETWORK, Revision::R0).as_deref(),
1066                Ok(tv.unified_addr),
1067            );
1068            // Revision 0 can represent every test vector, so it is the compatible choice.
1069            assert_eq!(ua.encode(&MAIN_NETWORK), tv.unified_addr,);
1070        }
1071    }
1072
1073    /// The lowest data typecode that ZIP 316 does not assign to a receiver type.
1074    const UNASSIGNED_DATA_TYPECODE: u32 = 0x04;
1075    /// The lowest metadata typecode in the ZIP 316 SHOULD-understand range.
1076    const SHOULD_UNDERSTAND_METADATA_TYPECODE: u32 = 0xC0;
1077    /// Payload bytes for the unknown items in the pruning tests.
1078    const UNKNOWN_ITEM_DATA: [u8; 4] = [0xAB; 4];
1079    /// Payload bytes for an unknown receiver that, alone in an address, meets the minimum
1080    /// encoded length.
1081    const UNKNOWN_RECEIVER_DATA: [u8; 32] = [0xCD; 32];
1082    /// Hash bytes for the P2PKH receiver in the pruning tests.
1083    const P2PKH_HASH: [u8; 20] = [0x11; 20];
1084    /// Expiry height for the pruning tests.
1085    const EXPIRY_HEIGHT: u32 = 3_000_000;
1086
1087    /// A Revision 2 address with a P2PKH receiver, an unknown receiver that holds
1088    /// `receiver_data`, and an unknown SHOULD-understand metadata item.
1089    fn address_with_unknown_items(receiver_data: &[u8]) -> UnifiedAddress {
1090        let container = unified::Address::try_from_items(
1091            Revision::R2,
1092            vec![
1093                Uitem::Data(unified::Receiver::P2pkh(P2PKH_HASH)),
1094                Uitem::Data(unified::Receiver::Unknown {
1095                    typecode: UNASSIGNED_DATA_TYPECODE,
1096                    data: receiver_data.to_vec(),
1097                }),
1098                Uitem::Metadata(MetadataItem::Unknown {
1099                    typecode: SHOULD_UNDERSTAND_METADATA_TYPECODE,
1100                    data: UNKNOWN_ITEM_DATA.to_vec(),
1101                }),
1102            ],
1103        )
1104        .expect("the items form a valid Revision 2 address");
1105        UnifiedAddress::try_from(container).expect("the receivers are valid")
1106    }
1107
1108    fn unassigned_data_typecode() -> Typecode {
1109        Typecode::try_from(UNASSIGNED_DATA_TYPECODE).unwrap()
1110    }
1111
1112    fn should_understand_metadata_typecode() -> Typecode {
1113        Typecode::try_from(SHOULD_UNDERSTAND_METADATA_TYPECODE).unwrap()
1114    }
1115
1116    #[test]
1117    fn prune_retaining_removes_unrequested_items() {
1118        let ua = address_with_unknown_items(&UNKNOWN_RECEIVER_DATA);
1119
1120        let pruned = ua
1121            .prune_retaining(&[unassigned_data_typecode()])
1122            .unwrap()
1123            .expect("the unknown receiver remains");
1124        assert!(!pruned.has_transparent());
1125        assert_eq!(
1126            pruned.unknown(),
1127            &[(UNASSIGNED_DATA_TYPECODE, UNKNOWN_RECEIVER_DATA.to_vec())]
1128        );
1129        // The SHOULD-understand metadata item is removed, and the result round-trips.
1130        assert_eq!(
1131            UnifiedAddress::decode(&MAIN_NETWORK, &pruned.encode(&MAIN_NETWORK)),
1132            Ok(pruned.clone())
1133        );
1134
1135        let pruned = ua
1136            .prune_retaining(&[Typecode::P2PKH])
1137            .unwrap()
1138            .expect("the transparent receiver remains");
1139        assert_eq!(
1140            pruned.transparent(),
1141            Some(&TransparentAddress::PublicKeyHash(P2PKH_HASH))
1142        );
1143        assert!(pruned.unknown().is_empty());
1144    }
1145
1146    #[test]
1147    fn prune_retaining_to_unencodable_address_is_error() {
1148        let ua = address_with_unknown_items(&UNKNOWN_ITEM_DATA);
1149        assert_matches!(
1150            ua.prune_retaining(&[unassigned_data_typecode()]),
1151            Err(PruneError::NotRepresentable(
1152                ParseError::InvalidEncodedLength(_)
1153            ))
1154        );
1155    }
1156
1157    #[test]
1158    fn prune_retaining_to_every_item_type_is_identity() {
1159        let ua = address_with_unknown_items(&UNKNOWN_RECEIVER_DATA);
1160        assert_eq!(
1161            ua.prune_retaining(&[
1162                Typecode::P2PKH,
1163                unassigned_data_typecode(),
1164                should_understand_metadata_typecode(),
1165            ]),
1166            Ok(Some(ua))
1167        );
1168    }
1169
1170    #[test]
1171    fn prune_retaining_without_receivers_is_none() {
1172        let ua = address_with_unknown_items(&UNKNOWN_RECEIVER_DATA);
1173        assert_eq!(ua.prune_retaining(&[]), Ok(None));
1174        assert_eq!(
1175            ua.prune_retaining(&[should_understand_metadata_typecode()]),
1176            Ok(None)
1177        );
1178    }
1179
1180    #[test]
1181    fn prune_retaining_retains_must_understand_metadata() {
1182        let ua = UnifiedAddress::from_receivers(
1183            #[cfg(feature = "orchard")]
1184            None,
1185            #[cfg(feature = "sapling")]
1186            None,
1187            Some(TransparentAddress::PublicKeyHash(P2PKH_HASH)),
1188            Some(BlockHeight::from_u32(EXPIRY_HEIGHT)),
1189            None,
1190        )
1191        .expect("a transparent-only unified address is valid");
1192
1193        assert_eq!(
1194            ua.prune_retaining(&[Typecode::P2PKH]),
1195            Err(PruneError::MustUnderstandMetadata(
1196                MetadataTypecode::ExpiryHeight
1197            ))
1198        );
1199        // An address without receivers still may not drop MUST-understand metadata.
1200        assert_eq!(
1201            ua.prune_retaining(&[]),
1202            Err(PruneError::MustUnderstandMetadata(
1203                MetadataTypecode::ExpiryHeight
1204            ))
1205        );
1206        assert_eq!(
1207            ua.prune_retaining(&[Typecode::P2PKH, MetadataTypecode::ExpiryHeight.into()]),
1208            Ok(Some(ua))
1209        );
1210    }
1211
1212    #[test]
1213    #[cfg(feature = "orchard")]
1214    fn prune_retaining_removes_transparent_receiver() {
1215        let sk = orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
1216        let orchard = orchard::keys::FullViewingKey::from(&sk)
1217            .address_at(0u32, orchard::keys::Scope::External);
1218        let ua = UnifiedAddress::from_receivers(
1219            Some(orchard),
1220            #[cfg(feature = "sapling")]
1221            None,
1222            Some(TransparentAddress::PublicKeyHash(P2PKH_HASH)),
1223            None,
1224            None,
1225        )
1226        .unwrap();
1227
1228        let pruned = ua
1229            .prune_retaining(&[Typecode::ORCHARD])
1230            .unwrap()
1231            .expect("the Orchard receiver remains");
1232        assert_eq!(pruned.orchard(), Some(&orchard));
1233        assert!(!pruned.has_transparent());
1234        assert_eq!(
1235            UnifiedAddress::decode(&MAIN_NETWORK, &pruned.encode(&MAIN_NETWORK)),
1236            Ok(pruned)
1237        );
1238    }
1239
1240    #[test]
1241    fn decode_reports_structured_errors() {
1242        let tv = &test_vectors::UNIFIED[0];
1243        assert_eq!(
1244            UnifiedAddress::decode(&TEST_NETWORK, tv.unified_addr),
1245            Err(UnifiedDecodingError::NetworkMismatch {
1246                expected: NetworkType::Test,
1247                actual: NetworkType::Main,
1248            })
1249        );
1250
1251        assert_matches!(
1252            UnifiedAddress::decode(&MAIN_NETWORK, "not an address"),
1253            Err(UnifiedDecodingError::Parse(_))
1254        );
1255
1256        // An Orchard receiver whose bytes do not encode a valid Orchard address.
1257        const INVALID_ORCHARD_RECEIVER: [u8; 43] = [0xff; 43];
1258        let invalid = unified::Address::try_from_items(
1259            Revision::R0,
1260            vec![Uitem::Data(unified::Receiver::Orchard(
1261                INVALID_ORCHARD_RECEIVER,
1262            ))],
1263        )
1264        .unwrap()
1265        .encode(&NetworkType::Main);
1266        #[cfg(feature = "orchard")]
1267        assert_eq!(
1268            UnifiedAddress::decode(&MAIN_NETWORK, &invalid),
1269            Err(UnifiedDecodingError::InvalidItem(Typecode::ORCHARD))
1270        );
1271        #[cfg(not(feature = "orchard"))]
1272        assert!(UnifiedAddress::decode(&MAIN_NETWORK, &invalid).is_ok());
1273    }
1274
1275    #[test]
1276    fn transparent_only_address_has_no_r0_encoding() {
1277        let ua = UnifiedAddress::from_receivers(
1278            #[cfg(feature = "orchard")]
1279            None,
1280            #[cfg(feature = "sapling")]
1281            None,
1282            Some(TransparentAddress::PublicKeyHash([0; 20])),
1283            None,
1284            None,
1285        )
1286        .expect("a transparent-only unified address is valid");
1287
1288        assert_eq!(
1289            ua.encode_revision(&MAIN_NETWORK, Revision::R0),
1290            Err(UnifiedEncodingError::NotRepresentable {
1291                revision: Revision::R0,
1292                cause: ParseError::OnlyTransparent,
1293            })
1294        );
1295        let encoded = ua
1296            .encode_revision(&MAIN_NETWORK, Revision::R2)
1297            .expect("every address has a Revision 2 encoding");
1298        // Revision 2 is the only revision that can represent this address.
1299        assert_eq!(ua.encode(&MAIN_NETWORK), encoded);
1300        assert_eq!(UnifiedAddress::decode(&MAIN_NETWORK, &encoded), Ok(ua));
1301    }
1302}