Skip to main content

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