[ERC6066] 署名主体がNFTである時の署名検証方法の仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は、署名主体が**ERC721**か**ERC1155**NFTである場合の署名検証方法の仕組みを提案している規格である**ERC6066**についてまとめていきます！

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

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

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

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

# 概要

通常、外部所有アカウント（Externally Owned Accounts）は、`ecrecover()`関数を使って、署名が本物かどうかを確認できます。
スマートコントラクトも、**ERC1271**のルールに従って署名を検証することができます。
ですが、NFTが作った署名を検証する標準的な方法は今のところありません。

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

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

ここで、NFTの署名が本物かどうかを誰でもチェックできる新しい方法が提案されています。
この方法は、**ERC1271**にある署名検証機能を改良したもので、`isValidSignature(tokenId, hash, data)`という関数を使用します。
この関数を使うと、あるNFT（それぞれに固有の`tokenId`で識別される）が、あるハッシュやデータに対してした署名が本物かどうかを判断できるようになります。

この新しい標準が採用されれば、NFTの署名がより透明で、誰でも検証可能になるため、NFTの使い道が広がると考えられます。

:::note info
### `ecrecover()`

#### `ecrecover()`関数の役割
- `ecrecover()`関数は、Ethereumの署名を検証するために使用されます。
- 署名されたメッセージと署名から、そのメッセージを署名したアドレスを特定することができます。

#### 具体例
- **署名のプロセス**
    - Aliceはメッセージ（例: "Hello, World!"）を持っています。
    - Aliceはそのメッセージに対して秘密鍵を用いて署名します。
    - この署名は、メッセージとともにBobに送られます。

- **検証のプロセス**
    - BobはAliceから受け取った署名とメッセージを持っています。
    - Bobは`ecrecover()`関数を使用して、この署名がどのアドレスによって作成されたかを確認します。
    - 関数は、署名されたメッセージと署名から、署名者のアドレス（この場合はAliceのアドレス）を返します。
    - もし返されたアドレスがAliceのものであれば、署名は有効とみなされます。

#### まとめ
`ecrecover()`関数は、署名されたメッセージが特定のアドレスから来たことを検証するために使われます。
これにより、メッセージの送信者の身元を確認することが可能になります。
一方、**ERC1271**の `isValidSignature`関数は、スマートコントラクトが署名を検証するために設計されており、署名の検証プロセスをコントラクトに組み込むことができます。
:::

# 動機

最近、非代替性トークン（NFT）は、何十億ものETHの取引量を伴い、大きな人気を集めています。
NFTは、特にアバターやプロフィール画像としてのアートワークを表すためによく使われていますが、**ERC721**や**ERC1155* といったトークン標準を使って、もっと多様な用途に活用できる可能性があります。

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

https://chaldene.net/erc721

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

https://chaldene.net/erc1155

例えば、NFTを使って組織内のオフィスや役職を表すことが考えられます。
こうすることで、署名を外部所有アカウント（EOA）やスマートコントラクトに依存せず、NFTに直接結び付けることが可能になります。
想定するのは、例えばある分散型自律組織（DAO）が、CEOやCOOといった管理ロールをNFTのバッジで表し、四半期ごとに民主的な選挙でこれらのロールを持つ人が変わるシステムです。
このシステムでは、以前のCOOが行った署名や認可は、新しいCOOに交代した後もそのオフィスではなく元のCOOだったEOAに残ることになります。
DAO全体のマルチシグウォレットを使うことも一つの解決策ですが、より細かいレベルで署名を生成することで責任の明確な分離が実現できます。
また、COOとしてスマートコントラクトを任命することも可能ですが、この方法は複雑さを増すだけです。
DAOが組織階層を確立するためにENSを使用している場合、この提案により、ラップされたENSサブドメイン（NFT）を使って署名を生成することが可能になります。

# 仕様

```javascript
pragma solidity ^0.8.0;

interface IERC6066 {
    /**
     * @dev MUST return if the signature provided is valid for the provided tokenId and hash
     * @param tokenId   Token ID of the signing NFT
     * @param hash      Hash of the data to be signed
     * @param data      OPTIONAL arbitrary data that may aid verification
     *
     * MUST return the bytes4 magic value 0x12edb34f when function passes.
     * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
     * MUST allow external calls
     *
     */
    function isValidSignature(
        uint256 tokenId,
        bytes32 hash,
        bytes calldata data
    ) external view returns (bytes4 magicValue);
}
```

---

### `isValidSignature`

```javascript
function isValidSignature(
    uint256 tokenId,
    bytes32 hash,
    bytes calldata data
) external view returns (bytes4 magicValue);
```

**概要**

提供されたトークンIDとハッシュに対する署名が有効か判定する関数。

**詳細**

`isValidSignature`関数は、NFTのトークンID、データのハッシュ、および任意の追加データを用いて、署名が有効かどうかを検証します。
この関数は、署名の検証に成功した場合に特定のバイト値`0x12edb34f`を返す必要があります。
また、状態を変更してはならず、外部からの呼び出しを許可する必要があります。

**引数**

- `tokenId`
    - 署名されたNFTのトークンID。
- `hash`
    - 署名されるデータのハッシュ。
- `data`
    - 署名検証に役立つ任意の追加データ（オプショナル）。

**戻り値**

