以太坊 - EIP-7702 介绍

  • EthTaipei
  • 发布于 2025-03-23 10:13
  • 阅读 80

EIP-7702是2025年Pectra硬分叉中提出的一项提案,旨在使外部拥有账户(EOA)能够像智能合约一样执行代码,从而解决EOA的单一故障点、功能有限以及Gas费用支付等问题。通过新类型的交易和权限委托设计,这一提案为EOA引入了多重签名、批量交易和自定义身份验证等功能,大大增强了用户体验。

理解 EIP-7702 是什么以及为什么 EIP-7702 会出现在 Pectra :)

特别感谢 NIC Lin Chang-Wu Chen 的反馈和审阅。

tl;dr

  • EIP-7702 将于 2025 年(今年)包含在 Pectra 硬分叉中。
  • EIP-7702 可以将 EOA 转换为智能合约账户;使用 7702,你的 EOA 可以像合约一样运作。
  • 私钥仍然应由用户自行保管。
  • EIP-7702 引入了一种新的交易类型(0x04)。

前言

本文旨在帮助人们理解该提案背后的基本概念。

本文将包含一些示例代码,主要聚焦于 exp-0001,这是 Ithaca 在 EIP-7702 中做的一个实验项目。

那么首先,EIP-7702 是什么?它想解决什么样的问题?

来自 @0xNairolf

以太坊的目标之一是抽象当前两种账户:

  1. 外部拥有账户 (EOA):EOA 可以发起交易但无法执行代码。
  2. 智能合约账户 (SCA):智能合约能够执行代码,但无法发起交易。

EIP-7702 起到了 EOA 和智能合约之间的桥梁作用,使 EOA 能够像智能合约一样运作。

例如,当 EOA 与智能合约交互时,看起来是这样的:

在 EIP-7702 设置后,交互将变为:

好吧,但 EOA 的问题是什么?

单点故障:EOA 仅由一个私钥控制,缺乏恢复机制或多重签名保护。

功能有限:EOA 只能发送交易而不能执行代码。这是糟糕的,因为它不能:

  • 批量交易
  • 自定义身份验证
  • 内置恢复选项

Gas费用支付:EOA 只能用 ETH 支付Gas费。

总的来说,当前的 EOA 设计用户体验较差。因此,几种账户抽象的想法应运而生。

关于 AA 的历史

账户抽象在过去几年一直在讨论。从 2021 到 2024,出现了相当多的账户抽象解决方案。

(2021) EIP-4337

  • = UserOps(内存池) + EntryContract + Bundler + Payment master。
  • 这是一个智能合约级别的设计

(2020~2024) EIP-3074

  • = Invoker + AUTH + AUTHCALL
  • 停滞不前;被 EIP-7702 替代

(2024) EIP-7702

  • 设置代码交易 + 委任设计者
  • 这是一个协议级别的设计

以太坊中的原生 AA

EIP-7702 的目标之一是当前(在 Pectra 硬分叉之前)账户设计。本 EIP 模糊了 EOA 和智能合约之间的界限,这也是以太坊希望实现的最终目标 — RIP-7560(即支持共识层的原生账户抽象)。

它的功能是什么,如何运作?

  1. EIP-7702 允许 EOA 通过指定委任设计者在交易期间执行智能合约代码。
  2. 此设计者指向“设置代码交易”后的智能合约代码。代码字段将在 EOA 取消授权之前保持不变(在另一个设置代码交易中添加 address(0)authorization_list 中)

我对委任设计者的想象是 Jojo 中的 Sutando

通过委任设计者执行交易的感觉是:

EIP-7702 的更多细节

目前,我们在以太坊中的 EOA 中有 4 个槽位:

  1. nonce
  2. balance
  3. code
  4. storage root

EOA 账户中的 code 是空的;也就是说,EOA 账户中没有代码值存储。EIP-7702 通过发送一种新的 EIP-2718 交易类型“设置代码交易”引入了一种设置代码字段的新方法。

交易代码与来自 EIP-1559 的普通交易不同(tx type = 0x02),它必须在交易负载中发送更多数据。

在此交易中,EOA 必须使用以下四个附加信息对消息进行签名:

  • chain_id:交易将发生的链
  • address:EOA 想要指向的智能合约地址
  • nonce:防重放的安全保护
  • y_parity (== v)rs:用于恢复权限的签名组件(签名人也是 EOA)(y_parity 实际上是 v,它们是相同的)

