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.
This commit is contained in:
parent
345bff6013
commit
eab63bc264
7 changed files with 100 additions and 18 deletions
|
@ -22,13 +22,27 @@ struct CMutableTransaction;
|
|||
|
||||
struct KeyOriginInfo
|
||||
{
|
||||
unsigned char fingerprint[4];
|
||||
unsigned char fingerprint[4]; //!< First 32 bits of the Hash160 of the public key at the root of the path
|
||||
std::vector<uint32_t> path;
|
||||
|
||||
friend bool operator==(const KeyOriginInfo& a, const KeyOriginInfo& b)
|
||||
{
|
||||
return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action)
|
||||
{
|
||||
READWRITE(fingerprint);
|
||||
READWRITE(path);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
memset(fingerprint, 0, 4);
|
||||
path.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/** An interface to be implemented by keystores that support signing. */
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <script/script.h>
|
||||
#include <script/standard.h>
|
||||
#include <sync.h>
|
||||
#include <util/bip32.h>
|
||||
#include <util/system.h>
|
||||
#include <util/time.h>
|
||||
#include <validation.h>
|
||||
|
@ -850,7 +851,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
|
|||
} else {
|
||||
file << "change=1";
|
||||
}
|
||||
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : ""));
|
||||
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : ""));
|
||||
}
|
||||
}
|
||||
file << "\n";
|
||||
|
|
|
@ -3659,6 +3659,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
|||
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
|
||||
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
|
||||
" \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n"
|
||||
" \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n"
|
||||
" \"labels\" (object) Array of labels associated with the address.\n"
|
||||
" [\n"
|
||||
" { (json object of label data)\n"
|
||||
|
@ -3721,9 +3722,10 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
|||
}
|
||||
if (meta) {
|
||||
ret.pushKV("timestamp", meta->nCreateTime);
|
||||
if (!meta->hdKeypath.empty()) {
|
||||
ret.pushKV("hdkeypath", meta->hdKeypath);
|
||||
if (meta->has_key_origin) {
|
||||
ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
|
||||
ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
|
||||
ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -256,16 +256,25 @@ void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey
|
|||
if (internal) {
|
||||
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
|
||||
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
hdChain.nInternalChainCounter++;
|
||||
}
|
||||
else {
|
||||
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
|
||||
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
hdChain.nExternalChainCounter++;
|
||||
}
|
||||
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
||||
secret = childKey.key;
|
||||
metadata.hd_seed_id = hdChain.seed_id;
|
||||
CKeyID master_id = masterKey.key.GetPubKey().GetID();
|
||||
std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
|
||||
metadata.has_key_origin = true;
|
||||
// update the chain model in the database
|
||||
if (!batch.WriteHDChain(hdChain))
|
||||
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,
|
|||
return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite);
|
||||
}
|
||||
|
||||
void CWallet::UpgradeKeyMetadata()
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||
if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& meta_pair : mapKeyMetadata) {
|
||||
CKeyMetadata& meta = meta_pair.second;
|
||||
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
|
||||
CKey key;
|
||||
GetKey(meta.hd_seed_id, key);
|
||||
CExtKey masterKey;
|
||||
masterKey.SetSeed(key.begin(), key.size());
|
||||
// Add to map
|
||||
CKeyID master_id = masterKey.key.GetPubKey().GetID();
|
||||
std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint);
|
||||
if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) {
|
||||
throw std::runtime_error("Invalid stored hdKeypath");
|
||||
}
|
||||
meta.has_key_origin = true;
|
||||
if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
|
||||
meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
|
||||
}
|
||||
|
||||
// Write meta to wallet
|
||||
CPubKey pubkey;
|
||||
if (GetPubKey(meta_pair.first, pubkey)) {
|
||||
WriteKeyMetadata(meta, pubkey, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
|
||||
}
|
||||
|
||||
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
|
||||
{
|
||||
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
|
||||
|
@ -453,8 +497,11 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key
|
|||
return false;
|
||||
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
|
||||
continue; // try another master key
|
||||
if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys))
|
||||
if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) {
|
||||
// Now that we've unlocked, upgrade the key metadata
|
||||
UpgradeKeyMetadata();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -1414,6 +1461,7 @@ CPubKey CWallet::DeriveNewSeed(const CKey& key)
|
|||
|
||||
// set the hd keypath to "s" -> Seed, refers the seed to itself
|
||||
metadata.hdKeypath = "s";
|
||||
metadata.has_key_origin = false;
|
||||
metadata.hd_seed_id = seed.GetID();
|
||||
|
||||
{
|
||||
|
@ -4494,16 +4542,9 @@ bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const
|
|||
meta = it->second;
|
||||
}
|
||||
}
|
||||
if (!meta.hdKeypath.empty()) {
|
||||
if (!ParseHDKeypath(meta.hdKeypath, info.path)) return false;
|
||||
// Get the proper master key id
|
||||
CKey key;
|
||||
GetKey(meta.hd_seed_id, key);
|
||||
CExtKey masterKey;
|
||||
masterKey.SetSeed(key.begin(), key.size());
|
||||
// Compute identifier
|
||||
CKeyID masterid = masterKey.key.GetPubKey().GetID();
|
||||
std::copy(masterid.begin(), masterid.begin() + 4, info.fingerprint);
|
||||
if (meta.has_key_origin) {
|
||||
std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint);
|
||||
info.path = meta.key_origin.path;
|
||||
} else { // Single pubkeys get the master fingerprint of themselves
|
||||
std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint);
|
||||
}
|
||||
|
|
|
@ -135,6 +135,9 @@ enum WalletFlags : uint64_t {
|
|||
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
|
||||
// unknown wallet flags in the lower section <= (1 << 31) will be tolerated
|
||||
|
||||
// Indicates that the metadata has already been upgraded to contain key origins
|
||||
WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
|
||||
|
||||
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
|
||||
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
|
||||
|
||||
|
@ -151,7 +154,7 @@ enum WalletFlags : uint64_t {
|
|||
WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
|
||||
};
|
||||
|
||||
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET;
|
||||
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA;
|
||||
|
||||
/** A key pool entry */
|
||||
class CKeyPool
|
||||
|
@ -868,6 +871,8 @@ public:
|
|||
//! Load metadata (used by LoadWallet)
|
||||
void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
|
||||
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
|
||||
void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
|
|
@ -534,6 +534,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||
if (wss.fAnyUnordered)
|
||||
result = pwallet->ReorderTransactions();
|
||||
|
||||
// Upgrade all of the wallet keymetadata to have the hd master key id
|
||||
// This operation is not atomic, but if it fails, updated entries are still backwards compatible with older software
|
||||
try {
|
||||
pwallet->UpgradeKeyMetadata();
|
||||
} catch (...) {
|
||||
result = DBErrors::CORRUPT;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <amount.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/sign.h>
|
||||
#include <wallet/db.h>
|
||||
#include <key.h>
|
||||
|
||||
|
@ -93,11 +94,14 @@ class CKeyMetadata
|
|||
public:
|
||||
static const int VERSION_BASIC=1;
|
||||
static const int VERSION_WITH_HDDATA=10;
|
||||
static const int CURRENT_VERSION=VERSION_WITH_HDDATA;
|
||||
static const int VERSION_WITH_KEY_ORIGIN = 12;
|
||||
static const int CURRENT_VERSION=VERSION_WITH_KEY_ORIGIN;
|
||||
int nVersion;
|
||||
int64_t nCreateTime; // 0 means unknown
|
||||
std::string hdKeypath; //optional HD/bip32 keypath
|
||||
std::string hdKeypath; //optional HD/bip32 keypath. Still used to determine whether a key is a seed. Also kept for backwards compatibility
|
||||
CKeyID hd_seed_id; //id of the HD seed used to derive this key
|
||||
KeyOriginInfo key_origin; // Key origin info with path and fingerprint
|
||||
bool has_key_origin = false; //< Whether the key_origin is useful
|
||||
|
||||
CKeyMetadata()
|
||||
{
|
||||
|
@ -120,6 +124,11 @@ public:
|
|||
READWRITE(hdKeypath);
|
||||
READWRITE(hd_seed_id);
|
||||
}
|
||||
if (this->nVersion >= VERSION_WITH_KEY_ORIGIN)
|
||||
{
|
||||
READWRITE(key_origin);
|
||||
READWRITE(has_key_origin);
|
||||
}
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
|
@ -128,6 +137,8 @@ public:
|
|||
nCreateTime = 0;
|
||||
hdKeypath.clear();
|
||||
hd_seed_id.SetNull();
|
||||
key_origin.clear();
|
||||
has_key_origin = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue