Merge #14481: Add P2SH-P2WSH support to listunspent RPC

6ca836ab3a Add release note for listunspent P2WSH change (MeshCollider)
928beae007 Add test for P2SH-P2WSH in signrawtransactionwithkey and listunspent (MeshCollider)
314784a60f Make listunspent and signrawtransaction RPCs support witnessScript (MeshCollider)

Pull request description:

  This is a reworked version of #11708 after #12427 and the `signrawtransaction` split.

  For a P2WSH address, listunspent should return the witness script, and for a P2SH-P2WSH address, it should also return the inner witness script (because SignTransaction will automatically wrap it in P2SH if required).

  Includes a test which also tests the behaviour of #12427, and release note.

Tree-SHA512: a8e72cf16930312bf48ec47e44a68f8d7e26664043c1b4cc0983eb25aec4087e511188ff9a0f181cd7b8a0c068c60d7f1e7e3f226b79e8c48890039dcf57f7b7
This commit is contained in:
Wladimir J. van der Laan 2019-02-14 22:16:48 +01:00
commit 3facd9fdc4
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
4 changed files with 81 additions and 10 deletions

View file

@ -0,0 +1,9 @@
Low-level RPC changes
----------------------
The `listunspent` RPC has been modified so that it also returns `witnessScript`,
the witness script in the case of a P2WSH or P2SH-P2WSH output.
The `signrawtransactionwithkey` and `signrawtransactionwithwallet` RPCs have been
modified so that they also optionally accept a `witnessScript`, the witness script in the
case of a P2WSH or P2SH-P2WSH output. This is compatible with the change to `listunspent`.

View file

@ -855,15 +855,25 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
RPCTypeCheckObj(prevOut, RPCTypeCheckObj(prevOut,
{ {
{"redeemScript", UniValueType(UniValue::VSTR)}, {"redeemScript", UniValueType(UniValue::VSTR)},
}); {"witnessScript", UniValueType(UniValue::VSTR)},
UniValue v = find_value(prevOut, "redeemScript"); }, true);
if (!v.isNull()) { UniValue rs = find_value(prevOut, "redeemScript");
std::vector<unsigned char> rsData(ParseHexV(v, "redeemScript")); if (!rs.isNull()) {
std::vector<unsigned char> rsData(ParseHexV(rs, "redeemScript"));
CScript redeemScript(rsData.begin(), rsData.end()); CScript redeemScript(rsData.begin(), rsData.end());
keystore->AddCScript(redeemScript); keystore->AddCScript(redeemScript);
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
// This is only for compatibility, it is encouraged to use the explicit witnessScript field instead.
keystore->AddCScript(GetScriptForWitness(redeemScript)); keystore->AddCScript(GetScriptForWitness(redeemScript));
} }
UniValue ws = find_value(prevOut, "witnessScript");
if (!ws.isNull()) {
std::vector<unsigned char> wsData(ParseHexV(ws, "witnessScript"));
CScript witnessScript(wsData.begin(), wsData.end());
keystore->AddCScript(witnessScript);
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
keystore->AddCScript(GetScriptForWitness(witnessScript));
}
} }
} }
} }
@ -948,7 +958,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH or P2WSH) redeem script"}, {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"},
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"},
}, },
}, },

View file

@ -2770,7 +2770,8 @@ static UniValue listunspent(const JSONRPCRequest& request)
" \"scriptPubKey\" : \"key\", (string) the script key\n" " \"scriptPubKey\" : \"key\", (string) the script key\n"
" \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n" " \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n" " \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" " \"redeemScript\" : \"script\" (string) The redeemScript if scriptPubKey is P2SH"
" \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n"
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
" \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
" \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n" " \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n"
@ -2884,6 +2885,28 @@ static UniValue listunspent(const JSONRPCRequest& request)
CScript redeemScript; CScript redeemScript;
if (pwallet->GetCScript(hash, redeemScript)) { if (pwallet->GetCScript(hash, redeemScript)) {
entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
// Now check if the redeemScript is actually a P2WSH script
CTxDestination witness_destination;
if (redeemScript.IsPayToWitnessScriptHash()) {
bool extracted = ExtractDestination(redeemScript, witness_destination);
assert(extracted);
// Also return the witness script
const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination);
CScriptID id;
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
CScript witnessScript;
if (pwallet->GetCScript(id, witnessScript)) {
entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
}
}
}
} else if (scriptPubKey.IsPayToWitnessScriptHash()) {
const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address);
CScriptID id;
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
CScript witnessScript;
if (pwallet->GetCScript(id, witnessScript)) {
entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
} }
} }
} }
@ -3139,7 +3162,8 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH or P2WSH) redeem script"}, {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"},
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"},
}, },
}, },

View file

@ -5,14 +5,17 @@
"""Test transaction signing using the signrawtransaction* RPCs.""" """Test transaction signing using the signrawtransaction* RPCs."""
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.util import assert_equal, assert_raises_rpc_error, bytes_to_hex_str, hex_str_to_bytes
from test_framework.messages import sha256
from test_framework.script import CScript, OP_0
from decimal import Decimal
class SignRawTransactionsTest(BitcoinTestFramework): class SignRawTransactionsTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.setup_clean_chain = True self.setup_clean_chain = True
self.num_nodes = 1 self.num_nodes = 2
self.extra_args = [["-deprecatedrpc=signrawtransaction"]] self.extra_args = [["-deprecatedrpc=signrawtransaction"], []]
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_wallet() self.skip_if_no_wallet()
@ -143,9 +146,33 @@ class SignRawTransactionsTest(BitcoinTestFramework):
assert_equal(rawTxSigned['errors'][1]['witness'], ["304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01", "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"]) assert_equal(rawTxSigned['errors'][1]['witness'], ["304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01", "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"])
assert not rawTxSigned['errors'][0]['witness'] assert not rawTxSigned['errors'][0]['witness']
def witness_script_test(self):
# Now test signing transaction to P2SH-P2WSH addresses without wallet
# Create a new P2SH-P2WSH 1-of-1 multisig address:
embedded_address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())
embedded_privkey = self.nodes[1].dumpprivkey(embedded_address["address"])
p2sh_p2wsh_address = self.nodes[1].addmultisigaddress(1, [embedded_address["pubkey"]], "", "p2sh-segwit")
# send transaction to P2SH-P2WSH 1-of-1 multisig address
self.nodes[0].generate(101)
self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999)
self.nodes[0].generate(1)
self.sync_all()
# Find the UTXO for the transaction node[1] should have received, check witnessScript matches
unspent_output = self.nodes[1].listunspent(0, 999999, [p2sh_p2wsh_address["address"]])[0]
assert_equal(unspent_output["witnessScript"], p2sh_p2wsh_address["redeemScript"])
p2sh_redeemScript = CScript([OP_0, sha256(hex_str_to_bytes(p2sh_p2wsh_address["redeemScript"]))])
assert_equal(unspent_output["redeemScript"], bytes_to_hex_str(p2sh_redeemScript))
# Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].getnewaddress(): Decimal("49.998")})
spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
# Check the signing completed successfully
assert 'complete' in spending_tx_signed
assert_equal(spending_tx_signed['complete'], True)
def run_test(self): def run_test(self):
self.successful_signing_test() self.successful_signing_test()
self.script_verification_error_test() self.script_verification_error_test()
self.witness_script_test()
self.test_with_lock_outputs() self.test_with_lock_outputs()