ZK-STARK 证明系统

STARK 中的多项式承诺

两种类型的多项式

从算术化的约束系统中,我们会得到两种类型的witness,第一是整个待证明程序的执行轨迹,也叫做execution trace,第二是在执行过程中需要满足的约束条件,称为 constraint。

执行轨迹可以看成是一组寄存器的状态转换过程。这里我们仍以斐波那契数列的计算为例,假设我们要求出 fib(10),且用两个寄存器 s0, s1 来保存计算的中间状态,那么在程序正确执行5步后,s0[5] 就是我们需要的计算结果。

image-20220314160016374

有了程序的执行轨迹,我们还需要额外的约束来保证执行轨迹确实是程序按照我们给出的计算方式来生成的。约束分为两部分:

  1. 在边界处,寄存器的状态必须和指定的寄存器状态一致。例如 s0, s1 的初始状态要满足 s0[0] = 0s1[0] = 1。另外,我们得到的计算结果必须是程序执行到第5步后 s0 寄存器的状态,即 result = s0[5]
  2. 寄存器每一次的状态转换必须满足如下计算规则:
    1. s0[i] + s1[i] = s0[i+1]
    2. s1[i] + s0[i+1] = s1[i+1]

有了这些约束,我们就能够信任程序的执行结果,即 fib(10) = result = s0[5] 了。接下来我们需要对执行轨迹和约束条件做多项式承诺。

承诺一个多项式

在讲具体的实现之前,我们先来了解 STARK 中是如何对一个多项式进行承诺的。我们知道一个多项式承诺方案(polynomial commitment scheme)需要满足如下使用场景:

  1. 设置:能够生成特定的代数结构 ,以及公私钥对 ,用于承诺一个度数小于 的多项式
  2. 承诺:输出多项式 的承诺
  3. 打开:对于验证者给定的 ,证明者给出多项式在该点处的值 ,以及关于该求值的一个证明。验证者能够利用之前的承诺 验证其求值是正确的

在常见的 多项式承诺方案中,证明者首先使用椭圆曲线点乘的方式,对多项式进行承诺,随后在打开时构造一个新的商多项式,使得验证者可以通过椭圆曲线配对,验证该商多项式正是原始的 在挑战点处构造出的多项式。具体的步骤可以参考 的论文,这里不再赘述。

STARK 使用默克尔树进行多项式承诺,实现方式如下:

  1. 设置:STARK不需要额外的可信设置,但需要事先确定用于构造默克尔树的哈希函数
  2. 承诺:首先求出多项式 在其求值域上所有单位根处的值,,以这些值为叶子节点,组成一颗默克尔树,最后公开得到的默克尔树树根,即是该多项式的承诺。
  3. 打开:验证者选定随机挑战点,证明者提供多项式在该点处的值,以及一条默克尔树路径。验证者检查该默克尔树路径合法。

轨迹多项式(Trace Polynomial)的承诺

对于任何一个 STARK 寄存器的执行轨迹,我们可以将它的每一行看作是其对应的轨迹多项式在某个单位根处的取值。因此,对于示例中的 ,我们有:

因为默克尔树要求叶子节点个数必须是2的幂次,这里我们需要补上程序继续按约束条件运行的结果:

这样就可以为 分别生成默克尔树承诺了。不过请稍等,为了协议的安全性,我们还需要进行扩展。

低度多项式扩展(Low-Degree Extension)

从程序的执行轨迹中,我们得到了数个长度为 的轨迹多项式。实际的使用场景里,出于安全性的考虑,我们需要在一个更大的求值域上承诺该多项式,一般需要将求值域扩大 倍,扩大的倍数称为爆炸倍数( blowup factor),该求值域称为 域。

我们会在 DEEP-FRI 章节讨论安全性的问题。现在来看如何将长度为 的轨迹多项式扩展到 。我们先利用拉格朗日插值法,求出该轨迹多项式的各项系数 (注意其单位根需要使用 求值域上的单位根),然后我们在 的求值域上用系数求出其他单位根处的多项式值。这种利用一个低次多项式生成更大的求值域上单位根处值的方法称为低度多项式扩展。

下图是 blowup factor = 2 时, 的低度多项式扩展示例,黄色的节点是多项式在原始的求值域上对应单位根处的值(它们也是在 域上所有单位根处值的一部分,因为 ),蓝色为 扩展后新的单位根处的求值:

约束多项式(Constraint Polynomial)的承诺

程序在执行过程中需要满足的约束条件,同样也可以转换为多项式来表示。例如,我们要约束 ,可以按如下的方式构造约束多项式:

根据多项式基本定理, 可以被 整除当且仅当 。也就是说,当约束条件满足的时候, 的次数小于 。我们利用这种方法,将所有的约束条件写成多项式的形式:

将约束多项式组合起来

如果一个个检查约束多项式的次数,代价会很大,也没有必要。我们可以将所有的约束多项式组合到一起,形成一个组合多项式(Composition Polynomial),对该组合多项式的检查即是对所有约束多项式的检查。

在组合之前,有一点要注意:上述的约束多项式的次数不是相同的。需要先将约束多项式补齐到相同的次数,然后再组合。补齐的方法是将它乘上一个随机的 次多项式,该随机多项式的系数是验证者在证明者承诺完轨迹多项式后随机产生的。

最后我们得到 组合多项式 ,别忘了我们需要在 域上承诺它,因此也要像之前的轨迹多项式那样,使用低度多项式扩展的方式进行承诺。

DEEP-FRI

得到轨迹多项式和组合多项式后,其实我们已经可以进行证明了。只要把轨迹多项式和组合多项式再做一次随机的线性组合,然后证明最终的多项式度数小于 ,就能证明所有的约束条件满足,且程序的执行过程和被承诺的Trace一致。

但是,从安全性角度出发,还需要做一些调整。STARK 使用称为 Domain Extension for Eliminating Pretenders (DEEP) 的方法进行检查。我们需要从基域上随机选一个点,然后检查在这个点上,各多项式的值是否满足约束。基域是自变量 的取值范围 ,是一个比 域大得多的域。

设我们挑选了随机点 ,则 多项式可以构造为:

在实际应用中, 的度数一般比轨迹多项式要高,取决于约束条件。如果约束条件有两列相乘(即二次的约束),则 的度数就为 。这种情况下,需要将 拆分为多个次数小于 的多项式。本文中因为约束是一次的,因此 的次数和轨迹多项式的次数一致。另外,上面构造的 多项式,正常来说其次数应该小于 (因为分子均为次数小于 次的多项式,而分母为 次多项式)。但为了后面做 FRI 承诺的时候计算方便,我们仍然通过乘上一个多项式的方式,将其次数升高一次,这样它的次数应该小于

关于构造 多项式的必要性,可以参考 Eli Ben Sasson 的文章 DEEP-FRI: Sampling Outside the Box Improves Soundness

构造完 后,如果能够证明 是一个次数小于 的多项式,就可以证明所有的多项式在 点处的取值即是所预期的值,进而证明程序的执行结果正确,且所有的约束条件都满足。

FRI 低度多项式测试

低度多项式测试的原理

怎样证明一个多项式 的次数小于特定的 次呢?低度多项式测试提供了一种方法,即:从 上任意挑选 个点值对 ,使用其中的 个点值对构造一个 次多项式 。若 在第 个点处的求值相等,则有较大的概率 是一个次数小于 的多项式。

出于安全性的考虑,可以将这个过程重复 次。若进行一次测试,证明者作弊成功的概率为 ,那么挑选 个点值对,证明者成功的概率就是

使用折叠减小开销

上述低度多项式测试的方法有一个缺点,即构造 需要 个点值对,在多项式度数非常大的前提下,测试需要的时间和通信量也会变得无法承受。FRI 通过一种称为“折叠”的方法,降低了通信开销。具体做法是:

  1. 首先将所有的 个点值对 列进行划分,得到一个 行, 列的矩阵
  2. 证明者需要承诺矩阵的每一行,承诺的方式是将一行中所有的 进行哈希,得到共 个哈希值,组成默克尔树并输出树根
  3. 对应每一行,证明者将该行的所有点值对使用随机数 进行线性组合:
  4. 注意到,如果我们精心挑选 ,则得到的新多项式可以看成原多项式的每一项按模 划分出的多项式的组合,其系数正是原多项式对应项系数的线性组合
  5. 这样我们得到了一个项数为 的多项式 ,再次重复1-4步,直到我们将新多项式的次数降到可以承受的范围之内。
  6. 假设我们重复了 次,则只需检查最后的多项式次数是否小于 即可

第4步的解释:

我们构造的新 可以看成是 个多项式的线性组合,假设原来的多项式的求值域上单位根分别为 ,那么新的多项式其求值域是原来的 ,其单位根变为

,则有

可见 是次数为 的多项式

验证者的检查

检查FRI步骤的正确性

首先,因为证明者每次 FRI 折叠的时候都承诺了折叠后所有新点值对的默克尔树,验证者只需在同一层上抽取 个相关的折叠点,在下一层上抽取一个点,就能验证本层的 个点确实按正确的随机数折叠到了下一层的同一个点。

其次,验证者检查最后余下的多项式是否满足度数小于 ,这个使用低度多项式测试进行抽查即可验证。

检验多项式是否匹配

事实上,证明者如果伪造一个 ,它的度数小于 ,那么它总是能够通过低度测试。 因此验证者需检查:通过 FRI 低度测试的多项式,确实为证明者构造的 多项式。验证者可以向证明者请求 域中任意一点处的 的值,以及之前的轨迹多项式,组合多项式在该点处的值 。验证者自行计算 ,若它和证明者发送的 不匹配,则验证不通过。

如果证明者想要伪造 ,他需要让该多项式的度数小于 。因此若原始的 是个高次多项式(次数很高,远高于 求值域的度数),则二者在 上最多可以在 个求值点处相等(若有多于 个求值点相等,则伪造的多项式 次数必定大于等于 )。此时,验证者随机检查一个求值点,两个多项式在点处值相等的概率即为 为爆炸倍数)。验证者可以多次重复这一过程,直到证明者作弊成功的概率小于目标安全参数即可。

ZKHack-Mini 上有一个证明者通过伪造 进行攻击的例子,可以参考 ZKHack-Mini: Puzzle 2

磨损因子(Grinding Factor)

因为上述的验证手段均为概率的,为防止证明者靠庞大的计算能力进行反复重试,可以要求证明者在提供 STARK 证明的同时也提交一个工作量证明 (PoW),使得重试的代价变大。

参考文献

StarkDEX Deep Dive: the STARK Core Engine

ethStark

Vitalik STARK series I: Proofs with Polynomials

Vitalik STARK series II: Thank Goodness It's FRI-day

Vitalik STARK series III: Into the weeds

DEEP-FRI: Sampling Outside the Box Improves Soundness

ZKHack