Repository: bitcoin/bitcoin
File: test/functional/rpc_psbt.py (Python)

Code before:
        assert "musig2_partial_sigs" in res_input
        in_partialsig = res_input["musig2_partial_sigs"][0]
        assert "participant_pubkey" in in_partialsig
        assert_equal(in_partialsig["participant_pubkey"], in_pubkey1.hex())
        assert "aggregate_pubkey" in in_partialsig
        assert_equal(in_partialsig["aggregate_pubkey"], in_fake_agg_pubkey.hex())
        assert "leaf_hash" in in_partialsig
        assert_equal(in_partialsig["leaf_hash"], fake_leaf_hash.hex())
        assert "partial_sig" in in_partialsig
        assert_equal(in_partialsig["partial_sig"], fake_partialsig.hex())

        assert "musig2_participant_pubkeys" in res_output
        out_participant_pks = res_output["musig2_participant_pubkeys"][0]
        assert "aggregate_pubkey" in out_participant_pks
        assert_equal(out_participant_pks["aggregate_pubkey"], out_fake_agg_pubkey.hex())
        assert "participant_pubkeys" in out_participant_pks
        assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])

    def assert_change_type(self, psbtx, expected_type):
        """Assert that the given PSBT has a change output with the given type."""

        # The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node
        decoded_psbt = self.nodes[0].decodepsbt(psbtx["psbt"])
        changepos = psbtx["changepos"]
        assert_equal(decoded_psbt["tx"]["vout"][changepos]["scriptPubKey"]["type"], expected_type)

    def run_test(self):
        # Create and fund a raw tx for sending 10 BTC
        psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']

        self.log.info("Test for invalid maximum transaction weights")
        dest_arg = [{self.nodes[0].getnewaddress(): 1}]
        min_tx_weight = MIN_STANDARD_TX_NONWITNESS_SIZE * WITNESS_SCALE_FACTOR
        assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": -1})
        assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": 0})
        assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": MAX_STANDARD_TX_WEIGHT + 1})

        # Base transaction vsize: version (4) + locktime (4) + input count (1) + witness overhead (1) = 10 vbytes
        base_tx_vsize = 10
        # One P2WPKH output vsize: outpoint (31 vbytes)
        p2wpkh_output_vsize = 31
        # 1 vbyte for output count
        output_count = 1
        tx_weight_without_inputs = (base_tx_vsize + output_count + p2wpkh_output_vsize) * WITNESS_SCALE_FACTOR
        # min_tx_weight is greater than transaction weight without inputs
        assert_greater_than(min_tx_weight, tx_weight_without_inputs)

        # In order to test for when the passed max weight is less than the transaction weight without inputs
        # Define destination with two outputs.
        dest_arg_large = [{self.nodes[0].getnewaddress(): 1}, {self.nodes[0].getnewaddress(): 1}]
        large_tx_vsize_without_inputs = base_tx_vsize + output_count + (p2wpkh_output_vsize * 2)

Review comment (bug):
This is the final hex when I run the test, which is same as the one before modifying the `sighash` type as per the test:
```
0200000000010208bd94fdd97de6bc3ec454b8e0088a5341f9e9611bfbe412387e7e0df35207960000000000fdffffff08bd94fdd97de6bc3ec454b8e0088a5341f9e9611bfbe412387e7e0df35207960100000000fdffffff0280f0fa0200000000160014f57919cd765629cd0d2ba39021a146ee065d697508c2f00800000000160014c7109263e9f5db5d2162ce40a1f898f6ac3933ad02473044022067caa211634d7f25bcac48704165ca7d257ff80a7d0b764825877c7a4a137eff02204ef676f27c9f47813be48a0d2cce9b1dfa2b6fb0ac44eb1d4273781299ad318c81210362cd5a24c1978e204d78b15e5b303c4a8bbb8234591d94b1b8faa27cc72fdeed01412427cd2bcb8d07bc16ccb73a4cffeb996f89a6ee80a39d8ca4cf80f342039e0ec90d12dd0bcb1cdc392c05b1b9e2006fb23fb5fb9ade3a30f9c7b40cd3a581a78100000000
```

These are the 2 signatures of the 2 inputs:
```
3044022067caa211634d7f25bcac48704165ca7d257ff80a7d0b764825877c7a4a137eff02204ef676f27c9f47813be48a0d2cce9b1dfa2b6fb0ac44eb1d4273781299ad318c81
2427cd2bcb8d07bc16ccb73a4cffeb996f89a6ee80a39d8ca4cf80f342039e0ec90d12dd0bcb1cdc392c05b1b9e2006fb23fb5fb9ade3a30f9c7b40cd3a581a781
```

I notice the `81` at the end in both of them signifying `ALL|ANYONECANPAY`.

Why does it not change to `01` when the transaction inputs are signed using `SIGHASH_ALL` later, and subsequently leading to changing of actual signature as well because all inputs are signed now?

Is it because the `finalizepsbt` doesn't actually sign the inputs again because there are signatures already in the PSBT albeit using a different sighash type? 

Shouldn't `PSBTInputSignedAndVerified()` fail here because the sighash type doesn't match with the signature?

Code after:

        assert "musig2_partial_sigs" in res_input
        in_partialsig = res_input["musig2_partial_sigs"][0]
        assert "participant_pubkey" in in_partialsig
        assert_equal(in_partialsig["participant_pubkey"], in_pubkey1.hex())
        assert "aggregate_pubkey" in in_partialsig
        assert_equal(in_partialsig["aggregate_pubkey"], in_fake_agg_pubkey.hex())
        assert "leaf_hash" in in_partialsig
        assert_equal(in_partialsig["leaf_hash"], fake_leaf_hash.hex())
        assert "partial_sig" in in_partialsig
        assert_equal(in_partialsig["partial_sig"], fake_partialsig.hex())

        assert "musig2_participant_pubkeys" in res_output
        out_participant_pks = res_output["musig2_participant_pubkeys"][0]
        assert "aggregate_pubkey" in out_participant_pks
        assert_equal(out_participant_pks["aggregate_pubkey"], out_fake_agg_pubkey.hex())
        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]: