EIP-7702 深度探讨与最佳实践

  • slowmist
  • 发布于 2025-03-28 12:56
  • 阅读 21

本文深入分析了以太坊即将到来的 Pectra 升级中的 EIP-7702,该提案通过引入新的交易类型,允许 EOA 指定智能合约地址和代码,从而使 EOA 具备可编程性和可组合性,模糊了 EOA 和合约账户之间的界限。

作者: Kong

编辑: Sherry

前言

以太坊即将进行 Pectra 升级,这无疑是一次重大的更新。许多重要的以太坊改进提案(EIPs)将通过这次机会引入。其中,EIP-7702 为以太坊的外部账户(EOAs)带来了一个变革性的变化。该提案模糊了 EOA 和合约账户(CAs)之间的界限,是继 EIP-4337 之后,迈向原生账户抽象的关键一步。它为以太坊生态系统引入了一种新的交互模式。

目前,Pectra 已经在测试网络上部署,预计很快将在主网上线。本文将深入分析 EIP-7702 的实现机制,探讨它可能带来的机遇和挑战,并为不同的参与者提供实用的操作指南。

协议分析

概述

EIP-7702 引入了一种新的交易类型,允许 EOA 指定一个智能合约地址并为其设置代码。这使得 EOA 能够像智能合约一样执行代码,同时仍然保留发起交易的能力。此功能授予 EOA 可编程性和可组合性,允许用户在 EOA 中实现诸如社交恢复、权限控制、多重签名管理、零知识验证、基于订阅的支付、交易赞助和批量处理等功能。

值得注意的是,EIP-7702 与 EIP-4337 实现的智能合约钱包完全兼容。这两个提案的无缝集成大大简化了新功能的开发和应用。

EIP-7702 的具体实现涉及引入一种新的交易类型,SET_CODE_TX_TYPE (0x04),其数据结构如下:

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, address, nonce, y_parity, r, s], ...]

在新的交易结构中,除了 authorization_list 字段外,所有其他字段都遵循与 EIP-4844 相同的语义。authorization_list 字段是列表类型,可以包含多个授权条目。每个授权条目包括:

  • chain_id:指定授权生效的链。
  • address:指定委托的目标地址。
  • nonce:必须与授权账户当前的 nonce 匹配。
  • y_parity, r, s:授权账户签署授权的签名数据。

交易中的 authorization_list 字段可以包含由多个不同的 EOA 签署的授权条目,这意味着交易发起者可以与授权者不同。这允许为授权操作进行 gas 赞助。

实现

当授权者签署授权数据时,他们必须首先对 chain_id, address, nonce 字段进行 RLP 编码。然后,使用 keccak256 对编码后的数据以及一个 MAGIC 数字进行哈希处理,生成要签名的数据[1]。最后,授权者的私钥对哈希后的数据进行签名,生成 y_parity, r, s 值。

// Go-ethereum/core/types/tx_setcode.go#L109-L113
func (a *SetCodeAuthorization) sigHash() common.Hash {
   return prefixedRlpHash(0x05, []any{
      a.ChainID,
      a.Address,
      a.Nonce,
   })
}

MAGIC (0x05) 数字用作域名分隔符,以确保不同类型的签名不会冲突。

如果授权条目的 chain_id0,则表示授权者允许[2]授权在所有支持 EIP-7702 的 EVM 兼容链上重放,前提是 nonce 也匹配。

// Go-ethereum/core/state_transition.go#L562
if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
   return authority, ErrAuthorizationWrongChainID
}

一旦授权数据被签名,交易发起者将其聚合到 authorization_list 字段中,签署交易,并通过 RPC 广播它。在交易包含在区块中之前,Proposer 会执行预检查[3],确保 to 地址有效。此检查阻止了合约创建交易,这意味着在 EIP-7702 交易中,to 地址不能为空[4]。

// Go-ethereum/core/state_transition.go#L388-L390
if msg.To == nil {
   return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
}

随后,在交易执行期间,节点首先增加交易发起者的 nonce 值,然后将 applyAuthorization 操作应用于 authorization_list 中的每个授权条目。在 applyAuthorization 操作中,节点首先检查授权的 nonce,然后增加授权的 nonce。这意味着如果交易发起者和授权者是同一用户 (EOA),则在签署授权交易时,nonce 值应递增 1。

