Alert Source Discuss
🛑 Withdrawn Standards Track: Core

EIP-3074: AUTH 和 AUTHCALL 操作码

允许外部拥有的账户将控制权委托给合约。

Authors Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Micah Zoltu (@micahzoltu)
Created 2020-10-15
Discussion Link https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880
Requires EIP-155

摘要

本 EIP 引入了两个 EVM 指令 AUTHAUTHCALL。第一个指令基于 ECDSA 签名设置一个上下文变量 authorized。第二个指令以 authorized 账户的名义发送调用。这本质上是将外部拥有的账户 (EOA) 的控制权委托给智能合约。

动机

为 EOA 增加更多功能一直是一个长期存在的功能请求。这些请求涵盖了实现批量处理能力、允许 gas 赞助、过期、脚本编写等方面。这些更改通常意味着协议的复杂性和僵化程度增加。在某些情况下,这也意味着攻击面增加。

这个 EIP 采取了一种不同的方法。它没有将这些功能作为交易有效性要求写入协议,而是允许用户将其 EOA 的控制权委托给合约。这为开发人员提供了一个灵活的框架,用于为 EOA 开发新的交易方案。本 EIP 的一个动机用例是,它允许任何 EOA 充当智能合约钱包,而无需部署合约。

尽管本 EIP 为个人用户提供了巨大的好处,但本 EIP 的主要动机是“赞助交易”。这是指交易费用由发起调用的账户以外的其他账户提供。

随着以太坊上代币的非凡增长,EOA 经常持有有价值的资产,而不持有任何以太币。如今,这些资产必须先转换为以太币才能用于支付 gas 费用。但是,如果没有以太币来支付转换费用,就不可能进行转换。赞助交易打破了循环依赖。

规范

约定

  • top - N - EVM 堆栈上第 N 个最近推送的值,其中 top - 0 是最近的值。
  • || - 字节连接运算符。
  • 无效执行 - 无效的执行,必须立即退出当前执行帧,并消耗所有剩余的 gas(与堆栈下溢或无效跳转的方式相同)。

常量

常量
MAGIC 0x04

MAGIC 用于 EIP-3074 签名,以防止与其他签名格式发生签名冲突。

上下文变量

变量 类型 初始值
authorized address 未设置

上下文变量 authorized 应指示当前执行帧中 AUTHCALL 指令的活动账户。如果已设置,则 authorized 应仅包含已授予合约代表其行事的授权的账户。未设置的值应指示未设置此类账户,并且 AUTHCALL 指令的当前执行帧中尚无活动账户。

该变量的作用域与程序计数器相同 – authorized 在合约的单个执行帧中持续存在,但不会通过任何调用(包括 DELEGATECALL)传递。如果在单独的执行帧中执行同一合约(例如,对自身的 CALL),则两个帧对于 authorized 具有独立的值。最初,在每个执行帧中,authorized 始终未设置,即使同一合约的先前执行帧具有值。

AUTH (0xf6)

应在 0xf6 创建一个新的操作码 AUTH。它应接受三个堆栈元素输入(最后两个描述一个内存范围),并且应返回一个堆栈元素。

输入

堆栈
堆栈
top - 0 authority
top - 1 offset
top - 2 length
内存

最后两个堆栈参数(offsetlength)描述了一个内存范围。该范围的内容格式为:

  • memory[offset : offset+1 ] - yParity
  • memory[offset+1 : offset+33] - r
  • memory[offset+33 : offset+65] - s
  • memory[offset+65 : offset+97] - commit

输出

堆栈
堆栈
top - 0 success
内存

此指令不会修改内存。

行为

如果 length 大于 97,则额外的字节将被忽略以进行签名验证(它们仍然会产生 gas 成本,如稍后定义)。范围之外的字节(如果 length 小于 97)将被视为零。

authority 是生成签名的账户地址。如果 authorityEXTCODESIZE 不为零,则认为操作不成功并取消设置 authorized

参数 (yParity, r, s) 被解释为 secp256k1 曲线上的 ECDSA 签名,消息为 keccak256(MAGIC || chainId || nonce || invokerAddress || commit),其中:

  • chainId 是当前链的 EIP-155 唯一标识符,填充为 32 字节。
  • nonce 是签名者的当前 nonce,左侧填充为 32 字节。
  • invokerAddress 是执行 AUTH 的合约地址(或 CALLCODEDELEGATECALL 上下文中的活动状态地址),用零左侧填充到总共 32 个字节(例如 0x000000000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)。
  • commit,传递给 AUTH 的参数之一,是一个 32 字节的值,可用于在调用者的预处理逻辑中提交到特定的附加有效性条件。

签名有效性和签名者恢复的处理方式与交易签名类似,包括更严格的 s 范围,以防止 ECDSA 延展性。请注意,yParity 预计为 01

如果签名有效且签名者地址等于 authority,则上下文变量 authorized 设置为 authority。特别是,如果 authority == tx.origin 也是如此,这在早期版本的 EIP 中是单独处理的(请参阅安全注意事项)。如果签名无效或签名者地址不等于 authority,则 authorized 将重置为未设置的值。

如果设置了 authorized,则 AUTH 返回 1,否则返回 0

Gas 成本

AUTH 的 gas 成本等于以下各项的总和:

  • 固定费用 3100
  • 内存扩展 gas 成本 (auth_memory_expansion_fee)。
  • 如果 authority 是热的,则为 100,如果它是冷的,则为 2600(根据 EIP-2929)。

固定费用等于 ecrecover 预编译的成本,再加上一点额外的费用来支付 keccak256 哈希和一些额外的逻辑。

内存扩展 gas 成本 (auth_memory_expansion_fee) 的计算方式应与 RETURN 相同,如果指定的范围超出当前分配,则会扩展内存。

AUTHCALL (0xf7)

应在 0xf7 创建一个新的操作码 AUTHCALL。它应接受七个堆栈元素并返回一个堆栈元素。它与现有的 CALL (0xF1) 指令的行为相匹配,但以下情况除外。

输入

堆栈
top - 0 gas
top - 1 addr
top - 2 value
top - 3 argsOffset
top - 4 argsLength
top - 5 retOffset
top - 6 retLength

输出

堆栈
top - 0 success

行为

AUTHCALL 的解释方式与 CALL 相同,但以下情况除外(注意:此列表也是逻辑检查的优先级顺序):

  • 如果 authorized 未设置,则执行无效(如上所述)。否则,调用的调用者地址设置为 authorized
  • gas 成本,包括子调用可用的 gas 量,在 Gas 成本部分中指定。
  • 如果 gas 操作数等于 0,则该指令将按照 EIP-150 发送所有可用 gas。
  • 如果子调用可用的 gas 小于 gas,则执行无效。
  • 没有 gas 补贴,即使对于非零 value 也是如此。
  • valueauthorized 的余额中扣除。如果 value 高于 authorized 的余额,则执行无效。

AUTHCALL 必须将调用深度增加 1。AUTHCALL 不得将调用深度增加 2,因为它会首先调用到授权帐户,然后再调用到目标帐户。

使用 RETURNDATASIZE (0x3d) 和 RETURNDATACOPY (0x3e) 访问的返回数据区域必须以与 CALL 指令相同的方式设置。

重要的是,AUTHCALL 不会重置 authorized,而是保持不变。

Gas 成本

AUTHCALL 的 gas 成本应为以下各项的总和

  • 静态 gas 成本 (warm_storage_read)
  • 内存扩展 gas 成本 (memory_expansion_fee)
  • 动态 gas 成本 (dynamic_gas)
  • 子调用中执行可用的 gas (subcall_gas)

内存扩展 gas 成本 (memory_expansion_fee) 的计算方式应与 CALL 相同。

