Skip to main content

zcash_address/
lib.rs

1//! *Parser for all defined Zcash address types.*
2//!
3//! This crate implements address parsing as a two-phase process, built around the opaque
4//! [`ZcashAddress`] type.
5//!
6//! - [`ZcashAddress`] can be parsed from, and encoded to, strings.
7//! - [`ZcashAddress::convert`] or [`ZcashAddress::convert_if_network`] can be used to
8//!   convert a parsed address into custom types that implement the [`TryFromAddress`] trait.
9//! - Custom types can be converted into a [`ZcashAddress`] via its implementation of the
10//!   [`ToAddress`] trait.
11//!
12//! ```text
13//!         s.parse()              .convert()
14//!         -------->              --------->
15//! Strings           ZcashAddress            Custom types
16//!         <--------              <---------
17//!         .encode()              ToAddress
18//! ```
19//!
20//! It is important to note that this crate does not depend on any of the Zcash protocol
21//! crates (e.g. `sapling-crypto` or `orchard`). This crate has minimal dependencies by
22//! design; it focuses solely on parsing, handling those concerns for you, while exposing
23//! APIs that enable you to convert the parsed data into the Rust types you want to use.
24//!
25//! # Using this crate
26//!
27//! ## I just need to validate Zcash addresses
28//!
29//! ```
30//! # use zcash_address::ZcashAddress;
31//! fn is_valid_zcash_address(addr_string: &str) -> bool {
32//!     addr_string.parse::<ZcashAddress>().is_ok()
33//! }
34//! ```
35//!
36//! ## I want to parse Zcash addresses in a Rust wallet app that uses the `zcash_primitives` transaction builder
37//!
38//! Use `zcash_client_backend::address::RecipientAddress`, which implements the traits in
39//! this crate to parse address strings into protocol types that work with the transaction
40//! builder in the `zcash_primitives` crate (as well as the wallet functionality in the
41//! `zcash_client_backend` crate itself).
42//!
43//! > We intend to refactor the key and address types from the `zcash_client_backend` and
44//! > `zcash_primitives` crates into a separate crate focused on dealing with Zcash key
45//! > material. That crate will then be what you should use.
46//!
47//! ## I want to parse Unified Addresses
48//!
49//! See the [`unified::Address`] documentation for examples.
50//!
51//! While the [`unified::Address`] type does have parsing methods, you should still parse
52//! your address strings with [`ZcashAddress`] and then convert; this will ensure that for
53//! other Zcash address types you get a [`ConversionError::Unsupported`], which is a
54//! better error for your users.
55//!
56//! ## I want to parse mainnet Zcash addresses in a language that supports C FFI
57//!
58//! As an example, you could use static functions to create the address types in the
59//! target language from the parsed data.
60//!
61//! ```
62//! use std::ffi::{CStr, c_char, c_void};
63//! use std::ptr;
64//!
65//! use zcash_address::{ConversionError, TryFromAddress, ZcashAddress};
66//! use zcash_protocol::consensus::NetworkType;
67//!
68//! // Functions that return a pointer to a heap-allocated address of the given kind in
69//! // the target language. These should be augmented to return any relevant errors.
70//! unsafe extern {
71//!     fn addr_from_sapling(data: *const u8) -> *mut c_void;
72//!     fn addr_from_transparent_p2pkh(data: *const u8) -> *mut c_void;
73//! }
74//!
75//! struct ParsedAddress(*mut c_void);
76//!
77//! impl TryFromAddress for ParsedAddress {
78//!     type Error = &'static str;
79//!
80//!     fn try_from_sapling(
81//!         _net: NetworkType,
82//!         data: [u8; 43],
83//!     ) -> Result<Self, ConversionError<Self::Error>> {
84//!         let parsed = unsafe { addr_from_sapling(data[..].as_ptr()) };
85//!         if parsed.is_null() {
86//!             Err("Reason for the failure".into())
87//!         } else {
88//!             Ok(Self(parsed))
89//!         }
90//!     }
91//!
92//!     fn try_from_transparent_p2pkh(
93//!         _net: NetworkType,
94//!         data: [u8; 20],
95//!     ) -> Result<Self, ConversionError<Self::Error>> {
96//!         let parsed = unsafe { addr_from_transparent_p2pkh(data[..].as_ptr()) };
97//!         if parsed.is_null() {
98//!             Err("Reason for the failure".into())
99//!         } else {
100//!             Ok(Self(parsed))
101//!         }
102//!     }
103//! }
104//!
105//! pub extern "C" fn parse_zcash_address(encoded: *const c_char) -> *mut c_void {
106//!     let encoded = unsafe { CStr::from_ptr(encoded) }.to_str().expect("valid");
107//!
108//!     let addr = match ZcashAddress::try_from_encoded(encoded) {
109//!         Ok(addr) => addr,
110//!         Err(e) => {
111//!             // This was either an invalid address encoding, or not a Zcash address.
112//!             // You should pass this error back across the FFI.
113//!             return ptr::null_mut();
114//!         }
115//!     };
116//!
117//!     match addr.convert_if_network::<ParsedAddress>(NetworkType::Main) {
118//!         Ok(parsed) => parsed.0,
119//!         Err(e) => {
120//!             // We didn't implement all of the methods of `TryFromAddress`, so if an
121//!             // address with one of those kinds is parsed, it will result in an error
122//!             // here that should be passed back across the FFI.
123//!             ptr::null_mut()
124//!         }
125//!     }
126//! }
127//! ```
128
129#![no_std]
130#![cfg_attr(docsrs, feature(doc_cfg))]
131#![cfg_attr(docsrs, doc(auto_cfg))]
132// Catch documentation errors caused by code changes.
133#![deny(rustdoc::broken_intra_doc_links)]
134
135#[macro_use]
136extern crate alloc;
137
138#[cfg(feature = "std")]
139extern crate std;
140
141use alloc::string::String;
142
143pub mod constants;
144mod convert;
145mod encoding;
146mod kind;
147
148#[cfg(feature = "test-dependencies")]
149pub mod test_vectors;
150
151pub use convert::{ConversionError, Converter, ToAddress, TryFromAddress, UnsupportedAddress};
152pub use encoding::ParseError;
153pub use kind::unified;
154use kind::unified::Receiver;
155
156use zcash_protocol::{PoolType, consensus::NetworkType};
157
158/// A Zcash address.
159#[derive(Clone, Debug, PartialEq, Eq, Hash)]
160pub struct ZcashAddress {
161    net: NetworkType,
162    kind: AddressKind,
163}
164
165/// Known kinds of Zcash addresses.
166#[derive(Clone, Debug, PartialEq, Eq, Hash)]
167enum AddressKind {
168    Sprout([u8; 64]),
169    Sapling([u8; 43]),
170    Unified(unified::Address),
171    P2pkh([u8; 20]),
172    P2sh([u8; 20]),
173    Tex([u8; 20]),
174}
175
176impl ZcashAddress {
177    /// Encodes this Zcash address in its canonical string representation.
178    ///
179    /// This provides the encoded string representation of the address as defined by the
180    /// [Zcash protocol specification](https://zips.z.cash/protocol/protocol.pdf) and/or
181    /// [ZIP 316](https://zips.z.cash/zip-0316). The [`Display` implementation] can also
182    /// be used to produce this encoding using [`address.to_string()`].
183    ///
184    /// [`Display` implementation]: core::fmt::Display
185    /// [`address.to_string()`]: alloc::string::ToString
186    pub fn encode(&self) -> String {
187        format!("{self}")
188    }
189
190    /// Attempts to parse the given string as a Zcash address.
191    ///
192    /// This simply calls [`s.parse()`], leveraging the [`FromStr` implementation].
193    ///
194    /// [`s.parse()`]: str::parse
195    /// [`FromStr` implementation]: ZcashAddress#impl-FromStr
196    ///
197    /// # Errors
198    ///
199    /// - If the parser can detect that the string _must_ contain an address encoding used
200    ///   by Zcash, [`ParseError::InvalidEncoding`] will be returned if any subsequent
201    ///   part of that encoding is invalid.
202    ///
203    /// - In all other cases, [`ParseError::NotZcash`] will be returned on failure.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use zcash_address::ZcashAddress;
209    ///
210    /// let encoded = "zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya";
211    /// let addr = ZcashAddress::try_from_encoded(&encoded);
212    /// assert_eq!(encoded.parse(), addr);
213    /// ```
214    pub fn try_from_encoded(s: &str) -> Result<Self, ParseError> {
215        s.parse()
216    }
217
218    /// Converts this address into another type.
219    ///
220    /// `convert` can convert into any type that implements the [`TryFromAddress`] trait.
221    /// This enables `ZcashAddress` to be used as a common parsing and serialization
222    /// interface for Zcash addresses, while delegating operations on those addresses
223    /// (such as constructing transactions) to downstream crates.
224    ///
225    /// If you want to get the encoded string for this address, use the [`encode`]
226    /// method or the [`Display` implementation] via [`address.to_string()`] instead.
227    ///
228    /// [`encode`]: Self::encode
229    /// [`Display` implementation]: core::fmt::Display
230    /// [`address.to_string()`]: alloc::string::ToString
231    pub fn convert<T: TryFromAddress>(self) -> Result<T, ConversionError<T::Error>> {
232        match self.kind {
233            AddressKind::Sprout(data) => T::try_from_sprout(self.net, data),
234            AddressKind::Sapling(data) => T::try_from_sapling(self.net, data),
235            AddressKind::Unified(data) => T::try_from_unified(self.net, data),
236            AddressKind::P2pkh(data) => T::try_from_transparent_p2pkh(self.net, data),
237            AddressKind::P2sh(data) => T::try_from_transparent_p2sh(self.net, data),
238            AddressKind::Tex(data) => T::try_from_tex(self.net, data),
239        }
240    }
241
242    /// Converts this address into another type, if it matches the expected network.
243    ///
244    /// `convert_if_network` can convert into any type that implements the [`TryFromAddress`]
245    /// trait. This enables `ZcashAddress` to be used as a common parsing and serialization
246    /// interface for Zcash addresses, while delegating operations on those addresses (such as
247    /// constructing transactions) to downstream crates.
248    ///
249    /// If you want to get the encoded string for this address, use the [`encode`]
250    /// method or the [`Display` implementation] via [`address.to_string()`] instead.
251    ///
252    /// [`encode`]: Self::encode
253    /// [`Display` implementation]: core::fmt::Display
254    /// [`address.to_string()`]: alloc::string::ToString
255    pub fn convert_if_network<T: TryFromAddress>(
256        self,
257        net: NetworkType,
258    ) -> Result<T, ConversionError<T::Error>> {
259        let network_matches = self.net == net;
260        // The Sprout and transparent address encodings use the same prefix for testnet
261        // and regtest, so we need to allow parsing testnet addresses as regtest.
262        let regtest_exception =
263            network_matches || (self.net == NetworkType::Test && net == NetworkType::Regtest);
264
265        match self.kind {
266            AddressKind::Sprout(data) if regtest_exception => T::try_from_sprout(net, data),
267            AddressKind::Sapling(data) if network_matches => T::try_from_sapling(net, data),
268            AddressKind::Unified(data) if network_matches => T::try_from_unified(net, data),
269            AddressKind::P2pkh(data) if regtest_exception => {
270                T::try_from_transparent_p2pkh(net, data)
271            }
272            AddressKind::P2sh(data) if regtest_exception => T::try_from_transparent_p2sh(net, data),
273            AddressKind::Tex(data) if network_matches => T::try_from_tex(net, data),
274            _ => Err(ConversionError::IncorrectNetwork {
275                expected: net,
276                actual: self.net,
277            }),
278        }
279    }
280
281    /// Converts this address into another type using the specified converter.
282    ///
283    /// `convert` can convert into any type `T` for which an implementation of the [`Converter<T>`]
284    /// trait exists. This enables conversion of [`ZcashAddress`] values into other types to rely
285    /// on additional context.
286    pub fn convert_with<T, C: Converter<T>>(
287        self,
288        converter: C,
289    ) -> Result<T, ConversionError<C::Error>> {
290        match self.kind {
291            AddressKind::Sprout(data) => converter.convert_sprout(self.net, data),
292            AddressKind::Sapling(data) => converter.convert_sapling(self.net, data),
293            AddressKind::Unified(data) => converter.convert_unified(self.net, data),
294            AddressKind::P2pkh(data) => converter.convert_transparent_p2pkh(self.net, data),
295            AddressKind::P2sh(data) => converter.convert_transparent_p2sh(self.net, data),
296            AddressKind::Tex(data) => converter.convert_tex(self.net, data),
297        }
298    }
299
300    /// Returns whether this address has the ability to receive transfers of the given pool type.
301    pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
302        use AddressKind::*;
303        match &self.kind {
304            Sprout(_) => false,
305            Sapling(_) => pool_type == PoolType::SAPLING,
306            Unified(addr) => addr.has_receiver_of_type(pool_type),
307            P2pkh(_) | P2sh(_) | Tex(_) => pool_type == PoolType::TRANSPARENT,
308        }
309    }
310
311    /// Returns whether this address can receive a memo.
312    pub fn can_receive_memo(&self) -> bool {
313        use AddressKind::*;
314        match &self.kind {
315            Sprout(_) | Sapling(_) => true,
316            Unified(addr) => addr.can_receive_memo(),
317            P2pkh(_) | P2sh(_) | Tex(_) => false,
318        }
319    }
320
321    /// Returns whether the only recognized receivers of this address are transparent protocol
322    /// receivers.
323    ///
324    /// A unified address with unknown receivers may be considered transparent-only if it does not
325    /// also contain a Sapling or Orchard receiver, i.e. if ordinary transaction construction
326    /// methods would produce a transaction sending to a transparent address.
327    pub fn is_transparent_only(&self) -> bool {
328        use AddressKind::*;
329        match &self.kind {
330            Sprout(_) | Sapling(_) => false,
331            Unified(addr) => {
332                addr.has_receiver_of_type(PoolType::Transparent)
333                    && !(addr.has_receiver_of_type(PoolType::SAPLING)
334                        || addr.has_receiver_of_type(PoolType::ORCHARD))
335            }
336            P2pkh(_) | P2sh(_) | Tex(_) => true,
337        }
338    }
339
340    /// Returns whether or not this address contains or corresponds to the given unified address
341    /// receiver.
342    pub fn matches_receiver(&self, receiver: &Receiver) -> bool {
343        match (&self.kind, receiver) {
344            (AddressKind::Unified(ua), r) => ua.contains_receiver(r),
345            (AddressKind::Sapling(d), Receiver::Sapling(r)) => r == d,
346            (AddressKind::P2pkh(d), Receiver::P2pkh(r)) => r == d,
347            (AddressKind::Tex(d), Receiver::P2pkh(r)) => r == d,
348            (AddressKind::P2sh(d), Receiver::P2sh(r)) => r == d,
349            _ => false,
350        }
351    }
352}
353
354#[cfg(feature = "test-dependencies")]
355pub mod testing {
356    use core::convert::TryInto;
357
358    use proptest::{array::uniform20, collection::vec, prelude::any, prop_compose, prop_oneof};
359
360    use crate::{AddressKind, ZcashAddress, unified::address::testing::arb_unified_address};
361    use zcash_protocol::consensus::NetworkType;
362
363    prop_compose! {
364        fn arb_sprout_addr_kind()(
365            r_bytes in vec(any::<u8>(), 64)
366        ) -> AddressKind {
367            AddressKind::Sprout(r_bytes.try_into().unwrap())
368        }
369    }
370
371    prop_compose! {
372        fn arb_sapling_addr_kind()(
373            r_bytes in vec(any::<u8>(), 43)
374        ) -> AddressKind {
375            AddressKind::Sapling(r_bytes.try_into().unwrap())
376        }
377    }
378
379    prop_compose! {
380        fn arb_p2pkh_addr_kind()(
381            r_bytes in uniform20(any::<u8>())
382        ) -> AddressKind {
383            AddressKind::P2pkh(r_bytes)
384        }
385    }
386
387    prop_compose! {
388        fn arb_p2sh_addr_kind()(
389            r_bytes in uniform20(any::<u8>())
390        ) -> AddressKind {
391            AddressKind::P2sh(r_bytes)
392        }
393    }
394
395    prop_compose! {
396        fn arb_unified_addr_kind()(
397            uaddr in arb_unified_address()
398        ) -> AddressKind {
399            AddressKind::Unified(uaddr)
400        }
401    }
402
403    prop_compose! {
404        fn arb_tex_addr_kind()(
405            r_bytes in uniform20(any::<u8>())
406        ) -> AddressKind {
407            AddressKind::Tex(r_bytes)
408        }
409    }
410
411    prop_compose! {
412        /// Create an arbitrary, structurally-valid `ZcashAddress` value.
413        ///
414        /// Note that the data contained in the generated address does _not_ necessarily correspond
415        /// to a valid address according to the Zcash protocol; binary data in the resulting value
416        /// is entirely random.
417        pub fn arb_address(net: NetworkType)(
418            kind in prop_oneof!(
419                arb_sprout_addr_kind(),
420                arb_sapling_addr_kind(),
421                arb_p2pkh_addr_kind(),
422                arb_p2sh_addr_kind(),
423                arb_unified_addr_kind(),
424                arb_tex_addr_kind()
425            )
426        ) -> ZcashAddress {
427            ZcashAddress { net, kind }
428        }
429    }
430}