// Go-ethereum/core/state_transition.go#L489-L497

func (st *stateTransition) execute() (*ExecutionResult, error) {

  ...

  st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)

  // Apply EIP-7702 authorizations.

  if msg.SetCodeAuthorizations != nil {

    for _, auth := range msg.SetCodeAuthorizations {

      // Note errors are ignored, we simply skip invalid authorizations here.

      st.applyAuthorization(&auth)

    }

  }

    ...

}

// Go-ethereum/core/state_transition.go#L604

func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error {

  authority, err := st.validateAuthorization(auth)

 ...

 st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)

 ...

}

// Go-ethereum/core/state_transition.go#L566

func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) {

 ...

 if auth.Nonce+1 < auth.Nonce {

  return authority, ErrAuthorizationNonceOverflow

 }

 ...

}

当节点应用授权条目时,如果遇到任何错误,将跳过该条目,并且交易不会失败。其他授权条目将继续应用,确保在批量授权场景中,不会出现 DoS 风险。

// Go-ethereum/core/state_transition.go#L494

for _, auth := range msg.SetCodeAuthorizations {

      // Note errors are ignored, we simply skip invalid authorizations here.

      st.applyAuthorization(&auth)

}

应用授权后,授权者地址的代码字段将设置为 0xef0100 || address,其中 0xef0100 是一个固定标识符,address 是委托的目标地址。由于 EIP-3541 的限制,用户无法通过常规方式部署以 0xef 开头的合约,从而确保此类标识符只能通过 SET_CODE_TX_TYPE (0x04) 类型的交易部署。

// Go-ethereum/core/state_transition.go#L612

st.state.SetCode(authority, types.AddressToDelegation(auth.Address))

// Go-ethereum/core/types/tx_setcode.go#L45

var DelegationPrefix = []byte{0xef, 0x01, 0x00}

func AddressToDelegation(addr common.Address) []byte {

 return append(DelegationPrefix, addr.Bytes()...)

}

授权后,如果授权者想要撤销授权,他们只需将委托的目标地址设置为 0 地址。

通过 EIP-7702 引入的新交易类型,授权者 (EOA) 可以像智能合约一样执行代码,同时保留发起交易的能力。与 EIP-4337 相比,这为用户提供了更接近原生账户抽象 (Native AA) 的体验,大大降低了用户的入门门槛。

最佳实践

虽然 EIP-7702 为以太坊生态系统带来了新的活力,但新应用场景的引入也带来了新的风险。以下是生态系统参与者在实践中应注意的一些方面:

私钥存储

即使 EOA(外部账户)可以利用智能合约中内置的社交恢复机制来解决私钥丢失导致的资产损失问题,但仍然无法避免私钥泄露的风险。需要注意的是,在委托之后,EOA 私钥仍然对账户具有最高的控制权;持有私钥使用户能够自由处置账户内的资产。用户或钱包服务提供商在完成 EOA 的委托后,无法完全消除私钥泄露的风险,尤其是在可能存在供应链攻击的情况下。

对于用户而言,私钥保护应始终是首要任务。务必记住:不是你的密钥,就不是你的币。

多链重放

当用户签署委托授权时,他们可以选择委托生效的链 ID。用户还可以选择使用链 ID 为 0,这允许委托在多个链上重放。但是,不同链上的相同合约地址可能具有不同的实现。

钱包服务提供商应检查委托的链是否与当前网络匹配,并警告用户使用链 ID 为 0 签署的委托可能在不同链上重放的风险。

用户还应该意识到,不同链上的相同合约地址可能并不总是具有相同的合约代码。在继续之前,务必了解委托目标的详细信息。

初始化问题

大多数主流智能合约钱包使用代理模型,其中钱包代理通过 DELEGATECALL 调用初始化函数,以实现钱包的原子初始化和部署。但是,对于 EIP-7702,地址的 code 字段只会更新,并且无法通过委托地址调用初始化函数。这限制了 EIP-7702 提供与 ERC-1967 代理合约相同钱包初始化功能,后者可以在部署期间调用初始化函数。

开发人员应确保在钱包初始化期间执行权限检查(例如,通过 ecrecover 验证签名地址),以避免初始化漏洞。

存储管理

