mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
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:
parent
b0a8522d9d
commit
ed0e2dd075
11
doc/release-notes-14021.md
Normal file
11
doc/release-notes-14021.md
Normal 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.
|
@ -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 \
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 += '\'';
|
||||
|
@ -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
66
src/util/bip32.cpp
Normal 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
19
src/util/bip32.h
Normal 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
|
@ -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');
|
||||
|
@ -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)) {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user