diff --git a/zcash_keys/CHANGELOG.md b/zcash_keys/CHANGELOG.md index 060a70dca..a0bb242d3 100644 --- a/zcash_keys/CHANGELOG.md +++ b/zcash_keys/CHANGELOG.md @@ -6,6 +6,12 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- `zcash_keys::encoding::decode_extfvk_with_network` +- `impl std::error::Error for Bech32DecodeError` +- `impl std::error::Error for DecodingError` +- `impl std::error::Error for DerivationError` + ## [0.3.0] - 2024-08-19 ### Notable changes - `zcash_keys`: diff --git a/zcash_keys/src/encoding.rs b/zcash_keys/src/encoding.rs index 8de7125a0..894103a04 100644 --- a/zcash_keys/src/encoding.rs +++ b/zcash_keys/src/encoding.rs @@ -6,7 +6,7 @@ use crate::address::UnifiedAddress; use bs58::{self, decode::Error as Bs58Error}; use std::fmt; -use zcash_primitives::consensus::NetworkConstants; +use zcash_primitives::consensus::{NetworkConstants, NetworkType}; use zcash_address::unified::{self, Encoding}; use zcash_primitives::{consensus, legacy::TransparentAddress}; @@ -66,6 +66,16 @@ impl fmt::Display for Bech32DecodeError { } } +#[cfg(feature = "sapling")] +impl std::error::Error for Bech32DecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self { + Bech32DecodeError::Bech32Error(e) => Some(e), + _ => None, + } + } +} + #[cfg(feature = "sapling")] fn bech32_decode(hrp: &str, s: &str, read: F) -> Result where @@ -245,9 +255,8 @@ pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingK bech32_encode(hrp, |w| extfvk.write(w)) } -/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string. -/// -/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey +/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string, verifying that it matches +/// the provided human-readable prefix. #[cfg(feature = "sapling")] pub fn decode_extended_full_viewing_key( hrp: &str, @@ -256,6 +265,39 @@ pub fn decode_extended_full_viewing_key( bech32_decode(hrp, s, |data| ExtendedFullViewingKey::read(&data[..]).ok()) } +/// Decodes an [`ExtendedFullViewingKey`] and the [`NetworkType`] that it is intended for use with +/// from a Bech32-encoded string. +#[cfg(feature = "sapling")] +pub fn decode_extfvk_with_network( + s: &str, +) -> Result<(NetworkType, ExtendedFullViewingKey), Bech32DecodeError> { + use zcash_protocol::constants::{mainnet, regtest, testnet}; + + let (decoded_hrp, data, variant) = bech32::decode(s)?; + if variant != Variant::Bech32 { + Err(Bech32DecodeError::IncorrectVariant(variant)) + } else { + let network = match &decoded_hrp[..] { + mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Main), + testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Test), + regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Regtest), + other => Err(Bech32DecodeError::HrpMismatch { + expected: format!( + "One of {}, {}, or {}", + mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + ), + actual: other.to_string(), + }), + }?; + let fvk = ExtendedFullViewingKey::read(&Vec::::from_base32(&data)?[..]) + .map_err(|_| Bech32DecodeError::ReadError)?; + + Ok((network, fvk)) + } +} + /// Writes a [`PaymentAddress`] as a Bech32-encoded string. /// /// # Examples diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index f1f8d6eb6..dbd8f7934 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -37,6 +37,9 @@ use { #[cfg(feature = "orchard")] use orchard::{self, keys::Scope}; +#[cfg(all(feature = "sapling", feature = "unstable"))] +use ::sapling::zip32::ExtendedFullViewingKey; + #[cfg(feature = "sapling")] pub mod sapling { pub use sapling::zip32::{ @@ -111,6 +114,8 @@ impl Display for DerivationError { } } +impl std::error::Error for DerivationError {} + /// A version identifier for the encoding of unified spending keys. /// /// Each era corresponds to a range of block heights. During an era, the unified spending key @@ -176,6 +181,8 @@ impl std::fmt::Display for DecodingError { } } +impl std::error::Error for DecodingError {} + #[cfg(feature = "unstable")] impl Era { /// Returns the unique identifier for the era. @@ -674,6 +681,24 @@ impl UnifiedFullViewingKey { vec![], ) } + + #[cfg(all(feature = "sapling", feature = "unstable"))] + pub fn from_sapling_extended_full_viewing_key( + sapling: ExtendedFullViewingKey, + ) -> Result { + Self::from_checked_parts( + #[cfg(feature = "transparent-inputs")] + None, + #[cfg(feature = "sapling")] + Some(sapling.to_diversifiable_full_viewing_key()), + #[cfg(feature = "orchard")] + None, + // We don't currently allow constructing new UFVKs with unknown items, but we store + // this to allow parsing such UFVKs. + vec![], + ) + } + /// Construct a UFVK from its constituent parts, after verifying that UIVK derivation can /// succeed. fn from_checked_parts(