查看原文
其他

引介 | 账户抽象化(EIP-2938):为什么 & 做了什么

Rajeev 以太坊爱好者 2022-10-19


以太坊有两种类型的账户:外部账户(Externally Owned Account, EOA)和合约账户(Contract Account, CA)。前者由用户私钥控制,而后者由存储在智能合约账户(有时也被称为智能钱包)内的合约代码控制。外部账户的权限要大于合约账户,因为只有外部账户可以通过支付 gas 启动交易的执行过程。账户抽象化(Account Abstraction, AA)则是一个可以让合约账户成为和外部账户一样的 “顶层” 账户的提案,实现了账户抽象化之后,合约账户也可以支付交易费和执行交易。

之所以提出账户抽象化,是想要在钱包、交易混淆器(mixer)、ÐApp 和 DeFi 等各种应用场景下,显著改善用户与以太坊链的交互体验。账户抽象化方案要为以太坊提供一个基础功能层,来确定何时支付,谁来支付以及怎样支付 gas。

以 Status Messenger 应用为例,其将隐私信使服务与以太坊钱包和 Web3 浏览器集成在一起。目前,Status 钱包仍然是外部账户钱包,因此其不能像智能合约钱包那样支持丰富的用户体验,比如多签安全、基于社交的钱包恢复(social recovery)、支付限额、地址黑/白名单以及无需 gas 费用的元交易。(之所以没有采用智能合约钱包,是因为)智能合约钱包的用户体验受累于 gas 价格波动,即使通过第三方的中继者也无法有效解决这个问题。账户抽象化旨在解决这个问题。

在本文中,我们先探讨智能合约钱包对账户抽象化的需求。然后描述协议的变更及其对节点的影响,借此深入到账户抽象化的关键部分。最后,我们讨论一些关于扩展功能的提案,并以阐释 Status 项目的计划路线图作为本文的总结,因为该项目要与以太坊配合因而势必受到账户抽象化的影响。


历史与动机


账户抽象化这个概念是在撰写于 2017 年的 EIP-86 里首次提出的,其目的是实现 “交易来源和签名的抽象”。不过其动机和想法可以追溯到 2016 年年初 vitalik 提交的一个 issue, 文中建议 “与其将 ECDSA 签名算法和默认的 nonce 机制写死在协议内作为 ‘标准’ 的账户安全机制,不如初步建立一个(统一的)账户模型,在未来把所有的账户都变成合约,让合约可以支付 gas,让用户可以自由定义自己的安全模型。”

要实现最初的提议非常有挑战,因为不仅要大幅修改协议,还要满足系统的安全保证。最近,Vitalik 等人提出了 EIP-2938 草案,该草案概述了一个更简单的实现。这个实现对 协议/共识 的改动最小,并且通过设置节点的内存池规则来满足所需的安全保证。Vitalik 的以太坊工程组 Meetup 演讲和 Sam Wilson 与 Ansgar Dietrichs(EIP 的另外 2 位作者)的ETH 线上演讲(以及附带的两篇文章:1 & 2)都对这个主题做了非常棒的介绍。本文会着重介绍这些资料的关键内容。

动机:账户抽象化背后的动机很简单,但会带来根本性的改变:当前,以太坊交易具备 功能可编程性(通过调用智能合约实现),但是 交易的验证方式 却是固定的。只有持有有效的 ECDSA 签名、有效的 nonce 值以及足够的账户余额,一笔交易才算有效。账户抽象化引入了一种新的交易类型 —— 抽象账户交易(AA Transaction)。这种交易总是由一个特殊地址产生,协议不会检查其签名,nonce 和余额。通过引入这种交易,账户抽象化实现了从固定验证方式到可编程验证方式的转变。抽象账户交易的有效性由其 target 字段指定的智能合约验证,通过验证之后,合约可以自行为该交易支付手续费。

那么,为什么账户抽象化这么有用呢?我们将用以太坊钱包为例,阐述它会带来哪些好处。

智能合约钱包:如今大多数以太坊钱包都是外部账户钱包,它们的安全性由助记词(seed phrase)生成的私钥保护。(一串遵循 BIP-39 标准的助记词由 12-24 个单词依序拼成,这些单词是从 2048 个单词中随机选择的。助记词为 PBKDF2 函数提供所需的信息熵,以生成一串二进制种子。接着,基于 BIP-32 标准的钱包使用该种子生成非对称密钥对。)为了以后能在其他钱包里恢复密钥,用户应当安全妥善地保管助记词。然而,这种钱包很容易因为私钥被盗或者助记词丢失导致用户的资产遭受损失。

(顾名思义)智能合约钱包是通过智能合约在链上实现的。这种钱包能够提供可编程的风险迁移和友好的用户体验,比如多签安全、基于社交或时间的钱包恢复、转账金额限制、地址黑白名单、无需 gas 费用的元交易和批量交易。

虽然智能合约面临因代码质量差而带来的安全风险,但是这种风险可以通过钱包提供方的安全审计来减轻。而外部账户钱包的风险完全由用户自己承担,用户无法获得智能合约那样的可编程的安全保障,用户必须自己保管好助记词。

当前可用的智能合约钱包有 Argent、Authereum、Dapper、Dharma、Gnos is Safe、Monolith 和 MYKEY。从下图可以看到,这类钱包的使用率看起来在增加。

Argent 通过守护者(Guardian)的概念实现了无需种子的基于社交的钱包恢复。守护者是用户信任的人或设备,可以帮助用户恢复钱包。Argent 还要实现类似银行的安全性(提供每日交易限额、账户锁定和可信联系人等功能)与类似 Venmo (译者注:Venmo 是 PayPal 旗下的一个移动支付服务)的可用性(用 ENS(译者注:Ethereum Name Servic,ENS 是一个可读的、去中心化且安全的域名系统)取代地址,支持元交易)。

Gnosis Safe 是一款多签智能合约钱包,专注于团队资金管理,任何一笔交易都要得到团队中最低人数(n 个成员中的至少 m 个)的批准才能发生。它还可以通过元交易实现无需 gas 的签名。

所有这些高级钱包功能都要用到灵活的智能合约。钱包用户要么通过外部账户发送交易,支付 gas 费以调用钱包合约,要么依赖钱包提供方支持元交易功能,借由钱包提供方的中继器或者第三方中继网络如 Gas Station Network 来使用钱包。前者通常是通过了 KYC 认证的用户依靠中心化交易所购买 ETH(以支付 gas 费);后者将用户的负担转移到中继器上,由钱包提供方或用户在链上或链下补偿中继器,从而提高用户体验。

然而,基于中继器的架构有三个主要缺点:(1)他们可能会面临中心化的诟病,引起交易审查的担忧;(2)由于中继器发出的交易需要支付额外 21000 单位的基础 gas 费,为了获得利润其必须收取更高的费用以覆盖这部分成本,这就导致了技术和经济上的低效;(3)对中继专用协议的使用,迫使应用不得不依赖非以太坊的基础设施,而较小的用户群无法支撑这些设施提供稳定可靠的服务。

账户抽象化将使智能合约能够接受用户的无需 gas 的元交易,并且无需依赖中继网络就能为其支付 gas 费。因此,这种底层能力在不牺牲去中心化的情况下,能够极大改善这些智能合约钱包的用户体验。

Tornodo Cash:另一个(将受益于账户抽象化的)相关应用是交易混淆器,例如 tornado.cash。Tornado 使用智能合约切断地址之间的链上关联以提升交易的隐私性,该合约允许用户存入 ETH 后通过另一个地址提款。用户在存款时提供一个秘密值的哈希,随后在提款时提供 zkSnark 证明。该证明可以在不泄露秘密值和存款信息的情况下,证明其知道这个秘密值。这样就把存款和提款脱钩了。

但是在提款时有一个鸡生蛋蛋生鸡的问题。为了在新生成的地址上执行提款交易,用户需要给该地址存入一些 ETH 以支付 gas 费。但这些 ETH 的来源(通常是一家交易所)本身又会破坏使用 Tornado 所带来的隐私性。首选的替代方案是再次使用中继网络,而这样做有上述的那些缺点。

账户抽象化就可以解决这个问题,Tornado 合约可以接受用户的提款抽象账户交易,验证 zkSnark,(从存款里)扣除 gas 费,然后把剩余的资金转给提款地址。


