Backends
R1CS and Plonkish constraint compilation backends.
Achronyme compiles the same SSA IR into two constraint-system backends in the zkc crate: R1CS (for Groth16) and Plonkish (for KZG-PlonK). The bytecode compiler (akronc) is a separate VM-mode compiler and is not where constraint backend emission lives.
R1CS Backend
File: zkc/src/r1cs_backend.rs
The R1CS backend compiles IR instructions into rank-1 constraint system constraints of the form A × B = C, where A, B, C are linear combinations of wire values.
Core Data Structures
R1CSCompiler {
cs: ConstraintSystem, // A×B=C constraints
bindings: HashMap<String, Variable>, // declared var → wire
lc_map: LcMap, // SSA var → linear combination
witness_ops: SegmentedVec<WitnessOp>, // trace for witness generation
proven_boolean: HashSet<SsaVar>, // skip boolean enforcement
}
Wire Layout
Index: 0 1..n_pub n_pub+1..
ONE public witness + intermediate
Wire 0 is always the constant 1. Public inputs are allocated before witnesses for snarkjs compatibility.
Compilation Strategy
The backend maps each SsaVar to a LinearCombination (sparse Vec<(Variable, FieldElement)>).
Free operations (LC arithmetic only, no constraints):
Add(a, b)→lc_a + lc_bSub(a, b)→lc_a - lc_bNeg(a)→-lc_aConst(v)→v * Variable::ONE
Constraint-emitting operations:
Mul(a, b)→ allocate wire, enforcea × b = result(1 constraint)Div(a, b)→ compute inverse, enforceb × inv = 1, thena × inv = result(2 constraints)Mux(c, t, f)→ enforcecboolean, enforcec × (t-f) = result - f(2 constraints)AssertEq(a, b)→ enforcea = b(1 constraint)
Constraint Costs
| Operation | Constraints | Notes |
|---|---|---|
| Add / Sub / Neg | 0 | LC arithmetic |
| Const / Input | 0 | Wire allocation |
| Mul | 1 | multiply_lcs |
| Div | 2 | inverse + multiply |
| Mux | 2 | boolean check + selection |
| AssertEq | 1 | enforce_equal |
| Assert | 2 | boolean check + enforce = 1 |
| Not | 1 | boolean enforcement |
| And / Or | 3 | 2 boolean checks + 1 multiply |
| IsEq / IsNeq | 2 | IsZero gadget |
| IsLt / IsLe | ~760 | 2×252-bit range checks + 253-bit decomposition |
| PoseidonHash | 361 | 360 round constraints + 1 capacity |
| RangeCheck(n) | n+1 | n bit decomposition + sum check |
Boolean Optimization
If a variable is in the proven_boolean set (from the bool_prop pass), the backend skips its boolean enforcement constraint. This saves 1 constraint per known-boolean variable used in Mux, And, Or, or Not.
R1CS Substitution Soundness
After IR-level optimization, an R1CS-level pass performs linear elimination and substitution to shrink the constraint count (this is what brings ECDSAVerify(64,4) to ~1.49M constraints, below circom’s own count). The greedy eliminator could previously leave a surviving constraint that still referenced a wire the pass had already eliminated — a forgeable-witness hazard, because the eliminated wire was no longer pinned by any constraint.
This is now closed via per-cluster cycle resolution: substitutions are resolved cluster-by-cluster (Tarjan SCC + RREF, including self-loops) on the un-composed substitution map, then the driver flattens the closure so no eliminated wire survives as a dangling reference. Generated witnesses are bit-identical to the pre-fix path and the optimized constraint count still holds below circom — the fix is purely a soundness repair, not a count regression.
Plonkish Backend
File: zkc/src/plonkish_backend/
The Plonkish backend compiles IR into a table of gate rows with columns, copy constraints, and lookup tables.
Standard Columns
| Column | Kind | Purpose |
|---|---|---|
s_arith | Fixed | Arithmetic gate selector |
s_range | Fixed | Range check selector |
constant | Fixed | Constant values |
a, b, c, d | Advice | Computation wires |
instance_0 | Instance | Public inputs |
Standard Arithmetic Gate
s_arith · (a · b + c − d) = 0
Each multiplication emits one row. Addition is encoded as a·1 + c = d.
Lazy Evaluation
The Plonkish backend uses PlonkVal for lazy evaluation:
PlonkVal::Cell(CellRef) // materialized in a cell
PlonkVal::Constant(FieldElement) // not yet placed
PlonkVal::DeferredAdd(a, b) // deferred until needed
PlonkVal::DeferredSub(a, b) // deferred until needed
PlonkVal::DeferredNeg(a) // deferred until needed
Add, Sub, and Neg operations build deferred expressions. Only when a value is needed for multiplication or a builtin does materialize_val() emit an actual row.
This means a chain of additions like a + b + c + d emits fewer rows than in a naive approach — only the final materialization emits rows.
Range Checks via Lookups
Unlike R1CS (which uses n+1 bit decomposition constraints), Plonkish range checks use a lookup table: 1 row per supported check. The range table is pre-populated with values [0, 2^bits), so very large bit widths are rejected rather than materializing an impractically large table.
Comparison Operations
IsLt(a, b)→ 252-bit range checks on both operands + 253-bit decomposition ofb − a + 2^252 − 1IsLe(a, b)→1 − IsLt(b, a)(swap and negate)IsZero(a)→ setsdto constant 0 (not an ArithRow) for sound enforcement
Witness Generation
PlonkishWitnessGenerator replays PlonkWitnessOp entries to fill the assignment table:
PlonkWitnessOp::AssignInput { cell, name }
PlonkWitnessOp::CopyValue { from, to }
PlonkWitnessOp::SetConstant { cell, value }
PlonkWitnessOp::ArithRow { row } // compute d = a*b + c
PlonkWitnessOp::InverseRow { row } // compute inverse
PlonkWitnessOp::IsZeroRow { row } // compute IsZero helper cells
PlonkWitnessOp::BitExtract { ... } // extract bit from value
PlonkWitnessOp::IntDivMod { ... } // integer quotient + remainder
Backend Comparison
| Feature | R1CS | Plonkish |
|---|---|---|
| Constraint model | A×B=C | Gate polynomials |
| Addition cost | 0 (LC) | 0 (deferred) |
| Multiplication cost | 1 constraint | 1 row |
| Range check | n+1 constraints | 1 lookup row |
| Binary export | .r1cs + .wtns (iden3) | Not yet |
| Proof system | Groth16 (arkworks) | KZG-PlonK (halo2) |
| CLI flag | --backend r1cs (default) | --backend plonkish |
Source Files
| Component | File |
|---|---|
| R1CS Backend | zkc/src/r1cs_backend.rs |
| Plonkish Backend | zkc/src/plonkish_backend/ |
| Constraint System | constraints/src/r1cs/ |
| Plonkish System | constraints/src/plonkish/ |
| Backend Errors | zkc/src/error.rs |