[ERC5791] NFTの所有権を物理チップに紐付ける仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、NFTの所有権を物理チップにリンクするための最小限のインターフェースを提案している**ERC5791**についてまとめていきます！

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

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

:::note warn
**ERC5008**は現在（2023年9月3日）では「**Draft**」段階です。
:::

# 概要

この規格は、**ERC721**の規格を基にしています。
**ERC721**は、非代替トークン（NFT）として知られるもので、独自の特性や所有権を持つデジタルアセットを表現するための枠組みです。

提案されている拡張規格は、「**物理的に裏付けられた**」NFTを可能にするものです。
このアイデアの背後には、現実世界の物体（例えば、絵画やコレクターアイテム）とNFTを結びつけるという概念があります。
つまり、その物体を所有する人が、NFTも所有するという関連性が生まれるわけです。

この新しい規格によって、物理的なアイテムの所有者とNFTの所有者の間に、直接的な関係が生まれます。
例えば、ある人が絵画を所有している場合、その絵画に対応するNFTも自動的に所有することになります。
逆に、NFTを持つ人は、それに関連付けられた物理的なアイテムの所有権も持つことになります。

この提案によって、デジタルアセットと現実世界の物体との間に新しいつながりが生まれ、コレクションやオーナーシップの概念がより複雑で興味深いものになる可能性があります。

# 動機

NFTコレクターは、デジタルアセットを集めてオンラインで共有することを楽しんでいます。
しかし、現在、物理的なアセットをNFTを使用して正当な所有権を持っていると認証されながら、オンラインで展示するためのルールや基準が定まっていません。
既存の解決策はバラバラであり、以下のいずれかの問題があります。

1. 物理的なアイテムの実際の所有者と、それに関連するNFTの所有者が一致しないこと。
2. 物理的なアイテムの正当性を確認するために、信頼できる第三者（例：**StockX**など）の介入が必要であること。

デジタルアセットのNFTコレクションは盛んですが、物理的なアセットをNFTとして所有権が確認された状態でオンラインで共有するための一貫した方法がないのが現状です。
また、既存の解決策はばらばらであり、物理的なアイテムの所有者とNFTの所有者がズレていることや、物理的なアイテムの正当性を確認するために信頼できる第三者の介入が必要であるという問題があります。

# 仕様

## 条件

この方法では、物理的なアイテムには特定の要件を満たすチップが取り付けられている必要があります。

1. チップは、**ECDSA** **secp256k1**という暗号方式に基づく非対称鍵ペアを安全に生成・保存できる能力を持つこと。
2. チップは、以前に生成した秘密鍵を使用してメッセージに署名することができること。
3. チップは、公開鍵を公開できること。
4. 秘密鍵は外部から取得できないこと。

また、このアプローチでは、コントラクトは**ERC721**のアカウントにバインドされた実装を採用します。
これにより、転送などの操作が行われる際にすべての**ERC721**コントラクト内の関数がエラーを返すようになります。
この実装は、**ERC721**の中でも「**読み取り専用NFTレジストリ**」と呼ばれるものに基づいています。

:::note info
**読み取り専用NFTレジストリ**
**ERC721**の機能をカスタマイズして、特定の目的に合わせたNFT管理を行うための方法です。
このレジストリは、以下の特徴を持っています。

1. **読み取り専用性**
通常の**ERC721**とは異なり、読み取り専用NFTレジストリでは、トークンの所有者が特定の操作（例：転送）を行えないように設計されています。
つまり、これらの操作はすべてエラーを返すため、所有権の移転が制限されます。

2. **物理アセットの確認**
物理的なアイテムに関連するNFTを管理する際、所有権が物理的なアイテムの所有者に制限されるようになっています。
これにより、物理アセットとNFTの関連性を強化し、正当な所有権が確保されます。

3. **第三者介入の削減**
通常、物理アイテムの正当性を確認するためには、信頼できる第三者（例：鑑定士や認定機関）の関与が必要です。
しかし、読み取り専用NFTレジストリでは、物理アイテムにチップを使用することで、正当性の確認を第三者介入なしに行うことができます。

まとめると、読み取り専用NFTレジストリは、物理アイテムをNFTと結びつける際に、所有権と正当性を管理しやすくするための特別な方法です。
このアプローチによって、NFTコレクターは物理アイテムの持つ価値と歴史をデジタルの形で保護し、より信頼性のあるオンライン展示が可能となります。
:::

これによって、物理的なアイテムの所有権が、トランスファーやNFTの所有権管理を行うために必要であることが確保されます。
新しいインターフェースで説明されている新機能を通じて、これらの操作が行われるようになります。

## アプローチ

各NFTは、物理的なチップと関連付けられています。

NFTが作成される際（`mint`される時）、対応するチップのアドレス（チップの公開鍵から生成される`20`バイトのアドレス）を含むイベントも同時に発行されます。
これにより、データの効率的な検索を可能にする後続のシステムが、どのチップがNFTコレクション内のどのトークンに関連付けられているかを知ることができます。
具体的なチップが特定のトークンIDと関連付けられていない場合、NFTは作成できません。

このインターフェースには、「`transferTokenWithChip`」という関数も含まれています。
この関数を使用すると、チップが署名した有効な署名が提供された場合、そのNFTを関数を呼び出した人に転送することができます。
有効な署名は、**ERC191**と**EIP2**という規格で定義された方法に従う必要があります。
署名のデータには、受信者のアドレス（関数を呼び出した人）と直近のブロックハッシュ（ブロックの最新情報）が含まれます。
ただし、具体的な実装によってブロックハッシュの制約は異なる可能性があります。

また、このインターフェースには、物理アイテム内のチップがコレクション内の既存のNFTを裏付けているかを誰でも確認できる他の関数も備わっています。

このアプローチによって、NFTと物理的なアイテムの間に確実な関連性が生まれ、正当な所有権とトランスファーの安全性が確保されるようになります。

## インターフェース

```javascript
interface IERC5791 {
    /// @notice Returns the token id for a given chip address.
    /// @dev Throws if there is no existing token for the chip in the collection.
    /// @param chipAddress The address for the chip embedded in the physical item (computed from the chip's public key).
    /// @return The token id for the passed in chip address.
    function tokenIdFor(address chipAddress) external view returns (uint256);

    /// @notice Returns true if the chip for the specified token id is the signer of the signature of the payload.
    /// @dev Throws if tokenId does not exist in the collection.
    /// @param tokenId The token id.
    /// @param payload Arbitrary data that is signed by the chip to produce the signature param.
    /// @param signature Chip's signature of the passed-in payload.
    /// @return Whether the signature of the payload was signed by the chip linked to the token id.
    function isChipSignatureForToken(uint256 tokenId, bytes calldata payload, bytes calldata signature)
        external
        view
        returns (bool);

    /// @notice Transfers the token into the message sender's wallet.
    /// @param signatureFromChip An EIP-191 signature of (msgSender, blockhash), where blockhash is the block hash for blockNumberUsedInSig.
    /// @param blockNumberUsedInSig The block number linked to the blockhash signed in signatureFromChip. Should be a recent block number.
    /// @param useSafeTransferFrom Whether EIP-721's safeTransferFrom should be used in the implementation, instead of transferFrom.
    ///
    /// @dev The implementation should check that block number be reasonably recent to avoid replay attacks of stale signatures.
    /// The implementation should also verify that the address signed in the signature matches msgSender.
    /// If the address recovered from the signature matches a chip address that's bound to an existing token, the token should be transferred to msgSender.
    /// If there is no existing token linked to the chip, the function should error.
    function transferTokenWithChip(
        bytes calldata signatureFromChip,
        uint256 blockNumberUsedInSig,
        bool useSafeTransferFrom
    ) external;

    /// @notice Calls transferTokenWithChip as defined above, with useSafeTransferFrom set to false.
    function transferTokenWithChip(bytes calldata signatureFromChip, uint256 blockNumberUsedInSig) external;

    /// @notice Emitted when a token is minted
    event PBTMint(uint256 indexed tokenId, address indexed chipAddress);

    /// @notice Emitted when a token is mapped to a different chip.
    /// Chip replacements may be useful in certain scenarios (e.g. chip defect).
    event PBTChipRemapping(uint256 indexed tokenId, address indexed oldChipAddress, address indexed newChipAddress);
}
```

**ERC721**トークンがこのEIP（提案された機能）によって物理的な結びつきを持つことを示すために、**ERC165**の関数`supportsInterface(bytes4 interfaceID)`が呼び出される時に、`interfaceID=0x4901df9f`として呼び出すことで、このEIPを実装しているコントラクトは必ず`true`を返す必要があります。

NFTの作成（`mint`）の方法は、実装によって異なる場合がありますが、`mint`されたNFTの所有者は、物理的なチップの所有者と一致すべきです。
この認証は、`transferTokenWithChip`の署名スキームを使用して実装できます。

要するに、このEIPで定義された物理的な結びつきを実装する**ERC721**トークンは、`supportsInterface`関数を特定の`interfaceID`（`0x4901df9f`）で呼び出すことで、その実装が認識されるようになります。
また、NFTの作成時には、NFTの所有者が物理的なチップの所有者と一致するように確認されるべきです。

これによって、NFTの物理的な結びつきが明確に示され、コレクション内のNFTと物理アイテムの関連性が強化されることになります。

もちろんです。各関数について日本語でわかりやすく説明します。

---

### tokenIdFor

```javascript
function tokenIdFor(address chipAddress) external view returns (uint256);
```

**概要**
指定されたチップアドレスに対応するトークンIDを取得する関数。

**詳細**
与えられたチップアドレスに対応するトークンIDを返します。
トークンがコレクション内に存在しない場合はエラーが投げられます。

**引数**
- `chipAddress`
    - 物理アイテムに埋め込まれたチップのアドレス（チップの公開鍵から計算される）。

**戻り値**
- `uint256`
    - 指定されたチップアドレスに対応するトークンID。

---

### isChipSignatureForToken

```javascript
function isChipSignatureForToken(uint256 tokenId, bytes calldata payload, bytes calldata signature)
    external
    view
    returns (bool);
```

**概要**
指定されたトークンIDのチップが、指定されたペイロードの署名者であるかどうかを判定する関数。

**詳細**
指定されたトークンIDとペイロードが与えられた場合に、ペイロードの署名が該当トークンのチップによって行われたものかどうかを判定します。
トークンがコレクション内に存在しない場合はエラーが投げられます。

**引数**
- `tokenId`
    - トークンID。
- `payload`
    - チップによって署名された任意のデータ。
- `signature`
    - チップによる署名。

**戻り値**
- `bool`
    - ペイロードの署名が指定されたトークンのチップによって行われた場合は`true`、そうでない場合は`false`。

---

### transferTokenWithChip

```javascript
function transferTokenWithChip(bytes calldata signatureFromChip, uint256 blockNumberUsedInSig, bool useSafeTransferFrom)
    external;
```

**概要**
チップからの署名を使用してトークンをメッセージ送信者のウォレットに転送する関数。

**詳細**
チップからの署名とブロック番号を使用して、トークンをメッセージ送信者のウォレットに転送します。
署名のブロック番号が有効であり、署名のアドレスが`msgSender`と一致することが確認されます。
署名から復元されたアドレスが既存のトークンにバインドされている場合、トークンはメッセージ送信者に転送されます。
トークンがチップにバインドされていない場合は、エラーが投げられます。

**引数**
- `signatureFromChip`
    - チップからの**EIP191**署名（msgSender、blockhashからなる）。
- `blockNumberUsedInSig`
    - 署名に使用されたブロック番号。
- `useSafeTransferFrom`
    - 実装において**EIP721**の`safeTransferFrom`を使用するかどうか（`true`または`false`）。

---

### transferTokenWithChip

```javascript
function transferTokenWithChip(bytes calldata signatureFromChip, uint256 blockNumberUsedInSig) external;
```

**概要**
`useSafeTransferFrom`を`false`として`transferTokenWithChip`を呼び出す関数。

**引数**
- `signatureFromChip`
    - チップからの**EIP191**署名。
- `blockNumberUsedInSig`
    - 署名に使用されたブロック番号。

---

# 補足

この提案の目的は、中央集権的な機関を介さずに、物理アイテムをデジタルNFTに関連付ける最も簡単な方法を提供することです。

このインターフェースには、「`transferTokenWithChip`」という関数が含まれており、将来的にこのEIPを実装する他のNFTでもトランスファーをサポートするための署名スキームを含んでいます。

# 範囲外

以下の問題についてはこのEIPの範囲外とされています。

- 特定のNFTコレクションのチップアドレスが、物理アイテムに実際に組み込まれたチップと対応しているかどうかの信頼性。
- チップが劣化したり損傷したりしないようにするための保証。
- チップが物理アイテムにしっかりと取り付けられていることの確認。
など。

