Documentation

Verify your first proof

Mosaic is a proof-system-agnostic, on-chain verification library for Solana. Add two crates, choose a proving system, and verify, the same code runs in your tests and on-chain, with no Groth16 wrapping in between.

Introduction

Zero-knowledge proofs let a program accept a short certificate in place of re-executing an expensive computation. On Solana, though, the verifier side has been a bottleneck: until now the ecosystem shipped exactly one production-grade ZK verifier, Light Protocol's groth16-solana.

Every other system, PLONK, HyperPlonk, Halo2-KZG, FRI-STARK, Risc0, Nova, ProtoStar, had to be awkwardly wrapped inside a Groth16 proof (adding a recursion step, a second trusted setup, and latency) or simply could not be verified on Solana L1 at all.

Mosaic removes that constraint. It exposes a single ProofSystem trait over a uniform byte-slice API, so you select a proving system through a generic parameter and swap systems without changing a line of program logic. The library is deliberately thin: it is verification plumbing, not new cryptography, and delegates every field and curve operation to ark-bn254 or audited Solana syscalls.

If you are building a rollup, a privacy protocol, an identity or attestation system, or any dApp that settles a proof on-chain, Mosaic is the verifier layer underneath it.

Installation

Add the core crate plus the crate for the proving system you intend to use. The solana feature switches the curve backend from arkworks (host) to Solana syscalls (on-chain), enable it for any crate compiled to the SBF target.

TOML
Cargo.toml
[dependencies]
mosaic-core      = { version = "0.1", features = ["solana"] }
mosaic-groth16   = { version = "0.1", features = ["solana"] }
solana-program   = "2.1"

# Add only the systems you need:
# mosaic-plonk   = { version = "0.1", features = ["solana"] }
# mosaic-nova    = { version = "0.1", features = ["solana"] }

Mosaic's minimum supported Rust version is 1.85.0 on the host and 1.89.0-dev for the Solana SBF target. Pin your toolchain accordingly so host tests and on-chain builds stay in lock-step.

Quick start

Start off-chain. The host backend runs the verifier with arkworks, which is the fastest way to confirm your verifying key, proof and public inputs line up before you ever touch a validator.

Rust
verify.rs
use mosaic_core::{ProofSystem, ProofSystemId};
use mosaic_groth16::Groth16Verifier;

let backend  = mosaic_core::syscall::host::HostBackend::new();
let verifier = Groth16Verifier::<_, false>::new(&backend);

verifier.verify(&vk_bytes, &proof_bytes, &public_inputs_bytes)?;

The const-generic false selects little-endian public inputs; the verifier takes three byte slices and returns Ok(()) on success. Now move the exact same verifier into a Solana instruction handler, only the backend changes:

Rust
lib.rs
use mosaic_core::syscall::solana::SolanaSyscallBackend;
use mosaic_groth16::Groth16Verifier;

pub fn process_instruction(
    _program_id: &Pubkey,
    _accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let backend  = SolanaSyscallBackend::new();
    let verifier = Groth16Verifier::<_, false>::new(&backend);

    let (vk, rest)        = decode_lp(instruction_data)?;
    let (proof, pi_bytes) = decode_lp(rest)?;
    verifier.verify(vk, proof, pi_bytes).map_err(Into::into)
}

decode_lp reads a length-prefixed segment from the instruction data, so the handler stays agnostic to how the client packed the verifying key, proof and inputs. Because the host and on-chain paths share one SyscallBackend trait, anything that passes in CI passes on-chain too.

Proof systems

Groth16 and KZG-PLONK are production-ready today. Four more families ship with complete structural scaffolds and estimated compute-unit budgets, the trait, dispatch and byte layout are in place while production hardening and audit proceed.

SystemFamilyStatusOn-chain CU
Groth16Pairing · BN254Production83,574
Groth16 BatchPairing · BN254 · N=5Production258,397
KZG-PLONKUniversal · BN254Production968,457
HyperPlonkUniversal · BN254Phase-3~505K
Halo2 KZGUniversal · BN254Phase-3~580K
FRI-STARKTransparent · Hash-basedPhase-3~9.4M
NovaFolding · IVCPhase-3~885K

Selecting a system is a type-level choice. Swapping Groth16Verifier for PlonkVerifier changes which crate you import and nothing else, the verify call site, the instruction handler and the client are identical.

Verifying a proof

Every verifier implements the same contract: take the verifying key, the proof and the public inputs as byte slices, and return Ok(()) on success or a typed error on failure. Keeping the surface at the byte level is what makes systems interchangeable.

Byte layout & forward compatibility

The serialized format is versioned by a LE_INPUTS const generic (endianness of public inputs) and a FormatTag wire enum. New proof systems extend the wire format without breaking existing consumers, so a verifying key serialized today still decodes after future upgrades.

Proofs from snarkjs

Proofs and keys produced with snarkjs decode straight into canonical bytes, and a local pre-flight runs the verifier with arkworks so you fail fast, before paying for a transaction that would revert.

Rust
decode.rs
use mosaic_serde::snarkjs::SnarkjsCodec;

let bundle = SnarkjsCodec::decode_bundle(
    &proof_json,
    &vk_json,
    &public_inputs_json,
)?;

