TurtleVm: A meta-circular evaluator for Bitcoin Cash Script (Proof of Concept)

TurtleVm is an evaluator for Bitcoin Cash Script (2025 & beyond) implemented in Bitcoin Cash Script (2025). It currently supports all the opcodes except: OP_PUSHDATA_X, OP_TOALTSTACK, OP_FROMALTSTACK, OP_CODESEPARATOR, OP_ACTIVEBYTECODE. It uses the primary stack of the underlying Bitcoin Cash VM and is therefore subject to the same limits on the size of the stack entries. It has a separate control stack with configurable size. TurtleVm is a Proof of Concept and is not fully developed and tested.

The main purpose of TurtleVm is to illustrate this point:

In practice, the difference between code and data in Bitcoin Cash Script is not clear cut.

Today P2SH contracts can accept data (byte strings) as input. They can transform/mutate data using several opcodes. Using the conditional branching opcodes, contracts can let data affect the control flow in the contract. Data affecting the control flow sounds a bit like code. If the data is made to be byte strings of Bitcoin Cash opcodes, then it also resembles code. If the contract can arrange its control flow based on these “byte strings of opcodes” in a similar way as the Bitcoin Cash VM would, so that the effect on the primary stack is the same, then the similarity with code is complete. This is how TurtleVm operates.

Due to lack of loops in BCH2025, TurtleVm’s opcode evaluator must be unrolled. Therefore TurtleVm grows with the length of the opcode sequences it should be able to evaluate. This makes it impractical/impossible to use the full TurtleVm in Mainnet transactions. However, it is possible to create versions of TurtleVm that can evaluate short programs consisting of a very limited subset of opcodes. One such limited version is MiniTurtleVm-101 which is small enough to be relayed in a transaction on Mainnet.

MiniTurtleVm-101 has the following properties:

  • P1: It can evaluate a trace of a maximum of 12 opcodes.

  • P2: It supports these opcodes:

    • OP_DATA_02 (0x02): ( – {two-byte string} )

    • OP_1 (0x51): ( – 1)

    • OP_1ADD (0x8b): ( x1 – {x1 + 1} )

    • OP_MUL (0x95): ( x1 x2 – {x1 * x2} )

    • OP_DEFINE (0x89): ( {bytes} – )

    • OP_INVOKE (0x8a): ( – {result of function evaluation} )

    • As seen above, MiniTurtleVm-101 supports OP_DEFINE/OP_INVOKE (limited versions); opcodes that are not present in the implementation language (Bitcoin Cash Script (2025)).

  • P3: The default OP_DEFINE function at VM startup is “OP_0”.

A contract using MiniTurtleVm-101 has been deployed in a P2SH transaction at:

bitcoincash:pzf0mthtyfnm8fmma9ew9n59282azuq4egu6hx6slq

with BCH 0.02 (~1 beer or so) in funds. The contract will release the funds to anyone who can provide a MiniTurtleVm-101 program satisfying the following conditions (or who can utilize any bugs in the contract):

  • C1: The program is a maximum of 9 bytes long.

  • C2: The program leaves the value 5 on the stack when finished (and nothing else).

  • C3: The program does not use the same byte/opcode twice in a row.

  • C4: The program does not use OP_1ADD right after OP_MUL.

The contract redeem script (which contains MiniTurtleVm-101) is:

8259a1697601ff7c768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b879169686768768251a27763517f527952798791697b0195876378018b8791696867686d01006b6b6c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c6375956702453100696868686868686775686c82009c636b5267517f6b517c7e68517f7c51876301007e815167750000686376529c63756c527f6b677601519c6375516776028b009c63758b67760289009c63756c6c757c6b6b6776028a009c63756c6c767b7e7c6b6b67760295009c637595670245310069686868686868677568559d51

The contract takes a single argument which is the MiniTurtleVm-101 program to evaluate.

The above P2SH contract illustrates that today’s contracts can accept “code” as input. They can also perform “code” mutation.

