ROOTVIELROOTVIEL
← Academy

Module 7 · Labs & experiments

Lab — a first zero-knowledge circuit (conceptual)

A guided, self-contained teaching model on synthetic data. You express a statement as a circuit (public input, private witness, constraints), then simulate the prove/verify split: the prover checks the witness satisfies the constraints, the verifier checks the public statement. It is a conceptual illustration of the shape of ZK — NOT a real proving system: it offers no soundness and no genuine zero-knowledge, and the code reveals the witness, which a real system would not.

Learning objective

Model a tiny statement — "I know a preimage x such that H(x) equals a public target" — as a set of constraints over a public input and a private witness, and walk through what proving and verifying mean, and precisely what such a proof does and does not reveal.

Track
Engineering
Module
7 · Labs & experiments
Prerequisites
Hash functions · What is a zero-knowledge proof · Arithmetic circuits and R1CS · Node.js 16+ (built-in crypto)

Lab goal

This is a teaching model, not a production proving system. It runs offline on synthetic data to make one idea concrete: a statement can be written as constraints over a public part (what everyone sees) and a private witness (what only the prover knows), and "proving" means showing the witness satisfies those constraints for the given public input. The chosen statement is knowledge of a hash preimage: "I know x such that H(x) = target", where target is public and x is the secret witness.

Note

Important honesty: the code here is NOT zero-knowledge and NOT sound as cryptography. The function that "checks the witness" is handed the witness in the clear, so nothing is hidden and nothing is cryptographically enforced. A real system (a SNARK/STARK over a committed circuit) proves the constraints are satisfied WITHOUT revealing the witness and WITHOUT the verifier re-running the computation. This lab only models the structure.

Statement, witness, constraints

Public input (statement)
The target digest. Anyone may hold it; the claim is "there exists an x hashing to this target".
Private witness
The preimage x. In a real ZK proof this stays secret; the proof convinces the verifier it exists without disclosing it.
Constraint
A checkable relation the witness and public input must satisfy — here, H(witness) == target. A circuit is a system of such constraints (see arithmetic circuits and R1CS).

Real systems reduce a computation like "compute H(x) and compare to target" into many small arithmetic constraints (R1CS) that a proof system can argue about succinctly. We skip that machinery and keep a single high-level constraint, because the point is the prove/verify split and the information boundary, not the arithmetic.

Step by step (conceptual model)

  1. 1Fix the public statement: a target digest everyone can see.
  2. 2The prover holds the witness x and locally checks the constraint H(x) == target is satisfied — this models "the prover knows a satisfying assignment".
  3. 3The prover emits a (modelled) proof object. In this teaching model it is just a boolean plus the public input; a real proof would be a short cryptographic object that reveals nothing about x.
  4. 4The verifier checks the proof against the public target only. In a real system it would run a verification equation; here we simulate acceptance and then state explicitly what a real verifier would and would not learn.
  5. 5Observe the boundary: the verifier is convinced a preimage exists, but learns nothing it could use to recover x — in the real setting. In THIS code, x is visible, which is exactly the gap between a teaching model and a proving system.
lab7.js — CONCEPTUAL model only, not a real ZK proof (Node.js crypto)
const crypto = require('crypto'); const H = (x) => crypto.createHash('sha256').update(x).digest('hex'); // ---- Public statement ---- const SECRET = 'synthetic-preimage-42'; // the prover's private witness (lab only) const target = H(SECRET); // public input everyone can see // ---- Constraint: the relation a valid witness must satisfy ---- function constraintSatisfied(witness, publicTarget) { return H(witness) === publicTarget; // H(x) == target } // ---- "Prover": checks it holds a satisfying witness ---- // NOTE: a real prover produces a proof WITHOUT revealing the witness. // This model just confirms it knows one, then hands back a stand-in proof. function prove(witness, publicTarget) { const satisfies = constraintSatisfied(witness, publicTarget); return { claim: 'I know x with H(x)=target', publicTarget, modelledValid: satisfies }; } // ---- "Verifier": in a real system checks a short proof against the public input ---- // Here we only simulate the accept/reject decision on the modelled proof. function verify(proof, publicTarget) { return proof.publicTarget === publicTarget && proof.modelledValid === true; } const good = prove(SECRET, target); console.log('verify honest proof :', verify(good, target)); // true // A prover without the preimage cannot satisfy the constraint: const bad = prove('wrong-guess', target); console.log('verify false proof :', verify(bad, target)); // false console.log('what the PUBLIC target reveals about x:', 'nothing feasible (preimage resistance)'); console.log('what THIS lab code leaks about x:', 'the witness itself — because it is a model, not a real proof');