// bundle.vk, bundle.proof, bundle.public_inputs, canonical bytes
mosaic_sdk::preflight(&request)?; // runs the verifier locally; fails fast

Error model

Mosaic uses a two-layer error model. On-chain, the verifier returns a deterministic OnChainError that maps to a stable program error code and keeps compute cheap. Off-chain, tooling gets a rich DiagnosticError with context about exactly which check failed, so developers stay informed while the chain stays lean.

From circuit to pairing

Under the hood, a zero-knowledge proof travels from an arithmetic circuit down to a single pairing equation that Mosaic checks on-chain. Scroll the chain to follow each step.

  1. 01

    Arithmetic circuit

    c=(a1+a2)a3c = (a_1 + a_2)\cdot a_3

    The statement is expressed as gates over a finite field, with every wire holding a field element.

  2. 02

    R1CS

    (aiz)(biz)=ciz(\vec{a}_i \cdot \vec{z})\,(\vec{b}_i \cdot \vec{z}) = \vec{c}_i \cdot \vec{z}

    Each gate becomes a rank-1 constraint: the product of two linear combinations of the witness z equals a third.

  3. 03

    QAP

    A(x)B(x)C(x)=H(x)Z(x)A(x)\,B(x) - C(x) = H(x)\,Z(x)

    Constraints are interpolated into polynomials. A valid witness makes A·B − C divisible by the target polynomial Z(x).

  4. 04

    Groth16 pairing check

    e(A,B)=e(α,β)e(Lpub,γ)e(C,δ)e(A, B) = e(\alpha, \beta)\, e(L_{\text{pub}}, \gamma)\, e(C, \delta)

    Mosaic verifies one pairing equation on-chain. If it holds, the proof is valid, in just 83,574 compute units.

Compute budgets

Solana meters execution in compute units (CU). Request a limit that matches your system before submitting: Groth16 single verification fits comfortably under 100k CU, batch verification of five proofs lands near 258k, and KZG-PLONK needs close to 1M.

Rust
client.rs
use solana_sdk::compute_budget::ComputeBudgetInstruction;

let cu_ix     = ComputeBudgetInstruction::set_compute_unit_limit(200_000);
let verify_ix = mosaic_sdk::build_verify_proof_ix(&request)?;

transaction.add(&cu_ix);
transaction.add(&verify_ix);

The reference program compiles to a 319 KBSBF ELF , only 30.4% of the 1 MB account cap, which leaves ample room to bundle Mosaic alongside the rest of your program's instructions in a single deploy.

Compression

Proof bytes are the dominant cost of getting a proof on-chain, so Mosaic ships compression codecs in the mosaic-chunked crate. They cut bandwidth by 50% for Groth16 and 40–42% for PLONK and Nova, shrinking transaction size and account rent.

Compression is the most security-sensitive surface in the library, so it is guarded by ten dedicated fuzz harnesses: a malformed or adversarial input can never decode into a different valid proof, it can only fail to decode.

Architecture

Mosaic is built to disappear behind your program. Four design rules keep it deterministic on-chain, informative off-chain, and forward-compatible as new systems land:

  • Object-safe ProofSystem trait. A single byte-slice API; the dispatcher monomorphizes each system through an exhaustive match, with no dynamic dispatch on the hot path.
  • Two-layer error model. Deterministic on-chain errors, rich diagnostics off-chain.
  • Syscall abstraction. One SyscallBackend trait bridges arkworks host tests and Solana syscalls, so the same verifier runs unchanged in both.
  • Forward-compatible layout. A LE_INPUTS const generic and FormatTag wire enum version the byte format.

The workspace splits cleanly by responsibility, core trait, one crate per proof family, serialization, compression, the reference program, client SDK, benchmarks and fuzzers:

Tree
mosaic/
crates/
├── mosaic-core/      # ProofSystem trait + dispatcher
├── mosaic-groth16/   # Groth16 (single + batch)
├── mosaic-plonk/     # KZG-PLONK, HyperPlonk, Halo2
├── mosaic-stark/     # FRI-STARK
├── mosaic-nova/      # Nova / HyperNova / ProtoStar
├── mosaic-serde/     # snarkjs + canonical codecs
├── mosaic-chunked/   # compression infrastructure
├── mosaic-program/   # reference Solana program
├── mosaic-sdk/       # client helpers + preflight
├── mosaic-bench/     # criterion benchmarks
└── mosaic-fuzz/      # 37 fuzz targets
docs/                 # ADRs, threat model, CU budget
tests/                # differential, integration, fixtures

Testing & audit

Correctness is checked differentially: on every run, the on-chain output is compared byte-for-byte against an arkworks reference implementation. If the two ever disagree, the test fails.

  • 549+ library tests across 12 crates
  • 152+ proptest sessions and 549+ differential tests
  • 37 fuzz targets, including 10 dedicated compression harnesses

Phase-1 and Phase-2 components are frozen at production quality; Phase-3 families are scaffolded and estimated. The library is ready for review and an external audit is pending commissioning , track status and read the threat model in the repository.

Have a question or want to integrate Mosaic? Reach the team on X or open an issue on GitHub.