Skip to content

Commit

Permalink
zcash_keys: Update key and address types to include ZIP-316 metadata …
Browse files Browse the repository at this point in the history
…items.
  • Loading branch information
nuttycom committed Jan 26, 2024
1 parent ba84251 commit 65f5784
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 141 deletions.
4 changes: 1 addition & 3 deletions components/zcash_address/src/kind/unified/fvk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,7 @@ mod tests {

use super::{Fvk, ParseError, Ufvk};
use crate::{
kind::unified::{
private::{SealedContainer}, Encoding,
},
kind::unified::{private::SealedContainer, Encoding},
unified::{Item, Typecode},
Network,
};
Expand Down
4 changes: 1 addition & 3 deletions components/zcash_address/src/kind/unified/ivk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,7 @@ mod tests {

use super::{Ivk, ParseError, Uivk};
use crate::{
kind::unified::{
private::{SealedContainer}, Encoding,
},
kind::unified::{private::SealedContainer, Encoding},
unified::{Item, Typecode},
Network,
};
Expand Down
2 changes: 1 addition & 1 deletion zcash_client_backend/src/data_api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ where
}
} else {
return Err(Error::NoSupportedReceivers(
ua.unknown().iter().map(|(tc, _)| *tc).collect(),
ua.unknown_data().iter().map(|(tc, _)| *tc).collect(),
));
}
}
Expand Down
6 changes: 4 additions & 2 deletions zcash_client_backend/src/zip321.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,11 +760,13 @@ pub mod testing {
sapling in arb_payment_address(),
transparent in option::of(arb_transparent_addr()),
) -> UnifiedAddress {
UnifiedAddress::from_receivers(
UnifiedAddress::new(
#[cfg(feature = "orchard")]
None,
Some(sapling),
transparent
transparent,
None,
None
).unwrap()
}
}
Expand Down
11 changes: 7 additions & 4 deletions zcash_keys/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ The entries below are relative to the `zcash_client_backend` crate as of

### Added
- The following modules have been extracted from `zcash_client_backend` and
moved to this crate:
moved to this crate:
- `address`
- `encoding`
- `keys`
- `zcash_keys::address::UnifiedAddress::unknown`:
- `zcash_keys::address`:
- `UnifiedAddress::{expiry_height, expiry_time, unknown_data, unknown_metadata}`
- `zcash_keys::keys`:
- `AddressGenerationError`
- `UnifiedAddressRequest`
Expand All @@ -25,8 +26,10 @@ The entries below are relative to the `zcash_client_backend` crate as of
- `zcash_keys::address`:
- `RecipientAddress` has been renamed to `Address`
- `Address::Shielded` has been renamed to `Address::Sapling`
- `UnifiedAddress::from_receivers` no longer takes an Orchard receiver
argument unless the `orchard` feature is enabled.
- `UnifiedAddress::from_receivers` has been renamed to `UnifiedAddress::new`.
It no longer takes an Orchard receiver argument unless the `orchard` feature
is enabled, and it now takes additional arguments to allow the caller to
specify metadata items.
- `UnifiedAddress::orchard` is now only available when the `orchard` feature
is enabled.

Expand Down
151 changes: 101 additions & 50 deletions zcash_keys/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use std::convert::TryFrom;

use sapling::PaymentAddress;
use zcash_address::{
unified::{self, Container, Encoding},
unified::{self, Container, Encoding, Item},
ConversionError, Network, ToAddress, TryFromRawAddress, ZcashAddress,
};
use zcash_primitives::{
consensus,
consensus::{self, BlockHeight},
legacy::TransparentAddress,
zip32::{AccountId, DiversifierIndex},
};
Expand Down Expand Up @@ -42,7 +42,10 @@ pub struct UnifiedAddress {
orchard: Option<orchard::Address>,
sapling: Option<PaymentAddress>,
transparent: Option<TransparentAddress>,
unknown: Vec<(u32, Vec<u8>)>,
unknown_data: Vec<(u32, Vec<u8>)>,
expiry_height: Option<BlockHeight>,
expiry_time: Option<u64>,
unknown_metadata: Vec<(u32, Vec<u8>)>,
}

