Skip to content

Commit 8d0ec74

Browse files
committed
Merge #14021: Import key origin data through descriptors in importmulti
cb3511b Add release notes for importing key origin info change (Andrew Chow) 4c75a69 Test importing descriptors with key origin information (Andrew Chow) 02d6586 Import KeyOriginData when importing descriptors (Andrew Chow) 3d235df Implement a function to add KeyOriginInfo to a wallet (Andrew Chow) eab63bc Store key origin info in key metadata (Andrew Chow) 345bff6 Remove hdmasterkeyid (Andrew Chow) bac8c67 Add a method to CWallet to write just CKeyMetadata (Andrew Chow) e7652d3 Add WriteHDKeypath function and move *HDKeypath to util/bip32.{h,cpp} (Andrew Chow) c45415f 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
2 parents 33480c6 + cb3511b commit 8d0ec74

19 files changed

+307
-87
lines changed

doc/release-notes-14021.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Miscellaneous RPC Changes
2+
-------------------------
3+
- Descriptors with key origin information imported through `importmulti` will have their key origin information stored in the wallet for use with creating PSBTs.
4+
- 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)
5+
6+
Miscellaneous Wallet changes
7+
----------------------------
8+
9+
- The key metadata will need to be upgraded the first time that the HD seed is available.
10+
For unencrypted wallets this will occur on wallet loading.
11+
For encrypted wallets this will occur the first time the wallet is unlocked.

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ BITCOIN_CORE_H = \
196196
txmempool.h \
197197
ui_interface.h \
198198
undo.h \
199+
util/bip32.h \
199200
util/bytevectorhash.h \
200201
util/system.h \
201202
util/memory.h \
@@ -456,6 +457,7 @@ libbitcoin_util_a_SOURCES = \
456457
support/cleanse.cpp \
457458
sync.cpp \
458459
threadinterrupt.cpp \
460+
util/bip32.cpp \
459461
util/bytevectorhash.cpp \
460462
util/system.cpp \
461463
util/moneystr.cpp \

src/rpc/rawtransaction.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <script/sign.h>
2727
#include <script/standard.h>
2828
#include <uint256.h>
29+
#include <util/bip32.h>
2930
#include <util/strencodings.h>
3031
#include <validation.h>
3132
#include <validationinterface.h>

src/script/descriptor.cpp

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <script/standard.h>
1111

1212
#include <span.h>
13+
#include <util/bip32.h>
1314
#include <util/system.h>
1415
#include <util/strencodings.h>
1516

@@ -25,16 +26,6 @@ namespace {
2526

2627
typedef std::vector<uint32_t> KeyPath;
2728

28-
std::string FormatKeyPath(const KeyPath& path)
29-
{
30-
std::string ret;
31-
for (auto i : path) {
32-
ret += strprintf("/%i", (i << 1) >> 1);
33-
if (i >> 31) ret += '\'';
34-
}
35-
return ret;
36-
}
37-
3829
/** Interface for public key objects in descriptors. */
3930
struct PubkeyProvider
4031
{
@@ -63,7 +54,7 @@ class OriginPubkeyProvider final : public PubkeyProvider
6354

6455
std::string OriginString() const
6556
{
66-
return HexStr(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint)) + FormatKeyPath(m_origin.path);
57+
return HexStr(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint)) + FormatHDKeypath(m_origin.path);
6758
}
6859

