[ERC2304] ENSで複数チェーンに対応した仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、ENS（Ethereum Name Service）でEthereum以外のブロックチェーンアドレスも解決可能にする仕組みを提案している**ERC2304**についてまとめていきます！

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

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

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

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

# 概要

**ERC2304**は、ENS（Ethereum Name Service）のリゾルバにおける `addr` フィールドに対して、異なるブロックチェーンのアドレスも扱えるようにするための新しい引数形式を追加する提案です。
これにより、Ethereum以外のブロックチェーンのアドレスもENSを通じて解決（リゾルブ）できるようになります。

# 動機

ENSはマルチコイン対応ウォレットの普及に伴い、Ethereum以外のチェーンのアドレスも扱う必要性が出てきています。
ウォレット開発者からは、ENSを用いて他のブロックチェーンのアドレスも解決できる機能の要望が寄せられていました。
**ERC2304**は、そのようなニーズに応えるために、クライアント間で一貫した方法で非Ethereumチェーンのアドレスを登録・取得できる仕組みを標準化することを目的としています。

# 仕様

## 関数

**ERC2304**では、ENSリゾルバに以下のような新しい関数が追加されます。

### `addr`

```javascript
function addr(bytes32 node, uint coinType) public view returns (bytes memory) {
    return _addresses[node][coinType];
}
```

任意のコインタイプのアドレスを取得する関数。
この関数は、特定のENSノード（`node`）と暗号通貨の種類（`coinType`）を指定することで、その通貨に対応するアドレスをバイナリ形式で返します。
`coinType` は [SLIP-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) に定義された仮想通貨の識別番号を用います。
未登録の場合は空バイト列が返されます。
この関数の**EIP165**におけるインターフェースIDは `0xf1cb7e06` です。

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

https://qiita.com/cardene/items/541184977a5cba51b759

**引数**

* `node`
    * 対象となるENSノード。
* `coinType`
    * 仮想通貨の種類（SLIP-0044に準拠）。

**戻り値**

* `bytes`
    * 対応するアドレスのバイナリ形式。

### `setAddr`

```javascript
function setAddr(bytes32 node, uint coinType, bytes memory a) public authorised(node) {
    emit AddressChanged(node, coinType, a);
    if (coinType == COIN_TYPE_ETH) {
        emit AddrChanged(node, bytesToAddress(a));
    }
    _addresses[node][coinType] = a;
}
```

任意のコインタイプのアドレスを登録する関数。
与えられたENSノードとコインタイプに対してアドレスを登録または更新します。Ethereum（60）の場合は従来の `AddrChanged` イベントも併せて発行します。

**引数**

* `node`
    * アドレスを登録するENSノード。
* `coinType`
    * コインの種類（SLIP-0044に準拠）。
* `a`
    * 登録するアドレス（バイナリ形式）。

## イベント

新たに以下のイベントが定義されます。

### `AddressChanged`

```javascript
event AddressChanged(bytes32 indexed node, uint coinType, bytes newAddress);
```

コインタイプを指定したアドレスが変更された時に発行されるイベント。
ENSノードに紐づく特定の暗号通貨アドレスが変更された場合に発行され、マルチコイン対応の新しい形式に対応しています。

**パラメータ**

* `node`
    * ENS名の namehash（ノード）。
* `coinType`
    * 暗号通貨のコインタイプ（SLIP-0044に準拠）。
* `newAddress`
    * 新しく設定されたアドレス（バイナリ形式）。

## アドレスのバイナリ形式とエンコーディング

ENSに保存するアドレスは、各ブロックチェーンにおけるネイティブなバイナリ形式である必要があります。
つまり、一般的なテキスト形式でのチェックサム付きアドレスではなく、バイト列としての形式です。

以下は代表的なチェーンの `coinType` とそのエンコーディング方式の一部です。

| 暗号通貨             | Coin Type | エンコーディング形式                                     |
| ---------------- | --------- | ---------------------------------------------- |
| Bitcoin          | 0 | P2PKH (0x00), P2SH (0x05), SegWit（bech32の`bc`） |
| Ethereum         | 60        | ChecksummedHex（EIP-55）                         |
| Litecoin         | 2         | P2PKH (0x30), P2SH (0x32, 0x05), SegWit（`ltc`） |
| Dogecoin         | 3         | P2PKH (0x1e), P2SH (0x16)                      |
| Ethereum Classic | 61        | ChecksummedHex                                 |
| Rootstock        | 137       | ChecksummedHex（RSKIP-60）                       |
| Ripple           | 144       | base58checkまたはX-address（タグ付き）                  |
| Bitcoin Cash     | 145       | CashAddr（bech32派生）                             |
| Binance Chain    | 714       | Bech32（プレフィックスは`bnb`）                          |

## 各アドレス形式の詳細

* **P2PKH / P2SH**
  Bitcoinなどで使われる形式。Base58Checkでエンコードされ、デコード後はバージョンバイト＋20バイトのハッシュとして構成されます。

* **SegWit (Bech32)**
  witness version と witness program に基づく形式。人間が読める部分（例: `bc`）と機械可読な部分から構成されます。

* **ChecksummedHex**
  Ethereum系で使われる形式で、`0x`を除いた後の16進文字列をデコードした20バイトのデータを使用します。
  チェックサム付き（EIP-55, RSKIP-60）の形式でないアドレスは警告またはエラー扱いとします。

* **Ripple**
  Ripple固有のBase58Check形式を使用し、`r-address`や`X-address`をデコードしてバイナリ保存します。

* **CashAddr（Bitcoin Cash）**
  Bitcoin Cash用に定義されたbech32派生形式で、デコード後のP2PKH/P2SH形式で保存します。

* **Bech32（Binance等）**
  `bnb`などのプレフィックスを持つbech32形式で、デコードされたバイト列をそのまま保存します。

## 実装例

```js
pragma solidity ^0.5.8;

contract AddrResolver is ResolverBase {
    bytes4 constant private ADDR_INTERFACE_ID = 0x3b3b57de;
    bytes4 constant private ADDRESS_INTERFACE_ID = 0xf1cb7e06;
    uint constant private COIN_TYPE_ETH = 60;

    event AddrChanged(bytes32 indexed node, address a);
    event AddressChanged(bytes32 indexed node, uint coinType, bytes newAddress);

    mapping(bytes32=>mapping(uint=>bytes)) _addresses;

    /**
     * Sets the address associated with an ENS node.
     * May only be called by the owner of that node in the ENS registry.
     * @param node The node to update.
     * @param a The address to set.
     */
    function setAddr(bytes32 node, address a) external authorised(node) {
        setAddr(node, COIN_TYPE_ETH, addressToBytes(a));
    }

    /**
     * Returns the address associated with an ENS node.
     * @param node The ENS node to query.
     * @return The associated address.
     */
    function addr(bytes32 node) public view returns (address) {
        bytes memory a = addr(node, COIN_TYPE_ETH);
        if(a.length == 0) {
            return address(0);
        }
        return bytesToAddress(a);
    }

    function setAddr(bytes32 node, uint coinType, bytes memory a) public authorised(node) {
        emit AddressChanged(node, coinType, a);
        if(coinType == COIN_TYPE_ETH) {
            emit AddrChanged(node, bytesToAddress(a));
        }
        _addresses[node][coinType] = a;
    }

    function addr(bytes32 node, uint coinType) public view returns(bytes memory) {
        return _addresses[node][coinType];
    }

    function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
        return interfaceID == ADDR_INTERFACE_ID || interfaceID == ADDRESS_INTERFACE_ID || super.supportsInterface(interfaceID);
    }
}
```

* Ethereum用アドレス (`coinType = 60`) のショートカット関数として `addr(bytes32)` を引き続きサポート
* マッピング構造 `mapping(bytes32 => mapping(uint => bytes))` を用いて、ノードとコインタイプに応じたアドレスを保存
* `setAddr` にてアドレス登録時に `AddressChanged` を必ず発行
* `supportsInterface` にて EIP-165 に基づいたインターフェース識別を実装

### `ADDR_INTERFACE_ID`

```javascript
bytes4 constant private ADDR_INTERFACE_ID = 0x3b3b57de;
```

ENSの従来の `addr(bytes32)` 関数に対応するインターフェースID。
この識別子は、**EIP165** 準拠の `supportsInterface` 関数において、Ethereumアドレスの取得をサポートしているかを識別するために使用されます。

### `ADDRESS_INTERFACE_ID`

```javascript
bytes4 constant private ADDRESS_INTERFACE_ID = 0xf1cb7e06;
```

マルチコインアドレス対応の `addr(bytes32,uint)` 関数に対応するインターフェースID。
このIDは、**EIP165** に基づき、複数のブロックチェーンのアドレスをサポートするリゾルバであることを示します。

### `COIN_TYPE_ETH`

```javascript
uint constant private COIN_TYPE_ETH = 60;
```

Ethereumのコインタイプを示す定数。
[SLIP-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) によって定義されたEthereumのコインタイプ番号で、60番が割り当てられています。

### `AddrChanged`

```javascript
event AddrChanged(bytes32 indexed node, address a);
```

Ethereumアドレスが変更された時に発行されるイベント。
従来の `addr(bytes32)` の形式に対応したアドレスが変更された際に使用されます。
**ERC137**の仕様に準拠しています。

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

https://qiita.com/cardene/items/17e6e1a452006f08a197

**パラメータ**

* `node`
    * ENS名の namehash（ノード）。
* `a`
    * Ethereumアドレス。

### `_addresses`

```javascript
mapping(bytes32 => mapping(uint => bytes)) _addresses;
```

ENSノードごとのコインタイプに対応するアドレスを保存するマッピング。
`_addresses[node][coinType]` の形式でアクセスすることで、任意のコインのバイナリ形式アドレスを取得または保存することができます。

### `addr`

```javascript
function addr(bytes32 node) public view returns (address) {
    bytes memory a = addr(node, COIN_TYPE_ETH);
    if (a.length == 0) {
        return address(0);
    }
    return bytesToAddress(a);
}
```

ENSノードに紐づくEthereumアドレスを取得する関数。
Ethereum（coinType 60）に限定された `addr(bytes32)` の形式で、対応するバイナリアドレスを取得し、デコードしてEthereumアドレスとして返します。

**引数**

* `node`
    * Ethereumアドレスを取得したいENSノード。

**戻り値**

* `address`
    * 紐づいたEthereumアドレス（登録がなければ address(0)）。

### `setAddr`

```javascript
function setAddr(bytes32 node, address a) external authorised(node) {
    setAddr(node, COIN_TYPE_ETH, addressToBytes(a));
}
```

Ethereumアドレスを設定する関数。
`coinType = 60`（Ethereum）を使って、指定されたENSノードにEthereumアドレスを登録します。
内部ではバイナリ変換を行い、マルチコイン形式の `setAddr` を呼び出します。

**引数**

* `node`
    * アドレスを登録するENSノード。
* `a`
    * Ethereumアドレス。

### `supportsInterface`

```javascript
function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
    return interfaceID == ADDR_INTERFACE_ID || interfaceID == ADDRESS_INTERFACE_ID || super.supportsInterface(interfaceID);
}
```

このコントラクトが指定されたインターフェースをサポートしているかどうかを確認する関数。
**EIP165**に準拠した形式で、従来の `addr(bytes32)` と**ERC2304**で提案される `addr(bytes32,uint)` の両方をサポートしているかを判定します。

**引数**

* `interfaceID`
    * インターフェース識別子（bytes4形式）。

**戻り値**

* `bool`
    * 指定のインターフェースをサポートしていれば `true`。そうでなければ `false`。

# 互換性

**ERC2304**は、**EIP137**で定義されている `addr(bytes32)` インターフェースとの互換性をもつよう設計されています。
具体的には、既存のENSリゾルバがこのEIPの新仕様に対応する場合、以下の点を満たす必要があります。

* `addr(bytes32 node)` の戻り値は、`addr(bytes32 node, uint coinType)` を `coinType = 60`（Ethereumのコインタイプ）として呼び出した場合と常に同じ値を返す必要があります。
* `AddrChanged` イベント（**EIP137**のイベント）が発行されるような操作では、必ず `AddressChanged` イベント（**ERC2304**で定義されたイベント）も同時に発行される必要があります。この時、`coinType` は `60` を指定します。
* 逆に、`coinType = 60` の `AddressChanged` イベントが発行された場合も、必ず `AddrChanged` イベントを発行する必要があります。

このように設計することで、既存のENS利用者やツールが影響を受けることなく、新しいマルチコイン対応機能を取り入れることができます。

# 引用

Nick Johnson <nick@ens.domains>, "ERC-2304: Multichain address resolution for ENS [DRAFT]," Ethereum Improvement Proposals, no. 2304, September 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2304.

# 最後に

今回は「ENS（Ethereum Name Service）でEthereum以外のブロックチェーンアドレスも解決可能にする仕組みを提案している**ERC2304**」についてまとめてきました！
いかがだったでしょうか？

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