Skip to content

Commit b312579

Browse files
committed
Merge #14454: Add SegWit support to importmulti
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
2 parents 29f429d + c11875c commit b312579

File tree

5 files changed

+294
-159
lines changed

5 files changed

+294
-159
lines changed

doc/release-notes-14454.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Low-level RPC changes
2+
----------------------
3+
4+
The `importmulti` RPC has been updated to support P2WSH, P2WPKH, P2SH-P2WPKH,
5+
P2SH-P2WSH. Each request now accepts an additional `witnessscript` to be used
6+
for P2WSH or P2SH-P2WSH.

src/wallet/rpcdump.cpp

Lines changed: 118 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -808,29 +808,24 @@ UniValue dumpwallet(const JSONRPCRequest& request)
808808
static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
809809
{
810810
try {
811-
bool success = false;
812-
813-
// Required fields.
811+
// First ensure scriptPubKey has either a script or JSON with "address" string
814812
const UniValue& scriptPubKey = data["scriptPubKey"];
815-
816-
// Should have script or JSON with "address".
817-
if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) {
818-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey");
813+
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
814+
if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
815+
throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
819816
}
817+
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
820818

821819
// Optional fields.
822820
const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
821+
const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : "";
823822
const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
824823
const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
825824
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
826825
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
827-
const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : "";
828-
829-
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
830-
bool isP2SH = strRedeemScript.length() > 0;
831-
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
826+
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
832827

833-
// Parse the output.
828+
// Generate the script and destination for the scriptPubKey provided
834829
CScript script;
835830
CTxDestination dest;
836831

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

855850
// Watchonly and private keys
856851
if (watchOnly && keys.size()) {
857-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys");
852+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Watch-only addresses should not include private keys");
858853
}
859854

860-
// Internal + Label
855+
// Internal addresses should not have a label
861856
if (internal && data.exists("label")) {
862-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label");
857+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
863858
}
864859

865-
// Keys / PubKeys size check.
866-
if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey
867-
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address");
860+
// Force users to provide the witness script in its field rather than redeemscript
861+
if (!strRedeemScript.empty() && script.IsPayToWitnessScriptHash()) {
862+
throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WSH addresses have an empty redeemscript. Please provide the witnessscript instead.");
868863
}
869864

870-
// Invalid P2SH redeemScript
871-
if (isP2SH && !IsHex(strRedeemScript)) {
872-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script");
873-
}
874-
875-
// Process. //
865+
CScript scriptpubkey_script = script;
866+
CTxDestination scriptpubkey_dest = dest;
867+
bool allow_p2wpkh = true;
876868

877869
// P2SH
878-
if (isP2SH) {
870+
if (!strRedeemScript.empty() && script.IsPayToScriptHash()) {
871+
// Check the redeemScript is valid
872+
if (!IsHex(strRedeemScript)) {
873+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script: must be hex string");
874+
}
875+
879876
// Import redeem script.
880877
std::vector<unsigned char> vData(ParseHex(strRedeemScript));
881878
CScript redeemScript = CScript(vData.begin(), vData.end());
879+
CScriptID redeem_id(redeemScript);
882880

883-
// Invalid P2SH address
884-
if (!script.IsPayToScriptHash()) {
885-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script");
881+
// Check that the redeemScript and scriptPubKey match
882+
if (GetScriptForDestination(redeem_id) != script) {
883+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The redeemScript does not match the scriptPubKey");
886884
}
887885

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

894-
CScriptID redeem_id(redeemScript);
895892
if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
896893
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
897894
}
898895

899-
CScript redeemDestination = GetScriptForDestination(redeem_id);
896+
// Now set script to the redeemScript so we parse the inner script as P2WSH or P2WPKH below
897+
script = redeemScript;
898+
ExtractDestination(script, dest);
899+
}
900900

901-
if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) {
902-
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
901+
// (P2SH-)P2WSH
902+
if (!witness_script_hex.empty() && script.IsPayToWitnessScriptHash()) {
903+
if (!IsHex(witness_script_hex)) {
904+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script: must be hex string");
903905
}
904906

905-
pwallet->MarkDirty();
907+
// Generate the scripts
908+
std::vector<unsigned char> witness_script_parsed(ParseHex(witness_script_hex));
909+
CScript witness_script = CScript(witness_script_parsed.begin(), witness_script_parsed.end());
910+
CScriptID witness_id(witness_script);
906911

907-
if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) {
908-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
912+
// Check that the witnessScript and scriptPubKey match
913+
if (GetScriptForDestination(WitnessV0ScriptHash(witness_script)) != script) {
914+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The witnessScript does not match the scriptPubKey or redeemScript");
909915
}
910916

911-
// add to address book or update label
912-
if (IsValidDestination(dest)) {
913-
pwallet->SetAddressBook(dest, label, "receive");
917+
// Add the witness script as watch only only if it is not for P2SH-P2WSH
918+
if (!scriptpubkey_script.IsPayToScriptHash() && !pwallet->AddWatchOnly(witness_script, timestamp)) {
919+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
914920
}
915921

916-
// Import private keys.
917-
if (keys.size()) {
918-
for (size_t i = 0; i < keys.size(); i++) {
919-
const std::string& privkey = keys[i].get_str();
920-
921-
CKey key = DecodeSecret(privkey);
922-
923-
if (!key.IsValid()) {
924-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
925-
}
926-
927-
CPubKey pubkey = key.GetPubKey();
928-
assert(key.VerifyPubKey(pubkey));
929-
930-
CKeyID vchAddress = pubkey.GetID();
931-
pwallet->MarkDirty();
932-
pwallet->SetAddressBook(vchAddress, label, "receive");
933-
934-
if (pwallet->HaveKey(vchAddress)) {
935-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
936-
}
937-
938-
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
922+
if (!pwallet->HaveCScript(witness_id) && !pwallet->AddCScript(witness_script)) {
923+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2wsh witnessScript to wallet");
924+
}
939925

940-
if (!pwallet->AddKeyPubKey(key, pubkey)) {
941-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
942-
}
926+
// Now set script to the witnessScript so we parse the inner script as P2PK or P2PKH below
927+
script = witness_script;
928+
ExtractDestination(script, dest);
929+
allow_p2wpkh = false; // P2WPKH cannot be embedded in P2WSH
930+
}
943931

944-
pwallet->UpdateTimeFirstKey(timestamp);
945-
}
932+
// (P2SH-)P2PK/P2PKH/P2WPKH
933+
if (dest.type() == typeid(CKeyID) || dest.type() == typeid(WitnessV0KeyHash)) {
934+
if (!allow_p2wpkh && dest.type() == typeid(WitnessV0KeyHash)) {
935+
throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WPKH cannot be embedded in P2WSH");
946936
}
947-
948-
success = true;
949-
} else {
950-
// Import public keys.
951-
if (pubKeys.size() && keys.size() == 0) {
937+
if (keys.size() > 1 || pubKeys.size() > 1) {
938+
throw JSONRPCError(RPC_INVALID_PARAMETER, "More than one key given for one single-key address");
939+
}
940+
CPubKey pubkey;
941+
if (keys.size()) {
942+
pubkey = DecodeSecret(keys[0].get_str()).GetPubKey();
943+
}
944+
if (pubKeys.size()) {
952945
const std::string& strPubKey = pubKeys[0].get_str();
953-
954946
if (!IsHex(strPubKey)) {
955947
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
956948
}
957-
958-
std::vector<unsigned char> vData(ParseHex(strPubKey));
959-
CPubKey pubKey(vData.begin(), vData.end());
960-
961-
if (!pubKey.IsFullyValid()) {
962-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
963-
}
964-
965-
CTxDestination pubkey_dest = pubKey.GetID();
966-
967-
// Consistency check.
968-
if (!(pubkey_dest == dest)) {
969-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
970-
}
971-
972-
CScript pubKeyScript = GetScriptForDestination(pubkey_dest);
973-
974-
if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
975-
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
949+
std::vector<unsigned char> vData(ParseHex(pubKeys[0].get_str()));
950+
CPubKey pubkey_temp(vData.begin(), vData.end());
951+
if (pubkey.size() && pubkey_temp != pubkey) {
952+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key does not match public key for address");
976953
}
977-
978-
pwallet->MarkDirty();
979-
980-
if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) {
981-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
954+
pubkey = pubkey_temp;
955+
}
956+
if (pubkey.size() > 0) {
957+
if (!pubkey.IsFullyValid()) {
958+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
982959
}
983960

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

989-
// TODO Is this necessary?
990-
CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
967+
// This is necessary to force the wallet to import the pubKey
968+
CScript scriptRawPubKey = GetScriptForRawPubKey(pubkey);
991969

992970
if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
993971
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
998976
if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) {
999977
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1000978
}
1001-
1002-
success = true;
1003979
}
980+
}
1004981

1005-
// Import private keys.
1006-
if (keys.size()) {
1007-
const std::string& strPrivkey = keys[0].get_str();
1008-
1009-
// Checks.
1010-
CKey key = DecodeSecret(strPrivkey);
1011-
1012-
if (!key.IsValid()) {
1013-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
1014-
}
1015-
1016-
CPubKey pubKey = key.GetPubKey();
1017-
assert(key.VerifyPubKey(pubKey));
1018-
1019-
CTxDestination pubkey_dest = pubKey.GetID();
982+
// Import the address
983+
if (::IsMine(*pwallet, scriptpubkey_script) == ISMINE_SPENDABLE) {
984+
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
985+
}
1020986

1021-
// Consistency check.
1022-
if (!(pubkey_dest == dest)) {
1023-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
1024-
}
987+
pwallet->MarkDirty();
1025988

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

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

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

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

1040-
pwallet->UpdateTimeFirstKey(timestamp);
1006+
// Checks.
1007+
CKey key = DecodeSecret(strPrivkey);
10411008

1042-
success = true;
1009+
if (!key.IsValid()) {
1010+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
10431011
}
10441012

1045-
// Import scriptPubKey only.
1046-
if (pubKeys.size() == 0 && keys.size() == 0) {
1047-
if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
1048-
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
1049-
}
1013+
CPubKey pubKey = key.GetPubKey();
1014+
assert(key.VerifyPubKey(pubKey));
10501015

1051-
pwallet->MarkDirty();
1016+
CKeyID vchAddress = pubKey.GetID();
1017+
pwallet->MarkDirty();
10521018

1053-
if (!pwallet->AddWatchOnly(script, timestamp)) {
1054-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1055-
}
1019+
if (pwallet->HaveKey(vchAddress)) {
1020+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
1021+
}
10561022

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

1062-
success = true;
1025+
if (!pwallet->AddKeyPubKey(key, pubKey)) {
1026+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
10631027
}
1028+
1029+
pwallet->UpdateTimeFirstKey(timestamp);
10641030
}
10651031

10661032
UniValue result = UniValue(UniValue::VOBJ);
1067-
result.pushKV("success", UniValue(success));
1033+
result.pushKV("success", UniValue(true));
10681034
return result;
10691035
} catch (const UniValue& e) {
10701036
UniValue result = UniValue(UniValue::VOBJ);
@@ -1117,7 +1083,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
11171083
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
11181084
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
11191085
" creation time of all keys being imported by the importmulti call will be scanned.\n"
1120-
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
1086+
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey\n"
1087+
" \"witnessscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey\n"
11211088
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
11221089
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
11231090
" \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"

0 commit comments

Comments
 (0)