
Circom
- 2 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-skills
Reference the circom language and compiler - arithmetic circuits, constraints, signals, templates, compilation to R1CS, and safety.
About
A reference skill for circom 2.x, the DSL and compiler for arithmetic circuits used in zero-knowledge proving systems, covering templates, constraints, signals, and R1CS output. A developer uses it when defining or compiling ZK circuits with circom and snarkjs.
- Templates, components, signals, and constraints
- Compiles to R1CS with WASM/C++ witness generators
Circom by the numbers
- 2 all-time installs (skills.sh)
- Ranked #408 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-skills --skill circomAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-skills ↗ |
What it does
Reference the circom language and compiler - arithmetic circuits, constraints, signals, templates, compilation to R1CS, and safety.
Files
Skill is based on circom 2.x, generated at 2026-02-24.
circom is a domain-specific language and compiler for defining arithmetic circuits used in zero-knowledge proving systems (e.g. with snarkjs). Circuits are built from parameterized templates and components; the compiler outputs R1CS (and optionally WASM/C++ witness generators). This skill focuses on language semantics, constraint generation, and practical usage for agents.
Core References
| Topic | Description | Reference |
|---|---|---|
| Signals | Input/output/intermediate, assignment operators, public/private, immutability | core-signals |
| Templates and components | Definition, instantiation, dot notation, arrays, parallel and custom templates | core-templates-components |
| Constraint generation | Quadratic constraints, ===, <== vs <-- + === | core-constraints |
| Main component | Entry point, public input list, single main | core-main-component |
| Pragma and include | Version, custom_templates, include, -l | core-pragma-include |
Features
| Topic | Description | Reference |
|---|---|---|
| Operators | Field, boolean, relational, bitwise; precedence; conditional ? : | features-operators |
| Control flow | if/for/while, known vs unknown conditions, instantiation order | features-control-flow |
| Functions | Pure computations, no signals/constraints, return on every path | features-functions |
| Variables and data types | var, arrays (known size), field and signal arrays | features-variables-data-types |
| Anonymous components and tuples | Inline instantiation, multiple outputs, _, array <== | features-anonymous-tuples |
| Tags | Signal tags (e.g. binary, maxbit), inheritance, valued tags | features-tags |
| Buses | Struct-like signal groups, tagging, nested/parameterized, input format | features-buses |
| Compilation | CLI flags (r1cs, wasm, c, sym, O0/O1/O2, prime, -l, inspect) | features-compilation |
| Scoping | Signals/components at top-level or known-condition if; var block scope | features-scoping |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| Signal safety | Prefer <==/==>; use <-- only when needed and add ===; use --inspect | best-practices-signal-safety |
| Assert and log | Compile-time vs witness-time assert; log() for debugging | best-practices-assert-log |
| Known vs unknown | Signals unknown; constraints and indices under known control flow | best-practices-unknowns |
| Simplification | When to use O0/O1/O2; PLONK vs Groth16; large circuits | best-practices-simplification |
Generation Info
- Source:
sources/circom - Doc path:
sources/circom/mkdocs/docs/ - Git SHA:
ad44e915a12bb047b05745c2884aad9cc8326bc6 - Generated: 2026-02-24
Assert and Log
assert(condition)
- Known condition (only template params/constants): evaluated at compile time. If false, compilation fails with "False assert reached".
- Unknown condition (involves signals): an assert is emitted in the witness generation code. If the condition fails at witness time, the witness is not produced.
assert(n > 0); // compile-time if n is template param
assert(in <= 254); // witness-time checkConstraints added with `===` also insert an assert in the witness by default; disable with `--sanity_check 0`.
log (debugging)
`log(expr)` / `log("msg", expr1, expr2, ...)` / `log()` (newline) — prints to stderr during witness generation. Use for debugging only; no stdin/stdout. Parameters must be non-conditional expressions (no ? : in the logged expression).
log(135);
log("value of c.b is", c.b);
log();<!-- Source references:
- https://docs.circom.io/circom-language/code-quality/code-assertion/
- https://docs.circom.io/circom-language/code-quality/debugging-operations/
- https://github.com/iden3/circom
-->
Signal and Constraint Safety
Prefer constrained assignment
Use `<==` and `==>` so the compiler adds the matching R1CS constraint. Reserve `<--` and `-->` for expressions that cannot be written as quadratic constraints (e.g. bit extraction, conditional inverse).
When using <--, always add an explicit `===` constraint that defines the relationship (e.g. out[k] * (out[k]-1) === 0 for a bit, or a*c === b when a <-- b/c).
Use --inspect
Compile with `--inspect` to get warnings for:
- Signals (including subcomponent I/O) that do not appear in any constraint — fix or mark with
_ <== signal;if intentional. - Assignments `<--` that could be `<==` (e.g.
out <-- in/4whenin/4is linear) — replace with<==when suggested.
Addressing these reduces risk of underconstrained or inconsistent circuits.
<!-- Source references:
- https://docs.circom.io/circom-language/signals/
- https://docs.circom.io/circom-language/constraint-generation/
- https://docs.circom.io/circom-language/code-quality/inspect/
- https://github.com/iden3/circom
-->
Constraint Simplification
- `--O1` (default): Removes simple equalities (signal = constant, signal = signal). Use for PLONK; full linear simplification (--O2) is not compatible with PLONK.
- `--O2`: Applies Gauss elimination to remove linear constraints; can greatly reduce constraint count and proof cost for Groth16. Compilation is slower and uses more memory; for very large circuits it can run for a long time or OOM. Prefer using `--O2` only in later stages or with `--O2round N` to cap rounds.
- `--O0`: No simplification; useful for debugging or when you need to preserve every constraint.
Use `--simplification_substitution` to inspect which substitutions were applied (see simplification JSON format).
<!-- Source references:
- https://docs.circom.io/getting-started/compilation-options/
- https://docs.circom.io/circom-language/circom-insight/simplification/
- https://github.com/iden3/circom
-->
Known vs Unknown
At compile time, template parameters and constants are known; signals are always unknown. Expressions that depend on any unknown are unknown.
Implications
- Constraints: Do not generate constraints inside if or for/ while whose condition is unknown. Do not use a var that gets its value only in such a branch in a constraint.
- Array size: Must be known (constant or template param).
var array[in];is invalid. - Array index in constraints: Must be known.
out <== array[in];is invalid (non-quadratic when index is unknown). - Bus parameters: Bus definitions and usages must be parameterized only by known values.
- Component parameters: Template arguments at instantiation must be known.
When in doubt, use only template parameters and constants for sizes, indices, and branch conditions that guard constraint generation.
<!-- Source references:
- https://docs.circom.io/circom-language/circom-insight/unknowns/
- https://docs.circom.io/circom-language/control-flow/
- https://github.com/iden3/circom
-->
The Main Component
The circuit entry point is the main component. It defines the global input and output of the circuit.
Syntax
component main {public [signal_list]} = tempid(v1,...,vn);{public [signal_list]} is optional. Inputs in the list are public; other inputs of the template are private. All outputs of main are always public and cannot be made private.
Example
template A(){
signal input in1;
signal input in2;
signal output out;
out <== in1 * in2;
}
component main {public [in1]} = A();Here in1 is public, in2 is private, out is public.
Rule
Exactly one main component must exist in the whole project (including all included files). Otherwise: "Multiple main components in the project structure".
<!-- Source references:
- https://docs.circom.io/circom-language/the-main-component/
- https://github.com/iden3/circom
-->
Pragma and Include
Version pragma
Every .circom file should start with:
pragma circom xx.yy.zz;This ensures compatibility with the compiler version. Omitting it triggers a warning.
Custom templates pragma
If the file (or any included file) declares custom templates, add after the version pragma:
pragma custom_templates;Otherwise the compiler errors and indicates which files need it.
Include
Pull in other circuits with include (default extension .circom):
include "montgomery.circom";
include "bitify.circom";Since 2.0.8, use `-l <directory>` to add library search paths:
circom circuit.circom -l ./lib -l ./node_modules/circomlib/circuits<!-- Source references:
- https://docs.circom.io/circom-language/pragma/
- https://docs.circom.io/circom-language/include/
- https://github.com/iden3/circom
-->
Signals
Signals hold field elements in Z/pZ. Declare with signal input, signal output, or plain signal (intermediate). Arrays: signal output out[N].
Assignment operators
- `<==` / `==>` — Assign and add an R1CS constraint (safe). Prefer these.
- `<--` / `-->` — Assign only in witness; no constraint (dangerous). Use only when the expression cannot be expressed as a quadratic constraint (e.g. bit extraction); then add an explicit
===constraint.
// Safe: constraint out === in[0]*in[1] is added
out <== in[0] * in[1];
// Dangerous: no constraint; must add one manually
out[k] <-- (in >> k) & 1;
out[k] * (out[k] - 1) === 0; // enforce binarySignals are immutable: once assigned, value cannot change. Double assignment is a compile error. At compile time signals are always treated as unknown (even if assigned a constant).
Public vs private
Only the main component distinguishes public/private. Declare public inputs as component main {public [in1,in2]} = MyTemplate();. All outputs of main are public; inputs not in the list are private. Intermediate signals are never public.
component main {public [in1,in2]} = Multiplier2();Output and intermediate signals can be initialized at declaration (since 2.0.4): signal output out <== in1 * in2;.
Key points
- Use
<==/==>unless the expression is non-quadratic; then use<--/-->and add===constraints. - Only main’s input list and all of main’s outputs are visible outside the circuit.
- Access component I/O via dot notation (e.g.
comp.out); intermediate signals are not visible.
<!-- Source references:
- https://docs.circom.io/circom-language/signals/
- https://github.com/iden3/circom
-->
Templates and Components
Templates define parameterized circuits. Components are instances of templates. Templates cannot define local functions or nested templates.
Template and instantiation
template tempid(param_1, ..., param_n) {
signal input a;
signal output b;
// ...
}
component c = tempid(v1, ..., vn);Parameters must be known at compile time. Do not assign to an input signal inside the same template where it is defined.
Component semantics
- Dot notation: access inputs/outputs only, e.g.
c.a <== y*z-1;,var x = c.b;. - Lazy instantiation: the component runs only when all its input signals are assigned. Outputs are usable only after all inputs are set. Order of assignment in code need not match execution order.
- Immutability: a component can be declared then assigned once (or in different branches with the same template). All elements of a component array must be the same template (possibly different parameters).
component comp1 = mult();
component comp2 = mult();
comp2.in[0] <== in[1];
comp2.in[1] <== in[2];
comp1.in[0] <== in[0];
comp1.in[1] <== in[3];
// comp2 is instantiated before comp1Arrays of components
Size must be known at compile time. Initialize element by element; all elements must be the same template.
component ands[2];
ands[0] = MultiAND(n1);
ands[1] = MultiAND(n2);Parallel witness computation
Tag a template for parallel C++ witness generation: template parallel NameTemplate(...){...}. Or at call site: component comp = parallel NameTemplate(...);. Only affects C++ backend.
Custom templates (PLONK)
Since 2.0.6, template custom Example() { ... } defines a custom gate for snarkjs/PLONK. No constraints or subcomponents inside. Add pragma custom_templates; in files that use them. With extern_c, the compiler can emit a C method stub for custom gates when using --c.
<!-- Source references:
- https://docs.circom.io/circom-language/templates-and-components/
- https://github.com/iden3/circom
-->
Anonymous Components and Tuples
Anonymous component syntax
`temp_name(args)(inputs)` — instantiate template, assign inputs, and (optionally) capture output in one expression. Inputs can be given by position or by name (since 2.1.1): Template(n)(b <== in[1], a <== in[0]).
// Instead of component + manual wiring:
signal out <== A(n)(in[0], in[1]);Only `<==`, `==>`, `=` are allowed for anonymous component output (not <--/-->). For multiple outputs use a tuple:
signal output o1, o2, o3;
(o1, o2, o3) <== Temp(args)(inputs);No output: use as statement Temp(args)(inputs);.
Ignoring outputs with _
Use `_` to ignore one or more outputs:
_ <== A(n)(in[0], in[1], in[2]);
(_, out1, _) <== A(n)(in);Useful with --inspect: _ <== comp.out; or _ <== comp.out[i] to mark intentionally unused signals.
Tuples and element-wise array assignment
Tuples allow multiple assignment: (a, b, c) = (1, a+1, A(2)); (element-wise, left-to-right). For signal arrays of the same size, `out <== in` is element-wise:
signal input in[n];
signal output out[n];
out <== in;Same size required. Combines naturally with anonymous components: o <== Ex(4,4)(i); where Ex has input/output arrays.
<!-- Source references:
- https://docs.circom.io/circom-language/anonymous-components-and-tuples/
- https://github.com/iden3/circom
-->
Buses (circom 2.2+)
Buses group related signals under one name (struct-like). They can be inputs, outputs, or intermediates and can be tagged.
Definition and use
bus Point() {
signal x;
signal y;
}
template Edwards2Montgomery() {
input Point() {edwards_point} in;
output Point() {montgomery_point} out;
out.x <-- (1 + in.y) / (1 - in.y);
out.y <-- out.x / in.x;
out.x * (1 - in.y) === (1 + in.y);
out.y * in.x === out.x;
}Assignments between buses require same type; otherwise assign field by field (e.g. b2.x <== b1.x). The compiler checks bus type and field tags on instantiation.
Nested and parameterized buses
Buses can contain other buses (no recursion). Parameters must be known at compile time:
bus PointN(dim) { signal x[dim]; }
bus Line(dim) {
PointN(dim) start;
PointN(dim) end;
}Circuit inputs (main)
For input buses to main, provide values either as serialized array (all fields in definition order) or JSON with field names; do not mix within one bus. Public input buses cannot be tagged.
<!-- Source references:
- https://docs.circom.io/circom-language/buses/
- https://github.com/iden3/circom
-->
Compilation Options
Output flags
- `--r1cs` — R1CS in binary.
- `--sym` — Symbol file (signal id, witness index, component id, qualified name) for debugging/annotated output.
- `--wasm` — WebAssembly witness generator (directory
*_js). - `-c` / `--c` — C++ witness generator (directory
*_cpp). Use `--no_asm` for portability. - `--json` — R1CS in JSON.
- `--wat` — Compile to WAT.
- `-o <dir>` — Output directory (default
.). - `--simplification_substitution` — JSON with substitutions from simplification.
Optimization
- `--O0` — No simplification.
- `--O1` — Signal-to-signal and signal-to-constant simplification (default).
- `--O2` — Full linear simplification (Gauss); reduces constraints but slower and more memory; not compatible with PLONK. Use `--O2round N` to limit rounds.
Only one of --O0, --O1, --O2/--O2round per run.
Other options
- `-p` / `--prime` — Curve/prime: bn128 (default), bls12377, bls12381, goldilocks, grumpkin, pallas, secq256r1, vesta.
- `-l <dir>` — Add directory to include path (repeatable).
- `--sanity_check` — 0 (none), 1 (assert for
===), 2 (also check subcomponents executed). Default 2. - `--inspect` — Extra R1CS checks (underconstrained signals,
<--that could be<==). - `--no_init` — Omit zero-initialization of
varin witness code. - `--verbose` — Log known values during constraint generation.
Example
circom circuit.circom --r1cs --wasm --sym -o build -l ./lib<!-- Source references:
- https://docs.circom.io/getting-started/compilation-options/
- https://docs.circom.io/getting-started/compiling-circuits/
- https://github.com/iden3/circom
-->
Control Flow
Standard constructs: if (condition) block else block, for (init; condition; step) block, while (condition) block. else is optional.
Known vs unknown conditions
- Constraint rule: If a constraint is generated inside an
ifor loop, the condition must be known at compile time (no signal in the condition). Otherwise: "There are constraints depending on the value of the condition and it can be unknown during the constraint generation phase". - Var rule: A
varassigned only inside a branch/loop with an unknown condition is considered unknown and cannot be used in a constraint.
// Error: constraint inside if with unknown condition
if (in > N1) { c = A(); }
// OK: no constraint depends on unknown
if (in > N) { t = 2; }
// OK: condition uses only template params
if (N1 > N2) { t = 2; }
x === t;For/while loop bounds and indices used in constraints must be known (template parameters or constants).
Component instantiation order
Component execution is triggered when all its inputs are assigned, not by code order. So instantiation order can differ from the sequence of lines.
comp2.in[0] <== in[1];
comp2.in[1] <== in[2];
comp1.in[0] <== in[0];
comp1.in[1] <== in[3];
// comp2 may run before comp1<!-- Source references:
- https://docs.circom.io/circom-language/control-flow/
- https://docs.circom.io/circom-language/circom-insight/unknowns/
- https://github.com/iden3/circom
-->
Functions
Functions compute values or expressions. They cannot declare signals or generate constraints (use templates for that).
Syntax and rules
function funid(param1, ..., paramn) {
// ...
return x;
}- Parameters and return can be numeric or arrays.
- Functions can be recursive.
- Every execution path must end in a return; otherwise: "In funid there are paths without return".
- Any use of signal or constraint operators inside a function causes "Template operator found".
Example
function nbits(a) {
var n = 1, r = 0;
while (n-1 < a) {
r++;
n *= 2;
}
return r;
}Use functions for compile-time or witness-time computations (e.g. array sizes, coefficients); use templates for circuit structure and constraints.
<!-- Source references:
- https://docs.circom.io/circom-language/functions/
- https://github.com/iden3/circom
-->
Basic Operators
Arithmetic is modulo p (default bn128 prime). Precedence and associativity follow Rust.
Field and conditional
- Conditional (top-level only, no nesting):
condition ? true_value : false_value - Arithmetic:
+,-,*,**,/,\(integer division),%; compound+=,-=,*=,**=,/=,\=,%=,++,-- - Boolean:
&&,||,! - Relational:
<,>,<=,>=,==,!=— defined viaval(x)(signed interpretation in Z/pZ)
Bitwise (mod p)
&, |, ~, ^, >>, << and compound &=, |=, ^=, >>=, <<=.
Shift semantics: for small k, x >> k = x/(2**k); left shift uses field size and mask. For k in upper half, shifts wrap (e.g. x >> k becomes x << (p-k)).
Example: IsZero and Num2Bits
template IsZero() {
signal input in;
signal output out;
signal inv;
inv <-- in!=0 ? 1/in : 0;
out <== -in*inv + 1;
in*out === 0;
}
template Num2Bits(n) {
signal input in;
signal output out[n];
var lc1=0, e2=1;
for (var i = 0; i<n; i++) {
out[i] <-- (in >> i) & 1;
out[i] * (out[i] - 1) === 0;
lc1 += out[i] * e2;
e2 = e2 + e2;
}
lc1 === in;
}<!-- Source references:
- https://docs.circom.io/circom-language/basic-operators/
- https://github.com/iden3/circom
-->
Scoping
circom uses static scoping (C/Rust-like). Signals, buses, and components must be in the top-level block of the template or, since 2.1.5, inside an if block whose condition is known at compile time.
// Error: aux inside for
for (var i = 0; i < N; i++) {
signal aux;
aux <== in[i]*in[i];
}
// OK: signal inside if with known condition
if (i < n) {
signal out <== 2;
i = out;
}A signal declared inside an if is visible only in that block. Var can be in any block; visibility is limited to that block.
Component and signal visibility
From a template T that has component c, only input and output signals of c are visible as c.inputName, c.outputName. No access to intermediate signals of c or to signals of subcomponents of c (e.g. c.comp2.x is invalid if comp2 is inside c).
<!-- Source references:
- https://docs.circom.io/circom-language/scoping/
- https://github.com/iden3/circom
-->
Signal Tags (circom 2.1+)
Tags annotate signals (e.g. "binary", "maxbit"). The compiler does not check validity; the programmer must enforce meaning with constraints.
Declaration and inheritance
signal input {binary} in[n];
signal output {maxbit} out;On substitution, tags inherit: if intermediate <== in and in has tag binary, intermediate is treated as binary for later checks. Outputs can be tagged (e.g. signal output {binary} out) when constraints guarantee the property (e.g. via IsZero or explicit constraints).
Valued tags
Tags can carry a value (e.g. bit width). Set before the signal receives a value; use in expressions with .:
signal output {maxbit} out;
out.maxbit = n;
lc1 ==> out;Modifying a tag value after the signal is assigned is an error. For arrays, the tag applies to the whole array; access via the array name, e.g. out.max = 10.
Tags on buses
Buses and their fields can be tagged in the same way (see buses reference).
<!-- Source references:
- https://docs.circom.io/circom-language/tags/
- https://github.com/iden3/circom
-->
Variables and Data Types
Variables (var)
Declared with `var`; they are mutable. Assignment with =. No use of = inside expressions.
var x;
x = 234556;
var y = 0;
var z[3] = [1,2,3];Vars hold field values or arithmetic expressions used when building constraints. Array size must be known at compile time (constants or template parameters). Declaration like var z = [2,8,4] is invalid—size must be explicit: var z[3] = [2,8,4].
Arrays
- Var arrays:
var x[n],var dbl[16][2]; accessm[i][j](no single-bracket matrix stylem[i,j]). - Signal arrays:
signal input in[3];,signal output out[2];— size fixed, assign by index. - Component arrays:
component c[N];— same template for all elements, instantiate per index. Inline arrays of buses or anonymous components allowed (2.2.3+).
Array indices in constraints must be known (no signal as index). Array length must be known (no signal as size).
<!-- Source references:
- https://docs.circom.io/circom-language/variables-and-mutability/
- https://docs.circom.io/circom-language/data-types/
- https://docs.circom.io/circom-language/circom-insight/unknowns/
- https://github.com/iden3/circom
-->