解读Starknet智能合约模型与原生AA:特立独行的技术巨匠

由于太过忽视用户,甚至公开在Discord开设“电子乞丐”频道,Starknet一度遭到撸毛党的抨击,在遭喷“不近人情”的同时,技术上的深厚造诣

在某些场景下,如果旧的以太坊合约被整个弃用,里面的资产状态就无法直接迁移到新去处,非常麻烦;而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节点收到了你发起的交易后,会调用你在链上账户中自定义的一系列交易处理逻辑。这样显然要更灵活。而在以太坊的设计中,身份验证(数字签名)等逻辑是写死在节点客户端代码里的,不能原生支持账户功能的自定义。

(Starknet架构师指明的原生AA方案示意图,交易验证和gas费资格验证都被转移到链上合约去处理,链的底层虚拟机可以调用用户自定义或指定的这些函数)

按照zkSyncEra和Starknet官方人员的说法,这套账户功能模块化的思路,借鉴了EIP-4337。但不同的是,zkSync和Starknet从一开始就把账户类型合并了,统一了交易类型,并且用统一入口接收处理所有交易,而以太坊因为存在历史包袱,且基金会希望尽可能避免硬分叉等粗暴的迭代方案,所以支持了EIP-4337这种“曲线救国”的方案,但这样的效果是,EOA账户和4337方案各自采用独立的交易处理流程,显得别扭而且臃肿,不像原生AA那么灵便。

_智能合约的行业应用_智能合约安全

(图片来源:ArgentWallet)但目前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生态做出了重要的探索。

Author:BticoinKOL,Source:https://bitcoinkol.com/archives/4379

Like (0)
Previous 11/03/2024 5:13 am
Next 11/03/2024 7:24 am

相关推荐

Leave a Reply

Your email address will not be published. Required fields are marked *