diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 4af0c63aa..bcf277197 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -62,7 +62,8 @@ use zcash_client_backend::{ use zcash_keys::{ address::UnifiedAddress, keys::{ - AddressGenerationError, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey, + AddressGenerationError, ReceiverRequirement, UnifiedAddressRequest, UnifiedFullViewingKey, + UnifiedSpendingKey, }, }; use zcash_primitives::{ @@ -154,14 +155,14 @@ pub(crate) const SAPLING_TABLES_PREFIX: &str = "sapling"; pub(crate) const ORCHARD_TABLES_PREFIX: &str = "orchard"; #[cfg(not(feature = "orchard"))] -pub(crate) const UA_ORCHARD: bool = false; +pub(crate) const UA_ORCHARD: ReceiverRequirement = ReceiverRequirement::Omit; #[cfg(feature = "orchard")] -pub(crate) const UA_ORCHARD: bool = true; +pub(crate) const UA_ORCHARD: ReceiverRequirement = ReceiverRequirement::Require; #[cfg(not(feature = "transparent-inputs"))] -pub(crate) const UA_TRANSPARENT: bool = false; +pub(crate) const UA_TRANSPARENT: ReceiverRequirement = ReceiverRequirement::Omit; #[cfg(feature = "transparent-inputs")] -pub(crate) const UA_TRANSPARENT: bool = true; +pub(crate) const UA_TRANSPARENT: ReceiverRequirement = ReceiverRequirement::Require; /// Unique identifier for a specific account tracked by a [`WalletDb`]. /// diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index c9c47562b..99db16e47 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -292,10 +292,7 @@ pub(crate) fn seed_matches_derived_account( let usk = UnifiedSpendingKey::from_seed(params, &seed.expose_secret()[..], account_index) .map_err(|_| SqliteClientError::KeyDerivationError(account_index))?; - let (seed_addr, _) = usk.to_unified_full_viewing_key().default_address(Some( - UnifiedAddressRequest::all().expect("At least one supported pool feature is enabled."), - ))?; - + let (seed_addr, _) = usk.to_unified_full_viewing_key().default_address(None)?; let (uivk_addr, _) = uivk.default_address(None)?; #[cfg(not(feature = "orchard"))] diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 80947101b..d9d25c2b9 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -443,7 +443,10 @@ mod tests { use zcash_keys::{ address::Address, encoding::{encode_extended_full_viewing_key, encode_payment_address}, - keys::{sapling, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey}, + keys::{ + sapling, ReceiverRequirement::*, UnifiedAddressRequest, UnifiedFullViewingKey, + UnifiedSpendingKey, + }, }; use zcash_primitives::transaction::{TransactionData, TxVersion}; use zcash_protocol::consensus::{self, BlockHeight, BranchId, Network, NetworkConstants}; @@ -984,7 +987,7 @@ 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(Omit, Require, UA_TRANSPARENT); let address_str = Address::Unified( ufvk.default_address(Some(ua_request)) .expect("A valid default address exists for the UFVK") @@ -1111,7 +1114,7 @@ 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(Omit, Require, Require); db_data .get_next_available_address(account_id, Some(ua_request)) .unwrap() diff --git a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs index 5b092e9bc..a7403c76d 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs @@ -393,7 +393,8 @@ mod tests { #[cfg(feature = "transparent-inputs")] fn migrate_from_wm2() { use ::transparent::keys::NonHardenedChildIndex; - use zcash_keys::keys::UnifiedAddressRequest; + use zcash_client_backend::keys::UnifiedAddressRequest; + use zcash_keys::keys::ReceiverRequirement::*; use zcash_protocol::value::Zatoshis; use crate::UA_TRANSPARENT; @@ -441,8 +442,8 @@ mod tests { let ufvk = usk.to_unified_full_viewing_key(); let (ua, _) = ufvk .default_address(Some(UnifiedAddressRequest::unsafe_new( - false, - true, + Omit, + Require, UA_TRANSPARENT, ))) .expect("A valid default address exists for the UFVK"); diff --git a/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs b/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs index a1edabde5..64b057a47 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs @@ -3,8 +3,12 @@ use std::collections::HashSet; use rusqlite::{named_params, Transaction}; use schemerz_rusqlite::RusqliteMigration; use uuid::Uuid; -use zcash_keys::{address::Address, keys::UnifiedFullViewingKey}; -use zcash_keys::{address::UnifiedAddress, encoding::AddressCodec, keys::UnifiedAddressRequest}; + +use zcash_keys::{ + address::{Address, UnifiedAddress}, + encoding::AddressCodec, + keys::{ReceiverRequirement::*, UnifiedAddressRequest, UnifiedFullViewingKey}, +}; use zcash_protocol::consensus; use zip32::{AccountId, DiversifierIndex}; @@ -87,7 +91,7 @@ impl RusqliteMigration for Migration

{ )); }; let (expected_address, idx) = ufvk.default_address(Some( - UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT), + UnifiedAddressRequest::unsafe_new(Omit, Require, UA_TRANSPARENT), ))?; if decoded_address != expected_address { return Err(WalletMigrationError::CorruptedData(format!( @@ -159,7 +163,7 @@ impl RusqliteMigration for Migration

{ )?; let (address, d_idx) = ufvk.default_address(Some( - UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT), + UnifiedAddressRequest::unsafe_new(Omit, Require, UA_TRANSPARENT), ))?; insert_address(transaction, &self.params, account, d_idx, &address)?; } diff --git a/zcash_client_sqlite/src/wallet/init/migrations/ensure_orchard_ua_receiver.rs b/zcash_client_sqlite/src/wallet/init/migrations/ensure_orchard_ua_receiver.rs index 66d6a5201..633fc37ae 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/ensure_orchard_ua_receiver.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/ensure_orchard_ua_receiver.rs @@ -5,7 +5,9 @@ use rusqlite::named_params; use schemerz_rusqlite::RusqliteMigration; use uuid::Uuid; -use zcash_keys::keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedIncomingViewingKey}; +use zcash_keys::keys::{ + ReceiverRequirement::*, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedIncomingViewingKey, +}; use zcash_protocol::consensus; use super::orchard_received_notes; @@ -64,7 +66,7 @@ impl RusqliteMigration for Migration

{ }; let (default_addr, diversifier_index) = uivk.default_address(Some( - UnifiedAddressRequest::unsafe_new(UA_ORCHARD, true, UA_TRANSPARENT), + UnifiedAddressRequest::unsafe_new(UA_ORCHARD, Require, UA_TRANSPARENT), ))?; let mut di_be = *diversifier_index.as_bytes(); @@ -90,8 +92,10 @@ mod tests { use secrecy::SecretVec; use tempfile::NamedTempFile; - use zcash_keys::address::Address; - use zcash_keys::keys::{UnifiedAddressRequest, UnifiedSpendingKey}; + use zcash_keys::{ + address::Address, + keys::{ReceiverRequirement::*, UnifiedAddressRequest, UnifiedSpendingKey}, + }; use zcash_protocol::consensus::Network; use crate::{ @@ -139,8 +143,8 @@ mod tests { let (addr, diversifier_index) = ufvk .default_address(Some(UnifiedAddressRequest::unsafe_new( - false, - true, + Omit, + Require, UA_TRANSPARENT, ))) .unwrap(); @@ -168,7 +172,7 @@ mod tests { Ok(Address::Unified(ua)) => { assert!(!ua.has_orchard()); assert!(ua.has_sapling()); - assert_eq!(ua.has_transparent(), UA_TRANSPARENT); + assert_eq!(ua.has_transparent(), UA_TRANSPARENT == Require); } other => panic!("Unexpected result from address decoding: {:?}", other), } @@ -184,9 +188,9 @@ mod tests { Ok(Address::decode(&db_data.params, &row.get::<_, String>(0)?).unwrap()) }) { Ok(Address::Unified(ua)) => { - assert_eq!(ua.has_orchard(), UA_ORCHARD); + assert_eq!(ua.has_orchard(), UA_ORCHARD == Require); assert!(ua.has_sapling()); - assert_eq!(ua.has_transparent(), UA_TRANSPARENT); + assert_eq!(ua.has_transparent(), UA_TRANSPARENT == Require); } other => panic!("Unexpected result from address decoding: {:?}", other), } diff --git a/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs b/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs index 895f1b08b..305b0f5eb 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs @@ -8,7 +8,7 @@ use uuid::Uuid; use zcash_keys::{ address::Address, - keys::{UnifiedAddressRequest, UnifiedSpendingKey}, + keys::{ReceiverRequirement::*, UnifiedAddressRequest, UnifiedSpendingKey}, }; use zcash_protocol::{consensus, PoolType}; use zip32::AccountId; @@ -85,7 +85,7 @@ impl RusqliteMigration for Migration

{ // 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(Omit, Require, 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 diff --git a/zcash_keys/CHANGELOG.md b/zcash_keys/CHANGELOG.md index 33b00bb7c..cabedc12a 100644 --- a/zcash_keys/CHANGELOG.md +++ b/zcash_keys/CHANGELOG.md @@ -9,9 +9,17 @@ and this library adheres to Rust's notion of ### Added - `no-std` compatibility (`alloc` is required). A default-enabled `std` feature flag has been added gating the `std::error::Error` usage. +- `zcash_keys::keys::ReceiverRequirement` ### Changed - Migrated to `nonempty 0.11` +- `zcash_keys::keys::UnifiedAddressRequest` has been substantially modified; + instead of a collection of boolean flags, it is now a collection of + `ReceiverRequirement` values that describe how addresses may be constructed + in the case that keys for a particular protocol are absent or it is not + possible to generate a specific receiver at a given diversifier index. + Behavior of methods that accept a `UnifiedAddressRequest` have been modified + accordingly. ## [0.6.0] - 2024-12-16 diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index 123dd20aa..5553ab717 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -552,72 +552,126 @@ impl fmt::Display for AddressGenerationError { #[cfg(feature = "std")] impl std::error::Error for AddressGenerationError {} +/// An enumeration of the ways in which a receiver may be requested to be present in a generated +/// [`UnifiedAddress`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ReceiverRequirement { + /// A receiver of the associated type is required to be present in the generated + /// `[UnifiedAddress`], and if it is not possible to generate a receiver of this type, the + /// address generation method should return an error. When calling [`Self::intersect`], this + /// variant will be preferred over [`ReceiverRequirement::Allow`]. + Require, + /// The associated receiver should be included, if a corresponding item exists in the IVK from + /// which the address is being derived and derivation of the receiver succeeds at the given + /// diversifier index. + Allow, + /// No receiver of the associated type may be included in the generated [`UnifiedAddress`] + /// under any circumstances. When calling [`Self::intersect`], this variant will be preferred + /// over [`ReceiverRequirement::Allow`]. + Omit, +} + +impl ReceiverRequirement { + /// Return the intersection of two requirements that chooses the stronger requirement, if one + /// exists. [`ReceiverRequirement::Require`] and [`ReceiverRequirement::Omit`] are + /// incompatible; attempting an intersection between these will return [`None`]. + pub fn intersect(self, other: Self) -> Option { + use ReceiverRequirement::*; + match (self, other) { + (Require, Omit) => None, + (Require, Require) => Some(Require), + (Require, Allow) => Some(Require), + (Allow, Require) => Some(Require), + (Allow, Allow) => Some(Allow), + (Allow, Omit) => Some(Omit), + (Omit, Require) => None, + (Omit, Allow) => Some(Omit), + (Omit, Omit) => Some(Omit), + } + } +} + /// Specification for how a unified address should be generated from a unified viewing key. #[derive(Clone, Copy, Debug)] pub struct UnifiedAddressRequest { - has_orchard: bool, - has_sapling: bool, - has_p2pkh: bool, + orchard: ReceiverRequirement, + sapling: ReceiverRequirement, + p2pkh: ReceiverRequirement, } 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 { - let has_shielded_receiver = has_orchard || has_sapling; - - if !has_shielded_receiver { + pub fn new( + orchard: ReceiverRequirement, + sapling: ReceiverRequirement, + p2pkh: ReceiverRequirement, + ) -> Option { + use ReceiverRequirement::*; + if orchard == Omit && sapling == Omit { None } else { Some(Self { - has_orchard, - has_sapling, - has_p2pkh, + orchard, + sapling, + p2pkh, }) } } - /// Constructs a new unified address request that includes a request for a receiver of each - /// type that is supported given the active feature flags. + /// Constructs a new unified address request that allows a receiver of each type that is + /// supported given the active feature flags. pub fn all() -> Option { - let _has_orchard = false; + use ReceiverRequirement::*; + let _orchard = Omit; #[cfg(feature = "orchard")] - let _has_orchard = true; + let _orchard = Allow; - let _has_sapling = false; + let _sapling = Omit; #[cfg(feature = "sapling")] - let _has_sapling = true; + let _sapling = Allow; - let _has_p2pkh = false; + let _p2pkh = Omit; #[cfg(feature = "transparent-inputs")] - let _has_p2pkh = true; + let _p2pkh = Allow; - Self::new(_has_orchard, _has_sapling, _has_p2pkh) + Self::new(_orchard, _sapling, _p2pkh) } - /// Constructs a new unified address request that includes only the receivers - /// that appear both in itself and a given other request. + /// Constructs a new unified address request that allows a receiver of each type. + pub const ALLOW_ALL: UnifiedAddressRequest = { + use ReceiverRequirement::*; + Self::unsafe_new(Allow, Allow, Allow) + }; + + /// Constructs a new unified address request that includes only the receivers that are allowed + /// both in itself and a given other request. Returns [`None`] if requirements are incompatible + /// or if no shielded receiver type is allowed. pub fn intersect(&self, other: &UnifiedAddressRequest) -> Option { - Self::new( - self.has_orchard && other.has_orchard, - self.has_sapling && other.has_sapling, - self.has_p2pkh && other.has_p2pkh, - ) + let orchard = self.orchard.intersect(other.orchard)?; + let sapling = self.sapling.intersect(other.sapling)?; + let p2pkh = self.p2pkh.intersect(other.p2pkh)?; + Self::new(orchard, sapling, p2pkh) } /// 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 `orchard` or `sapling` must be allowed. + pub const fn unsafe_new( + orchard: ReceiverRequirement, + sapling: ReceiverRequirement, + p2pkh: ReceiverRequirement, + ) -> Self { + use ReceiverRequirement::*; + if matches!(orchard, Omit) && matches!(sapling, Omit) { + panic!("At least one shielded receiver must be allowed.") } Self { - has_orchard, - has_sapling, - has_p2pkh, + orchard, + sapling, + p2pkh, } } } @@ -1120,78 +1174,93 @@ impl UnifiedIncomingViewingKey { } /// Attempts to derive the Unified Address for the given diversifier index and receiver types. - /// If `request` is None, the address should be derived to contain a receiver for each item in + /// If `request` is None, the address will be derived to contain a receiver for each item in /// this UFVK. /// - /// Returns `None` if the specified index does not produce a valid diversifier. + /// Returns an error if the this key does not produce a valid receiver for a required receiver + /// type at the given diversifier index. pub fn address( &self, _j: DiversifierIndex, request: Option, ) -> Result { + use ReceiverRequirement::*; + let request = request .or(self.to_address_request()) .ok_or(AddressGenerationError::ShieldedReceiverRequired)?; + #[cfg(feature = "orchard")] let mut orchard = None; - if request.has_orchard { + if request.orchard != Omit { #[cfg(not(feature = "orchard"))] - return Err(AddressGenerationError::ReceiverTypeNotSupported( - Typecode::Orchard, - )); + if request.orchard == Require { + return Err(AddressGenerationError::ReceiverTypeNotSupported( + Typecode::Orchard, + )); + } #[cfg(feature = "orchard")] if let Some(oivk) = &self.orchard { let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes()); orchard = Some(oivk.address_at(orchard_j)) - } else { + } else if request.orchard == Require { return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard)); } } #[cfg(feature = "sapling")] let mut sapling = None; - if request.has_sapling { + if request.sapling != Omit { #[cfg(not(feature = "sapling"))] - return Err(AddressGenerationError::ReceiverTypeNotSupported( - Typecode::Sapling, - )); + if request.sapling == Require { + return Err(AddressGenerationError::ReceiverTypeNotSupported( + Typecode::Sapling, + )); + } #[cfg(feature = "sapling")] if let Some(divk) = &self.sapling { // If a Sapling receiver type is requested, we must be able to construct an // address; if we're unable to do so, then no Unified Address exists at this // diversifier and we use `?` to early-return from this method. - sapling = Some( - divk.address_at(_j) - .ok_or(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))?, - ); - } else { + sapling = match (request.sapling, divk.address_at(_j)) { + (Require | Allow, Some(addr)) => Ok(Some(addr)), + (Require, None) => { + Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_j)) + } + _ => Ok(None), + }?; + } else if request.sapling == Require { return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling)); } } #[cfg(feature = "transparent-inputs")] let mut transparent = None; - if request.has_p2pkh { + if request.p2pkh != Omit { #[cfg(not(feature = "transparent-inputs"))] - return Err(AddressGenerationError::ReceiverTypeNotSupported( - Typecode::P2pkh, - )); + if request.p2pkh == Require { + return Err(AddressGenerationError::ReceiverTypeNotSupported( + Typecode::P2pkh, + )); + } #[cfg(feature = "transparent-inputs")] if let Some(tivk) = self.transparent.as_ref() { // If a transparent receiver type is requested, we must be able to construct an // address; if we're unable to do so, then no Unified Address exists at this // diversifier. - let transparent_j = to_transparent_child_index(_j) - .ok_or(AddressGenerationError::InvalidTransparentChildIndex(_j))?; - - transparent = Some( - tivk.derive_address(transparent_j) - .map_err(|_| AddressGenerationError::InvalidTransparentChildIndex(_j))?, - ); - } else { + let j = to_transparent_child_index(_j); + + transparent = match (request.p2pkh, j.and_then(|j| tivk.derive_address(j).ok())) { + (Require | Allow, Some(addr)) => Ok(Some(addr)), + (Require, None) => { + Err(AddressGenerationError::InvalidTransparentChildIndex(_j)) + } + _ => Ok(None), + }?; + } else if request.p2pkh == Require { return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh)); } } @@ -1208,23 +1277,33 @@ impl UnifiedIncomingViewingKey { .ok_or(AddressGenerationError::ShieldedReceiverRequired) } - /// Searches the diversifier space starting at diversifier index `j` for one which will - /// produce a valid diversifier, and return the Unified Address constructed using that - /// diversifier along with the index at which the valid diversifier was found. + /// Searches the diversifier space starting at diversifier index `j` for one which will produce + /// a valid address that conforms to the provided request, and returns that Unified Address + /// along with the index at which the valid diversifier was found. + /// + /// If [`None`] is specified for the `request` parameter, a default request that [`Require`]s a + /// receiver be present for each key item enabled by the feature flags in use will be used to + /// search the diversifier space. /// /// 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 enabled. + /// + /// [`Require`]: ReceiverRequirement::Require #[allow(unused_mut)] pub fn find_address( &self, mut j: DiversifierIndex, request: Option, ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> { + let request = request + .or_else(|| self.to_address_request()) + .ok_or(AddressGenerationError::ShieldedReceiverRequired)?; + // If we need to generate a transparent receiver, check that the user has not // specified an invalid transparent child index, from which we can never search to // find a valid index. #[cfg(feature = "transparent-inputs")] - if request.iter().any(|r| r.has_p2pkh) + if request.p2pkh == ReceiverRequirement::Require && self.transparent.is_some() && to_transparent_child_index(j).is_none() { @@ -1233,7 +1312,7 @@ impl UnifiedIncomingViewingKey { // Find a working diversifier and construct the associated address. loop { - let res = self.address(j, request); + let res = self.address(j, Some(request)); match res { Ok(ua) => { return Ok((ua, j)); @@ -1252,11 +1331,11 @@ impl UnifiedIncomingViewingKey { } /// Find the Unified Address corresponding to the smallest valid diversifier index, along with - /// that index. If `request` is None, the address should be derived to contain a receiver for - /// each item in this UFVK. + /// that index. If `request` is None, the address will be derived to contain a receiver for + /// each data item in this UFVK. /// - /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features - /// required to satisfy the unified address request are not properly enabled. + /// Returns an error if the this key does not produce a valid receiver for a required receiver + /// type at any diversifier index. pub fn default_address( &self, request: Option, @@ -1264,24 +1343,32 @@ impl UnifiedIncomingViewingKey { self.find_address(DiversifierIndex::new(), request) } - /// Constructs a [`UnifiedAddressRequest`] that includes the components of this UIVK. + /// Constructs a [`UnifiedAddressRequest`] that requires a receiver for each data item of this UIVK. + /// + /// Returns [`None`] if the resulting request would not include a shielded receiver. + #[allow(unused_mut)] pub fn to_address_request(&self) -> Option { + use ReceiverRequirement::*; + + let mut orchard = Omit; #[cfg(feature = "orchard")] - let has_orchard = self.orchard.is_some(); - #[cfg(not(feature = "orchard"))] - let has_orchard = false; + if self.orchard.is_some() { + orchard = Require; + } + let mut sapling = Omit; #[cfg(feature = "sapling")] - let has_sapling = self.sapling.is_some(); - #[cfg(not(feature = "sapling"))] - let has_sapling = false; + if self.sapling.is_some() { + sapling = Require; + } + let mut p2pkh = Omit; #[cfg(feature = "transparent-inputs")] - let has_p2pkh = self.transparent.is_some(); - #[cfg(not(feature = "transparent-inputs"))] - let has_p2pkh = false; + if self.transparent.is_some() { + p2pkh = Require; + } - UnifiedAddressRequest::new(has_orchard, has_sapling, has_p2pkh) + UnifiedAddressRequest::new(orchard, sapling, p2pkh) } } @@ -1499,7 +1586,7 @@ mod tests { fn ufvk_derivation() { use crate::keys::UnifiedAddressRequest; - use super::UnifiedSpendingKey; + use super::{ReceiverRequirement::*, UnifiedSpendingKey}; for tv in test_vectors::UNIFIED { let usk = UnifiedSpendingKey::from_seed( @@ -1522,7 +1609,7 @@ mod tests { let ua = ufvk .address( d_idx, - Some(UnifiedAddressRequest::unsafe_new(false, true, true)), + Some(UnifiedAddressRequest::unsafe_new(Omit, Require, Require)), ) .unwrap_or_else(|err| { panic!( @@ -1681,7 +1768,7 @@ mod tests { fn uivk_derivation() { use crate::keys::UnifiedAddressRequest; - use super::UnifiedSpendingKey; + use super::{ReceiverRequirement::*, UnifiedSpendingKey}; for tv in test_vectors::UNIFIED { let usk = UnifiedSpendingKey::from_seed( @@ -1706,7 +1793,7 @@ mod tests { let ua = uivk .address( d_idx, - Some(UnifiedAddressRequest::unsafe_new(false, true, true)), + Some(UnifiedAddressRequest::unsafe_new(Omit, Require, Require)), ) .unwrap_or_else(|err| { panic!(