99#include < merkleblock.h>
1010#include < rpc/server.h>
1111#include < rpc/util.h>
12+ #include < script/descriptor.h>
1213#include < script/script.h>
1314#include < script/standard.h>
1415#include < sync.h>
@@ -984,11 +985,14 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
984985 const bool internal = data.exists (" internal" ) ? data[" internal" ].get_bool () : false ;
985986 const bool watchOnly = data.exists (" watchonly" ) ? data[" watchonly" ].get_bool () : false ;
986987
988+ if (data.exists (" range" )) {
989+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Range should not be specified for a non-descriptor import" );
990+ }
991+
987992 // Generate the script and destination for the scriptPubKey provided
988993 CScript script;
989- CTxDestination dest;
990994 if (!isScript) {
991- dest = DecodeDestination (output);
995+ CTxDestination dest = DecodeDestination (output);
992996 if (!IsValidDestination (dest)) {
993997 throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Invalid address \" " + output + " \" " );
994998 }
@@ -999,6 +1003,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
9991003 }
10001004 std::vector<unsigned char > vData (ParseHex (output));
10011005 script = CScript (vData.begin (), vData.end ());
1006+ CTxDestination dest;
10021007 if (!ExtractDestination (script, dest) && !internal) {
10031008 throw JSONRPCError (RPC_INVALID_PARAMETER, " Internal must be set to true for nonstandard scriptPubKey imports." );
10041009 }
@@ -1103,6 +1108,91 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
11031108 return warnings;
11041109}
11051110
1111+ static UniValue ProcessImportDescriptor (ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool & have_solving_data, const UniValue& data)
1112+ {
1113+ UniValue warnings (UniValue::VARR);
1114+
1115+ const std::string& descriptor = data[" desc" ].get_str ();
1116+ FlatSigningProvider keys;
1117+ auto parsed_desc = Parse (descriptor, keys);
1118+ if (!parsed_desc) {
1119+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Descriptor is invalid" );
1120+ }
1121+
1122+ have_solving_data = parsed_desc->IsSolvable ();
1123+ const bool watch_only = data.exists (" watchonly" ) ? data[" watchonly" ].get_bool () : false ;
1124+
1125+ int64_t range_start = 0 , range_end = 0 ;
1126+ if (!parsed_desc->IsRange () && data.exists (" range" )) {
1127+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Range should not be specified for an un-ranged descriptor" );
1128+ } else if (parsed_desc->IsRange ()) {
1129+ if (!data.exists (" range" )) {
1130+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Descriptor is ranged, please specify the range" );
1131+ }
1132+ const UniValue& range = data[" range" ];
1133+ range_start = range.exists (" start" ) ? range[" start" ].get_int64 () : 0 ;
1134+ if (!range.exists (" end" )) {
1135+ throw JSONRPCError (RPC_INVALID_PARAMETER, " End of range for descriptor must be specified" );
1136+ }
1137+ range_end = range[" end" ].get_int64 ();
1138+ if (range_end < range_start || range_start < 0 ) {
1139+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid descriptor range specified" );
1140+ }
1141+ }
1142+
1143+ const UniValue& priv_keys = data.exists (" keys" ) ? data[" keys" ].get_array () : UniValue ();
1144+
1145+ FlatSigningProvider out_keys;
1146+
1147+ // Expand all descriptors to get public keys and scripts.
1148+ // TODO: get private keys from descriptors too
1149+ for (int i = range_start; i <= range_end; ++i) {
1150+ std::vector<CScript> scripts_temp;
1151+ parsed_desc->Expand (i, keys, scripts_temp, out_keys);
1152+ std::copy (scripts_temp.begin (), scripts_temp.end (), std::inserter (script_pub_keys, script_pub_keys.end ()));
1153+ }
1154+
1155+ for (const auto & x : out_keys.scripts ) {
1156+ import_data.import_scripts .emplace (x.second );
1157+ }
1158+
1159+ std::copy (out_keys.pubkeys .begin (), out_keys.pubkeys .end (), std::inserter (pubkey_map, pubkey_map.end ()));
1160+
1161+ for (size_t i = 0 ; i < priv_keys.size (); ++i) {
1162+ const auto & str = priv_keys[i].get_str ();
1163+ CKey key = DecodeSecret (str);
1164+ if (!key.IsValid ()) {
1165+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Invalid private key encoding" );
1166+ }
1167+ CPubKey pubkey = key.GetPubKey ();
1168+ CKeyID id = pubkey.GetID ();
1169+
1170+ // Check if this private key corresponds to a public key from the descriptor
1171+ if (!pubkey_map.count (id)) {
1172+ warnings.push_back (" Ignoring irrelevant private key." );
1173+ } else {
1174+ privkey_map.emplace (id, key);
1175+ }
1176+ }
1177+
1178+ // Check if all the public keys have corresponding private keys in the import for spendability.
1179+ // This does not take into account threshold multisigs which could be spendable without all keys.
1180+ // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are,
1181+ // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check.
1182+ bool spendable = std::all_of (pubkey_map.begin (), pubkey_map.end (),
1183+ [&](const std::pair<CKeyID, CPubKey>& used_key) {
1184+ return privkey_map.count (used_key.first ) > 0 ;
1185+ });
1186+ if (!watch_only && !spendable) {
1187+ warnings.push_back (" Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag." );
1188+ }
1189+ if (watch_only && spendable) {
1190+ warnings.push_back (" All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag." );
1191+ }
1192+
1193+ return warnings;
1194+ }
1195+
11061196static UniValue ProcessImport (CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
11071197{
11081198 UniValue warnings (UniValue::VARR);
@@ -1122,7 +1212,15 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
11221212 std::set<CScript> script_pub_keys;
11231213 bool have_solving_data;
11241214
1125- warnings = ProcessImportLegacy (import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
1215+ if (data.exists (" scriptPubKey" ) && data.exists (" desc" )) {
1216+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Both a descriptor and a scriptPubKey should not be provided." );
1217+ } else if (data.exists (" scriptPubKey" )) {
1218+ warnings = ProcessImportLegacy (import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
1219+ } else if (data.exists (" desc" )) {
1220+ warnings = ProcessImportDescriptor (import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
1221+ } else {
1222+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Either a descriptor or scriptPubKey must be provided." );
1223+ }
11261224
11271225 // If private keys are disabled, abort if private keys are being imported
11281226 if (pwallet->IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty ()) {
@@ -1132,7 +1230,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
11321230 // Check whether we have any work to do
11331231 for (const CScript& script : script_pub_keys) {
11341232 if (::IsMine (*pwallet, script) & ISMINE_SPENDABLE) {
1135- throw JSONRPCError (RPC_WALLET_ERROR, " The wallet already contains the private key for this address or script" );
1233+ throw JSONRPCError (RPC_WALLET_ERROR, " The wallet already contains the private key for this address or script ( \" " + HexStr (script. begin (), script. end ()) + " \" ) " );
11361234 }
11371235 }
11381236
@@ -1172,8 +1270,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
11721270 }
11731271 CTxDestination dest;
11741272 ExtractDestination (script, dest);
1175- if (!internal) {
1176- assert (IsValidDestination (dest));
1273+ if (!internal && IsValidDestination (dest)) {
11771274 pwallet->SetAddressBook (dest, label, " receive" );
11781275 }
11791276 }
@@ -1226,7 +1323,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
12261323 {
12271324 {" " , RPCArg::Type::OBJ, /* opt */ false , /* default_val */ " " , " " ,
12281325 {
1229- {" scriptPubKey" , RPCArg::Type::STR, /* opt */ false , /* default_val */ " " , " Type of scriptPubKey (string for script, json for address)" ,
1326+ {" desc" , RPCArg::Type::STR, /* opt */ true , /* default_val */ " " , " Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys" },
1327+ {" scriptPubKey" , RPCArg::Type::STR, /* opt */ false , /* default_val */ " " , " Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor" ,
12301328 /* oneline_description */ " " , {" \" <script>\" | { \" address\" :\" <address>\" }" , " string / json" }
12311329 },
12321330 {" timestamp" , RPCArg::Type::NUM, /* opt */ false , /* default_val */ " " , " Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n "
@@ -1249,6 +1347,12 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
12491347 {" key" , RPCArg::Type::STR, /* opt */ false , /* default_val */ " " , " " },
12501348 }
12511349 },
1350+ {" range" , RPCArg::Type::OBJ, /* opt */ true , /* default_val */ " " , " If a ranged descriptor is used, this specifies the start and end of the range to import" ,
1351+ {
1352+ {" start" , RPCArg::Type::NUM, /* opt */ true , /* default_val */ " 0" , " Start of the range to import" },
1353+ {" end" , RPCArg::Type::NUM, /* opt */ false , /* default_val */ " " , " End of the range to import (inclusive)" },
1354+ }
1355+ },
12521356 {" internal" , RPCArg::Type::BOOL, /* opt */ true , /* default_val */ " false" , " Stating whether matching outputs should be treated as not incoming payments (also known as change)" },
12531357 {" watchonly" , RPCArg::Type::BOOL, /* opt */ true , /* default_val */ " false" , " Stating whether matching outputs should be considered watchonly." },
12541358 {" label" , RPCArg::Type::STR, /* opt */ true , /* default_val */ " ''" , " Label to assign to the address, only allowed with internal=false" },
0 commit comments