diff --git a/components/zcash_address/src/convert.rs b/components/zcash_address/src/convert.rs index 66e466463..476140f6d 100644 --- a/components/zcash_address/src/convert.rs +++ b/components/zcash_address/src/convert.rs @@ -59,9 +59,9 @@ pub trait FromAddress: Sized { Err(UnsupportedAddress("Sapling")) } - fn from_orchard(net: Network, data: orchard::Data) -> Result { + fn from_unified(net: Network, data: unified::Data) -> Result { let _ = (net, data); - Err(UnsupportedAddress("Orchard")) + Err(UnsupportedAddress("Unified")) } fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Result { @@ -111,7 +111,7 @@ pub trait ToAddress: private::Sealed { fn from_sapling(net: Network, data: sapling::Data) -> Self; - fn from_orchard(net: Network, data: orchard::Data) -> Self; + fn from_unified(net: Network, data: unified::Data) -> Self; fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self; @@ -137,10 +137,10 @@ impl ToAddress for ZcashAddress { } } - fn from_orchard(net: Network, data: orchard::Data) -> Self { + fn from_unified(net: Network, data: unified::Data) -> Self { ZcashAddress { net, - kind: AddressKind::Orchard(data), + kind: AddressKind::Unified(data), } } diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 2a898d335..8195f8daa 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -9,8 +9,6 @@ use crate::{kind::*, AddressKind, Network, ZcashAddress}; pub enum ParseError { /// The string is an invalid encoding. InvalidEncoding, - /// The string might be an unknown Zcash address from the future. - MaybeZcash, /// The string is not a Zcash address. NotZcash, } @@ -19,10 +17,6 @@ impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ParseError::InvalidEncoding => write!(f, "Invalid encoding"), - ParseError::MaybeZcash => write!( - f, - "This might be a Zcash address from the future that we don't know about" - ), ParseError::NotZcash => write!(f, "Not a Zcash address"), } } @@ -40,49 +34,47 @@ impl FromStr for ZcashAddress { // Most Zcash addresses use Bech32, so try that first. match bech32::decode(s) { - // Zcash addresses only use the original Bech32 variant, since the data - // corresponding to a particular HRP always has a fixed length. - Ok((_, _, Variant::Bech32m)) => return Err(ParseError::NotZcash), + Ok((hrp, data, Variant::Bech32m)) => { + // If we reached this point, the encoding is supposed to be valid Bech32m. + let data = + Vec::::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?; + + let net = match hrp.as_str() { + unified::MAINNET => Network::Main, + unified::TESTNET => Network::Test, + unified::REGTEST => Network::Regtest, + // We will not define new Bech32m address encodings. + _ => { + return Err(ParseError::NotZcash); + } + }; + + return data[..] + .try_into() + .map(AddressKind::Unified) + .map_err(|_| ParseError::InvalidEncoding) + .map(|kind| ZcashAddress { net, kind }); + } Ok((hrp, data, Variant::Bech32)) => { // If we reached this point, the encoding is supposed to be valid Bech32. let data = Vec::::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?; let net = match hrp.as_str() { - sapling::MAINNET | orchard::MAINNET => Network::Main, - sapling::TESTNET | orchard::TESTNET => Network::Test, - sapling::REGTEST | orchard::REGTEST => Network::Regtest, + sapling::MAINNET => Network::Main, + sapling::TESTNET => Network::Test, + sapling::REGTEST => Network::Regtest, + // We will not define new Bech32 address encodings. _ => { - // Use some heuristics to try and guess whether this might be a Zcash - // address from the future: - // - Zcash HRPs always start with a 'z'. - // - Zcash shielded addresses with diversification have data of - // length 43, but if we added the simple form of detection keys - // the data would have length 75. Alternatively if we switch from a - // 11-byte diversifier to two field elements, that would be 64 bytes. - return Err( - if hrp.starts_with('z') - && (data.len() == 43 || data.len() == 64 || data.len() == 75) - { - ParseError::MaybeZcash - } else { - ParseError::NotZcash - }, - ); + return Err(ParseError::NotZcash); } }; - return match hrp.as_str() { - sapling::MAINNET | sapling::TESTNET | sapling::REGTEST => { - data[..].try_into().map(AddressKind::Sapling) - } - orchard::MAINNET | orchard::TESTNET | orchard::REGTEST => { - data[..].try_into().map(AddressKind::Orchard) - } - _ => unreachable!(), - } - .map_err(|_| ParseError::InvalidEncoding) - .map(|kind| ZcashAddress { net, kind }); + return data[..] + .try_into() + .map(AddressKind::Sapling) + .map_err(|_| ParseError::InvalidEncoding) + .map(|kind| ZcashAddress { net, kind }); } Err(_) => (), } @@ -113,6 +105,10 @@ impl FromStr for ZcashAddress { } } +fn encode_bech32m(hrp: &str, data: &[u8]) -> String { + bech32::encode(hrp, data.to_base32(), Variant::Bech32m).expect("hrp is invalid") +} + fn encode_bech32(hrp: &str, data: &[u8]) -> String { bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid") } @@ -143,11 +139,11 @@ impl fmt::Display for ZcashAddress { }, &data, ), - AddressKind::Orchard(data) => encode_bech32( + AddressKind::Unified(data) => encode_bech32m( match self.net { - Network::Main => orchard::MAINNET, - Network::Test => orchard::TESTNET, - Network::Regtest => orchard::REGTEST, + Network::Main => unified::MAINNET, + Network::Test => unified::TESTNET, + Network::Regtest => unified::REGTEST, }, &data, ), @@ -219,49 +215,30 @@ mod tests { } #[test] - fn orchard() { + fn unified() { encoding( "zo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq58lk79", ZcashAddress { net: Network::Main, - kind: AddressKind::Orchard([0; 43]), + kind: AddressKind::Unified([0; 43]), }, ); encoding( "ztestorchard1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcrmt3p", ZcashAddress { net: Network::Test, - kind: AddressKind::Orchard([0; 43]), + kind: AddressKind::Unified([0; 43]), }, ); encoding( "zregtestorchard1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq88jxqx", ZcashAddress { net: Network::Regtest, - kind: AddressKind::Orchard([0; 43]), + kind: AddressKind::Unified([0; 43]), }, ); } - #[test] - fn maybe_zcash() { - assert_eq!( - "zmaybe1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqql7xs38" - .parse::(), - Err(ParseError::MaybeZcash), - ); - assert_eq!( - "zpossibly1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq559klt" - .parse::(), - Err(ParseError::MaybeZcash), - ); - assert_eq!( - "nope1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg8f5j9" - .parse::(), - Err(ParseError::NotZcash), - ); - } - #[test] fn transparent() { encoding( diff --git a/components/zcash_address/src/kind.rs b/components/zcash_address/src/kind.rs index 647ceec59..3999fad39 100644 --- a/components/zcash_address/src/kind.rs +++ b/components/zcash_address/src/kind.rs @@ -1,4 +1,5 @@ -pub(crate) mod orchard; +pub(crate) mod unified; + pub(crate) mod sapling; pub(crate) mod sprout; diff --git a/components/zcash_address/src/kind/orchard.rs b/components/zcash_address/src/kind/orchard.rs deleted file mode 100644 index 3641fe17a..000000000 --- a/components/zcash_address/src/kind/orchard.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// The HRP for a Bech32-encoded mainnet Orchard address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.4.1][orchardpaymentaddrencoding]. -/// -/// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardpaymentaddrencoding -pub(crate) const MAINNET: &str = "zo"; - -/// The HRP for a Bech32-encoded testnet Orchard address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.4.1][orchardpaymentaddrencoding]. -/// -/// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardpaymentaddrencoding -pub(crate) const TESTNET: &str = "ztestorchard"; - -/// The HRP for a Bech32-encoded regtest Orchard address. -pub(crate) const REGTEST: &str = "zregtestorchard"; - -pub(crate) type Data = [u8; 43]; diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs new file mode 100644 index 000000000..35310099d --- /dev/null +++ b/components/zcash_address/src/kind/unified.rs @@ -0,0 +1,19 @@ +/// The HRP for a Bech32m-encoded mainnet Unified Address. +/// +/// Defined in [ZIP 316][zip-0316]. +/// +/// [zip-0316]: https://zips.z.cash/zip-0316 +pub(crate) const MAINNET: &str = "u"; + +/// The HRP for a Bech32m-encoded testnet Unified Address. +/// +/// Defined in [ZIP 316][zip-0316]. +/// +/// [zip-0316]: https://zips.z.cash/zip-0316 +pub(crate) const TESTNET: &str = "utest"; + +/// The HRP for a Bech32m-encoded regtest Unified Address. +pub(crate) const REGTEST: &str = "uregtest"; + +/// TODO +pub(crate) type Data = [u8; 43]; diff --git a/components/zcash_address/src/lib.rs b/components/zcash_address/src/lib.rs index cd239779d..8f8841aa1 100644 --- a/components/zcash_address/src/lib.rs +++ b/components/zcash_address/src/lib.rs @@ -31,7 +31,7 @@ pub enum Network { enum AddressKind { Sprout(kind::sprout::Data), Sapling(kind::sapling::Data), - Orchard(kind::orchard::Data), + Unified(kind::unified::Data), P2pkh(kind::p2pkh::Data), P2sh(kind::p2sh::Data), } @@ -46,17 +46,11 @@ impl ZcashAddress { /// /// # Errors /// - /// In most cases, [`ParseError::NotZcash`] will be returned on failure. The two - /// exceptions are: - /// /// - If the parser can detect that the string _must_ contain an address encoding used /// by Zcash, [`ParseError::InvalidEncoding`] will be returned if any subsequent /// part of that encoding is invalid. /// - /// - [`ParseError::MaybeZcash`] will be returned if the string is Bech32-encoded data - /// that satisfies some heuristics for probable future Zcash address formats (such - /// as beginning with a `z`). This can either be treated as an indication that this - /// library dependency should be updated, or mapped to [`ParseError::NotZcash`]. + /// - In all other cases, [`ParseError::NotZcash`] will be returned on failure. /// /// # Examples /// @@ -87,7 +81,7 @@ impl ZcashAddress { match self.kind { AddressKind::Sprout(data) => T::from_sprout(self.net, data), AddressKind::Sapling(data) => T::from_sapling(self.net, data), - AddressKind::Orchard(data) => T::from_orchard(self.net, data), + AddressKind::Unified(data) => T::from_unified(self.net, data), AddressKind::P2pkh(data) => T::from_transparent_p2pkh(self.net, data), AddressKind::P2sh(data) => T::from_transparent_p2sh(self.net, data), }