wallet: fix `gethdkeys` RPC for descriptors with partial xprvs
What changed, and why it matters
This commit fixes a bug in Bitcoin Core's `gethdkeys` RPC command. The command can crash with an unhandled 'key not found' error when a user requests private key information for a wallet that contains descriptors where some extended public keys (xpubs) do not have corresponding private keys in the wallet. The fix prevents the crash by only trying to return the private key when the wallet actually has it, and it updates the related documentation to clarify that the descriptor string shown is the public version.
Apply the patch. It is a minimal, targeted fix that prevents an RPC crash and improves documentation clarity. No immediate incident response is indicated beyond normal merge and release.
Security signals we found
Unhandled std::out_of_range exception in RPC path
Crash/DoS condition in wallet RPC when private=true and descriptor has partial xprvs
Informational documentation update clarifying descriptor string is public
Evidence from the diff
The gethdkeys RPC iterates over wallet descriptors and collects extended public keys (xpubs). When called with private=true, it attempts to look up each xpub’s corresponding extended private key (xprv) in a wallet_xprvs map and unconditionally calls wallet_xprvs.at(xpub). If a descriptor contains an xpub for which the wallet lacks the private key (e.g., a multisig descriptor with one private and one public key), the map lookup throws std::out_of_range, surfacing as map::at: key not found. The patch changes the conditional to if (priv && has_xprv) so the lookup is skipped when no xprv exists, and updates the desc help text to ‘public representation’.
Changed components
src/wallet/rpc/wallet.cppgethdkeys RPCInspect captured patch +3 / −3
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 49808a8d..f15ba83b 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -660,7 +660,7 @@ RPCHelpMan gethdkeys()
{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::STR, "desc", "Descriptor string public representation"},
{RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
}},
}},
@@ -707,7 +707,7 @@ RPCHelpMan gethdkeys()
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);
+ bool ok = desc_spkm->GetDescriptorString(desc_str, /*priv=*/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) {
@@ -731,7 +731,7 @@ RPCHelpMan gethdkeys()
UniValue xpub_info(UniValue::VOBJ);
xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
xpub_info.pushKV("has_private", has_xprv);
- if (priv) {
+ if (priv && has_xprv) {
xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
}
xpub_info.pushKV("descriptors", std::move(descriptors));
Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.