← Back to Bitcoin Improvement Proposals
BIP 119specificationDraftopcodestransactionsscriptsigning

CHECKTEMPLATEVERIFY

BIP: 119 Layer: Consensus (soft fork) Title: CHECKTEMPLATEVERIFY

No reviews
Jeremy Rubin·Updated Mar 29, 2026·0 reviews·0 attestations·View source
Collections:BIPs — Merged

Specification

  BIP: 119
  Layer: Consensus (soft fork)
  Title: CHECKTEMPLATEVERIFY
  Authors: Jeremy Rubin 
  Status: Draft
  Type: Specification
  Assigned: 2020-01-06
  License: BSD-3-Clause

Abstract

This BIP proposes a new opcode, OP_CHECKTEMPLATEVERIFY, to be activated as a change to the semantics of OP_NOP4.

Summary

OP_CHECKTEMPLATEVERIFY uses opcode OP_NOP4 (0xb3) as a soft fork upgrade.

OP_CHECKTEMPLATEVERIFY does the following:

  • There is at least one element on the stack, fail otherwise
  • The element on the stack is 32 bytes long, NOP otherwise
  • The DefaultCheckTemplateVerifyHash of the transaction at the current input index is equal to the element on the stack, fail otherwise
The DefaultCheckTemplateVerifyHash commits to the serialized version, locktime, scriptSigs hash (if any non-null scriptSigs), number of inputs, sequences hash, number of outputs, outputs hash, and currently executing input index.

The recommended standardness rules additionally:

  • Reject non-32 byte as SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS.

Motivation

This BIP introduces a transaction template, a simple spending restriction that pattern matches a transaction against a hashed transaction specification. OP_CHECKTEMPLATEVERIFY reduces many of the trust, interactivity, and storage requirements inherent with the use of pre-signing in applications. For more details on applications, please see the references.

Detailed Specification

The below code is the main logic for verifying CHECKTEMPLATEVERIFY, described in pythonic pseudocode. The canonical specification for the semantics of OP_CHECKTEMPLATEVERIFY as implemented in C++ in the context of Bitcoin Core can be seen in the reference implementation.

The execution of the opcode is as follows:

def execute_bip_119(self):
    # Before soft-fork activation / failed activation
    # continue to treat as NOP4
    if not self.flags.script_verify_default_check_template_verify_hash:
        # Potentially set for node-local policy to discourage premature use
        if self.flags.script_verify_discourage_upgradable_nops:
            return self.errors_with(errors.script_err_discourage_upgradable_nops)
        return self.return_as_nop()

# CTV always requires at least one stack argument if len(self.stack) < 1: return self.errors_with(errors.script_err_invalid_stack_operation)

# CTV only verifies the hash against a 32 byte argument if len(self.stack[-1]) == 32: # Ensure the precomputed data required for anti-DoS is available, # or cache it on first use if self.context.precomputed_ctv_data == None: self.context.precomputed_ctv_data = self.context.tx.get_default_check_template_precomputed_data()

# If the hashes do not match, return error if stack[-1] != self.context.tx.get_default_check_template_hash(self.context.nIn, self.context.precomputed_ctv_data): return self.errors_with(errors.script_err_template_mismatch)

return self.return_as_nop()

# future upgrade can add semantics for this opcode with different length args # so discourage use when applicable if self.flags.script_verify_discourage_upgradable_nops: return self.errors_with(errors.script_err_discourage_upgradable_nops) else: return self.return_as_nop()

The computation of this hash can be implemented as specified below (where self is the transaction type). Care must be taken that in any validation context, the precomputed data must be initialized to prevent Denial-of-Service attacks. Any implementation *must* cache these parts of the hash computation to avoid quadratic hashing DoS. All variable length computations must be precomputed including hashes of the scriptsigs, sequences, and outputs. See the section "Denial of Service and Validation Costs" below. This is not a performance optimization.

def ser_compact_size(l): r = b"" if l < 253: # Serialize as unsigned char r = struct.pack("B", l) elif l < 0x10000: # Serialize as unsigned char 253 followed by unsigned 2 byte integer (little endian) r = struct.pack("

def ser_string(s): return ser_compact_size(len(s)) + s

