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

Code before:
        return key
    assert False


def check_script(script):
    if isinstance(script, str):
        script = bytes.fromhex(script)  # Assuming this is hex string
    if isinstance(script, bytes) or isinstance(script, CScript):
        return script
    assert False


class TestFrameworkScriptUtil(unittest.TestCase):
    def test_multisig(self):
        fake_pubkey = bytes([0]*33)
        # check correct encoding of P2MS script with n,k <= 16
        normal_ms_script = keys_to_multisig_script([fake_pubkey]*16, k=15)
        self.assertEqual(len(normal_ms_script), 1 + 16*34 + 1 + 1)
        self.assertTrue(normal_ms_script.startswith(bytes([OP_15])))
        self.assertTrue(normal_ms_script.endswith(bytes([OP_16, OP_CHECKMULTISIG])))

        # check correct encoding of P2MS script with n,k > 16
        max_ms_script = keys_to_multisig_script([fake_pubkey]*20, k=19)
        self.assertEqual(len(max_ms_script), 2 + 20*34 + 2 + 1)
        self.assertTrue(max_ms_script.startswith(bytes([1, 19])))  # using OP_PUSH1
        self.assertTrue(max_ms_script.endswith(bytes([1, 20, OP_CHECKMULTISIG])))


Review comment (bug):
This works! Following the hint from https://github.com/bitcoin/bitcoin/pull/29415#discussion_r2160767733 I could integrate it into the tests of https://github.com/bitcoin/bitcoin/pull/29415 to try to send two transactions that have the same txid and different wtxid.

However using it is a bit involved - one has to create a new dedicated parent transaction, get it in the mempool and then broadcast the two children. Also, one has to handle the fees manually. Like this:

```py
self.log.info("Sending a pair of transactions with same txid and different and valid wtxid via RPC")
txgen = ValidWitnessMalleatedTx()
funding = wallet.get_utxo()
fee_sat = 1000
siblings_parent = txgen.build_parent_tx(funding["txid"], amount=funding["value"] * COIN - fee_sat)
sibling1, sibling2 = txgen.build_malleated_children(siblings_parent.txid_hex, amount=siblings_parent.vout[0].nValue - fee_sat)
self.log.info(f"  - sibling1: txid={sibling1.txid_hex}, wtxid={sibling1.wtxid_hex}")
self.log.info(f"  - sibling2: txid={sibling2.txid_hex}, wtxid={sibling2.wtxid_hex}")
wallet.sign_tx(siblings_parent)
tx_returner.send_without_ping(msg_tx(siblings_parent))
self.wait_until(lambda: len(tx_originator.getrawmempool()) > 1)
self.log.info("  - siblings' parent added to the mempool")
assert_equal(sibling1.txid_hex, sibling2.txid_hex)
assert_not_equal(sibling1.wtxid_hex, sibling2.wtxid_hex)
tx_originator.sendrawtransaction(hexstring=sibling1.serialize_with_witness().hex(), maxfeerate=0.1)
self.log.info(f"  - sent sibling1: ok")
ignoring_msg = f"Ignoring unnecessary request to schedule an already scheduled transaction: txid={sibling2.txid_hex}, wtxid={sibling2.wtxid_hex}"
with tx_originator.busy_wait_for_debug_log(expected_msgs=[ignoring_msg.encode()]):
    tx_originator.sendrawtransaction(hexstring=sibling2.serialize_with_witness().hex(), maxfeerate=0.1)
self.log.info(f"  - sibling2 rejected because it has the same txid: ok")
```

Given that the test already has a transaction that has been sent, I imagined it could be simpler:

```py
self.log.info("Sending a malleated transaction with a valid witness via RPC")
malleated_valid = malleate_tx(txs[0], valid_witness=True)
ignoring_msg = f"Ignoring unnecessary request to schedule an already scheduled transaction: txid={malleated_valid.txid_hex}, wtxid={malleated_valid.wtxid_hex}"
with tx_originator.busy_wait_for_debug_log(expected_msgs=[ignoring_msg.encode()]):                                                                 
    tx_originator.sendrawtransaction(hexstring=malleated_valid.serialize_with_witness().hex(), maxfeerate=0.1)                                 
```

Code after:
    p2shscript = CScript([OP_0, sha256(script)])
    return script_to_p2sh_script(p2shscript)


def output_key_to_p2tr_script(key):
    assert len(key) == 32
    return program_to_witness_script(1, key)


def check_key(key):
    if isinstance(key, str):
        key = bytes.fromhex(key)  # Assuming this is hex string
    if isinstance(key, bytes) and (len(key) == 33 or len(key) == 65):
        return key
    assert False


def check_script(script):
    if isinstance(script, str):
        script = bytes.fromhex(script)  # Assuming this is hex string
    if isinstance(script, bytes) or isinstance(script, CScript):
        return script
    assert False


class ValidWitnessMalleatedTx:
    """
    Creates a valid witness malleation transaction test case:
    - Parent transaction with a script supporting 2 branches
    - 2 child transactions with the same txid but different wtxids
    """
    def __init__(self):
        hashlock = hash160(b'Preimage')
        self.witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF])

    def build_parent_tx(self, funding_txid, amount):
        # Create an unsigned parent transaction paying to the witness script.
        witness_program = sha256(self.witness_script)
        script_pubkey = CScript([OP_0, witness_program])

        parent = CTransaction()
        parent.vin.append(CTxIn(COutPoint(int(funding_txid, 16), 0), b""))
        parent.vout.append(CTxOut(int(amount), script_pubkey))
        return parent

    def build_malleated_children(self, signed_parent_txid, amount):
        # Create 2 valid children that differ only in witness data.
        # 1. Create a new transaction with witness solving first branch
        child_witness_script = CScript([OP_TRUE])
        child_witness_program = sha256(child_witness_script)
        child_script_pubkey = CScript([OP_0, child_witness_program])