[ERC2477] トークンのメタデータが改ざんされていないことを検証する仕組みを理解しよう！

# はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、トークンのメタデータが改ざんされていないことを検証できる仕組みを提案している**ERC2477**についてまとめていきます！

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

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

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

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

# 概要

**ERC2477**は、トークンのメタデータが取得された時に、その内容が改ざんされていないことを検証できる仕組みを提案しています。
これは、Web2におけるW3CのSubresource Integrity（SRI）仕様に対応するWeb3版のようなものです。

:::note info
### W3Cとは？

W3C（World Wide Web Consortium） は、ウェブ技術の国際的な標準を決める組織です。
HTMLやCSS、WebAuthnなど、Webサイトを作るときに使っている基盤技術の多くは、W3Cによって定義されています。

W3Cが定める仕様は、ブラウザやWebサーバーが共通のルールで動作できるようにするための「決まりごと」です。
つまり、異なる会社のブラウザでもWebサイトが同じように表示されたり動作したりするのは、W3Cが標準化を行っているおかげです。

### Subresource Integrity（SRI）とは？

Subresource Integrity（SRI） は、Webページが外部から読み込むスクリプトやスタイルシート（＝サブリソース）について、それが改ざんされていないかどうかを検証する仕組みです。

例えば、CDN（コンテンツ配信ネットワーク）から読み込むJavaScriptがあるとします。
何者かがCDN上のファイルを改ざんしていた場合、そのままではWebサイトに悪意のあるコードが実行されてしまう可能性があります。

そこでSRIを使うと、スクリプトの正しいハッシュ値（データの要約）をHTMLに書いておくことで、読み込んだファイルがそのハッシュと一致するかを自動で検証できます。
一致しなければ、そのスクリプトは読み込まれません。
:::

**ERC2477**では、スマートコントラクトに `tokenURIIntegrity` および `tokenURISchemaIntegrity` という2つの関数を持つインターフェースを定義しています。
これにより、クライアント（例えば、NFTマーケットプレイスやウォレット）は、トークンに紐づくメタデータが予期せぬ変更や改ざんを受けていないかを確認できるようになります。

# 動機

トークンはさまざまな用途で使われており、その多くがオフチェーンの資産を表現・追跡・参照する目的で利用されています。
例えば、モバイルアプリにおけるゲーム内アイテムや、グローバルなサプライチェーンにおける高級腕時計などの物理商品などが挙げられます。

これらのトークンには、通常、URI形式（**RFC3986**）でメタデータが紐づけられており、以下のような既存の標準規格でサポートされています。

* **ERC721** メタデータ拡張（**ERC721Metadata**）
* **ERC1155** メタデータ拡張（**ERC1155MetadataURI**）
* **ERC1046** **ERC20**メタデータ拡張

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

https://qiita.com/cardene/items/90e8e7f6f1646f8c8cfe

**ERC2477**では、完全にオンチェーンでメタデータを保存する手段（`data` URI: **RFC2397**）や、IPFSのようなコンテンツアドレス方式の利用も可能ですが、現実的にはほとんどの実装が通常のURLを利用しています（例外としてThe SandboxはIPFS URIを使用しています）。
しかし、通常のURLでは、取得されたコンテンツが正しいものか、改ざんされていないかといった保証がありません。

**ERC2477**は、このような不確実性を排除し、トークンメタデータの正当性と不変性を保証する手段を提供するために設計されています。

# デザイン

**ERC2477**では、トークンに紐づくメタデータやスキーマの整合性（改ざんされていないこと）を保証するために、いくつかのアプローチが示されています。

## Approach A（従来の方法）

トークンは単にメタデータのURLを参照します。
メタデータが `$schema` キーでスキーマをURIとして参照することもありますが、これは標準化されていません。

* メリット
    * 実装が簡単。
* デメリット
    * メタデータもスキーマも改ざん可能で、整合性が保証されない。

```
TokenID → Metadata → Schema（改ざん検知なし）
```

## Approach B（メタデータの整合性のみ保証）

**ERC2477**では、メタデータのハッシュをオンチェーンに保存することで整合性を保証できます。
スキーマの参照は任意です。

* メリット
    * メタデータの改ざんは検出できる。
* デメリット
    * スキーマの整合性までは保証されない。

```
TokenID → Metadata（整合性あり）→ Schema（任意）
↑
tokenURIIntegrity（メタデータのハッシュ）
```

## Approach C（メタデータとスキーマ両方を厳密に検証）

メタデータが拡張されたJSON Schemaを使ってスキーマを参照し、さらにスキーマに対しても整合性保護を適用します。
これにより、メタデータとスキーマの両方が改ざんされていないことを保証できます。

* メリット
    * 最も堅牢な整合性保証。
* デメリット
    * 複雑性が増す。

```
TokenID → Metadata（整合性あり）→ Schema（整合性あり）
↑                            ↑
tokenURIIntegrity            tokenURISchemaIntegrity
```

## Approach D（メタデータは通常参照、スキーマはオンチェーンで整合性保証）

メタデータがスキーマを通常の方法で参照しますが、オンチェーンでスキーマのハッシュを保持することで整合性を確保します。

* メリット
    * メタデータとスキーマの整合性が保証される。
* デメリット
    * メタデータからのスキーマ参照自体には保証がない。

```
TokenID → Metadata（整合性あり）→ Schema（整合性あり）
↑                            ↑
tokenURIIntegrity            tokenURISchemaIntegrity
```

## Approach E（メタデータもスキーマも整合性付きで相互参照）

メタデータはスキーマを整合性付きで参照し、同時にオンチェーンでもスキーマのハッシュを保持します。
両者に対して改ざんの検知が可能です。

* メリット
    * 整合性と信頼性が最も高い設計。
* デメリット
    * 設計と実装の手間が最も大きい。

```
TokenID → Metadata（整合性あり）→ Schema（整合性あり）
↑                            ↑
tokenURIIntegrity            tokenURISchemaIntegrity
```

このように、**ERC2477**ではトークンのメタデータとそのスキーマに対して、どの程度の整合性保証を提供するかによって、柔軟に選べる複数の設計が提示されています。
ユースケースに応じて適切なアプローチを選択できます。

# 仕様

**ERC2477**は、トークンに関連付けられたメタデータやスキーマの改ざんを防ぐための整合性チェックの仕組みを定めたインターフェースを定義します。

**ERC2477**を実装するスマートコントラクトは、`ERC2477` インターフェースを実装しなければなりません。
このインターフェースには、トークンのメタデータおよびそのスキーマの暗号学的ハッシュ値を取得するための2つの関数 `tokenURIIntegrity` と `tokenURISchemaIntegrity` が含まれます。
これらの関数は、それぞれの対象（メタデータまたはスキーマ）に対するハッシュ値（digest）とハッシュアルゴリズムの名称（hashAlgorithm）を返します。
ハッシュアルゴリズムの種類は実装者が自由に選択できますが、クライアントは少なくとも `sha256` をサポートしなければなりません。
スマートコントラクトは、変更があった際に通知するためのログ出力（event）を実装してもよいし、省略しても問題ないです。

## メタデータ

メタデータは、任意で `$schema` と `$schemaIntegrity` を含む形式にできます。
`$schemaIntegrity` には、対象スキーマのハッシュとアルゴリズム情報が含まれます。
以下のようなJSON形式が例として挙げられます。

```json
{
  "$schema": "https://example.com/schema.json",
  "$schemaIntegrity": {
    "digest": "abc123...",
    "hashAlgorithm": "sha256"
  }
}
```

## クライアント

**ERC2477** に対応するクライアントは、最低でも `sha256` をサポートする必要があります。

## 注意点

EIPのメタデータ仕様の制限により、**ERC721**や**ERC1155**を「必須」と記載しているが、実際には `tokenURI(uint id)` のような関数を持つ任意のトークン規格で使用可能です。

## インターフェース

```js:ERC2477.sol
// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.7;

/// @title ERC-2477 Token Metadata Integrity
/// @dev See https://eips.ethereum.org/EIPS/eip-2477
/// @dev The ERC-165 identifier for this interface is 0x832a7e0e
interface ERC2477 /* is ERC165 */ {
    /// @notice Get the cryptographic hash of the specified tokenID's metadata
    /// @param  tokenId       Identifier for a specific token
    /// @return digest        Bytes returned from the hash algorithm, or "" if not available
    /// @return hashAlgorithm The name of the cryptographic hash algorithm, or "" if not available
    function tokenURIIntegrity(uint256 tokenId) external view returns(bytes memory digest, string memory hashAlgorithm);

    /// @notice Get the cryptographic hash for the specified tokenID's metadata schema
    /// @param  tokenId       Identifier for a specific token
    /// @return digest        Bytes returned from the hash algorithm, or "" if not available
    /// @return hashAlgorithm The name of the cryptographic hash algorithm, or "" if not available
    function tokenURISchemaIntegrity(uint256 tokenId) external view returns(bytes memory digest, string memory hashAlgorithm);
}
```

### `tokenURIIntegrity`

```javascript
function tokenURIIntegrity(uint256 tokenId) external view returns(bytes memory digest, string memory hashAlgorithm);
```

トークンのメタデータに対する整合性ハッシュを取得する関数。
この関数は、指定された `tokenId` に紐づくメタデータ文書の暗号学的ハッシュ値と、そのハッシュに使用されたアルゴリズムの名称を返します。
この値を検証することで、オフチェーンで取得されたメタデータが改ざんされていないことを確認できます。

**引数**

* `tokenId`
    * 対象となるトークンの識別子。

