EIP-2938 账户抽象(Account Abstraction)深度解读:PAYGAS 操作码与分层 AA 设计
EIP-2938 账户抽象Account Abstraction深度解读PAYGAS 操作码与分层 AA 设计【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs协议状态说明本仓库中的 EIPS/eip-2938.md 为Withdrawn已撤回状态撤回原因为 Very out of date; needs rewrite已严重过时需要重写。该文档由 Vitalik Buterin、Ansgar Dietrichs、Matt Garnett、Will Villanueva 与 Sam Wilson 于 2020-09-04 共同提出属于 Standards Track / Core 类别基于 EIP-2718 设计。尽管最终未进入主网其提出的 验证阶段 PAYGAS 执行阶段 双阶段模型、AA_ENTRY_POINT 入口地址、分层 AAsingle-tenant / multi-tenant等核心思想深刻影响了后续 ERC-4337、EIP-7702 等账户抽象方案的演进。本文基于仓库内的原始文档完整还原其技术设计并结合仓库内相关 EIP 进行源码级对照分析。导读本文围绕 EIP-2938 展开系统讲解如何让合约成为支付 gas 费并启动交易执行的顶层账户从而突破以太坊 ECDSA 签名 简单 nonce 账户余额 的刚性交易有效性约束。你将掌握AA 交易类型与PAYGAS/NONCE操作码的完整协议设计、single-tenant 与 multi-tenant 两档账户抽象的划分逻辑、矿工与验证节点在内存池层面的防 DoS 验证策略以及 Tornado Cash、Uniswap 等真实场景下 AA 的落地形态。文中所有引用均指向本仓库可查证的 EIP 文档。一、背景与动机为什么需要账户抽象1.1 现有协议的交易有效性模型EIP-2938 指出截至 Muir Glacier 升级以太坊协议对交易有效性Transaction validity的定义是刚性且固定的ECDSA 签名、一个简单的 nonce 计数器、以及账户余额充足。这种设计保证了矿工可以快速、确定地验证任意一笔交易是否可打包、是否最终会支付手续费。账户抽象Account AbstractionAA的目标是用受限制的任意 EVM 字节码执行来扩展交易的有效性条件让合约能够自定义什么样的交易对我是有效的。为了向协议发出我愿意支付的信号该 EIP 提出了一个新的 EVM 操作码PAYGAS由合约显式声明其愿意支付的 gas price 与 gas limit。1.2 五类被现有模型压制的创新场景原始文档列出了现有限制阻碍创新的五个重要方向非 ECDSA 签名的智能合约钱包支持 Schnorr、BLS、后量子等签名方案的合约钱包无法用固定 ECDSA 校验来表达多签验证与社会恢复钱包降低资产丢失/被盗的高发风险隐私保护系统如 tornado.cash 这类依赖零知识证明的应用DeFi 协议 gas 效率优化阻止不满足高层条件如不存在匹配订单的交易上链从而节省链上资源用非 ETH 代币支付手续费在交易内部实时将代币兑换为 ETH 以支付 gas。1.3 中间人方案的缺陷文档明确指出上述用例目前大多依赖中间人方案最典型的是 Gas Station Network 及各类应用专属替代品但存在双重低效技术低效每笔交易需额外支付约 21000 gas 给中继者relayer经济低效中继者需在代付的 gas 费之上再赚取利润。此外依赖中间协议意味着应用无法仅基于以太坊基础层设施运行而要依赖用户基数更小、未来可能停止服务、风险更高的第三方协议。五类用例与两档 AA 的对应关系为single-tenant AA大致支撑第 (1)、(2) 类钱包场景multi-tenant AA大致支撑第 (3)、(4) 类多用户应用场景。二、规范总览Single-Tenant AA 的协议设计从FORK_BLOCK之后协议将识别以下更改。EIP-2938 将账户抽象拆分为两档single-tenant AA单租户面向钱包等参与者较少的用例multi-tenant AA多租户面向参与者众多的应用如 tornado.cash、Uniswap。2.1 常量定义| 常量 | 值 | | - | - | |AA_ENTRY_POINT|0xffffffffffffffffffffffffffffffffffffffff| |AA_TX_TYPE|2| |FORK_BLOCK| TBD待定 | |AA_BASE_GAS_COST| 15000 |2.2 新交易类型基于 EIP-2718 信封EIP-2938 引入一种新的 EIP-2718 交易类型号为AA_TX_TYPE 2这类交易被称为AA 交易。其 payload 解释为rlp([nonce, target, data])对照 EIP-2718 的规范交易形式为TransactionType || TransactionPayloadTransactionType是0到0x7f之间的无符号 8 位正数TransactionPayload是依赖交易类型解释的不透明字节数组。EIP-2938 正是基于这一信封机制定义的第 2 号交易类型。关键设计点基础 gas 成本为AA_BASE_GAS_COST 15000而非普通交易的 21000以反映该交易类型没有内在的 ECDSA 签名开销Nonce 处理方式与现有交易类似检查tx.nonce tx.target.nonce不满足则交易无效满足则立即执行tx.nonce 1无内在 gas limit开始执行时gas limit 直接设为区块剩余 gas即block.gas_limit减去此前交易已消耗的 gas之后可通过PAYGAS操作码向下调整。2.3 交易级全局变量协议引入若干交易级全局变量其工作机制尤其是回滚逻辑与 SSTORE 退款计数器refund counter类似| 变量 | 类型 | 初始值 | | - | - | - | |globals.transaction_fee_paid|bool|False if type(tx) AA_TX_TYPE else True| |globals.gas_price|int|0 if type(tx) AA_TX_TYPE else tx.gas_price| |globals.gas_limit|int|0 if type(tx) AA_TX_TYPE else tx.gas_limit|可以看到普通交易的transaction_fee_paid初始即为True、gas_price/gas_limit直接取自身字段因此下述新行为对非 AA 交易完全无影响。三、核心操作码NONCE 与 PAYGAS3.1NONCE (0x48)操作码新操作码NONCE (0x48)gas 成本为G_base功能是将被调用方callee的 nonce 压入栈顶。其设计动机见 Rationale 章节是让验证代码中的签名校验能够校验 nonce从而在合约层实现更强的重放保护逻辑。3.2PAYGAS (0x49)操作码新操作码PAYGAS (0x49)gas 成本为G_base。它从栈上取两个参数栈顶version_number栈第二项memory_start初始实现中会执行assert version_number 0并从内存读取gas_price bytes_to_int(vm.memory[memory_start : memory_start 32])gas_limit bytes_to_int(vm.memory[memory_start 32 : memory_start 64])两次读取使用与MLOAD、CALL类似的机制必要时会触发内存扩展memory expansion。版本化设计未来硬分叉可通过不同 version_number 支持不同尺寸的内存切片与解释方式。文档特别点出两个潜在用例EIP-1559 的 gas 定价模型与 escalator自动扶梯式动态 gas 费算法。触发条件若以下三个条件外加上述版本号检查同时满足账户余额 gas_price * gas_limitglobals.transaction_fee_paid False当前处于顶层 AA 执行帧即当前 EVM 执行退出或回滚则整个交易的 EVM 执行即告结束。则执行以下动作从合约余额中扣除gas_price * gas_limit将globals.transaction_fee_paid置为True将globals.gas_price、globals.gas_limit分别设为gas_price、gas_limit将当前执行上下文的剩余 gas 设为gas_limit减去已消耗的 gas。任一条件不满足则抛出异常。交易结束时的结算规则AA 交易执行结束时必须满足globals.transaction_fee_paid True否则交易无效合约按globals.gas_price * remaining_gas获得剩余 gas 的退款(globals.gas_limit - remaining_gas) * globals.gas_price转账给矿工。PAYGAS 作为执行检查点PAYGAS同时充当 EVM 执行的检查点checkpoint若顶层执行帧在PAYGAS被调用之后发生回滚执行只回滚到PAYGAS调用刚结束的位置并在此退出。此时合约不获得任何退款globals.gas_limit * globals.gas_price全部转给矿工。这一设计直接决定了 Rationale 章节所说的双阶段模型验证阶段verification phasePAYGAS之前的较短执行。若此阶段抛出异常交易无效——如同现有系统中签名无效的交易执行阶段execution phasePAYGAS之后的较长执行。若此阶段抛出异常交易已支付费用矿工仍可将其打包。3.3 重放保护Replay Protection为防止 nonce 被复用以下两种方案必须实现其一方案 A要求SET_INDESTRUCTIBLE前缀要求被 AA 交易指向target的合约必须以 EIP-2937 的SET_INDESTRUCTIBLE操作码开头。不以该操作码开头的 AA 交易无效不能被包含进区块。AA_PREFIX需要相应修改以包含此操作码。对照 EIPS/eip-2937.md该 EIP 在0xA8引入SET_INDESTRUCTIBLE操作码gas 成本G_base将当前callee加入globals.indestructible集合此后该执行上下文中SELFDESTRUCT将抛出异常——即合约代码承诺永久存在、不可被销毁从而保证 AA 账户的 nonce 状态不会被归零清空。方案 BSELFDESTRUCT时保留 nonce另一种方案是跨SELFDESTRUCT调用保留合约 nonce而不是将其置零。3.4 其他环境语义变更Miscellaneous若在 AA 交易发起的调用的第一个执行帧中调用CALLER (0x33)必须返回AA_ENTRY_POINT若在 AA 交易的任意执行帧中调用ORIGIN (0x32)必须返回AA_ENTRY_POINTGASPRICE (0x3A)操作码现在压入globals.gas_price的值。文档特别强调GASPRICE的新定义不会导致非 AA 交易行为发生变化因为非 AA 交易的globals.gas_price初始化为tx.gas_price且PAYGAS无法被调用故该值不可变。四、矿工与重广播策略防 DoS 的核心战场文档指出账户抽象的大部分复杂度来源于矿工与验证节点决定是否接受并重广播交易的策略。矿工必须在极少量处理后就判断一笔交易是否真的会支付费用以避免 DoS 攻击向量验证节点则需要执行本质上相同的验证来决定是否重广播。通过把共识层改动保持在最小范围本 EIP 允许矿工和验证节点渐进式引入 AA 内存池mempool支持初期聚焦简单 single-tenant 用例后续再支持复杂的 multi-tenant 用例。4.1 固定 Nonce 交易的验证规则| 常量 | 值 | | - | - | |VERIFICATION_GAS_MULTIPLIER|6| |VERIFICATION_GAS_CAP| VERIFICATION_GAS_MULTIPLIER * AA_BASE_GAS_COST 90000| |AA_PREFIX|if(msg.sender ! shr(-1, 12)) { LOG1(msg.sender, msg.value); return }编译为 EVM 待定 |节点收到 AA 交易后会针对当前链头chain head的后状态post-state尝试执行以判定其有效性并持续执行直到出现以下事件之一若target的代码不以AA_PREFIX开头判定失败退出若执行中命中以下任一情况判定失败退出环境操作码BLOCKHASH、COINBASE、TIMESTAMP、NUMBER、DIFFICULTY、GASLIMITBALANCE任何账户的余额包括target自身外部调用/创建将callee改变为target或预编译precompile之外的任何对象的调用CALL、CALLCODE、STATICCALL、CREATE、CREATE2外部状态读取代码EXTCODESIZE、EXTCODEHASH、EXTCODECOPY以及CALLCODE、DELEGATECALL除非被读取代码的地址是target若执行消耗的 gas 超过VERIFICATION_GAS_CAP90000或超过区块可用 gas判定失败退出若执行到达PAYGAS则根据余额是否充足即balance gas_price * gas_limit判定成功或失败退出。内存池规则节点不保留 nonce 高于当前有效 nonce 的交易若内存池已有当前有效 nonce 的交易新到的同合约、同 nonce 交易gas price 足够高则替换否则丢弃。因此每个账户的内存池最多只有一笔待处理交易处理新区块时记录哪些账户是 AA 交易的target文档按当时每区块 12500000 gas、AA 交易成本 15000估算每区块最多12500000 // 15000 833个被定向账户丢弃所有定向到这些账户的待处理交易其余交易保留在内存池中。五、Single-Tenant 与 Multi-Tenant 扩展5.1 Single-Tenant放宽验证阶段的限制若 EIP-2937 的不可销毁合约方案被采纳Single-Tenant AA 可适配为允许在交易验证期间使用DELEGATECALL执行新的 AA 交易时对于首字节为SET_INDESTRUCTIBLE操作码的合约其代码读取类外部状态访问EXTCODESIZE、EXTCODEHASH、EXTCODECOPY、CALLCODE、DELEGATECALL不再被禁止。但改变callee的调用即CALLCODE与DELEGATECALL之外的调用仍不被允许。若 IS_STATIC EIP 被采纳允许前缀列表可扩展以支持允许传入静态调用但不允许状态变更调用的前缀前缀列表还可扩展以支持其他良性用例如记录收款日志。外部调用进入 AA 账户的允许方案新增RESERVE_GAS操作码接收参数N行为是立即烧掉Ngas 并将Ngas 加入退款。随后添加一个允许的AA_PREFIX其保留 AA_BASE_GAS_COST * 2的 gas——由于退款最多返还总消耗的 50%这确保了至少AA_BASE_GAS_COSTgas 必须被消耗才能调用进某账户并使该账户在内存池中的交易失效从而维持前述不变量。账户还可以设置更高的RESERVE_GAS值以安全地支持更高的VERIFICATION_GAS_CAP其目标是在编辑账户的最小 gas 成本即其RESERVE_GAS的一半与该账户允许的VERIFICATION_GAS_CAP之间保持VERIFICATION_GAS_MULTIPLIER : 1的比例进而维持上一节关于最大重验证 gas 消耗的不变量。5.2 Multi-Tenant Beyond多用户共享账户在更晚的阶段可以支持每个账户多笔待处理交易。主要挑战有二单笔交易可能引发状态变更使同一账户的其他所有交易失效若朴素地按 gas price 排序交易存在攻击向量愿意支付最高 gas price 的用户发布大量互斥的微小改动版本交易从而把其他人的交易挤出发送池。文档给出了缓解策略的草图要求传入交易包含 EIP-2930 风格的访问列表access list声明交易读取或修改的存储槽且该列表具有约束力——访问列表之外的访问将导致交易无效。交易只有在其访问列表与内存池中其他交易的访问列表互不相交或自身 gas price 更高时才会被纳入内存池。另一种等价思考方式是建立按存储槽划分的内存池per-storage-slot mempools而非仅按账户划分一笔交易可同时属于多个存储槽内存池可按需上限设为 5 个存储槽。此外multi-tenant AA 几乎必然要求允许矿工编辑传入交易的 nonce 以将其排序结果是交易最终哈希在发布时不可预测客户端需要显式处理这一点。文档明确表示这些想法仍需更多研究留待后续工作。六、Rationale 设计论证6.1 核心问题与双阶段执行模型账户抽象设置中的核心问题始终是矿工与网络节点需要验证其尝试打包或重广播的交易确实会支付费用。现有系统之所以简单是因为只要签名、nonce 有效且余额、gas price 充足交易就保证可打包并付费这些检查可以快速完成。AA 的目标是允许账户通过 EVM 代码建立更灵活的交易有效性条件但前提是这些代码可被快速验证且保持与现有系统相同的安全性。普通交易中顶层调用从tx.sender到tx.to并携带tx.value而 AA 交易中顶层调用从入口地址0xFFFF...FF即AA_ENTRY_POINT到tx.target。AA 各阶段之间的过渡完全通过矿工策略的变化实现第一阶段支持 single-tenant AAtx.target是代表用户账户的合约即智能合约钱包如多签后续阶段改进对日志、库等的支持并逐步迈向 multi-tenant AAtx.target是处理多个用户流入活动的应用。6.2 Nonce 的保留与双重用途single-tenant AA 仍强制执行 nonce以保证单目标 AA 不破坏每笔交易从而每个交易哈希只能被包含进链一次的不变量。虽然允许任意顺序包含交易有一定价值但不足以证明打破该不变量的合理性。文档特别指出 AA 账户中 nonce 存在双重用途既用于重放保护也用于使用CREATE操作码时的合约地址生成。这意味着单笔交易可能使 nonce 增加超过 1因为合约创建也会消耗 nonce。这被认为可接受因为 AA 引入的其他机制已打破了轻松验证长度超过一笔的交易链的能力。但文档强烈建议 AA 合约使用CREATE2而非CREATE。在 multi-tenant AA 中nonce 预期将变得可塑malleable使用方需要自行管理。Nonce 暴露给 EVM即NONCE操作码的目的是让验证代码中的签名校验能够验证 nonce。6.3 重放保护必须是共识层变更上述两种方案要求SET_INDESTRUCTIBLE或修改SELFDESTRUCT行为必须实现其一以确保 nonce 不可被复用。它必须是共识层变更而不只是AA_PREFIX的一部分因为要维持交易哈希的唯一性。6.4 为何矿工拒绝 PAYGAS 之前的外部数据访问传统交易的一个重要性质是源自账户 X 之外的事务活动不能使发送者为 X 的交易失效——外部交易能对 X 施加的唯一状态变更是增加其余额而这不会使交易失效。若允许 AA 合约在调用PAYGAS即验证阶段之前访问外部数据包括其他账户和环境变量如GASPRICE、DIFFICULTY等将打破这一不变量。文档举例如下有人发送成千上万笔执行if FOO.get_number() ! 5: throw()外部调用的 AA 交易。FOO.number在交易发送时可能为5但一笔发给FOO的普通交易就能把number改为其他值从而使依赖它的数千笔 AA 交易全部失效——这是严重的 DoS 向量。唯一的例外是不可销毁合约首字节为 EIP-2937 的SET_INDESTRUCTIBLE操作码因为所读取的数据不可能被改变是安全例外。禁止读取BALANCE则封堵了一个较温和的攻击向量攻击者仅需 6700 gas而非 15000 或 21000即可强制一笔交易被重新处理在最坏情况下使需要重处理的交易数量翻倍以上。长期来看AA 可扩展为允许读取外部数据但需要强制访问列表等保护措施。6.5 为何 AA 交易必须调用带前缀的合约前奏prelude用于确保只有 AA 交易能调用该合约这是维护上述不变量的又一项措施。若无此检查源自某 AA 账户 X 之外的交易可以调用进入 X 并制造存储变更从而以仅 5000 gas 的成本强制针对该账户的交易被重新处理。七、Multi-Tenant 的两大经典用例7.1 Tornado Cash隐私混币tornado.cash 的工作流为用户向 TC 合约发送交易存入标准数量代币如 1 ETH其存款记录包含用户所知秘密的哈希被加入一棵 Merkle 树树根存储在 TC 合约中用户之后想提款时生成并发送一个 ZK-SNARK证明自己知道某个秘密其哈希位于存款树的某个叶子中不泄露位置。TC 合约验证 ZK-SNARK并验证 nullifier 值同样可从秘密推导未被花费随后向用户指定地址发送 1 ETH 并记录该 nullifier 已花费。TC 的隐私性在于用户提款时可证明其来自某个唯一存款但除用户外无人知道具体是哪个。朴素实现有致命缺陷用户提款地址通常还没有 ETH而若用存款地址支付 gas会在链上建立存款地址与提款地址之间的关联。目前通过中继者解决但 AA 可以免去中继者用户直接向 TC 合约发送 AA 交易在验证步骤中完成 ZK-SNARK 验证与 nullifier 检查随后直接调用PAYGAS从而用提款地址收到的代币直接支付 gas。完整实现该功能要求 AA 支持多用户同时提款强制 nonce 会使这变得困难且单个账户同时支持 AA 交易提款与外部发起调用存款。7.2 Uniswap套利与链上验证可以构建新版本的 Uniswap允许交易直接定向 Uniswap 合约用户提前向 Uniswap 存入代币Uniswap 存储其余额以及用于验证花费这些余额的交易的公钥。AA 发起的 Uniswap 交易只能花费这些内部余额。这对普通交易者无用其代币在 Uniswap 合约之外但对套利者是强力工具套利者将代币存入 Uniswap每当外部市场条件变化即可发送执行套利的交易价格限制等逻辑可在验证阶段强制。因此未能上链的交易如被其他套利者抢先不会被包含套利者无需支付 gas垃圾交易数量减少从而提升实际可扩展性与市场效率——套利者能更精细地修正跨交易所价差。同样地这里的 Uniswap 也需要同时支持 AA 交易与外部发起调用。八、向后兼容性本 AA 实现保留现有交易类型。使用assert origin caller验证账户是否为 EOA 的现有做法仍然成立但无法扩展到 AA 账户——AA 交易中origin恒为AA_ENTRY_POINT设计不良的 single-tenant AA 合约会破坏交易不可变形non-malleability不变量可以取一笔在途 AA 交易、修改它且修改版仍然有效。AA 账户合约可以设计得使之不可能但这是合约自身的责任。Multi-tenant AA 将更彻底地破坏该不变量即使是合法使用 multi-tenant AA 功能的应用其交易哈希也不可预测不过在此之前已存在的应用不受进一步影响AA 合约除非显式内置否则可能没有重放保护。可使用 EIP-1344 引入的CHAINID (0x46)操作码实现。对照 EIPS/eip-1344.md该操作码将当前链的 EIP-155 链 ID 压入栈gas 成本G_base可用于签名校验中区分不同链防止跨链重放攻击。九、安全考虑9.1 重验证Re-validation的成本边界当交易进入内存池时客户端能快速判定其有效性一旦确认客户端可以确信该交易将持续有效除非来自同一账户的交易使其失效。但存在攻击者发布交易使现有交易失效并要求网络执行超过交易自身计算量的重计算的情况。本 EIP 维持的不变量是单个区块内重计算理论上限为区块 gas limit 的六倍——这比以前略贵但并未贵太多。9.2 对等节点拒绝服务Peer DoSDoS 攻击难以防御因为很难在节点列表中识别女巫sybil节点——任何时刻都可能有人或被贿赂者发起攻击。这不是账户抽象引入的问题用签名无效的交易淹没目标即可对现有客户端实施同样的攻击。但由于 AA 允许的验证工作配额更高限制对手能用无效交易迫使客户端消耗的计算量很重要。正因如此矿工最好遵循文档推荐的挖矿策略。十、测试与实现参考原始文档给出了两个外部参考由于本文只输出仓库内可查证的链接这里以文字说明测试用例位于 quilt/tests 仓库的 account-abstraction 分支参考实现位于 quilt/go-ethereum 仓库的 account-abstraction 分支。原始文档还引用了 ethresear.ch 上关于 geth 中账户抽象/验证泛化 DoS 向量的分析文章作为安全问题的深入参考。结语EIP-2938 的历史坐标EIP-2938 虽已 Withdrawn但其设计遗产清晰可辨AA_ENTRY_POINT作为统一入口的思想、验证/执行两阶段模型、对验证阶段状态访问的限制、以及访问列表用于多租户内存池并发的思路都在后续方案中得到继承或改进——例如 EIPS/eip-4337.mdERC-4337账户抽象的用户操作与入口点合约体系现已移至 ERC 仓库与 EIPS/eip-7702.mdSet Code for EOAs通过授权元组让 EOA 临时设置委托代码分别从Layer 2 式用户操作与EOA 直接执行两条路线继续推进账户抽象。理解 EIP-2938相当于理解以太坊账户抽象设计的第一性原理如何在保持矿工快速验证能力的前提下把交易有效性从签名nonce 的刚性格子里解放出来交给任意代码——而PAYGAS就是那个把我愿意付钱变成协议内建信号的开关。本文内容以仓库内 EIPS/eip-2938.md 为主体骨架辅以 EIPS/eip-2718.md、EIPS/eip-2937.md、EIPS/eip-1344.md、EIPS/eip-2930.md 等仓库内 EIP 文档交叉印证。文中涉及版本、常量与交易类型等细节均以当前仓库文档实际内容为准。【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考