Merge #14021: Import key origin data through descriptors in importmulti (+ changes from PRs partially merged earlier: 15368, 15749)

cb3511b9d Add release notes for importing key origin info change (Andrew Chow)
4c75a69f3 Test importing descriptors with key origin information (Andrew Chow)
02d6586d7 Import KeyOriginData when importing descriptors (Andrew Chow)
3d235dff5 Implement a function to add KeyOriginInfo to a wallet (Andrew Chow)
eab63bc26 Store key origin info in key metadata (Andrew Chow)
345bff601 Remove hdmasterkeyid (Andrew Chow)
bac8c676a Add a method to CWallet to write just CKeyMetadata (Andrew Chow)
e7652d3f6 Add WriteHDKeypath function and move *HDKeypath to util/bip32.{h,cpp} (Andrew Chow)
c45415f73 Refactor keymetadata writing to a separate method (Andrew Chow)

Pull request description:

  This PR allows for key origin data as defined by the descriptors document to be imported to the wallet when importing a descriptor using `importmulti`. This allows the `walletprocesspsbt` to include the BIP 32 derivation paths for keys that it is watching that are from a different HD wallet.

  In order to make this easier to use, a new field `hdmasterkeyfingerprint` has been added to `getaddressinfo`. Additionally I have removed `hdmasterkeyid` as was planned. I think that this API change is fine since it was going to be removed in 0.18 anyways. `CKeyMetadata` has also been extended to store key origin info to facilitate this.

Tree-SHA512: 9c7794f3c793da57e23c5abbdc3d58779ee9dea3d53168bb86c0643a4ad5a11a446264961e2f772f35eea645048cb60954ed58050002caee4e43cd9f51215097
This commit is contained in:
MeshCollider 2019-02-15 12:10:52 +13:00 committed by UdjinM6
parent b0a8522d9d
commit ed0e2dd075
No known key found for this signature in database
GPG Key ID: 83592BD1400D58D9
19 changed files with 337 additions and 83 deletions

View File

@ -0,0 +1,11 @@
Miscellaneous RPC Changes
-------------------------
- Descriptors with key origin information imported through `importmulti` will have their key origin information stored in the wallet for use with creating PSBTs.
- If `bip32derivs` of both `walletprocesspsbt` and `walletcreatefundedpsbt` is set to true but the key metadata for a public key has not been updated yet, then that key will have a derivation path as if it were just an independent key (i.e. no derivation path and its master fingerprint is itself)
Miscellaneous Wallet changes
----------------------------
- The key metadata will need to be upgraded the first time that the HD seed is available.
For unencrypted wallets this will occur on wallet loading.
For encrypted wallets this will occur the first time the wallet is unlocked.

View File

@ -286,6 +286,7 @@ BITCOIN_CORE_H = \
ui_interface.h \
undo.h \
unordered_lru_cache.h \
util/bip32.h \
util/bytevectorhash.h \
util/error.h \
util/fees.h \
@ -671,6 +672,7 @@ libdash_util_a_SOURCES = \
support/cleanse.cpp \
sync.cpp \
threadinterrupt.cpp \
util/bip32.cpp \
util/bytevectorhash.cpp \
util/error.cpp \
util/fees.cpp \

View File

@ -8,6 +8,9 @@
#include <tinyformat.h>
#include <util/system.h>
#include <util/strencodings.h>
#ifdef ENABLE_WALLET
#include <wallet/walletdb.h>
#endif
bool CHDChain::SetNull()
{
@ -156,7 +159,7 @@ uint256 CHDChain::GetSeedHash()
return Hash(vchSeed.begin(), vchSeed.end());
}
void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet)
void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet, CKeyMetadata& metadata)
{
LOCK(cs);
// Use BIP44 keypath scheme i.e. m / purpose' / coin_type' / account' / change / address_index
@ -182,6 +185,18 @@ void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_
accountKey.Derive(changeKey, fInternal ? 1 : 0);
// derive m/purpose'/coin_type'/account'/change/address_index
changeKey.Derive(extKeyRet, nChildIndex);
#ifdef ENABLE_WALLET
metadata.key_origin.path.push_back(44 | 0x80000000);
metadata.key_origin.path.push_back(Params().ExtCoinType() | 0x80000000);
metadata.key_origin.path.push_back(nAccountIndex | 0x80000000);
metadata.key_origin.path.push_back(fInternal ? 1 : 0);
metadata.key_origin.path.push_back(nChildIndex);
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;
#endif
}
void CHDChain::AddAccount()

