Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Every Circom program produces two artifacts: the R1CS (constraints) and the witness generator (WASM or C++ that computes signal values from inputs). The constraint system says 'these equations must hold.' The witness generator says 'given inputs, here's how to compute each signal.' They are separate artifacts. A bug in witness generation doesn't break soundness (the SNARK still proves the R1CS holds), but it does mean the prover can't produce valid witnesses for honest inputs. The <-- operator lets you write witness-only hints (like 'divide by inv'); the <== operator is <-- plus ===. Understanding this split is the mental model that unlocks debugging.
A divider circuit: prove 'I know a and b such that a/b = c (integer division, b ≠ 0).' Witness-gen: c = a / b, r = a mod b. Constraints: a === b * c + r, r < b (range check). The division happens in witness-gen as a hint; the SNARK verifies the multiplication relation.
pragma circom 2.1.6;
include "circomlib/circuits/comparators.circom";
template Divider() {
signal input a;
signal input b;
signal output c;
signal r;
// Witness-gen hint: do the real division
c <-- a \ b;
r <-- a - b * c;
// Constraints: a == b*c + r AND 0 <= r < b
a === b * c + r;
component lt = LessThan(32);
lt.in[0] <== r;
lt.in[1] <== b;
lt.out === 1;
}
component main = Divider();
// Input a=17, b=5 → c=3, r=2. Constraint: 17 = 5*3 + 2 ✓. r=2 < 5 ✓.c <-- a \ b with c <-- a / b (field division). Observe the weird integer results. Field division is not integer division.<-- lines entirely. Compile succeeds, but witness-gen fails with 'cannot compute signal.' The generator needs the hint to know what value to assign.Use these three in order. Each builds on the one before.
In one paragraph, explain the split between Circom constraints and witness generation — what each produces, and why `<--` vs `<==` matters.
Walk me through an under-constrained bug step by step: how can a witness-hint assign an arbitrary value if the constraint doesn't fully pin it down, and why does the SNARK still 'verify' the bogus proof?
Given I'm auditing a 5k-constraint Circom circuit, walk me through a systematic audit checklist: every `<--`, every conditional, every component instantiation — what properties must I verify to rule out under-constraint?