From 191fc2e152b94db232863d65f02763727593a91b Mon Sep 17 00:00:00 2001 From: Bartlomiej Lisiecki Date: Fri, 1 Feb 2019 14:54:48 +0100 Subject: [PATCH] Add support for importing and exporting sapling ivk --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/ivk_import_export.py | 137 +++++++++++++++++++++++++ src/gtest/test_keys.cpp | 14 +++ src/key_io.cpp | 28 ++++- src/wallet/gtest/test_wallet.cpp | 53 ++++++++++ src/wallet/gtest/test_wallet_zkeys.cpp | 3 + src/wallet/rpcdump.cpp | 63 ++++++++---- src/wallet/rpcwallet.cpp | 3 +- src/wallet/wallet.cpp | 99 ++++++++++++------ src/wallet/wallet.h | 13 +++ src/zcash/Address.hpp | 2 +- 11 files changed, 364 insertions(+), 52 deletions(-) create mode 100755 qa/rpc-tests/ivk_import_export.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index c4775fa85..24e8566d5 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -65,6 +65,7 @@ testScripts=( 'disablewallet.py' 'zcjoinsplit.py' 'zcjoinsplitdoublespend.py' + 'ivk_import_export.py' 'zkey_import_export.py' 'reorg_limit.py' 'getblocktemplate.py' diff --git a/qa/rpc-tests/ivk_import_export.py b/qa/rpc-tests/ivk_import_export.py new file mode 100755 index 000000000..0546b0a44 --- /dev/null +++ b/qa/rpc-tests/ivk_import_export.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python2 +# Copyright (c) 2019 Bartlomiej Lisiecki +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from decimal import Decimal +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_greater_than, start_nodes,\ + initialize_chain_clean, connect_nodes_bi, wait_and_assert_operationid_status + +import logging + +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + +fee = Decimal('0.0001') # constant (but can be changed within reason) + +class IVKImportExportTest (BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self, split=False): + self.nodes = start_nodes(4, self.options.tmpdir, [[ + '-nuparams=5ba81b19:101', # Overwinter + '-nuparams=76b809bb:102', # Sapling + ]] * 4) + + connect_nodes_bi(self.nodes,0,1) + connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes,0,3) + self.is_network_split=False + self.sync_all() + + def run_test(self): + [alice, bob, charlie, miner] = self.nodes + + # the sender loses 'amount' plus fee; to_addr receives exactly 'amount' + def z_send(from_node, from_addr, to_addr, amount): + global fee + opid = from_node.z_sendmany(from_addr, + [{"address": to_addr, "amount": Decimal(amount)}], 1, fee) + wait_and_assert_operationid_status(from_node, opid) + self.sync_all() + miner.generate(1) + self.sync_all() + + def verify_utxos(node, amts, zaddr): + amts.sort(reverse=True) + txs = node.z_listreceivedbyaddress(zaddr) + + def cmp_confirmations_high_to_low(a, b): + return cmp(b["amount"], a["amount"]) + + txs.sort(cmp_confirmations_high_to_low) + print("Sorted txs", txs) + print("amts", amts) + + try: + assert_equal(amts, [tx["amount"] for tx in txs]) + except AssertionError: + logging.error( + 'Expected amounts: %r; txs: %r', + amts, txs) + raise + + def get_private_balance(node): + balance = node.z_gettotalbalance() + return balance['private'] + + def find_imported_zaddr(node, import_zaddr): + zaddrs = node.z_listaddresses() + assert(import_zaddr in zaddrs) + return import_zaddr + + # activate sapling + alice.generate(102) + self.sync_all() + + # sanity-check the test harness + assert_equal(self.nodes[0].getblockcount(), 102) + + # shield alice's coinbase funds to her zaddr + alice_zaddr = alice.z_getnewaddress('sapling') + res = alice.z_shieldcoinbase("*", alice_zaddr) + wait_and_assert_operationid_status(alice, res['opid']) + self.sync_all() + miner.generate(1) + self.sync_all() + + # the amounts of each txn embodied which generates a single utxo: + amounts = map(Decimal, ['2.3', '3.7', '0.1', '0.5', '1.0', '0.19']) + + # internal test consistency assertion: + assert_greater_than( + get_private_balance(alice), + reduce(Decimal.__add__, amounts)) + + + # now get a pristine z-address for receiving transfers: + bob_zaddr = bob.z_getnewaddress('sapling') + verify_utxos(bob, [], bob_zaddr) + + logging.info("sending pre-export txns...") + for amount in amounts[0:2]: + z_send(alice, alice_zaddr, bob_zaddr, amount) + + logging.info("exporting ivk from bob...") + bob_ivk = bob.z_exportviewingkey(bob_zaddr) + + logging.info("sending post-export txns...") + for amount in amounts[2:4]: + z_send(alice, alice_zaddr, bob_zaddr, amount) + + verify_utxos(bob, amounts[:4], bob_zaddr) + + logging.info("importing bob_ivk into charlie...") + # we need to pass bob_zaddr since it's a sapling address + charlie.z_importviewingkey(bob_ivk, 'yes', 0, bob_zaddr) + + # z_importkey should have rescanned for new key, so this should pass: + verify_utxos(charlie, amounts[:4], bob_zaddr) + + # verify idempotent behavior: + charlie.z_importviewingkey(bob_ivk, 'yes', 0, bob_zaddr) + verify_utxos(charlie, amounts[:4], bob_zaddr) + + + logging.info("Sending post-import txns...") + for amount in amounts[4:]: + z_send(alice, alice_zaddr, bob_zaddr, amount) + + verify_utxos(bob, amounts, bob_zaddr) + verify_utxos(charlie, amounts, bob_zaddr) + +if __name__ == '__main__': + IVKImportExportTest().main() diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index bd9599421..ae5831261 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -44,5 +44,19 @@ TEST(Keys, EncodeAndDecodeSapling) auto addr2 = boost::get(paymentaddr2); EXPECT_EQ(addr, addr2); } + { + auto ivk = sk.ToXFVK().fvk.in_viewing_key(); + std::string ivk_string = EncodeViewingKey(ivk); + EXPECT_EQ( + ivk_string.substr(0, 5), + Params().Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY)); + + auto viewing_key = DecodeViewingKey(ivk_string); + EXPECT_TRUE(IsValidViewingKey(viewing_key)); + + auto ivk2 = boost::get(&viewing_key); + ASSERT_TRUE(ivk2 != nullptr); + EXPECT_EQ(*ivk2, ivk); + } } } diff --git a/src/key_io.cpp b/src/key_io.cpp index 1953d9623..014159d65 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -128,6 +128,19 @@ public: return ret; } + std::string operator()(const libzcash::SaplingIncomingViewingKey& vk) const + { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << vk; + std::vector serkey(ss.begin(), ss.end()); + std::vector data; + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end()); + std::string ret = bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY), data); + memory_cleanse(serkey.data(), serkey.size()); + memory_cleanse(data.data(), data.size()); + return ret; + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; @@ -175,6 +188,7 @@ public: // perform ceiling division to get the number of 5-bit clusters. const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5; const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5; +const size_t ConvertedSaplingIncomingViewingKeySize = (32 * 8 + 4) / 5; } // namespace CKey DecodeSecret(const std::string& str) @@ -346,7 +360,19 @@ libzcash::ViewingKey DecodeViewingKey(const std::string& str) return ret; } } - memory_cleanse(data.data(), data.size()); + data.clear(); + auto bech = bech32::Decode(str); + if(bech.first == Params().Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY) && + bech.second.size() == ConvertedSaplingIncomingViewingKeySize) { + // Bech32 decoding + data.reserve((bech.second.size() * 5) / 8); + if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) { + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + libzcash::SaplingIncomingViewingKey ret; + ss >> ret; + return ret; + } + } return libzcash::InvalidEncoding(); } diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index 2b1a89cc7..1fe9db11a 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -526,6 +526,59 @@ TEST(WalletTests, FindMySaplingNotes) { UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } +TEST(WalletTests, FindMySaplingNotesWithIvkOnly) { + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + auto consensusParams = Params().GetConsensus(); + + TestWallet wallet; + + // Generate dummy Sapling address + std::vector> rawSeed(32); + HDSeed seed(rawSeed); + auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed); + auto addr = sk.DefaultAddress(); + auto expsk = sk.expsk; + auto fvk = expsk.full_viewing_key(); + auto pk = sk.DefaultAddress(); + auto ivk = fvk.in_viewing_key(); + + // Generate dummy Sapling note + libzcash::SaplingNote note(pk, 50000); + auto cm = note.cm().get(); + SaplingMerkleTree tree; + tree.append(cm); + auto anchor = tree.root(); + auto witness = tree.witness(); + + // Generate transaction + auto builder = TransactionBuilder(consensusParams, 1); + ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness)); + builder.AddSaplingOutput(fvk.ovk, pk, 25000, {}); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + // No Sapling notes can be found in tx which does not belong to the wallet + CWalletTx wtx {&wallet, tx}; + ASSERT_FALSE(wallet.HaveSaplingSpendingKey(fvk)); + ASSERT_FALSE(wallet.HaveSaplingIncomingViewingKey(addr)); + auto noteMap = wallet.FindMySaplingNotes(wtx).first; + EXPECT_EQ(0, noteMap.size()); + + // Add ivk to wallet, so Sapling notes can be found + ASSERT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, addr)); + ASSERT_FALSE(wallet.HaveSaplingSpendingKey(fvk)); + ASSERT_TRUE(wallet.HaveSaplingIncomingViewingKey(addr)); + noteMap = wallet.FindMySaplingNotes(wtx).first; + EXPECT_EQ(2, noteMap.size()); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} + TEST(WalletTests, FindMySproutNotes) { CWallet wallet; diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 5f022c5d5..365533b6c 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -82,6 +82,9 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) { EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress())); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa)); + // verify that resets nTimeFirstKey, since there is no birthday info for watch-only keys + EXPECT_EQ(wallet.nTimeFirstKey, 1); + // Load a third key into the wallet auto sk2 = m.Derive(1); ASSERT_TRUE(wallet.LoadSaplingZKey(sk2)); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c66ce3485..05552b50b 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -694,7 +694,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() < 1 || params.size() > 3) + if (fHelp || params.size() < 1 || params.size() > 4) throw runtime_error( "z_importviewingkey \"vkey\" ( rescan startHeight )\n" "\nAdds a viewing key (as returned by z_exportviewingkey) to your wallet.\n" @@ -702,6 +702,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) "1. \"vkey\" (string, required) The viewing key (see z_exportviewingkey)\n" "2. rescan (string, optional, default=\"whenkeyisnew\") Rescan the wallet for transactions - can be \"yes\", \"no\" or \"whenkeyisnew\"\n" "3. startHeight (numeric, optional, default=0) Block height to start rescan from\n" + "4. zaddr (string, optional, default=\"\") zaddr in case of importing viewing key for Sapling\n" "\nNote: This call can take minutes to complete if rescan is true.\n" "\nExamples:\n" "\nImport a viewing key\n" @@ -712,6 +713,8 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) + HelpExampleCli("z_importviewingkey", "\"vkey\" whenkeyisnew 30000") + "\nRe-import the viewing key with longer partial rescan\n" + HelpExampleCli("z_importviewingkey", "\"vkey\" yes 20000") + + "\nImport the viewing key for Sapling address\n" + + HelpExampleCli("z_importviewingkey", "\"vkey\" no 0 \"zaddr\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("z_importviewingkey", "\"vkey\", \"no\"") ); @@ -751,14 +754,34 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) if (!IsValidViewingKey(viewingkey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key"); } - // TODO: Add Sapling support. For now, return an error to the user. - if (boost::get(&viewingkey) == nullptr) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout viewing keys are supported"); - } - auto vkey = boost::get(viewingkey); - auto addr = vkey.address(); - { + if (boost::get(&viewingkey) == nullptr) { + if (params.size() < 4) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key."); + } + string strAddress = params[3].get_str(); + auto address = DecodePaymentAddress(strAddress); + if (!IsValidPaymentAddress(address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); + } + + auto addr = boost::get(address); + auto ivk = boost::get(viewingkey); + + if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) { + if (fIgnoreExistingKey) { + return NullUniValue; + } + } else { + pwalletMain->MarkDirty(); + + if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); + } + } + } else { + auto vkey = boost::get(viewingkey); + auto addr = vkey.address(); if (pwalletMain->HaveSproutSpendingKey(addr)) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key"); } @@ -775,13 +798,12 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); } } - - // We want to scan for transactions and notes - if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); - } } + // We want to scan for transactions and notes + if (fRescan) { + pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); + } return NullUniValue; } @@ -853,12 +875,17 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp) if (!IsValidPaymentAddress(address)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr"); } - // TODO: Add Sapling support. For now, return an error to the user. - if (boost::get(&address) == nullptr) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout zaddrs are supported"); - } - auto addr = boost::get(address); + if (boost::get(&address) == nullptr) { + auto addr = boost::get(address); + libzcash::SaplingIncomingViewingKey ivk; + if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr"); + } + return EncodeViewingKey(ivk); + } + + auto addr = boost::get(address); libzcash::SproutViewingKey vk; if (!pwalletMain->GetSproutViewingKey(addr, vk)) { libzcash::SproutSpendingKey k; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a21de63f4..93b438e19 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3915,7 +3915,8 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp) } // Visitor to support Sprout and Sapling addrs - if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr)) { + if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr) && + !boost::apply_visitor(IncomingViewingKeyBelongsToWallet(pwalletMain), zaddr)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found."); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9b2975db1..fa39d204a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -185,6 +185,7 @@ bool CWallet::AddSaplingZKey( return false; } + nTimeFirstKey = 1; // No birthday information for viewing keys. if (!fFileBacked) { return true; } @@ -1541,26 +1542,29 @@ void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) { } else { uint64_t position = nd.witnesses.front().position(); - SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk); - OutputDescription output = wtx.vShieldedOutput[op.n]; - auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm); - if (!optPlaintext) { - // An item in mapSaplingNoteData must have already been successfully decrypted, - // otherwise the item would not exist in the first place. - assert(false); + // Skip if we only have incoming viewing key + if (mapSaplingFullViewingKeys.count(nd.ivk) != 0) { + SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk); + OutputDescription output = wtx.vShieldedOutput[op.n]; + auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm); + if (!optPlaintext) { + // An item in mapSaplingNoteData must have already been successfully decrypted, + // otherwise the item would not exist in the first place. + assert(false); + } + auto optNote = optPlaintext.get().note(nd.ivk); + if (!optNote) { + assert(false); + } + auto optNullifier = optNote.get().nullifier(fvk, position); + if (!optNullifier) { + // This should not happen. If it does, maybe the position has been corrupted or miscalculated? + assert(false); + } + uint256 nullifier = optNullifier.get(); + mapSaplingNullifiersToNotes[nullifier] = op; + item.second.nullifier = nullifier; } - auto optNote = optPlaintext.get().note(nd.ivk); - if (!optNote) { - assert(false); - } - auto optNullifier = optNote.get().nullifier(fvk, position); - if (!optNullifier) { - // This should not happen. If it does, maybe the position has been corrupted or miscalculated? - assert(false); - } - uint256 nullifier = optNullifier.get(); - mapSaplingNullifiersToNotes[nullifier] = op; - item.second.nullifier = nullifier; } } } @@ -1991,23 +1995,40 @@ std::pair CWallet::FindMySap // Protocol Spec: 4.19 Block Chain Scanning (Sapling) for (uint32_t i = 0; i < tx.vShieldedOutput.size(); ++i) { const OutputDescription output = tx.vShieldedOutput[i]; + bool found = false; for (auto it = mapSaplingFullViewingKeys.begin(); it != mapSaplingFullViewingKeys.end(); ++it) { SaplingIncomingViewingKey ivk = it->first; auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cm); - if (!result) { - continue; + if (result) { + auto address = ivk.address(result.get().d); + if (address && mapSaplingIncomingViewingKeys.count(address.get()) == 0) { + viewingKeysToAdd[address.get()] = ivk; + } + // We don't cache the nullifier here as computing it requires knowledge of the note position + // in the commitment tree, which can only be determined when the transaction has been mined. + SaplingOutPoint op {hash, i}; + SaplingNoteData nd; + nd.ivk = ivk; + noteData.insert(std::make_pair(op, nd)); + found = true; + break; } - auto address = ivk.address(result.get().d); - if (address && mapSaplingIncomingViewingKeys.count(address.get()) == 0) { - viewingKeysToAdd[address.get()] = ivk; + } + if (!found) { + for (auto it = mapSaplingIncomingViewingKeys.begin(); it != mapSaplingIncomingViewingKeys.end(); ++it) { + SaplingIncomingViewingKey ivk = it-> second; + auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cm); + if (!result) { + continue; + } + // We don't cache the nullifier here as computing it requires knowledge of the note position + // in the commitment tree, which can only be determined when the transaction has been mined. + SaplingOutPoint op {hash, i}; + SaplingNoteData nd; + nd.ivk = ivk; + noteData.insert(std::make_pair(op, nd)); + break; } - // We don't cache the nullifier here as computing it requires knowledge of the note position - // in the commitment tree, which can only be determined when the transaction has been mined. - SaplingOutPoint op {hash, i}; - SaplingNoteData nd; - nd.ivk = ivk; - noteData.insert(std::make_pair(op, nd)); - break; } } @@ -5071,6 +5092,22 @@ void CWallet::GetFilteredNotes( // Shielded key and address generalizations // +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const +{ + return m_wallet->HaveSproutViewingKey(zaddr); +} + +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const +{ + libzcash::SaplingIncomingViewingKey ivk; + return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk); +} + +bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::InvalidEncoding& no) const +{ + return false; +} + bool PaymentAddressBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const { return m_wallet->HaveSproutSpendingKey(zaddr) || m_wallet->HaveSproutViewingKey(zaddr); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 10aa83ce6..b00365a77 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1392,6 +1392,19 @@ public: bool operator()(const libzcash::InvalidEncoding& no) const; }; + +class IncomingViewingKeyBelongsToWallet : public boost::static_visitor +{ +private: + CWallet *m_wallet; +public: + IncomingViewingKeyBelongsToWallet(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; +}; + class HaveSpendingKeyForPaymentAddress : public boost::static_visitor { private: diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 42f01b57b..42ee0031b 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -219,7 +219,7 @@ public: }; typedef boost::variant PaymentAddress; -typedef boost::variant ViewingKey; +typedef boost::variant ViewingKey; }