Skip to content

Commit 45f2f6a

Browse files
committed
Determine inactive HD seeds from key metadata and track them in LegacyScriptPubKeyMan
1 parent b59b450 commit 45f2f6a

File tree

4 files changed

+116
-4
lines changed

4 files changed

+116
-4
lines changed

src/wallet/scriptpubkeyman.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -839,12 +839,29 @@ bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTim
839839
void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly)
840840
{
841841
LOCK(cs_KeyStore);
842-
if (!memonly && !WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain))
843-
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
842+
// memonly == true means we are loading the wallet file
843+
// memonly == false means that the chain is actually being changed
844+
if (!memonly) {
845+
// Store the new chain
846+
if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) {
847+
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
848+
}
849+
// When there's an old chain, add it as an inactive chain as we are now rotating hd chains
850+
if (!m_hd_chain.seed_id.IsNull()) {
851+
AddInactiveHDChain(m_hd_chain);
852+
}
853+
}
844854

845855
m_hd_chain = chain;
846856
}
847857

858+
void LegacyScriptPubKeyMan::AddInactiveHDChain(const CHDChain& chain)
859+
{
860+
LOCK(cs_KeyStore);
861+
assert(!chain.seed_id.IsNull());
862+
m_inactive_hd_chains[chain.seed_id] = chain;
863+
}
864+
848865
bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const
849866
{
850867
LOCK(cs_KeyStore);
@@ -1011,8 +1028,8 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
10111028
std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
10121029
metadata.has_key_origin = true;
10131030
// update the chain model in the database
1014-
if (!batch.WriteHDChain(hd_chain))
1015-
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
1031+
if (hd_chain.seed_id == m_hd_chain.seed_id && !batch.WriteHDChain(hd_chain))
1032+
throw std::runtime_error(std::string(__func__) + ": writing HD chain model failed");
10161033
}
10171034

10181035
void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)

src/wallet/scriptpubkeyman.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include <boost/signals2/signal.hpp>
2020

21+
#include <unordered_map>
22+
2123
enum class OutputType;
2224

2325
// Wallet storage things that ScriptPubKeyMans need in order to be able to store things to the wallet database.
@@ -143,6 +145,17 @@ class CKeyPool
143145
}
144146
};
145147

148+
class KeyIDHasher
149+
{
150+
public:
151+
KeyIDHasher() {}
152+
153+
size_t operator()(const CKeyID& id) const
154+
{
155+
return id.GetUint64(0);
156+
}
157+
};
158+
146159
/*
147160
* A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used in a wallet.
148161
* It contains the scripts and keys related to the scriptPubKeys it manages.
@@ -288,6 +301,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
288301

289302
/* the HD chain data model (external chain counters) */
290303
CHDChain m_hd_chain;
304+
std::unordered_map<CKeyID, CHDChain, KeyIDHasher> m_inactive_hd_chains;
291305

292306
/* HD derive new child key (on internal or external chain) */
293307
void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
@@ -397,6 +411,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
397411
/* Set the HD chain model (chain child index counters) */
398412
void SetHDChain(const CHDChain& chain, bool memonly);
399413
const CHDChain& GetHDChain() const { return m_hd_chain; }
414+
void AddInactiveHDChain(const CHDChain& chain);
400415

401416
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
402417
bool LoadWatchOnly(const CScript &dest);

src/wallet/walletdb.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <protocol.h>
1111
#include <serialize.h>
1212
#include <sync.h>
13+
#include <util/bip32.h>
1314
#include <util/system.h>
1415
#include <util/time.h>
1516
#include <wallet/wallet.h>
@@ -245,6 +246,7 @@ class CWalletScanState {
245246
std::map<uint256, DescriptorCache> m_descriptor_caches;
246247
std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
247248
std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
249+
std::map<uint160, CHDChain> m_hd_chains;
248250

249251
CWalletScanState() {
250252
}
@@ -405,6 +407,65 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
405407
ssValue >> keyMeta;
406408
wss.nKeyMeta++;
407409
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
410+
411+
// Extract some CHDChain info from this metadata if it has any
412+
if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {
413+
// Get the path from the key origin or from the path string
414+
// Not applicable when path is "s" as that indicates a seed
415+
bool internal = false;
416+
uint32_t index = 0;
417+
if (keyMeta.hdKeypath != "s") {
418+
std::vector<uint32_t> path;
419+
if (keyMeta.has_key_origin) {
420+
// We have a key origin, so pull it from its path vector
421+
path = keyMeta.key_origin.path;
422+
} else {
423+
// No key origin, have to parse the string
424+
if (!ParseHDKeypath(keyMeta.hdKeypath, path)) {
425+
strErr = "Error reading wallet database: keymeta with invalid HD keypath";
426+
return false;
427+
}
428+
}
429+
430+
// Extract the index and internal from the path
431+
// Path string is m/0'/k'/i'
432+
// Path vector is [0', k', i'] (but as ints OR'd with the hardened bit
433+
// k == 0 for external, 1 for internal. i is the index
434+
if (path.size() != 3) {
435+
strErr = "Error reading wallet database: keymeta found with unexpected path";
436+
return false;
437+
}
438+
if (path[0] != 0x80000000) {
439+
strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000) for the element at index 0", path[0]);
440+
return false;
441+
}
442+
if (path[1] != 0x80000000 && path[1] != (1 | 0x80000000)) {
443+
strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000 or 0x80000001) for the element at index 1", path[1]);
444+
return false;
445+
}
446+
if ((path[2] & 0x80000000) == 0) {
447+
strErr = strprintf("Unexpected path index of 0x%08x (expected to be greater than or equal to 0x80000000)", path[2]);
448+
return false;
449+
}
450+
internal = path[1] == (1 | 0x80000000);
451+
index = path[2] & ~0x80000000;
452+
}
453+
454+
// Insert a new CHDChain, or get the one that already exists
455+
auto ins = wss.m_hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());
456+
CHDChain& chain = ins.first->second;
457+
if (ins.second) {
458+
// For new chains, we want to default to VERSION_HD_BASE until we see an internal
459+
chain.nVersion = CHDChain::VERSION_HD_BASE;
460+
chain.seed_id = keyMeta.hd_seed_id;
461+
}
462+
if (internal) {
463+
chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
464+
chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index);
465+
} else {
466+
chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index);
467+
}
468+
}
408469
} else if (strType == DBKeys::WATCHMETA) {
409470
CScript script;
410471
ssKey >> script;
@@ -728,6 +789,20 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
728789
result = DBErrors::CORRUPT;
729790
}
730791

792+
// Set the inactive chain
793+
if (wss.m_hd_chains.size() > 0) {
794+
LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
795+
if (!legacy_spkm) {
796+
pwallet->WalletLogPrintf("Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");
797+
return DBErrors::CORRUPT;
798+
}
799+
for (const auto& chain_pair : wss.m_hd_chains) {
800+
if (chain_pair.first != pwallet->GetLegacyScriptPubKeyMan()->GetHDChain().seed_id) {
801+
pwallet->GetLegacyScriptPubKeyMan()->AddInactiveHDChain(chain_pair.second);
802+
}
803+
}
804+
}
805+
731806
return result;
732807
}
733808

src/wallet/walletdb.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ class CHDChain
116116
nInternalChainCounter = 0;
117117
seed_id.SetNull();
118118
}
119+
120+
bool operator==(const CHDChain& chain) const
121+
{
122+
return seed_id == chain.seed_id;
123+
}
119124
};
120125

121126
class CKeyMetadata

0 commit comments

Comments
 (0)