Finite Fields for Arithmetic Circuits
Finite Fields for Arithmetic Circuits
This article is the third in a series. We present finite fields in the context of circuits for zero-knowledge
proofs. The previous chapters are P vs NP and its Application to Zero Knowledge Proofs and
Arithmetic Circuits.
In the previous chapter on arithmetic circuits, we pointed out a limitation that we cannot encode the
number 2/3 because it cannot be represented precisely using binary. We also pointed out that we
didn’t explicitly have a way to handle overflow.
Both of these issues can be handled seamlessly with a variant of arithmetic, which is popular in
general cryptography, called finite fields.
Finite fields
Given a prime number p, we can make a finite field with p elements by taking the set of integers {0, 1, 2,
…, p-1} and define addition and multiplication to be done modulo p. We’ll start by limiting ourselves to
fields where the number of elements is a prime.
For example, if the prime number p is 7, then the elements in the finite field are {0, 1, 2, 3, 4, 5, 6}. Any
number outside this range (≥ p) is always mapped to an “equivalent” number in this range using
modulo. The technical word for “equivalent” is congruent.
Modulo calculates the remainder when dividing the number by the prime number. For example, if our
modulo is 7, the number 12 is congruent to 5 (12 mod 7 = 5), and the number 14 is congruent to 0.
Similarly, when we add two numbers, say 3 + 5, the resulting sum of 8 is congruent to 1 (8 mod 7 = 1).
The animation below illustrates this:
In this chapter, whenever we perform calculations, we’ll express our result as a number in the range of
0…(p-1), which is the set of elements in our finite field p. For example, 2 * 5 = 10, which we “simplify” to
3 (modulo 7).
Note how 3 + 5 “overflowed” the limit of 6. In finite fields, overflow is not a bad thing, we define the
overflowing behavior to be part of the calculation. In a finite field modulo 7, 5 + 3 is defined to be 1.
0 = 6 + 1 (mod 7)
Or more generally,
c = a + b (mod p)
where a and b are numbers in the finite field, c is the remainder that maps any number ≥ p back in the
set {0, 1, …, p - 1}.
Multiplication works similarly by multiplying the numbers together, then taking the modulus:
3 = 4 × 6 (mod 7) = 24 (mod 7) = 3
Because of this restriction, the order of the field is always equal to p. The order is the number of
elements in the field.
For example, if we have p = 5, the elements are {0, 1, 2, 3, 4}. There are 5 elements, so the order of that
field is 5.
Additive Inverse
Consider that 5 + (-5) = 0.
These additive inverse rules apply to finite fields also. Although we don’t have elements with negative
signs like “-5”, some elements can “behave” like negative numbers with respect to each other using
the modulo operator.
In regular arithmetic, “-5” is the number that, when added to 5, results in zero. If our finite field is p = 7,
then 2 can be considered “-5” because (5 + 2) mod 7 = 0. Similarly, 5 can be considered to be “-2”
because they are each other’s additive inverse. To be precise, “-2” is congruent to 5.
To compute the additive inverse of an element, simply compute p - a where a is the element we are
trying to find the additive inverse of. For example, to find the additive inverse of 14 modulo 23, we
compute 23 - 14 = 9. We can see 14 + 9 (mod 23) = 0. p is congruent to zero, so this is equivalent to
computing “-5” as 0 - 5.
The general pattern for additive inverses in a finite field is that the elements in the first half of the
finite field are the additive inverses of the elements in the second half, as show in the figure below.
Zero is the exception since it is its own additive inverse. The numbers connected by the green line are
each other’s additive inverse in the field p = 7:
Exercise: Let’s say we pick a p >= 3. Which, if any, non-zero values are their own additive inverse?
Multiplicative inverse
Consider that 5 × (1/5) = 1
The multiplicative inverse for a is a number b such that a * b = 1. Every element except zero has a
multiplicative inverse. With “regular numbers,” a multiplicative inverse is 1 / num. For example, the
multiplicative inverse of 5 is 1/5, and the multiplicative inverse of 19 is 1/19.
Although we do not have fractional numbers in finite fields, we can still find pairs of numbers that
“behave like” fractions when added or multiplied together.
For example, the multiplicative inverse of 4 in the finite field p = 7 is 2, because 4 * 2 = 8, and 8 mod 7 is
1. Thus, 2 “behaves” like 1/4 in the finite field, or to be precise, 1/4 is congruent to 2 (mod 7).
x * x === 1
x + 1 === 0
This means that aᵖ⁻² (mod p) is the multiplicative inverse of a, since a times aᵖ⁻² is 1.
Some examples:
The multiplicative inverse of 3 in the finite field p = 7 is 5. 3⁷⁻² = 5 (mod 7)
The multiplicative inverse of 8 in the finite field p = 11 is 7. 8¹¹⁻² = 7 (mod 11)
The advantage of this approach is we can use the expmod precompile in Ethereum to compute the
modular inverse in a smart contract.
In practice, this is not an ideal way to compute multiplicative inverses because raising a number to a
large power is computationally expensive. Libraries that compute the multiplicative inverse use more
efficient algorithms under the hood. However, when such a library is not a available, Fermat’s Little
Theorem can be used.
Example:
p = 17
print(pow(5, -1, p))
# 7assert
(5 * 7) % p == 1
Exercise: Find the multiplicative inverse of 3 modulo 5. There are only 5 possibilities, so try all of them
and see which ones work.
Exercise: What is the multiplicative inverse of 50 in the finite field p = 51? You do not need Python to
compute this, see the principles described in “General rules of multiplicative inverses.”
Exercise: Use Python to compute the multiplicative inverse of 288 in the finite field of p = 311. You can
check your work by validating that (288 * answer) % 311 == 1.
With real numbers, if we add 1/2 + 1/2, we expect to get 1. The same thing happens in a finite field.
Since 4 “behaves like” 1/2, we expect 4 + 4 (mod 7) = 1, and indeed it does.
We are able to encode the number “5/6” in a finite field by thinking of it as the operation 5 * (1/6), or 5 *
multiplicative_inverse(6).
Consider that 1/2 + 1/3 = 5/6. If our finite field is p = 7, then 1/2 is the multiplicative inverse of 2, 1/3 is
the multiplicative inverse of 3, and 5/6 is 5 times the multiplicative inverse of 6:
p = 7
one_half = pow(2, -1, p)
one_third = pow(3, -1, p)
five_over_six = (pow(6, -1, p) * 5) % p
assert (one_half + one_third) % p == five_over_six
# True
The general way to compute a “fraction” in a finite field is the numerator times the multiplicative
inverse of the denominator, modulo p:
It is not possible to do this when the denominator is a multiple of p. For example, 1/7 cannot be
represented in the finite field p = 7 because pow(7, -1, 7) has no solution. The modulo is taking the
remainder after division, and the remainder of 7/7 is zero, or more generally, 7/d is zero when d is a
multiple of 7. The multiplicative inverse means we can multiply a number and its inverse together to
get 1, but if one of the numbers is zero, there is nothing we can multiply by zero to get 1.
Exercise: run pow(7, -1, 7) in Python. You should see an exception get thrown, ValueError: base is not
invertible for the given modulus.
x + y + z === 1;
x === y;
y === z;
has an exact solution when done in a finite field. This would not be possible to do reliably if we used
fixed point or floating point numbers as the data types for our arithmetic circuits (consider that
adding 0.33333 + 0.33333 + 0.33333 will result in 0.99999 rather than 1).
p = 11
# x, y, z have value 1/3
x = pow(3, -1, 11)
y = pow(3, -1, 11)
z = pow(3, -1, 11)
assert x == y;
assert y == z;
assert (x + y + z) % p == 1
That is, “dividing by two” is really multiplying by the multiplicative inverse of 2, and that will always
result in another field element without “remainder.”
However, if we have a binary representation of the field element, then we can check if the element is
even or odd if it were cast to an integer. If the least significant bit is 1, then the number is odd (if
interpreted as an integer, not a finite field element).
Below, we translate the addition of fractions code from the above section (1/2 + 1/3 = 1/6) to use the
galois library instead. The library overwrites the math operators to work in a finite field:
import galois
GF7 = galois.GF(7) # GF7 is a class that wraps 7
negative_two = -GF(2)
assert negative_two + GF(2) == 0
import galois
GF = galois.GF(11)
Exercise: check that 3/4 * 1/2 = 3/8 in the finite field p = 17.
To understand this, consider the finite field p = 7. To multiply two numbers together and get 0 as a
result, then one of the terms a needs to be a multiple of 7, so that a (mod 7) is zero. However, if we
exclude zero, none of the remaining elements {1, 2, 3, 4, 5, 6} are a multiple of 7, so this cannot happen.
We will refer to this fact frequently when we design arithmetic circuit. For example, if we know
x₁ * x₂ * ... * x ≠ 0 ₙ
ₙ
Then we can be certain all of variables x₁, x₂, x are non-zero — even if we don’t know their values.
Here’s how we can use this trick for a realistic arithmetic circuit. Suppose we have signals x₁, x₂, x₃. We
wish to constrain at least one of these signals to have the value 8 without specifying which one does.
First we compute the additive inverse of 8 a_inv(8) for our field. Then we do:
with the understanding that -8 is the additive inverse of 8 for our finite field.
As long of one of the signals has the value 8, then that term will be zero and the whole expression will
multiply to zero. This trick relies on two facts:
If all of the terms are non-zero, there is no way for the expression to evaluate to zero
The additive inverse of 8 is unique, and 8 is the uniquely additive inverse for the additive
inverse of 8. In other words, there is no value except 8 that results in 8 + inv(8) being zero.
Therefore, the arithmetic circuit (x₁ + a_inv(8))(x₂ + a_inv(8))(x₃ + a_inv(8)) === 0 states that at least
ₙ
one of x₁, x₂, x has the value 8.
associative
(a + b) + c = a + (b + c) (mod p)
commutative addition
(a + b) = (b + a) (mod p)
commutative multiplication
ab = ba (mod p)
distributive
a(b + c) = ab + ac (mod p)
Just like regular square roots, which have two solutions: a positive and negative one, modular square
roots in a finite field also have two solutions. The exception is the element 0, which only has 0 as its
square root.
In the finite field modulo 11, the following elements have square roots:
0 0 n/a
1 1 10
3 5 6
4 2 9
5 4 7
9 3 8
Exercise: Verify the claimed square roots in the table are correct in the finite field modulo 11.
Observe that the second square root is always the additive inverse of the first square root, just like
real numbers.
For example:
In regular math, the square roots of 9 are 3 and -3, where both are additive inverses of each
other.
In a finite field of p = 11, the square roots of 9 are 3 and 8. 8 is the additive inverse of 3
because 8 + 3 (mod 11) is 0, just as 3 is the additive inverse of -3.
The elements 2, 6, 7, 8, and 10 do not have modular square roots in the finite field p = 11. This can be
discovered by squaring every element from 0 to 10 inclusive, and seeing that 2, 6, 7, 8, and 10 are
never produced.
numbers_with_roots = set()
p = 11
for i in range(0, p):
numbers_with_roots.add(i * i % p)
print("numbers_with_roots:", numbers_with_roots)
# numbers_with_roots: {0, 1, 3, 4, 5, 9}
Note that 3 is not a perfect square, but it does have a square root in this finite field.
When p can be written as 4k + 3 where k is an integer, then the modular square root can be computed
as follows:
The function above returns one of the square roots of of x modulo p. The other square root can be
computed by calculating the additive inverse of the value returned. If the prime number is not of the
form 4k + 3 then the Tonelli-Shanks algorithm must be used to compute the modular square root
(which the libnum library above implements).
The implication for this is that the arithmetic circuit x * x === y may have two solutions. For
example, in a finite field p = 11, it might seem that the arithmetic circuit x * x === 4 only admits the
value 2 because -2 is not a finite field element. However, that assumption is very wrong! The
assignment x = 9, which is congruent to -2, also satisfies the circuit.
Exercise: Use the code snippet above to compute the modular square root of 5 in the finite field of p =
23. The code will only give you one of the answers. How can you compute the other?
However, just because a linear system of equations over real numbers has zero, one, or infinite
solutions it does not imply that the same linear system of equations over a finite field will also have
zero, one or, p many solutions.
The reason we emphasize this is because we use arithmetic circuits and an assignment to the signals
to encode our solution to a problem in NP. However, because arithmetic circuits are encoded with
finite fields, we may end up encoding the problem in a way that does not capture the behavior of the
equations we are trying to model.
The following three examples show how the behavior of a system of equations can change when done
over a finite field.
It has one solution: (1, 0) for real numbers, but over the finite field p = 11, it has 11 solutions: (0, 6), (1, 0),
(2, 5), (3, 10), (4, 4), (5, 9), (6, 3), (7, 8), (8, 2), (9, 7), (10, 1).
Don’t assume that a system of equations (arithmetic circuit) that has a single solution in real
numbers has a single solution in a finite field!
Below we plot the solutions to the systems of equation over the finite fields to illustrate both
equations “intersect everywhere,” that is, have the same set of points that satisfy both equations:
This might seem extremely counterintuitive — let’s see how it happens. If we solve the original
equations:
for y we get:
“1/2” is the multiplicative inverse of 2. In a finite field of p = 11, 6 is the mutiplicative inverse of 2, i.e. 2
* 6 (mod 11) = 1. Therefore, x + 2y = 1 and 6x + y = 6 are actually the same equation in finite field p = 11.
That is, the equation y = 1/2 - 1/2x when encoded in a finite field is y = inv(2) - inv(2)x which is y = 6 -
6x — the same as the other equation in the system.
Exercise: Write code to bruteforce every combination of (x, y) over x = 0..10, y = 0..10 to verify the
above system has no solution over the finite field of p = 11.
Why does this system of equations have no solution in finite field p = 11?
If we solve
The fractions above do not have a congruent element in the finite field p = 11.
Recall that dividing by a number is equivalent to multiplying by its multiplicative inverse. Also, recall
the field order (in this case 11) won’t have a multiplicative inverse, because the field order is congruent
to 0.
The multiplicative inverse of a is the value b such that a * b = 1. However, if a = 0 (or any value
congruent to it) then there is no solution for b. So, the expressions we wrote for the solutions in the
real numbers can’t be translated into elements of our finite field.
Therefore the solutions (x, y) above are not part of the finite field. Hence, in a finite field p = 11, x + 2x =
1 and 7x + 3y = 2 are parallel lines.
To see this from another angle, we could solve the equations for y and get:
We saw in the previous section that 6 is the multiplicative inverse of 2, so the first equation has a
“slope” of 6 in the the finite field. In the second equation, we compute the slope by computing 7 times
the multiplicative inverse of 3: (7 * pow(3, -1, 11)) % 11 = 6 . We now show that their slopes are the same
in a finite field.
The slope is the coefficient of x in the form y = c + bx. For the two equations above, the first slope is
-1/2 and the second slope is -7/3. If we convert both of these fractions to an element in the finite field
of p = 11, we get the same value of 5:
import galois
GF11 = galois.GF(11)
negative_1 = -GF11(1)
negative_7 = -GF11(7)
x + 2 * y === 1
7 * x + 3 * y === 2
is that the arithmetic circuit does not have a satisfying assignment in a finite field p = 11.
Exercise: Convert the two equations to their finite field representation and see they are the same.
x + 2 * y === 3
4 * x + 8 * y === 1
For the finite field we are using, our constraints are redundant even though they “look different.” That
is, two different looking constraints actually constrain x and y to have the same values.
Nevertheless, polynomials in finite fields share a lot of properties with polynomials over real numbers:
A polynomial of degree d has at most d roots. The roots of a polynomial p(x) are the values r
such that p(r) = 0.
If we add two polynomials p1 and p2, the degree of p1 + p2 will be at most max(deg(p1),
deg(p2)). It’s possible for the degree of p1 + p2 to be less than max(deg(p1), deg(p2)). For
example, p1=x³ + 5x + 7 and p2 = -x³,
Adding polynomials in a finite field follows associative, commutative, and distributive laws.
If we multiply two polynomials p1 and p2, the roots of the product will be the union of the
roots of p1 and p2.
The domain of x are the elements of the finite field, and the output (range) must be a member of the
finite field as well. That is, note how all the x and y values lie in the interval of [0,16]. A polynomial over
a finite field can only have x and y values less than p.
The equivalent of y = -x² in finite field p = 17 is y = 16x² (mod 17) since 16 is the additive inverse of 1 in
that finite field.
Polynomials that do not have roots in real numbers may have roots in
a finite field
Just like our examples above with linear systems of equations, one should not assume that a
polynomial with certain roots in real numbers has the same roots in a finite field.
Below, we plot y = x² + 1 in the finite field p = 17. In real numbers, y = x² + 1 has no real roots. But in a
finite field, it has two roots at 4 and 13, marked in red dots below:
Let’s now explain why y = x² + 1 does not have roots in real numbers but does have roots in the finite
field p = 17. In the finite field p = 17, 17 is congruent zero. So if we plug a value into x such that x² + 1
becomes 17, then the polynomial will output zero, not 17. We can solvex² + 1 = 17 for x² = 17 - 1 = 16. In a
finite field of p = 17, x² = 16 has solutions 4 and 13. Therefore, y = x² + 1 has roots 4 and 13 in the finite
field p = 17.
import galois
GF103 = galois.GF(103) # p = 103
print(p1)
# x^2 + 2x + 102
print(p1 + p2)
# 2x^2 + 3x
The galois library is intelligent enough to interpret negative integers as additive inverses, as the code
below demonstrates:
import galois
GF103 = galois.GF(103) # p = 13
print(p3)
# 102x + 1
print(p4)
# 102x + 2
print(p3 * p4)
# x^2 + 100x + 2
Note that p3 and p4 are degree 1 polynomials and there product is a degree 2 polynomial.
print((p3 * p4).roots())
# [1, 2]
Practice problems
In the problems below, use a finite field p of
21888242871839275222246405745257275088548364400416034343698204186575808495617.
Beware that the galois library takes a while to initialize a GF object, galois.GF(p), of this size.
1. A dev creates an arithmetic circuit x * y * z === 0 and x + y + z === 0 with the intent of
constraining all the signals to be zero. Find a counter example to this where the constraints
are satisfied, but not all of x, y, and z are 0.
2. A dev creates a circuit with the polynomial x² + 2x + 3 === 11 and proves that 2 is a solution.
What is the other solution? Hint: write the circuit as x² + 2x - 8 === 0 then factor the polynomial
by hand to find the roots. Finally, compute the congruent element of the roots in the finite field
to find the other solution.