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}