diff options
author | MeshCollider <dobsonsa68@gmail.com> | 2018-12-13 13:55:48 +1300 |
---|---|---|
committer | MeshCollider <dobsonsa68@gmail.com> | 2019-02-05 19:42:04 +1300 |
commit | 9f48053d8f9a1feacc96d7e2a00c8a3a67576948 (patch) | |
tree | c0a7ae7139a1e7c61dd6733046a7911ff312eda8 /src/wallet | |
parent | d2b381cc91b2c4e74abe11e5bd66af647b70dafb (diff) |
[wallet] Allow descriptor imports with importmulti
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/rpcdump.cpp | 118 |
1 files changed, 111 insertions, 7 deletions
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index f91d545a3b..9ed2a16416 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -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"}, |