For the complete documentation index, see llms.txt. This page is also available as Markdown.

Dart

Dart FFI bindings for the Colibri stateless Ethereum proof library. Generate and verify cryptographic proofs for Ethereum RPC calls from Dart applications (CLI, servers, or desktop).

Overview

The Colibri Dart bindings provide an async Dart API that calls the Colibri C core via FFI. They are used both as a standalone package (colibri_stateless) and as the runtime behind the Flutter plugin (colibri_flutter). All RPC responses can be validated with Merkle proofs; ZK proofs are requested from remote provers when configured.

Core Features

  • Cryptographic verification – RPC results verified with Merkle (and optionally ZK) proofs

  • Async/awaitFuture-based API for RPC and proof operations

  • Pluggable storage – Implement [ColibriStorage] for custom cache backends

  • Multi-chain – Configurable chain ID, provers, eth Rpcs, beacon APIs

  • No Flutter dependency – Pure Dart; use from CLI or server

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     Dart Application Layer                       │
├─────────────────────────────────────────────────────────────────┤
│                   package:colibri_stateless                      │
│  • Colibri class (rpc, createProof, verifyProof)                │
│  • ColibriStorage interface                                      │
│  • Error types (ColibriError, ProofError, RPCError, …)           │
├─────────────────────────────────────────────────────────────────┤
│                      Dart FFI Layer                              │
│  • native/ (libcolibri.dylib / .so / .dll)                       │
│  • ColibriNative (load, registerStorage, createProverCtx, …)     │
├─────────────────────────────────────────────────────────────────┤
│                      Core C Libraries                            │
│  • Prover, Verifier, storage plugin system                       │
└─────────────────────────────────────────────────────────────────┘

Installation

From pub.dev (colibri_stateless)

You must build or provide the native library separately (see Building from source). Alternatively, use colibri_flutter for mobile/desktop with bundled binaries.

Development / from source

This produces a shared library in native/ (e.g. native/libcolibri.dylib on macOS). Set COLIBRI_DART_LIBRARY to its path, or pass libraryPath to the [Colibri] constructor.

Quick Start

Basic RPC

Local proof generation

Use empty provers and provide ethRpcs and beaconApis so the client can generate proofs locally:

API Reference

Colibri class

  • rpc – Executes an RPC call with proof generation and verification (remote or local). Returns the verified result.

  • createProof – Builds a proof locally (Merkle only; zkProof is ignored for local creation).

  • verifyProof – Verifies serialized proof bytes and returns the decoded result.

  • getMethodSupport – Returns whether the method is supported locally, only remotely, or not at all.

  • close – Releases native resources; call when done.

Constructor: libraryPath overrides the default native library (or use env COLIBRI_DART_LIBRARY). storage registers a custom cache; zkProof requests ZK proofs from remote provers when using rpc with provers. onDebug can contain sensitive data; do not forward to production logging.

Storage

Implement this to provide a custom cache backend. Default on desktop is native file storage; on Android/iOS the client uses [MemoryStorage] if no storage is given (native file storage is not used on mobile).

Configuration

Chain and endpoints

Privacy-preserving eth_call (oblivious + PAP + hybrid)

For an eth_call with full storage privacy, configure hybrid prover mode, PAP, and oblivious nodes (obliviousNodes defaults to []).

  • ProverMode.hybrid: only the block proof from the prover; storage/account data from RPC or oblivious node, verified locally.

  • PrivacyMode.basic (PAP): no eth_createAccessList on the prover; optimistic local EVM, only eth_getProof RPCs leave the client.

  • obliviousNodes: TEE RPC for eth_getProof; sets OBLIVIOUS + PAP verify flags automatically when non-empty. See Oblivious Labs for how oblivious nodes use TEE and ORAM.

Prover Mode

Controls how proofs are built and verified. Set via proverMode in the constructor:

  • ProverMode.local -- Proofs are built entirely on the client. Requires access to a Beacon API and execution layer RPC. Fully trustless, but slower and needs more infrastructure.

  • ProverMode.remote -- Proofs are fetched from a remote Colibri prover server. Fastest option but relies on the prover server for proof generation. The verifier still cryptographically checks every proof.

  • ProverMode.hybrid -- The consensus-layer proof (BlockHeaderProof) comes from the Colibri server, while execution-layer data (account proofs, storage, etc.) is fetched directly from the RPC provider. Best balance of performance and scalability -- the Colibri server only serves lightweight, cacheable header proofs while the heavy RPC load goes to your existing provider.

  • ProverMode.proxy -- Like remote, but the client sends its own RPC and Beacon API URLs to the prover server. The server uses these endpoints instead of its own. Useful when the client has access to private or premium RPC providers.

  • ProverMode.lightClient -- Like hybrid, with additional background polling of block headers to keep the cache warm. Call startLightClient() to begin and stopLightClient() to end polling. The polling interval defaults to 12 seconds (one Ethereum slot) and is configurable. By default only the compact eth_getBlockHeader is fetched; pass fullBlock: true to fetch the full block (useful when many eth_getTransactionByHash / eth_getTransactionReceipt calls follow).

