When the Proof Finally Met the Cell

Gm,

A week ago, I published
The Proof Is Valid. The Transition Might Not Be,
an update covering Weeks 7–9 of my Noir-to-CKB work.

That article followed the project from a minimal Noir circuit to a development Groth16 proof and then across the serialization boundary into the format expected by groth16-ckb. It also documented the uncomfortable result that shaped the project: successful proof verification does not, by itself, prove that a backend preserved Noir’s intended public interface, or that the proof describes the CKB transition currently being authorized.

The article ended at an important but incomplete milestone. I could verify the same supported proof consistently across snarkjs, arkworks, and the pinned CKB host implementation.

But the proof had not yet met a CKB transaction.

That was the focus of Week 10.

This week, I ran a Noir-derived Groth16 proof through the production RISC-V verifier inside CKB-VM, then added a separate Type Script that checks whether the proof’s public statement describes the exact Cell transition being validated.

The result is my first working proof-bound Capsule prototype:

valid proof + correct Capsule transition -> accept
valid proof + wrong Capsule transition   -> reject

By the end of Week 9, the project had crossed the artifact boundary.

The adapter could take a supported Groth16 verification key, proof, and ordered public-input vector; validate them with arkworks; serialize them canonically; and encode the Molecule payloads expected by the existing CKB verifier.

This answered an interoperability question:

Can these libraries agree on the same proof, key, public values, and bytes?

For the retained fixture, the answer was yes.

It did not yet answer the CKB application question:

Do those public values describe the Cells that this transaction is actually consuming and creating?

A generic proof verifier cannot answer that by itself. It sees a verification key, a proof, and public inputs. The transaction contains the application state.

Week 10 connected the two.

The Transaction Now Has Two Independent Checks

The prototype uses two cooperating CKB scripts with different responsibilities.

The existing groth16-ckb verifier is used as the Capsule Cell’s lock script. It checks that the Groth16 proof is mathematically valid under the verification key committed by the lock arguments.

A new capsule-binding Type Script checks the application meaning. It reads the actual input Cell, output Cell, and Type Script arguments, then derives the public statement that this transaction is supposed to represent.

For the current fixture, that ordered statement is:

[
  capsule_id,
  old_state_commitment,
  old_nullifier,
  new_state_commitment,
  action_id,
  new_nullifier,
  replay_domain,
]

The Type Script compares those transaction-derived values with the public inputs carried beside the proof.

So the scripts answer two different questions:

Groth16 lock:
Is this proof valid for this public statement?

Capsule Type Script:
Is this public statement about this exact Cell transition?

The transaction succeeds only when both answers are yes.

A Small Circuit With a Bigger Job

I replaced the earlier square-root control with a transition-aware Noir fixture. It has seven public fields and one private development witness.

Field Development value
Capsule ID 11
Old state commitment 65
Old nullifier 5
New state commitment 66
Action ID 1
New nullifier 96
Replay domain 13
Private authorization witness 7

The values and arithmetic are intentionally small. This is not a final commitment, nullifier, or authorization design. The purpose of the fixture is to make every application field part of the proved statement and to let each field be changed independently during negative testing.

Before generating the proof, I checked the Noir ABI and the lowered R1CS witness order. This matters because my earlier work found that a proof can verify even when a backend exposes the wrong Noir value as public. The Week 10 path continued only after the seven public wires matched the seven intended Capsule fields in the exact order.

One Transaction Was Supposed to Pass

I then ran a 12-case transaction matrix in CKB-VM.

Category Transaction case Expected result
Intended path Valid proof and exact Capsule transition Accept
Binding failures Same valid proof with a changed new state, Capsule ID, or replay domain Reject
Proof failures Invalid proof, missing VK Cell, or truncated witness Reject
Encoding failures Malformed Type Script arguments or malformed Capsule Cell data Reject
Structural failures Changed output lock or ambiguous duplicate input groups Reject

The result was:

12 passed
0 failed

The intended transaction executed both RISC-V scripts in 101,625,705 CKB-VM cycles in the retained development environment.

The most important cases used the same valid proof and changed only the transaction-derived state. The Groth16 statement did not suddenly become mathematically false. Instead, the Capsule Type Script detected that the Cells no longer matched the statement and rejected the transaction.

That is the difference between verifying evidence and authorizing a state transition.

What Another Developer Can Test Today

I have now added two reproduction paths to the repository.

The fast developer-preview path uses a committed, public, non-secret proof fixture. It builds the pinned generic verifier and Capsule binding script, runs the host-side checks, and executes the 12-case CKB-VM matrix

The full artifact-regeneration path starts from the Noir circuit, regenerates development Groth16 artifacts in an ignored directory, converts them into the CKB wire format, and passes the freshly selected fixture into the same transaction harness.

The repository records the relevant compiler versions, source revisions, binary hashes, expected test outcomes, and known limitations. The current interface is still command-oriented rather than polished, but the result can now be reproduced without relying on my original local artifact directory.

What I find compelling

The part I find most compelling about CKB is that proof verification and application authorization do not have to be hidden inside one monolithic contract.

The generic verifier can remain reusable. The verification key can remain circuit-specific data. The application Type Script can define what a valid state transition means for a particular Cell protocol.

That composition gives the project a clearer shape:

Noir defines the statement
Groth16 proves it
the generic CKB verifier checks the proof
the application Type Script checks the transition
CKB-VM requires both scripts to pass

This is closer to the developer experience I originally imagined: write a Noir circuit and generate the pieces needed to use it safely in a CKB application. On CKB, however, the useful output is not simply one generated verifier contract. It is more likely a bundle containing the verification-key data, proof encoding, script configuration, transition-binding rules, and positive and negative transaction tests.

What Is Still Experimental

This is a narrow development vertical slice, not a production release.

The prototype checks the commitment, nullifier, and replay-domain fields end to end, but their current values come from simple development formulas. A production version would replace them with reviewed cryptographic commitments, a finalized nullifier scheme, and a replay domain derived from the relevant CKB transaction context. The scripts have not been audited, and nothing has been deployed to devnet or mainnet. General Noir circuit compatibility still requires a sound solution for public-wire mapping.

The claim is deliberately limited:

For this retained fixture and pinned toolchain, a Noir-derived Groth16 proof
executes in CKB-VM, accepts the intended Capsule transition, and rejects the
tested wrong, malformed, and ambiguous transitions.

Next

Week 11 will focus on packaging the working path behind a small developer preview:

noir-ckb build
noir-ckb prove
noir-ckb test

The goal is to make the safe path easier to run: enforce the supported layout, preserve artifact provenance, generate the CKB-facing bundle, and always include the wrong-transition tests alongside the successful transaction.

Last week, the project could prepare a proof for the CKB boundary.

This week, the proof finally met the Cell, and the Cell was allowed to disagree.

References

5 Likes