Skip to content

Commit 80a8407

Browse files
committed
BIP44 derivation path + HD wallet functional tests passing.
1 parent beb5fcb commit 80a8407

File tree

10 files changed

+95
-32
lines changed

10 files changed

+95
-32
lines changed

src/init.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,10 +1959,12 @@ bool AppInit2()
19591959
LogPrintf("chainActive.Height() = %d\n", chainActive.Height());
19601960
#ifdef ENABLE_WALLET
19611961
{
1962-
LOCK(pwalletMain->cs_wallet);
1963-
LogPrintf("setKeyPool.size() = %u\n", pwalletMain ? pwalletMain->GetKeyPoolSize() : 0);
1964-
LogPrintf("mapWallet.size() = %u\n", pwalletMain ? pwalletMain->mapWallet.size() : 0);
1965-
LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0);
1962+
if (pwalletMain) {
1963+
LOCK(pwalletMain->cs_wallet);
1964+
LogPrintf("setKeyPool.size() = %u\n", pwalletMain ? pwalletMain->GetKeyPoolSize() : 0);
1965+
LogPrintf("mapWallet.size() = %u\n", pwalletMain ? pwalletMain->mapWallet.size() : 0);
1966+
LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0);
1967+
}
19661968
}
19671969
#endif
19681970

src/rpc/misc.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ UniValue getinfo(const UniValue& params, bool fHelp)
153153
#ifdef ENABLE_WALLET
154154
if (pwalletMain) {
155155
obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime()));
156-
obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize()));
156+
size_t kpExternalSize = pwalletMain->KeypoolCountExternalKeys();
157+
obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize));
157158
}
158159
if (pwalletMain && pwalletMain->IsCrypted())
159160
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));

src/wallet/rpcdump.cpp

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "bip38.h"
88
#include "init.h"
9+
#include "key_io.h"
910
#include "main.h"
1011
#include "rpc/server.h"
1112
#include "script/script.h"
@@ -363,7 +364,7 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp)
363364
return CBitcoinSecret(vchSecret).ToString();
364365
}
365366

366-
367+
// TODO: Update to support HD wallet dump.
367368
UniValue dumpwallet(const UniValue& params, bool fHelp)
368369
{
369370
if (fHelp || params.size() != 1)
@@ -382,6 +383,8 @@ UniValue dumpwallet(const UniValue& params, bool fHelp)
382383

383384
EnsureWalletIsUnlocked();
384385

386+
ScriptPubKeyMan* spk_man = pwalletMain->GetScriptPubKeyMan();
387+
385388
boost::filesystem::path filepath = params[0].get_str().c_str();
386389
filepath = boost::filesystem::absolute(filepath);
387390

@@ -391,9 +394,9 @@ UniValue dumpwallet(const UniValue& params, bool fHelp)
391394
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
392395

393396
std::map<CKeyID, int64_t> mapKeyBirth;
394-
std::set<CKeyID> setKeyPool;
395397
pwalletMain->GetKeyBirthTimes(mapKeyBirth);
396-
pwalletMain->GetAllReserveKeys(setKeyPool);
398+
const std::map<CKeyID, int64_t>& mapKeyPool = spk_man->GetAllReserveKeys();
399+
397400

398401
// sort time/key pairs
399402
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
@@ -409,19 +412,37 @@ UniValue dumpwallet(const UniValue& params, bool fHelp)
409412
file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString());
410413
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
411414
file << "\n";
415+
416+
// Add the base58check encoded extended master if the wallet uses HD
417+
CKeyID seed_id = spk_man->GetHDChain().GetID();
418+
if (!seed_id.IsNull())
419+
{
420+
CKey seed;
421+
if (pwalletMain->GetKey(seed_id, seed)) {
422+
CExtKey masterKey;
423+
masterKey.SetSeed(seed.begin(), seed.size());
424+
425+
file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n";
426+
}
427+
}
428+
412429
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
413430
const CKeyID& keyid = it->second;
414431
std::string strTime = EncodeDumpTime(it->first);
415432
std::string strAddr = CBitcoinAddress(keyid).ToString();
416433
CKey key;
417434
if (pwalletMain->GetKey(keyid, key)) {
435+
file << strprintf("%s %s ", EncodeSecret(key), strTime);
418436
if (pwalletMain->mapAddressBook.count(keyid)) {
419-
file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, EncodeDumpString(pwalletMain->mapAddressBook[keyid].name), strAddr);
420-
} else if (setKeyPool.count(keyid)) {
421-
file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr);
437+
file << strprintf("label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name));
438+
} else if (keyid == seed_id) {
439+
file << "hdseed=1";
440+
} else if (mapKeyPool.count(keyid)) {
441+
file << "reserve=1";
422442
} else {
423-
file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr);
443+
file << "change=1";
424444
}
445+
file << strprintf(" # addr=%s\n", strAddr);
425446
}
426447
}
427448
file << "\n";

