[ERC5169] クライアントスクリプトとNFTの安全な連携を可能にする仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、トークンの機能に関連する実行可能スクリプトを取得できる仕組みを提案している規格である**ERC5169**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-5169

他にも様々なERCについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

このEIPは、イーサリアムのブロックチェーン上で動くコントラクトが、自分に関連付けられた特定のスクリプトを簡単に見つけられるようにする新しい機能を提案しています。
この機能は`scriptURI()`という関数を通じて実現され、トークンに紐付いているスクリプトのインターネット上の場所（URI）を教えてくれます。

:::note info
ここで述べられている「**スクリプト**」とは、スマートコントラクトと連携して機能するプログラムやコードのことを指しています。
具体的には、ユーザーがスマートコントラクトとやり取りする際に、より便利かつ効率的に作業を行うためのクライアントサイドのアプリケーションや指示セットのことです。
これらのスクリプトは、トークンとのやり取りを拡張し、特定の機能を実行するために使用されます。

例えば、スクリプトは以下のようなものを含むことがあります。

- **miniDapp**
    - 特定のトークンに特化した小規模な分散型アプリケーション（DApp）。
    - ユーザーがそのトークンに関連する特定の機能や情報にアクセスできるようにします。
- **TokenScript**
    - トークンの操作や情報の取得をサポートするスクリプト。
    - 例えば、ブラウザウォレットから特定のトークンに関するヒントや情報を提供したり、ウォレットが通常提供しない特定のコントラクト関数（例：`mint`関数）とのやり取りを可能にしたりします。
- **スマートロック操作**
    - 特定の認証トークンを受け取った後に、ユーザーがスマートロックなどのデバイスを操作するためのJavaScript指示。

これらのスクリプトは、ユーザーがスマートコントラクトとのやり取りをより直感的かつ効率的に行えるようにするためのツールとして機能し、特に**ERC20**や**ERC721**などのトークンを使用する時のユーザーエクスペリエンスを向上させることが目的です。
:::

# 動機

このEIPは、イーサリアムのスマートコントラクトを使ってトークンに特別なユーザー機能を追加したい開発者のための提案です。
NFTのように特定のトークンに多様な機能を持たせることが人気を博していますが、そのためにはトークンのコントラクトが、ユーザーが使う特定のクライアントスクリプトに直接リンクされていることが重要です。なぜなら、そのスクリプトがユーザーの代わりに信頼できる作業を行うからです。

具体的には、このEIPはコントラクトが公式スクリプトの場所を示すURIを提供する機能を提案しています。
このURIによって、ユーザーは自分が使用しているスクリプトが正しいかどうかを確認できます。
このURIはインターネット上のどこか（例えばIPFS、GitHub、クラウドストレージなど）を指していて、`scriptURI`関数を呼び出すことでアクセスできます。
このスクリプトは、トークンと連携して以下のような様々な機能をクライアント側で実行できるようにします。

- 1つのトークンだけに特化した小さなDApp（「`miniDapp`」）。
- ブラウザウォレットからヒントや情報を提供する「`TokenScript`」。
- 通常のウォレットでは使えない特殊な機能（例えば`mint`）とのやり取りを可能にする「`TokenScript`」。
- Ledgerなどのハードウェアウォレットにダウンロードできる拡張機能。
- オーナーがウォレットで特別なトークンを受け取った後、スマートロックを操作するためのJavaScriptの指示。

このように、このEIPによって、ユーザーは自分のトークンに関連する様々な作業を、安全かつ効率的に行うためのクライアントスクリプトを確実に見つけて使用できるようになります。

## 概要

このEIPは、スマートコントラクトがクライアントスクリプトへのリンクを管理し、ユーザーが正しいスクリプトを使用していることを確認できるようにする仕組みを提案しています。
ここでのポイントは次の通りです。

### SCPrivKey
これは、スマートコントラクトを管理するための秘密鍵です。
特別にこのEIP用に新しく作る必要はなく、多くのコントラクトが既にトークンの管理用に持っている鍵を使うことができます。
この鍵でクライアントスクリプトの場所を指し示す`scriptURI`を更新します。

### newScriptURI
これはクライアントスクリプトを見つけるための場所を示すURIのリストです。
ユーザーがこのリストから正しいスクリプトを見つけて使えるようにします。

プロセスは主に2つのステップから成り立ちます。

1. **発行**
    - トークン発行者がスマートコントラクトとトークンを発行し、`SCPrivKey`を使ってコントラクトを管理します。
    - 次に、`setScriptURI`関数を呼び出してスクリプトの場所（`scriptURI`）を設定します。

2. **scriptURIの更新**
    - 発行者は新しいスクリプトを様々な場所に配置し、それを指し示す新しい`scriptURI`を作ります。
    - そして、`setScriptURI`を再度呼び出して、この新しい情報をコントラクトに更新します。

これにより、トークン発行者はスマートコントラクトを使ってクライアントスクリプトの場所を確実に管理でき、ユーザーは安心して適切なスクリプトを使用できるようになります。
これはトークンとのやり取りをより安全で効率的にし、**ERC20**や**ERC721**などのトークンでより豊かな機能を提供する基盤となります。

# 仕様

```javascript
interface IERC5169 {
    /// @dev This event emits when the scriptURI is updated, 
    /// so wallets implementing this interface can update a cached script
    event ScriptUpdate(string[] newScriptURI);

    /// @notice Get the scriptURI for the contract
    /// @return The scriptURI
    function scriptURI() external view returns(string[] memory);

    /// @notice Update the scriptURI 
    /// emits event ScriptUpdate(scriptURI memory newScriptURI);
    function setScriptURI(string[] memory newScriptURI) external;
}
```

**IERC5169**は、スマートコントラクトがクライアントスクリプトの場所を指し示す`scriptURI`を管理するためのルールと関数を定義するインターフェースです。

ここでの主要な機能とルールは以下の通りです。

### ScriptUpdate
これは、`scriptURI`が更新されるたびに発行されるイベントです。
これにより、ウォレットなどのアプリケーションは、新しいスクリプトの情報に基づいて自身を更新できます。

### scriptURI()
これはコントラクトの現在の`scriptURI`を返す関数です。
つまり、クライアントスクリプトの現在の場所を知るために使用します。

### setScriptURI
これは`scriptURI`を新しい値に更新する関数です。
この関数を使うと、`ScriptUpdate`イベントを発行し、コントラクトの内部状態が新しい`scriptURI`に更新されます。

このインターフェースを使うコントラクトは、次のような特定のルールに従う必要があります。

- コントラクトは、`address owner`という変数を持っていて、これがコントラクトのオーナー（所有者）を示します。
- コントラクトが作成されたとき（コンストラクター内で）、オーナーはコントラクトを作成した人(`msg.sender`)に設定されます。
- `setScriptURI`を使って`scriptURI`を更新するときは、必ず`ScriptUpdate`イベントを発行する必要があります。
- `setScriptURI`を実行する前に、実行しようとしている人がオーナーであるかどうかを確認する必要があります（`owner == msg.sender`）。
- `scriptURI`を使って得たスクリプトを使用するユーザーは、そのスクリプトが変更されない場所にあるか、そのURIにハッシュダイジェストが含まれているか、または別のEIPを使用して真正性が保証されていることを確認する必要があります。

これらの機能とルールにより、コントラクトはクライアントスクリプトの場所と更新を安全に管理し、ユーザーは信頼できるスクリプトを使用して**ERC20**や**ERC721**などのトークンとのやり取りができるようになります。

# 補足

この方法では、特別なセキュリティが施された中央集権的な場所でスクリプトをホストする必要がなくなります。
代わりに、IPFS（分散型ファイルストレージ）、GitHub（コードホスティングサービス）、またはクラウドストレージのような、さまざまな場所にスクリプトを置くことができます。
これにより、コントラクトの開発者は、一か所に集中したサーバーに頼らずに済み、より自由に、そして簡単にスクリプトを配布できるようになります。
これは、**ERC20**や**ERC721**などのトークンを使うスマートコントラクトの機能を拡張するスクリプトを、広い範囲で利用できるようにすることを意味します。
結果として、スクリプトのアクセスと利用がより柔軟で便利になります。

# 後方互換性

この規格は、以下の一般的に使用されているものを含む、ほとんどの既存のトークン規格と互換性があります。

- **ERC20**
- **ERC721**
- **ERC777**
- **ERC1155**

:::note info
**ERC20**については以下の記事を参考にしてください。
:::

https://chaldene.net/erc20

:::note info
**ERC721**については以下の記事を参考にしてください。
:::

https://chaldene.net/erc721

:::note info
**ERC777**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/c0fcb8f3122f66985226

:::note info
**ERC1155**については以下の記事を参考にしてください。
:::

https://chaldene.net/erc1155

# テスト

スマートコントラクトがどのようにしてクライアントスクリプトのURIを保持し、管理するか、そしてそれをどのようにテストしているか説明します。

## テストコントラクト

```javascript
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);
    }
}
```

- `ERC5169`は、クライアントスクリプトのURIを管理するためのコントラクトです。
    - これは`IERC5169`インターフェースを実装し、`Ownable`という機能を使って、オーナーだけが特定の操作を行えるように制限しています。
- コントラクトには`_scriptURI`という変数があり、これがクライアントスクリプトの場所を示すURIのリストを保持します。
- `scriptURI()`関数を使って現在設定されているURIのリストを取得できます。
- `setScriptURI()`関数は新しいURIのリストを設定するためのもので、この関数を使うと`ScriptUpdate`イベントが発行されてURIが更新されます。
    - ただし、この関数はコントラクトのオーナーのみが使えます。

## テストケース

```javascript
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());
  });
  ```


- テストは`ERC5169`コントラクトの機能を確認するために実行されます。
    - 新しいコントラクトがデプロイされると、テストケースでは特定のURIのリスト（'uri1', 'uri2', 'uri3'）を設定し、コントラクトがこれを正しく更新できるかを検証します。
