Merge #14454: Add SegWit support to importmulti
c11875c590
Add segwit address tests for importmulti (MeshCollider)201451b1ca
Make getaddressinfo return solvability (MeshCollider)1753d217ea
Add release notes for importmulti segwit change (MeshCollider)353c064596
Fix typo in test_framework/blocktools (MeshCollider)f6ed748cf0
Add SegWit support to importmulti with some ProcessImport cleanup (MeshCollider) Pull request description: Add support for segwit to importmulti, supports P2WSH, P2WPKH, P2SH-P2WPKH, P2SH-P2WSH. Adds a new `witnessscript` parameter which must be used for the witness scripts in the relevant situations. Also includes some tests for the various import types. ~Also makes the change in #14019 redundant, but cherry-picks the test from that PR to test the behavior (@achow101).~ Fixes #12253, also addresses the second point in #12703, and fixes #14407 Tree-SHA512: 775a755c524d1c387a99acddd772f677d2073876b72403dcfb92c59f9b405ae13ceedcf4dbd2ee1d7a8db91c494f67ca137161032ee3a2071282eeb411be090a
This commit is contained in:
commit
b312579c69
5 changed files with 309 additions and 174 deletions
6
doc/release-notes-14454.md
Normal file
6
doc/release-notes-14454.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Low-level RPC changes
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The `importmulti` RPC has been updated to support P2WSH, P2WPKH, P2SH-P2WPKH,
|
||||||
|
P2SH-P2WSH. Each request now accepts an additional `witnessscript` to be used
|
||||||
|
for P2WSH or P2SH-P2WSH.
|
|
@ -808,29 +808,24 @@ UniValue dumpwallet(const JSONRPCRequest& request)
|
||||||
static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
|
static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
bool success = false;
|
// First ensure scriptPubKey has either a script or JSON with "address" string
|
||||||
|
|
||||||
// Required fields.
|
|
||||||
const UniValue& scriptPubKey = data["scriptPubKey"];
|
const UniValue& scriptPubKey = data["scriptPubKey"];
|
||||||
|
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
|
||||||
// Should have script or JSON with "address".
|
if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
|
||||||
if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) {
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey");
|
|
||||||
}
|
}
|
||||||
|
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
|
||||||
|
|
||||||
// Optional fields.
|
// Optional fields.
|
||||||
const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
|
const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
|
||||||
|
const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : "";
|
||||||
const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
|
const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
|
||||||
const UniValue& keys = data.exists("keys") ? data["keys"].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 internal = data.exists("internal") ? data["internal"].get_bool() : false;
|
||||||
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
|
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
|
||||||
const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : "";
|
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
|
||||||
|
|
||||||
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
|
// Generate the script and destination for the scriptPubKey provided
|
||||||
bool isP2SH = strRedeemScript.length() > 0;
|
|
||||||
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
|
|
||||||
|
|
||||||
// Parse the output.
|
|
||||||
CScript script;
|
CScript script;
|
||||||
CTxDestination dest;
|
CTxDestination dest;
|
||||||
|
|
||||||
|
@ -854,35 +849,38 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
|
||||||
|
|
||||||
// Watchonly and private keys
|
// Watchonly and private keys
|
||||||
if (watchOnly && keys.size()) {
|
if (watchOnly && keys.size()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Watch-only addresses should not include private keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal + Label
|
// Internal addresses should not have a label
|
||||||
if (internal && data.exists("label")) {
|
if (internal && data.exists("label")) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys / PubKeys size check.
|
// Force users to provide the witness script in its field rather than redeemscript
|
||||||
if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey
|
if (!strRedeemScript.empty() && script.IsPayToWitnessScriptHash()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WSH addresses have an empty redeemscript. Please provide the witnessscript instead.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid P2SH redeemScript
|
CScript scriptpubkey_script = script;
|
||||||
if (isP2SH && !IsHex(strRedeemScript)) {
|
CTxDestination scriptpubkey_dest = dest;
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script");
|
bool allow_p2wpkh = true;
|
||||||
}
|
|
||||||
|
|
||||||
// Process. //
|
|
||||||
|
|
||||||
// P2SH
|
// P2SH
|
||||||
if (isP2SH) {
|
if (!strRedeemScript.empty() && script.IsPayToScriptHash()) {
|
||||||
|
// Check the redeemScript is valid
|
||||||
|
if (!IsHex(strRedeemScript)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script: must be hex string");
|
||||||
|
}
|
||||||
|
|
||||||
// Import redeem script.
|
// Import redeem script.
|
||||||
std::vector<unsigned char> vData(ParseHex(strRedeemScript));
|
std::vector<unsigned char> vData(ParseHex(strRedeemScript));
|
||||||
CScript redeemScript = CScript(vData.begin(), vData.end());
|
CScript redeemScript = CScript(vData.begin(), vData.end());
|
||||||
|
CScriptID redeem_id(redeemScript);
|
||||||
|
|
||||||
// Invalid P2SH address
|
// Check that the redeemScript and scriptPubKey match
|
||||||
if (!script.IsPayToScriptHash()) {
|
if (GetScriptForDestination(redeem_id) != script) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The redeemScript does not match the scriptPubKey");
|
||||||
}
|
}
|
||||||
|
|
||||||
pwallet->MarkDirty();
|
pwallet->MarkDirty();
|
||||||
|
@ -891,103 +889,83 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
CScriptID redeem_id(redeemScript);
|
|
||||||
if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
|
if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
CScript redeemDestination = GetScriptForDestination(redeem_id);
|
// Now set script to the redeemScript so we parse the inner script as P2WSH or P2WPKH below
|
||||||
|
script = redeemScript;
|
||||||
|
ExtractDestination(script, dest);
|
||||||
|
}
|
||||||
|
|
||||||
if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) {
|
// (P2SH-)P2WSH
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
|
if (!witness_script_hex.empty() && script.IsPayToWitnessScriptHash()) {
|
||||||
|
if (!IsHex(witness_script_hex)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script: must be hex string");
|
||||||
}
|
}
|
||||||
|
|
||||||
pwallet->MarkDirty();
|
// Generate the scripts
|
||||||
|
std::vector<unsigned char> witness_script_parsed(ParseHex(witness_script_hex));
|
||||||
|
CScript witness_script = CScript(witness_script_parsed.begin(), witness_script_parsed.end());
|
||||||
|
CScriptID witness_id(witness_script);
|
||||||
|
|
||||||
if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) {
|
// Check that the witnessScript and scriptPubKey match
|
||||||
|
if (GetScriptForDestination(WitnessV0ScriptHash(witness_script)) != script) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The witnessScript does not match the scriptPubKey or redeemScript");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the witness script as watch only only if it is not for P2SH-P2WSH
|
||||||
|
if (!scriptpubkey_script.IsPayToScriptHash() && !pwallet->AddWatchOnly(witness_script, timestamp)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to address book or update label
|
if (!pwallet->HaveCScript(witness_id) && !pwallet->AddCScript(witness_script)) {
|
||||||
if (IsValidDestination(dest)) {
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2wsh witnessScript to wallet");
|
||||||
pwallet->SetAddressBook(dest, label, "receive");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import private keys.
|
// Now set script to the witnessScript so we parse the inner script as P2PK or P2PKH below
|
||||||
|
script = witness_script;
|
||||||
|
ExtractDestination(script, dest);
|
||||||
|
allow_p2wpkh = false; // P2WPKH cannot be embedded in P2WSH
|
||||||
|
}
|
||||||
|
|
||||||
|
// (P2SH-)P2PK/P2PKH/P2WPKH
|
||||||
|
if (dest.type() == typeid(CKeyID) || dest.type() == typeid(WitnessV0KeyHash)) {
|
||||||
|
if (!allow_p2wpkh && dest.type() == typeid(WitnessV0KeyHash)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WPKH cannot be embedded in P2WSH");
|
||||||
|
}
|
||||||
|
if (keys.size() > 1 || pubKeys.size() > 1) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than one key given for one single-key address");
|
||||||
|
}
|
||||||
|
CPubKey pubkey;
|
||||||
if (keys.size()) {
|
if (keys.size()) {
|
||||||
for (size_t i = 0; i < keys.size(); i++) {
|
pubkey = DecodeSecret(keys[0].get_str()).GetPubKey();
|
||||||
const std::string& privkey = keys[i].get_str();
|
|
||||||
|
|
||||||
CKey key = DecodeSecret(privkey);
|
|
||||||
|
|
||||||
if (!key.IsValid()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
|
|
||||||
}
|
|
||||||
|
|
||||||
CPubKey pubkey = key.GetPubKey();
|
|
||||||
assert(key.VerifyPubKey(pubkey));
|
|
||||||
|
|
||||||
CKeyID vchAddress = pubkey.GetID();
|
|
||||||
pwallet->MarkDirty();
|
|
||||||
pwallet->SetAddressBook(vchAddress, label, "receive");
|
|
||||||
|
|
||||||
if (pwallet->HaveKey(vchAddress)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
|
|
||||||
}
|
|
||||||
|
|
||||||
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
|
|
||||||
|
|
||||||
if (!pwallet->AddKeyPubKey(key, pubkey)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
|
||||||
}
|
|
||||||
|
|
||||||
pwallet->UpdateTimeFirstKey(timestamp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (pubKeys.size()) {
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
// Import public keys.
|
|
||||||
if (pubKeys.size() && keys.size() == 0) {
|
|
||||||
const std::string& strPubKey = pubKeys[0].get_str();
|
const std::string& strPubKey = pubKeys[0].get_str();
|
||||||
|
|
||||||
if (!IsHex(strPubKey)) {
|
if (!IsHex(strPubKey)) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
||||||
}
|
}
|
||||||
|
std::vector<unsigned char> vData(ParseHex(pubKeys[0].get_str()));
|
||||||
std::vector<unsigned char> vData(ParseHex(strPubKey));
|
CPubKey pubkey_temp(vData.begin(), vData.end());
|
||||||
CPubKey pubKey(vData.begin(), vData.end());
|
if (pubkey.size() && pubkey_temp != pubkey) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key does not match public key for address");
|
||||||
if (!pubKey.IsFullyValid()) {
|
}
|
||||||
|
pubkey = pubkey_temp;
|
||||||
|
}
|
||||||
|
if (pubkey.size() > 0) {
|
||||||
|
if (!pubkey.IsFullyValid()) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
|
||||||
}
|
}
|
||||||
|
|
||||||
CTxDestination pubkey_dest = pubKey.GetID();
|
// Check the key corresponds to the destination given
|
||||||
|
std::vector<CTxDestination> destinations = GetAllDestinationsForKey(pubkey);
|
||||||
// Consistency check.
|
if (std::find(destinations.begin(), destinations.end(), dest) == destinations.end()) {
|
||||||
if (!(pubkey_dest == dest)) {
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Key does not match address destination");
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CScript pubKeyScript = GetScriptForDestination(pubkey_dest);
|
// This is necessary to force the wallet to import the pubKey
|
||||||
|
CScript scriptRawPubKey = GetScriptForRawPubKey(pubkey);
|
||||||
if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
|
|
||||||
}
|
|
||||||
|
|
||||||
pwallet->MarkDirty();
|
|
||||||
|
|
||||||
if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
|
||||||
}
|
|
||||||
|
|
||||||
// add to address book or update label
|
|
||||||
if (IsValidDestination(pubkey_dest)) {
|
|
||||||
pwallet->SetAddressBook(pubkey_dest, label, "receive");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Is this necessary?
|
|
||||||
CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
|
|
||||||
|
|
||||||
if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
|
if (::IsMine(*pwallet, scriptRawPubKey) == 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");
|
||||||
|
@ -998,73 +976,61 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
|
||||||
if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) {
|
if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import private keys.
|
|
||||||
if (keys.size()) {
|
|
||||||
const std::string& strPrivkey = keys[0].get_str();
|
|
||||||
|
|
||||||
// Checks.
|
|
||||||
CKey key = DecodeSecret(strPrivkey);
|
|
||||||
|
|
||||||
if (!key.IsValid()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
|
|
||||||
}
|
|
||||||
|
|
||||||
CPubKey pubKey = key.GetPubKey();
|
|
||||||
assert(key.VerifyPubKey(pubKey));
|
|
||||||
|
|
||||||
CTxDestination pubkey_dest = pubKey.GetID();
|
|
||||||
|
|
||||||
// Consistency check.
|
|
||||||
if (!(pubkey_dest == dest)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
CKeyID vchAddress = pubKey.GetID();
|
|
||||||
pwallet->MarkDirty();
|
|
||||||
pwallet->SetAddressBook(vchAddress, label, "receive");
|
|
||||||
|
|
||||||
if (pwallet->HaveKey(vchAddress)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
|
|
||||||
}
|
|
||||||
|
|
||||||
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
|
|
||||||
|
|
||||||
if (!pwallet->AddKeyPubKey(key, pubKey)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
|
||||||
}
|
|
||||||
|
|
||||||
pwallet->UpdateTimeFirstKey(timestamp);
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import scriptPubKey only.
|
|
||||||
if (pubKeys.size() == 0 && keys.size() == 0) {
|
|
||||||
if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
|
|
||||||
}
|
|
||||||
|
|
||||||
pwallet->MarkDirty();
|
|
||||||
|
|
||||||
if (!pwallet->AddWatchOnly(script, timestamp)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
|
||||||
}
|
|
||||||
|
|
||||||
// add to address book or update label
|
|
||||||
if (IsValidDestination(dest)) {
|
|
||||||
pwallet->SetAddressBook(dest, label, "receive");
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import the address
|
||||||
|
if (::IsMine(*pwallet, scriptpubkey_script) == ISMINE_SPENDABLE) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
|
||||||
|
}
|
||||||
|
|
||||||
|
pwallet->MarkDirty();
|
||||||
|
|
||||||
|
if (!pwallet->AddWatchOnly(scriptpubkey_script, timestamp)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!watchOnly && !pwallet->HaveCScript(CScriptID(scriptpubkey_script)) && !pwallet->AddCScript(scriptpubkey_script)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding scriptPubKey script to wallet");
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to address book or update label
|
||||||
|
if (IsValidDestination(scriptpubkey_dest)) {
|
||||||
|
pwallet->SetAddressBook(scriptpubkey_dest, label, "receive");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import private keys.
|
||||||
|
for (size_t i = 0; i < keys.size(); i++) {
|
||||||
|
const std::string& strPrivkey = keys[i].get_str();
|
||||||
|
|
||||||
|
// Checks.
|
||||||
|
CKey key = DecodeSecret(strPrivkey);
|
||||||
|
|
||||||
|
if (!key.IsValid()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
|
||||||
|
}
|
||||||
|
|
||||||
|
CPubKey pubKey = key.GetPubKey();
|
||||||
|
assert(key.VerifyPubKey(pubKey));
|
||||||
|
|
||||||
|
CKeyID vchAddress = pubKey.GetID();
|
||||||
|
pwallet->MarkDirty();
|
||||||
|
|
||||||
|
if (pwallet->HaveKey(vchAddress)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
|
||||||
|
}
|
||||||
|
|
||||||
|
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
|
||||||
|
|
||||||
|
if (!pwallet->AddKeyPubKey(key, pubKey)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
|
||||||
|
}
|
||||||
|
|
||||||
|
pwallet->UpdateTimeFirstKey(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
UniValue result = UniValue(UniValue::VOBJ);
|
UniValue result = UniValue(UniValue::VOBJ);
|
||||||
result.pushKV("success", UniValue(success));
|
result.pushKV("success", UniValue(true));
|
||||||
return result;
|
return result;
|
||||||
} catch (const UniValue& e) {
|
} catch (const UniValue& e) {
|
||||||
UniValue result = UniValue(UniValue::VOBJ);
|
UniValue result = UniValue(UniValue::VOBJ);
|
||||||
|
@ -1117,7 +1083,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
||||||
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
|
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
|
||||||
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
|
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
|
||||||
" creation time of all keys being imported by the importmulti call will be scanned.\n"
|
" creation time of all keys being imported by the importmulti call will be scanned.\n"
|
||||||
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
|
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey\n"
|
||||||
|
" \"witnessscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey\n"
|
||||||
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
|
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
|
||||||
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
|
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
|
||||||
" \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"
|
" \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"
|
||||||
|
|
|
@ -3455,6 +3455,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
||||||
" \"address\" : \"address\", (string) The bitcoin address validated\n"
|
" \"address\" : \"address\", (string) The bitcoin address validated\n"
|
||||||
" \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n"
|
" \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n"
|
||||||
" \"ismine\" : true|false, (boolean) If the address is yours or not\n"
|
" \"ismine\" : true|false, (boolean) If the address is yours or not\n"
|
||||||
|
" \"solvable\" : true|false, (boolean) If the address is solvable by the wallet\n"
|
||||||
" \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
|
" \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
|
||||||
" \"isscript\" : true|false, (boolean) If the key is a script\n"
|
" \"isscript\" : true|false, (boolean) If the key is a script\n"
|
||||||
" \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
|
" \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
|
||||||
|
@ -3509,6 +3510,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
||||||
isminetype mine = IsMine(*pwallet, dest);
|
isminetype mine = IsMine(*pwallet, dest);
|
||||||
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
|
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
|
||||||
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
|
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
|
||||||
|
ret.pushKV("solvable", IsSolvable(*pwallet, scriptPubKey));
|
||||||
UniValue detail = DescribeWalletAddress(pwallet, dest);
|
UniValue detail = DescribeWalletAddress(pwallet, dest);
|
||||||
ret.pushKVs(detail);
|
ret.pushKVs(detail);
|
||||||
if (pwallet->mapAddressBook.count(dest)) {
|
if (pwallet->mapAddressBook.count(dest)) {
|
||||||
|
|
|
@ -169,7 +169,7 @@ def get_legacy_sigopcount_tx(tx, accurate=True):
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def witness_script(use_p2wsh, pubkey):
|
def witness_script(use_p2wsh, pubkey):
|
||||||
"""Create a scriptPubKey for a pay-to-wtiness TxOut.
|
"""Create a scriptPubKey for a pay-to-witness TxOut.
|
||||||
|
|
||||||
This is either a P2WPKH output for the given pubkey, or a P2WSH output of a
|
This is either a P2WPKH output for the given pubkey, or a P2WSH output of a
|
||||||
1-of-1 multisig for the given pubkey. Returns the hex encoding of the
|
1-of-1 multisig for the given pubkey. Returns the hex encoding of the
|
||||||
|
|
|
@ -11,8 +11,14 @@ from test_framework.util import (
|
||||||
assert_greater_than,
|
assert_greater_than,
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
bytes_to_hex_str,
|
bytes_to_hex_str,
|
||||||
|
hex_str_to_bytes
|
||||||
)
|
)
|
||||||
|
from test_framework.script import (
|
||||||
|
CScript,
|
||||||
|
OP_0,
|
||||||
|
hash160
|
||||||
|
)
|
||||||
|
from test_framework.messages import sha256
|
||||||
|
|
||||||
class ImportMultiTest(BitcoinTestFramework):
|
class ImportMultiTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
|
@ -90,6 +96,19 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
assert_equal(address_assert['ismine'], False)
|
assert_equal(address_assert['ismine'], False)
|
||||||
assert_equal(address_assert['timestamp'], timestamp)
|
assert_equal(address_assert['timestamp'], timestamp)
|
||||||
|
|
||||||
|
# ScriptPubKey + internal + label
|
||||||
|
self.log.info("Should not allow a label to be specified when internal is true")
|
||||||
|
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": address['scriptPubKey'],
|
||||||
|
"timestamp": "now",
|
||||||
|
"internal": True,
|
||||||
|
"label": "Example label"
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], False)
|
||||||
|
assert_equal(result[0]['error']['code'], -8)
|
||||||
|
assert_equal(result[0]['error']['message'], 'Internal addresses should not have a label')
|
||||||
|
|
||||||
# Nonstandard scriptPubKey + !internal
|
# Nonstandard scriptPubKey + !internal
|
||||||
self.log.info("Should not import a nonstandard scriptPubKey without internal flag")
|
self.log.info("Should not import a nonstandard scriptPubKey without internal flag")
|
||||||
nonstandardScriptPubKey = address['scriptPubKey'] + bytes_to_hex_str(script.CScript([script.OP_NOP]))
|
nonstandardScriptPubKey = address['scriptPubKey'] + bytes_to_hex_str(script.CScript([script.OP_NOP]))
|
||||||
|
@ -198,7 +217,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
}])
|
}])
|
||||||
assert_equal(result[0]['success'], False)
|
assert_equal(result[0]['success'], False)
|
||||||
assert_equal(result[0]['error']['code'], -8)
|
assert_equal(result[0]['error']['code'], -8)
|
||||||
assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys')
|
assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys')
|
||||||
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
assert_equal(address_assert['iswatchonly'], False)
|
assert_equal(address_assert['iswatchonly'], False)
|
||||||
assert_equal(address_assert['ismine'], False)
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
@ -339,7 +358,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
}])
|
}])
|
||||||
assert_equal(result[0]['success'], False)
|
assert_equal(result[0]['success'], False)
|
||||||
assert_equal(result[0]['error']['code'], -8)
|
assert_equal(result[0]['error']['code'], -8)
|
||||||
assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys')
|
assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys')
|
||||||
|
|
||||||
|
|
||||||
# Address + Public key + !Internal + Wrong pubkey
|
# Address + Public key + !Internal + Wrong pubkey
|
||||||
|
@ -355,7 +374,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
}])
|
}])
|
||||||
assert_equal(result[0]['success'], False)
|
assert_equal(result[0]['success'], False)
|
||||||
assert_equal(result[0]['error']['code'], -5)
|
assert_equal(result[0]['error']['code'], -5)
|
||||||
assert_equal(result[0]['error']['message'], 'Consistency check failed')
|
assert_equal(result[0]['error']['message'], 'Key does not match address destination')
|
||||||
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
assert_equal(address_assert['iswatchonly'], False)
|
assert_equal(address_assert['iswatchonly'], False)
|
||||||
assert_equal(address_assert['ismine'], False)
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
@ -375,7 +394,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
result = self.nodes[1].importmulti(request)
|
result = self.nodes[1].importmulti(request)
|
||||||
assert_equal(result[0]['success'], False)
|
assert_equal(result[0]['success'], False)
|
||||||
assert_equal(result[0]['error']['code'], -5)
|
assert_equal(result[0]['error']['code'], -5)
|
||||||
assert_equal(result[0]['error']['message'], 'Consistency check failed')
|
assert_equal(result[0]['error']['message'], 'Key does not match address destination')
|
||||||
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
assert_equal(address_assert['iswatchonly'], False)
|
assert_equal(address_assert['iswatchonly'], False)
|
||||||
assert_equal(address_assert['ismine'], False)
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
@ -395,7 +414,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
}])
|
}])
|
||||||
assert_equal(result[0]['success'], False)
|
assert_equal(result[0]['success'], False)
|
||||||
assert_equal(result[0]['error']['code'], -5)
|
assert_equal(result[0]['error']['code'], -5)
|
||||||
assert_equal(result[0]['error']['message'], 'Consistency check failed')
|
assert_equal(result[0]['error']['message'], 'Key does not match address destination')
|
||||||
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
assert_equal(address_assert['iswatchonly'], False)
|
assert_equal(address_assert['iswatchonly'], False)
|
||||||
assert_equal(address_assert['ismine'], False)
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
@ -414,7 +433,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
}])
|
}])
|
||||||
assert_equal(result[0]['success'], False)
|
assert_equal(result[0]['success'], False)
|
||||||
assert_equal(result[0]['error']['code'], -5)
|
assert_equal(result[0]['error']['code'], -5)
|
||||||
assert_equal(result[0]['error']['message'], 'Consistency check failed')
|
assert_equal(result[0]['error']['message'], 'Key does not match address destination')
|
||||||
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
assert_equal(address_assert['iswatchonly'], False)
|
assert_equal(address_assert['iswatchonly'], False)
|
||||||
assert_equal(address_assert['ismine'], False)
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
@ -458,6 +477,147 @@ class ImportMultiTest(BitcoinTestFramework):
|
||||||
"timestamp": "",
|
"timestamp": "",
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
# Import P2WPKH address as watch only
|
||||||
|
self.log.info("Should import a P2WPKH address as watch only")
|
||||||
|
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": address['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
|
assert_equal(address_assert['iswatchonly'], True)
|
||||||
|
assert_equal(address_assert['solvable'], False)
|
||||||
|
|
||||||
|
# Import P2WPKH address with public key but no private key
|
||||||
|
self.log.info("Should import a P2WPKH address and public key as solvable but not spendable")
|
||||||
|
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": address['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
"pubkeys": [ address['pubkey'] ]
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
assert_equal(address_assert['solvable'], True)
|
||||||
|
|
||||||
|
# Import P2WPKH address with key and check it is spendable
|
||||||
|
self.log.info("Should import a P2WPKH address with key")
|
||||||
|
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": address['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
"keys": [self.nodes[0].dumpprivkey(address['address'])]
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(address['address'])
|
||||||
|
assert_equal(address_assert['iswatchonly'], False)
|
||||||
|
assert_equal(address_assert['ismine'], True)
|
||||||
|
|
||||||
|
# P2WSH multisig address without scripts or keys
|
||||||
|
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
|
||||||
|
sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
|
||||||
|
multi_sig_script = self.nodes[0].addmultisigaddress(2, [sig_address_1['pubkey'], sig_address_2['pubkey']], "", "bech32")
|
||||||
|
self.log.info("Should import a p2wsh multisig as watch only without respective redeem script and private keys")
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": multi_sig_script['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now"
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
|
||||||
|
assert_equal(address_assert['solvable'], False)
|
||||||
|
|
||||||
|
# Same P2WSH multisig address as above, but now with witnessscript + private keys
|
||||||
|
self.log.info("Should import a p2wsh with respective redeem script and private keys")
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": multi_sig_script['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
"witnessscript": multi_sig_script['redeemScript'],
|
||||||
|
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address']) ]
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
|
||||||
|
assert_equal(address_assert['solvable'], True)
|
||||||
|
assert_equal(address_assert['ismine'], True)
|
||||||
|
assert_equal(address_assert['sigsrequired'], 2)
|
||||||
|
|
||||||
|
# P2SH-P2WPKH address with no redeemscript or public or private key
|
||||||
|
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit"))
|
||||||
|
pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey']))
|
||||||
|
pkscript = CScript([OP_0, pubkeyhash])
|
||||||
|
self.log.info("Should import a p2sh-p2wpkh without redeem script or keys")
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": sig_address_1['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now"
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
|
||||||
|
assert_equal(address_assert['solvable'], False)
|
||||||
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
|
||||||
|
# P2SH-P2WPKH address + redeemscript + public key with no private key
|
||||||
|
self.log.info("Should import a p2sh-p2wpkh with respective redeem script and pubkey as solvable")
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": sig_address_1['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
"redeemscript": bytes_to_hex_str(pkscript),
|
||||||
|
"pubkeys": [ sig_address_1['pubkey'] ]
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
|
||||||
|
assert_equal(address_assert['solvable'], True)
|
||||||
|
assert_equal(address_assert['ismine'], False)
|
||||||
|
|
||||||
|
# P2SH-P2WPKH address + redeemscript + private key
|
||||||
|
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit"))
|
||||||
|
pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey']))
|
||||||
|
pkscript = CScript([OP_0, pubkeyhash])
|
||||||
|
self.log.info("Should import a p2sh-p2wpkh with respective redeem script and private keys")
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": sig_address_1['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
"redeemscript": bytes_to_hex_str(pkscript),
|
||||||
|
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address'])]
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
|
||||||
|
assert_equal(address_assert['solvable'], True)
|
||||||
|
assert_equal(address_assert['ismine'], True)
|
||||||
|
|
||||||
|
# P2SH-P2WSH 1-of-1 multisig + redeemscript with no private key
|
||||||
|
sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
|
||||||
|
multi_sig_script = self.nodes[0].addmultisigaddress(1, [sig_address_1['pubkey']], "", "p2sh-segwit")
|
||||||
|
scripthash = sha256(hex_str_to_bytes(multi_sig_script['redeemScript']))
|
||||||
|
redeem_script = CScript([OP_0, scripthash])
|
||||||
|
self.log.info("Should import a p2sh-p2wsh with respective redeem script but no private key")
|
||||||
|
result = self.nodes[1].importmulti([{
|
||||||
|
"scriptPubKey": {
|
||||||
|
"address": multi_sig_script['address']
|
||||||
|
},
|
||||||
|
"timestamp": "now",
|
||||||
|
"redeemscript": bytes_to_hex_str(redeem_script),
|
||||||
|
"witnessscript": multi_sig_script['redeemScript']
|
||||||
|
}])
|
||||||
|
assert_equal(result[0]['success'], True)
|
||||||
|
address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
|
||||||
|
assert_equal(address_assert['solvable'], True)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ImportMultiTest ().main ()
|
ImportMultiTest ().main ()
|
||||||
|
|
Loading…
Reference in a new issue