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