Merge pull request #1113 from zcash/zcash_address-remove-type-aliases

zcash_address: Remove private type aliases for raw data sizes
This commit is contained in:
str4d 2024-01-12 15:15:55 +00:00 committed by GitHub
commit 75184acfdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 274 additions and 47 deletions

2
Cargo.lock generated
View File

@ -2990,7 +2990,7 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
[[package]]
name = "zcash_address"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"assert_matches",
"bech32",

View File

@ -7,6 +7,11 @@ and this library adheres to Rust's notion of
## [Unreleased]
## [0.3.1] - 2024-01-12
### Fixed
- Stubs for `zcash_address::convert` traits that are created by `rust-analyzer`
and similar LSPs no longer reference crate-private type aliases.
## [0.3.0] - 2023-06-06
### Changed
- Bumped bs58 dependency to `0.5`.

View File

@ -1,7 +1,7 @@
[package]
name = "zcash_address"
description = "Zcash address parsing and serialization"
version = "0.3.0"
version = "0.3.1"
authors = [
"Jack Grigg <jack@electriccoin.co>",
]
@ -14,6 +14,10 @@ rust-version = "1.52"
categories = ["cryptography::cryptocurrencies", "encoding"]
keywords = ["zcash", "address", "sapling", "unified"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
bech32 = "0.9"
bs58 = { version = "0.5", features = ["check"] }

View File

@ -2,7 +2,7 @@ use std::{error::Error, fmt};
use crate::{kind::*, AddressKind, Network, ZcashAddress};
/// An address type is not supported for conversion.
/// An error indicating that an address type is not supported for conversion.
#[derive(Debug)]
pub struct UnsupportedAddress(&'static str);
@ -107,12 +107,12 @@ pub trait TryFromRawAddress: Sized {
/// [`Self::try_from_raw_sapling`] as a valid Sapling address).
type Error;
fn try_from_raw_sprout(data: sprout::Data) -> Result<Self, ConversionError<Self::Error>> {
fn try_from_raw_sprout(data: [u8; 64]) -> 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>> {
fn try_from_raw_sapling(data: [u8; 43]) -> Result<Self, ConversionError<Self::Error>> {
let _ = data;
Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
}
@ -123,7 +123,7 @@ pub trait TryFromRawAddress: Sized {
}
fn try_from_raw_transparent_p2pkh(
data: p2pkh::Data,
data: [u8; 20],
) -> Result<Self, ConversionError<Self::Error>> {
let _ = data;
Err(ConversionError::Unsupported(UnsupportedAddress(
@ -131,9 +131,7 @@ pub trait TryFromRawAddress: Sized {
)))
}
fn try_from_raw_transparent_p2sh(
data: p2sh::Data,
) -> Result<Self, ConversionError<Self::Error>> {
fn try_from_raw_transparent_p2sh(data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
let _ = data;
Err(ConversionError::Unsupported(UnsupportedAddress(
"transparent P2SH",
@ -187,17 +185,14 @@ pub trait TryFromAddress: Sized {
/// [`Self::try_from_sapling`] as a valid Sapling address).
type Error;
fn try_from_sprout(
net: Network,
data: sprout::Data,
) -> Result<Self, ConversionError<Self::Error>> {
fn try_from_sprout(net: Network, data: [u8; 64]) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
}
fn try_from_sapling(
net: Network,
data: sapling::Data,
data: [u8; 43],
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
@ -213,7 +208,7 @@ pub trait TryFromAddress: Sized {
fn try_from_transparent_p2pkh(
net: Network,
data: p2pkh::Data,
data: [u8; 20],
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(ConversionError::Unsupported(UnsupportedAddress(
@ -223,7 +218,7 @@ pub trait TryFromAddress: Sized {
fn try_from_transparent_p2sh(
net: Network,
data: p2sh::Data,
data: [u8; 20],
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(ConversionError::Unsupported(UnsupportedAddress(
@ -235,16 +230,13 @@ 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>> {
fn try_from_sprout(net: Network, data: [u8; 64]) -> Result<Self, ConversionError<Self::Error>> {
T::try_from_raw_sprout(data).map(|addr| (net, addr))
}
fn try_from_sapling(
net: Network,
data: sapling::Data,
data: [u8; 43],
) -> Result<Self, ConversionError<Self::Error>> {
T::try_from_raw_sapling(data).map(|addr| (net, addr))
}
@ -258,14 +250,14 @@ impl<T: TryFromRawAddress> TryFromAddress for (Network, T) {
fn try_from_transparent_p2pkh(
net: Network,
data: p2pkh::Data,
data: [u8; 20],
) -> 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,
data: [u8; 20],
) -> Result<Self, ConversionError<Self::Error>> {
T::try_from_raw_transparent_p2sh(data).map(|addr| (net, addr))
}
@ -303,19 +295,19 @@ impl<T: TryFromRawAddress> TryFromAddress for (Network, T) {
/// );
/// ```
pub trait ToAddress: private::Sealed {
fn from_sprout(net: Network, data: sprout::Data) -> Self;
fn from_sprout(net: Network, data: [u8; 64]) -> Self;
fn from_sapling(net: Network, data: sapling::Data) -> Self;
fn from_sapling(net: Network, data: [u8; 43]) -> Self;
fn from_unified(net: Network, data: unified::Address) -> Self;
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self;
fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Self;
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self;
fn from_transparent_p2sh(net: Network, data: [u8; 20]) -> Self;
}
impl ToAddress for ZcashAddress {
fn from_sprout(net: Network, data: sprout::Data) -> Self {
fn from_sprout(net: Network, data: [u8; 64]) -> Self {
ZcashAddress {
net: if let Network::Regtest = net {
Network::Test
@ -326,7 +318,7 @@ impl ToAddress for ZcashAddress {
}
}
fn from_sapling(net: Network, data: sapling::Data) -> Self {
fn from_sapling(net: Network, data: [u8; 43]) -> Self {
ZcashAddress {
net,
kind: AddressKind::Sapling(data),
@ -340,7 +332,7 @@ impl ToAddress for ZcashAddress {
}
}
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self {
fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Self {
ZcashAddress {
net: if let Network::Regtest = net {
Network::Test
@ -351,7 +343,7 @@ impl ToAddress for ZcashAddress {
}
}
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self {
fn from_transparent_p2sh(net: Network, data: [u8; 20]) -> Self {
ZcashAddress {
net: if let Network::Regtest = net {
Network::Test

View File

@ -3,5 +3,3 @@ pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xb8];
/// The prefix for a Base58Check-encoded testnet transparent P2PKH address.
pub(crate) const TESTNET: [u8; 2] = [0x1d, 0x25];
pub(crate) type Data = [u8; 20];

View File

@ -3,5 +3,3 @@ pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xbd];
/// The prefix for a Base58Check-encoded testnet transparent P2SH address.
pub(crate) const TESTNET: [u8; 2] = [0x1c, 0xba];
pub(crate) type Data = [u8; 20];

View File

@ -18,5 +18,3 @@ pub(crate) const TESTNET: &str = "ztestsapling";
///
/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L493
pub(crate) const REGTEST: &str = "zregtestsapling";
pub(crate) type Data = [u8; 43];

View File

@ -11,5 +11,3 @@ pub(crate) const MAINNET: [u8; 2] = [0x16, 0x9a];
///
/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding
pub(crate) const TESTNET: [u8; 2] = [0x16, 0xb6];
pub(crate) type Data = [u8; 64];

View File

@ -1,3 +1,5 @@
//! Implementation of [ZIP 316](https://zips.z.cash/zip-0316) Unified Addresses and Viewing Keys.
use bech32::{self, FromBase32, ToBase32, Variant};
use std::cmp;
use std::convert::{TryFrom, TryInto};
@ -17,12 +19,23 @@ pub use ivk::{Ivk, Uivk};
const PADDING_LEN: usize = 16;
/// The known Receiver and Viewing Key types.
///
/// The typecodes `0xFFFA..=0xFFFF` reserved for experiments are currently not
/// distinguished from unknown values, and will be parsed as [`Typecode::Unknown`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Typecode {
/// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
P2pkh,
/// A transparent P2SH address.
///
/// This typecode cannot occur in a [`Ufvk`] or [`Uivk`].
P2sh,
/// A Sapling raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
Sapling,
/// An Orchard raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
Orchard,
/// An unknown or experimental typecode.
Unknown(u32),
}

View File

@ -1,5 +1,4 @@
use super::{private::SealedItem, ParseError, Typecode};
use crate::kind;
use std::convert::{TryFrom, TryInto};
@ -7,9 +6,9 @@ use std::convert::{TryFrom, TryInto};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Receiver {
Orchard([u8; 43]),
Sapling(kind::sapling::Data),
P2pkh(kind::p2pkh::Data),
P2sh(kind::p2sh::Data),
Sapling([u8; 43]),
P2pkh([u8; 20]),
P2sh([u8; 20]),
Unknown { typecode: u32, data: Vec<u8> },
}
@ -56,6 +55,49 @@ impl SealedItem for Receiver {
}
/// A Unified Address.
///
/// # Examples
///
/// ```
/// # use std::convert::Infallible;
/// # use std::error::Error;
/// use zcash_address::{
/// unified::{self, Container, Encoding},
/// ConversionError, TryFromRawAddress, ZcashAddress,
/// };
///
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # let address_from_user = || "u1pg2aaph7jp8rpf6yhsza25722sg5fcn3vaca6ze27hqjw7jvvhhuxkpcg0ge9xh6drsgdkda8qjq5chpehkcpxf87rnjryjqwymdheptpvnljqqrjqzjwkc2ma6hcq666kgwfytxwac8eyex6ndgr6ezte66706e3vaqrd25dzvzkc69kw0jgywtd0cmq52q5lkw6uh7hyvzjse8ksx";
/// let example_ua: &str = address_from_user();
///
/// // We can parse this directly as a `unified::Address`:
/// let (network, ua) = unified::Address::decode(example_ua)?;
///
/// // Or we can parse via `ZcashAddress` (which you should do):
/// struct MyUnifiedAddress(unified::Address);
/// impl TryFromRawAddress for MyUnifiedAddress {
/// // In this example we aren't checking the validity of the
/// // inner Unified Address, but your code should do so!
/// type Error = Infallible;
///
/// fn try_from_raw_unified(ua: unified::Address) -> Result<Self, ConversionError<Self::Error>> {
/// Ok(MyUnifiedAddress(ua))
/// }
/// }
/// let addr: ZcashAddress = example_ua.parse()?;
/// let parsed = addr.convert_if_network::<MyUnifiedAddress>(network)?;
/// assert_eq!(parsed.0, ua);
///
/// // We can obtain the receivers for the UA in preference order
/// // (the order in which wallets should prefer to use them):
/// let receivers: Vec<unified::Receiver> = ua.items();
///
/// // And we can create the UA from a list of receivers:
/// let new_ua = unified::Address::try_from_items(receivers)?;
/// assert_eq!(new_ua, ua);
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Address(pub(crate) Vec<Receiver>);

View File

@ -78,6 +78,30 @@ impl SealedItem for Fvk {
}
/// A Unified Full Viewing Key.
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// use zcash_address::unified::{self, Container, Encoding};
///
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # let ufvk_from_user = || "uview1cgrqnry478ckvpr0f580t6fsahp0a5mj2e9xl7hv2d2jd4ldzy449mwwk2l9yeuts85wjls6hjtghdsy5vhhvmjdw3jxl3cxhrg3vs296a3czazrycrr5cywjhwc5c3ztfyjdhmz0exvzzeyejamyp0cr9z8f9wj0953fzht0m4lenk94t70ruwgjxag2tvp63wn9ftzhtkh20gyre3w5s24f6wlgqxnjh40gd2lxe75sf3z8h5y2x0atpxcyf9t3em4h0evvsftluruqne6w4sm066sw0qe5y8qg423grple5fftxrqyy7xmqmatv7nzd7tcjadu8f7mqz4l83jsyxy4t8pkayytyk7nrp467ds85knekdkvnd7hqkfer8mnqd7pv";
/// let example_ufvk: &str = ufvk_from_user();
///
/// let (network, ufvk) = unified::Ufvk::decode(example_ufvk)?;
///
/// // We can obtain the pool-specific Full Viewing Keys for the UFVK in preference
/// // order (the order in which wallets should prefer to use their corresponding
/// // address receivers):
/// let fvks: Vec<unified::Fvk> = ufvk.items();
///
/// // And we can create the UFVK from a list of FVKs:
/// let new_ufvk = unified::Ufvk::try_from_items(fvks)?;
/// assert_eq!(new_ufvk, ufvk);
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ufvk(pub(crate) Vec<Fvk>);

View File

@ -83,6 +83,30 @@ impl SealedItem for Ivk {
}
/// A Unified Incoming Viewing Key.
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// use zcash_address::unified::{self, Container, Encoding};
///
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # let uivk_from_user = || "uivk1djetqg3fws7y7qu5tekynvcdhz69gsyq07ewvppmzxdqhpfzdgmx8urnkqzv7ylz78ez43ux266pqjhecd59fzhn7wpe6zarnzh804hjtkyad25ryqla5pnc8p5wdl3phj9fczhz64zprun3ux7y9jc08567xryumuz59rjmg4uuflpjqwnq0j0tzce0x74t4tv3gfjq7nczkawxy6y7hse733ae3vw7qfjd0ss0pytvezxp42p6rrpzeh6t2zrz7zpjk0xhngcm6gwdppxs58jkx56gsfflugehf5vjlmu7vj3393gj6u37wenavtqyhdvcdeaj86s6jczl4zq";
/// let example_uivk: &str = uivk_from_user();
///
/// let (network, uivk) = unified::Uivk::decode(example_uivk)?;
///
/// // We can obtain the pool-specific Incoming Viewing Keys for the UIVK in
/// // preference order (the order in which wallets should prefer to use their
/// // corresponding address receivers):
/// let ivks: Vec<unified::Ivk> = uivk.items();
///
/// // And we can create the UIVK from a list of IVKs:
/// let new_uivk = unified::Uivk::try_from_items(ivks)?;
/// assert_eq!(new_uivk, uivk);
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Uivk(pub(crate) Vec<Ivk>);

View File

@ -1,3 +1,134 @@
//! *Parser for all defined Zcash address types.*
//!
//! This crate implements address parsing as a two-phase process, built around the opaque
//! [`ZcashAddress`] type.
//!
//! - [`ZcashAddress`] can be parsed from, and encoded to, strings.
//! - [`ZcashAddress::convert`] or [`ZcashAddress::convert_if_network`] can be used to
//! convert a parsed address into custom types that implement the [`TryFromAddress`] or
//! [`TryFromRawAddress`] traits.
//! - Custom types can be converted into a [`ZcashAddress`] via its implementation of the
//! [`ToAddress`] trait.
//!
//! ```text
//! s.parse() .convert()
//! --------> --------->
//! Strings ZcashAddress Custom types
//! <-------- <---------
//! .encode() ToAddress
//! ```
//!
//! It is important to note that this crate does not depend on any of the Zcash protocol
//! crates (e.g. `sapling-crypto` or `orchard`). This crate has minimal dependencies by
//! design; it focuses solely on parsing, handling those concerns for you, while exposing
//! APIs that enable you to convert the parsed data into the Rust types you want to use.
//!
//! # Using this crate
//!
//! ## I just need to validate Zcash addresses
//!
//! ```
//! # use zcash_address::ZcashAddress;
//! fn is_valid_zcash_address(addr_string: &str) -> bool {
//! addr_string.parse::<ZcashAddress>().is_ok()
//! }
//! ```
//!
//! ## I want to parse Zcash addresses in a Rust wallet app that uses the `zcash_primitives` transaction builder
//!
//! Use `zcash_client_backend::address::RecipientAddress`, which implements the traits in
//! this crate to parse address strings into protocol types that work with the transaction
//! builder in the `zcash_primitives` crate (as well as the wallet functionality in the
//! `zcash_client_backend` crate itself).
//!
//! > We intend to refactor the key and address types from the `zcash_client_backend` and
//! > `zcash_primitives` crates into a separate crate focused on dealing with Zcash key
//! > material. That crate will then be what you should use.
//!
//! ## I want to parse Unified Addresses
//!
//! See the [`unified::Address`] documentation for examples.
//!
//! While the [`unified::Address`] type does have parsing methods, you should still parse
//! your address strings with [`ZcashAddress`] and then convert; this will ensure that for
//! other Zcash address types you get a [`ConversionError::Unsupported`], which is a
//! better error for your users.
//!
//! ## I want to parse mainnet Zcash addresses in a language that supports C FFI
//!
//! As an example, you could use static functions to create the address types in the
//! target language from the parsed data.
//!
//! ```
//! use std::ffi::{CStr, c_char, c_void};
//! use std::ptr;
//!
//! use zcash_address::{ConversionError, Network, TryFromRawAddress, ZcashAddress};
//!
//! // Functions that return a pointer to a heap-allocated address of the given kind in
//! // the target language. These should be augmented to return any relevant errors.
//! extern {
//! fn addr_from_sapling(data: *const u8) -> *mut c_void;
//! fn addr_from_transparent_p2pkh(data: *const u8) -> *mut c_void;
//! }
//!
//! struct ParsedAddress(*mut c_void);
//!
//! impl TryFromRawAddress for ParsedAddress {
//! type Error = &'static str;
//!
//! fn try_from_raw_sapling(
//! data: [u8; 43],
//! ) -> Result<Self, ConversionError<Self::Error>> {
//! let parsed = unsafe { addr_from_sapling(data[..].as_ptr()) };
//! if parsed.is_null() {
//! Err("Reason for the failure".into())
//! } else {
//! Ok(Self(parsed))
//! }
//! }
//!
//! fn try_from_raw_transparent_p2pkh(
//! data: [u8; 20],
//! ) -> Result<Self, ConversionError<Self::Error>> {
//! let parsed = unsafe { addr_from_transparent_p2pkh(data[..].as_ptr()) };
//! if parsed.is_null() {
//! Err("Reason for the failure".into())
//! } else {
//! Ok(Self(parsed))
//! }
//! }
//! }
//!
//! pub extern "C" fn parse_zcash_address(encoded: *const c_char) -> *mut c_void {
//! let encoded = unsafe { CStr::from_ptr(encoded) }.to_str().expect("valid");
//!
//! let addr = match ZcashAddress::try_from_encoded(encoded) {
//! Ok(addr) => addr,
//! Err(e) => {
//! // This was either an invalid address encoding, or not a Zcash address.
//! // You should pass this error back across the FFI.
//! return ptr::null_mut();
//! }
//! };
//!
//! match addr.convert_if_network::<ParsedAddress>(Network::Main) {
//! Ok(parsed) => parsed.0,
//! Err(e) => {
//! // We didn't implement all of the methods of `TryFromRawAddress`, so if an
//! // address with one of those kinds is parsed, it will result in an error
//! // here that should be passed back across the FFI.
//! ptr::null_mut()
//! }
//! }
//! }
//! ```
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
// Catch documentation errors caused by code changes.
#![deny(rustdoc::broken_intra_doc_links)]
mod convert;
mod encoding;
mod kind;
@ -35,11 +166,11 @@ pub enum Network {
/// Known kinds of Zcash addresses.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum AddressKind {
Sprout(kind::sprout::Data),
Sapling(kind::sapling::Data),
Sprout([u8; 64]),
Sapling([u8; 43]),
Unified(unified::Address),
P2pkh(kind::p2pkh::Data),
P2sh(kind::p2sh::Data),
P2pkh([u8; 20]),
P2sh([u8; 20]),
}
impl ZcashAddress {