zcash_address: Add `ZcashAddress::convert_if_network`
This, along with the corresponding `TryFromRawAddress` trait, enables converting `ZcashAddress` into a network-agnostic type. Closes zcash/librustzcash#564.
This commit is contained in:
parent
b6ba216c0f
commit
b8e8a0c491
|
@ -9,6 +9,8 @@ and this library adheres to Rust's notion of
|
|||
### Added
|
||||
- `zcash_address::ConversionError`
|
||||
- `zcash_address::TryFromAddress`
|
||||
- `zcash_address::TryFromRawAddress`
|
||||
- `zcash_address::ZcashAddress::convert_if_network`
|
||||
|
||||
### Removed
|
||||
- `zcash_address::FromAddress` (use `TryFromAddress` instead).
|
||||
|
|
|
@ -15,6 +15,8 @@ impl fmt::Display for UnsupportedAddress {
|
|||
/// 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.
|
||||
|
@ -30,6 +32,11 @@ impl<E> From<E> for ConversionError<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),
|
||||
}
|
||||
|
@ -40,12 +47,100 @@ impl Error for UnsupportedAddress {}
|
|||
impl<E: Error + 'static> Error for ConversionError<E> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
ConversionError::Unsupported(_) => None,
|
||||
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_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
|
||||
|
@ -58,7 +153,7 @@ impl<E: Error + 'static> Error for ConversionError<E> {
|
|||
/// #[derive(Debug)]
|
||||
/// struct MySapling([u8; 43]);
|
||||
///
|
||||
/// // Implement the FromAddress trait, overriding whichever conversion methods match your
|
||||
/// // 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,
|
||||
|
@ -137,6 +232,45 @@ pub trait TryFromAddress: Sized {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -5,7 +5,9 @@ mod kind;
|
|||
#[cfg(test)]
|
||||
mod test_vectors;
|
||||
|
||||
pub use convert::{ConversionError, ToAddress, TryFromAddress, UnsupportedAddress};
|
||||
pub use convert::{
|
||||
ConversionError, ToAddress, TryFromAddress, TryFromRawAddress, UnsupportedAddress,
|
||||
};
|
||||
pub use encoding::ParseError;
|
||||
pub use kind::unified;
|
||||
|
||||
|
@ -104,4 +106,43 @@ impl ZcashAddress {
|
|||
AddressKind::P2sh(data) => T::try_from_transparent_p2sh(self.net, data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this address into another type, if it matches the expected network.
|
||||
///
|
||||
/// `convert_if_network` can convert into any type that implements the
|
||||
/// [`TryFromRawAddress`] trait. This enables `ZcashAddress` to be used as a common
|
||||
/// parsing and serialization interface for Zcash addresses, while delegating
|
||||
/// operations on those addresses (such as constructing transactions) to downstream
|
||||
/// crates.
|
||||
///
|
||||
/// If you want to get the encoded string for this address, use the [`encode`]
|
||||
/// method or the [`Display` implementation] via [`address.to_string()`] instead.
|
||||
///
|
||||
/// [`encode`]: Self::encode
|
||||
/// [`Display` implementation]: std::fmt::Display
|
||||
/// [`address.to_string()`]: std::string::ToString
|
||||
pub fn convert_if_network<T: TryFromRawAddress>(
|
||||
self,
|
||||
net: Network,
|
||||
) -> Result<T, ConversionError<T::Error>> {
|
||||
let network_matches = self.net == net;
|
||||
// The Sprout and transparent address encodings use the same prefix for testnet
|
||||
// and regtest, so we need to allow parsing testnet addresses as regtest.
|
||||
let regtest_exception =
|
||||
network_matches || (self.net == Network::Test && net == Network::Regtest);
|
||||
|
||||
match self.kind {
|
||||
AddressKind::Sprout(data) if regtest_exception => T::try_from_raw_sprout(data),
|
||||
AddressKind::Sapling(data) if network_matches => T::try_from_raw_sapling(data),
|
||||
AddressKind::Unified(data) if network_matches => T::try_from_raw_unified(data),
|
||||
AddressKind::P2pkh(data) if regtest_exception => {
|
||||
T::try_from_raw_transparent_p2pkh(data)
|
||||
}
|
||||
AddressKind::P2sh(data) if regtest_exception => T::try_from_raw_transparent_p2sh(data),
|
||||
_ => Err(ConversionError::IncorrectNetwork {
|
||||
expected: net,
|
||||
actual: self.net,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue