OP_CHECKCONTRACTVERIFY
BIP: 443 Layer: Consensus (soft fork) Title: OP_CHECKCONTRACTVERIFY
No reviewsSpecification
BIP: 443 Layer: Consensus (soft fork) Title: OP_CHECKCONTRACTVERIFY Authors: Salvatore IngalaStatus: Draft Type: Specification Assigned: 2025-05-08 License: BSD-3-Clause Discussion: 2022-11-08: https://gnusha.org/pi/bitcoindev/CAMhCMoH9uZPeAE_2tWH6rf0RndqV+ypjbNzazpFwFnLUpPsZ7g@mail.gmail.com 2023-07-30: https://gnusha.org/pi/bitcoindev/CAMhCMoFYF+9NL1sqKfn=ma3C_mfQv7mj2fqbqO5WXVwd6vyhLw@mail.gmail.com
Abstract
This BIP proposes to add consensus support for a new tapscript opcode that enables a new type of output restrictions:
OP_CHECKCONTRACTVERIFY (OP_CCV).
This opcode enables users to create UTXOs that carry a dynamic commitment to a piece of data. The commitment can be validated during the execution of the script, allowing introspection to the committed data. Moreover, a script can constrain the internal public key and taptree of one or more outputs, and possibly the committed data.
In conjunction with an opcode for vector commitments, this allows to create and compose arbitrary state machines that define the possible future outcomes of a UTXO. The validity of a state transition depends on the conditions that can be expressed in the program (scripts in the taptree).
Copyright
This document is licensed under the 3-clause BSD license.
Motivation
The ability to constrain the future of coins beyond what is possible with presigned transactions is at the core of numerous attempts to improve bitcoin. Some of the proposed applications include:
- UTXO sharing schemes like Ark, CoinPools, Timeout Trees, etc. use various types of output restrictions in order to enable multiple parties to share the control of a UTXO, while ensuring that each participant controls their own balance.
OP_VAULTis a proposed opcode to implement a 2-step withdrawal process, enabling on-chain reactive security.OP_CHECKTEMPLATEVERIFYis a long-proposed opcode to constrain a transaction to a template with a fixed set of outputs.- Sidechains and rollups could be implemented via a UTXO encumbered with a recursive covenant, updating the sidechain state root every time it is spent.
Rather than providing a construct optimized for a specific application, this BIP aims to provide a fundamental building block that is widely applicable, and common to many constructions.
Design
OP_CHECKCONTRACTVERIFY is an implementation of a new primitive that could be called
state-carrying UTXOs. It allows to embed a commitment to a piece of data in a UTXO, and to validate it during the
execution of the script, and carry a (possibly dynamically computed) piece of data to the new UTXOs that are
produced.
We consider the program of a P2TR UTXO to be composed of an x-only public key (that we call the naked key), and a Merkle tree of scripts. If there is no data committed in the UTXO, then the naked key is the internal key as defined in BIP-341.
If the UTXO carries a commitment to a 32-byte hash (the data), the naked key is tweaked with a hash of the data. The resulting key is the taproot internal key per BIP-341.
This allows to embed a commitment to the data that can be validated during the script execution, while staying fully compatible with taproot. Notably:
- the committed data does not make the UTXO any larger;
- the keypath spend is still available to any party that possesses the private key of the naked key, as long as they have knowledge of the embedded data (or at least the data’s hash);
- if multiple scripts are in different leaves of the taptree, only the ones that actually need to access the data have to pay a cost for it, in terms of additional witness bytes.
OP_CHECKCONTRACTVERIFYcan be applied to introspect the program and data of one of the inputs of the transaction (typically, the UTXO being spent, in order to access its committed data), or one of the outputs of the transaction (in order to define its program, and possibly its committed data).Output amounts
When checking the script of one or more outputs with
OP_CHECKCONTRACTVERIFY, it is usually necessary to also check that the amount of the current input (that is, the UTXO being spent) is correctly distributed among the outputs in the expected way. Therefore, the opcode already includes an amount semantic that covers the common use cases.There are three supported modes for the opcode when checking an output, depending on the value of the
modeparameter:- default: the residual amount of the current input must be preserved in the output (aggregate across the inputs that specify the output);
- ignore: the output amount is ignored.
- deduct: the amount of the checked output is subtracted from the amount of the current input (the residual amount is then available for further checks);
The deduct logic allows to assign portions of the input amount to one or more outputs; the residual amount, checked with a final check using the default logic, can be used to enforce that the total amount is preserved.
The following figures illustrate some common examples of supported use cases for the amount logic. This list is not exhaustive, as there are many more possible combinations.
Remark: validation fails if the amount of an output is checked with both the default and the deduct logic in the same transaction, or multiple times with the deduct logic. This prevents duplicate or inconsistent counting of the same amounts.
Remark: it is allowed for multiple inputs to check the same output with the default logic. This allows multiple inputs to aggregate (in full or in part) their amounts to the same output.
-----
::framed|center|alt=1-to-1 amount logic|600px ::Figure 1: A UTXO
Asends the entire amount to some output contractB, usingCCVwith the default semantic.-----
::framed|center|alt=3-to-1 aggregate amount logic|600px ::Figure 2: Three UTXOs aggregate their amounts towards the same output contract, using
CCVwith the default semantic.-----
::framed|center|alt=split amount logic|600px ::Figure 3: A UTXO
Asends a portion of its amount to a contractA'identical to itself, and the rest to a different contractB. It would useCCVto introspect its own input's program, then to check the first output with the deduct semantic, then to check the second output with the default semantic to assign the residual amount.-----
::framed|center|alt=split and aggregate amount logic|600px ::Figure 4: Similar to the previous example, but a second input
Balso checks the same outputXwith the default semantic, aggregating its input with the residual amount of the first input.-----
Note that the deduct semantic does not allow to check the exact amount of its output. Therefore, in contracts using a scheme similar to figure 3 or 4 above, amounts should be constrained either with a signature, or with future introspection opcodes that allow fixing the amount. In lack of that, amounts would be malleable.
Paying fees
Since the amount-checking semantics of
OP_CHECKCONTRACTVERIFYare designed to preserve the entire input amount across one or more outputs, transaction fees must be paid exogenously. This can be achieved by adding an extra input to the transaction, by using an anchor output, or with other future mechanisms.The ignore amount mode is not a safe mechanism for paying endogenous fees. An output checked with this mode has no amount constraint, which would allow a miner to claim the entire value of that input. This mode is included for forward compatibility with potential future soft forks that may introduce other amount-related logic that is compatible with
OP_CHECKCONTRACTVERIFY's script checks.Specification
The tapscript opcode
OP_SUCCESS187(0xbb) is constrained with new rules to implementOP_CHECKCONTRACTVERIFY.When evaluating
OP_CHECKCONTRACTVERIFY(OP_SUCCESS187,0xbb), the expected format of the stack, shown bottom-to-top, is:where:
is a minimally encoded integer, according to one of the values defined below.is the Merkle root of the taproot tree, or a minimally encoded-1, or the empty buffer.is called the naked key, and it's a valid 32-byte x-only public key, or a minimally encoded-1, or the empty buffer.is a minimally encoded -1, or a minimally encoded non-negative integer.is a buffer of arbitrary length.
Verify that the input/output with the given 'index' is a P2TR UTXO whose taproot output key is obtained from 'pk', tweaked with the hash of 'data' (if non-empty), then taptweaked with 'taptree' (if non-empty).
If the
is non-empty, then the additive tweak for the data is computed as:data_tweak = sha256(pk || data)
In the following, the current input is the input whose script is being executed.
The following values of the
are defined:CCV_MODE_CHECK_INPUT = -1: Check an input's script; no amount check.CCV_MODE_CHECK_OUTPUT = 0: Check an output's script; preserve the (possibly residual) amount.CCV_MODE_CHECK_OUTPUT_IGNORE_AMOUNT = 1: Check an output's script; ignore amounts.CCV_MODE_CHECK_OUTPUT_DEDUCT_AMOUNT = 2: Check an output's script; deduct the output amount from the input's residual amount.
makes the opcode succeed validation immediately for the current input.The following values of the other parameters have special meanings:
- If the
is -1, it is replaced with the Merkle root of the current input's tapscript tree. If the taptree is the empty buffer, then the taptweak is skipped. - If the
is 0, it is replaced with the NUMS x-only pubkey0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0defined in BIP-341. If theis -1, it is replaced with the taproot internal key of the current input. - If the
is -1, it is replaced with the index of the current input. - If the
is the empty buffer, then there is no data tweak for the input/output being checked.
,,orparameters is invalid, and makes the opcode immediately fail validation.Script support for
OP_CHECKCONTRACTVERIFYThe specification is divided into three parts:
- the transaction-wide initialization;
- the input initialization;
- the opcode evaluation.
taproot_tweak_pubkeyfrom BIP341, except that a regular SHA256-hash is used instead of a tagged hash, and the pubkey is returned unchanged if the length ofdatais 0.def tweak_embed_data(pubkey, data): assert len(pubkey) == 32if len(data) == 0: return None, pubkey
data_tweak = sha256(pubkey + data)
t = int_from_bytes(data_tweak) if t >= SECP256K1_ORDER: raise ValueError P = lift_x(int_from_bytes(pubkey)) if P is None: raise ValueError Q = point_add(P, point_mul(G, t)) return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q))
The
taproot_tweak_pubkeyfunction is also used as a helper in the pseudocode below.The following notations are used in the pseudocode below:
n_inputsandn_outputsare the number of inputs and outputs of the transaction, respectively;inputs[i]is the i-th input of the transaction;outputs[i]is the i-th output of the transaction;this_input_index,this_input_internal_keyandthis_input_taptreeare the index,
P2TR(key)computes the scriptPubKey of the P2TR output with the given key as the taproot output key.
Transaction-wide initialization
This is executed once for the entire transaction, before any of the transaction input's scripts are evaluated. It initializes three arrays that are used to keep track of the amount information of the output.
output_min_amount = [0] * n_outputs output_checked_default = [False] * n_outputs output_checked_deduct = [False] * n_outputs
Input initialization
This is
[Content truncated — view full spec at source]
Discussion (0 threads)
Loading discussions...