Skip to content

Commit 5febe28

Browse files
committed
wallet, rpc: Add gethdkeys RPC
gethdkeys retrieves all HD keys stored in the wallet's descriptors.
1 parent 66632e5 commit 5febe28

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

src/rpc/client.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
275275
{ "logging", 1, "exclude" },
276276
{ "disconnectnode", 1, "nodeid" },
277277
{ "upgradewallet", 0, "version" },
278+
{ "gethdkeys", 0, "active_only" },
279+
{ "gethdkeys", 0, "options" },
280+
{ "gethdkeys", 0, "private" },
278281
// Echo with conversion (For testing only)
279282
{ "echojson", 0, "arg0" },
280283
{ "echojson", 1, "arg1" },

src/wallet/rpc/wallet.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,114 @@ static RPCHelpMan migratewallet()
817817
};
818818
}
819819

820+
RPCHelpMan gethdkeys()
821+
{
822+
return RPCHelpMan{
823+
"gethdkeys",
824+
"\nList all BIP 32 HD keys in the wallet and which descriptors use them.\n",
825+
{
826+
{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
827+
{"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
828+
{"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
829+
}},
830+
},
831+
RPCResult{RPCResult::Type::ARR, "", "", {
832+
{
833+
{RPCResult::Type::OBJ, "", "", {
834+
{RPCResult::Type::STR, "xpub", "The extended public key"},
835+
{RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
836+
{RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
837+
{RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
838+
{
839+
{RPCResult::Type::OBJ, "", "", {
840+
{RPCResult::Type::STR, "desc", "Descriptor string representation"},
841+
{RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
842+
}},
843+
}},
844+
}},
845+
}
846+
}},
847+
RPCExamples{
848+
HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
849+
+ HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
850+
},
851+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
852+
{
853+
const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
854+
if (!wallet) return UniValue::VNULL;
855+
856+
if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
857+
throw JSONRPCError(RPC_WALLET_ERROR, "gethdkeys is not available for non-descriptor wallets");
858+
}
859+
860+
LOCK(wallet->cs_wallet);
861+
862+
UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
863+
const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
864+
const bool priv{options.exists("private") ? options["private"].get_bool() : false};
865+
if (priv) {
866+
EnsureWalletIsUnlocked(*wallet);
867+
}
868+
869+
870+
std::set<ScriptPubKeyMan*> spkms;
871+
if (active_only) {
872+
spkms = wallet->GetActiveScriptPubKeyMans();
873+
} else {
874+
spkms = wallet->GetAllScriptPubKeyMans();
875+
}
876+
877+
std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
878+
std::map<CExtPubKey, CExtKey> wallet_xprvs;
879+
for (auto* spkm : spkms) {
880+
auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
881+
CHECK_NONFATAL(desc_spkm);
882+
LOCK(desc_spkm->cs_desc_man);
883+
WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
884+
885+
// Retrieve the pubkeys from the descriptor
886+
std::set<CPubKey> desc_pubkeys;
887+
std::set<CExtPubKey> desc_xpubs;
888+
w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
889+
for (const CExtPubKey& xpub : desc_xpubs) {
890+
std::string desc_str;
891+
bool ok = desc_spkm->GetDescriptorString(desc_str, false);
892+
CHECK_NONFATAL(ok);
893+
wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
894+
if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
895+
wallet_xprvs[xpub] = CExtKey(xpub, *key);
896+
}
897+
}
898+
}
899+
900+
UniValue response(UniValue::VARR);
901+
for (const auto& [xpub, descs] : wallet_xpubs) {
902+
bool has_xprv = false;
903+
UniValue descriptors(UniValue::VARR);
904+
for (const auto& [desc, active, has_priv] : descs) {
905+
UniValue d(UniValue::VOBJ);
906+
d.pushKV("desc", desc);
907+
d.pushKV("active", active);
908+
has_xprv |= has_priv;
909+
910+
descriptors.push_back(std::move(d));
911+
}
912+
UniValue xpub_info(UniValue::VOBJ);
913+
xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
914+
xpub_info.pushKV("has_private", has_xprv);
915+
if (priv) {
916+
xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
917+
}
918+
xpub_info.pushKV("descriptors", std::move(descriptors));
919+
920+
response.push_back(std::move(xpub_info));
921+
}
922+
923+
return response;
924+
},
925+
};
926+
}
927+
820928
// addresses
821929
RPCHelpMan getaddressinfo();
822930
RPCHelpMan getnewaddress();
@@ -907,6 +1015,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
9071015
{"wallet", &getaddressesbylabel},
9081016
{"wallet", &getaddressinfo},
9091017
{"wallet", &getbalance},
1018+
{"wallet", &gethdkeys},
9101019
{"wallet", &getnewaddress},
9111020
{"wallet", &getrawchangeaddress},
9121021
{"wallet", &getreceivedbyaddress},

src/wallet/scriptpubkeyman.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
672672
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const;
673673
int32_t GetEndRange() const;
674674

675-
bool GetDescriptorString(std::string& out, const bool priv) const;
675+
[[nodiscard]] bool GetDescriptorString(std::string& out, const bool priv) const;
676676

677677
void UpgradeDescriptorCache();
678678
};

0 commit comments

Comments
 (0)