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 another type.
67///
68/// [`ZcashAddress`]: crate::ZcashAddress
69///
70/// # Examples
71///
72/// ```
73/// use zcash_address::{ConversionError, TryFromAddress, UnsupportedAddress, ZcashAddress};
74/// use zcash_protocol::consensus::NetworkType;
75///
76/// #[derive(Debug)]
77/// struct MySapling([u8; 43]);
78///
79/// // Implement the TryFromAddress trait, overriding whichever conversion methods match your
80/// // requirements for the resulting type.
81/// impl TryFromAddress for MySapling {
82///     // In this example we aren't checking the validity of the inner Sapling address,
83///     // but your code should do so!
84///     type Error = &'static str;
85///
86///     fn try_from_sapling(
87///         net: NetworkType,
88///         data: [u8; 43],
89///     ) -> Result<Self, ConversionError<Self::Error>> {
90///         Ok(MySapling(data))
91///     }
92/// }
93///
94/// // For a supported address type, the conversion works.
95/// let addr: ZcashAddress =
96///     "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g"
97///         .parse()
98///         .unwrap();
99/// assert!(addr.convert::<MySapling>().is_ok());
100///
101/// // For an unsupported address type, we get an error.
102/// let addr: ZcashAddress = "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse().unwrap();
103/// assert_eq!(
104///     addr.convert::<MySapling>().unwrap_err().to_string(),
105///     "Zcash transparent P2PKH addresses are not supported",
106/// );
107/// ```
108pub trait TryFromAddress: Sized {
109    /// Conversion errors for the user type (e.g. failing to parse the data passed to
110    /// [`Self::try_from_sapling`] as a valid Sapling address).
111    type Error;
112
113    fn try_from_sprout(
114        net: NetworkType,
115        data: [u8; 64],
116    ) -> Result<Self, ConversionError<Self::Error>> {
117        let _ = (net, data);
118        Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
119    }
120
121    fn try_from_sapling(
122        net: NetworkType,
123        data: [u8; 43],
124    ) -> Result<Self, ConversionError<Self::Error>> {
125        let _ = (net, data);
126        Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
127    }
128
129    fn try_from_unified(
130        net: NetworkType,
131        data: unified::Address,
132    ) -> Result<Self, ConversionError<Self::Error>> {
133        let _ = (net, data);
134        Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
135    }
136
137    fn try_from_transparent_p2pkh(
138        net: NetworkType,
139        data: [u8; 20],
140    ) -> Result<Self, ConversionError<Self::Error>> {
141        let _ = (net, data);
142        Err(ConversionError::Unsupported(UnsupportedAddress(
143            "transparent P2PKH",
144        )))
145    }
146
147    fn try_from_transparent_p2sh(
148        net: NetworkType,
149        data: [u8; 20],
150    ) -> Result<Self, ConversionError<Self::Error>> {
151        let _ = (net, data);
152        Err(ConversionError::Unsupported(UnsupportedAddress(
153            "transparent P2SH",
154        )))
155    }
156
157    fn try_from_tex(
158        net: NetworkType,
159        data: [u8; 20],
160    ) -> Result<Self, ConversionError<Self::Error>> {
161        let _ = (net, data);
162        Err(ConversionError::Unsupported(UnsupportedAddress(
163            "transparent-source restricted P2PKH",
164        )))
165    }
166}
167
168impl<T: TryFromAddress> TryFromAddress for (NetworkType, T) {
169    type Error = T::Error;
170
171    fn try_from_sprout(
172        net: NetworkType,
173        data: [u8; 64],
174    ) -> Result<Self, ConversionError<Self::Error>> {
175        T::try_from_sprout(net, data).map(|addr| (net, addr))
176    }
177
178    fn try_from_sapling(
179        net: NetworkType,
180        data: [u8; 43],
181    ) -> Result<Self, ConversionError<Self::Error>> {
182        T::try_from_sapling(net, data).map(|addr| (net, addr))
183    }
184
185    fn try_from_unified(
186        net: NetworkType,
187        data: unified::Address,
188    ) -> Result<Self, ConversionError<Self::Error>> {
189        T::try_from_unified(net, data).map(|addr| (net, addr))
190    }
191
192    fn try_from_transparent_p2pkh(
193        net: NetworkType,
194        data: [u8; 20],
195    ) -> Result<Self, ConversionError<Self::Error>> {
196        T::try_from_transparent_p2pkh(net, data).map(|addr| (net, addr))
197    }
198
199    fn try_from_transparent_p2sh(
200        net: NetworkType,
201        data: [u8; 20],
202    ) -> Result<Self, ConversionError<Self::Error>> {
203        T::try_from_transparent_p2sh(net, data).map(|addr| (net, addr))
204    }
205
206    fn try_from_tex(
207        net: NetworkType,
208        data: [u8; 20],
209    ) -> Result<Self, ConversionError<Self::Error>> {
210        T::try_from_tex(net, data).map(|addr| (net, addr))
211    }
212}
213
214/// A trait for converter types that can project from a [`ZcashAddress`] into another type.
215///
216/// [`ZcashAddress`]: crate::ZcashAddress
217///
218/// # Examples
219///
220/// ```
221/// use zcash_address::{ConversionError, Converter, UnsupportedAddress, ZcashAddress};
222/// use zcash_protocol::consensus::NetworkType;
223///
224/// struct KeyFinder { }
225///
226/// impl KeyFinder {
227///     fn find_sapling_extfvk(&self, data: [u8; 43]) -> Option<[u8; 73]> {
228///         todo!()
229///     }
230/// }
231///
232/// // Makes it possible to use a KeyFinder to find the Sapling extfvk that corresponds
233/// // to a given ZcashAddress.
234/// impl Converter<Option<[u8; 73]>> for KeyFinder {
235///     type Error = &'static str;
236///
237///     fn convert_sapling(
238///         &self,
239///         net: NetworkType,
240///         data: [u8; 43],
241///     ) -> Result<Option<[u8; 73]>, ConversionError<Self::Error>> {
242///         Ok(self.find_sapling_extfvk(data))
243///     }
244/// }
245/// ```
246pub trait Converter<T> {
247    /// Conversion errors for the user type (e.g. failing to parse the data passed to
248    /// [`Self::convert_sapling`] as a valid Sapling address).
249    type Error;
250
251    fn convert_sprout(
252        &self,
253        net: NetworkType,
254        data: [u8; 64],
255    ) -> Result<T, ConversionError<Self::Error>> {
256        let _ = (net, data);
257        Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
258    }
259
260    fn convert_sapling(
261        &self,
262        net: NetworkType,
263        data: [u8; 43],
264    ) -> Result<T, ConversionError<Self::Error>> {
265        let _ = (net, data);
266        Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
267    }
268
269    fn convert_unified(
270        &self,
271        net: NetworkType,
272        data: unified::Address,
273    ) -> Result<T, ConversionError<Self::Error>> {
274        let _ = (net, data);
275        Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
276    }
277
278    fn convert_transparent_p2pkh(
279        &self,
280        net: NetworkType,
281        data: [u8; 20],
282    ) -> Result<T, ConversionError<Self::Error>> {
283        let _ = (net, data);
284        Err(ConversionError::Unsupported(UnsupportedAddress(
285            "transparent P2PKH",
286        )))
287    }
288
289    fn convert_transparent_p2sh(
290        &self,
291        net: NetworkType,
292        data: [u8; 20],
293    ) -> Result<T, ConversionError<Self::Error>> {
294        let _ = (net, data);
295        Err(ConversionError::Unsupported(UnsupportedAddress(
296            "transparent P2SH",
297        )))
298    }
299
300    fn convert_tex(
301        &self,
302        net: NetworkType,
303        data: [u8; 20],
304    ) -> Result<T, ConversionError<Self::Error>> {
305        let _ = (net, data);
306        Err(ConversionError::Unsupported(UnsupportedAddress(
307            "transparent-source restricted P2PKH",
308        )))
309    }
310}
311
312/// A helper trait for converting another type into a [`ZcashAddress`].
313///
314/// This trait is sealed and cannot be implemented for types outside this crate. Its
315/// purpose is to move these conversion functions out of the main `ZcashAddress` API
316/// documentation, as they are only required when creating addresses (rather than when
317/// parsing addresses, which is a more common occurrence).
318///
319/// [`ZcashAddress`]: crate::ZcashAddress
320///
321/// # Examples
322///
323/// ```
324/// use zcash_address::{ToAddress, ZcashAddress};
325/// use zcash_protocol::consensus::NetworkType;
326///
327/// #[derive(Debug)]
328/// struct MySapling([u8; 43]);
329///
330/// impl MySapling {
331///     /// Encodes this Sapling address for the given network.
332///     fn encode(&self, net: NetworkType) -> ZcashAddress {
333///         ZcashAddress::from_sapling(net, self.0)
334///     }
335/// }
336///
337/// let addr = MySapling([0; 43]);
338/// let encoded = addr.encode(NetworkType::Main);
339/// assert_eq!(
340///     encoded.to_string(),
341///     "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g",
342/// );
343/// ```
344pub trait ToAddress: private::Sealed {
345    fn from_sprout(net: NetworkType, data: [u8; 64]) -> Self;
346
347    fn from_sapling(net: NetworkType, data: [u8; 43]) -> Self;
348
349    fn from_unified(net: NetworkType, data: unified::Address) -> Self;
350
351    fn from_transparent_p2pkh(net: NetworkType, data: [u8; 20]) -> Self;
352
353    fn from_transparent_p2sh(net: NetworkType, data: [u8; 20]) -> Self;
354
355    fn from_tex(net: NetworkType, data: [u8; 20]) -> Self;
356}
357
358impl ToAddress for ZcashAddress {
359    fn from_sprout(net: NetworkType, data: [u8; 64]) -> Self {
360        ZcashAddress {
361            net: if let NetworkType::Regtest = net {
362                NetworkType::Test
363            } else {
364                net
365            },
366            kind: AddressKind::Sprout(data),
367        }
368    }
369
370    fn from_sapling(net: NetworkType, data: [u8; 43]) -> Self {
371        ZcashAddress {
372            net,
373            kind: AddressKind::Sapling(data),
374        }
375    }
376
377    fn from_unified(net: NetworkType, data: unified::Address) -> Self {
378        ZcashAddress {
379            net,
380            kind: AddressKind::Unified(data),
381        }
382    }
383
384    fn from_transparent_p2pkh(net: NetworkType, data: [u8; 20]) -> Self {
385        ZcashAddress {
386            net: if let NetworkType::Regtest = net {
387                NetworkType::Test
388            } else {
389                net
390            },
391            kind: AddressKind::P2pkh(data),
392        }
393    }
394
395    fn from_transparent_p2sh(net: NetworkType, data: [u8; 20]) -> Self {
396        ZcashAddress {
397            net: if let NetworkType::Regtest = net {
398                NetworkType::Test
399            } else {
400                net
401            },
402            kind: AddressKind::P2sh(data),
403        }
404    }
405
406    fn from_tex(net: NetworkType, data: [u8; 20]) -> Self {
407        ZcashAddress {
408            net,
409            kind: AddressKind::Tex(data),
410        }
411    }
412}
413
414mod private {
415    use crate::ZcashAddress;
416
417    pub trait Sealed {}
418    impl Sealed for ZcashAddress {}
419}