Skip to content

Commit

Permalink
Merge bitcoin#14021: Import key origin data through descriptors in im…
Browse files Browse the repository at this point in the history
…portmulti

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
  • Loading branch information
meshcollider authored and vijaydasmp committed Sep 28, 2021
1 parent f451701 commit ee69976
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 12 deletions.
11 changes: 11 additions & 0 deletions doc/release-notes-14021.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,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 \
Expand Down Expand Up @@ -634,6 +635,7 @@ libdash_util_a_SOURCES = \
support/cleanse.cpp \
sync.cpp \
threadinterrupt.cpp \
util/bip32.cpp \
util/bytevectorhash.cpp \
util/error.cpp \
util/fees.cpp \
Expand Down
15 changes: 3 additions & 12 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <script/standard.h>

#include <span.h>
#include <util/bip32.h>
#include <util/system.h>
#include <util/memory.h>
#include <util/strencodings.h>
Expand All @@ -26,16 +27,6 @@ namespace {

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
{
Expand Down Expand Up @@ -183,7 +174,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
}
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 += '\'';
Expand All @@ -194,7 +185,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
{
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 += '\'';
Expand Down
66 changes: 66 additions & 0 deletions src/util/bip32.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions src/util/bip32.h
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/wallet/test/psbt_wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <key_io.h>
#include <script/sign.h>
#include <util/bip32.h>
#include <util/strencodings.h>
#include <wallet/psbtwallet.h>
#include <wallet/rpcwallet.h>
Expand Down
65 changes: 65 additions & 0 deletions test/functional/wallet_importmulti.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,5 +460,70 @@ def run_test (self):
}])


# 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' : "wpkh([" + 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' : "wpkh([" + 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 ()

0 comments on commit ee69976

Please sign in to comment.