# [ENSIP3] Ethereumアドレスに名前を紐付ける仕組みを理解しよう！

> ENSIP3は、Ethereum Name Service（ENS）において「リバース解決（Reverse Resolution）」を行うための仕様を定めています。

- URL: https://cardene.dev/articles/845794b9ea21c5eaa4c9
- 公開日: 2025-08-21
- 更新日: 2025-08-21
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、Ethereumアドレスに名前を紐づけて、ウォレットやアプリがその名前を正しく表示できる仕組みを提案している**ENSIP3**についてまとめていきます！

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

https://docs.ens.domains/ensip/3/

他にも様々なEIP・BIP・SLIP・CAIP・ENSIPについてまとめています。

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

## 概要

**ENSIP3**は、Ethereum Name Service（ENS）において「**リバース解決（Reverse Resolution）**」を行うための仕様を定めています。
具体的には、トップレベルドメイン（TLD）、レジストラ、そしてリゾルバのインターフェースを定義し、Ethereumアドレスに対して人間が読める名前を関連付けられるようにします。
これにより、リゾルバ（名前を解決する仕組み）は、そのリバースレコードが該当するEthereumアドレスの所有者によって正しく公開されたものであると確認できます。

## 動機

通常のENSは「**正引き解決（Forward Resolution）**」が主な用途で、「**人間が読める名前からマシンが扱うアドレスへ変換**」するために利用されています。
しかし、逆方向の解決（リバース解決）にも重要な利用価値があります。

- **ユーザ体験の向上**
    例えば、ウォレットやアプリケーションでアカウント一覧を表示する時、ただの長いアドレスを見せるのではなく、そのアドレスが持つENS名を表示できればユーザは直感的にどのアカウントかを認識できます。

- **メタデータの付与**
    アドレスに説明文やその他の情報を紐づけておけば、そのアドレスがどのような用途かをすぐに理解できます。
    これにより、アドレスをどの経路で入手した場合でも、追加情報を簡単に取り出せます。

- **正当性の証明**
    ENSの正引き解決では、誰でも自分以外のアドレスに名前を紐づけることができます。
    しかしリバース解決では「**そのアドレスの所有者本人が登録した名前**」であることを保証できるため、信頼できる形で「**このアドレスの正式名称**」を宣言できます。

## 仕様

### リバースレコードの保存場所

ENSにおけるリバースレコードは、通常のENSレコードと同じようにENS階層に格納されます。
ただし、特別に予約されたドメイン `addr.reverse` の下に配置されます。
具体的には、Ethereumアドレスを小文字の16進数表記に変換し、それに `.addr.reverse` を付け加えた名前が、そのアドレスのリバースレコードの保存先になります。

例として、ENSレジストリのアドレス `0x112234455c3a32fd11230c42e7bccd4a84e02010` の場合、そのリバースレコードは以下の場所に保存されます。

```
112234455c3a32fd11230c42e7bccd4a84e02010.addr.reverse
```

この仕組みのため、コントラクトがリバース解決を動的に行う時には、アドレスを16進数（小文字）に変換する処理を自前で実装する必要があります。

### レジストラ（Registrar）

`addr.reverse` ドメインの所有者はレジストラと呼ばれるコントラクトであり、呼び出し元が自分自身のリバースレコードを取得できるようにします。
このレジストラは以下の関数を提供します。

#### `claim`

```javascript
function claim(address owner) returns (bytes32 node)
```

呼び出し元のアドレス `x` に基づき、`hex(x) + ".addr.reverse"` という名前の所有権を指定した `owner` アドレスに移転し、そのレコードの `namehash` を返します。
これにより、コントラクトはコンストラクタ内で簡単にリバースレコードを登録できます。

```javascript
reverseRegistrar.claim(msg.sender)
```

#### `claimWithResolver`

```javascript
function claimWithResolver(address owner, address resolver) returns (bytes32 node)
```

呼び出し元 `x` のリバースレコードに対して、まずリゾルバを指定された `resolver` に設定し、その後に所有権を `owner` に移転します。
これにより、カスタムリゾルバと所有者を少ないトランザクションで設定できます。