使用 EIP-7702 委托时,由于功能更改或钱包升级,用户可能需要重新委托到不同的合约地址。不同的合约可能具有不同的存储结构(例如,slot0 可能在不同的合约中代表不同类型的数据),并且重新委托可能会导致旧合约数据被重用,从而导致帐户锁定、资产损失和其他问题。

用户应仔细处理重新委托的情况。

开发人员应遵循 ERC-7201 中提出的 命名空间公式,将变量分配到指定的独立存储位置,以减轻存储冲突。此外,ERC-7779(草案)为 EIP-7702 专门提供了一个重新委托的标准流程,包括防止存储冲突并在重新委托之前验证兼容性。

虚假充值

委托后,EOA 也可以充当智能合约,这可能导致中心化交易所 (CEX) 面临广泛的智能合约充值风险。

CEX 应使用跟踪检查来监控每笔存款交易,以减轻来自智能合约的虚假存款的风险。

账户转换

通过 EIP-7702 委托,用户的帐户可以在 EOA 和智能合约之间自由转换,使帐户能够发起交易并被调用。这意味着当帐户调用自身或进行外部调用时,其 msg.sender 也将是 tx.origin,这打破了只有 EOA 参与的安全假设。

合约开发人员不应假设 tx.origin 始终是 EOA。同样,使用 msg.sender == tx.origin 作为防御重入攻击的手段将不再有效。

开发人员应假设未来的参与者都可能是智能合约。

合约兼容性

现有的 ERC-721 和 ERC-777 代币在转移代币时具有 hook 函数,这意味着接收者必须实现相应的回调函数才能成功接收代币。

开发人员应确保用户委托的目标合约实现了必要的回调函数,以确保与主流代币的兼容性。

钓鱼检查

在实现 EIP-7702 委托后,用户帐户中的资产可能由智能合约控制。如果用户将其帐户委托给恶意合约,攻击者可以轻松窃取资产。

钱包服务提供商应尽快支持 EIP-7702 交易,并且当用户签署委托时,应高亮显示目标合约,以降低钓鱼攻击的风险。

此外,对委托目标合约进行深入的自动分析(开源检查、权限检查等)可以帮助用户避免此类风险。

结论

本文探讨了即将到来的以太坊 Pectra 升级中的 EIP-7702。EIP-7702 引入了新的交易类型,赋予 EOA 可编程性和可组合性,模糊了 EOA 和合约帐户之间的界限。然而,由于目前没有经过广泛测试的与 EIP-7702 合约兼容的标准,因此各种生态系统参与者(如用户、钱包提供商、开发人员和 CEX)面临许多挑战和机遇。这里讨论的最佳实践无法涵盖所有潜在风险,但对于实际应用仍然有价值。

示例

[设置 EOA 帐户代码]

Holesky Transaction Hash (Txhash) Details | Etherscan \ \ Holesky (ETH) detailed transaction info for txhash 0x29252bf527155a29fc0df3a2eb7f5259564f5ee7a15792ba4e2ca59318080182…\ \ holesky.etherscan.io

[取消设置 EOA 帐户代码]

Holesky Transaction Hash (Txhash) Details | Etherscan \ \ Holesky (ETH) detailed transaction info for txhash 0xd410d2d2a2ad19dc82a19435faa9c19279fa5b96985988daad5d40d1a8ee2269…\ \ holesky.etherscan.io

相关链接

[1] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/types/tx_setcode.go#L109-L113

[2] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L562

[3] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L304

[4] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L388-L390

参考文献

[EIP-7702] https://eips.ethereum.org/EIPS/eip-7702

[EIP-4844] https://eips.ethereum.org/EIPS/eip-4844

[Go-ethereum] https://github.com/ethereum/go-ethereum/tree/7fed9584b5426be5db6d7b0198acdec6515d9c81

[EIP-3541] https://eips.ethereum.org/EIPS/eip-3541#backwards-compatibility

[Cobo: EIP-7702实用指南] https://mp.weixin.qq.com/s/ojh9uLw-sJNArQe-U73lHQ

[Viem] https://viem.sh/experimental/eip7702/signAuthorization

[ERC-7210] https://eips.ethereum.org/EIPS/eip-7201

[ERC-7779] https://eips.ethereum.org/EIPS/eip-7779

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

0 条评论

请先 登录 后评论
slowmist
slowmist
江湖只有他的大名,没有他的介绍。