解析被纳入布拉格升级的 EIP3074:用例、影响与风险
EIP3074提出了一种新的钱包外挂,称为invoker,它可以打破以前msg.sender必须来自前一个呼叫地址的限制。这使得任何EOA都可以像合约钱包一样操作。新的交易流程图使用AUTHCALL替代CALL来验证呼叫,对被呼叫的合约而言,msg.sender变成了最初授权签名的EOA。该EIP通过两个新的OP code实现,即AUTH和AUTHCALL。未来,钱包和invoker之间将建立信任关系,用户可以选择使用经过审计和可信的invoker。EIP3074将带来钱包和合约交易的革新,改善合约开发者的使用体验。
原文作者:Anton Cheng
原文来源:medium
前言
这几天由于一个跟AA相关的EIP3074被确认排入下一次Pectra硬分叉,因此引起了社群广泛的讨论。Twitter和一些内存块链媒体上都有许多关于EIP3074的介绍,但是由于EIP3074实际改动有点复杂,也有容易让人误解的地方,因此今天希望用这篇文章厘清大家对于这个EIP的疑虑。
EIP3074的主要目的,可以用一句话概括:让EOA(externally owned account)在不需要部署合约钱包(smart contract wallet)的情况下,拥有合约钱包的功能。这表示一般钱包用户能直接「升级」自己的EOA,就像安装扩充功能一样,不需要部署新合约或变更现有的地址。那究竟是怎么做到的呢?
EIP3074前的交易流程
大部分的合约在开发时,都仰赖msg.sender这个变数来判断来者何人,即「呼叫此合约的帐户」。如果有一连串的合约连续呼叫,则每次对外进行呼叫(CALL),接收方收到的msg.sender都会更新。
每一个call,都会开启一个新的call frame,有新的msg.sender
由于合约都仰赖msg.sender来判断呼叫者的身份,因此常常会不知不觉中限制了呼叫者的UX。如果Alice用了一个EOA(alice.eth)收取了ERC20代币,那么她必须要从自己的EOA花eth发出交易,才能制造出呼叫者msg.sender = alice.eth这样的条件,去呼叫合约进行转账。
同样的原因,加上需要先对代币合约进行approve,才能给其他Dapp使用,也造成许多交易都必须用EOA发起两笔交易才能完成。
需要两笔交易进行一个swap
这就最为人诟病的UX问题;也是所有AA文章都会介绍的两点:必须要亲自用eth付手续费、且不能使用批次交易。
在没有EIP3074之前,我们必须仰赖合约钱包来解决这些问题,因为合约钱包能自动帮你打包多笔交易,并且让别人代付手续费。但这样的升级不是无痛的,你必须要把所有的资产转移到你新的合约帐户中,并且以这个新的合约钱包作为你去收钱、以及做合约互动的主体:
使用传统「合约帐户」如何解决手续费代付,以及批次交易问题
如上图中,尽管透过一个合约能送多笔交易了,但实际对应用程序合约发送Call呼叫的主体从EOA变成了紫色的合约钱包,我们被记录在Dapp中的地址也从原本的alice.eth变成smartalice.eth,是被当作不同的两个实体。
这样的解决方法其实很有效,但是对于很多既有的EOA用户来说,迁移EOA成本很高,不但要找出所有存在不同协议里的仓位,哪天发现有忘记的钱或是有空投可以领又要大费周章转eth回来付gas。这或许是到今天为止合约ㄑㄧㄢ包都不普遍的原因之一,毕竟大家刚开始接触内存块链可能不会使用这么fancy的钱包,也不会想要这些比较进阶的功能,但使用久了又懒得换了。
EIP3074的解决方法
如果退一步想,我们使用msg.sender是为了验证发起交易者的身份,那么其实只要确保msg.sender有正当的授权,我们并不一定要强制他一定要是呼叫本合约的「前一个地址」。
对于EIP3074很好的解释方法,就是通过新的OP code,和一个新的钱包外挂:invoker(调用者)合约,打破既有的msg.sender必须来自前一个呼叫地址的限制。一旦我们打破这个限制,我们可以允许客制化的「代理逻辑」写在invoker合约中,创造非常多的变化。
A motivating use case of this EIP is that it allows any EOA to act like a smart contract wallet without deploying a contract.
EIP3074新的交易流程图如下:
经由Invoker验证后发出的呼叫,会使用新的AUTHCALL,而非CALL。对被呼叫的合约而言msg.sender变成了最初授权签章的EOA。
新的OP Code
这个EIP透过两个新的OP code来做到这个效果:AUTH与AUTHCALL。AUTH就是授权的步骤,会确认EOA的持有者有签过名,授权invoker做出代理呼叫,接着合约如果使用AUTHCALL取代CALL,则对外呼叫时msg.sender即可以被复写为EOA签名者。
注:这个EIP刚提出时,其实只有一个OP code,就是AUTHCALL,并且直接对着要对外呼叫的内容(calldata)进行签章,相当于每个对外的授权呼叫就要签名一次。但后考虑到invoker一个很大的使用情境是来帮大家做批次交易,如果每次对做AUTHCALL都要验一次签章,将会代表用户如果要批次做多笔交易,就要签名多次,体验不是很好。因此后来转向给予invoker最大的灵活度,把一个OPCode分成两个阶段,只要在同一个call frame中AUTH过一次,就可以做多次的AUTHCALL。
一样是一笔交易(1 tx),使用两次对外AUTHCALL来做到batching的效果
执得注意的EIP细节
Invoker的角色
这个EIP在第一眼看到时,看起来非常的可怕,毕竟「允许合约代表我的EOA」听起来是一个潘多拉的盒子,将引来无尽灾祸。但我认为这是因为我们平时在理解合约的时候,会时常联想到一些DeFi Protocol,他们通常可升级、有一些信任假设,并且常常被hacked。这些都让我们不信任智能合约。因此很多人对于这个EIP的质疑,来自合约的安全性。
但我自己认为一个比较好理解方式,是把invoker当作钱包的一部分,而非是一个传统的合约。换句话说,上述这两个Opcode不应该被任何DeFi protocol使用,相反的,他们只应该被新的钱包外挂使用。
未来钱包跟invoker将会有一层信任关系:钱包方会自己开发invoker、或使用开源并经过审计的invoker,并且仅允许用户使用他们审核过、并且相信的invoker。我们未来对钱包商的信任基础,也会包含这些invoker,有点像是选用合约钱包时一样,把它归为整体钱包的一部分。
签章内容
前面有提到,原本这个提案是主张一个AUTHCALL签一次名,不过在决定分成两个阶段后的一个重要精神,就是把大部分可以由invoker做到的工作都交给invoker,因此用户所须签的信息只包含了以下几点:
- type byte(0x04):为了确保不同种类的签名结果不被别人乱用,以太坊上的签章系统都会有一个特有的前缀。例如一般交易、signed message,EIP1559都有不同的前缀。
- chain id:目前的链id
- nonce:EOA目前交易的计数(同一般交易所需要附带的nonce)
- invoker:将会使用AUTHCALL的合约地址
- commit:32 bytes,通常拿来放invoker客制化逻辑中,需要确认使用者有签名确认的信息hash结果。例如有个invoker的验证逻辑是确保用户有对AUTHCALL的calldata签章,这个invoker就需要在合约的验证流程中,自己把呼叫者丢进来的calldata都hash起来,看这样能不能验过签章。
这个签章内容其实还没有盖棺定论:其中比较有争议的是chainId以及nonce。其实一开始作者们是期望把所有东西都拿掉,只剩下type,invoker,以及commit,这样就把所有的实作空间都留给不同的invoker合约。但这样的提案被core devs回馈有风险太多:
(1).关于chainId
没有chainId可能允许跨链的重送攻击:如果有两条EVM chain同时支持EIP3074,那么可能有用户在第一条链上授权一个invoker,该签名却被拿到另一条链上,被使用在一个同地址,却不同实做的合约中,那么用户在第二条链上的资产就会有危险。
补充:理论上用CREATE2 deployer来部署invoker可以解决这个问题,因为这个问题的主要风险是来自在不同链上,同个地址可以存在不同合约。例如我用一个简单的私钥部署一个看似正当且去中心化的invoker到主网上,地址为0xaaabbb,我等大家签了EIP3074授权之后,再到Arbitrum上面在一样的0xaaabbb地址部署一个可以对外做任何呼叫且没有验证的invoker,那我这个坏人就可以偷走你在Arbitrum链上的EOA控制权。
要如何用CREATE2 deployer解决这个问题呢?因为CREATE2这个Op code会强制一定要部署一样的合约代码及一样的salt(随机数),才有办法部署出一样的地址。因此透过通用的CREATE2 Deployer部署出来的合约,你可以相信这个地址要嘛在其他链上不存在合约,如果存在,则合约内容一定跟主网一样。
这里列几个常见的CREATE2 Deployer,连接到他们的repo或是etherscan:
- Seaport keyless CREATE2
- Deterministic-deployment-proxy
- Safe-singleton-factory
(2).关于nonce
没有nonce则是可能允许invoker拿到无法撤消的授权:假如今天一个invoker忘了实作签章不能被重复使用的逻辑(replay protection),有可能造成坏人能无限制的使用一个用户同个签名发起多次AUTHCALL。如果保留这个nonce的验证,用户在任何时候只要发起一笔普通交易,就可以让所有现在在外面的签章无效化,过程大概是:
假设一个EOA现在nonce是10 =>他的主人可以做一笔简单的以太坊交易,例如转账给自己=>以太坊纪录的EOA nonce会增加1变成11 =>之后验证AUTHCALL都会使用nonce = 11来验章,造成之前签的签章无效化。
这里牵扯到的问题是,到底这个EIP要给invoker多大的自由与弹性:没有了chainId的限制,invoker可以透过多链部署,让用户享有「一个签章、全部EVM chain都升级」的功能;而没有nonce的限制的话,能够让钱包设计invoker跟EOA能同步做交易,做到更多自动化。
目前这些细节都还在讨论中,以提案的现有状况来说,作者(SamWilsn)其实也是偏好开放最大自由度的,但core dev的立场比较偏好不要一次加入太多的attack vector,因此nonce跟chainId目前还是暂时被加回了需要签章的信息中。
这个讨论还在持续中,大家可以加入forum来发表意见
重送攻击的预防实作
尽管上面有说,EOA交易的nonce必须涵盖在签章中,但这只是为了确保「使用者100%可以让EIP3074签章无效化」,并不限制一个签章能被用几次。
这是因为当AUTH被使用的时候,他会检查这个nonce是否等于使用者目前EOA交易的nonce,只要相同就会给过,但并不会更改EOA的nonce,因此一个签名是可以被AUTH验证数次的。
Invoker有义务要自行实作一些重送攻击的预防逻辑,但是可以有很多变化,另如自己做一个像是Uniswap Permit2的nonce系统,不要用递增的方式来确保可以平行送出多笔交易、也可以批次取消等等。
关于【解析被纳入布拉格升级的 EIP3074:用例、影响与风险】的延伸阅读
链上流动性游戏:开发者、狙击者与交易者的层层猎杀
链上交易是一场竞争激烈的流动性游戏,参与者包括开发者、狙击者和链上数据交易者。狙击者利用机器人和内部消息抢购有前途的项目,然后在市值达到一定程度后抛售,导致项目死亡。链上数据交易者跟踪狙击者和内部人士的行动,但也会相互博弈,最终导致项目归零。其他交易者可能只关注炒作,缺乏基本分析能力。总的来说,链上交易的竞争激烈,回报往往只流向少数金字塔顶端的人。
EIP-3074 后,恶意的签名会导致以太坊账户资金被耗尽吗?
EIP-3074是一项新的标准,可能会耗尽以太坊账户余额。钱包需要谨慎处理,支持新的签名格式并确保调用者地址可信。尽管用户对钱包有高度信任,但仍需注意安全风险。该标准对账户抽象提供者和新用户都有益处,是引入加密领域和智能账户的最佳方式。作者建议钱包安全集成EIP-3074,并提供帮助。
风险与常见误解
以下列出对于这个EIP常见的误解:
1. EIP3074让钓鱼诈骗变得更简单了?
有了这个EIP之后,智能合约确实有了透过一个签章得到EOA控制权的能力。但如前面所说的,invoker应该要跟钱包是一个绑定的关系,钱包应该要拒绝所有不认识的EIP3074签章请求。
由于每种不同的签章有不同的前缀,目前所有市面上的钱包都会默认拒绝EIP3074的代理签章,因此不用担心你的钱包不支持让你被骗。(也没有任何钱包支持用private key盲签未知交易格式。)
除了钱包方需要主动的去开启这个功能,大家也并不预期钱包方会接受一个网站「要求签署EIP3074」签章,因为像上面说的,这并不是一个正常的DAPP会要求钱包做的事情,因此我们可以预期钱包只会允许启用他们自己认证过的Invokers。
这跟钓鱼网站不同的点是:钱包本来就会期待来自网站(Dapp端)的交易请求,因此钓鱼网站透过骗你点击按钮,传送「恶意交易请求」给钱包端,如果钱包端没有好的预防,你可能就会点下签署交易。而EIP 3074的签章是完全不同的形式,钱包也没有理由要允许来自网站的「代管私钥需求」,因此不用担心你的钱包会侦测不到。
这里补上作者之一lightclient对此问题的响应。
虽然理论上钱包不该开启客制invoker的功能,但还是有可能有些钱包为了给用户最大自由度,而提供这个进阶选项。如果真的有这样的钱包,那就可能会出现类似钓鱼网站的攻击了:例如他们做一个很酷的网站告诉你加入invoker后能有什么功能,骗用户忽略钱包的一些警告而签章。
2.无法夺回的钱包控制权
有人在介绍EIP3074时写到,这可能造成用户一个无法驳回的单次授权,造成所有的控制权丧失。这也是不对的,在目前的EIP中,因为签名内容保有nonce,任何用户都可以发交易来让签出去的签章无效化。
若是nonce没有被加入必要签章格式中,这样的情境仍然建立在「合约有漏洞」的情况下。在一般合约实作中,让nonce无效化是很简单的一个步骤功能,也有无数经过验证的实作方法,除非遇到恶意的invoker(恶意钱包),否则这方面要出错的机率很低。
我认为讨论这个风险(或是类似的多链重送攻击)就好像讨论「合约钱包可能让你永久失去控制权」一样,就是在讨论合约存在bug的风险,考虑到这个问题的复杂度,要防范不会太困难。
潜在的风险
上面说的两个点:恶意invoker钓鱼网站,还有bug造成的永久丧失控制权,我都觉得不是可行的攻击。但这个EIP确实还是有带来风险,我认为比较大的风险会在攻击硬件钱包这块。
软件钱包一直都有很大的风险:假如下载了一个假的软件钱包,把私钥存进去,那么你的钱就会直接不见了,因此大家对于软件钱包App已经有比较高的资安意识。但硬件钱包在以前是比较安全的,只要你的硬件不要掉,基本上不可能有人可以从中读走你的私钥。
但如果有开源的硬件钱包支持了EIP3074签章,不能排除有人做一些新的App,同样去接硬件钱包,并推广给用户(例如做一个手机App可以跟Ledger一起使用)。这些恶意的钱包App就可以接着骗你签一个签名来获得私钥控制权。当然,这个前提也是要先让你相信这个新的钱包App是真的,不过假如他们打着「跟老牌硬件钱包合作」的口号,可能很多用户会忽略这个风险。或是未来有更多诈骗会尝试攻击钱包App DNS、骗你下载假的Ledger App等等,都能用来偷取既有硬件钱包用户私钥控制权。
总体来说,大家只要继续谨记谨慎选钱包(无论软硬体),就不会有上述问题。遇到钱包推出新的invoker功能时,记得以「审视合约钱包」的安全指标来审视invoker,也能有效的避免一些bug造成的问题。
革新应用
这里提几个我觉得很有趣,非常值得期待的应用:
(1)老地址空投领取器:
现在如果有旧地址收到空投,都需要转一些eth回去当gas,领了空投后再转出来,如果有了EIP3074,就可以用领到的空投代付手续费,直接一个签名领取空投、付费并且转到新地址中。或是以前老旧钱包里的一些残留ERC20也可以拿来付交易手续费。
(2)签署对ENS的交易:
以前EOA交易格式都是签署转账到一个地址,未来可以让用户签署「要转给哪个ENS」,透过invoker解析出地址再进行转账,大大增加转账的UX。
(3)签署有到期日的交易:
以前的交易内容没有到期日这样的参数,如果你签出去之后一直没被confirm,后面悔改了要手动取消,否则他就会一直处在pending状态。有了invoker之后我们可以很轻松的加入「超过时间便无效」这种限制,简单的制造出有到期日的交易格式。
(4)更多元的钱包代理:
我们可以有更多「代理钱包」的玩法,甚至增加钱包的安全性:使用冷钱包授权不同的地址,指定各个地址只能做特定交易、有不同的到期时间,例如:每一个月用冷钱包授权一次我的metamask地址可以为我发起最多10笔Uniswap交易。这样对于「私钥」的安全等级我们可以有更多的弹性,也将不再受到「不同私钥不能管同笔资产」的限制。
EIP3074未来可期!
可能带来的改变
我自己觉得比较宏观来说,可能对整个市场带来的改变有几个:
1.钱包差异化更大,用户黏着度更高
以前做钱包的时候,最常想的就是如何留住用户:毕竟大家私钥自己存好、随时想换别的钱包,只要下载安装导入私钥,马上就搬家了。因为大家能做到的事情大同小异,就是发送交易。
未来有了EIP3074之后,各家EVM钱包UX可能会差的越来越多:有的可能单纯支持ERC20付手续费、进阶一点的就会有前述的代理、订阅制的手续费、批次帮所有用户一起交易等等。这些都会造成好的钱包跟一般钱包之间的差距放大,也会增加用户黏稠度。
2.钱包方可能拥有更大权力(交易排序权)
未来钱包可能在为用户安排orderflow这块会出现更大的竞争。我们可以越期越来越的用户会习惯把真正「送出交易」的步骤代理给钱包方,钱包方也可以透过invoker做出「只有我能代理上链」的交易,相较于传统交易在曝光到mempool后大家(MEV searcher)都可以任意排序,钱包方可能也有动机去限制这些交易的顺序,从中获利。
3.一般合约可以回归更基础的设计流程
以前为了提升UX,合约设计中会考虑permit、multicall,meta-transaction,wrap ETH等等。我一直很希望AA时代快点到来,就是因为觉得这些东西如果不需要在protocol层考虑,可以减少很多安全疑虑,例如完全让钱包端处理Wrap ETH可以确保一个protocol完全不需要考虑eth的转账以及重入风险。这个EIP我觉得可以期望看到现有的钱包商如Metamsk,Rabby都马上投入invoker的开发,大大增加了普及批次交易的机会,如果未来批次交易普及,就能省去很多这方面的合约优化。(不用每个人的合约都继承MultiCall也是帮整个网络省空间吧!)
4.对合约使用msg.sender与tx.origin的影响
最后一个讲的比较细节,跟合约开发者相关的议题。
合约常常会使用msg.sender == tx.origin来做不同的限制。在EIP3074之后,使用者可以使用特别的EIP3074交易绕过这个检查:最初发出交易的人(tx.origin)可以透过一个中继合约来做AUTHCALL,同时附上自己的签名,因此可以做到从中继合约打出去的AUTHCALL,然后仍然符合msg.sender == tx.origin的条件。
大部分合约使用msg.sender == tx.origin的检查是为了保证msg.sender为EOA,免得送ETH回去的时候触发重入攻击等。这个特性依然没有改变,因为tx.origin必定是一个EOA,msg.sender也会指向最一开始的发起者,不用担心不小心跟中继合约互动,造成意想不到的后果。
不过这个EIP还是会影响到一些usecase,例如用tx.origin == msg.sender来防止「合约依照一笔交易的状态来控制后续交易的行为」。例如有人用合约mint NFT,只要没有成功mint出希有的NFT,就把整笔交易revert掉。但这个usecase其实已经可以透过MEV等等手法透过跟builder合作来破解了,所以算已经是无效的防护,影响并不大。
另一个会被影响的usecase,是用同样的条件来防止flashloan。以前如果有合约不想被用户透过flashloan使用,可以透过tx.origin == msg.sender确定来的人没有透过flashloan借钱。现在发起者可以在flashloan借到钱后,透过中继合约呼叫AUTHCALL,对协议方来说,看起来就会是msg.sender真的带着大笔的钞票来使用这个合约。
理论上,好的合约不应该使用tx.origin做任何判断,因为这会破坏AA钱包或meta-transaction等体验,使用tx.origin也已经被大量推广为bad practice。因此尽管这个EIP打破了某些使用情境,对整体影响并不大。
结语
我自己十分期待下次升级的到来,毕竟AA说了这么久了,真的看到做出抽象帐户的钱包屈指可数,也一直没有等来说好的大量从EOA到合约钱包的升级。
我认为EIP3074给予了所有钱包开发商非常大的升级空间,因为大家可以几乎无痛(不需迁移)的为用户带来体感上的大升级。真的很期待真的上线时,各个钱包商能变出什么新玩法,这里也小小许愿,最后EIP可以以不限制nonce跟chainId的自由之姿进入硬分叉,这样就有更多跨链钱包等等功能可以期待了。
免责声明:本文仅代表作者个人观点,不代表链观CHAINLOOK立场,不承担法律责任。文章及观点也不构成投资意见。请用户理性看待市场风险,以及遵守所在国家和地区的相关法律法规。
图文来源:Anton Cheng,如有侵权请联系删除。转载或引用请注明文章出处!