这篇文章介绍了zk-STARKs和Cairo的基本概念及其在Ethereum扩展中的应用。重点讨论了EVM与Cairo语义之间的技术差异,特别是如何通过Warp编译器来实现EVM字节码到Cairo的转换,解决了这些不同导致的编程问题,并介绍了相关的算法和数据结构。文章深度解析了实现的原理与应用,内容丰富。
by Greg Vardy @0xGreg_
几周前,在开发了一个EVM字节码到Cairo转译器的初步草图后,StarkWare将工作交接给了Nethermind的Nubia团队。我们一直在致力于Warp转译器,以架起EVM兼容语言与StarkNet之间的桥梁。本文将简要介绍zk-STARKs和Cairo是什么,以及它们为何在扩展以太坊方面具有独特的关键角色。随后,我将讨论EVM和Cairo语义之间的一些技术差异,以及我们迄今为止是如何解决这些问题的。
zk-STARKs是由StarkWare的优秀团队创建的,是一类相对年轻的密码学工具,广泛被称为zk-SNARKs。毫不夸张地说,这些工具可能是近年来密码学家们所创造的一些最重要和最强大的工具。zk-SNARKs和zk-STARKs允许你生成一个证明,证明一个特定的计算产生了一个特定的结果,而不透露太多关于计算及其数据的细节。它们都允许任何人以比仅仅执行计算更快的方式来验证这个证明。最后一点是一个重要的观点。这意味着验证的复杂性增长远远慢于计算的复杂性(对于zk-STARKs来说是O(poly-log(N)))。而zk-SNARKs依赖复杂的椭圆曲线和椭圆曲线配对,做出强有力的密码学假设,并且不具备后量子安全性,大多数方案需要一个可信设置(某人需要选择椭圆曲线和配对的设置)。而zk-STARKs则不需要可信设置,具有后量子安全性,做出更简单的密码学假设,并且不依赖椭圆曲线配对(通常亲切地被称为“月球数学”)。深入讨论zk-SNARKs和STARKs的所有细节将需要几千个字,因此如果你有兴趣了解更多,我推荐你阅读Vitalik的一篇关于STARKS的优秀介绍和StarkWare提供的一些优秀资源。
对于大多数zk-SNARK方案,使程序适合生成证明需要将其转化为代数电路,然后转化为“二次算术程序”(QAP)。我不会详细说明,但正确执行这一步是一个非常不简单的任务。因此,对于你编写的每个新应用,你都需要手动编写一个伴随的电路和证明系统,对任何不是密码学高手的人来说这都是一项艰巨的任务。结果,你将得到一个特定于应用的证明系统。这种情况是不可扩展的;例如,你刚花了几个月在某个(虚构的)L2上编写了一个自动化做市商,现在你的社区在恳求你在这个L2上提供衍生品服务(谁不喜欢低费用呢?!)。你需要编写新的电路和证明系统,这意味着在L1上部署一个新的验证合约,这意味着需要花费更多的时间对该合约进行安全审计。这听上去并不有趣。
解决上述问题是Cairo如此出色的一个重要部分。使用Cairo,你可以忘记所有重复的复杂代数电路编写工作。在我们前一段落的例子中,创建一个新的衍生品服务只需要用Cairo编写一些新逻辑,仅此而已!我们现在可以通过同一个智能合约在L1上验证来自我们的AMM和我们的衍生品服务的L2交易的有效性。这种Cairo的聪明之举真是不可思议,这意味着你可以使用一个证明来声明一系列不同计算的有效性(例如代币交换、NFT交易等)。
将EVM字节码转译为Cairo有一些有趣的特性。第一个显著的区别是EVM将所有数字(以及数据)视为256位整数,而Cairo的数字类型是一个称为felt
的域元素。这意味着Cairo中的算术运算是模p进行的,其中p是一个较大的素数且p < 2^256。由于p < 2^256,我们需要在Cairo中重新创建256位整数。此问题通过一个包含两个成员的结构(高位和低位,表示256位整数)来解决,然后在该结构上实现与其EVM对应操作(ADD、MUL、SUB等操作码)相等的算术运算。这种方法允许我们将Cairo中使用的模算术映射到EVM中使用的标准算术。
Cairo和EVM之间的另一个区别是Cairo中的内存是不可变的。把Cairo中的内存视为一次性写入是很有帮助的:一旦你向一个内存单元写入,那个内存单元中的值在程序执行期间不能改变。在Cairo中模拟EVM的可变内存稍微复杂一些。幸运的是,Cairo标准库中有一个结构可以帮助我们做到这一点。它看起来像这样:
struct DictAccess:
member key : felt
member prev_value : felt
member new_value : felt
end
这里的想法是,我们需要使可读写结构适合证明和验证。如果我们有一个键,其值为10,并且我们将该值更改为42,我们可以通过查看prev_value变为10和new_value变为42来验证这个更改是合理的。
EVM将内存指定为可扩展的字节数组。我们可以直接模拟这个字节数组:只需将字节大小的felt存储到值中。然而,这样做占用了较多的内存。此外,EVM中的内存读/写通常是32字节宽的。这意味着每次要获取或修改一个值时,我们必须进行32次逐字节读/写操作!幸运的是,还有更好的解决方案:位打包。我们不是将每个字节单独存储,而是将它们打包成128位整数(我们无法打包256位,因为felt太小)。因此,我们将字节数组划分为16字节大小的段。每次我们需要访问一个32字节的字,都会查找与该字重叠的段,并通过位操作检索所需的字节。
我们刚刚开源了Warp,因此如果你希望帮助以太坊扩展,请确保查看我们的GitHub仓库。如果你有任何问题,请随时在Twitter上给我发私信!
感谢Artem Yurchenko对内存相关部分提供的意见,以及Szymon Kulec和Shahar Papini的审阅。
我们希望你成为我们旅程的一部分。
- 原文链接: medium.com/nethermind-et...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!