Add UnifiedAddress variant to ZTXOSelector

When a user supplies a unified address as the source of funds
for z_sendmany, the previous behavior could have risked user
privacy by spending transparent UTXOs belonging to different
diversified addresses in the same transaction, thereby linking
those addresses, and consequently any diversified shielded
receivers of unified addresses containing those transparent
receivers.

This change limits selection of transparent UTXOs to only those
explicitly associated with the unified receivers of the provided
source UA. Also, if a UA is provided as the source, the behavior
is maintained that only shielded notes in pools corresponding to
the receivers of that UA will be selected.
This commit is contained in:
Kris Nuttycombe 2022-03-16 17:04:09 -06:00
parent 12b37d5a11
commit 9d8c20ed78
11 changed files with 288 additions and 69 deletions

View File

@ -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) {

View File

@ -389,6 +389,44 @@ CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) c
return std::visit(FindUFVKId(*this), receiver);
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
CBasicKeyStore::GetUFVKMetadataForAddress(const libzcash::UnifiedAddress& addr) const
{
std::optional<libzcash::UFVKId> ufvkId;
std::optional<libzcash::diversifier_index_t> j;
bool jConflict = false;
for (const auto& receiver : addr) {
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<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const
{
std::optional<libzcash::UFVKId> result;

View File

@ -125,18 +125,23 @@ public:
const libzcash::UnifiedAddress& ua) = 0;
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId
) const = 0;
const libzcash::UFVKId& keyId) const = 0;
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver
) const = 0;
const libzcash::Receiver& receiver) const = 0;
virtual std::optional<libzcash::UFVKId>
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<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForAddress(
const libzcash::UnifiedAddress& addr) const = 0;
virtual std::optional<libzcash::UFVKId> GetUFVKIdForViewingKey(
const libzcash::ViewingKey& vk) const = 0;
};
typedef std::map<CKeyID, CKey> KeyMap;
@ -376,13 +381,34 @@ public:
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver
) const;
const libzcash::Receiver& receiver) const;
virtual std::optional<libzcash::UFVKId>
GetUFVKIdForViewingKey(
const libzcash::ViewingKey& vk
) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> 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<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForAddress(
const libzcash::UnifiedAddress& addr) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> 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<libzcash::UFVKId> GetUFVKIdForViewingKey(
const libzcash::ViewingKey& vk) const;
};
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;

View File

@ -319,6 +319,25 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
}
auto ovks = this->SelectOVKs(spendable);
auto allowChangeTypes = [&](const std::set<ReceiverType>& 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<uint256, uint256> AsyncRPCOperation_sendmany::SelectOVKs(const Spendab
if (!spendable.orchardNoteMetadata.empty()) {
std::optional<OrchardFullViewingKey> 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<uint256, uint256> 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<uint256, uint256> 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());

View File

@ -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;

View File

@ -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,

View File

@ -799,7 +799,7 @@ void CheckHaveAddr(const std::optional<libzcash::PaymentAddress>& addr) {
auto addr_of_type = std::get_if<ADDR_TYPE>(&(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<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> 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<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, taddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> 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<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> 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<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, pa, 1*COIN, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);

View File

@ -1644,7 +1644,11 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAccount(
}
}
std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(const libzcash::PaymentAddress& addr, bool requireSpendingKey) const {
std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(
const libzcash::PaymentAddress& addr,
bool requireSpendingKey,
bool allowAddressLinkability) const
{
auto self = this;
std::optional<ZTXOPattern> pattern = std::nullopt;
std::visit(match {
@ -1678,8 +1682,14 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(const libzcash::Paym
// determine a local account.
auto accountId = this->GetUnifiedAccountId(ufvkId.value());
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,27 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
result = self->GetUnifiedAccountId(ufvkid.value());
}
},
[&](const libzcash::UnifiedAddress& addr) {
std::optional<libzcash::AccountId> singleAccount;
for (const auto& receiver : addr) {
auto meta = GetUFVKMetadataForReceiver(receiver);
if (meta.has_value()) {
auto tmp = self->GetUnifiedAccountId(meta.value().first);
if (singleAccount.has_value()) {
// If a UA corresponds to more than one account, we cannot return
// a sensible result, so we report it as corresponding to no
// account.
if (tmp.has_value() && singleAccount.value() != tmp.value()) {
singleAccount = std::nullopt;
break;
}
} else {
singleAccount = tmp;
}
}
}
result = singleAccount;
},
[&](const libzcash::UnifiedFullViewingKey& vk) {
result = self->GetUnifiedAccountId(vk.GetKeyID(Params()));
},
@ -1796,6 +1827,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<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta;
std::visit(match {
@ -1854,6 +1906,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 +2132,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<OrchardIncomingViewingKey> {
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<OrchardIncomingViewingKey> {
auto fvk = ufvk.GetOrchardKey();
if (fvk.has_value()) {
@ -7061,6 +7142,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 +7160,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 +7168,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; }

View File

@ -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<OutputPool> recipientPools);
std::set<libzcash::OutputPool> recipientPools);
/**
* Compute the total ZEC amount of spendable inputs.
@ -1335,7 +1325,8 @@ public:
*/
std::optional<ZTXOSelector> 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<libzcash::RecipientAddress> GenerateChangeAddressForAccount(
libzcash::AccountId accountId,
std::set<OutputPool> changeOptions);
std::set<libzcash::OutputPool> changeOptions);
SpendableInputs FindSpendableInputs(
ZTXOSelector paymentSource,

View File

@ -188,14 +188,15 @@ std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(co
return addr;
}
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress() const {
if (orchardKey.has_value()) {
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(
const std::set<OutputPool>& 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();

View File

@ -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<RecipientAddress> GetChangeAddress() const;
std::optional<RecipientAddress> GetChangeAddress(const std::set<OutputPool>& allowedPools) const;
UnifiedFullViewingKey ToFullViewingKey() const;