From noir
Guidelines for writing idiomatic, efficient Noir programs. Use when writing or reviewing Noir code.
npx claudepluginhub critesjosh/noir-claude-plugin --plugin noirThis skill uses the workspace's default tool permissions.
These guidelines help you write Noir programs that are readable, idiomatic, and produce efficient circuits.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
These guidelines help you write Noir programs that are readable, idiomatic, and produce efficient circuits.
Computing a value is often more expensive in a circuit than verifying a claimed value is correct. Use unconstrained functions to compute results off-circuit, then verify them with cheap constraints.
// Expensive: sorting an array in-circuit requires many comparisons and swaps
let sorted = sort_in_circuit(arr);
// Cheaper: hint the sorted array, verify it's a valid permutation and is ordered
let sorted = unsafe { sort_hint(arr) };
// verify sorted order and that sorted is a permutation of arr
Note that the compiler already injects unconstrained helpers for some operations automatically (e.g., integer division). Don't hint what the compiler already optimizes — focus on higher-level computations like sorting, searching, and array construction where the compiler cannot automatically apply this pattern.
Hint the final result, not intermediate values. If your unconstrained function computes helper structures (masks, indices, accumulators) on the way to an answer, return only the answer and verify it directly against the inputs. Fewer hinted values means fewer constraints needed.
Every unsafe block must have a // Safety: comment that explains why the constrained code makes the hint sound — not just where the verification happens, but what property it enforces:
// Safety: each result element is checked against a[i] or b[i] depending on
// whether i <= index, so a dishonest prover cannot substitute arbitrary values
let result = unsafe { my_hint(a, b, index) };
Noir compiles to two different targets depending on context, and they have fundamentally different performance characteristics.
ACIR (constrained code) — the default. Every operation becomes arithmetic constraints in a circuit. Optimize for gate/constraint count: fewer constraints = faster proving.
Brillig (unconstrained code) — functions marked unconstrained. Runs on a conventional VM. Optimize for execution speed and bytecode size: familiar performance intuitions apply.
| Aspect | ACIR (constrained) | Brillig (unconstrained) |
|---|---|---|
| Loops | Fully unrolled — bounds must be comptime-known | Native loop support — runtime-dynamic bounds are fine |
| Control flow | Flattened into conditional selects — both branches are always evaluated | Real branching — only the taken branch executes |
| Function calls | Always inlined | Preserved when beneficial |
| Comparisons | Inequality (<, <=) requires range checks (costs gates) | Native comparison instructions (cheap) |
is_unconstrained()When a function may be called in either context, use is_unconstrained() to provide optimized implementations for each target. This is common in the standard library:
pub fn any<Env>(self, predicate: fn[Env](T) -> bool) -> bool {
let mut ret = false;
if is_unconstrained() {
// Brillig path: use the actual length directly
for i in 0..self.len {
ret |= predicate(self.storage[i]);
}
} else {
// ACIR path: iterate the full static capacity, guard with a flag
let mut exceeded_len = false;
for i in 0..MaxLen {
exceeded_len |= i == self.len;
if !exceeded_len {
ret |= predicate(self.storage[i]);
}
}
}
ret
}
The constrained path must iterate the full MaxLen because ACIR loops are unrolled at compile time — the compiler needs a static bound. The unconstrained path can loop over exactly self.len elements because Brillig supports runtime-dynamic loop bounds.
unconstrained functions — it wastes execution time without adding security (unconstrained results are verified by the constrained caller).Noir's type system provides range guarantees that make subsequent constraints cheaper — the compiler knows what values a type can hold and can emit simpler arithmetic as a result. Use typed values instead of manual field arithmetic.
bool Instead of Field ArithmeticThe bool type guarantees values are 0 or 1, so the compiler can use simpler constraints for operations on booleans. Prefer boolean operators over field multiplication for logical conditions:
// Prefer: readable, compiler knows switched[i] is 0 or 1
assert(!switched[i] | switched[i - 1]);
// Avoid: manual field encoding of the same logic
let s = switched[i] as Field;
let prev = switched[i - 1] as Field;
assert(s * (1 - prev) == 0);
Both compile to equivalent constraints, but the boolean version communicates intent.
if/else Expressions for Conditional ValuesThe compiler lowers if cond { a } else { b } into an optimized conditional select. Don't hand-roll the arithmetic:
// Prefer: clear intent
let val = if condition { x } else { y };
// Avoid: manual select
let c = condition as Field;
let val = c * (x - y) + y;
When both branches of an if/else contain assertions, extract the condition into a value and assert once. The compiler optimizes a single assertion against a conditional value better than separate assertions in each branch:
// Prefer: one assertion, compiler optimizes the conditional select
let expected = if condition { a } else { b };
assert_eq(result, expected);
// Avoid: duplicated assertions in each branch
if condition {
assert_eq(result, a);
} else {
assert_eq(result, b);
}
Use assert_eq over assert(x == y). It provides better error messages on failure and reads more naturally:
assert_eq(result[i], expected);
Integer comparisons (<, <=, >, >=) require range checks, which cost gates. Equality checks (==) are cheaper. Strategies to reduce comparison costs:
i <= index in a loop, do it once per iteration — don't check the same condition in multiple places.<= with flag-tracking (if i == index { flag = true }) adds state and may produce more gates. Always measure before committing to a "cleverer" approach.When writing or reviewing Noir code:
bool types and operators, not Field arithmetic?if/else expressions, not manual selects?assert_eq where applicable?