Design notes: verifying CKB state inside other chains (EVM-first, built on RFC 44/45)

Gm everyone!

I want to share a design I’ve been working on and have it picked apart before any code gets written. The cheapest time to be wrong is now.

NOTE: This is design-stage. Nothing is built. Full design doc with threat model, cost estimates and a phased plan is here: REPO LINK. This post covers the shape and the specific places where I want pushback.

Where this starts

A few weeks ago I posted about Cellora, an indexing and query service I’m building for CKB. The most valuable feedback on that thread was about trust: an indexer sitting between signers and consensus is a trusted oracle whether we name it or not, and the path out is Merkle inclusion proofs up to headers, anchored by the chain root MMR and Flyclient.

That sent me down a rabbit hole, and at the bottom of it was a bigger question. The CKB light client protocol (RFC 44 and RFC 45) already gives us trustless verification on devices. Phones. Browsers through WASM. But nothing runs that verification inside another chain’s execution environment. Which means:

a phone           can verify CKB trustlessly
a browser tab     can verify CKB trustlessly
an EVM contract   cannot. it trusts a bridge committee instead

Every path from CKB state to a contract on Ethereum, Solana or a Move chain runs through a multisig, an MPC set, or an oracle. RGB++ gives trustless BTC↔CKB binding for issued assets, but the moment those assets head toward other ecosystems, the trustless property is severed at the bridge. The CKB↔everything-else leg is the gap.

The shape

One-directional: CKB state verified on destination chains. EVM first, because that’s where RGB++ asset liquidity would actually get used.

The design rests on one cost asymmetry, splitting the work into two operations with very different profiles:

tip updates        expensive, rare
                   verify Eaglesong PoW + FlyClient sampling
                   runs inside a zkVM, contract checks one SNARK
                   periodic, not per-message

inclusion proofs   cheap, frequent
                   CBMT branch + MMR branch, pure hashing
                   ~60-100k gas natively on EVM
                   no ZK in the hot path

Eaglesong has no EVM precompile, so verifying headers natively on Ethereum is economically impossible. That’s why tip updates go through a zkVM (SP1 first): the guest program verifies a batch of sampled headers, the MMR root transition and cumulative difficulty, and the contract verifies a single proof for around 320k gas. But once a tip is verified, proving a transaction or cell against it is just hashing, and that’s affordable directly.

The protocol-design crux is making RFC 45’s sampling non-interactive. An on-chain verifier can’t pick random heights the way an interactive client does, so sample heights are derived Fiat–Shamir style from the tip hash itself. The prover can’t choose which heights to prove, and grinding for favorable samples costs a full valid PoW per attempt. The analysis is in the doc.

One more decision worth stating plainly: the consensus-critical logic lives in a single shared no_std Rust crate, consumed by the zkVM guest, by native verifiers on Solana and CosmWasm later, and by the relayer. Written once, audited once. The relayer itself is deliberately not consensus-critical. Relayer bugs are liveness bugs, never safety bugs, and anyone can run one.

What this is not

  • Not a messaging protocol and not an asset bridge product. It’s the verification layer. Bridges and asset integrations are downstream consumers.
  • Not the reverse direction (verifying foreign state on CKB).
  • Not a fix for custody trust. This deserves a precise statement, credit to Neon for forcing it: a custodially wrapped asset stays custodially trusted no matter how good the verification layer is. What becomes trustless is the CKB-side state verification. An asset issued on CKB or bound via RGB++ gets a trustless end-to-end story; a custodial wrapper does not, and the SDK must never imply otherwise. The doc has a full asset trust taxonomy making this concrete.

Relationship to work happening right now

This space is suddenly active, which I take as a good sign.

Chiral. @matt_ckb pointed me at the Chiral light paper, which independently arrives at the same core conclusion: succinct on-chain light client verification is the right primitive for committee-free CKB interop. The shapes differ. Chiral is symmetric (each chain verifies the other) and Cardano-targeted; this design is one-directional and EVM-first. But the primitives overlap, especially Eaglesong proving. To the Chiral author if you’re reading: I’d genuinely rather compare notes than duplicate circuit work. Shared Eaglesong proving infrastructure with different protocols on top seems like the right ecosystem outcome. And I’d especially like to understand the soundness gap you’ve flagged on your CKB→Cardano leg, since my design may face the same class of problem.

ZK verification on CKB. @Mulandi_Cecilia’s research notes and groth16-ckb work run in the opposite direction to mine: she’s verifying proofs on CKB (~102M cycles per Groth16 verification, about 2.9% of a block, which is genuinely usable), while this design proves CKB to other chains. The two directions are complementary. If both mature, CKB verifies the world and the world verifies CKB, with no committee in either direction.

ckb-light-client. The existing implementation is the verification logic this design ports. The intent is to adapt its crates inside the zkVM guest rather than reimplement, so the consensus logic stays as close to the reference as possible.

Where I’d value input

  1. Sampling parameterization. The FlyClient sample count and difficulty-weighted distribution, adapted non-interactively, is the one place where a subtle error is catastrophic and silent. If you’ve worked on RFC 45 or the light client implementation, this is the section that most needs adversarial eyes.

  2. Cell liveness. Inclusion proves a cell was created, not that it’s still unspent. Proving liveness needs a non-inclusion proof that the CBMT/MMR stack doesn’t natively give. The doc lays out options (covenant patterns, a live-cell-set commitment, optimistic claims), but I’d like to hear from builders: is “this cell existed at height N with k confirmations” enough for your use case, or do you need “this cell is live now”? The answer shapes the SDK’s scope more than any other question.

  3. The extension-field off-by-one. The chain root committed in a header’s extension covers headers up to the parent. If you’ve implemented against this, war stories welcome. It’s a classic consensus-bug shape.

  4. Eaglesong proving. If you’ve put Eaglesong inside any proving system, your cycle counts and pain points would compress my de-risking phase significantly. This is the first spike in my build plan and the biggest open unknown.

  5. Demand reality-check. For teams shipping RGB++ or xUDT assets: would trustless verification of your asset’s CKB state on an EVM chain change what you can build? Honest “no, because…” answers are as valuable as yes.

