1. 引言
在过去的两年中,STARKs已成为一种至关重要且不可替代的技术,可用于高效地对非常复杂的statement(如,证明某以太坊区块有效)进行易于验证的密码学证明。一个关键原因在于STARKs所用域size较小:
- 基于椭圆曲线的 SNARKs 需要处理 256 位整数才能足够安全,
- 而 STARKs 允许使用更小的域,从而提高效率:【详情见STARKs with small finite field:小域带来的迷人性能】
一个自然而然的问题是:
- 能否将这一趋势推向逻辑结论,通过直接对0和1进行运算来构建运行速度更快的证明系统?
- 这正是Binius(2023年Ulvetanna团队论文《Succinct Arguments over Towers of Binary Fields》)试图做的事情——Binius使用了许多数学技巧,使其与三年前的SNARKs和STARKs截然不同。
本文将介绍:
- 1)为什么小域使证明生成更有效率
- 2)为什么二进制域具有独特的强大功能
- 3)Binius 用来使二进制域上的证明如此有效的技巧。
Binius总体示意如上图所示,阅读完本文后,将可理解上图的每个部分。
2. 有限域
密码学证明系统的关键任务之一是处理大量数据,同时保持数字较小。如果能将有关大型程序的statement压缩为涉及一个少量数字的数学方程,但这些数字与原始程序一样大,那样的话毫无意义。
为了进行复杂的算术运算,同时保持数字较小,密码学家通常使用模运算。选择一些素数“模数”
p
p
p。
%
\%
% 运算符表示“取余数”:如
15
%
7
=
1
,
53
%
10
=
3
15\ \%\ 7 = 1, 53\ \%\ 10 = 3
15 % 7=1,53 % 10=3等。注意模运算结果总是非负的,如
−
1
%
10
=
9
-1\ \%\ 10 = 9
−1 % 10=9。
在加减时间的上下文中(如,9:00 过后四小时是几点?),实际对应的即为模运算。
不过,在密码学上下文中,不仅对某个数字进行加减模运算,还进行乘法、除法和取指数运算。
在此,重新定义:
x
+
y
⇒
(
x
+
y
)
%
p
x + y \Rightarrow (x + y) \% p
x+y⇒(x+y)%p
x
∗
y
⇒
(
x
∗
y
)
%
p
x * y \Rightarrow (x * y) \% p
x∗y⇒(x∗y)%p
x
y
⇒
(
x
y
)
%
p
x^y \Rightarrow (x^y) \% p
xy⇒(xy)%p
x
−
y
⇒
(
x
−
y
)
%
p
x - y \Rightarrow (x - y) \% p
x−y⇒(x−y)%p
x
/
y
⇒
(
x
∗
y
p
−
2
)
%
p
x / y \Rightarrow (x * y ^{p-2}) \% p
x/y⇒(x∗yp−2)%p
以上规则是自洽的。如,若 p = 7 p=7 p=7,则有:
- 5 + 3 = 1 5 + 3 = 1 5+3=1(因为 8 % 7 = 1 8\ \%\ 7=1 8 % 7=1)
- 1 − 3 = 5 1-3 = 5 1−3=5(因为 − 2 % 7 = 5 -2\ \%\ 7=5 −2 % 7=5)
- 2 ⋅ 5 = 3 2 \cdot 5 = 3 2⋅5=3(因为 10 % 7 = 3 10\ \%\ 7=3 10 % 7=3)
- 3 / 5 = 2 3/5 = 2 3/5=2(因为 ( 3 ⋅ 5 5 ) % 7 = 9375 % 7 = 2 (3\cdot 5^5)\ \%\ 7=9375\ \% \ 7 = 2 (3⋅55) % 7=9375 % 7=2)
这种结构的一个更通用的术语是有限域。有限域是一种遵循通常算术定律的数学结构,但其可能值的数量有限的,因此每个值都可用固定的大小来表示。
模运算(或素数域)是最常见的有限域类型,但还有另一种类型:扩域。可能之前已经见过扩域:如,复数。
- “想象”一个新元素,将其标记为 i i i,并声明其满足 i 2 = − 1 i^2=-1 i2=−1。
- 然后可取任意常规数字和 i i i的组合,即可构成一个复数。
- 对2个复数做乘法等运算,如: 6 i 2 + 12 i + 4 i + 8 = 16 i + 2 6i^2 + 12i + 4i + 8 = 16i + 2 6i2+12i+4i+8=16i+2
可类似地对素数域进行扩展,从而获得扩域。当开始处理较小的域时,素数域的扩域对于保护安全性变得越来越重要,而(Binius 所用的)binary二进制域完全依赖于扩域才能具有实用性。
3. 算术化
SNARKs 和 STARKs 证明计算机程序的方法是通过算术化:
- 将待证明的程序的statement转换为涉及多项式的数学方程。
- 该数学方程的有效解对应于程序的有效执行。
举个简单例子,假设Alice计算了第 100 个Fibonacci斐波那契数,想向Bob证明其具体值。
- Alice创建一个编码了Fibonacci数字的多项式 F F F,使得: F ( 0 ) = F ( 1 ) = 1 , F ( 2 ) = 2 , F ( 3 ) = 3 , F ( 4 ) = 5 F(0) = F(1) = 1, F(2) = 2, F(3) = 3, F(4) = 5 F(0)=F(1)=1,F(2)=2,F(3)=3,F(4)=5,以此类推,共100步。
- Alice需要证明的为,对于 x = { 0 , 1...98 } x = \{0, 1 ... 98\} x={0,1...98},有: F ( x + 2 ) = F ( x ) + F ( x + 1 ) F(x+2) = F(x) + F(x+1) F(x+2)=F(x)+F(x+1)。
- Alice可通过给出以下公式来让Bob相信这一点:
H ( x ) = F ( x + 2 ) − F ( x + 1 ) − F ( x ) Z ( x ) H(x) = \frac{F(x+2) - F(x+1) - F(x)}{Z(x)} H(x)=Z(x)F(x+2)−F(x+1)−F(x)
其中: Z ( x ) = ( x − 0 ) ∗ ( x − 1 ) ∗ . . . ∗ ( x − 98 ) Z(x) = (x - 0) * (x - 1) * ... * (x - 98) Z(x)=(x−0)∗(x−1)∗...∗(x−98)。- 若Alice提供满足以上方程式的有效 F F F和 H H H,则对于 x = { 0 , 1...98 } x = \{0, 1 ... 98\} x={0,1...98}, F F F必须满足 F ( x + 2 ) − F ( x ) − F ( x + 1 ) F(x+2) - F(x) - F(x+1) F(x+2)−F(x)−F(x+1)。
- 若Alice额外验证 F ( 0 ) = F ( 1 ) = 1 F(0) = F(1) = 1 F(0)=F(1)=1,则 F ( 100 ) F(100) F(100)必须为实际的第100个Fibonacci数字。
Alice若想证明一些更复杂的东西,那么可用更复杂的方程式来代替“简单” relation F ( x + 2 ) = F ( x ) + F ( x + 1 ) F(x+2) = F(x) + F(x+1) F(x+2)=F(x)+F(x+1),如“ F ( x + 1 ) F(x+1) F(x+1)是以 F ( x ) F(x) F(x)为初始状态的虚拟机,执行一个计算步骤的输出”。还可将数字 100 100 100 替换为更大的数字,如 100000000 100000000 100000000,以容纳更多步骤。
所有 SNARKs 和 STARKs 都基于这一理念,即使用基于多项式(或有时是向量和矩阵)的简单方程来表示单个值之间的大量关系。并非所有 SNARKs 和 STARKs 都涉及以 与上述相同的方式检查相邻计算步骤之间的等价性:如, PLONK就不涉及做这种相邻计算步骤之间的等价性检查,R1CS 也不涉及。但许多最有效的 SNARKs 和 STARKs 都涉及,因为多次执行相同的检查(或相同的少数检查)可以更轻松地最大限度地减少开销。
4. Plonky2:从 “256 位 SNARKs 和 STARKs” 到 “64 位……只有 STARKs”
五年前,对不同类型的零知识证明的合理总结如下。
有两种类型的证明:
- (基于椭圆曲线的)SNARKs
- 和(基于哈希的)STARKs。
从技术上讲,STARKs 是一种 SNARKs,但在实践中,通常使用“SNARK”来指代基于椭圆曲线的品种,使用“STARKs”来指代基于哈希的构造。
- SNARKs 很小,因此可非常快速地对其验证并轻松将其用于链上。
- STARKs 很大,但其不需要可信设置,且具有抗量子性。
STARKs 的工作原理是将数据视为多项式,在大量点上计算该多项式的evaluations,并使用该扩展数据的 Merkle 根作为“多项式承诺”。
这里的一个关键历史是:
基于椭圆曲线的 SNARKs 有一个关键限制:如果想使用基于椭圆曲线的 SNARKs,那么这些方程中的运算必须用整数模椭圆曲线上的点数来完成——即对椭圆曲线scalar域求模——这是一个很大的数字,通常接近 2 256 2^{256} 2256:如,对于 bn128 曲线,该值为 21888242871839275222246405745257275088548364400416034343698204186575808495617 21888242871839275222246405745257275088548364400416034343698204186575808495617 21888242871839275222246405745257275088548364400416034343698204186575808495617。
但实际计算重使用的是小数字:若考虑用所喜欢的语言编写的“真实”程序,其处理的大部分内容是计数器、for 循环中的索引、程序中的位置、表示 True 或 False 的各个位,以及其他几乎总是只有几位数字长的东西。
即使“原始”数据是由“小”数字组成的,证明过程也需要计算商、扩展、随机线性组合和其他数据变换,这会导致对象数量相等或更多,这些对象的平均大小与域的整个大小一样大。这造成了一个关键的低效率:为了证明对 n n n个小值的计算,必须对 n n n个大得多的值进行更多的计算。起初,STARKs 继承了 SNARKs 使用 256 位域的习惯,因此遭受了同样的低效率。
如上图所示为一些多项式evaluations的 Reed-Solomon 扩展。尽管原始值很小,但额外的值都会膨胀到整个域的大小(在本例中为
2
31
−
1
2^{31}-1
231−1)。
2022 年,Plonky2 发布。Plonky2 的主要创新是对较小的素数进行模运算: 2 64 − 2 32 + 1 = 18446744069414584321 2^{64} - 2^{32} + 1 = 18446744069414584321 264−232+1=18446744069414584321。这样,每次加法或乘法都可在 CPU 上仅用几条指令完成,并且将所有数据哈希在一起的速度比以前快 4 倍。但这有一个问题:
- 其仅适用于 STARKs。若尝试使用 SNARKs,则具有如此小size的椭圆曲线就会变得不安全。
为了继续保证安全,Plonky2 还需要引入扩域。检查算术方程的一个关键技术是:
- “在随机点采样”
若想检查 H ( x ) ∗ Z ( x ) H(x) * Z(x) H(x)∗Z(x)实际上是否等于 F ( x + 2 ) − F ( x + 1 ) − F ( x ) F(x+2) - F(x+1) - F(x) F(x+2)−F(x+1)−F(x),可选择某随机坐标 r r r,提供多项式承诺opening proofs H ( r ) , Z ( r ) , F ( r ) , F ( r + 1 ) , F ( r + 2 ) H(r), Z(r), F(r), F(r+1), F(r+2) H(r),Z(r),F(r),F(r+1),F(r+2),然后实际检查 H ( r ) ∗ Z ( r ) H(r) * Z(r) H(r)∗Z(r)是否等于 F ( r + 2 ) − F ( r + 1 ) − F ( r ) F(r+2) - F(r+1) - F(r) F(r+2)−F(r+1)−F(r)。
若攻击者可提前猜出该坐标 r r r,那么攻击者就可欺骗证明系统——这就是为什么 r r r必须是随机的。但这也意味着坐标 r r r必须从足够大的集合中采样,以至于攻击者无法随机猜出 r r r值。
- 若模数接近 2 256 2^{256} 2256,则集合足够大,可满足要求。
- 但若模数为
2
64
−
2
32
+
1
2^{64} - 2^{32} + 1
264−232+1,则不够大,不符合要求;当降到
2
31
−
1
2^{31} - 1
231−1,则必然不满足要求。
- 尝试20亿次来伪造一个证明,直到成功,这绝对在攻击者的能力范围内。
为阻止这种情况,可选择从扩域来采样 r r r值。如可定义 y y y,其中 y 3 = 5 y^3=5 y3=5,并取 1 , y , y 2 1,y,y^2 1,y,y2的组合。这将使坐标总数增加到大约 2 93 2^{93} 293。
- prover计算的大部分多项式不进入该扩域——其只需使用整数模 2 31 − 1 2^{31}-1 231−1,因此仍然可从使用小域中获得所有效率。
- 但随机点检查和 FRI 计算确实需深入到更大的扩域中,以获得所需的安全性。
5. 从小素数域到二进制域
计算机通过将较大的数字表示为0和1的序列,并在这些bits之上构建“电路”来计算加法和乘法等操作来进行算术运算。计算机特别适合使用 16 位、32 位和 64 位整数进行计算。
之所以选中
2
64
−
2
32
+
1
2^{64} - 2^{32} + 1
264−232+1和
2
31
−
1
2^{31} - 1
231−1这样的模数:
- 不仅是因为它们符合(fit within)这些界限,
- 而且因为它们与这些界限很好地吻合(allign well with):
- 通过执行常规的 32 位乘法,并在几个地方对输出进行按位移位和复制,即可完成模 2 64 − 2 32 + 1 2^{64} - 2^{32} + 1 264−232+1乘法运算。更多技巧见Remco Bloemen博客The Goldilocks Prime(2024年7月24日有更新)。
不过,更好的办法是直接用二进制进行计算。
- 如果加法可以“只是”XOR,而不必担心将某bit位置上的 1 + 1 上的进位溢出“carry”,"carrying"到下一bit位置,那会怎样?
- 如果乘法可以以同样的方式更加并行化,那会怎样?
而且这些优势都来自于能够仅用1个bit来表示 True/False 值。
直接利用二进制计算的优势正是 Binius 想要做的事情。Binius团队的 zkSummit 演示文稿中的一张表格显示了效率提升:
尽管“大小”大致相同,但 32 位二进制域运算所需的计算资源比 31 位Mersenne域运算少 5 倍。
若认可二进制的加法无进位溢出和乘法可并行化优势,且想要用bits(0 和 1)来做所有事情。则需考虑实际上如何承诺一个代表十亿bits的多项式?
在此面临两个实际问题:
- 1)对于表示大量值的多项式来说,这些值需要在多项式evaluations处可访问:如在上面斐波那契的例子中, F ( 0 ) , F ( 1 ) , ⋯ , F ( 100 ) F(0),F(1),\cdots,F(100) F(0),F(1),⋯,F(100)需可访问,而在更大的计算中,需访问的值可能将达数百万个。要求所用的域需要包含达到该大小的数字。
- 2)在 Merkle 树中证明所要承诺的关于某值的任何东西(所有 STARKs 都是如此)都需要对其进行 Reed-Solomon 编码:将 n n n个值,扩展为,如 8 n 8n 8n个值,使用冗余来防止恶意prover在计算过程中伪造一个值来作弊。 这也需要有足够大的域:要将一百万个值扩展到八百万个,需要八百万个不同的点来evaluate该多项式。
Binius 的一个关键思想是分别解决这两个问题,并通过以两种不同的方式表示相同的数据来实现。
- 1)使用多变量(具体是multilinear)多项式来代替单变量多项式,以其在“hypercubes”上的evaluations值来表示整个计算trace。
- 2)采用erasure coding,由于hypercube每个维度的长度均为2,无法像STARK那样做Reed-Solomon扩展,但可将hypercube看成是square(方形),基于该方形来做Reed-Solomon扩展。
5.1 从单变量多项式到hypercubes(即multilinear多变量多项式)
首先是多项式本身。基于椭圆曲线的 SNARKs、2019 年的 STARKs、Plonky2 和其他系统通常处理单变量的多项式:
F
(
x
)
F(x)
F(x)。
而 Binius 则从Spartan协议中汲取灵感,并使用多元多项式:
F
(
x
1
,
x
2
,
⋯
,
x
k
)
F(x_1,x_2,\cdots,x_k)
F(x1,x2,⋯,xk)。事实上,Binius中将整个计算trace表示在“hypercubes”的evaluations,其中每个
x
i
x_i
xi要么是 0,要么是 1。
如,若想要表示斐波那契数列,且仍然使用一个足够大的域来表示它们,则可将前16个数可视化为如下形式:【16个数,对应multilinear多项式仅需4个变量,即为4维空间】
即有:
- F ( 0 , 0 , 0 , 0 ) F(0,0,0,0) F(0,0,0,0)值为1
- F ( 1 , 0 , 0 , 0 ) F(1,0,0,0) F(1,0,0,0)值为1
- F ( 0 , 1 , 0 , 0 ) F(0,1,0,0) F(0,1,0,0)值为2
- ⋯ \cdots ⋯,以此类推
- F ( 1 , 1 , 1 , 1 ) F(1,1,1,1) F(1,1,1,1)值为987
根据以上hypecube evaluations值,只有一个multilinear(每个变量的degree为 1)多项式可产生这些evaluations值。因此,可将该组evaluations值视为表示多项式;实际上永远不需要费心计算该多项式的具体系数。【即以点值表示法来表示该多项式,而不需要用系数表示法来表示该多项式】
以上例子只是为了示意说明,在实践中,使用hypercubes的整个目的是让为了能使用individual bits。
计算斐波那契数的“Binius 原生”方法是使用更高维度的hypercubes,使用每组(如 16 bits)来存储数字。这需要一些聪明才智才能在bits之上实现整数加法,但使用 Binius 并不太难。
5.2 Reed-Solomon扩展:将hypercube看成是square(方形)
接下来聊聊erasure coding纠删码。STARKs 的工作方式是:
- 对于 n n n个值,Reed-Solomon 将其扩展为更大数量的值(通常为 8 n 8n 8n,一般取值范围为 2 n 2n 2n到 32 n 32n 32n),然后从该扩展集合中随机选择一些 Merkle 分支并对其进行某种检查。
hypercubes在每个维度上的长度为 2。因此,直接对hypercubes进行扩展是不切实际的:
- 没有足够的“空间”从 16 个值中采样 Merkle 分支。
那该怎么做呢?
- 假设hypercubes是一个正方形!
详细的如何基于该方形来做Reed-Solomon扩展,见下一节“简化版Binius示例”内容。
6. 简化版Binius示例
本简化版Binius示例Python代码见:
举个例子,在此为了方便起见,使用常规整数作为域(在实际实现中,这将是二进制域元素)。
-
1)首先,取要提交的hypercubes,并将其编码为正方形:
-
2)用 Reed-Solomon 扩展该方形。即,将每一行视为在
x = {0, 1, 2, 3}
处求值的 3阶多项式,并在x = {4, 5, 6, 7}
处对相同的多项式求值:
请注意,数字会迅速膨胀!这就是为什么在实际实现中,总是使用有限域,而不是常规整数:如,若对整数做模 11运算,则所扩展的第一行将只是[3, 10, 0, 6]。若想尝试扩展并亲自验证这些数字,可使用https://github.com/ethereum/research/blob/master/binius/utils.py#L123中的简单 Reed-Solomon 扩展代码。
-
3)将该扩展按列处理,并根据这些列构建一个 Merkle 树。该Merkle 树的根即为承诺。
-
4)现在,假设prover想要在某个时刻证明该多项式在 r = { r 0 , r 1 , r 2 , r 3 } r=\{r_0,r_1,r_2,r_3\} r={r0,r1,r2,r3}处的值。
- Binius 中有一个细微差别,使其比其他多项式承诺方案稍弱:prover不应该知道或能够猜测出 r r r值,直到prover承诺 Merkle 根之后(换言之,即 r r r应是依赖于 Merkle 根的伪随机值)。这使得该方案对于“数据库查找”毫无用处(如“好的,你给了我 Merkle 根,现在证明 P ( 0 , 0 , 1 , 0 ) P(0,0,1,0) P(0,0,1,0)给我看!”)。
- 但实际使用的零知识证明协议通常不需要“数据库查找”——只需要在随机评估点检查该多项式。因此,这种限制对于实现ZKP协议的目的来说是可以的。
- 假设选择 r = { 1 , 2 , 3 , 4 } r=\{1,2,3,4\} r={1,2,3,4}(该多项式在该点的值为 − 137 -137 −137,可通过https://github.com/ethereum/research/blob/master/binius/utils.py#L100代码来确认)。
-
5)现在,进入实际的证明过程。将 r r r分为两部分:
-
第一部分“column part”: { 1 , 2 } \{1,2\} {1,2},表示的是某行内各列的线性组合,对应计算“tensor product” ⨂ i = 0 1 ( 1 − r i , r i ) \bigotimes_{i=0}^1 (1 - r_i, r_i) ⨂i=01(1−ri,ri)。
-
第二部分“row part”: { 3 , 4 } \{3,4\} {3,4},表示的是各行的线性组合,对应计算“tensor product” ⨂ i = 2 3 ( 1 − r i , r i ) \bigotimes_{i=2}^3 (1 - r_i, r_i) ⨂i=23(1−ri,ri)。
-
5.1)先看第二部分(各行的线性组合)。注意有 ( a 1 , a 2 ) ⨂ ( b 1 , b 2 ) = [ a 1 ∗ b 1 , a 2 ∗ b 1 , a 1 ∗ b 2 , a 2 ∗ b 2 ] (a_1,a_2)\bigotimes(b_1,b_2)=[a_1*b_1,a_2*b_1,a_1*b_2,a_2*b_2] (a1,a2)⨂(b1,b2)=[a1∗b1,a2∗b1,a1∗b2,a2∗b2]。从而有 ⨂ i = 2 3 ( 1 − r i , r i ) = [ ( 1 − r 2 ) ∗ ( 1 − r 3 ) , r 2 ∗ ( 1 − r 3 ) , ( 1 − r 2 ) ∗ r 3 , r 2 ∗ r 3 ] \bigotimes_{i=2}^3 (1 - r_i, r_i)=[(1-r_2)*(1-r_3),r_2*(1-r_3),(1-r_2)*r_3,r_2*r_3] ⨂i=23(1−ri,ri)=[(1−r2)∗(1−r3),r2∗(1−r3),(1−r2)∗r3,r2∗r3]。
-
5.2)当此时取 r = { 1 , 2 , 3 , 4 } r=\{1,2,3,4\} r={1,2,3,4}时,有 r 2 = 3 , r 3 = 4 r_2=3,r_3=4 r2=3,r3=4,从而有:
⨂ i = 2 3 ( 1 − r i , r i ) = [ ( 1 − 3 ) ∗ ( 1 − 4 ) , 3 ∗ ( 1 − 4 ) , ( 1 − 3 ) ∗ 4 , 3 ∗ 4 ] = [ 6 , − 9 , − 8 , 12 ] \bigotimes_{i=2}^3 (1 - r_i, r_i)=[(1 - 3) * (1 - 4), 3 * (1 - 4), (1 - 3) * 4, 3 * 4] \\ = [6, -9, -8, 12] ⨂i=23(1−ri,ri)=[(1−3)∗(1−4),3∗(1−4),(1−3)∗4,3∗4]=[6,−9,−8,12] -
5.3)据此,计算现有各行的线性组合,可获得新“row” t ′ t' t′:
[ 3 , 1 , 4 , 1 ] ∗ 6 + [ 5 , 9 , 2 , 6 ] ∗ ( − 9 ) + [ 5 , 3 , 5 , 8 ] ∗ ( − 8 ) + [ 9 , 7 , 9 , 3 ] ∗ 12 = [ 41 , − 15 , 74 , − 76 ] \begin{matrix}[3, 1, 4, 1] * 6\ + \\ [5, 9, 2, 6] * (-9)\ + \\ [5, 3, 5, 8] * (-8)\ + \\ [9, 7, 9, 3] * 12 = \\ [41, -15, 74, -76] \end{matrix} [3,1,4,1]∗6 +[5,9,2,6]∗(−9) +[5,3,5,8]∗(−8) +[9,7,9,3]∗12=[41,−15,74,−76]
可将其看成是partial evaluation。若将full tensor product ⨂ i = 0 3 ( 1 − r i , r i ) \bigotimes_{i=0}^3 (1 - r_i, r_i) ⨂i=03(1−ri,ri),与,full vector of all values,相乘,则可获得evaluation值为 P ( 1 , 2 , 3 , 4 ) = − 137 P(1, 2, 3, 4) = -137 P(1,2,3,4)=−137。
在此,与partial tensor product相乘,仅使用了一半的evaluation coordinates,并将具有 N N N个值的grid,降低为,具有 N \sqrt{N} N个值的row。
若将该row给其它人,则其它人可使用剩下的一半evaluation coordinates,来完成剩余的计算。 -
-
6)prover将新“row” t ′ t' t′提供给verifier,与此同时,还提供某些随机采样列的Merkle proofs。总的数据量为 O ( N ) O(\sqrt{N}) O(N)。
- 在本例中,假设prover提供的为最后一列。实际应用时,prover需要提供几十列才能实现足够的安全性。
-
7)接下来,需利用 Reed-Solomon codes的线性性优势——其具有的关键属性为:对 某Reed-Solomon 扩展进行线性组合得到的结果,与,对线性组合进行 Reed-Solomon 扩展得到的结果,相同。 这种“顺序独立性”通常发生在两个都是线性的运算时。
Verifier利用该特性:- 7.1)计算prover所提供的row t ′ t' t′的扩展,即若 t ′ = [ 41 , − 15 , 74 , − 76 ] t'=[41, -15, 74, -76] t′=[41,−15,74,−76],则计算其扩展为 [ − 849 , − 2629 , − 800 , − 10746 ] [-849, -2629, -800, -10746] [−849,−2629,−800,−10746]。
- 7.2)计算prover所提供的列的相同线性组合——即将prover所提供的列
[
−
291
,
572
,
−
30
,
−
341
]
[-291,572,-30,-341]
[−291,572,−30,−341],与
⨂
i
=
2
3
(
1
−
r
i
,
r
i
)
=
[
6
,
−
9
,
−
8
,
12
]
\bigotimes_{i=2}^3 (1 - r_i, r_i)= [6, -9, -8, 12]
⨂i=23(1−ri,ri)=[6,−9,−8,12],相乘,获得结果
−
10746
-10746
−10746。
此时,在相同的列位置,二者具有相同值 − 10746 -10746 −10746。从而证明 Merkle 根是“善意”构建的(或至少“足够接近”),且其与 t ′ t' t′匹配——至少绝大多数列是相互兼容的,且与 t ′ t' t′兼容。
- 7.3)但verifier仍需再做一个检查:即检查该多项式在
r
=
{
r
0
,
r
1
,
r
2
,
r
3
}
r=\{r_0,r_1,r_2,r_3\}
r={r0,r1,r2,r3}处的求值。
到目前为止,verifier的任何步骤实际上都不依赖于prover所声称的值。因此,按照以下方式进行检查。- 7.3.1)取evaluation point
r
r
r的“column part” 的tensor product:
⨂
i
=
0
1
(
1
−
r
i
,
r
i
)
\bigotimes_{i=0}^1 (1 - r_i, r_i)
⨂i=01(1−ri,ri)
本例中,有 r = { 1 , 2 , 3 , 4 } r=\{1,2,3,4\} r={1,2,3,4},有 r 0 = 1 , r 1 = 2 r_0=1,r_1=2 r0=1,r1=2,从而有: ⨂ i = 0 1 ( 1 − r i , r i ) = [ ( 1 − 1 ) ∗ ( 1 − 2 ) , 1 ∗ ( 1 − 2 ) , ( 1 − 1 ) ∗ 2 , 1 ∗ 2 ] = [ 0 , − 1 , 0 , 2 ] \bigotimes_{i=0}^1 (1 - r_i, r_i)=[(1 - 1) * (1 - 2), 1 * (1 - 2), (1 - 1) * 2, 1 * 2] = [0, -1, 0, 2] ⨂i=01(1−ri,ri)=[(1−1)∗(1−2),1∗(1−2),(1−1)∗2,1∗2]=[0,−1,0,2] - 7.3.2)将“column part” 的tensor product,与 t ′ t' t′ row相乘,(即取 t ′ t' t′的线性组合),有: 0 ∗ 41 + ( − 1 ) ∗ ( − 15 ) + 0 ∗ 74 + 2 ∗ ( − 76 ) = − 137 0 * 41 + (-1) * (-15) + 0 * 74 + 2 * (-76) = -137 0∗41+(−1)∗(−15)+0∗74+2∗(−76)=−137,正好等于该多项式直接在 r = { 1 , 2 , 3 , 4 } r=\{1,2,3,4\} r={1,2,3,4}处计算的值。
- 7.3.1)取evaluation point
r
r
r的“column part” 的tensor product:
⨂
i
=
0
1
(
1
−
r
i
,
r
i
)
\bigotimes_{i=0}^1 (1 - r_i, r_i)
⨂i=01(1−ri,ri)
以上内容非常接近“简化版”Binius 协议的完整描述。
这已经具有一些有趣的优势,如:
- 由于数据被分成行和列,因此只需要一半大小的域。
但这还远远没有实现以二进制进行计算的全部好处。为此,需要完整版 Binius 协议。但首先,让需更深入地了解binary二进制域。
7. binary二进制域
最小可能的域是模2的算术域,它非常小,可写出其加法和乘法表:
可通过扩展来制作更大的二进制域:如从
F
2
F_2
F2(整数模 2)开始,然后定义
x
x
x,其中
x
2
=
x
+
1
x^2=x+1
x2=x+1,可获得如下加法和乘法表:
事实证明,通过重复此构造,可将二进制域扩展为任意大的尺寸。与基于实数的复数不同,基于实数的复数中,可添加一个新元素
i
i
i,但不能再添加任何内容(Quaternion四元数确实存在,但其在数学上很奇怪,如
a
b
≠
b
a
ab \neq ba
ab=ba。基于有限域,可不断添加新的扩展。具体为,定义如下元素:
- x 0 x_0 x0满足 x 0 2 = x 0 + 1 x_0^2=x_0+1 x02=x0+1
- x 1 x_1 x1满足 x 1 2 = x 1 x 0 + 1 x_1^2=x_1x_0+1 x12=x1x0+1
- x 2 x_2 x2满足 x 2 2 = x 2 x 1 + 1 x_2^2=x_2x_1+1 x22=x2x1+1
- x 3 x_3 x3满足 x 3 2 = x 3 x 2 + 1 x_3^2=x_3x_2+1 x32=x3x2+1
- ⋯ \cdots ⋯
这通常被称为tower construction(塔式构造),因为每次连续扩展都可以看作是向塔中添加新的一层。这不是构造任意大小的二进制域的唯一方法,但其具有 Binius 利用的一些独特优势。
将数字以bits list表示,如 1100101010001111 \texttt{1100101010001111} 1100101010001111:
- 第一个bit表示 1 1 1的倍数
- 第二个bit表示 x 0 x_0 x0的倍数
- 后续的bit依次表示 x 1 , x 1 ∗ x 0 , x 2 , x 2 ∗ x 0 x_1,x_1*x_0,x_2,x_2*x_0 x1,x1∗x0,x2,x2∗x0的倍数
- ⋯ \cdots ⋯
这种编码很好,因可将其分解为:【注意,其中有些类似
00
∗
x
1
=
0
00*x_1=0
00∗x1=0的项直接被去掉了。】
1100101010001111
=
11001010
+
10001111
∗
x
3
=
1100
+
1010
∗
x
2
+
1000
∗
x
3
+
1111
∗
x
2
x
3
=
11
+
10
∗
x
2
+
10
∗
x
2
x
1
+
10
∗
x
3
+
11
∗
x
2
x
3
+
11
∗
x
1
x
2
x
3
=
1
+
x
0
+
x
2
+
x
2
x
1
+
x
3
+
x
2
x
3
+
x
0
x
2
x
3
+
x
1
x
2
x
3
+
x
0
x
1
x
2
x
3
\texttt{1100101010001111} = \texttt{11001010} + \texttt{10001111} * x_3= \texttt{1100} + \texttt{1010} * x_2 + \texttt{1000} * x_3 + \texttt{1111} * x_2x_3\\= \texttt{11} + \texttt{10} * x_2 + \texttt{10} * x_2x_1 + \texttt{10} * x_3 + \texttt{11} * x_2x_3 + \texttt{11} * x_1x_2x_3\\ = 1 + x_0 + x_2 + x_2x_1 + x_3 + x_2x_3 + x_0x_2x_3 + x_1x_2x_3 + x_0x_1x_2x_3
1100101010001111=11001010+10001111∗x3=1100+1010∗x2+1000∗x3+1111∗x2x3=11+10∗x2+10∗x2x1+10∗x3+11∗x2x3+11∗x1x2x3=1+x0+x2+x2x1+x3+x2x3+x0x2x3+x1x2x3+x0x1x2x3
这是一种相对不常见的符号,但也可将二进制域元素表示为整数,采用bit表示法,其中更重要的bits在右边(即类似于little-endian表示)。从而有: 1 = 1 , x 0 = 01 = 2 , 1 + x 0 = 11 − 3 , 1 + x 0 + x 2 = , 1 + x 0 + x 2 = 11001000 = 19 , ⋯ 1=1, x_0=01=2, 1+x_0=11-3, 1+x_0+x_2=,1 + x_0 + x_2 = \texttt{11001000} = 19,\cdots 1=1,x0=01=2,1+x0=11−3,1+x0+x2=,1+x0+x2=11001000=19,⋯,最终有 1100101010001111 \texttt{1100101010001111} 1100101010001111,表示为 61779 61779 61779。
二进制域中的加法只是XOR异或(顺便说一下,减法也是如此),即意味着:
- 对于任意的 x x x,有 x + x = 0 x+x=0 x+x=0
对于二进制域中的乘法,如 x ∗ y x*y x∗y,则有非常简单的递归算法:
- 将每个数组切分为两半: x = L x + R x ∗ x k , y = L y + R y ∗ x k x = L_x + R_x * x_k, y = L_y + R_y * x_k x=Lx+Rx∗xk,y=Ly+Ry∗xk
- 对应乘法拆分为:
x
∗
y
=
(
L
x
∗
L
y
)
+
(
L
x
∗
R
y
)
∗
x
k
+
(
R
x
∗
L
y
)
∗
x
k
+
(
R
x
∗
R
y
)
∗
x
k
2
x * y = (L_x * L_y) + (L_x * R_y) * x_k + (R_x * L_y) * x_k + (R_x * R_y) * x_k^2
x∗y=(Lx∗Ly)+(Lx∗Ry)∗xk+(Rx∗Ly)∗xk+(Rx∗Ry)∗xk2
- 其中最后一项可应用reduction rule,将 ( R x ∗ R y ) ∗ x k 2 (R_x * R_y) * x_k^2 (Rx∗Ry)∗xk2,替换为 ( R x ∗ R y ) ∗ ( x k − 1 ∗ x k + 1 ) (R_x * R_y) * (x_{k-1}*x_k+1) (Rx∗Ry)∗(xk−1∗xk+1)。
有更有效的方法进行乘法运算,类似于Karatsuba 算法和快速傅里叶变换,本文对此不展开讨论。
二进制域中的除法是通过结合乘法和逆运算来完成的: 3 5 = 3 ∗ 1 5 \frac{3}{5} = 3 * \frac{1}{5} 53=3∗51。进行求逆的“简单但缓慢”的方法是广义费马小定理的应用:
- 1 x = x 2 2 k − 2 \frac{1}{x} = x^{2^{2^k}-2} x1=x22k−2,对于任意的 k k k,其中 2 2 k > x 2^{2^k}>x 22k>x。
此时有
1
5
=
5
14
=
14
\frac{1}{5} = 5^{14} = 14
51=514=14,从而有
3
5
=
3
∗
14
=
9
\frac{3}{5} = 3 * 14 = 9
53=3∗14=9。
还有一种更复杂但更高效的求逆算法——详情见1997年论文《On efficient inversion in tower fields of characteristic two》。
有可使用https://github.com/ethereum/research/blob/master/binius/binary_fields.py中代码,来尝试二进制域加法、乘法和除法运算。
上图所示为4-bit 二进制域元素的加法表(即仅由
1
,
x
0
,
x
1
,
x
0
x
1
1,x_0,x_1,x_0x_1
1,x0,x1,x0x1组合成的元素)
上图所示为4-bit 二进制域元素的乘法表。
修改https://github.com/ethereum/research/blob/master/binius/binary_fields.py中尾部代码为:
N=16
rawmulcache = [[None for _ in range(N)] for _ in range(N)]
addcache = [[None for _ in range(N)] for _ in range(N)]
mulcache = [[None for _ in range(N)] for _ in range(N)]
for i in range(N):
for j in range(N):
rawmulcache[i][j] = binmul(i, j)
addcache[i][j] = BinaryFieldElement(i ^ j)
mulcache[i][j] = BinaryFieldElement(binmul(i, j))
print(addcache)
print(mulcache)
对应的运行结果为:【实际即为以上2图的各结果值】
//加法表
[[<0>, <1>, <2>, <3>, <4>, <5>, <6>, <7>, <8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>], [<1>, <0>, <3>, <2>, <5>, <4>, <7>, <6>, <9>, <8>, <11>, <10>, <13>, <12>, <15>, <14>], [<2>, <3>, <0>, <1>, <6>, <7>, <4>, <5>, <10>, <11>, <8>, <9>, <14>, <15>, <12>, <13>], [<3>, <2>, <1>, <0>, <7>, <6>, <5>, <4>, <11>, <10>, <9>, <8>, <15>, <14>, <13>, <12>], [<4>, <5>, <6>, <7>, <0>, <1>, <2>, <3>, <12>, <13>, <14>, <15>, <8>, <9>, <10>, <11>], [<5>, <4>, <7>, <6>, <1>, <0>, <3>, <2>, <13>, <12>, <15>, <14>, <9>, <8>, <11>, <10>], [<6>, <7>, <4>, <5>, <2>, <3>, <0>, <1>, <14>, <15>, <12>, <13>, <10>, <11>, <8>, <9>], [<7>, <6>, <5>, <4>, <3>, <2>, <1>, <0>, <15>, <14>, <13>, <12>, <11>, <10>, <9>, <8>], [<8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>, <0>, <1>, <2>, <3>, <4>, <5>, <6>, <7>], [<9>, <8>, <11>, <10>, <13>, <12>, <15>, <14>, <1>, <0>, <3>, <2>, <5>, <4>, <7>, <6>], [<10>, <11>, <8>, <9>, <14>, <15>, <12>, <13>, <2>, <3>, <0>, <1>, <6>, <7>, <4>, <5>], [<11>, <10>, <9>, <8>, <15>, <14>, <13>, <12>, <3>, <2>, <1>, <0>, <7>, <6>, <5>, <4>], [<12>, <13>, <14>, <15>, <8>, <9>, <10>, <11>, <4>, <5>, <6>, <7>, <0>, <1>, <2>, <3>], [<13>, <12>, <15>, <14>, <9>, <8>, <11>, <10>, <5>, <4>, <7>, <6>, <1>, <0>, <3>, <2>], [<14>, <15>, <12>, <13>, <10>, <11>, <8>, <9>, <6>, <7>, <4>, <5>, <2>, <3>, <0>, <1>], [<15>, <14>, <13>, <12>, <11>, <10>, <9>, <8>, <7>, <6>, <5>, <4>, <3>, <2>, <1>, <0>]]
//乘法表
[[<0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>, <0>], [<0>, <1>, <2>, <3>, <4>, <5>, <6>, <7>, <8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>], [<0>, <2>, <3>, <1>, <8>, <10>, <11>, <9>, <12>, <14>, <15>, <13>, <4>, <6>, <7>, <5>], [<0>, <3>, <1>, <2>, <12>, <15>, <13>, <14>, <4>, <7>, <5>, <6>, <8>, <11>, <9>, <10>], [<0>, <4>, <8>, <12>, <9>, <13>, <1>, <5>, <14>, <10>, <6>, <2>, <7>, <3>, <15>, <11>], [<0>, <5>, <10>, <15>, <13>, <8>, <7>, <2>, <6>, <3>, <12>, <9>, <11>, <14>, <1>, <4>], [<0>, <6>, <11>, <13>, <1>, <7>, <10>, <12>, <2>, <4>, <9>, <15>, <3>, <5>, <8>, <14>], [<0>, <7>, <9>, <14>, <5>, <2>, <12>, <11>, <10>, <13>, <3>, <4>, <15>, <8>, <6>, <1>], [<0>, <8>, <12>, <4>, <14>, <6>, <2>, <10>, <7>, <15>, <11>, <3>, <9>, <1>, <5>, <13>], [<0>, <9>, <14>, <7>, <10>, <3>, <4>, <13>, <15>, <6>, <1>, <8>, <5>, <12>, <11>, <2>], [<0>, <10>, <15>, <5>, <6>, <12>, <9>, <3>, <11>, <1>, <4>, <14>, <13>, <7>, <2>, <8>], [<0>, <11>, <13>, <6>, <2>, <9>, <15>, <4>, <3>, <8>, <14>, <5>, <1>, <10>, <12>, <7>], [<0>, <12>, <4>, <8>, <7>, <11>, <3>, <15>, <9>, <5>, <13>, <1>, <14>, <2>, <10>, <6>], [<0>, <13>, <6>, <11>, <3>, <14>, <5>, <8>, <1>, <12>, <7>, <10>, <2>, <15>, <4>, <9>], [<0>, <14>, <7>, <9>, <15>, <1>, <8>, <6>, <5>, <11>, <2>, <12>, <10>, <4>, <13>, <3>], [<0>, <15>, <5>, <10>, <11>, <4>, <14>, <1>, <13>, <2>, <8>, <7>, <6>, <9>, <3>, <12>]]
这种类型的二进制域的妙处在于:
- 1)其结合了“常规”整数和模运算的一些最佳部分。
- 1.1)与常规整数一样:二进制域元素也是无界的,可根据需要不断扩展。
- 1.2)与模运算一样:若对某个大小限制内的值进行运算,则所有结果也都保持在同一界限内。如,取
42
42
42的连续次幂,有
1
,
42
,
199
,
215
,
245
,
249
,
180
,
91
,
⋯
1,42,199,215,245,249,180,91,\cdots
1,42,199,215,245,249,180,91,⋯,255次幂为
4
2
255
=
1
42^{255}=1
42255=1。
对应的执行结果为:【即对应为 4 2 0 , 4 2 1 , 4 1 2 , ⋯ , 4 2 255 42^0,42^1,41^2,\cdots,42^{255} 420,421,412,⋯,42255】N=256 rawmulcache = [[None for _ in range(N)] for _ in range(N)] powcache = [None for _ in range(N)] a = 42 temp = BinaryFieldElement(1) for i in range(N): if i == 0: powcache[i] = BinaryFieldElement(1) else: for j in range(i): powcache[i] = BinaryFieldElement(binmul(temp.value, a)) temp = powcache[i] print(powcache)
[<1>, <42>, <199>, <215>, <245>, <249>, <180>, <91>, <116>, <31>, <122>, <109>, <220>, <43>, <237>, <16>, <34>, <12>, <77>, <239>, <47>, <107>, <101>, <23>, <177>, <247>, <198>, <253>, <50>, <46>, <65>, <162>, <192>, <68>, <14>, <114>, <166>, <70>, <49>, <59>, <207>, <28>, <111>, <227>, <98>, <132>, <74>, <124>, <212>, <224>, <119>, <10>, <244>, <211>, <115>, <140>, <129>, <230>, <206>, <54>, <168>, <52>, <151>, <125>, <254>, <39>, <160>, <255>, <13>, <103>, <40>, <248>, <158>, <156>, <163>, <234>, <131>, <217>, <135>, <95>, <242>, <106>, <79>, <208>, <102>, <2>, <63>, <73>, <105>, <90>, <94>, <216>, <173>, <152>, <37>, <159>, <182>, <100>, <61>, <118>, <32>, <51>, <4>, <134>, <117>, <53>, <189>, <186>, <41>, <210>, <89>, <75>, <86>, <19>, <55>, <130>, <243>, <64>, <136>, <7>, <147>, <251>, <139>, <18>, <29>, <69>, <36>, <181>, <113>, <179>, <200>, <143>, <148>, <104>, <112>, <153>, <15>, <88>, <97>, <145>, <196>, <194>, <123>, <71>, <27>, <252>, <24>, <233>, <150>, <87>, <57>, <240>, <85>, <6>, <185>, <60>, <92>, <231>, <228>, <241>, <127>, <193>, <110>, <201>, <165>, <83>, <191>, <133>, <96>, <187>, <3>, <21>, <142>, <190>, <175>, <167>, <108>, <246>, <236>, <58>, <229>, <219>, <184>, <22>, <155>, <48>, <17>, <8>, <203>, <154>, <26>, <214>, <223>, <62>, <99>, <174>, <141>, <171>, <33>, <25>, <195>, <81>, <128>, <204>, <9>, <225>, <93>, <205>, <35>, <38>, <138>, <56>, <218>, <146>, <209>, <76>, <197>, <232>, <188>, <144>, <238>, <5>, <172>, <178>, <226>, <72>, <67>, <157>, <137>, <45>, <84>, <44>, <126>, <235>, <169>, <30>, <80>, <170>, <11>, <222>, <20>, <164>, <121>, <120>, <82>, <149>, <66>, <183>, <78>, <250>, <161>, <213>, <202>, <176>, <221>, <1>]
- 1.3)与常规整数和模运算一样:遵循常规数学定律: a ∗ b = b ∗ a , a ∗ ( b + c ) = a ∗ b + a ∗ c a*b = b*a, a * (b+c) = a*b + a*c a∗b=b∗a,a∗(b+c)=a∗b+a∗c,甚至一些奇怪的新定律,如 a 2 + b 2 = ( a + b ) 2 a^2 + b^2 = (a+b)^2 a2+b2=(a+b)2(其中 2 a b 2ab 2ab项被忽略掉,因为在binary域中有 1 + 1 = 0 1+1=0 1+1=0)。
- 2)二进制域可方便地与bits配合使用:若将
2
k
2^k
2k bits范围内的数字做数学运算,则所有运算结果也在
2
k
2^k
2k bits范围内。
- 2.1)这避免了以太坊EIP-4844中的尴尬——其中 blob 的各个“chunks”必须是对52435875175126190479447740508185965837690552500527637822603658699938581184513(为BLS12-381曲线scalar域)做模运算后的数字,因此编码二进制数据涉及丢弃一些空间并在应用层进行额外检查,以确保每个元素存储的值小于 2 248 2^{248} 2248。
- 2.2)这也意味着二进制域算法在计算机上的运行速度非常快——无论是 CPU,还是理论上最优的 FPGA 和 ASIC 设计。
以上二进制域的这些特性,意味着可以做类似上面的 Reed-Solomon 编码,但完全避免“简化版Binius示例”中的整数“膨胀”问题,而且这种方式对于计算机擅长的计算类型来说非常“原生”。二进制域的“splitting分裂”属性—— 1100101010001111 = 11001010 + 10001111 ∗ x 3 \texttt{1100101010001111} = \texttt{11001010} + \texttt{10001111} * x_3 1100101010001111=11001010+10001111∗x3,然后继续切分到想要的尽可能小的粒度,这对于实现很大的灵活性也至关重要。
8. 完整版Binius协议
完整版Binius协议Python代码实现见:
现在,对“简化版Binius”做如下调整,即可获得“完整 Binius”:
- 1)在二进制域上工作
- 2)对各个bit进行commit
完整版Binius协议难以理解的原因在于:
- 其在对待bits matrix的不同方式之间来回切换。
但只要理解了二进制域,就更容易理解该协议了——因为Binius 并不依赖任何“更难的数学”。
- 这不是椭圆曲线配对,那里有越来越深的代数几何兔子洞要钻;
- 在这里,二进制域就是所需的一切。
再看一下完整的图表:
现在,应该已熟悉了大部分组件。
- 1)将hypercubes “Flatten”为grid(上面也称为方形)的思想
- 2)将grid基于二进制域进行“Extend”,即为Reed-Solomon扩展;将Reed-Solomon扩展后的grid按列处理,构建Merkle树,以该Merkle tree root为承诺。
- 3)Prover:基于所选的evaluate point(上图所示为
2, 0, 3, 4
),分前后两半,使用tensor product,分别做column combination和row combination。在做row combination时,取得的新“row” t ′ t' t′。【上图中所示,row part的tensor product结果为10, 15, 8, 12
,column part的tensor product结果为3, 2, 0, 0
】
Prover给Verifier提供:- 在所选evaluate point的evaluation值(上图中下方紫色块)
14
- merkle proof列(上图右上角紫色区域的2列),
- 与新row
t
′
t'
t′(上图中间紫色区域的一行)
11, 4, 6, 1
。
- 在所选evaluate point的evaluation值(上图中下方紫色块)
- 4)Verifier:本质是:检查“Reed-Solomon 扩展然后计算row combination”和“计算row combination然后 Reed-Solomon 扩展”之间等价性的思想。
- 4.1)对Prover所提供的merkle proof列,基于所选的evaluate point,分前后两半,使用tensor product,然后计算row combination,如上图所示,获得
3, 9
——再按little endian形式,做"Decompose into bits"表示——3为0011,9为1001。 - 4.2)对Prover所提供的新row
t
′
t'
t′
11, 4, 6, 1
,逐列重新"Decompose into bits"表示——如11对应为1011,4对应为0100,6为0110,1为0001。从而获得一个grid,对该grid做Reed-Solomon扩展 “Extend”,检查所扩展出的相应列的值,与4.1)中Prover所提供的merkle proof列经row combination和“Decompose into bits”之后的值,是否相等——即“Check that both computation paths produce the same bits here”。
- 4.1)对Prover所提供的merkle proof列,基于所选的evaluate point,分前后两半,使用tensor product,然后计算row combination,如上图所示,获得
- 5)Verifier:除以上4.1)和4.2)操作之外,Verifier还需再做一个检查:即检查该多项式在所选的evaluate point的evaluation值。
- 5.1)基于所选的evaluate point的前半部分,计算column part的tensor product,如上图所示结果为
3, 2, 0, 0
- 5.2)将column part的tensor product的结果,与Prover所提供的新row
t
′
t'
t′
11, 4, 6, 1
相乘,计算所得结果为14
。检查该结果值与Prover所提供的evaluation值是否相等。
- 5.1)基于所选的evaluate point的前半部分,计算column part的tensor product,如上图所示结果为
“完整版 Binius” 有什么新功能?基本上有三件事:
- 1)hypercubes和方形中的各个值必须为bits(0 或 1)
- 2)扩展过程将bits扩展为更多bits,方法是将bits按列分组,并暂时假装它们是更大的域元素
- 3)在row combination之后,有一个逐元素“Decompose into bits”的步骤,会将扩展转换回bits。
核心流程主要有2个:
- 1)新的扩展流程。Reed-Solomon 码有一个基本限制,若将
n
n
n个值扩展为
k
∗
n
k*n
k∗n个值,即需要以
k
∗
n
k*n
k∗n个不同值为坐标,即意味着需要有具有
k
∗
n
k*n
k∗n个不同值的域size。使用
F
2
F_2
F2(又名bits),是无法满足该要求的。为此:
- 1.1)将相邻
F
2
F_2
F2元素pack在一起,以组成更大的值。
- 如上图中,将2个bits pack在一起组成 { 0 , 1 , 2 , 3 } \{0,1,2,3\} {0,1,2,3}中的元素。因为该扩展仅需要4个evaluation points,这样就足够了。
- 在“real” proof场景中,可能需要一次pack 16个bits。
- 1.2)然后基于这些packed values做Reed-Solomon code,然后再将其unpack回bits。
其中:from binary_fields import BinaryFieldElement as B # Polynomial evaluations evals = [0, B(3)] print(multilinear_poly_eval(evals, [0])) //0 print(multilinear_poly_eval(evals, [1])) //3 print(multilinear_poly_eval(evals, [2])) //1 print(multilinear_poly_eval(evals, [3])) //2 print(multilinear_poly_eval(evals, [99])) //210 print(multilinear_poly_eval(evals, [199])) //142
- 1.1)将相邻
F
2
F_2
F2元素pack在一起,以组成更大的值。
- 2)row combination。为了使 “evaluate at a random point” checks在密码上是安全的,需要从相当大的空间中采样该点,应比hypercubes本身大得多。因此,虽然hypercubes内的点是bits,但hypercubes外的求值将大得多。如上图中,row combination最终结果为
11, 4, 6, 1
。但这样存在的问题在于:已知如何将bits pairs组合为更大的值,然后对其做Reed-Solomon扩展,但如何处理这些更大值的pairs做相同的操作呢?Binius中的技巧为:- 按位处理:如上图中11对应为1011,4对应为0100,6为0110,1为0001。组合出新的grid,再做Reed-Solomon扩展。【上图例子中取的是4行,实际可能多达128行。第一行对应 1 1 1,第二行对应 x 0 x_0 x0,第三行对应 x 1 x_1 x1,第四行对应 x 0 ∗ x 1 x_0*x_1 x0∗x1,以此类推,第128行对应 x 6 ∗ ⋯ ∗ x 0 x_6*\cdots * x_0 x6∗⋯∗x0】
完整版Binius总体流程回顾:
- 1)取出hypercubes中的bits,并将其转换成grids
- 2)将每行上相邻的bits groups视为更大的域元素,并对它们进行算术运算以对各行做 Reed-Solomon 扩展
- 3)对每bits列进行row combination,其输出为每行对应一个列bits(方形越大于 4 × 4 4\times 4 4×4,则获得的列越小)
- 4)将该输出视为一个矩阵,并再次将其各bits按行处理
这种方式有效的原因在于:
- 1)在“正常”数学中,如果开始按数字分割digits,则(通常)以任意顺序执行线性运算并获得相同结果的能力将失效。如,若以数字 345 为例,将其乘以 8,然后乘以 3,得到 8280,如果反向执行这两个运算,也会得到 8280。但如果在这两个步骤之间插入“按digit分割”操作,它就会崩溃:如果先执行 8x 然后执行 3x,将得到: 345 → × 8 2760 → [ 2 , 7 , 6 , 0 ] → × 3 [ 6 , 21 , 18 , 0 ] 345 \xrightarrow{\times 8} 2760 \rightarrow [2, 7, 6, 0] \xrightarrow{\times 3} [6, 21, 18, 0] 345×82760→[2,7,6,0]×3[6,21,18,0];但若先做 3x,然后再做 8x,则会得到: 345 → × 3 1035 → [ 1 , 0 , 3 , 5 ] → × 8 [ 8 , 0 , 24 , 40 ] 345 \xrightarrow{\times 3} 1035 \rightarrow [1, 0, 3, 5] \xrightarrow{\times 8} [8, 0, 24, 40] 345×31035→[1,0,3,5]×8[8,0,24,40]。
- 2)但在以tower结构构建的二进制域中,这种事情确实有效。原因与其可分离性有关: 若将一个大值乘以一个小值,那么在每个segment中发生的事情将保留在每个segment中。如若将 1100101010001111 \texttt{1100101010001111} 1100101010001111乘以 11 \texttt{11} 11,其结果与先将 1100101010001111 \texttt{1100101010001111} 1100101010001111分解为 11 + 10 ∗ x 2 + 10 ∗ x 2 x 1 + 10 ∗ x 3 + 11 ∗ x 2 x 3 + 11 ∗ x 1 x 2 x 3 \texttt{11} + \texttt{10} * x_2 + \texttt{10} * x_2x_1 + \texttt{10} * x_3 + \texttt{11} * x_2x_3 + \texttt{11} * x_1x_2x_3 11+10∗x2+10∗x2x1+10∗x3+11∗x2x3+11∗x1x2x3,然后将每个部分分别乘以 11 \texttt{11} 11,二者结果相同。
9. 综合起来
一般来说,零知识证明系统通过对多项式来做出statement,这些statement同时代表了关于底层evaluations的statement:正如Fibonacci例子中所示, F ( X + 2 ) − F ( X + 1 ) − F ( X ) = Z ( X ) ∗ H ( X ) F(X+2) - F(X+1) - F(X) = Z(X) * H(X) F(X+2)−F(X+1)−F(X)=Z(X)∗H(X)同时检查Fibonacci检查的所有步骤。
通过证明随机点的evaluations来检查多项式的statement:
- 已知 F F F的commitment
- 随机选择如1892470为evaluate point
- 请求在该evaluate point处的 F , Z , H F,Z,H F,Z,H evaluation proof(以及 H H H在相邻点的evaluation proof)。
- 在该随机点的检查,即代替了对整个多项式的检查:若该多项式方程不匹配,则其在特定的随机坐标处匹配的机会就很小。
实际上,效率低下的主要原因在于,在实际程序中所处理的大多数数字都很小:for 循环中的索引、真/假值、计数器等。但是,当使用 Reed-Solomon 编码“扩展”数据以提供使基于 Merkle 证明的检查安全所需的冗余时,大多数“额外”值最终会占据整个域的大小,即使原始值很小。
为了解决该问题,希望将域尽可能地小。Plonky2 将数字从 256 位数减少到 64 位数,然后 Plonky3 进一步减少到 31 位。但即使这样也并非最优。使用二进制域,可对单个bits进行操作。这使得编码变得“dense密集”:若实际基础数据有n
bits,那么编码也将有n
bits,该扩展也将有8 * n
bits,而无需额外的开销。【此处的8 * n
,是为了呼应上面提到的8倍扩展。此处的"额外的开销",是指数据量与
n
n
n紧密相关,而不会过度扩大。此处的“编码”,是指将基础数据转换为有限域表示,当采用二进制域时,可用相同位数的有限域元素来表示该基础数据。 】
现在,第三次看一下该图:
在 Binius 中,致力于multilinear多项式:
- 1)有一个hypercube P ( x 0 , x 1 . . . x k ) P(x_0, x_1 ... x_k) P(x0,x1...xk),其中 P ( 0 , 0...0 ) , P ( 0 , 0...1 ) , ⋯ , P ( 1 , 1...1 ) P(0, 0 ... 0), P(0, 0 ... 1),\cdots,P(1, 1 ... 1) P(0,0...0),P(0,0...1),⋯,P(1,1...1)各个evaluations值,为所关心的值。
- 2)为证明在某点的某evaluation,会将hypercube中相同的数据“re-interpret重新解析”为方形。
- 3)然后对每行进行扩展,使用基于bits groups的Reed-Solomon编码,以提供随机Merkle branch queries安全所需的数据冗余性。
- 4)计算行的随机线性组合,其系数设计(即tensor product)为row combination后的新row,确实持有所关心的在随机点的evaluation值。
- 5)prover:将新row(将其重新解析为如128行bits),以及一些随机选择的具有Merkle branches的列,发送给verifier。相应的数据量为 O ( N ) O(\sqrt{N}) O(N),其中新row size为 O ( N ) O(\sqrt{N}) O(N),所随机选的各列的数据量为 O ( N ) O(\sqrt{N}) O(N)。
- 6)verifier:
- 将新row重新解析为如128行bits,然后做扩展后的结果(即"extension of the row combination");将prover所发来的随机扩展Merkle branches的列做row combination的结果(即"row combination of the extension"),二者必须匹配。
- 对新row做column combination,计算结果应于prover所声称的evaluation值匹配。
这即为Binius证明系统,或者称为多项式承诺方案——为证明系统的关键基石。
10. 本文未涉及内容
本文没有涉及的内容有:
- 1)高效的算法来做行扩展:这实际上是提高verifier
O
(
N
)
O(\sqrt{N})
O(N)计算效率所必需的。使用简单的Lagrange插值,只能得到
O
(
N
2
3
)
O(N^{\frac{2}{3}})
O(N32)。
为此,需使用二进制域的快速傅里叶变换,可参考Vitalik 2019年5月12日博客 Fast Fourier Transforms(尽管确切的实现会有所不同,因为本文使用了效率较低的、不基于递归扩展的构造)。 - 2)算术化。单变量多项式很方便,因为可以做类似的 F ( X + 2 ) − F ( X + 1 ) − F ( X ) = Z ( X ) ∗ H ( X ) F(X+2) - F(X+1) - F(X) = Z(X) * H(X) F(X+2)−F(X+1)−F(X)=Z(X)∗H(X)来关联计算中的相邻步骤。在hypercubes中,“下一步”的解释并不像“ X + 1 X + 1 X+1“一样干净清晰。可以做 X ∗ k X*k X∗k并跳转到取整的powers of k k k,但这种跳跃行为会牺牲 Binius 的许多关键优势。Binius论文介绍了此问题的解决方案(如,参见第 4.3 节),但这本身就是一个“深不可测的兔子洞”。
- 3)如何真正安全地进行特定值检查。Fibonacci示例需要检查关键边界条件: F ( 0 ) = F ( 1 ) = 1 F(0) = F(1) = 1 F(0)=F(1)=1,以及 F ( 100 ) F(100) F(100)的值。但是对于“raw” Binius,在预先知道的evaluation point进行检查是不安全的。有相当简单的方法可以将已知evaluation检查转换为未知evaluatioin检查,使用所谓的sum-check协议;但本文没有讨论这些。
- 4)lookup协议是另一项最近被广泛使用的技术,用于制作超高效的证明系统。Binius 可与lookup协议结合用于许多应用程序。
- 5)超越平方根验证时长。平方根很昂贵:一个 2 32 2^{32} 232 bits的Binius 证明长约 11 MB。可使用其他证明系统来解决这个问题,以制作“Binius 证明的证明”,从而获得 Binius 在证明主statement方面的效率和较小的proof size。另一个选择是更复杂的FRI-Binius协议(详情见2024年论文《Polylogarithmic Proofs for Multilinears over Binary Towers》),可创建(如常规 FRI的)poly-logarithmic-sized proof。
- 6)Binius 如何影响“SNARK-friendly”。基本总结是,如果使用 Binius,不再需要太关心如何使计算“算术友好”:“常规”哈希不再比传统算术哈希更有效率,乘法模 2 32 2^{32} 232或模 2 256 2^{256} 2256与乘法模 p p p相比不再是一个大难题,等等。但这是一个复杂的话题;当一切都以二进制完成时,很多事情都会发生变化。
期望在未来几个月内基于二进制域的证明技术会有更多改进。
参考资料
[1] Vitalik 2024年4月29日博客 Binius: highly efficient proofs over binary fields