diff --git a/test/functional/feature_checktemplateverify.py b/test/functional/feature_checktemplateverify.py new file mode 100755 index 00000000000..14b7ac81734 --- /dev/null +++ b/test/functional/feature_checktemplateverify.py @@ -0,0 +1,738 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test OP_CHECKTEMPLATEVERIFY (BIP-119) +""" + +from test_framework.blocktools import ( + create_coinbase, + create_block, + add_witness_commitment, +) +from test_framework.messages import ( + CTransaction, + CTxOut, + CTxIn, + CTxInWitness, + COutPoint, + COIN, + sha256, +) +from test_framework.p2p import P2PInterface +from test_framework.script import ( + CScript, + OP_TRUE, + OP_DEPTH, + OP_ENDIF, + OP_IF, + OP_CHECKTEMPLATEVERIFY, + OP_FALSE, + OP_DROP, + taproot_construct, +) +from test_framework.script_util import script_to_p2sh_script +from test_framework.key import ECKey, compute_xonly_pubkey +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.wallet import MiniWallet, MiniWalletMode +from decimal import Decimal +import random +from io import BytesIO +from test_framework.address import script_to_p2sh + +CHECKTEMPLATEVERIFY_ERROR = "mandatory-script-verify-flag-failed (Script failed an OP_CHECKTEMPLATEVERIFY operation)" +DISCOURAGED_ERROR = ( + "non-mandatory-script-verify-flag (NOPx reserved for soft-fork upgrades)" +) +STACK_TOO_SHORT_ERROR = ( + "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)" +) + + +def random_bytes(n): + return bytes(random.getrandbits(8) for i in range(n)) + + +def template_hash_for_outputs(outputs, nIn=0, nVin=1, vin_override=None): + c = CTransaction() + c.version = 2 + c.vin = vin_override + if vin_override is None: + c.vin = [CTxIn()] * nVin + c.vout = outputs + return c.get_standard_template_hash(nIn) + + +def random_p2sh(): + return script_to_p2sh_script(random_bytes(20)) + + +def random_real_outputs_and_script(n, nIn=0, nVin=1, vin_override=None): + outputs = [CTxOut((x + 1) * 1000, random_p2sh()) for x in range(n)] + script = CScript( + [ + template_hash_for_outputs(outputs, nIn, nVin, vin_override), + OP_CHECKTEMPLATEVERIFY, + ] + ) + return outputs, script + + +def random_secure_tree(depth): + leaf_nodes = [ + CTxOut(nValue=100, scriptPubKey=CScript(bytes([0, 0x14]) + random_bytes(20))) + for x in range(2 ** depth) + ] + outputs_tree = [[CTxOut()] * (2 ** i) for i in range(depth)] + [leaf_nodes] + for d in range(1, depth + 2): + idxs = zip( + range(0, len(outputs_tree[-d]), 2), range(1, len(outputs_tree[-d]), 2) + ) + for (idx, (a, b)) in enumerate( + [(outputs_tree[-d][i], outputs_tree[-d][j]) for (i, j) in idxs] + ): + s = CScript( + bytes([0x20]) + + template_hash_for_outputs([a, b]) + + bytes([OP_CHECKTEMPLATEVERIFY]) + ) + a = sum(o.nValue for o in [a, b]) + t = CTxOut(a + 1000, s) + outputs_tree[-d - 1][idx] = t + return outputs_tree + + +def create_transaction_to_script(node, wallet, txid, script, *, amount_sats): + """ + Return signed transaction spending the first output of the + input txid. Note that the node must be able to sign for the + output that is being spent, and the node must not be running + multiple wallets. + """ + random_address = script_to_p2sh(CScript()) + output = wallet.get_utxo(txid=txid) + rawtx = node.createrawtransaction( + inputs=[{"txid": output["txid"], "vout": output["vout"]}], + outputs={random_address: Decimal(amount_sats) / COIN}, + ) + tx = CTransaction() + tx.deserialize(BytesIO(bytes.fromhex(rawtx))) + # Replace with our script + tx.vout[0].scriptPubKey = script + # Sign + wallet.sign_tx(tx) + return tx + + +class CheckTemplateVerifyTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + # Use only one script thread to get the exact reject reason for testing + self.extra_args = [ + ["-par=1"] + ] + self.setup_clean_chain = True + self.rpc_timeout = 120 + + def get_block(self, txs): + self.tip = self.nodes[0].getbestblockhash() + self.height = self.nodes[0].getblockcount() + self.log.debug(self.height) + block = create_block(int(self.tip, 16), create_coinbase(self.height + 1)) + block.vtx.extend(txs) + add_witness_commitment(block) + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + return block.serialize(True).hex(), block.hash + + def add_block(self, txs): + block, h = self.get_block(txs) + reason = self.nodes[0].submitblock(block) + if reason: + self.log.debug("Reject Reason: [%s]", reason) + assert_equal(self.nodes[0].getbestblockhash(), h) + return h + + def fail_block(self, txs, cause=CHECKTEMPLATEVERIFY_ERROR): + block, h = self.get_block(txs) + assert_equal(self.nodes[0].submitblock(block), cause) + assert_equal(self.nodes[0].getbestblockhash(), self.tip) + + def run_test(self): + """ + The goal is to test a number of circumstances and combinations of parameters. Roughly: + + - Taproot OP_CTV + - SegWit OP_CTV + - SegWit OP_CTV wrong size on stack + - fails policy + - passes consensus + - SegWit OP_CTV no argument in stack from program + - fails policy and consensus with empty stack + - passes consensus and policy when argument is the correct hash + - passes consensus when argument is non 32 bytes + - fails policy when argument is non 32 bytes + - P2SH OP_CTV (impossible to spend w/o hash cycle!) + - Bare OP_CTV + - OP_CTV at vin index 0 + - OP_CTV at vin index > 0 + - OP_CTV with scriptSigs set + - OP_CTV without scriptSigs set + - OP_CTV with multiple inputs + - accepting correct parameters + - rejecting incorrect parameters + - OP_CTV in a tree + + A few tests may seem redundant, but it is because they are testing the cached computation of the hash + at vin index 0 + """ + wallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK) + self.nodes[0].add_p2p_connection(P2PInterface()) + + BLOCKS = 115 + self.log.info("Mining %d blocks for mature coinbases", BLOCKS) + # Drop the last 100 as they're unspendable! + coinbase_txids = [ + self.nodes[0].getblock(b)["tx"][0] + for b in self.generate(wallet, BLOCKS)[:-100] + ] + get_coinbase = lambda: coinbase_txids.pop() + + self.log.info("Creating setup transactions") + + self.log.info("Creating script for 10 random outputs") + outputs, script = random_real_outputs_and_script(10) + # Add some fee satoshis + amount_sats = sum(out.nValue for out in outputs) + 200 * 500 + + self.log.info("Creating funding txn for 10 random outputs as a Taproot script") + private_key = ECKey() + # use simple deterministic private key (k=1) + private_key.set((1).to_bytes(32, "big"), False) + assert private_key.is_valid + public_key, _ = compute_xonly_pubkey(private_key.get_bytes()) + taproot = taproot_construct(public_key, [("only-path", script, 0xC0)]) + taproot_ctv_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + taproot.scriptPubKey, + amount_sats=amount_sats, + ) + + self.log.info("Creating funding txn for 10 random outputs as a segwit script") + segwit_ctv_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + CScript([0, sha256(script)]), + amount_sats=amount_sats, + ) + + self.log.info("Creating a CTV with a non 32 byte stack segwit script") + segwit_ctv_funding_tx_wrongsize_stack = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + CScript([0, sha256(CScript([OP_TRUE, OP_CHECKTEMPLATEVERIFY]))]), + amount_sats=amount_sats, + ) + + self.log.info("Creating a CTV with an empty stack segwit script") + # allows either calling with empty witness stack or with a 32 byte hash (cleanstack rule) + empty_stack_script = CScript( + [OP_CHECKTEMPLATEVERIFY, OP_DEPTH, OP_IF, OP_DROP, OP_ENDIF, OP_TRUE] + ) + segwit_ctv_funding_tx_empty_stack = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + CScript([0, sha256(empty_stack_script)]), + amount_sats=amount_sats, + ) + + self.log.info( + "Creating funding txn for 10 random outputs as a p2sh script (impossible to spend)" + ) + p2sh_ctv_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + script_to_p2sh_script(script), + amount_sats=amount_sats, + ) + + # Small tree size 4 for test speed. + # Can be set to a large value like 16 (i.e., 65K txns). + TREE_SIZE = 4 + self.log.info(f"Creating script for tree size depth {TREE_SIZE}") + congestion_tree_txo = random_secure_tree(TREE_SIZE) + + self.log.info(f"Creating funding txn for tree size depth {TREE_SIZE}") + bare_ctv_tree_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + congestion_tree_txo[0][0].scriptPubKey, + amount_sats=congestion_tree_txo[0][0].nValue, + ) + + self.log.info("Creating script for spend at position 2") + outputs_position_2, script_position_2 = random_real_outputs_and_script(10, 1, 2) + # Add some fee satoshis + amount_position_2 = sum(out.nValue for out in outputs_position_2) + 200 * 500 + + self.log.info("Creating funding txn for spend at position 2") + bare_ctv_position_2_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + script_position_2, + amount_sats=amount_position_2, + ) + + self.log.info( + "Creating script for spend at position 1 with 2 non-null scriptsigs" + ) + ( + outputs_specific_scriptSigs, + script_specific_scriptSigs, + ) = random_real_outputs_and_script( + 10, + 0, + 2, + [CTxIn(scriptSig=CScript([OP_TRUE])), CTxIn(scriptSig=CScript([OP_FALSE]))], + ) + # Add some fee satoshis + amount_specific_scriptSigs = ( + sum(out.nValue for out in outputs_specific_scriptSigs) + 200 * 500 + ) + + self.log.info( + "Creating funding txn for spend at position 1 with 2 non-null scriptsigs" + ) + bare_ctv_specific_scriptSigs_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + script_specific_scriptSigs, + amount_sats=amount_specific_scriptSigs, + ) + + self.log.info( + "Creating script for spend at position 2 with 2 non-null scriptsigs" + ) + ( + outputs_specific_scriptSigs_position_2, + script_specific_scriptSigs_position_2, + ) = random_real_outputs_and_script( + 10, + 1, + 2, + [CTxIn(scriptSig=CScript([OP_TRUE])), CTxIn(scriptSig=CScript([OP_FALSE]))], + ) + # Add some fee satoshis + amount_specific_scriptSigs_position_2 = ( + sum(out.nValue for out in outputs_specific_scriptSigs_position_2) + + 200 * 500 + ) + + self.log.info( + "Creating funding txn for spend at position 2 with 2 non-null scriptsigs" + ) + bare_ctv_specific_scriptSigs_position_2_funding_tx = ( + create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + script_specific_scriptSigs_position_2, + amount_sats=amount_specific_scriptSigs_position_2, + ) + ) + + self.log.info("Creating funding txns for some anyone can spend outputs") + anyone_can_spend_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + CScript([OP_TRUE]), + amount_sats=amount_sats, + ) + bare_anyone_can_spend_funding_tx = create_transaction_to_script( + self.nodes[0], + wallet, + get_coinbase(), + CScript([OP_TRUE]), + amount_sats=amount_sats, + ) + + funding_txs = [ + taproot_ctv_funding_tx, + segwit_ctv_funding_tx, + segwit_ctv_funding_tx_wrongsize_stack, + segwit_ctv_funding_tx_empty_stack, + p2sh_ctv_funding_tx, + anyone_can_spend_funding_tx, + bare_ctv_tree_funding_tx, + bare_ctv_position_2_funding_tx, + bare_anyone_can_spend_funding_tx, + bare_ctv_specific_scriptSigs_funding_tx, + bare_ctv_specific_scriptSigs_position_2_funding_tx, + ] + + self.log.info("Obtaining TXIDs") + ( + taproot_ctv_outpoint, + segwit_ctv_outpoint, + segwit_ctv_wrongsize_stack_outpoint, + segwit_ctv_empty_stack_outpoint, + p2sh_ctv_outpoint, + anyone_can_spend_outpoint, + bare_ctv_tree_outpoint, + bare_ctv_position_2_outpoint, + bare_anyone_can_spend_outpoint, + bare_ctv_specific_scriptSigs_outpoint, + bare_ctv_specific_scriptSigs_position_2_outpoint, + ) = [COutPoint(int(tx.rehash(), 16), 0) for tx in funding_txs] + + self.log.info("Funding all outputs") + self.add_block(funding_txs) + + self.log.info("Testing Taproot OP_CHECKTEMPLATEVERIFY spend") + # Test sendrawtransaction + taproot_check_template_verify_tx = CTransaction() + taproot_check_template_verify_tx.version = 2 + taproot_check_template_verify_tx.vin = [CTxIn(taproot_ctv_outpoint)] + taproot_check_template_verify_tx.vout = outputs + taproot_check_template_verify_tx.wit.vtxinwit += [CTxInWitness()] + taproot_check_template_verify_tx.wit.vtxinwit[0].scriptWitness.stack = [ + script, + bytes([0xC0 + taproot.negflag]) + taproot.internal_pubkey, + ] + assert_equal( + self.nodes[0].sendrawtransaction( + taproot_check_template_verify_tx.serialize().hex() + ), + taproot_check_template_verify_tx.rehash(), + ) + self.log.info( + "Taproot OP_CHECKTEMPLATEVERIFY spend accepted by sendrawtransaction" + ) + + # Now we verify that a block with this transaction is also valid + blockhash = self.add_block([taproot_check_template_verify_tx]) + self.log.info("Taproot OP_CHECKTEMPLATEVERIFY spend accepted in a block") + + self.log.info("Rolling back the block") + # Reset tip + self.nodes[0].invalidateblock(blockhash) + + self.log.info("Testing Segwit OP_CHECKTEMPLATEVERIFY spend") + # Test sendrawtransaction + check_template_verify_tx = CTransaction() + check_template_verify_tx.version = 2 + check_template_verify_tx.vin = [CTxIn(segwit_ctv_outpoint)] + check_template_verify_tx.vout = outputs + + check_template_verify_tx.wit.vtxinwit += [CTxInWitness()] + check_template_verify_tx.wit.vtxinwit[0].scriptWitness.stack = [script] + assert_equal( + self.nodes[0].sendrawtransaction( + check_template_verify_tx.serialize().hex() + ), + check_template_verify_tx.rehash(), + ) + self.log.info( + "Segwit OP_CHECKTEMPLATEVERIFY spend accepted by sendrawtransaction" + ) + + # Now we verify that a block with this transaction is also valid + blockhash = self.add_block([check_template_verify_tx]) + self.log.info("Segwit OP_CHECKTEMPLATEVERIFY spend accepted in a block") + + self.log.info("Rolling back the block") + # Reset tip + self.nodes[0].invalidateblock(blockhash) + + # Show any modification will break the validity + self.log.info( + "Modifying Segwit OP_CHECKTEMPLATEVERIFY spend, block should fail" + ) + check_template_verify_tx_mutated_amount = check_template_verify_tx + check_template_verify_tx_mutated_amount.vout[0].nValue += 1 + check_template_verify_tx_mutated_amount.rehash() + self.fail_block([check_template_verify_tx_mutated_amount]) + self.log.info("Modified Segwit OP_CHECKTEMPLATEVERIFY spend failed to confirm") + + # Now show that only one input allowed + self.log.info("Testing that multiple inputs are disallowed when specified") + check_template_verify_two_inputs = check_template_verify_tx + check_template_verify_two_inputs.vin += [CTxIn(anyone_can_spend_outpoint)] + check_template_verify_two_inputs.rehash() + self.fail_block([check_template_verify_two_inputs]) + + self.log.info( + "Testing that the second input specified was actually spendable by itself" + ) + + # Second UTXO was actually spendable + spendtx = CTransaction() + spendtx.version = 2 + spendtx.vin = [CTxIn(anyone_can_spend_outpoint)] + spendtx.vout += [CTxOut(int(amount_sats - 1000), random_p2sh())] + spendtx.rehash() + blockhash = self.add_block([spendtx]) + # Reset tip + self.nodes[0].invalidateblock(blockhash) + + self.log.info( + "Testing Segwit OP_CHECKTEMPLATEVERIFY spend with a wrong size argument" + ) + # Test sendrawtransaction + check_template_verify_tx_wrongsize_stack = CTransaction() + check_template_verify_tx_wrongsize_stack.version = 2 + check_template_verify_tx_wrongsize_stack.vin = [ + CTxIn(segwit_ctv_wrongsize_stack_outpoint) + ] + check_template_verify_tx_wrongsize_stack.vout = outputs + + check_template_verify_tx_wrongsize_stack.wit.vtxinwit += [CTxInWitness()] + check_template_verify_tx_wrongsize_stack.wit.vtxinwit[0].scriptWitness.stack = [ + CScript([OP_TRUE, OP_CHECKTEMPLATEVERIFY]) + ] + + assert_raises_rpc_error( + -26, + DISCOURAGED_ERROR, + self.nodes[0].sendrawtransaction, + check_template_verify_tx_wrongsize_stack.serialize().hex(), + ) + self.log.info( + "OP_CHECKTEMPLATEVERIFY with wrong size stack rejected by sendrawtransaction as discouraged" + ) + + # Now we verify that a block with this transaction is valid + blockhash = self.add_block([check_template_verify_tx_wrongsize_stack]) + self.log.info( + "Segwit OP_CHECKTEMPLATEVERIFY with wrong size stack spend accepted in a block (soft fork upgradable)" + ) + + self.log.info( + "Testing Segwit OP_CHECKTEMPLATEVERIFY spend with an empty stack argument" + ) + # Test sendrawtransaction + check_template_verify_tx_empty_stack = CTransaction() + check_template_verify_tx_empty_stack.version = 2 + check_template_verify_tx_empty_stack.vin = [ + CTxIn(segwit_ctv_empty_stack_outpoint) + ] + check_template_verify_tx_empty_stack.vout = outputs + + check_template_verify_tx_empty_stack.wit.vtxinwit += [CTxInWitness()] + check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [ + empty_stack_script + ] + + assert_raises_rpc_error( + -26, + STACK_TOO_SHORT_ERROR, + self.nodes[0].sendrawtransaction, + check_template_verify_tx_empty_stack.serialize().hex(), + ) + + self.log.info( + "OP_CHECKTEMPLATEVERIFY with wrong size stack rejected by sendrawtransaction as discouraged" + ) + + # Now we verify that a block with this transaction is invalid + self.fail_block([check_template_verify_tx_empty_stack], STACK_TOO_SHORT_ERROR) + self.log.info( + "Segwit OP_CHECKTEMPLATEVERIFY with wrong size stack spend rejected from block" + ) + + # Show that putting some element on the stack makes it succeed for consensus but fail policy + self.log.info( + "Segwit OP_CHECKTEMPLATEVERIFY with CTV argument in the program should fail policy pass consensus if witness stack is not 32 bytes" + ) + check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [ + CScript([OP_TRUE]), + empty_stack_script, + ] + assert_raises_rpc_error( + -26, + DISCOURAGED_ERROR, + self.nodes[0].sendrawtransaction, + check_template_verify_tx_empty_stack.serialize().hex(), + ) + self.log.info( + "OP_CHECKTEMPLATEVERIFY with wrong size argument on the witness stack rejected by sendrawtransaction as discouraged" + ) + + # Now we verify that a block with this transaction is valid + blockhash = self.add_block([check_template_verify_tx_empty_stack]) + self.log.info( + "Segwit OP_CHECKTEMPLATEVERIFY with empty stack spend accepted in a block with something on the witness stack" + ) + self.log.info("Rolling back the block") + # Reset tip + self.nodes[0].invalidateblock(blockhash) + + # Put the correct hash on + self.log.info( + "Testing OP_CHECKTEMPLATEVERIFY spend with template hash from the witness stack" + ) + h = check_template_verify_tx_empty_stack.get_standard_template_hash(0) + check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [ + h, + empty_stack_script, + ] + assert_equal( + self.nodes[0].sendrawtransaction( + check_template_verify_tx_empty_stack.serialize().hex() + ), + check_template_verify_tx_empty_stack.rehash(), + ) + self.log.info( + "Witness stack defined OP_CHECKTEMPLATEVERIFY spend accepted by sendrawtransaction" + ) + # Now we verify that a block with this transaction is also valid + blockhash = self.add_block([check_template_verify_tx_empty_stack]) + self.log.info( + "Witness stack defined OP_CHECKTEMPLATEVERIFY spend accepted in a block" + ) + self.log.info("Rolling back the block") + # Reset tip + self.nodes[0].invalidateblock(blockhash) + + self.log.info( + "Testing that other 32 byte arguments passed from the witness stack fail" + ) + h = h[::-1] + check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [ + h, + empty_stack_script, + ] + self.fail_block([check_template_verify_tx_empty_stack]) + + # Test sendrawtransaction with P2SH + # Mathematically, this test cannot succeed since P2SH requires that the + # P2SH script is in the scriptsig (which contains H), and H much + # contain a commitment to the scriptsig. + # + # Note that this relies on RIPEMD160 being hard to find a cycle in. + # This *could* break one day (P2SH is no longer recommended), but + # there's no capability that would be gained by doing so (such as a + # recursive covenant), it'd just be a "party trick" (and you could + # also possibly steal all existing P2SH addresses). + p2sh_check_template_verify_tx = CTransaction() + p2sh_check_template_verify_tx.version = 2 + p2sh_check_template_verify_tx.vin = [ + CTxIn(p2sh_ctv_outpoint, CScript([script])) + ] + p2sh_check_template_verify_tx.vout = outputs + + assert_raises_rpc_error( + -26, + CHECKTEMPLATEVERIFY_ERROR, + self.nodes[0].sendrawtransaction, + p2sh_check_template_verify_tx.serialize().hex(), + ) + self.log.info( + "P2SH OP_CHECKTEMPLATEVERIFY spend rejected by sendrawtransaction" + ) + + # Now we verify that a block with this transaction is also invalid + self.fail_block([p2sh_check_template_verify_tx]) + self.log.info("P2SH OP_CHECKTEMPLATEVERIFY spend rejected in a block") + + self.log.info( + "Testing a congestion control tree using bare OP_CHECKTEMPLATEVERIFY" + ) + # Expand Congestion Control Tree to one specific input + out = bare_ctv_tree_outpoint + txs = [] + for level in congestion_tree_txo[1:]: + spendtx = CTransaction() + spendtx.version = 2 + spendtx.vin += [CTxIn(out)] + spendtx.vout += level[:2] + out = COutPoint(int(spendtx.rehash(), 16), 0) + txs.append(spendtx) + self.add_block(txs) + + self.log.info("Testing bare OP_CHECKTEMPLATEVERIFY with CTV at position 2") + check_template_verify_tx_pos_2 = CTransaction() + check_template_verify_tx_pos_2.version = 2 + check_template_verify_tx_pos_2.vin = [CTxIn(bare_ctv_position_2_outpoint)] + check_template_verify_tx_pos_2.vout = outputs_position_2 + self.log.info( + "Testing that the transaction fails because we have too few inputs" + ) + self.fail_block([check_template_verify_tx_pos_2]) + check_template_verify_tx_pos_2.vin += [CTxIn(bare_anyone_can_spend_outpoint)] + check_template_verify_tx_pos_2.rehash() + self.log.info( + "Testing that the transaction fails because the inputs are in the wrong order" + ) + self.fail_block([check_template_verify_tx_pos_2]) + self.log.info( + "Testing that the transaction succeeds when the inputs are in the correct order" + ) + check_template_verify_tx_pos_2.vin.reverse() + check_template_verify_tx_pos_2.rehash() + blockhash = self.add_block([check_template_verify_tx_pos_2]) + self.nodes[0].invalidateblock(blockhash) + check_template_verify_tx_pos_2.vin[0].scriptSig = CScript([OP_TRUE]) + check_template_verify_tx_pos_2.rehash() + self.log.info( + "Testing that the transaction fails because the scriptSig on the other input has been modified" + ) + self.fail_block([check_template_verify_tx_pos_2]) + + self.log.info( + "Testing bare OP_CHECKTEMPLATEVERIFY with CTV at position 1 with specific scriptSigs" + ) + check_template_verify_tx_specific_scriptSigs = CTransaction() + check_template_verify_tx_specific_scriptSigs.version = 2 + check_template_verify_tx_specific_scriptSigs.vin = [ + CTxIn(bare_ctv_specific_scriptSigs_outpoint, CScript([OP_TRUE])), + CTxIn(bare_anyone_can_spend_outpoint, CScript([OP_TRUE])), + ] + check_template_verify_tx_specific_scriptSigs.vout = outputs_specific_scriptSigs + check_template_verify_tx_specific_scriptSigs.rehash() + self.log.info( + "Testing bare OP_CHECKTEMPLATEVERIFY rejects incorrect scriptSigs" + ) + self.fail_block([check_template_verify_tx_specific_scriptSigs]) + + self.log.info("Testing bare OP_CHECKTEMPLATEVERIFY accepts correct scriptSigs") + check_template_verify_tx_specific_scriptSigs.vin[1].scriptSig = CScript( + [OP_FALSE] + ) + check_template_verify_tx_specific_scriptSigs.rehash() + blockhash = self.add_block([check_template_verify_tx_specific_scriptSigs]) + self.nodes[0].invalidateblock(blockhash) + + self.log.info( + "Testing bare OP_CHECKTEMPLATEVERIFY with CTV at position 2 with specific scriptSigs" + ) + # This is only really to test that uncached values work correctly with scriptSig set + check_template_verify_tx_specific_scriptSigs_position_2 = CTransaction() + check_template_verify_tx_specific_scriptSigs_position_2.version = 2 + check_template_verify_tx_specific_scriptSigs_position_2.vin = [ + CTxIn(bare_anyone_can_spend_outpoint, CScript([OP_TRUE])), + CTxIn( + bare_ctv_specific_scriptSigs_position_2_outpoint, CScript([OP_FALSE]) + ), + ] + check_template_verify_tx_specific_scriptSigs_position_2.vout = ( + outputs_specific_scriptSigs_position_2 + ) + check_template_verify_tx_specific_scriptSigs_position_2.rehash() + self.add_block([check_template_verify_tx_specific_scriptSigs_position_2]) + + +if __name__ == "__main__": + CheckTemplateVerifyTest(__file__).main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 4e2d670c4d4..f42d71d0e02 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -25,6 +25,7 @@ from io import BytesIO import math import random import socket +import struct import time import unittest @@ -633,6 +634,19 @@ class CTransaction: r += self.nLockTime.to_bytes(4, "little") return r + def get_standard_template_hash(self, nIn): + r = b"" + r += self.version.to_bytes(4, "little") + r += self.nLockTime.to_bytes(4, "little") + if any(inp.scriptSig for inp in self.vin): + r += sha256(b"".join(ser_string(inp.scriptSig) for inp in self.vin)) + r += struct.pack("