Quadratic Arithmetic Program

Table of Contents

1. zk-SNARK 和 QAP

零知识证明 zk-SNARK 在区块链中的应用最近几年比较活跃,其中往往用到 QAP(Quadratic Arithmetic Program)。

考虑问题:Verify 怎么验证 Prover 执行了下面代码,但 Prover 在证明过程中并不想泄露自己的输入值。这怎么才能实现呢?

function calc(w, arg1, arg2)
    if w then
        return arg1 * arg2
    else
        return arg1 + arg2
    end if
end function

zk-SNARK 的做法是: 先把程序转换为等价的 QAP,如果 Prover 给出了 QAP 的一个解,则说明 Prover 确实执行了程序(注:QAP 问题本身是公开的,它的求解很困难,但验证某个解是否正确是容易的)。当然,由于 Prover 不想泄露自己的解,所以他给出的解是某种加密的形式,我们还要仔细设计一下验证过程。

本文仅仅介绍如何把程序转换为等价的 QAP,并不会介绍 Prover 如何加密解,也不介绍 Verify 如何验证这个加密解的有效性。

注:QSP/QAP 的概念由 Rosario Gennaro 等在 2012 年的论文 Quadratic Span Programs and Succinct NIZKs without PCPs 中提出。

1.1. QAP 的定义

所谓 QAP 就是 给定一系列的多项式以及一个目标多项式,是否能根据这一系列的多项式,求得它们的一个线性组合,刚好可以整除目标多项式。 即如下描述:

  • 给定一系列多项式 {li(x),ri(x),oi(x)}i=0n ,目标多项式 t(x)
  • 求一个线性组合 {ui}i=1n ,使得下式成立 (l0(x)+i=1nuili(x))(r0(x)+i=1nuiri(x))(o0(x)+i=1nuioi(x))=t(x)h(x)

注:如果定义 u0=1 ,那么上面也可以表达为:求一个线性组合 {ui}i=0n ,使得下式成立 (i=0nuili(x))(i=0nuiri(x))(i=0nuioi(x))=t(x)h(x)
可记为 L(x)R(x)O(x)=t(x)h(x)

这个问题如果给出了一个解 {ui}i=0n (由于 u0=1 ,也可以省略这个固定值,把解记为 {ui}i=1n ,解也称为 witness),那么验证很简单,直接除 t(x) 看能否整除即可验证解是否正确,但是想求解就很难。

zk-SNARK 的思路就是,将原问题的解转化为一个 QAP 问题的解,然后公开 QAP 问题,这样的话拥有解的人公布自己的 witness(当然是某种加密形式),其他人来验证解是否成立。

1.2. zk-SNARK 中为什么使用 QAP

在 zk-SNARK 中,Verifier 需要“快速地”验证 Prover 的提供的证明是否正确。 QAP 有个特点:验证 QAP 的解是否正确很容易,而求解 QAP 的解则很难。 zk-SNARK 中,把原问题的解转化为一个 QAP 问题的解后,Verifier 就可以“快速地”验证解是否正确了。

2. 转换为 QAP

下面还是以前面提到的程序以例:

function calc(w, arg1, arg2)
    if w then
        return arg1 * arg2
    else
        return arg1 + arg2
    end if
end function

介绍如何把它转换为 QAP。

2.1. 从程序到多项式

首先,我们将程序改写为下面等价的多项式,其中 v 是程序 calc(w,arg1,arg2) 返回值:

w×(arg1×arg2)+(1w)×(arg1+arg2)=vw×(w1)=0

我们需要将这上面两个多项式转换成一系列多项式 {li(x),ri(x),oi(x)}i=0n ,并且给出目标多项式 t(x) ,最后代入解 {ai}i=0n 应该满足 i=0nai×(li(x)×ri(x)oi(x))=t(x)h(x) 形式。

2.2. 直观的做法

在文章 Zero-Knowledge Proof (zk-SNARK, Pinocchio) 7.8 计算示例 中介绍了把上面例子中程序转换为对应 QAP 的直观做法。

这种直观的做法不适合编程实现,我们需要一种标准的做法,请看下节。

2.3. 标准的做法

