Auto merge of #3491 - Eirik0:3218-sapling-import-export-wallet, r=str4d
Add Sapling support to z_importwallet and z_exportwallet Includes code adapted from upstream PR https://github.com/bitcoin/bitcoin/pull/8323 Closes #3218.
This commit is contained in:
commit
25c3f903c1
|
@ -16,6 +16,7 @@ testScripts=(
|
||||||
'wallet_treestate.py'
|
'wallet_treestate.py'
|
||||||
'wallet_anchorfork.py'
|
'wallet_anchorfork.py'
|
||||||
'wallet_changeindicator.py'
|
'wallet_changeindicator.py'
|
||||||
|
'wallet_import_export.py'
|
||||||
'wallet_protectcoinbase.py'
|
'wallet_protectcoinbase.py'
|
||||||
'wallet_shieldcoinbase.py'
|
'wallet_shieldcoinbase.py'
|
||||||
'wallet_mergetoaddress.py'
|
'wallet_mergetoaddress.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()
|
|
@ -226,3 +226,13 @@ bool CBasicKeyStore::GetSaplingIncomingViewingKey(const libzcash::SaplingPayment
|
||||||
}
|
}
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -273,6 +273,11 @@ public:
|
||||||
virtual bool GetSaplingIncomingViewingKey(
|
virtual bool GetSaplingIncomingViewingKey(
|
||||||
const libzcash::SaplingPaymentAddress &addr,
|
const libzcash::SaplingPaymentAddress &addr,
|
||||||
libzcash::SaplingIncomingViewingKey& ivkOut) const;
|
libzcash::SaplingIncomingViewingKey& ivkOut) const;
|
||||||
|
|
||||||
|
bool GetSaplingExtendedSpendingKey(
|
||||||
|
const libzcash::SaplingPaymentAddress &addr,
|
||||||
|
libzcash::SaplingExtendedSpendingKey &extskOut) const;
|
||||||
|
|
||||||
void GetSaplingPaymentAddresses(std::set<libzcash::SaplingPaymentAddress> &setAddress) const
|
void GetSaplingPaymentAddresses(std::set<libzcash::SaplingPaymentAddress> &setAddress) const
|
||||||
{
|
{
|
||||||
setAddress.clear();
|
setAddress.clear();
|
||||||
|
|
|
@ -63,7 +63,7 @@ TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) {
|
||||||
/**
|
/**
|
||||||
* This test covers methods on CWallet
|
* This test covers methods on CWallet
|
||||||
* GenerateNewZKey()
|
* GenerateNewZKey()
|
||||||
* AddZKey()
|
* AddSproutZKey()
|
||||||
* LoadZKey()
|
* LoadZKey()
|
||||||
* LoadZKeyMetadata()
|
* LoadZKeyMetadata()
|
||||||
*/
|
*/
|
||||||
|
@ -89,7 +89,7 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) {
|
||||||
|
|
||||||
// manually add new spending key to wallet
|
// manually add new spending key to wallet
|
||||||
auto sk = libzcash::SproutSpendingKey::random();
|
auto sk = libzcash::SproutSpendingKey::random();
|
||||||
ASSERT_TRUE(wallet.AddZKey(sk));
|
ASSERT_TRUE(wallet.AddSproutZKey(sk));
|
||||||
|
|
||||||
// verify wallet did add it
|
// verify wallet did add it
|
||||||
addr = sk.address();
|
addr = sk.address();
|
||||||
|
@ -116,7 +116,7 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) {
|
||||||
ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta));
|
ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta));
|
||||||
|
|
||||||
// check metadata is the same
|
// check metadata is the same
|
||||||
CKeyMetadata m= wallet.mapZKeyMetadata[addr];
|
CKeyMetadata m= wallet.mapSproutZKeyMetadata[addr];
|
||||||
ASSERT_EQ(m.nCreateTime, now);
|
ASSERT_EQ(m.nCreateTime, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
|
||||||
ASSERT_EQ(1, addrs.size());
|
ASSERT_EQ(1, addrs.size());
|
||||||
|
|
||||||
// wallet should have default metadata for addr with null createtime
|
// 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_EQ(m.nCreateTime, 0);
|
||||||
ASSERT_NE(m.nCreateTime, now);
|
ASSERT_NE(m.nCreateTime, now);
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
|
||||||
ASSERT_EQ(2, addrs.size());
|
ASSERT_EQ(2, addrs.size());
|
||||||
|
|
||||||
// check metadata is now the same
|
// check metadata is now the same
|
||||||
m = wallet.mapZKeyMetadata[addr];
|
m = wallet.mapSproutZKeyMetadata[addr];
|
||||||
ASSERT_EQ(m.nCreateTime, now);
|
ASSERT_EQ(m.nCreateTime, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// Let's see if the address is a valid Zcash spending key
|
||||||
if (fImportZKeys) {
|
if (fImportZKeys) {
|
||||||
auto spendingkey = DecodeSpendingKey(vstr[0]);
|
auto spendingkey = DecodeSpendingKey(vstr[0]);
|
||||||
|
int64_t nTime = DecodeDumpTime(vstr[1]);
|
||||||
|
// Only include hdKeypath and seedFpStr if we have both
|
||||||
|
boost::optional<std::string> hdKeypath = (vstr.size() > 3) ? boost::optional<std::string>(vstr[2]) : boost::none;
|
||||||
|
boost::optional<std::string> seedFpStr = (vstr.size() > 3) ? boost::optional<std::string>(vstr[3]) : boost::none;
|
||||||
if (IsValidSpendingKey(spendingkey)) {
|
if (IsValidSpendingKey(spendingkey)) {
|
||||||
// TODO: Add Sapling support. For now, ensure we can freely convert.
|
auto addResult = boost::apply_visitor(
|
||||||
assert(boost::get<libzcash::SproutSpendingKey>(&spendingkey) != nullptr);
|
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, hdKeypath, seedFpStr, true), spendingkey);
|
||||||
auto key = boost::get<libzcash::SproutSpendingKey>(spendingkey);
|
if (addResult == KeyAlreadyExists){
|
||||||
auto addr = key.address();
|
LogPrint("zrpc", "Skipping import of zaddr (key already present)\n");
|
||||||
if (pwalletMain->HaveSproutSpendingKey(addr)) {
|
} else if (addResult == KeyNotAdded) {
|
||||||
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)) {
|
|
||||||
// Something went wrong
|
// Something went wrong
|
||||||
fGood = false;
|
fGood = false;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
// Successfully imported zaddr. Now import the metadata.
|
|
||||||
pwalletMain->mapZKeyMetadata[addr].nCreateTime = nTime;
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
LogPrint("zrpc", "Importing detected an error: invalid spending key. Trying as a transparent key...\n");
|
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("# * 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("# * 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()));
|
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";
|
file << "\n";
|
||||||
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
|
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
|
||||||
const CKeyID &keyid = it->second;
|
const CKeyID &keyid = it->second;
|
||||||
|
@ -529,18 +530,37 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
|
||||||
file << "\n";
|
file << "\n";
|
||||||
|
|
||||||
if (fDumpZKeys) {
|
if (fDumpZKeys) {
|
||||||
std::set<libzcash::SproutPaymentAddress> addresses;
|
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
|
||||||
pwalletMain->GetSproutPaymentAddresses(addresses);
|
pwalletMain->GetSproutPaymentAddresses(sproutAddresses);
|
||||||
file << "\n";
|
file << "\n";
|
||||||
file << "# Zkeys\n";
|
file << "# Zkeys\n";
|
||||||
file << "\n";
|
file << "\n";
|
||||||
for (auto addr : addresses ) {
|
for (auto addr : sproutAddresses) {
|
||||||
libzcash::SproutSpendingKey key;
|
libzcash::SproutSpendingKey key;
|
||||||
if (pwalletMain->GetSproutSpendingKey(addr, 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));
|
file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(key), strTime, EncodePaymentAddress(addr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::set<libzcash::SaplingPaymentAddress> 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";
|
file << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,67 +570,6 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
|
||||||
return exportfilepath.string();
|
return exportfilepath.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddSpendingKeyToWallet : public boost::static_visitor<bool>
|
|
||||||
{
|
|
||||||
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)
|
UniValue z_importkey(const UniValue& params, bool fHelp)
|
||||||
{
|
{
|
||||||
|
@ -683,11 +642,14 @@ UniValue z_importkey(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sapling support
|
// Sapling support
|
||||||
auto keyAlreadyExists = boost::apply_visitor(
|
auto addResult = boost::apply_visitor(AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey);
|
||||||
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey);
|
if (addResult == KeyAlreadyExists && fIgnoreExistingKey) {
|
||||||
if (keyAlreadyExists && fIgnoreExistingKey) {
|
|
||||||
return NullUniValue;
|
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
|
// whenever a key is imported, we need to scan the whole chain
|
||||||
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
|
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
|
||||||
|
|
|
@ -2280,6 +2280,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
|
||||||
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
|
" \"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"
|
" \"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"
|
" \"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"
|
"}\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("getwalletinfo", "")
|
+ HelpExampleCli("getwalletinfo", "")
|
||||||
|
@ -2299,6 +2300,9 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
|
||||||
if (pwalletMain->IsCrypted())
|
if (pwalletMain->IsCrypted())
|
||||||
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
|
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
|
||||||
obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
|
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;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "key_io.h"
|
#include "key_io.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
#include "rpc/protocol.h"
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
#include "script/sign.h"
|
#include "script/sign.h"
|
||||||
#include "timedata.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
|
// Generate a new spending key and return its public payment address
|
||||||
libzcash::PaymentAddress CWallet::GenerateNewZKey()
|
libzcash::PaymentAddress CWallet::GenerateNewZKey()
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet); // mapZKeyMetadata
|
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
|
||||||
// TODO: Add Sapling support
|
// TODO: Add Sapling support
|
||||||
auto k = SproutSpendingKey::random();
|
auto k = SproutSpendingKey::random();
|
||||||
auto addr = k.address();
|
auto addr = k.address();
|
||||||
|
@ -94,10 +95,10 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey()
|
||||||
|
|
||||||
// Create new metadata
|
// Create new metadata
|
||||||
int64_t nCreationTime = GetTime();
|
int64_t nCreationTime = GetTime();
|
||||||
mapZKeyMetadata[addr] = CKeyMetadata(nCreationTime);
|
mapSproutZKeyMetadata[addr] = CKeyMetadata(nCreationTime);
|
||||||
|
|
||||||
if (!AddZKey(k))
|
if (!AddSproutZKey(k))
|
||||||
throw std::runtime_error("CWallet::GenerateNewZKey(): AddZKey failed");
|
throw std::runtime_error("CWallet::GenerateNewZKey(): AddSproutZKey failed");
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,18 +117,21 @@ SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
|
||||||
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
||||||
|
|
||||||
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
|
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
|
||||||
|
uint32_t bip44CoinType = Params().BIP44CoinType();
|
||||||
|
|
||||||
// We use a fixed keypath scheme of m/32'/coin_type'/account'
|
// We use a fixed keypath scheme of m/32'/coin_type'/account'
|
||||||
// Derive m/32'
|
// Derive m/32'
|
||||||
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
|
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
|
||||||
// Derive m/32'/coin_type'
|
// 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
|
// Derive account key at next index, skip keys already known to the wallet
|
||||||
libzcash::SaplingExtendedSpendingKey xsk;
|
libzcash::SaplingExtendedSpendingKey xsk;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT);
|
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
|
// Increment childkey index
|
||||||
hdChain.saplingAccountCounter++;
|
hdChain.saplingAccountCounter++;
|
||||||
} while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key()));
|
} while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key()));
|
||||||
|
@ -169,9 +173,9 @@ bool CWallet::AddSaplingZKey(
|
||||||
|
|
||||||
|
|
||||||
// Add spending key to keystore and persist to disk
|
// 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();
|
auto addr = key.address();
|
||||||
|
|
||||||
if (!CCryptoKeyStore::AddSproutSpendingKey(key))
|
if (!CCryptoKeyStore::AddSproutSpendingKey(key))
|
||||||
|
@ -187,7 +191,7 @@ bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key)
|
||||||
if (!IsCrypted()) {
|
if (!IsCrypted()) {
|
||||||
return CWalletDB(strWalletFile).WriteZKey(addr,
|
return CWalletDB(strWalletFile).WriteZKey(addr,
|
||||||
key,
|
key,
|
||||||
mapZKeyMetadata[addr]);
|
mapSproutZKeyMetadata[addr]);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -278,12 +282,12 @@ bool CWallet::AddCryptedSproutSpendingKey(
|
||||||
return pwalletdbEncryption->WriteCryptedZKey(address,
|
return pwalletdbEncryption->WriteCryptedZKey(address,
|
||||||
rk,
|
rk,
|
||||||
vchCryptedSecret,
|
vchCryptedSecret,
|
||||||
mapZKeyMetadata[address]);
|
mapSproutZKeyMetadata[address]);
|
||||||
} else {
|
} else {
|
||||||
return CWalletDB(strWalletFile).WriteCryptedZKey(address,
|
return CWalletDB(strWalletFile).WriteCryptedZKey(address,
|
||||||
rk,
|
rk,
|
||||||
vchCryptedSecret,
|
vchCryptedSecret,
|
||||||
mapZKeyMetadata[address]);
|
mapSproutZKeyMetadata[address]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -315,8 +319,8 @@ bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
|
||||||
|
|
||||||
bool CWallet::LoadZKeyMetadata(const SproutPaymentAddress &addr, const CKeyMetadata &meta)
|
bool CWallet::LoadZKeyMetadata(const SproutPaymentAddress &addr, const CKeyMetadata &meta)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet); // mapZKeyMetadata
|
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
|
||||||
mapZKeyMetadata[addr] = meta;
|
mapSproutZKeyMetadata[addr] = meta;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4553,14 +4557,9 @@ boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator
|
||||||
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
|
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
|
||||||
const libzcash::SaplingPaymentAddress &zaddr) const
|
const libzcash::SaplingPaymentAddress &zaddr) const
|
||||||
{
|
{
|
||||||
libzcash::SaplingIncomingViewingKey ivk;
|
libzcash::SaplingExtendedSpendingKey extsk;
|
||||||
libzcash::SaplingFullViewingKey fvk;
|
if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) {
|
||||||
libzcash::SaplingExtendedSpendingKey sk;
|
return libzcash::SpendingKey(extsk);
|
||||||
|
|
||||||
if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
|
|
||||||
m_wallet->GetSaplingFullViewingKey(ivk, fvk) &&
|
|
||||||
m_wallet->GetSaplingSpendingKey(fvk, sk)) {
|
|
||||||
return libzcash::SpendingKey(sk);
|
|
||||||
} else {
|
} else {
|
||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
@ -4572,3 +4571,58 @@ boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator
|
||||||
// Defaults to InvalidEncoding
|
// Defaults to InvalidEncoding
|
||||||
return libzcash::SpendingKey();
|
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");
|
||||||
|
}
|
||||||
|
|
|
@ -845,7 +845,7 @@ public:
|
||||||
|
|
||||||
std::set<int64_t> setKeyPool;
|
std::set<int64_t> setKeyPool;
|
||||||
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
||||||
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapZKeyMetadata;
|
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
|
||||||
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
|
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
|
||||||
|
|
||||||
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
|
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
|
||||||
|
@ -1031,7 +1031,7 @@ public:
|
||||||
//! Generates a new zaddr
|
//! Generates a new zaddr
|
||||||
libzcash::PaymentAddress GenerateNewZKey();
|
libzcash::PaymentAddress GenerateNewZKey();
|
||||||
//! Adds spending key to the store, and saves it to disk
|
//! 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)
|
//! Adds spending key to the store, without saving it to disk (used by LoadWallet)
|
||||||
bool LoadZKey(const libzcash::SproutSpendingKey &key);
|
bool LoadZKey(const libzcash::SproutSpendingKey &key);
|
||||||
//! Load spending key metadata (used by LoadWallet)
|
//! Load spending key metadata (used by LoadWallet)
|
||||||
|
@ -1376,4 +1376,38 @@ public:
|
||||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
|
boost::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum SpendingKeyAddResult {
|
||||||
|
KeyAlreadyExists,
|
||||||
|
KeyAdded,
|
||||||
|
KeyNotAdded,
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddSpendingKeyToWallet : public boost::static_visitor<SpendingKeyAddResult>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CWallet *m_wallet;
|
||||||
|
const Consensus::Params ¶ms;
|
||||||
|
int64_t nTime;
|
||||||
|
boost::optional<std::string> hdKeypath; // currently sapling only
|
||||||
|
boost::optional<std::string> 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<std::string> _hdKeypath,
|
||||||
|
boost::optional<std::string> _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
|
#endif // BITCOIN_WALLET_WALLET_H
|
||||||
|
|
|
@ -77,9 +77,13 @@ public:
|
||||||
class CKeyMetadata
|
class CKeyMetadata
|
||||||
{
|
{
|
||||||
public:
|
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;
|
int nVersion;
|
||||||
int64_t nCreateTime; // 0 means unknown
|
int64_t nCreateTime; // 0 means unknown
|
||||||
|
std::string hdKeypath; //optional HD/zip32 keypath
|
||||||
|
uint256 seedFp;
|
||||||
|
|
||||||
CKeyMetadata()
|
CKeyMetadata()
|
||||||
{
|
{
|
||||||
|
@ -87,7 +91,7 @@ public:
|
||||||
}
|
}
|
||||||
CKeyMetadata(int64_t nCreateTime_)
|
CKeyMetadata(int64_t nCreateTime_)
|
||||||
{
|
{
|
||||||
nVersion = CKeyMetadata::CURRENT_VERSION;
|
SetNull();
|
||||||
nCreateTime = nCreateTime_;
|
nCreateTime = nCreateTime_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,12 +101,19 @@ public:
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||||
READWRITE(this->nVersion);
|
READWRITE(this->nVersion);
|
||||||
READWRITE(nCreateTime);
|
READWRITE(nCreateTime);
|
||||||
|
if (this->nVersion >= VERSION_WITH_HDDATA)
|
||||||
|
{
|
||||||
|
READWRITE(hdKeypath);
|
||||||
|
READWRITE(seedFp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetNull()
|
void SetNull()
|
||||||
{
|
{
|
||||||
nVersion = CKeyMetadata::CURRENT_VERSION;
|
nVersion = CKeyMetadata::CURRENT_VERSION;
|
||||||
nCreateTime = 0;
|
nCreateTime = 0;
|
||||||
|
hdKeypath.clear();
|
||||||
|
seedFp.SetNull();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue