Skip to content

BIP 443: OP_CHECKCONTRACTVERIFY #1793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 16, 2025
Merged

BIP 443: OP_CHECKCONTRACTVERIFY #1793

merged 1 commit into from
May 16, 2025

Conversation

bigspider
Copy link
Contributor

@bigspider bigspider commented Mar 17, 2025

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.


In short, the semantics of the opcode with respect to the Script can be summarized as follows:

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).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

@Sjors Sjors Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b219995
I added the blockquote, and rephrased a bit - saying P2TR UTXO instead of the ambiguous 'output'. Does this make it clearer?

* If the <code><index></code> is -1, it is replaced with the index of the current input.
* If the <code><data></code> is the empty buffer, then there is no data tweak for the input/output being checked.

Any other value of the parameters (except the <code><flags></code> as specified above) is invalid, and makes the opcode fail validation immediately.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 b219995 I tried to make it more explicit by explicitly listing all the parameters.

@Sjors
Copy link
Member

Sjors commented Mar 18, 2025

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


-----

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to elaborate with an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add an example (perhaps if I add a section explaining the vault construction, it could also serve for that purpose); but I elaborated slightly more on the fact that the amount is malleable without further checks in 9bccf54.

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.
* <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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a vault example with OP_CCV would be useful, doesn't have to be perfectly identical to OP_VAULT.

Copy link
Member

@Sjors Sjors Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was a bit on the fence whether to put the code of the vault implementation in the BIP or in the bitcoin-core implementation, and ultimately I went for the latter, as functional tests are a lot more readable than dangling python scripts, or bare bitcoin Script fragments.

In general, I'm a bit concerned about focusing on the applications in the BIP, as that's extremely open-ended. I tried to focus on the programming tool that CCV introduces. It will be mostly devs reading it, after all.

I feel the CCV-only vault is in a sweet spot as it's probably the smallest interesting construction that uses all the three modes (all except the IGNORE_AMOUNT one)

Advise on the approach is welcome, though - please let me know if you would like a different approach, or some things to be covered more.

Copy link
Member

@Sjors Sjors Apr 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it a very useful example. Perhaps it's still useful to have a brief overview in the BIP itself, e.g. with the state transitions and perhaps even the taptrees and witness at each state.


* 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.
* <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.
* <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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto: having an example that (roughly) replicates OP_CTV would be useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CCV alone doesn't replicate CTV, as it misses the 'exact amounts'. It could do it with OP_AMOUNT (although, of course, CTV is extremely efficient when you just want to commit to all the outputs - plus it has txid stability, if that's needed).

In this paragraph, my intention is to just set the context and hint that 'additional output restrictions are generally useful' - not compare proposals.

* <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.
* Sidechains and rollups could be implemented via a UTXO encumbered with a recursive covenant, updating the sidechain state root every time it is spent.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


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:
* 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;
Copy link
Member

@Sjors Sjors Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

@Sjors Sjors Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 b219995 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:

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

Instead, if you have embedded data:

      naked_key ==(data tweak)==> internal_key ==(taptweak)==> taproot output key

== Examples ==

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.
In these examples, <code><></code> (empty buffer) and <code>0</code> both refer to an empty stack element.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you frame these examples in the context of an actual application?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 9bccf54 I added one- or two-sentence explainers after each example (and deleted the example with just the deduct mode, as the one right after already has a deduct check followed by a default one - which is the correct way of using it anyway).

@bigspider
Copy link
Contributor Author

  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!

In the following, the ''current input'' is the input whose Script is being executed.

The following value of the <code><flags></code> are defined:
* <code>CCV_FLAG_CHECK_INPUT = -1</code>: Check an input's script; no amount check.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

@Sjors Sjors Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

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

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.

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>.

The following values of the other parameters have special meanings:
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Sjors
Copy link
Member

Sjors commented Mar 24, 2025

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?

@Sjors
Copy link
Member

Sjors commented Mar 25, 2025

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".

@bigspider
Copy link
Contributor Author

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).

@Sjors
Copy link
Member

Sjors commented Mar 26, 2025

@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.

Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting looks mostly good, got one nit. This seems to have some action already, so I will take a look at the content when the open feedback has been processed.

Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only got up to the start of the section "Script support for OP_CHECKCONTRACTVERIFY" this time.


This BIP proposes a new tapscript opcode that adds consensus support for an opcode that enables a new type of output restrictions: <code>OP_CHECKCONTRACTVERIFY</code> (<code>OP_CCV</code>).

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 program (internal public key and taptree) and the data of one or more outputs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the programming language is also called "Script", I find the capitalization here confusing. Also, did you mean to be more specific? Do you mean an input script, output script, leaf script, etc.?

Suggested change
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 program (internal public key and taptree) and the data of one or more outputs.
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 program (internal public key and taptree) and the data of one or more outputs.

Also, in the final sentence, what do you mean with "the program"? How would an opcode that appears in a leaf script be able to affect the "internal public key"? The content of those parentheses throws up more questions than it answers, perhaps this could be clarified by defining what "the program" refers to in this context, or by using a different term for "the program" and dropping the parentheses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalization of script fixed in eb56ec5.
I rephrased in 1e12795, dropping the usage of the word 'program' and removing the parentheses.


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 program (internal public key and taptree) and the data of one or more outputs.

In conjunction with an opcode for ''vector commitments''<ref>''Vector commitments'' are cryptographic primitives that allow to commit to a vector of values via single short value. Hashing and concatenation trivially allow to commit to an entire vector, and later reveal all of its elements. Merkle trees are among the simplest efficient vector commitments, allowing to reveal individual elements with logarithmically-sized proofs.</ref>, 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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In conjunction with an opcode for ''vector commitments''<ref>''Vector commitments'' are cryptographic primitives that allow to commit to a vector of values via single short value. Hashing and concatenation trivially allow to commit to an entire vector, and later reveal all of its elements. Merkle trees are among the simplest efficient vector commitments, allowing to reveal individual elements with logarithmically-sized proofs.</ref>, 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).
In conjunction with an opcode for ''vector commitments''<ref>''Vector commitments'' are cryptographic primitives that allow to commit to a vector of values via a single short value. Hashing and concatenation trivially allow to commit to an entire vector, and later reveal all of its elements. Merkle trees are among the simplest efficient vector commitments, allowing to reveal individual elements with logarithmically-sized proofs.</ref>, 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).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be easier to provide detailed suggestions if the lines were broken to a shorter length, e.g., 120 characters, instead of whole paragraphs in one line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eb56ec5: fixed capitalization of 'script', and fixed typo in 808fa44.

Is it standard to split to 120 characters? That would make it harder to adapt the text to different layouts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was suggesting that the "MediaWiki source code" of your document should be line broken. This would not affect the formatting of the rendered document, but just the presentation of diffs between changes to this pull request and the amount of text that suggestions need to work with.

I’ve explained my suggestion in more detail on another PR before


== Specification ==

The tapscript opcode <code>OP_SUCCESS187</code> (<code>0xbb</code>) is constrained with new rules to implement <code>OP_CHECKCONTRACTVERIFY</code>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that BIP 345: OP_VAULT also proposes to use OP_SUCCESS187.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality-wise, OP_CCV is strictly more general than OP_VAULT/OP_VAULT_RECOVER*, so I don't think there is possibility of deployment conflict.

[*] this statement assumes that an opcode suitable for vector commitments (like OP_CAT or OP_PAIRCOMMIT) is deployed together with OP_CCV; there might be dirty tricks to make it true (inefficiently) with CCV alone, but that's not an interesting thing to do.
Similar ugly tricks would allow to inefficiently simulate CCV's functionality with OP_VAULT - so if one foregoes efficiency considerations, they are equivalent.

Comment on lines 106 to 112
<source>
<mode>
<taptree>
<pk>
<index>
<data>
</source>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIA, the content of the stack is usually written in one line bottom to top:

Suggested change
<source>
<mode>
<taptree>
<pk>
<index>
<data>
</source>
<source>
<data> <index> <pk> <taptree> <mode>
</source>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2655b8b (I was incorrectly saying 'top to bottom' when it was already 'bottom to top'. <data> is the top (as it's the most convenient place for something that is usually either forwarded through the witness, or computed).

data_tweak = sha256(pk || data)
</source>

In the following, the ''current input'' is the input whose Script is being executed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Current input" was used above without an introduction of the term. Please introduce the term on first use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2655b8b.

Copy link
Contributor Author

@bigspider bigspider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed most of the comments from @murchandamus (sorry for the delay, I didn't realize the review comments were left as 'Pending').


When checking the Script of one or more output with <code>OP_CHECKCONTRACTVERIFY</code>, it is usually necessary to also check that the amount of the current UTXO 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 <code>mode</code> parameter:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CCV can check the Script of either an input or an output of the transaction. Amount checking is only relevant when the mode is one that refers to an output (0, 1 or 2). Here 'checking an output' refers to the checks that are done in the interpreter when a CCV opcode is executed in one of the modes referring to an output.

Comment on lines 71 to 73
'''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 to check 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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It make more sense with CCV to consider each UTXO as an instance of a contract; so any logic to make multiple UTXOs behave as if they belong to the same logical 'wallet' needs to be explicitly programmed, and each UTXO/input explicitly specifies what must happen to its amount.

Note that CCV cannot enforce a specific proportion of the amount, but only that the amount of the inputs is accounted for in the output. So you cannot force an equal split without additional introspection opcodes (e.g. combine with a future OP_AMOUNT opcode, or OP_CTV/TXHASH, etc.).

Without additional opcodes, not sure if the deduct semantic is useful outside of vault/recovery use cases. Together with OP_AMOUNT, it could for example be used to withdraw a certain amount from a shared UTXO, leaving the rest in a new shared UTXO.


== Specification ==

The tapscript opcode <code>OP_SUCCESS187</code> (<code>0xbb</code>) is constrained with new rules to implement <code>OP_CHECKCONTRACTVERIFY</code>.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality-wise, OP_CCV is strictly more general than OP_VAULT/OP_VAULT_RECOVER*, so I don't think there is possibility of deployment conflict.

[*] this statement assumes that an opcode suitable for vector commitments (like OP_CAT or OP_PAIRCOMMIT) is deployed together with OP_CCV; there might be dirty tricks to make it true (inefficiently) with CCV alone, but that's not an interesting thing to do.
Similar ugly tricks would allow to inefficiently simulate CCV's functionality with OP_VAULT - so if one foregoes efficiency considerations, they are equivalent.


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 program (internal public key and taptree) and the data of one or more outputs.

In conjunction with an opcode for ''vector commitments''<ref>''Vector commitments'' are cryptographic primitives that allow to commit to a vector of values via single short value. Hashing and concatenation trivially allow to commit to an entire vector, and later reveal all of its elements. Merkle trees are among the simplest efficient vector commitments, allowing to reveal individual elements with logarithmically-sized proofs.</ref>, 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).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eb56ec5: fixed capitalization of 'script', and fixed typo in 808fa44.

Is it standard to split to 120 characters? That would make it harder to adapt the text to different layouts.


This BIP proposes a new tapscript opcode that adds consensus support for an opcode that enables a new type of output restrictions: <code>OP_CHECKCONTRACTVERIFY</code> (<code>OP_CCV</code>).

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 program (internal public key and taptree) and the data of one or more outputs.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalization of script fixed in eb56ec5.
I rephrased in 1e12795, dropping the usage of the word 'program' and removing the parentheses.

Comment on lines 106 to 112
<source>
<mode>
<taptree>
<pk>
<index>
<data>
</source>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2655b8b (I was incorrectly saying 'top to bottom' when it was already 'bottom to top'. <data> is the top (as it's the most convenient place for something that is usually either forwarded through the witness, or computed).

data_tweak = sha256(pk || data)
</source>

In the following, the ''current input'' is the input whose Script is being executed.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2655b8b.

The following values of the other parameters have special meanings:
* 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.
* If the <code><pk></code> is 0, 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.
* If the <code><index></code> is -1, it is replaced with the index of the current input.
Copy link
Contributor

@Christewart Christewart May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the usage of -1 as default arguments for several reasons

  1. It opens up malleability vectors. IIUC this proposal correctly, we have 3 different usages of -1 on the witness stack. This means we have 8 different combinations of potential valid values on the witness stack. I've pasted a table below of the different malleability combinations
  2. Its not composable with other index based opcodes. Other index based opcodes would need to adopt your -1 semantic to be composable with OP_CCV.
Combination # Taptree PK IDX
1 taptree pk idx
2 taptree pk -1
3 taptree -1 idx
4 taptree -1 -1
5 -1 pk idx
6 -1 pk -1
7 -1 -1 idx
8 -1 -1 -1

If you remove the -1 semantic you would get rid of malleability potential and make OP_CCV more composable with future index based opcodes (OP_INOUT_AMOUNT, and various elements project opcodes)

This has the tradeoff of increasing witness size, but I think the tradeoff is worth it for the benefit of composability. These values can be useful for other opcodes other than OP_CCV during script validation! :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It opens up malleability vectors. IIUC this proposal correctly, we have 3 different usages of -1 on the witness stack. This means we have 8 different combinations of potential valid values on the witness stack. I've pasted a table below of the different malleability combinations

In the vast majority of usages, all those arguments would not be passed via the witness stack. The only case where I used an index passed via the witness stack, so far, was for index argument in vaults, in order to make them more ergonomic (in that you have more freedom in how to compose your transactions).

If a script takes those arguments from the witness, then it's up to the script designer to deal with malleability, possibly by adding additional constraints (like a CHECKSIG) in order to avoid it.

Remark: I don't it would ever make sense for pk or taptree to directly come from the witness; in some case, it could come from a computation that depends on the witness arguments - for example, as the result of opening a commitment, which implicitly deals with malleability,

  1. Its not composable with other index based opcodes. Other index based opcodes would need to adopt your -1 semantic to be composable with OP_CCV.

Do you have a concrete example of lack of composability?

If you remove the -1 semantic you would get rid of malleability potential and make OP_CCV more composable with future index based opcodes (OP_INOUT_AMOUNT, and various elements project opcodes)
This has the tradeoff of increasing witness size, but I think the tradeoff is worth it for the benefit of composability. These values can be useful for other opcodes other than OP_CCV during script validation! :-)

The -1 semantic is extremely common and dramatically optimizes the script sizes.

The CCV_MODE_CHECK_OUTPUT_IGNORE_AMOUNT mode is there precisely to future-proof the opcode and make it composable with any other amount introspection - even if I don't currently have a use case for it.

It's unclear to me how removing a feature could make the opcode more composable. If you don't need the feature for your script, you can simply not use it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll reply on delving or the mailing list as this probably isn't the best place for posting long form results and to start the conversation about potentially use design changes. Apologies for starting the conversation here and not a more appropriate venue. I should have something ready in the next few days.

@jamesob jamesob mentioned this pull request May 6, 2025
Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a full review, more of a quick check of what has happened here lately and a skim of the changes based on my prior review.

I see a few todos in the document, could you describe in a couple sentences how you see the status of this document?


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 program (internal public key and taptree) and the data of one or more outputs.

In conjunction with an opcode for ''vector commitments''<ref>''Vector commitments'' are cryptographic primitives that allow to commit to a vector of values via single short value. Hashing and concatenation trivially allow to commit to an entire vector, and later reveal all of its elements. Merkle trees are among the simplest efficient vector commitments, allowing to reveal individual elements with logarithmically-sized proofs.</ref>, 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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was suggesting that the "MediaWiki source code" of your document should be line broken. This would not affect the formatting of the rendered document, but just the presentation of diffs between changes to this pull request and the amount of text that suggestions need to work with.

I’ve explained my suggestion in more detail on another PR before

@murchandamus
Copy link
Contributor

Let’s refer to this proposal as BIP 443. Could you please rename the file, add an entry to the table, and update the number and created date in the preamble?

@murchandamus murchandamus changed the title BIP draft: OP_CHECKCONTRACTVERIFY BIP 443: OP_CHECKCONTRACTVERIFY May 8, 2025
@bigspider
Copy link
Contributor Author

Updated header per number assignment, and split the long lines in bec994f; however, that breaks for lists, so I partially undid it c950a1e.

@murchandamus would it be a good time to squash all commits? I think there are no unaddressed review comments at this time.

@bigspider
Copy link
Contributor Author

I see a few todos in the document, could you describe in a couple sentences how you see the status of this document?

Regarding the TODOs for the sigops budget and policy changed: they both deserve more thought at this stage - so I think it's worth keeping the TODO as a reminder.

  • sigops budget: proper benchmarks are necessary to assess how to price it properly; a slight concern is that some natural looking scripts might have very few witness bytes, so they might run out of the sigops budget if a linear pricing is used (unless one stuffs more bytes in the witness just for the sake of gaining more budget). Also, some evaluations of CCV are cheaper and it might be worth pricing them accordingly.
  • Policy changes: BIP-345 included some policy changes to make some Scripts that do not use signatures (as the covenant already enforces the required constraints) compatible with the relay policies. It would be worth assessing whether those same considerations apply to CCV, and analogous or more generalized standardness rules would make certain signature-less scripts using CCV useful in practice.

@murchandamus
Copy link
Contributor

@murchandamus would it be a good time to squash all commits? I think there are no unaddressed review comments at this time.

Sure that sounds reasonable

@bigspider
Copy link
Contributor Author

Squashed all commits and rebased.

Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, this will still need to be amended in a few details to be ready for Proposed, but looks ready to be merged as Draft.

@murchandamus murchandamus merged commit 3b76d78 into bitcoin:master May 16, 2025
4 checks passed
@Sjors
Copy link
Member

Sjors commented May 21, 2025

Can you expand the BIP to mention that fees have to be paid exogenously?#1793 (comment)

That said, a mode similar to default that allows any amount to be missing as long as it goes to fees, could be useful.

In particular there's a privacy advantage when you don't have to use coins from some other wallet to initiate and/or cancel a vault withdrawal.

The trade-off there is that an attacker can (threaten to) burn your wallet, but they can't take it.

@jonatack
Copy link
Member

jonatack commented Jun 2, 2025

Can you expand the BIP to mention that fees have to be paid exogenously?#1793 (comment)

That said, a mode similar to default that allows any amount to be missing as long as it goes to fees, could be useful.

In particular there's a privacy advantage when you don't have to use coins from some other wallet to initiate and/or cancel a vault withdrawal.

The trade-off there is that an attacker can (threaten to) burn your wallet, but they can't take it.

@bigspider would you like to follow up on @Sjors' feedback?

@bigspider
Copy link
Contributor Author

Can you expand the BIP to mention that fees have to be paid exogenously?#1793 (comment)

I can add a comment about it if you think it's useful. However, I believe this is not specific to CCV: for any covenant that constrains an output Script, it would be highly unusual to not constrain amounts, as sending 0 to an output is equivalent to not having a covenant at all. CTV and VAULT reached the same conclusion.

That said, a mode similar to default that allows any amount to be missing as long as it goes to fees, could be useful.

In particular there's a privacy advantage when you don't have to use coins from some other wallet to initiate and/or cancel a vault withdrawal.

The trade-off there is that an attacker can (threaten to) burn your wallet, but they can't take it.

If the attacker is a miner, they can take the money. I'm not sure it would be a good idea to enshrine in the opcode a semantic that is inherently vulnerable to miner's theft.

Besides, I don't see a way of defining a semantic that is not ambiguous, once you take other inputs into account: it wouldn't be unequivocally determined whose amount is 'going' to fees, and whose is 'going' to the corresponding output.

@Sjors
Copy link
Member

Sjors commented Jun 3, 2025

If the attacker is a miner, they can take the money. I'm not sure it would be a good idea to enshrine in the opcode a semantic that is inherently vulnerable to miner's theft.

Or colludes with a miner. One could even imagine a malevolent accelerator that pays out its users half the fee for any transaction wasn't already in their mempool.

I do think it's good to clarify this, since not everyone reading this BIP will have read CTV and VAULT.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants