I'm @salvatoshi; also check MATT for my research on Bitcoin smart contracts.
I think discussing the primitives enabled by the various opcodes is much more important than the specific details of each proposal. You decide the what, then the how is an engineering problem. Starting from the how and later figuring out the what, while it's a fun exercise, is backward.
A good soft fork proposal should enable a well-defined set of primitives, and not force people to abuse, or find tricks, in order to do what they want in Script.
A consequence is also that assessing an individual opcodes can't be separated from "which other opcodes are enabled in Script".
Therefore: if an opcode enables a primitive in high demand in a bad way, it should only be enabled in combination with other opcodes that make that primitive nice to use.
I find each of the proposed opcodes fairly weak in isolation, but there are several well-thought combinations that are possible.
Reason to add an opcode:
a) Enabling a new primitive
b) Making an existing primitive more efficient
Reasons to reject an opcode:
x) It is easily superseded by more general opcodes (not future-proof, or application-specific)
y) It is unacceptable in that it structurally changes bitcoin in undesirable way (e.g.: adding global state that nodes have to keep track of)
z) It hurts the incentives in a way that leads to miner cetralization (probably the hardest to assess).
Note: By (b), opcodes that could be superseded by more general opcodes might still be valuable if the more general opcodes would be significantly less efficient.
Primitive: "send a precommitted transaction".
This is possibly the most minimal interesting covenant, in that most of the direct applications could be done today with presigned transaction (although with interactivity requirements that might make them impractical).
Useful, but not extremely compelling on its own. It is however a great primitive in combination with other opcodes.
While more generic covenants/introspection opcodes might also enable the same functionality, OP_CTV is so optimized for what it does that it will always be substantially cheaper, therefore satisfying reason (b).
Primitive: "Sign a thing".
This is simple, has some immediate use cases on its own (e.g.: delegation), and highly composable with other opcodes.
(Arguably, OP_CHECKSIG is the less atomic opcode, as it actually means OP_PUSH_TRANSACTION_HASH_AND_THEN_CHECKSIGFROMSTACK.)
Other thoughts
As it enables equivocation on arbitrary messages (which is what BitVM uses Lamport signatures for), it would also make such constructions more efficient.
Still, off-chain state is not a complete replacement for State-carrying UTXOs and only works for certain use cases.
Primitive: "Commit to two things".
Somewhat of an unusual implementation of the primitive, which is more intuitively implemented with OP_SHA256 and OP_CAT (with some caveats); therefore, it would be almost completely redundant if OP_CAT is added. It is of course designed to provide the primitive without OP_CAT, trying to avoid the controversies due to OP_CAT.
I feel it's unnatural; you wouldn't expect this to ever be present in a low-level language.
Perhaps an OP_VECTORCOMMIT might be more future-proof: for the case with 2 elements, it is only slightly less efficient than OP_PAIRCOMMIT, while it is more efficient than a repeated OP_PAIRCOMMIT for several elements (which is not uncommon).
Primitive: "Push the taproot internal key on the stack".
(not a covenant)
Not much to say. Not particularly compelling in isolation, but it makes sense in the presence of other introspection opcodes on inputs/outputs, and definitely useful to spare a few bytes in some cases.
Primitive: "Concatenate two things".
Primitive: "Vector commitments"
Implements badly: "Generic introspection"
Implements badly: "State-carrying UTXOs"
This is a very simple primitive, extremely useful for various things (notably: vector commitments and Merkle trees), and it generally enhances the computational capability of Script.
Since it happens to enable generic introspection by accident, and therefore an ugly version of state-carrying UTXOs, it shouldn't be enabled without more ergonomic opcodes for those use cases. Otherwise people can and will litter the chain with inefficient/ugly Scripts.
Primitive: "State-carrying UTXOs."
Disclosure: this is my own proposal - I'm working on a BIP draft - current specs are here.
I believe the capability to carry arbitrary state is both:
- compelling, as you can (in a sense) build all interesting smart contracts with it
- unavoidable once you have expressive-enough introspection
In fact:
CATalone accidentally enables it anywayTXHASHalso enables it, still in a similarly inconvenient way;VAULT, despite being intended as a purpose-specific opcode, fully enables it;- Lamport signatures (possible today) kinda enable it, too, as used in BitVM.
CHECKCONTRACTVERIFY is the cleanest and most ergonomic way I could come up with to enable this primitive on taproot UTXOs. It allows to "read" a piece of data committed to an input, or "write" data to an output (while choosing its program, in the form of a taptree).
The data is committed to inside the taproot pubkey, therefore not increasing the size of the UTXO.
It also keeps the taproot keypath advantage: people involved in the UTXO always have an incentive to cooperate and spend together via the keypath (if possible), rather than using the Script.
The opcode also checks that the amount of the current input correctly "flows" to the output in the desired way (you would probably not check just the Script of an output - you also want to know how much money is going there!). The amount logic is inspired by VAULT, and in fact it makes CCV essentially a drop-in replacement for it (more below on the relationship).
Implementing state-carrying within the UTXO (like CCV does) with more 'atomic' opcodes would probably require a combination of: direct introspection of input/output scripts and amounts, INTERNALKEY, TWEAK and 64-bit arithmetics. The result might still not be very ergonomic (for example, aggregating the amount of multiple UTXOs would still be challenging).
Primitive: "Multi-step transactions"
Implements (somewhat) badly: "State-carrying UTXOs."
Vault like the ones you can build with OP_VAULT would be extremely powerful to make self-custody easier and noob-proof. Moreover, it would be a great enhancement to miniscript-based wallets like Liana, by avoiding the need for timelocks in the recovery paths: timelocks need periodic refreshing by making a transaction, while a two-stage withdrawal flow would just require a watchtower, which is very easy to fully automate in software.
However, the opcode is designed as application-specific. The primitive itself is essentially a special case of "state-carrying UTXOs"; in fact CHECKCONTRACTVERIFY strictly generalizes VAULT and can implement the same vault constructions (and some more).
Other thoughts: relationship between VAULT and CCV
Both VAULT and CCV allow to "carry some data to the next output", and also to "decide the output's Script". VAULT mutates the output by recycling the input's taptree, and replacing one of the leaves (while leaving the rest unchanged). Instead, CCV allows to replace the entire taptree.
In terms of expressive power, they essentially enable the same set of constructions. That is because in the case where the taptree is a single leaf, they are basically doing the same thing in slightly different ways. And you could always replace a taptree with several leaves with an equivalent taptree with a single leaf — just smush all the leaves together! You never really need huge taptrees in state-carrying covenants, so that's indeed feasible in practice. Of course, CCV will be more efficient for those use cases, with the gap getting larger for bigger taptrees.
Primitive: "Generic introspection"
Implements badly: "State-carrying UTXOs"
TXHASH generalizes CHECKTEMPLATEVERIFY by allowing you to build the template that you want to check, that is: what inputs/outputs, and what transaction fields you want to hash together.
Therefore, it does enable arbitrary introspection. However, I can hardly see this as a semantically meaningful primitive. While the template in CTV is precisely defined "commit to a specific transaction", most templates you'd obtain with various combinations of parameters are meaningless.
In practice, I expect most use cases of introspection to be more surgical, like "check the first output's scriptPubKey". Something like "check that the second output's amount is greater than or equal than the sum of the amounts of the first two inputs." requires using TXHASH multiple times, plus shenanigans to get the values on the stack instead of their hashes (can't add two hashes, nor compare what number is bigger if you have their hashes). This is much more awkward script than what you'd get with direct introspection opcodes like OP_AMOUNT and comparisons.
Direct introspection opcodes like OP_AMOUNT, OP_INTERNALKEY, etc, are much easier to implement (or OP_TX if it's decided that a single opcode is preferred), and would way more ergonomic for most use cases, especially if other opcodes like OP_CTV and OP_CCV already cover many of the typical use cases for introspection.
Finally, OP_TXHASH can implement state-carrying UTXOs in a very dirty way: adding additional inputs/outputs with no other meaning than "storing some state". This is ugly, inefficient, and bloats the UTXO set - and it definitely will happen if TXHASH is enabled without also enabling a clean way to carry state.
Primitive: "Eltoo/Replaceable state in layer 2s"
I find it hard to think in terms of signatures, rather than state machines and state changes. APO seems the result of an attempt to fix signatures to make them work for a specific use-case, but the end-result is hard-to-reason (for me) and not flexible.
In general, SIGHASH flags are an encoding of specific predicates on the transaction, and I think the Script is better suited to carry the predicate. There is no interesting SIGHASH flag that couldn't be functionally simulated by introspection + CHECKSIGFROMSTACK (or other Script-based approaches), and that seems to me a much cleaner and ergonomic way to achieve the same goals.
While a lot of nonsense has been spoken about how covenants would kill bitcoin (ahem, perpetual government-enforced whitelists), (z) is a valid concern: do these opcodes alter the incentive structure of bitcoin? That is, will there be MEVil? That's reason (z) for rejecting an upgrade, and I didn't discuss it for any opcode.
This is hard to assess, in the sense that the incentives might be affected by what people build with them, and listing all the things that people can build with them is of course impossible.
All the proposed opcodes do not require introspection outside the content of the transaction that is being spent. Opcodes that add introspection outside of the transaction — for example, the content of the current block, or a past block, or other explicit global state — would generally be more dubious. I'll call this class of opcodes well-behaved.
Limiting the attention to the class of such well-behaved opcodes, my thoughts are the following:
- such opcodes only add new predicates, but have negligible impact on validation cost
- any such predicate could be simulated (for example) with a cosigner that signs if and only if the predicate is true
There are many practical ways to reduce the trust assumptions required for the cosigner (federations, blinded cosigning, bonded cosigning, etc.). Practical means: they can be built today. Just, nobody bothered doing it so far, but I expect that this will eventually change.
Therefore, there is no application that is not possible today and becomes possible with covenants. Rather, some applications that are only (known to be) possible in a trusted/trust-minimized way, would become trustless with the added opcodes.
From the point of view of miners' incentives it is completely irrelevant whether an application is executed fully trustlessly or not: trust-minimized MEVil is a bad as trustless MEVil. We also have plenty of evidence from the market (especially from altcoins) that being fully trustless is not really a requirement for having demand and users.
If covenants kill bitcoin, then a trust-worthy covenant-enforcing cosigning service also kills bitcoin.
Luckily, I think bitcoin is stronger than that.
Because of the considerations of the previous paragraph, I think it is appropriate to aim for completeness, in the sense that Script should be able to represent arbitrary predicates over the transaction being spent (which is the best technical meaning I can give to permissionless innovation). In order to facilitate technical review and consensus, we should consider opcodes that are well-behaved, future-proof, and simple.
My favorite combination would be something like:
OP_CTV<--- enables "commitments to a transaction"OP_CAT<--- enables Merkle trees and other Script improvementsOP_CCV<--- enables state-carrying UTXOsOP_CSFS<--- enables delegation (and others)OP_AMOUNT<--- for the few use cases whereCCV's output amount logic is not enough
If one accepts fraud proofs as a replacement for direct computation, I don't believe there is any known interesting construction that cannot be built with these opcodes. I'd like to challenge the reader to prove me wrong!
Clarifications on the challenge
Note: it's trivial to come up with opcodes that do some computation like "check a zero-knowledge proof", or "check a BLS signature", etc. However, state-carring UTXOs + vector commitments (== MATT) enable fraud proofs for arbitrary computations, as a viable replacement on layer 2 for such computations. So instead of validity rollups, you can do optimistic rollups, and so on.
Also, this excludes any construction that uses opcodes that are not well-behaved,
Other optional opcodes like the following would be nice-to-haves, but they are about optimizing rather than adding new capabilities:
- 64-bit arithmetic (or larger). No-brainer, although there are questions about how to enable it.
OP_INTERNALKEYOP_TWEAK: would allow covenants that can 'mutate' the set of participants in the taproot internal key- other direct introspection opcodes - avoid ugly
CATtricks if introspection not covered from the above opcode is needed