R1CS
A Rank-1 Constraint System (R1CS) is the default backend in Achronyme. It represents computation as a set of equations that a prover must satisfy, enabling Groth16 zero-knowledge proofs.
The Core Idea
Section titled “The Core Idea”Every R1CS constraint has the form:
A * B = Cwhere A, B, and C are linear combinations of circuit variables (wires). A linear combination is a weighted sum like 3x + 5y + 1.
The key restriction is rank-1: each constraint allows exactly one multiplication. This is what makes R1CS “simple enough” for efficient proof systems, but requires the compiler to decompose complex expressions into single-multiplication steps.
Wires and Layout
Section titled “Wires and Layout”A circuit has three kinds of wires:
| Wire | Index | Description |
|---|---|---|
| ONE | 0 | Constant wire, always 1. Used to encode constants in linear combinations. |
| Public inputs | 1..N | Values the verifier sees. Declared with public. |
| Witness | N+1.. | Private inputs + intermediate values. Declared with witness or allocated by the compiler. |
This layout is snarkjs-compatible — the .r1cs and .wtns files Achronyme exports work directly with snarkjs tooling.
From Achronyme to Constraints
Section titled “From Achronyme to Constraints”The compiler translates each operation into linear combinations and constraints:
Free operations (0 constraints)
Section titled “Free operations (0 constraints)”Addition, subtraction, negation, and multiplication by a constant are all linear operations — they just combine existing wires without needing new constraints:
// These are free:let sum = a + b // LC: a + blet diff = a - b // LC: a - blet scaled = a * 3 // LC: 3*alet neg = -a // LC: (-1)*aMultiplication (1 constraint)
Section titled “Multiplication (1 constraint)”Multiplying two variables requires a constraint because it’s the one non-linear operation:
let product = a * b// Emits: A=a, B=b, C=product → a * b = productDivision (2 constraints)
Section titled “Division (2 constraints)”Division by a variable requires computing the modular inverse:
let quotient = a / b// Constraint 1: b * b_inv = 1 (computes inverse)// Constraint 2: a * b_inv = quotientDivision by a constant is free (multiply by the constant’s inverse).
Equality (1 constraint)
Section titled “Equality (1 constraint)”assert_eq(x, y)// Emits: x * 1 = yLinear Combinations
Section titled “Linear Combinations”A LinearCombination is stored as sparse (variable, coefficient) pairs:
3x + 5y + 7 = [(x, 3), (y, 5), (ONE, 7)]The system automatically simplifies: x - x becomes the empty LC (zero), and 3x + 5x becomes 8x. This deduplication prevents constraint bloat.
When a linear combination needs to become a concrete wire (for example, as input to a multiplication), the compiler materializes it:
- If the LC is already a single variable (like just
x), no constraint is needed - Otherwise, a fresh witness wire is allocated and an equality constraint is added (1 constraint)
Example: Quadratic Equation
Section titled “Example: Quadratic Equation”Consider proving knowledge of x such that x^2 + x + 5 = 35:
circuit quadratic(out: Public, x: Witness) { let x_sq = x * x assert_eq(x_sq + x + 5, out)}This produces 2 constraints:
| # | A | B | C | Purpose |
|---|---|---|---|---|
| 1 | x | x | x_sq | x * x = x_sq |
| 2 | x_sq + x + 5*ONE | 1 | out | x_sq + x + 5 = out |
With witness x = 5, out = 35, both constraints are satisfied: 5*5 = 25 and 25 + 5 + 5 = 35.
Boolean Enforcement
Section titled “Boolean Enforcement”Several operations need to verify that a value is boolean (0 or 1). The standard gadget:
b * (1 - b) = 0This single constraint forces b to be either 0 or 1 — the only two solutions.
The bool_prop optimization pass tracks which variables are provably boolean (outputs of comparisons, RangeCheck(x, 1), etc.) and skips redundant enforcement, saving constraints.
IsZero Gadget
Section titled “IsZero Gadget”Equality and inequality comparisons use the IsZero gadget. Given a value d, it outputs 1 if d = 0, and 0 otherwise:
d * inv = 1 - resultd * result = 0- If
d = 0:result = 1,invcan be anything (the second constraint forcesresultordto be 0) - If
d != 0:result = 0,inv = 1/d
Cost: 2 constraints.
Ordering Comparisons
Section titled “Ordering Comparisons”<, <=, >, >= are the most expensive operations. The approach:
- Ensure both operands are bounded (252-bit range check if not already proven)
- Compute
diff = b - a + 2^bits - 1(offset to keep result positive) - Decompose
diffintobits + 1boolean-enforced bits - The top bit indicates the comparison result
Cost: ~760 constraints without prior range bounds. With range_check, the compiler reuses proven bounds, reducing to O(bits).
Constraint Cost Reference
Section titled “Constraint Cost Reference”| Operation | Constraints | Notes |
|---|---|---|
+, -, negation | 0 | Linear combination |
* constant | 0 | Scalar multiplication |
* variable | 1 | One R1CS constraint |
/ constant | 0 | Multiply by inverse |
/ variable | 2 | Inverse + multiplication |
assert_eq | 1 | Equality enforcement |
assert | 2 | Boolean check + enforce = 1 |
==, != | 2 | IsZero gadget |
<, <=, >, >= | ~760 | 2x252 range + 253-bit decomposition |
&&, || | 3 | 2 boolean checks + 1 multiplication |
! | 1 | Boolean check (0 if proven) |
mux (if/else) | 2 | Boolean check + selection |
range_check(x, n) | n + 1 | n boolean bits + 1 sum equality |
poseidon(l, r) | 361 | 360 permutation + 1 capacity |
Binary Export
Section titled “Binary Export”Achronyme exports .r1cs and .wtns files in the iden3 binary format, compatible with snarkjs:
.r1cs(v1): 3 sections — header, constraints, wire-to-label mapping.wtns(v2): 2 sections — header, witness values (32 bytes per field element)
Both use little-endian encoding with the BN254 scalar field prime embedded in the header.
Further Reading
Section titled “Further Reading”- Operators and Costs — detailed constraint costs for all circuit operations
- Builtins — constraint costs of built-in functions
- Proof Generation — using R1CS for Groth16 proofs