ERC-5791: 物理支持代币
用于将 ERC-721 NFT 的所有权链接到物理芯片的最小接口
Authors | 2pmflow (@2pmflow), locationtba (@locationtba), Cameron Robertson (@ccamrobertson), cygaar (@cygaar), Brian Weick (@bweick), vectorized (@vectorized), djdabs (@djdabs) |
---|---|
Created | 2022-10-17 |
Discussion Link | https://ethereum-magicians.org/t/physical-backed-tokens/11350 |
Requires | EIP-191, EIP-721 |
摘要
该标准是 ERC-721 的扩展。 它提出了一个最小接口,用于使 ERC-721 NFT 能够被“物理支持”,并由 NFT 物理对应物的所有者拥有。
动机
NFT 收藏家喜欢收集数字资产并在网上与他人分享。 但是,目前还没有这样的标准来展示作为具有验证过的真实性和所有权的 NFT 的物理资产。 现有的解决方案是分散的,并且往往容易受到以下至少一项的影响:
-
物理物品的所有权与 NFT 的所有权是分离的。
-
验证物理物品的真实性需要受信任的第三方的操作(例如 StockX)。
规范
本文档中的关键词“必须”,“禁止”,“需要”,“应该”,“不应该”,“推荐”,“可以”和“可选”应按照 RFC 2119 中的描述进行解释。
要求
这种方法要求物理项目必须附有一个芯片,该芯片应该是安全的并且可以发出真实性信号:
- 该芯片可以安全地生成和存储非对称密钥对;
- 该芯片可以使用先前生成的非对称密钥对的私钥对消息进行签名;
- 该芯片公开公钥;和
- 私钥不能通过设计提取或复制
该方法还要求合约使用 ERC-721 的帐户绑定实现(所有 ERC-721 函数都必须抛出转移,例如 ERC-721 中引用的“只读 NFT 注册表”实现)。 这确保了物理项目的所有权是启动转移和管理 NFT 所有权所必需的,通过下面描述的此接口中引入的新函数。
方法
每个 NFT 在概念上都链接到一个物理芯片。
当 chipId
与 tokenId
配对时,将发出一个事件。 这让下游索引器知道 NFT 集合中的哪些芯片地址映射到哪些代币。 如果没有将其 token id 链接到特定的芯片,则无法铸造 NFT。
该接口包括一个名为 transferToken
的函数,如果传入由芯片签名的有效签名,则该函数将 NFT 转移到函数调用者。 有效的签名必须遵循 ERC-191 和 EIP-2(s 值限制)中规定的方案,其中要签名的数据包括目标接收者地址(函数调用者)、芯片地址、区块时间戳以及用于实现中其他自定义逻辑的任何额外参数。
该接口还包括其他函数,这些函数可以让任何人验证物理项目中的芯片是否正在支持集合中现有的 NFT。
接口
interface IERC5791 {
/// @dev 返回给定芯片地址的 ERC-721 `tokenId`。
/// 如果 `chipId` 尚未与 `tokenId` 配对,则会恢复。
/// 为了最小化,如果 `tokenId` 不存在,这将不会恢复。
/// 如果需要检查代币是否存在,外部合约可以
/// 调用 `ERC721.ownerOf(uint256 tokenId)` 并检查它是否通过或恢复。
/// @param chipId 嵌入在物理物品中的芯片的地址
/// (从芯片的公钥计算得出)。
function tokenIdFor(address chipId) external view returns (uint256 tokenId);
/// @dev 如果 `signature` 是由分配给 `tokenId` 的芯片签名的,则返回 true,否则返回 false。
/// 如果 `tokenId` 尚未与芯片配对,则会恢复。
/// 为了最小化,如果 `tokenId` 不存在,这将不会恢复。
/// 如果需要检查代币是否存在,外部合约可以
/// 调用 `ERC721.ownerOf(uint256 tokenId)` 并检查它是否通过或恢复。
/// @param tokenId ERC-721 `tokenId`。
/// @param data 由芯片签名的任意字节字符串以生成 `signature`。
/// @param signature 芯片的 EIP-191 签名,用于检查。
function isChipSignatureForToken(uint256 tokenId, bytes calldata data, bytes calldata signature)
external
view
returns (bool);
/// @dev 将代币转移到该地址。
/// 返回转移的 `tokenId`。
/// @param to 接收者。 动态允许更容易地转移到金库。
/// @param chipId 正在转移的芯片的芯片 ID(地址)。
/// @param chipSignature 芯片的 EIP-191 签名,用于授权转移。
/// @param signatureTimestamp `chipSignature` 中使用的时间戳。
/// @param useSafeTransferFrom 是否应使用 ERC-721 的 `safeTransferFrom`,
/// 而不是 `transferFrom`。
/// @param extras 其他数据,可用于其他逻辑/上下文
/// 当 PBT 被转移时。
function transferToken(
address to,
address chipId,
bytes calldata chipSignature,
uint256 signatureTimestamp,
bool useSafeTransferFrom,
bytes calldata extras
) external returns (uint256 tokenId);
/// @dev 当 `chipId` 与 `tokenId` 配对时发出。
/// 在分配期间,`tokenId` 可能不一定存在。
/// 索引器可以将此事件与 {ERC721.Transfer} 事件组合起来,以
/// 推断哪些代币存在并且与芯片 ID 配对。
event ChipSet(uint256 indexed tokenId, address indexed chipId);
}
为了帮助识别 ERC-721 代币通过此 EIP 实现物理绑定:在调用 ERC-165 的 function supportsInterface(bytes4 interfaceID) external view returns (bool)
并使用 interfaceID=0x4901df9f
时,实现此 EIP 的合约必须返回 true。
铸币接口取决于实现。 铸造的 NFT 的所有者应该是物理芯片的所有者(此身份验证可以使用为 transferToken
定义的签名方案来实现)。
理由
该解决方案的目的是成为将物理物品链接到数字 NFT 的最简单途径,而无需中心化机构。
该接口包括一个 transferToken
函数,该函数对签名方案具有倾向性,以便启用下游聚合器类的产品,该产品将来支持任何实现此 EIP 的 NFT 的转移。
transferToken
中包含芯片地址,以允许智能合约验证签名。 这确保了物理支持的代币中的芯片不会严格绑定到实现 secp256k1 签名,而是可以使用各种签名方案,例如 P256 或 BabyJubJub。
超出范围
以下是一些有意不在此 EIP 范围内的外围问题:
- 信任特定 NFT 集合的芯片地址实际上映射到嵌入在物品中的物理芯片,而不是声称是芯片的任意 EOA
- 确保芯片不会变质或损坏
- 确保芯片始终连接到物理项目
- 等等。
目前正在并行解决这些挑战。
将代币 ID 映射到芯片地址也超出了范围。 这可以通过多种方式完成,例如,让合约所有者在铸币前预先设置此映射,或者将 (tokenId, chipId)
元组传递到由合约信任的地址预先签名的铸币函数中,或者通过在受信任的注册表中查找,或者通过在铸币时首先分配代币 ID,等等。
此外,物理项目的所有者可以将 NFT 转移给其他人拥有的钱包(通过向该人发送芯片签名以供使用)。 我们仍然认为 NFT 是物理支持的,因为所有权管理与物理项目相关联。 这可以解释为项目的所有者暂时将项目借给其他人,因为 (1) 项目的所有者必须参与此事,作为使用芯片签名的人,并且 (2) 项目的所有者可以随时收回 NFT 的所有权。
向后兼容性
此提案在 API 级别上与 ERC-721 向后兼容。 如上所述,为了使代币获得物理支持,合约必须使用 ERC-721 的帐户绑定实现(所有 ERC-721 函数的转移都必须抛出),以便转移通过此处引入的新函数进行,这需要芯片签名。
参考实现
以下是如何验证传输事件中的芯片签名的代码段。
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
/// @dev 将分配给 `chipId` 的 `tokenId` 转移到 `to`。
function transferToken(
address to,
address chipId,
bytes memory chipSignature,
uint256 signatureTimestamp,
bool useSafeTransfer,
bytes memory extras
) public virtual returns (uint256 tokenId) {
tokenId = tokenIdFor(chipId);
_validateSigAndUpdateNonce(to, chipId, chipSignature, signatureTimestamp, extras);
if (useSafeTransfer) {
_safeTransfer(ownerOf(tokenId), to, tokenId, "");
} else {
_transfer(ownerOf(tokenId), to, tokenId);
}
}
/// @dev 验证 `chipSignature` 并更新 `chipId` 未来签名的随机数。
function _validateSigAndUpdateNonce(
address to,
address chipId,
bytes memory chipSignature,
uint256 signatureTimestamp,
bytes memory extras
) internal virtual {
bytes32 hash = _getSignatureHash(signatureTimestamp, chipId, to, extras);
if (!SignatureCheckerLib.isValidSignatureNow(chipId, hash, chipSignature)) {
revert InvalidSignature();
}
chipNonce[chipId] = bytes32(uint256(hash) ^ uint256(blockhash(block.number - 1)));
}
/// @dev 返回要由 `chipId` 签名的摘要。
function _getSignatureHash(uint256 signatureTimestamp, address chipId, address to, bytes memory extras)
internal
virtual
returns (bytes32)
{
if (signatureTimestamp > block.timestamp) revert SignatureTimestampInFuture();
if (signatureTimestamp + maxDurationWindow < block.timestamp) revert SignatureTimestampTooOld();
bytes32 hash = keccak256(
abi.encode(address(this), block.chainid, chipNonce[chipId], to, signatureTimestamp, keccak256(extras))
);
return ECDSA.toEthSignedMessageHash(hash);
}
安全注意事项
传递给 transferToken
的 ERC-191 签名需要在其签名数据中包含函数调用者的地址,以便签名不能用于重放攻击。 它还需要最近的区块时间戳,以便恶意芯片所有者无法预先生成签名以在短时间内使用(例如,在物理项目的所有者更改之后)。 建议在生成签名时使用非确定性的 chipNonce
。
此外,人们对代币是否获得物理支持的信任程度取决于物理芯片的安全性,如上所述,这超出了此 EIP 的范围。
版权
版权及相关权利通过 CC0 放弃。
Citation
Please cite this document as:
2pmflow (@2pmflow), locationtba (@locationtba), Cameron Robertson (@ccamrobertson), cygaar (@cygaar), Brian Weick (@bweick), vectorized (@vectorized), djdabs (@djdabs), "ERC-5791: 物理支持代币 [DRAFT]," Ethereum Improvement Proposals, no. 5791, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5791.