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 23112^{31}-1) while still maintaining the mathematical properties needed for efficient FFT operations. In this explanatory article series, we will discuss the primary algorithm behind Circle STARK, named Circle FFT.

In Part 1 article, we first review how STARK-friendly primes have evolved, and then we explore the foundational concepts of Circle FFT—namely the circle curve, its group structure, and the roles of twin-cosets and standard-position cosets, using detailed examples and derivations. Then, in Part 2 article, we will describe the Circle FFT algorithm itself in detail.

We also provide a walkthrough of these core ideas—such as the circle group and the Circle FFT—together with explicit example computations for p=31p=31 (Mersenne prime with exponent 55: 2512^5 -1 ) with Python-script.

Our focus is on how each of these building blocks leads into a full-fledged FFT-like routine even when p1p-1 does not have a large two-adic factor.

If you’re interested in running the examples yourself, the full Python code is available here, and we will reference it in later sections such as Section 4 and 6, where we walk through the group and domain construction in code.

This article was written by Yugo in collaboration with RareSkills. This article was funded by a Soulforge Grant. We are grateful for their support.

0. From STARK‑Friendly Primes to Mersenne 31

Before diving into STARK-friendly prime, let’s first recall finite-field basics.

A field is a set in which addition, subtraction, multiplication, and division (except by zero) are defined. When the set of elements is finite, it is called a finite field. Fp\mathbb{F}_p refers to the set of integers from 00 up to p1p-1 (where pp is a prime), with addition, subtraction, and multiplication all taken modulo pp, and division defined as the inverse of multiplication modulo pp.

We denote by Fp\mathbb{F}_p^* the set Fp{0}\mathbb{F}_p \setminus \{0\}. Each element in Fp\mathbb{F}_p^* has a multiplicative inverse, so Fp\mathbb{F}_p^* can be viewed as a multiplicative group. The size of this group, Fp|\mathbb{F}_p^*|, is p1p-1. When we factorize p1p-1 into its prime factors, it is known that if a prime qq appears rr times, then there is a cyclic subgroup of size qrq^r.

You can briefly illustrate this with a small prime, for example p=7p=7. Since p1=6p-1=6 factors as 2×32\times 3, there must be subgroups of sizes 22 and 33. Indeed, in F7={1,2,3,4,5,6}\mathbb{F}_7^* = \{1,2,3,4,5,6\}, the element 66 generates a size-22 subgroup {1,6}\{1,6\}, and the element 22 generates a size-33 subgroup {1,2,4}\{1,2,4\}.

In many STARK protocols, the Number-Theoretic Transform (NTT) or Fast Fourier Transform (FFT) relies on such a subgroup where the factor is a power of 2, so that the subgroup has size 2r2^r. The largest integer rr such that 2r2^r divides (p1)(p-1) is called the two-adicity. A high two-adicity allows a larger power-of-2 size for FFT/NTT domains, which is key to achieving fast polynomial evaluations, interpolations, and polynomial multiplication. Moreover, implementing these NTT-based operations efficiently heavily depends on fast modular multiplications—since FFT/NTT involves repeatedly multiplying elements and reducing them modulo pp.

Originally, STARKs used a prime p=2251+172192+1p = 2^{251} + 17 \cdot 2^{192} + 1, which has a two-adicity of 192192. In modern STARK implementations, one of the improvements was made to reduce overhead by decreasing the size of the finite field. For example, the Goldilocks field is p=264232+1p = 2^{64} - 2^{32} + 1 and has a two-adicity of 3232. Crucially, because 2642321(modp),2^{64} \equiv 2^{32} - 1 \pmod{p}, the product of two 64-bit numbers can be split in base 2322^{32} and reduced with only a few additions and subtractions.

What about Circle STARKs? As proposed in the Circle STARKs paper, it uses a Mersenne prime,

p=2311.p = 2^{31} - 1.

A Mersenne prime is a prime number of the form 2n12^n -1 for some integer nn. Here, n=31n=31 gives 23112^{31}−1, which happens to be prime and is therefore categorized as a Mersenne prime.

One primary reason to choose 23112^{31}-1 is that it fits within a 32-bit machine word, making modular multiplication extremely fast. Specifically, when two 32-bit numbers are multiplied, the result is a 64-bit value, and modular reduction by 23112^{31}-1 can be done with only two additions.

This operation can be much faster with vectorized instructions. Modern CPUs excel at processing 32-bit integers natively, and the Mersenne 31 (23112^{31}-1) fits within this architecture, allowing for optimal utilization of hardware capabilities. Compared to the 256-bit prime used in the original STARK, Mersenne 31 offers approximately 125x faster modular multiplication, and a 1.3x speedup over Baby Bear (231227+12^{31} - 2^{27} + 1), as discussed in Why I’m Excited By Circle Stark And Stwo by Eli Ben-Sasson.

However, the traditional FFT or NTT approach requires a large 2-adic factor in p1p-1 to form a sufficient power-of-2 subgroup in Fp\mathbb{F}_p^*. Here, Fp=p1  =  2312,|\mathbb{F}_p^*| = p - 1 \;=\; 2^{31} - 2, which has a two-adicity of only 1. Thus, we cannot directly use a large power-of-2 subgroup under multiplication.

Circle STARK still employs the Mersenne prime field p=2311p = 2^{31} - 1. However, instead of working directly with Fp\mathbb{F}_p itself, we observe that the curve

x2+y2=1x^2 + y^2 = 1
over Fp\mathbb{F}_p (where p3(mod4)p \equiv 3 \pmod{4}) has exactly p+1p + 1 points. For instance, when p=3  (=221)p = 3\; (= 2^2 - 1), there are exactly four such points: (0,1),(0,2),(1,0),(2,0)(0,1), (0,2), (1,0), (2,0).

This is crucial because p+1p+1 can include a large power of 2, creating subgroups of size 2n2^n. Thus, instead of working with single elements in Fp\mathbb{F}_p, Circle STARK considers pairs (x,y)Fp2(x,y)\in \mathbb{F}_p^2 satisfying x2+y2=1x^2 + y^2 = 1 (i.e. points on the circle curve).
By doing so, Circle STARK can implement its own fast polynomial interpolation and evaluation—often referred to as the Circle FFT—directly over these circle elements.

1. Circle Curve

Let’s move on to the core mathematics of Circle STARKs. A circle curve over a finite field Fp\mathbb{F}_p is the subset of Fp2\mathbb{F}_p^2 defined by all points (x,y)(x,y) satisfying

x2+y2=1.x^2 + y^2 = 1.

We sometimes denote this set by C(Fp)C(\mathbb{F}_p) or simply “the circle.”

In the Circle STARKs, we focus on primes pp with p3(mod4)p \equiv 3 \pmod{4} (e.g., p=3,7,11,p=3,7,11,\dots). Under this condition, 1-1 is not a square in Fp\mathbb{F}_p. Concretely, this ensures that the equation x2+y2=1x^2 + y^2 = 1 has exactly p+1p+1 solutions in Fp2\mathbb{F}_p^2 and no additional outlier solutions (i.e., points at infinity). For this article, all we need is that x2+y2=1x^2 + y^2 = 1 in Fp2\mathbb{F}_p^2 yields exactly p+1p+1 points under that condition.

(However, for readers interested in a geometric perspective on why p3(mod4)p \equiv 3 \pmod{4} leads to exactly p+1p + 1 solutions, see the Appendix.)

1.1 Toy Example p=73(mod4)p=7\equiv 3 \pmod{4}

For example, if p=7p =7 (which is 3(mod4)3 \pmod{4}), then C(F7)C(\mathbb{F}_{7}) is the set of all (x,y)F72(x,y) \in \mathbb{F}_{7}^2 such that x2+y2=1.x^2 + y^2 = 1.

Here are a few pairs (x,y)(x,y) in F72\mathbb{F}_{7}^2 that satisfy x2+y21(mod7)x^2 + y^2 \equiv 1 \pmod{7}. For example:

  • (1,0)(1, 0) and (0,1)(0, 1) are obvious because 12+02=11^2 + 0^2=1 and 02+12=10^2 + 1^2=1.
  • (5,2)(5,2) also works because 52+22=25+4=295^2 + 2^2 = 25 + 4 = 29, and 29mod7=129 \bmod 7 = 1.

We see that there are exactly 88 (p+1)(p+1) such pairs for p=7p=7.

2. Circle Group

This kind of set is denoted by C(Fp)C(\mathbb{F}_p) and is often called the Circle Curve over Fp\mathbb{F}_p. One striking fact is that C(Fp)=p+1|C(\mathbb{F}_p)| = p + 1. For example, if p=7p = 7, then there are exactly 7+1=87 + 1 = 8 points on the circle — notably 8=238 = 2^3.

This is important because in the usual STARK setting, we often want a domain of size 2n2^n for FFT-like operations. In the case of p=7p = 7, having exactly 8 points on the circle means we can form subgroups of size 2n2^n. Later, we’ll see how this property (the high 2-adicity in p+1p+1) is exploited in the Circle FFT.

A key property is that C(Fp)C(\mathbb{F}_p) can be given with a group operation (which is, in essence, a binary operator) on its points. Specifically, we use “\cdot” to denote this operation: for two points (x0,y0)(x_0,y_0) and (x1,y1)(x_1,y_1) in C(Fp)C(\mathbb{F}_p),

(x0,y0)(x1,y1)  =  (x0x1y0y1,    x0y1+y0x1).(x_0, y_0)\,\cdot\,(x_1, y_1) \;=\; \bigl(x_0 x_1 - y_0 y_1,\;\; x_0 y_1 + y_0 x_1\bigr).

We can check that this group operation remains within C(Fp)C(\mathbb{F}_p) (the result still satisfies x2+y2=1x^2 + y^2=1) and makes the set into a cyclic group of size p+1p+1.

2.1 Checking the Group Axioms

As the axioms of a group, we typically list the following four properties: closure, the identity element, the inverse element, and associativity. Let’s take a closer look at each one to confirm they all hold.

2.1.1 Closure

Take two points (x0,y0)(x_0, y_0) and (x1,y1)(x_1, y_1) on the circle;

x02+y02=1andx12+y12=1.x_0^2 + y_0^2 = 1 \quad\text{and}\quad x_1^2 + y_1^2 = 1.

Define a new point

(x^,y^)  =  (x0x1    y0y1,    x0y1  +  y0x1).(\hat{x}, \hat{y}) \;=\; \bigl( x_0\,x_1 \;-\; y_0\,y_1, \;\; x_0\,y_1 \;+\; y_0\,x_1 \bigr).

We will show that (x^,y^)(\hat{x}, \hat{y}) also lies on the circle by computing (x^2+y^2)(\hat{x}^2 + \hat{y}^2) step by step:

x^2+y^2=  (x0x1y0y1)2  +  (x0y1+y0x1)2// expand squares=  x02x12  2x0x1y0y1+  y02y12  +  x02y12+  2x0y1y0x1+  y02x12=  x02x12  +  y02y12  +  x02y12  +  y02x12// canceled ±2x0y1y0x1=  x02(x12+y12)  +  y02(x12+y12)// factor out (x12+y12)=  (x02+y02)(x12+y12)// factor again using x02+y02\begin{aligned} \hat{x}^2 + \hat{y}^2 &=\; (x_0 x_1 - y_0 y_1)^2 \;+\; (x_0 y_1 + y_0 x_1)^2 \quad \text{// expand squares} \\[6pt] &=\; {x_0^2 x_1^2} -\; 2\,x_0 x_1\,y_0 y_1 +\; {y_0^2 y_1^2} \;+\; {x_0^2 y_1^2} +\; 2\,x_0 y_1\,y_0 x_1 +\; {y_0^2 x_1^2} \\[4pt] &=\; x_0^2 x_1^2 \;+\; y_0^2 y_1^2 \;+\; x_0^2 y_1^2 \;+\; y_0^2 x_1^2 \quad \text{// canceled $\pm2\,x_0 y_1\,y_0 x_1$} \\[3pt] &=\; x_0^2 \bigl(x_1^2 + y_1^2\bigr) \;+\; y_0^2 \bigl(x_1^2 + y_1^2\bigr) \quad \text{// factor out }(x_1^2 + y_1^2) \\[3pt] &=\; \bigl(x_0^2 + y_0^2\bigr)\, \bigl(x_1^2 + y_1^2\bigr) \quad \text{// factor again using }x_0^2 + y_0^2 \end{aligned}

Since we know x02+y02=1x_0^2 + y_0^2 = 1 and x12+y12=1x_1^2 + y_1^2 = 1, we substitute these values into the product:

x^2+y^2  =  (x02+y02)(x12+y12)  =  1×1  =  1.\hat{x}^2 + \hat{y}^2 \;=\; (x_0^2 + y_0^2)\,(x_1^2 + y_1^2) \;=\; 1 \times 1 \;=\; 1.

Hence (x^,y^)(\hat{x}, \hat{y}) also satisfies (x^2+y^2=1)(\hat{x}^2 + \hat{y}^2 = 1) and therefore lies on the circle. This confirms that the set is closed under the operation

(x0,y0)    (x1,y1)  =  (x^,y^).(x_0, y_0) \;\cdot\; (x_1, y_1) \;=\; (\hat{x}, \hat{y}).

2.1.2 Identity Element

The point (1,0)(1,0) serves as the identity element under the group operation. Concretely, for any (x,y)(x,y) on the circle,

(1,0)(x,y)  =  (1x0y,  1y+0x)  =  (x,y),(1,0)\cdot(x,y) \;=\; \bigl(1\cdot x - 0\cdot y,\;1\cdot y + 0\cdot x\bigr) \;=\; (x,y),

One might wonder about the point (0,1)(0,1). If we try to use (0,1)(0,1) as an identity, we see it fails:

(0,1)(x,y)  =  (0x1y,  0y+1x)  =  (y,  x),(0,1)\cdot(x,y) \;=\; \bigl(0\cdot x - 1\cdot y,\;0\cdot y + 1\cdot x\bigr) \;=\; (-y,\;x),

which is not equal to (x,y)(x,y) for general x,yx,y. Because the identity element is unique, (0,1)(0,1) cannot be the identity.

2.1.3 Inverse

In a group, the inverse of an element (x,y)(x,y) is the unique point (x,y)(x',y') satisfying

(x,y)(x,y)  =  (1,0).(x,y)\,\cdot\,(x',y') \;=\; (1,0).

On the circle, we see that (x,y)(x,-y) serves as the inverse of (x,y)(x,y) with respect to the group operation. Indeed,
(x,y)(x,y)  =  (xxy(y),  x(y)+yx)  =  (x2+y2,  0)  =  (1,0),(x,y)\,\cdot\,(x,\,-y) \;=\; \bigl(x\,x - y\,(-y),\; x\,(-y) + y\,x\bigr) \;=\; \bigl(x^2 + y^2,\;0\bigr) \;=\; (1,0),

since x2+y2=1x^2 + y^2 = 1.
Thus, the inverse of (x,y)(x,y) under the group operation is (x,y)(x,\,-y).

2.1.4 Associativity

The last group axiom is associativity: for any three points

(x0,y0),(x1,y1),(x2,y2)(x_0,y_0), (x_1,y_1), (x_2,y_2) on the circle,

((x0,y0)(x1,y1))(x2,y2)  =  (x0,y0)((x1,y1)(x2,y2)).\bigl((x_0,y_0)\,\cdot\,(x_1,y_1)\bigr)\,\cdot\,(x_2,y_2) \;=\; (x_0,y_0)\,\cdot\,\bigl((x_1,y_1)\,\cdot\,(x_2,y_2)\bigr).

This can be verified by expanding the polynomial, but we won’t go into detail here as it would be too long.

2.2 A Special Operation: The Squaring Map π\pi

Beyond these group axioms, there is another operation on the circle that is particularly useful in later sections, especially for the Circle FFT, namely the squaring map. This map applies the group operation to a point with itself:

π(x,y)  =  (x,y)(x,y)  =  (x2y2,  2xy).\pi(x,y) \;=\; (x,y)\,\cdot\,(x,y) \;=\; \bigl(x^2 - y^2,\;2\,x\,y\bigr).

