Quadratic Constraints

Circom Constraints

A Rank 1 Constraint System has at most one multiplication between signals per constraint. This is called a “quadratic” constraint. Any constraint containing an operation other than addition or multiplication will be rejected by Circom with the “Non quadratic constraints are not allowed” error.

The following two examples will not compile because they have more than one multiplication of signals per constraint.

Non quadratic constraint example 1

Compiling the following will result in the following error: [T3001]: Non quadratic constraints are not allowe

template QuadraticViolation1() {
  signal input a;
  signal input b;
  signal input c;
  signal input d;

  // two multiplications per constraint
  // is not allowed
  a * b === c * d;
}

Non quadratic constraint example 2

Similar to the previous example, the following constraint has two multiplications between signals.

template QuadraticViolation2() {
  signal input a;
  signal input b;
  signal input c;
  signal input d;

  // two multiplications per constraint
  // is not allowed
  a * b * c === d;
}

Constant multiplications do not count

Therefore, the following examples will compile, even though there is more than one multiplication.

a * b === c;

2*a * 3*b === 4*c; // integer coefficients allowed

a * b + c === d; // addition and one multiplication allowed

a + b + c === d; // multiplication is optional

a * b + c === d + e + f; // no restrictions on number of additions

Quadratic Form and R1CS

Recall that in arithmetization, we flatten our verification program into a series of intermediate steps, where each intermediate step only contains a single multiplication between unknown variables.

Consider the following verification example:

def someProblem(x, y, out):
  res = y^2 + 4*(x^2)*y -2 
  assert out == res, "incorrect inputs";

Conversion to an R1CS would yield us:

v1  === y * y
v2  === x * x 
out === v1 + (4v2 * y) - 2
  • The R1CS format requires us to restructure the problem into intermediate steps that only have 1 multiplication operation between signals to adhere the quadratic constraint limitation.
  • This creates our system of constraints.

Consequently, the R1CS representation would be:

//     Cw = Aw * Bw
       v1  = y * y
       v2  = x * x 
out -v1 +2 = (4v2 * y)

Since we previously ensured that there was only 1 multiplication per constraint, we are able to express the system of constraints in vector form, which is an R1CS.

Examples of Non-multiplicative Operators Causing a Non quadratic Constraint

If an illegal operation (not addition or multiplication) is used in a constraint, the Circom compiler will report the “Non quadratic constraints are not allowed!” error.

Here, we provide some examples.

Example 1: Signals Cannot Be Used to Index Signal Arrays

The following operation will result in a quadratic constraints violation. There is no direct translation from array indexing to addition and multiplication. The following code results in the error Non-quadratic constraint was detected statically, using unknown index will cause the constraint to be non-quadratic:

template KMustEqual5(n) {

  signal input in[n];
  signal input k;

  // not allowed
  in[k] === 5;
}

It is still technically possible to accomplish array indexing, but this requires a more complex solution we will show in a later chapter.

Example 2: Signals Cannot Use Operations Such as % and <<

The following constraints will create a “Non quadratic constraints are not allowed!” violation:

template Example() {
  signal input a;
  signal input b;

  // not allowed
  a === b % 5;

  // not allowed
  a === b << 2;
}

How Circom handles division

Somewhat subtly, Circom will allow “division” by a constant, because it can simply be replaced by multiplication by that number’s multiplicative inverse. As such, the following code is valid:

template Example() {
  signal input a;
  signal input b;

  a === b / 2;
}

component main = Example();

However, dividing signals is not allowed because that means we computed the signal’s multiplicative inverse, which does not have a direct translation to only addition and multiplication. Computing the multiplicative inverse is usually done with the extended euclidean algorithm, which requires loops and conditional statements — operations that cannot be natively expressed with addition and multiplication.

template Example() {
  signal input a;
  signal input b;
  signal input c;

  // not allowed
  a === b / c;
}

component main = Example();

In contrast, subtraction of signals is allowed because it directly translates to multiplication by the constant -1:

template Example() {
  signal input a;
  signal input b;

  // allowed
  a === b - a;

  // equivalent
  a === b + -1*a
}

component main = Example();

Integer division, as opposed to multiplication by the modular inverse is represented by the \ and it is not allowed to be applied to signals:

template Example() {
  signal input a;
  signal input b;

  // can only use \ with variables
  // not signals
  a === b \ 2;
}

component main = Example();

For variables, you have both integer division and “normal” division (i.e., multiplication with the multiplicative inverse of the divisor).

For signals, on the other hand, only “normal” division (in the above sense) is allowed.

Summary

A constraint can only have one multiplication between signals, but there is no limit on the number of additions.

It might seem that this restriction makes it impossible to express any interesting computation beyond simple arithmetic, but we will see later in this tutorial series that there exist numerous clever design patterns to work around this limitation.

Once we understand the design patterns, we can compose them to model much more complex algorithms.

Circle FFT — Part 1: Building the Circle Domain

Circle FFT — Part 1: Building the Circle Domain Circle STARKs is a new zk-STARK scheme that has been implemented in Stwo and Plonky3, and it has been adopted by several zkVM projects. Its key innovation lies in enabling the use of small 32-bit fields (Mersenne prime $2^{31}-1$) while still maintaining the mathematical properties needed for efficient FFT operations. […]

Multiplicative Subgroups and Primitive Elements

Multiplicative Subgroups and Primitive Elements Introduction This chapter continues our study of group theory by exploring subgroups and generators. The concept of a primitive element will be introduced at the end. We assume you are already familiar with the definition of a group. If you need a refresher, check out this article. To build intuition, […]

Square and Multiply Algorithm

Square and Multiply Algorithm The square and multiply algorithm computes integer exponents in $\mathcal{O}(\log n)$ (logarithmic time). The naive way to compute an exponent $x^n$ is to multiply $x$ by itself, $n$ times, which would require $\mathcal{O}(n)$ time to compute. Suppose we want to compute $x^{20}$. Instead of multiplying $x$ with itself 20 times, we […]

Computing the Current Tick Given sqrtPriceX96

Computing the Current Tick Given sqrtPriceX96 In the previous chapters, we saw that the protocol stores the square root of the price instead of the price itself. Therefore, it is necessary to relate ticks to the square root of the price in the fixed-point Q64.96 format. In this chapter, we will explore how to convert […]

Opportunities

Looking for an audit?

Leverage our extensive network of top security specialists.

Get A Quote
Security Engineer

Asymmetric Research is looking for someone to research and secure Solana/Rust-based smart contracts, build security tools, and lead audits.

Apply Now
Senior Rust Engineer

Irys is looking for a Senior Rust Engineer to lead protocol development, optimise core blockchain systems, and drive technical excellence.

Apply Now
Intermediate Rust Engineer

ZK Email is looking for a Rust engineer to build zero-knowledge tools, implement core features, and contribute to high-impact projects like zk-regex, zk-email, and partner integrations.

Apply Now