本文深入解析了 Groth16 协议,这是一种用于在不泄露敏感信息的情况下证明计算正确性的框架。文章详细介绍了将程序转换为算术电路或等效的 R1CS 的步骤,然后将其编译为二次算术程序,解释了协议如何转换基本方程以确保证明者无法作弊,验证者无法了解有关私有数据的任何信息。
在过去的十年里,SNARKs(简洁的、非交互的知识论证)和 STARKs(可扩展的、透明的知识论证)由于它们在可验证的私有计算和区块链的可扩展性方面的应用而备受关注。
Groth 在 2016 年介绍了这个 证明系统,并在 ZCash 中看到了早期的应用。该协议依赖于 pairing-friendly elliptic curves(配对友好的椭圆曲线),例如 BN254、BLS12-381 和 BLS12-377(稍后会详细介绍)。它的证明大小是最小的(仅由三个椭圆曲线元素组成),并且验证速度最快。主要的缺点是它需要每个程序都进行 trusted setup(可信设置)。换句话说,每当我们想证明一个新程序(或更改原始程序)时,都需要重新生成所有参数。
在这篇文章中,我们将描述 Groth16 的主要成分以及它的工作原理。正如路线图中所述,我们正在 Lambdaworks 库 中实现该协议。它也用作正在进行的 Sparkling Water Bootcamp 中的一个项目。
为了证明给定程序的执行,我们必须将其转换为 SNARK(简洁的、非交互的知识论证)友好的形式。其中一种形式是 arithmetic circuit satisfiability(算术电路可满足性),在这种形式中,可以证明对有效电路赋值的了解。第一步,称为 arithmetization(算术化),是将程序转换为算术电路或等效形式。
算术电路可以等效地表示为(二次)rank one constraint systems (R1CS)(秩一约束系统),它是以下形式的方程组:
(Az)×(Bz)=Cz(Az)×(Bz)=Cz
其中 A,B,CA,B,C 是大小为 m+1m+1 行、n+1n+1 列的矩阵,zz 是大小为 n+1n+1 的(列)向量,而 ×× 表示结果向量的按分量乘积。
我们可以选择将此紧凑形式视为
(∑ka0kzk)(∑kb0kzk)−(∑kc0kzk)=0(∑ka0kzk)(∑kb0kzk)−(∑kc0kzk)=0
(∑ka1kzk)(∑kb1kzk)−(∑kc1kzk)=0(∑ka1kzk)(∑kb1kzk)−(∑kc1kzk)=0
(∑ka2kzk)(∑kb2kzk)−(∑kc2kzk)=0(∑ka2kzk)(∑kb2kzk)−(∑kc2kzk)=0
⋮⋮
(∑kamkzk)(∑kbmkzk)−(∑kcmkzk)=0(∑kamkzk)(∑kbmkzk)−(∑kcmkzk)=0
通过使用多项式,我们可以更紧凑地表达这些方程,并更简洁地证明 R1CS 系统的解。为此,我们将介绍 quadratic arithmetic programs, QAP(二次算术程序)。
我们可以将 AA 矩阵的每一列解释为某些多项式在某些合适的域上的求值。这是许多 SNARK 中常见的做法,我们尝试将向量编码为多项式;例如,请参阅我们关于 STARKs 的文章。我们从有限域中采样 D0=x0,x1,...,xnD0=x0,x1,...,xn,并将多项式 Ai(x)Ai(x) 定义为最多 nn 次的多项式,使得 Ai(xk)=akiAi(xk)=aki。
出于性能原因,选择插值域 D0D0 作为单位的 n 次根是很方便的,因为我们可以使用快速傅里叶变换进行插值。类似地,我们可以将 BB 和 CC 的列解释为多项式 Bk(x)Bk(x) 和 Ck(x)Ck(x)。利用这些多项式,我们可以用多项式形式表示 R1CS 系统,
P(x)=(∑kAk(x)zk)(∑kBk(x)zk)−(∑kCk(x)zk)P(x)=(∑kAk(x)zk)(∑kBk(x)zk)−(∑kCk(x)zk)
我们可以看到,如果我们有一个有效的 R1CS 解,则多项式 P(x)P(x) 在 D0D0 上求值为 00(因为我们要求多项式插值矩阵列的值)。因此,我们可以将条件表示为
P(x)=0P(x)=0 对于 x∈D0x∈D0
现在,我们引入集合 D0D0 上的 vanishing polynomial(消失多项式),ZD(x)=∏k(x−xk)ZD(x)=∏k(x−xk)
因此,如果多项式 P(x)P(x) 在 D0D0 上求值为 00,则它可以被 ZD(x)ZD(x) 整除。这可以写成存在某个多项式 h(x)h(x) 使得
P(x)=h(x)ZD(x)P(x)=h(x)ZD(x)
多项式 h(x)h(x) 的次数是 PP 的次数减去 ZDZD 的次数。一个诚实的证明者应该能够找到结果商,并用它来表明他正确地执行了该程序。
如果我们要将其转换为零知识证明,我们需要对上述问题进行一些转换。有关此过程的更详细说明,请参见此处。我们必须确保证明者不能作弊,并且验证者无法了解有关私有输入或 witness(证据)的任何信息。一个关键成分是 polynomial commitment scheme (PCS)(多项式承诺方案):我们可以使证明者承诺给定的多项式,以便他以后无法更改它。一种这样的承诺方案是 KZG commitment(KZG 承诺),在其中我们使用 pairing-friendly elliptic curves(配对友好的椭圆曲线)将证明者绑定到多项式。该方案的安全性取决于曲线上离散对数问题的难度。配对可以被认为是一种允许椭圆曲线中点之间一次性乘法的运算。在我们的例子中,我们将在 type3 III pairings( III 型配对), †:G1×G2→Gt†:G1×G2→Gt 上工作,它具有以下优良性质(双线性):
(ag1)†(bg2)=(ab)(g1†g2)(ag1)†(bg2)=(ab)(g1†g2)
要使用 KZG 承诺来承诺一个多项式,我们需要采样一个随机标量 ττ(它被认为是 toxic waste(有毒垃圾),应该被遗忘,否则我们可以伪造证明)并生成椭圆曲线中的以下点序列,其生成器为 g1g1,
P0=g1P0=g1,
P1=τg1P1=τg1
P2=τ2g1P2=τ2g1
⋮⋮
Pn=τng1Pn=τng1
然后,给定一个多项式 p(x)=a0+a1x+a2x2+...+anxnp(x)=a0+a1x+a2x2+...+anxn 我们计算承诺为
cm(p)=a0P0+a1P1+...+anPncm(p)=a0P0+a1P1+...+anPn
这与 cm(p)=p(τ)g1cm(p)=p(τ)g1 相同,也就是说,将 p(x)p(x) 的求值隐藏在椭圆曲线内。由于离散对数问题很难,我们无法使用我们对 g1g1 和 cm(p)cm(p) 的了解来获得 p(τ)p(τ)。
要检查多项式 p(x)p(x) 在 zz 处求值为 vv,我们可以使用以下事实
p(x)−v=(x−z)q(x)p(x)−v=(x−z)q(x)
其中 q(x)q(x) 是 p(x)p(x) 除以 x−zx−z 的商多项式。证明者可以使用相同的技巧承诺 q(x)q(x) 来生成此类求值的证明。但是,验证者仍然需要一些附加信息(包含在 verifying key(验证密钥)中),g2g2(组 G2G2 的生成器)和 τg2τg2(记住,没有人必须知道 ττ)。然后,使用配对,验证者可以使用椭圆曲线中的点来检查求值,
(cm(p)−vg1†g2)=a=p(τ)(g1†g2)(cm(p)−vg1†g2)=a=p(τ)(g1†g2)
cm(q)†(τg2−zg2)=b=q(τ)(τ−z)(g1†g2)cm(q)†(τg2−zg2)=b=q(τ)(τ−z)(g1†g2)
如果 aa 和 bb 相同,并且由于 ττ 是一个具有高概率的随机点,我们假设 p(z)=vp(z)=v (这取决于 Schwartz-Zippel 引理)。
记住,我们想证明验证者知道一些 ww 和一个 m−1m−1 次多项式 h(x)h(x),这样,如果 z=(1,x,w)z=(1,x,w), 则以下条件成立
(∑kAk(x)zk)(∑kBk(x)zk)=(∑kCk(x)zk)+h(x)ZD(x)(∑kAk(x)zk)(∑kBk(x)zk)=(∑kCk(x)zk)+h(x)ZD(x)
如果我们首先强制证明者承诺多项式 Ak(x)Ak(x) 和 Bk(x)Bk(x),然后生成商多项式,我们必须确保他不能伪造 Ck(x)Ck(x) 来满足先前的条件。为此,我们将引入随机位移(αα 和 ββ)到求值:
cm(∑Aizi)=∑(Ai(τ)zi)g1+αg1cm(∑Aizi)=∑(Ai(τ)zi)g1+αg1
cm(∑Bizi)=∑(Bi(τ)zi)g2+βg2cm(∑Bizi)=∑(Bi(τ)zi)g2+βg2
Bi(x)Bi(x) 使用组 G2G2 进行承诺,以便我们可以通过配对计算左侧的乘积,
(cm(∑Aizi))†(cm(∑Bizi))=(∑Ai(τ)zi)(∑Bi(τ)zi)(g1†g2)(cm(∑Aizi))†(cm(∑Bizi))=(∑Ai(τ)zi)(∑Bi(τ)zi)(g1†g2)
因为我们引入了这些位移,所以我们需要相应地修改 CkCk 项,
(α+∑kAk(x)zk)(β+∑kBk(x)zk)=αβ+(∑k(Ck(x)+βAk(x)+αBk(x))zk)+h(x)ZD(x)(α+∑kAk(x)zk)(β+∑kBk(x)zk)=αβ+(∑k(Ck(x)+βAk(x)+αBk(x))zk)+h(x)ZD(x)
由于证明者无法知道 αα 和 ββ,我们需要将它们隐藏起来作为 trusted setup(可信设置)的一部分提供,如 αg1αg1 和 βg2βg2,这样我们就可以计算
(αg1)†(βg2)=αβ(g1†g2)(αg1)†(βg2)=αβ(g1†g2)
这样我们就可以将此结果与移位的 AiAi 和 BiBi 之间的配对进行比较。
另外,由于证明者没有 αα 和 ββ,因此需要向他提供 Ck(x)+βAk(x)+αBk(x)Ck(x)+βAk(x)+αBk(x) 形式的所有元素。但是,当我们想要计算这些项和 zz 之间的乘积时,我们必须记住 zz 包含 public input(公共输入)和 witness(证据)。验证者无法了解有关 witness(证据)的任何信息(因此,涉及 witness(证据)的求值应由证明者提供)。我们引入两个附加变量 γγ 和 δδ,以在 public input(公共输入)和 witness(证据)之间分割变量 zz。前 kk 项对应于 public input(公共输入),这些项编码为
Kvi=γ−1(Ci(τ)+βAi(τ)+αBi(τ))g1Kiv=γ−1(Ci(τ)+βAi(τ)+αBi(τ))g1
对于 i=0,1,2...,ki=0,1,2...,k。对于 witness(证据),我们有
Kpi=δ−1(Ci(τ)+βAi(τ)+αBi(τ))g1Kip=δ−1(Ci(τ)+βAi(τ)+αBi(τ))g1
使用这些新参数,我们得到
(α+∑jAj(x)zj)(β+∑jBj(x)zj)=αβ+γ(∑kiγ−1(Ci(x)+βAi(x)+αBi(x))xi)+δ(∑nj=k+1δ−1(Ci(x)+βAi(x)+αBi(x))xi)+h(x)ZD(x)(α+∑jAj(x)zj)(β+∑jBj(x)zj)=αβ+γ(∑ikγ−1(Ci(x)+βAi(x)+αBi(x))xi)+δ(∑j=k+1nδ−1(Ci(x)+βAi(x)+αBi(x))xi)+h(x)ZD(x)
我们可以将最后两个项合并为一个(因为它们包含验证者不得学习的所有信息)
D=(∑nj=k+1δ−1(Ci(x)+βAi(x)+αBi(x))xi)+h(x)ZD(x)δ−1D=(∑j=k+1nδ−1(Ci(x)+βAi(x)+αBi(x))xi)+h(x)ZD(x)δ−1
由于我们想借助一个配对来计算乘积 h(x)ZD(x)h(x)ZD(x),我们可以计算以下组元素,
Z0=δ−1ZD(τ)Z0=δ−1ZD(τ)
Z1=δ−1τZD(τ)Z1=δ−1τZD(τ)
Z2=δ−1τ2ZD(τ)Z2=δ−1τ2ZD(τ)
⋮⋮
Zm−1=δ−1τm−1ZD(τ)Zm−1=δ−1τm−1ZD(τ)
通过这些更改,QAP 的右侧是 3 个项的总和:
一个常数(与随机位移有关)。
涉及 public input(公共输入)的项。
一个包含秘密项(只有证明者知道)的项。
Groth16 需要采样五个随机域元素来生成 proving key(证明密钥)和 verifying key(验证密钥),即 t,α,β,γ,δt,α,β,γ,δ。这些都是 toxic waste(有毒垃圾),一旦密钥生成后,就应该丢弃并完全遗忘。
我们将使用 pairing-friendly elliptic curve(配对友好的椭圆曲线)(带有 III 型配对),其子组 G1G1 和 G2G2 具有素数阶 rr。我们将分别调用生成器 g1g1 和 g2g2。为了使符号更易于使用,我们将编写
[x]1=xg1[x]1=xg1
[x]2=xg2[x]2=xg2
表示 G1G1 和 G2G2 中的点,其中 xgxg 表示 xx 和组的生成器的标量积(即,将椭圆曲线组运算应用于生成器 x 次)。我们将遵循 DIZK 给出的符号。首先,我们计算以下向量,
Kvi(t)=γ−1(βAi(t)+αBi(t)+Ci(t))Kiv(t)=γ−1(βAi(t)+αBi(t)+Ci(t))
对于 i=0,1,2,...ki=0,1,2,...k,
Kpi(t)=δ−1(βAi(t)+αBi(t)+Ci(t))Kip(t)=δ−1(βAi(t)+αBi(t)+Ci(t))
对于 i=k+1,1,2,...ni=k+1,1,2,...n 和
Zk(t)=tkZD(t)δ−1Zk(t)=tkZD(t)δ−1
对于 k=0,1,2,...m−1k=0,1,2,...m−1。
proving key(证明密钥)由以下元素组成:
verifying key(验证密钥)短得多,除外,验证密钥还会包含一个配对的值,因为该值是常数:
证明者收到 proving key(证明密钥),并且知道表示程序和 public input(公共输入)的多项式,并且他想证明他具有满足该程序的 witness(证据)。首先,证明者需要计算商多项式 h(x)h(x),或者更准确地说,是计算它的系数。证明者必须计算
h(x)=∑Ak(x)zk∑Bk(x)zk−∑Ck(x)zkZD(X)h(x)=∑Ak(x)zk∑Bk(x)zk−∑Ck(x)zkZD(X)
评估此商的最佳方法是选择一个域 DevDev,其大小至少是商多项式的次数加 1,并且不包含来自 D0D0(插值域)的元素,并在 DevDev 的所有元素上评估分子和分母。由于我们至少具有与多项式 h(x)h(x) 的次数加 1 一样多的多项式 h(x)h(x) 的评估,因此我们可以通过插值来重构 h(x)h(x)。在实践中,最快的方法是使用快速傅里叶变换进行评估和插值。证明者现在拥有一个系数向量 h0,h1,h2,...,hmh0,h1,h2,...,hm。
为了确保 proof(证明)是零知识的,证明者对两个随机标量 rr 和 ss 进行采样。
证明者可以通过执行以下计算来计算 proof(证明)的三个元素,π=([π1]1,[π2]2,[π3]1)π=([π1]1,[π2]2,[π3]1),
[π1]1=[α]1+∑zk[Ak(t)]1+r[δ]1[π1]1=[α]1+∑zk[Ak(t)]1+r[δ]1
[π2]2=[β]2+∑zk[Bk(t)]2+s[δ]2[π2]2=[β]2+∑zk[Bk(t)]2+s[δ]2
[π2]1=[β]1+∑zk[Bk(t)]1+s[δ]1[π2]1=[β]1+∑zk[Bk(t)]1+s[δ]1
[h(t)z(t)]1=∑hi[Zi(t)]1[h(t)z(t)]1=∑hi[Zi(t)]1
[π3]1=∑wi[Kpi]1+[h(t)z(t)]1+s[π1]1+r[π2]1−rs[δ]1[π3]1=∑wi[Kip]1+[h(t)z(t)]1+s[π1]1+r[π2]1−rs[δ]1
验证者拥有 verifying key(验证密钥)和 public input(公共输入),并将 proof(证明)解析为 [π1]1,[π2]2,[π3]1[π1]1,[π2]2,[π3]1 并计算以下内容:
[π1]1†[π2]2=P1[π1]1†[π2]2=P1
[π3]1†[δ]2+[α]1†[β]2+(∑xi[Kvi]1)†[γ]2=P2[π3]1†[δ]2+[α]1†[β]2+(∑xi[Kiv]1)†[γ]2=P2
如果 P1P1 和 P2P2 一致,则 proof(证明)有效。这相当于检查修改后的 QAP。
在这篇文章中,我们介绍了 Groth16 协议,该协议提供了一个框架来证明计算的正确性,而无需透露敏感信息。它具有简洁的 proof(证明)和优雅的验证,但对于我们要证明的每个程序都需要 trusted setup(可信设置)。我们看到了将程序转换为 arithmetic circuits(算术电路)或其等效的 R1CS 的步骤,然后可以将其编译为 quadratic arithmetic program(二次算术程序)。我们解释了该协议如何转换基本方程,以确保证明者无法作弊,并且验证者不会了解有关 private data(私有数据)的任何信息。在即将发布的文章中,我们将介绍如何从头开始编码 Groth16。
- 原文链接: blog.lambdaclass.com/gro...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!