AIR or R1CS?

from arithmetic constraints to execution traces, witness vectors, R1CS matrices, AIR polynomials, SNARKs, STARKs, and how to decide which representation fits a proving system

Start with the problem

A zero-knowledge proof system needs a mathematical description of a computation.

Suppose a program computes:

z = (x + y) · w

We cannot directly hand the prover the sentence:

“prove that the program ran correctly.”

We first translate the computation into mathematical relationships.

program computation constraints proof

Two important ways of organizing those constraints are R1CS and AIR.

R1CS

Think: “Does this assignment vector satisfy all these algebraic equations?”

AIR

Think: “Does this execution trace satisfy the required transition and boundary equations?”

The shortest possible distinction

R1CS
(A · W)(B · W) = C · W
AIR
transition constraints + boundary constraints over a trace

Both are algebraic descriptions of computation. The major difference is how the computation is organized.

First principle: both live over a field

R1CS and AIR are normally defined over a finite field.

For a prime field:

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

Every variable, trace value, coefficient, and constraint evaluation belongs to this field.

For example, over F₇:

5 + 4 = 2
3 · 5 = 1

because the results are reduced modulo 7.

Important: field arithmetic is not ordinary integer arithmetic. A proving system's constraints must be interpreted using the exact field used by that system.

R1CS from first principles

R1CS means Rank-1 Constraint System.

At its core, R1CS works with a single assignment vector called the witness vector.

W = [1, w₁, w₂, …, wₙ]

The variables in W may contain:

  • public inputs;
  • private witness values;
  • intermediate values produced by the computation;
  • a constant 1 slot for representing constants.

Each R1CS constraint has the form:

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

where A, B, and C are vectors for one constraint, or rows of matrices when many constraints are collected together.

What does A · W actually mean?

Suppose:

W = [1, x, y, z]

and:

A = [0, 1, 1, 0]

Then:

A · W = 0·1 + 1·x + 1·y + 0·z
A · W = x + y

So an R1CS vector is simply a way to describe a linear combination of witness variables.

The multiplication of two such linear combinations gives the quadratic structure:

(A · W)(B · W)

R1CS example: multiplication

Suppose we want to enforce:

z = x · y

Use:

W = [1, x, y, z]

Choose:

A = [0, 1, 0, 0]
B = [0, 0, 1, 0]
C = [0, 0, 0, 1]

Then:

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

Therefore:

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

R1CS example: addition

Suppose:

z = x + y

We can represent the left side as:

(x + y) · 1

Using:

W = [1, x, y, z]

we can choose:

A = [0, 1, 1, 0]
B = [1, 0, 0, 0]
C = [0, 0, 0, 1]

Then:

(x + y) · 1 = z

so the constraint is satisfied exactly when the intended addition is satisfied.

Many R1CS constraints become matrices

A real computation has many constraints.

Instead of storing one A, B, and C vector, we stack them into matrices:

A
B
C

For every constraint row i:

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

The entire witness is valid only if every row satisfies its equation.

A B C + W all constraints satisfied

Arithmetic circuit → R1CS

Consider:

out = (a + b) · (c + d)

Introduce intermediate variables:

t₁ = a + b
t₂ = c + d
out = t₁ · t₂

Now we have three simple constraints:

t₁ − a − b = 0
t₂ − c − d = 0
t₁ · t₂ − out = 0

This is the basic workflow used by many circuit-based systems:

source expression gates intermediate variables R1CS

AIR from first principles

AIR means Algebraic Intermediate Representation.

The key idea is different from R1CS.

Instead of putting the whole computation into one long witness vector, we organize the execution into a trace.

Imagine a machine running for several steps:

step 0 → step 1 → step 2 → step 3 → …

At every step, we record relevant machine state.

The execution trace

Suppose a machine has a single register r.

It starts at 2 and increments by 1:

2 → 3 → 4 → 5 → 6

The trace can be represented as:

rowr
02
13
24
35
46

In a real zkVM, the trace can contain many columns:

pc, opcode, registers, memory-related values, flags, auxiliary columns, …

Transition constraints

The trace alone is just data. AIR defines what makes that trace valid.

For our incrementing register:

rᵢ₊₁ = rᵢ + 1

Move everything to one side:

rᵢ₊₁ − rᵢ − 1 = 0

This is a transition constraint.

It says that every pair of neighboring rows must follow the transition rule.

Boundary constraints

The transition rule alone does not specify where the computation starts.

For example, the transition:

rᵢ₊₁ = rᵢ + 1

allows:

100 → 101 → 102 → 103

as well as:

2 → 3 → 4 → 5

If the intended computation must start at 2, we need:

r₀ − 2 = 0

That is a boundary constraint.

We might also require a final value:

r₄ − 6 = 0

Complete AIR example

For the trace:

2 → 3 → 4 → 5 → 6

we can define:

initial boundary
r₀ − 2 = 0
transition
rᵢ₊₁ − rᵢ − 1 = 0
final boundary
r₄ − 6 = 0

Now the constraints describe the intended computation much more precisely.

Why AIR uses columns

A zkVM executes many different pieces of state at every step.

Imagine:

row pc opcode r0 r1
00ADD23
14ADD57
2812

Each column represents some piece of state. AIR constraints describe relationships between columns and between neighboring rows.

This makes AIR particularly natural for long computations.

Locality: the key AIR idea

Many AIR constraints are local.

A constraint may inspect:

current row
next row

For example:

pcᵢ₊₁ − pcᵢ − 4 = 0

or:

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

This local structure is extremely useful for proving long execution traces.

AIR becomes polynomial algebra

Now comes the important bridge to STARKs.

Suppose a trace column contains:

r₀, r₁, r₂, …

We interpret those values as evaluations of a polynomial on a domain.

If the domain contains:

1, ω, ω², ω³, …

then we can think of:

r(ωⁱ) = rᵢ

The transition relation:

rᵢ₊₁ − rᵢ − 1 = 0

can then be expressed algebraically as:

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

evaluated on the relevant domain.

Vanishing constraints

Suppose a polynomial constraint should be zero on a domain.

We can represent the domain using a vanishing polynomial.

For a domain D:

Z_D(x) = ∏d∈D(x − d)

By construction:

Z_D(d) = 0 for every d ∈ D

This lets STARK systems express the idea that a constraint polynomial vanishes over the required domain.

Constraint degree

Constraint degree matters in both representations, but it appears differently.

For R1CS:

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

the constraint is at most quadratic in the witness variables.

In AIR, a transition constraint can contain products of trace values and therefore can also have a degree greater than one.

The proving system tracks these degrees because polynomial degree affects the algebraic checks used later.

R1CS vs AIR: the conceptual difference

Question R1CS AIR
What is the main object? witness vector execution trace
What does a constraint relate? linear combinations of witness variables trace values, often across neighboring rows
Typical form (A·W)(B·W)=C·W transition + boundary equations
Natural mental model circuit machine execution trace
Common proving family SNARKs STARKs
zkVM fit possible, but circuit-heavy especially natural for trace-based zkVMs
Do not interpret this as an absolute rule: R1CS is not “only for SNARKs” and AIR is not “only for STARKs.” They are representations. Particular proving systems can transform between or use related representations.

The same computation in R1CS and AIR

Consider the simple recurrence:

rᵢ₊₁ = rᵢ + 1

R1CS view

We could create a variable for every trace value:

W = [1, r₀, r₁, r₂, r₃, r₄]

Then create constraints:

r₁ − r₀ − 1 = 0
r₂ − r₁ − 1 = 0
r₃ − r₂ − 1 = 0
r₄ − r₃ − 1 = 0

Each step becomes an explicit constraint over witness variables.

AIR view

Instead, describe the same repeated rule once:

rᵢ₊₁ − rᵢ − 1 = 0

and state the boundary:

r₀ = 2

The trace supplies all rows to which the transition rule applies.

same computation R1CS: many explicit constraints or AIR: repeated trace relation

Why AIR is natural for zkVMs

A CPU executes one step after another:

state₀ → state₁ → state₂ → … → stateₙ

A zkVM can record these states as rows of a trace.

For example:

PC, opcode, register values, memory state, flags, auxiliary data

Then AIR describes valid state transitions.

For an instruction that performs:

r₂ ← r₀ + r₁

the corresponding transition constraint can conceptually be:

r₂(next) − r₀(current) − r₁(current) = 0

Of course, a real zkVM needs many more constraints for opcode selection, register addressing, program counter updates, memory consistency, range checks, and other machine semantics.

A small zkVM-style AIR example

Imagine a toy machine with:

pc, r0, r1, r2

and an ADD instruction:

r2(next) = r0(current) + r1(current)

The PC advances by four bytes:

pc(next) = pc(current) + 4

The corresponding transition constraints are:

r2(next) − r0(current) − r1(current) = 0
pc(next) − pc(current) − 4 = 0

The trace might therefore look conceptually like:

rowpcr0r1r2
00230
14235

The transition constraint checks that row 1 correctly follows row 0.

Public inputs and boundaries

Public values often enter the algebraic representation through boundary or dedicated input constraints.

Suppose a computation must start with public input:

x = 7

A boundary constraint can enforce:

x₀ − 7 = 0

The important idea is that the public statement must be algebraically connected to the computation being proved.

From AIR constraints to a composition polynomial

A real AIR contains many constraints.

Conceptually, suppose we have:

C₀(x), C₁(x), C₂(x), …

A prover can combine them using random coefficients:

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

The random coefficients are typically obtained from the proof transcript through Fiat–Shamir.

This connects AIR to the STARK pipeline:

trace AIR constraints composition polynomial commit FRI

Rust: a tiny R1CS model

Educational R1CS implementation
rust
// Educational R1CS-style check.
// W = [1, x, y, z]
// Constraint: x * y = z

fn dot(a: &[i64], w: &[i64]) -> i64 {
    a.iter()
        .zip(w.iter())
        .map(|(x, y)| x * y)
        .sum()
}

fn check_r1cs(
    a: &[i64],
    b: &[i64],
    c: &[i64],
    w: &[i64],
) -> bool {
    dot(a, w) * dot(b, w) == dot(c, w)
}

fn main() {
    let x = 3;
    let y = 5;
    let z = 15;

    let w = [1, x, y, z];

    // A * W = x
    let a = [0, 1, 0, 0];

    // B * W = y
    let b = [0, 0, 1, 0];

    // C * W = z
    let c = [0, 0, 0, 1];

    assert!(check_r1cs(&a, &b, &c, &w));

    println!("R1CS constraint satisfied.");
}

This toy implementation uses i64 only to make the algebra visible. A real proving system performs the operations in a finite field.

Rust: a tiny AIR-style checker

Educational trace constraint checker
rust
// Educational AIR-style checker.
// Transition: r_next = r_current + 1

fn check_trace(trace: &[i64], initial: i64) -> bool {
    if trace.is_empty() {
        return false;
    }

    // Boundary constraint.
    if trace[0] != initial {
        return false;
    }

    // Transition constraints.
    for i in 0..trace.len() - 1 {
        let current = trace[i];
        let next = trace[i + 1];

        if next - current - 1 != 0 {
            return false;
        }
    }

    true
}

