Skip to main content

Verify ZK Proofs (Groth16)

One-Liner Verification

If you have snarkjs JSON output files, verify a proof in a single call:

var result = CircomGroth16Adapter.Verify(proofJson, vkJson, publicJson);

This parses all three files, runs the BN128 pairing check, and returns result.IsValid.

Why Verify ZK Proofs in .NET?

Zero-knowledge proofs allow one party to prove a statement is true without revealing the underlying data. Groth16 is the most widely used ZK proof system in Ethereum (used by Zcash, Tornado Cash, Privacy Pools, and many rollups). While proof generation typically happens in JavaScript or Rust, verification can happen anywhere — and for server-side .NET applications, native verification avoids costly interop with Node.js or WASM runtimes.

Common use cases:

  • Backend validation of ZK proofs before submitting transactions
  • Cross-checking native verification against on-chain Solidity verifiers
  • Privacy-preserving applications where proof verification is part of a .NET pipeline

Prerequisites

Install the NuGet package:

dotnet add package Nethereum.ZkProofsVerifier

You will also need three JSON files produced by snarkjs after proving a Circom circuit:

  • proof.json — The Groth16 proof (G1/G2 curve points)
  • verification_key.json — The verification key from the trusted setup
  • public.json — The public circuit inputs

How It Works

Groth16 verification checks a pairing equation on the BN128 elliptic curve:

e(-A, B) · e(Alpha, Beta) · e(vkX, Gamma) · e(C, Delta) = 1

Where vkX = IC[0] + IC[1]·input[0] + IC[2]·input[1] + ...

If the equation holds, the proof is valid — the prover knew a valid witness for the circuit without revealing it.

The simplest approach uses CircomGroth16Adapter which handles all parsing internally:

using Nethereum.ZkProofsVerifier.Circom;

// Load snarkjs output
var proofJson = File.ReadAllText("proof.json");
var vkJson = File.ReadAllText("verification_key.json");
var publicJson = File.ReadAllText("public.json");

// Verify
var result = CircomGroth16Adapter.Verify(proofJson, vkJson, publicJson);

if (result.IsValid)
{
Console.WriteLine("Proof verified successfully!");
}
else
{
Console.WriteLine($"Verification failed: {result.Error}");
}

Example 2: Detect Tampered Proofs

Groth16 verification rejects any modification to the proof, inputs, or verification key. The simplest way to demonstrate this is to verify a valid proof against a different circuit's verification key or public inputs:

using Nethereum.ZkProofsVerifier.Circom;

// Verify with mismatched files — proof from circuit A, VK from circuit B
var result = CircomGroth16Adapter.Verify(proofJsonA, vkJsonB, publicJsonA);
// result.IsValid == false — VK doesn't match the proof's circuit

// Or verify with tampered public inputs JSON
var tamperedPublicJson = "[\"999\"]";
var result2 = CircomGroth16Adapter.Verify(proofJson, vkJson, tamperedPublicJson);
// result2.IsValid == false — inputs don't match what was proven

Error Messages

When verification fails, result.Error contains one of these messages:

ErrorCause
"Pairing check failed"Proof is invalid — tampered proof, wrong inputs, or mismatched VK
"Proof JSON is null or empty"Empty or null proof JSON string
"Verification key JSON is null or empty"Empty or null VK JSON string
"Public inputs JSON is null or empty"Empty or null public inputs JSON string