“技術極客” 和 “使用者” 之間的最大代溝,更多歸因於人們對 Starknet 的認知欠缺
作者:Shew,Faust,極客 Web3
顧問:CryptoNerdCn,Starknet 生態核心開發者,瀏覽器端 Cairo 開發平臺 WASM Cairo 創始人
封面:Starknet
摘要:Starknet 最主要的幾大技術特性,包括利於 ZK 證明生成的 Cairo 語言、原生級別的 AA、業務邏輯與狀態存儲相獨立的智慧合約模型。
Cairo 是一種通用的 ZK 語言,既可以在 Starknet 上實現智能合約,也可以用於開發偏傳統的應用,其編譯流程中引入 Sierra 作為中間語言,使得 Cairo 可以頻繁反覆運算,但又不必變更最底層的位元組碼,只需要把變化傳導至中間語言身上; 在 Cairo 的標準庫內,還納入了帳戶抽象所需要的許多基本數據結構。
Starknet 智慧合約將業務邏輯與狀態數據分開來存儲,不同於 EVM 鏈,Cairo 合約部署包含 「編譯、聲明、部署」三階段,業務邏輯被聲明在 Contract class 中,包含狀態數據的 Contract 實例可以與 class 建立關聯,並調用後者所包含的代碼;
Starknet 的上述智慧合約模型利於代碼複用、合約狀態複用、存儲分層、檢測垃圾合約,也利於存儲租賃制和交易並行化的實現。 雖然後兩者目前暫未落地,但 Cairo 智慧合約的架構,還是為其創造了 “必要條件”。·
Starknet 鏈上只有智慧合約帳戶,沒有 EOA 帳戶,從一開始就支援原生級別的 AA 帳戶抽象。 其 AA 方案一定程度吸收了 ERC-4337 的思路,允許用戶選擇高度定製化的交易處理方案。 為了防止潛在的攻擊場景,Starknet 做出了諸多反制措施,為 AA 生態做出了重要的探索。
正文:繼 Starknet 發行代幣之後,STRK 逐漸成為乙太坊觀察者眼中不可或缺的要素之一。 這個向來以「特立獨行」「不重視用戶體驗」而聞名的乙太坊 Layer2 明星,就像一個與世無爭的隱士,在 EVM 相容大行其道的 Layer2 生態裡默默的開闢自己的一畝三分地。
由於太過忽視使用者,甚至公開在 Discord 開設「電子乞丐」頻道,Starknet 一度遭到擼毛黨的抨擊,在遭噴「不近人情」的同時,技術上的深厚造詣瞬間變得「一文不值」,似乎只有 UX 和造富效應才是一切。《金閣寺》中那句「不被人理解成了我唯一的自豪」,簡直就是 Starknet 的自我寫照。
但拋開這些江湖瑣事,單純從代碼極客們的「技術品味」出發,作為 ZK Rollup 先驅之一的 Starknet 和 StarkEx,幾乎就是 Cairo 愛好者眼中的瑰寶,在某些全鏈遊戲開發者心中,Starknet 和 Cairo 簡直就是 web3 的一切,無論是 Solidity 還是 Move 都無法與之相提並論。 現如今橫亙在「技術極客」和「使用者」之間的最大代溝,其實更多歸因於人們對 Starknet 的認知欠缺。
抱著對區塊鏈技術的興趣與探索欲,以及對 Starknet 的價值發現,本文作者從 Starknet 的智慧合約模型與原生 AA 出發,為大家簡單梳理其技術方案與機制設計,在為更多人展示 Starknet 技術特性的同時,也希望讓人們了解這個 “不被人所理解的獨行俠”。
Cairo 語言極簡科普
下文中我們將重點討論 Starknet 的智慧合約模型與原生帳戶抽象,說明 Starknet 是如何實現原生 AA 的。 讀完此文,大家也可以理解為什麼 Starknet 中不同錢包的助記詞不能混用。
但在介紹原生帳戶抽象前,讓我們先瞭解下 Starknet 獨創的 Cairo 語言。 在 Cairo 的發展歷程中,出現了名為 Cairo0 的早期版本,以及後來的的現代版。 Cairo 的現代版本整體語法類似於 Rust,實際上是一門通用的 ZK 語言,除了可以在 Starknet 上編寫智慧合約,也可以用於通用應用的開發。
比如我們可以用 Cairo 語言開發 ZK 身份驗證系統,這段程式可以在自己搭建的伺服器上運行,不必依賴於 StarkNet 網路。 可以說,任何需要可驗證計算屬性的程式都可以用 Cairo 語言來實現。 而 Cairo 可能是目前最利於生成 ZK 證明的程式設計語言。
從編譯流程來看,Cairo 使用了基於中間語言的編譯方法,如下圖所示。 圖中的 Sierra 是 Cairo 語言編譯過程中的一道中間形態(IR),而 Sierra 會再被編譯為更底層的二進位代碼形式,名為 CASM,在 Starknet 節點設備上直接運行。
引入 Sierra 作为中间形态,便于 Cairo 语言增加新特性,许多时候只要在 Sierra 这道中间语言上做手脚,不必直接变更底层的 CASM 代码,这就省去了很多麻烦事,Starknet 的节点客户端就不必频繁更新。这样就可以在不变更 StarkNet 底层逻辑的情况下,实现 Cairo 语言的频繁迭代。而在 Cairo 的标准库内,还纳入了账户抽象所需要的许多基本数据结构。
Cairo 的其他创新,包括一种被称为 Cairo Native 的理论方案,该方案计划把 Cairo 编译为能适配不同硬件设备的底层机器代码,Starknet 节点在运行智能合约时,将不必依赖于 CairoVM 虚拟机,这样可以大幅度提升代码执行速度【目前还处于理论阶段,未落地】。
Starknet 智能合约模型:代码逻辑与状态存储的剥离
与 EVM 兼容链不同,Starknet 在智能合约系统的设计上,有着突破性的创新,这些创新很大程度是为原生 AA 以及未来上线的并行交易功能准备的。在这里,我们要知道,以太坊等传统公链上,智能合约的部署往往遵循 “编译后部署” 的方式,以 ETH 智能合约举例:
1. 开发者在本地编写好智能合约后,通过编辑器将 Solidity 程序编译为 EVM 的字节码,这样就可以被 EVM 直接理解并处理;
2. 开发者发起一笔部署智能合约的交易请求,把编译好的 EVM 字节码部署到以太坊链上。
Starknet 的智能合约虽然也遵循 “先编译后部署” 的思路,智能合约以 CairoVM 支持的 CASM 字节码形式部署在链上,但在智能合约的调用方式与状态存储模式上,Starknet 与 EVM 兼容链有着巨大差异。
准确的说,以太坊智能合约=业务逻辑+状态信息,比如 USDT 的合约中不光实现了 Transfer、Approval 等常用的函数功能,还存放着所有 USDT 持有者的资产状态,代码和状态被耦合在了一起,这带来了诸多麻烦,首先不利于 DAPP 合约升级与状态迁移,也不利于交易的并行处理,是一种沉重的技术包袱。
对此,Starknet 对状态的存储方式进行了改良,在其智能合约实现方案中,DAPP 的业务逻辑与资产状态完全解耦,分别存放在不同地方,这样做的好处很明显,首先可以让系统更快速的分辨出,是否存在重复或多余的代码部署。这里的原理是这样:
以太坊的智能合约=业务逻辑+状态数据,假如有几个合约的业务逻辑部分完全一致,但状态数据不同,则这几个合约的 hash 也不同,此时系统不太好分辨是否有 “垃圾合约” 存在。
而在 Starknet 的方案中,代码部分和状态数据直接分开,系统根据代码部分的 hash,更容易分辨出是否有相同的代码被多次部署,因为他们的 hash 是相同的。这样便于制止重复的代码部署行为,节约 Starknet 节点的存储空间。
在 Starknet 的智能合约系统中,合约的部署与使用,分为 “编译、声明、部署”三个阶段。资产发行者如果要部署 Cairo 合约,第一步要在自己的设备本地,把写好的 Cairo 代码,编译为 Sierra 以及底层字节码 CASM 形式。
然后,合约部署者要发布声明 “declare” 交易,把合约的 CASM 字节码和 Sierra 中间代码部署到链上,名为 Contract Class。
之后,如果你要要采用该资产合约里定义的函数功能,可以通过 DAPP 前端发起 “deploy” 交易,部署一个和 Contract Class 相关联的 Contract 实例,这个实例里面会存放资产状态。之后,用户可以调用 Contract Class 里的函数功能,变更 Contract 实例的状态。
其实,但凡了解面向对象编程的人,都应该能很容易的理解 Starknet 这里的 Class 和 Instance 各自代表啥。开发者声明的 Contract Class,只包含智能合约的业务逻辑,是一段谁都可以调用的函数功能,但没有实际的资产状态,也就没有直接实现 “资产实体”,只有 “灵魂” 没有 “肉体”。
而当用户部署具体的 Contract 实例后,资产就完成了 “实体化”。如果你要对资产 “实体” 的状态进行变更,比如把自己的 token 转移给别人,可以直接调用 Contract Class 里写好的函数功能。上述过程就和传统面向对象编程语言里的 “实例化” 有些类似(但不完全一致)。
智能合约被分离为 Class 和实例后,业务逻辑与状态数据解耦合,为 Starknet 带来了以下特性:
1. 利于存储分层和 “存储租赁制” 的实现
所谓的存储分层,就是开发者可以按照自己的需求,将数据放在自定义的位置,比如 Starknet 链下。StarkNet 准备兼容 Celestia 等 DA 层,DAPP 开发者可以将数据存放在这些第三方 DA 层里。比如一个游戏可以将最重要的资产数据存放在 Starknet 主网上,而将其他数据存储在 Celestia 等链下 DA 层。这种按照安全需求定制化选择 DA 层的方案,被 Starknet 命名为”Volition”。
而所谓的存储租赁制,是指每个人应当持续的为自己占用的存储空间付费。你占用的链上空间有多少,理论上就该持续的支付租金。
在以太坊智能合约模型中,合约的所有权不明确,难以分辨出一个 ERC-20 合约应该由部署者还是资产持有者支付 “租金”,迟迟没有上线存储租赁功能,只在合约部署时向部署者收取一笔费用,这种存储费用模型并不合理。
而在 Starknet 和 Sui 以及 CKB、Solana 的智能合约模型下,智能合约的所有权划分更明确,便于收取存储资金【目前 Starknet 没有直接上线存储租赁制,但未来会实现】
2. 实现真正的代码复用,减少垃圾合约的部署
我们可以声明一个通用的代币合约作为 class 存储到链上,然后所有人都可以调用这个 class 里的函数,来部署属于自己的代币实例。而且合约也可以直接调用 class 内的代码,这就实现了类似于 Solidity 中的 Library 函数库的效果。
同时,Starknet 的这种智能合约模型,有助于分辨 “垃圾合约”。前面对此有所解释。在支持代码复用与垃圾合约检测后,Starknet 可以大幅度减少上链的数据量,尽可能减轻节点的存储压力。
3. 真正的合约 “状态” 复用
区块链上的合约升级主要涉及到业务逻辑的变更,在 Starknet 的场景下,智能合约的业务逻辑与资产状态天生就是分离的,合约实例变更了关联的合约类型 class,就可以完成业务逻辑升级,不需要把资产状态迁移到新去处,这种合约升级形式比以太坊的更彻底、更原生。
而以太坊合约要变更业务逻辑,往往就要把业务逻辑 “外包” 给代理合约,通过变更依赖的代理合约,来实现主合约业务逻辑的变更,但这种方式不够简洁,也 “不原生”。
在某些场景下,如果旧的以太坊合约被整个弃用,里面的资产状态就无法直接迁移到新去处,非常麻烦;而 Cairo 合约就不需要把状态迁移走,可以直接 “复用” 旧的状态。
4. 利于交易并行化处理
要尽可能提升不同交易指令的可并行度,必要一环是把不同人的资产状态分散开存储,这在比特币、CKB 和 Sui 身上可见一斑。而上述目标的先决条件,就是把智能合约的业务逻辑和资产状态数据剥离开。虽然 Starknet 还没有针对交易并行进行深度的技术实现,但未来将把并行交易作为一个重要目标。
Starknet 的原生 AA 与账户合约部署
其实,所谓的账户抽象与 AA,是以太坊社区发明出来的独特概念,在许多新公链中,并没有 EOA 账户和智能合约账户的分野,从一开始就避开了以太坊式账户体系的坑。比如在以太坊的设定下,EOA 账户控制者必须在链上有 ETH 才可以发起交易,没有办法直接选用多样性的身份验证方式,要添加一些定制化的支付逻辑也极为麻烦。甚至有人认为,以太坊的这种账户设计简直就是反人类的。
如果我们去观察 Starknet 或 zkSyncEra 等主打 “原生 AA”的链,可以观察到明显的不同:首先,Starknet 和 zkSyncEra 统一了账户类型,链上只有智能合约账户,从一开始就没有 EOA 账户这种东西(zkSync Era 会在用户新创建的账户上,默认部署一套合约代码,模拟出以太坊 EOA 账户的特征,这样就便于兼容 Metamask)。
而 Starknet 没有考虑直接兼容 Metamask 等以太坊周边设施,用户在初次使用 Starknet 钱包时,会自动部署专用的合约账户,说白了就是部署前面提到的合约实例,这个合约实例会和钱包项目方事先部署的合约 class 相关联,可以直接调用 class 里面写好的一些功能。
下面我们将谈及一个有意思的话题:在领取 STRK 空投时,很多人发现 Argent 与 Braavos 钱包彼此不能兼容,将 Argent 的助记词导入 Braavos 后,无法导出对应的账户,这其实是因为 Argent 和 Braavos 采用了不同的账户生成计算方式,导致相同助记词生成的账户地址不同。
具体而言,在 Starknet 中,新部署的合约地址可以通过确定性的算法得出,具体使用以下公式:
上述公式中的 pedersen(),是一种易于在 ZK 系统中使用的哈希算法,生成账户的过程,其实就是给 pedersen 函数输入几个特殊参数,产生相应的 hash,这个 hash 就是生成的账户地址。
上面的图片中显示了 Starknet 生成 “新的合约地址” 时用到的几个参数,deployer_address 代表 “合约部署者” 的地址,这个参数可以为空,即便你事先没有 Starknet 合约账户,也可以部署新的合约。
salt 为计算合约地址的盐值,简单来说,就是一个随机数,该变量实际上是为了避免合约地址重复引入的。class_hash 就是前面介绍过的,合约实例对应的 class 的哈希值。而 constructor_calldata_hash,代表合约初始化参数的哈希。
基于上述公式,用户可以在合约部署至链上之前,就预先算出生成的合约地址。Starknet 允许用户在事先没有 Starknet 账户的情况下,直接部署合约,流程如下:
1. 用户先确定自己要部署的合约实例,要关联哪个合约 class,把该 class 的 hash 作为初始化参数之一,并算出 salt,得知自己生成的合约地址;
2. 用户知道自己将会把合约部署在哪后,先向该地址转入一定量的 ETH,作为合约部署费用。一般来说,这部分 ETH 要通过跨链桥从 L1 跨到 Starknet 网络;
3. 用户发起合约部署的交易请求。
其实,所有的 Starknet 账户都是通过上述流程部署的,但大部分钱包屏蔽了这里面的细节,用户根本感知不到里面的过程,就好像自己转入 ETH 后合约账户就部署完了。
上述方案帶來了一些相容性問題,因為不同的錢包在生成帳戶位址時,生成的結果並不一致,只有滿足以下條件的錢包才可以混用:
- 錢包使用的私鑰派生公鑰與簽名演算法相同;
- 錢包的 salt 計算流程相同;
- 錢包的智能合約 class 在實現細節上沒有根本性不同;
在之前談到的案例中,Argent 與 Braavos 都使用了 ECDSA 簽名演算法,但雙方的 salt 計算方法不同,相同的助記詞在兩款錢包中生成的帳戶位址會不一致。
我們再回到帳戶抽象的話題上。 Starknet 和 zkSync Era 把交易處理流程中涉及的一系列流程,如身份驗證(驗證數字簽名)、Gas 費支付等核心邏輯,全部挪到 “鏈底層” 之外去實現。 用戶可以在自己的帳戶中,自定義上述邏輯的實現細節.
比如你可以在自己的 Starknet 智慧合約帳戶里,部署專用的數位簽名驗證函數,當 Starknet 節點收到了你發起的交易後,會調用你在鏈上帳戶中自定義的一系列交易處理邏輯。 這樣顯然要更靈活。
而在乙太坊的設計中,身份驗證(數字簽名)等邏輯是寫死在節點客戶端代碼里的,不能原生支持帳戶功能的自定義。
按照 zkSyncEra 和 Starknet 官方人員的說法,這套帳戶功能模組化的思路,借鑒了 EIP-4337。 但不同的是,zkSync 和 Starknet 從一開始就把帳戶類型合併了,統一了交易類型,並且用統一入口接收處理所有交易,而乙太坊因為存在歷史包袱,且基金會希望盡可能避免硬分叉等粗暴的反覆運算方案,所以支援了 EIP-4337 這種 “曲線救國” 的方案,但這樣的效果是,EOA 帳戶和 4337 方案各自採用獨立的交易處理流程,顯得彆扭而且臃腫,不像原生 AA 那麼靈便。
但目前 Starknet 的原生帳戶抽象還沒有達到完全的成熟,從實踐進度來看,Starknet 的 AA 帳戶實現了簽名驗證演算法的自定義,但對於手續費支付的自定義,目前 Starknet 實際上僅支援 ETH 和 STRK 繳納 gas 費,並且還沒有支援第三方代繳 gas。 所以 Starknet 在原生 AA 上的進度,可以說是 「理論方案基本成熟,實踐方案還在推進」。。
由於 Starknet 內只有智慧合約帳戶,所以其交易的全流程都考慮了帳戶智慧合約的影響。 首先,一筆交易被 Starknet 節點的記憶體池(Mempool)接收后,要進行校驗,驗證步驟包括:
- 交易的數字簽名是否正確,此時會調用交易發起者帳戶中,自定義的驗簽函數;
- 交易贊助者的賬戶餘額能否支付得起 gas 費;
這裡要注意,使用帳戶智慧合約中自定義的簽名驗證函數,就意味著存在攻擊場景。 因為記憶體池在對新來的交易進行簽名驗證時,並不收取 gas 費(如果直接收取 gas 費,會帶來更嚴重的攻擊場景)。 惡意使用者可以先在自己的帳戶合約中自定義超級複雜的驗簽函數,再發起大量交易,讓這些交易被驗簽時,都去調用自定義的複雜驗簽函數,這樣可以直接耗盡節點的計算資源。
為了避免此情況的發生,StarkNet 對交易進行了以下限制:
- 單一使用者在單位時間內,可發起的交易筆數有上限;
- Starknet 帳戶合約中自定義的簽名驗證函數,存在複雜度上的限制,過於複雜的驗簽函數不會被執行。 Starknet 限制了驗簽函數的 gas 消耗上限,如果驗簽函數消耗的 gas 量過高,則直接拒絕此交易。 同時,也不允許帳戶合約內的驗簽函數調用其他合約。
Starknet 交易的流程圖如下:
值得注意的是,為了進一步加速交易校驗流程,Starknet 節點用戶端中直接實現了 Braavos 和 Argent 錢包的簽名驗證演算法,節點發現交易生成自這兩大主流 Starknet 錢包時,會調用用戶端里自帶的 Braavos/Argent 簽名演算法,通過這種類似於緩存的思想,Starknet 可以縮短交易驗證時間。
交易數據再通過排序器的驗證后(排序器的驗證步驟比記憶體池驗證會深入很多),排序器會將來自記憶體池的交易打包處理,並遞交給 ZK 證明生成者。 進入此環節的交易即使失敗,也會被收取 gas。
但如果讀者瞭解 Starknet 的歷史,會發現早期的 Starknet 對執行失敗的交易不收取手續費,最常見的交易失敗情況是,使用者僅有 1ETH 的資金,但是對外轉出 10ETH,這種交易顯然有邏輯錯誤,最終必然失敗,但在具體執行前誰也不知道結果是啥。
但 StarkNet 在過去不會對這種失敗交易收取手續費。 這種無成本的錯誤交易會浪費 Starknet 節點的計算資源,會衍生出 ddos 攻擊場景。 表面上看,對錯誤交易收取手續費似乎很好實現,實際上卻相當複雜。 Starknet 推出新版的 Cairo1 語言,很大程度就是為了解決失敗交易的 gas 收取問題。
我們都知道,ZK Proof 是一種有效性證明,而執行失敗的交易,其結果是無效的,無法在鏈上留下輸出結果。 嘗試用有效性證明,來證明某筆指令執行無效,不能產生輸出結果,聽起來就相當奇怪,實際上也不可行。 所以過去的 Starknet 在生成證明時,直接把不能產生輸出結果的失敗交易都刨除了出去。
Starknet 團隊後來採用了更聰明的解決方案,構建了一門新的合約語言 Cairo1,使得 “所有交易指令都能產生輸出結果並 onchain”。 乍一看,所有交易都能產生輸出,就意味著從不出現邏輯錯誤,而大多數時候交易失敗,是因為遇到一些 bug,導致指令執行中斷了。
讓交易永不中斷並成功產生輸出,很難實現,但實際上有一種很簡單的替代方案,就是在交易遇到邏輯錯誤導致中斷時,也讓他產生輸出結果,只不過這時候會返回一個 False 值,使大家知道這筆交易的執行不順利。
但要注意,返回 False 值,也就返回了輸出結果,也就是說,Cairo1 裡面,不管指令有沒有遇到邏輯錯誤,有沒有臨時中斷,都能夠產生輸出結果並 onchain。 這個輸出結果可以是正確的,也可以是 False 報錯資訊。
For Example,假如存在以下代碼段
此處的 _balances::read(from)– amount 可能因為向下溢出而報錯,這個時候就會導致相應的交易指令中斷並停止執行,不會在鏈上留下交易結果; 而如果將其改寫為以下形式,在交易失敗時仍然返回一個輸出結果,留存在鏈上,單純從觀感上來看,這就好像所有的交易都能順利的在鏈上留下交易輸出,統一收取手續費就顯得特別合理。
StarknetAA 合約概述
考慮到本文有部分讀者可能存在程式設計背景,所以此處簡單展示了一下 Starknet 中的帳戶抽象合約的介面:
上述介面中的__validate_declare__,用於用戶發起的 declare 交易的驗證,而__validate__則用於一般交易的驗證,主要驗證使用者的簽名是否正確,而__execute__則用於交易的執行。 我們可以看到 Starknet 合約帳戶默認支援 multicall 即多重調用。 多重調用可以實現一些很有趣的功能,比如在進行某些 DeFi 交互時打包以下三筆交易:
- 第一筆交易將代幣授權給 DeFi 合約
- 第二筆交易觸發 DeFi 合約邏輯
- 第三筆交易清空對 DeFi 合約的授權
當然,由於多重調用是具有原子性的,所以存在一些更加複雜的用法,比如執行某些套利交易。
總結
- Starknet 最主要的幾大技術特性,包括利於 ZK 證明生成的 Cairo 語言、原生級別的 AA、業務邏輯與狀態存儲相獨立的智慧合約模型。
- Cairo 是一種通用的 ZK 語言,既可以在 Starknet 上實現智能合約,也可以用於開發偏傳統的應用,其編譯流程中引入 Sierra 作為中間語言,使得 Cairo 可以頻繁反覆運算,但又不必變更最底層的位元組碼,只需要把變化傳導至中間語言身上; 在 Cairo 的標準庫內,還納入了帳戶抽象所需要的許多基本數據結構。
- Starknet 智慧合約將業務邏輯與狀態數據分開來存儲,不同於 EVM 鏈,Cairo 合約部署包含 “編譯、聲明、部署” 三階段,業務邏輯被聲明在 Contract class 中,包含狀態數據的 Contract 實例可以與 class 建立關聯,並調用後者包含的代碼;
- Starknet 的上述智慧合約模型利於代碼複用、合約狀態複用、存儲分層、檢測垃圾合約,也利於存儲租賃制和交易並行化的實現。 雖然後兩者目前暫未落地,但 Cairo 智慧合約的架構,還是為其創造了 “必要條件”。
- Starknet 鏈上只有智慧合約帳戶,沒有 EOA 帳戶,從一開始就支援原生級別的 AA 帳戶抽象。 其 AA 方案一定程度吸收了 ERC-4337 的思路,允許用戶選擇高度定製化的交易處理方案。 為了防止潛在的攻擊場景,Starknet 做出了諸多反制措施,為 AA 生態做出了重要的探索。
免責聲明:作為區塊鏈資訊平臺,本站所發佈文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。 文章內的資訊僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。