diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 0bc6fefaa..fead21dff 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..ee9cc42ce --- /dev/null +++ b/qa/rpc-tests/wallet_import_export.py @@ -0,0 +1,82 @@ +#!/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_equal, assert_true, start_nodes + +class WalletImportExportTest (BitcoinTestFramework): + def setup_network(self, split=False): + 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') + + # 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) + + 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') + (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) + assert_true(sapling_address2 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() + # 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) + + 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/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 37f4b618a..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(); @@ -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..81e2969d5 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -297,28 +297,23 @@ 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]); + // 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)) { - // 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->AddZKey(key)) { + auto addResult = boost::apply_visitor( + 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) { // Something went wrong fGood = false; - continue; } - // Successfully imported zaddr. Now import the metadata. - pwalletMain->mapZKeyMetadata[addr].nCreateTime = nTime; 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. } } @@ -510,6 +505,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; @@ -529,18 +530,37 @@ 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->mapZKeyMetadata[addr].nCreateTime); + 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(); + CKeyMetadata keyMeta = pwalletMain->mapSaplingZKeyMetadata[ivk]; + std::string strTime = EncodeDumpTime(keyMeta.nCreateTime); + // 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"; } @@ -550,67 +570,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-> AddZKey(sk)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } - - m_wallet->mapZKeyMetadata[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) { @@ -683,11 +642,14 @@ UniValue z_importkey(const UniValue& params, bool fHelp) } // Sapling support - auto keyAlreadyExists = boost::apply_visitor( - AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey); - if (keyAlreadyExists && fIgnoreExistingKey) { + auto addResult = boost::apply_visitor(AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey); + 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/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; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d931f7384..6e2631db3 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" @@ -83,7 +84,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,10 +95,10 @@ 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"); + if (!AddSproutZKey(k)) + throw std::runtime_error("CWallet::GenerateNewZKey(): AddSproutZKey failed"); return addr; } @@ -116,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())); @@ -169,9 +173,9 @@ 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); // mapZKeyMetadata + AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata auto addr = key.address(); if (!CCryptoKeyStore::AddSproutSpendingKey(key)) @@ -187,7 +191,7 @@ bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key) if (!IsCrypted()) { return CWalletDB(strWalletFile).WriteZKey(addr, key, - mapZKeyMetadata[addr]); + mapSproutZKeyMetadata[addr]); } return true; } @@ -278,12 +282,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 +319,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; } @@ -4553,14 +4557,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; } @@ -4572,3 +4571,58 @@ boost::optional GetSpendingKeyForPaymentAddress::operator // Defaults to InvalidEncoding return libzcash::SpendingKey(); } + +SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const { + 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 = nTime; + return KeyAdded; + } else { + return KeyNotAdded; + } +} + +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(); + { + 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; + } else { + if (!m_wallet-> AddSaplingZKey(sk, addr)) { + return KeyNotAdded; + } + + // 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 = nTime; + } else { + // 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; + } + } +} + +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 dda1e4dfe..50acb2394 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; @@ -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) @@ -1376,4 +1376,38 @@ public: boost::optional operator()(const libzcash::InvalidEncoding& no) const; }; +enum SpendingKeyAddResult { + KeyAlreadyExists, + KeyAdded, + KeyNotAdded, +}; + +class AddSpendingKeyToWallet : public boost::static_visitor +{ +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), 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; + SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const; +}; + + #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 7cd446e9b..dc58adb65 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() { @@ -87,7 +91,7 @@ public: } CKeyMetadata(int64_t nCreateTime_) { - nVersion = CKeyMetadata::CURRENT_VERSION; + SetNull(); nCreateTime = nCreateTime_; } @@ -97,12 +101,19 @@ 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(); + seedFp.SetNull(); } };