How Noir circuits optimization works

Alex
Cryptography Engineer
Noir Circuit Optimizations Write-up
Overview of Clap paper
Paper: https://arxiv.org/pdf/2405.12115
The CLAP paper provides Semantics-preserving compilation(proved in Agda) and Safe automatic optimizations that match or sometimes outperform manual optimizations. CLAP paper also validates the thesis on the Boojum circuits are used in production by ZKsync Era and tested the expressivity and efficiency of CLAP on an implementation of the Poseidon2 and SHA2-256 hash functions.
Here, we will focus only on the circuit optimizations discussed in the CLAP paper and check if they are present in or can be implemented in the Noir circuit.
Section IV of the CLAP paper describes 5 optimizations at the circuit level in order to achieve the same (or better) circuit sizes.
Linear Inlining
Boolean Inlining
Circuit flattening
Common sub-expression elimination
Duplicate range-checks
Gate Conversions
Description of each optimization
1. Linear Inlining:
Substituting (inlining) the first equation into the second, we can eliminate first equation(intermediate witness) completely and simplify the circuit.
Example from CLAP:
$x_1 := c0 · x0 + c1$
$x3 := ql · x1 + qr · x2 + qm · (x1 · x2) + qc$
$x3 := (ql · c1) · x0 + (qr + qm · c2) · x2
+(qm · c1) · x0 · x2 + (ql · c2 + qc)$
2. Boolean Inlining:
Knowledge of a variable being boolean allows inlining opportunities because its degree does not increase. Using the fact that $a^2=a$ for booleans one can remove the squaring of boolean wires.
Example from CLAP:
$x := ql · b + qm · b^2 + qc$ where $b ∈ {0, 1}$
x := (ql + qm) · b + qc
3. Circuit flattening
Plonkish proving systems allow custom gates, where multiple arithmetic operations can be batched into one high-degree polynomial constraint. Custom gates allow to dramatically reduce the size of a circuit by encoding a potentially large computation as a single gate, provided that the degree is not too large. A smaller circuit requires less proving time but each custom gate adds to verification time and proof size, a trade-off that is sensible if the gate is used extensively.
4. Common sub-expression elimination
There are many occasions where the same expression is used multiple times in a circuit. In this optimization, the optimizer finds variables that are the output of the same gate and keeps only one, significantly reducing duplication and unnecessary intermediate variables.
5. Duplicate range-checks
This can be considered as a special case of common sub-expression eleminition when there is a multiple range check over the same variable. For instance, if a gate that receives as input is checked to be in range of u32 is later also checked to be in range of u64 when chained will give 2 constraint out of which one is redundant since if a variable fits into u32 will also fit into u64. Such duplicate checks can be avoided in this optimization.
6. Gate conversions
If multiple arithmetic gates are chained in a linear way, CLAP optimizes it by replacing them with a linear combination gate, saving many intermediate variables.
Example from CLAP:
$x1 := q0 · y0$
$x2 := q1 · y1 + x1$
$x3 := q2 · y2 + x2$
$x3 := \sum_{i=0}^{2} qi·yi$
We also looked into the optimizations Noir circuit provides and tried to map it with mentioned CLAP optimizations.
Noir program and circuit definition
A program in Noir is defined by:
where Circuit is a single ACIR circuit
The circuit contains Vec and the enum Opcodes contains AssertZero(Expression<F>) opcode primiraly express a constraint on witnesses. Whereas, BlackBoxFuncCall opcode is for supporting specilized constraints.
Here,
An expression representing a quadratic polynomial.
This struct is primarily used to express arithmetic relations between variables. It includes multiplication terms, linear combinations, and a constant term.
Optimizations in Noir circuit
The following optimizations are present in the Noir circuit:
General Optimizers
remove_zero_coefficients
simplify_mul_terms
simplify_linear_terms
eliminate_intermediate_variable and merging expressions
redundant range constraint optimization
Unused memory optimizations
Description of each optimization
remove_zero_coefficients
Remove all terms with zero as a coefficient in an expression.
Example: $0 w_1 w_5 + w_2 w_3 + 0 w_6 - w_4 + 12 = 0$
without optimization:
After optimization:
simplify_mul_terms
Simplifies all mul terms with the same bi-variate variables in an expression
Example: $3 \cdot w_1 w_2 + 5 \cdot w_2 w_1 + 4 \cdot w_3 w_4$
before:
After:
simplify_linear_terms
Simplifies all linear terms with the same variables within an expression
Example: $2 w_1 + 5 w_1 - 7 w_3 + 7 w_3$
Before:
After:
eliminate_intermediate_variable
This optimization analyzes the circuit and identifies intermediate variables that are only used in two arithmetic opcodes. It then merges the opcode which produces the intermediate variable into the second one that uses it.
First, the optimizer computes, for every witness, which opcodes use it. For each candidate witness, the opcode with the smallest index is merged into the other one via Gaussian elimination.
Example from Noir:
Expression1: $1 \cdot (w_2 \cdot w_3) + 2 \cdot w_2 + 2 \cdot w_1 + 1 \cdot w_3 = 0$
Expression 2: $2 \cdot (w_3 \cdot w_4) + 2 \cdot w_1 + 1 \cdot w_4 = 0$
After optimization:
$(2) \cdot (w_3 w_4) + (1) \cdot w_4 + (-1) \cdot w_2 + (-1/2) \cdot w_3 + (-1/2) \cdot (w_2 w_3) = 0$
Since witness 1 is used in exactly 2 constraint,
the expression with smaller index get merged into another via Gaussian elimination.
redundant range constraint
This optimization removes any redundant range constraint which doesn't result in additional restrictions on the value of witnesses.
Example from Noir:
Unused memory optimizations
This optimization removes initializations of any memory blocks which are unused. It start with a HashSet containing all memory blocks that were initialized. Then iterating over opcode, if MemoryOp uses that block or is used in BrilligCall, then it is removed from the HashSet. At the end whatever remains is the unused memory block.
Comparing the Noir and CLAP optimizations
The Noir Compiler implements most of the optimizations mentioned in CLAP paper and the rest a few optimizations are not applicable because of the use of BlackBoxFuncCall opcode for specialized computation such as AES encryption, Bitwise oerations, range, hashing, ecdsa signing etc.
Although every computation can be done using only assert-zero opcodes, the circuit constructed using that opcode is not always very efficient. hence, the BlackBoxFuncCall opcode is used.
Here we analyze all the optimizations mentioned in CLAP wrt Noir.
Linear Inlining:
Linear Inlining is basically substituting (inlining) the first equation into the second. In Noir that is done using
MergeExpressionsOptimizerwhich identifies intermediate variables that are only used in two arithmetic opcodes. It then merges the opcode which produces the intermediate variable into the second one that uses it.Boolean Inlining:
Knowledge of a variable being boolean can allow certain optimizations such as removing squaring of the boolean wires. This is not applicable in case of Noir because Noir uses
BlackBoxFuncCallopcode for bitwise operations such as AND and XOR instead ofAssertZero.Circuit flattening
This was not straightforward and require changes to the circuit design as the Expression defined in the Noir compiler is explicitly of degree less or equal to 2 and does not allow batching multiple arithmetic operations into one high-degree polynomial constraint. That being said, batching multiple arithmetic as a
BlackBoxFuncCallwould have been the choice but that also require changes in the circuit design and adding arbitrary operations inBlackBoxFuncCallwas not possible.Common sub-expression elimination
common expression used multiple times in a circuit can be optimized by keeps only one thus reducing duplication and unnecessary intermediate variables. In Noir,
eliminate_intermediate_variableof theMergeExpressionsOptimizerserves the same purpose of eliminating the common sub-expression by merging one expression with smallest index into the other one via Gaussian elimination.Duplicate range-checks
Redundant range check can be avoided over the same variable under this optimization. In Noir, this optimization is done in
RangeOptimizerwhere each Witness is only range constrained once to the lowest numberbit sizepossible.Gate conversions
This optimization allows replacing multiple arithmetic gates chained in a linear way with a linear combination gate. In Noir, this can be done using
eliminate_intermediate_variableof theMergeExpressionsOptimizeralong with theGeneralOptimizerwhich includesremove_zero_coefficients,simplify_mul_termsandsimplify_linear_terms.
This shows that all the optimizations mentioned in CLAP paper is directly or indirectly implemented in Noir.
