Skip to content

feat: trustless block verification (verifyPrecomputedBlock / checkBlockClaims)#10

Open
dkijania wants to merge 4 commits into
mainfrom
feat/trustless-block-verification
Open

feat: trustless block verification (verifyPrecomputedBlock / checkBlockClaims)#10
dkijania wants to merge 4 commits into
mainfrom
feat/trustless-block-verification

Conversation

@dkijania

Copy link
Copy Markdown
Collaborator

What

Adds an in-process block-proof verifier to the SDK. A block whose Pickles/kimchi SNARK proof verifies attests its entire chain history by recursion — so a JS client can check chain validity against an untrusted source (a node, an indexer, a GCS archive) without trusting the bytes.

import { verifyPrecomputedBlock, checkBlockClaims } from '@o1-labs/mina-sdk';

const facts = await verifyPrecomputedBlock(precomputedJson, { network: 'devnet' });
// -> { height, stateHash, previousStateHash, stagedLedgerHash }  (all proof-backed)

const { honest, mismatches } = await checkBlockClaims(precomputedJson, { stateHash: claimed });
if (!honest) console.error('endpoint lied:', mismatches);

API (src/verify.ts)

  • verifyPrecomputedBlock(precomputed, { network }) → VerifiedBlock
  • checkBlockClaims(precomputed, claimed) → { honest, facts, mismatches }
  • compareToClaims(facts, claimed) → HonestyResult — pure, no I/O
  • VerificationError (proof failed → do not ingest) / VerificationBackendError (backend missing)

Design

  • Proof verifier is a WebAssembly module, mina-verify-wasm, not bundled (several MB) and loaded lazily via a runtime dynamic import. SDK stays lightweight and installs without it; a clear error directs users to npm install mina-verify-wasm. Bundle size unchanged (~26 KB).
  • GraphQL is not sufficient: a daemon's GraphQL protocolState is a lossy projection and can't be re-hashed to verify a proof. The verifiable input is a precomputed block (the full-protocol-state JSON daemons publish to GCS / the archive).

Verification

  • End-to-end: a real devnet precomputed block verifies through the SDK and returns facts identical to native mina-verify; checkBlockClaims flags a lying endpoint.
  • Unit tests cover the comparison logic + backend-missing path (mocked → deterministic). 32 tests pass; lint/format/typecheck clean.

Caveat / follow-up

Single-threaded today (~tens of seconds/block) — fine for periodic/background checks. A threaded wasm backend (worker pool) is the planned perf follow-up.

🤖 Generated with Claude Code

dkijania and others added 2 commits June 12, 2026 13:05
…ckClaims)

Add an in-process block-proof verifier to the SDK. A block whose Pickles/kimchi
SNARK proof verifies attests its entire chain history by recursion, so a JS client
can check chain validity against an untrusted source (node, indexer, GCS archive)
without trusting the bytes.

New API (src/verify.ts):
- verifyPrecomputedBlock(precomputed, { network }) -> VerifiedBlock
  { height, stateHash, previousStateHash, stagedLedgerHash } (all proof-backed)
- checkBlockClaims(precomputed, claimed) -> { honest, facts, mismatches }
  endpoint-honesty primitive: does an untrusted source's claim match the proof?
- compareToClaims(facts, claimed) -> HonestyResult  (pure, no I/O)
- VerificationError (proof failed) / VerificationBackendError (backend missing)

The proof verifier is a WebAssembly module (mina-verify-wasm) that is NOT bundled
(it is several MB) and is loaded lazily via a runtime dynamic import, so the SDK
stays lightweight and installs without it; a clear error tells users to
`npm install mina-verify-wasm`. Bundle size unchanged (~26 KB).

Note: a daemon's GraphQL `protocolState` is a lossy projection and cannot be
re-hashed to verify a proof — the verifiable input is a precomputed block (the
full-protocol-state JSON daemons publish to GCS / the archive).

Verified end-to-end: a real devnet precomputed block verifies through the SDK and
returns facts identical to the native verifier; checkBlockClaims flags a lying
endpoint. Unit tests cover the comparison logic and the backend-missing path
(mocked, deterministic). Verification is single-threaded today (~tens of seconds
per block); a threaded backend is a planned follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proof check is a synchronous, CPU-bound wasm call (tens of seconds), so the
async wrapper bought nothing — it held the event loop during the verify either
way; the only async part was loading the backend. Load the backend synchronously
(createRequire(import.meta.url); the nodejs-target wasm pkg instantiates on
require) and return values directly.

- verifyPrecomputedBlock(...) -> VerifiedBlock        (was Promise<VerifiedBlock>)
- checkBlockClaims(...) -> HonestyResult              (was Promise<HonestyResult>)

Simpler call sites (no await); blocking is documented. Verified: both ESM and CJS
builds compile (esbuild shims import.meta.url in CJS), real devnet block verifies
synchronously through the SDK, all 32 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dkijania and others added 2 commits June 12, 2026 16:25
createRequire(import.meta.url) only finds mina-verify-wasm if it is hoisted next
to the SDK. Also try the host process cwd, so a backend installed at the app root
(or an SDK that is symlinked / pnpm-isolated) resolves. Needed for consumers like
the MCP server that install the backend at their own root.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant