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.
Circom circuits scale by composition. A template defines a parameterised circuit (e.g., 'a Merkle-path verifier for depth d'). Instantiating a template creates a component. Components can include other components. This is how circomlib gives you a Poseidon hash over any 2-element input, or a Merkle proof over any depth — by exposing templates parameterised on the relevant dimensions. Understanding the difference between templates and components, and how template parameters differ from signals, is the foundation of writing clean Circom.
A Multiplier template that's parameterised on N — the number of inputs. Instantiate with N=3 to multiply three numbers. N is a compile-time parameter (known when circom generates R1CS); signals are witness-time values.
pragma circom 2.1.6;
template NMultiplier(N) {
signal input in[N];
signal output out;
signal acc[N];
acc[0] <== in[0];
for (var i = 1; i < N; i++) {
acc[i] <== acc[i-1] * in[i];
}
out <== acc[N-1];
}
component main = NMultiplier(3);
// Witness: in = [2, 3, 5] → out = 30.NMultiplier(n) where n is a signal. Compile fails: template parameters MUST be compile-time. This constraint is the most surprising to programmers from dynamic languages.for (var i = 0; i < in[0]; i++)): compile fails. Circom's unrolling requires compile-time bounds.Use these three in order. Each builds on the one before.
In one paragraph, explain the difference between a Circom template and a component, and why template parameters must be compile-time constants.
Walk me through how circom compiles a parameterised template into R1CS: how loops are unrolled, how component instantiations are inlined, and why the resulting .r1cs is static.
I need to verify N signatures in one circuit, but N is user-configurable at deployment time. Walk me through how to structure this: do I generate one circuit per N, use conditional selectors, or deploy a maximum-N circuit and pad?