ZK L2 三剑客之一的 StarkNet,其原生编程语言 Cairo 的表现力从何而来?

CertiK
CertiK 机构得得号

Dec 16, 2022 CertiK 专注于区块链安全,被称为区块链安全世界的「普华永道」。

摘要: 在本文中,我们将对 Cairo 语言及其独特功能进行概述。

随着以太坊合并的完成,PoW 从生态系统中退出,以太坊也正式转为 PoS 机制。与此同时,以太坊 Layer 2 扩展解决方案的格局也正在迅速演变。

从广义上来讲,Layer 2 解决方案包括两种主要类型:optimistic Rollup 和 ZK(ZeroKnowledge) Rollup

而在 ZK Rollup 中,两个主要的 ZK 证明系统是:

  • ZK-SNARK(Zero-Knowledge Succinct Non-Interactive Argument of Knowledge)——简洁非交互零知识证明
  • ZK-STARK(Zero-Knowledge Scalable Transparent Argument of Knowledge)——可扩展、透明零知识证明

StarkNet 是用于扩展以太坊的 L2 网络,也是使用 STARK 证明系统构建的通用的 ZK Rollup,它的基础设施和智能合约均使用的是Cairo 编程语言

在本文中,我们将对 Cairo 语言及其独特功能进行概述。

请注意,Cairo 是一种相对较新的且正在发展的语言,其功能在未来可能会发生变化,本文内容主要是基于当前版本的 Cairo(0.10.0)。

Cairo 编程语言简介

Starknet 架构

在深入了解 Cairo 语言之前,我们可以先了解一下 Starknet 的架构。

图①显示了 Starknet 的主要组成部分、它与以太坊的关系,以及它的一般交易顺序。

在这一部分中,我们将重点讨论Sequencer、Prover 和 Verifier——这是理解 Starknet 工作原理必不可少的一步。

图①:Starknet 架构

  • Sequencer负责对交易进行排序、验证并将交易打包到块中。目前,Sequencer 由 Starkware 以中心化的方式运行,但该公司计划在未来将 Sequencer 去中心化。
  • Prover则负责生成关于 Sequence 执行轨迹有效性的加密证明。目前,这项工作是由单一的 Prover,即「Share Prover」或「SHARP」执行的。
  • Verifier是以太坊 L1 上,用于验证 Starknet Prover 产生证明的智能合约,如果运行成功,其将更新以太坊 L1 上的状态用于记录保存。

因此,其他用户就可以在以太坊 L1 上查询Starknet Core 智能合约的状态,并验证Starknet 上的某项交易是否已经成功执行。

值得注意的是,只能被 Prover 看到但Verifier 看不到的某些 Core 指令被称为 「提示」,它是一段嵌入在 Cairo 程序中的 Python 代码(我们下文会提到)。

这种Prover和 Verifier 之间的不对称性使得一些零知识应用成为可能——例如,用户可以证明他有一个加密问题的解决方案,但不需要从以太坊 L1 的 Verifier 角度来公开这个解决方案。

Cairo 概述

这一部分主要分享 Cairo 语言的概述,包括其数据类型和内存模型等构建模块,以及一些独特的功能,如内置、隐式参数、提示和嵌入式测试函数。

Cairo 程序和 Cairo 合约

在 Starknet 中,Cairo 程序和 Cairo 合约之间有着明显的区别

Cairo 程序是无状态(stateless)的。举个例子,不需要任何状态变量在 Starknet 上持续存在,一个 Cairo 程序即可对一个给定的输入执行哈希运算,并证明输出与特定的目标输出相匹配。

而 Cairo 合约是有状态(stateful)的。通过 Cairo 合约,状态变量会持续保留在区块链上,从而在 Starknet L2 上启动诸如 ERC20 token、自动做市商等应用程序。

数据类型

Cairo 的原始数据类型是「felt」,它代表了「field element」。felt 是一个整数,范围是 -P/2 < x

Cairo 对使用「felt」的算术运算没有内置的溢出保护。当出现溢出时,就会将其和 P 的适当倍数相加或相减,使结果回到这个范围内或对 P 进行有效的模数运算。

通过使用 felt 作为构建块,Cairo 支持包括元组、结构和数组在内的其他数据类型。

作为一种低级语言,Cairo 中指针的使用率也极高。

大括号 [x] 用于返回内存位置 x 中的值,而 &x 则用于返回变量 x 的内存地址。图②所示的例子声明了一个带有内存分配的数组,返回的指针与偏移量一起被用来表示数组中不同元素的内存位置。

图②:Cairo 中的数组

更复杂的数据类型(如 hashmap),可以用 storage_var 装饰器实现为函数,从而允许进行读写操作。

图③: Cairo 中的hashmap

内存模型

Cairo 拥有只读的非确定性内存,这意味着每个内存单元中的值只能被写入一次,而且在 Cairo 程序执行过程中不能被改变。

因此,根据一个数值是否被写入内存位置,断言的指令[x]==7 可能意味着以下两种情况

① 从内存位置读取值 x 并验证该值是 7

② 如果内存单元 x 还没有被写入,则将值 7 写入内存单元 x

在 Cairo 中有三个「register」用于低级别的内存访问,即「ap」、「p」和「pc」。

  • ap:分配指针,用于显示未使用的内存的起始位置
  • fp:帧指针,指向当前的函数
  • pc:程序计数器,指向当前指令

