zcash_keys: Update key and address types to include ZIP-316 metadata items.
This commit is contained in:
parent
0341171c84
commit
34ec1e5bdb
|
@ -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! {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
#[cfg(feature = "sapling")]
|
||||
let has_sapling = sapling.is_some();
|
||||
#[cfg(not(feature = "sapling"))]
|
||||
let has_sapling = false;
|
||||
|
||||
if has_orchard || has_sapling {
|
||||
Some(Self {
|
||||
) -> Self {
|
||||
Self::new_internal(
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard,
|
||||
#[cfg(feature = "sapling")]
|
||||
sapling,
|
||||
transparent,
|
||||
unknown: vec![],
|
||||
})
|
||||
} else {
|
||||
// UAs require at least one shielded receiver.
|
||||
None
|
||||
None,
|
||||
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,14 +191,52 @@ 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
|
||||
let data_items =
|
||||
self.unknown_data
|
||||
.iter()
|
||||
.map(|(typecode, data)| unified::Receiver::Unknown {
|
||||
typecode: *typecode,
|
||||
|
@ -183,7 +244,7 @@ impl UnifiedAddress {
|
|||
});
|
||||
|
||||
#[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,19 +252,37 @@ 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())
|
||||
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]
|
||||
|
|
|
@ -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 {
|
||||
for item in ufvk.items_as_parsed() {
|
||||
match item {
|
||||
Item::Data(unified::Fvk::Orchard(data)) => {
|
||||
#[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(),
|
||||
{
|
||||
orchard = Some(
|
||||
orchard::keys::FullViewingKey::from_bytes(data)
|
||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
unified::Fvk::Orchard(data) => Some(Ok::<_, DecodingError>((
|
||||
u32::from(unified::Typecode::Orchard),
|
||||
data.to_vec(),
|
||||
))),
|
||||
unknown_data.push((unified::DataTypecode::Orchard.into(), data.to_vec()));
|
||||
}
|
||||
Item::Data(unified::Fvk::Sapling(data)) => {
|
||||
#[cfg(feature = "sapling")]
|
||||
unified::Fvk::Sapling(data) => {
|
||||
{
|
||||
sapling = Some(
|
||||
sapling::DiversifiableFullViewingKey::from_bytes(data)
|
||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))
|
||||
.map(|pa| {
|
||||
sapling = Some(pa);
|
||||
None
|
||||
})
|
||||
.transpose()
|
||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "sapling"))]
|
||||
unified::Fvk::Sapling(data) => Some(Ok::<_, DecodingError>((
|
||||
u32::from(unified::Typecode::Sapling),
|
||||
data.to_vec(),
|
||||
))),
|
||||
unknown_data.push((unified::Typecode::SAPLING.into(), data.to_vec()));
|
||||
}
|
||||
Item::Data(unified::Fvk::P2pkh(data)) => {
|
||||
#[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(),
|
||||
{
|
||||
transparent = Some(
|
||||
legacy::AccountPubKey::deserialize(data)
|
||||
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
|
||||
);
|
||||
}
|
||||
|
||||
#[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<_, _>>()?;
|
||||
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,36 +811,55 @@ 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)| {
|
||||
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())
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -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,36 +1115,55 @@ 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)| {
|
||||
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())
|
||||
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.")
|
||||
}
|
||||
|
||||
|
@ -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 {}: {:?}",
|
||||
|
|
Loading…
Reference in New Issue