src/wallet/rpcwallet.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2721,6 +2721,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
27212721
" \"unlocked_until\": ttt, (numeric) the UNIX epoch time until which the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
27222722
" \"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"
27232723
" \"paytxfee\": x.xxxx (numeric) the transaction fee configuration, set in PIV/kB\n"
2724+
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
27242725
"}\n"
27252726

27262727
"\nExamples:\n" +
@@ -2743,6 +2744,13 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
27432744
size_t kpExternalSize = pwalletMain->KeypoolCountExternalKeys();
27442745
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
27452746

2747+
ScriptPubKeyMan* spk_man = pwalletMain->GetScriptPubKeyMan();
2748+
if (spk_man) {
2749+
const CKeyID& seed_id = spk_man->GetHDChain().GetID();
2750+
if (!seed_id.IsNull()) {
2751+
obj.pushKV("hdseedid", seed_id.GetHex());
2752+
}
2753+
}
27462754
if (pwalletMain->CanSupportFeature(FEATURE_HD_SPLIT)) {
27472755
obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwalletMain->GetKeyPoolSize() - kpExternalSize));
27482756
}

src/wallet/scriptpubkeyman.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ bool ScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal)
8787
return true;
8888
}
8989

90+
bool ScriptPubKeyMan::GetReservedKey(bool internal, int64_t& index, CKeyPool& keypool)
91+
{
92+
if (!CanGetAddresses(internal)) {
93+
return false;
94+
}
95+
96+
if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
97+
return false;
98+
}
99+
return true;
100+
}
101+
90102
bool ScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
91103
{
92104
nIndex = -1;
@@ -238,7 +250,6 @@ bool ScriptPubKeyMan::TopUp(unsigned int kpSize)
238250
}
239251
if (missingInternal + missingExternal > 0) {
240252
LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
241-
//LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size());
242253
}
243254
}
244255
// TODO: Implement this.
@@ -305,13 +316,14 @@ const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
305316
void ScriptPubKeyMan::DeriveNewChildKey(CWalletDB &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
306317
{
307318
AssertLockHeld(wallet->cs_wallet);
308-
// For now we use a fixed keypath scheme of m/0'/0'/k
309-
// TODO: Change it to bip44.
319+
// Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index
310320
CKey seed; //seed (256bit)
311321
CExtKey masterKey; //hd master key
312-
CExtKey accountKey; //key at m/0'
313-
CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
314-
CExtKey childKey; //key at m/0'/0'/<n>'
322+
CExtKey purposeKey; //key at m/purpose' --> key at m/44'
323+
CExtKey cointypeKey; //key at m/purpose'/coin_type' --> key at m/44'/119'
324+
CExtKey accountKey; //key at m/purpose'/coin_type'/account' ---> key at m/44'/119'/account_num
325+
CExtKey changeKey; //key at m/purpose'/coin_type'/account'/change ---> key at m/44'/119'/account_num/change', external = 0' or internal = 1'.
326+
CExtKey childKey; //key at m/purpose'/coin_type'/account'/change/address_index ---> key at m/44'/119'/account_num/change'/<n>'
315327

316328
// try to get the seed
317329
if (!wallet->GetKey(hdChain.GetID(), seed))
@@ -321,27 +333,30 @@ void ScriptPubKeyMan::DeriveNewChildKey(CWalletDB &batch, CKeyMetadata& metadata
321333

322334
// derive m/0'
323335
// use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
324-
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
325-
326-
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
336+
masterKey.Derive(purposeKey, 44 | BIP32_HARDENED_KEY_LIMIT);
337+
// derive m/purpose'/coin_type'
338+
purposeKey.Derive(cointypeKey, 119 | BIP32_HARDENED_KEY_LIMIT);
339+
// derive m/purpose'/coin_type'/account' // Hardcoded to account 0 for now.
340+
cointypeKey.Derive(accountKey, 0 | BIP32_HARDENED_KEY_LIMIT);
341+
// derive m/purpose'/coin_type'/account'/change
327342
assert(internal ? wallet->CanSupportFeature(FEATURE_HD_SPLIT) : true);
328-
accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
343+
accountKey.Derive(changeKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
329344

330345
// derive child key at next index, skip keys already known to the wallet
331346
do {
332347
// always derive hardened keys
333348
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
334349
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
335350
if (internal) {
336-
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
351+
changeKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
337352
//metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
338353
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
339354
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
340355
metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
341356
hdChain.nInternalChainCounter++;
342357
}
343358
else {
344-
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
359+
changeKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
345360
//metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
346361
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
347362
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);

src/wallet/scriptpubkeyman.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ class ScriptPubKeyMan {
8282

8383
//! Fetches a key from the keypool
8484
bool GetKeyFromPool(CPubKey &key, bool internal = false);
85+
//! Reserve + fetch a key from the keypool
86+
bool GetReservedKey(bool internal, int64_t& index, CKeyPool& keypool);
8587

88+
const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
8689
/**
8790
* Reserves a key from the keypool and sets nIndex to its index
8891
*

src/wallet/wallet.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ PairResult CWallet::getNewStakingAddress(CBitcoinAddress& ret, std::string label
104104
PairResult CWallet::getNewAddress(CBitcoinAddress& ret, const std::string addressLabel, const std::string purpose,
105105
const CChainParams::Base58Type addrType)
106106
{
107-
LOCK2(cs_main, cs_wallet);
107+
LOCK(cs_wallet);
108108

109109
// Refill keypool if wallet is unlocked
110110
if (!IsLocked())
@@ -696,7 +696,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
696696
return false;
697697
}
698698
} else {
699-
NewKeyPool();
699+
NewKeyPool(); // TODO: Remove this.
700700
}
701701
Lock();
702702

@@ -3129,9 +3129,21 @@ std::set<CTxDestination> CWallet::GetAccountAddresses(std::string strAccount) co
31293129

31303130
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
31313131
{
3132+
3133+
ScriptPubKeyMan* m_spk_man = pwallet->GetScriptPubKeyMan();
3134+
if (!m_spk_man) {
3135+
return false;
3136+
}
3137+
31323138
if (nIndex == -1) {
3139+
3140+
// Fill the pool if needed
3141+
m_spk_man->TopUp();
3142+
31333143
CKeyPool keypool;
3134-
pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal);
3144+
if (!m_spk_man->GetReservedKey(internal, nIndex, keypool))
3145+
return false;
3146+
31353147
if (nIndex != -1)
31363148
vchPubKey = keypool.vchPubKey;
31373149
else {

test/functional/wallet_dump.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def run_test (self):
7373
read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None)
7474
assert_equal(found_addr, test_addr_count) # all keys must be in the dump
7575
assert_equal(found_addr_chg, 0) # 0 blocks where mined
76-
assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one)
76+
assert_equal(found_addr_rsv, 90 * 2) # 90 keys plus 100% internal keys
7777

7878
#encrypt wallet, restart, unlock and dump
7979
self.nodes[0].node_encrypt_wallet('test')
@@ -86,8 +86,8 @@ def run_test (self):
8686
found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \
8787
read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc)
8888
assert_equal(found_addr, test_addr_count)
89-
assert_equal(found_addr_chg, 90 + 1) # old reserve keys are marked as change now
90-
assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one)
89+
assert_equal(found_addr_chg, 90 * 2 + 1) # old reserve keys are marked as change now. todo: The +1 needs to be removed once this is updated (master seed taken as an internal key)
90+
assert_equal(found_addr_rsv, 90 * 2)
9191

9292
if __name__ == '__main__':
9393
WalletDumpTest().main ()

test/functional/wallet_keypool.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class KeyPoolTest(PivxTestFramework):
1111
def set_test_params(self):
1212
self.num_nodes = 1
13+
self.extra_args = [['-keypool=1']]
1314

1415
def run_test(self):
1516
nodes = self.nodes
@@ -19,7 +20,7 @@ def run_test(self):
1920
# Encrypt wallet and wait to terminate
2021
nodes[0].node_encrypt_wallet('test')
2122
# Restart node 0
22-
self.start_node(0)
23+
self.start_node(0, self.extra_args[0])
2324
# Keep creating keys
2425
addr = nodes[0].getnewaddress()
2526
addr_data = nodes[0].validateaddress(addr)

test/functional/wallet_keypool_topup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class KeypoolRestoreTest(PivxTestFramework):
2323
def set_test_params(self):
2424
self.setup_clean_chain = True
2525
self.num_nodes = 2
26-
self.extra_args = [[], ['-keypool=100', '-keypoolmin=20']]
26+
self.extra_args = [['-keypool=3'], ['-keypool=100']]
2727

2828
def run_test(self):
2929
self.tmpdir = self.options.tmpdir

0 commit comments

Comments
 (0)