EIP-7702是2025年Pectra硬分叉中提出的一项提案,旨在使外部拥有账户(EOA)能够像智能合约一样执行代码,从而解决EOA的单一故障点、功能有限以及Gas费用支付等问题。通过新类型的交易和权限委托设计,这一提案为EOA引入了多重签名、批量交易和自定义身份验证等功能,大大增强了用户体验。
理解 EIP-7702 是什么以及为什么 EIP-7702 会出现在 Pectra :)
特别感谢 NIC Lin 和 Chang-Wu Chen 的反馈和审阅。
本文旨在帮助人们理解该提案背后的基本概念。
本文将包含一些示例代码,主要聚焦于 exp-0001
,这是 Ithaca 在 EIP-7702 中做的一个实验项目。
那么首先,EIP-7702 是什么?它想解决什么样的问题?
来自 @0xNairolf
以太坊的目标之一是抽象当前两种账户:
EIP-7702 起到了 EOA 和智能合约之间的桥梁作用,使 EOA 能够像智能合约一样运作。
例如,当 EOA 与智能合约交互时,看起来是这样的:
在 EIP-7702 设置后,交互将变为:
好吧,但 EOA 的问题是什么?
单点故障:EOA 仅由一个私钥控制,缺乏恢复机制或多重签名保护。
功能有限:EOA 只能发送交易而不能执行代码。这是糟糕的,因为它不能:
Gas费用支付:EOA 只能用 ETH 支付Gas费。
总的来说,当前的 EOA 设计用户体验较差。因此,几种账户抽象的想法应运而生。
账户抽象在过去几年一直在讨论。从 2021 到 2024,出现了相当多的账户抽象解决方案。
EIP-7702 的目标之一是当前(在 Pectra 硬分叉之前)账户设计。本 EIP 模糊了 EOA 和智能合约之间的界限,这也是以太坊希望实现的最终目标 — RIP-7560
(即支持共识层的原生账户抽象)。
它的功能是什么,如何运作?
address(0)
到 authorization_list
中)我对委任设计者的想象是 Jojo 中的 Sutando 。
通过委任设计者执行交易的感觉是:
目前,我们在以太坊中的 EOA 中有 4 个槽位:
EOA 账户中的 code
是空的;也就是说,EOA 账户中没有代码值存储。EIP-7702 通过发送一种新的 EIP-2718
交易类型“设置代码交易”引入了一种设置代码字段的新方法。
交易代码与来自 EIP-1559
的普通交易不同(tx type = 0x02
),它必须在交易负载中发送更多数据。
在此交易中,EOA 必须使用以下四个附加信息对消息进行签名:
chain_id
:交易将发生的链address
:EOA 想要指向的智能合约地址nonce
:防重放的安全保护y_parity (== v)
,r
和 s
:用于恢复权限的签名组件(签名人也是 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,我们可以做到:
secp256r1
这样的签名来授权帐户,而不是使用以太坊上的原生 secp256k1
ECDSA 签名。更多示例如:
来自 jchaskin22
所有这些听起来很好,并为用户提供了更好的用户体验!但是我们到底该如何利用 EIP-7702?
没有实现,这也很难理解 :D。因此我还阅读了一些 ithaca odyssey 中的代码,后者已经在其执行层中包含 EIP-7702。他们还有一篇写得很好博客,你可以查看一下。
在这一部分,我们将简要介绍 EIP-7702 实现的简化版本。我们将解释 EOA 如何将代码执行委托给智能合约,授权如何工作,以及如何执行批量交易。
在 exp-0001
中,ExperimentDelegation.sol 被用作委任设计者。P256
和 WebAuthenP256
公钥(曲线: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
函数使得多个调用的批量执行成为可能。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 时的样子(请参见这个)
是的,它们是兼容的。
是的,看到以下实现。
是的,也不是。如果你可以通过其他方式(例如,一次性密钥)授权你的钱包,那么你可以使用一次性密钥签署消息,而无需拿出私钥并签署 tx。或者你可以直接丢掉你的私钥,让它永远消失。
“不”的原因是账户应该是“自行保管”,这意味着如果你无法保管你的私钥,你可以转向使用其他服务:)。
我们可以使用相同的 EOA 私钥再次签署 authorization
交易,这将覆盖之前的委任。
不可以,因为在提案中规定:如果同一授权的多个元组出现,它将使用最后一个有效的出现中的地址设置代码。 所以,只有一个活动的委任设计者。
委任一个钱包代理,你可以将链 ID 设置为 0,以便在所有 EIP-7702 链上有效。钱包维护者还可以将单个 EIP-7702 授权消息硬编码到他们的钱包中,以便跨链代码兼容性从不成问题。
EIP-7702 标志着在以太坊上弥合 EOA 和智能合约账户之间差距的重要一步。通过允许 EOA“采用”智能合约代码,借助委任设计者,该提案使当前 EOA 克服了局限性,即依赖于单个私钥(没有多重签名恢复或其他身份验证方法)、缺乏本地可编程性或Gas费赞助。通过 EIP-7702,用户可以执行高级操作,例如 批量交易、自定义身份验证(如一次性密钥),甚至 多重签名 恢复,同时保持对其私钥的控制。
随着我们越来越接近 2025 年的 Pectra 硬分叉,通过 EIP-7702 的账户抽象演变是以太坊路线图上最有前途的发展之一。尽管代码展示了 EIP-7702 中设计的一个很好的例子,但我相信会有更多 EIP-7702 相关的账户和钱包出现 :)。
Linktree:
:) 请捐赠给我:0xF9B1810cFde1045d36eA198a07045a9F2bb47F32
- 原文链接: medium.com/taipei-ethere...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!