zcash_keys/
address.rs

1//! Structs for handling supported address types.
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5
6use transparent::address::TransparentAddress;
7use zcash_address::{
8    unified::{self, Container, Encoding, Typecode},
9    ConversionError, ToAddress, TryFromAddress, ZcashAddress,
10};
11use zcash_protocol::consensus::{self, NetworkType};
12
13#[cfg(feature = "sapling")]
14use sapling::PaymentAddress;
15use zcash_protocol::{PoolType, ShieldedProtocol};
16
17/// A Unified Address.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct UnifiedAddress {
20    #[cfg(feature = "orchard")]
21    orchard: Option<orchard::Address>,
22    #[cfg(feature = "sapling")]
23    sapling: Option<PaymentAddress>,
24    transparent: Option<TransparentAddress>,
25    unknown: Vec<(u32, Vec<u8>)>,
26}
27
28impl TryFrom<unified::Address> for UnifiedAddress {
29    type Error = &'static str;
30
31    fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
32        #[cfg(feature = "orchard")]
33        let mut orchard = None;
34        #[cfg(feature = "sapling")]
35        let mut sapling = None;
36        let mut transparent = None;
37
38        let mut unknown: Vec<(u32, Vec<u8>)> = vec![];
39
40        // We can use as-parsed order here for efficiency, because we're breaking out the
41        // receivers we support from the unknown receivers.
42        for item in ua.items_as_parsed() {
43            match item {
44                unified::Receiver::Orchard(data) => {
45                    #[cfg(feature = "orchard")]
46                    {
47                        orchard = Some(
48                            Option::from(orchard::Address::from_raw_address_bytes(data))
49                                .ok_or("Invalid Orchard receiver in Unified Address")?,
50                        );
51                    }
52                    #[cfg(not(feature = "orchard"))]
53                    {
54                        unknown.push((unified::Typecode::Orchard.into(), data.to_vec()));
55                    }
56                }
57
58                unified::Receiver::Sapling(data) => {
59                    #[cfg(feature = "sapling")]
60                    {
61                        sapling = Some(
62                            PaymentAddress::from_bytes(data)
63                                .ok_or("Invalid Sapling receiver in Unified Address")?,
64                        );
65                    }
66                    #[cfg(not(feature = "sapling"))]
67                    {
68                        unknown.push((unified::Typecode::Sapling.into(), data.to_vec()));
69                    }
70                }
71
72                unified::Receiver::P2pkh(data) => {
73                    transparent = Some(TransparentAddress::PublicKeyHash(*data));
74                }
75
76                unified::Receiver::P2sh(data) => {
77                    transparent = Some(TransparentAddress::ScriptHash(*data));
78                }
79
80                unified::Receiver::Unknown { typecode, data } => {
81                    unknown.push((*typecode, data.clone()));
82                }
83            }
84        }
85
86        Ok(Self {
87            #[cfg(feature = "orchard")]
88            orchard,
89            #[cfg(feature = "sapling")]
90            sapling,
91            transparent,
92            unknown,
93        })
94    }
95}
96
97impl UnifiedAddress {
98    /// Constructs a Unified Address from a given set of receivers.
99    ///
100    /// Returns `None` if the receivers would produce an invalid Unified Address (namely,
101    /// if no shielded receiver is provided).
102    pub fn from_receivers(
103        #[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
104        #[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
105        transparent: Option<TransparentAddress>,
106        // TODO: Add handling for address metadata items.
107    ) -> Option<Self> {
108        #[cfg(feature = "orchard")]
109        let has_orchard = orchard.is_some();
110        #[cfg(not(feature = "orchard"))]
111        let has_orchard = false;
112
113        #[cfg(feature = "sapling")]
114        let has_sapling = sapling.is_some();
115        #[cfg(not(feature = "sapling"))]
116        let has_sapling = false;
117
118        if has_orchard || has_sapling {
119            Some(Self {
120                #[cfg(feature = "orchard")]
121                orchard,
122                #[cfg(feature = "sapling")]
123                sapling,
124                transparent,
125                unknown: vec![],
126            })
127        } else {
128            // UAs require at least one shielded receiver.
129            None
130        }
131    }
132
133    /// Returns whether this address has an Orchard receiver.
134    ///
135    /// This method is available irrespective of whether the `orchard` feature flag is enabled.
136    pub fn has_orchard(&self) -> bool {
137        #[cfg(not(feature = "orchard"))]
138        return false;
139        #[cfg(feature = "orchard")]
140        return self.orchard.is_some();
141    }
142
143    /// Returns the Orchard receiver within this Unified Address, if any.
144    #[cfg(feature = "orchard")]
145    pub fn orchard(&self) -> Option<&orchard::Address> {
146        self.orchard.as_ref()
147    }
148
149    /// Returns whether this address has a Sapling receiver.
150    pub fn has_sapling(&self) -> bool {
151        #[cfg(not(feature = "sapling"))]
152        return false;
153
154        #[cfg(feature = "sapling")]
155        return self.sapling.is_some();
156    }
157
158    /// Returns the Sapling receiver within this Unified Address, if any.
159    #[cfg(feature = "sapling")]
160    pub fn sapling(&self) -> Option<&PaymentAddress> {
161        self.sapling.as_ref()
162    }
163
164    /// Returns whether this address has a Transparent receiver.
165    pub fn has_transparent(&self) -> bool {
166        self.transparent.is_some()
167    }
168
169    /// Returns the transparent receiver within this Unified Address, if any.
170    pub fn transparent(&self) -> Option<&TransparentAddress> {
171        self.transparent.as_ref()
172    }
173
174    /// Returns the set of unknown receivers of the unified address.
175    pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
176        &self.unknown
177    }
178
179    fn to_address(&self, net: NetworkType) -> ZcashAddress {
180        let items = self
181            .unknown
182            .iter()
183            .map(|(typecode, data)| unified::Receiver::Unknown {
184                typecode: *typecode,
185                data: data.clone(),
186            });
187
188        #[cfg(feature = "orchard")]
189        let items = items.chain(
190            self.orchard
191                .as_ref()
192                .map(|addr| addr.to_raw_address_bytes())
193                .map(unified::Receiver::Orchard),
194        );
195
196        #[cfg(feature = "sapling")]
197        let items = items.chain(
198            self.sapling
199                .as_ref()
200                .map(|pa| pa.to_bytes())
201                .map(unified::Receiver::Sapling),
202        );
203
204        let items = items.chain(self.transparent.as_ref().map(|taddr| match taddr {
205            TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
206            TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
207        }));
208
209        let ua = unified::Address::try_from_items(items.collect())
210            .expect("UnifiedAddress should only be constructed safely");
211        ZcashAddress::from_unified(net, ua)
212    }
213
214    /// Returns the string encoding of this `UnifiedAddress` for the given network.
215    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
216        self.to_address(params.network_type()).to_string()
217    }
218
219    /// Returns the set of receiver typecodes.
220    pub fn receiver_types(&self) -> Vec<Typecode> {
221        let result = core::iter::empty();
222        #[cfg(feature = "orchard")]
223        let result = result.chain(self.orchard.map(|_| Typecode::Orchard));
224        #[cfg(feature = "sapling")]
225        let result = result.chain(self.sapling.map(|_| Typecode::Sapling));
226        let result = result.chain(self.transparent.map(|taddr| match taddr {
227            TransparentAddress::PublicKeyHash(_) => Typecode::P2pkh,
228            TransparentAddress::ScriptHash(_) => Typecode::P2sh,
229        }));
230        let result = result.chain(
231            self.unknown()
232                .iter()
233                .map(|(typecode, _)| Typecode::Unknown(*typecode)),
234        );
235        result.collect()
236    }
237}
238
239/// An enumeration of protocol-level receiver types.
240///
241/// While these correspond to unified address receiver types, this is a distinct type because it is
242/// used to represent the protocol-level recipient of a transfer, instead of a part of an encoded
243/// address.
244pub enum Receiver {
245    #[cfg(feature = "orchard")]
246    Orchard(orchard::Address),
247    #[cfg(feature = "sapling")]
248    Sapling(PaymentAddress),
249    Transparent(TransparentAddress),
250}
251
252impl Receiver {
253    /// Converts this receiver to a [`ZcashAddress`] for the given network.
254    ///
255    /// This conversion function selects the least-capable address format possible; this means that
256    /// Orchard receivers will be rendered as Unified addresses, Sapling receivers will be rendered
257    /// as bare Sapling addresses, and Transparent receivers will be rendered as taddrs.
258    pub fn to_zcash_address(&self, net: NetworkType) -> ZcashAddress {
259        match self {
260            #[cfg(feature = "orchard")]
261            Receiver::Orchard(addr) => {
262                let receiver = unified::Receiver::Orchard(addr.to_raw_address_bytes());
263                let ua = unified::Address::try_from_items(vec![receiver])
264                    .expect("A unified address may contain a single Orchard receiver.");
265                ZcashAddress::from_unified(net, ua)
266            }
267            #[cfg(feature = "sapling")]
268            Receiver::Sapling(addr) => ZcashAddress::from_sapling(net, addr.to_bytes()),
269            Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
270                ZcashAddress::from_transparent_p2pkh(net, *data)
271            }
272            Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
273                ZcashAddress::from_transparent_p2sh(net, *data)
274            }
275        }
276    }
277
278    /// Returns whether or not this receiver corresponds to `addr`, or is contained
279    /// in `addr` when the latter is a Unified Address.
280    pub fn corresponds(&self, addr: &ZcashAddress) -> bool {
281        addr.matches_receiver(&match self {
282            #[cfg(feature = "orchard")]
283            Receiver::Orchard(addr) => unified::Receiver::Orchard(addr.to_raw_address_bytes()),
284            #[cfg(feature = "sapling")]
285            Receiver::Sapling(addr) => unified::Receiver::Sapling(addr.to_bytes()),
286            Receiver::Transparent(TransparentAddress::PublicKeyHash(data)) => {
287                unified::Receiver::P2pkh(*data)
288            }
289            Receiver::Transparent(TransparentAddress::ScriptHash(data)) => {
290                unified::Receiver::P2sh(*data)
291            }
292        })
293    }
294}
295
296/// An address that funds can be sent to.
297#[derive(Debug, PartialEq, Eq, Clone)]
298pub enum Address {
299    /// A Sapling payment address.
300    #[cfg(feature = "sapling")]
301    Sapling(PaymentAddress),
302
303    /// A transparent address corresponding to either a public key hash or a script hash.
304    Transparent(TransparentAddress),
305
306    /// A [ZIP 316] Unified Address.
307    ///
308    /// [ZIP 316]: https://zips.z.cash/zip-0316
309    Unified(UnifiedAddress),
310
311    /// A [ZIP 320] transparent-source-only P2PKH address, or "TEX address".
312    ///
313    /// [ZIP 320]: https://zips.z.cash/zip-0320
314    Tex([u8; 20]),
315}
316
317#[cfg(feature = "sapling")]
318impl From<PaymentAddress> for Address {
319    fn from(addr: PaymentAddress) -> Self {
320        Address::Sapling(addr)
321    }
322}
323
324impl From<TransparentAddress> for Address {
325    fn from(addr: TransparentAddress) -> Self {
326        Address::Transparent(addr)
327    }
328}
329
330impl From<UnifiedAddress> for Address {
331    fn from(addr: UnifiedAddress) -> Self {
332        Address::Unified(addr)
333    }
334}
335
336impl TryFromAddress for Address {
337    type Error = &'static str;
338
339    #[cfg(feature = "sapling")]
340    fn try_from_sapling(
341        _net: NetworkType,
342        data: [u8; 43],
343    ) -> Result<Self, ConversionError<Self::Error>> {
344        let pa = PaymentAddress::from_bytes(&data).ok_or("Invalid Sapling payment address")?;
345        Ok(pa.into())
346    }
347
348    fn try_from_unified(
349        _net: NetworkType,
350        ua: zcash_address::unified::Address,
351    ) -> Result<Self, ConversionError<Self::Error>> {
352        UnifiedAddress::try_from(ua)
353            .map_err(ConversionError::User)
354            .map(Address::from)
355    }
356
357    fn try_from_transparent_p2pkh(
358        _net: NetworkType,
359        data: [u8; 20],
360    ) -> Result<Self, ConversionError<Self::Error>> {
361        Ok(TransparentAddress::PublicKeyHash(data).into())
362    }
363
364    fn try_from_transparent_p2sh(
365        _net: NetworkType,
366        data: [u8; 20],
367    ) -> Result<Self, ConversionError<Self::Error>> {
368        Ok(TransparentAddress::ScriptHash(data).into())
369    }
370
371    fn try_from_tex(
372        _net: NetworkType,
373        data: [u8; 20],
374    ) -> Result<Self, ConversionError<Self::Error>> {
375        Ok(Address::Tex(data))
376    }
377}
378
379impl Address {
380    /// Attempts to decode an [`Address`] value from its [`ZcashAddress`] encoded representation.
381    ///
382    /// Returns `None` if any error is encountered in decoding. Use
383    /// [`Self::try_from_zcash_address(s.parse()?)?`] if you need detailed error information.
384    pub fn decode<P: consensus::Parameters>(params: &P, s: &str) -> Option<Self> {
385        Self::try_from_zcash_address(params, s.parse::<ZcashAddress>().ok()?).ok()
386    }
387
388    /// Attempts to decode an [`Address`] value from its [`ZcashAddress`] encoded representation.
389    pub fn try_from_zcash_address<P: consensus::Parameters>(
390        params: &P,
391        zaddr: ZcashAddress,
392    ) -> Result<Self, ConversionError<&'static str>> {
393        zaddr.convert_if_network(params.network_type())
394    }
395
396    /// Converts this [`Address`] to its encoded [`ZcashAddress`] representation.
397    pub fn to_zcash_address<P: consensus::Parameters>(&self, params: &P) -> ZcashAddress {
398        let net = params.network_type();
399
400        match self {
401            #[cfg(feature = "sapling")]
402            Address::Sapling(pa) => ZcashAddress::from_sapling(net, pa.to_bytes()),
403            Address::Transparent(addr) => match addr {
404                TransparentAddress::PublicKeyHash(data) => {
405                    ZcashAddress::from_transparent_p2pkh(net, *data)
406                }
407                TransparentAddress::ScriptHash(data) => {
408                    ZcashAddress::from_transparent_p2sh(net, *data)
409                }
410            },
411            Address::Unified(ua) => ua.to_address(net),
412            Address::Tex(data) => ZcashAddress::from_tex(net, *data),
413        }
414    }
415
416    /// Converts this [`Address`] to its encoded string representation.
417    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
418        self.to_zcash_address(params).to_string()
419    }
420
421    /// Returns whether or not this [`Address`] can receive funds in the specified pool.
422    pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
423        match self {
424            #[cfg(feature = "sapling")]
425            Address::Sapling(_) => {
426                matches!(pool_type, PoolType::Shielded(ShieldedProtocol::Sapling))
427            }
428            Address::Transparent(_) | Address::Tex(_) => {
429                matches!(pool_type, PoolType::Transparent)
430            }
431            Address::Unified(ua) => match pool_type {
432                PoolType::Transparent => ua.has_transparent(),
433                PoolType::Shielded(ShieldedProtocol::Sapling) => ua.has_sapling(),
434                PoolType::Shielded(ShieldedProtocol::Orchard) => ua.has_orchard(),
435            },
436        }
437    }
438
439    /// Returns the transparent address corresponding to this address, if it is a transparent
440    /// address, a Unified address with a transparent receiver, or ZIP 320 (TEX) address.
441    pub fn to_transparent_address(&self) -> Option<TransparentAddress> {
442        match self {
443            #[cfg(feature = "sapling")]
444            Address::Sapling(_) => None,
445            Address::Transparent(addr) => Some(*addr),
446            Address::Unified(ua) => ua.transparent().copied(),
447            Address::Tex(addr_bytes) => Some(TransparentAddress::PublicKeyHash(*addr_bytes)),
448        }
449    }
450
451    /// Returns the Sapling address corresponding to this address, if it is a ZIP 32-encoded
452    /// Sapling address, or a Unified address with a Sapling receiver.
453    #[cfg(feature = "sapling")]
454    pub fn to_sapling_address(&self) -> Option<PaymentAddress> {
455        match self {
456            Address::Sapling(addr) => Some(*addr),
457            Address::Transparent(_) => None,
458            Address::Unified(ua) => ua.sapling().copied(),
459            Address::Tex(_) => None,
460        }
461    }
462}
463
464#[cfg(all(
465    any(
466        feature = "orchard",
467        feature = "sapling",
468        feature = "transparent-inputs"
469    ),
470    any(test, feature = "test-dependencies")
471))]
472pub mod testing {
473    use proptest::prelude::*;
474    use zcash_protocol::consensus::Network;
475
476    use crate::keys::{testing::arb_unified_spending_key, UnifiedAddressRequest};
477
478    use super::{Address, UnifiedAddress};
479
480    #[cfg(feature = "sapling")]
481    use sapling::testing::arb_payment_address;
482    use transparent::address::testing::arb_transparent_addr;
483
484    pub fn arb_unified_addr(
485        params: Network,
486        request: UnifiedAddressRequest,
487    ) -> impl Strategy<Value = UnifiedAddress> {
488        arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).0)
489    }
490
491    #[cfg(feature = "sapling")]
492    pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
493        prop_oneof![
494            arb_payment_address().prop_map(Address::Sapling),
495            arb_transparent_addr().prop_map(Address::Transparent),
496            arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
497            proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
498        ]
499    }
500
501    #[cfg(not(feature = "sapling"))]
502    pub fn arb_addr(request: UnifiedAddressRequest) -> impl Strategy<Value = Address> {
503        return prop_oneof![
504            arb_transparent_addr().prop_map(Address::Transparent),
505            arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
506            proptest::array::uniform20(any::<u8>()).prop_map(Address::Tex),
507        ];
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use zcash_address::test_vectors;
514    use zcash_protocol::consensus::MAIN_NETWORK;
515
516    use super::{Address, UnifiedAddress};
517
518    #[cfg(feature = "sapling")]
519    use crate::keys::sapling;
520
521    #[cfg(any(feature = "orchard", feature = "sapling"))]
522    use zip32::AccountId;
523
524    #[test]
525    #[cfg(any(feature = "orchard", feature = "sapling"))]
526    fn ua_round_trip() {
527        #[cfg(feature = "orchard")]
528        let orchard = {
529            let sk =
530                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
531            let fvk = orchard::keys::FullViewingKey::from(&sk);
532            Some(fvk.address_at(0u32, orchard::keys::Scope::External))
533        };
534
535        #[cfg(feature = "sapling")]
536        let sapling = {
537            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
538            let dfvk = extsk.to_diversifiable_full_viewing_key();
539            Some(dfvk.default_address().1)
540        };
541
542        let transparent = None;
543
544        #[cfg(all(feature = "orchard", feature = "sapling"))]
545        let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
546
547        #[cfg(all(not(feature = "orchard"), feature = "sapling"))]
548        let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
549
550        #[cfg(all(feature = "orchard", not(feature = "sapling")))]
551        let ua = UnifiedAddress::from_receivers(orchard, transparent).unwrap();
552
553        let addr = Address::Unified(ua);
554        let addr_str = addr.encode(&MAIN_NETWORK);
555        assert_eq!(Address::decode(&MAIN_NETWORK, &addr_str), Some(addr));
556    }
557
558    #[test]
559    #[cfg(not(any(feature = "orchard", feature = "sapling")))]
560    fn ua_round_trip() {
561        let transparent = None;
562        assert_eq!(UnifiedAddress::from_receivers(transparent), None)
563    }
564
565    #[test]
566    fn ua_parsing() {
567        for tv in test_vectors::UNIFIED {
568            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
569                Some(Address::Unified(ua)) => {
570                    assert_eq!(
571                        ua.has_transparent(),
572                        tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some()
573                    );
574                    #[cfg(feature = "sapling")]
575                    assert_eq!(ua.has_sapling(), tv.sapling_raw_addr.is_some());
576                    #[cfg(feature = "orchard")]
577                    assert_eq!(ua.has_orchard(), tv.orchard_raw_addr.is_some());
578                }
579                Some(_) => {
580                    panic!(
581                        "{} did not decode to a unified address value.",
582                        tv.unified_addr
583                    );
584                }
585                None => {
586                    panic!(
587                        "Failed to decode unified address from test vector: {}",
588                        tv.unified_addr
589                    );
590                }
591            }
592        }
593    }
594}