#### `setName`

```javascript
function setName(string name) returns (bytes32 node)
```

呼び出し元 `x` に基づき、デフォルトのリゾルバを設定した上で、`hex(x) + ".addr.reverse"` に対して指定した `name` を登録します。
この関数はユーザーがシンプルなリバースレコードを一度のトランザクションで設定するのに便利です。

### リゾルバインターフェース

リバースレコードを扱うために、新しいリゾルバインターフェースが定義されています。

```javascript
function name(bytes32 node) constant returns (string);
```

この関数は、指定されたノードに対して有効なENS名を返します。
もし名前が設定されていない場合は空文字を返します。

このインターフェースのIDは `0x691f3431` です。
将来的には、リバースレコードに対応するさらなるレコードタイプがENSIPによって追加される可能性があります。

## Registrar参考実装

```js
pragma solidity ^0.4.10;
 
import "./AbstractENS.sol";
 
contract Resolver {
    function setName(bytes32 node, string name) public;
}
 
/**
 * @dev Provides a default implementation of a resolver for reverse records,
 * which permits only the owner to update it.
 */
contract DefaultReverseResolver is Resolver {
    AbstractENS public ens;
    mapping(bytes32=>string) public name;
 
    /**
     * @dev Constructor
     * @param ensAddr The address of the ENS registry.
     */
    function DefaultReverseResolver(AbstractENS ensAddr) {
        ens = ensAddr;
    }
 
    /**
     * @dev Only permits calls by the reverse registrar.
     * @param node The node permission is required for.
     */
    modifier owner_only(bytes32 node) {
        require(msg.sender == ens.owner(node));
        _;
    }
 
    /**
     * @dev Sets the name for a node.
     * @param node The node to update.
     * @param _name The name to set.
     */
    function setName(bytes32 node, string _name) public owner_only(node) {
        name[node] = _name;
    }
}
 
contract ReverseRegistrar {
    // namehash('addr.reverse')
    bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;
 
    AbstractENS public ens;
    Resolver public defaultResolver;
 
    /**
     * @dev Constructor
     * @param ensAddr The address of the ENS registry.
     * @param resolverAddr The address of the default reverse resolver.
     */
    function ReverseRegistrar(AbstractENS ensAddr, Resolver resolverAddr) {
        ens = ensAddr;
        defaultResolver = resolverAddr;
    }
 
    /**
     * @dev Transfers ownership of the reverse ENS record associated with the
     *      calling account.
     * @param owner The address to set as the owner of the reverse record in ENS.
     * @return The ENS node hash of the reverse record.
     */
    function claim(address owner) returns (bytes32 node) {
        return claimWithResolver(owner, 0);
    }
 
    /**
     * @dev Transfers ownership of the reverse ENS record associated with the
     *      calling account.
     * @param owner The address to set as the owner of the reverse record in ENS.
     * @param resolver The address of the resolver to set; 0 to leave unchanged.
     * @return The ENS node hash of the reverse record.
     */
    function claimWithResolver(address owner, address resolver) returns (bytes32 node) {
        var label = sha3HexAddress(msg.sender);
        node = sha3(ADDR_REVERSE_NODE, label);
        var currentOwner = ens.owner(node);
 
        // Update the resolver if required
        if(resolver != 0 && resolver != ens.resolver(node)) {
            // Transfer the name to us first if it's not already
            if(currentOwner != address(this)) {
                ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, this);
                currentOwner = address(this);
            }
            ens.setResolver(node, resolver);
        }
 
        // Update the owner if required
        if(currentOwner != owner) {
            ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner);
        }
 
        return node;
    }
 
    /**
     * @dev Sets the `name()` record for the reverse ENS record associated with
     * the calling account. First updates the resolver to the default reverse
     * resolver if necessary.
     * @param name The name to set for this address.
     * @return The ENS node hash of the reverse record.
     */
    function setName(string name) returns (bytes32 node) {
        node = claimWithResolver(this, defaultResolver);
        defaultResolver.setName(node, name);
        return node;
    }
 
    /**
     * @dev Returns the node hash for a given account's reverse records.
     * @param addr The address to hash
     * @return The ENS node hash.
     */
    function node(address addr) constant returns (bytes32 ret) {
        return sha3(ADDR_REVERSE_NODE, sha3HexAddress(addr));
    }
 
    /**
     * @dev An optimised function to compute the sha3 of the lower-case
     *      hexadecimal representation of an Ethereum address.
     * @param addr The address to hash
     * @return The SHA3 hash of the lower-case hexadecimal encoding of the
     *         input address.
     */
    function sha3HexAddress(address addr) private returns (bytes32 ret) {
        addr; ret; // Stop warning us about unused variables
        assembly {
            let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000
            let i := 40
        loop:
            i := sub(i, 1)
            mstore8(i, byte(and(addr, 0xf), lookup))
            addr := div(addr, 0x10)
            i := sub(i, 1)
            mstore8(i, byte(and(addr, 0xf), lookup))
            addr := div(addr, 0x10)
            jumpi(loop, i)
            ret := sha3(0, 40)
        }
    }
}
```

### 変数

#### `ens`

```javascript
AbstractENS public ens;
```

ENSレジストリを参照するための変数。
この変数はENSレジストリコントラクトのアドレスを保持します。
リバースレコードの所有者やリゾルバの情報を取得・更新するときに利用されます。

**パラメータ**

* `ens`
    * ENSレジストリのコントラクト参照。

#### `name`

```javascript
mapping(bytes32=>string) public name;
```

ノードに対応する名前を保存するマッピング。
各ENSノードに対して登録された名前文字列を保持します。
ノードは `namehash` によって一意に識別されて対応する文字列を取り出せます。

**パラメータ**

* `bytes32`
    * ENSノードのハッシュ値。
* `string`
    * ノードに対応する人間が読める名前。

#### `ADDR_REVERSE_NODE`

```javascript
bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;
```

`addr.reverse` ドメインの `namehash` を保持する定数。
ENSにおいてリバースレコードは `addr.reverse` ドメインの下に保存されます。
この変数はそのドメインの固定されたハッシュ値を定義しています。

**パラメータ**

* `ADDR_REVERSE_NODE`
    * `addr.reverse` の namehash。

#### `defaultResolver`

```javascript
Resolver public defaultResolver;
```

デフォルトで利用するリバースリゾルバを保持する変数。
ユーザーが簡単に名前を設定できるように、デフォルトのリゾルバがあらかじめ指定されています。
`setName` 関数で利用されます。

**パラメータ**

* `defaultResolver`
    * デフォルトのReverse Resolverコントラクト。

### 修飾子

#### `owner_only`

```javascript
modifier owner_only(bytes32 node) {
    require(msg.sender == ens.owner(node));
    _;
}
```

ノードの所有者のみが操作可能であることを保証する修飾子。
特定のENSノードに対して処理を行うとき、そのノードの所有者アドレスと呼び出し元アドレスが一致することを確認します。
この制御により、権限を持たないアドレスによる不正な更新を防ぎます。

**パラメータ**

* `node`
    * 処理対象となるENSノードのハッシュ。

### 関数

#### `DefaultReverseResolver`

```javascript
function DefaultReverseResolver(AbstractENS ensAddr) {
    ens = ensAddr;
}
```

`DefaultReverseResolver` のコンストラクタ。
ENSレジストリのアドレスを受け取り、それを `ens` 変数に格納します。

**引数**

* `ensAddr`
    * ENSレジストリのコントラクトアドレス。

#### `setName`

```javascript
function setName(bytes32 node, string _name) public owner_only(node) {
    name[node] = _name;
}
```

指定したノードに名前を設定する関数。
ノードの所有者のみが呼び出せます。
指定されたENSノードに対して、人間が読める名前を登録します。

**引数**

* `node`
    * ENSノードのハッシュ。
* `_name`
    * 設定する名前。

#### `ReverseRegistrar`

```javascript
function ReverseRegistrar(AbstractENS ensAddr, Resolver resolverAddr) {
    ens = ensAddr;
    defaultResolver = resolverAddr;
}
```

