@@ -817,6 +817,114 @@ static RPCHelpMan migratewallet()
817
817
};
818
818
}
819
819
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
+
820
928
// addresses
821
929
RPCHelpMan getaddressinfo ();
822
930
RPCHelpMan getnewaddress ();
@@ -907,6 +1015,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
907
1015
{" wallet" , &getaddressesbylabel},
908
1016
{" wallet" , &getaddressinfo},
909
1017
{" wallet" , &getbalance},
1018
+ {" wallet" , &gethdkeys},
910
1019
{" wallet" , &getnewaddress},
911
1020
{" wallet" , &getrawchangeaddress},
912
1021
{" wallet" , &getreceivedbyaddress},
0 commit comments