View File

@ -6,6 +6,8 @@
#include <key.h>
#include <sync.h>
class CKeyMetadata;
/* hd account data model */
class CHDAccount
{
@ -108,7 +110,7 @@ public:
uint256 GetID() const { LOCK(cs); return id; }
uint256 GetSeedHash();
void DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet);
void DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet, CKeyMetadata& metadata);
void AddAccount();
bool GetAccount(uint32_t nAccountIndex, CHDAccount& hdAccountRet);

View File

@ -28,9 +28,10 @@
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/bip32.h>
#include <util/moneystr.h>
#include <util/validation.h>
#include <util/strencodings.h>
#include <util/validation.h>
#include <validation.h>
#include <validationinterface.h>

View File

@ -10,10 +10,11 @@
#include <script/standard.h>
#include <span.h>
#include <util/spanparsing.h>
#include <util/system.h>
#include <util/bip32.h>
#include <util/memory.h>
#include <util/spanparsing.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <memory>
#include <string>
@ -146,16 +147,6 @@ std::string AddChecksum(const std::string& str) { return str + "#" + DescriptorC
typedef std::vector<uint32_t> KeyPath;
std::string FormatKeyPath(const KeyPath& path)
{
std::string ret;
for (auto i : path) {
ret += strprintf("/%i", (i << 1) >> 1);
if (i >> 31) ret += '\'';
}
return ret;
}
/** Interface for public key objects in descriptors. */
struct PubkeyProvider
{
@ -184,7 +175,7 @@ class OriginPubkeyProvider final : public PubkeyProvider
std::string OriginString() const
{
return HexStr(m_origin.fingerprint) + FormatKeyPath(m_origin.path);
return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path);
}
public:
@ -305,7 +296,7 @@ public:
}
std::string ToString() const override
{
std::string ret = EncodeExtPubKey(m_extkey) + FormatKeyPath(m_path);
std::string ret = EncodeExtPubKey(m_extkey) + FormatHDKeypath(m_path);
if (IsRange()) {
ret += "/*";
if (m_derive == DeriveType::HARDENED) ret += '\'';
@ -316,7 +307,7 @@ public:
{
CExtKey key;
if (!GetExtKey(arg, key)) return false;
out = EncodeExtKey(key) + FormatKeyPath(m_path);
out = EncodeExtKey(key) + FormatHDKeypath(m_path);
if (IsRange()) {
out += "/*";
if (m_derive == DeriveType::HARDENED) out += '\'';

View File

@ -21,13 +21,24 @@ 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;
}
SERIALIZE_METHODS(KeyOriginInfo, obj)
{
READWRITE(obj.fingerprint, obj.path);
}
void clear()
{
memset(fingerprint, 0, 4);
path.clear();
}
};
/** An interface to be implemented by keystores that support signing. */

66
src/util/bip32.cpp Normal file
View File

@ -0,0 +1,66 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <sstream>
#include <stdio.h>
#include <tinyformat.h>
#include <util/bip32.h>
#include <util/strencodings.h>
bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath)
{
std::stringstream ss(keypath_str);
std::string item;
bool first = true;
while (std::getline(ss, item, '/')) {
if (item.compare("m") == 0) {
if (first) {
first = false;
continue;
}
return false;
}
// Finds whether it is hardened
uint32_t path = 0;
size_t pos = item.find("'");
if (pos != std::string::npos) {
// The hardened tick can only be in the last index of the string
if (pos != item.size() - 1) {
return false;
}
path |= 0x80000000;
item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
}
// Ensure this is only numbers
if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
return false;
}
uint32_t number;
if (!ParseUInt32(item, &number)) {
return false;
}
path |= number;
keypath.push_back(path);
first = false;
}
return true;
}
std::string FormatHDKeypath(const std::vector<uint32_t>& path)
{
std::string ret;
for (auto i : path) {
ret += strprintf("/%i", (i << 1) >> 1);
if (i >> 31) ret += '\'';
}
return ret;
}
std::string WriteHDKeypath(const std::vector<uint32_t>& keypath)
{
return "m" + FormatHDKeypath(keypath);
}

