Builtins
Circuits provide built-in functions for assertions, hashing, conditional selection, and range checking. Each builtin compiles to a fixed number of constraints.
Quick Reference
Section titled “Quick Reference”| Builtin | Constraints (R1CS) | Purpose |
|---|---|---|
assert_eq(a, b) | 1 | Enforce equality |
assert(expr) | 2 | Enforce boolean expression is true |
poseidon(a, b) | 361 | Poseidon 2-to-1 hash |
poseidon_many(a, b, c, ...) | (N−1) × 361 | Left-fold Poseidon hash |
mux(cond, a, b) | 2 | Conditional selection |
range_check(x, bits) | bits + 1 | Value fits in N bits |
merkle_verify(root, leaf, path, indices) | depth × ~363 | Merkle membership proof |
len(arr) | 0 | Compile-time array length |
assert_eq
Section titled “assert_eq”Enforces that two expressions are equal. Costs 1 constraint.
circuit multiply(out: Public, a: Witness, b: Witness) { let product = a * b assert_eq(product, out)}This is the most common builtin — use it to bind computed values to public outputs.
assert
Section titled “assert”Enforces that a boolean expression is true. Costs 2 constraints (one for boolean enforcement, one to enforce the value equals 1).
circuit ordering(x: Witness, y: Witness) { assert(x < y)}Unlike assert_eq, this works with any expression that evaluates to 0 or 1.
poseidon
Section titled “poseidon”Computes a Poseidon 2-to-1 hash. Costs 361 constraints (360 for the permutation rounds + 1 for the capacity wire). The output is compatible with circomlibjs.
circuit hash_check(expected: Public, a: Witness, b: Witness) { let h = poseidon(a, b) assert_eq(h, expected)}poseidon_many
Section titled “poseidon_many”Hashes an arbitrary number of values by left-folding poseidon. Three arguments cost 2 × 361 = 722 constraints, four arguments cost 3 × 361 = 1083, and so on.
circuit hash_many(expected: Public, a: Witness, b: Witness, c: Witness) { // poseidon_many(a, b, c) == poseidon(poseidon(a, b), c) let h = poseidon_many(a, b, c) assert_eq(h, expected)}Conditional selection: returns a when cond is 1, b when cond is 0. Costs 2 constraints (one boolean enforcement for cond, one selection constraint).
circuit select(out: Public, cond: Witness, a: Witness, b: Witness) { let selected = mux(cond, a, b) assert_eq(selected, out)}Both a and b are always evaluated — there is no short-circuit behavior.
range_check
Section titled “range_check”Proves that a value fits within a given number of bits. In R1CS, this costs bits + 1 constraints (boolean decomposition). In Plonkish, it costs 1 lookup.
circuit range_demo(x: Witness, y: Witness) { // x fits in 8 bits (0..255) range_check(x, 8)
// y fits in 16 bits (0..65535) range_check(y, 16)}merkle_verify
Section titled “merkle_verify”Verifies a Merkle membership proof. Takes a root, a leaf, an array of sibling hashes (the path), and an array of direction indices (0 = left, 1 = right). The proof is unrolled at the IR level — each level costs roughly 363 constraints (one Poseidon hash + a mux).
circuit merkle_check(root: Public, leaf: Witness, path: Witness[1], indices: Witness[1]) { merkle_verify(root, leaf, path, indices)}The path and indices arrays must have the same length, which determines the tree depth.
Returns the compile-time length of an array. Costs 0 constraints — the value is resolved during compilation.
circuit len_demo(vals: Witness[3]) { let n = len(vals) assert_eq(n, 3)}