6960
public:
@@ -184,7 +175,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
184175
}
185176
std::string ToString() const override
186177
{
187-
std::string ret = EncodeExtPubKey(m_extkey) + FormatKeyPath(m_path);
178+
std::string ret = EncodeExtPubKey(m_extkey) + FormatHDKeypath(m_path);
188179
if (IsRange()) {
189180
ret += "/*";
190181
if (m_derive == DeriveType::HARDENED) ret += '\'';
@@ -195,7 +186,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
195186
{
196187
CExtKey key;
197188
if (!GetExtKey(arg, key)) return false;
198-
out = EncodeExtKey(key) + FormatKeyPath(m_path);
189+
out = EncodeExtKey(key) + FormatHDKeypath(m_path);
199190
if (IsRange()) {
200191
out += "/*";
201192
if (m_derive == DeriveType::HARDENED) out += '\'';

src/script/sign.h

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

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

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

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

src/util/bip32.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2019 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <sstream>
6+
#include <stdio.h>
7+
#include <tinyformat.h>
8+
#include <util/bip32.h>
9+
#include <util/strencodings.h>
10+
11+
12+
bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath)
13+
{
14+
std::stringstream ss(keypath_str);
15+
std::string item;
16+
bool first = true;
17+
while (std::getline(ss, item, '/')) {
18+
if (item.compare("m") == 0) {
19+
if (first) {
20+
first = false;
21+
continue;
22+
}
23+
return false;
24+
}
25+
// Finds whether it is hardened
26+
uint32_t path = 0;
27+
size_t pos = item.find("'");
28+
if (pos != std::string::npos) {
29+
// The hardened tick can only be in the last index of the string
30+
if (pos != item.size() - 1) {
31+
return false;
32+
}
33+
path |= 0x80000000;
34+
item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
35+
}
36+
37+
// Ensure this is only numbers
38+
if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
39+
return false;
40+
}
41+
uint32_t number;
42+
if (!ParseUInt32(item, &number)) {
43+
return false;
44+
}
45+
path |= number;
46+
47+
keypath.push_back(path);
48+
first = false;
49+
}
50+
return true;
51+
}
52+
53+
std::string FormatHDKeypath(const std::vector<uint32_t>& path)
54+
{
55+
std::string ret;
56+
for (auto i : path) {
57+
ret += strprintf("/%i", (i << 1) >> 1);
58+
if (i >> 31) ret += '\'';
59+
}
60+
return ret;
61+
}
62+
63+
std::string WriteHDKeypath(const std::vector<uint32_t>& keypath)
64+
{
65+
return "m" + FormatHDKeypath(keypath);
66+
}

src/util/bip32.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) 2019 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_UTIL_BIP32_H
6+
#define BITCOIN_UTIL_BIP32_H
7+
8+
#include <attributes.h>
9+
#include <string>
10+
#include <vector>
11+
12+
/** Parse an HD keypaths like "m/7/0'/2000". */
13+
NODISCARD bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath);
14+
15+
/** Write HD keypaths as strings */
16+
std::string WriteHDKeypath(const std::vector<uint32_t>& keypath);
17+
std::string FormatHDKeypath(const std::vector<uint32_t>& path);
18+
19+
#endif // BITCOIN_UTIL_BIP32_H

src/util/strencodings.cpp

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -546,47 +546,6 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out)
546546
return true;
547547
}
548548

549-
bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath)
550-
{
551-
std::stringstream ss(keypath_str);
552-
std::string item;
553-
bool first = true;
554-
while (std::getline(ss, item, '/')) {
555-
if (item.compare("m") == 0) {
556-
if (first) {
557-
first = false;
558-
continue;
559-
}
560-
return false;
561-
}
562-
// Finds whether it is hardened
563-
uint32_t path = 0;
564-
size_t pos = item.find("'");
565-
if (pos != std::string::npos) {
566-
// The hardened tick can only be in the last index of the string
567-
if (pos != item.size() - 1) {
568-
return false;
569-
}
570-
path |= 0x80000000;
571-
item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
572-
}
573-
574-
// Ensure this is only numbers
575-
if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
576-
return false;
577-
}
578-
uint32_t number;
579-
if (!ParseUInt32(item, &number)) {
580-
return false;
581-
}
582-
path |= number;
583-
584-
keypath.push_back(path);
585-
first = false;
586-
}
587-
return true;
588-
}
589-
590549
void Downcase(std::string& str)
591550
{
592551
std::transform(str.begin(), str.end(), str.begin(), [](char c){return ToLower(c);});

src/util/strencodings.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,6 @@ bool ConvertBits(const O& outfn, I it, I end) {
197197
return true;
198198
}
199199

200-
/** Parse an HD keypaths like "m/7/0'/2000". */
201-
NODISCARD bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath);
202-
203200
/**
204201
* Converts the given character to its lowercase equivalent.
205202
* This function is locale independent. It only converts uppercase

src/wallet/rpcdump.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <script/script.h>
1414
#include <script/standard.h>
1515
#include <sync.h>
16+
#include <util/bip32.h>
1617
#include <util/system.h>
1718
#include <util/time.h>
1819
#include <validation.h>
@@ -850,7 +851,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
850851
} else {
851852
file << "change=1";
852853
}
853-
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : ""));
854+
file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : ""));
854855
}
855856
}
856857
file << "\n";
@@ -887,6 +888,7 @@ struct ImportData
887888
// Output data
888889
std::set<CScript> import_scripts;
889890
std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability)
891+
std::map<CKeyID, KeyOriginInfo> key_origins;
890892
};
891893

892894
enum class ScriptContext
@@ -1157,7 +1159,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
11571159
}
11581160

11591161
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
1160-
1162+
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
11611163
for (size_t i = 0; i < priv_keys.size(); ++i) {
11621164
const auto& str = priv_keys[i].get_str();
11631165
CKey key = DecodeSecret(str);
@@ -1260,6 +1262,11 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
12601262
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
12611263
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
12621264
}
1265+
const auto& key_orig_it = import_data.key_origins.find(id);
1266+
if (key_orig_it != import_data.key_origins.end()) {
1267+
pwallet->AddKeyOrigin(pubkey, key_orig_it->second);
1268+
}
1269+
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
12631270
}
12641271

12651272
for (const CScript& script : script_pub_keys) {

0 commit comments

Comments
 (0)