mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-27 12:37:57 +02:00
Merge bitcoin/bitcoin#31622: psbt: add non-default sighash types to PSBTs and unify sighash type match checking
ee045b61ef
rpc, psbt: Require sighashes match for descriptorprocesspsbt (Ava Chow)2b7682c372
psbt: use sighash type field to determine whether to remove non-witness utxos (Ava Chow)28781b5f06
psbt: Add sighash types to PSBT when not DEFAULT or ALL (Ava Chow)15ce1bd73f
psbt: Enforce sighash type of signatures matches psbt (Ava Chow)1f71cd337a
wallet: Remove sighash type enforcement from FillPSBT (Ava Chow)4c7d767e49
psbt: Check sighash types in SignPSBTInput and take sighash as optional (Ava Chow)a118256948
script: Add IsPayToTaproot() (Ava Chow)d6001dcd4a
wallet: change FillPSBT to take sighash as optional (Ava Chow)e58b680923
psbt: Return PSBTError from SignPSBTInput (Ava Chow)2adfd81532
tests: Test PSBT sighash type mismatch (Ava Chow)5a5d26d612
psbt: Require ECDSA signatures to be validly encoded (Ava Chow) Pull request description: Currently, we do not add the sighash field to PSBTs at all, even when we have signed with a non-default sighash. This PR changes the behavior such that when we (attempt to) sign with a sighash other than DEFAULT or ALL, the sighash type field will be added to the PSBT to inform the later signers that a different sighash type was used by a signer. Notably, this is necessary for MuSig2 support as all signers must sign using the same sighash type, but the sighash is not provided in partial signatures. Furthermore, because the sighash type can also be provided on the command line, we require that if both a command line sighash type and the sighash field is present, they must specify the same sighash type. However, this was being checked by the wallet, rather than the signing code, so the `descriptorprocesspsbt` RPC was not enforcing this restriction at all, and in fact ignored the sighash field entirely. This PR refactors the checking code so that the underlying PSBT signing function `SignPSBTInput` does the check. ACKs for top commit: theStack: re-ACKee045b61ef
rkrux: re-ACKee045b61ef
fjahr: Code review ACKee045b61ef
Tree-SHA512: 4ead5be1ef6756251b827f594beba868a145d75bf7f4ef6f15ad21f0ae4b8d71b38c83494e5a6b75f37fadd097178cddd93d614b962a2c72fc134f00ba2f74ae
This commit is contained in:
@@ -27,6 +27,7 @@ from test_framework.psbt import (
|
||||
PSBT_GLOBAL_UNSIGNED_TX,
|
||||
PSBT_IN_RIPEMD160,
|
||||
PSBT_IN_SHA256,
|
||||
PSBT_IN_SIGHASH_TYPE,
|
||||
PSBT_IN_HASH160,
|
||||
PSBT_IN_HASH256,
|
||||
PSBT_IN_MUSIG2_PARTIAL_SIG,
|
||||
@@ -37,7 +38,7 @@ from test_framework.psbt import (
|
||||
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS,
|
||||
PSBT_OUT_TAP_TREE,
|
||||
)
|
||||
from test_framework.script import CScript, OP_TRUE
|
||||
from test_framework.script import CScript, OP_TRUE, SIGHASH_ALL, SIGHASH_ANYONECANPAY
|
||||
from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
@@ -274,6 +275,93 @@ class PSBTTest(BitcoinTestFramework):
|
||||
assert "participant_pubkeys" in out_participant_pks
|
||||
assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])
|
||||
|
||||
def test_sighash_mismatch(self):
|
||||
self.log.info("Test sighash type mismatches")
|
||||
self.nodes[0].createwallet("sighash_mismatch")
|
||||
wallet = self.nodes[0].get_wallet_rpc("sighash_mismatch")
|
||||
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
||||
|
||||
addr = wallet.getnewaddress(address_type="bech32")
|
||||
def_wallet.sendtoaddress(addr, 5)
|
||||
self.generate(self.nodes[0], 6)
|
||||
|
||||
# Retrieve the descriptors so we can do all of the tests with descriptorprocesspsbt as well
|
||||
descs = wallet.listdescriptors(True)["descriptors"]
|
||||
|
||||
# Make a PSBT
|
||||
psbt = wallet.walletcreatefundedpsbt([], [{def_wallet.getnewaddress(): 1}])["psbt"]
|
||||
|
||||
# Modify the PSBT and insert a sighash field for ALL|ANYONECANPAY on input 0
|
||||
mod_psbt = PSBT.from_base64(psbt)
|
||||
mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL | SIGHASH_ANYONECANPAY).to_bytes(4, byteorder="little")
|
||||
psbt = mod_psbt.to_base64()
|
||||
|
||||
# Mismatching sighash type fails, including when no type is specified
|
||||
for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]:
|
||||
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, sighash)
|
||||
|
||||
# Matching sighash type succeeds
|
||||
proc = wallet.walletprocesspsbt(psbt, True, "ALL|ANYONECANPAY")
|
||||
assert_equal(proc["complete"], True)
|
||||
|
||||
# Repeat with descriptorprocesspsbt
|
||||
# Mismatching sighash type fails, including when no type is specified
|
||||
for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]:
|
||||
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, sighash)
|
||||
|
||||
# Matching sighash type succeeds
|
||||
proc = self.nodes[0].descriptorprocesspsbt(psbt, descs, "ALL|ANYONECANPAY")
|
||||
assert_equal(proc["complete"], True)
|
||||
|
||||
wallet.unloadwallet()
|
||||
|
||||
def test_sighash_adding(self):
|
||||
self.log.info("Test adding of sighash type field")
|
||||
self.nodes[0].createwallet("sighash_adding")
|
||||
wallet = self.nodes[0].get_wallet_rpc("sighash_adding")
|
||||
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
||||
|
||||
outputs = [{wallet.getnewaddress(address_type="bech32"): 1}]
|
||||
outputs.append({wallet.getnewaddress(address_type="bech32m"): 1})
|
||||
descs = wallet.listdescriptors(True)["descriptors"]
|
||||
def_wallet.send(outputs)
|
||||
self.generate(self.nodes[0], 6)
|
||||
utxos = wallet.listunspent()
|
||||
|
||||
# Make a PSBT
|
||||
psbt = wallet.walletcreatefundedpsbt(utxos, [{def_wallet.getnewaddress(): 0.5}])["psbt"]
|
||||
|
||||
# Process the PSBT with the wallet
|
||||
wallet_psbt = wallet.walletprocesspsbt(psbt=psbt, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]
|
||||
|
||||
# Separately process the PSBT with descriptors
|
||||
desc_psbt = self.nodes[0].descriptorprocesspsbt(psbt=psbt, descriptors=descs, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]
|
||||
|
||||
for psbt in [wallet_psbt, desc_psbt]:
|
||||
# Check that the PSBT has a sighash field on all inputs
|
||||
dec_psbt = self.nodes[0].decodepsbt(psbt)
|
||||
for input in dec_psbt["inputs"]:
|
||||
assert_equal(input["sighash"], "ALL|ANYONECANPAY")
|
||||
|
||||
# Make sure we can still finalize the transaction
|
||||
fin_res = self.nodes[0].finalizepsbt(psbt)
|
||||
assert_equal(fin_res["complete"], True)
|
||||
fin_hex = fin_res["hex"]
|
||||
assert_equal(self.nodes[0].testmempoolaccept([fin_hex])[0]["allowed"], True)
|
||||
|
||||
# Change the sighash field to a different value and make sure we can no longer finalize
|
||||
mod_psbt = PSBT.from_base64(psbt)
|
||||
mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
|
||||
mod_psbt.i[1].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
|
||||
psbt = mod_psbt.to_base64()
|
||||
fin_res = self.nodes[0].finalizepsbt(psbt)
|
||||
assert_equal(fin_res["complete"], False)
|
||||
|
||||
self.nodes[0].sendrawtransaction(fin_hex)
|
||||
self.generate(self.nodes[0], 1)
|
||||
|
||||
wallet.unloadwallet()
|
||||
|
||||
def assert_change_type(self, psbtx, expected_type):
|
||||
"""Assert that the given PSBT has a change output with the given type."""
|
||||
|
||||
@@ -1121,6 +1209,8 @@ class PSBTTest(BitcoinTestFramework):
|
||||
self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed")
|
||||
assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all")
|
||||
|
||||
self.test_sighash_mismatch()
|
||||
self.test_sighash_adding()
|
||||
|
||||
if __name__ == '__main__':
|
||||
PSBTTest(__file__).main()
|
||||
|
Reference in New Issue
Block a user