19
src/util/bip32.h Normal file
View File

@ -0,0 +1,19 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_BIP32_H
#define BITCOIN_UTIL_BIP32_H
#include <attributes.h>
#include <string>
#include <vector>
/** Parse an HD keypaths like "m/7/0'/2000". */
[[nodiscard]] bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath);
/** Write HD keypaths as strings */
std::string WriteHDKeypath(const std::vector<uint32_t>& keypath);
std::string FormatHDKeypath(const std::vector<uint32_t>& path);
#endif // BITCOIN_UTIL_BIP32_H

View File

@ -575,47 +575,6 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out)
return true;
}
bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath)
{
std::stringstream ss(keypath_str);
std::string item;
bool first = true;
while (std::getline(ss, item, '/')) {
if (item.compare("m") == 0) {
if (first) {
first = false;
continue;
}
return false;
}
// Finds whether it is hardened
uint32_t path = 0;
size_t pos = item.find("'");
if (pos != std::string::npos) {
// The hardened tick can only be in the last index of the string
if (pos != item.size() - 1) {
return false;
}
path |= 0x80000000;
item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
}
// Ensure this is only numbers
if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
return false;
}
uint32_t number;
if (!ParseUInt32(item, &number)) {
return false;
}
path |= number;
keypath.push_back(path);
first = false;
}
return true;
}
std::string HexStr(const Span<const uint8_t> s)
{
std::string rv(s.size() * 2, '\0');

View File

@ -14,6 +14,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>
@ -1081,7 +1082,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
} else {
file << "change=1";
}
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapHdPubKeys.count(keyid) ? " hdkeypath="+pwallet->mapHdPubKeys[keyid].GetKeyPath() : ""));
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : ""));
}
}
file << "\n";
@ -1119,6 +1120,7 @@ struct ImportData
// Output data
std::set<CScript> import_scripts;
std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability)
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins;
};
enum class ScriptContext
@ -1347,7 +1349,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
}
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
for (size_t i = 0; i < priv_keys.size(); ++i) {
const auto& str = priv_keys[i].get_str();
CKey key = DecodeSecret(str);
@ -1372,6 +1374,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(),
[&](const std::pair<CKeyID, CPubKey>& used_key) {
return privkey_map.count(used_key.first) > 0;
}) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(),
[&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) {
return privkey_map.count(entry.first) > 0;
});
if (!watch_only && !spendable) {
warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
@ -1432,7 +1437,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
if (!pwallet->ImportPubKeys(pubkey_map, timestamp)) {
if (!pwallet->ImportPubKeys(pubkey_map, timestamp, import_data.key_origins)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, internal, timestamp)) {

View File

@ -18,6 +18,7 @@
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <util/bip32.h>
#include <util/fees.h>
#include <util/system.h>
#include <util/moneystr.h>
@ -3735,8 +3736,9 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
" \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n"
" \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n"
" \"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"
" \"hdchainid\" : \"<hash>\" (string, optional) The ID of the HD chain\n"
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\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"
@ -3801,9 +3803,12 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
ret.pushKV("timestamp", meta->nCreateTime);
CHDChain hdChainCurrent;
if (key_id && pwallet->mapHdPubKeys.count(*key_id) && pwallet->GetHDChain(hdChainCurrent)) {
ret.pushKV("hdkeypath", pwallet->mapHdPubKeys[*key_id].GetKeyPath());
ret.pushKV("hdchainid", hdChainCurrent.GetID().GetHex());
}
if (meta->has_key_origin) {
ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint));
}
}
// Currently only one label can be associated with an address, return an array

View File

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <key_io.h>
#include <util/bip32.h>
#include <util/strencodings.h>
#include <wallet/psbtwallet.h>
#include <wallet/wallet.h>

View File

