@@ -817,6 +817,114 @@ static RPCHelpMan migratewallet()
817817 };
818818}
819819
820+ RPCHelpMan gethdkeys ()
821+ {
822+ return RPCHelpMan{
823+ " gethdkeys" ,
824+ " \n List 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
821929RPCHelpMan getaddressinfo ();
822930RPCHelpMan 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},
0 commit comments