- `setScriptURI()`を使ってURIを設定した後、`ScriptUpdate`イベントが適切に発行されるかを確認し、その後で`scriptURI()`関数を呼び出して、実際にURIが期待通りに更新されたかを検証します。

このテストプロセスにより、開発者はコントラクトが正しく機能していることを確認できます。

# 参考実装

Smart Token Labs（STL）が開発したオフィスドアトークンは、**NFT**を使ってドアのロック解除システムを操作する直感的な方法です。
このシステムでは、STLの従業員にNFTが発行され、そのNFTにはドアインターフェースを操作するための指示が含まれています。
この指示は、`scriptURI()`関数を通じてトークンコントラクトに添付された`TokenScript`という形で提供されます。

使い方は以下の通りです。

1. IoTデバイスからランダムな文字列（例：'Apples-5E3FA1'）を問い合わせます。
    - この文字列は、ドアを開けるための一時的なパスワードのようなものです。
2. この文字列は、ユーザーのトークンビューに表示され、「**Sign Personal**」というアクションをユーザーに促します。
    - これは、その文字列にユーザーが署名することを意味します。
3. 署名された文字列は、IoTデバイスに送り返されます。
4. IoTデバイスは、署名された文字列を受け取り、それがNFTを保有するアドレスから来たものであることを確認できれば、ドアを解錠します。

この方法では、`scriptURI()`を使ってNFTの機能をすぐに利用できるため、ユーザーはNFTを受け取った後すぐにドアのロックを操作できます。
これにより、使い勝手が大幅に向上します。

このシステムは、Smart Token Labsのオフィスドアだけでなく、多くの他の設置物で使用されています。
具体的なコントラクトと`TokenScript`の実装例は、「[**ERC-5169 コントラクト例**](https://eips.ethereum.org/assets/eip-5169/contract/ExampleContract.sol)」と「[**ERC-5169 TokenScript例**](https://eips.ethereum.org/assets/eip-5169/tokenscript/ExampleScript.xml)」で確認できます。
また、ファームウェアと完全なサンプルへのリンクは、関連する議論で提供されています。
`TokenScript`は、コントラクトから`scriptURI()`を使用してアクセスできます。

## スクリプトロケーション

NFTに関連したスクリプトをスマートコントラクトに保存するのは手軽ですが、いくつか問題があります。
スクリプトを更新するためには、頻繁に使用されることでセキュリティリスクが高まる署名キーが必要です。
また、更新が多いとスマートコントラクトの呼び出し費用がかさむこと、そして大きなスクリプトを保存すると高額な料金がかかることも問題です。

これらの問題を避けるため、トークンの機能を強化するスクリプトを外部リソースに保存するのが推奨されます。
外部リソースには、クラウドサービスのような中央集権的なもの、自分のサーバーのような私的なもの、またはインタープラネタリファイルシステムのような分散型のものがあります。

中央集権的なストレージはWeb3の理念に反するかもしれませんが、完全分散型の方法は速度やコスト、スペースの問題があるかもしれません。
この問題を解決するために、このEIPでは`ScriptURI`関数が複数のURIを返すことができ、これには中央集権的、個別にホストされた、分散型の場所が混在することがあります。

このEIPはスクリプトの形式を指定しませんが、スクリプト自体が他のスクリプトやデータソースへのリンクを含むことを許可し、遅延読み込みなどの高度なトークンスクリプトの展開を可能にします。
このような二次データソースの整合性は、スクリプトの形式に依存するため、開発者が注意深く考慮する必要があります。

# セキュリティ考慮事項

## サーバーが関与する場合
クライアントスクリプトがブロックチェーンノードだけでなく、サーバーAPIも利用する場合、そのサーバーAPIの信頼性が疑われることがあります。
このEIPでは、APIアクセスポイントの真実性を保証する方法は提供されていません。
つまり、クライアントスクリプトが信頼できると仮定されていれば、それは任意のサーバーAPIを利用してトークンの機能を実行することができるということです。
しかし、これにはサーバーAPIが信頼できないかもしれないというリスクが伴います。

## scriptURIに整合性情報が含まれていない場合
スクリプトがその内容のハッシュ値で表されるURIに保存されていない場合、そのスクリプトの真実性と整合性をどう保証するかが問題になります。
この問題に対処するために、別のEIP「**クライアントスクリプトの真正性に関するEIP**」があります。
これはデジタル署名を利用して、スクリプトの真正性と整合性を効率的かつ簡潔に保証する方法を案内します。

:::note info
クライアントスクリプトやサーバーAPIを安全に使用するためには、信頼性の確保が重要です。
また、スクリプトの保存場所や形式によっては、追加の保護手段が必要になることがあるということを理解しておく必要があります。
:::

# 引用

James (@JamesSmartCell), Weiwu (@weiwu-zhang), "ERC-5169: Client Script URI for Token Contracts," Ethereum Improvement Proposals, no. 5169, May 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5169.

# 最後に

今回は「トークンの機能に関連する実行可能スクリプトを取得できる仕組みを提案している規格である**ERC5169**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58


https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4