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 zcash_protocol::{consensus::NetworkType, value::testing::arb_zatoshis};
|
||||||
|
|
||||||
use super::{MemoBytes, Payment, TransactionRequest};
|
use super::{MemoBytes, Payment, TransactionRequest};
|
||||||
|
|
||||||
pub const VALID_PARAMNAME: &str = "[a-zA-Z][a-zA-Z0-9+-]*";
|
pub const VALID_PARAMNAME: &str = "[a-zA-Z][a-zA-Z0-9+-]*";
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
|
|
|
@ -133,7 +133,7 @@ pub(crate) const UA_TRANSPARENT: bool = false;
|
||||||
pub(crate) const UA_TRANSPARENT: bool = true;
|
pub(crate) const UA_TRANSPARENT: bool = true;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_UA_REQUEST: UnifiedAddressRequest =
|
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.
|
/// The ID type for accounts.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
#[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))
|
Recipient::External(wallet_address, PoolType::Shielded(ShieldedProtocol::Orchard))
|
||||||
};
|
};
|
||||||
|
|
||||||
wallet::put_sent_output(
|
wallet::put_sent_output(
|
||||||
wdb.conn.0,
|
wdb.conn.0,
|
||||||
*output.account(),
|
*output.account(),
|
||||||
|
|
|
@ -683,7 +683,7 @@ pub(crate) fn get_legacy_transparent_address<P: consensus::Parameters>(
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
) -> Result<Option<(TransparentAddress, NonHardenedChildIndex)>, SqliteClientError> {
|
) -> Result<Option<(TransparentAddress, NonHardenedChildIndex)>, SqliteClientError> {
|
||||||
use zcash_address::unified::Container;
|
use zcash_address::unified::{Container, Item};
|
||||||
use zcash_primitives::legacy::keys::ExternalIvk;
|
use zcash_primitives::legacy::keys::ExternalIvk;
|
||||||
|
|
||||||
// Get the UIVK for the account.
|
// 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).
|
// Derive the default transparent address (if it wasn't already part of a derived UA).
|
||||||
for item in uivk.items() {
|
for item in uivk.items_as_parsed() {
|
||||||
if let Ivk::P2pkh(tivk_bytes) = item {
|
if let Item::Data(Ivk::P2pkh(tivk_bytes)) = item {
|
||||||
let tivk = ExternalIvk::deserialize(&tivk_bytes)?;
|
let tivk = ExternalIvk::deserialize(tivk_bytes)?;
|
||||||
return Ok(Some(tivk.default_address()));
|
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
|
// Unified addresses at the time of the addition of migrations did not contain an
|
||||||
// Orchard component.
|
// 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(
|
let address_str = Address::Unified(
|
||||||
ufvk.default_address(ua_request)
|
ufvk.default_address(ua_request)
|
||||||
.expect("A valid default address exists for the UFVK")
|
.expect("A valid default address exists for the UFVK")
|
||||||
|
@ -1545,7 +1546,8 @@ mod tests {
|
||||||
assert_eq!(tv.unified_addr, ua.encode(&Network::MainNetwork));
|
assert_eq!(tv.unified_addr, ua.encode(&Network::MainNetwork));
|
||||||
|
|
||||||
// hardcoded with knowledge of what's coming next
|
// 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
|
db_data
|
||||||
.get_next_available_address(account_id, ua_request)
|
.get_next_available_address(account_id, ua_request)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -443,12 +443,12 @@ mod tests {
|
||||||
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
|
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
|
||||||
let ufvk = usk.to_unified_full_viewing_key();
|
let ufvk = usk.to_unified_full_viewing_key();
|
||||||
let (ua, _) = ufvk
|
let (ua, _) = ufvk
|
||||||
.default_address(UnifiedAddressRequest::unsafe_new(
|
.default_address(UnifiedAddressRequest::unsafe_new_without_expiry(
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
UA_TRANSPARENT,
|
UA_TRANSPARENT,
|
||||||
))
|
))
|
||||||
.expect("A valid default address exists for the UFVK");
|
.unwrap();
|
||||||
let taddr = ufvk
|
let taddr = ufvk
|
||||||
.transparent()
|
.transparent()
|
||||||
.and_then(|k| {
|
.and_then(|k| {
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let (expected_address, idx) = ufvk.default_address(
|
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 {
|
if decoded_address != expected_address {
|
||||||
return Err(WalletMigrationError::CorruptedData(format!(
|
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(
|
let (address, d_idx) = ufvk.default_address(
|
||||||
false,
|
UnifiedAddressRequest::unsafe_new_without_expiry(false, true, UA_TRANSPARENT),
|
||||||
true,
|
)?;
|
||||||
UA_TRANSPARENT,
|
|
||||||
))?;
|
|
||||||
insert_address(transaction, &self.params, account, d_idx, &address)?;
|
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(
|
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();
|
let mut di_be = *diversifier_index.as_bytes();
|
||||||
|
@ -144,7 +144,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (addr, diversifier_index) = ufvk
|
let (addr, diversifier_index) = ufvk
|
||||||
.default_address(UnifiedAddressRequest::unsafe_new(
|
.default_address(UnifiedAddressRequest::unsafe_new_without_expiry(
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
UA_TRANSPARENT,
|
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.
|
// our second assumption above, and we report this as corrupted data.
|
||||||
let mut seed_is_relevant = false;
|
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([])?;
|
let mut rows = stmt_fetch_accounts.query([])?;
|
||||||
while let Some(row) = rows.next()? {
|
while let Some(row) = rows.next()? {
|
||||||
// We only need to check for the presence of the seed if we have keys that
|
// 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,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap()
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,9 +544,7 @@ pub(crate) mod tests {
|
||||||
return Ok(result.map(|(note, addr, memo)| {
|
return Ok(result.map(|(note, addr, memo)| {
|
||||||
(
|
(
|
||||||
Note::Orchard(note),
|
Note::Orchard(note),
|
||||||
UnifiedAddress::from_receivers(Some(addr), None, None)
|
UnifiedAddress::from_receivers(Some(addr), None, None).into(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
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
|
## [0.2.0] - 2024-03-25
|
||||||
|
|
||||||
### Added
|
### 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 Display for zcash_keys::keys::AddressGenerationError`
|
||||||
- `impl std::error::Error for zcash_keys::keys::AddressGenerationError`
|
- `impl std::error::Error for zcash_keys::keys::AddressGenerationError`
|
||||||
- `impl From<hdwallet::error::Error> for zcash_keys::keys::DerivationError`
|
- `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`
|
- `UnifiedFullViewingKey::new` has been placed behind the `test-dependencies`
|
||||||
feature flag. UFVKs should only be produced by derivation from the USK, or
|
feature flag. UFVKs should only be produced by derivation from the USK, or
|
||||||
parsed from their string representation.
|
parsed from their string representation.
|
||||||
|
- `zcash_keys::address::UnifiedAddress::from_receivers`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `UnifiedFullViewingKey::find_address` can now find an address for a diversifier
|
- `UnifiedFullViewingKey::find_address` can now find an address for a diversifier
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
//! Structs for handling supported address types.
|
//! Structs for handling supported address types.
|
||||||
|
|
||||||
use zcash_address::{
|
use zcash_address::{
|
||||||
unified::{self, Container, Encoding, Typecode},
|
unified::{self, Container, DataTypecode, Encoding, Item, Typecode},
|
||||||
ConversionError, ToAddress, TryFromRawAddress, ZcashAddress,
|
ConversionError, ToAddress, TryFromRawAddress, ZcashAddress,
|
||||||
};
|
};
|
||||||
use zcash_primitives::legacy::TransparentAddress;
|
use zcash_primitives::legacy::TransparentAddress;
|
||||||
use zcash_protocol::consensus::{self, NetworkType};
|
use zcash_protocol::{
|
||||||
|
consensus::{self, BlockHeight, NetworkType},
|
||||||
|
PoolType, ShieldedProtocol,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
use sapling::PaymentAddress;
|
use sapling::PaymentAddress;
|
||||||
use zcash_protocol::{PoolType, ShieldedProtocol};
|
|
||||||
|
|
||||||
/// A Unified Address.
|
/// A Unified Address.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -19,7 +21,10 @@ pub struct UnifiedAddress {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
sapling: Option<PaymentAddress>,
|
sapling: Option<PaymentAddress>,
|
||||||
transparent: Option<TransparentAddress>,
|
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 {
|
impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
|
@ -31,14 +36,16 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
let mut unknown_data = vec![];
|
||||||
let mut unknown: Vec<(u32, Vec<u8>)> = 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
|
// We can use as-parsed order here for efficiency, because we're breaking out the
|
||||||
// receivers we support from the unknown receivers.
|
// receivers we support from the unknown receivers.
|
||||||
for item in ua.items_as_parsed() {
|
for item in ua.items_as_parsed() {
|
||||||
match item {
|
match item {
|
||||||
unified::Receiver::Orchard(data) => {
|
Item::Data(unified::Receiver::Orchard(data)) => {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
{
|
{
|
||||||
orchard = Some(
|
orchard = Some(
|
||||||
|
@ -48,11 +55,11 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[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")]
|
#[cfg(feature = "sapling")]
|
||||||
{
|
{
|
||||||
sapling = Some(
|
sapling = Some(
|
||||||
|
@ -62,20 +69,26 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "sapling"))]
|
#[cfg(not(feature = "sapling"))]
|
||||||
{
|
{
|
||||||
unknown.push((unified::Typecode::Sapling.into(), data.to_vec()));
|
unknown_data.push((unified::Typecode::SAPLING.into(), data.to_vec()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Item::Data(unified::Receiver::P2pkh(data)) => {
|
||||||
unified::Receiver::P2pkh(data) => {
|
|
||||||
transparent = Some(TransparentAddress::PublicKeyHash(*data));
|
transparent = Some(TransparentAddress::PublicKeyHash(*data));
|
||||||
}
|
}
|
||||||
|
Item::Data(unified::Receiver::P2sh(data)) => {
|
||||||
unified::Receiver::P2sh(data) => {
|
|
||||||
transparent = Some(TransparentAddress::ScriptHash(*data));
|
transparent = Some(TransparentAddress::ScriptHash(*data));
|
||||||
}
|
}
|
||||||
|
Item::Data(unified::Receiver::Unknown { typecode, data }) => {
|
||||||
unified::Receiver::Unknown { typecode, data } => {
|
unknown_data.push((*typecode, data.clone()));
|
||||||
unknown.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")]
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
transparent,
|
transparent,
|
||||||
unknown,
|
unknown_data,
|
||||||
|
expiry_height,
|
||||||
|
expiry_time,
|
||||||
|
unknown_metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,36 +110,43 @@ impl TryFrom<unified::Address> for UnifiedAddress {
|
||||||
impl UnifiedAddress {
|
impl UnifiedAddress {
|
||||||
/// Constructs a Unified Address from a given set of receivers.
|
/// Constructs a Unified Address from a given set of receivers.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the receivers would produce an invalid Unified Address (namely,
|
/// This method is only available when the `test-dependencies` feature is enabled, as
|
||||||
/// if no shielded receiver is provided).
|
/// 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(
|
pub fn from_receivers(
|
||||||
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
||||||
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
||||||
transparent: Option<TransparentAddress>,
|
transparent: Option<TransparentAddress>,
|
||||||
// TODO: Add handling for address metadata items.
|
) -> Self {
|
||||||
) -> Option<Self> {
|
Self::new_internal(
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let has_orchard = orchard.is_some();
|
orchard,
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(feature = "sapling")]
|
||||||
let has_orchard = false;
|
sapling,
|
||||||
|
transparent,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
pub(crate) fn new_internal(
|
||||||
let has_sapling = sapling.is_some();
|
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
|
||||||
#[cfg(not(feature = "sapling"))]
|
#[cfg(feature = "sapling")] sapling: Option<PaymentAddress>,
|
||||||
let has_sapling = false;
|
transparent: Option<TransparentAddress>,
|
||||||
|
expiry_height: Option<BlockHeight>,
|
||||||
if has_orchard || has_sapling {
|
expiry_time: Option<u64>,
|
||||||
Some(Self {
|
) -> Self {
|
||||||
#[cfg(feature = "orchard")]
|
Self {
|
||||||
orchard,
|
#[cfg(feature = "orchard")]
|
||||||
#[cfg(feature = "sapling")]
|
orchard,
|
||||||
sapling,
|
#[cfg(feature = "sapling")]
|
||||||
transparent,
|
sapling,
|
||||||
unknown: vec![],
|
transparent,
|
||||||
})
|
unknown_data: vec![],
|
||||||
} else {
|
expiry_height,
|
||||||
// UAs require at least one shielded receiver.
|
expiry_time,
|
||||||
None
|
unknown_metadata: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,22 +191,60 @@ impl UnifiedAddress {
|
||||||
self.transparent.as_ref()
|
self.transparent.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the set of unknown receivers of the unified address.
|
/// Returns any unknown data items parsed from the encoded form of the address.
|
||||||
pub fn unknown(&self) -> &[(u32, Vec<u8>)] {
|
pub fn unknown_data(&self) -> &[(u32, Vec<u8>)] {
|
||||||
&self.unknown
|
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 {
|
fn to_address(&self, net: NetworkType) -> ZcashAddress {
|
||||||
let items = self
|
let data_items =
|
||||||
.unknown
|
self.unknown_data
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(typecode, data)| unified::Receiver::Unknown {
|
.map(|(typecode, data)| unified::Receiver::Unknown {
|
||||||
typecode: *typecode,
|
typecode: *typecode,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.orchard
|
self.orchard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|addr| addr.to_raw_address_bytes())
|
.map(|addr| addr.to_raw_address_bytes())
|
||||||
|
@ -191,20 +252,38 @@ impl UnifiedAddress {
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.sapling
|
self.sapling
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|pa| pa.to_bytes())
|
.map(|pa| pa.to_bytes())
|
||||||
.map(unified::Receiver::Sapling),
|
.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::PublicKeyHash(data) => unified::Receiver::P2pkh(*data),
|
||||||
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
|
TransparentAddress::ScriptHash(data) => unified::Receiver::P2sh(*data),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let ua = unified::Address::try_from_items(items.collect())
|
let meta_items = self
|
||||||
.expect("UnifiedAddress should only be constructed safely");
|
.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)
|
ZcashAddress::from_unified(net, ua)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,17 +296,17 @@ impl UnifiedAddress {
|
||||||
pub fn receiver_types(&self) -> Vec<Typecode> {
|
pub fn receiver_types(&self) -> Vec<Typecode> {
|
||||||
let result = std::iter::empty();
|
let result = std::iter::empty();
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let result = result.chain(self.orchard.map(|_| Typecode::Orchard));
|
let result = result.chain(self.orchard.map(|_| Typecode::ORCHARD));
|
||||||
#[cfg(feature = "sapling")]
|
#[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 {
|
let result = result.chain(self.transparent.map(|taddr| match taddr {
|
||||||
TransparentAddress::PublicKeyHash(_) => Typecode::P2pkh,
|
TransparentAddress::PublicKeyHash(_) => Typecode::P2PKH,
|
||||||
TransparentAddress::ScriptHash(_) => Typecode::P2sh,
|
TransparentAddress::ScriptHash(_) => Typecode::P2SH,
|
||||||
}));
|
}));
|
||||||
let result = result.chain(
|
let result = result.chain(
|
||||||
self.unknown()
|
self.unknown_data()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(typecode, _)| Typecode::Unknown(*typecode)),
|
.map(|(typecode, _)| Typecode::Data(DataTypecode::Unknown(*typecode))),
|
||||||
);
|
);
|
||||||
result.collect()
|
result.collect()
|
||||||
}
|
}
|
||||||
|
@ -256,7 +335,8 @@ impl Receiver {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
Receiver::Orchard(addr) => {
|
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])
|
let ua = unified::Address::try_from_items(vec![receiver])
|
||||||
.expect("A unified address may contain a single Orchard receiver.");
|
.expect("A unified address may contain a single Orchard receiver.");
|
||||||
ZcashAddress::from_unified(net, ua)
|
ZcashAddress::from_unified(net, ua)
|
||||||
|
@ -440,7 +520,7 @@ pub mod testing {
|
||||||
params: Network,
|
params: Network,
|
||||||
request: UnifiedAddressRequest,
|
request: UnifiedAddressRequest,
|
||||||
) -> impl Strategy<Value = UnifiedAddress> {
|
) -> 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")]
|
#[cfg(feature = "sapling")]
|
||||||
|
@ -495,13 +575,13 @@ mod tests {
|
||||||
let transparent = None;
|
let transparent = None;
|
||||||
|
|
||||||
#[cfg(all(feature = "orchard", feature = "sapling"))]
|
#[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"))]
|
#[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")))]
|
#[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 = Address::Unified(ua);
|
||||||
let addr_str = addr.encode(&MAIN_NETWORK);
|
let addr_str = addr.encode(&MAIN_NETWORK);
|
||||||
|
@ -512,7 +592,7 @@ mod tests {
|
||||||
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
|
#[cfg(not(any(feature = "orchard", feature = "sapling")))]
|
||||||
fn ua_round_trip() {
|
fn ua_round_trip() {
|
||||||
let transparent = None;
|
let transparent = None;
|
||||||
assert_eq!(UnifiedAddress::from_receivers(transparent), None)
|
assert_eq!(UnifiedAddress::new_internal(transparent, None, None), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::{
|
||||||
error,
|
error,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
};
|
};
|
||||||
|
use zcash_address::unified::{self, Container, Encoding, Item, MetadataItem, Typecode};
|
||||||
use zcash_address::unified::{self, Container, Encoding, Typecode, Ufvk, Uivk};
|
use zcash_primitives::consensus::BlockHeight;
|
||||||
use zcash_protocol::consensus;
|
use zcash_protocol::consensus;
|
||||||
use zip32::{AccountId, DiversifierIndex};
|
use zip32::{AccountId, DiversifierIndex};
|
||||||
|
|
||||||
|
@ -258,7 +258,10 @@ impl UnifiedSpendingKey {
|
||||||
sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
|
sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: Some((&self.orchard).into()),
|
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")]
|
#[cfg(feature = "orchard")]
|
||||||
{
|
{
|
||||||
let orchard_key = self.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();
|
let orchard_key_bytes = orchard_key.to_bytes();
|
||||||
CompactSize::write(&mut result, orchard_key_bytes.len()).unwrap();
|
CompactSize::write(&mut result, orchard_key_bytes.len()).unwrap();
|
||||||
|
@ -308,7 +311,7 @@ impl UnifiedSpendingKey {
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
{
|
{
|
||||||
let sapling_key = self.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();
|
let sapling_key_bytes = sapling_key.to_bytes();
|
||||||
CompactSize::write(&mut result, sapling_key_bytes.len()).unwrap();
|
CompactSize::write(&mut result, sapling_key_bytes.len()).unwrap();
|
||||||
|
@ -318,7 +321,7 @@ impl UnifiedSpendingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
{
|
{
|
||||||
let account_tkey = self.transparent();
|
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();
|
let account_tkey_bytes = account_tkey.to_bytes();
|
||||||
CompactSize::write(&mut result, account_tkey_bytes.len()).unwrap();
|
CompactSize::write(&mut result, account_tkey_bytes.len()).unwrap();
|
||||||
|
@ -334,6 +337,8 @@ impl UnifiedSpendingKey {
|
||||||
#[allow(clippy::unnecessary_unwrap)]
|
#[allow(clippy::unnecessary_unwrap)]
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
pub fn from_bytes(era: Era, encoded: &[u8]) -> Result<Self, DecodingError> {
|
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 mut source = std::io::Cursor::new(encoded);
|
||||||
let decoded_era = source
|
let decoded_era = source
|
||||||
.read_u32::<LittleEndian>()
|
.read_u32::<LittleEndian>()
|
||||||
|
@ -353,21 +358,23 @@ impl UnifiedSpendingKey {
|
||||||
loop {
|
loop {
|
||||||
let tc = CompactSize::read_t::<_, u32>(&mut source)
|
let tc = CompactSize::read_t::<_, u32>(&mut source)
|
||||||
.map_err(|_| DecodingError::ReadError("typecode"))
|
.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)
|
let len = CompactSize::read_t::<_, u32>(&mut source)
|
||||||
.map_err(|_| DecodingError::ReadError("key length"))?;
|
.map_err(|_| DecodingError::ReadError("key length"))?;
|
||||||
|
|
||||||
match tc {
|
match tc {
|
||||||
Typecode::Orchard => {
|
DataTypecode::Orchard => {
|
||||||
if len != 32 {
|
if len != 32 {
|
||||||
return Err(DecodingError::LengthMismatch(Typecode::Orchard, len));
|
return Err(DecodingError::LengthMismatch(Typecode::ORCHARD, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut key = [0u8; 32];
|
let mut key = [0u8; 32];
|
||||||
source
|
source
|
||||||
.read_exact(&mut key)
|
.read_exact(&mut key)
|
||||||
.map_err(|_| DecodingError::InsufficientData(Typecode::Orchard))?;
|
.map_err(|_| DecodingError::InsufficientData(Typecode::ORCHARD))?;
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
{
|
{
|
||||||
|
@ -375,43 +382,43 @@ impl UnifiedSpendingKey {
|
||||||
Option::<orchard::keys::SpendingKey>::from(
|
Option::<orchard::keys::SpendingKey>::from(
|
||||||
orchard::keys::SpendingKey::from_bytes(key),
|
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 {
|
if len != 169 {
|
||||||
return Err(DecodingError::LengthMismatch(Typecode::Sapling, len));
|
return Err(DecodingError::LengthMismatch(Typecode::SAPLING, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut key = [0u8; 169];
|
let mut key = [0u8; 169];
|
||||||
source
|
source
|
||||||
.read_exact(&mut key)
|
.read_exact(&mut key)
|
||||||
.map_err(|_| DecodingError::InsufficientData(Typecode::Sapling))?;
|
.map_err(|_| DecodingError::InsufficientData(Typecode::SAPLING))?;
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
{
|
{
|
||||||
sapling = Some(
|
sapling = Some(
|
||||||
sapling::ExtendedSpendingKey::from_bytes(&key)
|
sapling::ExtendedSpendingKey::from_bytes(&key)
|
||||||
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::Sapling))?,
|
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Typecode::P2pkh => {
|
DataTypecode::P2pkh => {
|
||||||
if len != 64 {
|
if len != 64 {
|
||||||
return Err(DecodingError::LengthMismatch(Typecode::P2pkh, len));
|
return Err(DecodingError::LengthMismatch(Typecode::P2PKH, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut key = [0u8; 64];
|
let mut key = [0u8; 64];
|
||||||
source
|
source
|
||||||
.read_exact(&mut key)
|
.read_exact(&mut key)
|
||||||
.map_err(|_| DecodingError::InsufficientData(Typecode::P2pkh))?;
|
.map_err(|_| DecodingError::InsufficientData(Typecode::P2PKH))?;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
{
|
{
|
||||||
transparent = Some(
|
transparent = Some(
|
||||||
legacy::AccountPrivKey::from_bytes(&key)
|
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")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard.unwrap(),
|
orchard.unwrap(),
|
||||||
)
|
)
|
||||||
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh));
|
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,10 +460,8 @@ impl UnifiedSpendingKey {
|
||||||
pub fn default_address(
|
pub fn default_address(
|
||||||
&self,
|
&self,
|
||||||
request: UnifiedAddressRequest,
|
request: UnifiedAddressRequest,
|
||||||
) -> (UnifiedAddress, DiversifierIndex) {
|
) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
|
||||||
self.to_unified_full_viewing_key()
|
self.to_unified_full_viewing_key().default_address(request)
|
||||||
.default_address(request)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -549,22 +554,28 @@ pub struct UnifiedAddressRequest {
|
||||||
has_orchard: bool,
|
has_orchard: bool,
|
||||||
has_sapling: bool,
|
has_sapling: bool,
|
||||||
has_p2pkh: bool,
|
has_p2pkh: bool,
|
||||||
|
expiry_height: Option<BlockHeight>,
|
||||||
|
expiry_time: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnifiedAddressRequest {
|
impl UnifiedAddressRequest {
|
||||||
/// Construct a new unified address request from its constituent parts.
|
/// Construct a new unified address request from its constituent parts
|
||||||
///
|
pub fn new(
|
||||||
/// Returns `None` if the resulting unified address would not include at least one shielded receiver.
|
has_orchard: bool,
|
||||||
pub fn new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Option<Self> {
|
has_sapling: bool,
|
||||||
let has_shielded_receiver = has_orchard || has_sapling;
|
has_p2pkh: bool,
|
||||||
|
expiry_height: Option<BlockHeight>,
|
||||||
if !has_shielded_receiver {
|
expiry_time: Option<u64>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
if !(has_sapling || has_orchard || has_p2pkh) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Self {
|
Some(Self {
|
||||||
has_orchard,
|
has_orchard,
|
||||||
has_sapling,
|
has_sapling,
|
||||||
has_p2pkh,
|
has_p2pkh,
|
||||||
|
expiry_height,
|
||||||
|
expiry_time,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,21 +595,27 @@ impl UnifiedAddressRequest {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let _has_p2pkh = true;
|
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.
|
/// Construct a new unified address request from its constituent parts.
|
||||||
///
|
///
|
||||||
/// Panics: at least one of `has_orchard` or `has_sapling` must be `true`.
|
/// Panics: at least one of `has_orchard`, `has_sapling`, or `has_p2pkh` must be `true`.
|
||||||
pub const fn unsafe_new(has_orchard: bool, has_sapling: bool, has_p2pkh: bool) -> Self {
|
pub const fn unsafe_new_without_expiry(
|
||||||
if !(has_orchard || has_sapling) {
|
has_orchard: bool,
|
||||||
panic!("At least one shielded receiver must be requested.")
|
has_sapling: bool,
|
||||||
|
has_p2pkh: bool,
|
||||||
|
) -> Self {
|
||||||
|
if !(has_orchard || has_sapling || has_p2pkh) {
|
||||||
|
panic!("At least one receiver must be requested.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
has_orchard,
|
has_orchard,
|
||||||
has_sapling,
|
has_sapling,
|
||||||
has_p2pkh,
|
has_p2pkh,
|
||||||
|
expiry_height: None,
|
||||||
|
expiry_time: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,7 +636,10 @@ pub struct UnifiedFullViewingKey {
|
||||||
sapling: Option<sapling::DiversifiableFullViewingKey>,
|
sapling: Option<sapling::DiversifiableFullViewingKey>,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: Option<orchard::keys::FullViewingKey>,
|
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 {
|
impl UnifiedFullViewingKey {
|
||||||
|
@ -645,6 +665,9 @@ impl UnifiedFullViewingKey {
|
||||||
// We don't currently allow constructing new UFVKs with unknown items, but we store
|
// We don't currently allow constructing new UFVKs with unknown items, but we store
|
||||||
// this to allow parsing such UFVKs.
|
// this to allow parsing such UFVKs.
|
||||||
vec![],
|
vec![],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +677,10 @@ impl UnifiedFullViewingKey {
|
||||||
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
|
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
|
||||||
#[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
|
#[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
|
||||||
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
|
#[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> {
|
) -> Result<UnifiedFullViewingKey, DerivationError> {
|
||||||
// Verify that IVK derivation succeeds; we don't want to construct a UFVK
|
// Verify that IVK derivation succeeds; we don't want to construct a UFVK
|
||||||
// that can't derive transparent addresses.
|
// that can't derive transparent addresses.
|
||||||
|
@ -671,7 +697,10 @@ impl UnifiedFullViewingKey {
|
||||||
sapling,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
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
|
/// [ZIP 316]: https://zips.z.cash/zip-0316
|
||||||
pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
|
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();
|
let expected_net = params.network_type();
|
||||||
if net != expected_net {
|
if net != expected_net {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
@ -694,64 +724,71 @@ impl UnifiedFullViewingKey {
|
||||||
/// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
|
/// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
|
||||||
///
|
///
|
||||||
/// [ZIP 316]: https://zips.z.cash/zip-0316
|
/// [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")]
|
#[cfg(feature = "orchard")]
|
||||||
let mut orchard = None;
|
let mut orchard = None;
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
let mut unknown_data = vec![];
|
||||||
|
let mut expiry_height = None;
|
||||||
|
let mut expiry_time = None;
|
||||||
|
let mut unknown_metadata = vec![];
|
||||||
|
|
||||||
// We can use as-parsed order here for efficiency, because we're breaking out the
|
// We can use as-parsed order here for efficiency, because we're breaking out the
|
||||||
// receivers we support from the unknown receivers.
|
// receivers we support from the unknown receivers.
|
||||||
let unknown = ufvk
|
for item in ufvk.items_as_parsed() {
|
||||||
.items_as_parsed()
|
match item {
|
||||||
.iter()
|
Item::Data(unified::Fvk::Orchard(data)) => {
|
||||||
.filter_map(|receiver| match receiver {
|
#[cfg(feature = "orchard")]
|
||||||
#[cfg(feature = "orchard")]
|
{
|
||||||
unified::Fvk::Orchard(data) => orchard::keys::FullViewingKey::from_bytes(data)
|
orchard = Some(
|
||||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))
|
orchard::keys::FullViewingKey::from_bytes(data)
|
||||||
.map(|addr| {
|
.ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
|
||||||
orchard = Some(addr);
|
);
|
||||||
None
|
}
|
||||||
})
|
|
||||||
.transpose(),
|
#[cfg(not(feature = "orchard"))]
|
||||||
#[cfg(not(feature = "orchard"))]
|
unknown_data.push((unified::DataTypecode::Orchard.into(), data.to_vec()));
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "sapling"))]
|
Item::Data(unified::Fvk::Sapling(data)) => {
|
||||||
unified::Fvk::Sapling(data) => Some(Ok::<_, DecodingError>((
|
#[cfg(feature = "sapling")]
|
||||||
u32::from(unified::Typecode::Sapling),
|
{
|
||||||
data.to_vec(),
|
sapling = Some(
|
||||||
))),
|
sapling::DiversifiableFullViewingKey::from_bytes(data)
|
||||||
#[cfg(feature = "transparent-inputs")]
|
.ok_or(DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
|
||||||
unified::Fvk::P2pkh(data) => legacy::AccountPubKey::deserialize(data)
|
);
|
||||||
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
|
}
|
||||||
.map(|tfvk| {
|
#[cfg(not(feature = "sapling"))]
|
||||||
transparent = Some(tfvk);
|
unknown_data.push((unified::Typecode::SAPLING.into(), data.to_vec()));
|
||||||
None
|
}
|
||||||
})
|
Item::Data(unified::Fvk::P2pkh(data)) => {
|
||||||
.transpose(),
|
#[cfg(feature = "transparent-inputs")]
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
{
|
||||||
unified::Fvk::P2pkh(data) => Some(Ok::<_, DecodingError>((
|
transparent = Some(
|
||||||
u32::from(unified::Typecode::P2pkh),
|
legacy::AccountPubKey::deserialize(data)
|
||||||
data.to_vec(),
|
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
|
||||||
))),
|
);
|
||||||
unified::Fvk::Unknown { typecode, data } => Some(Ok((*typecode, data.clone()))),
|
}
|
||||||
})
|
|
||||||
.collect::<Result<_, _>>()?;
|
#[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(
|
Self::from_checked_parts(
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -760,9 +797,12 @@ impl UnifiedFullViewingKey {
|
||||||
sapling,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
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.
|
/// 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.
|
/// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
|
||||||
fn to_ufvk(&self) -> Ufvk {
|
fn to_ufvk(&self) -> zcash_address::unified::Ufvk {
|
||||||
let items = std::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
|
let data_items =
|
||||||
unified::Fvk::Unknown {
|
std::iter::empty().chain(self.unknown_data.iter().map(|(typecode, data)| {
|
||||||
typecode: *typecode,
|
unified::Fvk::Unknown {
|
||||||
data: data.clone(),
|
typecode: *typecode,
|
||||||
}
|
data: data.clone(),
|
||||||
}));
|
}
|
||||||
|
}));
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.orchard
|
self.orchard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|fvk| fvk.to_bytes())
|
.map(|fvk| fvk.to_bytes())
|
||||||
.map(unified::Fvk::Orchard),
|
.map(unified::Fvk::Orchard),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.sapling
|
self.sapling
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|dfvk| dfvk.to_bytes())
|
.map(|dfvk| dfvk.to_bytes())
|
||||||
.map(unified::Fvk::Sapling),
|
.map(unified::Fvk::Sapling),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.transparent
|
self.transparent
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|tfvk| tfvk.serialize().try_into().unwrap())
|
.map(|tfvk| tfvk.serialize().try_into().unwrap())
|
||||||
.map(unified::Fvk::P2pkh),
|
.map(unified::Fvk::P2pkh),
|
||||||
);
|
);
|
||||||
|
|
||||||
unified::Ufvk::try_from_items(items.collect())
|
let meta_items = std::iter::empty()
|
||||||
.expect("UnifiedFullViewingKey should only be constructed safely")
|
.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.
|
/// 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()),
|
sapling: self.sapling.as_ref().map(|s| s.to_external_ivk()),
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: self.orchard.as_ref().map(|o| o.to_ivk(Scope::External)),
|
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()
|
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
|
/// Attempts to derive the Unified Address for the given diversifier index and
|
||||||
/// receiver types.
|
/// receiver types.
|
||||||
///
|
///
|
||||||
|
@ -857,10 +940,9 @@ impl UnifiedFullViewingKey {
|
||||||
///
|
///
|
||||||
/// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
|
/// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
|
||||||
/// required to satisfy the unified address request are not properly enabled.
|
/// required to satisfy the unified address request are not properly enabled.
|
||||||
#[allow(unused_mut)]
|
|
||||||
pub fn find_address(
|
pub fn find_address(
|
||||||
&self,
|
&self,
|
||||||
mut j: DiversifierIndex,
|
j: DiversifierIndex,
|
||||||
request: UnifiedAddressRequest,
|
request: UnifiedAddressRequest,
|
||||||
) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
|
) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
|
||||||
self.to_unified_incoming_viewing_key()
|
self.to_unified_incoming_viewing_key()
|
||||||
|
@ -889,8 +971,10 @@ pub struct UnifiedIncomingViewingKey {
|
||||||
sapling: Option<::sapling::zip32::IncomingViewingKey>,
|
sapling: Option<::sapling::zip32::IncomingViewingKey>,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard: Option<orchard::keys::IncomingViewingKey>,
|
orchard: Option<orchard::keys::IncomingViewingKey>,
|
||||||
/// Stores the unrecognized elements of the unified encoding.
|
unknown_data: Vec<(u32, Vec<u8>)>,
|
||||||
unknown: Vec<(u32, Vec<u8>)>,
|
expiry_height: Option<BlockHeight>,
|
||||||
|
expiry_time: Option<u64>,
|
||||||
|
unknown_metadata: Vec<(u32, Vec<u8>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnifiedIncomingViewingKey {
|
impl UnifiedIncomingViewingKey {
|
||||||
|
@ -906,7 +990,10 @@ impl UnifiedIncomingViewingKey {
|
||||||
>,
|
>,
|
||||||
#[cfg(feature = "sapling")] sapling: Option<::sapling::zip32::IncomingViewingKey>,
|
#[cfg(feature = "sapling")] sapling: Option<::sapling::zip32::IncomingViewingKey>,
|
||||||
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::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 {
|
||||||
UnifiedIncomingViewingKey {
|
UnifiedIncomingViewingKey {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -917,7 +1004,10 @@ impl UnifiedIncomingViewingKey {
|
||||||
orchard,
|
orchard,
|
||||||
// We don't allow constructing new UFVKs with unknown items, but we store
|
// We don't allow constructing new UFVKs with unknown items, but we store
|
||||||
// this to allow parsing such UFVKs.
|
// 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
|
/// [ZIP 316]: https://zips.z.cash/zip-0316
|
||||||
pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
|
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();
|
let expected_net = params.network_type();
|
||||||
if net != expected_net {
|
if net != expected_net {
|
||||||
return Err(format!(
|
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.
|
/// 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")]
|
#[cfg(feature = "orchard")]
|
||||||
let mut orchard = None;
|
let mut orchard = None;
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let mut sapling = None;
|
let mut sapling = None;
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let mut transparent = None;
|
let mut transparent = None;
|
||||||
|
let mut unknown_data = vec![];
|
||||||
let mut unknown = 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
|
// We can use as-parsed order here for efficiency, because we're breaking out the
|
||||||
// receivers we support from the unknown receivers.
|
// receivers we support from the unknown receivers.
|
||||||
for receiver in uivk.items_as_parsed() {
|
for receiver in uivk.items_as_parsed() {
|
||||||
match receiver {
|
match receiver {
|
||||||
unified::Ivk::Orchard(data) => {
|
Item::Data(unified::Ivk::Orchard(data)) => {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
{
|
{
|
||||||
orchard = Some(
|
orchard = Some(
|
||||||
Option::from(orchard::keys::IncomingViewingKey::from_bytes(data))
|
Option::from(orchard::keys::IncomingViewingKey::from_bytes(data))
|
||||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
|
.ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "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")]
|
#[cfg(feature = "sapling")]
|
||||||
{
|
{
|
||||||
sapling = Some(
|
sapling = Some(
|
||||||
Option::from(::sapling::zip32::IncomingViewingKey::from_bytes(data))
|
Option::from(::sapling::zip32::IncomingViewingKey::from_bytes(data))
|
||||||
.ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))?,
|
.ok_or(DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "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")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
{
|
{
|
||||||
transparent = Some(
|
transparent = Some(
|
||||||
legacy::ExternalIvk::deserialize(data)
|
legacy::ExternalIvk::deserialize(data)
|
||||||
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
|
.map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[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 } => {
|
Item::Data(unified::Ivk::Unknown { typecode, data }) => {
|
||||||
unknown.push((*typecode, data.clone()));
|
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,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
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.
|
/// Converts this unified incoming viewing key to a unified encoding.
|
||||||
fn render(&self) -> Uivk {
|
fn render(&self) -> zcash_address::unified::Uivk {
|
||||||
let items = std::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
|
let data_items =
|
||||||
unified::Ivk::Unknown {
|
std::iter::empty().chain(self.unknown_data.iter().map(|(typecode, data)| {
|
||||||
typecode: *typecode,
|
unified::Ivk::Unknown {
|
||||||
data: data.clone(),
|
typecode: *typecode,
|
||||||
}
|
data: data.clone(),
|
||||||
}));
|
}
|
||||||
|
}));
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.orchard
|
self.orchard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|ivk| ivk.to_bytes())
|
.map(|ivk| ivk.to_bytes())
|
||||||
.map(unified::Ivk::Orchard),
|
.map(unified::Ivk::Orchard),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.sapling
|
self.sapling
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|divk| divk.to_bytes())
|
.map(|divk| divk.to_bytes())
|
||||||
.map(unified::Ivk::Sapling),
|
.map(unified::Ivk::Sapling),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let items = items.chain(
|
let data_items = data_items.chain(
|
||||||
self.transparent
|
self.transparent
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|tivk| tivk.serialize().try_into().unwrap())
|
.map(|tivk| tivk.serialize().try_into().unwrap())
|
||||||
.map(unified::Ivk::P2pkh),
|
.map(unified::Ivk::P2pkh),
|
||||||
);
|
);
|
||||||
|
|
||||||
unified::Uivk::try_from_items(items.collect())
|
let meta_items = std::iter::empty()
|
||||||
.expect("UnifiedIncomingViewingKey should only be constructed safely.")
|
.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.
|
/// Returns the Transparent external IVK, if present.
|
||||||
|
@ -1062,6 +1185,26 @@ impl UnifiedIncomingViewingKey {
|
||||||
&self.orchard
|
&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
|
/// Attempts to derive the Unified Address for the given diversifier index and
|
||||||
/// receiver types.
|
/// receiver types.
|
||||||
///
|
///
|
||||||
|
@ -1076,7 +1219,7 @@ impl UnifiedIncomingViewingKey {
|
||||||
if request.has_orchard {
|
if request.has_orchard {
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||||
Typecode::Orchard,
|
Typecode::ORCHARD,
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
@ -1084,7 +1227,7 @@ impl UnifiedIncomingViewingKey {
|
||||||
let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
|
let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
|
||||||
orchard = Some(oivk.address_at(orchard_j))
|
orchard = Some(oivk.address_at(orchard_j))
|
||||||
} else {
|
} else {
|
||||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
|
return Err(AddressGenerationError::KeyNotAvailable(Typecode::ORCHARD));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1093,7 +1236,7 @@ impl UnifiedIncomingViewingKey {
|
||||||
if request.has_sapling {
|
if request.has_sapling {
|
||||||
#[cfg(not(feature = "sapling"))]
|
#[cfg(not(feature = "sapling"))]
|
||||||
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||||
Typecode::Sapling,
|
Typecode::SAPLING,
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
|
@ -1106,7 +1249,7 @@ impl UnifiedIncomingViewingKey {
|
||||||
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))?,
|
.ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))?,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
|
return Err(AddressGenerationError::KeyNotAvailable(Typecode::SAPLING));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1115,7 +1258,7 @@ impl UnifiedIncomingViewingKey {
|
||||||
if request.has_p2pkh {
|
if request.has_p2pkh {
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
return Err(AddressGenerationError::ReceiverTypeNotSupported(
|
||||||
Typecode::P2pkh,
|
Typecode::P2PKH,
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -1131,20 +1274,21 @@ impl UnifiedIncomingViewingKey {
|
||||||
.map_err(|_| AddressGenerationError::InvalidTransparentChildIndex(_j))?,
|
.map_err(|_| AddressGenerationError::InvalidTransparentChildIndex(_j))?,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
|
return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2PKH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
let transparent = None;
|
let transparent = None;
|
||||||
|
|
||||||
UnifiedAddress::from_receivers(
|
Ok(UnifiedAddress::new_internal(
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
#[cfg(feature = "sapling")]
|
#[cfg(feature = "sapling")]
|
||||||
sapling,
|
sapling,
|
||||||
transparent,
|
transparent,
|
||||||
)
|
std::cmp::min(self.expiry_height, request.expiry_height),
|
||||||
.ok_or(AddressGenerationError::ShieldedReceiverRequired)
|
std::cmp::min(self.expiry_time, request.expiry_time),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Searches the diversifier space starting at diversifier index `j` for one which will
|
/// 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
|
/// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
|
||||||
/// required to satisfy the unified address request are not properly enabled.
|
/// required to satisfy the unified address request are not properly enabled.
|
||||||
#[allow(unused_mut)]
|
|
||||||
pub fn find_address(
|
pub fn find_address(
|
||||||
&self,
|
&self,
|
||||||
mut j: DiversifierIndex,
|
mut j: DiversifierIndex,
|
||||||
|
@ -1181,6 +1324,8 @@ impl UnifiedIncomingViewingKey {
|
||||||
Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
|
Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
|
||||||
if j.increment().is_err() {
|
if j.increment().is_err() {
|
||||||
return Err(AddressGenerationError::DiversifierSpaceExhausted);
|
return Err(AddressGenerationError::DiversifierSpaceExhausted);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(other) => {
|
Err(other) => {
|
||||||
|
@ -1236,7 +1381,7 @@ mod tests {
|
||||||
#[cfg(any(feature = "sapling", feature = "orchard"))]
|
#[cfg(any(feature = "sapling", feature = "orchard"))]
|
||||||
use {
|
use {
|
||||||
super::{UnifiedFullViewingKey, UnifiedIncomingViewingKey},
|
super::{UnifiedFullViewingKey, UnifiedIncomingViewingKey},
|
||||||
zcash_address::unified::{Encoding, Uivk},
|
zcash_address::unified::Encoding,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
@ -1372,13 +1517,13 @@ mod tests {
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
feature = "transparent-inputs"
|
feature = "transparent-inputs"
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 0);
|
assert_eq!(decoded_with_t.unknown_data.len(), 0);
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "orchard",
|
feature = "orchard",
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
not(feature = "transparent-inputs")
|
not(feature = "transparent-inputs")
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
assert_eq!(decoded_with_t.unknown_data.len(), 1);
|
||||||
|
|
||||||
// Orchard enabled
|
// Orchard enabled
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -1386,13 +1531,13 @@ mod tests {
|
||||||
not(feature = "sapling"),
|
not(feature = "sapling"),
|
||||||
feature = "transparent-inputs"
|
feature = "transparent-inputs"
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
assert_eq!(decoded_with_t.unknown_data.len(), 1);
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "orchard",
|
feature = "orchard",
|
||||||
not(feature = "sapling"),
|
not(feature = "sapling"),
|
||||||
not(feature = "transparent-inputs")
|
not(feature = "transparent-inputs")
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 2);
|
assert_eq!(decoded_with_t.unknown_data.len(), 2);
|
||||||
|
|
||||||
// Sapling enabled
|
// Sapling enabled
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -1400,13 +1545,13 @@ mod tests {
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
feature = "transparent-inputs"
|
feature = "transparent-inputs"
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
assert_eq!(decoded_with_t.unknown_data.len(), 1);
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(feature = "orchard"),
|
not(feature = "orchard"),
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
not(feature = "transparent-inputs")
|
not(feature = "transparent-inputs")
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 2);
|
assert_eq!(decoded_with_t.unknown_data.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1435,7 +1580,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ua = ufvk
|
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| {
|
.unwrap_or_else(|err| {
|
||||||
panic!(
|
panic!(
|
||||||
"unified address generation failed for account {}: {:?}",
|
"unified address generation failed for account {}: {:?}",
|
||||||
|
@ -1497,6 +1645,10 @@ mod tests {
|
||||||
sapling,
|
sapling,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
orchard,
|
orchard,
|
||||||
|
vec![],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
);
|
);
|
||||||
|
|
||||||
let encoded = uivk.render().encode(&NetworkType::Main);
|
let encoded = uivk.render().encode(&NetworkType::Main);
|
||||||
|
@ -1517,7 +1669,7 @@ mod tests {
|
||||||
assert_eq!(encoded, _encoded_no_t);
|
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);
|
let reencoded = decoded.render().encode(&NetworkType::Main);
|
||||||
assert_eq!(encoded, reencoded);
|
assert_eq!(encoded, reencoded);
|
||||||
|
|
||||||
|
@ -1538,7 +1690,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let decoded_with_t =
|
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")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoded_with_t.transparent.map(|t| t.serialize()),
|
decoded_with_t.transparent.map(|t| t.serialize()),
|
||||||
|
@ -1551,13 +1703,13 @@ mod tests {
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
feature = "transparent-inputs"
|
feature = "transparent-inputs"
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 0);
|
assert_eq!(decoded_with_t.unknown_data.len(), 0);
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "orchard",
|
feature = "orchard",
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
not(feature = "transparent-inputs")
|
not(feature = "transparent-inputs")
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
assert_eq!(decoded_with_t.unknown_data.len(), 1);
|
||||||
|
|
||||||
// Orchard enabled
|
// Orchard enabled
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -1565,13 +1717,13 @@ mod tests {
|
||||||
not(feature = "sapling"),
|
not(feature = "sapling"),
|
||||||
feature = "transparent-inputs"
|
feature = "transparent-inputs"
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
assert_eq!(decoded_with_t.unknown_data.len(), 1);
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "orchard",
|
feature = "orchard",
|
||||||
not(feature = "sapling"),
|
not(feature = "sapling"),
|
||||||
not(feature = "transparent-inputs")
|
not(feature = "transparent-inputs")
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 2);
|
assert_eq!(decoded_with_t.unknown_data.len(), 2);
|
||||||
|
|
||||||
// Sapling enabled
|
// Sapling enabled
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -1579,13 +1731,13 @@ mod tests {
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
feature = "transparent-inputs"
|
feature = "transparent-inputs"
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 1);
|
assert_eq!(decoded_with_t.unknown_data.len(), 1);
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(feature = "orchard"),
|
not(feature = "orchard"),
|
||||||
feature = "sapling",
|
feature = "sapling",
|
||||||
not(feature = "transparent-inputs")
|
not(feature = "transparent-inputs")
|
||||||
))]
|
))]
|
||||||
assert_eq!(decoded_with_t.unknown.len(), 2);
|
assert_eq!(decoded_with_t.unknown_data.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1616,7 +1768,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ua = uivk
|
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| {
|
.unwrap_or_else(|err| {
|
||||||
panic!(
|
panic!(
|
||||||
"unified address generation failed for account {}: {:?}",
|
"unified address generation failed for account {}: {:?}",
|
||||||
|
|
Loading…
Reference in New Issue