Circle STARKs 是一种新型 zk-STARK 方案,已在 Stwo 和 Plonky3 中实现,并被多个 zkVM 项目所采用。其关键创新点在于能够使用小型的 32 位域(Mersenne 素数 ),同时仍然保持高效 FFT 操作所需的数学属性。在这个解释性系列文章中,我们将讨论 Circle STARK 背后的主要算法,即 Circle FFT。
在第 1 部分文章中,我们首先回顾 STARK 友好素数的演变过程,然后通过详细的示例和推导,探索 Circle FFT 的基础概念——即 Circle 曲线、其群结构以及孪生陪集 (twin-cosets) 和标准位置陪集 (standard-position cosets) 的作用。随后,在第 2 部分文章中,我们将详细描述 Circle FFT 算法本身。
我们还结合 Python 脚本提供了对这些核心概念(例如 Circle 群和 Circle FFT)的详细演练,以及针对 (指数为 的 Mersenne 素数:)的明确计算示例。
我们的重点是:即使在 没有较大的 2-adic 因子的情况下,这些构建模块中的每一个是如何引导出一个完整的类似 FFT 的例程的。
如果您有兴趣自己运行这些示例,完整的 Python 代码可以在这里找到,我们将在后面的部分(如第 4 节和第 6 节)引用它,在这些部分我们将通过代码演练群和域的构建。
本文由 Yugo 与 RareSkills 合作编写。本文由 Soulforge Grant 提供资金支持。我们对他们的支持表示感谢。
0. 从 STARK 友好素数到 Mersenne 31
在深入探讨 STARK 友好素数之前,让我们先回顾一下有限域的基础知识。
域是一个定义了加法、减法、乘法和除法(除数不为零)的集合。当集合的元素是有限的时候,它被称为有限域。 指的是从 到 (其中 是一个素数)的整数集合,其加法、减法和乘法均在模 下进行,除法被定义为模 乘法的逆元。
我们用 表示集合 。 中的每个元素都有一个乘法逆元,因此可以将 视为一个乘法群。这个群的大小 为 。当我们将 分解为其素因数时,众所周知,如果一个素数 出现了 次,那么必定存在一个大小为 的循环子群。
你可以用一个小素数(例如 )来简略说明这一点。由于 可分解为 ,因此必定存在大小为 和 的子群。实际上,在 中,元素 生成了一个大小为 的子群 ,元素 生成了一个大小为 的子群 。
在许多 STARK 协议中,数论变换 (NTT) 或快速傅里叶变换 (FFT) 依赖于这样一个子群:其因子是 2 的幂次,从而使该子群的大小为 。使得 能整除 的最大整数 被称为 two-adicity。较高的 two-adicity 允许为 FFT/NTT 域设置更大的 2 的幂次大小,这是实现快速多项式求值、插值和多项式乘法的关键。此外,高效实现基于 NTT 的这些操作严重依赖于快速的模乘运算——因为 FFT/NTT 涉及重复地将元素相乘并对 取模。
最初,STARKs 使用的素数是 ,其 two-adicity 为 。在现代 STARK 实现中,为了通过减小有限域的规模来降低开销,进行了一项改进。例如,Goldilocks 域为 ,其 two-adicity 为 。至关重要的是,因为 ,两个 64 位数字的乘积可以在基数 下进行拆分,并只需几次加法和减法即可完成归约。
那么 Circle STARKs 呢?如 Circle STARKs 论文 中提出的,它使用了一个 Mersenne 素数,
Mersenne 素数是形式为 的素数,其中 为某个整数。在这里, 得到了 ,它恰好是素数,因此被归类为 Mersenne 素数。
选择 的一个主要原因是它刚好能放入一个 32 位的机器字中,使得模乘运算极其快速。具体来说,当两个 32 位数字相乘时,结果是一个 64 位的值,而对 进行模归约只需进行两次加法即可完成。
借助向量化指令,此操作的速度还能大幅提升。现代 CPU 擅长原生处理 32 位整数,而 Mersenne 31 () 正好契合这种架构,从而允许最佳地利用硬件能力。正如 Eli Ben-Sasson 在 Why I’m Excited By Circle Stark And Stwo 中所讨论的,与原始 STARK 中使用的 256 位素数相比,Mersenne 31 的模乘速度大约快 125 倍,并且比 Baby Bear () 快 1.3 倍。
然而,传统的 FFT 或 NTT 方法要求在 中存在一个较大的 2-adic 因子,以便在 中形成足够大的 2 的幂次子群。但在本例中,,其 two-adicity 仅为 1。因此,我们不能直接使用乘法下的较大 2 的幂次子群。
Circle STARK 仍然采用了 Mersenne 素数域 。然而,我们不再直接在 内部操作,而是观察到曲线
这非常关键,因为 可以包含一个很大的 2 的幂次,从而创建出大小为 的子群。因此,Circle STARK 没有在 中处理单个元素,而是考虑满足 的坐标对 (即 Circle 曲线上的点)。
通过这种方式,Circle STARK 可以直接在这些 Circle 元素上实现自己专属的快速多项式插值和求值——这通常被称为 Circle FFT。
1. Circle 曲线
让我们进入 Circle STARKs 的核心数学。有限域 上的 Circle 曲线是 的一个子集,由所有满足下式的点 定义:
我们有时将该集合表示为 ,或者简称为“Circle”。
在 Circle STARKs 中,我们重点关注满足 (例如 )的素数 。在这个条件下, 在 中不是一个平方数。具体而言,这确保了方程 在 中恰好有 个解,并且没有额外的异常解(即无穷远点)。对于本文来说,我们只需知道在该条件下, 中的 恰好能给出 个点。
(不过,对于有兴趣从几何角度理解为何 会恰好得出 个解的读者,请参阅附录。)
1.1 简单示例
例如,如果 (即 ),那么 就是所有满足 的 构成的集合。
以下是 中满足 的几个 数对示例:
- 和 是显而易见的,因为 且 。
- 也成立,因为 ,而 。
我们可以看到,对于 ,恰好有 (即 )个这样的数对。
2. Circle 群
这类集合记为 ,并且通常被称为 上的 Circle 曲线。一个惊人的事实是 。例如,如果 ,那么圆上恰好有 个点——值得注意的是 。
这很重要,因为在通常的 STARK 设定中,我们经常需要一个大小为 的域来进行类似于 FFT 的操作。在 的情况下,圆上恰好有 8 个点,这意味着我们可以形成大小为 的子群。稍后,我们将看到 Circle FFT 是如何利用这一属性(即 中的高 2-adicity)的。
一个关键的属性是, 可以在其点上赋予一个群运算(本质上是一个二元运算符)。具体来说,我们使用“”来表示这种运算:对于 中的两点 和 ,
我们可以验证这种群运算仍然保持在 内(结果仍然满足 ),并使该集合成为一个大小为 的循环群。
2.1 检验群公理
作为群的公理,我们通常列出以下四个属性:封闭性、单位元、逆元和结合律。让我们仔细看看每一个以确认它们都成立。
2.1.1 封闭性
取圆上的两点 和 ;
定义一个新点
我们将通过逐步计算 来证明 也位于圆上:
因为我们已知 且 ,我们将这些值代入乘积中:
因此 也满足 ,并因此位于圆上。这证实了集合在如下运算下是封闭的:
2.1.2 单位元
在群运算中,点 充当单位元。具体而言,对于圆上的任何点 ,
有人可能会对点 感到好奇。如果我们试图使用 作为单位元,我们会发现它不成立:
对于一般的 ,这并不等于 。因为单位元是唯一的,所以 不能是单位元。
2.1.3 逆元
在一个群中,元素 的逆元是满足下式的唯一解 :
在圆上,我们看到 可以作为 在该群运算下的逆元。确实,
因为 。
因此,在群运算下 的逆元是 。
2.1.4 结合律
最后一个群公理是结合律:对于圆上的任意三点
,
这可以通过展开多项式来验证,但由于过程太长,我们不在这里详细讨论。
2.2 一个特殊运算:平方映射
除了这些群公理之外,圆上还有另一个运算,它在后续章节中非常有用,特别是在 Circle FFT 中,即平方映射。该映射将群运算应用于点与其自身:
由于圆满足 ,我们得到 ,因此
在 Circle FFT 中,我们将使用 以递归的方式将某些求值域减半——每次应用 都会使域的大小缩小两倍。这是因为 与群运算是兼容的。具体来说,对于圆上的两点 和 ,
这种兼容性意味着 可以将大小为 的孪生陪集映射成另一个大小为 的孪生陪集。稍后我们将详细讨论这一域减半特性。
2.3 一个特殊运算:对合映射
除了平方映射之外,还有另一个重要的运算。该运算被称为对合映射 (involution),定义为
从几何上看,它翻转了 坐标的符号,同时保持 坐标不变。请注意, 的逆是它自己——连续两次应用 会使你回到原来的点:
在圆 上,对合映射 能够将曲线上的每一个点保持在曲线上(因为取 的负值并不影响 )。然而, 的点在 的作用下是固定的,这意味着 。
2.4 时的 Circle 群具体操作
在之前的具体示例中,我们探讨了 时的 ,这作为一个有用的简单示例,帮助我们建立直觉。然而, 太小了,不足以揭示 Circle 群的深层结构特性——例如更长的子群链,或孪生陪集及标准位置陪集的构建。
因此,我们现在转向稍大一些的素数 (其满足 ),以便以更有意义的方式展示这些更丰富的行为。
给定符合 的 ,满足方程 的 中的点集 记作 ,如下图所示:

以下是 中满足 的几个坐标对示例 :
- 也成立,因为 ,并且 。
- 同样是一个解: (mod 31) 且 (mod 31),因此 (mod 31)。
- 说明 ,因此 (mod 31)。
为了了解 Circle 群,让我们在 上进行运算,此时圆 具有 32 个点。假设我们取点
我们可以通过群运算将 与点 结合:
确实,我们可以验证
因此 也落在圆 上。
同时, 的逆元是
因为
此外,平方映射可以作用于点 。回想一下
因此,
3. Circle 群的子群
我们已经确定 是一个大小为 的循环群。如果 是 2 的幂次,假设 ,那么就存在一条嵌套的子群链
其中每个 的阶为 。
换句话说, 是大小为 2 的子群, 大小为 4,依此类推,直到 本身,其大小为 。
例如,如果 (此时 ),我们得到一条链
其中 并且每个 的大小都是 。其摘要如下所示:
| 1 | |
| 2 | |
| 4 | |
| 8 | |
| 16 | |
| 32 |
在下方,我们明确列出了 中的每一个子群。
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 代码 1:Circle 群
我们现在将通过简单的 Python 实现来回顾 Circle 群代码。这里仅描述了最重要的函数或部分,并且所有代码都可以在这里找到。
在此,我们定义了两个核心类——FieldElement 和 CirclePoint——以分别处理底层有限域中的算术运算和曲线上的群运算。
4.1 FieldElement
首先,FieldElement 类定义了有限域 中的算术运算,例如模 31 加法、乘法和求逆。这是在 Circle 曲线上进行所有计算的基础。
# 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
CirclePoint 类代表 Circle 曲线 上的点。add 方法实现了群运算,而 double 则应用了平方映射。
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())
然后,你可以如第 2.4 节所述,模拟一个 Circle 群运算示例。
例如,将 CirclePoint 和 CirclePoint 相加会得到 Point 。
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)
对 进行 double 会产生
# squaring map
p1_double = p1.double()
print(f"π(p1) = {p1_double}")
π(p1) = Point(27, 27)
的逆元是 ,将它们相加得到单位元
# 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 群
generate_subgroup 函数生成一个阶为 的子群 。它从 get_nth_generator 函数获取适当的生成元,并重复执行群运算 add 来构建该子群。
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) ]
例如,你可以获取大小为 8 的子群。
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. 陪集、孪生陪集与标准位置陪集
孪生陪集和标准位置陪集是 Circle STARKs 中的域 (domains)。在理解孪生陪集和标准位置陪集的数学属性之前,让我们简要回顾一下传统 STARKs 中域是如何被使用的。正如第 0 节所讨论的,传统的 STARKs 通常利用表示为 的乘法(子)群作为它们的域。这些域主要用于两个目的。
首先,当通过 IFFT 从计算轨迹构建多项式时,它们充当求值域 (evaluation domain),我们需要通过 FFT 使用扩展域来进行低次扩展 (LDE),并构建约束多项式并在 LDE 上进行求值。
其次,在低次测试中,特别是在使用 FRI 承诺时,必须在域上通过 FFT 对多项式进行求值,并且在递归 FRI 折叠步骤中,这些域会被迭代地减半。
此外,在 FRI 折叠步骤中,随着多项式的度数减半,域的大小也需要减半。将域大小减半是 FRI 和 FFT 的核心。我们在继续学习时请记住这一点。
因为虽然 Circle 群本身已经提供了高 2-adicity,但孪生陪集或标准位置陪集的构建能够确保:在 FFT 的递归平方步骤中,求值域(即孪生陪集或标准位置陪集)的规模同样可以减半,同时保持其陪集结构。这对于在递归的每一层支持基于这些域的类 FFT 操作而言至关重要。
5.1 陪集
让我们回顾一下群论中陪集的定义。假设 是群 的子群,且设 。那么 关于 的左陪集就是集合
如果 ,那么 。否则,在 的情况下, 是一个与 不相交的集合,但仍具有与 相同的大小。这之所以成立,是因为如果 ,群运算下 的封闭性确保了 ;而如果 ,则 中的任何元素都不能在 中(否则暗示 ),同时双射 保证了大小的一致。
在我们特定的设定中,,它是大小为 的循环群。从上一节我们知道,存在一条子群链 ,其中 。
因此,举例来说,如果我们固定阶为 的 ,那么对于圆上的任意点 ,陪集
被称为 的一个陪集。
该集合将拥有相同的基数 ,并且除非 本身已经属于 ,否则它与 本身是不相交的。
5.1.1 陪集示例
让我们快速说明一下这个示例。在 的情况下,回想一下 ,所以存在子群链 ,其中 。具体而言,
现在取一个不在 中的点 。例如,
既然 不在 中,那么集合
将是 的一个不同陪集,与 自身不相交。
因此,
其大小确实为 2。这是对应于 的 的陪集,它显然不同于子群 本身。
5.2 孪生陪集
现在,我们通过合并两个陪集来定义一个孪生陪集: 及其逆向陪集 。具体来说,设
如果满足以下条件,我们就说 是一个大小为 的孪生陪集:
-
不相交性 (Disjointness)
两个陪集 和 是不相交的。
实际上,这种不相交性等价于确保 。这种等价性源于陪集的性质。如果 ,那么 将同时位于 和 中(出现重叠)。反之,如果两个陪集不重叠,那么 不能被写为 (对任意的 ),这意味着 。 -
在对合映射 下没有不动点
如果 ,则点 被称为映射 的不动点。在我们的案例中,我们考虑的是对合映射
如果 中包含任何 的点,那么 将保持不变。这就是一个不动点。我们希望避免在 中出现这样的点,因为根据定义,孪生陪集排除了 的任何不动点。
在这些条件下,每个陪集 和 都各有 个不同的点,这让 的总点数达到 。直观地说,我们将一个陪集与其逆向陪集合并,从而形成一个拥有 个元素的域。
5.2.1 孪生陪集示例
接续第 5.1.1 节中的陪集示例(当时我们选择了一个不在 中的 ),现在我们来构建一个大小为 的孪生陪集。我们已经看到:
接着,计算 并形成陪集 :
因此,
进行并集操作,孪生陪集为:
这个域 大小为 4,并且满足孪生陪集条件:两个陪集是不相交的(因为 ),并且 里面不包含任何 的点,因此在对合映射 下没有不动点。所以, 确实是一个大小为 的孪生陪集。
5.3 标准位置陪集
标准位置陪集是大小为 的一种特殊类型的孪生陪集 ,它同时巧合地等同于子群 的单个陪集:
在这里,回忆一下 是 中阶为 的唯一子群,就像第 3 节中介绍的那样。我们拥有如下的子群链:
并且每个 的大小都为 。因此, 包含了 ,并且我们将使用一个阶为 的点 来关联这些子群的陪集。
让我们逐步拆解:
首先,因为 ,我们可以将 视为由 的两个不相交的陪集组成,也就是:
如果我们现在用一个固定且阶为 的点 乘以它,我们得到:
结果表明 ,因此:
因此,陪集 可以写作两个不相交陪集的并集:一个基于 ,另一个基于 ,且两者都是在较小的子群 之上。这恰好就是孪生陪集的定义。
但并非所有的孪生陪集都是这样产生的。为了确保这种构建正常运作,我们需要:
- 不相交性:。否则,这两个陪集会重叠。
- 对合下没有不动点:生成集合 必须避免含有 的点,以便让对合映射 在 中没有不动点。
- 正确的阶: 的阶必须是 ,以保证 能够覆盖整整 个不同的元素。
当这些条件被满足时,陪集 会给出一个标准位置陪集:它是一个大小为 的域,同时既是一个孪生陪集,也是一个较大子群的单个陪集。
5.4 手算示例: 时的孪生陪集与标准位置陪集
以下,我们展示在 时如何应用孪生陪集和标准位置陪集的概念。由于 ,所以存在子群 ,其中 以及 。我们将展示 的标准位置陪集为 。
5.4.1 孪生陪集构建
-
设定
令 。那么 大小为 4。选择一个阶为 16 的点 (所以 ,但 )。具体来说,我们可以选择 -
形成孪生陪集
因为 有 , , , 和 四个点,每一个点都要乘以 或者是 。
- ,
- ,
- ,
- ,
- ,
- ,
- ,
- 。
合并这些点可以给出 8 个不同的点。没有 中的元素被 所固定,因此 是一个大小为 的孪生陪集。合并这些就得到了 8 个不同的点。由于 中的所有元素都不在 的映射下产生不动点,因此 是一个大小为 的孪生陪集。
在下方图表中,🔴 红点表示 ,而 🔵 蓝点表示 。它们合在一起形成了 8 个点的孪生陪集 。

5.4.2 检查标准位置陪集
对于某个大小为 8 的子群 ,这个相同的 同样也等于 ,这意味着 是一个标准位置陪集。
是子群 ,那么将它的每个元素与 相乘正好产生 中的这 8 个点。
因此
因为 同时是 的一个孪生陪集,也是 的单个陪集,所以我们称其为大小为 8 的标准位置陪集。
在下方图表中,🟢 绿点表示子群 ,🔴 红点和 🔵 蓝点分别表示两个不相交的陪集 和 。它们的并集形成了孪生陪集 ,并且整个由 8 个点组成的集合构成了标准位置陪集 。

因此,只要 的阶是 ,我们就可以建立一个大小为 的标准位置陪集。这种结构在 Circle STARK 中非常重要,因为即使 不具备充足的 2-adicity,它依然能够提供一个整齐的有 个点的域。
6. Python 代码 2:Circle Domain
我们现在将通过简单的 Python 实现来回顾 Circle Domain 代码。该域的构建(比如孪生陪集和标准位置陪集)同样在相同的 Python 笔记本中得到了实现。你可以运行它并一步一步检查这些集合是如何建立的。
6.1 孪生陪集
这个函数通过对子群上的 及其逆 应用群运算 add 来生成域,以确保对称性和规模 。
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 标准位置陪集
这个函数通过对 和子群应用群运算 add 来生成标准位置陪集。
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)]
通过这部分代码,你可以检查标准位置陪集 的等价性:
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. 将较大的标准位置陪集分解为较小的孪生陪集
在 Circle FFT 和 FRI 的许多步骤中,我们需要管理各种不同大小的求值域,并且所有这些域都位于 Circle 曲线上。实现这一点的一项关键方法源于 Circle STARKs 论文中的引理 2,该引理确保一个大小为 的标准位置陪集能够分解为任意 大小为 的较小孪生陪集。
7.1 引理 2 声明
设 为一个大小为 的标准位置陪集。那么对于任意 ,都可以将 分解为大小为 的孪生陪集。具体来说,如果
那么
用更简单的话来说,如果 是一个大小为 的标准位置陪集,我们可以使用 的幂次把它切片成更小的孪生陪集(每个大小为 )。这种划分允许大家精确构造出 Circle FFT 协议某一步中所需的那部分 。
7.2 手算示例:将大小为 8 的标准位置陪集拆分为 2 个大小为 4 的孪生陪集
假设 ,所以 。我们存在子群 ,其中 。
- 我们的标准位置陪集
令 ,具有 。这里, 的阶为 ,意思是 ,但 形成了一个大小为 的陪集。
- 目标
将 拆分为大小为 的孪生陪集。令 ,因此 且 。
那么引理 2 提示
因为 ,我们有 和 。
7.2.1 的情况
- 。
- 。
计算:
因此,第一个孪生陪集拥有 4 个不同的点。
7.2.2 的情况
- 。
也就是 。 - 同理,。
再次,用它们各自乘以 和 :
这产生了另外 4 个点,形成了第二个大小为 4 的孪生陪集。
7.2.3 两个孪生陪集的并集
将两个 4 点集合(对应于 和 )进行并集操作,我们恢复了拥有 8 个元素的整个陪集 。因此:
这恰好与引理 2 的声明完全一致,展示了一个包含 8 个点的标准位置陪集是如何被分解为两个大小均为 4 的孪生陪集的。
7.3 手算示例:将大小为 8 的标准位置陪集拆分为 4 个大小为 2 的孪生陪集
我们同样可以将这个大小为 8 的标准位置陪集分成更多个更小的孪生陪集。
令 ,那么 。引理 2 给出:
由于 ,每个孪生陪集有 2 个点:
- :
- : $
={(24, 13), (24, 18)}$ - : ${Q^9 \cdot G_0 = Q^9, Q^{-9} \cdot G_0 = Q^{-9}}
$ - : $
={(7, 18), (7, 13)}$
这把 分解成了四个大小为 的孪生陪集。
8. 平方映射将孪生陪集减半
引理 2 的重点是将大规模陪集拆分为较小的孪生陪集,而引理 3 则利用平方映射将一个孪生陪集的大小减半。该过程用于 Circle FFT 中的域减半步骤,在那里我们迭代地缩减域的规模。
8.1 引理 3 声明
该引理出现在 Circle STARKs 论文 中,用于形式化由平方映射支持的递归域减半机制。
假设 是一个大小为 的孪生陪集(且 )。随后应用平方映射 ,它将把 转化为另一个大小为 的孪生陪集 。并且,如果 原本是一个标准位置陪集,那么 也将保持作为一个标准位置陪集。
直观地说, 是一个群自同态,它将子群 映射到 。由此,大小为 的孪生陪集自然能生成另一个大小为 的孪生陪集。
8.1.1 引理 3 证明的抽象视角
设 是大小为 的孪生陪集,这在抽象记法下意味着
其中 。
特别地,由于群自同态, 将子群 映射成了 。
这正好是子群 (大小为 )上孪生陪集的定义。相应地, 本身就成了一个大小为 的孪生陪集。
如果 最初是标准位置陪集,那么应用 仍会保留该标准位置的性质,因为 。因此 再次成为了一个标准位置陪集,但现在它的大小变成了 。
8.2 手算示例(将大小为 8 的孪生陪集减半到大小 4)
假设 且让 为一个大小为 的孪生陪集。具体而言,
由于 ,所以有 。我们来检查 是如何影响 的:
8.2.1 计算 和
8.2.2 将 应用于
子群 的大小为 2,即 。根据引理 3,
$$
\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*}
\cup
{(27,4), (4,27)}$$
这就是一个大小为 4 的孪生陪集。
因此, 将 从 8 个元素减半到了 4 个元素,恰如引理 3 所述。重复应用 继续以 2 的幂次缩小域,在 Circle FFT 的递归结构中起着至关重要的作用。
在下方图表中:
- 左圆展示了原始的孪生陪集 ,其中 🔴 红点 = ,🔵 蓝点 = 。
- 右圆展示了减半后的孪生陪集 ,其中 🔵 蓝点 = ,🔴 红点 = 。
- ⚫ 黑点标记了子群 。
该视觉图说明了 是如何通过群自同态将原始大小为 的域映射成一个新的大小为 的孪生陪集的。

9. 第 1 部分的总结
在本文中,我们重点讨论了如何将 上的 Circle 曲线 转换为大小为 的循环群,并通过具体示例讨论了孪生陪集和标准位置陪集。我们还展示了两项关键技术:一种用于将较大规模的标准位置陪集拆分为较小的孪生陪集,另一种通过应用平方映射将孪生陪集的大小减半。结合起来,这些技术让我们能够随心所欲地重构大小为 的域。
在下一部分中,我们将深入探讨 Circle FFT 本身,展示这些 大小的域——再加上对其进行拆分或减半的能力——是如何实现在 不具备充分的 2-adicity 时依然做到快速的多项式求值和插值的。
参考资料
- Circle STARKs
https://eprint.iacr.org/2024/278 - Plonky3
https://github.com/Plonky3/Plonky3 - ZK11: Circle STARKs - Ulrich Haböck & Shahar Papini
https://youtu.be/NAhLYMysSdk?si=OlzNKS1DSTRkPnUR - Circle STARKs: Part I, Mersenne
https://blog.zksecurity.xyz/posts/circle-starks-1/ - Why I’m Excited By Circle Stark And Stwo
https://starkware.co/integrity-matters-blog/why-im-excited-by-circle-stark-and-stwo/ - Exploring circle STARKs
https://vitalik.eth.limo/general/2024/07/23/circlestarks.html - An introduction to circle STARKs
https://blog.lambdaclass.com/an-introduction-to-circle-starks/ - Yet another circle STARK tutorial
https://research.chainsafe.io/blog/circle-starks/ - Deep dive into Circle-STARKs FFT
https://ihagopian.com/posts/deep-dive-into-circle-starks-fft - Coset’s Circle STARKs lecture videos
https://youtube.com/playlist?list=PLbQFt1T_44DyihAOawprEABRPWgYeXB5u&si=AJe0dP7FUfp2Bebe - Ethereum/research/circlestark(Python 实现)
https://github.com/ethereum/research/tree/master/circlestark - Rust Implementation Demo of Circle Domain and Circle FFT
https://youtu.be/ur3c4mIi1Jc?si=lxU10mZ6vOFCrzvw
https://github.com/0xxEric/CircleFFT - STARKの原理
https://zenn.dev/qope/articles/8d60f77e3a7630
附录
在下文中,我们介绍了 Circle 曲线的射影视图与仿射视图,接着解释了为什么 Circle STARKs 专门选择满足 的素数来避免诸如无穷远点之类的射影复杂性。
A.1 射影视图与仿射视图
在 上的仿射平面中,一个点简单地记为 ,其中 。相比之下,在射影平面 中,每一个点都记为 ,但所有非零标量倍数均代表同一位置。形式上,
- 当 时,我们通过将每个坐标除以 来重新缩放,从而得出一个仿射点 。
- 当 时,我们得到一个无穷远点,其没有对应的仿射点。
A.2 为什么要避免无穷远点?
为了看看无穷远点是如何出现的,我们将 重写为射影形式:
在这里,如果 ,我们将其视为 和 ,因此 变成了 。
一个无穷远点就是在 时的任何解。这类解是否存在取决于 在 中是不是一个平方数:
- 例如,如果 ,那么 在 中是一个平方数。
具体而言,存在某个 使得 ,暗示了 在 时可能有非零的 。
这将在圆上产生额外的无穷远点,从而使得群运算和实现变得复杂。
示例:- 这里,在 中 ,而 确实是 。
- 所以 允许在 时存在非零的 ,这就给出了两个无穷远点。
- 仿射方程 恰好有四个仿射解:
- 且射影的无穷远点是
,所以共有 个解——符合 (仿射 4 + 无穷远 2)。 - 如果 ,那么 在 中不是一个平方数。
结果就是, 在 时并没有非零解。我们就不会得到任何无穷远点,所有的解都保留为仿射形式。
示例:- 因为 不是一个平方数(我们有 以及 ),所以不存在能够满足 于 时的 。
- 此后,方程 只有仿射解。检查 会得出:
总共 个解,恰好符合 。
在 Circle STARK 中,我们特地选择 使得 在 中不是平方数。这保证了我们的圆 没有无穷远(射影)点,将所有的解都留在了仿射坐标系里,并避免了处理无穷远点所带来的种种复杂性。