This is a first draft implementation 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:
- Draft BIP specifications.
- Post on delving bitcoin focusing on the amount logic of the opcode.
CCV
is a general purpose opcode to build state machines based on UTXO; therefore it is not strictly tied to any specific application.
However, as vaults are probably the simplest application of such state machines, the PR also includes a functional test for a very simple (but practical) vault construction, only using CCV
. More feature-complete vaults are achievable in combinations with other opcodes.
This PR does not contain activation logic, and can’t be merged as-is, since it would make the opcode immediately active.
I’d like encourage limiting discussions to the implementation, while specs can be discussed in the BIP draft PR and external places like Delving Bitcoin.
Challenges with the cross-input amount logic
A challenge in implementing CCV
is the cross-input amount semantic: the opcode adds restrictions on the output amounts that are inherently transaction-wide.
This difficulty seems to be common to other possible changes in the working of the Script interpreter (including without new soft forks):
- adding batch validation for Schnorr signatures seems to require a similar shared state, like the
BatchSchnorrVerifier
class using a mutex in this experimental PR. Cross-Input Signature Aggregation would also have the same nature. - OP_TXHASH used a transaction-wide
TxHashCache
, guarded by a mutex. - OP_VAULT also has some deferred checks based on information that accumulated during the inputs’ Script evaluation.
In this implementation, I added a general purpose TransactionExecutionData
struct to contain persisting data that is accessible during Script evaluation. This allows the interpreter to cause a failure if either:
- an output’s amount is insufficient as per the
CCV
checks across inputs; - an output is used as a target of two
CCV
checks with incompatible amount semantic.
By the monotonic nature of these checks, it is impossible that a transaction validity is affected by the order of evaluation of the input Scripts. This is an important property for any usage of the struct in the Script interpreter.
As the operations done while the mutex is locked in CCV
are trivial, it should have a negligible impact on validation. This should, however, be validated with the appropriate benchmarks.
Alternative approaches to the implementation could be:
- a deferred checks framework like in the OP_VAULT implementation. This is functionally equivalent, but I found the approach slightly harder to implement, and requiring more boilerplate code overall.
- making the Script validation sequential for the inputs of the same transaction (that is, distributing transactions to different worker threads instead of individual inputs). This would make any present and future tx-wide semantic easier and avoid mutexes. However, this refactoring might require bigger changes to the interpreter, and should carefully be benchmarked as well.
Finally, there is a possibility that changing the semantic of CCV
somehow might make it possible to implement semantically (or practically) equivalent checks, without requiring explicit sinchronization during input execution. Here’s a gist from darosior with some early ideas about using the taproot annex for this purpose. At this time, I’m not convinced that this direction is possible without significant tradeoffs - but I have no proof of this, and look forward to your ideas.
Related links
- https://merkle.fun - entry point for the resources under the umbrella of MATT (Merkleize All The Things).
- https://github.com/Merkleize/pymatt - Python framework to explore constructions using
OP_CHECKCONTRACTVERIFY
(with or without other opcodes).