Hi all, I’m back from leave and catching up here.
Much of the past few weeks’ discussion about “code that writes code” misunderstands what is already practical on BCH.
Reenabling opcodes (2018) closed the 2011-era “static analysis” discussion for BCH
With regard to “static analysis”, please review Aside: the 2011-era “static analysis” debate
from April 23 and Rationale: Non-Impact on Performance or Static Analysis in the CHIP since May 2. I’ll highlight an excerpt from the CHIP:
While the Bitcoin VM initially had few limits, following the 2010 emergency patches, it could be argued that the expected runtime of a contract was (in principle) a function of the contract’s length, i.e. long contracts take longer to validate than short contracts.
In practice, expensive operations (hashing and signature checking) have always dominated other operations by many orders of magnitude, but some developers still considered it potentially useful that contracts could “in principle” be somehow “reviewed” prior to execution, limiting wasted computing resources vs. “fully evaluating” the contract.
As is now highlighted by more than a decade of disuse in node implementations and other software: such “review” would necessarily optimize for the uncommon case (an invalid transaction from a misbehaving peer) by penalizing performance in the common case (standard transactions) leading to worse overall validation performance and network throughput – even if validation cost weren’t dominated by the most expensive operations.
The 2018 restoration of disabled opcodes further reduced the plausibility of non-evaluated contract analysis by reenabling opcodes that could unpredictably branch or operate on hashes (OP_SPLIT
, bitwise operations, etc.). For example, the pattern OP_HASH256 OP_1 OP_SPLIT OP_DROP OP_0 OP_GREATERTHAN OP_IF OP_HASH256 ...
branches based on the result of a hash, obviating any further attempt to inspect without computing the hash. Note that simply “counting” OP_HASH256
operations here also isn’t meaningful: valid contracts can rely on many hashing operations (e.g. Merkle trees), and the more performance-relevant digest iteration count of any evaluation depends on the precise input lengths of hashed stack items; the existence of hash-based branching implies that such lengths cannot be predicted without a vulnerability in the hashing algorithm.
Later, the SigChecks (2020) resolved long-standing issues with the SigOps limit by counting signature checks over the course of evaluation rather than by attempting to naively parse VM bytecode. Finally, the VM limits (2025) upgrade retargeted all VM limits to use similar density-based evaluation limits.
In summary, fast validation is a fundamental and intentional feature of the VM itself – critical for the overall throughput of the Bitcoin Cash network. Hypothetical “pre-validation” of contracts never offered improved performance, would have unnecessarily complicated all implementing software, and has been further obviated by multiple protocol upgrades in the intervening years.
We already have “code that writes code”
PSA: CashVM is Turing complete within atomic transactions following the CashTokens (2023) upgrade. This was a core motivation behind PMv3 and ultimately the CashTokens (2023) upgrade – see “Proofs by Induction” and this later CashTokens announcement post.
In fact, BCH was arguably Turing complete following the 2018 upgrade (and more-practically after 2019), but lack of introspection and details in OP_CHECKSIG
's behavior caused transaction sizes to explode and quickly hit limits after a few iterations, see the illustration in “Fixed-Size Inductive Proofs”.
Anyways, with CashTokens we can now efficiently continue execution across transaction inputs. Inputs can use inspection to reference each other, and it’s very practical to create complex graphs both within and across transactions. Jedex (2022) included some of the first published examples.
So, PSA:
- Bitcoin Cash is Turing complete.
- It is already simple today to write “code that writes code”.
- It is already simple today to write code that executes “arbitrary code” provided at spend time.
Here’s a simple, practical example of “code that writes code”. This contract executes “arbitrary code” – inserted by the user’s wallet at spend time – within a single, atomic transaction. Again, this can already be done on mainnet BCH today, without any 2026 CHIPs. (Contracts and detailed comments are here, published July 1):
In the Quantumroot Schnorr + LM-OTS Vault, we defer specifying the quantum signing serialization algorithm until spending time. This maximizes privacy and efficiency:
- Vaults can support multiple serialization functions without publishing unused options to the blockchain, and
- Signing serialization algorithms and other vault behavior can be upgraded locally (via software update to the user’s wallet) without sweeping any UTXOs.
To do this, the Quantum Lock
contract 1) verifies that an appropriate quantum signature covers the quantum_signed_message
, then 2) executes quantum_signed_message
, a short script that commits to a specific transaction digest hash and then checks that the surrounding transaction matches it. This is particularly necessary because we cannot rely on the quantum-vulnerable OP_CHECKSIG
or OP_CHECKDATASIG
opcodes to extract a signing serialization, so we must construct our own via introspection to prevent a quantum attacker from malleating the transaction in the mempool.
One option to downlevel this contract for CashVM 2025 without modifying any functionality: we can setup the quantum_signed_message
in a “function output” using an instantly-spent setup transaction (note this could be funded by a “quantum hot wallet” which doesn’t defer specification of signing serialization algorithm). The Quantum Lock
output is then modified to accept an index in place of quantum_signed_message
. To extract the offloaded signing serialization + commitment, the downleveled Quantum Lock
simply inspects the P2SH redeem bytecode (OP_INPUTBYTECODE
) at the provided index (or to save bytes in this particular case, sign the P2SH OP_UTXOBYTECODE
directly, ignoring the P2SH template). Upon valid signature, the Quantum Lock
contract succeeds, delegating validation to whatever arbitrary code is included in the “function output”, all within the same atomic “execution transaction”.
Some important things to note:
-
The “arbitrary code” is specifically executed within the same, atomic transaction, not in a setup transaction. It’s easy, for example, to place something like a Zero-Confirmation Escrow on the execution transaction, and we can even build this directly into a larger DEX or decentralized application in which external contracts or behaviors rely on the atomicity of the execution transaction.
-
The invoking contract is requiring the execution of “arbitrary code” based on the result of a computation. Here it first checked a quantum signature, but it could have just as easily OP_CAT
ed-away, relying entirely on metaprogramming and equivalence: <snippet_a> <snippet_b> OP_CAT OP_SIZE OP_SWAP OP_CAT <index> OP_INPUTBYTECODE OP_EQUAL
.
-
These “function outputs” today have to self-ensure their own spend-authorization and non-malleation to avoid griefing in the mempool (e.g. here it’s quantum signed). In fact, these function outputs “work” today without such protections, unexpectedly creating real, practical security and denial-of-service issues that will only become apparent when attackers notice that the contract’s author didn’t protect those outputs in the mempool.
-
Even though the Bitcoin Cash VM supports this use case today and real products can fully rely on it, the overall interaction is wasteful (in terms of transaction bytes, fees, and nodes wasting validation resources), and the setup transaction introduces the possibility of network latency or interruption causing poor user experiences. That resulting flakiness is fine for some products (e.g. primarily-online, async, decentralized applications), while it will appear to cause real-world lag in other products (e.g. products that have to work fast and reliably in-person). If end users are ever frustrated by the lag, “the BCH network” will be reasonably blamed as “slow” or “laggy”, despite the contract working correctly from the perspective of zero-conf systems. If we want BCH to be widely used permissionless money for the world, we should aim to alleviate such impediments to practical usefulness.
Hopefully someone will find all of this to be an interesting thought experiment because:
Loops CHIP would make the above discussion irrelevant
Confusingly, some stakeholders are supportive of native loops but simultaneously cite this “code that writes code” behavior as a concern for Functions CHIP lock-in.
Above I’ve demonstrated that we don’t even need loops to write “code that writes code” on mainnet today. Obviously the activation of native loops would make these positions even more logically incompatible.
Shoutout: TurtleVM – CashVM running inside of CashVM
Related: thanks to @albaDsl for demonstrating last month how practical it is to implement a CashVM interpreter on CashVM with the TurtleVm Proof of Concept. Very cool project!
Summary
Thank you to @cculianu, @bitcoincashautist, @Jonas and others who have dedicated time and effort to examining this “code that writes code” contention over the past few weeks.
The underlying issue is a misunderstanding: BCH already supports “code that writes code” today – and all other trappings of Turing completeness.
That’s a good thing: powerful contract capabilities make Bitcoin Cash more useful as permissionless money for the world.