- `magicValue`
    - 関数が成功した場合のバイト値`0x12edb34f`。

---

`isValidSignature` は、NFTを使った署名が正しいかどうかを判断するための関数です。
この関数は、NFTの所有者がメッセージに署名することを可能にするために **ERC721**や**ERC1155**準拠のコントラクトによって使われることがあります。

具体的には、ある人が**ERC721**や**ERC1155**のNFTを持っていて、そのNFTを使ってメッセージに署名した場合、`isValidSignature`関数はその署名が本物かどうかを確認するために使われます。
これは、NFTを利用した新しいやり取りや機能を可能にするための重要なステップです。

:::note info
もしNFTを使った署名をサポートする機能が必要な場合、**ERC721**や**ERC1155**準拠のコントラクトは `isValidSignature`を実装し、NFTの所有者が行った署名が有効かどうかをチェックすることができるようになります。
これにより、NFTを使ってより複雑で豊かなやり取りが可能になるわけです。
:::

# 補足

この提案は、わざと署名を生成するための固定された標準を設けていません。
これは、署名のメカニズムをより柔軟にするためです。
同じように、**ERC1271**はスマートコントラクトの署名方法に特定の標準を課していません。
さらに、この提案では、シンプルで効果的なGnosis Safeのコントラクト署名の方法を参考にしています。
また、署名検証のために追加データが必要な場合に備えて、`bytes calldata data`というパラメータをオプションとしています。
これにより、このEIP（Ethereum Improvement Proposal）は、将来的な標準である **ERC5750**にも対応しています。

つまり、この提案は署名生成に関する厳格なルールを設けずに、柔軟性を保つことを目指しています。Gnosis Safeのような簡単で実用的な方法を参考にしており、必要に応じて追加情報を含められるようにしています。これにより、将来の標準である **ERC5750** にも準拠することが可能です。

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

https://qiita.com/cardene/items/216265154b75c1612904

# 後方互換性

このEIP（Ethereum Improvement Proposal）は、暗号学的に生成された署名を検証する従来の方法とは異なります。
具体的には、この提案では「**署名**」というのは、単に同意を示すためのブール値（真偽値）のフラグです。
これは、Gnosis Safeのコントラクト署名の実装方法と同じです。

:::note info
このEIPでは、複雑な暗号技術を使った署名の検証を行わず、より単純な「**はい**」または「**いいえ**」という形式で同意を示す方法を採用しています。
このアプローチはGnosis Safeの方法に倣っており、署名プロセスをシンプルで直感的なものにしています。
これにより、署名のプロセスが簡素化され、より取り扱いやすくなっています。
:::

# 参考実装

**ERC721**と**ERC6066**に準拠したカスタム署名関数の実装例は以下になります。

```javascript
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./interfaces/IERC6066.sol";

contract ERC6066Reference is ERC721, IERC6066 {
    // type(IERC6066).interfaceId
    bytes4 public constant MAGICVALUE = 0x12edb34f;
    bytes4 public constant BADVALUE = 0xffffffff;

    mapping(uint256 => mapping(bytes32 => bool)) internal _signatures;

    error ENotTokenOwner();

    /**
     * @dev Checks if the sender owns NFT with ID tokenId
     * @param tokenId   Token ID of the signing NFT
     */
    modifier onlyTokenOwner(uint256 tokenId) {
        if (ownerOf(tokenId) != _msgSender()) revert ENotTokenOwner();
        _;
    }

    constructor(string memory name_, string memory symbol_)
        ERC721(name_, symbol_)
    {}

    /**
     * @dev SHOULD sign the provided hash with NFT of tokenId given sender owns said NFT
     * @param tokenId   Token ID of the signing NFT
     * @param hash      Hash of the data to be signed
     */
    function sign(uint256 tokenId, bytes32 hash)
        external
        onlyTokenOwner(tokenId)
    {
        _signatures[tokenId][hash] = true;
    }

    /**
     * @dev MUST return if the signature provided is valid for the provided tokenId, hash, and optionally data
     */
    function isValidSignature(uint256 tokenId, bytes32 hash, bytes calldata data)
        external
        view
        override
        returns (bytes4 magicValue)
    {
        // The data parameter is unused in this example
        return _signatures[tokenId][hash] ? MAGICVALUE : BADVALUE;
    }

    /**
     * @dev ERC-165 support
     */
    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override returns (bool) {
        return
            interfaceId == type(IERC6066).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}
```

# セキュリティ考慮事項

このEIPにおいて、コントラクトベースの署名は取り消すことが可能な性質を持っています。
これは、開発者やユーザーが特に注意すべき点です。

:::note info
この提案に基づくコントラクトの署名は、状況に応じていつでも取り消しや変更ができるという特性を持っています。
これは、開発者やユーザーにとって重要な考慮事項です。
署名が柔軟に変更可能であることは便利ですが、同時に署名の安定性や継続性に影響を及ぼす可能性があるため、この特性を理解してそれに基づいて署名を使用することが必要です。
:::

# 引用

Jack Boyuan Xu (@boyuanx), "ERC-6066: Signature Validation Method for NFTs," Ethereum Improvement Proposals, no. 6066, November 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6066.

# 最後に

今回は「署名主体が**ERC721**か**ERC1155**NFTである場合の署名検証方法の仕組みを提案している規格である**ERC6066**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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