**戻り値**

* `digest`
    * メタデータのハッシュ値。
* `hashAlgorithm`
    * 使用されたハッシュアルゴリズム名（例：sha256）。

### `tokenURISchemaIntegrity`

```javascript
function tokenURISchemaIntegrity(uint256 tokenId) external view returns(bytes memory digest, string memory hashAlgorithm);
```

トークンのメタデータが参照するスキーマに対する整合性ハッシュを取得する関数。
この関数は、`tokenId` に紐づくメタデータのスキーマの暗号学的ハッシュ値と、そのハッシュに使用されたアルゴリズムの名称を返します。
これにより、メタデータが依存するスキーマが改ざんされていないかを確認できます。

**引数**

* `tokenId`
    * 対象となるトークンの識別子。

**戻り値**

* `digest`
    * スキーマのハッシュ値。
* `hashAlgorithm`
    * 使用されたハッシュアルゴリズム名（例：sha256）。

# 補足

## 関数名とパラメータの命名

**ERC2477** では、「整合性検証」という目的をわかりやすく示すために、W3Cの Subresource Integrity（SRI）仕様と同様に `integrity` という名前を使っています。
SRIに慣れている人にとっても親しみやすくする意図があります。

## 戻り値の形式

SRIの `integrity` 属性は、`(ハッシュ関数名, ダイジェスト, オプション)` の形式を使いますが、**ERC2477**ではオプションを除いた `(digest, hashAlgorithm)` のタプルを返します。
SRIのオプションは未使用であり、将来の形式が不明なため、互換性のためにあえて除外しています。

さらに、`digest` を先に返すのは、オンチェーンでこの値だけを使うケースが多く、処理効率の観点から最適化されています。

## 戻り値の型

`digest` はバイト配列（`bytes`）であり、ハッシュの長さが異なっていても対応できます。
Ethereumの環境ではbase64ではなく `bytes` を使う方が自然です。
`hashAlgorithm` は文字列（`string`）で、現在統一的な命名規則はありませんが、SRIと同じく `sha256` などの簡易な名前を使用しています。

## ハッシュ長の選定理由

クライアントは少なくとも `SHA-256` をサポートすべきですが、`SHA-384` や `SHA-512` のサポートは任意です。
SRI仕様では3つ全てが必須ですが、Ethereum上では `SHA-256` が最も簡単かつ安価に扱えるため、現実的な選択肢として `SHA-256` のみを必須としています。

一方、`SHA-1` は複数の衝突が既知であり、安全性が失われているため、この仕様では推奨されません。
アメリカ国家安全保障局（NSA）では、機密性の高いネットワークでの利用には384ビット以上のハッシュを求めていることも考慮されています。

現在では、`SHA-256` と `0xcert` による資産認証方式がメタデータの保護に使われると予想されています。

## URLパラメータによる代替手法を採用しなかった理由

`?integrity=XXX` のようなURL末尾への付与は、理論上は可能ですが採用されていません。
主な理由は以下の通りです。

* Webでも同様の課題があり、SRIではURLとは別に独立したフィールドを採用している。
* オンチェーンで使う際、URL文字列の解析よりハッシュを直接保持した方が簡単。
* 全てのURIがパラメータ追加に適しているとは限らない。

この方針は `tokenURISchemaIntegrity` にも同様に適用されています。
JSON-LDはスキーマをリンクできますが、整合性検証はできません。
そのため、新たな仕様や構文変更を加えるのではなく、明示的に `tokenURISchemaIntegrity` を追加する設計としています。

# 互換性

**ERC2477** は、既存のメタデータ規格と競合せず、拡張的に機能します。

* **ERC721**および**ERC1155**は、どちらもURIとJSONスキーマによるメタデータ記述に対応しており、**ERC2477**はそれらと完全に互換性があります。
* **ERC1523** は、**ERC721**を拡張して保険ポリシーのトークン化を行う提案ですが、**ERC721**に準拠しているため、**ERC2477**とも自然に互換性があります。
* **ERC1046** は**ERC20**トークン向けのメタデータ拡張です。現在の**ERC2477**は `tokenId` を前提としているため、この標準とは直接互換性はありません。今後**ERC1046**が標準化された場合には、`tokenId` を不要にした別の**ERC2477**派生提案が歓迎されるでしょう。
* 同様に、**ERC918**も**ERC20**の拡張としてメタデータを含む設計を提案しており、**ERC1046**と同様の対応が期待されます。

# 引用

Kristijan Sedlak (@xpepermint), William Entriken <github.com@phor.net>, Witek Radomski <witek@enjin.io>, "ERC-2477: Token Metadata Integrity [DRAFT]," Ethereum Improvement Proposals, no. 2477, January 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2477.

# 最後に

今回は「トークンのメタデータが改ざんされていないことを検証できる仕組みを提案している**ERC2477**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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