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
}