zcash_address/
convert.rs

1use core::fmt;
2
3#[cfg(feature = "std")]
4use std::error::Error;
5
6use zcash_protocol::consensus::NetworkType;
7
8use crate::{kind::*, AddressKind, ZcashAddress};
9
10/// An error indicating that an address type is not supported for conversion.
11#[derive(Debug)]
12pub struct UnsupportedAddress(&'static str);
13
14impl fmt::Display for UnsupportedAddress {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "Zcash {} addresses are not supported", self.0)
17    }
18}
19
20/// An error encountered while converting a parsed [`ZcashAddress`] into another type.
21#[derive(Debug)]
22pub enum ConversionError<E> {
23    /// The address is for the wrong network.
24    IncorrectNetwork {
25        expected: NetworkType,
26        actual: NetworkType,
27    },
28    /// The address type is not supported by the target type.
29    Unsupported(UnsupportedAddress),
30    /// A conversion error returned by the target type.
31    User(E),
32}
33
34impl<E> From<E> for ConversionError<E> {
35    fn from(e: E) -> Self {
36        ConversionError::User(e)
37    }
38}
39
40impl<E: fmt::Display> fmt::Display for ConversionError<E> {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::IncorrectNetwork { expected, actual } => write!(
44                f,
45                "Address is for {:?} but we expected {:?}",
46                actual, expected,
47            ),
48            Self::Unsupported(e) => e.fmt(f),
49            Self::User(e) => e.fmt(f),
50        }
51    }
52}
53
54#[cfg(feature = "std")]
55impl Error for UnsupportedAddress {}
56#[cfg(feature = "std")]
57impl<E: Error + 'static> Error for ConversionError<E> {
58    fn source(&self) -> Option<&(dyn Error + 'static)> {
59        match self {
60            ConversionError::IncorrectNetwork { .. } | ConversionError::Unsupported(_) => None,
61            ConversionError::User(e) => Some(e),
62        }
63    }
64}
65
66/// A helper trait for converting a [`ZcashAddress`] into a network-agnostic type.
67///
68/// A blanket implementation of [`TryFromAddress`] is provided for `(NetworkType, T)` where
69/// `T: TryFromRawAddress`.
70///
71/// [`ZcashAddress`]: crate::ZcashAddress
72///
73/// # Examples
74///
75/// ```
76/// use zcash_address::{ConversionError, TryFromRawAddress, UnsupportedAddress, ZcashAddress};
77/// use zcash_protocol::consensus::NetworkType;
78///
79/// #[derive(Debug, PartialEq)]
80/// struct MySapling([u8; 43]);
81///
82/// // Implement the TryFromRawAddress trait, overriding whichever conversion methods match
83/// // your requirements for the resulting type.
84/// impl TryFromRawAddress for MySapling {
85///     // In this example we aren't checking the validity of the inner Sapling address,
86///     // but your code should do so!
87///     type Error = &'static str;
88///
89///     fn try_from_raw_sapling(data: [u8; 43]) -> Result<Self, ConversionError<Self::Error>> {
90///         Ok(MySapling(data))
91///     }
92/// }
93///
94/// // For a supported address type, the conversion works.
95/// let addr_string = "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g";
96///
97/// // You can use `ZcashAddress::convert_if_network` to get your type directly.
98/// let addr: ZcashAddress = addr_string.parse().unwrap();
99/// let converted = addr.convert_if_network::<MySapling>(NetworkType::Main);
100/// assert!(converted.is_ok());
101/// assert_eq!(converted.unwrap(), MySapling([0; 43]));
102///
103/// // Using `ZcashAddress::convert` gives us the tuple `(network, converted_addr)`.
104/// let addr: ZcashAddress = addr_string.parse().unwrap();
105/// let converted = addr.convert::<(_, MySapling)>();
106/// assert!(converted.is_ok());
107/// assert_eq!(converted.unwrap(), (NetworkType::Main, MySapling([0; 43])));
108///
109/// // For an unsupported address type, we get an error.
110/// let addr: ZcashAddress = "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse().unwrap();
111/// assert_eq!(
112///     addr.convert::<(_, MySapling)>().unwrap_err().to_string(),
113///     "Zcash transparent P2PKH addresses are not supported",
114/// );
115/// ```
116pub trait TryFromRawAddress: Sized {
117    /// Conversion errors for the user type (e.g. failing to parse the data passed to
118    /// [`Self::try_from_raw_sapling`] as a valid Sapling address).
119    type Error;
120
121    fn try_from_raw_sprout(data: [u8; 64]) -> Result<Self, ConversionError<Self::Error>> {
122        let _ = data;
123        Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
124    }
125
126    fn try_from_raw_sapling(data: [u8; 43]) -> Result<Self, ConversionError<Self::Error>> {
127        let _ = data;
128        Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
129    }
130
131    fn try_from_raw_unified(data: unified::Address) -> Result<Self, ConversionError<Self::Error>> {
132        let _ = data;
133        Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
134    }
135
136    fn try_from_raw_transparent_p2pkh(
137        data: [u8; 20],
138    ) -> Result<Self, ConversionError<Self::Error>> {
139        let _ = data;
140        Err(ConversionError::Unsupported(UnsupportedAddress(
141            "transparent P2PKH",
142        )))
143    }
144
145    fn try_from_raw_transparent_p2sh(data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
146        let _ = data;
147        Err(ConversionError::Unsupported(UnsupportedAddress(
148            "transparent P2SH",
149        )))
150    }
151
152    fn try_from_raw_tex(data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
153        let _ = data;
154        Err(ConversionError::Unsupported(UnsupportedAddress(
155            "transparent-source restricted P2PKH",
156        )))
157    }
158}
159
160/// A helper trait for converting a [`ZcashAddress`] into another type.
161///
162/// [`ZcashAddress`]: crate::ZcashAddress
163///
164/// # Examples
165///
166/// ```
167/// use zcash_address::{ConversionError, TryFromAddress, UnsupportedAddress, ZcashAddress};
168/// use zcash_protocol::consensus::NetworkType;
169///
170/// #[derive(Debug)]
171/// struct MySapling([u8; 43]);
172///
173/// // Implement the TryFromAddress trait, overriding whichever conversion methods match your
174/// // requirements for the resulting type.
175/// impl TryFromAddress for MySapling {
176///     // In this example we aren't checking the validity of the inner Sapling address,
177///     // but your code should do so!
178///     type Error = &'static str;
179///
180///     fn try_from_sapling(
181///         net: NetworkType,
182///         data: [u8; 43],
183///     ) -> Result<Self, ConversionError<Self::Error>> {
184///         Ok(MySapling(data))
185///     }
186/// }
187///
188/// // For a supported address type, the conversion works.
189/// let addr: ZcashAddress =
190///     "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g"
191///         .parse()
192///         .unwrap();
193/// assert!(addr.convert::<MySapling>().is_ok());
194///
195/// // For an unsupported address type, we get an error.
196/// let addr: ZcashAddress = "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse().unwrap();
197/// assert_eq!(
198///     addr.convert::<MySapling>().unwrap_err().to_string(),
199///     "Zcash transparent P2PKH addresses are not supported",
200/// );
201/// ```
202pub trait TryFromAddress: Sized {
203    /// Conversion errors for the user type (e.g. failing to parse the data passed to
204    /// [`Self::try_from_sapling`] as a valid Sapling address).
205    type Error;
206
207    fn try_from_sprout(
208        net: NetworkType,
209        data: [u8; 64],
210    ) -> Result<Self, ConversionError<Self::Error>> {
211        let _ = (net, data);
212        Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
213    }
214
215    fn try_from_sapling(
216        net: NetworkType,
217        data: [u8; 43],
218    ) -> Result<Self, ConversionError<Self::Error>> {
219        let _ = (net, data);
220        Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
221    }
222
223    fn try_from_unified(
224        net: NetworkType,
225        data: unified::Address,
226    ) -> Result<Self, ConversionError<Self::Error>> {
227        let _ = (net, data);
228        Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
229    }
230
231    fn try_from_transparent_p2pkh(
232        net: NetworkType,
233        data: [u8; 20],
234    ) -> Result<Self, ConversionError<Self::Error>> {
235        let _ = (net, data);
236        Err(ConversionError::Unsupported(UnsupportedAddress(
237            "transparent P2PKH",
238        )))
239    }
240
241    fn try_from_transparent_p2sh(
242        net: NetworkType,
243        data: [u8; 20],
244    ) -> Result<Self, ConversionError<Self::Error>> {
245        let _ = (net, data);
246        Err(ConversionError::Unsupported(UnsupportedAddress(
247            "transparent P2SH",
248        )))
249    }
250
251    fn try_from_tex(
252        net: NetworkType,
253        data: [u8; 20],
254    ) -> Result<Self, ConversionError<Self::Error>> {
255        let _ = (net, data);
256        Err(ConversionError::Unsupported(UnsupportedAddress(
257            "transparent-source restricted P2PKH",
258        )))
259    }
260}
261
262impl<T: TryFromRawAddress> TryFromAddress for (NetworkType, T) {
263    type Error = T::Error;
264
265    fn try_from_sprout(
266        net: NetworkType,
267        data: [u8; 64],
268    ) -> Result<Self, ConversionError<Self::Error>> {
269        T::try_from_raw_sprout(data).map(|addr| (net, addr))
270    }
271
272    fn try_from_sapling(
273        net: NetworkType,
274        data: [u8; 43],
275    ) -> Result<Self, ConversionError<Self::Error>> {
276        T::try_from_raw_sapling(data).map(|addr| (net, addr))
277    }
278
279    fn try_from_unified(
280        net: NetworkType,
281        data: unified::Address,
282    ) -> Result<Self, ConversionError<Self::Error>> {
283        T::try_from_raw_unified(data).map(|addr| (net, addr))
284    }
285
286    fn try_from_transparent_p2pkh(
287        net: NetworkType,
288        data: [u8; 20],
289    ) -> Result<Self, ConversionError<Self::Error>> {
290        T::try_from_raw_transparent_p2pkh(data).map(|addr| (net, addr))
291    }
292
293    fn try_from_transparent_p2sh(
294        net: NetworkType,
295        data: [u8; 20],
296    ) -> Result<Self, ConversionError<Self::Error>> {
297        T::try_from_raw_transparent_p2sh(data).map(|addr| (net, addr))
298    }
299
300    fn try_from_tex(
301        net: NetworkType,
302        data: [u8; 20],
303    ) -> Result<Self, ConversionError<Self::Error>> {
304        T::try_from_raw_tex(data).map(|addr| (net, addr))
305    }
306}
307
308/// A trait for converter types that can project from a [`ZcashAddress`] into another type.
309///
310/// [`ZcashAddress`]: crate::ZcashAddress
311///
312/// # Examples
313///
314/// ```
315/// use zcash_address::{ConversionError, Converter, UnsupportedAddress, ZcashAddress};
316/// use zcash_protocol::consensus::NetworkType;
317///
318/// struct KeyFinder { }
319///
320/// impl KeyFinder {
321///     fn find_sapling_extfvk(&self, data: [u8; 43]) -> Option<[u8; 73]> {
322///         todo!()
323///     }
324/// }
325///
326/// // Makes it possible to use a KeyFinder to find the Sapling extfvk that corresponds
327/// // to a given ZcashAddress.
328/// impl Converter<Option<[u8; 73]>> for KeyFinder {
329///     type Error = &'static str;
330///
331///     fn convert_sapling(
332///         &self,
333///         net: NetworkType,
334///         data: [u8; 43],
335///     ) -> Result<Option<[u8; 73]>, ConversionError<Self::Error>> {
336///         Ok(self.find_sapling_extfvk(data))
337///     }
338/// }
339/// ```
340pub trait Converter<T> {
341    /// Conversion errors for the user type (e.g. failing to parse the data passed to
342    /// [`Self::convert_sapling`] as a valid Sapling address).
343    type Error;
344
345    fn convert_sprout(
346        &self,
347        net: NetworkType,
348        data: [u8; 64],
349    ) -> Result<T, ConversionError<Self::Error>> {
350        let _ = (net, data);
351        Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
352    }
353
354    fn convert_sapling(
355        &self,
356        net: NetworkType,
357        data: [u8; 43],
358    ) -> Result<T, ConversionError<Self::Error>> {
359        let _ = (net, data);
360        Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
361    }
362
363    fn convert_unified(
364        &self,
365        net: NetworkType,
366        data: unified::Address,
367    ) -> Result<T, ConversionError<Self::Error>> {
368        let _ = (net, data);
369        Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
370    }
371
372    fn convert_transparent_p2pkh(
373        &self,
374        net: NetworkType,
375        data: [u8; 20],
376    ) -> Result<T, ConversionError<Self::Error>> {
377        let _ = (net, data);
378        Err(ConversionError::Unsupported(UnsupportedAddress(
379            "transparent P2PKH",
380        )))
381    }
382
383    fn convert_transparent_p2sh(
384        &self,
385        net: NetworkType,
386        data: [u8; 20],
387    ) -> Result<T, ConversionError<Self::Error>> {
388        let _ = (net, data);
389        Err(ConversionError::Unsupported(UnsupportedAddress(
390            "transparent P2SH",
391        )))
392    }
393
394    fn convert_tex(
395        &self,
396        net: NetworkType,
397        data: [u8; 20],
398    ) -> Result<T, ConversionError<Self::Error>> {
399        let _ = (net, data);
400        Err(ConversionError::Unsupported(UnsupportedAddress(
401            "transparent-source restricted P2PKH",
402        )))
403    }
404}
405
406/// A helper trait for converting another type into a [`ZcashAddress`].
407///
408/// This trait is sealed and cannot be implemented for types outside this crate. Its
409/// purpose is to move these conversion functions out of the main `ZcashAddress` API
410/// documentation, as they are only required when creating addresses (rather than when
411/// parsing addresses, which is a more common occurrence).
412///
413/// [`ZcashAddress`]: crate::ZcashAddress
414///
415/// # Examples
416///
417/// ```
418/// use zcash_address::{ToAddress, ZcashAddress};
419/// use zcash_protocol::consensus::NetworkType;
420///
421/// #[derive(Debug)]
422/// struct MySapling([u8; 43]);
423///
424/// impl MySapling {
425///     /// Encodes this Sapling address for the given network.
426///     fn encode(&self, net: NetworkType) -> ZcashAddress {
427///         ZcashAddress::from_sapling(net, self.0)
428///     }
429/// }
430///
431/// let addr = MySapling([0; 43]);
432/// let encoded = addr.encode(NetworkType::Main);
433/// assert_eq!(
434///     encoded.to_string(),
435///     "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g",
436/// );
437/// ```
438pub trait ToAddress: private::Sealed {
439    fn from_sprout(net: NetworkType, data: [u8; 64]) -> Self;
440
441    fn from_sapling(net: NetworkType, data: [u8; 43]) -> Self;
442
443    fn from_unified(net: NetworkType, data: unified::Address) -> Self;
444
445    fn from_transparent_p2pkh(net: NetworkType, data: [u8; 20]) -> Self;
446
447    fn from_transparent_p2sh(net: NetworkType, data: [u8; 20]) -> Self;
448
449    fn from_tex(net: NetworkType, data: [u8; 20]) -> Self;
450}
451
452impl ToAddress for ZcashAddress {
453    fn from_sprout(net: NetworkType, data: [u8; 64]) -> Self {
454        ZcashAddress {
455            net: if let NetworkType::Regtest = net {
456                NetworkType::Test
457            } else {
458                net
459            },
460            kind: AddressKind::Sprout(data),
461        }
462    }
463
464    fn from_sapling(net: NetworkType, data: [u8; 43]) -> Self {
465        ZcashAddress {
466            net,
467            kind: AddressKind::Sapling(data),
468        }
469    }
470
471    fn from_unified(net: NetworkType, data: unified::Address) -> Self {
472        ZcashAddress {
473            net,
474            kind: AddressKind::Unified(data),
475        }
476    }
477
478    fn from_transparent_p2pkh(net: NetworkType, data: [u8; 20]) -> Self {
479        ZcashAddress {
480            net: if let NetworkType::Regtest = net {
481                NetworkType::Test
482            } else {
483                net
484            },
485            kind: AddressKind::P2pkh(data),
486        }
487    }
488
489    fn from_transparent_p2sh(net: NetworkType, data: [u8; 20]) -> Self {
490        ZcashAddress {
491            net: if let NetworkType::Regtest = net {
492                NetworkType::Test
493            } else {
494                net
495            },
496            kind: AddressKind::P2sh(data),
497        }
498    }
499
500    fn from_tex(net: NetworkType, data: [u8; 20]) -> Self {
501        ZcashAddress {
502            net,
503            kind: AddressKind::Tex(data),
504        }
505    }
506}
507
508mod private {
509    use crate::ZcashAddress;
510
511    pub trait Sealed {}
512    impl Sealed for ZcashAddress {}
513}