zcash_address: Replace Orchard address encodings with Unified Addresses
This commit removes the now-undefined Orchard encoding logic, and adds the general Bech32m encoding/decoding logic for Unified Addresses. The internal data format of Unified Addresses is not correct in this commit.
This commit is contained in:
parent
9f7398cd05
commit
ae2b8bfd6d
|
@ -59,9 +59,9 @@ pub trait FromAddress: Sized {
|
||||||
Err(UnsupportedAddress("Sapling"))
|
Err(UnsupportedAddress("Sapling"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_orchard(net: Network, data: orchard::Data) -> Result<Self, UnsupportedAddress> {
|
fn from_unified(net: Network, data: unified::Data) -> Result<Self, UnsupportedAddress> {
|
||||||
let _ = (net, data);
|
let _ = (net, data);
|
||||||
Err(UnsupportedAddress("Orchard"))
|
Err(UnsupportedAddress("Unified"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Result<Self, UnsupportedAddress> {
|
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Result<Self, UnsupportedAddress> {
|
||||||
|
@ -111,7 +111,7 @@ pub trait ToAddress: private::Sealed {
|
||||||
|
|
||||||
fn from_sapling(net: Network, data: sapling::Data) -> Self;
|
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;
|
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 {
|
ZcashAddress {
|
||||||
net,
|
net,
|
||||||
kind: AddressKind::Orchard(data),
|
kind: AddressKind::Unified(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ use crate::{kind::*, AddressKind, Network, ZcashAddress};
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
/// The string is an invalid encoding.
|
/// The string is an invalid encoding.
|
||||||
InvalidEncoding,
|
InvalidEncoding,
|
||||||
/// The string might be an unknown Zcash address from the future.
|
|
||||||
MaybeZcash,
|
|
||||||
/// The string is not a Zcash address.
|
/// The string is not a Zcash address.
|
||||||
NotZcash,
|
NotZcash,
|
||||||
}
|
}
|
||||||
|
@ -19,10 +17,6 @@ impl fmt::Display for ParseError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ParseError::InvalidEncoding => write!(f, "Invalid encoding"),
|
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"),
|
ParseError::NotZcash => write!(f, "Not a Zcash address"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,47 +34,45 @@ impl FromStr for ZcashAddress {
|
||||||
|
|
||||||
// Most Zcash addresses use Bech32, so try that first.
|
// Most Zcash addresses use Bech32, so try that first.
|
||||||
match bech32::decode(s) {
|
match bech32::decode(s) {
|
||||||
// Zcash addresses only use the original Bech32 variant, since the data
|
Ok((hrp, data, Variant::Bech32m)) => {
|
||||||
// corresponding to a particular HRP always has a fixed length.
|
// If we reached this point, the encoding is supposed to be valid Bech32m.
|
||||||
Ok((_, _, Variant::Bech32m)) => return Err(ParseError::NotZcash),
|
let data =
|
||||||
|
Vec::<u8>::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)) => {
|
Ok((hrp, data, Variant::Bech32)) => {
|
||||||
// If we reached this point, the encoding is supposed to be valid Bech32.
|
// If we reached this point, the encoding is supposed to be valid Bech32.
|
||||||
let data =
|
let data =
|
||||||
Vec::<u8>::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?;
|
Vec::<u8>::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?;
|
||||||
|
|
||||||
let net = match hrp.as_str() {
|
let net = match hrp.as_str() {
|
||||||
sapling::MAINNET | orchard::MAINNET => Network::Main,
|
sapling::MAINNET => Network::Main,
|
||||||
sapling::TESTNET | orchard::TESTNET => Network::Test,
|
sapling::TESTNET => Network::Test,
|
||||||
sapling::REGTEST | orchard::REGTEST => Network::Regtest,
|
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
|
return Err(ParseError::NotZcash);
|
||||||
// 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 match hrp.as_str() {
|
return data[..]
|
||||||
sapling::MAINNET | sapling::TESTNET | sapling::REGTEST => {
|
.try_into()
|
||||||
data[..].try_into().map(AddressKind::Sapling)
|
.map(AddressKind::Sapling)
|
||||||
}
|
|
||||||
orchard::MAINNET | orchard::TESTNET | orchard::REGTEST => {
|
|
||||||
data[..].try_into().map(AddressKind::Orchard)
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
.map_err(|_| ParseError::InvalidEncoding)
|
.map_err(|_| ParseError::InvalidEncoding)
|
||||||
.map(|kind| ZcashAddress { net, kind });
|
.map(|kind| ZcashAddress { net, kind });
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
fn encode_bech32(hrp: &str, data: &[u8]) -> String {
|
||||||
bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid")
|
bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid")
|
||||||
}
|
}
|
||||||
|
@ -143,11 +139,11 @@ impl fmt::Display for ZcashAddress {
|
||||||
},
|
},
|
||||||
&data,
|
&data,
|
||||||
),
|
),
|
||||||
AddressKind::Orchard(data) => encode_bech32(
|
AddressKind::Unified(data) => encode_bech32m(
|
||||||
match self.net {
|
match self.net {
|
||||||
Network::Main => orchard::MAINNET,
|
Network::Main => unified::MAINNET,
|
||||||
Network::Test => orchard::TESTNET,
|
Network::Test => unified::TESTNET,
|
||||||
Network::Regtest => orchard::REGTEST,
|
Network::Regtest => unified::REGTEST,
|
||||||
},
|
},
|
||||||
&data,
|
&data,
|
||||||
),
|
),
|
||||||
|
@ -219,49 +215,30 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn orchard() {
|
fn unified() {
|
||||||
encoding(
|
encoding(
|
||||||
"zo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq58lk79",
|
"zo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq58lk79",
|
||||||
ZcashAddress {
|
ZcashAddress {
|
||||||
net: Network::Main,
|
net: Network::Main,
|
||||||
kind: AddressKind::Orchard([0; 43]),
|
kind: AddressKind::Unified([0; 43]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoding(
|
encoding(
|
||||||
"ztestorchard1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcrmt3p",
|
"ztestorchard1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcrmt3p",
|
||||||
ZcashAddress {
|
ZcashAddress {
|
||||||
net: Network::Test,
|
net: Network::Test,
|
||||||
kind: AddressKind::Orchard([0; 43]),
|
kind: AddressKind::Unified([0; 43]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoding(
|
encoding(
|
||||||
"zregtestorchard1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq88jxqx",
|
"zregtestorchard1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq88jxqx",
|
||||||
ZcashAddress {
|
ZcashAddress {
|
||||||
net: Network::Regtest,
|
net: Network::Regtest,
|
||||||
kind: AddressKind::Orchard([0; 43]),
|
kind: AddressKind::Unified([0; 43]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn maybe_zcash() {
|
|
||||||
assert_eq!(
|
|
||||||
"zmaybe1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqql7xs38"
|
|
||||||
.parse::<ZcashAddress>(),
|
|
||||||
Err(ParseError::MaybeZcash),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
"zpossibly1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq559klt"
|
|
||||||
.parse::<ZcashAddress>(),
|
|
||||||
Err(ParseError::MaybeZcash),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
"nope1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg8f5j9"
|
|
||||||
.parse::<ZcashAddress>(),
|
|
||||||
Err(ParseError::NotZcash),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transparent() {
|
fn transparent() {
|
||||||
encoding(
|
encoding(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub(crate) mod orchard;
|
pub(crate) mod unified;
|
||||||
|
|
||||||
pub(crate) mod sapling;
|
pub(crate) mod sapling;
|
||||||
pub(crate) mod sprout;
|
pub(crate) mod sprout;
|
||||||
|
|
||||||
|
|
|
@ -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];
|
|
|
@ -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];
|
|
@ -31,7 +31,7 @@ pub enum Network {
|
||||||
enum AddressKind {
|
enum AddressKind {
|
||||||
Sprout(kind::sprout::Data),
|
Sprout(kind::sprout::Data),
|
||||||
Sapling(kind::sapling::Data),
|
Sapling(kind::sapling::Data),
|
||||||
Orchard(kind::orchard::Data),
|
Unified(kind::unified::Data),
|
||||||
P2pkh(kind::p2pkh::Data),
|
P2pkh(kind::p2pkh::Data),
|
||||||
P2sh(kind::p2sh::Data),
|
P2sh(kind::p2sh::Data),
|
||||||
}
|
}
|
||||||
|
@ -46,17 +46,11 @@ impl ZcashAddress {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # 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
|
/// - If the parser can detect that the string _must_ contain an address encoding used
|
||||||
/// by Zcash, [`ParseError::InvalidEncoding`] will be returned if any subsequent
|
/// by Zcash, [`ParseError::InvalidEncoding`] will be returned if any subsequent
|
||||||
/// part of that encoding is invalid.
|
/// part of that encoding is invalid.
|
||||||
///
|
///
|
||||||
/// - [`ParseError::MaybeZcash`] will be returned if the string is Bech32-encoded data
|
/// - In all other cases, [`ParseError::NotZcash`] will be returned on failure.
|
||||||
/// 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`].
|
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -87,7 +81,7 @@ impl ZcashAddress {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
AddressKind::Sprout(data) => T::from_sprout(self.net, data),
|
AddressKind::Sprout(data) => T::from_sprout(self.net, data),
|
||||||
AddressKind::Sapling(data) => T::from_sapling(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::P2pkh(data) => T::from_transparent_p2pkh(self.net, data),
|
||||||
AddressKind::P2sh(data) => T::from_transparent_p2sh(self.net, data),
|
AddressKind::P2sh(data) => T::from_transparent_p2sh(self.net, data),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue