diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index dba999e7e..405cde231 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -888,13 +888,14 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) { bool AsyncRPCOperation_sendmany::find_unspent_notes() { - std::vector entries; + std::vector sproutEntries; + std::vector saplingEntries; { LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->GetFilteredNotes(entries, fromaddress_, mindepth_); + pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_); } - for (CSproutNotePlaintextEntry & entry : entries) { + for (CSproutNotePlaintextEntry & entry : sproutEntries) { z_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get(frompaymentaddress_)), CAmount(entry.plaintext.value()))); std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end()); LogPrint("zrpcunsafe", "%s: found unspent note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n", @@ -906,6 +907,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() { HexStr(data).substr(0, 10) ); } + // TODO: Do something with Sapling notes if (z_inputs_.size() == 0) { return false; diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index d79b69ea0..86e3e2a2b 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -186,13 +186,16 @@ TEST(WalletTests, FindUnspentSproutNotes) { EXPECT_FALSE(wallet.IsSproutSpent(nullifier)); // We currently have an unspent and unconfirmed note in the wallet (depth of -1) - std::vector entries; - wallet.GetFilteredNotes(entries, "", 0); - EXPECT_EQ(0, entries.size()); - entries.clear(); - wallet.GetFilteredNotes(entries, "", -1); - EXPECT_EQ(1, entries.size()); - entries.clear(); + std::vector sproutEntries; + std::vector saplingEntries; + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0); + EXPECT_EQ(0, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", -1); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Fake-mine the transaction EXPECT_EQ(-1, chainActive.Height()); @@ -212,15 +215,18 @@ TEST(WalletTests, FindUnspentSproutNotes) { // We now have an unspent and confirmed note in the wallet (depth of 1) - wallet.GetFilteredNotes(entries, "", 0); - EXPECT_EQ(1, entries.size()); - entries.clear(); - wallet.GetFilteredNotes(entries, "", 1); - EXPECT_EQ(1, entries.size()); - entries.clear(); - wallet.GetFilteredNotes(entries, "", 2); - EXPECT_EQ(0, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2); + EXPECT_EQ(0, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Let's spend the note. @@ -247,21 +253,25 @@ TEST(WalletTests, FindUnspentSproutNotes) { EXPECT_TRUE(wallet.IsSproutSpent(nullifier)); // The note has been spent. By default, GetFilteredNotes() ignores spent notes. - wallet.GetFilteredNotes(entries, "", 0); - EXPECT_EQ(0, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0); + EXPECT_EQ(0, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Let's include spent notes to retrieve it. - wallet.GetFilteredNotes(entries, "", 0, false); - EXPECT_EQ(1, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0, false); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // The spent note has two confirmations. - wallet.GetFilteredNotes(entries, "", 2, false); - EXPECT_EQ(1, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // It does not have 3 confirmations. - wallet.GetFilteredNotes(entries, "", 3, false); - EXPECT_EQ(0, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 3, false); + EXPECT_EQ(0, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Let's receive a new note @@ -301,21 +311,25 @@ TEST(WalletTests, FindUnspentSproutNotes) { wallet.AddToWallet(wtx3, true, NULL); // We now have an unspent note which has one confirmation, in addition to our spent note. - wallet.GetFilteredNotes(entries, "", 1); - EXPECT_EQ(1, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Let's return the spent note too. - wallet.GetFilteredNotes(entries, "", 1, false); - EXPECT_EQ(2, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1, false); + EXPECT_EQ(2, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Increasing number of confirmations will exclude our new unspent note. - wallet.GetFilteredNotes(entries, "", 2, false); - EXPECT_EQ(1, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false); + EXPECT_EQ(1, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // If we also ignore spent notes at this depth, we won't find any notes. - wallet.GetFilteredNotes(entries, "", 2, true); - EXPECT_EQ(0, entries.size()); - entries.clear(); + wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, true); + EXPECT_EQ(0, sproutEntries.size()); + sproutEntries.clear(); + saplingEntries.clear(); // Tear down chainActive.SetTip(NULL); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 603024215..133f4a5ea 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2556,10 +2556,11 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) UniValue results(UniValue::VARR); if (zaddrs.size() > 0) { - std::vector entries; - pwalletMain->GetUnspentFilteredNotes(entries, zaddrs, nMinDepth, nMaxDepth, !fIncludeWatchonly); + std::vector sproutEntries; + std::vector saplingEntries; + pwalletMain->GetUnspentFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, !fIncludeWatchonly); std::set> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs); - for (CUnspentSproutNotePlaintextEntry & entry : entries) { + for (CUnspentSproutNotePlaintextEntry & entry : sproutEntries) { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("txid", entry.jsop.hash.ToString())); obj.push_back(Pair("jsindex", (int)entry.jsop.js )); @@ -2576,6 +2577,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) } results.push_back(obj); } + // TODO: Sapling } return results; @@ -3248,12 +3250,16 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ign CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) { CAmount balance = 0; - std::vector entries; + std::vector sproutEntries; + std::vector saplingEntries; LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->GetFilteredNotes(entries, address, minDepth, true, ignoreUnspendable); - for (auto & entry : entries) { + pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, address, minDepth, true, ignoreUnspendable); + for (auto & entry : sproutEntries) { balance += CAmount(entry.plaintext.value()); } + for (auto & entry : saplingEntries) { + balance += CAmount(entry.note.value()); + } return balance; } @@ -3309,10 +3315,11 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp) } UniValue result(UniValue::VARR); - std::vector entries; - pwalletMain->GetFilteredNotes(entries, fromaddress, nMinDepth, false, false); + std::vector sproutEntries; + std::vector saplingEntries; + pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress, nMinDepth, false, false); auto nullifierSet = hasSproutSpendingKey ? pwalletMain->GetNullifiersForAddresses({zaddr}) : std::set>(); - for (CSproutNotePlaintextEntry & entry : entries) { + for (CSproutNotePlaintextEntry & entry : sproutEntries) { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("txid", entry.jsop.hash.ToString())); obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.plaintext.value())))); @@ -3326,6 +3333,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp) } result.push_back(obj); } + // TODO: Sapling return result; } @@ -3338,8 +3346,8 @@ UniValue z_getbalance(const UniValue& params, bool fHelp) throw runtime_error( "z_getbalance \"address\" ( minconf )\n" "\nReturns the balance of a taddr or zaddr belonging to the node’s wallet.\n" - "\nCAUTION: If address is a watch-only zaddr, the returned balance may be larger than the actual balance," - "\nbecause spends cannot be detected with incoming viewing keys.\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.\n" "\nArguments:\n" "1. \"address\" (string) The selected address. It may be a transparent or private address.\n" "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" @@ -3374,11 +3382,8 @@ UniValue z_getbalance(const UniValue& params, bool fHelp) if (!IsValidPaymentAddress(res)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); } - // TODO: Add Sapling support. For now, ensure we can freely convert. - assert(boost::get(&res) != nullptr); - auto zaddr = boost::get(res); - if (!(pwalletMain->HaveSproutSpendingKey(zaddr) || pwalletMain->HaveSproutViewingKey(zaddr))) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found."); + if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), res)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, spending key or viewing key not found."); } } @@ -3402,15 +3407,16 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp) throw runtime_error( "z_gettotalbalance ( minconf includeWatchonly )\n" "\nReturn the total value of funds stored in the node’s wallet.\n" - "\nCAUTION: If the wallet contains watch-only zaddrs, the returned private balance may be larger than the actual balance," - "\nbecause spends cannot be detected with incoming viewing keys.\n" + "\nCAUTION: If the wallet contains any addresses for which it only has incoming viewing keys," + "\nthe returned private balance may be larger than the actual balance, because spends cannot" + "\nbe detected with incoming viewing keys.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) Only include private and transparent transactions confirmed at least this many times.\n" "2. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress' and 'z_importviewingkey')\n" "\nResult:\n" "{\n" " \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n" - " \"private\": xxxxx, (numeric) the total balance of private funds\n" + " \"private\": xxxxx, (numeric) the total balance of private funds (in both Sprout and Sapling addresses)\n" " \"total\": xxxxx, (numeric) the total balance of both transparent and private funds\n" "}\n" "\nExamples:\n" @@ -4247,11 +4253,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) if (useAny || useAnyNote || zaddrs.size() > 0) { // Get available notes - std::vector entries; - pwalletMain->GetFilteredNotes(entries, zaddrs); + std::vector sproutEntries; + std::vector saplingEntries; + pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs); // Find unspent notes and update estimated size - for (CSproutNotePlaintextEntry& entry : entries) { + for (CSproutNotePlaintextEntry& entry : sproutEntries) { noteCounter++; CAmount nValue = entry.plaintext.value(); @@ -4265,8 +4272,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) maxedOutNotesFlag = true; } else { estimatedTxSize += increase; - // TODO: Add Sapling support - auto zaddr = boost::get(entry.address); + auto zaddr = entry.address; SproutSpendingKey zkey; pwalletMain->GetSproutSpendingKey(zaddr, zkey); noteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey); @@ -4278,6 +4284,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) remainingNoteValue += nValue; } } + // TODO: Add Sapling support } size_t numUtxos = utxoInputs.size(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 90d6a72e0..04a61ed13 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4091,7 +4091,13 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee) * Find notes in the wallet filtered by payment address, min depth and ability to spend. * These notes are decrypted and added to the output parameter vector, outEntries. */ -void CWallet::GetFilteredNotes(std::vector & outEntries, std::string address, int minDepth, bool ignoreSpent, bool ignoreUnspendable) +void CWallet::GetFilteredNotes( + std::vector& sproutEntries, + std::vector& saplingEntries, + std::string address, + int minDepth, + bool ignoreSpent, + bool ignoreUnspendable) { std::set filterAddresses; @@ -4099,7 +4105,7 @@ void CWallet::GetFilteredNotes(std::vector & outEntri filterAddresses.insert(DecodePaymentAddress(address)); } - GetFilteredNotes(outEntries, filterAddresses, minDepth, ignoreSpent, ignoreUnspendable); + GetFilteredNotes(sproutEntries, saplingEntries, filterAddresses, minDepth, ignoreSpent, ignoreUnspendable); } /** @@ -4107,7 +4113,8 @@ void CWallet::GetFilteredNotes(std::vector & outEntri * These notes are decrypted and added to the output parameter vector, outEntries. */ void CWallet::GetFilteredNotes( - std::vector& outEntries, + std::vector& sproutEntries, + std::vector& saplingEntries, std::set& filterAddresses, int minDepth, bool ignoreSpent, @@ -4123,10 +4130,6 @@ void CWallet::GetFilteredNotes( continue; } - if (wtx.mapSproutNoteData.size() == 0) { - continue; - } - for (auto & pair : wtx.mapSproutNoteData) { JSOutPoint jsop = pair.first; SproutNoteData nd = pair.second; @@ -4172,7 +4175,7 @@ void CWallet::GetFilteredNotes( hSig, (unsigned char) j); - outEntries.push_back(CSproutNotePlaintextEntry{jsop, pa, plaintext}); + sproutEntries.push_back(CSproutNotePlaintextEntry{jsop, pa, plaintext}); } catch (const note_decryption_failed &err) { // Couldn't decrypt with this spending key @@ -4182,13 +4185,61 @@ void CWallet::GetFilteredNotes( throw std::runtime_error(strprintf("Error while decrypting note for payment address %s: %s", EncodePaymentAddress(pa), exc.what())); } } + + for (auto & pair : wtx.mapSaplingNoteData) { + SaplingOutPoint op = pair.first; + SaplingNoteData nd = pair.second; + + auto maybe_pt = SaplingNotePlaintext::decrypt( + wtx.vShieldedOutput[op.n].encCiphertext, + nd.ivk, + wtx.vShieldedOutput[op.n].ephemeralKey, + wtx.vShieldedOutput[op.n].cm); + assert(static_cast(maybe_pt)); + auto notePt = maybe_pt.get(); + + auto maybe_pa = nd.ivk.address(notePt.d); + assert(static_cast(maybe_pa)); + auto pa = maybe_pa.get(); + + // skip notes which belong to a different payment address in the wallet + if (!(filterAddresses.empty() || filterAddresses.count(pa))) { + continue; + } + + if (ignoreSpent && nd.nullifier && IsSaplingSpent(*nd.nullifier)) { + continue; + } + + // skip notes which cannot be spent + if (ignoreUnspendable) { + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + if (!(GetSaplingIncomingViewingKey(pa, ivk) && + GetSaplingFullViewingKey(ivk, fvk) && + HaveSaplingSpendingKey(fvk))) { + continue; + } + } + + // skip locked notes + // TODO: Add locking for Sapling notes + // if (IsLockedNote(jsop)) { + // continue; + // } + + auto note = notePt.note(nd.ivk).get(); + saplingEntries.push_back(SaplingNoteEntry { + op, pa, note, notePt.memo() }); + } } } /* Find unspent notes filtered by payment address, min depth and max depth */ void CWallet::GetUnspentFilteredNotes( - std::vector& outEntries, + std::vector& sproutEntries, + std::vector& saplingEntries, std::set& filterAddresses, int minDepth, int maxDepth, @@ -4204,10 +4255,6 @@ void CWallet::GetUnspentFilteredNotes( continue; } - if (wtx.mapSproutNoteData.size() == 0) { - continue; - } - for (auto & pair : wtx.mapSproutNoteData) { JSOutPoint jsop = pair.first; SproutNoteData nd = pair.second; @@ -4248,7 +4295,7 @@ void CWallet::GetUnspentFilteredNotes( hSig, (unsigned char) j); - outEntries.push_back(CUnspentSproutNotePlaintextEntry{jsop, pa, plaintext, wtx.GetDepthInMainChain()}); + sproutEntries.push_back(CUnspentSproutNotePlaintextEntry{jsop, pa, plaintext, wtx.GetDepthInMainChain()}); } catch (const note_decryption_failed &err) { // Couldn't decrypt with this spending key @@ -4258,6 +4305,71 @@ void CWallet::GetUnspentFilteredNotes( throw std::runtime_error(strprintf("Error while decrypting note for payment address %s: %s", EncodePaymentAddress(pa), exc.what())); } } + + for (auto & pair : wtx.mapSaplingNoteData) { + SaplingOutPoint op = pair.first; + SaplingNoteData nd = pair.second; + + auto maybe_pt = SaplingNotePlaintext::decrypt( + wtx.vShieldedOutput[op.n].encCiphertext, + nd.ivk, + wtx.vShieldedOutput[op.n].ephemeralKey, + wtx.vShieldedOutput[op.n].cm); + assert(static_cast(maybe_pt)); + auto notePt = maybe_pt.get(); + + auto maybe_pa = nd.ivk.address(notePt.d); + assert(static_cast(maybe_pa)); + auto pa = maybe_pa.get(); + + // skip notes which belong to a different payment address in the wallet + if (!(filterAddresses.empty() || filterAddresses.count(pa))) { + continue; + } + + // skip note which has been spent + if (nd.nullifier && IsSaplingSpent(*nd.nullifier)) { + continue; + } + + // skip notes where the spending key is not available + if (requireSpendingKey) { + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + if (!(GetSaplingIncomingViewingKey(pa, ivk) && + GetSaplingFullViewingKey(ivk, fvk) && + HaveSaplingSpendingKey(fvk))) { + continue; + } + } + + auto note = notePt.note(nd.ivk).get(); + saplingEntries.push_back(UnspentSaplingNoteEntry { + op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() }); + } } } +// +// Shielded key and address generalizations +// + +bool PaymentAddressBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const +{ + return m_wallet->HaveSproutSpendingKey(zaddr) || m_wallet->HaveSproutViewingKey(zaddr); +} + +bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingIncomingViewingKey ivk; + + // If we have a SaplingSpendingKey or SaplingExpandedSpendingKey in the + // wallet, then we will also have the corresponding SaplingFullViewingKey. + return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && + m_wallet->HaveSaplingFullViewingKey(ivk); +} + +bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const +{ + return false; +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b1655eebd..acefa01d8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -307,6 +307,24 @@ struct CUnspentSproutNotePlaintextEntry { int nHeight; }; +/** Sapling note and its location in a transaction. */ +struct SaplingNoteEntry +{ + SaplingOutPoint op; + libzcash::SaplingPaymentAddress address; + libzcash::SaplingNote note; + std::array memo; +}; + +/** Sapling note, location in a transaction, and confirmation height. */ +struct UnspentSaplingNoteEntry { + SaplingOutPoint op; + libzcash::SaplingPaymentAddress address; + libzcash::SaplingNote note; + std::array memo; + int nHeight; +}; + /** A transaction with a merkle branch linking it to the block chain. */ class CMerkleTx : public CTransaction { @@ -1206,21 +1224,24 @@ public: void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } /* Find notes filtered by payment address, min depth, ability to spend */ - void GetFilteredNotes(std::vector & outEntries, + void GetFilteredNotes(std::vector& sproutEntries, + std::vector& saplingEntries, std::string address, int minDepth=1, bool ignoreSpent=true, bool ignoreUnspendable=true); /* Find notes filtered by payment addresses, min depth, ability to spend */ - void GetFilteredNotes(std::vector& outEntries, + void GetFilteredNotes(std::vector& sproutEntries, + std::vector& saplingEntries, std::set& filterAddresses, int minDepth=1, bool ignoreSpent=true, bool ignoreUnspendable=true); /* Find unspent notes filtered by payment address, min depth and max depth */ - void GetUnspentFilteredNotes(std::vector& outEntries, + void GetUnspentFilteredNotes(std::vector& sproutEntries, + std::vector& saplingEntries, std::set& filterAddresses, int minDepth=1, int maxDepth=INT_MAX, @@ -1282,4 +1303,20 @@ public: } }; +// +// Shielded key and address generalizations +// + +class PaymentAddressBelongsToWallet : public boost::static_visitor +{ +private: + CWallet *m_wallet; +public: + PaymentAddressBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {} + + bool operator()(const libzcash::SproutPaymentAddress &zaddr) const; + bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const; + bool operator()(const libzcash::InvalidEncoding& no) const; +}; + #endif // BITCOIN_WALLET_WALLET_H