Since the circle satisfies x2+y2=1x^2 + y^2 = 1, we get x2y2=2x21x^2 - y^2 = 2x^2 - 1, so

π(x,y)  =  (2x21,  2xy).\pi(x,y) \;=\; \bigl(2\,x^2 - 1,\;2\,x\,y\bigr).

In the Circle FFT, we will use π\pi to halve certain evaluation domains in a recursive manner—each application of π\pi reduces the domain size by a factor of 2. This is because π\pi is compatible with the group operation. Concretely, for two points PP and QQ on the circle,

π(PQ)  =  π(P)    π(Q).\pi\bigl(P \cdot Q\bigr) \;=\; \pi(P) \;\cdot\; \pi(Q).

This compatibility implies that π\pi maps a twin-coset of size 2n2^n into another twin-coset of size 2n12^{n-1}. We will return to this domain-halving property in detail later.

2.3 A Special Operation: The Involution JJ

In addition to the squaring map, there is another important operation. This operation, called the involution, is defined by

J(x,y)  =  (x,  y).J(x,y) \;=\; (x,\;-y).

Geometrically, it flips the sign of the yy-coordinate while leaving the xx-coordinate unchanged. Notice that JJ is its own inverse—applying JJ twice returns you to the original point:
J(J(x,y))  =  J(x,  y)  =  (x,  y).J\bigl(J(x,y)\bigr) \;=\; J(x,\;-y) \;=\; (x,\;y).

On the circle x2+y2=1x^2 + y^2 = 1, the involution J(x,y)J(x,y) keeps every point on the curve (since negating yy does not affect x2+y2x^2 + y^2). However, a point with y=0y=0 is fixed under JJ, meaning J(x,0)=(x,0)J(x,\,0) = (x,\,0).

2.4 Concrete Operation of Circle Group at p=31p=31

In the previous concrete example, we explored C(Fp)C(\mathbb{F}_p) with p=7p = 7, which served as a useful toy example for building intuition. However, p=7p = 7 is too small to reveal the deeper structural properties of the Circle Group—such as longer subgroup chains or the construction of twin-cosets or standard positon coset.

Therefore, we now turn to a slightly larger prime, p=31p = 31 (which satisfies p=251p = 2^5 - 1), to illustrate these richer behaviors in a more meaningful way.

Given p=31p = 31, which is congruent to 3(mod4)3 \pmod{4}, the set of points (x,y)F312(x, y) \in \mathbb{F}_{31}^2 satisfying the equation x2+y2=1x^2 + y^2 = 1, denoted as C(F31)C(\mathbb{F}_{31}), is illustrated in the following diagram:

Circle in F_31

Here are a few pair examples (x,y)(x,y) in F312\mathbb{F}_{31}^2 that satisfy x2+y21(mod31)x^2 + y^2 \equiv 1 \pmod{31}:

  • (5,10)(5,10) also works because 52+102=25+100=1255^2 + 10^2 = 25 + 100 = 125, and 125mod31=125431=1125 \bmod 31 = 125 - 4\cdot31 = 1.
  • (7,13)(7,13) is also a solution: 72=49187^2 = 49 \equiv 18 (mod 31) and 132=1691413^2 = 169 \equiv 14 (mod 31), so 18+14=32118 + 14 = 32 \equiv 1 (mod 31).
  • (30,0)(30,0) demonstrates that 301(mod31)30 \equiv -1 \pmod{31}, hence (1)2+021(-1)^2 + 0^2 \equiv 1 (mod 31).

To see Circle Group, let us work over F31\mathbb{F}_{31}, where the circle C(F31)C(\mathbb{F}_{31}) has 32 points. Suppose we pick the point

Q  =  (13,7).Q \;=\; (13,\,7).

We can combine QQ with the point (30,0)(30,0) via the group operation:
(13,7)(30,0)  =  (133070,    130+730)  =  (390,210)    (18,24)(mod31).(13,\,7) \,\cdot\,(30,\,0) \;=\; \bigl( 13 \cdot 30 - 7 \cdot 0,\;\; 13 \cdot 0 + 7 \cdot 30 \bigr) \;=\; (390,\,210) \;\equiv\; (18,\,24) \pmod{31}.

Indeed, one verifies
182+242    324+576    900    1(mod31),18^2 + 24^2 \;\equiv\; 324 + 576 \;\equiv\; 900 \;\equiv\; 1 \pmod{31},

so (18,24)(18,\,24) also lies on the circle x2+y2=1x^2 + y^2 = 1.
Meanwhile, the inverse of QQ is

Q1  =  (13,7)    (13,24)(mod31),Q^{-1} \;=\; (13,\,-7)\;\equiv\;(13,\,24)\pmod{31},

because

(13,7)(13,24)  =  (1,0).(13,\,7)\,\cdot\,(13,\,24) \;=\; (1,\,0).

Additionally, squaring map can work on the point Q=(13,7)Q=(13,7). Recall that

π(x,y)  =  (x,y)(x,y).\pi(x,y) \;=\; (x,y)\,\cdot\,(x,y).

Hence,
π(Q)  =  (13,7)(13,7)  =  (131377,  137+713)      (27,27)(mod31).\pi(Q) \;=\; (13,7)\,\cdot\,(13,7) \;=\; \bigl(13\cdot 13 - 7\cdot 7,\;13\cdot 7 + 7\cdot 13\bigr) \; \;\equiv\; (27,\,27) \pmod{31}.

3. Subgroups of Circle Group

We have established that C(Fp)C(\mathbb{F}_p) is a cyclic group of size p+1p+1. If p+1p + 1 is a power of two, say p+1=2mp+1 = 2^m, then there is a nested chain of subgroups

G0    G1    G2        Gm  =  C(Fp)G_0 \;\subset\; G_1 \;\subset\; G_2 \;\subset\; \dots \;\subset\; G_m \;=\; C(\mathbb{F}_p)

where each GkG_k has order Gk=2k|G_k| = 2^k.
In other words, G1G_1 is a subgroup of size 2, G2G_2 is size 4, and so on, up to Gm=C(Fp)G_m = C(\mathbb{F}_p) itself, which has size 2m=p+12^m = p+1.

For example, if p=31p = 31 (so p+1=32=25p+1 = 32 = 2^5), we obtain a chain

G0,G1,G2,G3,G4,G5G_0,\,G_1,\,G_2,\,G_3,\,G_4,\,G_5

where G5=C(F31)G_5 = C(\mathbb{F}_{31}) and each GkG_k has size 2k2^k. A summary might look like this:

kk Gk=2k\lvert G_k\rvert = 2^k
G0G_0 1
G1G_1 2
G2G_2 4
G3G_3 8
G4G_4 16
G5G_5 32

Below, we explicitly list each subgroup in C(F31)C(\mathbb{F}_{31}).

Size 1:  [Point(1, 0)]
Size 2:  [Point(1, 0), Point(30, 0)]
Size 4:  [Point(1, 0), Point(0, 30), Point(30, 0), Point(0, 1)]
Size 8:  [Point(1, 0), Point(4, 27), Point(0, 30), Point(27, 27), Point(30, 0), Point(27, 4), Point(0, 1), Point(4, 4)]
Size 16:  [Point(1, 0), Point(7, 13), Point(4, 27), Point(18, 24), Point(0, 30), Point(13, 24), Point(27, 27), Point(24, 13), Point(30, 0), Point(24, 18), Point(27, 4), Point(13, 7), Point(0, 1), Point(18, 7), Point(4, 4), Point(7, 18)]
Size 32:  [Point(1, 0), Point(2, 11), Point(7, 13), Point(26, 10), Point(4, 27), Point(21, 5), Point(18, 24), Point(20, 29), Point(0, 30), Point(11, 29), Point(13, 24), Point(10, 5), Point(27, 27), Point(5, 10), Point(24, 13), Point(29, 11), Point(30, 0), Point(29, 20), Point(24, 18), Point(5, 21), Point(27, 4), Point(10, 26), Point(13, 7), Point(11, 2), Point(0, 1), Point(20, 2), Point(18, 7), Point(21, 26), Point(4, 4), Point(26, 21), Point(7, 18), Point(2, 20)]

4. Python Code 1: Circle Group

We will now review the Circle Group code through the Simple Python implementation. Only the most important functions or parts are described here, and all code is available here.

Here, we define two key classes-FieldElement and CirclePoint—to handle arithmetic in the underlying finite field and group operations on the curve, respectively.

4.1 FieldElement

First, the FieldElement class defines arithmetic operations in the finite field F31\mathbb{F}_{31}, such as addition, multiplication, and inversion modulo 31. This is the foundation for all computations on the circle curve.

# Mersenne 5
MOD = 31

class FieldElement:
    def __init__(self, value):
        """Initialize a field element"""
        self.value = value % MOD

    def __add__(self, other):
        """Add two field elements"""
        return FieldElement((self.value + other.value) % MOD)

    def __mul__(self, other):
        """Multiply two field elements"""
        return FieldElement((self.value * other.value) % MOD)

    def inv(self):
        """Compute the multiplicative inverse"""
        return FieldElement(pow(self.value, MOD - 2, MOD))

    def square(self):
        """Compute the square"""
        return self * self

4.2 CirclePoint

The CirclePoint class represents points on the circle curve x2+y2=1x^2+y^2=1. The add method implements the group operation, while double applies the squaring map.

class CirclePoint:
    def __init__(self, x, y):
        """Initialize a point on the circle x^2 + y^2 = 1"""
        if (x.square() + y.square()).value != 1:
            raise ValueError("Point does not lie on the circle")
        self.x = x
        self.y = y

    def add(self, other):
       """Perform group operation: (x1,y1)・(x2,y2) = (x1*x2 - y1*y2, x1*y2 + x2*y1)."""
        nx = self.x * other.x - self.y * other.y
        ny = self.x * other.y + other.x * self.y
        return CirclePoint(nx, ny)

    def double(self):
        """Apply squaring map: π(x,y) = (2*x^2 - 1, 2*x*y), since x^2 + y^2 = 1."""
        xx = self.x.square().double() - FieldElement.one()
        yy = self.x.double() * self.y
        return CirclePoint(xx, yy)

    @classmethod
    def identity(cls):
        """Return the identity element (1, 0) of the circle group."""
        return cls(FieldElement.one(), FieldElement.zero())

Then you can simulate a Circle Group operation example as described in section 2.4.

For instance, adding CirclePoint (13,7)(13, 7) and CirclePoint (30,0)(30, 0) results in Point (18,24)(18, 24).

p1 = CirclePoint(FieldElement(13), FieldElement(7))
p2 = CirclePoint(FieldElement(30), FieldElement(0))

# group operation
p3 = p1.add(p2)
print(f"p1・p2 = {p3}")
p1・p2 = Point(18, 24)

Doubling p1=(13,7)p1=(13,7) yields (27,27)(27, 27)

# squaring map
p1_double = p1.double()
print(f"π(p1) = {p1_double}")
π(p1) = Point(27, 27)

The inverse of (13,7)(13, 7) is (13,24)(13, 24), and adding them gives the identity (1,0)(1, 0)

# Inverse
p1_inv = p1.inverse()
print(f"p1's inverse = {p1_inv}")
p1_plus_inv = p1.add(p1_inv)
print(f"p1・p1_inv = {p1_plus_inv}")
p1's inverse = Point(13, 24)
p1・p1_inv = Point(1, 0)

4.3 Circle Group

The function generate_subgroup generates a subgroup GkG_k of order 2k2^k. It obtains the appropriate generator from the get_nth_generator function and repeats the group operation add to construct the subgroup.

def generate_subgroup(k: int) -> list[CirclePoint]:
    """Generate a subgroup of size 2^k using the generator."""
    g_k = get_nth_generator(k)
    p = CirclePoint.identity()
    return [ (p := p.add(g_k)) if i else p
             for i in range(1 << k) ]

For example, you can get size 8 subgroup.

G3 = generate_subgroup(3)
Size 8:  [Point(1, 0), Point(4, 27), Point(0, 30), Point(27, 27), Point(30, 0), Point(27, 4), Point(0, 1), Point(4, 4)]

5. Coset, Twin-Cosets and Standard Position Cosets

Twin-Cosets and Standard Position Cosets are the domains in Circle STARKs. Let’s briefly review how domains are used in traditional STARKS before understanding the mathematical properties of twin-cosets and standard position cosets. As discussed in Section 0, traditional STARKS typically utilized multiplicative (sub)groups represented as Fp\mathbb{F}_p^* as their domains. These domains served two primary purposes.

Firstly, they acted as the evaluation domain when constructing polynomials from the computation trace via IFFT, and we have to conduct the Low Degree Extension (LDE) with the extended domain via FFT and also we construct constraint polynomials and evaluate them on LDE.

Secondly, in Low Degree Testing, particularly with FRI commitments, polynomials must be evaluated via FFT over domains that are iteratively halved during the recursive FRI folding steps.

In addition, during the FRI folding steps, as the degree of the polynomial was halved, the size of the domain also needed to be halved. Halving the domain size is central to both FRI and FFT. Keep this in mind as we proceed.

Because while the circle group itself already offers high 2-adicity, the construction of Twin-Cosets or Standard Position Cosets ensures that, during recursive squaring steps in the FFT, the evaluation domains (i.e., the Twin-Cosets or Standard Position Coset) can also be halved in size while preserving their coset structure. This is essential for enabling FFT-style operations over these domains in each level of the recursion.

5.1 Coset

Let us recall the definition of a coset in group theory. Suppose HH is a subgroup of a group G\mathcal{G}, and let QGQ \in \mathcal{G}. Then the left coset of HH by QQ is the set

QH  =  {Qh    hH}.Q \,\cdot\, H \;=\; \{\,Q \cdot h \;\mid\; h \in H\}.

If QHQ \in H, then QH=HQ \cdot H = H. Otherwise, in case of QHQ \notin H , QHQ \cdot H is a disjoint set from HH, but still has the same size as HH. This holds because if QHQ \in H, HH’s closure under the group operation ensures QH=HQ \cdot H = H, while if QHQ \notin H, no element of QHQ \cdot H can be in HH without implying QHQ \in H, and the bijection hQhh \mapsto Q \cdot h preserves the size.

In our particular setting, G=C(Fp)\mathcal{G} = C(\mathbb{F}_p), which is cyclic of size p+1p+1. From the previous section, we know there is a chain of subgroups G0G1GmG_0 \subset G_1 \subset \dots \subset G_m with Gk=2k|G_k| = 2^k.

Hence, for instance, if we fix Gn1G_{n-1} of order 2n12^{n-1}, then for any point QQ on the circle, the coset

QGn1  =  {Qg    gGn1}Q \,\cdot\, G_{n-1} \;=\; \bigl\{Q\cdot g \;\mid\; g\in G_{n-1}\bigr\}

is called a coset of Gn1G_{n-1}.
That set will have the same cardinality 2n12^{n-1}, and is disjoint from Gn1G_{n-1} itself unless QQ already belongs to Gn1G_{n-1}.

5.1.1 Coset Example

Let’s illustrate this example quickly. In the case p=31p=31, recall that p+1=32=25p+1=32=2^5, so there is a chain of subgroups G1,G2,,G5G_1, G_2, \dots, G_5 where G1=2|G_1|=2. Concretely,

G1  =  {(1,0),(30,0)}.G_1 \;=\; \{\,(1,0),\,(30,0)\}.

Now take a point QQ not in G1G_1. For instance,

Q  =  (2,11).Q \;=\; (2,\,11).

Since QQ is not in G1G_1, the set

QG1  =  {(2,11)(1,0),(2,11)(30,0)}Q \,\cdot\, G_1 \;=\; \{\,(2,11)\cdot(1,0),\,(2,11)\cdot(30,0)\}

will be a distinct coset of G1G_1, disjoint from G1G_1 itself.

  • (2,11)(1,0)  =  (21110,    20+111)  =  (2,11),(2,11)\cdot(1,0) \;=\; (\,2\cdot1 - 11\cdot0,\;\;2\cdot0 + 11\cdot1) \;=\; (2,\,11),

  • (2,11)(30,0)  =  (230110,    20+3011)    (29,20)  (mod31).(2,11)\cdot(30,0) \;=\; (\,2\cdot30 - 11\cdot0,\;\;2\cdot0 + 30\cdot11) \;\equiv\; (29,\,20) \;\pmod{31}.

Hence,

QG1  =  {(2,11),(29,20)},Q \cdot G_1 \;=\; \{(2,\,11),\,(29,\,20)\},

which indeed has size 2. This is the coset of G1G_1 corresponding to Q=(2,11)Q=(2,11), clearly different from the subgroup G1G_1 itself.

5.2 Twin-Cosets

We now define a twin-coset by taking the union of two cosets: QGn1Q \cdot G_{n-1} and its inverse coset Q1Gn1Q^{-1} \cdot G_{n-1}. Concretely, let

D  =  (QGn1)    (Q1Gn1).D \;=\; \bigl(Q \,\cdot\, G_{n-1}\bigr) \;\cup\; \bigl(Q^{-1} \,\cdot\, G_{n-1}\bigr).

We say that DD is a twin-coset of size 2n2^n if the following conditions hold:

  1. Disjointness
    The two cosets QGn1Q \cdot G_{n-1} and Q1Gn1Q^{-1} \cdot G_{n-1} are disjoint.
    In practice, this disjointness is equivalent to ensuring Q2Gn1Q^2 \notin G_{n-1}. This equivalence arises from the properties of cosets. If Q2Gn1Q^2 \in G_{n-1}, then Q=Q1Q2Q = Q^{-1} \cdot Q^2 would be in both QGn1Q \cdot G_{n-1} and Q1Gn1Q^{-1} \cdot G_{n-1} (overlap). Conversely, if the cosets don’t overlap, then QQ cannot be written as Q1gQ^{-1} \cdot g (for any gGn1g \in G_{n-1}), which implies Q2Gn1Q^2 \notin G_{n-1}.

  2. No fixed points under the involution JJ
    A point PP is called a fixed point of a map ff if f(P)=Pf(P) = P. In our case, we consider the involution J(x,y)  =  (x,  y).J(x,y) \;=\; \bigl(x,\;-y\bigr).
    If DD contained any point where y=0y=0, then J(x,0)=(x,0)J(x,0) = (x,0) would remain unchanged. It is a fixed point. We want to avoid having such points in DD, because by definition a twin-coset excludes any fixed points of JJ.

Under these conditions, each coset QGn1Q \cdot G_{n-1} and Q1Gn1Q^{-1} \cdot G_{n-1} has 2n12^{n-1} distinct points, giving a total of 2n2^n points in DD. Intuitively, we merge a coset with its inverse coset, forming a domain of 2n2^n elements.

5.2.1 Twin-Coset Example

Continuing from the coset example in Section 5.1.1 (where we picked Q=(2,11)Q=(2,11) not in G1G_1), let us now form a twin-coset of size 2n=22=42^n = 2^2 = 4. We already saw:

QG1  =  {(2,11),(29,20)}.Q \cdot G_1 \;=\; \{(2,\,11),\,(29,\,20)\}.

Next, compute Q1=(2,20)Q^{-1}=(2,\,20) and form the coset Q1G1Q^{-1}\cdot G_1:

Q1G1  =  {(2,20)(1,0),(2,20)(30,0)}Q^{-1} \,\cdot\, G_1 \;=\; \{\,(2,20)\cdot(1,0),\,(2,20)\cdot(30,0)\}
  • (2,20)(1,0)  =  (21    200,    20  +  120)  =  (2,20).(2,20)\cdot(1,0)\;=\;\bigl(2\cdot1 \;-\;20\cdot0,\;\;2\cdot0 \;+\;1\cdot20\bigr)\;=\;(2,\,20).

  • (2,20)(30,0)  =  (230    200,    20  +  3020)  =  (60,600)    (29,11)  (mod31).(2,20)\cdot(30,0)\;=\;\bigl(2\cdot30 \;-\;20\cdot0,\;\;2\cdot0 \;+\;30\cdot20\bigr)\;=\;(60,\,600)\;\equiv\;(29,\,11)\;\pmod{31}.

Hence,

Q1G1  =  {(2,20),(29,11)}.Q^{-1}\cdot G_1 \;=\; \{(2,\,20),\,(29,\,11)\}.

Taking the union, the twin-coset is:

D  =  (QG1)    (Q1G1)  =  {(2,11),(29,20)}    {(2,20),(29,11)}.D \;=\; \bigl(Q \cdot G_1\bigr) \;\cup\; \bigl(Q^{-1}\cdot G_1\bigr) \;=\; \{(2,\,11),\,(29,\,20)\} \;\cup\; \{(2,\,20),\,(29,\,11)\}.

This domain DD has size 4 and satisfies the twin-coset conditions: the two cosets are disjoint (since Q2G1Q^2\notin G_1), and DD contains no point with y=0y=0, so there are no fixed points under the involution J(x,y)=(x,y)J(x,y)=(x,-y). Thus, DD is indeed a twin-coset of size 22=42^2=4.

5.3 Standard Position Cosets

A standard position coset is a special kind of twin-coset DD of size 2n2^n which also coincides with a single coset of the subgroup GnG_n:

D  =  QGn1    Q1Gn1  =  QGn.D \;=\; Q \cdot G_{n-1} \;\cup\; Q^{-1}\cdot G_{n-1} \;=\; Q \cdot G_n.

Here, recall that GnG_n is the unique subgroup of C(Fp)C(\mathbb{F}_p) of order 2n2^n, as introduced earlier in Section 3. We have a chain of subgroups:

G0G1GnC(Fp),G_0 \subset G_1 \subset \dots \subset G_n \subset \dots \subset C(\mathbb{F}_p),

and each GkG_k is of size 2k2^k. So, GnG_n contains Gn1G_{n-1}, and we will relate the cosets of these subgroups using a point QQ of order 2n+12^{n+1}.

Let’s unpack this step by step:

First, since Gn1GnG_{n-1} \subset G_n, we can view GnG_n as consisting of two disjoint cosets of Gn1G_{n-1}, namely:

Gn=Gn1RGn1,for some RGn1.G_n = G_{n-1} \cup R \cdot G_{n-1},\quad \text{for some } R \notin G_{n-1}.

If we now multiply this by a fixed point QQ of order 2n+12^{n+1}, we obtain:

QGn=QGn1QRGn1.Q \cdot G_n = Q \cdot G_{n-1} \cup Q \cdot R \cdot G_{n-1}.

It turns out that QR=Q1Q \cdot R = Q^{-1}, so:

QGn=QGn1Q1Gn1.Q \cdot G_n = Q \cdot G_{n-1} \cup Q^{-1} \cdot G_{n-1}.

Therefore, the coset QGnQ \cdot G_n can be written as the union of two disjoint cosets: one by QQ, the other by Q1Q^{-1}, each over the smaller subgroup Gn1G_{n-1}. This is exactly the definition of a twin-coset.

But not all twin-cosets arise in this way. To ensure this construction works properly, we require:

  1. Disjointness: Q2Gn1Q^2 \notin G_{n-1}. Otherwise, the two cosets would overlap.
  2. No fixed points under involution: The resulting set DD must avoid points with y=0y = 0, so the involution J(x,y)=(x,y)J(x, y) = (x, -y) has no fixed points in DD.
  3. Correct order: QQ must have order 2n+12^{n+1} to guarantee that QGnQ \cdot G_n spans exactly 2n2^n distinct elements.

When these are satisfied, the coset D=QGnD = Q \cdot G_n gives a standard position coset: a domain of size 2n2^n that is simultaneously a twin-coset and a single coset of a larger subgroup.

5.4 Hand-Computed Example: Twin-Cosets and Standard Position Cosets at p=31p=31

Below, we illustrate how the notions of twin-cosets and standard position cosets apply when p=31.p=31. We have p+1=32=25p+1=32=2^5, so subgroups G1,G2,G3,G4,G5G_1,\,G_2,\,G_3,\,G_4,\,G_5 exist, where G3=8|G_3|=8 and G2=4|G_2|=4. We will show the standard position coset for G3G_3 that is D=QG2    Q1G2=QG3D=Q \cdot G_2 \;\cup\; Q^{-1} \cdot G_2=Q \cdot G_3.

5.4.1 Twin-Coset Construction

  • Setup
    Let n=3n=3. Then G2G_2 has size 4. Pick a point QQ of order 16 (so QG3Q\notin G_3, but QG4Q \in G_4). Concretely, one may choose Q=(13,7)Q = (13,\,7)

  • Forming the Twin-Coset

    D=(QG2)    (Q1G2).D= \bigl(Q \cdot G_2\bigr) \;\cup\; \bigl(Q^{-1}\cdot G_2\bigr).

    Since G2G_2 has the four points (1,0)(1,0), (0,30)(0,30), (30,0)(30,0), and (0,1)(0,1), each point is multiplied by Q=(13,7)Q = (13,\,7) or Q1=(13,24)Q^{-1}=(13,24).

    1. Q(1,0)=(13,7)Q \cdot (1,0)=(13,7),
    2. Q(0,30)=(7,18)Q \cdot (0,30)=(7,18),
    3. Q(30,0)=(18,24)Q \cdot (30,0)=(18,24),
    4. Q(0,1)=(24,13)Q \cdot (0,1)=(24,13),
    5. Q1(1,0)=(13,24)Q^{-1}\cdot(1,0)=(13,24),
    6. Q1(0,30)=(24,18)Q^{-1}\cdot(0,30)=(24,18),
    7. Q1(30,0)=(18,7)Q^{-1}\cdot(30,0)=(18,7),
    8. Q1(0,1)=(7,13)Q^{-1}\cdot(0,1)=(7,13).
D=(QG2)    (Q1G2)={(13,7),(7,18),(18,24),(24,13),(13,24),(24,18),(18,7),(7,13)}D= \bigl(Q \cdot G_2\bigr) \;\cup\; \bigl(Q^{-1}\cdot G_2\bigr)= \{(13,7),(7,18),(18,24),(24,13),(13,24),(24,18),(18,7),(7,13)\}

Combining these gives 8 distinct points. No element of DD is fixed by JJ, so DD is a twin-coset of size 23=82^3=8. Combining these gives 8 distinct points. No element of DD is fixed by JJ, so DD is a twin-coset of size 23=82^3 = 8.

In the diagram below, 🔴 red points represent QG2Q \cdot G_2 and 🔵 blue points represent Q1G2Q^{-1} \cdot G_2. Together, they form the 8-point twin-coset DD.

Twin coset

5.4.2 Checking Standard Position coset

This same DD also equals QG3Q\cdot G_3 for some subgroup G3G_3 of size 8, meaning DD is a standard position coset.

G3G_3 is the subgroup {(1,0),(4,27),(0,30),(27,27),(30,0),(27,4),(0,1),(4,4)}\{(1,0),(4,27),(0,30),(27,27),(30,0),(27,4),(0,1),(4,4)\}, then multiplying each element by Q=(13,7)Q=(13,7) yields exactly the 8 points in DD.

  1. Q(1,0)=(13,7)Q \cdot (1,\,0) = (13,\,7)
  2. Q(4,27)=(18,7)Q \cdot (4,\,27) = (18,\,7)
  3. Q(0,30)=(7,18)Q \cdot (0,\,30) = (7,\,18)
  4. Q(27,27)=(7,13)Q \cdot (27,\,27) = (7,\,13)
  5. Q(30,0)=(18,24)Q \cdot (30,\,0) = (18,\,24)
  6. Q(27,4)=(13,24)Q \cdot (27,\,4) = (13,\,24)
  7. Q(0,1)=(24,13)Q \cdot (0,\,1) = (24,\,13)
  8. Q(4,4)=(24,18)Q \cdot (4,\,4) = (24,\,18)
QG3  =  {(13,7),(18,7),(7,18),(7,13),(18,24),(13,24),(24,13),(24,18)}.Q \cdot G_3 \;=\; \{ (13,7),\,(18,7),\,(7,18),\,(7,13), (18,24),\,(13,24),\,(24,13),\,(24,18) \}.

Hence

D=QG3=QG2    Q1G2.D= Q\cdot G_3 = Q\cdot G_2 \;\cup\; Q^{-1}\cdot G_2.

Because DD is both a twin-coset of G2G_2 and a single coset of G3G_3, we call it a standard position coset of size 8.

In the diagram below, 🟢 green points represent the subgroup G3G_3, 🔴 red and 🔵 blue points represent the two disjoint cosets QG2Q \cdot G_2 and Q1G2Q^{-1} \cdot G_2, respectively. Their union forms the twin-coset DD, and the entire 8-point set constitutes the standard position coset QG3Q \cdot G_3.

standard position coset

Thus, whenever QQ has order 2n+12^{n+1}, we can build a standard position coset of size 2n2^n. This structure is important in Circle STARK, as it provides a neat domain of 2n2^n points even if p1p-1 is not sufficiently two-adic.

6. Python Code 2: Circle Domain

We will now review the Circle Domain code through the Simple Python implementation. The domain construction (such as twin-coset and standard position coset) is also implemented in the same Python notebook. You can run and inspect how these sets are built step-by-step.

6.1 Twin-Coset

This function generates a domain by applying group operations add to QQ and its inverse Q1Q^{-1} over the subgroup, ensuring symmetry and size 2n2^n.

def generate_twin_coset(Q, G_n_minus_1):
    """Generate twin-coset: D = Q・G_(n-1) ∪ Q^{-1}・G_(n-1)."""
    Q_inv = Q.inverse()
    coset_Q = [Q.add(g) for g in G_n_minus_1]
    coset_Q_inv = [Q_inv.add(g) for g in G_n_minus_1]
    return coset_Q + coset_Q_inv

G2 = generate_subgroup(2)
Q = CirclePoint(FieldElement(13), FieldElement(7))
twin_cosets = generate_twin_coset(Q, G2)
print(twin_cosets)
[Point(13, 7), Point(7, 18), Point(18, 24), Point(24, 13), Point(13, 24), Point(24, 18), Point(18, 7), Point(7, 13)]

6.2 Standard Position Coset

This function generates the standard position coset by applying group operation add for QQ and subgroup.

def generate_standard_position_coset(Q, G_n):
    """Generate standard position coset D = Q・G_n."""
    return [Q.add(g) for g in G_n]

G3 = generate_subgroup(3)
Q = CirclePoint(FieldElement(13), FieldElement(7))
D = generate_standard_position_coset(Q, G3)
[Point(13, 7), Point(18, 7), Point(7, 18), Point(7, 13), Point(18, 24), Point(13, 24), Point(24, 13), Point(24, 18)]

This kind of code, you can check the equality of standard position coset D  =  QGn1    Q1Gn1  =  QGn.D \;=\;Q \,\cdot\, G_{n-1}\;\cup\;Q^{-1} \,\cdot\, G_{n-1}\;=\;Q \,\cdot\, G_n.

D_std = generate_standard_position_coset(Q, G3)
D_twin = generate_twin_coset(Q, G2)
print("Q・G3:", D_std)
print("(Q・G2) ∪ (Q^-1・G2):", D_twin)
if set(D_std) == set(D_twin):
    print("Equal: standard position coset Q·G3 = Q·G2 ∪ Q^-1·G2")
else:
    print("Not Equal")

7. Decomposition of a Larger Standard Position Coset into Smaller Twin-Cosets

In many steps of the Circle FFT and FRI, we need to manage evaluation domains of different sizes, all of which lie on the circle curve. One key method to achieve this is Lemma 2 from the Circle STARKs paper, which ensures that a standard position coset of size 2m2^m can be broken down into smaller twin-cosets of size 2n2^n for any nmn \le m.

7.1 Statement of Lemma 2

Let DC(Fp)D \subset C(\mathbb{F}_p) be a standard position coset of size 2m2^m. Then for any nmn \le m, one can decompose DD into twin-cosets of size 2n2^n. Concretely, if

D  =  QGmwhereGm=2m,D \;=\; Q \cdot G_m \quad \text{where}\quad |G_m| = 2^m,

then

D=k=0(2m2n)1(Q4k+1Gn1Q(4k+1)Gn1)D = \bigcup_{k=0}^{\left(\frac{2^m}{2^n}\right)-1} \Bigl( Q^{4k+1} \cdot G_{n-1} \cup Q^{-(4k+1)} \cdot G_{n-1} \Bigr)

In simpler terms, if DD is a standard position coset of size 2m2^m, we can slice it into smaller twin-cosets (each of size 2n2^n) using powers of QQ. This partitioning allows one to make precisely the parts of DD that are needed in a given step of the Circle FFT protocol.

7.2 Hand-Computed Example: Splitting a Size-88 standard position coset into 2 Twin-Cosets of Size 44

Suppose p=31p = 31, so p+1=32=25p+1 = 32 = 2^5. We have subgroups G1,G2,G3,G_1, G_2, G_3, \dots, where G3=8\lvert G_3\rvert=8.

  • Our standard position coset
    Let D=QG3D = Q \cdot G_3 with D=8\lvert D\rvert=8. Here, QQ has order 1616, meaning QG3Q\notin G_3 but QG3Q \cdot G_3 forms a coset of size 88.
D=(QG2)    (Q1G2)=QG3={(13,7),(7,18),(18,24),(24,13),(13,24),(24,18),(18,7),(7,13)}D= \bigl(Q \cdot G_2\bigr) \;\cup\; \bigl(Q^{-1}\cdot G_2\bigr)= Q\cdot G_3 = \{(13,7),(7,18),(18,24),(24,13),(13,24),(24,18),(18,7),(7,13)\}
  • Goal
    Break DD into twin-cosets of size 2n=42^n=4. Let n=2n=2, so G2=4\lvert G_2\rvert=4 and G1=2\lvert G_1\rvert=2.
    Then Lemma 2 suggests
    D=k=0(8/4)1(Q4k+1G1    Q(4k+1)G1).D= \bigcup_{k=0}^{(8/4)-1} \Bigl( Q^{\,4k+1}\cdot G_{1} \;\cup\; Q^{-\,\bigl(4k+1\bigr)}\cdot G_{1} \Bigr).

    Because 8/4=28/4=2, we have k=0k=0 and k=1k=1.

7.2.1 Case k=0k=0

  • Q40+1=Q1=QQ^{4\cdot0+1}=Q^1=Q.
  • G1={(1,0),(30,0)}G_1=\{(1,0), (30,0)\}.

Compute:

  1. Q(1,0)=(13,7)Q\cdot(1,0) = (13,7)
  2. Q(30,0)=(18,24)Q\cdot(30,0) = (18,24)
  3. Q1(1,0)=(13,24)Q^{-1}\cdot(1,0) = (13,24)
  4. Q1(30,0)=(18,7)Q^{-1}\cdot(30,0) = (18,7)

Thus, the first twin-coset has 4 distinct points.

7.2.2 Case k=1k=1

  • Q41+1=Q5Q^{4\cdot1+1} = Q^5.
    That is Q5=(24,13)Q^5 = (24,13).
  • Likewise, Q5=(24,13)(24,18)Q^{-5} = (24, -13)\equiv (24,18).

Again, multiply each by (1,0)(1,0) and (30,0)(30,0):

  1. Q5(1,0)=(24,13)Q^5\cdot(1,0) = (24,13)
  2. Q5(30,0)=(7,18)Q^5\cdot(30,0) = (7,18)
  3. Q5(1,0)=(24,18)Q^{-5}\cdot(1,0) = (24,18)
  4. Q5(30,0)=(7,13)Q^{-5}\cdot(30,0) = (7,13)

This yields another 4 points, forming the second twin-coset of size 4.

7.2.3 Union of the Two Twin-Cosets

Taking the union of both 4-point sets (for k=0k=0 and k=1k=1) recovers the entire 8-element coset DD. Hence:

D=(Q1G1    Q1G1)        (Q5G1    Q5G1).D=\Bigl( Q^1 \cdot G_1 \;\cup\; Q^{-1} \cdot G_1 \Bigr) \;\;\cup\;\; \Bigl( Q^5 \cdot G_1 \;\cup\; Q^{-5} \cdot G_1 \Bigr).

This exactly matches the statement of Lemma 2, showing how an 8-point standard position coset can be decomposed into two twin-cosets each of size 4.

7.3 Hand-Computed Example: Splitting a Size-88 standard position coset into 4 Twin-Cosets of Size 22

And we can also divide this size-8 standard position coset into multiple smaller twin-cosets.

Let n=1n=1, so G1=2\lvert G_1\rvert=2. Lemma 2 gives:

D=k=0(8/2)1(Q4k+1G0    Q(4k+1)G0).D = \bigcup_{k=0}^{(8/2)-1} \Bigl( Q^{4k+1}\cdot G_0 \;\cup\; Q^{-\,(4k+1)}\cdot G_0 \Bigr).

Since G0={(1,0)}G_0 = \{(1,0)\}, each twin-coset has 2 points:

  • k=0k=0: {Q1G0=Q,Q1G0=Q1}\{Q^1 \cdot G_0 = Q, Q^{-1} \cdot G_0 = Q^{-1}\} ={(13,7),(13,24)}=\{(13, 7), (13, 24)\}
  • k=1k=1: {Q5G0=Q5,Q5G0=Q5}\{Q^5 \cdot G_0 = Q^5, Q^{-5} \cdot G_0 = Q^{-5}\}$
    ={(24, 13), (24, 18)}$
  • k=2k=2: ${Q^9 \cdot G_0 = Q^9, Q^{-9} \cdot G_0 = Q^{-9}}
    $={(18,24),(18,7)}=\{(18, 24), (18, 7)\}
  • k=3k=3: {Q13G0=Q13,Q13G0=Q13}\{Q^{13} \cdot G_0 = Q^{13}, Q^{-13} \cdot G_0 = Q^{-13}\}$
    ={(7, 18), (7, 13)}$
    D={(13,7),(13,24)}{(24,13),(24,18)}{(18,24),(18,7)}{(7,18),(7,13)}D = \{(13,7),(13,24)\} \cup \{(24,13),(24,18)\} \cup \{(18,24),(18,7)\} \cup \{(7,18),(7,13)\}
    This decomposes DD into four twin-cosets of size 22.

8. Squaring Map Halves a Twin-Coset

While Lemma 2 focuses on splitting large cosets into smaller twin-cosets, Lemma 3 exploits a squaring map to halve the size of a twin-coset. This process is used for the domain halving steps in Circle FFT, where we iteratively reduce the domain size.

8.1 Statement of Lemma 3

This lemma appears in the Circle STARKs paper, where it is used to formalize the recursive domain halving enabled by the squaring map.
Suppose DD is a twin-coset of size 2n2^n (with n2n \ge 2). Then applying the squaring map π(x,y)=(x2y2,2xy)\pi(x,y) = (x^2 - y^2,\,2xy) transforms DD into another twin-coset π(D)\pi(D) of size 2n12^{n-1}. Moreover, if DD was a standard position coset, then π(D)\pi(D) also remains a standard position coset.
Intuitively, π\pi is a group endomorphism, and it maps a subgroup Gn1G_{n-1} into Gn2G_{n-2}. Hence, a twin-coset of size 2n2^n naturally yields another twin-coset of size 2n12^{n-1}.

8.1.1 Abstract View of Lemma 3’s Proof

Let DD be a twin-coset of size 2n2^n, which in an abstract notation means

D=QGn1    Q1Gn1,D = Q \cdot G_{n-1} \;\cup\; Q^{-1}\cdot G_{n-1},

where Gn1=2n1|G_{n-1}| = 2^{n-1}.
In particular, π\pi maps the subgroup Gn1G_{n-1} onto Gn2G_{n-2} due to a group endomorphism.
π(D)  =  π ⁣(QGn1Q1Gn1)=(π(Q)Gn2)    (π(Q)1Gn2).\pi(D) \;=\; \pi\!\Bigl(Q \cdot G_{n-1} \cup Q^{-1}\cdot G_{n-1}\Bigr)= \bigl(\pi(Q) \cdot G_{n-2}\bigr) \;\cup\; \bigl(\pi(Q)^{-1} \cdot G_{n-2}\bigr).

This is exactly the definition of a twin-coset in the subgroup Gn2G_{n-2}, which has size 2n22^{n-2}. Accordingly, π(D)\pi(D) is itself a twin-coset of size 2n12^{n-1}.
If DD was originally a standard position coset, then applying π\pi preserves that standard position property, because π(Gn)=Gn1\pi(G_n) = G_{n-1}. Thus π(D)\pi(D) is again a standard position coset, but now of size 2n12^{n-1}.

8.2 Hand-Computed Example (Halving a Size-88 Twin-Coset to Size 44)

Assume p=31p=31 and let DD be a twin-coset of size 23=82^3=8. Concretely,

D=QG2    Q1G2withQ=(13,7).D = Q\cdot G_2 \;\cup\; Q^{-1}\cdot G_2 \quad\text{with}\quad Q=(13,7).

Since G2=4\lvert G_2\rvert=4, we have D=8\lvert D\rvert=8. We check how π\pi affects DD:

8.2.1 Computing π(Q)\pi(Q) and π(Q1)\pi(Q^{-1})

π(Q)=(13272,  2137)=(27,27),\pi(Q)= \bigl(13^2 - 7^2,\;2\cdot13\cdot7\bigr)= (27,\,27),
π(Q1)=(27,4).\pi(Q^{-1})= (27,\,4).

8.2.2 Applying π\pi to DD

The subgroup G1G_1 has size 2, say G1={(1,0),(30,0)}G_1 = \{(1,0), (30,0)\}. By Lemma 3,

π(D)=(π(Q)G1)(π(Q)1G1).\pi(D) = \bigl(\pi(Q)\cdot G_1\bigr) \cup \bigl(\pi(Q)^{-1}\cdot G_1\bigr).

$$
\begin{align*}
\pi(Q)\cdot(1,0) &= (27,27)\
\pi(Q)\cdot(30,0) &= (4,4)\
\pi(Q^{-1})\cdot(1,0) &= (27,4)\
\pi(Q^{-1})\cdot(30,0) &= (4,27)

\end{align*}

{(27,27), (4,4)}
\cup
{(27,4), (4,27)}$$
which is a twin-coset of size 4.
Thus, π\pi halves DD from 8 elements to 4, exactly as Lemma 3 states. Repeated application of π\pi continues to shrink the domain in powers of 2, playing a critical role in the recursive structure of the Circle FFT.
In the diagram below:

  • The left circle shows the original twin-coset D=QG2Q1G2D = Q \cdot G_2 \cup Q^{-1} \cdot G_2, where 🔴 red = QG2Q \cdot G_2 and 🔵 blue = Q1G2Q^{-1} \cdot G_2.
  • The right circle shows the halved twin-coset π(D)\pi(D), where 🔵 blue = π(Q)G1\pi(Q) \cdot G_1 and 🔴 red = π(Q1)G1\pi(Q^{-1}) \cdot G_1.
  • ⚫ Black points mark the subgroup G1={(1,0),(30,0)}G_1 = \{(1, 0), (30, 0)\}.
    This visual illustrates how π\pi maps the original domain of size 23=82^3 = 8 into a new twin-coset of size 22=42^2 = 4 via group endomorphism.
    Twin coset size 8

9. What We Built in Part 1

In this article, we focused on how the circle curve x2+y2=1x^2 + y^2 = 1 over Fp\mathbb{F}_p can be turned into a cyclic group of size p+1p + 1, and we discussed twin-cosets and standard position cosets with concrete examples. We also showed two key techniques: one for splitting a larger standard position coset into smaller twin-cosets, and another for halving the size of a twin-coset by applying the squaring map. Together, these techniques let us reshape domains of size 2n2^n at will.
In the next part, we will dive deeper into the Circle FFT itself, showing how these 2n2^n-sized domains—together with the ability to split or halve them—enable fast polynomial evaluation and interpolation, even when p1p - 1 is not sufficiently two-adic.

References

Appendix

Below, we introduce the Projective vs Affine view of the circle curve, and then explain why Circle STARKs specifically chooses primes p3(mod4)p \equiv 3 \pmod{4} to avoid projective complications like points at infinity.

A.1 Projective and Affine

In an affine plane over Fp\mathbb{F}_p, a point is simply written as (x,y)(x,y) with x,yFpx,y \in \mathbb{F}_p. By contrast, in the projective plane P2(Fp)\mathbb{P}^2(\mathbb{F}_p), each point is denoted by (X:Y:Z)(X : Y : Z), but all nonzero scalar multiples represent the same location. Formally,

(X:Y:Z)    (λX:λY:λZ)(λ0).(X : Y : Z) \;\equiv\; (\lambda X : \lambda Y : \lambda Z)\quad (\lambda \neq 0).

  • When Z0Z \neq 0, we can re-scale by dividing each coordinate by ZZ, giving an affine point (X/Z,  Y/Z)\bigl(X/Z,\;Y/Z\bigr).
  • When Z=0Z=0, we get a point at infinity, which has no affine counterpart.

A.2 Why Avoid Points at Infinity?

To see how points at infinity arise, rewrite x2+y2=1x^2 + y^2 = 1 in projective form:

X2+Y2  =  Z2.X^2 + Y^2 \;=\; Z^2.

Here, if Z0Z \neq 0, we regard x=X/Zx = X/Z and y=Y/Zy = Y/Z, so x2+y2=1x^2 + y^2 = 1 becomes X2+Y2=Z2X^2 + Y^2 = Z^2.
A point at infinity is any solution with Z=0Z=0. Whether such solutions exist depends on whether 1-1 is a square in Fp\mathbb{F}_p:

  • For example, if p1(mod4)p \equiv 1 \pmod{4}, then 1-1 is a square in Fp\mathbb{F}_p.
    Concretely, there is some aa with a21(modp)a^2 \equiv -1 \pmod{p}, implying X2+Y20(modp)X^2 + Y^2 \equiv 0 \pmod{p} can have nonzero (X,Y)(X,Y) at Z=0Z=0.
    This yields extra infinite points on the circle, complicating group operations and implementation.
    Example: p=5p=5
    • Here, 14-1 \equiv 4 in F5\mathbb{F}_5, and 44 is indeed (22)(2^2).
    • So X2+Y20(mod5)X^2 + Y^2 \equiv 0 \pmod{5} admits nonzero (X,Y)(X,Y) at Z=0Z=0, giving two points at infinity.
    • The affine equation x2+y2=1x^2 + y^2=1 has exactly four affine solutions:
      {(0,1),(0,4),(1,0),(4,0)}.\{(0,1),\,(0,4),\,(1,0),\,(4,0)\}.
  • And the projective points at infinity are
    {(2:1:0),(3:1:0)}\{(2:1:0), (3:1:0)\}, there are 66 solutions in total — matching p+1=6p+1=6 (affine 44 + infinite 22).
  • If p3(mod4)p \equiv 3 \pmod{4}, then 1-1 is not a square in Fp\mathbb{F}_p.
    Consequently, X2+Y20(modp)X^2 + Y^2 \equiv 0 \pmod{p} has no nonzero solutions at Z=0Z=0. We get no points at infinity, and all solutions remain affine.
    Example: p=3p=3
    • Since 12(mod3)-1 \equiv 2 \pmod{3} is not a square (we have 1211^2 \equiv 1 and 2212^2 \equiv 1), no (X,Y)(X,Y) satisfy X2+Y20X^2+Y^2\equiv0 at Z=0Z=0.
    • The equation x2+y2=1x^2 + y^2=1 then has only affine solutions. Checking x,y{0,1,2}x,y \in \{0,1,2\} yields:
      {(0,1),(0,2),(1,0),(2,0)},\{(0,1),\,(0,2),\,(1,0),\,(2,0)\},
      for a total of 44 solutions, matching p+1=4p+1=4.

In Circle STARK, we specifically choose p3(mod4)p \equiv 3 \pmod{4} so that 1-1 is not a square in Fp\mathbb{F}_p. This ensures our circle x2+y2=1x^2 + y^2=1 has no infinite (projective) points, keeping every solution in affine coordinates and avoids the complexities of handling points at infinity.

Ready to Get Started?Join Thousands of Users Today

Start your free trial now and experience the difference. No credit card required.

© 2025 Better-Start. All rights reserved.