现在,交易负载看起来像:

rlp([
    chain_id,
    nonce,
    max_priority_fee_per_gas,
    max_fee_per_gas, gas_limit,
    destination,
    value,
    data,
    access_list,
    "authorization_list",
    signature_y_parity,
    signature_r,
    signature_s
])

其中 authorization_list 看起来像:

authorization_list = [
    [chain_id_1, address_1, nonce_1, y_parity_1, r_1, s_1],
    [chain_id_2, address_2, nonce_2, y_parity_2, r_2, s_2],
    ...
]

签名后,生成的交易包含授权列表。这实际上意味着:“作为一个 EOA,你授权/允许某些代码在你的账户中存在。”

然而,目前,EIP-3607 出现并表示:“ 不,我不会允许这样做”,因为如果你的账户中有一些代码,你就不可能发起交易。

在这里,EIP-7702 介绍了另一种使其工作的方式,即“委任设计者”。所做的授权是将代码实际设置到目标地址,如下所示:

0xef + 0100 + target address

通过这样做,EIP-3607 得到了满足,更好的是,EOA 不必存储其合约的完整字节码;我们可以只使用一个已部署的合约将你的 EOA 转换为智能合约账户!

* EIP-3541 禁止合约地址具有 “0xef” 前缀。“0xef” 被设置为以太坊中的 EOF(=Ethereum Object Format)。

* 原始设计是将合约字节码存储在代码字段中;这被改进为仅存储一个指向合约的短地址。

_* authorizationlist 是一个存储签名者希望在其 EOA 上下文中执行的代码地址的元组列表。

_* 使用 chainid = 0,你可以将委任设计者设置为所有 EIP-7702 链。

EIP-7702 的一些示例

没有一些示例,很难理解 我们为什么需要 EIP-7702。使用 EIP-7702,我们可以做到:

  • 批量调用以批准和“其他功能”:委任设计者能够在合约中实现批量调用,并在一次交易中执行多个操作。
  • 批量转账:现在你可以将 100 个 A 代币、52 个 B 代币、2 个 C 代币分别发送给 Alice、Bob 和 Charlie。
  • 委任给 ERC-4337:EIP-7702 向后和向前兼容 EIP-4337。你的 EOA 可以将代码委任给一个已部署的 ERC-4337 账户抽象实现。
  • 使用不同的签名授权交易:我们可以设置像 secp256r1 这样的签名来授权帐户,而不是使用以太坊上的原生 secp256k1 ECDSA 签名。

更多示例如:

来自 jchaskin22

所有这些听起来很好,并为用户提供了更好的用户体验!但是我们到底该如何利用 EIP-7702?

EIP-7702 的实现

没有实现,这也很难理解 :D。因此我还阅读了一些 ithaca odyssey 中的代码,后者已经在其执行层中包含 EIP-7702。他们还有一篇写得很好博客,你可以查看一下

在这一部分,我们将简要介绍 EIP-7702 实现的简化版本。我们将解释 EOA 如何将代码执行委托给智能合约,授权如何工作,以及如何执行批量交易。

exp-0001 中,ExperimentDelegation.sol 被用作委任设计者。P256WebAuthenP256 公钥(曲线:secp256r1)存储在合约中。

这意味着:你可以使用密钥(即 FaceId、TouchId)来签署交易,而无需使用私钥。

以下是其简化版本。

contract ExperimentDelegation is MultiSendCallOnly {
    ////////////////////////////////////////////////////////////////////////
    // 数据结构
    ////////////////////////////////////////////////////////////////////////

    /// @notice 可以用于授权调用的密钥。
    /// @custom:property authorized - 密钥是否被授权。
    /// @custom:property publicKey - ECDSA 公钥。
    /// @custom:property expiry - 密钥到期的 Unix 时间戳。
    struct Key {
        bool authorized;
        uint256 expiry;
        ECDSA.PublicKey publicKey;
    }
    // ...
    ////////////////////////////////////////////////////////////////////////
    // 函数
    ////////////////////////////////////////////////////////////////////////
    /// @notice 与权限相关的密钥列表。
    Key[] public keys;
    /// @notice 在授权的基础上再次授权新的公钥,提供权限签名。
    /// @param publicKey - 要授权的公钥。
    /// @param expiry - 密钥到期的 Unix 时间戳。
    /// @param signature - EOA secp256k1 签名,签署公钥。
    function authorize(
        ECDSA.PublicKey calldata publicKey,
        uint256 expiry,
        ECDSA.RecoveredSignature calldata signature
    ) public returns (uint32 keyIndex) {
        bytes32 digest = keccak256(
            abi.encodePacked(nonce++, publicKey.x, publicKey.y, expiry)
        );
        address signer = ecrecover(
            digest,
            signature.yParity == 0 ? 27 : 28,
            bytes32(signature.r),
            bytes32(signature.s)
        );
        if (signer != address(this)) revert InvalidSignature();
        Key memory key = Key({
            authorized: true,
            expiry: expiry,
            publicKey: publicKey
        });
        keys.push(key);
        return uint32(keys.length - 1);
    }
    // ...
    /// @notice 根据提供的 WebAuthn 包装的 P256 签名、WebAuthn 元数据以及 invoker 的索引,代为执行一组调用。
    /// @param calls - 要执行的调用。
    /// @param signature - 针对调用的 WebAuthn 包装 P256 签名:`p256.sign(keccak256(nonce ‖ calls))`。
    /// @param metadata - WebAuthn 元数据。
    /// @param keyIndex - 要使用的授权公钥的索引。
    function execute(
        bytes memory calls,
        ECDSA.Signature memory signature,
        WebAuthnP256.Metadata memory metadata,
        uint32 keyIndex
    ) public {
        bytes32 challenge = keccak256(abi.encodePacked(nonce++, calls));
        Key memory key = keys[keyIndex];
        if (!key.authorized) revert KeyNotAuthorized();
        if (key.expiry > 0 && key.expiry < block.timestamp) revert KeyExpired();
        if (!WebAuthnP256.verify(challenge, metadata, signature, key.publicKey))
            revert InvalidSignature();
        multiSend(calls);
    }
}

合约中使用了两个主要功能:

authorize

  • authorize 函数允许 EOA 将新公钥(密钥)与委任合约相关联。

execute

  • execute 函数使得多个调用的批量执行成为可能。
  • 它还验证 WebAuthn 包装的 P256(也为 P256)签名。
  • 它使用 multiSend 执行批量调用。

签署授权

首先,我们需要签署此 SetCodeTransaction 的消息。通过 viem::signAuthorization 函数,我们可以简单地发送钱包客户端并签署交易。此消息表明你的 EOA 批准将代码执行的授权给指定的合约:

import { walletClient } from './client'

const authorization = await walletClient.signAuthorization({
  contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // 委任设计者
})

// 其中 authorization_list 看起来像:
// [{
//   chainId: 1,
//   contractAddress: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
//   nonce: 1,
//   r: "0xf507fb8fa33ffd05a7f26c980bbb8271aa113affc8f192feba87abe26549bda1",
//   s: "0x1b2687608968ecb67230bbf7944199560fa2b3cffe9cc2b1c024e1c8f86a9e08",
//   yParity: 0,
// }]

const hash = await walletClient.sendTransaction({
  authorizationList: [authorization],
  data: '0xdeadbeef',
  to: walletClient.account.address,
})

然而,签署消息还不够。你仍然需要在链上发送交易以设置你 EOA 的代码。

授权和执行

一旦授权签名,委任代码将注入到你的 EOA 中。

// 签署 EIP-7702 授权以将 ExperimentDelegation 合约
// 注入到 EOA 中。
const authorization = await signAuthorization(client, {
  account,
  contractAddress: ExperimentDelegation.address,
  delegate: true,
})

// 发送 EIP-7702 合约写入以在 EOA 上授权 WebAuthn 密钥。
const hash = await writeContract(client, {
  abi: ExperimentDelegation.abi,
  address: account.address,
  functionName: 'authorize',
  args: [
    {
      x: publicKey.x,
      y: publicKey.y,
    },
    expiry,
    {
      r: BigInt(signature.r),
      s: BigInt(signature.s),
      yParity: signature.yParity,
    },
  ],
  authorizationList: [authorization],
  account: null, // 由序列化器填充
})

authorize() 之后,Odyssey 区块链浏览器上的 contract 字段将具有这个地址:

0xef010035202a6e6317f3cc3a177eeee562d3bcda4a6fcc

可以看到,委任设计者被设置为此地址,并且委任合约已部署,地址为:35202a6e6317f3cc3a177eeee562d3bcda4a6fcc(请查看实现

执行

授权后,你可以通过委任设计者执行 EOA。并且因为委任合约已经实现 multiSend。所以你可以在同一交易中执行多个调用。

// 从委任 EOA 的合约中获取下一个可用的 nonce。
const nonce = await readContract(client, {
  abi: ExperimentDelegation.abi,
  address: account.address,
  functionName: 'nonce',
})

// 将调用编码为合约所需的格式。
const calls_encoded = concat(
  calls.map((call) =>
    encodePacked(
      ['uint8', 'address', 'uint256', 'uint256', 'bytes'],
      [
        0,
        call.to,
        call.value ?? 0n,
        BigInt(size(call.data ?? '0x')),
        call.data ?? '0x',
      ],
    ),
  ),
)

// 计算要为 execute 函数签名的摘要。
const digest = keccak256(
  encodePacked(['uint256', 'bytes'], [nonce, calls_encoded]),
)

// 使用授权的 WebAuthn 密钥签署摘要。
const { signature, webauthn } = await sign({
  hash: digest,
  credentialId: account.key.id,
})

// 从签名中提取 r 和 s 值。
const r = BigInt(slice(signature, 0, 32))
const s = BigInt(slice(signature, 32, 64))

// 执行调用。
return await writeContract(client, {
  abi: ExperimentDelegation.abi,
  address: account.address,
  functionName: 'execute',
  args: [calls_encoded, { r, s }, webauthn, 0],
  account: null, // 由序列化器填充
})

exp-0001 中将代币发送给 Alice 和 Bob 时的样子(请参见这个

我的一些问题

EIP-4337 和 EIP-7702 兼容吗?它们是如何做到的,有什么例子吗?

是的,它们是兼容的。

是的,看到以下实现。

授权是否减少缺失私钥(EOA 拥有的私钥)的概率?

是的,也不是。如果你可以通过其他方式(例如,一次性密钥)授权你的钱包,那么你可以使用一次性密钥签署消息,而无需拿出私钥并签署 tx。或者你可以直接丢掉你的私钥,让它永远消失。

“不”的原因是账户应该是“自行保管”,这意味着如果你无法保管你的私钥,你可以转向使用其他服务:)。

如果遗失一次性密钥,我们可以更改授权吗?

我们可以使用相同的 EOA 私钥再次签署 authorization 交易,这将覆盖之前的委任。

EOA 可以委任给多个委任设计者吗?

不可以,因为在提案中规定:如果同一授权的多个元组出现,它将使用最后一个有效的出现中的地址设置代码。 所以,只有一个活动的委任设计者。

如果我们想在多个链上委任呢?

委任一个钱包代理,你可以将链 ID 设置为 0,以便在所有 EIP-7702 链上有效。钱包维护者还可以将单个 EIP-7702 授权消息硬编码到他们的钱包中,以便跨链代码兼容性从不成问题。

结论

EIP-7702 标志着在以太坊上弥合 EOA 和智能合约账户之间差距的重要一步。通过允许 EOA“采用”智能合约代码,借助委任设计者,该提案使当前 EOA 克服了局限性,即依赖于单个私钥(没有多重签名恢复或其他身份验证方法)、缺乏本地可编程性或Gas费赞助。通过 EIP-7702,用户可以执行高级操作,例如 批量交易自定义身份验证(如一次性密钥),甚至 多重签名 恢复,同时保持对其私钥的控制。

随着我们越来越接近 2025 年的 Pectra 硬分叉,通过 EIP-7702 的账户抽象演变是以太坊路线图上最有前途的发展之一。尽管代码展示了 EIP-7702 中设计的一个很好的例子,但我相信会有更多 EIP-7702 相关的账户和钱包出现 :)。

参考

视频

实现

Twitter 上的帖子

Linktree:

:) 请捐赠给我:0xF9B1810cFde1045d36eA198a07045a9F2bb47F32

  • 原文链接: medium.com/taipei-ethere...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
EthTaipei
EthTaipei
Taipei Ethereum Meetup