zcash_address: Improve documentation

This commit is contained in:
Jack Grigg 2024-01-12 02:44:48 +00:00
parent 6570116384
commit 22271145a7
6 changed files with 231 additions and 1 deletions

View File

@ -2,7 +2,7 @@ use std::{error::Error, fmt};
use crate::{kind::*, AddressKind, Network, ZcashAddress}; 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)] #[derive(Debug)]
pub struct UnsupportedAddress(&'static str); pub struct UnsupportedAddress(&'static str);

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 bech32::{self, FromBase32, ToBase32, Variant};
use std::cmp; use std::cmp;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
@ -17,12 +19,23 @@ pub use ivk::{Ivk, Uivk};
const PADDING_LEN: usize = 16; 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Typecode { pub enum Typecode {
/// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
P2pkh, P2pkh,
/// A transparent P2SH address.
///
/// This typecode cannot occur in a [`Ufvk`] or [`Uivk`].
P2sh, P2sh,
/// A Sapling raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
Sapling, Sapling,
/// An Orchard raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316).
Orchard, Orchard,
/// An unknown or experimental typecode.
Unknown(u32), Unknown(u32),
} }

View File

@ -55,6 +55,49 @@ impl SealedItem for Receiver {
} }
/// A Unified Address. /// 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)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Address(pub(crate) Vec<Receiver>); pub struct Address(pub(crate) Vec<Receiver>);

View File

@ -78,6 +78,30 @@ impl SealedItem for Fvk {
} }
/// A Unified Full Viewing Key. /// 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)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ufvk(pub(crate) Vec<Fvk>); pub struct Ufvk(pub(crate) Vec<Fvk>);

View File

@ -83,6 +83,30 @@ impl SealedItem for Ivk {
} }
/// A Unified Incoming Viewing Key. /// 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)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Uivk(pub(crate) Vec<Ivk>); pub struct Uivk(pub(crate) Vec<Ivk>);

View File

@ -1,3 +1,129 @@
//! *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_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
// Catch documentation errors caused by code changes. // Catch documentation errors caused by code changes.