Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zcash_address 0.3.2: ZIP 320 TEX address support #1226

Merged
merged 3 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ zcash_address = { version = "0.3", path = "components/zcash_address" }
zcash_client_backend = { version = "0.11", path = "zcash_client_backend" }
zcash_encoding = { version = "0.2", path = "components/zcash_encoding" }
zcash_keys = { version = "0.1", path = "zcash_keys" }
zcash_protocol = { version = "0.0", path = "components/zcash_protocol" }
zcash_protocol = { version = "0.1", path = "components/zcash_protocol" }

zcash_note_encryption = "0.4"
zcash_primitives = { version = "0.14", path = "zcash_primitives", default-features = false }
Expand Down
7 changes: 7 additions & 0 deletions components/zcash_address/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this library adheres to Rust's notion of

## [Unreleased]

## [0.3.2] - 2024-03-06
### Added
- `zcash_address::convert`:
- `TryFromRawAddress::try_from_raw_tex`
- `TryFromAddress::try_from_tex`
- `ToAddress::from_tex`

## [0.3.1] - 2024-01-12
### Fixed
- Stubs for `zcash_address::convert` traits that are created by `rust-analyzer`
Expand Down
2 changes: 1 addition & 1 deletion components/zcash_address/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "zcash_address"
description = "Zcash address parsing and serialization"
version = "0.3.1"
version = "0.3.2"
authors = [
"Jack Grigg <[email protected]>",
]
Expand Down
27 changes: 27 additions & 0 deletions components/zcash_address/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@
"transparent P2SH",
)))
}

fn try_from_raw_tex(data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
let _ = data;
Err(ConversionError::Unsupported(UnsupportedAddress(
"transparent-source restricted P2PKH",

Check warning on line 144 in components/zcash_address/src/convert.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/convert.rs#L141-L144

Added lines #L141 - L144 were not covered by tests
)))
}
}

/// A helper trait for converting a [`ZcashAddress`] into another type.
Expand Down Expand Up @@ -225,6 +232,13 @@
"transparent P2SH",
)))
}

fn try_from_tex(net: Network, data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(ConversionError::Unsupported(UnsupportedAddress(
"transparent-source restricted P2PKH",

Check warning on line 239 in components/zcash_address/src/convert.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/convert.rs#L236-L239

Added lines #L236 - L239 were not covered by tests
)))
}
}

impl<T: TryFromRawAddress> TryFromAddress for (Network, T) {
Expand Down Expand Up @@ -261,6 +275,10 @@
) -> Result<Self, ConversionError<Self::Error>> {
T::try_from_raw_transparent_p2sh(data).map(|addr| (net, addr))
}

fn try_from_tex(net: Network, data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
T::try_from_raw_tex(data).map(|addr| (net, addr))

Check warning on line 280 in components/zcash_address/src/convert.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/convert.rs#L279-L280

Added lines #L279 - L280 were not covered by tests
}
}

/// A helper trait for converting another type into a [`ZcashAddress`].
Expand Down Expand Up @@ -304,6 +322,8 @@
fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Self;

fn from_transparent_p2sh(net: Network, data: [u8; 20]) -> Self;

fn from_tex(net: Network, data: [u8; 20]) -> Self;
}

impl ToAddress for ZcashAddress {
Expand Down Expand Up @@ -353,6 +373,13 @@
kind: AddressKind::P2sh(data),
}
}

fn from_tex(net: Network, data: [u8; 20]) -> Self {

Check warning on line 377 in components/zcash_address/src/convert.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/convert.rs#L377

Added line #L377 was not covered by tests
ZcashAddress {
net,
kind: AddressKind::Tex(data),

Check warning on line 380 in components/zcash_address/src/convert.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/convert.rs#L380

Added line #L380 was not covered by tests
}
}
}

mod private {
Expand Down
143 changes: 120 additions & 23 deletions components/zcash_address/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,56 @@
kind: AddressKind::Unified(data),
});
}
Err(unified::ParseError::NotUnified) => {
// allow decoding to fall through to Sapling/Transparent
Err(unified::ParseError::NotUnified | unified::ParseError::UnknownPrefix(_)) => {
// allow decoding to fall through to Sapling/TEX/Transparent
}
Err(e) => {
return Err(ParseError::from(e));
}
}

// Try decoding as a Sapling address (Bech32)
if let Ok((hrp, data, Variant::Bech32)) = bech32::decode(s) {
// If we reached this point, the encoding is supposed to be valid Bech32.
// Try decoding as a Sapling or TEX address (Bech32/Bech32m)
if let Ok((hrp, data, variant)) = bech32::decode(s) {
// If we reached this point, the encoding is found to be valid Bech32 or Bech32m.
let data = Vec::<u8>::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?;

let net = match hrp.as_str() {
mainnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Main,
testnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Test,
regtest::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Regtest,
// We will not define new Bech32 address encodings.
_ => {
return Err(ParseError::NotZcash);
match variant {
Variant::Bech32 => {
let net = match hrp.as_str() {
mainnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Main,
testnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Test,
regtest::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Regtest,
// We will not define new Bech32 address encodings.
_ => {
return Err(ParseError::NotZcash);

Check warning on line 80 in components/zcash_address/src/encoding.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/encoding.rs#L80

Added line #L80 was not covered by tests
}
};

return data[..]
.try_into()
.map(AddressKind::Sapling)
.map_err(|_| ParseError::InvalidEncoding)
.map(|kind| ZcashAddress { net, kind });
}
};
Variant::Bech32m => {
// Try decoding as a TEX address (Bech32m)
let net = match hrp.as_str() {
mainnet::HRP_TEX_ADDRESS => NetworkType::Main,
testnet::HRP_TEX_ADDRESS => NetworkType::Test,
regtest::HRP_TEX_ADDRESS => NetworkType::Regtest,
// Not recognized as a Zcash address type
_ => {
return Err(ParseError::NotZcash);
}
};

return data[..]
.try_into()
.map(AddressKind::Sapling)
.map_err(|_| ParseError::InvalidEncoding)
.map(|kind| ZcashAddress { net, kind });
return data[..]
.try_into()
.map(AddressKind::Tex)
.map_err(|_| ParseError::InvalidEncoding)
.map(|kind| ZcashAddress { net, kind });
}
}
}

// The rest use Base58Check.
Expand Down Expand Up @@ -122,8 +144,8 @@
}
}

fn encode_bech32(hrp: &str, data: &[u8]) -> String {
bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid")
fn encode_bech32(hrp: &str, data: &[u8], variant: Variant) -> String {
bech32::encode(hrp, data.to_base32(), variant).expect("hrp is invalid")
}

fn encode_b58(prefix: [u8; 2], data: &[u8]) -> String {
Expand All @@ -137,19 +159,26 @@
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded = match &self.kind {
AddressKind::Sprout(data) => encode_b58(self.net.b58_sprout_address_prefix(), data),
AddressKind::Sapling(data) => {
encode_bech32(self.net.hrp_sapling_payment_address(), data)
}
AddressKind::Sapling(data) => encode_bech32(
self.net.hrp_sapling_payment_address(),
data,
Variant::Bech32,
),
AddressKind::Unified(addr) => addr.encode(&self.net),
AddressKind::P2pkh(data) => encode_b58(self.net.b58_pubkey_address_prefix(), data),
AddressKind::P2sh(data) => encode_b58(self.net.b58_script_address_prefix(), data),
AddressKind::Tex(data) => {
encode_bech32(self.net.hrp_tex_address(), data, Variant::Bech32m)
}
};
write!(f, "{}", encoded)
}
}

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;

use super::*;
use crate::{kind::unified, Network};

Expand Down Expand Up @@ -258,6 +287,74 @@
);
}

#[test]
fn tex() {
let p2pkh_str = "t1VmmGiyjVNeCjxDZzg7vZmd99WyzVby9yC";
let tex_str = "tex1s2rt77ggv6q989lr49rkgzmh5slsksa9khdgte";

// Transcode P2PKH to TEX
let p2pkh_zaddr: ZcashAddress = p2pkh_str.parse().unwrap();
assert_matches!(p2pkh_zaddr.net, Network::Main);
if let AddressKind::P2pkh(zaddr_data) = p2pkh_zaddr.kind {
let tex_zaddr = ZcashAddress {
net: p2pkh_zaddr.net,
kind: AddressKind::Tex(zaddr_data),
};

assert_eq!(tex_zaddr.to_string(), tex_str);
} else {
panic!("Decoded address should have been a P2PKH address.");
}

// Transcode TEX to P2PKH
let tex_zaddr: ZcashAddress = tex_str.parse().unwrap();
assert_matches!(tex_zaddr.net, Network::Main);
if let AddressKind::Tex(zaddr_data) = tex_zaddr.kind {
let p2pkh_zaddr = ZcashAddress {
net: tex_zaddr.net,
kind: AddressKind::P2pkh(zaddr_data),
};

assert_eq!(p2pkh_zaddr.to_string(), p2pkh_str);
} else {
panic!("Decoded address should have been a TEX address.");
}
}

#[test]
fn tex_testnet() {
let p2pkh_str = "tm9ofD7kHR7AF8MsJomEzLqGcrLCBkD9gDj";
let tex_str = "textest1qyqszqgpqyqszqgpqyqszqgpqyqszqgpfcjgfy";

// Transcode P2PKH to TEX
let p2pkh_zaddr: ZcashAddress = p2pkh_str.parse().unwrap();
assert_matches!(p2pkh_zaddr.net, Network::Test);
if let AddressKind::P2pkh(zaddr_data) = p2pkh_zaddr.kind {
let tex_zaddr = ZcashAddress {
net: p2pkh_zaddr.net,
kind: AddressKind::Tex(zaddr_data),
};

assert_eq!(tex_zaddr.to_string(), tex_str);
} else {
panic!("Decoded address should have been a P2PKH address.");
}

// Transcode TEX to P2PKH
let tex_zaddr: ZcashAddress = tex_str.parse().unwrap();
assert_matches!(tex_zaddr.net, Network::Test);
if let AddressKind::Tex(zaddr_data) = tex_zaddr.kind {
let p2pkh_zaddr = ZcashAddress {
net: tex_zaddr.net,
kind: AddressKind::P2pkh(zaddr_data),
};

assert_eq!(p2pkh_zaddr.to_string(), p2pkh_str);
} else {
panic!("Decoded address should have been a TEX address.");
}
}

