How Verifiers Work Inside zkVerify

What actually happens when a proof arrives

You submit a proof to zkVerify. Something on the other end checks it. But what exactly is doing the checking, and how does it work?

That something is a verifier pallet. Understanding how pallets work helps you reason about why zkVerify behaves differently from a smart contract verifier, and why that difference matters for your application.

<div style={{ marginTop: "1.5rem", marginBottom: "1.5rem" }}> !zkVerify </div>

What a pallet is

In Substrate-based blockchains, a pallet is a self-contained runtime module. Think of it like a plugin that gets compiled directly into the chain's execution environment. It is not a smart contract sitting on top of the chain. It is part of the chain itself.

This distinction matters. A smart contract verifier on Ethereum runs inside the EVM, inherits EVM constraints, pays EVM gas, and is limited by what the EVM can express efficiently. A pallet runs at the runtime level. It has direct access to Rust libraries, native cryptographic primitives, and hardware-level performance with none of the EVM overhead.

zkVerify has one dedicated pallet per proof system. Each pallet knows exactly one thing: how to verify its specific proof type.

What happens inside a verifier pallet

When your proof arrives at a zkVerify node, the proof submission interface reads the proof type field from your transaction and routes it to the matching pallet. From that point, the pallet takes over.

Every pallet runs the same sequence:

Deserialize — the proof bytes, public inputs, and verification key are decoded from their wire format into the data structures the verification algorithm expects. Different proof systems use different serialization formats, which is why each pallet handles this independently.

Verify — the core cryptographic check runs. For pairing-based systems like Groth16, this involves elliptic curve pairing operations. For STARK-based systems like RISC Zero, this involves FRI protocol checks and hash verification. The pallet uses the specific mathematical operations that proof system requires, implemented in Rust using native cryptographic libraries.

Attest — if verification passes, the pallet records the result on-chain as a statement hash. This hash is what the aggregation engine picks up and eventually what your smart contract verifies against.

If verification fails at any point, the transaction is rejected and nothing is recorded. There is no partial state, no "pending" result. Pass or fail.

A look at specific pallets

Groth16 uses the arkworks ark-groth16 library internally. It supports three elliptic curves: BN128, BN254, and BLS12-381. It also supports proofs generated by snarkjs, arkworks, and gnark — each library uses slightly different serialization, and the pallet handles the conversion. This is the most widely used pallet given Groth16's dominance across rollups and privacy protocols.

RISC Zero is a STARK verifier using the FRI protocol with Poseidon2 as its hash function. This is significant because it means RISC Zero proofs can be verified natively without wrapping in a Groth16 shell. That wrapping step, which most teams do today to get Ethereum compatibility, adds latency and cost. On zkVerify it is not needed.

FFlonk is Polygon's optimized variant of PLONK, used by the Polygon CDK prover. The zkVerify FFlonk pallet is a direct Rust implementation of Polygon's verifier. A proof here is 768 bytes, the public input is 32 bytes, and verification is fast.

Plonky2 requires specifying the hash function used during proof generation, either Poseidon or Keccak with Goldilocks field. The verification key format is specific to zkVerify's implementation, so a CLI converter tool is available if you are coming from the standard Plonky2 toolchain.

SP1 accepts shrunk STARK proofs in compressed form. The verification key is hashed using the hash_babybear method and serialized as little-endian bytes. The public inputs come directly from the SP1ProofWithPublicValues output.

UltraHonk is the current Noir proof type via Barretenberg. UltraPlonk is supported but deprecated as of Barretenberg v0.87.0. If you are building with Noir, use UltraHonk.

EZKL was added natively in Runtime Upgrade 1.3.1 in November 2025. It is specifically for ML inference proofs, making it the entry point for zkML applications on zkVerify.

The verification key registration pattern

Most pallets support registering your verification key once before submitting any proofs. This is worth understanding because it directly affects your costs.

Without registration, you send the full verification key with every proof submission. Verification keys can be large, and including them in every transaction adds to your fees.

With registration, you submit the key once, get back a vkHash, and reference that hash in all future submissions using .withRegisteredVk(). The pallet looks up the key on-chain rather than deserializing it fresh each time.

```typescript // Register once const { regevent } = await session .registerVerificationKey() .groth16({ library: Library.snarkjs, curve: CurveType.bn128 }) .execute(verificationKey);

// Use the hash for every proof after that const { events, transactionResult } = await session .verify() .groth16({ library: Library.snarkjs, curve: CurveType.bn128 }) .withRegisteredVk() .execute({ proofData: { vk: vkHash, proof, publicSignals } }); ```

For any application submitting proofs at volume, registration is not optional. It is the correct default.

Why this design is auditable

Because each pallet is a discrete Rust module compiled into the runtime, its behavior is deterministic and inspectable. zkVerify has been audited by Trail of Bits in February 2025 and SRLabs in September 2025. The pallet code is open source and verifiable independently.

This is meaningfully different from a smart contract verifier where the verification logic can be upgraded by a contract owner without the same level of formal review. In zkVerify, runtime upgrades require governance. The verification logic you rely on today is not going to change without the network knowing about it.

Answer the quiz correctly to continue →

Video · Quiz1 / 3

Why does zkVerify verify STARK-based proofs natively instead of wrapping them in a Groth16 proof?