`ReverseRegistrar` のコンストラクタ。
ENSレジストリとデフォルトリゾルバのアドレスを受け取り、内部変数に保存します。

**引数**

* `ensAddr`
    * ENSレジストリのコントラクトアドレス。
* `resolverAddr`
    * デフォルトリバースリゾルバのコントラクトアドレス。

#### `claim`

```javascript
function claim(address owner) returns (bytes32 node) {
    return claimWithResolver(owner, 0);
}
```

呼び出し元アカウントのリバースレコード所有権を移転する関数。
ENSレジストリ内のリバースレコードを指定した `owner` に移転します。
内部的に `claimWithResolver` を呼び出します。

**引数**

* `owner`
    * リバースレコードの新しい所有者アドレス。

**戻り値**

* `node`
    * 対象のENSノードハッシュ。

#### `claimWithResolver`

```javascript
function claimWithResolver(address owner, address resolver) returns (bytes32 node) {
    var label = sha3HexAddress(msg.sender);
    node = sha3(ADDR_REVERSE_NODE, label);
    var currentOwner = ens.owner(node);

    if(resolver != 0 && resolver != ens.resolver(node)) {
        if(currentOwner != address(this)) {
            ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, this);
            currentOwner = address(this);
        }
        ens.setResolver(node, resolver);
    }

    if(currentOwner != owner) {
        ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner);
    }

    return node;
}
```

リバースレコードの所有権およびリゾルバを設定する関数。
呼び出し元アドレスを基にリバースレコードのノードを計算し、指定された `owner` に所有権を移します。
必要に応じてリゾルバも更新します。

**引数**

* `owner`
    * 移転先の所有者アドレス。
* `resolver`
    * 設定するリゾルバアドレス。`0` の場合は変更なし。

**戻り値**

* `node`
    * 処理対象のENSノードハッシュ。

#### `setName`

```javascript
function setName(string name) returns (bytes32 node) {
    node = claimWithResolver(this, defaultResolver);
    defaultResolver.setName(node, name);
    return node;
}
```

呼び出し元アドレスのリバースレコードに名前を設定する関数。
デフォルトのリゾルバを用いて、リバースレコードに人間が読める名前を設定します。
所有権が未設定の場合は、内部的に `claimWithResolver` を利用して設定します。

**引数**

* `name`
    * 登録する名前。

**戻り値**

* `node`
    * ENSノードハッシュ。

#### `node`

```javascript
function node(address addr) constant returns (bytes32 ret) {
    return sha3(ADDR_REVERSE_NODE, sha3HexAddress(addr));
}
```

指定アドレスのリバースレコードのノードハッシュを返す関数。
与えられたEthereumアドレスを16進小文字に変換し、そのハッシュを計算してノードを返します。

**引数**

* `addr`
    * 対象のEthereumアドレス。

**戻り値**

* `ret`
    * 計算されたENSノードハッシュ。

#### `sha3HexAddress`

```javascript
function sha3HexAddress(address addr) private returns (bytes32 ret) {
    addr; ret;
    assembly {
        let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000
        let i := 40
    loop:
        i := sub(i, 1)
        mstore8(i, byte(and(addr, 0xf), lookup))
        addr := div(addr, 0x10)
        i := sub(i, 1)
        mstore8(i, byte(and(addr, 0xf), lookup))
        addr := div(addr, 0x10)
        jumpi(loop, i)
        ret := sha3(0, 40)
    }
}
```

Ethereumアドレスを小文字16進文字列に変換し、そのSHA3ハッシュを返す関数。
EVMのアセンブリコードを用いて、アドレスを効率的に16進数文字列へ変換し、その文字列のSHA3を計算します。
ENSノードを生成するために利用されます。

**引数**

* `addr`
    * Ethereumアドレス。

**戻り値**

* `ret`
    * 小文字16進表記アドレスのSHA3ハッシュ。

## 最後に

今回は「Ethereumアドレスに名前を紐づけて、ウォレットやアプリがその名前を正しく表示できる仕組みを提案している**ENSIP3**」についてまとめてきました！
いかがだったでしょうか？

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