Alert Source Discuss
Standards Track: ERC

ERC-5169: Token 合约的客户端脚本 URI

添加一个 scriptURI,指向与 token 功能相关的可执行脚本。

Authors James (@JamesSmartCell), Weiwu (@weiwu-zhang)
Created 2022-05-03
Requires EIP-20, EIP-165, EIP-721, EIP-777, EIP-1155

摘要

本 EIP 提供了一个合约接口,添加了一个 scriptURI() 函数,用于定位与 token 相关的可执行脚本。

动机

通常,智能合约的作者希望通过客户端脚本向他们的 token 提供一些用户功能。这个想法在功能丰富的 NFT 中变得流行。重要的是,token 的合约与其客户端脚本相关联,因为客户端脚本可以执行受信任的任务,例如为用户创建交易。

本 EIP 允许用户通过合约提供 URI 到官方脚本,并通过调用 token 合约本身 (scriptURI) 来确保他们正在使用正确的脚本。此 URI 可以是任何符合 RFC 3986 的 URI,例如指向 IPFS multihash、GitHub gist 或云存储提供商的链接。每个实现此 EIP 的合约都实现了一个 scriptURI 函数,该函数返回客户端脚本的下载 URI。该脚本为托管 token 提供客户端可执行文件。此类脚本的示例可以是:

  • ‘miniDApp’,这是一个为单个 token 定制的精简版 DApp。
  • ‘TokenScript’,它从浏览器钱包提供 TIPS。
  • ‘TokenScript’,允许用户与通常不由钱包提供的合约函数进行交互,例如“mint”函数。
  • 扩展,可以使用扩展框架(如 Ledger)下载到硬件钱包。
  • JavaScript 指令,用于在所有者在其钱包中收到授权 token 后操作智能锁。

概述

考虑到上述讨论,我们概述了本 EIP 提出的解决方案。为此,我们考虑以下变量:

  • SCPrivKey:管理实现此 EIP 的智能合约的私钥签名密钥。请注意,这不必是专门为此 EIP 添加的新密钥。今天制作的大多数智能合约已经有一个管理密钥来管理发行的 token。它可以用于更新 scriptURI

  • newScriptURI:用于查找客户端脚本的不同方式的 URI 数组。

我们可以描述 scriptURI 功能的生命周期:

  • 发行
  1. token 发行者发行 token 和实现此 EIP 的智能合约,智能合约的管理密钥为 SCPrivKey
  2. token 发行者使用 scriptURI 调用 setScriptURI
  • 更新 scriptURI
  1. token 发行者将所需的 script 存储在所有新的 URI 位置,并基于此构造一个新的 scriptURI 结构。
  2. token 发行者使用新的 scriptURI 结构调用 setScriptURI

规范

本文档中的关键字“MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”,“RECOMMENDED”,“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。

我们使用 string[] 定义一个 scriptURI 元素。 基于此,我们定义以下智能合约接口:

interface IERC5169 {
    /// @dev 此事件在 scriptURI 更新时发出,
    /// 因此实现此接口的钱包可以更新缓存的脚本
    event ScriptUpdate(string[] newScriptURI);

    /// @notice 获取合约的 scriptURI
    /// @return scriptURI
    function scriptURI() external view returns(string[] memory);

    /// @notice 更新 scriptURI
    /// 发出事件 ScriptUpdate(scriptURI memory newScriptURI);
    function setScriptURI(string[] memory newScriptURI) external;
}

该接口必须在以下约束下实现:

  • 实现 IERC5169 的智能合约必须在其状态下存储变量 address owner

  • 实现 IERC5169 的智能合约必须在其构造函数中设置 owner=msg.sender

  • setScriptURI 函数更新 scriptURI 时,必须发出 ScriptUpdate(...) 事件。

  • setScriptURI(...) 函数必须在执行其逻辑和更新任何状态 之前 验证 owner == msg.sender

  • setScriptURI(...) 函数必须更新其内部状态,使得 currentScriptURI = newScriptURI

  • scriptURI() 函数必须返回 currentScriptURI 状态。

  • scriptURI() 函数可以实现为 pure 或 view。

  • scriptURI 获知的脚本的任何用户必须验证该脚本是否位于不可变的位置,其 URI 包含其哈希摘要,或者它实现了单独的 Authenticity for Client Script EIP,该 EIP 使用签名而不是摘要来断言真实性。

原理

此方法避免了构建安全和经过认证的集中式托管的需求,并允许脚本托管在任何地方:IPFS、GitHub 或云存储。

向后兼容性

此标准与大多数现有的 token 标准向后兼容,包括以下常用的标准:

测试用例

测试合约


