Skip to content

Commit 9f48053

Browse files
committed
[wallet] Allow descriptor imports with importmulti
1 parent d2b381c commit 9f48053

File tree

1 file changed

+111
-7
lines changed

1 file changed

+111
-7
lines changed

src/wallet/rpcdump.cpp

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
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+
11061196
static 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

Comments
 (0)