A solution in search of a problem
A few years ago I was CTO of Mozak, and we built a RISC-V zkVM: write Rust (or anything that compiles to RISC-V), run it, get a zero-knowledge proof that it ran correctly. The company is dormant now, and some of our best design ideas were never written up. This post starts paying down that debt with the one I had the most trouble explaining while it mattered: how independent programs coordinate.
People did not find the design wrong; I failed to convince smart people in the field that it solved a real problem. At some point I started calling it “a solution in search of a problem” myself. This post explains the design, why I failed to sell it, and what I misunderstood.
Programs that cannot speak
Strictly speaking, a zero-knowledge proof cannot produce output. It can only accept or reject input. In a non-deterministic setting that turns out to be enough: instead of emitting a value, a program is handed a candidate value as advice and checks “yes — this is what I would have said” (or fails). Output becomes validated input. Every zkVM leans on this trick somewhere.
Our idea was to play the same trick between programs. Present every program participating in a joint action — a token transfer, say — with a shared script: an opaque record of the intended dialogue. Who called whom, with what arguments, what came back. A program accepting the script means:
If the others say what the script says they say, then I approve of what the script says I do.
Like two parties signing a contract a lawyer drafted for them, or diplomats initialling a protocol an aide prepared. Each signs only for their own part; none needs to read the others’ private reasoning. No call ever happens between programs. Each performs its part alone, in its own proof, and the parts line up because each was validated against the same script.
To the proof system the script is a black box of bytes — its meaning is whatever convention the programs share. One thing, though, is verified rather than conventional: the cast, the ordered list of programs party to the script, each named by its code hash. The verifier checks each proof, checks every seat in the cast is matched by a proof of exactly that program at exactly that position, and checks everyone was bound to the same script. It never reads what anyone said.
So: a token program insists the spending wallet approves of each transfer; a wallet approves by proving its owner signed off; each is a black box to the other; and a transaction settles when all the proofs land and the cast checks out.
“Why not just verify the proof?”
Whenever I pitched this, the counter-proposal was always the same, and always reasonable: have the token program verify the wallet’s proof inside its own execution. Proof recursion. Every major zkVM supports it, and it is the answer the field reaches for by reflex.
Recursion is sound, and for its intended job — compressing many proofs into one — we used it too. I used to answer this counter-proposal too broadly. A modern recursive verifier need not bake the wallet’s circuit into the token at authoring time: it can take a program identifier or verification key as an input. Mozak’s own recursive verifier does exactly that. An unforeseen program can therefore join a recursive construction if the surrounding policy permits it.
Recursion leaves one question unanswered: who vouches for the verification tree? A proof of a black-box user program says that this particular program ran with these public commitments. It does not give the settlement layer an independently checkable list of the proofs that program recursively verified. Perhaps it verified the wallet; perhaps it did not. Perhaps it merely emitted bytes saying that it did. To infer more, the settlement layer must understand and trust that program’s composition semantics, or constrain them through another protocol.
Pinning a trusted root program makes the inference sound, but gives one program privileged responsibility for the composition. A generic recursive accumulator can avoid fixing the child programs in advance, but it must itself verify every child and propagate a commitment to the complete participant set. That works by implementing a cast protocol recursively, not by using recursion alone as the coordination mechanism.
The script is that protocol made explicit. The transaction verifier, not any user program, checks a proof for every seat in the cast, checks that its program identifier matches that seat, and checks that all proofs carry the same cast and call-script commitments. No black-box participant is trusted to report its own recursive closure.
Now run the scenario that actually motivated us. A corporate wallet requires CEO and CTO approval, or a sign-off from compliance above some threshold — private, changing rules. Years after the token shipped, a regulator requires a screening attestation from an accredited provider for large transfers. Dynamic recursion could let the wallet verify that new provider. In the script model the provider instead joins the cast: the wallet demands its presence, while the generic transaction verifier independently checks that its proof is really there. The token program neither knows nor cares — with separate scripts, a composition mechanism the later posts have to make precise, it need not even learn compliance was involved. The participant set is decided per transaction without trusting one participant to describe the rest.
I found this argument completely convincing. The people I pitched it to mostly did not. They kept asking, gently, when a set of programs that genuinely do not trust each other, cannot read each other, and were written in ignorance of each other would ever need to agree on one atomic action. I eventually found an answer.
The one-program world
Almost everyone building zk systems is, at the proof level, running exactly one program: an interpreter. On Ethereum rollups it is the EVM; elsewhere it is some other state-transition function. The contracts people actually write live one storey up, as data that the interpreter executes. And at that level, contracts are not black boxes at all. The interpreter holds all their bytecode, executes every one of them, mediates every call, owns all the shared state. Cross-program coordination is solved by ordinary interpreter mechanics — transparently, by a single executor that sees everything. The proof at the bottom certifies one sentence: the interpreter ran correctly.
If that is your world, my problem statement is unintelligible. Mutually distrusting programs? They all trust the interpreter. Opaque counterparties? The executor reads every contract. Unforeseen third parties? Deploy another contract; the interpreter mediates. The problem does not exist — because the architecture bought its absence, and paid with a single all-seeing executor, contracts that are public to it, one monolithic execution trace, and the standing cost of proving an interpreter interpreting instead of programs running.
Mozak used no interpreter. Contracts were zkVM-native programs proved independently, so scripts supplied the coordination that an interpreter would normally provide.
Distributed systems has a name for this distinction. Orchestration:
a central conductor holds the logic and tells every participant what
to do — the EVM, exactly. Choreography: no conductor; each
participant knows its own part and stays in step by following a plan
agreed in advance. Programs on Mozak are choreographed. (The theatre
metaphor runs deeper — the participant list was literally called the
cast_list in our code — and the precise mechanics of scripts,
seats, and settlement deserve their own post.)
What refusing the interpreter buys
Everything follows from the one move. Nobody holds all the programs, so different distrusting parties prove different programs separately — a hardware signer can produce the wallet’s approval offline on Monday and a relayer can assemble the settlement on Friday without ever touching a key. Nobody sees all the programs, so counterparties stay opaque: in the finished design, a program reveals neither its code nor its private inputs, only what the script says. (The prototype proved execution; the zero-knowledge blinding itself was never built.) Mediation lives in a document rather than an executor, so the participant set stays open — the compliance program nobody foresaw joins by appearing in a cast. And there is no interpreter in the circuit to pay for: programs compile to the machine’s own instruction set and are proven directly.
The honest comparison, briefly. Bare program-directed recursion leaves the composition semantics inside an opaque root program. A generic recursive accumulator can recover open membership, but only by making the cast binding a protocol of its own. Monolithic proving (zkEVMs, Cairo) assumes a single, all-seeing executor and public contracts. The nearest cousin is Aleo, which also proves calls as separate transitions bound into a transaction — and where, as Equilibrium’s deep dive puts it plainly, “there is no composability between private applications”: one application cannot take another’s private state as input. That wall is exactly where the script begins.
Applications and unfinished work
Mutual distrust, private programs, and participants unknown in advance arise in regulated custody, cross-institution settlement, confidential governance, and private applications. Interpreter-based systems conceal this coordination problem by assigning it to an all-seeing intermediary.
That is the diagnosis, and also the apology I owe my past conversation partners: I was describing a solution to a problem my listeners’ architecture had defined away.
I am resurrecting these ideas from the dormant codebase, which is frozen, so the write-ups are landing here instead. The posts that follow will cover the mechanism in earnest: how settlement binds proofs to seats without reading anyone’s output, why “who are you in this transaction” is a subtler question than it looks, and how we made global state advance in parallel by making most operations commute. Although the prototype was never finished, I think these ideas still deserve a full account.
There is also room to continue the work. Finishing it no longer means building a zkVM: the 2026 provers supply execution proofs and recursion off the shelf, so what remains is the part that was always the point — the coordination layer, the state model, and a handful of open problems the next posts will state precisely. If picking up a good unfinished idea appeals to you, write to me.