使用以上定义,诸如 [ap]=[ap-1]*[fp] 这样的表达式将取前一个分配指针的值,乘以帧指针的值,并将结果写入当前分配指针的内存位置。

由于 Cairo 的只读内存特性,它通常使用递归而不是 for 循环。例如,图④所示的函数使用递归来计算第 n 个斐波那契数。

图④:一个递归的斐波那契函数

内置参数和隐式参数

与 EVM 中的预编译合约类似,Cairo 包含内置函数——这些内置函数是经过优化的低级执行单元,可执行预定义的计算,如哈希函数、系统调用和范围检查。

所有使用内置程序的函数都需要获得内置程序的指针作为参数,并返回一个更新的指针到下一个未使用的实例。

由于这种模式非常普遍,Cairo 为它创造了一个语法糖(Syntactic sugar),叫做「隐式参数」。如图⑤所示,大括号声明 hash_ptr0 是一个「隐式参数」,允许该函数调用预定义的 hash2() 函数。这就自动给函数添加了一个参数和一个返回值,所以工程师不必再手动添加每个利用低级执行单元的函数。

图⑤:内置参数和隐式参数

提示

「提示」是 Cairo 语言的一个独特功能。

它是一个 Python 代码块,由 Starknet Prover 执行于下一条指令之前。「提示」可以与 Cairo 程序的变量或内存进行交互,允许工程师在 Cairo 程序中运用 Python 的功能。为了使用提示,Python 代码需要通过 %{ 和 %} 作为代码首尾(如图⑥所示)。

请注意,Starknet Verifier在 Cairo 程序执行中看不到「提示」,这允许用户在不向以太坊 L1 Verifier 提供任何秘密信息的情况下生成一个证明。此外,「提示」只能在 Cairo 程序中使用,而不用于 Cairo合约,除非该合约被 Starknet 列入白名单。

图⑥:Cairo中的提示

测试函数

名称以 test_开头的外部函数在 Cairo 中被解释为单元测试。这使得工程师可以直接在 Cairo 中实现单元测试,而无需编写单独的测试文件。图⑦显示了一个验证简单算术运算结果的测试函数。

图⑦: Cairo 中的测试函数

在 Cairo 中编写智能合约

在 Cairo 中编写智能合约需要熟悉它的一些基本设计模式。本文的这一部分将介绍一些简单且常见的模式,并在适用的地方将它们与 Solidity 进行比较。

库导入,而非合约继承

Cairo 语言并不像 Solidity 那样支持合约继承。

为了使用其它 Cairo 合约中的逻辑或存储变量,工程师需要导入另一个合约(通常称为「库」),并使用该合约的命名空间来代替相关函数或状态变量。

库定义了可重用的逻辑和存储变量,然后可以通过合约公开这些变量。

例如,图⑧中的 ERC20 库包含了传递函数的实现,但不能直接调用(没有任何装饰器的函数被默认为内部函数),而图⑨中的 ERC20 合约通过使用「ERC20」命名空间、函数名称以及外部装饰器来公开传递函数。

图⑧:ERC20 库中的 transfer() 函数

图⑨:ERC20 合约中的 transfer() 函数

访问控制

Cairo 语言并不像 Solidity 那样支持修饰符。

为了实现对特权函数的访问控制,工程师需要通过内置的 get_caller_address() 函数提取合约的调用者(相当于 Solidity 中的 msg.sender),并检查调用者是否拥有必要的权限——就像 Ownable 库中的所有权检查,如图⑩所示。

图⑩:Cairo的访问控制

可升级的合约

在 Cairo 中,可通过代理合约与实现合约来实现合约的可升级(类似于 Solidity 中的代理模式)。

其中,代理合约包含一个__default__函数(类似于 Solidit 中 fallback() 函数),如图⑪所示。

实现合约导入「proxy」命名空间并初始化代理,并包含了一个具有适当访问控制的合约升级机制(类似于 Solidity 中的 UUPS 代理模式)。

如果要部署一个可升级的合约,首先需要声明一个实现合约类,并计算它的类哈希值。然后用实现合约的类哈希值,以及描述调用初始化代理合约的输入来部署代理合约。

图⑪:代理合约默认函数

Cairo 发展路线图

Cairo 语言正在积极开发中,它的重大升级 Cairo1.0 计划在 2023 年初进行。目前,Starkware 已经开源了 Cairo 1.0 编译器的第一个版本。

Cairo1.0 引入了「Sierra」,这是一个介于 Cairo 1.0 和 Cairo 字节码之间的安全中间表示,可以证明每一次 Cairo 的运行。

此外,Cairo 1.0 将包含简化的语法和更容易使用的语言结构。例如在 Cairo 中,「for」循环将成为可能,且将支持布尔表达式。除此之外,还将引入本地 uint256 数据类型,以及常规的整数除法和相关类型的溢出保护。

Cairo1.0 还将加入改进的类型安全保证,以及更直观的库(如词典及数组)。Cairo1.0 将带来的功能十分值得期待,这些功能可以有助于极大地缓解当前版本部分开发者的痛点。

链得得仅提供相关信息展示,不构成任何投资建议
本文系作者 CertiK 授权链得得发表,并经链得得编辑,转载请注明出处、作者和本文链接

更多精彩内容,关注链得得微信号(ID:ChainDD),或者下载链得得App

分享到:

相关推荐

    评论(0

    Oh! no

    您是否确认要删除该条评论吗?

    分享到微信