Skip to content

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.

Every R1CS constraint has the form:

A * B = C

where 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.

A circuit has three kinds of wires:

WireIndexDescription
ONE0Constant wire, always 1. Used to encode constants in linear combinations.
Public inputs1..NValues the verifier sees. Declared with public.
WitnessN+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.

The compiler translates each operation into linear combinations and 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 + b
let diff = a - b // LC: a - b
let scaled = a * 3 // LC: 3*a
let neg = -a // LC: (-1)*a

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 = product

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 = quotient

Division by a constant is free (multiply by the constant’s inverse).

assert_eq(x, y)
// Emits: x * 1 = y

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)

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:

#ABCPurpose
1xxx_sqx * x = x_sq
2x_sq + x + 5*ONE1outx_sq + x + 5 = out

With witness x = 5, out = 35, both constraints are satisfied: 5*5 = 25 and 25 + 5 + 5 = 35.

Several operations need to verify that a value is boolean (0 or 1). The standard gadget:

b * (1 - b) = 0

This 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.

Equality and inequality comparisons use the IsZero gadget. Given a value d, it outputs 1 if d = 0, and 0 otherwise:

d * inv = 1 - result
d * result = 0
  • If d = 0: result = 1, inv can be anything (the second constraint forces result or d to be 0)
  • If d != 0: result = 0, inv = 1/d

Cost: 2 constraints.

<, <=, >, >= are the most expensive operations. The approach:

  1. Ensure both operands are bounded (252-bit range check if not already proven)
  2. Compute diff = b - a + 2^bits - 1 (offset to keep result positive)
  3. Decompose diff into bits + 1 boolean-enforced bits
  4. 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).

OperationConstraintsNotes
+, -, negation0Linear combination
* constant0Scalar multiplication
* variable1One R1CS constraint
/ constant0Multiply by inverse
/ variable2Inverse + multiplication
assert_eq1Equality enforcement
assert2Boolean check + enforce = 1
==, !=2IsZero gadget
<, <=, >, >=~7602x252 range + 253-bit decomposition
&&, ||32 boolean checks + 1 multiplication
!1Boolean check (0 if proven)
mux (if/else)2Boolean check + selection
range_check(x, n)n + 1n boolean bits + 1 sum equality
poseidon(l, r)361360 permutation + 1 capacity

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.