import "@openzeppelin/contracts/access/Ownable.sol";
import "./IERC5169.sol";
contract ERC5169 is IERC5169, Ownable {
    string[] private _scriptURI;
    function scriptURI() external view override returns(string[] memory) {
        return _scriptURI;
    }

    function setScriptURI(string[] memory newScriptURI) external onlyOwner override {
        _scriptURI = newScriptURI;

        emit ScriptUpdate(newScriptURI);
    }
}

测试用例


const { expect } = require('chai');
const { BigNumber, Wallet } = require('ethers');
const { ethers, network, getChainId } = require('hardhat');

describe('ERC5169', function () {
  before(async function () {
    this.ERC5169 = await ethers.getContractFactory('ERC5169');
  });

  beforeEach(async function () {
    // targetNFT
    this.erc5169 = await this.ERC5169.deploy();
  });

  it('Should set script URI', async function () {
    const scriptURI = [
      'uri1', 'uri2', 'uri3'
    ];

    await expect(this.erc5169.setScriptURI(scriptURI))
      .emit(this.erc5169, 'ScriptUpdate')
      .withArgs(scriptURI);
    
    const currentScriptURI = await this.erc5169.scriptURI();

    expect(currentScriptURI.toString()).to.be.equal(scriptURI.toString());
  });
  

参考实现

一个直观的实现是 STL 办公室门禁 token。此 NFT 被铸造并转移给 STL 员工。通过 scriptURI() 函数附加到 token 合约的 TokenScript 包含有关如何操作门禁界面的说明。这采用以下形式:

  1. 查询挑战字符串(来自 IoT 界面的随机消息,例如“Apples-5E3FA1”)。

  2. 在 Token View 上接收并显示挑战字符串,并请求“Sign Personal”。

  3. 在获得挑战字符串的签名后,将其发送回 IoT 设备。

  4. 如果 ec-recovered 地址持有 NFT,则 IoT 设备将解锁门。

有了 scriptURI(),用户体验大大增强,因为用户的流程是:

  1. 接收 NFT。

  2. 立即在钱包中使用经过身份验证的 NFT 功能。

包含合约、TokenScript 和 IoT 固件的项目正在被 Smart Token Labs 办公室门禁和许多其他安装使用。示例实现合约:ERC-5169 Contract Example 和 TokenScript:ERC-5169 TokenScript Example。固件和完整示例的链接可以在标题中链接的相关讨论中找到。 相关的 TokenScript 可以使用 scriptURI() 从合约中读取。

脚本位置

虽然促进与 NFT 相关的特定脚本使用的最直接的解决方案,显然是将此类脚本存储在智能合约上。然而,这有几个缺点:

  1. 需要智能合约签名密钥来进行更新,导致密钥更加暴露,因为它被更频繁地使用。

  2. 更新需要智能合约交互。如果需要频繁更新,智能合约调用可能会成为一个昂贵的障碍。

  3. 存储费用。如果脚本很大,则更新脚本的成本会很高。客户端脚本通常比智能合约大得多。

由于这些原因,将易失性数据(例如 token 增强功能)存储在外部资源上是有意义的。这种外部资源可以集中托管,例如通过云提供商,也可以通过私有服务器私下托管,或者分散托管,例如星际文件系统。

虽然分散功能的集中式存储有悖于 web3 的精神,但完全分散的解决方案可能会带来速度、价格或空间方面的损失。本 EIP 通过允许函数 ScriptURI 返回多个 URI 来处理此问题,这些 URI 可能是集中式、单独托管和分散位置的混合。

虽然本 EIP 没有规定存储脚本的格式,但脚本本身可以包含指向多个其他脚本和数据源的指针,从而允许扩展 token 脚本的高级方法,例如延迟加载。 此类辅助数据源的完整性处理取决于脚本的格式。

安全考虑

当涉及服务器时

当客户端脚本不纯粹依赖于与区块链节点的连接,而是调用服务器 API 时,服务器 API 的可信度会受到质疑。本 EIP 不提供任何机制来断言 API 访问点的真实性。相反,只要客户端脚本是受信任的,就假定它可以调用任何服务器 API 以执行 token 功能。这意味着客户端脚本可能不信任服务器 API 访问点。

当 scriptURI 不包含完整性(哈希)信息时

我们单独撰写了 Authenticity for Client Script EIP,以指导如何有效且简洁地使用数字签名,以确保未存储在作为脚本本身的摘要的 URI 中的脚本的真实性和完整性。

版权

版权和相关权利通过 CC0 放弃。

Citation

Please cite this document as:

James (@JamesSmartCell), Weiwu (@weiwu-zhang), "ERC-5169: Token 合约的客户端脚本 URI," Ethereum Improvement Proposals, no. 5169, May 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5169.