@ -25,6 +25,7 @@
#include <script/script.h>
#include <script/sign.h>
#include <txmempool.h>
#include <util/bip32.h>
#include <util/error.h>
#include <util/fees.h>
#include <util/moneystr.h>
@ -273,7 +274,7 @@ CPubKey CWallet::GenerateNewKey(WalletBatch &batch, uint32_t nAccountIndex, bool
return pubkey;
}
void CWallet::DeriveNewChildKey(WalletBatch &batch, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal)
void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal)
{
CHDChain hdChainTmp;
if (!GetHDChain(hdChainTmp)) {
@ -294,7 +295,7 @@ void CWallet::DeriveNewChildKey(WalletBatch &batch, const CKeyMetadata& metadata
CExtKey childKey;
uint32_t nChildIndex = fInternal ? acc.nInternalChainCounter : acc.nExternalChainCounter;
do {
hdChainTmp.DeriveChildExtKey(nAccountIndex, fInternal, nChildIndex, childKey);
hdChainTmp.DeriveChildExtKey(nAccountIndex, fInternal, nChildIndex, childKey, metadata);
// increment childkey index
nChildIndex++;
} while (HaveKey(childKey.key.GetPubKey().GetID()));
@ -366,7 +367,8 @@ bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const
throw std::runtime_error(std::string(__func__) + ": Wrong HD chain!");
CExtKey extkey;
hdChainCurrent.DeriveChildExtKey(hdPubKey.nAccountIndex, hdPubKey.nChangeIndex != 0, hdPubKey.extPubKey.nChild, extkey);
CKeyMetadata metadataTmp;
hdChainCurrent.DeriveChildExtKey(hdPubKey.nAccountIndex, hdPubKey.nChangeIndex != 0, hdPubKey.extPubKey.nChild, extkey, metadataTmp);
keyOut = extkey.key;
return true;
@ -500,6 +502,56 @@ void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata&
m_script_metadata[script_id] = meta;
}
// Writes a keymetadata for a public key. overwrite specifies whether to overwrite an existing metadata for that key if there exists one.
bool CWallet::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite)
{
return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite);
}
void CWallet::UpgradeKeyMetadata()
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA) || !IsHDEnabled()) {
return;
}
CHDChain hdChainCurrent;
if (!GetHDChain(hdChainCurrent))
throw std::runtime_error(std::string(__func__) + ": GetHDChain failed");
if (!DecryptHDChain(hdChainCurrent))
throw std::runtime_error(std::string(__func__) + ": DecryptHDChain failed");
CExtKey masterKey;
SecureVector vchSeed = hdChainCurrent.GetSeed();
masterKey.SetSeed(vchSeed.data(), vchSeed.size());
CKeyID master_id = masterKey.key.GetPubKey().GetID();
for (auto& meta_pair : mapKeyMetadata) {
const CKeyID& keyid = meta_pair.first;
CKeyMetadata& meta = meta_pair.second;
if (!meta.has_key_origin) {
std::map<CKeyID, CHDPubKey>::const_iterator mi = mapHdPubKeys.find(keyid);
if (mi == mapHdPubKeys.end()) {
continue;
}
// Add to map
std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint);
if (!ParseHDKeypath(mi->second.GetKeyPath(), meta.key_origin.path)) {
throw std::runtime_error("Invalid HD keypath");
}
meta.has_key_origin = true;
if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
}
// Write meta to wallet
WriteKeyMetadata(meta, mi->second.extPubKey.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);
@ -626,6 +678,8 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool fForMixingOnl
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
continue; // try another master key
if (CCryptoKeyStore::Unlock(_vMasterKey, fForMixingOnly, accept_no_keys)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
if(nWalletBackups == -2) {
TopUpKeyPool();
WalletLogPrintf("Keypool replenished, re-initializing automatic backups.\n");
@ -1220,7 +1274,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256
bool res = GetPubKey(keyid, vchPubKey);
assert(res); // this should never fail
mapKeyMetadata[keyid].nCreateTime = block_time;
batch.WriteKeyMeta(vchPubKey, mapKeyMetadata[keyid]);
batch.WriteKeyMetadata(mapKeyMetadata[keyid], vchPubKey, true);
UpdateTimeFirstKey(block_time);
}
}
@ -2061,7 +2115,7 @@ bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const in
return true;
}
bool CWallet::ImportPubKeys(const std::map<CKeyID, CPubKey>& pubkey_map, const int64_t timestamp)
bool CWallet::ImportPubKeys(const std::map<CKeyID, CPubKey>& pubkey_map, const int64_t timestamp, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins)
{
WalletBatch batch(*database);
for (const auto& entry : pubkey_map) {
@ -2073,6 +2127,9 @@ bool CWallet::ImportPubKeys(const std::map<CKeyID, CPubKey>& pubkey_map, const i
}
mapKeyMetadata[id].nCreateTime = timestamp;
}
for (const auto& entry : key_origins) {
AddKeyOrigin(entry.second.first, entry.second.second);
}
return true;
}
@ -5599,6 +5656,20 @@ bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const {
meta = it->second;
}
}
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);
}
return true;
}
bool CWallet::AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info)
{
LOCK(cs_wallet);
std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint);
mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path;
mapKeyMetadata[pubkey.GetID()].has_key_origin = true;
return WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
}