class CTxOut: def serialize(self): r = b"" # serialize as signed 8 byte integer (little endian) r += struct.pack("

def get_default_check_template_precomputed_data(self): result = {} # If there are no scriptSigs we do not need to precompute a hash if any(inp.scriptSig for inp in self.vin): result["scriptSigs"] = sha256(b"".join(ser_string(inp.scriptSig) for inp in self.vin)) # The same value is also pre-computed for and defined in BIP-341 and can be shared. # each nSequence is packed as 4 byte unsigned integer (little endian) result["sequences"] = sha256(b"".join(struct.pack("

# parameter precomputed must be passed in for DoS resistance def get_default_check_template_hash(self, nIn, precomputed = None): if precomputed == None: precomputed = self.get_default_check_template_precomputed_data() r = b"" # Serialize as 4 byte signed integer (little endian) r += struct.pack("

A PayToBareDefaultCheckTemplateVerifyHash output matches the following template:

# Extra-fast test for pay-to-basic-standard-template CScripts:
def is_pay_to_bare_default_check_template_verify_hash(self):
    return len(self) 

34 and self[0]

0x20 and self[-1] == OP_CHECKTEMPLATEVERIFY

Deployment

Activation logic is elided from this BIP and is more appropriately discussed elsewhere.

Until BIP-119 reaches ACTIVE state and the SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH flag is enforced, node implementations should (are recommended to) execute a NOP4 as SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS (to deny entry to the mempool) for policy and must evaluate as a NOP for consensus (during block validation).

In order to facilitate using CHECKTEMPLATEVERIFY, the common case of a PayToBareDefaultCheckTemplateVerifyHash with no scriptSig data may (is recommended to) be made standard to permit relaying. Future bare scripts may be standardized later as policy changes at the preference of the implementer.

Reference Implementation

A reference implementation and tests are available here in the PR to Bitcoin Core https://github.com/bitcoin/bitcoin/pull/21702.

It is not ideal to link to a PR, as it may be rebased and changed, but it is the best place to find the current implementation and review comments of others. A recent commit hash in that PR including tests and vectors can be found here https://github.com/jeremyrubin/bitcoin/commit/3109df5616796282786706738994a5b97b8a5a38. Once the PR is merged, this BIP should be updated to point to the specific code released.

Test vectors are available in [/bip-0119/vectors the bip-0119/vectors directory] for checking compatibility with the reference implementation and BIP.

Rationale

OP_CHECKTEMPLATEVERIFY's design is a small code change and simple to analyze. It is compatible with future upgrades if new template types are required for more complex but demonstrably safe use cases.

Below we'll discuss the rules one-by-one:

The DefaultCheckTemplateVerifyHash of the transaction at the current input index matches the top of the stack

The set of data committed to is a superset of data which can impact the TXID of the transaction, other than the inputs. This ensures that for a given known input, the TXIDs can also be known ahead of time. Otherwise, CHECKTEMPLATEVERIFY would not be usable for Batched Channel Creation constructions as the redemption TXID could be malleated and pre-signed transactions invalidated, unless the channels are built using an LN-Symmetry-like protocol. Note that there may be other types of pre-signed contracts that may or may not be able to use LN-Symmetry-like constructs, therefore making TXIDs predictable makes CTV more composable with arbitrary sub-protocols.

=Committing to the version and locktime

=

Were these values not committed, it would be possible to delay the spending of an output arbitrarily as well as possible to change the TXID.

Committing these values, rather than restricting them to specific values, is more flexible as it permits users of CHECKTEMPLATEVERIFY to set the version and locktime as they please.

=Committing to the ScriptSigs Hash

=

The scriptsig in a segwit transaction must be exactly empty, unless it is a P2SH segwit transaction in which case it must be only the exact redeemscript. P2SH is incompatible (unless the P2SH hash is broken) with CHECKTEMPLATEVERIFY because the template hash must commit to the ScriptSig, which must contain the redeemscript, which is a hash cycle.

To prevent malleability when not using a segwit input, we also commit to the scriptsig. This makes it possible to use a 2 input CHECKTEMPLATEVERIFY with a legacy pre-signed spend, as long as the exact scriptsig for the legacy output is committed. This is more robust than simply disallowing any scriptSig to be set with CHECKTEMPLATEVERIFY.

