ACIR (Abstract Circuit Intermediate Representation) is the intermediate format that the Noir compiler produces. It sits between your Noir source code and the backend-specific constraint system used to generate proofs. Every proving backend that works with Noir consumes ACIR.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/noir-lang/noir/llms.txt
Use this file to discover all available pages before exploring further.
Why an intermediate representation?
Noir is designed to be backend-agnostic. Rather than targeting a specific proof system like PLONK or Groth16 directly, Noir compiles to ACIR, and each backend translates ACIR into its own native representation. This separation provides several benefits:- Backend portability — the same Noir program runs on any ACIR-compatible backend without rewriting.
- Independent optimization — the Noir compiler can improve ACIR emission without affecting backend implementations, and backends can optimize their own translation independently.
- Ecosystem composability — new backends only need to implement the ACVM interface to support the full Noir ecosystem.
The compiler pipeline
noirc_evaluator) lowers from SSA to two output formats:
- ACIR — encodes the constraints that must hold for the proof to be valid.
- Brillig — encodes unconstrained computation that runs during witness generation but is not included in the proof.
ACIR opcodes
A compiled Noir program is aProgram containing one or more Circuit values. Each Circuit holds a list of Opcode values that define the constraint system.
The core opcode types are:
AssertZero
AssertZero
The fundamental constraint opcode. It asserts that a multivariate polynomial over witnesses evaluates to zero:This single opcode can express equality, inequality (via negation), and multiplication constraints. For example, asserting that witness
z equals x * y is written as z - x*y = 0.BlackBoxFuncCall
BlackBoxFuncCall
Calls to backend-implemented “gadgets” — specialized constraints for operations that are expensive to express with bare arithmetic.Examples of available black box functions:
Backends must implement support for the black box functions they advertise.
| Function | Purpose |
|---|---|
RANGE | Assert a witness fits within a given bit width |
AND, XOR | Bitwise operations |
Blake2s, Blake3 | Hash functions |
EcdsaSecp256k1, EcdsaSecp256r1 | ECDSA signature verification |
MultiScalarMul | Elliptic curve multi-scalar multiplication |
Poseidon2Permutation | ZK-friendly hash permutation |
Keccakf1600 | Keccak-f[1600] permutation |
RecursiveAggregation | Recursive proof verification |
MemoryOp and MemoryInit
MemoryOp and MemoryInit
ACIR supports arrays of witnesses via memory opcodes.
MemoryInit declares an array with a fixed length, and MemoryOp performs reads and writes at a given index. This allows Noir’s array types to be represented efficiently in the circuit.BrilligCall
BrilligCall
Invokes an unconstrained Brillig function during witness generation. Brillig execution does not add constraints — it computes witness values that are then used by surrounding constrained opcodes. See Brillig below.
Call
Call
Calls another ACIR function (circuit) within the same program. Enables function calls to be represented as separate sub-circuits, which is required for recursive proof schemes.
Brillig
Brillig is the bytecode format for unconstrained execution in Noir. When your program calls anunconstrained function, the Noir compiler emits Brillig bytecode instead of ACIR opcodes.
Brillig runs on the Brillig VM during witness generation. It can perform arbitrary computation — loops over runtime-determined bounds, branching, division, sorting — without adding any constraints. The results it produces are then passed back into the constrained ACIR portion of the circuit, where they must be verified.
The separation is intentional. Many operations that are expensive in a constraint system are cheap to compute and cheap to verify:
- Division — computing
q = a / bin Brillig and then assertingb * q == ain ACIR is far cheaper than implementing division as constraints. - Square root — computing in Brillig and squaring to verify.
- Byte decomposition — computing the bytes of a field element in Brillig and asserting the reconstruction in ACIR.
Program struct and referenced by ID from BrilligCall opcodes:
The ACVM crate
Theacvm crate in the ACVM repository is the reference executor for ACIR. It:
- Walks ACIR opcodes and solves each one using provided witness values.
- Invokes the Brillig VM for
BrilligCallopcodes. - Delegates
BlackBoxFuncCallopcodes to a pluggableBlackBoxFunctionSolverimplementation.
acvm by implementing the solver interface for the black box functions they support. This is the primary integration point between Noir’s output and a backend’s proving system.
Inspecting ACIR
You can inspect the ACIR your Noir program compiles to usingnargo: