372 lines
12 KiB
Rust
372 lines
12 KiB
Rust
use std::{error::Error, fmt};
|
|
|
|
use crate::{kind::*, AddressKind, Network, ZcashAddress};
|
|
|
|
/// An address type is not supported for conversion.
|
|
#[derive(Debug)]
|
|
pub struct UnsupportedAddress(&'static str);
|
|
|
|
impl fmt::Display for UnsupportedAddress {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "Zcash {} addresses are not supported", self.0)
|
|
}
|
|
}
|
|
|
|
/// An error encountered while converting a parsed [`ZcashAddress`] into another type.
|
|
#[derive(Debug)]
|
|
pub enum ConversionError<E> {
|
|
/// The address is for the wrong network.
|
|
IncorrectNetwork { expected: Network, actual: Network },
|
|
/// The address type is not supported by the target type.
|
|
Unsupported(UnsupportedAddress),
|
|
/// A conversion error returned by the target type.
|
|
User(E),
|
|
}
|
|
|
|
impl<E> From<E> for ConversionError<E> {
|
|
fn from(e: E) -> Self {
|
|
ConversionError::User(e)
|
|
}
|
|
}
|
|
|
|
impl<E: fmt::Display> fmt::Display for ConversionError<E> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::IncorrectNetwork { expected, actual } => write!(
|
|
f,
|
|
"Address is for {:?} but we expected {:?}",
|
|
actual, expected,
|
|
),
|
|
Self::Unsupported(e) => e.fmt(f),
|
|
Self::User(e) => e.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for UnsupportedAddress {}
|
|
impl<E: Error + 'static> Error for ConversionError<E> {
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
match self {
|
|
ConversionError::IncorrectNetwork { .. } | ConversionError::Unsupported(_) => None,
|
|
ConversionError::User(e) => Some(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A helper trait for converting a [`ZcashAddress`] into a network-agnostic type.
|
|
///
|
|
/// A blanket implementation of [`TryFromAddress`] is provided for `(Network, T)` where
|
|
/// `T: TryFromRawAddress`.
|
|
///
|
|
/// [`ZcashAddress`]: crate::ZcashAddress
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use zcash_address::{ConversionError, Network, TryFromRawAddress, UnsupportedAddress, ZcashAddress};
|
|
///
|
|
/// #[derive(Debug, PartialEq)]
|
|
/// struct MySapling([u8; 43]);
|
|
///
|
|
/// // Implement the TryFromRawAddress trait, overriding whichever conversion methods match
|
|
/// // your requirements for the resulting type.
|
|
/// impl TryFromRawAddress for MySapling {
|
|
/// // In this example we aren't checking the validity of the inner Sapling address,
|
|
/// // but your code should do so!
|
|
/// type Error = &'static str;
|
|
///
|
|
/// fn try_from_raw_sapling(data: [u8; 43]) -> Result<Self, ConversionError<Self::Error>> {
|
|
/// Ok(MySapling(data))
|
|
/// }
|
|
/// }
|
|
///
|
|
/// // For a supported address type, the conversion works.
|
|
/// let addr_string = "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g";
|
|
///
|
|
/// // You can use `ZcashAddress::convert_if_network` to get your type directly.
|
|
/// let addr: ZcashAddress = addr_string.parse().unwrap();
|
|
/// let converted = addr.convert_if_network::<MySapling>(Network::Main);
|
|
/// assert!(converted.is_ok());
|
|
/// assert_eq!(converted.unwrap(), MySapling([0; 43]));
|
|
///
|
|
/// // Using `ZcashAddress::convert` gives us the tuple `(network, converted_addr)`.
|
|
/// let addr: ZcashAddress = addr_string.parse().unwrap();
|
|
/// let converted = addr.convert::<(_, MySapling)>();
|
|
/// assert!(converted.is_ok());
|
|
/// assert_eq!(converted.unwrap(), (Network::Main, MySapling([0; 43])));
|
|
///
|
|
/// // For an unsupported address type, we get an error.
|
|
/// let addr: ZcashAddress = "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse().unwrap();
|
|
/// assert_eq!(
|
|
/// addr.convert::<(_, MySapling)>().unwrap_err().to_string(),
|
|
/// "Zcash transparent P2PKH addresses are not supported",
|
|
/// );
|
|
/// ```
|
|
pub trait TryFromRawAddress: Sized {
|
|
/// Conversion errors for the user type (e.g. failing to parse the data passed to
|
|
/// [`Self::try_from_raw_sapling`] as a valid Sapling address).
|
|
type Error;
|
|
|
|
fn try_from_raw_sprout(data: sprout::Data) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = data;
|
|
Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
|
|
}
|
|
|
|
fn try_from_raw_sapling(data: sapling::Data) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = data;
|
|
Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
|
|
}
|
|
|
|
fn try_from_raw_unified(data: unified::Address) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = data;
|
|
Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
|
|
}
|
|
|
|
fn try_from_raw_transparent_p2pkh(
|
|
data: p2pkh::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = data;
|
|
Err(ConversionError::Unsupported(UnsupportedAddress(
|
|
"transparent P2PKH",
|
|
)))
|
|
}
|
|
|
|
fn try_from_raw_transparent_p2sh(
|
|
data: p2sh::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = data;
|
|
Err(ConversionError::Unsupported(UnsupportedAddress(
|
|
"transparent P2SH",
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// A helper trait for converting a [`ZcashAddress`] into another type.
|
|
///
|
|
/// [`ZcashAddress`]: crate::ZcashAddress
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use zcash_address::{ConversionError, Network, TryFromAddress, UnsupportedAddress, ZcashAddress};
|
|
///
|
|
/// #[derive(Debug)]
|
|
/// struct MySapling([u8; 43]);
|
|
///
|
|
/// // Implement the TryFromAddress trait, overriding whichever conversion methods match your
|
|
/// // requirements for the resulting type.
|
|
/// impl TryFromAddress for MySapling {
|
|
/// // In this example we aren't checking the validity of the inner Sapling address,
|
|
/// // but your code should do so!
|
|
/// type Error = &'static str;
|
|
///
|
|
/// fn try_from_sapling(
|
|
/// net: Network,
|
|
/// data: [u8; 43],
|
|
/// ) -> Result<Self, ConversionError<Self::Error>> {
|
|
/// Ok(MySapling(data))
|
|
/// }
|
|
/// }
|
|
///
|
|
/// // For a supported address type, the conversion works.
|
|
/// let addr: ZcashAddress =
|
|
/// "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g"
|
|
/// .parse()
|
|
/// .unwrap();
|
|
/// assert!(addr.convert::<MySapling>().is_ok());
|
|
///
|
|
/// // For an unsupported address type, we get an error.
|
|
/// let addr: ZcashAddress = "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse().unwrap();
|
|
/// assert_eq!(
|
|
/// addr.convert::<MySapling>().unwrap_err().to_string(),
|
|
/// "Zcash transparent P2PKH addresses are not supported",
|
|
/// );
|
|
/// ```
|
|
pub trait TryFromAddress: Sized {
|
|
/// Conversion errors for the user type (e.g. failing to parse the data passed to
|
|
/// [`Self::try_from_sapling`] as a valid Sapling address).
|
|
type Error;
|
|
|
|
fn try_from_sprout(
|
|
net: Network,
|
|
data: sprout::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = (net, data);
|
|
Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
|
|
}
|
|
|
|
fn try_from_sapling(
|
|
net: Network,
|
|
data: sapling::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = (net, data);
|
|
Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
|
|
}
|
|
|
|
fn try_from_unified(
|
|
net: Network,
|
|
data: unified::Address,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = (net, data);
|
|
Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
|
|
}
|
|
|
|
fn try_from_transparent_p2pkh(
|
|
net: Network,
|
|
data: p2pkh::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = (net, data);
|
|
Err(ConversionError::Unsupported(UnsupportedAddress(
|
|
"transparent P2PKH",
|
|
)))
|
|
}
|
|
|
|
fn try_from_transparent_p2sh(
|
|
net: Network,
|
|
data: p2sh::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
let _ = (net, data);
|
|
Err(ConversionError::Unsupported(UnsupportedAddress(
|
|
"transparent P2SH",
|
|
)))
|
|
}
|
|
}
|
|
|
|
impl<T: TryFromRawAddress> TryFromAddress for (Network, T) {
|
|
type Error = T::Error;
|
|
|
|
fn try_from_sprout(
|
|
net: Network,
|
|
data: sprout::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
T::try_from_raw_sprout(data).map(|addr| (net, addr))
|
|
}
|
|
|
|
fn try_from_sapling(
|
|
net: Network,
|
|
data: sapling::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
T::try_from_raw_sapling(data).map(|addr| (net, addr))
|
|
}
|
|
|
|
fn try_from_unified(
|
|
net: Network,
|
|
data: unified::Address,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
T::try_from_raw_unified(data).map(|addr| (net, addr))
|
|
}
|
|
|
|
fn try_from_transparent_p2pkh(
|
|
net: Network,
|
|
data: p2pkh::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
T::try_from_raw_transparent_p2pkh(data).map(|addr| (net, addr))
|
|
}
|
|
|
|
fn try_from_transparent_p2sh(
|
|
net: Network,
|
|
data: p2sh::Data,
|
|
) -> Result<Self, ConversionError<Self::Error>> {
|
|
T::try_from_raw_transparent_p2sh(data).map(|addr| (net, addr))
|
|
}
|
|
}
|
|
|
|
/// A helper trait for converting another type into a [`ZcashAddress`].
|
|
///
|
|
/// This trait is sealed and cannot be implemented for types outside this crate. Its
|
|
/// purpose is to move these conversion functions out of the main `ZcashAddress` API
|
|
/// documentation, as they are only required when creating addresses (rather than when
|
|
/// parsing addresses, which is a more common occurrence).
|
|
///
|
|
/// [`ZcashAddress`]: crate::ZcashAddress
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use zcash_address::{ToAddress, Network, ZcashAddress};
|
|
///
|
|
/// #[derive(Debug)]
|
|
/// struct MySapling([u8; 43]);
|
|
///
|
|
/// impl MySapling {
|
|
/// /// Encodes this Sapling address for the given network.
|
|
/// fn encode(&self, net: Network) -> ZcashAddress {
|
|
/// ZcashAddress::from_sapling(net, self.0)
|
|
/// }
|
|
/// }
|
|
///
|
|
/// let addr = MySapling([0; 43]);
|
|
/// let encoded = addr.encode(Network::Main);
|
|
/// assert_eq!(
|
|
/// encoded.to_string(),
|
|
/// "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g",
|
|
/// );
|
|
/// ```
|
|
pub trait ToAddress: private::Sealed {
|
|
fn from_sprout(net: Network, data: sprout::Data) -> Self;
|
|
|
|
fn from_sapling(net: Network, data: sapling::Data) -> Self;
|
|
|
|
fn from_unified(net: Network, data: unified::Address) -> Self;
|
|
|
|
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self;
|
|
|
|
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self;
|
|
}
|
|
|
|
impl ToAddress for ZcashAddress {
|
|
fn from_sprout(net: Network, data: sprout::Data) -> Self {
|
|
ZcashAddress {
|
|
net: if let Network::Regtest = net {
|
|
Network::Test
|
|
} else {
|
|
net
|
|
},
|
|
kind: AddressKind::Sprout(data),
|
|
}
|
|
}
|
|
|
|
fn from_sapling(net: Network, data: sapling::Data) -> Self {
|
|
ZcashAddress {
|
|
net,
|
|
kind: AddressKind::Sapling(data),
|
|
}
|
|
}
|
|
|
|
fn from_unified(net: Network, data: unified::Address) -> Self {
|
|
ZcashAddress {
|
|
net,
|
|
kind: AddressKind::Unified(data),
|
|
}
|
|
}
|
|
|
|
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self {
|
|
ZcashAddress {
|
|
net: if let Network::Regtest = net {
|
|
Network::Test
|
|
} else {
|
|
net
|
|
},
|
|
kind: AddressKind::P2pkh(data),
|
|
}
|
|
}
|
|
|
|
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self {
|
|
ZcashAddress {
|
|
net: if let Network::Regtest = net {
|
|
Network::Test
|
|
} else {
|
|
net
|
|
},
|
|
kind: AddressKind::P2sh(data),
|
|
}
|
|
}
|
|
}
|
|
|
|
mod private {
|
|
use crate::ZcashAddress;
|
|
|
|
pub trait Sealed {}
|
|
impl Sealed for ZcashAddress {}
|
|
}
|