mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-01 00:34:01 +02:00
wallet: Add addhdkey RPC
This commit is contained in:
@@ -840,6 +840,82 @@ static RPCMethod createwalletdescriptor()
|
||||
};
|
||||
}
|
||||
|
||||
RPCMethod addhdkey()
|
||||
{
|
||||
return RPCMethod{
|
||||
"addhdkey",
|
||||
"Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
|
||||
{
|
||||
{"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"Automatically generated new key"}, "The BIP 32 extended private key to add. If none is provided, a randomly generated one will be added."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
|
||||
},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
|
||||
},
|
||||
[&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return UniValue::VNULL;
|
||||
|
||||
if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "addhdkey is not available for wallets without private keys");
|
||||
}
|
||||
|
||||
EnsureWalletIsUnlocked(*wallet);
|
||||
|
||||
CExtKey hdkey;
|
||||
if (request.params[0].isNull()) {
|
||||
CKey seed_key = GenerateRandomKey();
|
||||
hdkey.SetSeed(seed_key);
|
||||
} else {
|
||||
hdkey = DecodeExtKey(request.params[0].get_str());
|
||||
if (!hdkey.key.IsValid()) {
|
||||
// Check if the user gave us an xpub and give a more descriptive error if so
|
||||
CExtPubKey xpub = DecodeExtPubKey(request.params[0].get_str());
|
||||
if (xpub.pubkey.IsValid()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extended public key (xpub) provided, but extended private key (xprv) is required");
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not parse HD key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::string desc_str = "unused(" + EncodeExtKey(hdkey) + ")";
|
||||
FlatSigningProvider keys;
|
||||
std::string error;
|
||||
std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_str, keys, error, false);
|
||||
CHECK_NONFATAL(!descs.empty());
|
||||
WalletDescriptor w_desc(std::move(descs.at(0)), GetTime(), 0, 0, 0);
|
||||
if (wallet->GetDescriptorScriptPubKeyMan(w_desc) != nullptr) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "HD key already exists");
|
||||
}
|
||||
|
||||
auto spkm = wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", /*internal=*/false);
|
||||
if (!spkm) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(spkm).original);
|
||||
}
|
||||
|
||||
UniValue response(UniValue::VOBJ);
|
||||
const DescriptorScriptPubKeyMan& desc_spkm = spkm->get();
|
||||
LOCK(desc_spkm.cs_desc_man);
|
||||
std::set<CPubKey> pubkeys;
|
||||
std::set<CExtPubKey> extpubs;
|
||||
desc_spkm.GetWalletDescriptor().descriptor->GetPubKeys(pubkeys, extpubs);
|
||||
CHECK_NONFATAL(pubkeys.size() == 0);
|
||||
CHECK_NONFATAL(extpubs.size() == 1);
|
||||
response.pushKV("xpub", EncodeExtPubKey(*extpubs.begin()));
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// addresses
|
||||
RPCMethod getaddressinfo();
|
||||
RPCMethod getnewaddress();
|
||||
@@ -907,6 +983,7 @@ std::span<const CRPCCommand> GetWalletRPCCommands()
|
||||
{"rawtransactions", &fundrawtransaction},
|
||||
{"wallet", &abandontransaction},
|
||||
{"wallet", &abortrescan},
|
||||
{"wallet", &addhdkey},
|
||||
{"wallet", &backupwallet},
|
||||
{"wallet", &bumpfee},
|
||||
{"wallet", &psbtbumpfee},
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
import shutil
|
||||
|
||||
from test_framework.blocktools import COINBASE_MATURITY
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
wallet_importprivkey,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
|
||||
|
||||
@@ -25,6 +27,56 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def test_addhdkey(self):
|
||||
self.log.info("Test addhdkey")
|
||||
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
||||
self.nodes[0].createwallet("hdkey")
|
||||
wallet = self.nodes[0].get_wallet_rpc("hdkey")
|
||||
|
||||
assert_equal(len(wallet.gethdkeys()), 1)
|
||||
|
||||
wallet.addhdkey()
|
||||
xpub_info = wallet.gethdkeys()
|
||||
assert_equal(len(xpub_info), 2)
|
||||
for x in xpub_info:
|
||||
if len(x["descriptors"]) == 1 and x["descriptors"][0]["desc"].startswith("unused("):
|
||||
break
|
||||
else:
|
||||
assert False, "Did not find HD key with no descriptors"
|
||||
|
||||
imp_xpub_info = def_wallet.gethdkeys(private=True)[0]
|
||||
imp_xpub = imp_xpub_info["xpub"]
|
||||
imp_xprv = imp_xpub_info["xprv"]
|
||||
|
||||
assert_raises_rpc_error(-5, "Extended public key (xpub) provided, but extended private key (xprv) is required", wallet.addhdkey, imp_xpub)
|
||||
add_res = wallet.addhdkey(imp_xprv)
|
||||
expected_unused_desc = descsum_create(f"unused({imp_xpub})")
|
||||
assert_equal(add_res["xpub"], imp_xpub)
|
||||
xpub_info = wallet.gethdkeys()
|
||||
assert_equal(len(xpub_info), 3)
|
||||
for x in xpub_info:
|
||||
if x["xpub"] == imp_xpub:
|
||||
assert_equal(len(x["descriptors"]), 1)
|
||||
assert_equal(x["descriptors"][0]["desc"], expected_unused_desc)
|
||||
break
|
||||
else:
|
||||
assert False, "Added HD key was not found in wallet"
|
||||
|
||||
for d in wallet.listdescriptors()["descriptors"]:
|
||||
if d["desc"] == expected_unused_desc:
|
||||
assert_equal(d["active"], False)
|
||||
break
|
||||
else:
|
||||
assert False, "Added HD key's descriptor was not found in wallet"
|
||||
|
||||
assert_raises_rpc_error(-4, "HD key already exists", wallet.addhdkey, imp_xprv)
|
||||
|
||||
def test_addhdkey_noprivs(self):
|
||||
self.log.info("Test addhdkey is not available for wallets without privkeys")
|
||||
self.nodes[0].createwallet("hdkey_noprivs", disable_private_keys=True)
|
||||
wallet = self.nodes[0].get_wallet_rpc("hdkey_noprivs")
|
||||
assert_raises_rpc_error(-4, "addhdkey is not available for wallets without private keys", wallet.addhdkey)
|
||||
|
||||
def run_test(self):
|
||||
# Make sure we use hd, keep masterkeyid
|
||||
hd_fingerprint = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['hdmasterfingerprint']
|
||||
@@ -124,6 +176,8 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
|
||||
assert_equal(keypath[0:14], "m/84h/1h/0h/1/")
|
||||
|
||||
self.test_addhdkey()
|
||||
self.test_addhdkey_noprivs()
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletHDTest(__file__).main()
|
||||
|
||||
Reference in New Issue
Block a user