账户抽象化


在 EIP-2938 中提出的账户抽象化,接纳合约作为顶层账户,使之可以支付交易费以及启动交易的执行过程。实现账户抽象化需要变更协议以增加新的抽象账户交易类型、修改内存池规则以及引入扩展功能以支持高级用法。抽象账户交易需要增加两个新的操作码:NONCE 和 PAYGAS。账户类型仍然保持现有的两种(外部账户和合约账户),所有的变更都向后兼容以支持当前的交易、智能合约和协议。

账户抽象化应用可以分成两类:1) 单租户应用,如智能合约钱包,其会为每个用户创建一个新合约;2) 多租户应用,例如 tornado.cash 或 Uniswap,多个用户与同一组智能合约交互。

目前还不能支持多租户应用,这需要更多的研究工作,以期未来早日实现。所以本文将重点介绍单租户的账户抽象化。


协议变更

对协议唯一的变更就是引入了一种新的交易类型,以及相关的两个操作码 NONCE 和 PAYGAS

抽象账户交易:协议引入了一种新的交易类型:AA_TX_TYPE。它的二进制数据 (payload) 会被解析为 RLP([nonce, target, data]) ,而不是现有交易的 RLP([nonce, gas_price, gas_limit, to, value, data, v, r, s]) 。

Nonce 检查和现有交易类似,需满足 tx.nonce == tx.target.nonce。如果检查失败,则交易无效,如果通过检查,则 tx.target.nonce 将会递增,交易继续进行。

抽象账户交易的基础 gas 消耗量将从 21000 降到 15000(以反映移除了内置的 ECDSA 签名所节省的成本)。此外,抽象账户交易没有内置的 gas 限额。当交易执行时,gas 限额只需设定为当前 block 还剩余的 gas 限额即可。

NONCE opcodeNONCE 操作码 (0x48) 推送被调用者 —— 即抽象账户交易的 target 字段所指定的合约 —— 的 nonce 值到 EVM 的栈上。借此,nonce 值被暴露给了 EVM, 因而抽象账户合约可以将交易的任何字段(包括 nonce)作为验签逻辑的一部分。

PAYGAS opcodePAYGAS 操作码 (0x49) 从栈上弹出 2 个参数:(栈顶)version_number,(次栈顶)memory_start

Version_number 允许未来通过新的实现变更操作码的语义。目前,操作码的语义如下:

  1. 检查 version_number == 0(否则抛出异常)
  2. 读取 gas_price = bytes_to_int(vm.memory[memory_start: memory_start + 32])
  3. 读取 gas_limit = bytes_to_int(vm.memory[memory_start + 32: memory_start + 64])
  4. 检查 contract.balance >= gas_price * gas_limit(否则抛出异常)
  5. 检查 globals.transaction_fee_paid == False(否则抛出异常)
  6. 检查 AA execution frame == top-level frame,也就是若当前 EVM 执行退出或回退,整个交易的 EVM 执行就暂停了(否则抛出异常)
  7. 设置 contract.balance -= gas_price * gas_limit
  8. 设置 globals.transaction_fee_paid = True
  9. 设置 globals.gas_price = gas_priceglobals.gas_limit = gas_limit
  10. 设置当前的执行上下文的 remaining_gas = gas_limit - 已经消耗的 gas
在执行抽象账户交易的最后,(globals.limit - remaining_gas) * globals.gas_price 的费用将转给矿工,remaining_gas * globals.gas_price 的金额则退还给抽象账户合约。
PAYGAS 操作码充当 EVM 执行过程中的检查点。任何回退都只退回到上一个检查点,此时合约收不到退款,globals.gas_limit * globals.gas_price 的金额全都转给矿工。
这个新的交易类型和这两种新的操作码构成了 协议/共识 级别的全部变更,它们的语义相对直观,易于理解。


内存池规则