Expected result

expected console output
verify honest proof : true verify false proof : false what the PUBLIC target reveals about x: nothing feasible (preimage resistance) what THIS lab code leaks about x: the witness itself — because it is a model, not a real proof

The honest "proof" is accepted because the witness satisfies H(x) == target; a guess that does not hash to the target is rejected. The two final lines make the boundary explicit: the public target alone reveals nothing usable about x (hash preimage resistance), but this teaching code does see the witness directly — which is exactly what a real zero-knowledge proof removes. Treat the acceptance as "the structure checks out", not as a cryptographic guarantee.

What a real proving system adds

  • ▸Zero-knowledge: the proof reveals nothing about the witness beyond the truth of the statement — unlike this model, which holds the witness in the clear.
  • ▸Soundness: a prover who does not know a valid witness cannot produce an accepting proof except with negligible probability — this model simply trusts a boolean.
  • ▸Succinctness (for SNARKs): the proof is short and fast to verify, and the verifier does NOT re-run the computation — here the "verifier" trusts a flag the prover computed.
  • ▸A compiled circuit: the statement is reduced to many arithmetic constraints (e.g. R1CS) that the system argues about, rather than one high-level equality.
Note

Because a proof certifies only the statement, it says nothing about whether acting on it is correct or safe. A valid proof of "I hold an authorized credential" still does not make the requested action a good idea — authenticity and scope are not judgement.

Common mistakes

  • ▸Mistaking this model for a real proof. It has no zero-knowledge and no soundness; it only shows the public/private/constraint shape and the prove/verify split.
  • ▸Thinking the verifier in a real SNARK re-executes the computation. It checks a succinct proof against the public input; not re-running the witness computation is the point.
  • ▸Assuming the public target leaks the preimage. A good hash is preimage-resistant, so the target alone does not; but low-entropy or guessable witnesses can still be brute-forced regardless of the proof system.
  • ▸Confusing "the proof verifies" with "the claim is wise to act on". A proof binds a statement, not the correctness or safety of any resulting decision.

Exercise

Note

This model "verifies" by trusting a boolean the prover computed, so it has no soundness: a dishonest prover could just set modelledValid to true. Describe, at a high level, what a real proving system provides in place of that trusted boolean so that a prover without a valid witness cannot make the verifier accept.

Expected direction: A real system replaces the trusted flag with a cryptographic proof tied to a compiled circuit of the statement. The prover must produce a proof object that satisfies a verification equation which can only be satisfied (except with negligible probability) if the prover actually knows a witness making every constraint hold — this is soundness, and in a proof of knowledge it extends to knowledge soundness. The verifier checks that equation against the public input without seeing the witness and without re-running the computation, so a prover who lacks a valid witness cannot fabricate an accepting proof by merely asserting success.

Check your understanding

In the preimage statement, what is public, what is the witness, and what is the constraint?
Public: the target digest. Witness: the preimage x. Constraint: H(x) == target. The claim is that a witness satisfying the constraint exists, and in a real proof the prover shows this without revealing x.
Why is the lab code explicitly NOT a zero-knowledge proof?
Because the checking function is given the witness in the clear and the verifier trusts a boolean the prover computed — it provides neither secrecy of the witness (zero-knowledge) nor a guarantee that a prover without a witness would fail (soundness). It only models the structure.
Does a valid zero-knowledge proof mean the proven action is safe to perform?
No. A proof certifies the truth or knowledge of a statement (authenticity/scope). Whether acting on it is correct or safe is a separate judgement the proof does not make.

References

  1. [1]Goldwasser, Micali & Rackoff — The Knowledge Complexity of Interactive Proof-Systems
  2. [2]NIST FIPS 180-4 — Secure Hash Standard (SHA-256)
  3. [3]Thaler — Proofs, Arguments, and Zero-Knowledge (survey)

References point to public, well-known technical sources for further study.

Related articles