Skip to main content

zcash_client_backend/data_api/
defaults.rs

1//! Reference implementations of [`WalletRead`] and [`WalletWrite`] trait methods.
2//!
3//! This module provides standalone free functions that implement the canonical semantics of
4//! selected trait methods in terms of other, more primitive operations. Wallet backend authors
5//! who do not have a more efficient implementation available can delegate the corresponding
6//! trait method to the function provided here; backends that can do better (e.g. via an
7//! indexed database lookup) should implement the trait method directly.
8//!
9//! [`WalletRead`]: super::WalletRead
10//! [`WalletWrite`]: super::WalletWrite
11
12use zcash_keys::address::{Address, UnifiedAddress};
13use zcash_protocol::consensus;
14
15use super::{Account, WalletRead, error::FindAccountForAddressError};
16
17/// Returns `true` if `address` contains any receiver that matches one of the receivers of the
18/// given [`UnifiedAddress`].
19pub fn address_receiver_matches_ua<P: consensus::Parameters>(
20    address: &Address,
21    ua: &UnifiedAddress,
22    params: &P,
23) -> bool {
24    let zcash_address = address.to_zcash_address(params);
25    ua.as_understood_receivers()
26        .iter()
27        .any(|r| r.corresponds(&zcash_address))
28}
29
30/// Reference implementation of [`WalletRead::find_account_for_address`].
31///
32/// Any address whose shielded component can be represented as a [`UnifiedAddress`] — either
33/// directly, or by synthesizing a single-receiver UA around a bare Sapling address — is
34/// first resolved via UIVK algebra: each account's
35/// [`UnifiedIncomingViewingKey::decrypt_diversifiers`] is asked whether it could have derived
36/// any shielded receiver of the (possibly synthesized) UA. An account matches if at least
37/// one shielded receiver is attributable to it, which finds addresses the account could
38/// produce even if they have never been exposed. If more than one account matches,
39/// [`FindAccountForAddressError::UnifiedAddressConflict`] is returned.
40///
41/// If the algebraic step does not resolve a match (or the address has no shielded receiver,
42/// as is the case for transparent and TEX addresses), this falls back to a linear scan over
43/// each account's tracked addresses via [`WalletRead::list_addresses`], returning the first
44/// account that holds a matching address (as an exact match, or as a receiver embedded in a
45/// stored UA).
46///
47/// Backends that can answer this query more efficiently (e.g. via an indexed database
48/// lookup) should implement [`WalletRead::find_account_for_address`] directly rather than
49/// delegating to this function.
50///
51/// See [`WalletRead::find_account_for_address`] for the semantics of the return value.
52///
53/// [`UnifiedIncomingViewingKey`]: zcash_keys::keys::UnifiedIncomingViewingKey
54/// [`UnifiedIncomingViewingKey::decrypt_diversifiers`]: zcash_keys::keys::UnifiedIncomingViewingKey::decrypt_diversifiers
55pub fn find_account_for_address<W: WalletRead, P: consensus::Parameters>(
56    wallet: &W,
57    params: &P,
58    address: &Address,
59) -> Result<Option<W::AccountId>, FindAccountForAddressError<W::Error>> {
60    // Promote bare shielded addresses to a synthesized single-receiver UA so that every
61    // address with a shielded component is resolved through the same UIVK algebra. The
62    // synthesized UA is owned by this local binding; `shielded_ua` borrows from either the
63    // original address or this binding.
64    let synthesized_ua;
65    let shielded_ua: Option<&UnifiedAddress> = match address {
66        Address::Unified(ua) => Some(ua),
67        Address::Sapling(pa) => {
68            synthesized_ua = UnifiedAddress::from_receivers(
69                #[cfg(feature = "orchard")]
70                None,
71                Some(**pa),
72                None,
73                None,
74                None,
75            );
76            synthesized_ua.as_ref()
77        }
78        Address::Transparent(_) | Address::Tex(_) => None,
79    };
80
81    if let Some(ua) = shielded_ua {
82        let mut found_acc_id: Option<W::AccountId> = None;
83        for acc_id in wallet.get_account_ids()? {
84            let Some(account) = wallet.get_account(acc_id)? else {
85                continue;
86            };
87            if !account.uivk().decrypt_diversifiers(ua).is_empty() {
88                match found_acc_id {
89                    None => found_acc_id = Some(acc_id),
90                    Some(prev) if prev == acc_id => {}
91                    Some(_) => return Err(FindAccountForAddressError::UnifiedAddressConflict),
92                }
93            }
94        }
95        if found_acc_id.is_some() {
96            return Ok(found_acc_id);
97        }
98    }
99
100    // Fall back to a linear scan over each account's tracked addresses. This handles
101    // addresses with no shielded component (transparent and TEX) as well as UAs whose
102    // shielded receivers are not derivable from any account's UIVK but whose transparent
103    // receiver matches a stored address.
104    let zcash_address = address.to_zcash_address(params);
105    for acc_id in wallet.get_account_ids()? {
106        for addr_info in wallet.list_addresses(acc_id)? {
107            let stored = addr_info.address();
108            if stored == address {
109                return Ok(Some(acc_id));
110            }
111            if let Address::Unified(stored_ua) = stored
112                && stored_ua
113                    .as_understood_receivers()
114                    .iter()
115                    .any(|r| r.corresponds(&zcash_address))
116            {
117                return Ok(Some(acc_id));
118            }
119        }
120    }
121    Ok(None)
122}