本文以直观的方式解释了Groth16零知识证明系统的工作原理。
Groth16 是零知识证明(ZKP)系统的 GOAT(最棒),考虑到它诞生于 2016 年(如名称所示),恰恰在 ZKP 领域最令人瞩目的突破性十年的开端,这尤为引人注目。这样一个精巧紧凑的方案有个缺点:似乎没有人能直观地理解它。或者至少,如果有人能理解,他们也没有为我们其他人进行综合阐述!我们希望为你带来该方案的直观解释,从而改变这一状况。所以,如果你终于想搞懂它,请继续往下读。
![]()
首先,什么是 Groth16,为什么人们仍然关心它?答案很简单:它是一个具有恒定大小证明的 SNARK,需要电路特定的可信设置。实际上,当我这么说时,也提到了它的缺点……那么让我解释一下两者:
但优点如此强大,以至于对很多人来说似乎超过了缺点,以至于 Groth16 被广泛使用,甚至在那些使用更现代、更灵活证明系统的协议中也是如此。现在的典型用法是用 Groth16 在最后包裹主要证明系统(本质上 Groth16 证明其证明系统的验证程序,即递归!)
顺便说一下,我们将逐步展示证明者如何创建证明 $A,B,C$,以及验证者如何检查方程:
$$A\cdot B=[\alpha][\beta]+\gamma\cdot\sum_{i=0}^{\ell}a_i\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\gamma}\right]+C[\delta]$$
这有点复杂,先别细看,但希望到文章结尾你能理解这里发生了什么!
在讨论 Groth16 的核心之前,我需要简要提一下 Groth16 证明的是 R1CS 约束。我不想在这方面花费太多时间,但本质上,不同的证明系统使用不同的算术化,它们允许你将算术电路(由乘法和加法门构成)写成某种可证明的形状。有些系统使用 Plonkish 或 AIR 算术化,Groth16 使用 R1CS。
我们在此有一整节关于所有这些算术化的白板讲解,但为了使本文自成一体,让我们一起来回忆 R1CS 是什么。
我理解它的方式相当简单:我们的 R1CS 电路写成如下形式:
一个长的内存数组 $a$,用于存储值 $1$(作为第一个条目)、公共输入和输出,以及电路中使用的所有其他(秘密)值。
一定数量的 “R1CS”门,假设有 $n$ 个,每个门由三个向量描述,这些向量告诉我们该门使用了内存数组中的哪些条目(以及它们在哪里使用)。
“R1CS”门本质上将内存数组条目的两个线性组合相乘,并检查该乘积是否等于另一个类似的线性组合。
例如,对于一个由向量 $u,v,w$ 描述的单门,Groth16 将强制执行:
$$\left(\sum_i a_i u_i\right)\left(\sum_i a_i v_i\right)=\sum_i a_i w_i$$
我们可以看几个例子来理解它是如何工作的:
注意
你可能在其他地方见过 R1CS 的另一种典型表示方式,即把所有门看作三个矩阵 $U, V, W$,并且我们强制执行的约束为 $$Ua \odot Va = Wa$$ (其中 $\odot$ 表示 Hadamard 乘积)
当然,最好的方式(也是我最喜欢的方式)是通过四个表格来看待这一切:$U, V, W, a$,其中 $a$ 是单列,而 $U, V, W$ 的列数与 $a$ 的条目数相同。
还有一步。Groth16 将其对 R1CS 的“视图”称为 QAP,因为它需要将 R1CS 算术化视为多项式方程以便证明。为了将上述 R1CS 内容“多项式化”(并变成 QAP),我们可以做的是将每个表格的每一列转换为多项式,方法是假设列中的每个条目都是某个点上的求值。
例如,我们可以假设第一列的条目表示多项式 $u_0$,使得:
我们可以轻松地(相信我)将其插值成多项式 $u_0(x)=x^2+2x+3$。
这样,我们可以将所有门的先前 R1CS 方程表示为以下方程:
$$\left(\sum_i a_i u_i(x)\right)\left(\sum_i a_i v_i(x)\right)=\sum_i a_i w_i(x)$$
在我们的例子中,我们只关心该方程在点 $0$, $1$ 和 $2$ 上成立,所以我们可以更一般地写成
$$\left(\sum_i a_i u_i(x)\right)\left(\sum_i a_i v_i(x)\right)=\sum_i a_i w_i(x)+t(x)q(x)$$
其中 $t(x)=(x-0)(x-1)(x-2)$,$q(x)$ 是次数为 $n-2$ 的多项式(确保你理解为什么)
注意
我们通常称 $t$ 为消失多项式,$q$ 为商多项式。很多 ZKP 方案的大部分工作都是用来证明存在这样一个商多项式。
让我们穿越到未来。最终 Groth16 会要求证明者产生三个证明元素 $A$, $B$ 和 $C$,它们看起来几乎像这样:
($C$ 实际上会非常不同,但我们现在先这样假设)
然后验证者最终会检查一个看起来几乎像这样的东西:
$$A\cdot B=C+t(x)q(x)$$
这应该看起来像我们之前看到的 R1CS 方程,让我们称之为验证者的 QAP 检查。
重要的是,像我们知道的每一个简洁证明系统一样,我们即将检查的多项式恒等式最终会在一个随机的 $x$ 处求值,而不是在每个 $x$ 处检查(例如 $x=0$, $x=1$ 等)。
注意
为什么这样?简而言之,我们可以随机选择的求值点 $x$ 的数量非常大。另一方面,如果左边和右边的多项式不相等,那么它们在大多数这样的点上求值时不会匹配。
因此,通过选择一个随机点,并检查多项式恒等式在该随机点处求值是否成立,我们以非常高的概率检查了它在所有点上成立。这就是 Schwartz-Zippel 引理 所涉及的(更准确地说,它是关于量化在此处被欺骗的风险)。
几乎所有证明系统都使用这种“在随机求值点检查多项式恒等式”的技巧,但有些系统为你生成求值结果以便明文检查,而 Groth16 则隐藏地检查它们(或者像有些人说的“在指数中隐藏”)。
隐藏地进行允许 Groth16 提前选择那个点,这也是 Groth16 如此高效的主要原因之一。提前意味着随机 $x$ 将在(可信)设置期间生成,并编码在证明者和验证者都可以使用的参数中。
我们通常将在可信设置期间生成的这些参数称为公共参考字符串(CRS),有时也称为结构化参考字符串(SRS),以强调它不仅仅是随机的东西,而是具有代数结构(这两个术语目前都在使用)。
注意
直观上,你在设置时编码到 CRS 中的信息越多,你的方案看起来就越高效。Groth16 将那个点以及电路都编码到了 CRS 中(我们稍后会看到)。正如我们之前看到的,这也带来了可信设置的缺点,更糟糕的是,还有电路特定的可信设置。
为了隐藏地进行,我们使用椭圆曲线承诺,即如果我们有一个生成子群的生成元 $G$,我们可以通过生成 $s\cdot G$ 来获得 $s$ 的承诺。为了简化符号,我将在后面写成 $[s]$。
所以此时,我们只需假设可信设置已经生成了完全描述我们电路的多项式的求值结果:
$$CRS={[u_i(x)], [v_i(x)], [w_i(x)]}_i$$
然后这允许证明者生成对所有点的承诺:
然后验证者可以检查类似 $A\cdot B=C+[t(x)]\cdot [q(x)]$ 的东西。
注意
在 Groth16 中,他们实际上在 CRS 中发布 $x$ 的幂次,而不是 $u_i(x), v_i(x), w_i(x)$,这仍然允许证明者生成相同的证明元素。
这个方案本身并不安全,而且到目前为止我们还没有讨论如何生成 $[t(x)]$ 和 $[q(x)]$ 的承诺。我们稍后会解决这两个问题,现在有更紧迫的事情要处理:我们甚至不能进行承诺的乘法运算。
既然我们使用了承诺,我们就有点麻烦了。证明元素现在被计算为承诺,但验证者不能再像上面那样计算检查 $A\cdot B=C+[t(x)]\cdot [q(x)]$,因为这里涉及两个承诺之间的乘法,而我们通常不知道如何做到这一点。
实际上,并非如此,我们有一种方法:只需使用椭圆曲线配对!
我不想在这里过多解释配对,所以我只说这么多:如果你有两个椭圆曲线承诺 $[a]$ 和 $[b]$,你可以使用配对获得一个新的承诺 $[a\times b]$!
人们这样写两个承诺(使用配对)的乘法:
$$e([a],[b])=[ab]$$
不过,结果是在一个不同的群(我们称之为目标群),所以我们通常这样写:
$$e([a],[b])=[ab]_T$$
在使用曲线 BN254(并非所有曲线都支持配对)的常见实例化中,配对是“类型 3”配对,所以参数也必须在由 $G_1$ 和 $G_2$ 生成的不同曲线/群中,因此你通常会看到上述方程写成这样:
$$e([a]_1,[b]_2)=[ab]_T$$
但 Groth16 被证明在对称和非对称配对设置中都是安全的,因此在本文的其余部分,我们将假设 CRS 中的所有内容都在同一个群 $G_1$ 中:
$$e([a]_1,[b]_1)=[ab]_T$$
由于我真的想让我们抽象化配对,只需假设当你看到两个承诺 $[a]\cdot[b]$ 之间的乘法时,我是在使用配对。

好的,现在我们对方案有了初步的草图。可信设置生成以下 CRS:
$$CRS={[u_i(x)], [v_i(x)], [w_i(x)]}_i$$
证明者使用这个承诺生成三个证明元素:
然后验证者可以使用配对执行检查 $A\cdot B=C+[t(x)][q(x)]$,并检查左边得到的承诺是否等于右边得到的承诺。
注意
为了在目标群中获得所有内容,承诺 $C$ 也必须参与配对。因此验证者必须执行 $e([C],[1])$ 或 $e([1],[C])$ 将 $C$ 移动到目标群。
Groth16 使用第一个配对($C$ 是左参数),因为当使用像 BN254 这样的曲线实例化时,$G_1$ 群元素的大小是 $G_2$ 群元素的一半。这个决定使证明更短!
现在我们需要谈谈我们还没有做的事情:
证明者是否在所有证明点上使用了相同的 $a_i$(见证)?
证明者是否真的在 $A$ 中产生了 $u_i$(在 $B$ 中产生 $v_i$,在 $C$ 中产生 $w_i$?)
公共输入呢?还有第一个值 $a_0=1$ 呢?
别担心,我们将一一回答所有问题。
重要的是,此时我们必须假设证明者在构造其证明元素时受到限制。虽然它可以生成对自己随机值的承诺,但它不能提取 CRS 的元素,也不能自由地相乘它们。
因此,证明元素 $A,B,C$ 总是被视为 CRS 元素的线性组合(你也可以想象 $[1]$ 是 CRS 的一部分)。
这基本上就像使用 LEGO 积木!如果 CRS 是一个 LEGO 积木盒,那么证明者在创建证明元素时只能使用被给予的积木块。
注意
这个比喻非常贴切,因为 Groth16 展示整个方案安全性的方式 大致如此:证明者点 $A,B,C$ 是基于 CRS 中的 LEGO 积木块构建的,通过观察验证者检查的行为,可以想象在证明者点的构造中必须使用了哪些 LEGO 积木块。
现在让我们解释如何在验证者方程中生成 $[t(x)]$ 和 $[q(x)]$。
我们可以简单地将 $x$ 的幂次($[1],[x],[x^2],\cdots$)作为 CRS 的一部分添加进去,这样验证者可以自己生成 $[t(x)]$,而证明者可以生成 $[q(x)]$。
这差不多就是我们要做的,但注意到由于消失多项式 $t(x)$ 完全由电路决定,我们可以在设置期间预计算它,并将 $[t(x)]$ 直接添加到 CRS 中。
但 Groth16 更进一步,它发布了足够的 LEGO 积木块,让证明者能够直接从 CRS 生成完整的 $[t(x)q(x)]$ 多项式:
$$CRS+={[t(x)], [t(x)x], [t(x)x^2], \cdots, [t(x)x^{n-2}]}$$
所以现在,我们可以想象证明者给我们一个额外的证明点 $D=[t(x)q(x)]$,但别习惯它,因为稍后我们会将其移到 $C$ 中。

让我们再次审视我们的验证者方程和 CRS。
$$CRS={[u_i(x)], [v_i(x)], [w_i(x)]}_i\cup{[t(x)], [t(x)x], [t(x)x^2], \cdots, [t(x)x^{n-2}]}$$
然后我们有一个现在看起来像这样的验证者“QAP 检查”:
$$A\cdot B=C+D$$
其中 $D$ 按照上一节所述计算为 $[t(x)q(x)]$。
我们的 LEGO 比喻在这里有点失效,因为我们引入的这个新证明元素 $D$ 可以使用 CRS 中的任何东西,而不仅仅是我们希望它使用的积木块。
这里的解决方案是使用分隔因子。
分隔因子的工作方式如下:在可信设置期间,生成一个随机值供验证者使用,然后将其倒数与我们要分隔的 LEGO 积木块关联起来。
在我们的例子中,让我们在可信设置期间生成 $[\delta]$(一个对某些无人知晓的随机 $\delta$ 的承诺),并修改我们的 CRS,将所有商多项式 LEGO 积木块乘以 $\delta^{-1}$:
$$CRS={[u_i(x)], [v_i(x)], [w_i(x)]}_i\cup{[\delta]}\cup\left{\left[\frac{t(x)x^i}{\delta}\right]\right}_i$$
最后,让我们修改验证者检查,从证明元素 $D$ 中移除 $\delta^{-1}$:
$$A\cdot B=C+D\cdot[\delta]$$
这可行!$D$ 不能使用 CRS 中的任何其他项作为 LEGO 积木块(否则它们不会被“标准化”,$D$ 就会乱套)。另一方面,$A$、$B$ 和 $C$ 如果没有引入这个讨厌的 $\delta^{-1}$ 项,就不能使用 $\left{\left[\frac{t(x)x^i}{\delta}\right]\right}_i$ 中的 LEGO 积木块。
不过 $A$、$B$ 和 $C$ 还有其它问题……它们不必使用相同的见证 $a_i$,并且可以使用 $u_i$、$v_i$ 或 $w_i$ 中的任意承诺。接下来我们来处理这个问题。
好的,请记住三个点 $A,B,C$ 是这样计算的:
现在我们不仅要检查它们是否使用了相同的 $a_i$(相同的线性组合),还要检查它们是否使用了 CRS 中正确的元素 $u_i,v_i,w_i$。此时我们可以使用分隔因子,但 Groth16 做了一些不同的事情。
密码学中一切的秘诀就是对事物进行随机线性组合。
让我们尝试一下,看看会发生什么。在三个证明元素上使用随机值 $\alpha$ 和 $\beta$:
$$\beta A+\alpha B+C$$
我们得到,大概,如果证明者正确生成了这些点,它应该看起来像这样:
$$\beta\left(\sum_i a_i [u_i(x)]\right)+\alpha\left(\sum_i a_i [v_i(x)]\right)+\sum_i a_i [w_i(x)]$$
哦,但注意,我们可以用 $a_i$ 在外面的形式重写它:
$$\sum_i a_i\left(\beta [u_i(x)]+\alpha [v_i(x)]+[w_i(x)]\right)$$
这看起来不像我们可以作为验证者检查的东西吗?
所以让我们添加一个新的见证一致性检查(除了我们之前的 QAP 检查之外)。
但是,由于我们不希望证明者创建包含这些挑战的倒数的证明点,因此这些挑战必须作为承诺引入 CRS 中。
$$[\beta]A+[\alpha]B+C=\sum_i a_i \cdot \left([\beta][u_i(x)]+[\alpha][v_i(x)]+[w_i(x)]\right)$$
右边有很多东西可以在 CRS 中预计算……如果我们将这些新的承诺引入 CRS,我们可以让证明者为我们生成右边,而不会泄露任何见证值 $a_i$。所以我们可以将这些添加到 CRS 中:
$$CRS+=\left{\left[\beta u_i(x)+\alpha v_i(x)+w_i(x)\right]\right}_i$$
并让证明者生成一个新的证明点(我们稍后会去掉它……耐心点)
$$E=\sum_i a_i\left[\beta u_i(x)+\alpha v_i(x)+w_i(x)\right]$$
当然,我们必须使用另一个分隔因子 $\epsilon$ 来强制 $E$ 只使用正确的 LEGO 积木块。所以我们将 CRS 的最后一次更新固定为:
$$CRS+=\left{[\epsilon]\right}\cup\left{\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\epsilon}\right]\right}_i$$
现在我们的验证者执行两个检查:
我们不仅检查了证明元素中是否使用了相同的见证 $a_i$,还强制 $A$、$B$ 和 $C$ 具有特定的形状(分别是 $u_i$、$v_i$ 和 $w_i$ 的组合,这些是完全描述我们电路的值)。
回顾一下,我们的 CRS 现在看起来像这样:
$$CRS={[u_i(x)], [v_i(x)], [w_i(x)]}_i\cup{[\delta],[\epsilon]}\cup\left{\left[\frac{t(x)x^i}{\delta}\right]\right}_i\cup\left{\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\epsilon}\right]\right}_i$$
注意,在见证一致性检查中,有来自 QAP 检查的 $C$,我们可以将其重写为 $C=A\cdot B-D\cdot[\delta]$
让我们将其代入见证一致性检查,得到:
$$[\beta]A+[\alpha]B+A\cdot B=D\cdot[\delta]+[\epsilon]E$$
现在这是一个单一的检查! 我们现在正在检查 $A$ 和 $B$ 以及 $A\cdot B$ 具有正确的形状。
我们可以做得更好,注意到左边看起来像 $([\alpha]+A)([\beta]+B)$ 的乘法,只是缺少了一项 $([\alpha][\beta])$。让我们将其添加到检查的两边:
$$[\alpha][\beta]+[\beta]A+[\alpha]B+A\cdot B=[\alpha][\beta]+D\cdot[\delta]+[\epsilon]E$$
现在左边可以重写为更简单的乘法
$$([\alpha]+A)([\beta]+B)=[\alpha][\beta]+D\cdot[\delta]+[\epsilon]E$$
由于 $[\alpha]$ 和 $[\beta]$ 都是 CRS 的一部分,只需检查
$$A\cdot B=[\alpha][\beta]+D\cdot[\delta]+[\epsilon]E$$
并让证明者从一开始就生成具有正确形状的 $A$ 和 $B$:
最后一件事:注意到没有理由分隔 $E$ 和 $D$ 的 CRS LEGO 积木块,因此我们可以简化分隔因子 $\epsilon=\delta$,并将 $E$ 和 $D$ 都合并到我们新的 $C$ 证明元素中(我们想念你了 $C$!)。
$$CRS={[\alpha],[\beta],[\delta]}\cup{[u_i(x)],[v_i(x)],[w_i(x)]}_i\cup\left{\left[\frac{t(x)x^i}{\delta}\right]\right}_i\cup\left{\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\delta}\right]\right}_i$$
现在验证者检查是
$$A\cdot B=[\alpha][\beta]+C[\delta]$$
其中:
我们一直有点忽略公共输入,但它们很重要!验证者不仅要确保 $a_0=1$(正如我们之前讨论的,以确保我们可以在电路中添加常数),还要固定作为公共输入一部分的 $a_i$。
Groth16 通过在验证者检查中固定前 $l+1$ 个(如果我们有 $l$ 个公共输入)来实现这一点:
$$A\cdot B=[\alpha][\beta]+\sum_{i=0}^{\ell} a_i\left[\beta u_i(x)+\alpha v_i(x)+w_i(x)\right]+C[\delta]$$
但现在注意,证明者仍然可以通过包含 CRS 中仍然可用的 $\left[\beta u_i(x)+\alpha v_i(x)+w_i(x)\right]$ 项(对于 $i\le \ell$)来干扰公共输入!
别慌,我们仍然可以使用我们的分隔因子来分隔这些项。我们的 CRS 之前有:
$$\left{\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\delta}\right]\right}_i$$
我们将其替换为
$$\left{\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\delta}\right]\right}{i>\ell}\cup\left{\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\gamma}\right]\right}{i\le\ell}\cup[\gamma]$$
并将验证者方程改为
$$A\cdot B=[\alpha][\beta]+\gamma\cdot\sum_{i=0}^{\ell}a_i\left[\frac{\beta u_i(x)+\alpha v_i(x)+w_i(x)}{\gamma}\right]+C[\delta]$$
瞧!这就是我们一开始的那个方程,希望你明白它是如何工作的了 :)
哦对了,我有点跳过了这部分。展示如何向方案添加零知识性是相当容易的,只需让证明者生成随机数 $r$ 和 $s$,并生成证明元素如下:
这有效地以不可预测的方式随机化了 $A$ 和 $B$(使它们看起来完全随机),并在 $C$ 中抵消了变化($C$ 由 $A$ 和 $B$ 唯一确定)。
为什么这就足够了,留给你思考。
zkSecurity 为密码学系统提供审计、研究和开发服务,包括零知识证明、MPC、FHE 和共识协议。
- 原文链接: blog.zksecurity.xyz/post...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码