Skip to content

Commit

Permalink
Merge #14454: Add SegWit support to importmulti
Browse files Browse the repository at this point in the history
c11875c Add segwit address tests for importmulti (MeshCollider)
201451b Make getaddressinfo return solvability (MeshCollider)
1753d21 Add release notes for importmulti segwit change (MeshCollider)
353c064 Fix typo in test_framework/blocktools (MeshCollider)
f6ed748 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
  • Loading branch information
laanwj committed Oct 31, 2018
2 parents 29f429d + c11875c commit b312579
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 159 deletions.
6 changes: 6 additions & 0 deletions doc/release-notes-14454.md
Original file line number Diff line number Diff line change
@@ -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.
269 changes: 118 additions & 151 deletions src/wallet/rpcdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
try {
bool success = false;

// Required fields.
// First ensure scriptPubKey has either a script or JSON with "address" string
const UniValue& scriptPubKey = data["scriptPubKey"];

// Should have script or JSON with "address".
if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid 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 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& 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;
const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : "";

bool isScript = scriptPubKey.getType() == UniValue::VSTR;
bool isP2SH = strRedeemScript.length() > 0;
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
const std::string& label = data.exists("label") ? data["label"].get_str() : "";

// Parse the output.
// Generate the script and destination for the scriptPubKey provided
CScript script;
CTxDestination dest;

Expand All @@ -854,35 +849,38 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con

// Watchonly and private keys
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")) {
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.
if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address");
// Force users to provide the witness script in its field rather than redeemscript
if (!strRedeemScript.empty() && script.IsPayToWitnessScriptHash()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WSH addresses have an empty redeemscript. Please provide the witnessscript instead.");
}

// Invalid P2SH redeemScript
if (isP2SH && !IsHex(strRedeemScript)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script");
}

// Process. //
CScript scriptpubkey_script = script;
CTxDestination scriptpubkey_dest = dest;
bool allow_p2wpkh = true;

// 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.
std::vector<unsigned char> vData(ParseHex(strRedeemScript));
CScript redeemScript = CScript(vData.begin(), vData.end());
CScriptID redeem_id(redeemScript);

// Invalid P2SH address
if (!script.IsPayToScriptHash()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script");
// Check that the redeemScript and scriptPubKey match
if (GetScriptForDestination(redeem_id) != script) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The redeemScript does not match the scriptPubKey");
}

pwallet->MarkDirty();
Expand All @@ -891,103 +889,83 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}

CScriptID redeem_id(redeemScript);
if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
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) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
// (P2SH-)P2WSH
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)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
// 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 to address book or update label
if (IsValidDestination(dest)) {
pwallet->SetAddressBook(dest, label, "receive");
// 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");
}

// Import private keys.
if (keys.size()) {
for (size_t i = 0; i < keys.size(); i++) {
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->HaveCScript(witness_id) && !pwallet->AddCScript(witness_script)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2wsh witnessScript to wallet");
}

if (!pwallet->AddKeyPubKey(key, pubkey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
// 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
}

pwallet->UpdateTimeFirstKey(timestamp);
}
// (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");
}

success = true;
} else {
// Import public keys.
if (pubKeys.size() && keys.size() == 0) {
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()) {
pubkey = DecodeSecret(keys[0].get_str()).GetPubKey();
}
if (pubKeys.size()) {
const std::string& strPubKey = pubKeys[0].get_str();

if (!IsHex(strPubKey)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
}

std::vector<unsigned char> vData(ParseHex(strPubKey));
CPubKey pubKey(vData.begin(), vData.end());

if (!pubKey.IsFullyValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
}

CTxDestination pubkey_dest = pubKey.GetID();

// Consistency check.
if (!(pubkey_dest == dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
}

CScript pubKeyScript = GetScriptForDestination(pubkey_dest);

if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
std::vector<unsigned char> vData(ParseHex(pubKeys[0].get_str()));
CPubKey pubkey_temp(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");
}

pwallet->MarkDirty();

if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
pubkey = pubkey_temp;
}
if (pubkey.size() > 0) {
if (!pubkey.IsFullyValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
}

// add to address book or update label
if (IsValidDestination(pubkey_dest)) {
pwallet->SetAddressBook(pubkey_dest, label, "receive");
// Check the key corresponds to the destination given
std::vector<CTxDestination> destinations = GetAllDestinationsForKey(pubkey);
if (std::find(destinations.begin(), destinations.end(), dest) == destinations.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Key does not match address destination");
}

// TODO Is this necessary?
CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
// This is necessary to force the wallet to import the pubKey
CScript scriptRawPubKey = GetScriptForRawPubKey(pubkey);

if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
Expand All @@ -998,73 +976,61 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) {
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();
// 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");
}

// Consistency check.
if (!(pubkey_dest == dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
}
pwallet->MarkDirty();

CKeyID vchAddress = pubKey.GetID();
pwallet->MarkDirty();
pwallet->SetAddressBook(vchAddress, label, "receive");
if (!pwallet->AddWatchOnly(scriptpubkey_script, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}

if (pwallet->HaveKey(vchAddress)) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
if (!watchOnly && !pwallet->HaveCScript(CScriptID(scriptpubkey_script)) && !pwallet->AddCScript(scriptpubkey_script)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding scriptPubKey script to wallet");
}

pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
// add to address book or update label
if (IsValidDestination(scriptpubkey_dest)) {
pwallet->SetAddressBook(scriptpubkey_dest, label, "receive");
}

if (!pwallet->AddKeyPubKey(key, pubKey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
// Import private keys.
for (size_t i = 0; i < keys.size(); i++) {
const std::string& strPrivkey = keys[i].get_str();

pwallet->UpdateTimeFirstKey(timestamp);
// Checks.
CKey key = DecodeSecret(strPrivkey);

success = true;
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
}

// 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");
}
CPubKey pubKey = key.GetPubKey();
assert(key.VerifyPubKey(pubKey));

pwallet->MarkDirty();
CKeyID vchAddress = pubKey.GetID();
pwallet->MarkDirty();

if (!pwallet->AddWatchOnly(script, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
if (pwallet->HaveKey(vchAddress)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
}

// add to address book or update label
if (IsValidDestination(dest)) {
pwallet->SetAddressBook(dest, label, "receive");
}
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;

success = true;
if (!pwallet->AddKeyPubKey(key, pubKey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}

pwallet->UpdateTimeFirstKey(timestamp);
}

UniValue result = UniValue(UniValue::VOBJ);
result.pushKV("success", UniValue(success));
result.pushKV("success", UniValue(true));
return result;
} catch (const UniValue& e) {
UniValue result = UniValue(UniValue::VOBJ);
Expand Down Expand Up @@ -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"
" 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"
" \"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"
" \"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"
Expand Down
Loading

0 comments on commit b312579

Please sign in to comment.