All of the above ties into the risk discussions around “CHIP-2025-05 Functions”. I’m for the Functions CHIP in its current formulation: https://github.com/bitjson/bch-functions (3c40074).

I’ll push the source code as part of the albaDsl project later.

4 Likes

Very creative - fun demo too (I paid it back to the same address if others want to have a go - it’s a good exercise).

FWIW, I’m also leaning in support of the CHIP-2025-05 Functions CHIP. And I think MiniTurtleVM is a very good counter to the argument against the Functions CHIP on the grounds that a Contract Dev might do something unsafe (e.g. running a program by introspecting a token commitment program):

  1. There are many other ways for a contract dev to shoot themselves in the foot anyway.
  2. MiniTurtleVM demonstrates that, if they really wanted to, they’d still be able to do it anyway (and probably with a far larger program size once we get looping?)
3 Likes

Cool that you already solved it! Your solution is 9 bytes. I was aware of this solution. I’m aware of a couple of shorter solutions too :).

Yes, there are already countless ways to shoot yourself in the foot. Every time that we extend the VM they multiply. It is a natural consequence of the script language gaining in power. Higher level languages easily rule them out by offering the right constructs to developers.

Yes, with loops there is no restriction on the size of programs that TurtleVm can evaluate, apart from the normal VM limits which all contracts are subject to.

4 Likes

I think I know one of them (a “useless” define approach), but I felt like I would’ve been cheating :sweat_smile:

1 Like

The shortest solution is nice but a bit of a trick solution since it relies on limitations in MiniTurtleVm-101.

1 Like

I published the code for TurtleVm and the MiniTurtleVM Challenge contract.

TurtleVm implementation:
https://github.com/albaDsl/alba-dsl/tree/main/src/DslDemo/TurtleVm

MiniTurtleVm101 main entry point:
https://github.com/albaDsl/alba-dsl/blob/main/src/DslDemo/TurtleVm/MiniTurtleVm101.hs

Challenge contract:
https://github.com/albaDsl/alba-dsl/blob/main/apps/contracts/miniTurtleChallenge/Contract.hs

Jimtendo’s solution:
https://github.com/albaDsl/alba-dsl/blob/9a8aa9f271fc407e20fc4fcaf5d49824fd2c7034/apps/contracts/miniTurtleChallenge/Spend.hs#L43

2 Likes

I like this work, I love people actually showing up and proving things one way or another with actual running code. Cool!

I think the latest state of the functions chip was not really in conflict with the statement you saw. So while useful to have this proven, I think it was clear to many already.

The latest state was about how the default, most used way of doing things is including a clear demand that all the code used to unlock the script is known at the time of locking the funds on the chain.
So there has to be a simple opcode that does it like that.

Naturally, people should be free to shoot themselves in the foot with low level code, because you can’t avoid such hurt if you intent to provide powerful commands.
So a second opcode that doesn’t validate the script may be useful for those minor usecases. And in case people react to the word “minor”, remember that the innovation that is shown here by TurtleVM is novel, is new and smart. So after 15 years it not being widely used is a good indication that this way of doing things is not the most used way.
Sure, we may want to support it. Why not?
But bypassing verification is not something that the majority of the usages will want to do. It wastes bytes and creates opportunities for mistakes.

As stated by @albaDsl TurtleVM isn’t even possible/practical without loops and the mini variants used in the example is only possible since the VM Limits upgrade a little over 2 months ago. It has not been used at all during the last 15 years because it isn’t possible.

On that note actually, @albaDsl assuming looping, any idea (approximately) how many bytes the full TurtleVM would use?

With loops and with the current feature set I think it would be in the 1750 - 2000 byte range. If we also rely on OP_DEFINE/OP_INVOKE we can save a few bytes here and there. Also, with OP_DEFINE/OP_INVOKE one could assign TurtleVm (or part of it) to a function slot and use it as an inefficient OP_EVAL. Possibly too inefficient to be useful in practice though.

Implementing TurtleVm on top of the suggested BCH2026 opcodes is something I plan to explore.

1 Like

Thanks Tom!

I’ll comment with my view on using script hashes in OP_DEFINE later.