Arithmetic constraints

from ordinary equations to finite-field constraints, arithmetic circuits, R1CS, Boolean logic, range checks, AIR, zkVMs, and how a prover turns computation into algebra

Why do we need arithmetic constraints?

A computer program is usually written using operations such as:

add
subtract
multiply
compare
branch
load / store

A ZK proving system cannot simply be told:

“run this program and prove that it was correct.”

It needs a mathematical description of what “correct” means.

Arithmetic constraints provide that description.

computation variables arithmetic constraints proof

At the most basic level, a constraint is simply an equation that must evaluate to zero.

x + y − z = 0

If the variables satisfy the equation, the constraint is satisfied. If they do not, it is violated.

Mental model: arithmetic constraints turn “this computation is correct” into a collection of mathematical equations that a proving system can check.

First principle: arithmetic happens in a field

In modern algebraic proof systems, arithmetic is usually performed over a finite field.

Let the field be:

F

Every variable is an element of that field:

x, y, z ∈ F

And every constraint is evaluated using field addition, subtraction, multiplication, and inversion where defined.

For a prime field:

Fₚ = {0, 1, 2, …, p−1}

with all arithmetic performed modulo p.

For example, over F₇:

5 + 4 = 2
3 · 5 = 1

because:

9 mod 7 = 2
15 mod 7 = 1

This is important: an arithmetic constraint is not necessarily an equation over the ordinary integers. It is usually an equation over a specific field.

The standard form: polynomial equals zero

Consider:

x + y = z

Move everything to one side:

x + y − z = 0

This is a convenient representation because proving systems can ask whether a polynomial expression vanishes.

For example:

addition constraint
x + y − z = 0
multiplication constraint
x · y − z = 0
constant constraint
x − 7 = 0

The same idea scales from one equation to millions of constraints.

Variables and assignments

A constraint describes what values are allowed, but the prover needs an actual assignment.

Suppose:

x + y − z = 0

and the assignment is:

x = 3
y = 5
z = 8

Then:

3 + 5 − 8 = 0

so the constraint is satisfied.

If instead:

z = 9

then:

3 + 5 − 9 = −1 ≠ 0

and the assignment is invalid for that constraint.

From computation to arithmetic circuits

Suppose we want to compute:

z = (x + y) · w

Introduce an intermediate variable:

t = x + y

Then:

z = t · w

These become two arithmetic constraints:

x + y − t = 0
t · w − z = 0
x + y t × w z

The intermediate variable t is important. Instead of describing one complicated expression, we decompose the computation into simple algebraic relationships.

Arithmetic gates

An arithmetic circuit is commonly built from simple gates.

addition gate

z = x + y

multiplication gate

z = x · y

constant gate

z = x + 7

negation

z = −x

Each gate can be translated into an algebraic constraint.

Linear constraints

A linear constraint has variables only to the first power and does not multiply variables by one another.

For example:

3x + 5y − 7z + 4 = 0

This is linear in x, y, z.

Linear constraints are comparatively simple to represent.

Many arithmetic circuits, however, need multiplication between variables.

Nonlinear constraints

A nonlinear constraint contains products or powers of variables.

Examples:

x · y − z = 0
x² − y = 0
x · y · z − 1 = 0

These constraints are important because general computation requires multiplication.

For example, hashing, encryption, signatures, and arithmetic programs all contain nonlinear operations once represented algebraically.

R1CS: the important normal form

One of the most important constraint representations in SNARK systems is R1CS, or Rank-1 Constraint System.

An R1CS constraint has the form:

(A · W) · (B · W) = C · W

Here:

  • W is the witness/assignment vector;
  • A, B, and C describe linear combinations of variables.

Equivalently:

(A · W)(B · W) − C · W = 0

This gives a very structured way to represent quadratic constraints.

R1CS example from scratch

Consider:

z = x · y

Let the assignment vector be:

W = [1, x, y, z]

The constant 1 lets us represent constants as linear combinations.

Choose:

A · W = x
B · W = y
C · W = z

Then the R1CS equation becomes:

x · y = z

which is exactly the desired computation.

How addition fits into R1CS

Suppose:

z = x + y

We can use a multiplication by one:

(x + y) · 1 = z

So:

(A · W)(B · W) = C · W
(x + y) · 1 = z

This shows why the R1CS form can represent both linear and multiplication relationships.

Constants in constraints

Suppose we want:

z = 3x + 7

Move everything into a polynomial equation:

3x + 7 − z = 0

Because the assignment vector contains a constant 1, the constant 7 can be represented as:

7 · 1

This is why the constant slot is useful in many R1CS conventions.

How do we represent Boolean values?

Fields contain many values, not just zero and one.

So if a variable is supposed to be a bit, we must constrain it.

The standard Boolean constraint is:

b(b − 1) = 0

