Skip to content

Groth16

Ref

Arithmetization 算数化

对于经过算数化的电路,我们可以表示为 Oω=LωRω

其中,O,L,Rn×m 的矩阵,ω 是一个 m×1 的约束向量,表示电路的输入和中间变量。n 是约束的数量, m 是所有变量的数量。(ω, witness)

记:

ω=[a1,a2,a3,...,am]u(x)=Lω=[u1(x),u2(x),u3(x),...,un(x)]ωT

这里的角标是指矩阵列插值得出的多项式。

其他同理。

QAP

对于验证矩阵的正确性,我们可以很自然地想到把矩阵通过插值计算为多项式形式,然后通过 Schwartz-Zippel 引理 来验证其正确性。这里有 n 个约束,故而可以构造出三个 n1 次的多项式:

u(x)v(x)=w(x)

其中:

u(x)=Lω=i=1maiui(x)v(x)=Rω=i=1maivi(x)w(x)=Oω=i=1maiwi(x)

但是这里观察到左边的次数是 2(n1),而右边的次数是 n1,两边次数不匹配,使得等式只能在 n 个点上成立(Schwartz-Zippel)。为了解决这个问题,我们引入了一个特殊的多项式 t(x)=(x1)(x2)(x3)...(xn), 称为目标多项式(target polynomial), 这个等式在 x(0,n)时为0不影响计算,在范围之外可以约束等式使其相等,最终可以使得:

u(x)v(x)w(x)=h(x)t(x)

[u(x),v(x),w(x)], n1 次;t(x) , n 次;h(x) , n2 次。故而 |左边 2(n1)=n+n2 右边|。 这样就保证了等式在任意取值上成立而不是仅在 n 个点上成立。

从而,通过公开的 L,R,O,验证者可以知道 Prover 的运算程序,而 ω (用户的输入和中间的运算轨迹)是私有的。但是, Verifier 仍然可以通过验证 u(x)v(x)w(x)=h(x)t(x) 来保证 Prover 的计算是正确的(可验证计算)。

这样,我们就有一个初步的 Sigma 协议雏形:

  1. Prover 对自己的多项式做出承诺 Commit 传输给 Verifier
  2. Verifie 发送随机值 rProver.
  3. Prover 计算出多项式 u(x),v(x),Verifier
  4. Verifier 进行验证。

接下来,我们的目标就是如何确保 Provr 在第三部时确实是根据正确的多项式进行运算的。

利用SRS计算多项式

以下对于 GT 的加法都是乘法

这里我们使用可信设置(多方安全计算)生成一个秘密值 τ, 再计算公共参考字符串(SRS),这里需要确保 τ 在用后即销毁。

我们有:

SRSG1=([1]1,[τ]1,[τ2]1,...,[τm1]1)SRSG2=([1]2,[τ]2,[τ2]2,...,[τm1]2)

Note that [u(x),w(x),t(x),h(x)]G1, v(x)G2

验证 SRS 的正确性

记住我们同时给出了 [1]1,[τ]1,[τ2]1,...,[τm1]1[1]2,[τ]2

证明生成 τG2=[τ]2 的正确性:e([1]1,[τ]2) = e([τ]1,[1]2)

证明生成 τiG1=[τi]1 的正确性:e([τi]1,[1]2) = e([1]1,[τi]2)

这样,当我们删除 τ 后,我们就可以得到一个公共的参考字符串,任何人都可以使用这个字符串来计算多项式的评估值,同时还能保证没人能够知道计算评估值 f(τ) 时具体计算的是哪个点的取值,并且通过配对来验证多项式等式。

对于多项式的计算,例如对于点 τ 的评估,可以写为:

f(τ)=a0[1]1+a1[τ]1+a2[τ2]1+...+ad[τd]1

其中 ai 是多项式 f(x) 的系数,对于在 G2 上的多项式评估同样适用:

f(τ)=a0[1]2+a1[τ]2+a2[τ2]2+...+ad[τd]2

使用SRS计算QAP多项式

我们的目标是计算:

u(x)v(x)=w(x)+h(x)t(x)

此时的问题是,我们希望 rhsG1 上的元素,但是计算 h(x)t(x) 需要长度为 2(n1) 的SRS。

这里我们考虑到 t(x) 是已知并且固定的,故而我们可以先计算 t(τ) 的评估值:

  1. 由于我们预先知道 t(x), 并且在可信设置我们可以提前先计算 t(τ) 再丢弃 τ(这里是可信设置时直接计算的,不是低度扩展),得到:
SRSt(τ)=(Y0,Y1,...,Yn2)=(t(τ)[1]1,t(τ)[τ]1,t(τ)[τ2]1,...,t(τ)[τn2]1)
  1. 从而有如下等式关系可以容易得到 h(τ) :(这里的 hh 的系数)
h(τ)t(τ)=(h0,h1,h2,...,hn2)(Y0,Y1,Y2,...,Yn2)

证明:

h(τ)t(τ)=(h0,h1,h2,...,hn2)(t(τ)[1]1,t(τ)[τ]1,t(τ)[τ2]1,...,t(τ)[τn2]1)=(h0,h1,h2,...,hn2)([1]1,[τ]1,[τ2]1,...,[τn2]1)t(τ)=h(τ)t(τ)
此时我们有:

  • SRSSRS1SRS2SRSt(τ)

  • Prover: [A]1=u(τ),[B]2=v(τ),[C]1=w(τ)+h(τ)t(τ)

  • Verifier: 需要验证 e([A]1,[B]2)=e([C]1,[1]2)

NOTE HERE

引入随机性,保证QAP正确性

之后,我们需要在可信设置随机生成两个秘密标量,α&β,并且投射到曲线上,有:

[α]1&[β]2

这时我们修改 [A]1[B]2 的计算方式,令:

1=[α]1+u(τ)[B]1=[β]2+v(τ)

并且为了同时使得上面的等式仍然成立 Here,修改 [C]1 为:

αv(τ)+βu(τ)+w(τ)+h(τ)t(τ)

需要验证的式子变为:

e([A]1,[B]2)=e([α]1,[β]2)+e([C]1,G2)

易证:

e([A]1,[B]2)=e([α]1+u(τ),[β]2+v(τ))=e([α]1,[β]2)+e([α]1,v(τ))+e(u(τ),[β]2)+e(u(τ),v(τ))=e([α]1,[β]2)+e([α]1,v(τ))+e(u(τ),[β]2)+e(w(τ)+h(τ)t(τ),G2)=e([α]1,[β]2)+e([C]1,G2)

这样变换 [C]1 的原因很显然,把配对操作从 4 次减少为了两次。

但是这里又出现了一个问题,计算 [C]1 时我们需要 αβ 的值,故而方法也很显然,跟前面计算h(τ)v(τ)时一样,我们在进行可信设置时预计算。

这里先把多项式展开回上面的形式:

u(x)=i=1maiui(x)v(x)=i=1maivi(x)w(x)=i=1maiwi(x)

注意到对于 Prover 之外的人来说,ai 是不可见的( ω=[a1,a2,a3,...,am] 是私有数据),有且只有 ui(x),vi(x),wi(x) 可见。

再回顾一下我们的目标:计算 [C]1=αv(τ)+βu(τ)+w(τ)+h(τ)t(τ)

故而在可信设置时,我们还需计算:

i=1m[αvi(τ)+βui(τ)+wi(τ)]

然后再由 Prover 计算:

i=1mai[αvi(τ)+βui(τ)+wi(τ)]

从而,通过可信设置(类似于承诺),我们可以确保 Prover 在生成证据时,使用的是正确的程序(QAP),这也解释了为什么在SnarkJS中Groth16要再Contribute一次,而Plonk不用。

说明

对于要验证的 e([A]1,[B]2)=e([α]1,[β]2)+e([C]1,G2),展开得到:

lhs=e([α]1,[β]2)+e([α]1,v(τ))+e(u(τ),[β]2)+e(u(τ),v(τ))rhs=e([α]1,[β]2)+e(αv(τ)+βu(τ)+w(τ)+h(τ)t(τ),G2)
  • 注意到可信设置确保了 e([α]1,v(τ))+e(u(τ),[β]2)=e(αv(τ)+βu(τ),G2) 的不可伪造性,对于 rhs 来说 Prover 不可能在使用错误的 v(τ)||u(τ) 下计算得出目标
  • 而对于:e(u(τ),v(τ))=e(w(τ)+h(τ)t(τ),G2) 来说,确保了计算的正确性
此时我们有

  • SRS:

    • random α,β,τ
    • SRS1([1]1,[τ]1,[τ2]1,...,[τm1]1)
    • SRS2([1]2,[τ]2,[τ2]2,...,[τm1]2)
    • SRSt(τ)(t(τ)[1]1,t(τ)[τ]1,t(τ)[τ2]1,...,t(τ)[τn2]1)
    • [ψi]1=[αvi(τ)+βui(τ)+wi(τ)]1
  • Public

    • [α]1,[β]2,SRS1,SRS2,SRSt(τ),[ψi]1
  • Prover

    • [A]1=[α]1+u(τ)[B]1=[β]2+v(τ)
    • [C]1=i=1mai[ψi]1+h(τ)t(τ)
  • Verifier

    • e([A]1,[B]2)=e([α]1,[β]2)+e([C]1,G2)

支持公开输入

在Circom中,我们可以通过 signal public 来声明公开输入,例如下面的Hash代码:

js
template Hash() {
    signal input a;
    signal input b;
    signal input value;
    signal output c;
    signal 

    conponent hasher = Poseidon(2);
    hasher.inputs[0] <== a;
    hasher.inputs[1] <== b;
    c <== hasher.out;
    value === c;

    log("hash: ", c);
}

component main{ public [a, value] } = Hash();

目前为止,我们所有的 ω(witness)都是私有的,但是我们可以在计算 [C]1 时把公开输入的部分单独拿出来计算,这里假设我们只公开前 l 个元素,那么我们可以把 [C]1 的计算方式修改为:

[C1]=i=l+1mai[ψi]1+h(τ)t(τ)

注意这里的角标变了,既不隐藏 ω 的前 l 个元素

然后由验证者计算:

[X]1=i=1lai[ψi]1

这时候,验证的公式变为:

e([A]1,[B]2)=e([α]1,[β]2)+e([X]1,G2)++e([C]1,G2)

但是,我们该如何限制Prover只使用私有变量计算 [C]1,而不使用 [X]1 中的公开变量呢? For example: alt text 这里我们设置 l=5,既前五个元素是可见的,Prover原本的计算方式应该是:

e(a1ψ1+a2ψ2+...+a5ψ5,G2)+(a6ψ6+a7ψ7+h(τ)t(τ),G2)

但是如果 Prover 想要作弊的话,假设这里把sym2给隐藏,既使得 Verifier 彻底无法知道程序的正确性,可以把 [C]1 的计算方式修改为:

e(a1ψ1+a2ψ2+...+0ψ5,G2)+(a5ψ5+a6ψ6+a7ψ7+h(τ)t(τ),G2)

这样,把公开变量从 [a1,a2,...a5] 变为了 [a1,a2,...,0], 但是仍然使得验证通过了,这就导致了 Prover 可以在不使用公开输入的情况下计算出正确的证明,造成了安全隐患。

为了解决这个问题,我们继续在可信设置时引入两个变量 γδ,我们把 SRSt(τ) 变为:

SRSt(τ)=((t(τ)[1]1δ,(t(τ)[τ]1δ,(t(τ)[τ2]1δ,...,(t(τ)[τn2]1δ)

再把 [ψi=1l]1[αvi(τ)+βui(τ)+wi(τ)γ]1, [ψi=(l+1)m]1[αvi(τ)+βui(τ)+wi(τ)δ]1

同时修改验证的计算方式

[X]1=i=1lai[ψi]1e([A]1,[B]2)=e([α]1,[β]2)+[X]1[γ]2+e([C]1,G2)[δ]2

这里的原因也是显然的,把 [C]1 的计算和 δ 进行绑定, 把 [X]1 的计算和 γ 进行绑定,这样 Prover 就无法在不使用公开输入的情况下计算出正确的证明了。

此时我们有

  • SRS:

    • random α,β,τ,γ,δ
    • SRS1([1]1,[τ]1,[τ2]1,...,[τm1]1)
    • SRS2([1]2,[τ]2,[τ2]2,...,[τm1]2)
    • SRSt(τ)=((t(τ)[1]1δ,(t(τ)[τ]1δ,(t(τ)[τ2]1δ,...,(t(τ)[τn2]1δ)
    • [ψi]1l[αvi(τ)+βui(τ)+wi(τ)γ]1 \ [ψi](l+1)m[αvi(τ)+βui(τ)+wi(τ)δ]1
  • Public

    • [α]1,[β]2,[γ]2,[δ]2,SRS1,SRS2,SRSt(τ),[ψi]1
  • Prover

    • [A]1=[α]1+u(τ)[B]1=[β]2+v(τ)
    • [C]1=i=l+1mai[ψi]1+h(τ)t(τ)
  • Verifier

    • [X]1=i=1lai[ψi]1
    • e([A]1,[B]2)=e([α]1,[β]2)+e([X]1,[γ]2)+e([C]1,[δ]2)

实现零知识

由于运算程序是已知的,并且当前对于证明者,我们没有引入任何的随机性,故而攻击者可以通过遍历所有的输入可能从而获得证明者的输入和中间变量的值,造成隐私泄露。

为了保障零知识性质,Prover 在生成证据 π 时 可以生成两个随机数 rs,并且把 [A]1[B]2[C]3 的计算方式修改为:

[A]1=[α]1+u(τ)+r[δ]1[B]1=[β]1+v(τ)+s[δ]1[B]2=[β]2+v(τ)+s[δ]2[C]1=i=l+1mai[ψi]1+h(τ)t(τ)+s[A]1+r[B]1rs[δ]1

这样,通过引入随机性,我们就可以保证 Prover 的输入和中间变量的值是不可预测的,从而保障了零知识性质。

推导e([A]1,[B]2)=e([α]1+u(τ)+r[δ]1,[β]2+v(τ)+s[δ]2)以下的  为配对操作:=[α]1[β]2+[α]1v(τ)+u(τ)[β]2+u(τ)t(τ)+[α]1s[δ]2+u(τ)s[δ]2+r[δ]1[β]2+r[δ]1v(τ)+rs[δ]1[δ]2(Δ)可以看到第一行就是最开始引入 α 和 β 时的结果,我们把 Δ 单独提取出来:=[α]1s[δ]2+u(τ)s[δ]2+r[δ]1[β]2+r[δ]1v(τ)+rs[δ]1[δ]2(Δ)变换一下:=[α]1s[δ]2+u(τ)s[δ]2+rs[δ]1[δ]2~+r[δ]1[β]2+r[δ]1v(τ)+rs[δ]1[δ]2~rs[δ]1[δ]2~=([α]1+u(τ)+r[δ]1)s[δ]2+(r[δ]1)([β]2+v(τ)+s[δ]2)rs[δ]1[δ]2=s[A]1[δ]2+r[δ]1[B]2rs[δ]1[δ]2=s[A]1[δ]2+r[B]1[δ]2rs[δ]1[δ]2=(s[A]1+r[B]1rs[δ]1)[δ]2这样就得了 [C]1 的后半部分

这时候我们的验证公式:

e([A]1,[B]2)==e([α]+u(τ)+r[δ]1,[β]+v(τ)+s[δ]2)e([α]1,[β]2)+e([X]1,[γ]2)+e([C]1,[δ]2)==e([α]1,[β]2)+e([X]1,[γ]2)+e(i=l+1mai[ψi]1+h(τ)t(τ)+s[A]1+r[B]1rs[δ]1,[δ]2)

这时候,我们发现:

e([X]1,[γ]2)=e(i=1lai[αvi(τ)+βui(τ)+wi(τ)γ]1,[γ]2)e(i=l+1mai[ψi]1,[δ]2)=e(i=l+1mai[αvi(τ)+βui(τ)+wi(τ)δ]1,[δ]2)e(h(τ)t(τ),[δ]2)=e(i=0n2hi[t(τ)[τi]1δ]1,[δ]2)

这样分母就被消掉了。

这样,我们就得到最终的Groth16

对于 GT 的加法实际上是乘法(

  • SRS:

    • random α,β,τ,γ,δ
    • SRS1([1]1,[τ]1,[τ2]1,...,[τm1]1)
    • SRS2([1]2,[τ]2,[τ2]2,...,[τm1]2)
    • SRSt(τ)=((t(τ)[1]1δ,(t(τ)[τ]1δ,(t(τ)[τ2]1δ,...,(t(τ)[τn2]1δ)
    • [ψi]1l[αvi(τ)+βui(τ)+wi(τ)γ]1
    • [ψi](l+1)m[αvi(τ)+βui(τ)+wi(τ)δ]1
  • Public

    • [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,SRS1,SRS2,SRSt(τ),[ψi]1
  • Prover

    • [A]1=[α]1+u(τ)+r[δ]1
    • [B]1=[β]1+v(τ)+s[δ]1
    • [B]2=[β]2+v(τ)+s[δ]2
    • [C]1=i=l+1mai[ψi]1+h(τ)t(τ)+s[A]1+r[B]1rs[δ]1
  • Verifier

    • [X]1=i=1lai[ψi]1
    • e([A]1,[B]2)=e([α]1,[β]2)+e([X]1,[γ]2)+e([C]1,[δ]2)