From d6b31d59b50cd20b8416e46f01f1700af67865d3 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Mon, 27 Aug 2018 15:26:52 -0600 Subject: [PATCH 01/12] Rename map to include sprout --- src/wallet/gtest/test_wallet_zkeys.cpp | 6 +++--- src/wallet/rpcdump.cpp | 6 +++--- src/wallet/wallet.cpp | 16 ++++++++-------- src/wallet/wallet.h | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 37f4b618a..8a2ecd8d3 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -116,7 +116,7 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) { ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta)); // check metadata is the same - CKeyMetadata m= wallet.mapZKeyMetadata[addr]; + CKeyMetadata m= wallet.mapSproutZKeyMetadata[addr]; ASSERT_EQ(m.nCreateTime, now); } @@ -215,7 +215,7 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) { ASSERT_EQ(1, addrs.size()); // wallet should have default metadata for addr with null createtime - CKeyMetadata m = wallet.mapZKeyMetadata[addr]; + CKeyMetadata m = wallet.mapSproutZKeyMetadata[addr]; ASSERT_EQ(m.nCreateTime, 0); ASSERT_NE(m.nCreateTime, now); @@ -235,7 +235,7 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) { ASSERT_EQ(2, addrs.size()); // check metadata is now the same - m = wallet.mapZKeyMetadata[addr]; + m = wallet.mapSproutZKeyMetadata[addr]; ASSERT_EQ(m.nCreateTime, now); } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 50d2ce78c..f6bc6528f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -314,7 +314,7 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys continue; } // Successfully imported zaddr. Now import the metadata. - pwalletMain->mapZKeyMetadata[addr].nCreateTime = nTime; + pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime = nTime; continue; } else { LogPrint("zrpc", "Importing detected an error: invalid spending key. Trying as a transparent key...\n"); @@ -537,7 +537,7 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) for (auto addr : addresses ) { libzcash::SproutSpendingKey key; if (pwalletMain->GetSproutSpendingKey(addr, key)) { - std::string strTime = EncodeDumpTime(pwalletMain->mapZKeyMetadata[addr].nCreateTime); + std::string strTime = EncodeDumpTime(pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime); file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(key), strTime, EncodePaymentAddress(addr)); } } @@ -571,7 +571,7 @@ public: throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); } - m_wallet->mapZKeyMetadata[addr].nCreateTime = 1; + m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = 1; return false; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d931f7384..f4e692219 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -83,7 +83,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const // Generate a new spending key and return its public payment address libzcash::PaymentAddress CWallet::GenerateNewZKey() { - AssertLockHeld(cs_wallet); // mapZKeyMetadata + AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata // TODO: Add Sapling support auto k = SproutSpendingKey::random(); auto addr = k.address(); @@ -94,7 +94,7 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey() // Create new metadata int64_t nCreationTime = GetTime(); - mapZKeyMetadata[addr] = CKeyMetadata(nCreationTime); + mapSproutZKeyMetadata[addr] = CKeyMetadata(nCreationTime); if (!AddZKey(k)) throw std::runtime_error("CWallet::GenerateNewZKey(): AddZKey failed"); @@ -171,7 +171,7 @@ bool CWallet::AddSaplingZKey( // Add spending key to keystore and persist to disk bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key) { - AssertLockHeld(cs_wallet); // mapZKeyMetadata + AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata auto addr = key.address(); if (!CCryptoKeyStore::AddSproutSpendingKey(key)) @@ -187,7 +187,7 @@ bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key) if (!IsCrypted()) { return CWalletDB(strWalletFile).WriteZKey(addr, key, - mapZKeyMetadata[addr]); + mapSproutZKeyMetadata[addr]); } return true; } @@ -278,12 +278,12 @@ bool CWallet::AddCryptedSproutSpendingKey( return pwalletdbEncryption->WriteCryptedZKey(address, rk, vchCryptedSecret, - mapZKeyMetadata[address]); + mapSproutZKeyMetadata[address]); } else { return CWalletDB(strWalletFile).WriteCryptedZKey(address, rk, vchCryptedSecret, - mapZKeyMetadata[address]); + mapSproutZKeyMetadata[address]); } } return false; @@ -315,8 +315,8 @@ bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) bool CWallet::LoadZKeyMetadata(const SproutPaymentAddress &addr, const CKeyMetadata &meta) { - AssertLockHeld(cs_wallet); // mapZKeyMetadata - mapZKeyMetadata[addr] = meta; + AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata + mapSproutZKeyMetadata[addr] = meta; return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index dda1e4dfe..e660c82df 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -845,7 +845,7 @@ public: std::set setKeyPool; std::map mapKeyMetadata; - std::map mapZKeyMetadata; + std::map mapSproutZKeyMetadata; std::map mapSaplingZKeyMetadata; typedef std::map MasterKeyMap; From 5e360fb29f1f3b8447d693c3b3bf720cc5e03006 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Wed, 12 Sep 2018 05:01:08 -0600 Subject: [PATCH 02/12] Add sapling spending keys to z_exportwallet --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/wallet_import_export.py | 54 ++++++++++++++++++++++++++++ src/keystore.cpp | 10 ++++++ src/keystore.h | 5 +++ src/wallet/rpcdump.cpp | 21 ++++++++--- src/wallet/wallet.cpp | 11 ++---- 6 files changed, 90 insertions(+), 12 deletions(-) create mode 100755 qa/rpc-tests/wallet_import_export.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 25ecc409a..fd71877f2 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -16,6 +16,7 @@ testScripts=( 'wallet_treestate.py' 'wallet_anchorfork.py' 'wallet_changeindicator.py' + 'wallet_import_export.py' 'wallet_protectcoinbase.py' 'wallet_shieldcoinbase.py' 'wallet_mergetoaddress.py' diff --git a/qa/rpc-tests/wallet_import_export.py b/qa/rpc-tests/wallet_import_export.py new file mode 100755 index 000000000..b23143c9e --- /dev/null +++ b/qa/rpc-tests/wallet_import_export.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python2 +# Copyright (c) 2018 The Zcash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_true, start_nodes + +class WalletImportExportTest (BitcoinTestFramework): + def setup_network(self, split=False): + extra_args = [["-exportdir={}/export{}".format(self.options.tmpdir, i)] for i in range(2)] + self.nodes = start_nodes(2, self.options.tmpdir, extra_args) + + def run_test(self): + sprout_address0 = self.nodes[0].z_getnewaddress('sprout') + sapling_address0 = self.nodes[0].z_getnewaddress('sapling') + + # node 0 should have the keys + dump_path = self.nodes[0].z_exportwallet('walletdump') + (t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path) + + assert_true(sprout_address0 in sprout_keys0) + assert_true(sapling_address0 in sapling_keys0) + + # node 1 should not + dump_path = self.nodes[1].z_exportwallet('walletdump') + (t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path) + + assert_true(sprout_address0 not in sprout_keys1) + assert_true(sapling_address0 not in sapling_keys1) + +# Helper functions +def parse_wallet_file(dump_path): + file_lines = open(dump_path, "r").readlines() + + (t_keys, i) = parse_wallet_file_lines(file_lines, 0) + (sprout_keys, i) = parse_wallet_file_lines(file_lines, i) + (sapling_keys, i) = parse_wallet_file_lines(file_lines, i) + + return (t_keys, sprout_keys, sapling_keys) + +def parse_wallet_file_lines(file_lines, i): + keys = [] + # skip blank lines and comments + while i < len(file_lines) and (file_lines[i] == '\n' or file_lines[i].startswith("#")): + i += 1 + # add keys until we hit another blank line or comment + while i < len(file_lines) and not (file_lines[i] == '\n' or file_lines[i].startswith("#")): + keys.append(file_lines[i]) + i += 1 + return ("".join(keys), i) + +if __name__ == '__main__': + WalletImportExportTest().main() \ No newline at end of file diff --git a/src/keystore.cpp b/src/keystore.cpp index 8262c8aab..fd42f7634 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -226,3 +226,13 @@ bool CBasicKeyStore::GetSaplingIncomingViewingKey(const libzcash::SaplingPayment } return false; } + +bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymentAddress &addr, + libzcash::SaplingExtendedSpendingKey &extskOut) const { + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + + return GetSaplingIncomingViewingKey(addr, ivk) && + GetSaplingFullViewingKey(ivk, fvk) && + GetSaplingSpendingKey(fvk, extskOut); +} diff --git a/src/keystore.h b/src/keystore.h index a8a8f62f5..5a80384bb 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -273,6 +273,11 @@ public: virtual bool GetSaplingIncomingViewingKey( const libzcash::SaplingPaymentAddress &addr, libzcash::SaplingIncomingViewingKey& ivkOut) const; + + bool GetSaplingExtendedSpendingKey( + const libzcash::SaplingPaymentAddress &addr, + libzcash::SaplingExtendedSpendingKey &extskOut) const; + void GetSaplingPaymentAddresses(std::set &setAddress) const { setAddress.clear(); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index f6bc6528f..87d29a6ec 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -318,7 +318,7 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys continue; } else { LogPrint("zrpc", "Importing detected an error: invalid spending key. Trying as a transparent key...\n"); - // Not a valid spending key, so carry on and see if it's a Zcash style address. + // Not a valid spending key, so carry on and see if it's a Zcash style t-address. } } @@ -529,18 +529,31 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) file << "\n"; if (fDumpZKeys) { - std::set addresses; - pwalletMain->GetSproutPaymentAddresses(addresses); + std::set sproutAddresses; + pwalletMain->GetSproutPaymentAddresses(sproutAddresses); file << "\n"; file << "# Zkeys\n"; file << "\n"; - for (auto addr : addresses ) { + for (auto addr : sproutAddresses) { libzcash::SproutSpendingKey key; if (pwalletMain->GetSproutSpendingKey(addr, key)) { std::string strTime = EncodeDumpTime(pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime); file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(key), strTime, EncodePaymentAddress(addr)); } } + std::set saplingAddresses; + pwalletMain->GetSaplingPaymentAddresses(saplingAddresses); + file << "\n"; + file << "# Sapling keys\n"; + file << "\n"; + for (auto addr : saplingAddresses) { + libzcash::SaplingExtendedSpendingKey extsk; + if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) { + auto ivk = extsk.expsk.full_viewing_key().in_viewing_key(); + std::string strTime = EncodeDumpTime(pwalletMain->mapSaplingZKeyMetadata[ivk].nCreateTime); + file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, EncodePaymentAddress(addr)); + } + } file << "\n"; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f4e692219..abd064501 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4553,14 +4553,9 @@ boost::optional GetSpendingKeyForPaymentAddress::operator boost::optional GetSpendingKeyForPaymentAddress::operator()( const libzcash::SaplingPaymentAddress &zaddr) const { - libzcash::SaplingIncomingViewingKey ivk; - libzcash::SaplingFullViewingKey fvk; - libzcash::SaplingExtendedSpendingKey sk; - - if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) && - m_wallet->GetSaplingFullViewingKey(ivk, fvk) && - m_wallet->GetSaplingSpendingKey(fvk, sk)) { - return libzcash::SpendingKey(sk); + libzcash::SaplingExtendedSpendingKey extsk; + if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) { + return libzcash::SpendingKey(extsk); } else { return boost::none; } From a0783bb957b4782b49b60ec8992b9547eaaa421a Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Wed, 12 Sep 2018 05:02:09 -0600 Subject: [PATCH 03/12] Rename AddZKey to include sprout --- src/wallet/gtest/test_wallet_zkeys.cpp | 4 ++-- src/wallet/rpcdump.cpp | 4 ++-- src/wallet/wallet.cpp | 6 +++--- src/wallet/wallet.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 8a2ecd8d3..e4a8eeeb4 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -63,7 +63,7 @@ TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) { /** * This test covers methods on CWallet * GenerateNewZKey() - * AddZKey() + * AddSproutZKey() * LoadZKey() * LoadZKeyMetadata() */ @@ -89,7 +89,7 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) { // manually add new spending key to wallet auto sk = libzcash::SproutSpendingKey::random(); - ASSERT_TRUE(wallet.AddZKey(sk)); + ASSERT_TRUE(wallet.AddSproutZKey(sk)); // verify wallet did add it addr = sk.address(); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 87d29a6ec..c2f4f510f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -308,7 +308,7 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys } int64_t nTime = DecodeDumpTime(vstr[1]); LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); - if (!pwalletMain->AddZKey(key)) { + if (!pwalletMain->AddSproutZKey(key)) { // Something went wrong fGood = false; continue; @@ -580,7 +580,7 @@ public: } else { m_wallet->MarkDirty(); - if (!m_wallet-> AddZKey(sk)) { + if (!m_wallet-> AddSproutZKey(sk)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index abd064501..e39c687c3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -96,8 +96,8 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey() int64_t nCreationTime = GetTime(); mapSproutZKeyMetadata[addr] = CKeyMetadata(nCreationTime); - if (!AddZKey(k)) - throw std::runtime_error("CWallet::GenerateNewZKey(): AddZKey failed"); + if (!AddSproutZKey(k)) + throw std::runtime_error("CWallet::GenerateNewZKey(): AddSproutZKey failed"); return addr; } @@ -169,7 +169,7 @@ bool CWallet::AddSaplingZKey( // Add spending key to keystore and persist to disk -bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key) +bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key) { AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata auto addr = key.address(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e660c82df..1b7331d0c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1031,7 +1031,7 @@ public: //! Generates a new zaddr libzcash::PaymentAddress GenerateNewZKey(); //! Adds spending key to the store, and saves it to disk - bool AddZKey(const libzcash::SproutSpendingKey &key); + bool AddSproutZKey(const libzcash::SproutSpendingKey &key); //! Adds spending key to the store, without saving it to disk (used by LoadWallet) bool LoadZKey(const libzcash::SproutSpendingKey &key); //! Load spending key metadata (used by LoadWallet) From fcab001b1ed9696f050e0f47b4808eac17766a27 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Fri, 14 Sep 2018 15:44:03 -0600 Subject: [PATCH 04/12] Move AddSpendingKeyToWallet --- src/wallet/rpcdump.cpp | 61 ------------------------------------------ src/wallet/wallet.cpp | 51 +++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 15 +++++++++++ 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c2f4f510f..e9ce51bcb 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -563,67 +563,6 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) return exportfilepath.string(); } -class AddSpendingKeyToWallet : public boost::static_visitor -{ -private: - CWallet *m_wallet; - const Consensus::Params ¶ms; -public: - AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms) : - m_wallet(wallet), params(params) {} - - bool operator()(const libzcash::SproutSpendingKey &sk) const { - auto addr = sk.address(); - // Don't throw error in case a key is already there - if (m_wallet->HaveSproutSpendingKey(addr)) { - return true; - } else { - m_wallet->MarkDirty(); - - if (!m_wallet-> AddSproutZKey(sk)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } - - m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = 1; - - return false; - } - } - - bool operator()(const libzcash::SaplingExtendedSpendingKey &sk) const { - auto fvk = sk.expsk.full_viewing_key(); - auto ivk = fvk.in_viewing_key(); - auto addr = sk.DefaultAddress(); - { - // Don't throw error in case a key is already there - if (m_wallet->HaveSaplingSpendingKey(fvk)) { - return true; - } else { - m_wallet->MarkDirty(); - - if (!m_wallet-> AddSaplingZKey(sk, addr)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } - - // Sapling addresses can't have been used in transactions prior to activation. - if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) { - m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1; - } else { - // Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates - m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1540512000; - } - - return false; - } - } - } - - bool operator()(const libzcash::InvalidEncoding& no) const { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); - } - -}; - UniValue z_importkey(const UniValue& params, bool fHelp) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e39c687c3..dc2670f9d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -14,6 +14,7 @@ #include "key_io.h" #include "main.h" #include "net.h" +#include "rpc/protocol.h" #include "script/script.h" #include "script/sign.h" #include "timedata.h" @@ -4567,3 +4568,53 @@ boost::optional GetSpendingKeyForPaymentAddress::operator // Defaults to InvalidEncoding return libzcash::SpendingKey(); } + +bool AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const { + auto addr = sk.address(); + // Don't throw error in case a key is already there + if (m_wallet->HaveSproutSpendingKey(addr)) { + return true; + } else { + m_wallet->MarkDirty(); + + if (!m_wallet-> AddSproutZKey(sk)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + + m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = 1; + + return false; + } +} + +bool AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const { + auto fvk = sk.expsk.full_viewing_key(); + auto ivk = fvk.in_viewing_key(); + auto addr = sk.DefaultAddress(); + { + // Don't throw error in case a key is already there + if (m_wallet->HaveSaplingSpendingKey(fvk)) { + return true; + } else { + m_wallet->MarkDirty(); + + if (!m_wallet-> AddSaplingZKey(sk, addr)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + + // Sapling addresses can't have been used in transactions prior to activation. + if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) { + m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1; + } else { + // Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates + m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1540512000; + } + + return false; + } + } +} + +bool AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1b7331d0c..c0b36d88d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1376,4 +1376,19 @@ public: boost::optional operator()(const libzcash::InvalidEncoding& no) const; }; +class AddSpendingKeyToWallet : public boost::static_visitor +{ +private: + CWallet *m_wallet; + const Consensus::Params ¶ms; +public: + AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms) : + m_wallet(wallet), params(params) {} + + bool operator()(const libzcash::SproutSpendingKey &sk) const; + bool operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; + bool operator()(const libzcash::InvalidEncoding& no) const; +}; + + #endif // BITCOIN_WALLET_WALLET_H From 0f03de55364218c4915928ebb00ebfe207e5013f Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Wed, 12 Sep 2018 05:03:13 -0600 Subject: [PATCH 05/12] Return more information when adding a spending key --- src/wallet/rpcdump.cpp | 8 ++++++-- src/wallet/wallet.cpp | 29 +++++++++++------------------ src/wallet/wallet.h | 14 ++++++++++---- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index e9ce51bcb..9dc7f69fc 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -635,11 +635,15 @@ UniValue z_importkey(const UniValue& params, bool fHelp) } // Sapling support - auto keyAlreadyExists = boost::apply_visitor( + auto addResult = boost::apply_visitor( AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey); - if (keyAlreadyExists && fIgnoreExistingKey) { + if (addResult == KeyAlreadyExists && fIgnoreExistingKey) { return NullUniValue; } + pwalletMain->MarkDirty(); + if (addResult == KeyNotAdded) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } // whenever a key is imported, we need to scan the whole chain pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index dc2670f9d..e4695b301 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4569,37 +4569,30 @@ boost::optional GetSpendingKeyForPaymentAddress::operator return libzcash::SpendingKey(); } -bool AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const { +SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const { auto addr = sk.address(); // Don't throw error in case a key is already there if (m_wallet->HaveSproutSpendingKey(addr)) { - return true; - } else { - m_wallet->MarkDirty(); - - if (!m_wallet-> AddSproutZKey(sk)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } - + return KeyAlreadyExists; + } else if (m_wallet-> AddSproutZKey(sk)) { m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = 1; - - return false; + return KeyAdded; + } else { + return KeyNotAdded; } } -bool AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const { +SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const { auto fvk = sk.expsk.full_viewing_key(); auto ivk = fvk.in_viewing_key(); auto addr = sk.DefaultAddress(); { // Don't throw error in case a key is already there if (m_wallet->HaveSaplingSpendingKey(fvk)) { - return true; + return KeyAlreadyExists; } else { - m_wallet->MarkDirty(); - if (!m_wallet-> AddSaplingZKey(sk, addr)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + return KeyNotAdded; } // Sapling addresses can't have been used in transactions prior to activation. @@ -4610,11 +4603,11 @@ bool AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingK m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1540512000; } - return false; + return KeyAdded; } } } -bool AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const { +SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c0b36d88d..0032138e1 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1376,7 +1376,13 @@ public: boost::optional operator()(const libzcash::InvalidEncoding& no) const; }; -class AddSpendingKeyToWallet : public boost::static_visitor +enum SpendingKeyAddResult { + KeyAlreadyExists, + KeyAdded, + KeyNotAdded, +}; + +class AddSpendingKeyToWallet : public boost::static_visitor { private: CWallet *m_wallet; @@ -1385,9 +1391,9 @@ public: AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms) : m_wallet(wallet), params(params) {} - bool operator()(const libzcash::SproutSpendingKey &sk) const; - bool operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; - bool operator()(const libzcash::InvalidEncoding& no) const; + SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const; + SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; + SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const; }; From 9bcf90e2de13d96f0209b61b9d8bb5be15686d12 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Wed, 12 Sep 2018 05:03:45 -0600 Subject: [PATCH 06/12] Add sapling support to z_importwallet --- qa/rpc-tests/wallet_import_export.py | 20 +++++++++++++++----- src/wallet/rpcdump.cpp | 20 ++++++-------------- src/wallet/wallet.cpp | 17 +++++++++++------ src/wallet/wallet.h | 6 +++++- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/qa/rpc-tests/wallet_import_export.py b/qa/rpc-tests/wallet_import_export.py index b23143c9e..6577acb53 100755 --- a/qa/rpc-tests/wallet_import_export.py +++ b/qa/rpc-tests/wallet_import_export.py @@ -16,19 +16,29 @@ class WalletImportExportTest (BitcoinTestFramework): sapling_address0 = self.nodes[0].z_getnewaddress('sapling') # node 0 should have the keys - dump_path = self.nodes[0].z_exportwallet('walletdump') - (t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path) + dump_path0 = self.nodes[0].z_exportwallet('walletdump') + (t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0) assert_true(sprout_address0 in sprout_keys0) assert_true(sapling_address0 in sapling_keys0) - # node 1 should not - dump_path = self.nodes[1].z_exportwallet('walletdump') - (t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path) + # node 1 should not have the keys + dump_path1 = self.nodes[1].z_exportwallet('walletdumpbefore') + (t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1) assert_true(sprout_address0 not in sprout_keys1) assert_true(sapling_address0 not in sapling_keys1) + # import wallet to node 1 + self.nodes[1].z_importwallet(dump_path0) + + # node 1 should now have the keys + dump_path1 = self.nodes[1].z_exportwallet('walletdumpafter') + (t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1) + + assert_true(sprout_address0 in sprout_keys1) + assert_true(sapling_address0 in sapling_keys1) + # Helper functions def parse_wallet_file(dump_path): file_lines = open(dump_path, "r").readlines() diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 9dc7f69fc..42023de4a 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -297,24 +297,16 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys // Let's see if the address is a valid Zcash spending key if (fImportZKeys) { auto spendingkey = DecodeSpendingKey(vstr[0]); + int64_t nTime = DecodeDumpTime(vstr[1]); if (IsValidSpendingKey(spendingkey)) { - // TODO: Add Sapling support. For now, ensure we can freely convert. - assert(boost::get(&spendingkey) != nullptr); - auto key = boost::get(spendingkey); - auto addr = key.address(); - if (pwalletMain->HaveSproutSpendingKey(addr)) { - LogPrint("zrpc", "Skipping import of zaddr %s (key already present)\n", EncodePaymentAddress(addr)); - continue; - } - int64_t nTime = DecodeDumpTime(vstr[1]); - LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); - if (!pwalletMain->AddSproutZKey(key)) { + auto addResult = boost::apply_visitor( + AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, true), spendingkey); + if (addResult == KeyAlreadyExists){ + LogPrint("zrpc", "Skipping import of zaddr (key already present)\n"); + } else if (addResult == KeyNotAdded) { // Something went wrong fGood = false; - continue; } - // Successfully imported zaddr. Now import the metadata. - pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime = nTime; continue; } else { LogPrint("zrpc", "Importing detected an error: invalid spending key. Trying as a transparent key...\n"); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e4695b301..7fd20d69b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4570,12 +4570,14 @@ boost::optional GetSpendingKeyForPaymentAddress::operator } SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const { - auto addr = sk.address(); - // Don't throw error in case a key is already there + auto addr = sk.address(); + if (log){ + LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); + } if (m_wallet->HaveSproutSpendingKey(addr)) { return KeyAlreadyExists; } else if (m_wallet-> AddSproutZKey(sk)) { - m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = 1; + m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = nTime; return KeyAdded; } else { return KeyNotAdded; @@ -4587,6 +4589,9 @@ SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingE auto ivk = fvk.in_viewing_key(); auto addr = sk.DefaultAddress(); { + if (log){ + LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); + } // Don't throw error in case a key is already there if (m_wallet->HaveSaplingSpendingKey(fvk)) { return KeyAlreadyExists; @@ -4597,10 +4602,10 @@ SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingE // Sapling addresses can't have been used in transactions prior to activation. if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) { - m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1; + m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime; } else { - // Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates - m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1540512000; + // 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates + m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime); } return KeyAdded; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0032138e1..5b2d6731d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1387,9 +1387,13 @@ class AddSpendingKeyToWallet : public boost::static_visitor Date: Sat, 9 Jul 2016 11:41:01 +0200 Subject: [PATCH 07/12] [Wallet] extend CKeyMetadata with HD keypath Zcash: modified for zip32 --- src/wallet/wallet.cpp | 5 ++++- src/wallet/walletdb.h | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7fd20d69b..be1b27036 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -117,18 +117,21 @@ SaplingPaymentAddress CWallet::GenerateNewSaplingZKey() throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found"); auto m = libzcash::SaplingExtendedSpendingKey::Master(seed); + uint32_t bip44CoinType = Params().BIP44CoinType(); // We use a fixed keypath scheme of m/32'/coin_type'/account' // Derive m/32' auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); // Derive m/32'/coin_type' - auto m_32h_cth = m_32h.Derive(Params().BIP44CoinType() | ZIP32_HARDENED_KEY_LIMIT); + auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); // Derive account key at next index, skip keys already known to the wallet libzcash::SaplingExtendedSpendingKey xsk; do { xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT); + metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(hdChain.saplingAccountCounter) + "'"; + metadata.seedFp = hdChain.seedFp; // Increment childkey index hdChain.saplingAccountCounter++; } while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key())); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 7cd446e9b..a42bb937c 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -77,9 +77,13 @@ public: class CKeyMetadata { public: - static const int CURRENT_VERSION=1; + static const int VERSION_BASIC=1; + static const int VERSION_WITH_HDDATA=10; + static const int CURRENT_VERSION=VERSION_WITH_HDDATA; int nVersion; int64_t nCreateTime; // 0 means unknown + std::string hdKeypath; //optional HD/zip32 keypath + uint256 seedFp; CKeyMetadata() { @@ -89,6 +93,7 @@ public: { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = nCreateTime_; + hdKeypath.clear(); } ADD_SERIALIZE_METHODS; @@ -97,12 +102,18 @@ public: inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(this->nVersion); READWRITE(nCreateTime); + if (this->nVersion >= VERSION_WITH_HDDATA) + { + READWRITE(hdKeypath); + READWRITE(seedFp); + } } void SetNull() { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; + hdKeypath.clear(); } }; From 82e71233b0a62ef895e6c6f870077518ca630c3b Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Sat, 9 Jul 2016 11:25:02 +0200 Subject: [PATCH 08/12] [Wallet] print hd masterkeyid in getwalletinfo Zcash: modified for zip32 --- src/wallet/rpcwallet.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a9dfbfb0e..39d019b92 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2280,6 +2280,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" + " \"seedfp\": \"uint256\", (string) the BLAKE2b-256 hash of the HD seed\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2299,6 +2300,9 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); + uint256 seedFp = pwalletMain->GetHDChain().seedFp; + if (!seedFp.IsNull()) + obj.push_back(Pair("seedfp", seedFp.GetHex())); return obj; } From 002753ae644d798470cde5e36482ed8e96b1e5ec Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 15 Jul 2016 10:33:25 +0200 Subject: [PATCH 09/12] [Wallet] ensure CKeyMetadata.hdMasterKeyID will be cleared during SetNull() Zcash: modified for zip32 --- src/wallet/walletdb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index a42bb937c..dc58adb65 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -91,9 +91,8 @@ public: } CKeyMetadata(int64_t nCreateTime_) { - nVersion = CKeyMetadata::CURRENT_VERSION; + SetNull(); nCreateTime = nCreateTime_; - hdKeypath.clear(); } ADD_SERIALIZE_METHODS; @@ -114,6 +113,7 @@ public: nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; hdKeypath.clear(); + seedFp.SetNull(); } }; From ae807af41344192af599046298a07ff69118bc98 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Fri, 14 Sep 2018 17:26:02 -0600 Subject: [PATCH 10/12] Export comment on HDSeed and fingerprint with wallet --- qa/rpc-tests/wallet_import_export.py | 4 +++- src/wallet/rpcdump.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/wallet_import_export.py b/qa/rpc-tests/wallet_import_export.py index 6577acb53..355c16d05 100755 --- a/qa/rpc-tests/wallet_import_export.py +++ b/qa/rpc-tests/wallet_import_export.py @@ -42,7 +42,9 @@ class WalletImportExportTest (BitcoinTestFramework): # Helper functions def parse_wallet_file(dump_path): file_lines = open(dump_path, "r").readlines() - + # WE expect information about the HDSeed and fingerpring in the header + assert_true("HDSeed" in file_lines[4], "Expected HDSeed") + assert_true("fingerprint" in file_lines[4], "Expected fingerprint") (t_keys, i) = parse_wallet_file_lines(file_lines, 0) (sprout_keys, i) = parse_wallet_file_lines(file_lines, i) (sapling_keys, i) = parse_wallet_file_lines(file_lines, i) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 42023de4a..446f3bc41 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -502,6 +502,12 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); + { + HDSeed hdSeed; + pwalletMain->GetHDSeed(hdSeed); + file << strprintf("# HDSeed=%s fingerprint=%s", pwalletMain->GetHDChain().seedFp.GetHex(), hdSeed.Fingerprint().GetHex()); + file << "\n"; + } file << "\n"; for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; From 2fe39561ec8845cbf812c9b95e45648190df0e46 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Fri, 14 Sep 2018 17:27:20 -0600 Subject: [PATCH 11/12] Export zip32 metadata with sapling keys --- qa/rpc-tests/wallet_import_export.py | 11 +++++++++-- src/wallet/rpcdump.cpp | 12 +++++++----- src/wallet/wallet.cpp | 11 +++++++++-- src/wallet/wallet.h | 15 ++++++++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/qa/rpc-tests/wallet_import_export.py b/qa/rpc-tests/wallet_import_export.py index 355c16d05..8169e36ba 100755 --- a/qa/rpc-tests/wallet_import_export.py +++ b/qa/rpc-tests/wallet_import_export.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_true, start_nodes +from test_framework.util import assert_equal, assert_true, start_nodes class WalletImportExportTest (BitcoinTestFramework): def setup_network(self, split=False): @@ -18,6 +18,9 @@ class WalletImportExportTest (BitcoinTestFramework): # node 0 should have the keys dump_path0 = self.nodes[0].z_exportwallet('walletdump') (t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0) + + for sapling_key0 in sapling_keys0.splitlines(): + assert_equal(4, len(sapling_key0.split(' #')[0].split()), "Should have 4 parameters before ' #'") assert_true(sprout_address0 in sprout_keys0) assert_true(sapling_address0 in sapling_keys0) @@ -39,6 +42,10 @@ class WalletImportExportTest (BitcoinTestFramework): assert_true(sprout_address0 in sprout_keys1) assert_true(sapling_address0 in sapling_keys1) + # make sure we have perserved the metadata + for sapling_key0 in sapling_keys0.splitlines(): + assert_true(sapling_key0 in sapling_keys1) + # Helper functions def parse_wallet_file(dump_path): file_lines = open(dump_path, "r").readlines() @@ -60,7 +67,7 @@ def parse_wallet_file_lines(file_lines, i): while i < len(file_lines) and not (file_lines[i] == '\n' or file_lines[i].startswith("#")): keys.append(file_lines[i]) i += 1 - return ("".join(keys), i) + return ("\n".join(keys), i) if __name__ == '__main__': WalletImportExportTest().main() \ No newline at end of file diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 446f3bc41..e6bd0fe3e 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -298,9 +298,11 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys if (fImportZKeys) { auto spendingkey = DecodeSpendingKey(vstr[0]); int64_t nTime = DecodeDumpTime(vstr[1]); + boost::optional hdKeypath = (vstr.size() > 2) ? boost::optional(vstr[2]) : boost::none; + boost::optional seedFpStr = (vstr.size() > 3) ? boost::optional(vstr[3]) : boost::none; if (IsValidSpendingKey(spendingkey)) { auto addResult = boost::apply_visitor( - AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, true), spendingkey); + AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, hdKeypath, seedFpStr, true), spendingkey); if (addResult == KeyAlreadyExists){ LogPrint("zrpc", "Skipping import of zaddr (key already present)\n"); } else if (addResult == KeyNotAdded) { @@ -548,8 +550,9 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) libzcash::SaplingExtendedSpendingKey extsk; if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) { auto ivk = extsk.expsk.full_viewing_key().in_viewing_key(); - std::string strTime = EncodeDumpTime(pwalletMain->mapSaplingZKeyMetadata[ivk].nCreateTime); - file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, EncodePaymentAddress(addr)); + CKeyMetadata keyMeta = pwalletMain->mapSaplingZKeyMetadata[ivk]; + std::string strTime = EncodeDumpTime(keyMeta.nCreateTime); + file << strprintf("%s %s %s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, keyMeta.hdKeypath, keyMeta.seedFp.GetHex(), EncodePaymentAddress(addr)); } } file << "\n"; @@ -633,8 +636,7 @@ UniValue z_importkey(const UniValue& params, bool fHelp) } // Sapling support - auto addResult = boost::apply_visitor( - AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey); + auto addResult = boost::apply_visitor(AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey); if (addResult == KeyAlreadyExists && fIgnoreExistingKey) { return NullUniValue; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index be1b27036..6e2631db3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4573,7 +4573,7 @@ boost::optional GetSpendingKeyForPaymentAddress::operator } SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const { - auto addr = sk.address(); + auto addr = sk.address(); if (log){ LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr)); } @@ -4610,7 +4610,14 @@ SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingE // 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime); } - + if (hdKeypath) { + m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.get(); + } + if (seedFpStr) { + uint256 seedFp; + seedFp.SetHex(seedFpStr.get()); + m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp; + } return KeyAdded; } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 5b2d6731d..50acb2394 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1388,12 +1388,21 @@ private: CWallet *m_wallet; const Consensus::Params ¶ms; int64_t nTime; + boost::optional hdKeypath; // currently sapling only + boost::optional seedFpStr; // currently sapling only bool log; public: AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms) : - m_wallet(wallet), params(params), nTime(1), log(false) {} - AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms, int64_t _nTime, bool _log) : - m_wallet(wallet), params(params), nTime(_nTime), log(_log) {} + m_wallet(wallet), params(params), nTime(1), hdKeypath(boost::none), seedFpStr(boost::none), log(false) {} + AddSpendingKeyToWallet( + CWallet *wallet, + const Consensus::Params ¶ms, + int64_t _nTime, + boost::optional _hdKeypath, + boost::optional _seedFp, + bool _log + ) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {} + SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const; SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; From b37dc4e22fa2702af53e71082620cfe0b3d63ce4 Mon Sep 17 00:00:00 2001 From: Eirik Ogilvie-Wigley Date: Fri, 14 Sep 2018 18:36:24 -0600 Subject: [PATCH 12/12] Don't export empty zip32 metadata --- qa/rpc-tests/wallet_import_export.py | 23 ++++++++++++++++------- src/wallet/rpcdump.cpp | 10 ++++++++-- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/qa/rpc-tests/wallet_import_export.py b/qa/rpc-tests/wallet_import_export.py index 8169e36ba..ee9cc42ce 100755 --- a/qa/rpc-tests/wallet_import_export.py +++ b/qa/rpc-tests/wallet_import_export.py @@ -8,10 +8,15 @@ from test_framework.util import assert_equal, assert_true, start_nodes class WalletImportExportTest (BitcoinTestFramework): def setup_network(self, split=False): - extra_args = [["-exportdir={}/export{}".format(self.options.tmpdir, i)] for i in range(2)] - self.nodes = start_nodes(2, self.options.tmpdir, extra_args) + num_nodes = 3 + extra_args = [["-exportdir={}/export{}".format(self.options.tmpdir, i)] for i in range(num_nodes)] + self.nodes = start_nodes(num_nodes, self.options.tmpdir, extra_args) def run_test(self): + sapling_address2 = self.nodes[2].z_getnewaddress('sapling') + privkey2 = self.nodes[2].z_exportkey(sapling_address2) + self.nodes[0].z_importkey(privkey2) + sprout_address0 = self.nodes[0].z_getnewaddress('sprout') sapling_address0 = self.nodes[0].z_getnewaddress('sapling') @@ -19,11 +24,14 @@ class WalletImportExportTest (BitcoinTestFramework): dump_path0 = self.nodes[0].z_exportwallet('walletdump') (t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0) - for sapling_key0 in sapling_keys0.splitlines(): - assert_equal(4, len(sapling_key0.split(' #')[0].split()), "Should have 4 parameters before ' #'") - + sapling_line_lengths = [len(sapling_key0.split(' #')[0].split()) for sapling_key0 in sapling_keys0.splitlines()] + assert_equal(2, len(sapling_line_lengths), "Should have 2 sapling keys") + assert_true(2 in sapling_line_lengths, "Should have a key with 2 parameters") + assert_true(4 in sapling_line_lengths, "Should have a key with 4 parameters") + assert_true(sprout_address0 in sprout_keys0) assert_true(sapling_address0 in sapling_keys0) + assert_true(sapling_address2 in sapling_keys0) # node 1 should not have the keys dump_path1 = self.nodes[1].z_exportwallet('walletdumpbefore') @@ -41,6 +49,7 @@ class WalletImportExportTest (BitcoinTestFramework): assert_true(sprout_address0 in sprout_keys1) assert_true(sapling_address0 in sapling_keys1) + assert_true(sapling_address2 in sapling_keys1) # make sure we have perserved the metadata for sapling_key0 in sapling_keys0.splitlines(): @@ -49,7 +58,7 @@ class WalletImportExportTest (BitcoinTestFramework): # Helper functions def parse_wallet_file(dump_path): file_lines = open(dump_path, "r").readlines() - # WE expect information about the HDSeed and fingerpring in the header + # We expect information about the HDSeed and fingerpring in the header assert_true("HDSeed" in file_lines[4], "Expected HDSeed") assert_true("fingerprint" in file_lines[4], "Expected fingerprint") (t_keys, i) = parse_wallet_file_lines(file_lines, 0) @@ -67,7 +76,7 @@ def parse_wallet_file_lines(file_lines, i): while i < len(file_lines) and not (file_lines[i] == '\n' or file_lines[i].startswith("#")): keys.append(file_lines[i]) i += 1 - return ("\n".join(keys), i) + return ("".join(keys), i) if __name__ == '__main__': WalletImportExportTest().main() \ No newline at end of file diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index e6bd0fe3e..81e2969d5 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -298,7 +298,8 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys if (fImportZKeys) { auto spendingkey = DecodeSpendingKey(vstr[0]); int64_t nTime = DecodeDumpTime(vstr[1]); - boost::optional hdKeypath = (vstr.size() > 2) ? boost::optional(vstr[2]) : boost::none; + // Only include hdKeypath and seedFpStr if we have both + boost::optional hdKeypath = (vstr.size() > 3) ? boost::optional(vstr[2]) : boost::none; boost::optional seedFpStr = (vstr.size() > 3) ? boost::optional(vstr[3]) : boost::none; if (IsValidSpendingKey(spendingkey)) { auto addResult = boost::apply_visitor( @@ -552,7 +553,12 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) auto ivk = extsk.expsk.full_viewing_key().in_viewing_key(); CKeyMetadata keyMeta = pwalletMain->mapSaplingZKeyMetadata[ivk]; std::string strTime = EncodeDumpTime(keyMeta.nCreateTime); - file << strprintf("%s %s %s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, keyMeta.hdKeypath, keyMeta.seedFp.GetHex(), EncodePaymentAddress(addr)); + // Keys imported with z_importkey do not have zip32 metadata + if (keyMeta.hdKeypath.empty() || keyMeta.seedFp.IsNull()) { + file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, EncodePaymentAddress(addr)); + } else { + file << strprintf("%s %s %s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, keyMeta.hdKeypath, keyMeta.seedFp.GetHex(), EncodePaymentAddress(addr)); + } } } file << "\n";