[wallet] Allow descriptor imports with importmulti
This commit is contained in:
parent
d2b381cc91
commit
9f48053d8f
1 changed files with 111 additions and 7 deletions
|
@ -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;
|
||||
|
||||
warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, 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…
Reference in a new issue