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
process table; `zcashd-wallet-tool` is specifically provided to avoid this
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
-----------

View File

@ -43,10 +43,11 @@ class WalletAccountsTest(BitcoinTestFramework):
# Check we only have balances in the expected pools.
# Remember that empty pools are omitted from the output.
def check_address_balance(self, address, expected, minconf=None):
fvk = self.nodes[0].z_exportviewingkey(address)
if minconf is None:
actual = self.nodes[0].z_getbalanceforaddress(address)
actual = self.nodes[0].z_getbalanceforviewingkey(fvk)
else:
actual = self.nodes[0].z_getbalanceforaddress(address, minconf)
actual = self.nodes[0].z_getbalanceforviewingkey(fvk, minconf)
assert_equal(set(expected), set(actual['pools']))
for pool in expected:
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):
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):
# 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()) {
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();
}
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {

View File

@ -373,6 +373,32 @@ CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) c
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>>>
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);

View File

@ -132,6 +132,11 @@ public:
GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver
) const = 0;
virtual std::optional<libzcash::UFVKId>
GetUFVKIdForViewingKey(
const libzcash::ViewingKey& vk
) const = 0;
};
typedef std::map<CKeyID, CKey> KeyMap;
@ -372,6 +377,11 @@ public:
GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver
) const;
virtual std::optional<libzcash::UFVKId>
GetUFVKIdForViewingKey(
const libzcash::ViewingKey& vk
) const;
};
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.
builder_.SendChangeToSprout(addr);
},
[&](const libzcash::SproutViewingKey& vk) {
// for Sprout, we return change to the originating address.
builder_.SendChangeToSprout(vk.address());
},
[&](const libzcash::SaplingPaymentAddress& addr) {
// for Sapling, if using a legacy address, return change to the
// originating address; otherwise return it to the Sapling internal
@ -303,6 +307,29 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
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) {
for (ReceiverType rtype : acct.GetReceiverTypes()) {
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"
"\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."
"\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"
"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"
@ -3618,32 +3618,33 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
return ValueFromAmount(nBalance);
}
UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp)
UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error(
"z_getbalanceforaddress \"address\" ( minconf )\n"
"\nReturns the per-pool balances of a Unified Address belonging to the node's wallet."
"z_getbalanceforviewingkey \"fvk\" ( minconf )\n"
"\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"
"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"
"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"
"\nResult:\n"
"{\n"
" \"pools\": {\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"
" \"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"
" \"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"
" \"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"
" \"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"
"Pools for which the balance is zero are not shown.\n"
"\nExamples:\n"
"\nThe per-pool amount received by address \"myaddress\" with at least 1 block confirmed\n"
+ HelpExampleCli("z_getbalanceforaddress", "\"myaddress\"") +
"\nThe per-pool amount received by address \"myaddress\" with at least 5 blocks confirmed\n"
+ HelpExampleCli("z_getbalanceforaddress", "\"myaddress\" 5") +
"\nThe per-pool amount viewable by key \"myfvk\" with at least 1 block confirmed\n"
+ HelpExampleCli("z_getbalanceforviewingkey", "\"myfvk\"") +
"\nThe per-pool amount viewable by key \"myfvk\" with at least 5 blocks confirmed\n"
+ HelpExampleCli("z_getbalanceforviewingkey", "\"myfvk\" 5") +
"\nAs a JSON RPC call\n"
+ HelpExampleRpc("z_getbalanceforaddress", "\"myaddress\", 5")
+ HelpExampleRpc("z_getbalanceforviewingkey", "\"myfvk\", 5")
);
if (!fExperimentalOrchardWallet) {
@ -3664,11 +3665,11 @@ UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp)
}
KeyIO keyIO(Params());
auto decoded = keyIO.DecodePaymentAddress(params[0].get_str());
auto decoded = keyIO.DecodeViewingKey(params[0].get_str());
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;
if (params.size() > 1) {
@ -3680,14 +3681,17 @@ UniValue z_getbalanceforaddress(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
// Get the receivers for this address.
auto selector = pwalletMain->ToZTXOSelector(address, false);
// Sprout viewing keys cannot provide accurate balance information because they
// 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()) {
// 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(
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);
@ -4278,11 +4282,20 @@ size_t EstimateTxSize(
[&](const CScriptID& scriptId) {
return true;
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
return ufvk.GetTransparentKey().has_value();
},
[&](const libzcash::SproutPaymentAddress& addr) {
return false;
},
[&](const libzcash::SproutViewingKey& addr) {
return false;
},
[&](const libzcash::SaplingPaymentAddress& addr) {
return false;
},
[&](const libzcash::SaplingExtendedFullViewingKey& addr) {
return false;
}
}, ztxoSelector.GetPattern());
@ -4380,7 +4393,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
auto fromaddress = params[0].get_str();
ZTXOSelector ztxoSelector = [&]() {
if (fromaddress == "ANY_TADDR") {
return CWallet::LegacyTransparentZTXOSelector();
return CWallet::LegacyTransparentZTXOSelector(true);
} else {
auto decoded = keyIO.DecodePaymentAddress(fromaddress);
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'.");
}
auto ztxoSelectorOpt = pwalletMain->ToZTXOSelector(decoded.value(), true);
auto ztxoSelectorOpt = pwalletMain->ZTXOSelectorForAddress(decoded.value(), true);
if (!ztxoSelectorOpt.has_value()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
@ -5542,7 +5555,7 @@ static const CRPCCommand commands[] =
{ "wallet", "z_listunspent", &z_listunspent, false },
{ "wallet", "z_getbalance", &z_getbalance, 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_mergetoaddress", &z_mergetoaddress, 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()));
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) {
@ -1235,7 +1235,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// 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);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
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
{
auto selector = pwalletMain->ToZTXOSelector(zaddr1, true).value();
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1, 100*COIN, "DEADBEEF") };
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())
{
auto selector = pwalletMain->ToZTXOSelector(zaddr1, true).value();
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
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);
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::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 0));
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;
std::optional<ZTXOPattern> pattern = std::nullopt;
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(
AccountZTXOPattern(ZCASH_LEGACY_ACCOUNT, {ReceiverType::P2PKH, ReceiverType::P2SH}),
true);
requireSpendingKey);
}
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::SproutViewingKey& vk) { },
[&](const libzcash::SaplingPaymentAddress& addr) {
auto meta = GetUFVKMetadataForReceiver(addr);
if (meta.has_value()) {
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) {
if (self->mnemonicHDChain.has_value() &&
self->mapUnifiedAccountKeys.count(
@ -1554,6 +1599,8 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
return result;
}
// SelectorMatchesAddress is overloaded for:
// Transparent
bool CWallet::SelectorMatchesAddress(
const ZTXOSelector& selector,
const CTxDestination& address) const {
@ -1568,7 +1615,17 @@ bool CWallet::SelectorMatchesAddress(
return address == scriptIdDest;
},
[&](const libzcash::SproutPaymentAddress& addr) { return false; },
[&](const libzcash::SproutViewingKey& vk) { 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) {
if (acct.IncludesP2PKH() || acct.IncludesP2SH()) {
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta;
@ -1591,16 +1648,17 @@ bool CWallet::SelectorMatchesAddress(
}
}, selector.GetPattern());
}
// Sprout
bool CWallet::SelectorMatchesAddress(
const ZTXOSelector& selector,
const libzcash::SproutPaymentAddress& a0) const {
return std::visit(match {
[&](const libzcash::SproutPaymentAddress& a1) { return a0 == a1; },
[&](const libzcash::SproutViewingKey& vk) { return a0 == vk.address(); },
[&](const auto& addr) { return false; },
}, selector.GetPattern());
}
// Sapling
bool CWallet::SelectorMatchesAddress(
const ZTXOSelector& selector,
const libzcash::SaplingPaymentAddress& a0) const {
@ -1609,7 +1667,25 @@ bool CWallet::SelectorMatchesAddress(
[&](const CKeyID& keyId) { return false; },
[&](const CScriptID& scriptId) { 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) {
if (acct.IncludesSapling()) {
const auto meta = self->GetUFVKMetadataForReceiver(a0);
@ -6218,8 +6294,11 @@ std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
const libzcash::UnifiedAddress &uaddr) const
{
// TODO
return std::nullopt;
auto ufvkid = m_wallet->FindUnifiedFullViewingKey(uaddr);
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
@ -6355,33 +6434,25 @@ std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(co
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
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
// corrupt and the node should shut down.
const auto& metadata = wallet.mapUfvkAddressMetadata.at(ufvkid);
librustzcash_sapling_diversifier_index(
ufvk.value().GetSaplingKey().value().dk.begin(),
saplingAddr.d.begin(),
j.begin());
auto receivers = metadata.GetReceivers(j);
if (receivers.has_value()) {
auto addr = ufvk.value().Address(j, receivers.value());
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
if (addrPtr == nullptr) {
return std::nullopt;
} else {
return addrPtr->first;
auto saplingKey = ufvk.value().GetSaplingKey();
if (saplingKey.has_value()) {
diversifier_index_t j = saplingKey.value().DecryptDiversifier(saplingAddr.d);
auto receivers = metadata.GetReceivers(j);
if (receivers.has_value()) {
auto addr = ufvk.value().Address(j, receivers.value());
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
if (addrPtr != nullptr) {
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 {
// We do not currently generate unified addresses containing P2SH components,
@ -6429,16 +6500,25 @@ bool ZTXOSelector::SelectsTransparent() {
[](const CKeyID& keyId) { return true; },
[](const CScriptID& scriptId) { return true; },
[](const libzcash::SproutPaymentAddress& addr) { return false; },
[](const libzcash::SproutViewingKey& vk) { 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(); }
}, this->pattern);
}
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() {
return std::visit(match {
[](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 auto& addr) { return false; }
}, this->pattern);

View File

@ -738,7 +738,10 @@ typedef std::variant<
CKeyID,
CScriptID,
libzcash::SproutPaymentAddress,
libzcash::SproutViewingKey,
libzcash::SaplingPaymentAddress,
libzcash::SaplingExtendedFullViewingKey,
libzcash::UnifiedFullViewingKey,
AccountZTXOPattern> ZTXOPattern;
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);
/**
* 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
* the wallet.
@ -1234,15 +1237,30 @@ public:
std::set<libzcash::ReceiverType> receiverTypes={}) const;
/**
* Obtain the ZTXO selector for the specified payment address. If the
* `requireSpendingKey` flag is set, this will only return a selector
* that will choose outputs for which this wallet holds the spending keys.
* Returns the ZTXO selector for the specified payment address, if the
* address 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> ToZTXOSelector(
std::optional<ZTXOSelector> ZTXOSelectorForAddress(
const libzcash::PaymentAddress& addr,
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

View File

@ -177,6 +177,17 @@ public:
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)
{
if (this != &key) {

View File

@ -157,3 +157,31 @@ std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(co
}, req);
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;
/**
* 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)
{
return a.transparentKey == b.transparentKey && a.saplingKey == b.saplingKey;

View File

@ -11,6 +11,7 @@
#include "uint256.h"
#include "utiltime.h"
#include "zcash/address/sapling.hpp"
#include <librustzcash.h>
#include <rust/zip339.h>
#include <optional>
@ -171,6 +172,12 @@ public:
*/
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;
template <typename Stream, typename Operation>