Default: ProverMode.remote when prover URLs are configured, ProverMode.local otherwise.

Weak Subjectivity Period check

Whenever a sync crosses the Weak Subjectivity Period (WSP) -- typically ~2 to 4 months on Ethereum mainnet -- the verifier anchors the highest finalized header it has just accepted against an external checkpointz / Beacon API endpoint (/eth/v1/beacon/blocks/{slot}/root). The check runs for all three sync paths: verifier-driven Light Client updates, prover-supplied LCSyncData, and prover-supplied ZKSyncData. For ZKSyncData the verifier prefers configured witness signatures (checkpointWitnessKeys + matching signatures from the prover) and only falls back to checkpointz when no witness anchor is available. Background: see the threat model -- long range attacks.

  • skipWspCheck (bool, default false) -- sets VERIFY_FLAG_SKIP_WSP_CHECK (bit 1 << 7) and disables the round-trip. SECURITY: only safe when another trust anchor (witness signatures, hard-coded checkpoint, signed package) is in place. Disabling raises the risk of long-range attacks across periods older than the WSP.

Freshness window for latest proofs

Proofs that target the latest block tag remain cryptographically valid forever -- without a freshness window, a months-old proof could still be replayed as "current". The Dart binding therefore reads DateTime.now() and forwards now - maxLatestAgeSeconds to the verifier, which rejects proofs whose block timestamp is older with "proof for latest too old".

The gate covers the following RPC methods:

  • EVM: eth_call, eth_estimateGas, colibri_simulateTransaction

  • Account: eth_getBalance, eth_getCode, eth_getStorageAt, eth_getTransactionCount, eth_getProof

  • Block / header: eth_getBlockByNumber, eth_getBlockHeader, eth_blobBaseFee, eth_maxPriorityFeePerGas

  • Implicit-latest: eth_blockNumber

eth_getLogs is not covered yet (tracked in issue #128). Account methods rely on a slim timestamp leaf inside the state proof which is only emitted by prover version ≥ 1.1.27; against older provers the verifier fails closed ("cannot verify freshness of latest block without block context").

  • maxLatestAgeSeconds (int, default 60 ≈ 5 Ethereum slots) -- upper bound on the accepted age. Set to 0 to disable the check (e.g. when using legacy proof formats that do not embed a block context).

Caveat: the gate fires only on "latest" (not "safe"/"finalized"). If the host wallclock is behind maxLatestAgeSeconds (devices without configured time, fresh simulators), the lower bound clamps to 0 and the check is silently disabled. Make sure your runtime has a synced clock or set maxLatestAgeSeconds: 0 explicitly to acknowledge this state.

PAP mode: the freshness check also applies to PAP, where the call proof arrives via colibri_proofCall (same proof structure as a direct eth_call). This requires a prover that embeds the block context (≥ 1.1.15); against an older PAP proof without a block timestamp the check fails closed ("cannot verify freshness of latest block without block context"). Set maxLatestAgeSeconds: 0 to opt out.

Environment

  • COLIBRI_DART_LIBRARY – Path to the native shared library if not passing libraryPath.

Error handling

Verified EVM reverts (RevertError)

When an eth_call (or similar EVM execution) is verified successfully but the EVM itself executed a REVERT, the binding throws a [RevertError]. This is a fully verified outcome -- not a transport or proof failure -- and matches the Geth-style RPC error { code: 3, message: "execution reverted", data: "0x..." }.

RevertError extends [RPCError] (code = 3) and exposes the raw revert return data as a 0x-prefixed hex string in data. Callers typically ABI-decode this against the contract's error definitions (custom errors, Error(string), etc.). This is the mechanism that lets dApp libraries decode OffchainLookup (EIP-3668 / CCIP-Read) for example for the ENS off-chain resolver.

Building from source

Native library output:

  • macOS: native/libcolibri.dylib

  • Linux: native/libcolibri.so

  • Windows: native/colibri.dll

Testing

Set COLIBRI_DART_LIBRARY if the library is not in the default path. Coverage: ./test/run_coverage.sh (output in test/coverage/).

Further information


Last updated