Skip to content

Commit 3c481f8

Browse files
committed
signrawtransactionwithkey: better error messages for bad redeemScript/witnessScript
This adds checks to ensure the redeemScript/witnessScript actually correspond to the provided scriptPubKey, and, if both are provided, that they are sensibly related to each other. Thanks to github user passionofvc for raising this issue.
1 parent b5a8d0c commit 3c481f8

File tree

2 files changed

+78
-16
lines changed

2 files changed

+78
-16
lines changed

src/rpc/rawtransaction_util.cpp

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -196,32 +196,73 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst
196196
}
197197

198198
// if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
199-
if (keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) {
199+
const bool is_p2sh = scriptPubKey.IsPayToScriptHash();
200+
const bool is_p2wsh = scriptPubKey.IsPayToWitnessScriptHash();
201+
if (keystore && (is_p2sh || is_p2wsh)) {
200202
RPCTypeCheckObj(prevOut,
201203
{
202204
{"redeemScript", UniValueType(UniValue::VSTR)},
203205
{"witnessScript", UniValueType(UniValue::VSTR)},
204206
}, true);
205207
UniValue rs = find_value(prevOut, "redeemScript");
206-
if (!rs.isNull()) {
207-
std::vector<unsigned char> rsData(ParseHexV(rs, "redeemScript"));
208-
CScript redeemScript(rsData.begin(), rsData.end());
209-
keystore->AddCScript(redeemScript);
210-
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
211-
// This is only for compatibility, it is encouraged to use the explicit witnessScript field instead.
212-
keystore->AddCScript(GetScriptForWitness(redeemScript));
213-
}
214208
UniValue ws = find_value(prevOut, "witnessScript");
215-
if (!ws.isNull()) {
216-
std::vector<unsigned char> wsData(ParseHexV(ws, "witnessScript"));
217-
CScript witnessScript(wsData.begin(), wsData.end());
218-
keystore->AddCScript(witnessScript);
219-
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
220-
keystore->AddCScript(GetScriptForWitness(witnessScript));
221-
}
222209
if (rs.isNull() && ws.isNull()) {
223210
throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript");
224211
}
212+
213+
// work from witnessScript when possible
214+
std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript"));
215+
CScript script(scriptData.begin(), scriptData.end());
216+
keystore->AddCScript(script);
217+
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
218+
// This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
219+
CScript witness_output_script{GetScriptForWitness(script)};
220+
keystore->AddCScript(witness_output_script);
221+
222+
if (!ws.isNull() && !rs.isNull()) {
223+
// if both witnessScript and redeemScript are provided,
224+
// they should either be the same (for backwards compat),
225+
// or the redeemScript should be the encoded form of
226+
// the witnessScript (ie, for p2sh-p2wsh)
227+
if (ws.get_str() != rs.get_str()) {
228+
std::vector<unsigned char> redeemScriptData(ParseHexV(rs, "redeemScript"));
229+
CScript redeemScript(redeemScriptData.begin(), redeemScriptData.end());
230+
if (redeemScript != witness_output_script) {
231+
throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript does not correspond to witnessScript");
232+
}
233+
}
234+
}
235+
236+
if (is_p2sh) {
237+
const CTxDestination p2sh{ScriptHash(script)};
238+
const CTxDestination p2sh_p2wsh{ScriptHash(witness_output_script)};
239+
if (scriptPubKey == GetScriptForDestination(p2sh)) {
240+
// traditional p2sh; arguably an error if
241+
// we got here with rs.IsNull(), because
242+
// that means the p2sh script was specified
243+
// via witnessScript param, but for now
244+
// we'll just quietly accept it
245+
} else if (scriptPubKey == GetScriptForDestination(p2sh_p2wsh)) {
246+
// p2wsh encoded as p2sh; ideally the witness
247+
// script was specified in the witnessScript
248+
// param, but also support specifying it via
249+
// redeemScript param for backwards compat
250+
// (in which case ws.IsNull() == true)
251+
} else {
252+
// otherwise, can't generate scriptPubKey from
253+
// either script, so we got unusable parameters
254+
throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
255+
}
256+
} else if (is_p2wsh) {
257+
// plain p2wsh; could throw an error if script
258+
// was specified by redeemScript rather than
259+
// witnessScript (ie, ws.IsNull() == true), but
260+
// accept it for backwards compat
261+
const CTxDestination p2wsh{WitnessV0ScriptHash(script)};
262+
if (scriptPubKey != GetScriptForDestination(p2wsh)) {
263+
throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
264+
}
265+
}
225266
}
226267
}
227268
}

test/functional/rpc_createmultisig.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,27 @@ def do_multisig(self):
134134

135135
assert_raises_rpc_error(-8, "Missing redeemScript/witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
136136

137+
# if witnessScript specified, all ok
138+
prevtx_err["witnessScript"] = prevtxs[0]["redeemScript"]
139+
node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
140+
141+
# both specified, also ok
142+
prevtx_err["redeemScript"] = prevtxs[0]["redeemScript"]
143+
node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
144+
145+
# redeemScript mismatch to witnessScript
146+
prevtx_err["redeemScript"] = "6a" # OP_RETURN
147+
assert_raises_rpc_error(-8, "redeemScript does not correspond to witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
148+
149+
# redeemScript does not match scriptPubKey
150+
del prevtx_err["witnessScript"]
151+
assert_raises_rpc_error(-8, "redeemScript/witnessScript does not match scriptPubKey", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
152+
153+
# witnessScript does not match scriptPubKey
154+
prevtx_err["witnessScript"] = prevtx_err["redeemScript"]
155+
del prevtx_err["redeemScript"]
156+
assert_raises_rpc_error(-8, "redeemScript/witnessScript does not match scriptPubKey", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err])
157+
137158
rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs)
138159
rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs)
139160

0 commit comments

Comments
 (0)