View File

@ -120,6 +120,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),
@ -136,7 +139,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
@ -642,7 +645,7 @@ private:
void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* HD derive new child key (on internal or external chain) */
void DeriveNewChildKey(WalletBatch& batch, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet);
std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet);
@ -736,6 +739,8 @@ public:
// Map from governance object hash to governance object, they are added by gobject_prepare.
std::map<uint256, CGovernanceObject> m_gobjects;
bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, bool overwrite);
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID = 0;
@ -867,6 +872,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);
@ -1000,7 +1007,7 @@ public:
bool ImportScripts(const std::set<CScript> scripts) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPubKeys(const std::map<CKeyID, CPubKey>& pubkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPubKeys(const std::map<CKeyID, CPubKey>& pubkey_map, const int64_t timestamp, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
@ -1246,6 +1253,9 @@ public:
/** Implement lookup of key origin information through wallet key metadata. */
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
/** Add a KeyOriginInfo to the wallet */
bool AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info);
};
/**

View File

@ -11,6 +11,7 @@
#include <key_io.h>
#include <fs.h>
#include <governance/object.h>
#include <hdchain.h>
#include <protocol.h>
#include <serialize.h>
#include <sync.h>
@ -88,14 +89,14 @@ bool WalletBatch::EraseTx(uint256 hash)
return EraseIC(std::make_pair(DBKeys::TX, hash));
}
bool WalletBatch::WriteKeyMeta(const CPubKey& vchPubKey, const CKeyMetadata& keyMeta)
bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& keyMeta, const CPubKey& vchPubKey, const bool overwrite)
{
return WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta, true);
return WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta, overwrite);
}
bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{
if (!WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta, false)) {
if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) {
return false;
}
@ -112,7 +113,7 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta)
{
if (!WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta)) {
if (!WriteKeyMetadata(keyMeta, vchPubKey, true)) {
return false;
}
@ -601,6 +602,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;
}

View File

@ -7,8 +7,8 @@
#define BITCOIN_WALLET_WALLETDB_H
#include <amount.h>
#include <script/sign.h>
#include <wallet/db.h>
#include <hdchain.h>
#include <key.h>
#include <stdint.h>
@ -31,6 +31,8 @@ static const bool DEFAULT_FLUSHWALLET = true;
struct CBlockLocator;
class CGovernanceObject;
class CHDChain;
class CHDPubKey;
class CKeyPool;
class CMasterKey;
class CScript;
@ -86,9 +88,13 @@ extern const std::string WATCHS;
class CKeyMetadata
{
public:
static const int CURRENT_VERSION=1;
static const int VERSION_BASIC=1;
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
KeyOriginInfo key_origin; // Key origin info with path and fingerprint
bool has_key_origin = false; //< Whether the key_origin is useful
CKeyMetadata()
{
@ -103,12 +109,17 @@ public:
SERIALIZE_METHODS(CKeyMetadata, obj)
{
READWRITE(obj.nVersion, obj.nCreateTime);
if (obj.nVersion >= VERSION_WITH_KEY_ORIGIN) {
READWRITE(obj.key_origin, obj.has_key_origin);
}
}
void SetNull()
{
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
key_origin.clear();
has_key_origin = false;
}
};
@ -166,8 +177,7 @@ public:
bool WriteTx(const CWalletTx& wtx);
bool EraseTx(uint256 hash);
bool WriteKeyMeta(const CPubKey& vchPubKey, const CKeyMetadata &keyMeta);
bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite);
bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata &keyMeta);
bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta);
bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey);

View File

@ -57,6 +57,7 @@ class ImportMultiTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
self.extra_args = [['-usehd=1']] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@ -560,13 +561,77 @@ class ImportMultiTest(BitcoinTestFramework):
self.log.info("Should import a 1-of-2 bare multisig from descriptor")
self.test_importmulti({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
"timestamp": "now"},
success=True)
True,
warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
self.test_address(key1.p2pkh_addr,
ismine=False,
iswatchonly=False)
# Import pubkeys with key origin info
self.log.info("Addresses should have hd keypath and master key id after import with key origin")
pub_addr = self.nodes[1].getnewaddress()
pub_addr = self.nodes[1].getnewaddress()
info = self.nodes[1].getaddressinfo(pub_addr)
pub = info['pubkey']
pub_keypath = info['hdkeypath']
pub_fpr = info['hdmasterfingerprint']
result = self.nodes[0].importmulti(
[{
'desc' : descsum_create("pkh([" + pub_fpr + pub_keypath[1:] +"]" + pub + ")"),
"timestamp": "now",
}]
)
assert result[0]['success']
pub_import_info = self.nodes[0].getaddressinfo(pub_addr)
assert_equal(pub_import_info['hdmasterfingerprint'], pub_fpr)
assert_equal(pub_import_info['pubkey'], pub)
assert_equal(pub_import_info['hdkeypath'], pub_keypath)
# Import privkeys with key origin info
priv_addr = self.nodes[1].getnewaddress()
info = self.nodes[1].getaddressinfo(priv_addr)
priv = self.nodes[1].dumpprivkey(priv_addr)
priv_keypath = info['hdkeypath']
priv_fpr = info['hdmasterfingerprint']
result = self.nodes[0].importmulti(
[{
'desc' : descsum_create("pkh([" + priv_fpr + priv_keypath[1:] + "]" + priv + ")"),
"timestamp": "now",
}]
)
assert result[0]['success']
priv_import_info = self.nodes[0].getaddressinfo(priv_addr)
assert_equal(priv_import_info['hdmasterfingerprint'], priv_fpr)
assert_equal(priv_import_info['hdkeypath'], priv_keypath)
# Make sure the key origin info are still there after a restart
self.stop_nodes()
self.start_nodes()
import_info = self.nodes[0].getaddressinfo(pub_addr)
assert_equal(import_info['hdmasterfingerprint'], pub_fpr)
assert_equal(import_info['hdkeypath'], pub_keypath)
import_info = self.nodes[0].getaddressinfo(priv_addr)
assert_equal(import_info['hdmasterfingerprint'], priv_fpr)
assert_equal(import_info['hdkeypath'], priv_keypath)
# Check legacy import does not import key origin info
self.log.info("Legacy imports don't have key origin info")
pub_addr = self.nodes[1].getnewaddress()
info = self.nodes[1].getaddressinfo(pub_addr)
pub = info['pubkey']
result = self.nodes[0].importmulti(
[{
'scriptPubKey': {'address': pub_addr},
'pubkeys': [pub],
"timestamp": "now",
}]
)
assert result[0]['success']
pub_import_info = self.nodes[0].getaddressinfo(pub_addr)
assert_equal(pub_import_info['pubkey'], pub)
assert 'hdmasterfingerprint' not in pub_import_info
assert 'hdkeypath' not in pub_import_info
if __name__ == '__main__':
ImportMultiTest().main()

View File

@ -40,6 +40,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"governance/governance -> masternode/sync -> governance/governance"
"governance/governance -> net_processing -> governance/governance"
"governance/object -> governance/validators -> governance/object"
"hdchain -> wallet/walletdb -> hdchain"
"llmq/quorums -> llmq/utils -> llmq/quorums"
"llmq/blockprocessor -> net_processing -> llmq/blockprocessor"
"llmq/chainlocks -> llmq/instantsend -> llmq/chainlocks"