ERC-7679: UserOperation 构建器
构建 UserOperations 而无需耦合账户特定的逻辑。
Authors | Derek Chiang (@derekchiang), Garvit Khatri (@plusminushalf), Fil Makarov (@filmakarov), Kristof Gazso (@kristofgazso), Derek Rein (@arein), Tomas Rocchi (@tomiir), bumblefudge (@bumblefudge) |
---|---|
Created | 2024-04-05 |
Discussion Link | https://ethereum-magicians.org/t/erc-7679-smart-account-interfaces/19547 |
Requires | EIP-4337 |
Table of Contents
摘要
不同的 ERC-4337 智能账户实现以不同的方式编码其签名、nonce 和 calldata。这使得 DApp、钱包和智能账户工具难以与智能账户集成,除非与账户特定的 SDK 集成,这会引入供应商锁定并损害智能账户的采用。
我们提出了一种标准方式,让智能账户实现将其账户特定的编码逻辑放在链上。这可以通过实现接受原始签名、nonce 或 calldata(以及上下文)作为输入的方法来实现,并以正确的格式输出它们,以便智能账户可以在验证和执行 User Operation 时使用它们。
动机
目前,要为智能账户构建一个 ERC-4337 UserOperation(简称 UserOp),需要详细了解智能账户实现的工作方式,因为每个实现都可以自由地以不同的方式编码其 nonce、calldata 和签名。
作为一个简单的例子,一个账户可能使用名为 executeFoo
的执行函数,而另一个账户可能使用名为 executeBar
的执行函数。即使它们执行相同的调用,这也会导致两个账户之间的 calldata
不同。
因此,想要为给定的智能账户发送 UserOp 的人需要:
- 弄清楚该账户正在使用哪个智能账户实现。
- 根据智能账户实现正确编码签名/nonce/calldata,或使用知道如何执行此操作的账户特定的 SDK。
实际上,这意味着今天大多数 DApp、钱包和 AA 工具都与特定的智能账户实现绑定,从而导致碎片化和供应商锁定。
规范
本文档中的关键词“必须 (MUST)”,“禁止 (MUST NOT)”,“需要 (REQUIRED)”,“应当 (SHALL)”,“不应 (SHALL NOT)”,“应该 (SHOULD)”,“不应该 (SHOULD NOT)”,“推荐 (RECOMMENDED)”,“可以 (MAY)”,和“可选 (OPTIONAL)”按照 RFC 2119 中的描述进行解释。
UserOp 构建器
为了符合此标准,智能账户实现必须提供一个“UserOp 构建器”合约,该合约实现 IUserOperationBuilder
接口,如下定义:
struct Execution {
address target;
uint256 value;
bytes callData;
}
interface IUserOperationBuilder {
/**
* @dev 返回账户实现支持的 ERC-4337 EntryPoint。
*/
function entryPoint() external view returns (address);
/**
* @dev 返回 UserOp 要使用的 nonce,给定上下文。
* @param smartAccount 是 UserOp 发送者的地址。
* @param context 是 UserOp 构建器正确计算 UserOp
* 请求字段所需的数据。
*/
function getNonce(
address smartAccount,
bytes calldata context
) external view returns (uint256);
/**
* @dev 返回 UserOp 的 calldata,给定上下文和执行。
* @param smartAccount 是 UserOp 发送者的地址。
* @param executions 是 (destination, value, callData) 元组,
* UserOp 想要执行。它是一个数组,因此 UserOp 可以
* 批量执行。
* @param context 是 UserOp 构建器正确计算 UserOp
* 请求字段所需的数据。
*/
function getCallData(
address smartAccount,
Execution[] calldata executions,
bytes calldata context
) external view returns (bytes memory);
/**
* @dev 返回正确编码的签名,给定一个 UserOp,
* 该 UserOp 已被正确填写,除了签名字段。
* @param smartAccount 是 UserOp 发送者的地址。
* @param userOperation 是 UserOp。UserOp 的每个字段都应该
* 有效,除了签名字段。“PackedUserOperation”
* 结构体的定义与 ERC-4337 中相同。
* @param context 是 UserOp 构建器正确计算 UserOp
* 请求字段所需的数据。
*/
function formatSignature(
address smartAccount,
PackedUserOperation calldata userOperation,
bytes calldata context
) external view returns (bytes memory signature);
}
使用 UserOp 构建器
要使用 UserOp 构建器构建 UserOp,构建方应按如下步骤进行:
- 从账户所有者处获取
UserOpBuilder
的地址和context
。从构建方的角度来看,context
是一个不透明的字节数组。UserOpBuilder
实现可能需要context
才能正确确定 UserOp 字段。有关更多信息,请参见 Rationale。 - 执行一个 multicall(批量
eth_call
),使用context
和 executions 调用getNonce
和getCallData
。构建方现在将获得 nonce 和 calldata。 - 使用先前获得的数据填写 UserOp。Gas 值可以随机设置或设置得很低。此 userOp 将用于获取 gas 估算的虚拟签名。对 userOp 的哈希进行签名。(有关什么是虚拟签名,请参见 Rationale。 有关虚拟签名安全性的详细信息,请参见 Security Considerations)。
- 使用 UserOp 和
context
调用(通过eth_call
)formatSignature
以获得具有正确格式的虚拟签名的 UserOp。现在,此 userOp 可以用于 gas 估算。 - 在 UserOp 中,将现有的 gas 值更改为从适当的 gas 估算获得的值。除了
signature
字段外,此 UserOp 必须有效。对 UserOp 的哈希进行签名,并将签名放入 UserOp.signature 字段中。 - 使用 UserOp 和
context
调用(通过eth_call
)formatSignature
以获得完全有效的 UserOp。- 请注意,UserOp 比
nonce
、callData
和signature
具有更多的字段,但是构建方如何获得其他字段不在本文档的范围内,因为只有这三个字段在很大程度上取决于智能账户实现。
- 请注意,UserOp 比
此时,构建方具有一个完全有效的 UserOp,然后他们可以将其提交给 bundler 或对其进行任何处理。
在账户尚未部署时使用 UserOp 构建器
为了向构建方提供准确的数据,在大多数情况下,UserOpBuilder
必须调用该账户。
如果该账户尚未部署,这意味着构建方希望为此账户发送第一个 UserOp,则构建方可以按如下方式修改上述流程:
- 除了
UserOpBuilder
地址和context
之外,构建方还获得如 ERC-4337 中定义的factory
和factoryData
。 - 在调用 UserOp 构建器上的一个 view 函数时,构建方可以使用
eth_call
来部署CounterfactualCall
合约,该合约将部署该账户并调用UserOpBuilder
(请参见下文)。 - 在填写 UserOp 时,构建方包括
factory
和factoryData
。
CounterfactualCall
合约应:
- 使用构建方提供的
factory
和factoryData
部署该账户。 - 如果部署未成功,则回退。
- 如果该账户已成功部署,则调用
UserOpBuilder
并将UserOpBuilder
返回的数据返回给构建方。
有关 CounterfactualCall
合约的更多详细信息,请参见参考实现部分。
理由
上下文
context
是一个字节数组,它编码 UserOp 构建器为了正确确定 nonce、calldata 和签名所需的所有数据。据推测,context
是由账户所有者在钱包软件的帮助下构建的。
在这里,我们概述了 context
的一种可能用法:委托。假设账户所有者希望委托一个由构建方执行的交易。账户所有者可以将构建方的公钥的签名编码在 context
内。我们将账户所有者的这个签名称为 authorization
。
然后,当构建方填写 UserOp 时,它将使用其自己的私钥生成的签名来填充 signature
字段。当它在 UserOp 构建器上调用 getSignature
时,UserOp 构建器将从 context
中提取 authorization
,并将其与构建方的签名连接起来。据推测,智能账户的实现方式是从签名中恢复构建方的公钥,并检查该公钥实际上是否已由 authorization
签署。如果检查成功,则智能账户将执行 UserOp,从而允许构建方代表用户执行 UserOp。
虚拟签名
“虚拟签名”是指在发送给 bundler 以估计 gas (通过 eth_estimateUserOperationGas
)的 UserOp 中使用的签名。需要虚拟签名,因为在 bundler 估计 gas 时,有效的签名尚未存在,因为有效的签名本身取决于 UserOp 的 gas 值,从而形成循环依赖。为了打破循环依赖,使用了虚拟签名。
但是,虚拟签名不仅仅是任何智能账户都可以使用的固定值。必须构造虚拟签名,使其导致 UserOp 使用与真实签名大约相同的 gas。因此,虚拟签名会根据智能账户用于验证 UserOp 的特定验证逻辑而变化,使其依赖于智能账户的实现。
向后兼容性
此 ERC 旨在与截至 EntryPoint 0.7 的所有 ERC-4337 智能账户向后兼容。
对于针对 EntryPoint 0.6 部署的智能账户,需要修改 IUserOperationBuilder
接口,以便将 PackedUserOperation
结构体替换为 EntryPoint 0.6 中的相应结构体。
参考实现
Counterfactual call 合约
反事实调用合约的灵感来自 ERC-6492,它设计了一种针对预部署(反事实)合约执行 isValidSignature
(请参见 ERC-1271)的机制。
contract CounterfactualCall {
error CounterfactualDeployFailed(bytes error);
constructor(
address smartAccount,
address create2Factory,
bytes memory factoryData,
address userOpBuilder,
bytes memory userOpBuilderCalldata
) {
if (address(smartAccount).code.length == 0) {
(bool success, bytes memory ret) = create2Factory.call(factoryData);
if (!success || address(smartAccount).code.length == 0) revert CounterfactualDeployFailed(ret);
}
assembly {
let success := call(gas(), userOpBuilder, 0, add(userOpBuilderCalldata, 0x20), mload(userOpBuilderCalldata), 0, 0)
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
if iszero(success) {
revert(ptr, returndatasize())
}
return(ptr, returndatasize())
}
}
}
这是使用 ethers 和 viem 库调用此合约的示例:
// ethers
const nonce = await provider.call({
data: ethers.utils.concat([
counterfactualCallBytecode,
(
new ethers.utils.AbiCoder()).encode(['address','address', 'bytes', 'address','bytes'],
[smartAccount, userOpBuilder, getNonceCallData, factory, factoryData]
)
])
})
// viem
const nonce = await client.call({
data: encodeDeployData({
abi: parseAbi(['constructor(address, address, bytes, address, bytes)']),
args: [smartAccount, userOpBuilder, getNonceCalldata, factory, factoryData],
bytecode: counterfactualCallBytecode,
})
})
安全考虑
虚拟签名安全性
由于格式正确的虚拟签名将被公开披露,因此从理论上讲,它可能会被中间人拦截和使用。但是,这样做的风险和潜在危害非常低,因为在提交最终 UserOp 之后,虚拟签名将实际上无法使用(因为两个 UserOp 都使用相同的 nonce)。但是,为了缓解即使是这个小问题,建议填写 UserOp(对 UserOp 的哈希进行签名以获得未格式化的虚拟签名)(上述步骤 3)时,gas 值应非常低。
版权
版权及相关权利均通过 CC0 放弃。
Citation
Please cite this document as:
Derek Chiang (@derekchiang), Garvit Khatri (@plusminushalf), Fil Makarov (@filmakarov), Kristof Gazso (@kristofgazso), Derek Rein (@arein), Tomas Rocchi (@tomiir), bumblefudge (@bumblefudge), "ERC-7679: UserOperation 构建器 [DRAFT]," Ethereum Improvement Proposals, no. 7679, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7679.