本文主要对Hermez 2.0 zkEVM设计思路进行简单介绍。
Fibonacci 状态机
先从Fibonacci状态机示例开始,Fibonacci是一个序列,可以通过两个寄存器A和B表示的状态机:
初始条件为: , 并且寄存器的状态满足以下关系:
可以将寄存器的状态表示为多项式, 定义域为 为 上的8次单位根。可以得到以下关系:
将寄存器状态进一步表示为恒等关系式为:
但是这个恒等关系并不完全成立:
- 寄存器是非循环的,当在 估值时:
- 可以改变初始条件,例如 , 同样满足恒等关系。
为了解决这些问题,可以添加一个辅助寄存器C:
C 对应的多项式为:
通过辅助寄存器, 新的多项式恒等关系为:
当时, 恒等关系满足:
若初始条件为: , 多项式恒等关系为:
上例中:
状态机证明
多项式关系可以通过多项式承诺证明,例如 Kate 和基于FRI的承诺。
多项式承诺满足 binding
和 hiding
的关系。
简单的状态机
状态机可以表示为下图过程:
下述汇编指令表示带有两个寄存器 A 和 B, 接受一个外部输入:
其中:
- : 将一个输入的值保存到寄存器 X;
- : 将一个常量移动到寄存器 X ;
- : 将寄存器 A 和 B的值加在一起,将输入保存到寄存器 A 中;
当输入为7的时候,执行的路径为:
在上述的指令中, 表示寄存器 的下一步的状态。
指令会重置状态的值。
可以在脱离指令的情况下,通过一系列值推导寄存器的下一个状态。此时,需要添加辅助状态和选择子(selectors)来表示这种关系。寄存器 A 和 B 更新的状态是辅助寄存器和选择子的线性关系,如下图所示:
其中:
- : 选择子,用以包含寄存器 是否在线性组合中;
- : 选择子,以表示是否将线性组合的结果保存到寄存器 中;
- : 包含执行程序的输入;
- : 包含指令的固定值。
通过引入输助变量,可以得到下述的表格:
因此寄存器代数表达式的关系为:
可以将寄存器状态表示为四步的多项式 , 定义域为: , 可以定义一个循环关系:
则程序执行完全由常量(公开)多项式: 和 表示。
多项式 可以公开的承诺的多项式,对于同一个程序,可以在不同的初始条件下进行执行。
在上述程序中,执行结果为 。
选择条件
下面将在汇编程序中添加 指令,若执行操作为0, 将会跳到指定的位置。
在下面的程序中, 会跳到位置 4 ,若 为0的话。
当出现条件语句的时候,执行路径的长度不是固定的,依赖于 公开输入:
第一执行步骤是5步,每二个是4步。
管理条件选择
可以引入一种新的模型,管理条件选择(conditional jump):
上述添加了 , PC是一种特别的寄存器,包含程序执行指定的位置。
使用 表示状态机的线性组合,用以简化约束条件的描述,如下所示:
若 为0, 指令将会跳到地址. 下面将介绍 如何检查 是否为0.
为了检查域 的某个元素是否为0, 可以通过 是否有逆元素判断 。若 , 有一个乘法逆元。
因此可以使用下述定义,限制 检查。
可以把这两个方程合成一个约束:
可以引入下述机制以引入 jumps
:
上述添加一个选择子 , 以编码 指令,并表述 的行为。则约束集合如下所示:
由以上可知:
- 若 , 则 ;
- 若 , 则 .
则公开输入为分别为7 和 3的时候,执行路径分别为:
其中 列 表示为 的逆。
并且 是非常重要的寄存器,因为它可以修改指令序列的执行方式。
目前多项式没有经过预处理,表格的值不仅依赖于程序,还依赖输入的值。因此,需要保证待验证的程序是正确的。
程序执行的证明
目前为止,可以证明每个指令都正确执行,但是怎么证明正确执行了指定的程序指令?需要检查每个执行的指令在我们的程序中,但是如何简洁实现这种证明方式?
为了实现这个目标,我们提供了每个指令的编码方式,并且检查每个执行指令的编码确实在程序的指令中。
首先来展示如何对指令编码,作为一个简单示例, 采用4位编码一个指令(实际中可能需要32位编码),4位编码如下所示:
去掉数值 , 采用 -7 到 7的范围,可以编码 的域元素为:
因此可以约束
可以约束上述的条件为:
直接以2为底表示 , 以避免sum 运算。
下面将介绍 如何编码程序执行的每个一指令。
可以通过下面的规则编码指令:
另外,需要检查 选择子是二进制的,由4位组成,即
当 , 其表示 .
为了证明该程序,每个指令通过其代码和位置唯一识别,上例中采用4位来表示位置。
定义 , 由每个指令和位置来定义表示:
因此相应的 表示为:
我们可以采用 来编码程序路径:
其中 指令 设置 值为0, 使这个多项式构成循环。
那该如何检查执行了指定的程序了呢?
这时可以采用Plookup协议,检查对应的项目被正确有执行,采用Plookup 检查:
由此表明, 若指令的trace
包含在程序的ROM
中,则执行的指令是实际的程序。
证明执行路径的恒等式
其中:
并且,我们还需要添加下述的检查:
$$
\begin{aligned}
& const(x) + 7 \subset {0,1,\cdots,14}, \
& addr(x) \subset {0,1,\cdots, 15}, \
& position(x)\subset {0,1,\cdots, 15}, \
& PC(x) \subset {0,1,\cdots, 15}, \
& insTrace(x) \subset \mathsf{ROM}(x).
\end{aligned}
\begin{aligned}
& \textsf{instruction}(x) := 2^{13}(\textsf{const(x)}+7)+ 2^9 \cdot addr(x) + 2^5\cdot \textsf{selJMPIZ}(x)+ 2^4\cdot \textsf{setB}(x) + 2^3\cdot \textsf{setA}(x)+ 2^2\cdot \textsf{inFreeIn}(x)+ 2\cdot \textsf{inB}(x) + \textsf{inA}(x), \
& \textsf{ROM}(x) := 2^{17} \cdot \textsf{position}(x) + \textsf{instruction}(x), \
& \textsf{insTrace}(x):= 2^{17}\cdot \textsf{PC(x)} + \textsf{instruction}(x).
\end{aligned}
$$
最后,需要检查一系列选择子是二进制的,即:
关于状态机中的多项式:
- 需要对多项式 和 进行承诺;
- 惟一需要预处理的多项式为:
模块化设计
分而治之
对上面的思路进一步扩展,例如要处理乘法运算。
可以通过增加列的方式, 来表述这这些运算,但是这将导致设计异常复杂难以处理。 但是我们可以采用divide and conquer
策略:
-
zk-EVM
架构由多个不同的相互连接的状态机构成; - 每个状态机证明其相应任务的执行路径;
- 不同状态机的相关列多项式通过
inclusion proofs
关联在一起(采用plookup
)。
下面将通过一个示例展现这个过程:
- 设计一个状态机 执行32位的数值操作;
- 通过
inclusion proof
将该状态机和主状态机连在一起。
数值状态机
数值状态机能够执行32位的 sum
, substraction
, multiplication
, division
的运算, 采用如下所示的5个寄存器:
可以将上述关系表示为一个在子群 上的循环多项式的恒等式,即:
注意需要约束 在 定义域 的值为 32 bit.
设计如下状态机检查 上述的操作:
其中 用于表示寄存器 的下一个状态。采用 表示是否已准备好检查 实际的值。
其中 并不依赖输入,是不变的。
用于包含数值操作的一些值。
因此,定义数值状态机的多项式恒等式如下所示:
因为 的值都来源于 , 所以有检查约束条件: . 下图展示了数值状态机的设计图:
扩展主状态机
基于主状态机,可以将 其扩展5个寄存器,添加一个arith
标签,将数值状态机边上主状态机。当airth
标签为1的时候,允许检查 数值运算。整体的设计图如下所示:
下图展示了如何连接两个状态机,关键点在于当 flag
值为1时,需要确何寄存器的值当 值为1时 在数值表中存在,即数值表的约束条件成立。因为,需要保证以下包含关系:
从下图可以看出,我们采用
Plookup
连接主状态机和其它具体的状态机。
它可以允许以模块化方式设计虚拟状态机,最后采用零知识证明技术验证。
参考
https://docs.hermez.io/zkEVM/architecture/introduction/
https://eprint.iacr.org/2020/315.pdf