Merge pull request #5522 from nuttycom/feature/wallet_unified_addresses-balance_for_vk

Replace z_getbalanceforaddress with z_getbalanceforviewingkey
This commit is contained in:
Kris Nuttycombe 2022-02-09 20:30:09 -07:00 committed by GitHub
commit 09593559f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 309 additions and 70 deletions

View File

@ -47,6 +47,11 @@ New RPC Methods
being confirmed might be leaked via the user's shell history or the system being confirmed might be leaked via the user's shell history or the system
process table; `zcashd-wallet-tool` is specifically provided to avoid this process table; `zcashd-wallet-tool` is specifically provided to avoid this
problem. problem.
- 'z_getbalanceforviewingkey' This newly created API allows a user to obtain
balance information for funds visible to a Sapling or Unified full
viewing key; if a Sprout viewing key is provided, this method allows
retrieval of the balance only in the case that the wallet controls the
corresponding spending key.
RPC Changes RPC Changes
----------- -----------

View File

@ -43,10 +43,11 @@ class WalletAccountsTest(BitcoinTestFramework):
# Check we only have balances in the expected pools. # Check we only have balances in the expected pools.
# Remember that empty pools are omitted from the output. # Remember that empty pools are omitted from the output.
def check_address_balance(self, address, expected, minconf=None): def check_address_balance(self, address, expected, minconf=None):
fvk = self.nodes[0].z_exportviewingkey(address)
if minconf is None: if minconf is None:
actual = self.nodes[0].z_getbalanceforaddress(address) actual = self.nodes[0].z_getbalanceforviewingkey(fvk)
else: else:
actual = self.nodes[0].z_getbalanceforaddress(address, minconf) actual = self.nodes[0].z_getbalanceforviewingkey(fvk, minconf)
assert_equal(set(expected), set(actual['pools'])) assert_equal(set(expected), set(actual['pools']))
for pool in expected: for pool in expected:
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat']) assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])

View File

@ -35,7 +35,8 @@ class WalletZSendmanyTest(BitcoinTestFramework):
def check_balance(self, node, account, address, expected, minconf=None): def check_balance(self, node, account, address, expected, minconf=None):
self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf) self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
self._check_balance_for_rpc('z_getbalanceforaddress', node, address, expected, minconf) fvk = self.nodes[node].z_exportviewingkey(address)
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
def run_test(self): def run_test(self):
# z_sendmany is expected to fail if tx size breaks limit # z_sendmany is expected to fail if tx size breaks limit

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->ToZTXOSelector(zaddr.value(), true); auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true);
minerAddressInLocalWallet = ztxoSelector.has_value(); minerAddressInLocalWallet = ztxoSelector.has_value();
} }
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) { if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {

View File

@ -373,6 +373,32 @@ CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) c
return std::visit(FindUFVKId(*this), receiver); return std::visit(FindUFVKId(*this), receiver);
} }
std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const
{
std::optional<libzcash::UFVKId> result;
std::visit(match {
[&](const libzcash::SproutViewingKey& vk) {},
[&](const libzcash::SaplingExtendedFullViewingKey& extfvk) {
const auto saplingIvk = extfvk.ToIncomingViewingKey();
const auto ufvkId = mapSaplingKeyUnified.find(saplingIvk);
if (ufvkId != mapSaplingKeyUnified.end()) {
result = ufvkId->second;
}
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
const auto saplingDfvk = ufvk.GetSaplingKey();
if (saplingDfvk.has_value()) {
const auto saplingIvk = saplingDfvk.value().ToIncomingViewingKey();
const auto ufvkId = mapSaplingKeyUnified.find(saplingIvk);
if (ufvkId != mapSaplingKeyUnified.end()) {
result = ufvkId->second;
}
}
}
}, vk);
return result;
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const { FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr); const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);

View File

@ -132,6 +132,11 @@ public:
GetUFVKMetadataForReceiver( GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver const libzcash::Receiver& receiver
) const = 0; ) 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;
@ -372,6 +377,11 @@ public:
GetUFVKMetadataForReceiver( GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver const libzcash::Receiver& receiver
) const; ) const;
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