利用 R1CS(rank 1 constraint system)实现到 QAP 的转换。

2.3.1. R1CS 的定义

R1CS 是由一系列三向量组 (a,b,c) 组成的序列,R1CS 有一个解向量 s ,需要满足:
ai,s×bi,sci,s=0fori
其中 , 表示两个向量的点积。

2.3.2. 从多项式到算术电路(Flatten)

把多项式写为 z=x(op)y 的形式,其中 op 是加减乘除等运算:

1: sym_1 = arg1 * arg2
2: sym_2 = w * sym_1
3: sym_3 = 1 - w
4: sym_4 = arg1 + arg2
5: sym_5 = sym_3 * sym_4
6: v = sym_2 + sym_5
7: w = w * w

上面的形式即算数电路(Arithmetic circuit),这个过程称为拍平(Flatten)。

为了将所有操作都转成 z=x(op)y 的形式,我们加入了 sym_1,sym_2,sym_5 中间变量。

2.3.3. 从算术电路到 R1CS

有一种简单的方法可以把拍平后的算术电路转移为 R1CS。把电路中涉及的所有变量(包含中间变量)组成的下面向量作为解向量:
s=(1warg1arg2sym_1sym_2sym_3sym_4sym_5v)
注意,上面 解向量 s 的首个分量固定为数字 1, 这是为了编码常量而故意留的,下面会看到这点。

有了这个解向量后,我们可以按下面方法寻找出满足条件的一系列三向量组 (a,b,c)

针对第 1 行的约束 arg1×arg2=sym_1 ,可以设置:

a0=[0,0,1,0,0,0,0,0,0,0]b0=[0,0,0,1,0,0,0,0,0,0]c0=[0,0,0,0,1,0,0,0,0,0]

它满足 R1CS 的要求: a0,s×b0,sc0,s=0 ,这容易验证,把 a0,b0,c0,s 代入进来就是 arg1×arg2sym_1=0 ,这显然成立。

针对第 2 行的约束 w×sym_1=sym_2 ,可以设置:

a1=[0,1,0,0,0,0,0,0,0,0]b1=[0,0,0,0,1,0,0,0,0,0]c1=[0,0,0,0,0,1,0,0,0,0]

它也满足 R1CS 的要求: a1,s×b1,sc1,s=0 ,这容易验证,把 a1,b1,c1,s 代入进来就是 w×sym_1sym_2=0 ,这显然成立。

针对第 3 行的约束 (1w)×1=sym_3 ,可以设置:

a2=[1,1,0,0,0,0,0,0,0,0]b2=[1,0,0,0,0,0,0,0,0,0]c2=[0,0,0,0,0,0,1,0,0,0]

它也满足 R1CS 的要求: a2,s×b2,sc2,s=0 ,这容易验证,把 a2,b2,c2,s 代入进来就是 (1w)×1sym_3=0 ,这显然成立。

针对第 4 行的约束 (arg1+arg2)×1=sym_4 ,可以设置:

a3=[0,0,1,1,0,0,0,0,0,0]b3=[1,0,0,0,0,0,0,0,0,0]c3=[0,0,0,0,0,0,0,1,0,0]

它也满足 R1CS 的要求: a3,s×b3,sc3,s=0 ,这容易验证,把 a3,b3,c3,s 代入进来就是 (arg1+arg2)×1sym_4=0 ,这显然成立。

针对第 5 行的约束 sym_3×sym_4=sym_5 ,可以设置:

a4=[0,0,0,0,0,0,1,0,0,0]b4=[0,0,0,0,0,0,0,1,0,0]c4=[0,0,0,0,0,0,0,0,1,0]

它也满足 R1CS 的要求: a4,s×b4,sc4,s=0 ,这容易验证,把 a4,b4,c4,s 代入进来就是 sym_3×sym_4sym_5=0 ,这显然成立。

针对第 6 行的约束 (sym_2+sym_5)×1=v ,可以设置:

a5=[0,0,0,0,0,1,0,0,1,0]b5=[1,0,0,0,0,0,0,0,0,0]c5=[0,0,0,0,0,0,0,0,0,1]

它也满足 R1CS 的要求: a5,s×b5,sc5,s=0 ,这容易验证,把 a5,b5,c5,s 代入进来就是 (sym_2+sym_5)×1v=0 ,这显然成立。

针对第 7 行的约束 w×w=w ,可以设置:

a6=[0,1,0,0,0,0,0,0,0,0]b6=[0,1,0,0,0,0,0,0,0,0]c6=[0,1,0,0,0,0,0,0,0,0]

它也满足 R1CS 的要求: a6,s×b6,sc6,s=0 ,这容易验证,把 a6,b6,c6,s 代入进来就是 w×ww=0 ,这显然成立。

上面这一系列三向量组 (a,b,c) 组成的序列就是 R1CS:

A
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[1, -1, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]

B
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]

C
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]

2.3.4. 从 R1CS 到 QAP

R1CS 是使用向量点积来实现约束,下面介绍如何把这些约束转换为 QAP。

把前面得到的 a 序列组成矩阵 A
A=(0010000000010000000011000000000011000000000000100000000100100100000000)

l0(x) 是经过点 (1,0),(2,0),(3,1),(4,0),(5,0),(6,0),(7,0) (注: x1,2,,7 ,而 y 值依次是 A 的第 1 列的每个元素)的多项式,由拉格朗日插值公式(利用这个公式,可以在已经某未知函数经过某些点的情况下,还原出函数。当然不一定要用拉格朗日插值公式,其它多项式插值方法也可以)可得:
l0(x)=x64825x548+247x4481219x348+389x26949x12+35

l1(x) 是经过点 (1,0),(2,1),(3,1),(4,0),(5,0),(6,0),(7,1) (注: x1,2,,7 ,而 y 值依次是 A 的第 2 列的每个元素)的多项式,由拉格朗日插值公式可得:
l1(x)=x636+17x524515x472+869x3246863x272+1447x1255

使用类似的方法,可以求得:

l2(x)=19x6720+151x5240845x4144+1297x34811449x2180+1447x2028l3(x)=x636+2x53113x418+88x332545x236+82x35l4(x)=0l5(x)=x6120+11x56019x412+41x361849x2120+1019x607l6(x)=x64823x548+69x416925x348+134x23201x4+21l7(x)=0l8(x)=x6120+11x56019x412+41x361849x2120+1019x607l9(x)=0

同样地,我们根据 R1CS 中的矩阵 B 可得 r0(x),r1(x),,r9(x) ;根据 R1CS 中的矩阵 C 可得 o0(x),o1(x),,o9(x) 。此外, t(x)=(x1)(x2)(x3)(x4)(x5)(x6)(x7)

上面例子中,假设 v=6 ,Prover 想证明他知道的那个解为 w=1,arg1=2,arg2=3 ,则可以计算出中间变量 sym_1=6,sym_2=6,sym_3=0,sym_4=5,sym_5=0 ,从而 R1CS 的解为:
s=(1123660506)

这个解就是 QAP 的一个解(witness),即 {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9}={1,1,2,3,6,6,0,5,0,6}

注:Vitalik Buterin 在文章 Quadratic Arithmetic Programs: from Zero to Hero 中介绍了从 R1CS 到 QAP 的转换,且在 https://github.com/ethereum/research/blob/master/zksnark/qap_creator.py 中使用 Python 编写了相应的转换程序。

2.4. 有限域上计算

前面的计算,我们发现会出现浮点数,这样会有舍入误差。在实际中我们会在“有限域”上做算术运算,这样结果都是整数,不存在舍入误差了。

在有限素域 GF(p) 中,元素范围是整数 0p1 ,其中 p 为大素数,域上加减乘除(注:域上只定义加法乘法两种运算,减法和除法看作它们的逆运算)运算都要对素数 p 取模。比如 p=13 ,有 10+6=3,3×5=2,1/2=7(as7×2=1)

文章 R1CS to QAP (quadratic arithmetic program) with zkSNARKs in Go 中有“在有限域上求 QAP”的例子。

Author: cig01

Created: <2020-08-05 Wed>

Last updated: <2020-10-20 Tue>

Creator: Emacs 27.1 (Org mode 9.4)