“内存池是指以太坊节点内部的,存储在内存里的数据结构,其中存储了用于挖矿的候选交易。Geth 称之为 ‘交易池(transaction pool)’;Parity 称之为 ‘交易队列(transaction queue)’。不管叫什么,它都是一个存储将被区块包含的交易的数据池。我们可以把它看做是待打包区块的 “候车厅”。
抽象账户交易的可编程验证方式引入了更多的复杂性。抽象账户交易并不预付 gas 费,而是依靠 target 字段指定的抽象账户合约(通过 PAYGAS 操作码)来支付 gas 费。理论上,抽象账户交易的处理分两个阶段:较短的验证阶段(在执行 PAYGAS 操作码之前)和较长的执行阶段(在执行 PAYGAS 操作码之后)。如果验证阶段失败(或抛出异常),会导致交易无效(就像签名无效的非抽象账户交易一样),该交易便不会被区块包含,因而矿工也就拿不到交易费了
因此,矿工和节点需要一个可预测的机制,以避免待处理的抽象账户交易的有效性依赖内存池中的其他待处理交易。否则,一笔交易执行失败可能会导致许多甚至全部的抽象账户交易无效,以此可以构造出拒绝服务攻击。为了避免这种情况发生,我们建议内存池对抽象账户交易执行两条规则(由矿工和节点执行,但不需要修改协议本身)。
操作码限制
为了防止抽象账户交易的有效性取决于抽象账户合约以外的任何状态,在验证阶段(即在执行 PAYGAS 操作码之前)以下操作码被视为无效:执行环境操作码BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMITBALANCE(对任何账户的,包括 target 合约地址)、除了对 target 合约或预编译合约之外的任何外部调用/创建操作码CALL, CALLCODE, STATICCALL, CREATE, CREATE2、对除了 target 合约之外的任何外部状态的读取操作码EXTCODESIZE, EXTCODEHASH, ETXCODECOPY, DELEGATECALL
如果有抽象账户交易的 target 字段所指向的抽象账户合约违反了这个操作码限制规则,节点就要丢弃这个交易。如此,只要抽象合约的状态不发生变化,内存池中有效的抽象账户交易就将继续有效。
字节码前缀限制
如果非抽象账户交易可以影响抽象账户合约的状态,那么其就可以影响内存池中的抽象账户交易的有效性。为了防止这种情况发生,抽象账户交易应当只接受字节码前缀为 AA_PREFIX 的 target 合约。AA_PREFIX 实现了对 msg.sender 的检查,确保其是特殊的 ENTRY_POINT 地址。这有效地防止了非抽象账户交易与抽象账户交易发生交互。
节点如果检查到抽象账户交易指定的合约没有这个 AA_PREFIX 作为字节码前缀(即不是抽象合约),就应该删除这笔交易。
这两条对抽象账户合约的限制确保了:(1)只有抽象账户合约的内部状态可以影响抽象账户交易的验证逻辑;(2)只有以同一个抽象账户合约为 target 的抽象账户交易可以修改该合约的状态。
因此,只有区块包含了以同一个抽象账户合约为 target 的另一笔抽象账户交易,才有可能使一个待处理的抽象账户交易无效。不过,鉴于这不是协议/共识层的变更,矿工在打包交易时可以随意违反这些规则。


扩展

上述协议变更和内存池规则足以安全地实现诸如智能合约钱包这样的单租户应用。其他高级用法则需要放宽上述规则或者需要实现多租户应用,这就要账户抽象化支持以下形式的扩展:
  1. SET_INDESTRUCTIBLE 操作码,它会禁用 SELFDESTRUCT,并允许抽象账户合约在验证阶段通过 DELEGATECALL 安全地调用库函数。
  2. S_STATIC 操作码,如果当前的上下文是静态的则返回 true,并允许非抽象账户交易的调用者绕过之前的字节码前缀限制,安全地从抽象账户合约里读取数据。
  3. RESERVE_GAS 操作码,当一笔非抽象账户交易试图对一个抽象账户合约写入状态时,设置一个 gas 消耗的下限。这样做是为了强制攻击者在试图让内存池的抽象账户交易无效时至少要消耗一定的 gas,以此抑制其攻击动机。
还有一些其他的扩展,例如存储多笔待处理交易(译者注:内存池存储同一地址的多笔待处理抽象账户交易)、缓存验证结果、验证过程的动态 gas 限制和担保交易等,这些都是为了支持多租户应用和零知识证明。这些内容不在本文的讨论范围内。