@ -290,6 +290,10 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
// for Sprout, we return change to the originating address. // for Sprout, we return change to the originating address.
builder_.SendChangeToSprout(addr); builder_.SendChangeToSprout(addr);
}, },
[&](const libzcash::SproutViewingKey& vk) {
// for Sprout, we return change to the originating address.
builder_.SendChangeToSprout(vk.address());
},
[&](const libzcash::SaplingPaymentAddress& addr) { [&](const libzcash::SaplingPaymentAddress& addr) {
// for Sapling, if using a legacy address, return change to the // for Sapling, if using a legacy address, return change to the
// originating address; otherwise return it to the Sapling internal // originating address; otherwise return it to the Sapling internal
@ -303,6 +307,29 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
builder_.SendChangeTo(changeAddr.value(), ovks.first); builder_.SendChangeTo(changeAddr.value(), ovks.first);
} }
}, },
[&](const libzcash::SaplingExtendedFullViewingKey& fvk) {
// for Sapling, if using a legacy address, return change to the
// originating address; otherwise return it to the Sapling internal
// address corresponding to the UFVK.
if (sendFromAccount_ == ZCASH_LEGACY_ACCOUNT) {
builder_.SendChangeTo(fvk.DefaultAddress(), ovks.first);
} else {
auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
sendFromAccount_, allowedChangeTypes_);
assert(changeAddr.has_value());
builder_.SendChangeTo(changeAddr.value(), ovks.first);
}
},
[&](const libzcash::UnifiedFullViewingKey& fvk) {
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), fvk);
auto changeAddr = zufvk.GetChangeAddress();
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) { [&](const AccountZTXOPattern& acct) {
for (ReceiverType rtype : acct.GetReceiverTypes()) { for (ReceiverType rtype : acct.GetReceiverTypes()) {
switch (rtype) { switch (rtype) {

View File

@ -3551,7 +3551,7 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
"\nReturns the balance of a taddr or zaddr belonging to the node's wallet.\n" "\nReturns the balance of a taddr or zaddr belonging to the node's wallet.\n"
"\nCAUTION: If the wallet has only an incoming viewing key for this address, then spends cannot be" "\nCAUTION: If the wallet has only an incoming viewing key for this address, then spends cannot be"
"\ndetected, and so the returned balance may be larger than the actual balance." "\ndetected, and so the returned balance may be larger than the actual balance."
"\nThe argument address may not be a Unified Address; please use z_getbalanceforaddress instead.\n" "\nThe argument address may not be a Unified Address; please use z_getbalanceforviewingkey instead.\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"address\" (string) The selected address. It may be a transparent or shielded address.\n" "1. \"address\" (string) The selected address. It may be a transparent or shielded address.\n"
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
@ -3618,32 +3618,33 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
return ValueFromAmount(nBalance); return ValueFromAmount(nBalance);
} }
UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp) UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
{ {
if (!EnsureWalletIsAvailable(fHelp)) if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue; return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 2) if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error( throw runtime_error(
"z_getbalanceforaddress \"address\" ( minconf )\n" "z_getbalanceforviewingkey \"fvk\" ( minconf )\n"
"\nReturns the per-pool balances of a Unified Address belonging to the node's wallet." "\nReturns the per-pool balances viewable by a full viewing key known to the node's wallet."
"\nSprout viewing keys may be used only if the wallet controls the corresponding spending key."
"\nArguments:\n" "\nArguments:\n"
"1. \"address\" (string) The selected address. It may be a transparent or shielded address.\n" "1. \"fvk\" (string) The selected full viewing key.\n"
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
"\nResult:\n" "\nResult:\n"
"{\n" "{\n"
" \"pools\": {\n" " \"pools\": {\n"
" \"transparent\": {\n" " \"transparent\": {\n"
" \"valueZat\": amount (numeric) The amount held in the transparent pool by this account\n" " \"valueZat\": amount (numeric) The amount held in the transparent pool viewable by this fvk\n"
" \"},\n" " \"},\n"
" \"sprout\": {\n" " \"sprout\": {\n"
" \"valueZat\": amount (numeric) The amount held in the sprout pool by this account\n" " \"valueZat\": amount (numeric) The amount held in the sprout pool viewable by this fvk\n"
" \"},\n" " \"},\n"
" \"sapling\": {\n" " \"sapling\": {\n"
" \"valueZat\": amount (numeric) The amount held in the sapling pool by this account\n" " \"valueZat\": amount (numeric) The amount held in the sapling pool viewable by this fvk\n"
" \"},\n" " \"},\n"
" \"orchard\": {\n" " \"orchard\": {\n"
" \"valueZat\": amount (numeric) The amount held in the orchard pool by this account\n" " \"valueZat\": amount (numeric) The amount held in the orchard pool viewable by this fvk\n"
" \"}\n" " \"}\n"
" \"},\n" " \"},\n"
" \"minimum_confirmations\": n (numeric) The given minconf argument\n" " \"minimum_confirmations\": n (numeric) The given minconf argument\n"
@ -3651,12 +3652,12 @@ UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp)
"Result amounts are in units of " + MINOR_CURRENCY_UNIT + ".\n" "Result amounts are in units of " + MINOR_CURRENCY_UNIT + ".\n"
"Pools for which the balance is zero are not shown.\n" "Pools for which the balance is zero are not shown.\n"
"\nExamples:\n" "\nExamples:\n"
"\nThe per-pool amount received by address \"myaddress\" with at least 1 block confirmed\n" "\nThe per-pool amount viewable by key \"myfvk\" with at least 1 block confirmed\n"
+ HelpExampleCli("z_getbalanceforaddress", "\"myaddress\"") + + HelpExampleCli("z_getbalanceforviewingkey", "\"myfvk\"") +
"\nThe per-pool amount received by address \"myaddress\" with at least 5 blocks confirmed\n" "\nThe per-pool amount viewable by key \"myfvk\" with at least 5 blocks confirmed\n"
+ HelpExampleCli("z_getbalanceforaddress", "\"myaddress\" 5") + + HelpExampleCli("z_getbalanceforviewingkey", "\"myfvk\" 5") +
"\nAs a JSON RPC call\n" "\nAs a JSON RPC call\n"
+ HelpExampleRpc("z_getbalanceforaddress", "\"myaddress\", 5") + HelpExampleRpc("z_getbalanceforviewingkey", "\"myfvk\", 5")
); );
if (!fExperimentalOrchardWallet) { if (!fExperimentalOrchardWallet) {
@ -3664,11 +3665,11 @@ UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp)
} }
KeyIO keyIO(Params()); KeyIO keyIO(Params());
auto decoded = keyIO.DecodePaymentAddress(params[0].get_str()); auto decoded = keyIO.DecodeViewingKey(params[0].get_str());
if (!decoded.has_value()) { if (!decoded.has_value()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid full viewing key");
} }
auto address = decoded.value(); auto fvk = decoded.value();
int minconf = 1; int minconf = 1;
if (params.size() > 1) { if (params.size() > 1) {
@ -3680,14 +3681,17 @@ UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
// Get the receivers for this address. // Sprout viewing keys cannot provide accurate balance information because they
auto selector = pwalletMain->ToZTXOSelector(address, false); // cannot detect spends, so we require that the wallet control the spending key
// in the case that a Sprout viewing key is provided. Sapling and unified
// FVKs make it possible to correctly determine balance without having the
// spending key, so we permit that here.
bool requireSpendingKey = std::holds_alternative<libzcash::SproutViewingKey>(fvk);
auto selector = pwalletMain->ZTXOSelectorForViewingKey(fvk, requireSpendingKey);
if (!selector.has_value()) { if (!selector.has_value()) {
// The only way we'd reach this is if the address is a unified address for which
// we do not know its UFVK.
throw JSONRPCError( throw JSONRPCError(
RPC_INVALID_PARAMETER, RPC_INVALID_PARAMETER,
"Error: wallet does not have the Unified Full Viewing Key for the given address"); "Error: the wallet does not recognize the specified viewing key.");
} }
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf); auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf);
@ -4278,11 +4282,20 @@ size_t EstimateTxSize(
[&](const CScriptID& scriptId) { [&](const CScriptID& scriptId) {
return true; return true;
}, },
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
return ufvk.GetTransparentKey().has_value();
},
[&](const libzcash::SproutPaymentAddress& addr) { [&](const libzcash::SproutPaymentAddress& addr) {
return false; return false;
}, },
[&](const libzcash::SproutViewingKey& addr) {
return false;
},
[&](const libzcash::SaplingPaymentAddress& addr) { [&](const libzcash::SaplingPaymentAddress& addr) {
return false; return false;
},
[&](const libzcash::SaplingExtendedFullViewingKey& addr) {
return false;
} }
}, ztxoSelector.GetPattern()); }, ztxoSelector.GetPattern());
@ -4380,7 +4393,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
auto fromaddress = params[0].get_str(); auto fromaddress = params[0].get_str();
ZTXOSelector ztxoSelector = [&]() { ZTXOSelector ztxoSelector = [&]() {
if (fromaddress == "ANY_TADDR") { if (fromaddress == "ANY_TADDR") {
return CWallet::LegacyTransparentZTXOSelector(); return CWallet::LegacyTransparentZTXOSelector(true);
} else { } else {
auto decoded = keyIO.DecodePaymentAddress(fromaddress); auto decoded = keyIO.DecodePaymentAddress(fromaddress);
if (!decoded.has_value()) { if (!decoded.has_value()) {
@ -4389,7 +4402,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
"Invalid from address: should be a taddr, a zaddr, or the string 'ANY_TADDR'."); "Invalid from address: should be a taddr, a zaddr, or the string 'ANY_TADDR'.");
} }
auto ztxoSelectorOpt = pwalletMain->ToZTXOSelector(decoded.value(), true); auto ztxoSelectorOpt = pwalletMain->ZTXOSelectorForAddress(decoded.value(), true);
if (!ztxoSelectorOpt.has_value()) { if (!ztxoSelectorOpt.has_value()) {
throw JSONRPCError( throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY, RPC_INVALID_ADDRESS_OR_KEY,
@ -5542,7 +5555,7 @@ static const CRPCCommand commands[] =
{ "wallet", "z_listunspent", &z_listunspent, false }, { "wallet", "z_listunspent", &z_listunspent, false },
{ "wallet", "z_getbalance", &z_getbalance, false }, { "wallet", "z_getbalance", &z_getbalance, false },
{ "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, { "wallet", "z_gettotalbalance", &z_gettotalbalance, false },
{ "wallet", "z_getbalanceforaddress", &z_getbalanceforaddress, false }, { "wallet", "z_getbalanceforviewingkey",&z_getbalanceforviewingkey,false },
{ "wallet", "z_getbalanceforaccount", &z_getbalanceforaccount, false }, { "wallet", "z_getbalanceforaccount", &z_getbalanceforaccount, false },
{ "wallet", "z_mergetoaddress", &z_mergetoaddress, false }, { "wallet", "z_mergetoaddress", &z_mergetoaddress, false },
{ "wallet", "z_sendmany", &z_sendmany, false }, { "wallet", "z_sendmany", &z_sendmany, false },

View File

@ -801,7 +801,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->ToZTXOSelector(*addr_of_type, true).has_value()); BOOST_CHECK(pwalletMain->ZTXOSelectorForAddress(*addr_of_type, true).has_value());
} }
BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) { BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) {
@ -1235,7 +1235,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// there are no utxos to spend // there are no utxos to spend
{ {
auto selector = pwalletMain->ToZTXOSelector(taddr1, true).value(); auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain); TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") }; std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1)); std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
@ -1247,7 +1247,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->ToZTXOSelector(zaddr1, true).value(); auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain); TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1, 100*COIN, "DEADBEEF") }; std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1)); std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
@ -1259,7 +1259,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// get_memo_from_hex_string()) // get_memo_from_hex_string())
{ {
auto selector = pwalletMain->ToZTXOSelector(zaddr1, true).value(); auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain); TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") }; std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1)); std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
@ -1360,7 +1360,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain); auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain);
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight); mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
auto selector = pwalletMain->ToZTXOSelector(taddr, true).value(); auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true).value();
std::vector<SendManyRecipient> recipients = { SendManyRecipient(pa, 1*COIN, "ABCD") }; std::vector<SendManyRecipient> recipients = { SendManyRecipient(pa, 1*COIN, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 0)); std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(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

@ -1466,7 +1466,7 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAccount(
} }
} }
std::optional<ZTXOSelector> CWallet::ToZTXOSelector(const libzcash::PaymentAddress& addr, bool requireSpendingKey) const { std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(const libzcash::PaymentAddress& addr, bool requireSpendingKey) 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 {
@ -1513,10 +1513,45 @@ std::optional<ZTXOSelector> CWallet::ToZTXOSelector(const libzcash::PaymentAddre
} }
} }
ZTXOSelector CWallet::LegacyTransparentZTXOSelector() { std::optional<ZTXOSelector> CWallet::ZTXOSelectorForViewingKey(
const libzcash::ViewingKey& vk,
bool requireSpendingKey) const
{
auto self = this;
std::optional<ZTXOPattern> pattern = std::nullopt;
std::visit(match {
[&](const libzcash::SaplingExtendedFullViewingKey& vk) {
if (!requireSpendingKey || self->HaveSaplingSpendingKey(vk)) {
pattern = vk;
}
},
[&](const libzcash::SproutViewingKey& vk) {
if (!requireSpendingKey || self->HaveSproutSpendingKey(vk.address())) {
pattern = vk;
}
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
auto ufvkId = ufvk.GetKeyID(Params());
auto accountId = this->GetUnifiedAccountId(ufvkId);
if (accountId.has_value()) {
pattern = AccountZTXOPattern(accountId.value(), ufvk.GetKnownReceiverTypes());
} else {
pattern = ufvk;
}
}
}, vk);
if (pattern.has_value()) {
return ZTXOSelector(pattern.value(), requireSpendingKey);
} else {
return std::nullopt;
}
}
ZTXOSelector CWallet::LegacyTransparentZTXOSelector(bool requireSpendingKey) {
return ZTXOSelector( return ZTXOSelector(
AccountZTXOPattern(ZCASH_LEGACY_ACCOUNT, {ReceiverType::P2PKH, ReceiverType::P2SH}), AccountZTXOPattern(ZCASH_LEGACY_ACCOUNT, {ReceiverType::P2PKH, ReceiverType::P2SH}),
true); requireSpendingKey);
} }
std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSelector& selector) const { std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSelector& selector) const {
@ -1536,12 +1571,22 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
} }
}, },
[&](const libzcash::SproutPaymentAddress& addr) { }, [&](const libzcash::SproutPaymentAddress& addr) { },
[&](const libzcash::SproutViewingKey& vk) { },
[&](const libzcash::SaplingPaymentAddress& addr) { [&](const libzcash::SaplingPaymentAddress& addr) {
auto meta = GetUFVKMetadataForReceiver(addr); auto meta = GetUFVKMetadataForReceiver(addr);
if (meta.has_value()) { if (meta.has_value()) {
result = self->GetUnifiedAccountId(meta.value().first); result = self->GetUnifiedAccountId(meta.value().first);
} }
}, },
[&](const libzcash::SaplingExtendedFullViewingKey& vk) {
auto ufvkid = GetUFVKIdForViewingKey(vk);
if (ufvkid.has_value()) {
result = self->GetUnifiedAccountId(ufvkid.value());
}
},
[&](const libzcash::UnifiedFullViewingKey& vk) {
result = self->GetUnifiedAccountId(vk.GetKeyID(Params()));
},
[&](const AccountZTXOPattern& acct) { [&](const AccountZTXOPattern& acct) {
if (self->mnemonicHDChain.has_value() && if (self->mnemonicHDChain.has_value() &&
self->mapUnifiedAccountKeys.count( self->mapUnifiedAccountKeys.count(
@ -1554,6 +1599,8 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
return result; return result;
} }
// SelectorMatchesAddress is overloaded for:
// Transparent
bool CWallet::SelectorMatchesAddress( bool CWallet::SelectorMatchesAddress(
const ZTXOSelector& selector, const ZTXOSelector& selector,
const CTxDestination& address) const { const CTxDestination& address) const {
@ -1568,7 +1615,17 @@ bool CWallet::SelectorMatchesAddress(
return address == scriptIdDest; return address == scriptIdDest;
}, },
[&](const libzcash::SproutPaymentAddress& addr) { return false; }, [&](const libzcash::SproutPaymentAddress& addr) { 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::UnifiedFullViewingKey& ufvk) {
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta;
std::visit(match {
[&](const CNoDestination& none) { meta = std::nullopt; },
[&](const auto& addr) { meta = self->GetUFVKMetadataForReceiver(addr); }
}, address);
return (meta.has_value() && meta.value().first == ufvk.GetKeyID(Params()));
},
[&](const AccountZTXOPattern& acct) { [&](const AccountZTXOPattern& acct) {
if (acct.IncludesP2PKH() || acct.IncludesP2SH()) { if (acct.IncludesP2PKH() || acct.IncludesP2SH()) {
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;
@ -1591,16 +1648,17 @@ bool CWallet::SelectorMatchesAddress(
} }
}, selector.GetPattern()); }, selector.GetPattern());
} }
// Sprout
bool CWallet::SelectorMatchesAddress( bool CWallet::SelectorMatchesAddress(
const ZTXOSelector& selector, const ZTXOSelector& selector,
const libzcash::SproutPaymentAddress& a0) const { const libzcash::SproutPaymentAddress& a0) const {
return std::visit(match { return std::visit(match {
[&](const libzcash::SproutPaymentAddress& a1) { return a0 == a1; }, [&](const libzcash::SproutPaymentAddress& a1) { return a0 == a1; },
[&](const libzcash::SproutViewingKey& vk) { return a0 == vk.address(); },
[&](const auto& addr) { return false; }, [&](const auto& addr) { return false; },
}, selector.GetPattern()); }, selector.GetPattern());
} }
// Sapling
bool CWallet::SelectorMatchesAddress( bool CWallet::SelectorMatchesAddress(
const ZTXOSelector& selector, const ZTXOSelector& selector,
const libzcash::SaplingPaymentAddress& a0) const { const libzcash::SaplingPaymentAddress& a0) const {
@ -1609,7 +1667,25 @@ bool CWallet::SelectorMatchesAddress(
[&](const CKeyID& keyId) { return false; }, [&](const CKeyID& keyId) { return false; },
[&](const CScriptID& scriptId) { return false; }, [&](const CScriptID& scriptId) { return false; },
[&](const libzcash::SproutPaymentAddress& addr) { return false; }, [&](const libzcash::SproutPaymentAddress& addr) { return false; },
[&](const libzcash::SaplingPaymentAddress& a1) { return a0 == a1; }, [&](const libzcash::SproutViewingKey& vk) { return false; },
[&](const libzcash::SaplingPaymentAddress& a1) {
return a0 == a1;
},
[&](const libzcash::SaplingExtendedFullViewingKey& extfvk) {
auto j = extfvk.DecryptDiversifier(a0.d);
auto addr = extfvk.Address(j);
return addr.has_value() && addr.value() == a0;
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
auto saplingKey = ufvk.GetSaplingKey();
if (saplingKey.has_value()) {
auto j = saplingKey.value().DecryptDiversifier(a0.d);
auto addr = saplingKey.value().Address(j);
return addr.has_value() && addr.value() == a0;
} else {
return false;
}
},
[&](const AccountZTXOPattern& acct) { [&](const AccountZTXOPattern& acct) {
if (acct.IncludesSapling()) { if (acct.IncludesSapling()) {
const auto meta = self->GetUFVKMetadataForReceiver(a0); const auto meta = self->GetUFVKMetadataForReceiver(a0);
@ -6218,8 +6294,11 @@ std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()( std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
const libzcash::UnifiedAddress &uaddr) const const libzcash::UnifiedAddress &uaddr) const
{ {
// TODO auto ufvkid = m_wallet->FindUnifiedFullViewingKey(uaddr);
return std::nullopt; if (!ufvkid.has_value()) return std::nullopt;
auto zufvk = m_wallet->GetUnifiedFullViewingKey(ufvkid.value());
if (!zufvk.has_value()) return std::nullopt;
return zufvk.value().ToFullViewingKey();
} }
// AddViewingKeyToWallet // AddViewingKeyToWallet
@ -6355,33 +6434,25 @@ std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(co
if (ufvkPair.has_value()) { if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first; auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid); auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value()); assert(ufvk.has_value());
diversifier_index_t j;
// If the wallet is missing metadata at this UFVK id, it is probably // If the wallet is missing metadata at this UFVK id, it is probably
// corrupt and the node should shut down. // corrupt and the node should shut down.
const auto& metadata = wallet.mapUfvkAddressMetadata.at(ufvkid); const auto& metadata = wallet.mapUfvkAddressMetadata.at(ufvkid);
librustzcash_sapling_diversifier_index( auto saplingKey = ufvk.value().GetSaplingKey();
ufvk.value().GetSaplingKey().value().dk.begin(), if (saplingKey.has_value()) {
saplingAddr.d.begin(), diversifier_index_t j = saplingKey.value().DecryptDiversifier(saplingAddr.d);
j.begin()); auto receivers = metadata.GetReceivers(j);
auto receivers = metadata.GetReceivers(j); if (receivers.has_value()) {
if (receivers.has_value()) { auto addr = ufvk.value().Address(j, receivers.value());
auto addr = ufvk.value().Address(j, receivers.value()); auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr); if (addrPtr != nullptr) {
if (addrPtr == nullptr) { return addrPtr->first;
return std::nullopt; }
} else {
return addrPtr->first;
} }
} else {
// If we don't know the receiver types at which the address was originally
// generated, we can't reconstruct the address.
return std::nullopt;
} }
} else {
return std::nullopt;
} }
return std::nullopt;
} }
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CScriptID& scriptId) const { std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CScriptID& scriptId) const {
// We do not currently generate unified addresses containing P2SH components, // We do not currently generate unified addresses containing P2SH components,
@ -6429,16 +6500,25 @@ bool ZTXOSelector::SelectsTransparent() {
[](const CKeyID& keyId) { return true; }, [](const CKeyID& keyId) { return true; },
[](const CScriptID& scriptId) { return true; }, [](const CScriptID& scriptId) { return true; },
[](const libzcash::SproutPaymentAddress& addr) { return false; }, [](const libzcash::SproutPaymentAddress& addr) { 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::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);
} }
bool ZTXOSelector::SelectsSprout() { bool ZTXOSelector::SelectsSprout() {
return std::holds_alternative<libzcash::SproutPaymentAddress>(this->pattern); return std::visit(match {
[](const libzcash::SproutViewingKey& addr) { return true; },
[](const libzcash::SproutPaymentAddress& extfvk) { return true; },
[](const auto& addr) { return false; }
}, this->pattern);
} }
bool ZTXOSelector::SelectsSapling() { bool ZTXOSelector::SelectsSapling() {
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::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; }
}, this->pattern); }, this->pattern);

