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//! 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
143mod convert;
144mod encoding;
145mod kind;
146
147#[cfg(any(test, feature = "test-dependencies"))]
148pub mod test_vectors;
149
150pub use convert::{ConversionError, Converter, ToAddress, TryFromAddress, UnsupportedAddress};
151pub use encoding::ParseError;
152pub use kind::unified;
153use kind::unified::Receiver;
154
155use zcash_protocol::{consensus::NetworkType, PoolType};
156
157/// A Zcash address.
158#[derive(Clone, Debug, PartialEq, Eq, Hash)]
159pub struct ZcashAddress {
160 net: NetworkType,
161 kind: AddressKind,
162}
163
164/// Known kinds of Zcash addresses.
165#[derive(Clone, Debug, PartialEq, Eq, Hash)]
166enum AddressKind {
167 Sprout([u8; 64]),
168 Sapling([u8; 43]),
169 Unified(unified::Address),
170 P2pkh([u8; 20]),
171 P2sh([u8; 20]),
172 Tex([u8; 20]),
173}
174
175impl ZcashAddress {
176 /// Encodes this Zcash address in its canonical string representation.
177 ///
178 /// This provides the encoded string representation of the address as defined by the
179 /// [Zcash protocol specification](https://zips.z.cash/protocol/protocol.pdf) and/or
180 /// [ZIP 316](https://zips.z.cash/zip-0316). The [`Display` implementation] can also
181 /// be used to produce this encoding using [`address.to_string()`].
182 ///
183 /// [`Display` implementation]: core::fmt::Display
184 /// [`address.to_string()`]: alloc::string::ToString
185 pub fn encode(&self) -> String {
186 format!("{}", self)
187 }
188
189 /// Attempts to parse the given string as a Zcash address.
190 ///
191 /// This simply calls [`s.parse()`], leveraging the [`FromStr` implementation].
192 ///
193 /// [`s.parse()`]: str::parse
194 /// [`FromStr` implementation]: ZcashAddress#impl-FromStr
195 ///
196 /// # Errors
197 ///
198 /// - If the parser can detect that the string _must_ contain an address encoding used
199 /// by Zcash, [`ParseError::InvalidEncoding`] will be returned if any subsequent
200 /// part of that encoding is invalid.
201 ///
202 /// - In all other cases, [`ParseError::NotZcash`] will be returned on failure.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// use zcash_address::ZcashAddress;
208 ///
209 /// let encoded = "zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya";
210 /// let addr = ZcashAddress::try_from_encoded(&encoded);
211 /// assert_eq!(encoded.parse(), addr);
212 /// ```
213 pub fn try_from_encoded(s: &str) -> Result<Self, ParseError> {
214 s.parse()
215 }
216
217 /// Converts this address into another type.
218 ///
219 /// `convert` can convert into any type that implements the [`TryFromAddress`] trait.
220 /// This enables `ZcashAddress` to be used as a common parsing and serialization
221 /// interface for Zcash addresses, while delegating operations on those addresses
222 /// (such as constructing transactions) to downstream crates.
223 ///
224 /// If you want to get the encoded string for this address, use the [`encode`]
225 /// method or the [`Display` implementation] via [`address.to_string()`] instead.
226 ///
227 /// [`encode`]: Self::encode
228 /// [`Display` implementation]: core::fmt::Display
229 /// [`address.to_string()`]: alloc::string::ToString
230 pub fn convert<T: TryFromAddress>(self) -> Result<T, ConversionError<T::Error>> {
231 match self.kind {
232 AddressKind::Sprout(data) => T::try_from_sprout(self.net, data),
233 AddressKind::Sapling(data) => T::try_from_sapling(self.net, data),
234 AddressKind::Unified(data) => T::try_from_unified(self.net, data),
235 AddressKind::P2pkh(data) => T::try_from_transparent_p2pkh(self.net, data),
236 AddressKind::P2sh(data) => T::try_from_transparent_p2sh(self.net, data),
237 AddressKind::Tex(data) => T::try_from_tex(self.net, data),
238 }
239 }
240
241 /// Converts this address into another type, if it matches the expected network.
242 ///
243 /// `convert_if_network` can convert into any type that implements the [`TryFromAddress`]
244 /// trait. This enables `ZcashAddress` to be used as a common parsing and serialization
245 /// interface for Zcash addresses, while delegating operations on those addresses (such as
246 /// constructing transactions) to downstream crates.
247 ///
248 /// If you want to get the encoded string for this address, use the [`encode`]
249 /// method or the [`Display` implementation] via [`address.to_string()`] instead.
250 ///
251 /// [`encode`]: Self::encode
252 /// [`Display` implementation]: core::fmt::Display
253 /// [`address.to_string()`]: alloc::string::ToString
254 pub fn convert_if_network<T: TryFromAddress>(
255 self,
256 net: NetworkType,
257 ) -> Result<T, ConversionError<T::Error>> {
258 let network_matches = self.net == net;
259 // The Sprout and transparent address encodings use the same prefix for testnet
260 // and regtest, so we need to allow parsing testnet addresses as regtest.
261 let regtest_exception =
262 network_matches || (self.net == NetworkType::Test && net == NetworkType::Regtest);
263
264 match self.kind {
265 AddressKind::Sprout(data) if regtest_exception => T::try_from_sprout(net, data),
266 AddressKind::Sapling(data) if network_matches => T::try_from_sapling(net, data),
267 AddressKind::Unified(data) if network_matches => T::try_from_unified(net, data),
268 AddressKind::P2pkh(data) if regtest_exception => {
269 T::try_from_transparent_p2pkh(net, data)
270 }
271 AddressKind::P2sh(data) if regtest_exception => T::try_from_transparent_p2sh(net, data),
272 AddressKind::Tex(data) if network_matches => T::try_from_tex(net, data),
273 _ => Err(ConversionError::IncorrectNetwork {
274 expected: net,
275 actual: self.net,
276 }),
277 }
278 }
279
280 /// Converts this address into another type using the specified converter.
281 ///
282 /// `convert` can convert into any type `T` for which an implementation of the [`Converter<T>`]
283 /// trait exists. This enables conversion of [`ZcashAddress`] values into other types to rely
284 /// on additional context.
285 pub fn convert_with<T, C: Converter<T>>(
286 self,
287 converter: C,
288 ) -> Result<T, ConversionError<C::Error>> {
289 match self.kind {
290 AddressKind::Sprout(data) => converter.convert_sprout(self.net, data),
291 AddressKind::Sapling(data) => converter.convert_sapling(self.net, data),
292 AddressKind::Unified(data) => converter.convert_unified(self.net, data),
293 AddressKind::P2pkh(data) => converter.convert_transparent_p2pkh(self.net, data),
294 AddressKind::P2sh(data) => converter.convert_transparent_p2sh(self.net, data),
295 AddressKind::Tex(data) => converter.convert_tex(self.net, data),
296 }
297 }
298
299 /// Returns whether this address has the ability to receive transfers of the given pool type.
300 pub fn can_receive_as(&self, pool_type: PoolType) -> bool {
301 use AddressKind::*;
302 match &self.kind {
303 Sprout(_) => false,
304 Sapling(_) => pool_type == PoolType::SAPLING,
305 Unified(addr) => addr.has_receiver_of_type(pool_type),
306 P2pkh(_) | P2sh(_) | Tex(_) => pool_type == PoolType::TRANSPARENT,
307 }
308 }
309
310 /// Returns whether this address can receive a memo.
311 pub fn can_receive_memo(&self) -> bool {
312 use AddressKind::*;
313 match &self.kind {
314 Sprout(_) | Sapling(_) => true,
315 Unified(addr) => addr.can_receive_memo(),
316 P2pkh(_) | P2sh(_) | Tex(_) => false,
317 }
318 }
319
320 /// Returns whether the only recognized receivers of this address are transparent protocol
321 /// receivers.
322 ///
323 /// A unified address with unknown receivers may be considered transparent-only if it does not
324 /// also contain a Sapling or Orchard receiver, i.e. if ordinary transaction construction
325 /// methods would produce a transaction sending to a transparent address.
326 pub fn is_transparent_only(&self) -> bool {
327 use AddressKind::*;
328 match &self.kind {
329 Sprout(_) | Sapling(_) => false,
330 Unified(addr) => {
331 addr.has_receiver_of_type(PoolType::Transparent)
332 && !(addr.has_receiver_of_type(PoolType::SAPLING)
333 || addr.has_receiver_of_type(PoolType::ORCHARD))
334 }
335 P2pkh(_) | P2sh(_) | Tex(_) => true,
336 }
337 }
338
339 /// Returns whether or not this address contains or corresponds to the given unified address
340 /// receiver.
341 pub fn matches_receiver(&self, receiver: &Receiver) -> bool {
342 match (&self.kind, receiver) {
343 (AddressKind::Unified(ua), r) => ua.contains_receiver(r),
344 (AddressKind::Sapling(d), Receiver::Sapling(r)) => r == d,
345 (AddressKind::P2pkh(d), Receiver::P2pkh(r)) => r == d,
346 (AddressKind::Tex(d), Receiver::P2pkh(r)) => r == d,
347 (AddressKind::P2sh(d), Receiver::P2sh(r)) => r == d,
348 _ => false,
349 }
350 }
351}
352
353#[cfg(feature = "test-dependencies")]
354pub mod testing {
355 use core::convert::TryInto;
356
357 use proptest::{array::uniform20, collection::vec, prelude::any, prop_compose, prop_oneof};
358
359 use crate::{unified::address::testing::arb_unified_address, AddressKind, ZcashAddress};
360 use zcash_protocol::consensus::NetworkType;
361
362 prop_compose! {
363 fn arb_sprout_addr_kind()(
364 r_bytes in vec(any::<u8>(), 64)
365 ) -> AddressKind {
366 AddressKind::Sprout(r_bytes.try_into().unwrap())
367 }
368 }
369
370 prop_compose! {
371 fn arb_sapling_addr_kind()(
372 r_bytes in vec(any::<u8>(), 43)
373 ) -> AddressKind {
374 AddressKind::Sapling(r_bytes.try_into().unwrap())
375 }
376 }
377
378 prop_compose! {
379 fn arb_p2pkh_addr_kind()(
380 r_bytes in uniform20(any::<u8>())
381 ) -> AddressKind {
382 AddressKind::P2pkh(r_bytes)
383 }
384 }
385
386 prop_compose! {
387 fn arb_p2sh_addr_kind()(
388 r_bytes in uniform20(any::<u8>())
389 ) -> AddressKind {
390 AddressKind::P2sh(r_bytes)
391 }
392 }
393
394 prop_compose! {
395 fn arb_unified_addr_kind()(
396 uaddr in arb_unified_address()
397 ) -> AddressKind {
398 AddressKind::Unified(uaddr)
399 }
400 }
401
402 prop_compose! {
403 fn arb_tex_addr_kind()(
404 r_bytes in uniform20(any::<u8>())
405 ) -> AddressKind {
406 AddressKind::Tex(r_bytes)
407 }
408 }
409
410 prop_compose! {
411 /// Create an arbitrary, structurally-valid `ZcashAddress` value.
412 ///
413 /// Note that the data contained in the generated address does _not_ necessarily correspond
414 /// to a valid address according to the Zcash protocol; binary data in the resulting value
415 /// is entirely random.
416 pub fn arb_address(net: NetworkType)(
417 kind in prop_oneof!(
418 arb_sprout_addr_kind(),
419 arb_sapling_addr_kind(),
420 arb_p2pkh_addr_kind(),
421 arb_p2sh_addr_kind(),
422 arb_unified_addr_kind(),
423 arb_tex_addr_kind()
424 )
425 ) -> ZcashAddress {
426 ZcashAddress { net, kind }
427 }
428 }
429}