はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、ENSに「ワイルドカード解決」を導入し、未登録のサブドメインでも親ドメインの設定を使って自動的に解決できるようにする仕組みを提案しているENSIP10についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIPについてまとめています。
概要
Ethereum Name Service(ENS)は、人が覚えやすい名前をブロックチェーン上のアドレスなどに対応づける仕組みです。
通常は以下の手順で解決(リゾルブ)が行われます。
- クライアントが入力された名前に対して「
namehash
」という計算を行い、固有の識別子(node)を得る。 - その
node
をENSレジストリに問い合わせ、どのリゾルバ(解決コントラクト)が割り当てられているかを調べる。 - リゾルバが設定されていれば、その
node
をリゾルバに渡し、アドレスなどの情報を取得する。
しかし、従来の仕様では「リゾルバが設定されていない場合」に解決処理がそこで止まってしまいます。
ENSIP10では、この場合に追加の手順を導入します。
左端のラベル(サブドメイン部分)を取り除き、新しい node
を生成して再びENSレジストリを参照し、リゾルバが存在すればそれを利用して最初の完全な名前の解決を行います。
この処理はリゾルバが見つかるまで繰り返されます。
さらに、新しい resolve()
メソッドを導入し、従来より柔軟な解決処理を可能にします。これにより、より高度な名前解決の仕組みを実現できます。
動機
この仕様変更の背景には、ENSをより多くのユーザーにとって使いやすくするという目的があります。
特に以下の課題がありました。
まず、ウォレットや取引所、dApp などのサービスは、ユーザーに対して共通の親ドメイン(例:example.eth
)のサブドメインを割り当てたいというニーズを持っています。
しかし、従来は各サブドメインごとにENSレジストリに個別の設定を行う必要があり、大量のユーザーを抱えるサービスにとってコストが非常に高くなっていました。
また、新規ユーザーはアカウント作成直後にサブドメインをすぐに利用できず、リゾルバを設定するためのトランザクションが承認されるのを待たなければなりませんでした。
これがユーザー体験を損ねる要因になっていました。
今回の拡張で導入される「ワイルドカード解決」により、未設定のサブドメインでも親ドメイン側のリゾルバが自動的に解決を行えるようになります。
これにより、ユーザーは即時に ENS 名を利用でき、サービス側はコストを大幅に削減できます。
さらに、ワイルドカード解決は柔軟に設計できるため、例として以下のような利用が可能です。
-
CREATE2
(コントラクトの予測可能なアドレス生成)によるアドレス割り当て - 特定のフォールバックアドレスに自動的に紐づける仕組み
CREATE2
については以下の記事を参考にしてください。
重要なのは、この変更が既存のENSレジストリやリゾルバを壊さず互換性を維持する点です。
従来のクライアントは新機能を理解しなくても既存の ENS 名を解決できます。
仕様
用語の定義
仕様を理解するために以下の用語が定義されています。
-
namehash
ENSIP1で定義されているアルゴリズム。ドメイン名を固定長の識別子(node)に変換します。
ENSIP1については以下の記事を参考にしてください。
-
dnsencode
DNS名をバイト列に変換する方法。
RFC1035を基準にしていますが、ENSでは長さの制限がなく、空文字列は「.」と同じく0オクテットで表されます。 -
parent
ドメインから最初のラベルを削除する関数。
例えばfoo.eth
の親はeth
になります。
トップレベル(例:eth
)の親は空文字列となります。 -
ens
現在利用しているネットワークのENSレジストリコントラクト。
新しいリゾルバインターフェース
ENSIP10では、既存の仕様に準拠したリゾルバが新しいインターフェースを実装できるようになっています。
interface ExtendedResolver {
function resolve(bytes calldata name, bytes calldata data) external view returns(bytes);
}
もしリゾルバがこの resolve
関数を実装している場合は、supportsInterface()
を呼んだときに 0x9061b923
を返す必要があります。
クライアントは DNS形式にエンコードした名前と、必要な解決関数のABIエンコード済みデータを resolve
に渡します。
リゾルバは対応していれば結果を返し、対応していなければ revert(失敗)します。
リゾルバを探す手順
ENSクライアントは、名前に対応するリゾルバを以下の手順で決定します。
-
currentname
を最初は完全な名前(例:foo.bar.eth
)に設定する。 -
ens.resolver(namehash(currentname))
を呼び出し、リゾルバを探す。 - リゾルバが設定されていればそれを返し、処理を終了する。
- もし
currentname
が空文字(または.
)であれば、リゾルバが見つからなかったとして終了する。 - そうでなければ
parent(currentname)
を計算し、再び手順 2 に戻る。
この繰り返しによって、親ドメインにリゾルバが設定されていれば、それを利用できる仕組みになっています。
レコードを解決する手順
リゾルバが見つかった場合、クライアントは次のように処理します。
- 必要な関数のABIエンコード済みデータを用意する(例: アドレスを解決する場合は
addr(namehash(name))
のデータ)。 - リゾルバが
0x9061b923
をサポートしているか確認する。 - サポートしていれば
resolver.resolve(dnsencode(name), calldata)
を呼び出し、返ってきたデータをデコードする。 - サポートしていないが
name
とresolverName
が一致する場合は、従来の関数(例:resolver.addr(namehash(name))
)を呼ぶ。 - どちらも満たさなければ解決は失敗する。
- 結果をデコードして返す。
重要な点は、ワイルドカード解決が使われている場合(つまり name
と currentname
が異なる場合)、クライアントは addr
のような従来のメソッドを直接呼んではならないことです。
従来メソッドは、完全一致でリゾルバが設定されている場合のみ利用できます。
擬似コードによる流れ
リゾルバを取得する関数と、レコードを解決する関数は次のように表せます。
function getResolver(name) {
for(let currentname = name; currentname !== ''; currentname = parent(currentname)) {
const node = namehash(currentname);
const resolver = ens.resolver(node);
if(resolver != '0x0000000000000000000000000000000000000000') {
return [resolver, currentname];
}
}
return [null, ''];
}
function resolve(name, func, ...args) {
const [resolver, resolverName] = getResolver(name);
if(resolver === null) {
return null;
}
const supportsENSIP10 = resolver.supportsInterface('0x9061b923');
if(supportsENSIP10) {
const calldata = resolver[func].encodeFunctionCall(namehash(name), ...args);
const result = resolver.resolve(dnsencode(name), calldata);
return resolver[func].decodeReturnData(result);
} else if(name == resolverName) {
return resolver[func](...args);
} else {
return null;
}
}
このコードが示すように、まずリゾルバを探索し、対応するインターフェースをサポートしていれば resolve()
を使って柔軟に解決します。
そうでなければ、従来どおり直接関数を呼び出す方式に fallback します。
補足
既存システムへの影響を最小化
ENSIP10の重要な点は、ワイルドカード解決を導入しても既存のENSシステムへの影響を最小限に抑えていることです。
新しい仕組みをゼロから作るのではなく、すでにあるアルゴリズムや処理手順をできるだけ再利用することで、クライアント開発者や運用者に余計な負担をかけないように設計されています。
ワイルドカード解決の合意と普及
ENSにワイルドカード解決を導入すること自体は、以前から望まれていた方向性でした。
この提案は、その合意を具体化するものです。
特に、スケーラビリティ(大量のユーザーに対応する能力)における課題を解消することで、より多くのサービスやアプリケーションに ENS を普及させやすくなります。
新しい resolve
関数の意義
今回導入される resolve
関数は、従来の仕組みに比べて少し複雑になります。
この関数は「ハッシュ化されていない名前」と「解決関数の入力データ」を直接受け取れるため、リゾルバがプレーンなラベル(例: id
や nifty
など)を扱えるようになります。
これによって、これまで不可能だった柔軟な解決が可能になります。
例えば id.nifty.eth
という名前を解決するとき、ワイルドカードリゾルバが id
を数値として解釈し、特定のNFTコレクションの所有者を返す、といった使い方ができます。
従来の namehash
方式ではすべてがハッシュ値になってしまい、ラベルの中身を直接利用できないため、このような応用はできませんでした。
DNS形式を採用する理由
ENSIP10では、名前の表現にDNSのワイヤーフォーマット(dnsencode)を使います。
理由は以下の通りです。
- 名前を効率的にハッシュ化できる
- 個別のラベルを簡単に取り出したり削除したりできる
- ドット区切りの文字列形式(例:
foo.bar.eth
)よりも処理が高速でガスコストも安い
つまり、DNSフォーマットを採用することで、ブロックチェーン上での計算コストを抑えつつ、ワイルドカード解決に必要な操作を柔軟に行えるようになります。
互換性
ENSIP10は既存のENSクライアントとの互換性を意識して設計されています。
ENSIP1に準拠した古いクライアントは、ワイルドカードレコードを解決できず、単に失敗として扱います。
一方で、ENSIP10に準拠した新しいクライアントは、従来のENSレコードをそのまま正しく解決できるか拒否します。
新しい resolve
関数は、ワイルドカードだけでなく通常の名前解決にも利用できます。
しかし、レガシークライアントは従来どおり addr
などの個別関数を呼び出すため、リゾルバを実装する側はその場合の戻り値をどう扱うかを考える必要があります。
そうすることで古いクライアントとの互換性をより高められます。
また、クライアントがワイルドカードリゾルバに対して addr
のような従来の関数を直接呼び出すことは禁止されています。
もしこれを許可すると、すべての名前に回答してしまうリゾルバとの間で互換性問題が発生する可能性があるためです。
セキュリティ
セキュリティ面でもいくつかの注意点があります。
ENSクライアントの基本的な動作として、リゾルバが設定されていないレコードは解決しません。
しかし、誤って設定されたクライアントは、存在しないリゾルバを参照したり、null アドレス(0 アドレス)をリゾルバとして扱ってしまう危険があります。
これは、誤った解決や予期しない挙動を引き起こす要因になります。
さらに、ワイルドカードリゾルバが任意のサブドメインを解決できるようになると、タイプミスによる資金誤送付のリスクが高まります。
例えば、正しい名前のつもりで誤字を含んだENS名に送金すると、意図しない相手に資産が送られてしまう可能性があります。
この問題を防ぐために、アプリケーションは追加の検証機能や送金回復の仕組みを導入することが推奨されます。
また、一部のアプリケーションは特定のサブドメインに対して「解決されないこと」を前提とする場合があります。
しかし、この場合でも親ドメインがそのサブドメインを解決してしまう可能性があります。
最後に
今回は「」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!