Skip to content

Commit

Permalink
Store key origin info in key metadata
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
achow101 committed Feb 14, 2019
1 parent 345bff6 commit eab63bc
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 18 deletions.
16 changes: 15 additions & 1 deletion src/script/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
3 changes: 2 additions & 1 deletion src/wallet/rpcdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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";
Expand Down
6 changes: 4 additions & 2 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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));
}
}

Expand Down
63 changes: 52 additions & 11 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

{
Expand Down Expand Up @@ -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);
}
Expand Down
7 changes: 6 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand All @@ -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
Expand Down Expand Up @@ -868,6 +871,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
//! 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);
Expand Down
8 changes: 8 additions & 0 deletions src/wallet/walletdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
15 changes: 13 additions & 2 deletions src/wallet/walletdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <amount.h>
#include <primitives/transaction.h>
#include <script/sign.h>
#include <wallet/db.h>
#include <key.h>

Expand Down Expand Up @@ -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()
{
Expand All @@ -120,6 +124,11 @@ class CKeyMetadata
READWRITE(hdKeypath);
READWRITE(hd_seed_id);
}
if (this->nVersion >= VERSION_WITH_KEY_ORIGIN)
{
READWRITE(key_origin);
READWRITE(has_key_origin);
}
}

void SetNull()
Expand All @@ -128,6 +137,8 @@ class CKeyMetadata
nCreateTime = 0;
hdKeypath.clear();
hd_seed_id.SetNull();
key_origin.clear();
has_key_origin = false;
}
};

Expand Down

0 comments on commit eab63bc

Please sign in to comment.