本文翻译自V神的Medium文章:二次计算方程:从0到1,详细讲解了QAP,并给出了一个程序实现,本文是上半部分,介绍了把计算转化为R1CS的过程。zcash官方博客对QAP这部分讲解的比较粗糙,V神的这篇文章是个很好的补充。友情提示:本文偏技术化,适合对技术非常感兴趣的同学阅读。
(本文授权BH好文好报群摘编、转载以及相关转授权推文行为)
二次计算方程:从0到1
最近zk-SNARKs吸引了很多人的注意力,因为她那难以捉摸的复杂性,所以有人把它称之为“月亮数学”;人人都想揭开它的神秘面纱。zk-SNARKs确实比较难以掌握,它里面包含了好几个比较难以理解的知识点;如果我们把这些知识点分开来去掌握,就没那么难了。
这篇文章,不会把zk-SNARKs的内部都介绍一边,而是假设你已经有了如下基础知识:
- 你知道zk-SNARKs是什么以及它有什么用
- 你有多项式相关的数学知识(如果这样的多项式对你来说很熟悉: P(x) + Q(x) = (P+X)(x),其中P和Q都是多项式。
那么你就没问题)。不仅如此,本文还会对技术背后的机制做深入的研究,并试图解释下图中前半部分的知识点。
这些步骤可以分成两部分。第一,zk-SNARKs不是所有的计算问题都可以直接用的;你需要把问题转换为正确的“形式”之后,才能进行后续操作。这种形式被称作“二次计算方程”(QAP),把一个函数的代码转化为这种形式本身就不是一件简单的事情。在这个转换过程之外,还有另外一个过程,就是如果你有这段代码的输入,就能够创建相应的“解”(有时候被叫做QAP的"见证者")。然后,为这个“解”创建真正的"零知识证明",以及验证证明的过程,不过这些都是本篇内容之外的内容,这里不做细述了。
我们选用的是一个简单的例子: 证明你知道下面这个三次方程的解:
x^3 + x + 5 = 35(提示:答案是3).
这问题非常简单,这样生成的QAP不会太大,所以比较好模拟;但是已经足够我们研究相关的技术机制了。
我们把这个函数这样写下来:
def qeval(x)
y = x**3
return x + y + 5
这里用到的这个特别的编程语言支持基本的算术运算(+,-, *, /
),常数指数运算(类似于x**7
,而不是x**y
),以及变量赋值。这些已经足够强大了,理论上,你可以用他们做任何计算。注意,求余运算(%
)和比较运算(<, >, <=, >=
)是不支持的。这是因为在有限循环群里面,没有求余运算和比较运算的高效方法(幸亏是这样;不然椭圆曲线加密算法就可以很快被破解了)。
要是想支持求余和比较运算,你可以给这个语言增加位分解功能(比如: 13 = 2 ^3 + 2^2 + 1),它可以作为一种辅助输入,在二进制电路里面进行一些数学计算并验证分解的正确性;在有限域运算中,检测相等(==)也是可行,实际上还更容易一点,不过这些都是一些细节了,我们这里不再深入了。我们可以给这个语言增加条件判断(比如:if x < 5: y= 7; else: y = 9
),可以通过把它们转化为算术形式来实现:y = 7*(x < 5) + 9 * (x >= 5)
, 不过这种这种形式还是会有两个条件运算需要执行。如果你有很多的嵌套的条件判断,那么这个计算量的开销也是很大的。
现在让我们一步步的走一遍这个过程。如果你想自己实现一些代码,可以参考我实现的这个翻译器(仅供学习使用;不能作为真实场景中的zk-SNARKs使用)
抹平
第一步就是"抹平"操作,我们把原始的包含各种复杂的语句和表达式的代码,转化为一组这样的语句序列:x = y
(y
可以是一个变量或者数字)和x = y (op) z
(op
可以是+, -, * /
, y
和z
可以是变量,数字,或者子表达式)。你可以把每条语句想象成某种电路的逻辑门, 上面的代码被抹平后的结果如下:
sym_1 = x * x
y = sym_1 * x
sym_2 = y + x
~out = sym_2 + 5
如果你对比下原来的代码,你会很容易发现,它们是等价的。
转化为R1CS
现在,我们把这个东西转化为一级约束系统(R1CS
)。一个R1CS是一组三向量(a, b, c
)构成的一个序列。假设R1CS的解是一个向量s
,那么s
必须满足这个等式s·a * s·b - s·c = 0
。这里的·
代表向量的点乘,简单说,s·a
就是把s , a
一对一并在一起,把他们相同位置上的元素相乘然后结果相加,就得到了点乘的结果,然后我们对于s·b
和s·c
也这么操作,那么如果第三个结果就等于前两个结果的和,上面的式子就满足了,就像下面这样:
上面图示的只是一个约束,我们将会有多个约束:每个逻辑门一个约束。把逻辑门转换为(a, b, c)
三个向量组有一个标准的方法,这个方法于运算符(+,-,*,/
)及其操作数有关。每个向量的长度等于这个系统中所有变量的个数,同时也包括代表1的变量~one
,我们把它放在向量第一个索引位置;还包括代表输出的~out
,以及中间变量(上面的sym1, sym2
)。这些向量通常比较稀疏,只在逻辑门相关的变量位置才有值,其他地方都为0。
首先,我们先提供我们将要使用的变量位置映射如下:
one, x, ~out, sym_1, y, sym_2
解向量 的值代表着依次给上面变量的赋值。
译者注:假设解向量的结果是[1, 2, 3, 4, 5, 6],那么代表着x == 2, ~out == 3, sym_1 == 4, y = 5, sym_2 = 6
现在我们给出第一组逻辑门的向量(a, b, c)
:
a = [0, 1, 0, 0, 0, 0]
b = [0, 1, 0, 0, 0, 0]
c = [0, 0, 0, 1, 0, 0]
译者注:参见这个逻辑门 sym_1 = x * x
, a
向量在对应位置存储运算符(*)左边操作数x
的系数1,参见上面的映射中变量x
的位置,它在第2个位置,所以就把系数1也写在a
向量的第2个位置。
你可以看到,如果解向量在第二个位置包含3,在第4个位置包含9,那么不管解向量中其他位置是什么,这个点乘(s·a * s·b = s·c
)就变成了3*3 = 9
,如此就能通过测试——做这个检测是仅仅为了验证第一个逻辑门的输入和输出是否一致。
现在我们继续第二个逻辑门:
a = [0, 0, 0, 1, 0, 0]
b = [0, 1, 0, 0, 0, 0]
c = [0, 0, 0, 0, 1, 0]
同样的检测的方式,我们可以验证它是与sym_1 * x = y
相符的。
再来第三个门:
a = [0, 1, 0, 0, 1, 0]
b = [1, 0, 0, 0, 0, 0]
c = [0, 0, 0, 0, 0, 1]
这里,有一点不同:它是把解向量中第1个元素与第二个元素相乘,再加上第五个元素,然后检查是否与第6个元素相等。因为解向量中第一个元素总是1,所以这只是一个加法的检查,看两个输入的和是否等于输出。
最后,第四个门:
a = [5, 0, 0, 0, 0, 1]
b = [1, 0, 0, 0, 0, 0]
c = [0, 0, 1, 0, 0, 0]
这里,我们做最后一个检查~out = sym_2 + 5
。把解向量中的第六个元素与5被的第一个元素相加(记住,第一个元素是1,所以最终变成 加5),然后与第三个元素相比较。
这就是我们带四个约束条件的R1CS。它的见证者(witness)很简单,就是对所有这些变量(包括输入输出和中间变量)的一组赋值。
[1, 3, 35, 9, 27, 30]
你可以自己执行以下试试,利用下上面的被抹平的代码,从给变量的赋值x=3
开始,然后计算出中间值,并与上面解向量对比一下。
我们把完整的R1CS放在一起如下(译者注:A代表a向量组,B代表b向量组,C代表c向量组):
A
[0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0]
[0, 1, 0, 0, 1, 0]
[5, 0, 0, 0, 0, 1]
B
[0, 1, 0, 0, 0, 0]
[0, 1, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0]
C
[0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0]
译者注:V神的文章喜欢用长语句,我翻译的比较慢,怕翻译错了。今天先到这里吧,今天是上半部分,明天继续下半部分
早赞声明:为方便早赞、避免乱赞,“BH好文好报群”为点赞者、写作者牵线搭桥,实行“先审后赞、定时发表”的规则,也让作品脱颖而出、速登热门!加群微信:we01230123(天平)。