BIP draft: OP_CHECKCONTRACTVERIFY #1793

pull bigspider wants to merge 3 commits into bitcoin:master from Merkleize:ccv changing 5 files +420 −0
  1. bigspider commented at 5:58 am on March 17, 2025: contributor

    Hi all,

    This is a draft for the formal specifications of the OP_CHECKCONTRACTVERIFY (CCV) opcode.

    CCV enables to build Script-based state machines that span across multiple transactions, by providing an ergonomic tool to commit to - and introspect - the Script and possibly some data that is committed inside inputs or outputs.

    Related to this PR:

    Not covered in this draft:

    • sigops budget (benchmarks needed)
    • activation logic
    • policy considerations (if any)

    I recommend delving bitcoin for high level discussions about alternative implementations, applications, etc.

  2. BIP draft for OP_CHECKCONTRACTVERIFY 4240fa8fb1
  3. bigspider force-pushed on Mar 17, 2025
  4. jonatack added the label New BIP on Mar 17, 2025
  5. in bip-ccv.mediawiki:252 in 4240fa8fb1 outdated
    247+  if target_script != P2TR(final_key):
    248+    return fail()
    249+
    250+  # Amount checks
    251+
    252+  if flags == 0:
    


    instagibbs commented at 1:02 pm on March 17, 2025:
    0  if flags == CCV_FLAG_CHECK_OUTPUT:
    

    bigspider commented at 6:09 pm on March 22, 2025:
    Done in b2199950, thanks!
  6. in bip-ccv.mediawiki:122 in 4240fa8fb1 outdated
    117+* <code><index></code> is a minimally encoded -1, or a minimally encoded non-negative integer.
    118+* <code><data></code> is a buffer of arbitrary length.
    119+
    120+In short, the semantics of the opcode with respect to the Script can be summarized as follows:
    121+
    122+  Verify that the input/output with the given <code>index</code> is a P2TR output where the public key is obtained from <code><pk></code>, tweaked with the hash of <data> (if non-empty), then taptweaked with <code><taptree</code> (if present).
    


    jirijakes commented at 10:03 am on March 18, 2025:
    This renders as a code block without wrapping ⇒ have to scroll to the right to read it. Plain text (or blockquote, if you want to emphasize) might be better.

    Sjors commented at 4:17 pm on March 18, 2025:
    The phrasing “input/output” followed by “is a P2TR output” is also confusing. Maybe just have a separate sentence to explain the input handling. And in particular whether inputs can refer to other inputs.

    bigspider commented at 6:22 pm on March 22, 2025:
    b2199950 I added the blockquote, and rephrased a bit - saying P2TR UTXO instead of the ambiguous ‘output’. Does this make it clearer?
  7. in bip-ccv.mediawiki:146 in 4240fa8fb1 outdated
    141+* If the <code><taptree></code> 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.
    142+* If the <code><pk></code> is 0, it is replaced with it is replaced with the NUMS x-only pubkey <code>0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0</code> defined in BIP-0340. If the <code><pk></code> is -1, it is replaced with the taproot internal key of the current input.
    143+* If the <code><index></code> is -1, it is replaced with the index of the current input.
    144+* If the <code><data></code> is the empty buffer, then there is no data tweak for the input/output being checked.
    145+
    146+Any other value of the parameters (except the <code><flags></code> as specified above) is invalid, and makes the opcode fail validation immediately.
    


    Sjors commented at 4:21 pm on March 18, 2025:
    I assume you mean that first flags are checked. If they’re different the transaction is valid. And only after that the other params are checked? In other words, params can be changed / added / removed in later soft forks by introducing a new flag?

    bigspider commented at 6:24 pm on March 22, 2025:
    Yes, undefined flags (which is more an extension of the opcode) are left for possible future extensions, while that seems dangerous/footgun-prone for the other parameters (which could be passed via the witness) In b2199950 I tried to make it more explicit by explicitly listing all the parameters.
  8. Sjors commented at 5:25 pm on March 18, 2025: member

    Some initial questions that come to mind:

    1. Do you really need inputs to check other inputs? What does that enable? Otherwise my sense is that evaluating each input independently of others makes life easier (but you’ll find out when implementing).

    2. Can you make it so the output ordering doesn’t matter?

    3. You bring up “vector commitments” with no further comment, but it would be good to at least briefly explain what they’re good for.

    And see inline.


    update:

    1. maybe it’s not too bad, because you’re only checking the scriptPubKey other inputs are spending
  9. in bip-ccv.mediawiki:95 in 4240fa8fb1 outdated
    90+::[[File:bip-ccv/amount_example_4.png|framed|center|alt=split and aggregate amount logic|600px]]
    91+::'''Figure 4:''' Similar to the previous example, but a second input <code>B</code> also checks the same output <code>X</code> with the <i>default</i> semantic, aggregating its input with the residual amount of the first input.
    92+
    93+-----
    94+
    95+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 be constrained with a signature, or with future introspection opcodes that allow fixing the amount.
    


    Sjors commented at 5:25 pm on March 18, 2025:
    It would be good to elaborate with an example.
  10. in bip-ccv.mediawiki:33 in 4240fa8fb1 outdated
    28+=== Motivation ===
    29+
    30+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:
    31+
    32+* 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.
    33+* <code>OP_VAULT</code><ref>[[bip-0345.mediawiki|BIP-345]]</ref> is a proposed opcode to implement a 2-step withdrawal process, enabling on-chain reactive security.
    


    Sjors commented at 5:28 pm on March 18, 2025:
    Having a vault example with OP_CCV would be useful, doesn’t have to be perfectly identical to OP_VAULT.

  11. in bip-ccv.mediawiki:34 in 4240fa8fb1 outdated
    29+
    30+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:
    31+
    32+* 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.
    33+* <code>OP_VAULT</code><ref>[[bip-0345.mediawiki|BIP-345]]</ref> is a proposed opcode to implement a 2-step withdrawal process, enabling on-chain reactive security.
    34+* <code>OP_CHECKTEMPLATEVERIFY</code><ref>[[bip-119.mediawiki|BIP-114]]</ref> is a long-proposed opcode to constrain the transaction to a ''template'' with a fixed set of outputs.
    


    Sjors commented at 5:28 pm on March 18, 2025:
    Ditto: having an example that (roughly) replicates OP_CTV would be useful
  12. in bip-ccv.mediawiki:37 in 4240fa8fb1 outdated
    32+* 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.
    33+* <code>OP_VAULT</code><ref>[[bip-0345.mediawiki|BIP-345]]</ref> is a proposed opcode to implement a 2-step withdrawal process, enabling on-chain reactive security.
    34+* <code>OP_CHECKTEMPLATEVERIFY</code><ref>[[bip-119.mediawiki|BIP-114]]</ref> is a long-proposed opcode to constrain the transaction to a ''template'' with a fixed set of outputs.
    35+* Sidechains and rollups could be implemented via a UTXO encumbered with a recursive covenant, updating the sidechain state root every time it is spent.
    36+
    37+Constructions like BitVM<ref>https://bitvm.org/</ref> try to side-step the lack of a primitive allowing UTXOs to carry state with a clever use of Lamport Signatures, and optimistic execution of smart contracts. This comes with an extremely high cost in term of complexity, interactivity, and (potentially) in block size occupation, for some of the possible execution paths. Moreover, the design of fully trustless bridges remains elusive.
    


    Sjors commented at 5:30 pm on March 18, 2025:
    Out of scope for a BIP, but maybe someone who works on BitVM can demonstrate the equivalent with OP_CCV? Presumably it doesn’t completely replace it, but makes certain aspects more compact. Do they need additional op codes? Etc.
  13. in bip-ccv.mediawiki:49 in 4240fa8fb1 outdated
    44+
    45+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.
    46+
    47+This allows to embed a commitment to the data that can be validated during the Script execution, with an ad-hoc opcode called <code>OP_CHECKCONTRACTVERIFY</code>, while staying fully compatible with taproot. Notably:
    48+* the committed data does not make the UTXO any larger;
    49+* 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;
    


    Sjors commented at 5:34 pm on March 18, 2025:
    I’m confused by what you mean with “data”, as opposed to a “program”. Does this “party” have to know the pre-image or just the hash? And why is that helpful. Hopefully the future examples can illustrate this.

    Sjors commented at 5:40 pm on March 18, 2025:

    The implementation uses the term “double tweak”. Perhaps that’s a helpful term to explain the concept better?

    And IIUC maybe use the term “single tweak” instead of “naked”.


    bigspider commented at 6:32 pm on March 22, 2025:

    I’m confused by what you mean with “data”, as opposed to a “program”. Does this “party” have to know the pre-image or just the hash? And why is that helpful. Hopefully the future examples can illustrate this.

    In b2199950 I added an additional introductory sentence in the paragraph, and also a footnote to explain how this could be used (which is anyway identical to taproot’s keypath vs script spending, anyway; the point I’m trying to make is that this feature is not lost when using Scripts with CCV, despite tampering with the internal key).

    And IIUC maybe use the term “single tweak” instead of “naked”.

    What I call the ’naked key’ is the key before any tweak. So if you have no embedded data:

    0      naked_key ==(taptweak)==> taproot output key         (where naked_key == internal key)
    

    Instead, if you have embedded data:

    0      naked_key ==(data tweak)==> internal_key ==(taptweak)==> taproot output key
    
  14. Address some comments from review b219995050
  15. Fix formatting inside the blockquote aa291c23fb
  16. in bip-ccv.mediawiki:295 in 4240fa8fb1 outdated
    290+TODO
    291+
    292+== Examples ==
    293+
    294+This section documents some common Script fragments that use <code>OP_CHECKCONTRACTVERIFY</code> for various common choices of the parameters. Depending on the use case, some of the parameters might be passed via the witness stack.
    295+In these examples, <code><></code> (empty buffer) and <code>0</code> both refer to an empty stack element.
    


    Sjors commented at 5:38 pm on March 18, 2025:
    Can you frame these examples in the context of an actual application?
  17. bigspider commented at 6:57 pm on March 22, 2025: contributor
    1. Do you really need inputs to check other inputs? What does that enable? Otherwise my sense is that evaluating each input independently of others makes life easier (but you’ll find out when implementing).

    I don’t think it would make it easier; in fact, apart from the amount semantic (amounts are not checked when you CCV-check and input), the opcode’s behavior is currently very symmetric. So in a way, I think the current formulation is simpler by not restricting to just the current input (while staying more general).

    In everything I worked on so far (even in combinations with other opcodes, e.g. see pymatt), checking just the current input is indeed sufficient. However, I think it’s likely that it would be useful in some more advanced constructions. For example, you could create a separate sentinel UTXO, and have some spending condition (possibly for many UTXOs at the same time) be only available if the sentinel UTXO is present in the transaction. Once the sentinel is spent, those spending conditions become unavailable for all the remaining. That seems similar in spirit to the connector outputs used in Ark, but without presigned transactions, so I suspect it’s useful.

    1. Can you make it so the output ordering doesn’t matter?

    You can leave flexibility as to where outputs must be by either

    • using -1 for the output index (meaning, the corresponding output must be in the same position as the input index); this would for example allow batching multiple 1-input-1-output CCV-encumbered spends (where each input must “produce” its own output, without aggregation) in the same transaction
    • passing the <index> parameter in the witness of the transaction. E.g. in the vault example, the revault index and the trigger index of the trigger_and_revault clause are arbitrary in this way.
    1. You bring up “vector commitments” with no further comment, but it would be good to at least briefly explain what they’re good for.

    I’ll add a footnote, thanks!

  18. in bip-ccv.mediawiki:137 in aa291c23fb
    132+</source>
    133+
    134+In the following, the ''current input'' is the input whose Script is being executed.
    135+
    136+The following value of the <code><flags></code> are defined:
    137+* <code>CCV_FLAG_CHECK_INPUT = -1</code>: Check an input's script; no amount check.
    


    Sjors commented at 7:36 pm on March 24, 2025:

    Most flags in Bitcoin Core are usigned integers, which seems easier to read. But I guess this is because the script interpreter uses (variable size) signed integers? On the bright side, it means you can expand the number of bit flags easily…

    When this flag is absent, I assume you don’t check the input?


    bigspider commented at 11:01 pm on March 25, 2025:

    The flag can’t be ‘absent’, as it’s the value of the flags parameter. Note that it’s not a bitmap: -1 means “check input”, 0, 1 and 2 are the three defferent amount behaviors for checking the output (default, deduct and ignore).

    Perhaps the confusion comes from calling it flags - let me know if you have suggestions for a better name.


    Sjors commented at 8:12 am on March 26, 2025:

    At least to me flags imply a bitmap.

    Since they’re all mututally exclusive, I would probably call it “mode”.

    And to prevent confusion, maybe rename the last two: CCV_MODE_CHECK_OUTPUT_IGNORE_AMOUNT and CCV_MODE_CHECK_OUTPUT_DEDUCT_AMOUNT

    Terminology aside, why not use a bitmap?

    A bitmap based scheme could do something like:

    0CCV_FLAG_INPUT = 0; // default behavior is to check output
    1CCV_FLAG_IGNORE_AMOUNT = 1; // default is to preserve, can't be combined with CCV_FLAG_INPUT or CCV_FLAG_DEDUCT_AMOUNT
    2CCV_FLAG_DEDUCT_AMOUNT = 2; // default is to preserve, can't be combined with CCV_FLAG_INPUT or CCV_FLAG_IGNORE_AMOUNT
    3CCV_FLAG_CURRENT_INPUT_TAPTREE = 4; // Merkle root of the current input's tapscript tree. Omit taptree from witness)
    4CCV_FLAG_CURRENT_INPUT_INTERNAL_KEY = 8; // taproot internal key of the current input. Omit pk from witness. Mutually exclusive with CCV_FLAG_NUMS_KEY
    5CCV_FLAG_NUMS_KEY = 16; // use NUMS point. Mutually exclusive with CCV_FLAG_CURRENT_INPUT_INTERNAL_KEY.
    6...
    

    I tend to agree it’s not pretty. Too many mutually exclusive flags, and often times you really need 3 modalities, e.g. NUMS, current input key or custom key.

  19. in bip-ccv.mediawiki:138 in aa291c23fb
    133+
    134+In the following, the ''current input'' is the input whose Script is being executed.
    135+
    136+The following value of the <code><flags></code> are defined:
    137+* <code>CCV_FLAG_CHECK_INPUT = -1</code>: Check an input's script; no amount check.
    138+* <code>CCV_FLAG_CHECK_OUTPUT = 0</code>: Check an output's script; preserve the (possibly residual) amount.
    


    Sjors commented at 7:39 pm on March 24, 2025:

    When this flag is absent, you don’t check the output?

    And CCV_FLAG_CHECK_INPUT can be used in any combination with CCV_FLAG_CHECK_INPUT, including not checking either?


    bigspider commented at 11:02 pm on March 25, 2025:
    As per this comment, the defined flags are all mutually exclusive.
  20. in bip-ccv.mediawiki:145 in aa291c23fb
    140+* <code>CCV_FLAG_DEDUCT_OUTPUT_AMOUNT = 2</code>: Check an output's script; deduct the output amount from the input's residual amount.
    141+
    142+Any other value of the <code><flags></code> makes the opcode succeed validation immediately for the current input<ref>This allows to soft-fork future behavior by introducing new values for the <code><flags></code>. As the flags would always be hard-coded via a push in the Script, the risk of mistakes seems negligible.</ref>.
    143+
    144+The following values of the other parameters have special meanings:
    145+* If the <code><taptree></code> 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.
    


    Sjors commented at 7:47 pm on March 24, 2025:
    Have you considered using flags for these special cases and then having fewer stack elements? That seems both more space efficient and probably makes scripts easier to read.

    bigspider commented at 11:15 pm on March 25, 2025:
    All the flags and the special parameters are encoded as 1-byte push opcodes, so I don’t think you can really save much by aggregating them in the flags (e.g. using extra bits that are currently in the OP_SUCCESS behavior). Moreover, I think it would be very inconvenient (both in implementation and in Script readability) if the number of stack arguments is different for different use cases of CCV.
  21. in bip-ccv.mediawiki:211 in aa291c23fb
    206+The following code is executed every time the <code>OP_CHECKCONTRACTVERIFY</code> opcode is encountered during the evalutation of a taproot Script spend. <code>this_input_index</code>, <code>this_input_internal_key</code> and <code>this_input_taptree</code> are the index, taproot internal key and taproot Merkle root of the current input.
    207+
    208+<code>BIP341_NUMS_KEY</code> is the x-only provably unspendable key <code>50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0</code> defined in [[bip-341.mediawiki|BIP-341]].
    209+
    210+<source lang="python">
    211+  if flags < CCV_FLAG_CHECK_INPUT or flags > CCV_FLAG_DEDUCT_OUTPUT_AMOUNT:
    


    Sjors commented at 7:59 pm on March 24, 2025:

    I assume you mean to check that no unknown flags are in use? This seems wrong though, because CCV_FLAG_DEDUCT_OUTPUT_AMOUNT | CCV_FLAG_IGNORE_OUTPUT_AMOUNT > CCV_FLAG_DEDUCT_OUTPUT_AMOUNT

    Probably more clear to have something like ALL_FLAGS = ... | CCV_FLAG_DEDUCT_OUTPUT_AMOUNT | CCV_FLAG_IGNORE_OUTPUT_AMOUNT | ... and check flags && !ALL_FLAGS == 0


    bigspider commented at 11:04 pm on March 25, 2025:
    Since flags are mutually exclusive and the defined values are -1, 0, 1, 2, this is just checking if you’re out of the range (then, SUCCESS as an upgrade hook to add future flags).

    Sjors commented at 8:29 am on March 26, 2025:

    See above comment, I think calling it “mode” would make this code clear:

    0if mode < CCV_FLAG_CHECK_INPUT or mode > CCV_FLAG_DEDUCT_OUTPUT_AMOUNT:
    
  22. Sjors commented at 8:17 pm on March 24, 2025: member

    Do you have any thoughts on what this could look like in terms of descriptors? Either in the general case of anything that can be expressed in miniscript, or more narrowly for something like a vault?

    E.g. fresh vault deposit addresses could be generated from something like tr(cold,{trigger_leaf}), where trigger_leaf could be something like ccv(-1,{withdraw_leaf,recover_leaf},0,...), and recover_leaf is tr(cold), etc.

    Since programs can take arbitrary data, e.g. a withdrawal address for a vault, I could imagine that when a wallet signs (or detects an unauthorized) a trigger transaction it generates a descriptor for that, so that it knows how to spend it (and when).

    For the first part the wallet software doesn’t even need to know what a vault is. For the second part it probably does need to “understand” it in order to know what descriptors to generate, and in order to prompt the user for the right action.

    But how generalisable is that?

  23. Sjors commented at 2:37 pm on March 25, 2025: member

    Do you really need inputs to check other inputs? What does that enable?

    However, I think it’s likely that it would be useful in some more advanced constructions. For example, you could create a separate sentinel UTXO,

    Just saw the mailinglist thread about this from 2023: https://gnusha.org/pi/bitcoindev/CALZpt+F251k7gSpogwFYHxFtGxc_tZjB4UU4SVEr=WvrsyMVMQ@mail.gmail.com/

    The BIP should probably address the pros and cons of “cross-input inspection”.

  24. bigspider commented at 11:52 pm on March 25, 2025: contributor

    Do you have any thoughts on what this could look like in terms of descriptors? Either in the general case of anything that can be expressed in miniscript, or more narrowly for something like a vault? @sanket1729 has given several great talks on generalizing miniscript for covenant use cases, you can look for those if you’re interested.

    My general take is that descriptors are the wrong tool for this purpose: a spend from UTXO X to UTXO Y where f(Y) = X needs to somehow encode the relation between X and Y as a predicate. While you could do that (every program is a predicate, and every program expressible in Script is a predicate that can be expressed in a tree structure like miniscript…) it quickly becomes unmanageable.

    For CCV, what works well (kinda by design) is to think in terms of states and state transitions. You can check these docs and the code examples in the pymatt repo if you’re interested in more details in how I’m framing it - the PR in bitcoin-core strips most of those useful abstractions for the sake of conciseness, but that’s certainly not how one would write those contracts in practice.

    The framework has some nice properties: once you define the contracts, all you need to know is the contract definition, and the initial parameters. Everything else can be deterministically derived from that, and the blockchain. You can scan for the initial UTXOs matching that contract, and given a transaction spending those UTXOs, you can deterministically deduce what are the next states, parameters (and data, if any) of the new UTXOs that are produced. That’s even when someone else in the contract made transactions, as it’s easy to ‘decode’ the witness to understand what clause (tapleaf) was used, and with what arguments.

    Just saw the mailinglist thread about this from 2023: https://gnusha.org/pi/bitcoindev/CALZpt+F251k7gSpogwFYHxFtGxc_tZjB4UU4SVEr=WvrsyMVMQ@mail.gmail.com/

    The BIP should probably address the pros and cons of “cross-input inspection”.

    I personally think speculation on all the possible ways people might use (and abuse) the opcode is out of scope, and it quickly gets unmanageable - because there is an infinite number of ways of (per-)using the opcode. For example, both OP_CAT and OP_TXHASH both enable fine-grained introspection of other inputs, and would therefore raise the same concern - if one thinks that is a valid concern; yet the BIPs don’t discuss this.

    IMHO that kind of discussion would be more fitting for a BIP describing a proposal to activate a certain package of opcodes as a soft-fork. OP_CCV is not meant to be activated as a stand-alone opcode (that would be kinda silly for a number of reasons). Such a BIP could in fact focus on the capabilities, ignoring the exact details of the opcodes (that are not interesting in that context).

  25. Sjors commented at 8:42 am on March 26, 2025: member

    @sanket1729 has given several great talks on generalizing miniscript for covenant use cases, you can look for those if you’re interested.

    Found one, which I’ll watch: https://www.youtube.com/watch?v=xNAn9LTzk2g

    My general take is that descriptors are the wrong tool for this purpose:

    You can check these docs and the code examples in the pymatt repo if you’re interested in more details in how I’m framing it

    I’ll look into that. With previous soft forks like SegWit and Taproot it took many years after activation for enough tooling to be developed to fully take advantage. I’m trying to get a sense of that in this case. It seems fine keep the actual Bitcoin Core implementation simpler.

    IMHO that kind of discussion would be more fitting for a BIP describing a proposal to activate a certain package of opcodes as a soft-fork

    That makes sense. My impression so far is that there exists some sort of covenant threshold beyond which we open the pandora’s box, and it doesn’t really matter how we cross the threshold.

    Perhaps there could be a BIP that discusses several combinations of op codes, explain which combinations cross the threshold, and then explain why that’s fine, or not.

    If it’s fine to cross the threshold, I tend to think we should go for the most powerful tool(s) with simplest and safest implementation.

    If it’s not fine, we would be limited to extremely restricted op codes that give us desired functionality without crossing the threshold.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bips. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-03-29 07:10 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me