⚠ This example is being rewritten against the verified V0.8 syntax. The code shown may use a pre-V0.8 sketch that will not compile as-is. For the canonical, compiler-verified V0.8 examples — every one of them derived from a compiler test fixture — see the Playground Examples gallery. The 4 reference examples already updated on this site: Hello, ERC-20 Token, FHE Basics, Cryptographic Amnesia. The remaining ones land in Sprint 29.

10 — Zero-Knowledge Proofs

Zero-knowledge proofs let a prover convince a verifier that a statement is true without revealing the witness (secret input). Covenant exposes ZK as zk_prove / zk_verify — scheme-agnostic, circuit-agnostic language primitives.

Basic pattern

contract AgeVerifier {
  // Anyone can verify a ZK proof that the caller is >= 18
  // without learning the actual birthdate.
  action prove_adult(proof: ZkProof, pub_inputs: Bytes) {
    let circuit = Circuit("age_18_plus_v1");   // circuit ID registered at deployment
    require(
      zk_verify(circuit, proof, pub_inputs),
      InvalidProof
    );
    emit AdultVerified(msg.sender);
  }
}

Supported schemes

Configure in covenant.toml:

[zk]
scheme = "groth16"   # groth16 | plonk | halo2 | fflonk
SchemeProof sizeVerify gasTrusted setup
Groth16~200 B~280 KPer-circuit
PLONK~800 B~600 KUniversal
Halo2~1.2 KB~900 KNone
fflonk~256 B~350 KUniversal

Generating proofs off-chain

import { CovenantProver } from "@covenant-lang/zk-sdk";

const prover  = new CovenantProver("age_18_plus_v1");
const witness = { birthdate: 19900101n, today: 20260101n };
const { proof, publicInputs } = await prover.prove(witness);

await contract.prove_adult(proof, publicInputs);

Circuit registration

Circuits are deployed as precompile registrations at contract init time:

init() {
  register_circuit("age_18_plus_v1", circuit_vk: AGE_VK_BYTES);
}

AGE_VK_BYTES is the verification key, embedded at compile time via @embed("circuits/age.vk").

zk_prove (on-chain proving)

For small circuits, Covenant can prove on-chain using the chain’s native zkVM precompile:

action commit(secret: u256) {
  let pub_hash = zk_prove(
    circuit: "pedersen_commit_v1",
    private_inputs: [secret],
    public_inputs: []
  );
  self.commitments[msg.sender] = pub_hash;
}

On-chain proving is only practical for circuits with < 2^16 constraints. For larger circuits, use off-chain proving + zk_verify.

Recursive proofs

action verify_rollup_batch(
  inner_proofs: List<ZkProof>,
  aggregated_proof: ZkProof,
  pub_inputs: Bytes
) {
  let agg_circuit = Circuit("plonk_aggregator_v2");
  require(zk_verify(agg_circuit, aggregated_proof, pub_inputs), BadAggregation);
  // Each inner proof is verified by the aggregator circuit itself
}