impl TryFrom<unified::Address> for UnifiedAddress {
Expand All @@ -53,54 +56,62 @@ impl TryFrom<unified::Address> for UnifiedAddress {
let mut orchard = None;
let mut sapling = None;
let mut transparent = None;
let mut unknown_data = vec![];
let mut expiry_height = None;
let mut expiry_time = None;
let mut unknown_metadata = vec![];

// We can use as-parsed order here for efficiency, because we're breaking out the
// receivers we support from the unknown receivers.
let unknown = ua
.items_as_parsed()
.iter()
.filter_map(|receiver| match receiver {
for item in ua.items_as_parsed() {
match item {
#[cfg(feature = "orchard")]
unified::Receiver::Orchard(data) => {
Option::from(orchard::Address::from_raw_address_bytes(data))
.ok_or("Invalid Orchard receiver in Unified Address")
.map(|addr| {
orchard = Some(addr);
None
})
.transpose()
Item::Data(unified::Receiver::Orchard(data)) => {
orchard = Some(
Option::from(orchard::Address::from_raw_address_bytes(data))
.ok_or("Invalid Orchard receiver in Unified Address")?,
);
}
#[cfg(not(feature = "orchard"))]
unified::Receiver::Orchard(data) => {
Some(Ok((unified::Typecode::Orchard.into(), data.to_vec())))
Item::Data(unified::Receiver::Orchard(data)) => {
unknown_data.push((unified::Typecode::ORCHARD.into(), data.to_vec()));
}
Item::Data(unified::Receiver::Sapling(data)) => {
sapling = Some(
PaymentAddress::from_bytes(data)
.ok_or("Invalid Sapling receiver in Unified Address")?,
);
}
unified::Receiver::Sapling(data) => PaymentAddress::from_bytes(data)
.ok_or("Invalid Sapling receiver in Unified Address")
.map(|pa| {
sapling = Some(pa);
None
})
.transpose(),
unified::Receiver::P2pkh(data) => {
Item::Data(unified::Receiver::P2pkh(data)) => {
transparent = Some(TransparentAddress::PublicKey(*data));
None
}
unified::Receiver::P2sh(data) => {
Item::Data(unified::Receiver::P2sh(data)) => {
transparent = Some(TransparentAddress::Script(*data));
None
}
unified::Receiver::Unknown { typecode, data } => {
Some(Ok((*typecode, data.clone())))
Item::Data(unified::Receiver::Unknown { typecode, data }) => {
unknown_data.push((*typecode, data.clone()));
}
})
.collect::<Result<_, _>>()?;
Item::Metadata(unified::MetadataItem::ExpiryHeight(h)) => {
expiry_height = Some(BlockHeight::from(*h));
}
Item::Metadata(unified::MetadataItem::ExpiryTime(t)) => {
expiry_time = Some(*t);
}
Item::Metadata(unified::MetadataItem::Unknown { typecode, data }) => {
unknown_metadata.push((*typecode, data.clone()));
}
}
}

Ok(Self {
#[cfg(feature = "orchard")]
orchard,
sapling,
transparent,
unknown,
unknown_data,
expiry_height,
expiry_time,
unknown_metadata,
})
}
}
Expand All @@ -110,10 +121,12 @@ impl UnifiedAddress {
///
/// Returns `None` if the receivers would produce an invalid Unified Address (namely,
/// if no shielded receiver is provided).
pub fn from_receivers(
pub fn new(
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
sapling: Option<PaymentAddress>,
transparent: Option<TransparentAddress>,
expiry_height: Option<BlockHeight>,
expiry_time: Option<u64>,
) -> Option<Self> {
#[cfg(feature = "orchard")]
let has_orchard = orchard.is_some();
Expand All @@ -126,7 +139,10 @@ impl UnifiedAddress {
orchard,
sapling,
transparent,
unknown: vec![],
unknown_data: vec![],
expiry_height,
expiry_time,
unknown_metadata: vec![],
})
} else {
// UAs require at least one shielded receiver.
Expand All @@ -150,9 +166,30 @@ impl UnifiedAddress {
self.transparent.as_ref()
}

/// Returns the set of unknown receivers of the unified address.
pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
&self.unknown
/// Returns any unknown data items parsed from the encoded form of the address.
pub fn unknown_data(&self) -> &[(u32, Vec<u8>)] {
self.unknown_data.as_ref()
}

/// Returns the expiration height for this address.
pub fn expiry_height(&self) -> Option<BlockHeight> {
self.expiry_height
}

/// Returns the expiration time for this address.
pub fn expiry_time(&self) -> Option<u64> {
self.expiry_time
}

/// Returns any unknown metadata items parsed from the encoded form of the address.
pub fn unknown_metadata(&self) -> &[(u32, Vec<u8>)] {
self.unknown_metadata.as_ref()
}

/// Returns the string encoding of this `UnifiedAddress` for the given network.
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
self.to_address(params.address_network().expect("Unrecognized network"))
.to_string()
}

fn to_address(&self, net: Network) -> ZcashAddress {
Expand All @@ -165,8 +202,9 @@ impl UnifiedAddress {
#[cfg(not(feature = "orchard"))]
let orchard_receiver = None;

let ua = unified::Address::try_from_items(
self.unknown
let ua = unified::Address::try_from_items({
let data_items = self
.unknown_data
.iter()
.map(|(typecode, data)| unified::Receiver::Unknown {
typecode: *typecode,
Expand All @@ -183,17 +221,30 @@ impl UnifiedAddress {
.map(unified::Receiver::Sapling),
)
.chain(orchard_receiver)
.collect(),
)
.map(Item::Data);

let meta_items = self
.unknown_metadata
.iter()
.map(|(typecode, data)| unified::MetadataItem::Unknown {
typecode: *typecode,
data: data.clone(),
})
.chain(
self.expiry_height
.map(|h| unified::MetadataItem::ExpiryHeight(u32::from(h))),
)
.chain(
self.expiry_time
.map(|t| unified::MetadataItem::ExpiryTime(t)),
)
.map(Item::Metadata);

data_items.chain(meta_items).collect()
})
.expect("UnifiedAddress should only be constructed safely");
ZcashAddress::from_unified(net, ua)
}

/// Returns the string encoding of this `UnifiedAddress` for the given network.
pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
self.to_address(params.address_network().expect("Unrecognized network"))
.to_string()
}
}

/// An address that funds can be sent to.
Expand Down Expand Up @@ -299,10 +350,10 @@ mod tests {
let transparent = { None };

#[cfg(feature = "orchard")]
let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
let ua = UnifiedAddress::new(orchard, sapling, transparent, None, None).unwrap();

#[cfg(not(feature = "orchard"))]
let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
let ua = UnifiedAddress::new(sapling, transparent, None, None).unwrap();

let addr = Address::Unified(ua);
let addr_str = addr.encode(&MAIN_NETWORK);
Expand Down
Loading

0 comments on commit 65f5784

Please sign in to comment.