动态 gas 部分 (dynamic_gas) 和子调用中执行可用的 gas (subcall_gas) 应计算为:

dynamic_gas = 0

if addr not in accessed_addresses:
    dynamic_gas += 2500         # cold_account_access - warm_storage_read

if value > 0:
    dynamic_gas += 6700         # NB: 不像 `CALL` 中那样为 9000
    if is_empty(addr):
        dynamic_gas += 25000

remaining_gas = available_gas - dynamic_gas
all_but_one_64th = remaining_gas - (remaining_gas // 64)

if gas == 0:
    subcall_gas = all_but_one_64th
elif all_but_one_64th < gas:
    raise                       # 执行无效。
else:
    subcall_gas = gas

CALL 一样,完整的 gas 成本会立即收取,与实际执行调用无关。

理由

内存中的签名

签名格式 (yParityrs) 是固定的,因此 auth 接受动态内存范围可能看起来很奇怪。签名放置在内存中,以便将来可以升级 auth 以与合约帐户(可能使用非 ECDSA 签名)而不仅仅是 EOA 一起使用。

签名地址 auth 参数

authority(签名地址)作为 auth 的参数包括在内,允许将来升级指令以与合约帐户一起使用,而不仅仅是 EOA。

如果不包括 authority 并且允许多种签名方案,则仅从签名本身计算授权帐户的地址是不可能的。

保留可用 Gas 的六十四分之一

由于 EIP-150 中列举的原因,AUTHCALL 将不会传递超过可用 gas 的 63/64。

AUTHCALL 期间为未设置的 authorized 抛出异常

一个行为良好的合约在成功设置 authorized 之前,永远不应达到 AUTHCALL。因此,最安全的行为是立即退出当前的执行帧。这在事务赞助/中继的上下文中尤其重要,预计这将是本 EIP 的主要用例之一。在赞助事务中,无法区分由 sponsee 引起的故障(如失败的子调用)和由赞助商引起的故障(如失败的 AUTH)尤其危险,应避免,因为它会向 sponsee 收取不公平的费用。

另一个赞助交易 EIP

有两种通用方法可以将“费用支付者”与“行为发起者”分开。

第一种是引入一种新的交易类型。这需要对客户端进行重大更改才能支持,并且通常不如其他解决方案(例如,此 EIP)更具可升级性。这种方法也不能立即与账户抽象 (AA) 兼容。这些提案需要来自赞助商帐户的签名交易,这对于 AA 合约是不可能的,因为它没有用于签名的私钥。新交易类型的主要优点是有效性要求由协议强制执行,因此无效交易不会污染区块空间。

另一种主要方法是在 EVM 中引入一种新的机制来伪装成其他帐户。本 EIP 引入了 AUTHAUTHCALL 以 EOA 的身份进行调用。此机制有许多不同的排列。另一种机制是添加一个操作码,该操作码可以基于与 CREATE2 类似的地址创建方案进行任意调用。尽管此机制今天不会使用户受益,但它将立即允许这些帐户发送和接收以太币——使其感觉更像是一个一流的原语。

除了与 AA 更好的兼容性之外,在 EVM 中引入一种新机制比一种新的交易类型更不具侵入性。这种方法不需要更改现有钱包,并且对其他工具的更改也很少。

AUTHCALLCALL 的唯一偏差是设置 CALLER。它实现了最小的功能,以实现赞助交易的发件人抽象。这种专一性使 AUTHCALL 能够与现有的以太坊功能更好地组合。

可以在 AUTHCALL 指令周围实现更多逻辑,从而在不牺牲 sponsee 的安全性或用户体验的情况下,为调用者和赞助商提供更多控制权。

签署什么?

正如最初编写的那样,该提案指定了一个带有存储的预编译来跟踪 nonce。由于带有存储的预编译是前所未有的,因此修订版已将重放保护移至调用者合约中,从而要求用户对调用者具有一定的信任度。扩展对受信任调用者的这一想法,其他签名字段最终被逐一消除,直到仅剩下 invokercommit。为了平息对跨链重放攻击和不可撤销签名的担忧,chainIdnonce 字段返回到签名消息。

invoker 将特定的签名消息绑定到单个调用者。如果调用者不是消息的一部分,则任何调用者都可以重用签名以完全破坏 EOA。这允许用户信任他们的消息将按他们的预期进行验证,尤其是 commit 中承诺的值。

了解 commit

此 EIP 的早期迭代包括用于重放保护的机制,并且还对 AUTHCALL 的 value、gas 和其他参数进行了签名。经过进一步调查,我们将此 EIP 修订为当前状态:明确地将这些职责委派给调用者合约。

用户将专门与他们信任的调用者交互。因为他们信任此合约能够忠实地执行,所以他们将通过计算调用值的哈希值来“提交”他们想要进行的调用的某些属性。他们可以确定,如果能够验证提交的值(例如,用于防止重放攻击的 nonce),调用者将仅允许该调用继续进行。这种确定性来自用户签署的 commit 值。这是调用者将验证的值的哈希值。一个安全的调用者应该接受用户的值并自己计算 commit 哈希值。这确保了调用者对用户授权的相同输入进行操作。

auth 消息格式

使用 commit 作为值的哈希允许调用者实现任意约束。例如,他们可以允许帐户具有 N 个并行 nonce。或者,他们可以允许用户使用单个签名提交到多个调用。这将允许多交易流程,例如 ERC-20 approve-transfer 操作,可以压缩为具有单个签名验证的单个交易。对多个调用的提交将类似于下图。

多调用 auth 消息

另一个有趣的用途是将 EOA 的控制权委托给其他密钥。这将意味着 EOA 使用密钥地址和访问策略(如果适用)对 commit 消息进行签名。当委托想要以 EOA 的身份进行调用时,它将构造一个签名,该签名涵盖调用者指定的调用格式,并将其(带有实际调用数据)与授予其访问该帐户的签名和 commit 一起中继到链上。然后,调用者将能够确定 EOA 已允许此备用密钥代表其进行调用。

委托 auth 消息

调用者合约

调用者合约是赞助商和 sponsee 之间的无信任中介。sponsee 对 invoker 进行签名,以要求该交易仅由他们信任的合约进行处理。这使他们能够在不需要信任赞助商的情况下与赞助商进行交互。

选择调用程序类似于选择智能合约钱包实现。重要的是选择一个经过社区彻底审查、测试和接受为安全的调用程序。我们预计大多数主要交易中继提供商将使用一些调用程序设计,而少数异常值将提供更多新颖的机制。

一个重要的注意事项是,调用者合约不得可升级。如果可以以不同的代码将调用者重新部署到同一地址,则可以使用不正确验证 commit 的代码重新部署调用者,并且对该调用者签名消息的任何帐户都将受到破坏。尽管这听起来很可怕,但这与通过 DELEGATECALL 使用智能合约钱包没有什么不同。如果使用不同的逻辑重新部署该钱包,则使用其代码的所有钱包都可能受到破坏。

关于调用深度

EVM 限制了嵌套调用的最大数量,并且天真地允许赞助商在到达调用者之前操纵调用深度会引入针对 sponsee 的拒绝服务攻击。也就是说,通过 63/64th gas 规则和 AUTHCALL 的成本,该堆栈实际上受到比 gas 参数的硬性最大值小得多的深度的限制。

因此,调用者保证最小数量的 gas 就足够了,因为不可能以任何合理的(即少于数十亿)数量的 gas 达到硬性最大调用深度。

value 的来源

在此 EIP 的先前迭代中,认为在执行过程中从 EOA 中扣除 value 是有问题的。这是由于挂起交易的不变性,该不变性允许 tx pool 静态地确定给定交易的有效性。

但是,经过进一步调查,我们发现打破不变性是安全的。这主要是因为在两种情况下,最坏的情况都是相似的。

当前,攻击者可以在 tx pool 中跨多个帐户排队许多交易,并使用一个区块一次性使它们全部无效,其中每个排队的帐户都发送一个 tx 来移动其全部余额。在此 EIP 之后,此攻击将变得更容易和更便宜,因为它将不再需要直接访问区块构建器,并且发起每个 tx 不需要完整的 21000 gas。但是,该攻击对网络没有实质性影响,因此降低难度和成本并不令人担忧。

允许 tx.origin 作为签名者

允许 authorized 等于 tx.origin 可以实现简单的交易批处理,其中外部交易的发件人将是签名帐户。目前需要两个单独的交易的 ERC-20 approve-then-transfer 模式可以通过此提案在单个交易中完成。

AUTH 允许签名由 tx.origin 签名。对于任何此类签名,后续的 AUTHCALL 在其第一层执行中都具有 msg.sender == tx.origin。如果没有 EIP-3074,则仅在交易的最顶层执行层中才会出现这种情况。此 EIP 打破了该不变量,因此会影响包含 require(msg.sender == tx.origin) 检查的智能合约。此检查可用于至少三个目的:

  1. 确保 msg.sender 是 EOA(因为 tx.origin 始终必须是 EOA)。此不变量不依赖于执行层深度,因此不受影响。
  2. 防止原子三明治攻击,例如闪电贷,这些攻击依赖于在同一原子交易中在目标合约执行之前和之后修改状态的能力。此保护将被此 EIP 打破。但是,以这种方式依赖 tx.origin 被认为是错误的实践,并且已经可以通过矿工有条件地在区块中包含交易来规避。
  3. 防止重入。

可以在部署在以太坊主网上的合约中找到 (1) 和 (2) 的示例,其中 (1) 更常见(并且不受此提案的影响)。另一方面,用例 (3) 更严重地受到此提案的影响,但此 EIP 的作者未找到任何这种形式的重入保护的示例,尽管搜索是非详尽的。

此事件分布——许多 (1)、一些 (2) 和没有 (3)——正是此 EIP 的作者所期望的,因为:

  • 在没有 tx.origin 的情况下确定 msg.sender 是否为 EOA 很困难(如果不是不可能的话)。
  • 唯一免受原子三明治攻击的执行上下文是最顶层的上下文,而 tx.origin == msg.sender 是检测该上下文的唯一方法。
  • 相比之下,有许多直接和灵活的方法可以防止重入(例如,使用存储变量)。由于 msg.sender == tx.origin 仅在最顶层的上下文中为真,因此它将成为防止重入的模糊工具,而不是其他更常见的方法。

还有其他方法可以缓解此限制,而不会破坏不变量:

  • 对于 AUTHCALL,将 tx.origin 设置为常量 ENTRY_POINT 地址。
  • 对于 AUTHCALL,将 tx.origin 设置为调用者地址。
  • tx.origin 设置为从发件人、调用者和/或签名者地址派生的特殊地址。
  • 禁止 authorized == tx.origin。这将使简单的批处理用例变得不可能,但将来可能会放宽。

发送 value 时,AUTHCALLCALL 便宜

使用 CALL 发送非零 value 会将其成本增加 9,000。其中,6,700 涵盖了余额转移的额外开销,而 2,300 用作子调用的补贴,以播种其 gas 计数器。AUTHCALL 不提供补贴,因此仅收取基本 6,700。

协议内撤销

此 EIP 已经 来回 关于如何处理 AUTH 消息撤销。如果没有撤销,此 EIP 将为开发人员提供极其强大和灵活的原语。但是,对于使用不安全和/或恶意调用者的用户,它确实存在风险。

大部分风险是由于用户能够在一个交易中批量处理多个操作的新能力。帐户被耗尽变得更加容易。无论此 EIP 是否被采用,这都是一个将继续增长的风险,因为对该功能的压倒性渴望以及在协议级别和应用级别支持它的尝试。

为不安全和有错误的调用者引入了一类新的风险。如果调用者已按照作者的建议实施了重放保护,则这应大大包含爆炸半径。但是,如果该错误允许对手规避重放保护机制,它可能会使他们完全访问与易受攻击的调用者进行交互的任何 EOA。

尽管这是一个真正的灾难性事件,预计不可能通过信誉良好的钱包实现,但这是一个严重的考虑因素。如果没有协议内撤销,用户将无法从易受攻击的调用者中删除其帐户。

因此,AUTH 要求消息中的 nonce 等于签名者的当前 nonce。这样,来自 EOA 的单个交易将导致 nonce 增加,从而使所有未完成的授权失效。

EXTCODESIZE 检查中失败

EIP-3607 中,确定协议应拒绝来自具有代码的帐户的任何交易。尽管此 EIP 侧重于交易发起,但 EIP-3074 的作者认为意图是明确的:不允许具有代码和已知私钥的帐户代表该帐户进行任意调用。因此,此属性在此 EIP 中得到维护。有关完整理由,请参阅 EIP-3607

向后兼容性

尽管此 EIP 对向后兼容性没有造成任何问题,但人们担心它通过进一步巩固 ECDSA 签名来限制了对帐户的未来更改。例如,可能希望完全消除 EOA 的概念,并用模拟相同行为的智能合约钱包来替换它们。这与编写的 EIP 完全兼容,但是,如果用户可以选择“升级”其智能合约钱包以使用其他身份验证方法(例如,转换为多重签名),则会变得棘手。在没有任何更改的情况下,AUTH 将不会尊重此新逻辑,并将继续允许旧的私钥代表该帐户执行操作。

一种解决方案是,在删除 EOA 的同时,修改 AUTH 的逻辑以实际使用一些标准消息调用到该帐户,并允许该帐户确定签名/见证是否有效。应进行进一步研究,以了解在这种情况下调用者需要如何更改以及如何以与未来兼容的方式编写它们。

安全注意事项

安全调用者

以下是非详尽的检查/陷阱/条件列表,调用者警惕:

  • 重放保护(例如,nonce)应由调用者实施,并包含在 commit 中。如果没有它,恶意行为者可以重复使用签名,重复其效果。
  • value 应包含在 commit 中。如果没有它,恶意赞助商可能会在被调用者中造成意外影响。
  • gas 应包含在 commit 中。如果没有它,恶意赞助商可能会导致被调用者耗尽 gas 并失败,从而给 sponsee 带来麻烦。
  • addrcalldata 应包含在 commit 中。如果没有它们,恶意行为者可能会在任意合约中调用任意函数。

实施不佳的调用者可以允许恶意行为者几乎完全控制签名者的 EOA

允许 tx.origin 作为签名者

允许 authorized 等于 tx.origin 可能会:

  • 打破依赖于 tx.origin 的原子三明治保护;
  • 打破 require(tx.origin == msg.sender) 样式的重入保护。

此 EIP 的作者认为,出于理由部分中概述的原因,允许 authorized 等于 tx.origin 的风险是可以接受的。

赞助交易中继器

authorized 帐户可能会导致赞助交易中继器花费 gas,而不会因使授权失效(即增加帐户的 nonce)或通过清除帐户中的相关资产来获得补偿。中继器的设计应考虑到这些情况,可能需要存入保证金或实施信誉系统。

版权

通过 CC0 放弃版权和相关权利。

Citation

Please cite this document as:

Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Micah Zoltu (@micahzoltu), "EIP-3074: AUTH 和 AUTHCALL 操作码 [DRAFT]," Ethereum Improvement Proposals, no. 3074, October 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3074.