mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-10 10:52:31 -05:00
wallet, rpc: Add gethdkeys RPC
gethdkeys retrieves all HD keys stored in the wallet's descriptors.
This commit is contained in:
parent
66632e5c24
commit
5febe28c9e
3 changed files with 113 additions and 1 deletions
|
@ -275,6 +275,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "logging", 1, "exclude" },
|
{ "logging", 1, "exclude" },
|
||||||
{ "disconnectnode", 1, "nodeid" },
|
{ "disconnectnode", 1, "nodeid" },
|
||||||
{ "upgradewallet", 0, "version" },
|
{ "upgradewallet", 0, "version" },
|
||||||
|
{ "gethdkeys", 0, "active_only" },
|
||||||
|
{ "gethdkeys", 0, "options" },
|
||||||
|
{ "gethdkeys", 0, "private" },
|
||||||
// Echo with conversion (For testing only)
|
// Echo with conversion (For testing only)
|
||||||
{ "echojson", 0, "arg0" },
|
{ "echojson", 0, "arg0" },
|
||||||
{ "echojson", 1, "arg1" },
|
{ "echojson", 1, "arg1" },
|
||||||
|
|
|
@ -817,6 +817,114 @@ static RPCHelpMan migratewallet()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RPCHelpMan gethdkeys()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{
|
||||||
|
"gethdkeys",
|
||||||
|
"\nList all BIP 32 HD keys in the wallet and which descriptors use them.\n",
|
||||||
|
{
|
||||||
|
{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
|
||||||
|
{"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
|
||||||
|
{"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
RPCResult{RPCResult::Type::ARR, "", "", {
|
||||||
|
{
|
||||||
|
{RPCResult::Type::OBJ, "", "", {
|
||||||
|
{RPCResult::Type::STR, "xpub", "The extended public key"},
|
||||||
|
{RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
|
||||||
|
{RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
|
||||||
|
{RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::OBJ, "", "", {
|
||||||
|
{RPCResult::Type::STR, "desc", "Descriptor string representation"},
|
||||||
|
{RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
RPCExamples{
|
||||||
|
HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
|
||||||
|
+ HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
if (!wallet) return UniValue::VNULL;
|
||||||
|
|
||||||
|
if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "gethdkeys is not available for non-descriptor wallets");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK(wallet->cs_wallet);
|
||||||
|
|
||||||
|
UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
|
||||||
|
const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
|
||||||
|
const bool priv{options.exists("private") ? options["private"].get_bool() : false};
|
||||||
|
if (priv) {
|
||||||
|
EnsureWalletIsUnlocked(*wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::set<ScriptPubKeyMan*> spkms;
|
||||||
|
if (active_only) {
|
||||||
|
spkms = wallet->GetActiveScriptPubKeyMans();
|
||||||
|
} else {
|
||||||
|
spkms = wallet->GetAllScriptPubKeyMans();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
|
||||||
|
std::map<CExtPubKey, CExtKey> wallet_xprvs;
|
||||||
|
for (auto* spkm : spkms) {
|
||||||
|
auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
|
||||||
|
CHECK_NONFATAL(desc_spkm);
|
||||||
|
LOCK(desc_spkm->cs_desc_man);
|
||||||
|
WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
|
||||||
|
|
||||||
|
// Retrieve the pubkeys from the descriptor
|
||||||
|
std::set<CPubKey> desc_pubkeys;
|
||||||
|
std::set<CExtPubKey> desc_xpubs;
|
||||||
|
w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
|
||||||
|
for (const CExtPubKey& xpub : desc_xpubs) {
|
||||||
|
std::string desc_str;
|
||||||
|
bool ok = desc_spkm->GetDescriptorString(desc_str, false);
|
||||||
|
CHECK_NONFATAL(ok);
|
||||||
|
wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
|
||||||
|
if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
|
||||||
|
wallet_xprvs[xpub] = CExtKey(xpub, *key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue response(UniValue::VARR);
|
||||||
|
for (const auto& [xpub, descs] : wallet_xpubs) {
|
||||||
|
bool has_xprv = false;
|
||||||
|
UniValue descriptors(UniValue::VARR);
|
||||||
|
for (const auto& [desc, active, has_priv] : descs) {
|
||||||
|
UniValue d(UniValue::VOBJ);
|
||||||
|
d.pushKV("desc", desc);
|
||||||
|
d.pushKV("active", active);
|
||||||
|
has_xprv |= has_priv;
|
||||||
|
|
||||||
|
descriptors.push_back(std::move(d));
|
||||||
|
}
|
||||||
|
UniValue xpub_info(UniValue::VOBJ);
|
||||||
|
xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
|
||||||
|
xpub_info.pushKV("has_private", has_xprv);
|
||||||
|
if (priv) {
|
||||||
|
xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
|
||||||
|
}
|
||||||
|
xpub_info.pushKV("descriptors", std::move(descriptors));
|
||||||
|
|
||||||
|
response.push_back(std::move(xpub_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// addresses
|
// addresses
|
||||||
RPCHelpMan getaddressinfo();
|
RPCHelpMan getaddressinfo();
|
||||||
RPCHelpMan getnewaddress();
|
RPCHelpMan getnewaddress();
|
||||||
|
@ -907,6 +1015,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
|
||||||
{"wallet", &getaddressesbylabel},
|
{"wallet", &getaddressesbylabel},
|
||||||
{"wallet", &getaddressinfo},
|
{"wallet", &getaddressinfo},
|
||||||
{"wallet", &getbalance},
|
{"wallet", &getbalance},
|
||||||
|
{"wallet", &gethdkeys},
|
||||||
{"wallet", &getnewaddress},
|
{"wallet", &getnewaddress},
|
||||||
{"wallet", &getrawchangeaddress},
|
{"wallet", &getrawchangeaddress},
|
||||||
{"wallet", &getreceivedbyaddress},
|
{"wallet", &getreceivedbyaddress},
|
||||||
|
|
|
@ -672,7 +672,7 @@ public:
|
||||||
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const;
|
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const;
|
||||||
int32_t GetEndRange() const;
|
int32_t GetEndRange() const;
|
||||||
|
|
||||||
bool GetDescriptorString(std::string& out, const bool priv) const;
|
[[nodiscard]] bool GetDescriptorString(std::string& out, const bool priv) const;
|
||||||
|
|
||||||
void UpgradeDescriptorCache();
|
void UpgradeDescriptorCache();
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue