Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides11 min read

How Zero-Knowledge Proofs Work: A Practical Guide to Prover, Verifier, and On-Chain Verification

A technical walkthrough of how zero-knowledge proofs work, from the three required properties and interactive protocols to non-interactive SNARK/STARK verification and a reproducible local test harness.

TL;DR

A zero-knowledge proof lets a prover convince a verifier that a statement is true without revealing the secret that makes it true. The protocol must satisfy three properties: completeness (true statements are accepted), soundness (false statements are rejected), and zero-knowledge (the verifier learns nothing beyond the statement's validity). In practice, most blockchain systems use non-interactive proofs derived from interactive protocols via the Fiat-Shamir transform, then verify them either on-chain or off-chain. This guide explains the mechanism, shows a runnable local verification harness, and describes how to measure proof generation and verification behavior on your own infrastructure.

Direct Answer and Scope

A zero-knowledge proof (ZKP) works by having a prover run a computation over a secret witness and produce a proof object that a verifier can check against a public statement. If the proof checks out, the verifier is convinced the statement is true, but the proof reveals nothing about the witness beyond what the statement already implies.

This article covers the protocol mechanics, the difference between interactive and non-interactive proofs, how SNARKs and STARKs differ at a practical level, and a reproducible procedure for testing proof verification locally and against an RPC endpoint. It does not recommend a specific endpoint or provider; for concrete endpoint selection, use the RPC Assistant.

  • Prover: holds the secret witness and generates the proof.
  • Verifier: checks the proof against a public statement and a verification key.
  • Statement: the public claim being proven, such as 'I know a preimage of this hash'.
  • Witness: the private input that makes the statement true.

Prerequisites and Assumptions

You should be comfortable with basic cryptography concepts such as hash functions and public-key signatures, and with running Node.js or Python locally. The examples assume you have a working RPC_URL for an EVM-compatible chain if you want to test on-chain verification, but the local harness runs without any network access.

No specific ZK library is required to follow the conceptual sections. The code samples use placeholder values and standard library calls so you can adapt them to your chosen proving system.

The Three Properties That Define a ZK Proof

Every zero-knowledge proof system must satisfy three properties. Completeness means that if the statement is true and both parties follow the protocol honestly, the verifier accepts. Soundness means that if the statement is false, no cheating prover can convince the verifier except with negligible probability. Zero-knowledge means the verifier learns nothing beyond the truth of the statement, formally captured by the existence of a simulator that can produce transcripts indistinguishable from real ones without the witness.

These properties are what separate a ZK proof from a plain signature or hash commitment. A signature proves you hold a key, but it does not hide the message. A ZK proof hides the witness while still binding the verifier to the statement's truth.

Interactive Proofs: The Challenge-Response Mental Model

In an interactive ZK proof, the prover and verifier exchange multiple messages. The classic structure is commit-challenge-response: the prover commits to a value derived from the witness, the verifier sends a random challenge, and the prover responds in a way that only someone with the witness could do consistently across many challenges.

The Ali Baba cave and the color-blind friend examples illustrate the intuition. The prover repeatedly demonstrates knowledge under randomly chosen challenges; a cheating prover has only a 50% chance of guessing correctly each round, so after enough rounds the probability of cheating undetected becomes negligible. The verifier never learns the secret because each transcript could have been simulated by someone without the witness.

  • Round count controls soundness error: each round halves the cheating probability in the simplest protocols.
  • Interaction is expensive on-chain because it requires multiple transactions or messages.
  • Interactive proofs are useful for off-chain protocols where both parties are online.

Non-Interactive Proofs and the Fiat-Shamir Transform

Most blockchain applications need non-interactive proofs: a single proof object that anyone can verify later without talking to the prover. The Fiat-Shamir heuristic converts an interactive protocol into a non-interactive one by replacing the verifier's random challenge with a hash of the prover's commitment and the public statement.

This works because the prover cannot predict the hash output before committing, so the challenge remains unpredictable. The resulting proof is a single message that binds the statement, the commitment, and the response together. Security depends on the hash function behaving like a random oracle, which is why the choice of hash and domain separation matters in production systems.

SNARKs vs STARKs: Practical Differences

ZK-SNARKs (Succinct Non-Interactive Arguments of Knowledge) produce small proofs and fast verification, but often require a trusted setup ceremony to generate proving and verification keys. ZK-STARKs (Scalable Transparent Arguments of Knowledge) avoid trusted setup and rely on hash-based cryptography, which makes them plausibly post-quantum resistant, but proofs are typically larger and verification can be more expensive.

The right choice depends on your constraints. If on-chain verification cost dominates, SNARKs are often preferred. If transparency and setup assumptions matter more, STARKs may be preferable. Both are non-interactive and both satisfy the three ZK properties when implemented correctly.

  • SNARK: small proof size, fast verification, possible trusted setup.
  • STARK: transparent setup, larger proofs, hash-based security assumptions.
  • Both require careful circuit design; a bug in the circuit can break soundness.

Runnable Local Verification Harness

The following Node.js script simulates the verifier side of a non-interactive proof check. It does not implement a real proving system; it demonstrates the control flow you will use when integrating a real verifier library. Replace the placeholder proof and verification key with outputs from your chosen proving system.

Run it with node verify.js after setting the environment variables. The script exits with code 0 on success and 1 on failure, which makes it easy to wire into CI.

// verify.js
const crypto = require('crypto');

const statement = process.env.STATEMENT || 'I know the preimage of this commitment';
const proof = process.env.PROOF || 'placeholder-proof';
const vk = process.env.VERIFICATION_KEY || 'placeholder-vk';

function verify(statement, proof, vk) {
  // Replace this with a real verifier call, e.g. snarkjs.groth16.verify(vk, publicSignals, proof)
  const digest = crypto.createHash('sha256').update(statement + proof + vk).digest('hex');
  return digest.length === 64;
}

const ok = verify(statement, proof, vk);
console.log(ok ? 'PROOF ACCEPTED' : 'PROOF REJECTED');
process.exit(ok ? 0 : 1);

Diagnostic Procedure: Measuring Proof Verification Behavior

To understand how a ZK system behaves on your infrastructure, measure three things: proof generation time, proof size, and verification time. Run each measurement at least 30 times and record the median and 95th percentile. Do not rely on a single run, because proving time varies with witness size and machine load.

Use the following procedure to collect results in a table you can compare across machines or proving systems. The table below is a template; fill in your own measured values rather than assuming any numbers.

  • Step 1: Fix the circuit and witness size so runs are comparable.
  • Step 2: Time proof generation with a monotonic clock and record proof byte size.
  • Step 3: Time verification separately, including any deserialization cost.
  • Step 4: Repeat 30 times and compute median and p95.
  • Step 5: Record the machine type, library version, and hash function used.
| Run | Prove time (ms) | Proof size (bytes) | Verify time (ms) |
|-----|-----------------|--------------------|------------------|
| 1   |                 |                    |                  |
| 2   |                 |                    |                  |
| ... |                 |                    |                  |
| p50 |                 |                    |                  |
| p95 |                 |                    |                  |

On-Chain Verification via RPC

If your verifier is a smart contract, you can test verification by calling the contract's verify function through an RPC endpoint. The following curl example uses a placeholder RPC_URL and contract address. Replace them with your own values. For endpoint selection and reliability considerations, see RPC pricing and plans and the networks directory.

The call returns a boolean or a transaction receipt depending on whether the verifier is a view function or a state-changing function. A view function is cheaper to test because it does not require a transaction.

curl -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [{
      "to": "0xYourVerifierContract",
      "data": "0xYourEncodedProofAndPublicInputs"
    }, "latest"],
    "id": 1
  }'

Expected Verification Result and Troubleshooting

A successful verification returns true or a receipt with status 1. A failed verification returns false, reverts, or returns status 0. If you see a revert, decode the revert reason before assuming the proof is invalid; common causes include mismatched public inputs, a stale verification key, or a proof generated for a different circuit version.

If verification succeeds locally but fails on-chain, check that the public inputs are encoded in the same order and format the contract expects. If verification is slow, measure gas usage and consider whether the verifier contract can be optimized or whether an off-chain verifier with on-chain attestation is more appropriate.

  • Mismatched public inputs: re-encode and compare byte-for-byte.
  • Wrong verification key: confirm the key matches the circuit and proving system version.
  • Hash mismatch: ensure the Fiat-Shamir transcript uses the same domain separator.
  • Gas limits: increase the gas limit for view calls if the node enforces one.

Limitations and Tradeoffs

Zero-knowledge proofs are not free. Proving is computationally expensive, and the cost grows with circuit complexity. Trusted setups introduce ceremony assumptions that must be managed carefully. Proof systems also have version-specific quirks, and a proof generated by one library version may not verify with another.

Zero-knowledge does not mean anonymous by default. The statement itself may leak metadata, and the timing or size of proofs can correlate with witness properties. Treat ZK as one component of a privacy design, not a complete solution.

Next Steps

Start with a small circuit and a local verifier, then move to on-chain verification once the local harness passes consistently. Use the measurement procedure above to establish a baseline before optimizing. When you are ready to test against a live chain, choose an endpoint through the RPC Assistant and review dedicated node options if you need predictable throughput.

For broader context on how RPC endpoints fit into blockchain workflows, see the RPC endpoints guide.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started