Skip to content

Commit eab63bc

Browse files
committed
Store key origin info in key metadata
Store the master key fingerprint and derivation path in the key metadata. hdKeypath is kept to indicate the seed and for backwards compatibility, but all key derivation path output uses the key origin info instead of hdKeypath.
1 parent 345bff6 commit eab63bc

File tree

7 files changed

+100
-18
lines changed

7 files changed

+100
-18
lines changed

src/script/sign.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,27 @@ struct CMutableTransaction;
2222

2323
struct KeyOriginInfo
2424
{
25-
unsigned char fingerprint[4];
25+
unsigned char fingerprint[4]; //!< First 32 bits of the Hash160 of the public key at the root of the path
2626
std::vector<uint32_t> path;
2727

2828
friend bool operator==(const KeyOriginInfo& a, const KeyOriginInfo& b)
2929
{
3030
return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path;
3131
}
32+
33+
ADD_SERIALIZE_METHODS;
34+
template <typename Stream, typename Operation>
35+
inline void SerializationOp(Stream& s, Operation ser_action)
36+
{
37+
READWRITE(fingerprint);
38+
READWRITE(path);
39+
}
40+
41+
void clear()
42+
{
43+
memset(fingerprint, 0, 4);
44+
path.clear();
45+
}
3246
};
3347

3448
/** An interface to be implemented by keystores that support signing. */

src/wallet/rpcdump.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <script/script.h>
1414
#include <script/standard.h>
1515
#include <sync.h>
16+
#include <util/bip32.h>
1617
#include <util/system.h>
1718
#include <util/time.h>
1819
#include <validation.h>
@@ -850,7 +851,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
850851
} else {
851852
file << "change=1";
852853
}
853-
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : ""));
854+
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : ""));
854855
}
855856
}
856857
file << "\n";

src/wallet/rpcwallet.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3659,6 +3659,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
36593659
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
36603660
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
36613661
" \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n"
3662+
" \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n"
36623663
" \"labels\" (object) Array of labels associated with the address.\n"
36633664
" [\n"
36643665
" { (json object of label data)\n"
@@ -3721,9 +3722,10 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
37213722
}
37223723
if (meta) {
37233724
ret.pushKV("timestamp", meta->nCreateTime);
3724-
if (!meta->hdKeypath.empty()) {
3725-
ret.pushKV("hdkeypath", meta->hdKeypath);
3725+
if (meta->has_key_origin) {
3726+
ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
37263727
ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
3728+
ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4));
37273729
}
37283730
}
37293731

src/wallet/wallet.cpp

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -256,16 +256,25 @@ void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey
256256
if (internal) {
257257
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
258258
metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
259+
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
260+
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
261+
metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
259262
hdChain.nInternalChainCounter++;
260263
}
261264
else {
262265
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
263266
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
267+
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
268+
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
269+
metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
264270
hdChain.nExternalChainCounter++;
265271
}
266272
} while (HaveKey(childKey.key.GetPubKey().GetID()));
267273
secret = childKey.key;
268274
metadata.hd_seed_id = hdChain.seed_id;
275+
CKeyID master_id = masterKey.key.GetPubKey().GetID();
276+
std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
277+
metadata.has_key_origin = true;
269278
// update the chain model in the database
270279
if (!batch.WriteHDChain(hdChain))
271280
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
@@ -355,6 +364,41 @@ bool CWallet::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey,
355364
return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite);
356365
}
357366

367+
void CWallet::UpgradeKeyMetadata()
368+
{
369+
AssertLockHeld(cs_wallet); // mapKeyMetadata
370+
if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
371+
return;
372+
}
373+
374+
for (auto& meta_pair : mapKeyMetadata) {
375+
CKeyMetadata& meta = meta_pair.second;
376+
if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin
377+
CKey key;
378+
GetKey(meta.hd_seed_id, key);
379+
CExtKey masterKey;
380+
masterKey.SetSeed(key.begin(), key.size());
381+
// Add to map
382+
CKeyID master_id = masterKey.key.GetPubKey().GetID();
383+
std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint);
384+
if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) {
385+
throw std::runtime_error("Invalid stored hdKeypath");
386+
}
387+
meta.has_key_origin = true;
388+
if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
389+
meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
390+
}
391+
392+
// Write meta to wallet
393+
CPubKey pubkey;
394+
if (GetPubKey(meta_pair.first, pubkey)) {
395+
WriteKeyMetadata(meta, pubkey, true);
396+
}
397+
}
398+
}
399+
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
400+
}
401+
358402
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
359403
{
360404
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
@@ -453,8 +497,11 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key
453497
return false;
454498
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
455499
continue; // try another master key
456-
if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys))
500+
if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) {
501+
// Now that we've unlocked, upgrade the key metadata
502+
UpgradeKeyMetadata();
457503
return true;
504+
}
458505
}
459506
}
460507
return false;
@@ -1414,6 +1461,7 @@ CPubKey CWallet::DeriveNewSeed(const CKey& key)
14141461

