ERC-5639: 委托注册表
委托权限以实现更安全、更便捷的签名操作。
Authors | foobar (@0xfoobar), Wilkins Chung (@wwhchung) <wilkins@manifold.xyz>, ryley-o (@ryley-o), Jake Rockland (@jakerockland), andy8052 (@andy8052) |
---|---|
Created | 2022-09-09 |
Table of Contents
摘要
本 EIP 描述了委托注册表的详细信息,这是一种提议的协议和 ABI 定义,它提供了一种将一个或多个委托钱包链接到 vault 钱包的方法,允许链接的委托钱包证明对 vault 钱包的控制权和资产所有权。
动机
在以太坊生态系统中,向第三方应用程序证明资产所有权是很常见的。用户经常签署数据负载以验证自己的身份,然后才能获得执行某些操作的权限。然而,这种方法——类似于赋予第三方对其主钱包的根访问权限——既不安全也不方便。
例子:
- 为了在 OpenSea 上编辑您的个人资料,您必须使用您的钱包签署一条消息。
- 为了访问 NFT 门控内容,您必须使用包含 NFT 的钱包签署一条消息,以证明所有权。
- 为了获得参加活动的资格,您必须使用包含所需 NFT 的钱包签署一条消息,以证明所有权。
- 为了领取空投,您必须使用符合条件的钱包与智能合约交互。
- 为了证明 NFT 的所有权,您必须使用拥有该 NFT 的钱包签署一个 payload。
在以上所有示例中,您都使用钱包本身与 dApp 或智能合约交互,这可能是
- 不方便(如果它通过硬件钱包或多重签名控制)
- 不安全(因为上述操作是只读的,但您是通过具有写入权限的钱包进行签名/交互)
相反,应该能够批准多个钱包代表给定的钱包进行身份验证。
现有方法和解决方案的问题
不幸的是,我们已经看到很多用户意外签署恶意 payload 的案例。结果几乎总是与委托地址相关的资产的重大损失。
除此之外,许多用户将其资产的很大一部分保存在“冷存储”中。由于“冷存储”解决方案提高了安全性,我们通常会看到可访问性降低,因为用户自然会增加访问这些钱包所需的障碍。
提案:使用委托注册表
本提案旨在提供一种机制,允许 vault 钱包向委托钱包授予钱包、合约或 token 级别的权限。这将实现一种更安全、更方便的签名和身份验证方式,并通过一个或多个辅助钱包提供对 vault 钱包的“只读”访问权限。
从那里,好处是双重的。本 EIP 通过将潜在的恶意签名操作外包给更容易访问的钱包(热钱包)来提高用户的安全性,同时能够保持不经常用于签名操作的钱包的预期安全假设。
提高 dApp 交互安全性
许多 dApp 需要证明对钱包的控制权才能获得访问权限。目前,这意味着您必须使用钱包本身与 dApp 交互。这是一个安全问题,因为恶意 dApp 或网络钓鱼网站可能会导致钱包的资产因签署恶意 payload 而受到损害。
但是,如果使用辅助钱包进行这些交互,则可以减轻这种风险。恶意交互将被隔离到辅助钱包中持有的资产,该钱包可以设置为不包含任何有价值的东西。
提高多个设备访问的安全性
为了在多个设备上使用非硬件钱包,您必须将助记词导入到每个设备。每次在新设备上输入助记词时,钱包被盗用的风险都会增加,因为您正在增加了解助记词的设备的表面积。
相反,每个设备都可以拥有自己的唯一钱包,该钱包是主钱包的授权辅助钱包。如果设备专用钱包被盗用或丢失,您可以简单地删除身份验证授权。
此外,钱包身份验证可以链接起来,以便辅助钱包本身可以授权一个或多个三级钱包,然后这些钱包同时具有辅助地址和根主地址的签名权。这样,团队中的每个人都可以拥有自己的签名者,而主钱包只需撤销根源的权利即可轻松地使整个树无效。
提高便利性
许多人使用硬件钱包以获得最大安全性。但是,这通常很不方便,因为许多人不想一直随身携带硬件钱包。
相反,如果您批准了一个非硬件钱包用于身份验证活动(例如移动设备),您将能够使用大多数 dApp,而无需随身携带硬件钱包。
规范
本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。
假设:
vault
代表我们试图进行身份验证或证明资产所有权的 vault 地址。delegate
代表我们想要用来代替vault
进行签名的地址。
委托注册表必须实现 IDelegationRegistry
/**
* @title 一个不可变的注册合约,可以作为独立的原始合约部署
* @dev 新项目启动可以从此读取之前的冷钱包 -> 热钱包委托
* 并将这些权限集成到他们的流程中
*/
interface IDelegationRegistry {
/// @notice 委托类型
enum DelegationType {
NONE,
ALL,
CONTRACT,
TOKEN
}
/// @notice 有关单个委托的信息,用于链上枚举
struct DelegationInfo {
DelegationType type_;
address vault;
address delegate;
address contract_;
uint256 tokenId;
}
/// @notice 有关单个合约级委托的信息
struct ContractDelegation {
address contract_;
address delegate;
}
/// @notice 有关单个 token 级委托的信息
struct TokenDelegation {
address contract_;
uint256 tokenId;
address delegate;
}
/// @notice 当用户委托他们的整个钱包时发出
event DelegateForAll(address vault, address delegate, bool value);
/// @notice 当用户委托一个特定的合约时发出
event DelegateForContract(address vault, address delegate, address contract_, bool value);
/// @notice 当用户委托一个特定的 token 时发出
event DelegateForToken(address vault, address delegate, address contract_, uint26 tokenId, bool value);
/// @notice 当用户撤销所有委托时发出
event RevokeAllDelegates(address vault);
/// @notice 当用户撤销给定委托的所有委托时发出
event RevokeDelegate(address vault, address delegate);
/**
* ----------- WRITE -----------
*/
/**
* @notice 允许委托代表您对所有合约执行操作
* @param delegate 代表您执行操作的热钱包
* @param value 是否为此地址启用或禁用委托,true 用于设置,false 用于撤销
*/
function delegateForAll(address delegate, bool value) external;
/**
* @notice 允许委托代表您对特定合约执行操作
* @param delegate 代表您执行操作的热钱包
* @param contract_ 您要委托的合约的地址
* @param value 是否为此地址启用或禁用委托,true 用于设置,false 用于撤销
*/
function delegateForContract(address delegate, address contract_, bool value) external;
/**
* @notice 允许委托代表您对特定 token 执行操作
* @param delegate 代表您执行操作的热钱包
* @param contract_ 您要委托的合约的地址
* @param tokenId 您要委托的 token 的 token id
* @param value 是否为此地址启用或禁用委托,true 用于设置,false 用于撤销
*/
function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external;
/**
* @notice 撤销所有委托
*/
function revokeAllDelegates() external;
/**
* @notice 撤销特定委托的所有权限
* @param delegate 要撤销的热钱包
*/
function revokeDelegate(address delegate) external;
/**
* @notice 将自己从特定 vault 的委托中移除
* @param vault 将 msg.sender 委托给 vault,应将其移除
*/
function revokeSelf(address vault) external;
/**
* ----------- READ -----------
*/
/**
* @notice 返回给定委托能够代表执行的所有活动委托
* @param delegate 您想要检索其委托的委托
* @return info DelegationInfo 结构的数组
*/
function getDelegationsByDelegate(address delegate) external view returns (DelegationInfo[] memory);
/**
* @notice 返回给定 vault 的所有钱包级委托的数组
* @param vault 发出委托的冷钱包
* @return addresses 给定 vault 的所有钱包级委托的数组
*/
function getDelegatesForAll(address vault) external view returns (address[] memory);
/**
* @notice 返回给定 vault 和合约的所有合约级委托的数组
* @param vault 发出委托的冷钱包
* @param contract_ 您要委托的合约的地址
* @return addresses 给定 vault 和合约的所有合约级委托的数组
*/
function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory);
/**
* @notice 返回给定 vault 的 token 的所有合约级委托的数组
* @param vault 发出委托的冷钱包
* @param contract_ 包含 token 的合约的地址
* @param tokenId 您要委托的 token 的 token id
* @return addresses 给定 vault 的 token 的所有合约级委托的数组
*/
function getDelegatesForToken(address vault, address contract_, uint256 tokenId)
external
view
returns (address[] memory);
/**
* @notice 返回给定 vault 的所有合约级委托
* @param vault 发出委托的冷钱包
* @return delegations ContractDelegation 结构的数组
*/
function getContractLevelDelegations(address vault)
external
view
returns (ContractDelegation[] memory delegations);
/**
* @notice 返回给定 vault 的所有 token 级委托
* @param vault 发出委托的冷钱包
* @return delegations TokenDelegation 结构的数组
*/
function getTokenLevelDelegations(address vault) external view returns (TokenDelegation[] memory delegations);
/**
* @notice 如果该地址被委托代表整个 vault 执行操作,则返回 true
* @param delegate 代表您执行操作的热钱包
* @param vault 发出委托的冷钱包
*/
function checkDelegateForAll(address delegate, address vault) external view returns (bool);
/**
* @notice 如果该地址被委托代表您对 token 合约或整个 vault 执行操作,则返回 true
* @param delegate 代表您执行操作的热钱包
* @param contract_ 您要委托的合约的地址
* @param vault 发出委托的冷钱包
*/
function checkDelegateForContract(address delegate, address vault, address contract_)
external
view
returns (bool);
/**
* @notice 如果该地址被委托代表您对特定 token、token 的合约或整个 vault 执行操作,则返回 true
* @param delegate 代表您执行操作的热钱包
* @param contract_ 您要委托的合约的地址
* @param tokenId 您要委托的 token 的 token id
* @param vault 发出委托的冷钱包
*/
function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId)
external
view
returns (bool);
}
检查委托
dApp 或智能合约将通过检查 checkDelegateForAll 的返回值来检查委托是否已针对 vault 进行身份验证。
dApp 或智能合约将通过检查 checkDelegateForContract 的返回值来检查委托是否可以针对与 关联的合约进行身份验证。
dApp 或智能合约将通过检查 checkDelegateForToken 的返回值来检查委托是否可以针对 vault 拥有的特定 token 进行身份验证。
如果委托具有 token 级别的委托、合约级别的委托(对于该 token 的合约)或 vault 级别的委托,则委托可以对 token 执行操作。
如果委托具有合约级别的委托或 vault 级别的委托,则委托可以对合约执行操作。
为了节省 gas,如果委托检查是在智能合约级别执行的,则 dApp 预计会向智能合约提供一个提示,说明委托具有哪个级别的委托,以便智能合约可以使用 gas 效率最高的检查方法通过委托注册表进行验证。
理由
允许 vault、合约或 token 级别的委托
为了支持广泛的委托用例,拟议的规范允许 vault 委托其控制的所有资产、特定合约的资产或特定 token。这确保了 vault 对其资产的安全性进行细粒度控制,并允许围绕仅向第三方钱包授予对与其相关的资产的有限访问权限的新兴行为。
链上枚举
为了支持易于集成和采用,本规范选择包括委托的链上枚举,并承担与支持枚举相关的额外 gas 成本。链上枚举允许 dApp 前端识别任何连接的钱包可以访问的委托,并可以提供 UI 选择器。
如果没有链上枚举,dApp 将要求用户手动输入 vault,或者需要一种方法来索引所有委托事件。
安全考虑
本 EIP 的核心目的是提高安全性,并在不需要主钱包以及不需要移动主钱包持有的资产时,促进一种更安全的方式来验证钱包控制和资产所有权。可以将其视为进行“只读”身份验证的一种方式。
版权
在 CC0 下放弃版权及相关权利。
Citation
Please cite this document as:
foobar (@0xfoobar), Wilkins Chung (@wwhchung) <wilkins@manifold.xyz>, ryley-o (@ryley-o), Jake Rockland (@jakerockland), andy8052 (@andy8052), "ERC-5639: 委托注册表 [DRAFT]," Ethereum Improvement Proposals, no. 5639, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5639.