diff --git a/src/init.cpp b/src/init.cpp index b4f485bec..e76f8a16d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1683,7 +1683,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!zaddr.has_value()) { return InitError(_("-mineraddress is not a valid " PACKAGE_NAME " address.")); } - auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true); + auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true, false); minerAddressInLocalWallet = ztxoSelector.has_value(); } if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) { diff --git a/src/keystore.cpp b/src/keystore.cpp index b8f67f97b..1af6a3218 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -389,6 +389,47 @@ CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) c return std::visit(FindUFVKId(*this), receiver); } +std::optional>> +CBasicKeyStore::GetUFVKMetadataForAddress(const libzcash::UnifiedAddress& addr) const +{ + std::optional ufvkId; + std::optional j; + bool jConflict = false; + for (const auto& receiver : addr) { + // skip unknown receivers + if (libzcash::HasKnownReceiverType(receiver)) { + auto tmp = GetUFVKMetadataForReceiver(receiver); + if (ufvkId.has_value() && tmp.has_value()) { + // If the unified address contains receivers that are associated with + // different UFVKs, we cannot return a singular value. + if (tmp.value().first != ufvkId.value()) { + return std::nullopt; + } + + if (tmp.value().second.has_value()) { + if (j.has_value()) { + if (tmp.value().second.value() != j.value()) { + jConflict = true; + j = std::nullopt; + } + } else if (!jConflict) { + j = tmp.value().second.value(); + } + } + } else if (tmp.has_value()) { + ufvkId = tmp.value().first; + j = tmp.value().second; + } + } + } + + if (ufvkId.has_value()) { + return std::make_pair(ufvkId.value(), j); + } else { + return std::nullopt; + } +} + std::optional CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const { std::optional result; diff --git a/src/keystore.h b/src/keystore.h index 925cc3a3b..10a421cc8 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -125,18 +125,23 @@ public: const libzcash::UnifiedAddress& ua) = 0; virtual std::optional GetUnifiedFullViewingKey( - const libzcash::UFVKId& keyId - ) const = 0; + const libzcash::UFVKId& keyId) const = 0; virtual std::optional>> GetUFVKMetadataForReceiver( - const libzcash::Receiver& receiver - ) const = 0; + const libzcash::Receiver& receiver) const = 0; - virtual std::optional - GetUFVKIdForViewingKey( - const libzcash::ViewingKey& vk - ) const = 0; + /** + * If all the receivers of the specified address correspond to a single + * UFVK, return that key's metadata. If all the receivers correspond to + * the same diversifier index, that diversifier index is also returned. + */ + virtual std::optional>> + GetUFVKMetadataForAddress( + const libzcash::UnifiedAddress& addr) const = 0; + + virtual std::optional GetUFVKIdForViewingKey( + const libzcash::ViewingKey& vk) const = 0; }; typedef std::map KeyMap; @@ -376,13 +381,34 @@ public: virtual std::optional>> GetUFVKMetadataForReceiver( - const libzcash::Receiver& receiver - ) const; + const libzcash::Receiver& receiver) const; - virtual std::optional - GetUFVKIdForViewingKey( - const libzcash::ViewingKey& vk - ) const; + std::optional GetUFVKForReceiver( + const libzcash::Receiver& receiver) const { + auto ufvkMeta = GetUFVKMetadataForReceiver(receiver); + if (ufvkMeta.has_value()) { + return GetUnifiedFullViewingKey(ufvkMeta.value().first); + } else { + return std::nullopt; + } + } + + virtual std::optional>> + GetUFVKMetadataForAddress( + const libzcash::UnifiedAddress& addr) const; + + std::optional GetUFVKForAddress( + const libzcash::UnifiedAddress& addr) const { + auto ufvkMeta = GetUFVKMetadataForAddress(addr); + if (ufvkMeta.has_value()) { + return GetUnifiedFullViewingKey(ufvkMeta.value().first); + } else { + return std::nullopt; + } + } + + virtual std::optional GetUFVKIdForViewingKey( + const libzcash::ViewingKey& vk) const; }; typedef std::vector > CKeyingMaterial; diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 08810aee4..84f8bf362 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -319,6 +319,25 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { } auto ovks = this->SelectOVKs(spendable); + auto allowChangeTypes = [&](const std::set& receiverTypes) { + for (ReceiverType rtype : receiverTypes) { + switch (rtype) { + case ReceiverType::P2PKH: + case ReceiverType::P2SH: + allowedChangeTypes.insert(OutputPool::Transparent); + break; + case ReceiverType::Sapling: + allowedChangeTypes.insert(OutputPool::Sapling); + break; + case ReceiverType::Orchard: + if (builder_.SupportsOrchard()) { + allowedChangeTypes.insert(OutputPool::Orchard); + } + break; + } + } + }; + std::visit(match { [&](const CKeyID& keyId) { allowedChangeTypes.insert(OutputPool::Transparent); @@ -368,34 +387,37 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { builder_.SendChangeTo(changeAddr.value(), ovks.first); } }, - [&](const libzcash::UnifiedFullViewingKey& fvk) { - auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), fvk); - auto changeAddr = zufvk.GetChangeAddress(); + [&](const libzcash::UnifiedAddress& ua) { + allowChangeTypes(ua.GetKnownReceiverTypes()); + + auto zufvk = pwalletMain->GetUFVKForAddress(ua); + if (!zufvk.has_value()) { + throw JSONRPCError( + RPC_WALLET_ERROR, + "Could not determine full viewing key for unified address."); + } + + auto changeAddr = zufvk.value().GetChangeAddress(allowedChangeTypes); if (!changeAddr.has_value()) { throw JSONRPCError( RPC_WALLET_ERROR, - "Could not generate a change address from the specified full viewing key "); + "Could not generate a change address from the inferred full viewing key."); + } + builder_.SendChangeTo(changeAddr.value(), ovks.first); + }, + [&](const libzcash::UnifiedFullViewingKey& fvk) { + allowChangeTypes(fvk.GetKnownReceiverTypes()); + auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), fvk); + auto changeAddr = zufvk.GetChangeAddress(allowedChangeTypes); + if (!changeAddr.has_value()) { + throw JSONRPCError( + RPC_WALLET_ERROR, + "Could not generate a change address from the specified full viewing key."); } builder_.SendChangeTo(changeAddr.value(), ovks.first); }, [&](const AccountZTXOPattern& acct) { - for (ReceiverType rtype : acct.GetReceiverTypes()) { - switch (rtype) { - case ReceiverType::P2PKH: - case ReceiverType::P2SH: - allowedChangeTypes.insert(OutputPool::Transparent); - break; - case ReceiverType::Sapling: - allowedChangeTypes.insert(OutputPool::Sapling); - break; - case ReceiverType::Orchard: - if (builder_.SupportsOrchard()) { - allowedChangeTypes.insert(OutputPool::Orchard); - } - break; - } - } - + allowChangeTypes(acct.GetReceiverTypes()); auto changeAddr = pwalletMain->GenerateChangeAddressForAccount( acct.GetAccountId(), allowedChangeTypes); @@ -477,7 +499,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { }, [&](const libzcash::SaplingPaymentAddress& addr) { auto value = r.amount; - auto memo = get_memo_from_hex_string(r.memo.has_value() ? r.memo.value() : ""); + auto memo = get_memo_from_hex_string(r.memo.value_or("")); builder_.AddSaplingOutput(ovks.second, addr, value, memo); }, @@ -553,15 +575,27 @@ std::pair AsyncRPCOperation_sendmany::SelectOVKs(const Spendab if (!spendable.orchardNoteMetadata.empty()) { std::optional fvk; std::visit(match { - [&](const libzcash::UnifiedFullViewingKey& ufvk) { + [&](const UnifiedAddress& addr) { + auto ufvk = pwalletMain->GetUFVKForAddress(addr); + // This is safe because spending key checks will have ensured that we + // have a UFVK corresponding to this address, and Orchard notes will + // not have been selected if the UFVK does not contain an Orchard key. + fvk = ufvk.value().GetOrchardKey().value(); + }, + [&](const UnifiedFullViewingKey& ufvk) { + // Orchard notes will not have been selected if the UFVK does not contain + // an Orchard key. fvk = ufvk.GetOrchardKey().value(); }, [&](const AccountZTXOPattern& acct) { + // By definition, we have a UFVK for every known account. auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()); + // Orchard notes will not have been selected if the UFVK does not contain + // an Orchard key. fvk = ufvk.value().GetOrchardKey().value(); }, [&](const auto& other) { - throw std::runtime_error("unreachable"); + throw std::runtime_error("SelectOVKs: Selector cannot select Orchard notes."); } }, this->ztxoSelector_.GetPattern()); assert(fvk.has_value()); @@ -576,12 +610,27 @@ std::pair AsyncRPCOperation_sendmany::SelectOVKs(const Spendab assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)); dfvk = extsk.ToXFVK(); }, + [&](const UnifiedAddress& addr) { + auto ufvk = pwalletMain->GetUFVKForAddress(addr); + // This is safe because spending key checks will have ensured that we + // have a UFVK corresponding to this address, and Sapling notes will + // not have been selected if the UFVK does not contain a Sapling key. + dfvk = ufvk.value().GetSaplingKey().value(); + }, + [&](const UnifiedFullViewingKey& ufvk) { + // Sapling notes will not have been selected if the UFVK does not contain + // a Sapling key. + dfvk = ufvk.GetSaplingKey().value(); + }, [&](const AccountZTXOPattern& acct) { + // By definition, we have a UFVK for every known account. auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()); + // Sapling notes will not have been selected if the UFVK does not contain + // a Sapling key. dfvk = ufvk.value().GetSaplingKey().value(); }, [&](const auto& other) { - throw std::runtime_error("unreachable"); + throw std::runtime_error("SelectOVKs: Selector cannot select Sapling notes."); } }, this->ztxoSelector_.GetPattern()); assert(dfvk.has_value()); @@ -598,16 +647,31 @@ std::pair AsyncRPCOperation_sendmany::SelectOVKs(const Spendab [&](const CScriptID& keyId) { tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey(); }, + [&](const UnifiedAddress& addr) { + // This is safe because spending key checks will have ensured that we + // have a UFVK corresponding to this address, and transparent UTXOs will + // not have been selected if the UFVK does not contain a transparent key. + auto ufvk = pwalletMain->GetUFVKForAddress(addr); + tfvk = ufvk.value().GetTransparentKey().value(); + }, + [&](const UnifiedFullViewingKey& ufvk) { + // Transparent UTXOs will not have been selected if the UFVK does not contain + // a transparent key. + tfvk = ufvk.GetTransparentKey().value(); + }, [&](const AccountZTXOPattern& acct) { if (acct.GetAccountId() == ZCASH_LEGACY_ACCOUNT) { tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey(); } else { + // By definition, we have a UFVK for every known account. auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()).value(); + // Transparent UTXOs will not have been selected if the UFVK does not contain + // a transparent key. tfvk = ufvk.GetTransparentKey().value(); } }, [&](const auto& other) { - throw std::runtime_error("unreachable"); + throw std::runtime_error("SelectOVKs: Selector cannot select transparent UTXOs."); } }, this->ztxoSelector_.GetPattern()); assert(tfvk.has_value()); diff --git a/src/wallet/gtest/test_note_selection.cpp b/src/wallet/gtest/test_note_selection.cpp index b4438c283..4ed7bf843 100644 --- a/src/wallet/gtest/test_note_selection.cpp +++ b/src/wallet/gtest/test_note_selection.cpp @@ -6,6 +6,8 @@ #include "zcash/address/sapling.hpp" #include "zcash/address/sprout.hpp" +using namespace libzcash; + void PrintTo(const OutputPool& pool, std::ostream* os) { switch (pool) { case OutputPool::Orchard: *os << "Orchard"; break; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 97c7a0f1c..baad7e242 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3707,7 +3707,7 @@ UniValue z_getbalance(const UniValue& params, bool fHelp) nBalance = getBalanceZaddr(addr, nMinDepth, INT_MAX, false); }, [&](const libzcash::UnifiedAddress& addr) { - auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true); + auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true, false); if (!selector.has_value()) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, @@ -4525,7 +4525,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) "Invalid from address: should be a taddr, zaddr, UA, or the string 'ANY_TADDR'."); } - auto ztxoSelectorOpt = pwalletMain->ZTXOSelectorForAddress(decoded.value(), true); + auto ztxoSelectorOpt = pwalletMain->ZTXOSelectorForAddress(decoded.value(), true, false); if (!ztxoSelectorOpt.has_value()) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index b5e181ccf..ac61ac966 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -799,7 +799,7 @@ void CheckHaveAddr(const std::optional& addr) { auto addr_of_type = std::get_if(&(addr.value())); BOOST_ASSERT(addr_of_type != nullptr); - BOOST_CHECK(pwalletMain->ZTXOSelectorForAddress(*addr_of_type, true).has_value()); + BOOST_CHECK(pwalletMain->ZTXOSelectorForAddress(*addr_of_type, true, false).has_value()); } BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) { @@ -1233,7 +1233,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) // there are no utxos to spend { - auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true).value(); + auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true, false).value(); TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain); std::vector recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") }; std::shared_ptr operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1)); @@ -1245,7 +1245,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) // there are no unspent notes to spend { - auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value(); + auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true, false).value(); TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain); std::vector recipients = { SendManyRecipient(std::nullopt, taddr1, 100*COIN, "DEADBEEF") }; std::shared_ptr operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1)); @@ -1257,7 +1257,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) // get_memo_from_hex_string()) { - auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value(); + auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true, false).value(); TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain); std::vector recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") }; std::shared_ptr operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1)); @@ -1358,7 +1358,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling) auto builder = TransactionBuilder(consensusParams, nextBlockHeight, std::nullopt, pwalletMain); mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight); - auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true).value(); + auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true, false).value(); std::vector recipients = { SendManyRecipient(std::nullopt, pa, 1*COIN, "ABCD") }; std::shared_ptr operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0)); std::shared_ptr ptr = std::dynamic_pointer_cast (operation); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 30d71ca94..80c1f1933 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1644,7 +1644,11 @@ std::optional CWallet::ZTXOSelectorForAccount( } } -std::optional CWallet::ZTXOSelectorForAddress(const libzcash::PaymentAddress& addr, bool requireSpendingKey) const { +std::optional CWallet::ZTXOSelectorForAddress( + const libzcash::PaymentAddress& addr, + bool requireSpendingKey, + bool allowAddressLinkability) const +{ auto self = this; std::optional pattern = std::nullopt; std::visit(match { @@ -1669,17 +1673,23 @@ std::optional CWallet::ZTXOSelectorForAddress(const libzcash::Paym } }, [&](const libzcash::UnifiedAddress& ua) { - auto ufvkId = this->FindUnifiedFullViewingKey(ua); - if (ufvkId.has_value()) { + auto ufvkMeta = this->GetUFVKMetadataForAddress(ua); + if (ufvkMeta.has_value()) { // TODO: at present, the `false` value for the `requireSpendingKey` argument // is not respected for unified addresses, because we have no notion of // an account for which we do not control the spending key. An alternate // approach would be to use the UFVK directly in the case that we cannot // determine a local account. - auto accountId = this->GetUnifiedAccountId(ufvkId.value()); + auto accountId = this->GetUnifiedAccountId(ufvkMeta.value().first); if (accountId.has_value()) { - pattern = AccountZTXOPattern(accountId.value(), ua.GetKnownReceiverTypes()); + if (allowAddressLinkability) { + pattern = AccountZTXOPattern(accountId.value(), ua.GetKnownReceiverTypes()); + } else { + pattern = ua; + } } + } else { + pattern = ua; } } }, addr); @@ -1762,6 +1772,12 @@ std::optional CWallet::FindAccountForSelector(const ZTXOSel result = self->GetUnifiedAccountId(ufvkid.value()); } }, + [&](const libzcash::UnifiedAddress& addr) { + auto meta = GetUFVKMetadataForAddress(addr); + if (meta.has_value()) { + result = self->GetUnifiedAccountId(meta.value().first); + } + }, [&](const libzcash::UnifiedFullViewingKey& vk) { result = self->GetUnifiedAccountId(vk.GetKeyID(Params())); }, @@ -1796,6 +1812,27 @@ bool CWallet::SelectorMatchesAddress( [&](const libzcash::SproutViewingKey& vk) { return false; }, [&](const libzcash::SaplingPaymentAddress& addr) { return false; }, [&](const libzcash::SaplingExtendedFullViewingKey& extfvk) { return false; }, + [&](const libzcash::UnifiedAddress& uaSelector) { + // for a UA selector when matching transparent addresses, we only match addresses + // that explicitly appear as receivers in the UA. + for (const auto& receiver : uaSelector) { + bool matches = std::visit(match { + [&](const libzcash::OrchardRawAddress& orchardAddr) { return false; }, + [&](const libzcash::SaplingPaymentAddress& saplingAddr) { return false; }, + [&](const libzcash::UnknownReceiver& receiver) { return false; }, + [&](const CScriptID& scriptId) { + CTxDestination scriptIdDest = scriptId; + return address == scriptIdDest; + }, + [&](const CKeyID& keyId) { + CTxDestination keyIdDest = keyId; + return address == keyIdDest; + } + }, receiver); + if (matches) return true; + } + return false; + }, [&](const libzcash::UnifiedFullViewingKey& ufvk) { std::optional>> meta; std::visit(match { @@ -1854,6 +1891,19 @@ bool CWallet::SelectorMatchesAddress( auto addr = extfvk.Address(j); return addr.has_value() && addr.value() == a0; }, + [&](const libzcash::UnifiedAddress& ua) { + const auto a0Meta = self->GetUFVKMetadataForReceiver(a0); + auto saplingReceiver = ua.GetSaplingReceiver(); + if (saplingReceiver.has_value()) { + const auto uaMeta = self->GetUFVKMetadataForReceiver(saplingReceiver.value()); + // if the Sapling address is derived from any UFVK corresponding to + // the Sapling component of the unified address, we consider that a + // match + return a0Meta.has_value() && uaMeta.has_value() && + a0Meta.value().first == uaMeta.value().first; + } + return false; + }, [&](const libzcash::UnifiedFullViewingKey& ufvk) { auto saplingKey = ufvk.GetSaplingKey(); if (saplingKey.has_value()) { @@ -2067,6 +2117,22 @@ SpendableInputs CWallet::FindSpendableInputs( if (selectOrchard) { // for Orchard, we select both the internal and external IVKs. auto orchardIvks = std::visit(match { + [&](const libzcash::UnifiedAddress& selectorUA) -> std::vector { + auto orchardReceiver = selectorUA.GetOrchardReceiver(); + if (orchardReceiver.has_value()) { + auto meta = GetUFVKMetadataForReceiver(orchardReceiver.value()); + if (meta.has_value()) { + auto ufvk = GetUnifiedFullViewingKey(meta.value().first); + if (ufvk.has_value()) { + auto fvk = ufvk->GetOrchardKey(); + if (fvk.has_value()) { + return {fvk->ToIncomingViewingKey(), fvk->ToInternalIncomingViewingKey()}; + } + } + } + } + return {}; + }, [&](const libzcash::UnifiedFullViewingKey& ufvk) -> std::vector { auto fvk = ufvk.GetOrchardKey(); if (fvk.has_value()) { @@ -6585,30 +6651,6 @@ void CWallet::GetFilteredNotes( } } -std::optional CWallet::FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const { - std::optional ufvkId; - for (const auto& receiver : addr) { - // skip unknown receivers - if (libzcash::HasKnownReceiverType(receiver)) { - auto ufvkPair = this->GetUFVKMetadataForReceiver(receiver); - if (ufvkPair.has_value()) { - if (ufvkId.has_value()) { - // Check that all the receivers belong to the same ufvk; if not, - // we cannot identify a unique UFVK for the address. - if (ufvkPair.value().first != ufvkId.value()) { - return std::nullopt; - } - } else { - ufvkId = ufvkPair.value().first; - } - } else { - return std::nullopt; - } - } - } - return ufvkId; -} - std::optional CWallet::GetUnifiedAccountId(const libzcash::UFVKId& ufvkId) const { auto addrMetaIt = mapUfvkAddressMetadata.find(ufvkId); if (addrMetaIt != mapUfvkAddressMetadata.end()) { @@ -6618,10 +6660,6 @@ std::optional CWallet::GetUnifiedAccountId(const libzcash:: } } -std::optional CWallet::FindUFVKByReceiver(const libzcash::Receiver& receiver) const { - return std::visit(UFVKForReceiver(*this), receiver); -} - std::optional CWallet::FindUnifiedAddressByReceiver(const Receiver& receiver) const { return std::visit(UnifiedAddressForReceiver(*this), receiver); } @@ -6655,7 +6693,7 @@ bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAdd } bool PaymentAddressBelongsToWallet::operator()(const libzcash::UnifiedAddress &uaddr) const { - return m_wallet->FindUnifiedFullViewingKey(uaddr).has_value(); + return m_wallet->GetUFVKForAddress(uaddr).has_value(); } // GetSourceForPaymentAddress @@ -6748,13 +6786,13 @@ PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::Sapl PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::UnifiedAddress &uaddr) const { auto hdChain = m_wallet->GetMnemonicHDChain(); - auto ufvkid = m_wallet->FindUnifiedFullViewingKey(uaddr); + auto ufvkid = m_wallet->GetUFVKMetadataForAddress(uaddr); if (ufvkid.has_value()) { // Look through the UFVKs that we have generated, and confirm that the // seed fingerprint for the key we find for the ufvkid corresponds to // the wallet's mnemonic seed. for (const auto& [k, v] : m_wallet->mapUnifiedAccountKeys) { - if (v == ufvkid.value() && hdChain.has_value() && k.first == hdChain.value().GetSeedFingerprint()) { + if (v == ufvkid.value().first && hdChain.has_value() && k.first == hdChain.value().GetSeedFingerprint()) { return PaymentAddressSource::MnemonicHDSeed; } } @@ -6809,9 +6847,7 @@ std::optional GetViewingKeyForPaymentAddress::operator()( std::optional GetViewingKeyForPaymentAddress::operator()( const libzcash::UnifiedAddress &uaddr) const { - auto ufvkid = m_wallet->FindUnifiedFullViewingKey(uaddr); - if (!ufvkid.has_value()) return std::nullopt; - auto zufvk = m_wallet->GetUnifiedFullViewingKey(ufvkid.value()); + auto zufvk = m_wallet->GetUFVKForAddress(uaddr); if (!zufvk.has_value()) return std::nullopt; return zufvk.value().ToFullViewingKey(); } @@ -7061,6 +7097,9 @@ bool ZTXOSelector::SelectsTransparent() const { [](const libzcash::SproutViewingKey& vk) { return false; }, [](const libzcash::SaplingPaymentAddress& addr) { return false; }, [](const libzcash::SaplingExtendedFullViewingKey& vk) { return false; }, + [](const libzcash::UnifiedAddress& ua) { + return ua.GetP2PKHReceiver().has_value() || ua.GetP2SHReceiver().has_value(); + }, [](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetTransparentKey().has_value(); }, [](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); } }, this->pattern); @@ -7076,6 +7115,7 @@ bool ZTXOSelector::SelectsSapling() const { return std::visit(match { [](const libzcash::SaplingPaymentAddress& addr) { return true; }, [](const libzcash::SaplingExtendedSpendingKey& extfvk) { return true; }, + [](const libzcash::UnifiedAddress& ua) { return ua.GetSaplingReceiver().has_value(); }, [](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetSaplingKey().has_value(); }, [](const AccountZTXOPattern& acct) { return acct.IncludesSapling(); }, [](const auto& addr) { return false; } @@ -7083,6 +7123,7 @@ bool ZTXOSelector::SelectsSapling() const { } bool ZTXOSelector::SelectsOrchard() const { return std::visit(match { + [](const libzcash::UnifiedAddress& ua) { return ua.GetOrchardReceiver().has_value(); }, [](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetOrchardKey().has_value(); }, [](const AccountZTXOPattern& acct) { return acct.IncludesOrchard(); }, [](const auto& addr) { return false; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 19d23f711..a0553ef1c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -778,6 +778,7 @@ typedef std::variant< libzcash::SproutViewingKey, libzcash::SaplingPaymentAddress, libzcash::SaplingExtendedFullViewingKey, + libzcash::UnifiedAddress, libzcash::UnifiedFullViewingKey, AccountZTXOPattern> ZTXOPattern; @@ -805,17 +806,6 @@ public: bool SelectsOrchard() const; }; -/** - * An enumeration of the fund pools for which a transaction may produce outputs. - * It is sorted in descending preference order, so that when iterating over a - * set of output pools the most-preferred pool is selected first. - */ -enum class OutputPool { - Orchard, - Sapling, - Transparent, -}; - class SpendableInputs { private: bool limited = false; @@ -840,7 +830,7 @@ public: bool LimitToAmount( CAmount amount, CAmount dustThreshold, - std::set recipientPools); + std::set recipientPools); /** * Compute the total ZEC amount of spendable inputs. @@ -1335,7 +1325,8 @@ public: */ std::optional ZTXOSelectorForAddress( const libzcash::PaymentAddress& addr, - bool requireSpendingKey) const; + bool requireSpendingKey, + bool allowAddressLinkability) const; /** * Returns the ZTXO selector for the specified viewing key, if that key @@ -1377,7 +1368,7 @@ public: */ std::optional GenerateChangeAddressForAccount( libzcash::AccountId accountId, - std::set changeOptions); + std::set changeOptions); SpendableInputs FindSpendableInputs( ZTXOSelector paymentSource, @@ -1595,11 +1586,16 @@ public: //! failures in reconstructing the cache. bool LoadCaches(); - std::optional FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const; std::optional GetUnifiedAccountId(const libzcash::UFVKId& ufvkId) const; - std::optional FindUFVKByReceiver(const libzcash::Receiver& receiver) const; - std::optional FindUnifiedAddressByReceiver(const libzcash::Receiver& receiver) const; + /** + * Reconstructs a unified address by determining the UFVK that the receiver + * is associated with, combined with the set of receiver types that were + * associated with the diversifier index that the provided receiver + * corresponds to. + */ + std::optional FindUnifiedAddressByReceiver( + const libzcash::Receiver& receiver) const; /** * Increment the next transaction order id diff --git a/src/zcash/address/unified.cpp b/src/zcash/address/unified.cpp index 4718e5fe3..b3ea37481 100644 --- a/src/zcash/address/unified.cpp +++ b/src/zcash/address/unified.cpp @@ -188,14 +188,15 @@ std::optional ZcashdUnifiedFullViewingKey::GetChangeAddress(co return addr; } -std::optional ZcashdUnifiedFullViewingKey::GetChangeAddress() const { - if (orchardKey.has_value()) { +std::optional ZcashdUnifiedFullViewingKey::GetChangeAddress( + const std::set& allowedPools) const { + if (orchardKey.has_value() && allowedPools.count(OutputPool::Orchard) > 0) { return orchardKey.value().GetChangeAddress(); } - if (saplingKey.has_value()) { + if (saplingKey.has_value() && allowedPools.count(OutputPool::Sapling) > 0) { return saplingKey.value().GetChangeAddress(); } - if (transparentKey.has_value()) { + if (transparentKey.has_value() && allowedPools.count(OutputPool::Transparent) > 0) { auto changeAddr = transparentKey.value().GetChangeAddress(diversifier_index_t(0)); if (changeAddr.has_value()) { return changeAddr.value(); diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h index 61ae43dc7..03a455915 100644 --- a/src/zcash/address/unified.h +++ b/src/zcash/address/unified.h @@ -27,6 +27,17 @@ enum class ReceiverType: uint32_t { Orchard = 0x03 }; +/** + * An enumeration of the fund pools for which a transaction may produce outputs. + * It is sorted in descending preference order, so that when iterating over a + * set of output pools the most-preferred pool is selected first. + */ +enum class OutputPool { + Orchard, + Sapling, + Transparent, +}; + enum class UnifiedAddressGenerationError { ShieldedReceiverNotFound, ReceiverTypeNotAvailable, @@ -226,7 +237,7 @@ public: * *any* shielded pool) in which case the change address returned will be * associated with diversifier index 0. */ - std::optional GetChangeAddress() const; + std::optional GetChangeAddress(const std::set& allowedPools) const; UnifiedFullViewingKey ToFullViewingKey() const;