#[test]
fn whitespace() {
assert_eq!(
Expand Down
3 changes: 3 additions & 0 deletions components/zcash_address/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
Unified(unified::Address),
P2pkh([u8; 20]),
P2sh([u8; 20]),
Tex([u8; 20]),
}

impl ZcashAddress {
Expand Down Expand Up @@ -222,6 +223,7 @@
AddressKind::Unified(data) => T::try_from_unified(self.net, data),
AddressKind::P2pkh(data) => T::try_from_transparent_p2pkh(self.net, data),
AddressKind::P2sh(data) => T::try_from_transparent_p2sh(self.net, data),
AddressKind::Tex(data) => T::try_from_tex(self.net, data),

Check warning on line 226 in components/zcash_address/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/lib.rs#L226

Added line #L226 was not covered by tests
}
}

Expand Down Expand Up @@ -257,6 +259,7 @@
T::try_from_raw_transparent_p2pkh(data)
}
AddressKind::P2sh(data) if regtest_exception => T::try_from_raw_transparent_p2sh(data),
AddressKind::Tex(data) if network_matches => T::try_from_raw_tex(data),

Check warning on line 262 in components/zcash_address/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_address/src/lib.rs#L262

Added line #L262 was not covered by tests
_ => Err(ConversionError::IncorrectNetwork {
expected: net,
actual: self.net,
Expand Down
7 changes: 5 additions & 2 deletions components/zcash_protocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.0] - 2024-03-06
The entries below are relative to the `zcash_primitives` crate as of the tag
`zcash_primitives-0.14.0`.

Expand All @@ -17,8 +19,9 @@ The entries below are relative to the `zcash_primitives` crate as of the tag
- `zcash_protocol::value` replaces `zcash_primitives::transaction::components::amount`
- `zcash_protocol::consensus`:
- `NetworkConstants` has been extracted from the `Parameters` trait. Relative to the
state prior to the extraction, the bech32 prefixes now return `&'static str` instead
of `&str`.
state prior to the extraction:
- The Bech32 prefixes now return `&'static str` instead of `&str`.
- Added `NetworkConstants::hrp_tex_address`.
- `NetworkType`
- `Parameters::b58_sprout_address_prefix`
- `zcash_protocol::consensus`:
Expand Down
2 changes: 1 addition & 1 deletion components/zcash_protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "zcash_protocol"
description = "Zcash protocol network constants and value types."
version = "0.0.0"
version = "0.1.0"
authors = [
"Jack Grigg <[email protected]>",
"Kris Nuttycombe <[email protected]>",
Expand Down
20 changes: 20 additions & 0 deletions components/zcash_protocol/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@
///
/// [`TransparentAddress::Script`]: zcash_primitives::legacy::TransparentAddress::Script
fn b58_script_address_prefix(&self) -> [u8; 2];

/// Returns the Bech32-encoded human-readable prefix for TEX addresses, for the
/// network to which this `NetworkConstants` value applies.
///
/// Defined in [ZIP 320].
///
/// [ZIP 320]: https://zips.z.cash/zip-0320
fn hrp_tex_address(&self) -> &'static str;
}

/// The enumeration of known Zcash network types.
Expand Down Expand Up @@ -264,6 +272,14 @@
NetworkType::Regtest => regtest::B58_SCRIPT_ADDRESS_PREFIX,
}
}

fn hrp_tex_address(&self) -> &'static str {
match self {
NetworkType::Main => mainnet::HRP_TEX_ADDRESS,
NetworkType::Test => testnet::HRP_TEX_ADDRESS,
NetworkType::Regtest => regtest::HRP_TEX_ADDRESS,

Check warning on line 280 in components/zcash_protocol/src/consensus.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_protocol/src/consensus.rs#L280

Added line #L280 was not covered by tests
}
}
}

/// Zcash consensus parameters.
Expand Down Expand Up @@ -310,6 +326,10 @@
fn b58_script_address_prefix(&self) -> [u8; 2] {
self.network_type().b58_script_address_prefix()
}

fn hrp_tex_address(&self) -> &'static str {
self.network_type().hrp_tex_address()

Check warning on line 331 in components/zcash_protocol/src/consensus.rs

View check run for this annotation

Codecov / codecov/patch

components/zcash_protocol/src/consensus.rs#L330-L331

Added lines #L330 - L331 were not covered by tests
}
}

/// Marker struct for the production network.
Expand Down
Loading
Loading