これらの課題に関しては、別途取り組まれる予定です。

トークンIDとチップアドレスの対応付けも範囲外であり、実現方法はいくつかあります。
例えば、コントラクトオーナーが事前に対応付けを設定する方法、特定のアドレスによって署名された（`tokenId`、`chipAddress`）のタプルを`mint`関数に渡す方法、信頼できるレジストリでの検索方法、`mint`時にトークンIDを先着順で割り当てる方法などが考えられます。

また、物理アイテムの所有者は、NFTを他の人のウォレットにトランスファーすることができます（その他の人にチップの署名を送信して使用することで）。
ただし、所有権管理は物理アイテムに関連しており、NFTは物理的にバックされていると考えられます。
これは、（1）アイテムの所有者がチップで署名するために関与し、（2）アイテムの所有者がいつでもNFTの所有権を取り戻すことができるためです。

# 後方互換性

この提案は、**ERC721**という既存の仕様とAPIの互換性を維持しています。
前述した通り、トークンを物理的にバックするためには、コントラクトは**ERC721**の機能をアカウントに紐付けて実装する必要があります（**ERC721**の`transfer`関数が例外を発生させるようにします）。
これにより、トークンの転送はここで導入された新しい関数を通じて行われ、その際にはチップの署名が必要です。

# 参考実装

```javascript
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';

function getChipAddressFromChipSignature(
  bytes calldata signatureFromChip,
  uint256 blockNumberUsedInSig
) internal returns (TokenData memory) {
  if (block.number <= blockNumberUsedInSig) {
    revert InvalidBlockNumber();
  }
  unchecked {
    if (block.number - blockNumberUsedInSig > getMaxBlockhashValidWindow()) {
      revert BlockNumberTooOld();
    }
  }
  bytes32 blockHash = blockhash(blockNumberUsedInSig);
  bytes32 signedHash = keccak256(abi.encodePacked(_msgSender(), blockHash))
    .toEthSignedMessageHash();
  address chipAddr = signedHash.recover(signatureFromChip);
}
```

### getChipAddressFromChipSignature

**概要**
チップからの署名を使用してチップのアドレスを取得する関数。

**詳細**
チップからの署名とブロック番号を使用して、チップのアドレスを復元します。
まず、現在のブロック番号と署名時のブロック番号を比較し、署名時のブロック番号が有効であることを確認します。
その後、署名に使用されたブロックハッシュを取得し、ブロックハッシュと呼び出し元のアドレスを組み合わせて署名されたハッシュを生成します。
最終的に、署名からチップのアドレスを復元します。

**引数**
- `signatureFromChip`
    - チップからの署名。
- `blockNumberUsedInSig`
    - 署名に使用されたブロック番号。

**戻り値**
- `chipAddr`
    - 復元されたチップのアドレス。

---

# セキュリティ考慮事項
「`transferTokenWithChip`」関数に渡される**ERC191**署名には、関数呼び出し元のアドレスが署名されたデータに含まれています。
これにより、署名を使用したリプレイ攻撃を防ぐことができます。
また、悪意のあるチップ所有者が短い時間ウィンドウ（例えば、物理アイテムの所有者が変更された後）に使用するために、事前に署名を生成することを防ぐために、最新のブロックハッシュも必要です。

さらに、トークンが物理的にバックされているかどうかの信頼性は、物理チップのセキュリティに依存します。
しかし、このセキュリティに関する信頼性は、この提案の対象外とされています。
つまり、提案された仕組みは「`transferTokenWithChip`」関数の署名を通じてセキュリティを確保しますが、トークンの物理的なバックアップのセキュリティは物理チップ自体にかかっているということです。

要するに、この提案はデジタルNFTと物理アイテムの関連付けに対するセキュリティを提供していますが、物理チップのセキュリティは別途対策が必要とされています。

# 引用

2pmflow (@2pmflow), locationtba (@locationtba), Cameron Robertson (@ccamrobertson), cygaar (@cygaar), Brian Weick (@bweick), "ERC-5791: Physical Backed Tokens [DRAFT]," Ethereum Improvement Proposals, no. 5791, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5791.


# 最後に

今回は「NFTの所有権を物理チップにリンクするための最小限のインターフェースを提案している**ERC5791**」についてまとめてきました！
いかがだったでしょうか？

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

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