[wallet] Allow descriptor imports with importmulti

pull/643/head
MeshCollider 6 years ago
parent d2b381cc91
commit 9f48053d8f

@ -9,6 +9,7 @@
#include <merkleblock.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/standard.h>
#include <sync.h>
@ -984,11 +985,14 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
if (data.exists("range")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import");
}
// Generate the script and destination for the scriptPubKey provided
CScript script;
CTxDestination dest;
if (!isScript) {
dest = DecodeDestination(output);
CTxDestination dest = DecodeDestination(output);
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
}
@ -999,6 +1003,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
}
std::vector<unsigned char> vData(ParseHex(output));
script = CScript(vData.begin(), vData.end());
CTxDestination dest;
if (!ExtractDestination(script, dest) && !internal) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports.");
}
@ -1103,6 +1108,91 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
return warnings;
}
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)
{
UniValue warnings(UniValue::VARR);
const std::string& descriptor = data["desc"].get_str();
FlatSigningProvider keys;
auto parsed_desc = Parse(descriptor, keys);
if (!parsed_desc) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid");
}
have_solving_data = parsed_desc->IsSolvable();
const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
int64_t range_start = 0, range_end = 0;
if (!parsed_desc->IsRange() && data.exists("range")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
} else if (parsed_desc->IsRange()) {
if (!data.exists("range")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range");
}
const UniValue& range = data["range"];
range_start = range.exists("start") ? range["start"].get_int64() : 0;
if (!range.exists("end")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "End of range for descriptor must be specified");
}
range_end = range["end"].get_int64();
if (range_end < range_start || range_start < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid descriptor range specified");
}
}
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
FlatSigningProvider out_keys;
// Expand all descriptors to get public keys and scripts.
// TODO: get private keys from descriptors too
for (int i = range_start; i <= range_end; ++i) {
std::vector<CScript> scripts_temp;
parsed_desc->Expand(i, keys, scripts_temp, out_keys);
std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
}
for (const auto& x : out_keys.scripts) {
import_data.import_scripts.emplace(x.second);
}
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
for (size_t i = 0; i < priv_keys.size(); ++i) {
const auto& str = priv_keys[i].get_str();
CKey key = DecodeSecret(str);
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
}
CPubKey pubkey = key.GetPubKey();
CKeyID id = pubkey.GetID();
// Check if this private key corresponds to a public key from the descriptor
if (!pubkey_map.count(id)) {
warnings.push_back("Ignoring irrelevant private key.");
} else {
privkey_map.emplace(id, key);
}
}
// Check if all the public keys have corresponding private keys in the import for spendability.
// This does not take into account threshold multisigs which could be spendable without all keys.
// Thus, threshold multisigs without all keys will be considered not spendable here, even if they are,
// perhaps triggering a false warning message. This is consistent with the current wallet IsMine check.
bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(),
[&](const std::pair<CKeyID, CPubKey>& used_key) {
return privkey_map.count(used_key.first) > 0;
});
if (!watch_only && !spendable) {
warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
}
if (watch_only && spendable) {
warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
}
return warnings;
}
static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
UniValue warnings(UniValue::VARR);
@ -1122,7 +1212,15 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
std::set<CScript> script_pub_keys;
bool have_solving_data;
if (data.exists("scriptPubKey") && data.exists("desc")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
} else if (data.exists("scriptPubKey")) {
warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
} else if (data.exists("desc")) {
warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
}
// If private keys are disabled, abort if private keys are being imported
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
// Check whether we have any work to do
for (const CScript& script : script_pub_keys) {
if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script.begin(), script.end()) + "\")");
}
}
@ -1172,8 +1270,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
}
CTxDestination dest;
ExtractDestination(script, dest);
if (!internal) {
assert(IsValidDestination(dest));
if (!internal && IsValidDestination(dest)) {
pwallet->SetAddressBook(dest, label, "receive");
}
}
@ -1226,7 +1323,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
{
{"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "",
{
{"scriptPubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Type of scriptPubKey (string for script, json for address)",
{"desc", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"},
{"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",
/* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
},
{"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)
{"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""},
}
},
{"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",
{
{"start", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Start of the range to import"},
{"end", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "End of the range to import (inclusive)"},
}
},
{"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"},
{"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watchonly."},
{"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"},

Loading…
Cancel
Save