Factor it:

b = 0 or b = 1

Therefore:

boolean constraint
b² − b = 0

This is a beautiful example of converting a logical requirement into algebra.

Building logic from arithmetic

Once we can constrain bits, we can express Boolean operations algebraically.

NOT

NOT(a) = 1 − a

AND

AND(a,b) = a · b

OR

OR(a,b) = a + b − ab

For bits, these expressions produce exactly the expected Boolean results.

Range constraints

A field element can represent a very large number, but sometimes a value must lie inside a small integer range.

Suppose:

x ∈ {0,1,2,3}

One common strategy is to decompose it into bits:

x = b₀ + 2b₁

and constrain:

b₀² − b₀ = 0
b₁² − b₁ = 0

Now the possible values are exactly:

0, 1, 2, 3

This is the basis of many range-check constructions.

Representing division

Division is not normally treated as a primitive gate.

Suppose:

z = x / y

For nonzero y, rewrite it as:

z · y = x

which becomes:

z · y − x = 0

But there is an important edge case: what if y = 0?

A correct circuit must handle or constrain that case according to the intended semantics.

Important: algebraic constraints can accidentally accept values that would be invalid under ordinary program semantics if exceptional cases are not explicitly constrained.

Comparisons are not primitive field operations

Fields naturally support:

+, −, ×, inverse

But a statement like:

x < 10

is not a native field equation.

To prove comparisons, systems usually use techniques such as:

  • bit decomposition;
  • range checks;
  • lookup arguments;
  • specialized comparison gadgets.

This is one reason arithmetic circuits can be substantially more complicated than ordinary source code.

Constraints and the witness

A constraint system defines what assignments are valid.

The witness supplies one concrete assignment.

constraints + witness W evaluate all zero?

For a valid witness:

C₀(W) = 0
C₁(W) = 0
C₂(W) = 0

The prover then uses the satisfied system to construct a proof.

Public inputs inside constraints

Suppose the statement is:

w² = x

where:

x = public input
w = private witness

The constraint is:

w² − x = 0

The public input is therefore directly bound into the relation.

The proof is not merely:

“some square exists.”

It is:

“there exists a w such that w² = this exact public x.”

Arithmetic constraints in AIR

R1CS is not the only way to express constraints.

AIR — Algebraic Intermediate Representation — is especially important in STARKs and zkVMs.

Instead of describing every operation as an independent circuit gate, AIR often describes relationships between neighboring rows of an execution trace.

Suppose a register increments:

rᵢ₊₁ = rᵢ + 1

The transition constraint becomes:

rᵢ₊₁ − rᵢ − 1 = 0

This constraint must hold for every appropriate row i.

Transition vs boundary constraints

AIR commonly separates constraints into two broad categories.

transition constraints

Describe how one row of the trace relates to another row.

boundary constraints

Specify required values at particular positions, such as the first or final row.

For example:

rᵢ₊₁ − rᵢ − 1 = 0
r₀ − 2 = 0
rₙ − 5 = 0

Together they describe the intended execution.

Arithmetic constraints in a zkVM

A zkVM converts machine execution into algebraic constraints.

Imagine a trace with columns:

pc, opcode, r₀, r₁, r₂, …

A transition might enforce:

pcᵢ₊₁ = pcᵢ + instruction_size

which becomes:

pcᵢ₊₁ − pcᵢ − 4 = 0

A register update might be:

r₂ᵢ₊₁ − r₀ᵢ − r₁ᵢ = 0

Conditional execution, opcode decoding, memory consistency, and instruction semantics require additional constraints.

CPU execution trace AIR constraints polynomials STARK

Constraint degree matters

Consider:

x + y − z = 0

This has degree 1.

Now:

x · y − z = 0

has degree 2.

And:

x · y · z − 1 = 0

has degree 3.

The degree of constraints affects how the proving system represents and checks them.

This is one reason systems often normalize computations into restricted forms such as quadratic R1CS constraints or carefully structured AIR constraints.

Combining many constraints

Suppose we have:

C₀(W) = 0
C₁(W) = 0
C₂(W) = 0

A proving system may need to represent a large collection of constraints compactly.

STARK-style systems often combine constraint evaluations into a composition polynomial using random coefficients:

C(x) = α₀C₀(x) + α₁C₁(x) + α₂C₂(x) + …

where the αᵢ values are random challenges, commonly derived through Fiat–Shamir.

The idea is that a cheating prover should not be able to make many independently violated constraints disappear in the same random linear combination.

Why constraints eventually become polynomials

This connects arithmetic constraints to the other fundamentals you are studying.

Suppose a trace column gives values:

r₀, r₁, r₂, …, rₙ₋₁

These values can be interpreted as evaluations of a polynomial on a chosen domain:

r(ωⁱ) = rᵢ