下一步


目前,关于账户抽象化的 EIP-2938 还只是一个草案,正在以太坊研究论坛中讨论。下一步是考虑将这个 EIP 纳入即将到来的硬分叉中。EIP 的作者显然瞄着 Berlin (Berlin 暂时定于 2021 年年初的某个时间)之后的硬分叉,具体的时间表还不确定。所以,对于 EIP-2938 来说,现在还处于早期阶段。
此外,也不清楚是否有必要在以太坊的 Layer 1 (L1) 加入 EIP-2938。鉴于 Layer 2 (L2) 方案的灵活性(如我们之前的文章所述),我们也可以在特定的 L2 上实现账户抽象化,这样就不需要升级整个 L1 了。不过,在 L1 上统一支持账户抽象化要比在不同的 L2 上各自实现账户抽象化好。因此,在哪里实现,怎样实现账户抽象化还有待观察。
“账户抽象化在某种程度上不那么重要,因为无论 L1 是否支持,它都可以在 L2 上实现。” —— Vitalik 在谈及基础层的升级需求时表达了这个观点(这在他写的那篇以 rollup 为中心的以太坊路线图的帖子中提到了)。
Status:Status 钱包目前是一个外部账户钱包,它的独门绝技是将聊天支付和 Keycard (译者注:一种硬件钱包)集成到一个隐私信使服务中。目前其正在考虑加入一些智能合约钱包的特性,如多签和基于社交的钱包恢复。如前所述,支持 EIP-2938 有助于消除对中心化和低效的中继架构的依赖。
Status 也在评估 L2 解决方案,正如我们之前的文章里说的,这样做既可以在钱包里支持多链,也可以为多种用例提供所需要的扩展。例如,Keycard 正在探索一种支付网络,其所需的信用卡级别的可扩展性和近乎即时的终局性是目前以太坊网络无法满足的。此外,还有许多其他计划,如推荐计划、SNT reaction、Tribute-to-Talk 和 ENS 域名。所有这些计划都适合在 L2 上实现,因为这在部署上可行,而且能够提供还不错的用户体验。如果一个可行的 L2 方案实现了账户抽象化,那么在该 L2 上的项目都能享受账户抽象化的好处,而不必依赖 L1。


总结


以太坊协议的一个根本问题是,只有外部账户(EOS(可以支付 gas 费和启动交易执行过程,而合约账户(CA(做不到。账户抽象化(AA(是一个旨在改变这种区别的提案,允许具有特定构造的合约账户可以检查新增的一类交易 —— 抽象账户交易的有效性,并代为支付 gas 费,从而在不需要外部账户的前提下有效地启动交易的执行过程。
账户抽象化能够在不依赖中心化和低效的中继架构的情况下,显著改善钱包、交易混淆器、ÐApps 和 DeFi 的用户体验。通过引入一种新的交易类型,两种操作码以及两条内存池规则,账户抽象化能够安全地支持基本的单租户场景,如智能合约钱包。如果要支持更高级的多租户应用,如 Tornodo Cash,则需要对协议和节点规则做更多的扩展。
本文阐述了智能合约钱包对账户抽象化的需求。通过描述协议的变更和对节点的影响来强调账户抽象化的关键部分。我们还谈到了一些针对高级用法的扩展提案,最后通过介绍 Status 项目在以太坊上的路线图和优先级让读者对账户抽象化有一个比较清楚的定位。
减少 Web3 的摩擦及改善用户体验是这个生态里所有项目的首要任务。账户抽象化,最终一定会以某种形式在未来发挥重要的作用。
(感谢 Hester Bruikman 审阅本文草稿并提供有益的反馈。感谢 Alex Howell 提供贴心的插图。)

(完)

(文内有许多超链接,可点击左下 ”阅读原文“ 从 EthFans 网站上获取)


原文链接:

https://our.status.im/account-abstraction-eip-2938/

作者: Rajeev Gopalakrishna

翻译&校对: 戡乱 & 阿剑

你可能还喜欢:

引介 | 以太坊上的通用经济抽象

引介 | 概述以太坊账户抽象化

闪电贷:DeFi 乐高中最具魔力的一块积木

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存