diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1139,13 +1139,184 @@ } } -static UniValue ProcessImportLegacy(ImportData &import_data, +static UniValue ProcessImportLegacy(CWallet *const pwallet, + ImportData &import_data, std::map &pubkey_map, std::map &privkey_map, std::set &script_pub_keys, bool &have_solving_data, const UniValue &data) { UniValue warnings(UniValue::VARR); + + // First ensure scriptPubKey has either a script or JSON with "address" + // string + const UniValue &scriptPubKey = data["scriptPubKey"]; + bool isScript = scriptPubKey.getType() == UniValue::VSTR; + if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && + scriptPubKey.exists("address"))) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "scriptPubKey must be string with script or JSON " + "with address string"); + } + const std::string &output = + isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); + + // Optional fields. + const std::string &strRedeemScript = + data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; + const UniValue &pubKeys = + data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); + const UniValue &keys = + data.exists("keys") ? data["keys"].get_array() : UniValue(); + const bool internal = + data.exists("internal") ? data["internal"].get_bool() : false; + const bool watchOnly = + data.exists("watchonly") ? data["watchonly"].get_bool() : false; + + // Generate the script and destination for the scriptPubKey provided + CScript script; + CTxDestination dest; + if (!isScript) { + dest = DecodeDestination(output, pwallet->chainParams); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid address \"" + output + "\""); + } + script = GetScriptForDestination(dest); + } else { + if (!IsHex(output)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid scriptPubKey \"" + output + "\""); + } + std::vector vData(ParseHex(output)); + script = CScript(vData.begin(), vData.end()); + if (!ExtractDestination(script, dest) && !internal) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Internal must be set to true for " + "nonstandard scriptPubKey imports."); + } + } + script_pub_keys.emplace(script); + + // Parse all arguments + if (strRedeemScript.size()) { + if (!IsHex(strRedeemScript)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid redeem script \"" + strRedeemScript + + "\": must be hex string"); + } + auto parsed_redeemscript = ParseHex(strRedeemScript); + import_data.redeemscript = std::make_unique( + parsed_redeemscript.begin(), parsed_redeemscript.end()); + } + for (size_t i = 0; i < pubKeys.size(); ++i) { + const auto &str = pubKeys[i].get_str(); + if (!IsHex(str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Pubkey \"" + str + "\" must be a hex string"); + } + auto parsed_pubkey = ParseHex(str); + CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Pubkey \"" + str + + "\" is not a valid public key"); + } + pubkey_map.emplace(pubkey.GetID(), pubkey); + } + for (size_t i = 0; i < keys.size(); ++i) { + const auto &str = 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(); + if (pubkey_map.count(id)) { + pubkey_map.erase(id); + } + privkey_map.emplace(id, key); + } + + // Verify and process input data + have_solving_data = + import_data.redeemscript || pubkey_map.size() || privkey_map.size(); + if (have_solving_data) { + // Match up data in import_data with the scriptPubKey in script. + auto error = RecurseImportData(script, import_data, ScriptContext::TOP); + + // Verify whether the watchonly option corresponds to the + // availability of private keys. + bool spendable = std::all_of( + import_data.used_keys.begin(), import_data.used_keys.end(), + [&](const std::pair &used_key) { + return privkey_map.count(used_key.first) > 0; + }); + if (!watchOnly && !spendable) { + warnings.push_back("Some private keys are missing, outputs " + "will be considered watchonly. If this is " + "intentional, specify the watchonly flag."); + } + if (watchOnly && spendable) { + warnings.push_back( + "All private keys are provided, outputs will be considered " + "spendable. If this is intentional, do not specify the " + "watchonly flag."); + } + + // Check that all required keys for solvability are provided. + if (error.empty()) { + for (const auto &require_key : import_data.used_keys) { + if (!require_key.second) { + // Not a required key + continue; + } + + if (pubkey_map.count(require_key.first) == 0 && + privkey_map.count(require_key.first) == 0) { + error = "some required keys are missing"; + } + } + } + + if (!error.empty()) { + warnings.push_back("Importing as non-solvable: " + error + + ". If this is intentional, don't provide " + "any keys, pubkeys or redeemscript."); + import_data = ImportData(); + pubkey_map.clear(); + privkey_map.clear(); + have_solving_data = false; + } else { + // RecurseImportData() removes any relevant redeemscript from + // import_data, so we can use that to discover if a superfluous + // one was provided. + if (import_data.redeemscript) { + warnings.push_back( + "Ignoring redeemscript as this is not a P2SH script."); + } + for (auto it = privkey_map.begin(); it != privkey_map.end();) { + auto oldit = it++; + if (import_data.used_keys.count(oldit->first) == 0) { + warnings.push_back("Ignoring irrelevant private key."); + privkey_map.erase(oldit); + } + } + for (auto it = pubkey_map.begin(); it != pubkey_map.end();) { + auto oldit = it++; + auto key_data_it = import_data.used_keys.find(oldit->first); + if (key_data_it == import_data.used_keys.end() || + !key_data_it->second) { + warnings.push_back("Ignoring public key \"" + + HexStr(oldit->first) + + "\" as it doesn't appear inside P2PKH."); + pubkey_map.erase(oldit); + } + } + } + } + return warnings; } @@ -1173,189 +1344,18 @@ bool have_solving_data; warnings = - ProcessImportLegacy(import_data, pubkey_map, privkey_map, + ProcessImportLegacy(pwallet, import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data); - // First ensure scriptPubKey has either a script or JSON with "address" - // string - const UniValue &scriptPubKey = data["scriptPubKey"]; - bool isScript = scriptPubKey.getType() == UniValue::VSTR; - if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && - scriptPubKey.exists("address"))) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "scriptPubKey must be string with script or " - "JSON with address string"); - } - const std::string &output = isScript - ? scriptPubKey.get_str() - : scriptPubKey["address"].get_str(); - - // Optional fields. - const std::string &strRedeemScript = - data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; - const UniValue &pubKeys = - data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); - const UniValue &keys = - data.exists("keys") ? data["keys"].get_array() : UniValue(); - const bool watchOnly = - data.exists("watchonly") ? data["watchonly"].get_bool() : false; - // If private keys are disabled, abort if private keys are being // imported if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && - !keys.isNull()) { + !privkey_map.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with " "private keys disabled"); } - // Generate the script and destination for the scriptPubKey provided - CScript script; - CTxDestination dest; - if (!isScript) { - dest = DecodeDestination(output, pwallet->chainParams); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid address \"" + output + "\""); - } - script = GetScriptForDestination(dest); - } else { - if (!IsHex(output)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid scriptPubKey \"" + output + "\""); - } - std::vector vData(ParseHex(output)); - script = CScript(vData.begin(), vData.end()); - if (!ExtractDestination(script, dest) && !internal) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "Internal must be set to true for " - "nonstandard scriptPubKey imports."); - } - } - script_pub_keys.emplace(script); - - // Parse all arguments - if (strRedeemScript.size()) { - if (!IsHex(strRedeemScript)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid redeem script \"" + - strRedeemScript + - "\": must be hex string"); - } - auto parsed_redeemscript = ParseHex(strRedeemScript); - import_data.redeemscript = std::make_unique( - parsed_redeemscript.begin(), parsed_redeemscript.end()); - } - for (size_t i = 0; i < pubKeys.size(); ++i) { - const auto &str = pubKeys[i].get_str(); - if (!IsHex(str)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Pubkey \"" + str + - "\" must be a hex string"); - } - auto parsed_pubkey = ParseHex(str); - CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); - if (!pubkey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Pubkey \"" + str + - "\" is not a valid public key"); - } - pubkey_map.emplace(pubkey.GetID(), pubkey); - } - for (size_t i = 0; i < keys.size(); ++i) { - const auto &str = 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(); - if (pubkey_map.count(id)) { - pubkey_map.erase(id); - } - privkey_map.emplace(id, key); - } - - // Verify and process input data - have_solving_data = - import_data.redeemscript || pubkey_map.size() || privkey_map.size(); - if (have_solving_data) { - // Match up data in import_data with the scriptPubKey in script. - auto error = - RecurseImportData(script, import_data, ScriptContext::TOP); - - // Verify whether the watchonly option corresponds to the - // availability of private keys. - bool spendable = std::all_of( - import_data.used_keys.begin(), import_data.used_keys.end(), - [&](const std::pair &used_key) { - return privkey_map.count(used_key.first) > 0; - }); - if (!watchOnly && !spendable) { - warnings.push_back("Some private keys are missing, outputs " - "will be considered watchonly. If this is " - "intentional, specify the watchonly flag."); - } - if (watchOnly && spendable) { - warnings.push_back( - "All private keys are provided, outputs will be considered " - "spendable. If this is intentional, do not specify the " - "watchonly flag."); - } - - // Check that all required keys for solvability are provided. - if (error.empty()) { - for (const auto &require_key : import_data.used_keys) { - if (!require_key.second) { - // Not a required key - continue; - } - - if (pubkey_map.count(require_key.first) == 0 && - privkey_map.count(require_key.first) == 0) { - error = "some required keys are missing"; - } - } - } - - if (!error.empty()) { - warnings.push_back("Importing as non-solvable: " + error + - ". If this is intentional, don't provide " - "any keys, pubkeys or redeemscript."); - import_data = ImportData(); - pubkey_map.clear(); - privkey_map.clear(); - have_solving_data = false; - } else { - // RecurseImportData() removes any relevant redeemscript from - // import_data, so we can use that to discover if a superfluous - // one was provided. - if (import_data.redeemscript) { - warnings.push_back( - "Ignoring redeemscript as this is not a P2SH script."); - } - for (auto it = privkey_map.begin(); it != privkey_map.end();) { - auto oldit = it++; - if (import_data.used_keys.count(oldit->first) == 0) { - warnings.push_back("Ignoring irrelevant private key."); - privkey_map.erase(oldit); - } - } - for (auto it = pubkey_map.begin(); it != pubkey_map.end();) { - auto oldit = it++; - auto key_data_it = import_data.used_keys.find(oldit->first); - if (key_data_it == import_data.used_keys.end() || - !key_data_it->second) { - warnings.push_back( - "Ignoring public key \"" + HexStr(oldit->first) + - "\" as it doesn't appear inside P2PKH."); - pubkey_map.erase(oldit); - } - } - } - } - // Check whether we have any work to do for (const CScript &s : script_pub_keys) { if (::IsMine(*pwallet, s) & ISMINE_SPENDABLE) {