zcash_keys: Update key and address types to include ZIP-316 metadata items.

This commit is contained in:
Kris Nuttycombe 2024-01-25 14:12:19 -07:00
parent 0341171c84
commit 34ec1e5bdb
12 changed files with 498 additions and 259 deletions

View File

@ -799,6 +799,7 @@ pub mod testing {
use zcash_protocol::{consensus::NetworkType, value::testing::arb_zatoshis};
use super::{MemoBytes, Payment, TransactionRequest};
pub const VALID_PARAMNAME: &str = "[a-zA-Z][a-zA-Z0-9+-]*";
prop_compose! {

View File

@ -133,7 +133,7 @@ pub(crate) const UA_TRANSPARENT: bool = false;
pub(crate) const UA_TRANSPARENT: bool = true;
pub(crate) const DEFAULT_UA_REQUEST: UnifiedAddressRequest =
UnifiedAddressRequest::unsafe_new(UA_ORCHARD, true, UA_TRANSPARENT);
UnifiedAddressRequest::unsafe_new_without_expiry(UA_ORCHARD, true, UA_TRANSPARENT);
/// The ID type for accounts.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
@ -1157,7 +1157,6 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
Recipient::External(wallet_address, PoolType::Shielded(ShieldedProtocol::Orchard))
};
wallet::put_sent_output(
wdb.conn.0,
*output.account(),

View File

@ -683,7 +683,7 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
conn: &rusqlite::Connection,
account_id: AccountId,
) -> Result<Option<(TransparentAddress, NonHardenedChildIndex)>, SqliteClientError> {
use zcash_address::unified::Container;
use zcash_address::unified::{Container, Item};
use zcash_primitives::legacy::keys::ExternalIvk;
// Get the UIVK for the account.
@ -705,9 +705,9 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
}
// Derive the default transparent address (if it wasn't already part of a derived UA).
for item in uivk.items() {
if let Ivk::P2pkh(tivk_bytes) = item {
let tivk = ExternalIvk::deserialize(&tivk_bytes)?;
for item in uivk.items_as_parsed() {
if let Item::Data(Ivk::P2pkh(tivk_bytes)) = item {
let tivk = ExternalIvk::deserialize(tivk_bytes)?;
return Ok(Some(tivk.default_address()));
}
}

View File

@ -1418,7 +1418,8 @@ mod tests {
// Unified addresses at the time of the addition of migrations did not contain an
// Orchard component.
let ua_request = UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT);
let ua_request =
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT);
let address_str = Address::Unified(
ufvk.default_address(ua_request)
.expect("A valid default address exists for the UFVK")
@ -1545,7 +1546,8 @@ mod tests {
assert_eq!(tv.unified_addr, ua.encode(&Network::MainNetwork));
// hardcoded with knowledge of what's coming next
let ua_request = UnifiedAddressRequest::unsafe_new(false, true, true);
let ua_request =
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, true);
db_data
.get_next_available_address(account_id, ua_request)
.unwrap()

View File

@ -443,12 +443,12 @@ mod tests {
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
let ufvk = usk.to_unified_full_viewing_key();
let (ua, _) = ufvk
.default_address(UnifiedAddressRequest::unsafe_new(
.default_address(UnifiedAddressRequest::unsafe_new_without_expiry(
false,
true,
UA_TRANSPARENT,
))
.expect("A valid default address exists for the UFVK");
.unwrap();
let taddr = ufvk
.transparent()
.and_then(|k| {

View File

@ -86,7 +86,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
));
};
let (expected_address, idx) = ufvk.default_address(
UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT),
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT),
)?;
if decoded_address != expected_address {
return Err(WalletMigrationError::CorruptedData(format!(
@ -157,11 +157,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
],
)?;
let (address, d_idx) = ufvk.default_address(UnifiedAddressRequest::unsafe_new(
false,
true,
UA_TRANSPARENT,
))?;
let (address, d_idx) = ufvk.default_address(
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT),
)?;
insert_address(transaction, &self.params, account, d_idx, &address)?;
}

View File

@ -70,7 +70,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
};
let (default_addr, diversifier_index) = uivk.default_address(
UnifiedAddressRequest::unsafe_new(UA_ORCHARD, true, UA_TRANSPARENT),
UnifiedAddressRequest::unsafe_new_without_expiry(UA_ORCHARD, true, UA_TRANSPARENT),
)?;
let mut di_be = *diversifier_index.as_bytes();
@ -144,7 +144,7 @@ mod tests {
.unwrap();
let (addr, diversifier_index) = ufvk
.default_address(UnifiedAddressRequest::unsafe_new(
.default_address(UnifiedAddressRequest::unsafe_new_without_expiry(
false,
true,
UA_TRANSPARENT,

View File

@ -83,7 +83,8 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
// our second assumption above, and we report this as corrupted data.
let mut seed_is_relevant = false;
let ua_request = UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT);
let ua_request =
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT);
let mut rows = stmt_fetch_accounts.query([])?;
while let Some(row) = rows.next()? {
// We only need to check for the presence of the seed if we have keys that

View File

@ -463,7 +463,6 @@ pub(crate) mod tests {
None,
None,
)
.unwrap()
.into()
}
@ -545,9 +544,7 @@ pub(crate) mod tests {
return Ok(result.map(|(note, addr, memo)| {
(
Note::Orchard(note),
UnifiedAddress::from_receivers(Some(addr), None, None)
.unwrap()
.into(),
UnifiedAddress::from_receivers(Some(addr), None, None).into(),
MemoBytes::from_bytes(&memo).expect("correct length"),
)
}));

View File

@ -12,7 +12,12 @@ and this library adheres to Rust's notion of
## [0.2.0] - 2024-03-25
### Added
- `zcash_keys::address::Address::has_receiver`
- `zcash_keys::address`:
- `Address::has_receiver`
- `UnifiedAddress::{
new, expiry_height, expiry_time,
unknown_data, unknown_metadata
}`
- `impl Display for zcash_keys::keys::AddressGenerationError`
- `impl std::error::Error for zcash_keys::keys::AddressGenerationError`
- `impl From<hdwallet::error::Error> for zcash_keys::keys::DerivationError`
@ -35,6 +40,7 @@ and this library adheres to Rust's notion of
- `UnifiedFullViewingKey::new` has been placed behind the `test-dependencies`
feature flag. UFVKs should only be produced by derivation from the USK, or
parsed from their string representation.
- `zcash_keys::address::UnifiedAddress::from_receivers`
### Fixed
- `UnifiedFullViewingKey::find_address` can now find an address for a diversifier

View File

@ -1,15 +1,17 @@
//! Structs for handling supported address types.
use zcash_address::{
unified::{self, Container, Encoding, Typecode},
unified::{self, Container, DataTypecode, Encoding, Item, Typecode},
ConversionError, ToAddress, TryFromRawAddress, ZcashAddress,
};
use zcash_primitives::legacy::TransparentAddress;
use zcash_protocol::consensus::{self, NetworkType};
use zcash_protocol::{
consensus::{self, BlockHeight, NetworkType},
PoolType, ShieldedProtocol,
};
#[cfg(feature = "sapling")]
use sapling::PaymentAddress;
use zcash_protocol::{PoolType, ShieldedProtocol};
/// A Unified Address.
#[derive(Clone, Debug, PartialEq, Eq)]
@ -19,7 +21,10 @@ pub struct UnifiedAddress {
#[cfg(feature = "sapling")]
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 {
@ -31,14 +36,16 @@ impl TryFrom<unified::Address> for UnifiedAddress {
#[cfg(feature = "sapling")]
let mut sapling = None;
let mut transparent = None;
let mut unknown: Vec<(u32, Vec<u8>)> = vec![];
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.
for item in ua.items_as_parsed() {
match item {
unified::Receiver::Orchard(data) => {
Item::Data(unified::Receiver::Orchard(data)) => {
#[cfg(feature = "orchard")]
{
orchard = Some(
@ -48,11 +55,11 @@ impl TryFrom<unified::Address> for UnifiedAddress {
}
#[cfg(not(feature = "orchard"))]
{
unknown.push((unified::Typecode::Orchard.into(), data.to_vec()));
unknown_data.push((unified::Typecode::ORCHARD.into(), data.to_vec()));
}
}
unified::Receiver::Sapling(data) => {
Item::Data(unified::Receiver::Sapling(data)) => {
#[cfg(feature = "sapling")]
{
sapling = Some(
@ -62,20 +69,26 @@ impl TryFrom<unified::Address> for UnifiedAddress {
}
#[cfg(not(feature = "sapling"))]
{
unknown.push((unified::Typecode::Sapling.into(), data.to_vec()));
unknown_data.push((unified::Typecode::SAPLING.into(), data.to_vec()));
}
}
unified::Receiver::P2pkh(data) => {
Item::Data(unified::Receiver::P2pkh(data)) => {
transparent = Some(TransparentAddress::PublicKeyHash(*data));
}
unified::Receiver::P2sh(data) => {
Item::Data(unified::Receiver::P2sh(data)) => {
transparent = Some(TransparentAddress::ScriptHash(*data));
}
unified::Receiver::Unknown { typecode, data } => {
unknown.push((*typecode, data.clone()));
Item::Data(unified::Receiver::Unknown { typecode, data }) => {
unknown_data.push((*typecode, data.clone()));
}
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()));
}
}
}
@ -86,7 +99,10 @@ impl TryFrom<unified::Address> for UnifiedAddress {
#[cfg(feature = "sapling")]
sapling,
transparent,
unknown,
unknown_data,
expiry_height,
expiry_time,
unknown_metadata,
})
}
}
@ -94,36 +110,43 @@ impl TryFrom<unified::Address> for UnifiedAddress {
impl UnifiedAddress {
/// Constructs a Unified Address from a given set of receivers.
///
/// Returns `None` if the receivers would produce an invalid Unified Address (namely,
/// if no shielded receiver is provided).
/// This method is only available when the `test-dependencies` feature is enabled, as
/// derivation from the UFVK or UIVK, or deserialization from the serialized form should be
/// used instead.
#[cfg(any(test, feature = "test-dependencies"))]
pub fn from_receivers(
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
transparent: Option<TransparentAddress>,
// TODO: Add handling for address metadata items.
) -> Option<Self> {
#[cfg(feature = "orchard")]
let has_orchard = orchard.is_some();
#[cfg(not(feature = "orchard"))]
let has_orchard = false;
) -> Self {
Self::new_internal(
#[cfg(feature = "orchard")]
orchard,
#[cfg(feature = "sapling")]
sapling,
transparent,
None,
None,
)
}
#[cfg(feature = "sapling")]
let has_sapling = sapling.is_some();
#[cfg(not(feature = "sapling"))]
let has_sapling = false;
if has_orchard || has_sapling {
Some(Self {
#[cfg(feature = "orchard")]
orchard,
#[cfg(feature = "sapling")]
sapling,
transparent,
unknown: vec![],
})
} else {
// UAs require at least one shielded receiver.
None
pub(crate) fn new_internal(
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
transparent: Option<TransparentAddress>,
expiry_height: Option<BlockHeight>,
expiry_time: Option<u64>,
) -> Self {
Self {
#[cfg(feature = "orchard")]
orchard,
#[cfg(feature = "sapling")]
sapling,
transparent,
unknown_data: vec![],
expiry_height,
expiry_time,
unknown_metadata: vec![],
}
}
@ -168,22 +191,60 @@ 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
}
/// Sets the expiry height of this address.
pub fn set_expiry_height(&mut self, height: BlockHeight) {
self.expiry_height = Some(height);
}
/// Removes the expiry height from this address.
pub fn unset_expiry_height(&mut self) {
self.expiry_height = None;
}
/// Returns the expiration time for this address as a Unix Epoch Time.
pub fn expiry_time(&self) -> Option<u64> {
self.expiry_time
}
/// Sets the expiry time of this address.
pub fn set_expiry_time(&mut self, time: u64) {
self.expiry_time = Some(time);
}
/// Removes the expiry time from this address.
pub fn unset_expiry_time(&mut self) {
self.expiry_time = None;
}
/// Returns any unknown metadata items parsed from the encoded form of the address.
///
/// Unknown metadata items are guaranteed by construction and parsing to not have keys in the
/// MUST-understand metadata typecode range.
pub fn unknown_metadata(&self) -> &[(u32, Vec<u8>)] {
self.unknown_metadata.as_ref()
}
fn to_address(&self, net: NetworkType) -> ZcashAddress {
let items = self
.unknown
.iter()
.map(|(typecode, data)| unified::Receiver::Unknown {
typecode: *typecode,
data: data.clone(),
});
let data_items =
self.unknown_data
.iter()
.map(|(typecode, data)| unified::Receiver::Unknown {
typecode: *typecode,
data: data.clone(),
});
#[cfg(feature = "orchard")]
let items = items.chain(
let data_items = data_items.chain(
self.orchard
.as_ref()
.map(|addr| addr.to_raw_address_bytes())
@ -191,20 +252,38 @@ impl UnifiedAddress {
);
#[cfg(feature = "sapling")]
let items = items.chain(
let data_items = data_items.chain(
self.sapling
.as_ref()
.map(|pa| pa.to_bytes())
.map(unified::Receiver::Sapling),
);
let items = items.chain(self.transparent.as_ref().map(|taddr| match taddr {
let data_items = data_items.chain(self.transparent.as_ref().map(|taddr| match taddr {
TransparentAddress::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
}));
let ua = unified::Address::try_from_items(items.collect())
.expect("UnifiedAddress should only be constructed safely");
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(unified::MetadataItem::ExpiryTime));
let ua = unified::Address::try_from_items(
data_items
.map(Item::Data)
.chain(meta_items.map(Item::Metadata))
.collect(),
)
.expect("UnifiedAddress should only be constructed safely");
ZcashAddress::from_unified(net, ua)
}
@ -217,17 +296,17 @@ impl UnifiedAddress {
pub fn receiver_types(&self) -> Vec<Typecode> {
let result = std::iter::empty();
#[cfg(feature = "orchard")]
let result = result.chain(self.orchard.map(|_| Typecode::Orchard));
let result = result.chain(self.orchard.map(|_| Typecode::ORCHARD));
#[cfg(feature = "sapling")]
let result = result.chain(self.sapling.map(|_| Typecode::Sapling));
let result = result.chain(self.sapling.map(|_| Typecode::SAPLING));
let result = result.chain(self.transparent.map(|taddr| match taddr {
TransparentAddress::PublicKeyHash(_) => Typecode::P2pkh,
TransparentAddress::ScriptHash(_) => Typecode::P2sh,
TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
TransparentAddress::ScriptHash(_) => Typecode::P2SH,
}));
let result = result.chain(
self.unknown()
self.unknown_data()
.iter()
.map(|(typecode, _)| Typecode::Unknown(*typecode)),
.map(|(typecode, _)| Typecode::Data(DataTypecode::Unknown(*typecode))),
);
result.collect()
}
@ -256,7 +335,8 @@ impl Receiver {
match self {
#[cfg(feature = "orchard")]
Receiver::Orchard(addr) => {
let receiver = unified::Receiver::Orchard(addr.to_raw_address_bytes());
let receiver =
unified::Item::Data(unified::Receiver::Orchard(addr.to_raw_address_bytes()));
let ua = unified::Address::try_from_items(vec![receiver])
.expect("A unified address may contain a single Orchard receiver.");
ZcashAddress::from_unified(net, ua)
@ -440,7 +520,7 @@ pub mod testing {
params: Network,
request: UnifiedAddressRequest,
) -> impl Strategy<Value = UnifiedAddress> {
arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).0)
arb_unified_spending_key(params).prop_map(move |k| k.default_address(request).unwrap().0)
}
#[cfg(feature = "sapling")]
@ -495,13 +575,13 @@ mod tests {
let transparent = None;
#[cfg(all(feature = "orchard", feature = "sapling"))]
let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap();
let ua = UnifiedAddress::new_internal(orchard, sapling, transparent, None, None);
#[cfg(all(not(feature = "orchard"), feature = "sapling"))]
let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap();
let ua = UnifiedAddress::new_internal(sapling, transparent, None, None);
#[cfg(all(feature = "orchard", not(feature = "sapling")))]
let ua = UnifiedAddress::from_receivers(orchard, transparent).unwrap();
let ua = UnifiedAddress::new_internal(orchard, transparent, None, None);
let addr = Address::Unified(ua);
let addr_str = addr.encode(&MAIN_NETWORK);
@ -512,7 +592,7 @@ mod tests {
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
fn ua_round_trip() {
let transparent = None;
assert_eq!(UnifiedAddress::from_receivers(transparent), None)
assert_eq!(UnifiedAddress::new_internal(transparent, None, None), None)
}
#[test]

View File

@ -3,8 +3,8 @@ use std::{
error,
fmt::{self, Display},
};
use zcash_address::unified::{self, Container, Encoding, Typecode, Ufvk, Uivk};
use zcash_address::unified::{self, Container, Encoding, Item, MetadataItem, Typecode};
use zcash_primitives::consensus::BlockHeight;
use zcash_protocol::consensus;
use zip32::{AccountId, DiversifierIndex};
@ -258,7 +258,10 @@ impl UnifiedSpendingKey {
sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
#[cfg(feature = "orchard")]
orchard: Some((&self.orchard).into()),
unknown: vec![],
unknown_data: vec![],
expiry_height: None,
expiry_time: None,
unknown_metadata: vec![],
}
}
@ -298,7 +301,7 @@ impl UnifiedSpendingKey {
#[cfg(feature = "orchard")]
{
let orchard_key = self.orchard();
CompactSize::write(&mut result, usize::try_from(Typecode::Orchard).unwrap()).unwrap();
CompactSize::write(&mut result, usize::try_from(Typecode::ORCHARD).unwrap()).unwrap();
let orchard_key_bytes = orchard_key.to_bytes();
CompactSize::write(&mut result, orchard_key_bytes.len()).unwrap();
@ -308,7 +311,7 @@ impl UnifiedSpendingKey {
#[cfg(feature = "sapling")]
{
let sapling_key = self.sapling();
CompactSize::write(&mut result, usize::try_from(Typecode::Sapling).unwrap()).unwrap();
CompactSize::write(&mut result, usize::try_from(Typecode::SAPLING).unwrap()).unwrap();
let sapling_key_bytes = sapling_key.to_bytes();
CompactSize::write(&mut result, sapling_key_bytes.len()).unwrap();
@ -318,7 +321,7 @@ impl UnifiedSpendingKey {
#[cfg(feature = "transparent-inputs")]
{
let account_tkey = self.transparent();
CompactSize::write(&mut result, usize::try_from(Typecode::P2pkh).unwrap()).unwrap();
CompactSize::write(&mut result, usize::try_from(Typecode::P2PKH).unwrap()).unwrap();
let account_tkey_bytes = account_tkey.to_bytes();
CompactSize::write(&mut result, account_tkey_bytes.len()).unwrap();
@ -334,6 +337,8 @@ impl UnifiedSpendingKey {
#[allow(clippy::unnecessary_unwrap)]
#[cfg(feature = "unstable")]
pub fn from_bytes(era: Era, encoded: &[u8]) -> Result<Self, DecodingError> {
use zcash_address::unified::DataTypecode;
let mut source = std::io::Cursor::new(encoded);
let decoded_era = source
.read_u32::<LittleEndian>()
@ -353,21 +358,23 @@ impl UnifiedSpendingKey {
loop {
let tc = CompactSize::read_t::<_, u32>(&mut source)
.map_err(|_| DecodingError::ReadError("typecode"))
.and_then(|v| Typecode::try_from(v).map_err(|_| DecodingError::TypecodeInvalid))?;
.and_then(|v| {
DataTypecode::try_from(v).map_err(|_| DecodingError::TypecodeInvalid)
})?;
let len = CompactSize::read_t::<_, u32>(&mut source)
.map_err(|_| DecodingError::ReadError("key length"))?;
match tc {
Typecode::Orchard => {
DataTypecode::Orchard => {
if len != 32 {
return Err(DecodingError::LengthMismatch(Typecode::Orchard, len));
return Err(DecodingError::LengthMismatch(Typecode::ORCHARD, len));
}
let mut key = [0u8; 32];
source
.read_exact(&mut key)
.map_err(|_| DecodingError::InsufficientData(Typecode::Orchard))?;
.map_err(|_| DecodingError::InsufficientData(Typecode::ORCHARD))?;
#[cfg(feature = "orchard")]
{
@ -375,43 +382,43 @@ impl UnifiedSpendingKey {
Option::<orchard::keys::SpendingKey>::from(
orchard::keys::SpendingKey::from_bytes(key),
)
.ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
.ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
);
}
}
Typecode::Sapling => {
DataTypecode::Sapling => {
if len != 169 {
return Err(DecodingError::LengthMismatch(Typecode::Sapling, len));
return Err(DecodingError::LengthMismatch(Typecode::SAPLING, len));
}
let mut key = [0u8; 169];
source
.read_exact(&mut key)
.map_err(|_| DecodingError::InsufficientData(Typecode::Sapling))?;
.map_err(|_| DecodingError::InsufficientData(Typecode::SAPLING))?;
#[cfg(feature = "sapling")]
{
sapling = Some(
sapling::ExtendedSpendingKey::from_bytes(&key)
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::Sapling))?,
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
);
}
}
Typecode::P2pkh => {
DataTypecode::P2pkh => {
if len != 64 {
return Err(DecodingError::LengthMismatch(Typecode::P2pkh, len));
return Err(DecodingError::LengthMismatch(Typecode::P2PKH, len));
}
let mut key = [0u8; 64];
source
.read_exact(&mut key)
.map_err(|_| DecodingError::InsufficientData(Typecode::P2pkh))?;
.map_err(|_| DecodingError::InsufficientData(Typecode::P2PKH))?;
#[cfg(feature = "transparent-inputs")]
{
transparent = Some(
legacy::AccountPrivKey::from_bytes(&key)
.ok_or(DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
.ok_or(DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
);
}
}
@ -444,7 +451,7 @@ impl UnifiedSpendingKey {
#[cfg(feature = "orchard")]
orchard.unwrap(),
)
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh));
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH));
}
}
}
@ -453,10 +460,8 @@ impl UnifiedSpendingKey {
pub fn default_address(
&self,
request: UnifiedAddressRequest,
) -> (UnifiedAddress, DiversifierIndex) {
self.to_unified_full_viewing_key()
.default_address(request)
.unwrap()
) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
self.to_unified_full_viewing_key().default_address(request)
}
#[cfg(all(
@ -549,22 +554,28 @@ pub struct UnifiedAddressRequest {
has_orchard: bool,
has_sapling: bool,
has_p2pkh: bool,
expiry_height: Option<BlockHeight>,
expiry_time: Option<u64>,
}
impl UnifiedAddressRequest {
/// Construct a new unified address request from its constituent parts.
///
/// Returns `None` if the resulting unified address would not include at least one shielded receiver.
pub fn new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Option<Self> {
let has_shielded_receiver = has_orchard || has_sapling;
if !has_shielded_receiver {
/// Construct a new unified address request from its constituent parts
pub fn new(
has_orchard: bool,
has_sapling: bool,
has_p2pkh: bool,
expiry_height: Option<BlockHeight>,
expiry_time: Option<u64>,
) -> Option<Self> {
if !(has_sapling || has_orchard || has_p2pkh) {
None
} else {
Some(Self {
has_orchard,
has_sapling,
has_p2pkh,
expiry_height,
expiry_time,
})
}
}
@ -584,21 +595,27 @@ impl UnifiedAddressRequest {
#[cfg(feature = "transparent-inputs")]
let _has_p2pkh = true;
Self::new(_has_orchard, _has_sapling, _has_p2pkh)
Self::new(_has_orchard, _has_sapling, _has_p2pkh, None, None)
}
/// Construct a new unified address request from its constituent parts.
///
/// Panics: at least one of `has_orchard` or `has_sapling` must be `true`.
pub const fn unsafe_new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Self {
if !(has_orchard || has_sapling) {
panic!("At least one shielded receiver must be requested.")
/// Panics: at least one of `has_orchard`, `has_sapling`, or `has_p2pkh` must be `true`.
pub const fn unsafe_new_without_expiry(
has_orchard: bool,
has_sapling: bool,
has_p2pkh: bool,
) -> Self {
if !(has_orchard || has_sapling || has_p2pkh) {
panic!("At least one receiver must be requested.")
}
Self {
has_orchard,
has_sapling,
has_p2pkh,
expiry_height: None,
expiry_time: None,
}
}
}
@ -619,7 +636,10 @@ pub struct UnifiedFullViewingKey {
sapling: Option<sapling::DiversifiableFullViewingKey>,
#[cfg(feature = "orchard")]
orchard: Option<orchard::keys::FullViewingKey>,
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 UnifiedFullViewingKey {
@ -645,6 +665,9 @@ impl UnifiedFullViewingKey {
// We don't currently allow constructing new UFVKs with unknown items, but we store
// this to allow parsing such UFVKs.
vec![],
None,
None,
vec![],
)
}
@ -654,7 +677,10 @@ impl UnifiedFullViewingKey {
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
#[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
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>)>,
) -> Result<UnifiedFullViewingKey, DerivationError> {
// Verify that IVK derivation succeeds; we don't want to construct a UFVK
// that can't derive transparent addresses.
@ -671,7 +697,10 @@ impl UnifiedFullViewingKey {
sapling,
#[cfg(feature = "orchard")]
orchard,
unknown,
unknown_data,
expiry_height,
expiry_time,
unknown_metadata,
})
}
@ -679,7 +708,8 @@ impl UnifiedFullViewingKey {
///
/// [ZIP 316]: https://zips.z.cash/zip-0316
pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
let (net, ufvk) = unified::Ufvk::decode(encoding).map_err(|e| e.to_string())?;
let (net, ufvk) =
zcash_address::unified::Ufvk::decode(encoding).map_err(|e| e.to_string())?;
let expected_net = params.network_type();
if net != expected_net {
return Err(format!(
@ -694,64 +724,71 @@ impl UnifiedFullViewingKey {
/// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
///
/// [ZIP 316]: https://zips.z.cash/zip-0316
pub fn parse(ufvk: &Ufvk) -> Result<Self, DecodingError> {
pub fn parse(ufvk: &zcash_address::unified::Ufvk) -> Result<Self, DecodingError> {
#[cfg(feature = "orchard")]
let mut orchard = None;
#[cfg(feature = "sapling")]
let mut sapling = None;
#[cfg(feature = "transparent-inputs")]
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 = ufvk
.items_as_parsed()
.iter()
.filter_map(|receiver| match receiver {
#[cfg(feature = "orchard")]
unified::Fvk::Orchard(data) => orchard::keys::FullViewingKey::from_bytes(data)
.ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))
.map(|addr| {
orchard = Some(addr);
None
})
.transpose(),
#[cfg(not(feature = "orchard"))]
unified::Fvk::Orchard(data) => Some(Ok::<_, DecodingError>((
u32::from(unified::Typecode::Orchard),
data.to_vec(),
))),
#[cfg(feature = "sapling")]
unified::Fvk::Sapling(data) => {
sapling::DiversifiableFullViewingKey::from_bytes(data)
.ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))
.map(|pa| {
sapling = Some(pa);
None
})
.transpose()
for item in ufvk.items_as_parsed() {
match item {
Item::Data(unified::Fvk::Orchard(data)) => {
#[cfg(feature = "orchard")]
{
orchard = Some(
orchard::keys::FullViewingKey::from_bytes(data)
.ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
);
}
#[cfg(not(feature = "orchard"))]
unknown_data.push((unified::DataTypecode::Orchard.into(), data.to_vec()));
}
#[cfg(not(feature = "sapling"))]
unified::Fvk::Sapling(data) => Some(Ok::<_, DecodingError>((
u32::from(unified::Typecode::Sapling),
data.to_vec(),
))),
#[cfg(feature = "transparent-inputs")]
unified::Fvk::P2pkh(data) => legacy::AccountPubKey::deserialize(data)
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
.map(|tfvk| {
transparent = Some(tfvk);
None
})
.transpose(),
#[cfg(not(feature = "transparent-inputs"))]
unified::Fvk::P2pkh(data) => Some(Ok::<_, DecodingError>((
u32::from(unified::Typecode::P2pkh),
data.to_vec(),
))),
unified::Fvk::Unknown { typecode, data } => Some(Ok((*typecode, data.clone()))),
})
.collect::<Result<_, _>>()?;
Item::Data(unified::Fvk::Sapling(data)) => {
#[cfg(feature = "sapling")]
{
sapling = Some(
sapling::DiversifiableFullViewingKey::from_bytes(data)
.ok_or(DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
);
}
#[cfg(not(feature = "sapling"))]
unknown_data.push((unified::Typecode::SAPLING.into(), data.to_vec()));
}
Item::Data(unified::Fvk::P2pkh(data)) => {
#[cfg(feature = "transparent-inputs")]
{
transparent = Some(
legacy::AccountPubKey::deserialize(data)
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
);
}
#[cfg(not(feature = "transparent-inputs"))]
unknown_data.push((unified::DataTypecode::P2pkh.into(), data.to_vec()));
}
Item::Data(unified::Fvk::Unknown { typecode, data }) => {
unknown_data.push((*typecode, data.clone()));
}
Item::Metadata(MetadataItem::ExpiryHeight(h)) => {
expiry_height = Some(BlockHeight::from(*h));
}
Item::Metadata(MetadataItem::ExpiryTime(t)) => {
expiry_time = Some(*t);
}
Item::Metadata(MetadataItem::Unknown { typecode, data }) => {
unknown_metadata.push((*typecode, data.clone()));
}
}
}
Self::from_checked_parts(
#[cfg(feature = "transparent-inputs")]
@ -760,9 +797,12 @@ impl UnifiedFullViewingKey {
sapling,
#[cfg(feature = "orchard")]
orchard,
unknown,
unknown_data,
expiry_height,
expiry_time,
unknown_metadata,
)
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH))
}
/// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
@ -771,37 +811,56 @@ impl UnifiedFullViewingKey {
}
/// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
fn to_ufvk(&self) -> Ufvk {
let items = std::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
unified::Fvk::Unknown {
typecode: *typecode,
data: data.clone(),
}
}));
fn to_ufvk(&self) -> zcash_address::unified::Ufvk {
let data_items =
std::iter::empty().chain(self.unknown_data.iter().map(|(typecode, data)| {
unified::Fvk::Unknown {
typecode: *typecode,
data: data.clone(),
}
}));
#[cfg(feature = "orchard")]
let items = items.chain(
let data_items = data_items.chain(
self.orchard
.as_ref()
.map(|fvk| fvk.to_bytes())
.map(unified::Fvk::Orchard),
);
#[cfg(feature = "sapling")]
let items = items.chain(
let data_items = data_items.chain(
self.sapling
.as_ref()
.map(|dfvk| dfvk.to_bytes())
.map(unified::Fvk::Sapling),
);
#[cfg(feature = "transparent-inputs")]
let items = items.chain(
let data_items = data_items.chain(
self.transparent
.as_ref()
.map(|tfvk| tfvk.serialize().try_into().unwrap())
.map(unified::Fvk::P2pkh),
);
unified::Ufvk::try_from_items(items.collect())
.expect("UnifiedFullViewingKey should only be constructed safely")
let meta_items = std::iter::empty()
.chain(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(unified::MetadataItem::ExpiryTime));
zcash_address::unified::Ufvk::try_from_items(
data_items
.map(Item::Data)
.chain(meta_items.map(Item::Metadata))
.collect(),
)
.expect("UnifiedFullViewingKey should only be constructed safely")
}
/// Derives a Unified Incoming Viewing Key from this Unified Full Viewing Key.
@ -816,7 +875,11 @@ impl UnifiedFullViewingKey {
sapling: self.sapling.as_ref().map(|s| s.to_external_ivk()),
#[cfg(feature = "orchard")]
orchard: self.orchard.as_ref().map(|o| o.to_ivk(Scope::External)),
unknown: Vec::new(),
expiry_height: self.expiry_height,
expiry_time: self.expiry_time,
// We cannot translate unknown data or metadata items, as they may not be relevant to the IVK
unknown_data: vec![],
unknown_metadata: vec![],
}
}
@ -839,6 +902,26 @@ impl UnifiedFullViewingKey {
self.orchard.as_ref()
}
/// Returns any unknown data items parsed from the encoded form of the key.
pub fn unknown_data(&self) -> &[(u32, Vec<u8>)] {
self.unknown_data.as_ref()
}
/// Returns the expiration height for this key.
pub fn expiry_height(&self) -> Option<BlockHeight> {
self.expiry_height
}
/// Returns the expiration time for this key.
pub fn expiry_time(&self) -> Option<u64> {
self.expiry_time
}
/// Returns any unknown metadata items parsed from the encoded form of the key.
pub fn unknown_metadata(&self) -> &[(u32, Vec<u8>)] {
self.unknown_metadata.as_ref()
}
/// Attempts to derive the Unified Address for the given diversifier index and
/// receiver types.
///
@ -857,10 +940,9 @@ impl UnifiedFullViewingKey {
///
/// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
/// required to satisfy the unified address request are not properly enabled.
#[allow(unused_mut)]
pub fn find_address(
&self,
mut j: DiversifierIndex,
j: DiversifierIndex,
request: UnifiedAddressRequest,
) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
self.to_unified_incoming_viewing_key()
@ -889,8 +971,10 @@ pub struct UnifiedIncomingViewingKey {
sapling: Option<::sapling::zip32::IncomingViewingKey>,
#[cfg(feature = "orchard")]
orchard: Option<orchard::keys::IncomingViewingKey>,
/// Stores the unrecognized elements of the unified encoding.
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 UnifiedIncomingViewingKey {
@ -906,7 +990,10 @@ impl UnifiedIncomingViewingKey {
>,
#[cfg(feature = "sapling")] sapling: Option<::sapling::zip32::IncomingViewingKey>,
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::IncomingViewingKey>,
// TODO: Implement construction of UIVKs with metadata items.
unknown_data: Vec<(u32, Vec<u8>)>,
expiry_height: Option<BlockHeight>,
expiry_time: Option<u64>,
unknown_metadata: Vec<(u32, Vec<u8>)>,
) -> UnifiedIncomingViewingKey {
UnifiedIncomingViewingKey {
#[cfg(feature = "transparent-inputs")]
@ -917,7 +1004,10 @@ impl UnifiedIncomingViewingKey {
orchard,
// We don't allow constructing new UFVKs with unknown items, but we store
// this to allow parsing such UFVKs.
unknown: vec![],
unknown_data,
expiry_height,
expiry_time,
unknown_metadata,
}
}
@ -925,7 +1015,7 @@ impl UnifiedIncomingViewingKey {
///
/// [ZIP 316]: https://zips.z.cash/zip-0316
pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
let (net, ufvk) = unified::Uivk::decode(encoding).map_err(|e| e.to_string())?;
let (net, uivk) = unified::Uivk::decode(encoding).map_err(|e| e.to_string())?;
let expected_net = params.network_type();
if net != expected_net {
return Err(format!(
@ -934,62 +1024,73 @@ impl UnifiedIncomingViewingKey {
));
}
Self::parse(&ufvk).map_err(|e| e.to_string())
Self::parse(&uivk).map_err(|e| e.to_string())
}
/// Constructs a unified incoming viewing key from a parsed unified encoding.
fn parse(uivk: &Uivk) -> Result<Self, DecodingError> {
fn parse(uivk: &zcash_address::unified::Uivk) -> Result<Self, DecodingError> {
#[cfg(feature = "orchard")]
let mut orchard = None;
#[cfg(feature = "sapling")]
let mut sapling = None;
#[cfg(feature = "transparent-inputs")]
let mut transparent = None;
let mut unknown = vec![];
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.
for receiver in uivk.items_as_parsed() {
match receiver {
unified::Ivk::Orchard(data) => {
Item::Data(unified::Ivk::Orchard(data)) => {
#[cfg(feature = "orchard")]
{
orchard = Some(
Option::from(orchard::keys::IncomingViewingKey::from_bytes(data))
.ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
.ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
);
}
#[cfg(not(feature = "orchard"))]
unknown.push((u32::from(unified::Typecode::Orchard), data.to_vec()));
unknown_data.push((u32::from(unified::Typecode::ORCHARD), data.to_vec()));
}
unified::Ivk::Sapling(data) => {
Item::Data(unified::Ivk::Sapling(data)) => {
#[cfg(feature = "sapling")]
{
sapling = Some(
Option::from(::sapling::zip32::IncomingViewingKey::from_bytes(data))
.ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))?,
.ok_or(DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
);
}
#[cfg(not(feature = "sapling"))]
unknown.push((u32::from(unified::Typecode::Sapling), data.to_vec()));
unknown_data.push((u32::from(unified::Typecode::SAPLING), data.to_vec()));
}
unified::Ivk::P2pkh(data) => {
Item::Data(unified::Ivk::P2pkh(data)) => {
#[cfg(feature = "transparent-inputs")]
{
transparent = Some(
legacy::ExternalIvk::deserialize(data)
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
);
}
#[cfg(not(feature = "transparent-inputs"))]
unknown.push((u32::from(unified::Typecode::P2pkh), data.to_vec()));
unknown_data.push((u32::from(unified::Typecode::P2PKH), data.to_vec()));
}
unified::Ivk::Unknown { typecode, data } => {
unknown.push((*typecode, data.clone()));
Item::Data(unified::Ivk::Unknown { typecode, data }) => {
unknown_data.push((*typecode, data.clone()));
}
Item::Metadata(MetadataItem::ExpiryHeight(h)) => {
expiry_height = Some(BlockHeight::from(*h));
}
Item::Metadata(MetadataItem::ExpiryTime(t)) => {
expiry_time = Some(*t);
}
Item::Metadata(MetadataItem::Unknown { typecode, data }) => {
unknown_metadata.push((*typecode, data.clone()));
}
}
}
@ -1001,7 +1102,10 @@ impl UnifiedIncomingViewingKey {
sapling,
#[cfg(feature = "orchard")]
orchard,
unknown,
unknown_data,
expiry_height,
expiry_time,
unknown_metadata,
})
}
@ -1011,37 +1115,56 @@ impl UnifiedIncomingViewingKey {
}
/// Converts this unified incoming viewing key to a unified encoding.
fn render(&self) -> Uivk {
let items = std::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
unified::Ivk::Unknown {
typecode: *typecode,
data: data.clone(),
}
}));
fn render(&self) -> zcash_address::unified::Uivk {
let data_items =
std::iter::empty().chain(self.unknown_data.iter().map(|(typecode, data)| {
unified::Ivk::Unknown {
typecode: *typecode,
data: data.clone(),
}
}));
#[cfg(feature = "orchard")]
let items = items.chain(
let data_items = data_items.chain(
self.orchard
.as_ref()
.map(|ivk| ivk.to_bytes())
.map(unified::Ivk::Orchard),
);
#[cfg(feature = "sapling")]
let items = items.chain(
let data_items = data_items.chain(
self.sapling
.as_ref()
.map(|divk| divk.to_bytes())
.map(unified::Ivk::Sapling),
);
#[cfg(feature = "transparent-inputs")]
let items = items.chain(
let data_items = data_items.chain(
self.transparent
.as_ref()
.map(|tivk| tivk.serialize().try_into().unwrap())
.map(unified::Ivk::P2pkh),
);
unified::Uivk::try_from_items(items.collect())
.expect("UnifiedIncomingViewingKey should only be constructed safely.")
let meta_items = std::iter::empty()
.chain(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(unified::MetadataItem::ExpiryTime));
zcash_address::unified::Uivk::try_from_items(
data_items
.map(Item::Data)
.chain(meta_items.map(Item::Metadata))
.collect(),
)
.expect("UnifiedIncomingViewingKey should only be constructed safely.")
}
/// Returns the Transparent external IVK, if present.
@ -1062,6 +1185,26 @@ impl UnifiedIncomingViewingKey {
&self.orchard
}
/// Returns any unknown data items parsed from the encoded form of the key.
pub fn unknown_data(&self) -> &[(u32, Vec<u8>)] {
self.unknown_data.as_ref()
}
/// Returns the expiration height for this key.
pub fn expiry_height(&self) -> Option<BlockHeight> {
self.expiry_height
}
/// Returns the expiration time for this key.
pub fn expiry_time(&self) -> Option<u64> {
self.expiry_time
}
/// Returns any unknown metadata items parsed from the encoded form of the key.
pub fn unknown_metadata(&self) -> &[(u32, Vec<u8>)] {
self.unknown_metadata.as_ref()
}
/// Attempts to derive the Unified Address for the given diversifier index and
/// receiver types.
///
@ -1076,7 +1219,7 @@ impl UnifiedIncomingViewingKey {
if request.has_orchard {
#[cfg(not(feature = "orchard"))]
return Err(AddressGenerationError::ReceiverTypeNotSupported(
Typecode::Orchard,
Typecode::ORCHARD,
));
#[cfg(feature = "orchard")]
@ -1084,7 +1227,7 @@ impl UnifiedIncomingViewingKey {
let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
orchard = Some(oivk.address_at(orchard_j))
} else {
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
return Err(AddressGenerationError::KeyNotAvailable(Typecode::ORCHARD));
}
}
@ -1093,7 +1236,7 @@ impl UnifiedIncomingViewingKey {
if request.has_sapling {
#[cfg(not(feature = "sapling"))]
return Err(AddressGenerationError::ReceiverTypeNotSupported(
Typecode::Sapling,
Typecode::SAPLING,
));
#[cfg(feature = "sapling")]
@ -1106,7 +1249,7 @@ impl UnifiedIncomingViewingKey {
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))?,
);
} else {
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
return Err(AddressGenerationError::KeyNotAvailable(Typecode::SAPLING));
}
}
@ -1115,7 +1258,7 @@ impl UnifiedIncomingViewingKey {
if request.has_p2pkh {
#[cfg(not(feature = "transparent-inputs"))]
return Err(AddressGenerationError::ReceiverTypeNotSupported(
Typecode::P2pkh,
Typecode::P2PKH,
));
#[cfg(feature = "transparent-inputs")]
@ -1131,20 +1274,21 @@ impl UnifiedIncomingViewingKey {
.map_err(|_| AddressGenerationError::InvalidTransparentChildIndex(_j))?,
);
} else {
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2PKH));
}
}
#[cfg(not(feature = "transparent-inputs"))]
let transparent = None;
UnifiedAddress::from_receivers(
Ok(UnifiedAddress::new_internal(
#[cfg(feature = "orchard")]
orchard,
#[cfg(feature = "sapling")]
sapling,
transparent,
)
.ok_or(AddressGenerationError::ShieldedReceiverRequired)
std::cmp::min(self.expiry_height, request.expiry_height),
std::cmp::min(self.expiry_time, request.expiry_time),
))
}
/// Searches the diversifier space starting at diversifier index `j` for one which will
@ -1153,7 +1297,6 @@ impl UnifiedIncomingViewingKey {
///
/// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
/// required to satisfy the unified address request are not properly enabled.
#[allow(unused_mut)]
pub fn find_address(
&self,
mut j: DiversifierIndex,
@ -1181,6 +1324,8 @@ impl UnifiedIncomingViewingKey {
Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
if j.increment().is_err() {
return Err(AddressGenerationError::DiversifierSpaceExhausted);
} else {
continue;
}
}
Err(other) => {
@ -1236,7 +1381,7 @@ mod tests {
#[cfg(any(feature = "sapling", feature = "orchard"))]
use {
super::{UnifiedFullViewingKey, UnifiedIncomingViewingKey},
zcash_address::unified::{Encoding, Uivk},
zcash_address::unified::Encoding,
};
#[cfg(feature = "orchard")]
@ -1372,13 +1517,13 @@ mod tests {
feature = "sapling",
feature = "transparent-inputs"
))]
assert_eq!(decoded_with_t.unknown.len(), 0);
assert_eq!(decoded_with_t.unknown_data.len(), 0);
#[cfg(all(
feature = "orchard",
feature = "sapling",
not(feature = "transparent-inputs")
))]
assert_eq!(decoded_with_t.unknown.len(), 1);
assert_eq!(decoded_with_t.unknown_data.len(), 1);
// Orchard enabled
#[cfg(all(
@ -1386,13 +1531,13 @@ mod tests {
not(feature = "sapling"),
feature = "transparent-inputs"
))]
assert_eq!(decoded_with_t.unknown.len(), 1);
assert_eq!(decoded_with_t.unknown_data.len(), 1);
#[cfg(all(
feature = "orchard",
not(feature = "sapling"),
not(feature = "transparent-inputs")
))]
assert_eq!(decoded_with_t.unknown.len(), 2);
assert_eq!(decoded_with_t.unknown_data.len(), 2);
// Sapling enabled
#[cfg(all(
@ -1400,13 +1545,13 @@ mod tests {
feature = "sapling",
feature = "transparent-inputs"
))]
assert_eq!(decoded_with_t.unknown.len(), 1);
assert_eq!(decoded_with_t.unknown_data.len(), 1);
#[cfg(all(
not(feature = "orchard"),
feature = "sapling",
not(feature = "transparent-inputs")
))]
assert_eq!(decoded_with_t.unknown.len(), 2);
assert_eq!(decoded_with_t.unknown_data.len(), 2);
}
#[test]
@ -1435,7 +1580,10 @@ mod tests {
}
let ua = ufvk
.address(d_idx, UnifiedAddressRequest::unsafe_new(false, true, true))
.address(
d_idx,
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, true),
)
.unwrap_or_else(|err| {
panic!(
"unified address generation failed for account {}: {:?}",
@ -1497,6 +1645,10 @@ mod tests {
sapling,
#[cfg(feature = "orchard")]
orchard,
vec![],
None,
None,
vec![],
);
let encoded = uivk.render().encode(&NetworkType::Main);
@ -1517,7 +1669,7 @@ mod tests {
assert_eq!(encoded, _encoded_no_t);
}
let decoded = UnifiedIncomingViewingKey::parse(&Uivk::decode(&encoded).unwrap().1).unwrap();
let decoded = UnifiedIncomingViewingKey::decode(&MAIN_NETWORK, &encoded).unwrap();
let reencoded = decoded.render().encode(&NetworkType::Main);
assert_eq!(encoded, reencoded);
@ -1538,7 +1690,7 @@ mod tests {
);
let decoded_with_t =
UnifiedIncomingViewingKey::parse(&Uivk::decode(encoded_with_t).unwrap().1).unwrap();
UnifiedIncomingViewingKey::decode(&MAIN_NETWORK, encoded_with_t).unwrap();
#[cfg(feature = "transparent-inputs")]
assert_eq!(
decoded_with_t.transparent.map(|t| t.serialize()),
@ -1551,13 +1703,13 @@ mod tests {
feature = "sapling",
feature = "transparent-inputs"
))]
assert_eq!(decoded_with_t.unknown.len(), 0);
assert_eq!(decoded_with_t.unknown_data.len(), 0);
#[cfg(all(
feature = "orchard",
feature = "sapling",
not(feature = "transparent-inputs")
))]
assert_eq!(decoded_with_t.unknown.len(), 1);
assert_eq!(decoded_with_t.unknown_data.len(), 1);
// Orchard enabled
#[cfg(all(
@ -1565,13 +1717,13 @@ mod tests {
not(feature = "sapling"),
feature = "transparent-inputs"
))]
assert_eq!(decoded_with_t.unknown.len(), 1);
assert_eq!(decoded_with_t.unknown_data.len(), 1);
#[cfg(all(
feature = "orchard",
not(feature = "sapling"),
not(feature = "transparent-inputs")
))]
assert_eq!(decoded_with_t.unknown.len(), 2);
assert_eq!(decoded_with_t.unknown_data.len(), 2);
// Sapling enabled
#[cfg(all(
@ -1579,13 +1731,13 @@ mod tests {
feature = "sapling",
feature = "transparent-inputs"
))]
assert_eq!(decoded_with_t.unknown.len(), 1);
assert_eq!(decoded_with_t.unknown_data.len(), 1);
#[cfg(all(
not(feature = "orchard"),
feature = "sapling",
not(feature = "transparent-inputs")
))]
assert_eq!(decoded_with_t.unknown.len(), 2);
assert_eq!(decoded_with_t.unknown_data.len(), 2);
}
#[test]
@ -1616,7 +1768,10 @@ mod tests {
}
let ua = uivk
.address(d_idx, UnifiedAddressRequest::unsafe_new(false, true, true))
.address(
d_idx,
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, true),
)
.unwrap_or_else(|err| {
panic!(
"unified address generation failed for account {}: {:?}",