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()) { if (!zaddr.has_value()) {
return InitError(_("-mineraddress is not a valid " PACKAGE_NAME " address.")); 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(); minerAddressInLocalWallet = ztxoSelector.has_value();
} }
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) { if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {

View File

@ -389,6 +389,44 @@ CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) c
return std::visit(FindUFVKId(*this), receiver); 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> CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const
{ {
std::optional<libzcash::UFVKId> result; std::optional<libzcash::UFVKId> result;

View File

@ -125,18 +125,23 @@ public:
const libzcash::UnifiedAddress& ua) = 0; const libzcash::UnifiedAddress& ua) = 0;
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey( virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId const libzcash::UFVKId& keyId) const = 0;
) const = 0;
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver( GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver const libzcash::Receiver& receiver) const = 0;
) const = 0;
virtual std::optional<libzcash::UFVKId> /**
GetUFVKIdForViewingKey( * If all the receivers of the specified address correspond to a single
const libzcash::ViewingKey& vk * UFVK, return that key's metadata. If all the receivers correspond to
) const = 0; * 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; typedef std::map<CKeyID, CKey> KeyMap;
@ -376,13 +381,34 @@ public:
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver( GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver const libzcash::Receiver& receiver) const;
) const;
virtual std::optional<libzcash::UFVKId> std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForReceiver(
GetUFVKIdForViewingKey( const libzcash::Receiver& receiver) const {
const libzcash::ViewingKey& vk auto ufvkMeta = GetUFVKMetadataForReceiver(receiver);
) const; 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; 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 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 { std::visit(match {
[&](const CKeyID& keyId) { [&](const CKeyID& keyId) {
allowedChangeTypes.insert(OutputPool::Transparent); allowedChangeTypes.insert(OutputPool::Transparent);
@ -368,34 +387,37 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
builder_.SendChangeTo(changeAddr.value(), ovks.first); builder_.SendChangeTo(changeAddr.value(), ovks.first);
} }
}, },
[&](const libzcash::UnifiedFullViewingKey& fvk) { [&](const libzcash::UnifiedAddress& ua) {
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), fvk); allowChangeTypes(ua.GetKnownReceiverTypes());
auto changeAddr = zufvk.GetChangeAddress();
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()) { if (!changeAddr.has_value()) {
throw JSONRPCError( throw JSONRPCError(
RPC_WALLET_ERROR, 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); builder_.SendChangeTo(changeAddr.value(), ovks.first);
}, },
[&](const AccountZTXOPattern& acct) { [&](const AccountZTXOPattern& acct) {
for (ReceiverType rtype : acct.GetReceiverTypes()) { allowChangeTypes(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;
}
}
auto changeAddr = pwalletMain->GenerateChangeAddressForAccount( auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
acct.GetAccountId(), acct.GetAccountId(),
allowedChangeTypes); allowedChangeTypes);
@ -477,7 +499,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
}, },
[&](const libzcash::SaplingPaymentAddress& addr) { [&](const libzcash::SaplingPaymentAddress& addr) {
auto value = r.amount; 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); builder_.AddSaplingOutput(ovks.second, addr, value, memo);
}, },
@ -553,15 +575,27 @@ std::pair<uint256, uint256> AsyncRPCOperation_sendmany::SelectOVKs(const Spendab
if (!spendable.orchardNoteMetadata.empty()) { if (!spendable.orchardNoteMetadata.empty()) {
std::optional<OrchardFullViewingKey> fvk; std::optional<OrchardFullViewingKey> fvk;
std::visit(match { 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(); fvk = ufvk.GetOrchardKey().value();
}, },
[&](const AccountZTXOPattern& acct) { [&](const AccountZTXOPattern& acct) {
// By definition, we have a UFVK for every known account.
auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()); 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(); fvk = ufvk.value().GetOrchardKey().value();
}, },
[&](const auto& other) { [&](const auto& other) {
throw std::runtime_error("unreachable"); throw std::runtime_error("SelectOVKs: Selector cannot select Orchard notes.");
} }
}, this->ztxoSelector_.GetPattern()); }, this->ztxoSelector_.GetPattern());
assert(fvk.has_value()); assert(fvk.has_value());
@ -576,12 +610,27 @@ std::pair<uint256, uint256> AsyncRPCOperation_sendmany::SelectOVKs(const Spendab
assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)); assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk));
dfvk = extsk.ToXFVK(); 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) { [&](const AccountZTXOPattern& acct) {
// By definition, we have a UFVK for every known account.
auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()); 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(); dfvk = ufvk.value().GetSaplingKey().value();
}, },
[&](const auto& other) { [&](const auto& other) {
throw std::runtime_error("unreachable"); throw std::runtime_error("SelectOVKs: Selector cannot select Sapling notes.");
} }
}, this->ztxoSelector_.GetPattern()); }, this->ztxoSelector_.GetPattern());
assert(dfvk.has_value()); assert(dfvk.has_value());
@ -598,16 +647,31 @@ std::pair<uint256, uint256> AsyncRPCOperation_sendmany::SelectOVKs(const Spendab
[&](const CScriptID& keyId) { [&](const CScriptID& keyId) {
tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey(); 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) { [&](const AccountZTXOPattern& acct) {
if (acct.GetAccountId() == ZCASH_LEGACY_ACCOUNT) { if (acct.GetAccountId() == ZCASH_LEGACY_ACCOUNT) {
tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey(); tfvk = pwalletMain->GetLegacyAccountKey().ToAccountPubKey();
} else { } else {
// By definition, we have a UFVK for every known account.
auto ufvk = pwalletMain->GetUnifiedFullViewingKeyByAccount(acct.GetAccountId()).value(); 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(); tfvk = ufvk.GetTransparentKey().value();
} }
}, },
[&](const auto& other) { [&](const auto& other) {
throw std::runtime_error("unreachable"); throw std::runtime_error("SelectOVKs: Selector cannot select transparent UTXOs.");
} }
}, this->ztxoSelector_.GetPattern()); }, this->ztxoSelector_.GetPattern());
assert(tfvk.has_value()); assert(tfvk.has_value());

View File

@ -6,6 +6,8 @@
#include "zcash/address/sapling.hpp" #include "zcash/address/sapling.hpp"
#include "zcash/address/sprout.hpp" #include "zcash/address/sprout.hpp"
using namespace libzcash;
void PrintTo(const OutputPool& pool, std::ostream* os) { void PrintTo(const OutputPool& pool, std::ostream* os) {
switch (pool) { switch (pool) {
case OutputPool::Orchard: *os << "Orchard"; break; 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); nBalance = getBalanceZaddr(addr, nMinDepth, INT_MAX, false);
}, },
[&](const libzcash::UnifiedAddress& addr) { [&](const libzcash::UnifiedAddress& addr) {
auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true); auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true, false);
if (!selector.has_value()) { if (!selector.has_value()) {
throw JSONRPCError( throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY, 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'."); "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()) { if (!ztxoSelectorOpt.has_value()) {
throw JSONRPCError( throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY, 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())); auto addr_of_type = std::get_if<ADDR_TYPE>(&(addr.value()));
BOOST_ASSERT(addr_of_type != nullptr); 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) { 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 // 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); TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") }; 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)); 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 // 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); TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, taddr1, 100*COIN, "DEADBEEF") }; 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)); 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()) // 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); TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") }; 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)); 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); auto builder = TransactionBuilder(consensusParams, nextBlockHeight, std::nullopt, pwalletMain);
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight); 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::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> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation); 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; auto self = this;
std::optional<ZTXOPattern> pattern = std::nullopt; std::optional<ZTXOPattern> pattern = std::nullopt;
std::visit(match { std::visit(match {
@ -1678,9 +1682,15 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(const libzcash::Paym
// determine a local account. // determine a local account.
auto accountId = this->GetUnifiedAccountId(ufvkId.value()); auto accountId = this->GetUnifiedAccountId(ufvkId.value());
if (accountId.has_value()) { if (accountId.has_value()) {
if (allowAddressLinkability) {
pattern = AccountZTXOPattern(accountId.value(), ua.GetKnownReceiverTypes()); pattern = AccountZTXOPattern(accountId.value(), ua.GetKnownReceiverTypes());
} else {
pattern = ua;
} }
} }
} else {
pattern = ua;
}
} }
}, addr); }, addr);
@ -1762,6 +1772,27 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
result = self->GetUnifiedAccountId(ufvkid.value()); 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) { [&](const libzcash::UnifiedFullViewingKey& vk) {
result = self->GetUnifiedAccountId(vk.GetKeyID(Params())); result = self->GetUnifiedAccountId(vk.GetKeyID(Params()));
}, },
@ -1796,6 +1827,27 @@ bool CWallet::SelectorMatchesAddress(
[&](const libzcash::SproutViewingKey& vk) { return false; }, [&](const libzcash::SproutViewingKey& vk) { return false; },
[&](const libzcash::SaplingPaymentAddress& addr) { return false; }, [&](const libzcash::SaplingPaymentAddress& addr) { return false; },
[&](const libzcash::SaplingExtendedFullViewingKey& extfvk) { 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) { [&](const libzcash::UnifiedFullViewingKey& ufvk) {
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta; std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta;
std::visit(match { std::visit(match {
@ -1854,6 +1906,19 @@ bool CWallet::SelectorMatchesAddress(
auto addr = extfvk.Address(j); auto addr = extfvk.Address(j);
return addr.has_value() && addr.value() == a0; 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) { [&](const libzcash::UnifiedFullViewingKey& ufvk) {
auto saplingKey = ufvk.GetSaplingKey(); auto saplingKey = ufvk.GetSaplingKey();
if (saplingKey.has_value()) { if (saplingKey.has_value()) {
@ -2067,6 +2132,22 @@ SpendableInputs CWallet::FindSpendableInputs(
if (selectOrchard) { if (selectOrchard) {
// for Orchard, we select both the internal and external IVKs. // for Orchard, we select both the internal and external IVKs.
auto orchardIvks = std::visit(match { 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> { [&](const libzcash::UnifiedFullViewingKey& ufvk) -> std::vector<OrchardIncomingViewingKey> {
auto fvk = ufvk.GetOrchardKey(); auto fvk = ufvk.GetOrchardKey();
if (fvk.has_value()) { if (fvk.has_value()) {
@ -7061,6 +7142,9 @@ bool ZTXOSelector::SelectsTransparent() const {
[](const libzcash::SproutViewingKey& vk) { return false; }, [](const libzcash::SproutViewingKey& vk) { return false; },
[](const libzcash::SaplingPaymentAddress& addr) { return false; }, [](const libzcash::SaplingPaymentAddress& addr) { return false; },
[](const libzcash::SaplingExtendedFullViewingKey& vk) { 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 libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetTransparentKey().has_value(); },
[](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); } [](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); }
}, this->pattern); }, this->pattern);
@ -7076,6 +7160,7 @@ bool ZTXOSelector::SelectsSapling() const {
return std::visit(match { return std::visit(match {
[](const libzcash::SaplingPaymentAddress& addr) { return true; }, [](const libzcash::SaplingPaymentAddress& addr) { return true; },
[](const libzcash::SaplingExtendedSpendingKey& extfvk) { 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 libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetSaplingKey().has_value(); },
[](const AccountZTXOPattern& acct) { return acct.IncludesSapling(); }, [](const AccountZTXOPattern& acct) { return acct.IncludesSapling(); },
[](const auto& addr) { return false; } [](const auto& addr) { return false; }
@ -7083,6 +7168,7 @@ bool ZTXOSelector::SelectsSapling() const {
} }
bool ZTXOSelector::SelectsOrchard() const { bool ZTXOSelector::SelectsOrchard() const {
return std::visit(match { return std::visit(match {
[](const libzcash::UnifiedAddress& ua) { return ua.GetOrchardReceiver().has_value(); },
[](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetOrchardKey().has_value(); }, [](const libzcash::UnifiedFullViewingKey& ufvk) { return ufvk.GetOrchardKey().has_value(); },
[](const AccountZTXOPattern& acct) { return acct.IncludesOrchard(); }, [](const AccountZTXOPattern& acct) { return acct.IncludesOrchard(); },
[](const auto& addr) { return false; } [](const auto& addr) { return false; }

View File

@ -778,6 +778,7 @@ typedef std::variant<
libzcash::SproutViewingKey, libzcash::SproutViewingKey,
libzcash::SaplingPaymentAddress, libzcash::SaplingPaymentAddress,
libzcash::SaplingExtendedFullViewingKey, libzcash::SaplingExtendedFullViewingKey,
libzcash::UnifiedAddress,
libzcash::UnifiedFullViewingKey, libzcash::UnifiedFullViewingKey,
AccountZTXOPattern> ZTXOPattern; AccountZTXOPattern> ZTXOPattern;
@ -805,17 +806,6 @@ public:
bool SelectsOrchard() const; 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 { class SpendableInputs {
private: private:
bool limited = false; bool limited = false;
@ -840,7 +830,7 @@ public:
bool LimitToAmount( bool LimitToAmount(
CAmount amount, CAmount amount,
CAmount dustThreshold, CAmount dustThreshold,
std::set<OutputPool> recipientPools); std::set<libzcash::OutputPool> recipientPools);
/** /**
* Compute the total ZEC amount of spendable inputs. * Compute the total ZEC amount of spendable inputs.
@ -1335,7 +1325,8 @@ public:
*/ */
std::optional<ZTXOSelector> ZTXOSelectorForAddress( std::optional<ZTXOSelector> ZTXOSelectorForAddress(
const libzcash::PaymentAddress& addr, const libzcash::PaymentAddress& addr,
bool requireSpendingKey) const; bool requireSpendingKey,
bool allowAddressLinkability) const;
/** /**
* Returns the ZTXO selector for the specified viewing key, if that key * Returns the ZTXO selector for the specified viewing key, if that key
@ -1377,7 +1368,7 @@ public:
*/ */
std::optional<libzcash::RecipientAddress> GenerateChangeAddressForAccount( std::optional<libzcash::RecipientAddress> GenerateChangeAddressForAccount(
libzcash::AccountId accountId, libzcash::AccountId accountId,
std::set<OutputPool> changeOptions); std::set<libzcash::OutputPool> changeOptions);
SpendableInputs FindSpendableInputs( SpendableInputs FindSpendableInputs(
ZTXOSelector paymentSource, ZTXOSelector paymentSource,

View File

@ -188,14 +188,15 @@ std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(co
return addr; return addr;
} }
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress() const { std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(
if (orchardKey.has_value()) { const std::set<OutputPool>& allowedPools) const {
if (orchardKey.has_value() && allowedPools.count(OutputPool::Orchard) > 0) {
return orchardKey.value().GetChangeAddress(); return orchardKey.value().GetChangeAddress();
} }
if (saplingKey.has_value()) { if (saplingKey.has_value() && allowedPools.count(OutputPool::Sapling) > 0) {
return saplingKey.value().GetChangeAddress(); 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)); auto changeAddr = transparentKey.value().GetChangeAddress(diversifier_index_t(0));
if (changeAddr.has_value()) { if (changeAddr.has_value()) {
return changeAddr.value(); return changeAddr.value();

View File

@ -27,6 +27,17 @@ enum class ReceiverType: uint32_t {
Orchard = 0x03 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 { enum class UnifiedAddressGenerationError {
ShieldedReceiverNotFound, ShieldedReceiverNotFound,
ReceiverTypeNotAvailable, ReceiverTypeNotAvailable,
@ -226,7 +237,7 @@ public:
* *any* shielded pool) in which case the change address returned will be * *any* shielded pool) in which case the change address returned will be
* associated with diversifier index 0. * associated with diversifier index 0.
*/ */
std::optional<RecipientAddress> GetChangeAddress() const; std::optional<RecipientAddress> GetChangeAddress(const std::set<OutputPool>& allowedPools) const;
UnifiedFullViewingKey ToFullViewingKey() const; UnifiedFullViewingKey ToFullViewingKey() const;