If no scriptSigs are set in the transaction, there is no purpose in hashing the data or including it in the DefaultCheckTemplateVerifyHash, so we elide it. It is expected to be common that no scriptSigs will be set as segwit mandates that the scriptSig must be empty (to avoid malleability).

We commit to the hash rather than the values themselves as this is already precomputed for each transaction to optimize SIGHASH_ALL signatures.

Committing to the hash additionally makes it simpler to construct DefaultCheckTemplateVerifyHash safely and unambiguously from script.

=Committing to the number of inputs

=

If we allow more than one input to be spent in the transaction then it would be possible for two outputs to request payment to the same set of outputs, resulting in half the intended payments being discarded, the "half-spend" problem.

Furthermore, the restriction on which inputs can be co-spent is critical for payments-channel constructs where a stable TXID is a requirement (updates would need to be signed on all combinations of inputs).

However, there are legitimate use cases for allowing multiple inputs. For example:

Script paths:

Path A: <+24 hours> OP_CHECKSEQUENCEVERIFY OP_CHECKTEMPLATEVERIFY Path B: OP_CHECKTEMPLATEVERIFY

In this case, there are 24 hours for the output to, with the addition of a second output, pay Bob 2 BTC. If 24 hours lapses, then Alice may redeem her 1 BTC from the contract. Both input UTXOs may have the exact same Path B, or only one.

The issue with these constructs is that there are N! orders that the inputs can be ordered in and it's not generally possible to restrict the ordering.

CHECKTEMPLATEVERIFY allows for users to guarantee the exact number of inputs being spent. In general, using CHECKTEMPLATEVERIFY with more than one input is difficult and exposes subtle issues, so multiple inputs should not be used except in specific applications.

In principle, committing to the Sequences Hash (below) implicitly commits to the number of inputs, making this field strictly redundant. However, separately committing to this number makes it easier to construct DefaultCheckTemplateVerifyHash from script.

We treat the number of inputs as a `uint32_t` because Bitcoin's consensus decoding logic limits vectors to `MAX_SIZE=33554432` and that is larger than `uint16_t` and smaller than `uint32_t`. 32 bits is also friendly for manipulation using Bitcoin's current math opcodes, should `OP_CAT` be added. Note that the max inputs in a block is further restricted by the block size to around 25,000, which would fit into a `uint16_t`, but that is an unnecessary abstraction leak.

=Committing to the Sequences Hash

=

If we don't commit to the sequences, then the TXID can be malleated. This also allows us to enforce a relative sequence lock without an OP_CSV. It is insufficient to just pair CHECKTEMPLATEVERIFY with OP_CSV because OP_CSV enforces a minimum nSequence value, not a literal value.

We commit to the hash rather than the values themselves as this is already precomputed for each transaction to optimize SIGHASH_ALL signatures.

Committing to the hash additionally makes it simpler to construct DefaultCheckTemplateVerifyHash safely and unambiguously from script.

=Committing to the Number of Outputs

=

In principle, committing to the Outputs Hash (below) implicitly commits to the number of outputs, making this field strictly redundant. However, separately committing to this number makes it easier to construct DefaultCheckTemplateVerifyHash from script.

We treat the number of outputs as a `uint32_t` because a `COutpoint` index is a `uint32_t`. Further, Bitcoin's consensus decoding logic limits vectors to `MAX_SIZE=33554432` and that is larger than `uint16_t` and smaller than `uint32_t`. 32 bits is also friendly for manipulation using Bitcoin's current math opcodes, should `OP_CAT` be added.

=Committing to the outputs hash

=

This ensures that spending the UTXO is guaranteed to create the exact outputs requested.

We commit to the hash rather than the values themselves as this is already precomputed for each transaction to optimize SIGHASH_ALL signatures.

Committing to the hash additionally makes it simpler to construct DefaultCheckTemplateVerifyHash safely and unambiguously from script.

=Committing to the current input's index

=

Committ

[Content truncatedview full spec at source]

Discussion (0 threads)

Loading discussions...