目前,每个以太坊节点都必须验证以太坊虚拟机中的每笔交易。这意味着每笔交易都会增加每个人都必须做的工作来验证以太坊的历史。更糟糕的是,每笔交易都需要由每个新节点进行验证。这使得每个新节点同步到网络所需的工作不断增长。我们想为以太坊区块建立一个有效性证明来避免这种情况。有两个目标:
- 制作一个支持智能合约的 zkrollup
- 为每个以太坊区块创建有效性证明
这意味着为 EVM + 状态读取/写入 + 签名制作有效性证明。
为了简化,我们将证明分为两个部分:
一. 状体证明
状态/内存/堆栈操作已正确执行。这不会检查是否读取/写入了正确的位置。我们允许我们的证明者在这里选择任何位置,并在 EVM 证明中确认它是正确的。
状态证明帮助 EVM 证明检查所有随机读写访问记录是否有效,首先按其唯一索引对它们进行分组,然后按访问顺序对其进行排序。我们称之为访问顺序ReadWriteCounter,它计算访问记录的数量,也可以作为记录的唯一标识符。生成状态证明时,也会生成状态证明BusMapping并将其作为查找表共享给 EVM 证明。
1. 随机读写数据
在 EVM 中,解释器有能力对当前范围内的账户余额、账户存储或堆栈和内存等数据进行任何随机访问,但很难 EVM 电路不断跟踪这些数据以确保它们不时的一致性。因此,EVM 证明具有状态证明,可提供由 索引的随机读写访问记录的有效列表GlobalCounter作为查找表,以便随时进行随机访问。
我们称之为随机读写访问记录列表,BusMapping因为它就像计算机中的总线,在组件之间传输数据。类似地,只读数据作为查找表加载到电路中以进行随机访问。
1.1. 状态证明维护EVM 证明的随机可访问数据的读写部分。
目标 | 指数 | 访问者 | 描述 |
---|---|---|---|
Block |
{enum} |
Read |
块常量在执行块之前决定 |
BlockHash |
{index} |
Read |
以前的 256 个块哈希作为编码字数组 |
AccountNonce |
{address} |
Read , Write |
帐户的随机数 |
AccountBalance |
{address} |
Read , Write |
账户余额 |
AccountCodeHash |
{address} |
Read , Write |
帐户的代码哈希 |
AccountStorage |
{address}.{key} |
Read , Write |
帐户的存储作为键值映射 |
Code |
{hash}.{index} |
Read |
以字节数组形式执行的代码 |
Call |
{id}.{enum} |
Read |
调用者决定的调用上下文(包括 EOA 和内部调用) |
CallCalldata |
{id}.{index} |
Read |
调用的 calldata 作为字节数组(仅适用于 EOA 调用) |
CallSignature |
{id}.{index} |
Read |
调用的签名为字节数组(仅适用于 EOA 调用) |
CallState |
{id}.{enum} |
Read , Write |
调用的内部状态 |
CallStateStack |
{id}.{index} |
Read , Write |
调用的堆栈作为编码的单词数组 |
CallStateMemory |
{id}.{index} |
Read , Write |
调用的内存为字节数组 |
1.2. 状态证明中记录的操作
状态证明中记录的操作有:
- Memory: 调用的内存为字节数组
- Stack: 调用堆栈作为 RLC 编码的字数组
- Storage: 账户的存储为键值映射
- CallContext: 调用的上下文
- Account: 账户状态(nonce, balance, code hash)
- TxRefund: 退还给 tx 发件人的价值
- TxAccessListAccount:帐户访问列表的状态
- TxAccessListAccountStorage:帐户存储访问列表的状态
- AccountDestructed: 账户销毁状态
每个操作使用不同的参数进行索引。有关完整的详细信息,请参阅RW 表。
所有表键的连接成为数据的唯一索引。每条记录都将附加一个ReadWriteCounter,并且记录被限制为首先按其唯一索引分组,并按其ReadWriteCounter递增排序。鉴于对先前记录的访问,每个目标都有自己的格式和更新规则,例如,其中的值Memory应该适合 8 位。
2. 电路约束
约束分为两组:
影响所有操作的全局约束,例如键的字典顺序。 对每个操作的特殊限制。每个操作类型都使用一个类似选择器的表达式,以启用仅适用于该操作的额外约束。 对于必须保证正确排序/转换值的所有约束,我们在固定查找表的帮助下使用范围检查连续单元格之间的差异。由于我们使用查找表来证明正确的排序,对于必须排序的每一列,我们需要定义它可以包含的最大值(这将对应于固定的查找表大小);这样,按顺序排列的两个连续单元格将在表中找到差异,并且反向排序将使差异回绕到一个非常高的值(由于字段算术),导致结果不在桌子。
确切的约束列表在 python 代码实现中作为注释详细记录。
对应的 Python 代码
https://github.com/scroll-tech/zkevm-specs/blob/master/src/zkevm_specs/state.py
二. EVM 证明
检查是否在正确的时间调用了正确的操作码。它检查这些操作码的有效性并确认这些操作码和状态证明中的每一个都执行了正确的操作。
EVM 证明通过验证块中包含的所有交易具有正确的执行结果,认为状态树根的转换是有效的。
EVM 电路重新实现了 EVM,但从验证的角度来看,这意味着证明者可以帮助提供提示,只要它不与结果相矛盾。例如,证明者可以提示此调用是否会恢复,或者此操作码是否遇到错误,然后 EVM 电路可以验证执行结果是否正确。
一个区块中包含的交易可以是简单的以太币转账、合约创建或合约交互,而且由于每笔交易都有可变的执行轨迹,我们不能有固定的电路布局来验证特定区域的特定逻辑,而是需要一个芯片来验证特定区域的特定逻辑。能够验证所有可能的逻辑,并且该芯片会重复自身以填充整个电路。
1. 自定义类型
我们为类型提示和可读性定义了以下 Python 自定义类型:
姓名 | 类型 | 描述 |
---|---|---|
GlobalCounter | int | 随机访问BusMapping的顺序,应该是顺序的 |
BusMapping | List[Tuple[int, ...]] | GlobalCounter以索引为索引的随机读写访问数据列表 |
2.电路约束
重复芯片有2个主要状态,一个是调用初始化,另一个是字节码执行。
三.总结
只有在验证了上面两个证明都有效之后,我们才有信心以太坊区块被正确执行。