fn main() {
    let trace = [2, 3, 4, 5, 6];

    assert!(check_trace(&trace, 2));

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

The important distinction is visible in the code:

R1CS checker → one assignment vector + equations
AIR checker → rows of a trace + transition/boundary rules

What changes in a real proving system?

The toy Rust programs above only check constraints. They do not produce a ZK proof.

A real system adds layers such as:

  • finite-field arithmetic;
  • constraint generation;
  • witness or trace generation;
  • polynomial interpolation;
  • low-degree extension;
  • commitments;
  • Fiat–Shamir challenges;
  • polynomial identity testing;
  • FRI or another polynomial proof system;
  • verification.

Therefore R1CS and AIR should be understood as intermediate representations, not complete proof systems by themselves.

Why zkVM architecture often prefers a trace representation

Consider a program with one million execution steps.

A circuit representation can conceptually turn those steps into a very large collection of constraints and intermediate variables.

A trace-oriented representation instead naturally says:

“Here are one million rows. The same local transition rules must hold across them.”

This matches the temporal structure of machine execution.

That does not make AIR universally superior. It means that the representation aligns naturally with the problem being represented.

Trade-offs

R1CS strengths

Very explicit circuit semantics, mature tooling, natural fit for many circuit-based SNARK constructions, and a simple algebraic normal form.

R1CS costs

Large computations can produce many variables and constraints; representing machine-style execution may require substantial circuit structure.

AIR strengths

Natural representation for repeated state transitions and long execution traces; particularly useful for STARK-style proving and zkVMs.

AIR costs

Constraint design can become subtle, especially for memory, lookups, permutations, range checks, instruction decoding, and auxiliary trace columns.

Common mistakes when learning AIR and R1CS

mistake 01

Thinking AIR and R1CS are proof systems. They are representations used by proving systems.

mistake 02

Thinking AIR means “just a trace.” The constraints defining valid traces are the important part.

mistake 03

Thinking every field element is automatically a valid integer, bit, pointer, or machine word.

mistake 04

Forgetting boundary constraints. A transition rule alone can allow many unintended executions.

mistake 05

Assuming a computation is constrained merely because its value appears in the witness or trace.

mistake 06

Confusing ordinary integer semantics with finite-field semantics.

Where AIR and R1CS fit in the ZK pipeline

program execution / circuit AIR or R1CS
AIR / R1CS + witness / trace polynomial representation
polynomials commitments Fiat–Shamir proof

For the STARK-style path you are building in your zkVM, the conceptual chain is:

program → execution trace → AIR → composition polynomial → low-degree extension → Merkle commitment → FRI

For a circuit-based R1CS system, the conceptual path is more like:

program / circuit → witness → R1CS → polynomial encoding → SNARK-specific proving system

So which one should you use?

If you are representing a circuit
R1CS is a natural algebraic representation.
If you are representing machine execution
AIR is a natural trace-oriented representation.
If you are building a STARK-style zkVM
think: execution trace → AIR → polynomial constraints → FRI
If you are building a circuit-based SNARK
think: circuit → witness → R1CS / related constraint system → SNARK

The choice is ultimately an engineering choice about which algebraic representation makes the target computation easiest and most efficient to prove.

The whole idea in one map

first-principles map
1. ZK systems need a mathematical description of computation.
2. That computation is usually expressed over a finite field.
3. R1CS organizes computation around a witness vector.
4. Each R1CS constraint has the form (A·W)(B·W)=C·W.
5. AIR organizes computation around an execution trace.
6. AIR defines transition constraints between trace rows.
7. AIR also defines boundary constraints for required starting/ending values.
8. Trace columns represent pieces of machine state.
9. Trace relationships become polynomial constraints.
10. Constraint polynomials can be combined and checked for low degree.
11. STARKs naturally use AIR-like representations and polynomial IOPs.
12. Many SNARK systems naturally use circuit/R1CS-like representations.
13. Neither AIR nor R1CS is itself a complete ZK proof system.
14. The best representation depends on the computation and proving architecture.

If you remember only one distinction, remember this: R1CS thinks in terms of variables and algebraic equations; AIR thinks in terms of machine states and algebraic transition rules. Both ultimately turn computation into algebra so that a proving system can establish correctness without requiring the verifier to execute the entire computation.