Programmable zero knowledge proofs unlock a new paradigm of programming akin to imperative, functional, and other kinds. It is the paradigm of verifiable computations over confidential inputs:

import "hashes/sha256/sha256" as sha256;

def main(private u32[16] hashIn, u32[8] hashOut) {
    u32[8] actualHash = sha256([hashIn]);
    assert(hashOut == actualHash);
}

Above is a ZoKrates program that takes the input and output of a SHA256 hash function and checks if they are correct. Note, however, that hashIn has a private modifier specified. Alice can then run this program with any SHA256 input-output of her choosing, and produce a cryptographic proof. She gives the proof to Bob together with hashOut (but not hashIn). The proof attests to Bob that hashOut was created for the above program, in other words, Alice knows a secret value hashIn, which when hashed via SHA256, produces hashOut.

For this procedure to work, the high level syntax needs to be compiled into an arithmetic circuit that is compatible with some SNARK proving system. Lately I have been playing with SNARK toolboxes and DSLs like ZoKrates, Circom, and others, in particular for teaching students or as part of my involvement in ZK-Plus project. These tools massively streamline the compilation and proof generation processes by providing high level interfaces and programming syntax that are familiar and accessible to a broad spectrum of users, abstracting away the underlying complexities of circuits, and cryptography.

For various reasons, though, certain semantics of the proving system remain visible in SNARK toolboxes and require a certain level of familiarity with them. Take for example the field data type that occurs in ZoKrates and other DSLs. It is a special data type that in some cases behaves like an unsigned integer, often leading to misconception that fields and integers can be used interchangeably.

To demonstrate, consider the following two ZoKrates programs that divide x and y and output the result:

// Program 1
def main() -> u32 {
    u32 x = 5;
    u32 y = 2;
    u32 z = x / y;
    return z;
}

// Program 2
def main() -> field {
    field x = 5;
    field y = 2;
    return x / y;
}

In the first program, the numbers are of type u32 and the program outputs 2. The second program declares them as field, and produces the output 10944121435919637611123202872628637544274182200208017171849102093287904247811.

Where this peculiarly large value comes from, what field variables mean, and when should fields (not) be used, are the subjects of this post.

We will look at:

  • Groups, Fields, and Elliptic Curves
  • Elliptic Curves underlying ZoKrates and their relation to the Field data type
  • Circuit-friendly vs Pairing-friendly curves
  • What Embedded Curves are and what role they play in generating proofs

According to ZoKrates' official documentation:

Field is the most basic type in ZoKrates, and it represents a field element with positive integer values in [0, p - 1] where p is a (large) prime number.

To shed more light on the meaning of the statement, this section explains the groups and fields algebraic structures.

A group is a set of values together with a group operation (like addition or multiplication) that follows a few simple rules:

Closure. If you take any two values from the group and apply the group operation, the result is also in the group. Think of it like a function that never returns an "out-of-bounds" value.

Associativity. The order in which you group operations does not matter. So for example (a + b) + c is the same as a + (b + c).

Identity. There’s a special value in the group (called the identity) that does not change anything when used as operand in the operation. Like adding 0 to a number, or multiplying by 1 - the result stays the same.

Inverses. For every value in the group, there exists another value (called its inverse) such that when you apply the group operation between them, you get the identity value. Think of it like this: If a * b = identity, then b is the inverse of a.

A field is a powerful structure that supports two operations simultaneously (usually addition and multiplication), each with their own group-like behavior that interact nicely with one another. Thus, it is a set of values with the following 2 main rules:

1. Addition forms a group. Just like the group definition, this means the field elements together with the addition operator + have:

  • Closure
  • Associativity
  • Identity value is 0 (a + 0 = a for every group element a)
  • Inverses (every element a has -a such that a + (-a) = 0
  • Often also commutative, i.e., a + b = b + a

2. Multiplication (excluding 0) forms a group. All non-zero elements together with the multiplication operator * have:

  • Closure
  • Associativity
  • Identity value is 1 (a * 1 = a for every group element a)
  • Inverses: For any a ≠ 0, there is b = 1/a such that a * b = 1
  • Often also commutative: a * b = b * a

The group also has multiplication distributivity: a * (b + c) == a * b + a * c

Groups and fields build the Elliptic Curves used in constructing zero knowledge proofs.

In ZoKrates, the field data type is nothing but an implementation of the Field definition above where variables of that type may take any value in the set of integers from 0 to p-1 where p = 21888242871839275222246405745257275088548364400416034343698204186575808495617.

In this set, the addition operators (+, -) form an additive group, and the multiplication operators (*, /) form a multiplicative one, i.e., all arithmetic operations on field variables are performed modulo p, thus all outputs remain inside the group (closure). Note that division is just a multiplication by the multiplicative inverse.

To demonstrate, observe the following python program:

p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
x = 5
y = 2
inverse_ y = pow(y, -1, p)
result = x * inverse_y
print(result)

Running this program outputs 10944121435919637611123202872628637544274182200208017171849102093287904247811, which matches exactly the ZoKrates program in the Introduction.

By now you should have an understanding of how arithmetic operations on field elements differ from integers. But why does p take this particular value? To answer this question, we need to take a look at elliptic curves and explain their relation to fields and groups.

In Elliptic Curve Cryptography, an Elliptic Curve is commonly represented by the mathematical equation \(y^2 \equiv x^3 + ax + b \mod p\) for some constant \(a\) and \(b\), where the \((x,y)\) are all points on the curve, i.e., satisfying this equation. This type of equation is also called the Weierstrass form and is typically visualized as follows:

Elliptic Curves that are useful to Cryptography are those that have a large number of its points form a Group, where the group elements are points on the curve and the arithmetic operation is addition. In this case, there are algorithms for executing the arithmetic group operations over points on the curve such adding two points, doubling a point, and multiplying a point with a scalar (non-point) value, i.e, repeated addition of a point to itself.

An elliptic curve carries the following set of properties:

Curve Order \(n\): A curve order is the total number of points on the curve, i.e., the set of all \((x,y)\) that satisfy the curve equation.

Base Field \(q\): Describes a finite field \(\mathbb{F}_q\) over which the curve is defined, i.e., from which all values \(x\) and \(y\) are taken. \(q\) is then the number of elements in that field.

Scalar Field \(r\): For cryptographic operations on Elliptic curves to be secure, they must be performed within a large group of prime order (size). If the curve's order is already prime, then this group contains all points on the curve, thus \(r = n\). If, on the other hand, the curve's order is a composite, then we are operating within a subgroup of order \(r < n\), i.e., within a subset of the points on the curve whose size is a large prime \(r\).

Generator: A point on the curve that can generate all other points via successive addition of the point to itself, and referred to as the primitive element. If the group order is prime, then all points except the point at infinity are generators.

In addition to the Weierstrass form, there are other methods to describe curves where they are represented via different equations and algorithms for the arithmetic operations, and may enable useful features.


These are well suited for fast scalar multiplication by making use of the Montegomery ladder algorithm, but are also more resistant to side-channel attacks. These curves are represented by the following equation: $$ By^2 = x^3 + Ax^2 + x $$

Prominent examples are the Curve25519 and Curve448 used in Diffie-Hellman key exchange protocols.


Edwards curves are another interesting form of elliptic curves with different properties, represented by: $$ Ax^2 + y^2 = 1 + Dx^2 \times y^2 $$

They possess a full addition law meaning that point addition has no special case (e.g., for identity element). They also have unified addition meaning both point addition and doubling follow the same formulas. This makes it both simpler in implementation and side-channel resistant as there are no special cases to handle.

Generalization of Edwards curves that include a special affine transformation that makes the curve "twisted", and more efficient for certain mapping operations. Because of a birational equivalance relation between Montgomery and Twisted Edwards curves, it is common to first begin by specifying a Montgomery curve then transform it to a twisted Edwards form.

Prominent examples are Edwards25519 that is birationally equivalent to Curve25519 and underlies Ed25519 signature scheme and the Edwards448 (Cf. Curve448) underlying EdDSA.

Pairing-based Cryptography is a branch of cryptography that is based on elliptic curves that support a special operation called *pairing.

Pairing is a function \(e : G_1 \times G_2 \to G_3\), i.e., a mapping from pairs of elements from 2 source groups onto a target group \(G_3\). The groups are of prime order and the pairing function is bilinear and non-degenerate. Pairing friendly curves are then curves that contain sub-groups \(G_1, G_2\) and a efficient mapping to a secure, target group \(G_3\).

Pairing friendly curves constitute the backbone of prominent proving systems such as Groth16.

A SNARK typically consists of a proving system and a process of compiling a program into something the proving system can work with. The compilation procedure involves encoding the program as a series of arithmetic operations over some Field \(\mathbb{F}_r\), called an arithmetic circuit. A proving system like Groth16 can then generate proofs out of this representation.

Although verification of SNARKs is cheap (by definition of Succinctness), proof generation depends on the size of the circuit and can be very costly. The size of the circuit (and in turn, the proof computation cost) is particularly amplified when the statement to be proven involves field or Elliptic Curve arithmetic, which typically appears in cryptographic operations such as verifying a digital signature.

Hence, two primary scalability factors directly impact circuit size:

  1. Complexity of field and elliptic curve arithmetic operations (addition, multiplication, ..etc)
  2. Whether the field is native to the proving system.

If the arithmetic operations are expensive, for example due to having to handle special cases, it results in more complex circuit representation. If the field itself in which the arithmetics take place is not compatible with the proving system's field (non-native), the target field needs to be simulated, producing prohibitively expensive circuits.

The first can be addressed by restricting the proving to statements (e.g., signature validity) over Twisted-Edwards curves in order to make use of efficiency gains of full and unified addition properties. Twisted-Edwards curves are therefore often referred to as circuit-friendly curves. Groth16 proving system, however, requires a pairing-friendly curve due to the use of pairings. Unfortunately, pairing-friendly curves are not great for efficient arithmetization, and it turns out there are no curves that are both circuit and pairing friendly at the same time. So how can we have the best of both worlds? Embedded Curves!

An embedded curve is an Elliptic Curve defined over the scalar field of another curve, that is, if \(E_1\) is an elliptic curve embedded within another Elliptic curve \(E_2\), then \(E_1\)'s base field equals \(E_2\)'s scalar field. Intuitively this means that if \((x,y)\) is a point on \(E_1\), then \(x\) and \(y\) are valid scalars in \(E_2\). This notion is also referred to as chaining and it addresses the other scalability challenge: statements in the base field of \(E_1\) are also in the scalar field of \(E_2\), thus native to the proving system.

Groth16-based implementations of SNARKS thus harness the efficiency benefits of chaining by supporting the expression of statements to prove on a circuit-friendly curve (called the proving/inner curve) which is embedded on a pairing-friendly curve for verification (called the verification/outer curve).

  • Groth16 is an efficient proving system that operates on arithmetic circuits and requires a pairing-friendly curve
  • Converting a computation into an arithmetic circuit is best done on circuit-friendly curves for efficiency
  • Embedded curves enable compatibility of the circuit-friendly with the pairing-friendly curve

ZoKrates supports multiple curves for compatibility with multiple blockchains. For Ethereum, it uses the Twisted-Edwards curve BabyJubJub as the proving curve, which is embedded on the pairing friendly BN128 curve- a curve natively supported by Ethereum. ZoKrates also supports further pairing-friendly curves like BLS12_381, BLS12_377, BW6-761, unfortunately with no yet support for respective embedded curves in the standard library.

BN128 Curve Parameters

Curve Order = 21888242871839275222246405745257275088548364400416034343698204186575808495617
Scalar Field = 21888242871839275222246405745257275088548364400416034343698204186575808495617 (same like order)
Base Field (modulus) = 21888242871839275222246405745257275088696311157297823662689037894645226208583
Generator of G1 = (1, 2)
Generator of G2 = ( x = (11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781), y = (4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930))
Type-3 pairings (G1 != G2)

Baby JubJub Parameters

Scalar Field = 2736030358979909402780800718157159386076813972158567259200215660948447373041
Base Field (modulus)= 21888242871839275222246405745257275088548364400416034343698204186575808495617
Cofactor = 8
Twisted Edwards