本文详细介绍了以太坊账户系统的核心概念,包括椭圆曲线密码学(ECC)及ECDSA签名算法在账户授权中的应用,区分了外部账户(EOA)与合约账户(CAC)的功能差异。
包含敏感资源的数字系统通常通过某种执行用户认证的访问控制机制来保护,这是一种验证给定代理是否真正拥有执行某些功能所需权限的方式。我们可以将这些访问控制机制视为安全措施,负责防止对数字资源(如数据库、应用程序、账户,甚至数字资产)的未授权访问,使用的方法包括:
这些方法可以单独使用,也可以结合使用,以确保基于数字的对手无法获取属于特定用户的非物理资产。因此,这些数字访问控制机制在加密领域更加重要就合情合理了;在那里,默认情况下:假名实体在不同的弱信任环境中进行交易,并完全负责持有和保护自己的资产。因此,可以说加密领域所倡导的自我托管功能是通过在区块链之上实现数字访问控制系统而实现的,前者通过管理对这些资产的访问来保护用户的资产。
在本节中,我们将讨论椭圆曲线密码学(ECC)的组成部分;这是一种可用于构建这些访问控制机制的密码学原语,也是一种基于椭圆曲线离散对数问题(ECDLP)的非对称密钥对密码学。几行之后可能会变得令人困惑,因为它与火箭科学相邻;所以,亲爱的读者,非常重要的一点是,你要不断提醒自己,我们本节讨论的所有内容只是以太坊用于最终用户身份验证和授权检查的特定机制的实现细节。
我们在本小节的主要目标是明确并理解 ECDLP 是以太坊公钥派生和使用的基础。这需要一些对所谓单向函数的基本理解;这些是易于计算但本质上不可逆的数学问题。单向函数用于创建非常难以“破解”的算法,换句话说,它们用于创建现有计算方案无法轻易计算的計算系统(也许可以将其视为一个无法使用任何已知数学运算组合完全求解的方程)。这种函数可以创建的一种计算系统是非对称密钥对,它由一个私钥(用于生成附加到消息的数字签名)和一个关联的公钥(使观察者能够验证签名是由实际拥有私钥的签名者生成的)组成。
区块链通常用于从随机采样的私钥派生公钥的特定单向函数是标量乘法,当试图进行逆向操作(即从公钥派生出私钥)时,由于在有限域上应用的椭圆曲线的循环子群所表现出的性质,会产生 ECDLP。这有点拗口,所以接下来的两个小节会尽可能简单地解释其中一些概念——它们对于理解文章的其余部分并不是非常必要,但了解一些东西很有趣,而且也没有编辑阻止我 🫡。
椭圆曲线被定义为由方程 (y^2 = x^3 + ax + b) 描述的一组点。其外观最简单的特征是它通常是平滑且渐进的;如果你愿意进一步探索它们的行为,我建议阅读这篇文章,我向你保证它并不是特别消遣性的。
椭圆曲线可以是伪随机的,这样它们的系数是从一个种子加密哈希的输出生成的;或者它们可以是特殊的曲线,其系数被选择以优化计算效率。在以太坊非对称密钥的背景下,我们关心的特殊曲线是 secp256k1,这是一种科布利茨曲线,其特征是:(a = 0) 且 (b = 7)。因此,我们关心的曲线方程简化为:(y^2 = x^3 + 7)。
使用 k-256 曲线而不是更流行的替代方案(如 p-256(secp256r1))一直是钱包提供商的眼中钉,尤其是因为它极大地导致了链上资产授权和恢复机制的匮乏。虽然选择 k-256 是为了避免将潜在的国家支持的后门引入当时被宣传为反国家货币的系统中,这是可以理解的;但直到今天,缺乏这种漏洞确实存在的证据,让许多人怀疑,近二十年来使用与大型科技公司不同的密码系统是否值得。毫不奇怪,弃用 k-256 并概括以太坊账户的底层密码系统是账户抽象常被引用的原因之一,我们很快就会看到!
椭圆曲线的一个普遍特征是它们可以在一个域上定义,以获得域的笛卡尔积与自身集合中的元素值。也就是说:假设我们有一个域 (F),使用域中的元素应用我们的曲线方程将得到包含在 (F \times F) 中的元素;其中 (F) 是任何元素集合,在该集合上加法、减法、乘法和除法的行为与在有理数上的行为相同。
在域的问题上,我们只对有限变体感兴趣;我们可以将有限域描述为一个包含有限数量的“可操作”元素的集合。粗略地说,这意味着一个有限域必须满足两个性质:它的元素在加法、减法、乘法或除法时的行为可以像有理数一样;并且该集合本身有有限的基数,我们称之为其阶((N))。
由于有多种方法可以生成不同类型的有限域,有必要记住,我们这里感兴趣的特定类型的有限域是模 (p) 的整数集合,其中 (p) 被选择为一个大素数。通过这种方式,在有限域上耦合一个椭圆曲线将始终将任何点的 (y) 坐标的最大值限制为 (p - 1)。
在有限域上定义的椭圆曲线点的标量乘法产生一个循环子群;这仅仅是由域内定义的椭圆曲线上的单个点生成的一组重复值。这个子群显然是我们的有限域的一个子集,因此它有自己的阶,我们记为 (n)。虽然 (N) 和 (n) 分别表示在模 (p) 的整数集合上定义的椭圆曲线的基数,以及在预定义点 (G) 处生成的循环子群的基数;后者也可以定义为最小的可能整数,它将 (G) 的坐标(即 (G_x) 和 (G_y))相乘,得到无穷远点的坐标(其中无穷远点是循环子群的单位元)。
此外,(N) 和 (n) 之间的关系根据拉格朗日定理定义,因此:(h = N / n)。其中 (h) 被称为曲线的辅因子,最好是一个小值,以便椭圆曲线(模 (p))中的大多数点也在我们选择的点生成的循环子群中。这个条件是必要的,以防止“子群分解”,当给定的循环子群包含可以作为更小循环子群生成器的点时,就会发生这种情况,这样前一个构造的 ECDLP 可以通过其不那么棘手的更小子群的 ECDLP 来解决。
综合起来:我们有一条椭圆曲线,其定义域被限制为大素数 (p) 的模,曲线上的点位于一个基数为 (N) 的集合中;我们必须从这个集合中选择一个合适的生成点 (G),其倍数形成一个阶为 (n) 的循环子群;其中 (N \equiv n),因此它们的辅因子 (h) 为 (1)。这些参数构成一个六元组,表征了一个基于椭圆曲线的密码系统,特别是 secp256k1,它通常用于区块链中的公钥派生,其具体值可以在这里找到。
椭圆曲线密码学利用了获取值 (k) 的不可行性,该值将乘以静态生成点 (G (x, y)) 以在给定曲线上产生某个预定义点 (Q (x, y));我想这是定义 ECDLP 的一种方式!再次强调:给定一个点 (G (x, y)) 及其在已知椭圆曲线(应用于模 (p) 的整数集合内)上的“倍数” (Q (x, y)),能否获得乘数的值 (k)?目前,对这个问题的最终答案完全取决于你拥有多少“计算能力”!更准确地说,解决一个构造良好¹的 ECDLP 变体需要一个运行时间不受多项式表达式限制的量子算法,以及一个操作基于量子比特且通常不存在的量子计算机²。
因此,验证 secp256k1 上的给定点 (Q) 是生成点 (G) 的倍数(即 (Q = kG))是一件微不足道的事情,但当 (k) 是集合 ({1, \ldots, n - 1}) 中的随机元素并且 (n) 是一个适当大的素数时,求解 (k) 总是不可行的。在这里,私钥 (privkey) 对应于这个不可获取的 (k),而生成点 (G) 是所选曲线上的一个公开点;因此它们的乘积仍然是曲线上的一点,称为公钥 (pubkey)。
上述方式形成的密钥对的一个潜在应用是构建数字签名算法(DSA)。DSA 是 NIST 推荐的用于生成数字签名的标准算法,其中签名是任何能够证明给定消息来源的对象。因此,椭圆曲线数字签名算法(ECDSA)是 DSA 的一个变体,它基于 ECDLP,允许前一种算法在更低的比特位数下享有比后者更高的安全保证。
ECDSA 中签名的生成和附加到消息的过程如下:
要发送的消息(比如 (m))通常是一个可变长度的字符串,但我们必须将其折叠成一个哈希,因为 ECDSA 只能对 256 位的值进行操作。以太坊为实现此目的而实现的函数是 keccak256 函数,它将 (m) 折叠成一个相应的 256 位值 (z)。
然后从集合 ({1, \ldots, n - 1})(即从我们的循环子群)中选择一个临时随机数 (e),要求它对每次操作都是不可预测且唯一的。然后我们用它作为生成点 (G) 的乘数(更准确地说,我们将 (G) 自加 (e) 次),得到它们的相应乘积 (P)(仍然是一个点)。数学上:(e \cdot G = P)。
所得点 (P) 的 (x) 坐标 (P_x) 是我们签名的第一个分量,因此:(P_x \bmod n = r)。并且如果 (r = 0),则选择不同的 (e) 并重复步骤 2。
我们签名的第二个分量定义为:(s = e^{-1} \cdot (z + r \cdot privkey) \bmod n),其中 (e^{-1}) 是临时随机数的模乘法逆元。如果 (s = 0),则选择另一个 (e) 并重复步骤 2。
传统 ECDSA 中的最终签名被给出为结构体:r,s,其中每个值都是一个整数。但关于以太坊,没有任何东西可以真正被称为传统的(我们的 PoS 并非真正的 PoS,我们的 SHA3 也并非真正的 SHA3)。
标准单签名者 ECDSA 方案大致涉及两方:一个消息发送者,他使用临时随机数在曲线上选择一个随机点,并使用自己的私钥将一条消息提交到此点;以及一个验证者,他通过使用发送者生成的签名元素和后者的公钥求解该点来确保签名有效。那个引子埋得太深了,不便打扰,但我想说的是,如果消息接收者没有发送者的公钥,签名验证就无法进行。不过这可能也不是什么大问题,因为我们可以让发送者事先将他们的公钥传达给接收者,瞧!好建议,同志!好到这正是 ECDSA 通常所做的。
但是,在区块链上执行公钥广播(甚至缓存)会增加整体运营成本,这是你通常希望尽可能避免的。那么你应该怎么做?随便你,因为除了以太坊当前使用的方法(即通过 yParity 变量进行公钥恢复)之外,我们并不真正关心其他优化技术。
还记得我们说过 ECDSA 通常在消息传输之前进行公钥广播吗?嗯,是“通常”,因为公钥恢复在这里也是一个选项!ECDSA 中公钥恢复的一般逻辑是,对于任何给定的 ([z, r, s]),只要提供一些关于从中获得 (r) 的点 (P) 的额外信息,就可以恢复公钥。这是必要的,因为如前几段所述:双方本质上是通过不同的方程组求解同一个点;但随后 (r) 参数将只向消息接收者揭示任何点的 (x) 坐标,这是一个问题,因为椭圆曲线是关于 (x) 轴对称的,所以任何给定的 (x) 坐标总是有两个具有不同奇偶性的有效 (y) 坐标³。这意味着如果只给出 (r),可以恢复出两个(有时是四个⁴)同样有效的点,而其中只有一个对于给定的签名是正确的。因此,从 (P) 中提取的额外数据允许消息接收者更加确定计算出的点的正确性,并防止浪费的签名验证周期。
以太坊为实现密钥恢复而提取的数据是 (P_y) 的奇偶性,我们记为 (yParity)。这里,(yParity = P_y \bmod 2),因此当 (P_y) 为偶数时 (yParity) 为 (0),当 (P_y) 为奇数时为 (1)。这不会引入任何计算复杂性,因为消息发送者在签名过程的第二步已经计算了整个点。因此,为了恢复公钥,我们必须在丢弃实际值并计算签名第三个分量 (v) 之前记录 (P_y) 的奇偶性;然后将后者连同 (r)、(s) 和 (z) 一起传递给接收者。根据 EIP-155:([v] = 2 \cdot \text{chainID} + yParity + 35);它指定了附加到传统交易的最终结构体的第三个签名元素:(([v], [r], [s]))⁵。
总之,传统 ECDSA 方案中的签名验证通常基于对公钥的了解,并在这里进行了广泛讨论;而以太坊对于给定的 ([z, v, r, s]) 的公钥恢复(和签名验证机制!)过程如下:
验证 (r) 和 (s) 是集合 ({1, \ldots, n - 1}) 中的整数。如果不是,则签名无效。
使用值 (r) 作为 (x),求解 (y^2 = x^3 + 7) 中的 (y),其中 (y) 和 (x) 对应于 (P_y) 和 (P_x) 的值。这将产生两个 (y) 值,我们根据 (v) 的值选择我们的 (P_y);如果 (v) 是 (0),则 (P_y) 是“正”值,如果 (v) 是 (1),则是“负”值。这一步结束时,我们将有效地重建签名过程第二步中定义的 (P)。
求解公钥:(pubkey = r^{-1} (s \cdot P - z \cdot G) \bmod n)。
使用 keccak256 算法对所得公钥值进行哈希处理,并获得一个图像:(address = \text{keccak256}(pubkey)的最后20字节)。
将恢复的图像与 (z) 的“来自”字段进行比较。如果两个值匹配,则签名是真实的!

到目前为止的整个讨论都是关于地址生成的典型方式,所以如果你想知道这为什么重要,是可以理解的。在大多数情况下(尤其是 DAU 测量 lol),将地址描述为唯一实体的表示可能就足够了;但更准确地说,它们代表称为“账户”的结构的唯一实例,这些结构是与世界计算机™交互的主要接口。
因此,以太坊地址基本上是 42 个字符的十六进制字符串,作为账户的唯一标识符,后者向以太坊网络发送交易进行处理。因此我们可以推断,在我们之前对 ECDSA 的讨论中,账户等同于消息发送者和签名生成者;他们发送到网络的消息可能包含一些可执行逻辑,这些逻辑只有在通过所描述的 ECDSA 过程对账户进行身份验证后才能处理。这就是为什么首选定义对于以太坊账户来说,它们是指拥有以太币余额并且能够在以太坊区块链上发送交易的实体。但这个定义暂时与我们无关。
为了我们这里的目的,我们会将账户视为具有四个字段的状态对象,其特征性的更高级功能取决于其字段的值。以太坊账户的四个字段是:
balance,代表账户以 wei 计价的 ETH 持有量;nonce,一个简单的基于十进制系统的序列计数器,没什么特别的;codeHash,账户关联字节码的 keccak256 哈希值;storageHash,账户状态变量的根,这些变量在 RLP 编码和 keccak256 哈希的 Merkle Patricia Trie 上表示。后两个字段的内容是区分两种主要以太坊账户类型的基础:外部拥有账户(EOA)和合约账户(在本文其余部分我们将其缩写为 CAC,以避免与更常见的 CA 用于合约地址混淆)。确切地说:
EOA 按照惯例没有任何“有状态”元数据;因此它们既不包含字节码也不包含存储变量,相应地,它们的 codeHash 字段始终包含空字符串的 keccak256 哈希值,而它们的 storageHash 字段实际上保持为空,这是账户的默认状态。EOA 可以生成签名、发送消息和发起交易;但它们不能自行执行包含多个可执行消息调用的交易。还需要注意的是,EOA 中的 nonce 跟踪该账户已发送的交易数量;使其能够作为从 EOA 发出的待处理交易的交易池排序机制,并防止交易重放攻击。
在 CAC 中,余额的可支出性取决于其 codeHash 指向的字节码的逻辑,以及 storageHash 中包含的账户自身状态。它们不拥有基于 ECC 的密钥对,因此无法生成签名或发起交易,但它们可以构建和发送包含复杂可执行逻辑的消息调用,最大可达指定交易大小。
这些差异的总结性后果是,所有 EOA 都是平等的(只要你的钱包提供商在他们那端做得足够好),而 CAC 通过其内在逻辑提供的功能在理论上是无限的。此外,CAC 可以被编程为用户面对的智能合约钱包(SCW),这些钱包在其交易中充分利用 EVM 的功能,从而启用诸如:多调用交易、原子交易、账户多租户和恢复、由不同账户赞助其交易费用等功能。此类 SCW 的流行实现包括:ZeroDev、Argent 和 Safe。
此时应该清楚,这两种账户类型之间的功能差距在执行交易时最为明显;其中交易被定义为任何从账户发送的、能够导致网络状态变化的操作。
与账户类似,交易也可以被视为状态对象;因此可以说它们的字段指定了用于构建状态转换执行上下文的变量。这些字段包括⁶:
destination:指定交易目标账户的地址。由于交易实际上是一系列可能产生期望结果的调用,对该字段更准确的描述是,它是交易首次调用所针对的账户地址。
value:指定要从广播签名交易的账户中转出的 ETH 数量。
chainID:一个静态值,指定交易将在其上处理的底层链。
nonce:签名地址的传出交易计数值,包括当前正在广播处理的交易。
data:包含所有调用的 ABI 编码,这些调用必须通过指定智能合约暴露的某个给定函数以及每次调用传递的变量传递给它们。因此,它是一个可变长度字符串,包含 EVM 执行用户请求所需的详细信息。
maxPriorityFeePerGas:通常称为区块提议者的小费或优先费用,用于从当前Slot的提议者那里获得区块包含保证。
maxFeePerGas:指定用户愿意为其交易在当前Slot被处理而支付的每单位气体的绝对最高费用。它用于保护用户免受基础费用波动的影响。
gasLimit:指定指定交易可以消耗的气体量的上限。它通过根据交易模拟运行的气体估算设置交易可使用气体量的限制,来防止因掠夺性或复杂的合约逻辑而导致的多付Gas费用。
(yParity, r, s):这些与我们在上一节中描述的签名项相同。
accessList:可以选择填充交易打算调用或操作的合约地址和存储槽列表。
transactionType:指定正在处理的交易属于哪类符合 EIP-2718 的交易,并可能允许交易携带其他字段。
destination、value 和 data 字段在决定发生何种交互中发挥作用,如下所示:
如果 destination 指向一个 EOA 并且 data 留空,那么必须有一个非零的 value 由 EVM 从发送者传递给 destination 中的 EOA。
如果 destination 指向一个 CAC 并且 value 为零,那么 data 必须包含一个函数选择器以及要传递给 destination 中 CAC 的调用的 ABI 编码。或者,如果 value 是非零数量,那么 data 必须为空,并且只有在 CAC 的字节码实现了 receive() 或 fallback() 函数时,EVM 才能传递指定的 value。
如果 destination 为空,并且 data 包含原始 EVM 字节码字符串与 ABI 编码的构造函数参数连接而成;那么 EVM 将 data 视为“初始化代码”,默认情况下通过 CREATE 操作码用于初始化一个新的 CAC。如果交易带有 value,则将其转入部署的合约。
此外,由于以太坊有五种(从零开始)不同类别的交易,它们服务于不同的目的,并且通常需要不同的处理方式,因此客户端有必要在开始任何处理工作之前确定交易属于哪个类别。EIP-2718 的类型化交易信封使这项任务变得简单,其 transactionType 变量被客户端用来确定用于给定交易负载的整体处理逻辑。EIP-1559 之后,每笔交易都必须携带上面定义的交易字段,但它们是可配置的,也可能包含交易特定的字段,例如:BLOB_TX_TYPE 交易的 maxFeePerBlobGas 和 blobVersionedHashes,以及 SET_CODE_TX_TYPE 交易的 authorisationList。
交易处理经历一系列事件,主要包括钱包实例构建交易、客户端节点交易池内的交易验证、EVM 执行交易以及从网络检索收据。这三个中的每一个都对应交易生命周期中的一个不同阶段,分别是:
验证阶段 - 通常发生在用户钱包软件的链下,然后在交易池中。在以太坊上,此阶段由一组限制性条件组成,客户端期望交易在考虑进行 EVM 执行之前必须(或应该)始终满足这些条件;这些条件主要取决于交易的类型和发送交易的账户类型。
执行阶段 - 在此期间,构建好的交易由 EVM 执行,并达到新的状态。解释 EVM 远远超出了本文的范围,所以我们只说一下,它是以太坊中负责实际执行用户在交易中传达的请求的部分,通过一组称为操作码的最小化操作。
检索阶段 - 在此期间构建收据,并根据需要记录执行数据,以帮助链浏览器和数据提供商等第三方服务发现。它还可能涉及一些执行后记账,例如 ERC-4337 的 userOp,其交易费用可能由支付主合约赞助。
虽然这三阶段对于任何给定的交易都必须发生,但它们的进行方式可能截然不同。例如:在处理交易类型 0x00 to 0x04 的实际迭代中,这些概述的阶段之间没有明确的区分。另外,虽然后两个阶段对于每笔交易都会发生,并且对于任一类型的账户通常遵循相同的逻辑,但验证阶段是为 EOA 量身定制的。这是直观的,因为如前所述,交易总是起源于 EOA;因此它们应该始终能够通过验证阶段的检查,而 CAC 则隐含地不能。交易类型 0x00-0x03 的验证阶段在特定客户端节点实例上进行如下:
交易被反序列化,客户端检查其消耗的总气体不超过某个预定义的最大值。
它检查交易的指定 chainID 与底层链的 chainID 相同。
验证提供的签名,如第一部分中所述。
如果交易通过了到目前为止的三个检查,它就会被广播给其他客户端并添加到区块生产者的交易池中,该交易池可以以多种方式配置,以实现特定的产品功能,例如私有化和防止三明治攻击等。无论具体实现如何,任何接受将由 EVM 执行的交易的交易池都必须进行以下检查:
验证交易的 gas 限制小于或等于区块的 gas 限制;区块 gas 限制是一个区块参数,指定了给定区块中所有打包交易可消耗的总气体上限。
检查交易的大小低于其类型预期的某个最大值。
检查账户的 nonce 是否与交易中指定的 nonce 匹配;并且它的 balance 是否足够支付 value 和 gas 费用。
在客户端完成初步区块准备后,它必须通过 asMessage 函数将交易转换为一个不同的状态对象,称为消息;其中消息是一个完全派生的交易(即,执行所需的每个变量都在消息中指定(发送者的地址已被恢复并定义为 from)。如果交易存活到这一步,那么客户端会恢复网络的更新状态,以便应用交易;但在此之前,必须始终执行以下检查:
消息调用者 nonce 值检查(确保它低于最大可能的 nonce)
交易费用检查
Gas 限制值检查
在这三个检查之后,客户端购买将在执行阶段消耗的 gas,然后过程继续进行如下:
检查购买的 gas 是否至少足以覆盖内在气体。
计算内在气体并检查计算过程中没有溢出。获得的气体值从用户的 balance 中扣除。
确保调用者有足够的剩余 balance 来覆盖消息顶层调用中的任何 ETH 转账。
确保调用者对应的账户不包含代码,符合 EIP-3607。
如果这 13 个检查都通过了,再加上客户端实现的任何其他定制检查,那么执行(将交易应用于状态)就可以开始了。讨论交易应用于状态超出了我们这里的范围;但如果你愿意,可以阅读更多相关内容。
评估我们到目前为止讨论的所有内容,我们可以推断出账户标准实际上存在于自治性和可编程性之间的频谱上:EOA 拥有完全的自治性和基本上没有可编程性——它们可以自我认证并随时发送交易,但这些交易必须遵循严格的格式才能被视为有效——而 SCW 拥有完全的可编程性但有限的自治性,因此它们的交易不必遵循任何严格的格式,但只有在 EOA 调用它们暴露的某个函数时才能被处理。
对于 EOA 来说,这种缺乏可编程性体现为一系列限制,我们可以总结为更高的运营摩擦。EOA 每签名只能发送一笔交易,并且他们发送的任何交易只能包含一个调用;这限制了用户通过原子性和批量交易等功能更简洁地表达他们交易的能力。此外,EOA 需要始终用自己的 ETH 支付交易费用;因此他们必须始终持有一些 ETH 才能使用公共交易池,并且他们必须为他们发送的每笔交易付费。对于可能不想(或不能)因库存风险而一直持有 ETH 的用户来说,这有点问题;对于像 Tornado Cash 这样的应用级隐私解决方案来说,这是一个更大的问题,因为这一要求意味着用户无法在不事先在该地址拥有一些 ETH 的情况下将其混合资金提取到新创建的地址。
而对于 SCW 来说,缺乏自治性意味着必须在根层级有一个 EOA 来生成一个客户端可用于用户认证的 ECDSA 签名;因此,SCW 本质上是被神化的二等账户,因为每一笔以太坊交易类型(在 EIP-7702 之前,它把事情稍微搅浑了一点)的第一次调用必须始终来自一个 EOA。
这给我们带来了以太坊最令人恼火的困惑方面之一:用户身份验证和授权之间的阈限空间,以及 ecrecover 在其中扮演的角色。
ECRECOVER虽然我们在上一节中概述的 ECDSA 签名验证过程在合理范围内是一个计算简单的算法,但在链上(即作为 EVM 的一部分)实现它并不可行,因为去中心化区块链存在经济限制——特别是,要求客户端节点为每笔交易执行公钥恢复将给每笔交易带来静态的禁止性成本,因为它增加了交易处理时间。这就是 ecrecover 存在的原因。
地址 (0x01) 处的 ecrecover 预编译用于在 k-256 曲线上进行 ECDSA 公钥恢复。像所有预编译合约一样,它在客户端特定代码中实现,但仍可从 EVM 内部调用;当被调用时,它接收哈希和签名元素,并返回相应公钥的地址,该公钥的私钥生成了签名元素。经过身份验证的地址被称为 tx.origin,并且始终是一个 EOA,因为只有 EOA 才能生成协议接受的有效 ECDSA 签名。
EVM 使交易具有高度可编程性,因此它们可以由许多嵌套在彼此内部的可执行“调用”组成。例如,Uniswap V4 上的代币交换可能包含多达 4 次调用,涉及除 tx.origin 之外的 3 个不同的合约账户;其中每个调用对应于一个由称为 msg.sender 的账户触发的不同执行步骤。这很重要,因为 EVM 最终不关心调用是“为什么”被触发的;它只关心发出调用的账户是否被授权这样做,这可以通过协议级别的 tx.origin 和通过 msg.sender 为所有其他应用程序建立。ecrecover 通过 tx.origin 有效地将 EOA 的身份验证和授权折叠成一个单一逻辑;但由于 SCW 无法生成 ECDSA 签名,它们无法在协议级别进行身份验证。
总结:协议级别认证必须在账户的交易到达 EVM 之前发生,并且基于对 ECDSA 密钥对的拥有,通过 ecrecover 进行检查;但 SCW 不拥有任何密钥,因此只能成为一个 msg.sender。msg.sender 授权可以通过诸如 PERMIT、PERMIT2 和 ERC-1271 等显式模式在 EVM 内部进行验证,这些模式通常允许你定义账户可以实施哪些更改,因此 EVM 简单地验证账户希望执行的操作在其权限范围内。
正是这些不同的限制使得账户抽象成为必要,其核心是概括以太坊账户的每个方面,使它们易于扩展,因此可以以多种方式实现,以提供各种用例,同时满足某些属性。在下一节中,我们将定义我们的期望,然后比较一些现有和提议的实现与它们的属性。
已经有很多关于账户抽象(AA)的定义在流传,所以,最好我们只关注大多数这些定义的整体含义,即:以太坊账户的特定方面最好被设计为模块化表面,最终允许用户以无需信任的方式实现他们自己偏好的功能和特性。这个条件可以被提炼为抽象以太坊账户最终状态的若干无序要求:
其他协议级认证方案的可用性:在一个大多已从基于 ECC 方案过渡的世界中,将协议限制为只接受 ECDSA 签名带来了安全和用户体验方面的挑战。抽象认证方案最优的方式是设计协议,使得新的认证方案可以根据其受欢迎程度(secp256r1)或安全保证(ed25519)按需实现并提供给用户,从而启用其他经过实战检验的认证机制,并确保以太坊网络在后量子世界中的生存路径。
无信任的交易费用赞助:他们的 gas 支付可以无信任地得到赞助,这样用户就不需要通过规范交易池持有 ETH 才能在网络上进行交易;这也为应用级隐私协议带来了一些改进。
自定义处理逻辑:他们应该能够通过规范的公共交易池发送具有任何形式验证逻辑和任意数量可执行调用的交易,从而实现大量用户体验改进。
状态重新建模:账户树应该被替换为更高效的数据结构,这将实现以太坊向无状态模型的推进,并增强协议的抗量子性。
因此,只有当所有这些要求都已满足,并且在任何层面都没有严格的中间人要求时,我们才能说以太坊已经完全实现了账户抽象。但这也不意味着所有这些要求都必须由一个单一的设计/实现来满足,因为这将是一项极其复杂的工作。因此,我们在这里的框架是,任何允许账户重新定义其配置的任何部分的机制都实现了账户抽象;因此,最终可能存在多种机制可以组合起来,以满足上面概述的要求并实现完整的账户抽象。这在某种程度上是 AA 的当前现实,这也进一步导致了它的争议性。
在 EIP 网站上回顾一些符合我们框架的提议实现,很容易看到大多数设计试图通过不同的方式提供相同的功能;因此,我们可以根据它们提供的主要功能将其子分类为:
这些机制的例子包括已实现的 EIP-7702 和被拒绝的 EIP:3074、5806 和 6913。
这类机制的例子包括:EIP 5003、7377 和 7851。
由于设计适当的智能账户机制存在普遍复杂性,ERC-4337 被提出作为一种协议外的智能账户实现,它允许 SCW 将准交易发送到由称为捆绑者的实体管理的特殊交易池。
此后,为了解决 ERC-4337 的局限性并提供一种合适的方法来奉行 ERC-4337 所启用的功能的超集,其他设计也被提出。其中一些例子包括:EIP - 7701、- 8130 和 - 8141;以及针对 L2 的:RIP-7560 及其扩展 RIPs - 7711 和 - 7712。
请注意,还有其他针对应用层(钱包和 RPC)改进的提案,我们这里没有提及,以便将范围限制在我们现在讨论的协议级实现上。

ERC-4337 降低了使用 SCW 的复杂性,它消除了用户同时管理一个 EOA 以通过协议级认证检查并结算交易费用的需求。它有效地将 SCW 的 EOA 要求向上移动到堆栈中,到达捆绑者指定的受益地址;因此在此模型中,捆绑者始终是 tx.origin。这些捆绑者负责端到端处理 4337 交易(称为 userOperation)的实体;因此,他们必须:
ERC-4337 还通过称为支付主的实体原生支持无信任 gas 赞助;支付主是辅助合约,旨在根据与用户预先达成的协议来结算 userOp 的处理成本。这是其最受赞誉的功能之一,因为它消除了 SCW 采用的最大摩擦点——需要在 EOA 上持有 ETH 以支付交易费用——并大大改善了以太坊上的隐私状况,因为账户可以被创建并立即开始交易,而无需从他们已有的账户中为自己注资。支付主还启用了其他功能,例如基于订阅的交易费用和 dApp 费用补贴。
此外,4337 标准通过签名聚合器优化了自定义授权方案的认证,签名聚合器是辅助合约,捆绑者可以信任它提供单个签名来验证多个 userOp。它们的核心功能是允许多个 userOperation 签名被合并成一个签名,捆绑者可以轻松检查,从而降低处理成本。
总之,这些功能满足了我们的一些期望,即使这主要是 SCW 已经拥有的特性的结果,例如定义其首选验证和执行逻辑以及显式授权方案的能力。
ERC-4337 堆栈引入了一个替代交易池(其策略在 ERC-7562 中定义),用于称为 userOperation 对象的新交易结构,这些对象由称为捆绑者的协议客户端打包成一个单一的批量交易,称为捆绑包。然后,捆绑者可以通过向预定义的 Entrypoint 合约发送 handleOps() 调用来将批量交易包含在一个区块中,该合约执行必要的执行工作。因此,可以预期,对于捆绑者执行的每个 handleOps() 调用,我们最终都会得到一组被批处理和执行的准交易,然后作为单个交易与其他已建立的交易类型一起包含在一个区块中。
userOperation 对象的字段是:
sender- 构建要在此账户内执行的 userOperation 的账户地址。
nonce- 一个二维值,通常起到与典型 nonce 相同的交易重放预防功能。对于 userOp 来说,携带不同的 nonce 值很重要,这有助于其哈希的唯一性,但这必须以不限制 SCW 执行机制(如 EOA 那样)的方式进行设计;因此,userOp nonce 值是未签名的 256 位整数,可以分解为一个 192 位的密钥(可以任意定义)和一个 64 位的序列(对于每个 userOp,必须像通常的交易 nonce 值一样以单调和顺序的方式增加)。
factory- 可以是账户工厂的地址,这是一个辅助合约,通过 CREATE2 操作码按需初始化 sender 合约;也可以是 0x7702 标志,用于寻求发送 userOp 的 EIP-7702 账户。如果 userOp 中没有发送者部署,则该字段设置为 address(0)。
factoryData- 与 factory 字段相对应,可以是传递给账户工厂的数据,也可以是 7702 初始化数据。如果没有发生部署,则保留为空数组。
callData- 在 userOp 的 EVM 执行期间必须传递给发送者的数据。
callGasLimit- 指定分配给 callData 执行的气体预算。
verificationGasLimit- 指定分配给 userOp 验证阶段的气体量。
preVerificationGas- 指定必须支付给捆绑者的额外气体,用于他们在链上验证和执行 userOp 之前执行的处理工作。
maxFeePerGas- 同前。
maxPriorityFeePerGas- 同前。
paymaster- 赞助给定 userOp 交易费用的支付主合约的地址。
paymasterVerificationGasLimit- 指定分配给验证支付主逻辑的气体量。
paymasterPostOpGasLimit- 指定分配给执行支付主操作后逻辑的气体量。
paymasterData- 指定当入口点合约调用 validatePaymasterUserOp 函数时传递给支付主合约的数据。
signature- 传递给 sender 进行授权的数据。
当传输到链上时,userOperation 结构体会被重新打包成 packedUserOperation 结构体,这本质上将相关字段的值合并到一个通用的字段中,以降低调用数据成本。例如:factory 和 factoryData 值被连接成一个单一的 initcode 字符串;verificationGasLimit 和 callGasLimit 被连接成一个 accountGasLimits 字符串;maxFeePerGas 和 maxPriorityFeePerGas 被合并到 gasFees 字段中,而 paymaster* 字段被合并到 paymasterAndData 中。
UserOperation 验证:捆绑者、替代交易池和入口点与正常交易类似,userOp 的生命周期始终始于一个 sender;该 sender 向捆绑者的 RPC 发送 eth_sendUserOperation 调用,并附带一个 userOperation 结构体。当捆绑者收到 userOp 时,他们需要对入口点执行 handleOps() 视图调用,并在将 userOp 添加到其交易池之抢跑以下检查:
sender 必须是一个现存的合约,或者 packeduserOp 必须指定一个 initCode 值。如果存在 initCode 值,则捆绑者必须将其前 20 个字节解析为工厂地址或 EIP-7702 标志;在前一种情况下,捆绑者还必须检查指定的账户工厂是否在系统中有质押,以防其可以在逻辑处理期间访问链上状态。如果 sender 不存在且 initCode 字段为空,则整个调用必须立即失败。
verificationGasLimit 和 paymasterVerificationGasLimit 必须低于指定的 MAX_VERIFICATION_GAS 上限 500K gas;并且 preVerificationGas 必须至少为 userOperation 序列化成本加上一个 PRE_VERIFICATION_OVERHEAD_GAS 常数 50K gas。
paymasterAndData 字段必须为空,或者以支付主合约的地址开头。如果发送者使用支付主,捆绑者必须检查指定的合约:
userOp 的处理费用callGasLimit 值至少是执行一个非空 CALL 的成本。
检查 userOp 提供的 maxFeePerGas 和 maxPriorityFeePerGas 值是否在他们愿意接受的范围内,最低限度应相当于最新区块的当前基础费用。
最后,除非捆绑者支持多样性,否则他们必须检查每个 sender 在他们的交易池中没有其他 userOp;如果有,那么他们的新 userOp 必须是早期条目的替换,因此两个 userOp 的 sender 和 nonce 必须相同,但 maxFeePerGas 和 maxPriorityFeePerGas 值更高。如果一个 sender 在入口点合约拥有质押,则可以免除这个约束。
这些检查是为了确保 userOperation 结构体中每个值的单独有效性,因此定义了 userOperation 在捆绑者将其添加到其交易池之前必须通过的第一个验证阶段。当捆绑者开始构建捆绑包时,他们必须模拟每个 userOp 的验证。这包括:a. 通过提供的 initCode 模拟 sender 部署;b. 通过 account.validateUserOp 追踪模拟发送者的验证逻辑;c. 通过 validatePaymasterUserOp 追踪模拟支付主验证。
此外,捆绑者在捆绑包创建期间应强制执行以下约束:
排除访问同一捆绑包中另一个 userOp 的 sender 的 userOp。
按 userOp 使用的支付主合约(如果有)排序,并跟踪每个支付主的余额;以便轻松检查一个支付主能否轻松支付捆绑包中使用它的所有 userOp。
按 userOp 使用的聚合器(如果有)排序,然后按顺序为每个聚合器运行聚合器特定代码,以便为每个 userOp 创建正确的聚合签名。
这些措施是为了防止相互依赖的 userOp 及其相关合约之间的冲突。如果这些模拟中的任何一个产生错误,那么违规的 userOp 必须从捆绑者的交易池中移除。正是在这个模拟/第二个验证阶段,ERC-7562 的规则被应用于 userOperation。然后,捆绑包中填充了到目前为止满足所有约束的 userOp,并在提交给入口点合约之前按照以下方式重新验证:
运行 debug_traceCall 以强制执行 7562 关于整个批处理的操作码和存储访问的验证规则。
如果前面的调用在完成前回退,捆绑者将使用返回的结果找到违规实体,并从捆绑包及其交易池中移除该实体的关联 userOp。
如果违规实体是一个 factory 或 paymaster 合约,并且 sender 在入口点合约没有质押,则捆绑者将酌情对该实体发出禁令。如果 sender 拥有质押,则捆绑者将转而禁止他们。
这些后续检查背后的理由是,有质押的实体被授予更多的瞬时存储访问权限,因此它们可以按设计与同一捆绑包中的多个 userOp 交互。这使得捆绑者容易受到 DDoS 攻击,如果存在信誉系统,则可以更好地管理这些攻击。你可以在此处查看 7562 的规范,或在此处阅读技术摘要/概述。
捆绑者通过执行 handleOps() 调用并传入一个 userOperation 数组来向入口点合约提交捆绑包。正是这个入口点随后会在某个定义的发送者合约内执行给定 userOp 逻辑的必要调用;因此,在 userOperation 内的调用中,它始终是 msg.sender。它(某种程度上)还充当了专用于 4337 的交易池的守门人,负责:
运行对 userOperation 验证逻辑的检查;
强制向捆绑者预先支付 gas;以及
在其账户内执行 sender 指定的 calldata。
它暴露了以下接口,捆绑者必须调用该接口以开始 userOp 的实际处理:
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary);
当被调用时,入口点必须执行以下验证逻辑:
如果 sender 在运行时不存在,则使用 initCode 中的细节创建并部署它。如果 factory == 0x7702,则 sender 必须是一个带有委托指示器的 EOA;如果 sender 不存在且 initCode 为空或未将代码部署到 sender,则调用必须失败;如果 sender 已完全初始化且指定了 initCode,则应忽略 initCode。
根据验证和调用 gas 限制以及运行时的 gas 值,计算最大可能的处理费用。
如有必要,计算发送者必须添加到其在入口点合约质押中的费用。
通过 validateUserOp 函数调用发送者,并传递 userOperation、其哈希值以及先前计算得出的费用。当以这种方式调用时,sender 必须执行以下检查:
userOpHash] 的有效组成部分。withdrawTo 调用来提取。authorizer,即负责为 userOp 提供有效签名的地址;b. validUntil,一个 6 字节的时间戳,用于指定 userOp 必须在此时间之前执行,否则视为无效;c. validAfter,一个时间戳值,用于指定 userOp 有效期开始的时刻,因此从 validAfter 开始,userOp 进入一个执行时间窗口,结束于 validUntil。通过入口点验证循环的 userOperation 随后被执行,这我们就不特别关心了。
所以,ERC-4337 整体上是一项伟大的工作,但更重要的问题是它在多大程度上满足了我们的期望以及它的可扩展性如何,而总结性的答案是:它是一个复杂的基础设施,拥有一个极其严格的交易池策略,限制了其复杂性所提供的可编程性的应用⁷。可选的交易池策略依赖于一个新的实体(捆绑者),因此在 userOp 执行管道中引入了新的信任假设⁸。此外,捆绑者的进入和参与成本从未被明确确立,这可能在区块生产者对该标准的有限支持⁹中起了一定作用,并进一步恶化了 userOps 的抗审查性。
总之,根据我们的期望评估该标准:
其他协议级认证方案的可用性:超出其范围,因此主要认证在协议级别仍然是基于 ECDSA 的。这就是为什么捆绑者仍然是一个 EOA,因为它直观地作为每个 userOp 批次的 tx.origin。
无信任的交易费用赞助:通过链下支付主满足,这可能是信任的。
自定义处理逻辑:是的,userOp 可以定义自己的验证逻辑,并且它们可以包含任何形式的可执行消息调用;但它们不是通过规范交易池处理的¹⁰,因此并未完全满足这个要求。
状态重新建模:与范围不兼容,因为 userOp 主要是作为一种元协议包来实现的。
EIP-7702 引入了一种新的符合 2718 的交易类别——表示为 SET_CODE_TX_TYPE——它总结性地使 EOA 能够将其交易执行阶段的逻辑委托给一些预先存在的代码。
7702 交易被定义为:keccak256(SET_CODE_TX_TYPE || transactionPayload),其中 transactionPayload 是交易字段对应值的 RLP 序列化。其字段及其逻辑与我们之前讨论的相同;增加了 [authorisationList],它为来自已委托其执行逻辑的账户的交易附加了一个授权元组列表。因此:transactionPayload = rlp ([chainID, nonce, destination, value, data, maxPriorityFeePerGas, maxFeePerGas, gasLimit, accessList, authorisationList, yParity, r, s]);而 authorisationList = [[chainID, address, nonce, yParity, r, s]...]
对于 authorisationList 中的每个元组,一个委托指示器,表示为 (0xef0100 || address),被写入授权交易的账户的代码中。其中 address 对应包含字节码的账户的相关地址,每当源自授权 EOA 的代码执行操作时,该字节码必须被加载并在该 EOA 的上下文中执行。
由于主要是执行逻辑受到影响,SET_CODE_TX_TYPE 交易的验证过程与 EOA 交易相同;除非是账户首次执行该交易,则验证阶段进行如下:
确保 authorisationList 的长度 > 0,否则交易无效。然后,对于每个元组断言:auth.chainID < 2^256;auth.nonce < 2^64;len(auth.address) = 20;auth.yParity < 28,且 auth.r 和 auth.s < 2^256。如果这些检查通过,则发送者的 nonce 增加,并开始处理 authorisationList。
验证交易的 chainID 是 0 或底层链的 chainID。
检查 s < secp256k1n/2,如 EIP-2 所定义;然后使用消息和签名执行 ecrecover 以获得交易的发起者 tx.origin,表示为 authority。
验证交易的 nonce 与 authority 的 nonce 匹配。
如果交易来自一个新的 authority,则必须根据 EIP-2929 将其添加到客户端的 accessedAddresses 缓存中,以便在整个执行过程中更容易从状态中读取。
检查 authority 是否已经包含委托指示器;如果 authority 不为空,则将静态的 PER_EMPTY_ACCT_COST - PER_AUTH_BASE_COST 值添加到全局退款计数器。
将 authority 的代码设置为 (0xef0100 || address);如果 address 是零地址 (address(0)),则必须清除 authority 的代码内容,它们将恢复为普通的 EOA。
将 authority 的 nonce 增加 1。
如果上述任何步骤失败,则必须跳过当前元组,并处理 authorisationList 中的下一个元组。对于来自同一 authority 的多个元组,最后有效的地址被委托;对于嵌套委托,只应处理第一个委托。此外,只要 EOA 的第一个 7702 交易通过上述验证检查,就必须使用其指定的委托初始化它。
类似于 ERC-4337,建议客户端不应在同一个区块中包含来自同一个带有委托指示器的 EOA 的多个待处理交易。
执行 SET_CODE_TX_TYPE 交易为 EOA 提供了以前 SCW 独有的功能,包括:交易批处理、gas 赞助和细粒度访问策略。这些功能最终只是我们期望的任何完整 AA 机制所需属性的一个子集;因此 7702 更像是对 EOA 功能集的扩展,使它们能够有点像 SCW 一样运作,并且几乎没有任何抽象在进行——将其与我们的期望进行比较是不公平的,并且可能加剧对其实际功能的混淆,所以我不打算这样做。
关于 7702 缺乏采用的其他问题,请参考这里的精彩文章。

请记住,我们对智能账户机制的基本要求是,它最终允许 SCW 的交易到达规范交易池,而不需要通过中间人;平心而论,这是一个简化框架,忽略了这种构造对网络的影响。任何有希望满足我们 AA 期望的、被奉行的账户设计的实际复杂性来自于这样一个要求:处理其交易的客户端在能够执行交易之前,应该模拟一些任意的 EVM 字节码。这意味着客户端需要在实际开始应用交易之前访问链上状态,这可能会使协议容易受到 DDoS 攻击,因为区块生产者可能无法断言给定交易会支付其执行的任何处理工作。这里的角度是,链上发生的每个动作都必须以 gas 计量,因此即使来自智能账户的验证交易也会产生一些成本,如果交易最终无效,这些成本将由构建者承担;如果这种情况发生得足够频繁,以至于构建有利可图的区块成本过高,那么Slot就会丢失,网络的完整性受到威胁¹¹。这个漏洞是由于以太坊的状态模型造成的,在该模型中,交易是相互依赖的,但默认情况下,不会事先指定它们希望影响链上状态的哪些部分¹²。
这对于以太坊的普通交易池策略/验证机制(即交易类型 0x00 到 0x04)来说不是问题,因为它将交易的验证阶段限制在 EOA 的本地环境中;这样无效交易只会影响 EOA,而不会影响网络或区块构建者。然而,来自智能账户的交易的非确定性是有问题的,因为它们定义为依赖于可变状态;这就是为什么 ERC-4337 通过 ERC-7562 引入了一组任何处理 userOp 的交易池都必须遵守的约束。
为了在协议内解决这个漏洞,重要的是协议客户端有一种方法可以将账户的交易明确分离为其组成阶段(还记得吗?验证、执行和检索,有时还有更多);这样它们就可以限制任何交易验证阶段的逻辑,并进而限制在它们能够断言交易为其处理付费之前必须执行的计算工作。因此,将交易明确分为两个阶段——一个阶段是区块构建者不确定交易是否有效,且账户尚未承诺支付费用,另一个阶段是区块构建者已验证交易且某个账户已同意支付交易包含的最低费用——允许网络对账户的行为施加某些限制,直到它们对所产生的气体负责。
我们现在将探讨似乎是对账户困境多年研究的解决方案的设计细节。
当前的 EIP-8141 草案建议添加一种新的交易类型,表示为 FRAME_TX_TYPE,以及五个新的操作码:APPROVE (0xaa)、TXPARAM (0xb0)、FRAMEPARAM (0xb1)、FRAMEDATALOAD (0xb2) 和 FRAMEDATACOPY (0xb3)。
一个 FRAME_TX_TYPE 交易的字段是:chainID、nonce、sender、frames、maxPriorityFeePerGas、maxFeePerGas、maxFeePerBlobGas 和 blobVersionedHashes。其中 frames = [[mode, flags, target, gasLimit, value, data], …]。
由于 target、gasLimit、value 和 data 字段是按每个 frame 嵌套的,并且单个交易可能包含多个帧;我们可以立即推断出这种交易类型使账户能够执行原子、批处理以及可能并行的交易,而无需任何额外的修改——最终减少了更高级别实现的复杂性和设计分歧。