View File

@ -738,7 +738,10 @@ typedef std::variant<
CKeyID, CKeyID,
CScriptID, CScriptID,
libzcash::SproutPaymentAddress, libzcash::SproutPaymentAddress,
libzcash::SproutViewingKey,
libzcash::SaplingPaymentAddress, libzcash::SaplingPaymentAddress,
libzcash::SaplingExtendedFullViewingKey,
libzcash::UnifiedFullViewingKey,
AccountZTXOPattern> ZTXOPattern; AccountZTXOPattern> ZTXOPattern;
class ZTXOSelector { class ZTXOSelector {
@ -1220,7 +1223,7 @@ public:
static bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet); static bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet);
/** /**
* Obtain the ZTXO selector for the specified account ID. * Returns the ZTXO selector for the specified account ID.
* *
* Returns `std::nullopt` if the account ID has not been generated yet by * Returns `std::nullopt` if the account ID has not been generated yet by
* the wallet. * the wallet.
@ -1234,15 +1237,30 @@ public:
std::set<libzcash::ReceiverType> receiverTypes={}) const; std::set<libzcash::ReceiverType> receiverTypes={}) const;
/** /**
* Obtain the ZTXO selector for the specified payment address. If the * Returns the ZTXO selector for the specified payment address, if the
* `requireSpendingKey` flag is set, this will only return a selector * address is known to the wallet. If the `requireSpendingKey` flag is set,
* that will choose outputs for which this wallet holds the spending keys. * this will only return a selector that will choose outputs for which this
* wallet holds the spending keys.
*/ */
std::optional<ZTXOSelector> ToZTXOSelector( std::optional<ZTXOSelector> ZTXOSelectorForAddress(
const libzcash::PaymentAddress& addr, const libzcash::PaymentAddress& addr,
bool requireSpendingKey) const; bool requireSpendingKey) const;
static ZTXOSelector LegacyTransparentZTXOSelector(); /**
* Returns the ZTXO selector for the specified viewing key, if that key
* is known to the wallet. If the `requireSpendingKey` flag is set, this
* will only return a selector that will choose outputs for which this
* wallet holds the spending keys.
*/
std::optional<ZTXOSelector> ZTXOSelectorForViewingKey(
const libzcash::ViewingKey& vk,
bool requireSpendingKey) const;
/**
* Returns the ZTXO selector that will select UTXOs sent to legacy
* transparent addresses managed by this wallet.
*/
static ZTXOSelector LegacyTransparentZTXOSelector(bool requireSpendingKey);
/** /**
* Look up the account for a given selector. This resolves the account ID * Look up the account for a given selector. This resolves the account ID

View File

@ -177,6 +177,17 @@ public:
UFVKId GetKeyID(const KeyConstants& keyConstants) const; UFVKId GetKeyID(const KeyConstants& keyConstants) const;
std::set<ReceiverType> GetKnownReceiverTypes() const {
std::set<ReceiverType> result;
if (GetTransparentKey().has_value()) {
result.insert(ReceiverType::P2PKH);
}
if (GetSaplingKey().has_value()) {
result.insert(ReceiverType::Sapling);
}
return result;
}
UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key) UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key)
{ {
if (this != &key) { if (this != &key) {

View File

@ -157,3 +157,31 @@ std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(co
}, req); }, req);
return addr; return addr;
} }
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress() const {
if (saplingKey.has_value()) {
return saplingKey.value().GetChangeAddress();
}
if (transparentKey.has_value()) {
auto changeAddr = transparentKey.value().GetChangeAddress(diversifier_index_t(0));
if (changeAddr.has_value()) {
return changeAddr.value();
}
}
return std::nullopt;
}
UnifiedFullViewingKey ZcashdUnifiedFullViewingKey::ToFullViewingKey() const {
UnifiedFullViewingKeyBuilder builder;
if (transparentKey.has_value()) {
builder.AddTransparentKey(transparentKey.value());
}
if (saplingKey.has_value()) {
builder.AddSaplingKey(saplingKey.value());
}
// This call to .value() is safe as ZcashdUnifiedFullViewingKey values are always
// constructed to contain all required components.
return builder.build().value();
}

View File

@ -214,6 +214,18 @@ public:
*/ */
std::optional<RecipientAddress> GetChangeAddress(const ChangeRequest& req) const; std::optional<RecipientAddress> GetChangeAddress(const ChangeRequest& req) const;
/**
* Return the "best available" change address. This returns `std::nullopt`
* only in the case of derivation failure for all key types. This will only
* return a transparent change address under highly exceptional
* circumstances, (i.e. it was not possible to derive a change address for
* *any* shielded pool) in which case the change address returned will be
* associated with diversifier index 0.
*/
std::optional<RecipientAddress> GetChangeAddress() const;
UnifiedFullViewingKey ToFullViewingKey() const;
friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b) friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b)
{ {
return a.transparentKey == b.transparentKey && a.saplingKey == b.saplingKey; return a.transparentKey == b.transparentKey && a.saplingKey == b.saplingKey;

View File

@ -11,6 +11,7 @@
#include "uint256.h" #include "uint256.h"
#include "utiltime.h" #include "utiltime.h"
#include "zcash/address/sapling.hpp" #include "zcash/address/sapling.hpp"
#include <librustzcash.h>
#include <rust/zip339.h> #include <rust/zip339.h>
#include <optional> #include <optional>
@ -171,6 +172,12 @@ public:
*/ */
std::pair<uint256, uint256> GetOVKs() const; std::pair<uint256, uint256> GetOVKs() const;
diversifier_index_t DecryptDiversifier(const diversifier_t& d) const {
diversifier_index_t j;
librustzcash_sapling_diversifier_index(dk.begin(), d.begin(), j.begin());
return j;
}
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation> template <typename Stream, typename Operation>