14151462
// set the hd keypath to "s" -> Seed, refers the seed to itself
14161463
metadata.hdKeypath = "s";
1464+
metadata.has_key_origin = false;
14171465
metadata.hd_seed_id = seed.GetID();
14181466

14191467
{
@@ -4494,16 +4542,9 @@ bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const
44944542
meta = it->second;
44954543
}
44964544
}
4497-
if (!meta.hdKeypath.empty()) {
4498-
if (!ParseHDKeypath(meta.hdKeypath, info.path)) return false;
4499-
// Get the proper master key id
4500-
CKey key;
4501-
GetKey(meta.hd_seed_id, key);
4502-
CExtKey masterKey;
4503-
masterKey.SetSeed(key.begin(), key.size());
4504-
// Compute identifier
4505-
CKeyID masterid = masterKey.key.GetPubKey().GetID();
4506-
std::copy(masterid.begin(), masterid.begin() + 4, info.fingerprint);
4545+
if (meta.has_key_origin) {
4546+
std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint);
4547+
info.path = meta.key_origin.path;
45074548
} else { // Single pubkeys get the master fingerprint of themselves
45084549
std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint);
45094550
}

src/wallet/wallet.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ enum WalletFlags : uint64_t {
135135
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
136136
// unknown wallet flags in the lower section <= (1 << 31) will be tolerated
137137

138+
// Indicates that the metadata has already been upgraded to contain key origins
139+
WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
140+
138141
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
139142
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
140143

@@ -151,7 +154,7 @@ enum WalletFlags : uint64_t {
151154
WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
152155
};
153156

154-
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET;
157+
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA;
155158

156159
/** A key pool entry */
157160
class CKeyPool
@@ -868,6 +871,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
868871
//! Load metadata (used by LoadWallet)
869872
void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
870873
void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
874+
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
875+
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
871876

872877
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
873878
void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

src/wallet/walletdb.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
534534
if (wss.fAnyUnordered)
535535
result = pwallet->ReorderTransactions();
536536

537+
// Upgrade all of the wallet keymetadata to have the hd master key id
538+
// This operation is not atomic, but if it fails, updated entries are still backwards compatible with older software
539+
try {
540+
pwallet->UpgradeKeyMetadata();
541+
} catch (...) {
542+
result = DBErrors::CORRUPT;
543+
}
544+
537545
return result;
538546
}
539547

src/wallet/walletdb.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <amount.h>
1010
#include <primitives/transaction.h>
11+
#include <script/sign.h>
1112
#include <wallet/db.h>
1213
#include <key.h>
1314

@@ -93,11 +94,14 @@ class CKeyMetadata
9394
public:
9495
static const int VERSION_BASIC=1;
9596
static const int VERSION_WITH_HDDATA=10;
96-
static const int CURRENT_VERSION=VERSION_WITH_HDDATA;
97+
static const int VERSION_WITH_KEY_ORIGIN = 12;
98+
static const int CURRENT_VERSION=VERSION_WITH_KEY_ORIGIN;
9799
int nVersion;
98100
int64_t nCreateTime; // 0 means unknown
99-
std::string hdKeypath; //optional HD/bip32 keypath
101+
std::string hdKeypath; //optional HD/bip32 keypath. Still used to determine whether a key is a seed. Also kept for backwards compatibility
100102
CKeyID hd_seed_id; //id of the HD seed used to derive this key
103+
KeyOriginInfo key_origin; // Key origin info with path and fingerprint
104+
bool has_key_origin = false; //< Whether the key_origin is useful
101105

102106
CKeyMetadata()
103107
{
@@ -120,6 +124,11 @@ class CKeyMetadata
120124
READWRITE(hdKeypath);
121125
READWRITE(hd_seed_id);
122126
}
127+
if (this->nVersion >= VERSION_WITH_KEY_ORIGIN)
128+
{
129+
READWRITE(key_origin);
130+
READWRITE(has_key_origin);
131+
}
123132
}
124133

125134
void SetNull()
@@ -128,6 +137,8 @@ class CKeyMetadata
128137
nCreateTime = 0;
129138
hdKeypath.clear();
130139
hd_seed_id.SetNull();
140+
key_origin.clear();
141+
has_key_origin = false;
131142
}
132143
};
133144

0 commit comments

Comments
 (0)