一个 FRAME_TX_TYPE 交易的帧是整个交易的一个子单元,由对单个目标的一系列调用组成,具有不同的执行上下文和其自身的隔离气体预算。帧中包含的两个新参数毫无疑问地承载了新交易类型引入的新功能的大部分功能。
帧的 mode 指定了帧内可执行逻辑的上下文,因此它使帧能够明确指示其包含的逻辑是用于验证、执行还是其他处理操作。这最终使客户端能够在执行包含它的帧之前识别某些任意字节码的作用,从而满足智能账户的验证-执行分离先决条件。帧可以指定三种模式:
DEFAULT 模式,指定帧包含账户部署/初始化逻辑;
VERIFY 模式,指定帧包含用于整个交易的验证逻辑;以及
SENDER 模式,指定帧内的调用必须应用于当前状态。
从以上我们可以推断,执行指定 DEFAULT/VERIFY 模式的帧是 FRAME_TX_TYPE 交易中相当于验证阶段的部分。这在交易内部通过要求从 DEFAULT/VERIFY 模式帧调用的合约应确保它们的 msg.sender(此处称为 CALLER)是 ENTRY_POINT 来进一步强制执行;ENTRY_POINT 的唯一指定属性是它服务于与地址相同的目的(即它是一个身份标记!),其唯一功能是防止账户触发自己的验证逻辑。此外,DEFAULT/VERIFY 模式的帧不允许携带非零的 value,因为对账户余额的访问应在授权发生后进行,而不是在授权之前或授权正在验证时。
相应地,执行指定 SENDER 模式的帧对应于 FRAME_TX_TYPE 交易的执行阶段;以这种模式从帧调用的合约应检查 CALLER == tx.sender。
flags 字段总结性地允许在给定的 mode 下配置额外的帧执行约束。它们分布在一个从零开始的字节上;其中位 0 - 1 表示批准范围,可以在任何模式中指定;位 2 表示“原子批处理”,这是一个仅在帧处于 SENDER 模式时才有效的标志。
通过以上,我们可以直观地理解,每个 FRAME_TX_TYPE 交易本质上是一个 EVM 可执行帧的序列,每个帧拥有不同的模式、标志、目标、气体预算和调用。
APPROVEAPPROVE (0xaa) 操作码是一个新的操作码,只能由帧的 resolved_target(即 frame.target)调用,以向某个验证者证明他们的授权;该操作码退出当前执行上下文,并根据其 scope 操作数的值执行帧指定的“批准范围”操作。操作码的 scope 是一个位掩码,覆盖以下常量:APPROVE_SCOPE_NONE = 0x0;APPROVE_PAYMENT = 0x1;APPROVE_EXECUTION = 0x2;APPROVE_PAYMENT_AND_EXECUTION = APPROVE_PAYMENT | APPROVE_EXECUTION = 0x3;APPROVE_SCOPE_MASK = APPROVE_PAYMENT_AND_EXECUTION。
对于一个 APPROVE 调用要有效,其 scope 必须是 APPROVE_SCOPE_MASK 的子集,因此它必须是:
0x1 - 导致 payer_approved 布尔值被设置为 true,前提是 sender_approved 已经设置为 true。它还会增加 tx.sender 的 nonce 并从其 resolved_target 中收取定义的最大成本;
0x2 - 在 resolved_target == tx.sender 的条件下设置 sender_approved = true。它不能增加 tx.sender 的 nonce 或携带任何 value;或者
0x3 - 将 0x2 和 0x1 的操作合并为一个原子操作。
一个帧的 allowed_scope 是其指定的批准范围,因此它实际上是对 APPROVE 调用操作的 CALLER 定义的限制。这定义了 8141 交易的访问策略;因此 sender_approved 必须在每个帧交易开始时设置为 false,并且只能通过调用 APPROVE (0x2) 或 APPROVE (0x3) 设置为 true,这仅在帧由 tx.sender 调用时才成功。
其他操作码不太重要:TXPARAM 允许交易内省;FRAMEPARAM 允许帧内省,这对于支付主合约最有用,因为它允许它们在承诺费用赞助之前检查帧的内容;FRAMEDATALOAD 相当于帧的 CALLDATALOAD(接受 offset 和 frameIndex,并从指定 offset 开始的 frameIndex 处的帧返回一段数据);FRAMEDATACOPY 将指定帧(以 frameIndex 给出)的一段 length 数据(从给定的 dataOffset 开始)复制到内存中(从 memOffset 指示的位置开始)。
FRAME_TX_TYPE 验证EIP-8141 将修改以太坊的公共交易池策略,采用类似 ERC-7562 的约束,但没有质押或信誉的概念。这是通过“验证前缀”来实现的,验证前缀是在给定 FRAME_TX_TYPE 交易被添加到区块生产者的交易池之前必须执行的一组帧。根据我们在本节开头的讨论,处理来自智能账户交易的实体的目标应该是确定某个指定实体支付交易的处理工作;我们可以将帧交易的验证前缀描述为成功设置 payer_approved = true 的最短交易部分。
有四种类型的验证前缀被公共交易池识别和接受,它们基于交易希望实现的最终行动以及预期的支付者是谁。
自我支付的基本交易有一个 self_verify 验证前缀;
自我支付的账户部署交易有一个 deploy | self_verify 前缀;
赞助的基本交易有一个 only_verify | pay prefix;以及
赞助的账户部署交易有一个 deploy | only_verify | pay prefix。
当客户端收到一个 8141 交易时,他们必须首先模拟其验证前缀的执行,以确保:
验证前缀中包含的帧在执行时不会回退。
每个 VERIFY 帧都通过了其必需的 APPROVE 调用。
验证前缀的执行成本不超过 MAX_VERIFY_GAS。
验证前缀不包含被禁止的操作码。
前缀的执行不会执行任何非确定性账户部署的状态写入,并且此部署是通过已知部署者合约在交易的第一个帧中发生的。
如果存在部署帧,其执行总是以在 tx.sender 处设置代码结束。
前缀的执行除了 tx.sender 的存储外,不读取任何存储。
如果这些检查通过,它会执行一个有状态检查,以确保 tx.nonce == state[tx.sender].nonce,将 payer_approved 和 sender_approved 设置为 false,并对每个帧执行以下操作:
目标解析 - 任何帧的目标都明确指定为 frame.target,并且每当它为空时,必须将其设置为 tx.sender;即,令 resolved_target = frame.target 如果 frame.target 不为空,否则为 tx.sender。在目标解析之后,客户端将使用帧内指定的 mode、flags、gasLimit、value 和 data 在 resolved_target 处执行调用。
SENDER 模式;其 CALLER 设置为 tx.sender,前提是在某个先前的帧中 sender_approved 已被设置为 true。CALLER 必须是 ENTRY_POINT。resolved_target 处没有代码或委托指示器,则必须在其内部执行默认代码逻辑。检查帧是否不在 VERIFY 模式;如果是,则它必须已经成功调用了 APPROVE。如果它处于 VERIFY 模式且未通过 APPROVE 调用,则交易无效。
正如以上验证过程中所述,如果帧的 resolved_target 是一个没有代码或委托的账户(即一个普通的 EOA),则必须在其上下文中执行默认代码;因此,此默认代码实现了使 EOA 能够开箱即用地执行 8141 交易的逻辑。其逻辑取决于其被调用时帧的模式。
在 DEFAULT 模式下,帧必须回退;因为账户已经存在,所以没有要执行的部署逻辑。
在 VERIFY 模式下:
allowed_scope,其值必须是 APPROVE_SCOPE_MASK 的子集;如果 allowed_scope = 0x0,或者 resolved_target ≠ tx.sender,则发生回退。frame.data 的第一个字节必须作为 signature_type 读取;其中 signature_type 0x0 是在 k-256 曲线上的 ECDSA 签名,其认证必须通过 ecrecover 执行;signature_type 0x1 是在 p-256 曲线上的 ECDSA 签名,其认证通过 P256VERIFY 预编译执行。APPROVE(allowed_scope)。在 SENDER 模式下:
resolved_target ≠ tx.sender,则可以推断该调用是对不同账户的 frame.value 转账。resolved_target == tx.sender,则 frame.data 必须被视为调用列表的 RLP 编码,这些调用必须以 CALLER = tx.sender 执行。
那么,所有这些与我们表达的期望相比如何?相当不错!
其他协议级认证方案的可用性 通过 P256VERIFY 预编译提供,并且交易池被设计为可扩展到其他实现,特别是抗量子安全的实现。
无信任的交易费用赞助 得到支持,因为 8141 与现有的支付主生态系统向后兼容;此外,该功能可以扩展以实现其他功能,如多币种 gas(即用代币支付设计)和链上支付主。
自定义处理逻辑 在 FRAME_TX_TYPE 的每个阶段都得到支持,并且它们通过协议规范交易池进行验证,受限于一个源于但比 7562 限制更少的策略。
状态重新建模 未完全支持,因为当前的 8141 规范并未完全与当前的无状态目标向前兼容;但这仍然是一个活跃的研究领域。
当前帧交易的架构是为以太坊智能账户搭建的一个很好的框架,它满足大多数期望的功能,但未必是完整的;其规范正在顺利进行中¹³,但也引起了一些批评。在本节中,我们将尝试解决一些高层次的批评。
对 EIP-8141 最常见的批评之一是它过于可扩展,在某种程度上,我认为这只是以下原因的结果:a. 对 4337 和 7702 都未能完全达到其(被误解且经常传错)期望的 AA 疲劳;b. 普遍认为以太坊的目标通常不实用,对此真的没什么好说的¹⁴。范围广泛本身并没有错;账户抽象正在解决以太坊上的许多问题,因此与许多其他协议方面重叠,例如密码学安全、用户身份验证和授权、交易池和状态设计以及隐私等——如果我们能通过一种机制实现大部分必要的更改,那将是一件好事,因为那意味着随着时间的推移争论会减少¹⁵。
另一个常见的批评是,该提案实际上并未解决后量子问题,因为它没有奉行任何后量子方案;但我认为它确实通过将任何账户的验证转移到 EVM 中,并有效地将 ecrecover 从唯一的协议级认证原语降级为与 p256verify 认证方案并列的选项,为实现该目标提供了一条单跳路径。虽然它不抗量子,但 8141 的 p256verify 预编译将允许验证在广泛使用的 p-256 曲线上生成的签名,因此应用程序可以利用设备上的安全飞地并获得更高的安全保证。
奉行后量子签名将给客户端节点带来自己的影响——直接的影响是它们将不得不处理至少计算密集 100 倍的交易和更大的区块大小——如果不加小心,这可能会使网络倾向于中心化。当后量子以太坊的经济学问题被解决后(相关工作进展顺利!),有多种实现就绪的 EOA 迁移机制和抗量子签名方案可以被奉行以达到此目的。
我认为每个部分本身都很有结论性,所以这里不需要重复,我已经重复得够多了。我最初构想的这篇文章方向是 4337、8130 和 8141 之间的比较;但一些对话让我意识到 7702 仍然被严重误解,所以我涵盖了它来代替 8130。也许我以后会整理我的 8130 笔记吧,不过我并不认为它们非常必要,但做一个跨智能账户设计的适当比较可能会很有趣。第二类乐趣,因为它是无偿工作,但仍是乐趣。
我未来想探索的一个方向是账户/交易设计如何与交易池和状态设计相交织;所以请继续关注和/或联系我们,如果你愿意的话!感谢阅读!
1 你应该只在自己构建密码学并投入生产时,才想成为为什么其他人不应该这样做的例子,因为构造不当的 ECDLP 可以很容易地被经典算法和计算机解决。
3 鉴于我们的点是在有限域中定义的,这数值上对应于 $y$ 坐标的一个值是偶数,另一个是奇数。
4 如所解释的,其中两个点是由于椭圆曲线在给定 $P_x$ 处的 $x$ 对称性质;另外两个点是由于点 $P_x$ 的模数与签名元素 $r$ 的模数之间的差异,这可能允许 $r + n$ 成为一个有效的 $P_x$,进而产生其自身的两个不同点。
5 所有其他交易类型直接携带 $yParity$,正如我们在下一小节中看到的。
6 对于类型 0x01 到 0x04 的交易。
7 这些因素(可以理解)导致了现有 EOA 用户对其的有限采用。
8 这在实践上转变为在区块构建期间的新形式的 MEV 动态。
9 我想知道这是由于 4337 生态系统中存在的垂直整合程度,还是我搞反了。
10 ERC-7562 策略是可选的(废话,它是 ERC,不是 EIP),并且只有在区块生产者希望成为捆绑者或提供某些 4337 相关功能时才会被采用。
11 谁准备好了解最终确认、证明、同步委员会和证明者了。
12 这就是为什么希望同时更新相同状态的交易都支付某个区块的基础费用,并且如果它们没有为该状态提供最高的优先费用,仍然可能失败。
13 并且一如既往地是一个完全开放的过程,如果你愿意,可以参与其中。
14 #25millionblocksandnodowntime
15 不是说我抱怨,有时还挺有趣的。
- 原文链接: zhev.substack.com/p/an-i...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码