Then a transition constraint can become a polynomial expression such as:

r(ωx) − r(x) − 1

The proving system can then test whether this expression vanishes on the required domain.

This is the bridge:

execution trace constraint expressions polynomials commitments / FRI

Arithmetic constraints in Rust

Before using a proving library, you can model the core idea directly.

Complete educational Rust implementation
rust
// A tiny arithmetic constraint system.
// This is educational, not a cryptographic proving system.

#[derive(Debug)]
struct Assignment {
    x: i64,
    y: i64,
    z: i64,
}

fn addition_constraint(a: &Assignment) -> i64 {
    a.x + a.y - a.z
}

fn multiplication_constraint(a: &Assignment) -> i64 {
    a.x * a.y - a.z
}

fn boolean_constraint(b: i64) -> i64 {
    b * b - b
}

fn main() {
    let a = Assignment {
        x: 3,
        y: 5,
        z: 8,
    };

    assert_eq!(addition_constraint(&a), 0);

    let product = Assignment {
        x: 3,
        y: 5,
        z: 15,
    };

    assert_eq!(multiplication_constraint(&product), 0);

    // Valid Boolean values satisfy b² - b = 0.
    assert_eq!(boolean_constraint(0), 0);
    assert_eq!(boolean_constraint(1), 0);

    // 2 is not Boolean.
    assert_ne!(boolean_constraint(2), 0);

    println!("All constraints satisfied.");
}

A real implementation would replace ordinary integers with a finite-field type and would build a structured constraint system rather than directly evaluating the equations.

Why the Rust implementation should use a field

Suppose the field modulus is p.

Then the constraint:

x + y − z = 0

means:

(x + y − z) mod p = 0

A production Rust implementation would therefore use a type such as a field element rather than i64.

Conceptually:

FieldElement<P>

This is important because the proving system's algebra is defined over the field.

A constraint system is more than equations

A practical system must also know:

  • which variables exist;
  • which variables are public;
  • which variables are private;
  • which field is being used;
  • which constraints apply;
  • how variables map to witness positions;
  • how constants are represented;
  • how the system handles special operations.

So the mathematical idea is simple, but the engineering around it can become substantial.

Why constraints matter for soundness

Suppose the intended computation is:

z = x · y

If the system forgets the constraint:

x · y − z = 0

then the prover may be able to provide an invalid assignment without the proving system noticing.

This illustrates a fundamental rule:

If an intended property is not encoded as a constraint, the proof system does not automatically know that the property matters.

Constraint completeness is therefore a major part of proving-system correctness.

Common constraint-system bugs

missing constraint

A required relationship is never enforced.

wrong variable

A constraint accidentally references the wrong witness position.

missing range check

A value intended to be a bit or bounded integer can take arbitrary field values.

wrong field semantics

Ordinary integer intuition is applied to modular field arithmetic.

unconstrained intermediate

An intermediate value is used without adequately enforcing how it was produced.

exceptional case

Division by zero, overflow assumptions, or other edge cases are not represented correctly.

R1CS vs AIR

ConceptR1CSAIR
basic objectconstraint over an assignment vectorconstraint over trace rows/columns
typical form(A·W)(B·W) = C·Wpolynomial relation between trace values
natural usearithmetic circuits / SNARKsexecution traces / STARKs / zkVMs
multiplicationnaturally represented by quadratic formrepresented in transition/boundary polynomials
trace-orientednot inherentlyyes

Where arithmetic constraints fit in ZK

program / statement arithmetic representation constraints
constraints + witness polynomials commitments
commitments Fiat–Shamir proof

This connects directly to the fundamentals you have already built:

finite fields → arithmetic constraints → polynomials → commitments → Fiat–Shamir → FRI / SNARK proof

The whole idea in one map

first-principles map
1. A computation must be translated into mathematics.
2. ZK arithmetic normally happens over a finite field.
3. A constraint is a condition that an assignment must satisfy.
4. A convenient form is polynomial(expression) = 0.
5. Addition becomes a linear constraint.
6. Multiplication creates nonlinear constraints.
7. Arithmetic circuits decompose computations into simple relationships.
8. R1CS expresses constraints as (A·W)(B·W) = C·W.
9. Boolean values require constraints such as b² − b = 0.
10. Range checks can be built from bit decomposition or specialized gadgets.
11. AIR expresses constraints over execution-trace rows and columns.
12. zkVMs use AIR-like constraints to describe valid machine execution.
13. Constraints are combined and transformed into polynomial objects.
14. Those polynomials can then be committed to and proved about.
15. Missing constraints can become soundness vulnerabilities.

If witnesses answer “what assignment makes the computation true?”, arithmetic constraints answer “what equations must that assignment satisfy?”. From there, R1CS and AIR turn those equations into structures that modern SNARK and STARK proving systems can actually operate on.