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.
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)
- 1Fix the public statement: a target digest everyone can see.
- 2The prover holds the witness x and locally checks the constraint H(x) == target is satisfied — this models "the prover knows a satisfying assignment".
- 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.
- 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.
- 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 outputverify 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.
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
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]Goldwasser, Micali & Rackoff — The Knowledge Complexity of Interactive Proof-Systems
- [2]NIST FIPS 180-4 — Secure Hash Standard (SHA-256)
- [3]Thaler — Proofs, Arguments, and Zero-Knowledge (survey)
References point to public, well-known technical sources for further study.
Related articles