The full doc covers the threat model, per-chain implementation plans, relayer economics, cost estimates and a phased build plan with written kill criteria: REPO LINK

Direct and critical feedback welcome.


References:

— Antismart

9 Likes

Thanks for reaching out.

You’ve got the core conclusion right, and the split makes sense. The CKB side proving (Eaglesong PoW, header chain following, membership) is shared infrastructure. The verifier differs by target chain and topology: your one directional EVM vs my symmetric Cardano. Same primitive, different protocols on top.

On the soundness gap, since it probably hits your design too: it isn’t in the PoW verification, it’s in making per event membership cheap. Full chain membership is too deep to prove per event, so you amortize into a relayer maintained checkpoint and prove against that instead of full history. The catch is binding the proven header to the live checkpoint tip. If the checkpoint is a fixed size rolling window, old slots get overwritten as it advances, so membership alone lets someone replay a header that was valid earlier but is now stale. The fix is a height bound: the checkpoint commits its current tip, and each event proves the header sits within the last W blocks of it. Cheap, but without it the whole thing is unsound. Same class for any checkpoint based design, EVM or Cardano. The other shared piece is anchoring the checkpoint to one canonical genesis and enforcing cumulative work so a shallow reorg can’t advance it.

Status on my side: testnet, unaudited. There’s also a real proving cost trade in the chain accumulator that I made a specific choice on, worth getting into once we compare designs properly.

Happy to walk through the height bound, call or writeup. I’m in on shared Eaglesong infra. Curious what you’re doing on the verifier and membership side.

3 Likes

This is exactly the catch I was hoping posting would surface, thank you. You’re right that it hits my design, and you’ve named the precise spot.

My inclusion path proved MMR membership against a stored root but didn’t bind recency. Since the MMR is append-only, membership is permanent, so a relayer could replay a once-valid header against a retained root and it would verify. Lower-bounding confirmations doesn’t help because that’s the wrong side of the bound. The fix is the height bound you describe: prove the block sits within the last W blocks of the current tip, membership checked against that tip’s root, not any historical one. Two-sided bound, both halves mandatory. I’ve written it into the design now and credited you for it.

And agreed it’s the same class of issue for any checkpoint-based verifier regardless of topology, which is what makes shared infra make sense here. The CKB-side proving (Eaglesong, header following, membership) is identical work whether the verifier is symmetric Cardano or one-directional EVM. No reason to grind that circuit twice.

I’d take you up on comparing designs properly. Two things I’m most curious about on your side: the accumulator proving-cost trade you mentioned, and exactly how you anchor and advance the checkpoint (cumulative work enforcement, genesis binding). On my side the parts most worth your eyes are the non-interactive FlyClient sampling (Fiat-Shamir from the tip hash, with a grinding bound) and the membership/height-bound path we just talked about.

Writeup works for me. Full design doc is here so we’re not starting cold: Design Doc

2 Likes

@T_Silva thanks again, this was easily the most useful review the design has had. Quick status and then a proposal to actually take you up on comparing notes.

The fix is in. I wrote the two-sided bound into the design as its own soundness section and credited you. verify_tx_inclusion now enforces min_confirmations ≤ tip_height − block_height ≤ W, with W capped at the store’s root-retention window. Inclusion only checks against the live canonical MMR root, so a superseded root gets rejected for new checks rather than just shadowed in the ring buffer, and advancement is gated on strictly greater cumulative difficulty from a single genesis anchor. Membership plus recency, both halves mandatory, which is exactly how you framed it.

One nuance worth putting on the table so we’re comparing like for like. My MMR is full-history append-only, so old slots never get overwritten and membership-at-k against a given root stays sound for that root. That doesn’t remove the need for the height bound, it just moves the whole replay surface over to which root the contract will verify against. My hunch is your rolling-checkpoint design puts the bound in a slightly different place than mine, and that gap is the interesting part to dig into.

For my half of the comparison, the non-interactive sampling is probably the piece you don’t carry on the Cardano side. I derive the sample heights as PRF(new_tip_hash, i) mapped onto RFC 45’s difficulty-weighted distribution. Since the tip hash isn’t known until the block is mined, a prover can’t grind which heights it has to prove. The adversary gets exactly one fresh sample-set per candidate tip it mines, and that’s what bounds λ. I’ll write this up with the full parameter derivation. It’s the one spot where a subtle error is silent and catastrophic, so I’d really value your eyes on it.

Two things I’d love from your side:

  1. The chain-accumulator proving-cost trade. You mentioned you made a specific choice there. What’s the trade, and do you have a measured proving cost or cycle count for an Eaglesong batch on your testnet?
  2. Your checkpoint anchoring and cumulative-work advancement, in enough detail that I can diff it against mine.

On shared infra, I think the cleanest version of “CKB-side proving is shared” is one no_std verification crate (Eaglesong PoW, header linkage, MMR membership and extension) that we both build on. Your Cardano verifier and my SP1 guest end up as thin shims over the same audited core. To make that concrete I need to know what you’re proving inside on the Chiral side: is it a zkVM, and if so which one, or native? If we can agree on the guest interface, a second backend becomes a recompile instead of a rewrite.

Writeup works for me like you suggested. Want me to start a shared doc with four sections (your accumulator and anchoring, my sampling, the membership and height-bound path) so we each fill in our half?

3 Likes