はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、Ethereumのアドレスを簡単な名前に変換する、ENSと似た目的を持った提案している規格であるERC4834についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なERCについてまとめています。
概要
コントラクトを人間にとって親しみやすい名前で特定できるようにするための規格です。
これはERC137、つまり「ENS(Ethereum Name Service)」と似た目的を持っています。
ENSは、複雑なイーサリアムのアドレスを簡単な名前に変換するシステムです。
ERC137については以下を参考にしてください。
このEIPによって、ドメインと呼ばれる特定のコントラクトは、簡単な名前で識別できるようになります。
これは、難解なアドレスやコードの代わりに、覚えやすい名前でコントラクトにアクセスできることを意味します。
また、このシステムは複雑なアクセス制御と名前解決の機能を提供します。
これにより、ユーザーのロール(権限や役職)や条件に応じて、コントラクトとのやり取りが可能になります。
たとえば、特定のロールを持つユーザーだけが、mint
などの特定の関数を実行できるような制御が設定できます。
このEIPはイーサリアムのブロックチェーンをより使いやすく、セキュアなものにするための名前解決とアクセス制御システムを目指しています。
動機
このテキストでは、イーサリアムのブロックチェーンにおける特定のEIP(Ethereum Improvement Proposal)が、従来の標準と比較してどのような利点を持っているかを説明しています。
このEIPの大きな利点は、名前解決のための最小限のインターフェースを提供し、標準化されたアクセス制御を導入し、シンプルなアーキテクチャを持つことです
ENS(Ethereum Name Service)と比較すると、ENSは便利ですがより複雑なアーキテクチャを持ち、標準化されたアクセス制御がないという点で異なります。
加えて、このEIPでは、すべてのドメイン(サブドメイン、トップレベルドメイン(TLD)、ルート自体を含む)が実際にドメインとして実装されています。
これにより、名前解決は簡単な反復アルゴリズムによって行われるようになり、これはDNS(Domain Name System)に似たものです。
このEIPはイーサリアムのブロックチェーンでコントラクトの名前解決をより簡単かつ効率的に行う方法を提供します。
これにより、コントラクトの管理がよりシンプルで使いやすくなります。
また、アクセス制御が標準化されているため、セキュリティと運用管理が強化されます。
仕様
コントラクトインターフェース
interface IDomain {
/// @notice Query if a domain has a subdomain with a given name
/// @param name The subdomain to query, in right to left order
/// @return `true` if the domain has a subdomain with the given name, `false` otherwise
function hasDomain(string[] memory name) external view returns (bool);
/// @notice Fetch the subdomain with a given name
/// @dev This should revert if `hasDomain(name)` is `false`
/// @param name The subdomain to fetch, in right to left order
/// @return The subdomain with the given name
function getDomain(string[] memory name) external view returns (address);
}
関数
hasDomain
function hasDomain(string[] memory name) external view returns (bool);
概要
ドメインが特定の名前のサブドメインを持っているか確認する関数。
詳細
この関数は、指定された名前のサブドメインがドメインに存在するかどうかを確認します。
サブドメインは右から左の順序で指定されます。
引数
-
name
- クエリするサブドメインの名前(右から左の順序)。
戻り値
-
bool
- ドメインが指定された名前のサブドメインを持っている場合は
true
、そうでない場合はfalse
。
- ドメインが指定された名前のサブドメインを持っている場合は
getDomain
function getDomain(string[] memory name) external view returns (address);
概要
特定の名前のサブドメインを取得する関数。
詳細
この関数は、hasDomain(name)
がfalse
の場合には失敗するべきです。サブドメインは右から左の順序で指定されます。
引数
-
name
- 取得するサブドメインの名前(右から左の順序)。
戻り値
-
address
- 指定された名前のサブドメイン。
名前解決
このテキストでは、イーサリアムのブロックチェーン上で動作するスマートコントラクトの中で、特定の名前(ドメイン名)を解決する方法について説明しています。
具体的には、a.b.cのような名前をどのようにして特定のアドレスに対応付けるかについての説明です。
まず、解決したい名前をピリオド(.)で区切って分割します。
例えば a.b.cは ["a", "b", "c"] という配列になります。
次に、最初のドメインをルートドメイン(最上位のドメイン)とし、パス(途中経過を記録するリスト)を空に設定します。
その後、配列の最後の要素(この例では c)を取り出し、パスに追加します。
そして、domain.hasDomain(path)
関数を使用して、現在のドメインがそのサブドメイン(ここでは c)を持っているかを確認します。
もし持っていなければ、名前解決は失敗となります。
持っていれば、domain.getDomain(path)
関数を使って新しいドメイン(サブドメイン)を取得し、これを現在のドメインとして設定します。
このプロセスは、配列の要素がなくなるまで繰り返します。
つまり、一つずつ要素を取り出し、それが有効なサブドメインであるかを確認して次のサブドメインに進んでいきます。
このようにして、最終的に完全なドメイン名に対応するアドレスを得ることができます。
例として挙げられた resolve
関数は、この名前解決プロセスを実装したSolidityのコードです。
この関数は、分割された名前の配列とルートドメインを受け取り、上記の手順に従って名前を解決し、最終的に対応するアドレスを返します。
このような仕組みにより、イーサリアムのブロックチェーン上で効率的に名前解決を行うことができます。
function resolve(string[] calldata splitName, IDomain root) public view returns (address) {
IDomain current = root;
string[] memory path = [];
for (uint i = splitName.length - 1; i >= 0; i--) {
// Append to back of list
path.push(splitName[i]);
// Require that the current domain has a domain
require(current.hasDomain(path), "Name resolution failed");
// Resolve subdomain
current = current.getDomain(path);
}
return current;
}
登録機能(オプション拡張)
interface IDomainRegisterable is IDomain {
//// Events
/// @notice Must be emitted when a new subdomain is created (e.g. through `createDomain`)
/// @param sender msg.sender for createDomain
/// @param name name for createDomain
/// @param subdomain subdomain in createDomain
event SubdomainCreate(address indexed sender, string name, address subdomain);
/// @notice Must be emitted when the resolved address for a domain is changed (e.g. with `setDomain`)
/// @param sender msg.sender for setDomain
/// @param name name for setDomain
/// @param subdomain subdomain in setDomain
/// @param oldSubdomain the old subdomain
event SubdomainUpdate(address indexed sender, string name, address subdomain, address oldSubdomain);
/// @notice Must be emitted when a domain is unmapped (e.g. with `deleteDomain`)
/// @param sender msg.sender for deleteDomain
/// @param name name for deleteDomain
/// @param subdomain the old subdomain
event SubdomainDelete(address indexed sender, string name, address subdomain);
//// CRUD
/// @notice Create a subdomain with a given name
/// @dev This should revert if `canCreateDomain(msg.sender, name, pointer)` is `false` or if the domain exists
/// @param name The subdomain name to be created
/// @param subdomain The subdomain to create
function createDomain(string memory name, address subdomain) external payable;
/// @notice Update a subdomain with a given name
/// @dev This should revert if `canSetDomain(msg.sender, name, pointer)` is `false` of if the domain doesn't exist
/// @param name The subdomain name to be updated
/// @param subdomain The subdomain to set
function setDomain(string memory name, address subdomain) external;
/// @notice Delete the subdomain with a given name
/// @dev This should revert if the domain doesn't exist or if `canDeleteDomain(msg.sender, name)` is `false`
/// @param name The subdomain to delete
function deleteDomain(string memory name) external;
//// Parent Domain Access Control
/// @notice Get if an account can create a subdomain with a given name
/// @dev This must return `false` if `hasDomain(name)` is `true`.
/// @param updater The account that may or may not be able to create/update a subdomain
/// @param name The subdomain name that would be created/updated
/// @param subdomain The subdomain that would be set
/// @return Whether an account can update or create the subdomain
function canCreateDomain(address updater, string memory name, address subdomain) external view returns (bool);
/// @notice Get if an account can update or create a subdomain with a given name
/// @dev This must return `false` if `hasDomain(name)` is `false`.
/// If `getDomain(name)` is also a domain implementing the subdomain access control extension, this should return `false` if `getDomain(name).canMoveSubdomain(msg.sender, this, subdomain)` is `false`.
/// @param updater The account that may or may not be able to create/update a subdomain
/// @param name The subdomain name that would be created/updated
/// @param subdomain The subdomain that would be set
/// @return Whether an account can update or create the subdomain
function canSetDomain(address updater, string memory name, address subdomain) external view returns (bool);
/// @notice Get if an account can delete the subdomain with a given name
/// @dev This must return `false` if `hasDomain(name)` is `false`.
/// If `getDomain(name)` is a domain implementing the subdomain access control extension, this should return `false` if `getDomain(name).canDeleteSubdomain(msg.sender, this, subdomain)` is `false`.
/// @param updater The account that may or may not be able to delete a subdomain
/// @param name The subdomain to delete
/// @return Whether an account can delete the subdomain
function canDeleteDomain(address updater, string memory name) external view returns (bool);
}
イベント
SubdomainCreate
event SubdomainCreate(address indexed sender, string name, address subdomain);
概要
新しいサブドメインが作成されたときに発行されるイベント。
詳細
このイベントは、例えばcreateDomain
関数を通じて新しいサブドメインが作成された場合に発行されます。
パラメータ
-
sender
-
createDomain
を呼び出した際の送信者のアドレス。
-
-
name
-
createDomain
で使用される名前。
-
-
subdomain
-
createDomain
で作成されたサブドメインのアドレス。
-
SubdomainUpdate
event SubdomainUpdate(address indexed sender, string name, address subdomain, address oldSubdomain);
概要
ドメインの解決されたアドレスが変更されたときに発行されるイベント。
詳細
このイベントは、例えばsetDomain
を使用してドメインの解決されたアドレスが変更された場合に発行されます。
パラメータ
-
sender
-
setDomain
を呼び出した際の送信者のアドレス。
-
-
name
-
setDomain
で使用される名前。
-
-
subdomain
-
setDomain
で指定された新しいサブドメインのアドレス。
-
-
oldSubdomain
- 以前のサブドメインのアドレス。
SubdomainDelete
event SubdomainDelete(address indexed sender, string name, address subdomain);
概要
ドメインがアンマップ(削除)されたときに発行されるイベント。
詳細
このイベントは、例えばdeleteDomain
を使用してドメインがアンマップされた場合に発行されます。
パラメータ
-
sender
-
deleteDomain
を呼び出した際の送信者のアドレス。
-
-
name
-
deleteDomain
で使用される名前。
-
-
subdomain
- 削除されたサブドメインのアドレス。
関数
createDomain
function createDomain(string memory name, address subdomain) external payable;
概要
指定された名前でサブドメインを作成する関数。
詳細
この関数は、canCreateDomain(msg.sender, name, pointer)
がfalse
である場合、またはドメインが既に存在する場合には失敗します。
引数
-
name
- 作成するサブドメインの名前。
-
subdomain
- 作成するサブドメイン。
setDomain
function setDomain(string memory name, address subdomain) external;
概要
指定された名前のサブドメインを更新する関数。
詳細
この関数は、canSetDomain(msg.sender, name, pointer)
がfalse
である場合、またはドメインが存在しない場合には失敗します。
引数
-
name
- 更新するサブドメインの名前。
-
subdomain
- 設定するサブドメイン。
deleteDomain
function deleteDomain(string memory name) external;
概要
指定された名前のサブドメインを削除する関数。
詳細
この関数は、ドメインが存在しない場合やcanDeleteDomain(msg.sender, name)
がfalse
である場合には失敗します。
引数
-
name
- 削除するサブドメインの名前。
canCreateDomain
function canCreateDomain(address updater, string memory name, address subdomain) external view returns (bool);
概要
アカウントが指定された名前でサブドメインを作成できるか取得する関数。
詳細
この関数は、hasDomain(name)
がtrue
である場合にfalse
を返さなければなりません。
引数
-
updater
- サブドメインを作成/更新する可能性があるアカウント。
-
name
- 作成/更新されるサブドメインの名前。
-
subdomain
- 設定されるサブドメイン。
戻り値
-
bool
- アカウントがサブドメインを更新または作成できるかどうか。
canSetDomain
function canSetDomain(address updater, string memory name, address subdomain) external view returns (bool);
概要
アカウントが指定された名前でサブドメインを更新または作成できるか取得する関数。
詳細
この関数は、hasDomain(name)
がfalse
である場合にfalse
を返さなければなりません。
また、getDomain(name)
がサブドメインアクセス制御拡張を実装しているドメインである場合、getDomain(name).canMoveSubdomain(msg.sender, this, subdomain)
がfalse
であればfalse
を返します。
引数
-
updater
- サブドメインを作成/更新する可能性があるアカウント。
-
name
- 作成/更新されるサブドメインの名前。
-
subdomain
- 設定されるサブドメイン。
戻り値
-
bool
- アカウントがサブドメインを更新または作成できるかどうか。
canDeleteDomain
function canDeleteDomain(address updater, string memory name) external view returns (bool);
概要
アカウントが指定された名前のサブドメインを削除できるか取得する関数。
詳細
この関数は、hasDomain(name)
がfalse
である場合にfalse
を返さなければなりません。
また、getDomain(name)
がサブドメインアクセス制御拡張を実装しているドメインである場合、getDomain(name).canDeleteSubdomain(msg.sender, this, subdomain)
がfalse
であればfalse
を返します。
引数
-
updater
- サブドメインを削除する可能性があるアカウント。
-
name
- 削除されるサブドメインの名前。
戻り値
-
bool
- アカウントがサブドメインを削除できるかどうか。
列挙機能(オプション拡張)
interface IDomainEnumerable is IDomain {
/// @notice Query all subdomains. Must revert if the number of domains is unknown or infinite.
/// @return The subdomain with the given index.
function subdomainByIndex(uint256 index) external view returns (string memory);
/// @notice Get the total number of subdomains. Must revert if the number of domains is unknown or infinite.
/// @return The total number of subdomains.
function totalSubdomains() external view returns (uint256);
}
subdomainByIndex
function subdomainByIndex(uint256 index) external view returns (string memory);
概要
指定されたインデックスにあるサブドメインを照会する関数。
詳細
この関数は、指定されたインデックスに対応するサブドメインを返します。
ドメインの数が未知または無限である場合、この関数は失敗する必要があります。
引数
-
index
- 照会するサブドメインのインデックス。
戻り値
-
string memory
- 指定されたインデックスのサブドメイン。
totalSubdomains
function totalSubdomains() external view returns (uint256);
概要
全てのサブドメインの総数を取得する関数。
詳細
この関数は、サブドメインの総数を返します。
ドメインの数が未知または無限である場合、この関数は失敗する必要があります。
戻り値
-
uint256
- サブドメインの総数。
アクセスコントロール(オプション拡張)
interface IDomainAccessControl is IDomain {
/// @notice Get if an account can move the subdomain away from the current domain
/// @dev May be called by `canSetDomain` of the parent domain - implement access control here!!!
/// @param updater The account that may be moving the subdomain
/// @param name The subdomain name
/// @param parent The parent domain
/// @param newSubdomain The domain that will be set next
/// @return Whether an account can update the subdomain
function canMoveSubdomain(address updater, string memory name, IDomain parent, address newSubdomain) external view returns (bool);
/// @notice Get if an account can unset this domain as a subdomain
/// @dev May be called by `canDeleteDomain` of the parent domain - implement access control here!!!
/// @param updater The account that may or may not be able to delete a subdomain
/// @param name The subdomain to delete
/// @param parent The parent domain
/// @return Whether an account can delete the subdomain
function canDeleteSubdomain(address updater, string memory name, IDomain parent) external view returns (bool);
}
canMoveSubdomain
function canMoveSubdomain(address updater, string memory name, IDomain parent, address newSubdomain) external view returns (bool);
概要
アカウントが現在のドメインからサブドメインを移動できるか取得する関数。
詳細
この関数は、あるアカウントが特定のサブドメインを新しいドメインに移動できるかどうかを判断します。
これは、親ドメインのcanSetDomain
から呼び出される可能性があり、アクセス制御をここで実装する必要があります。
引数
-
updater
- サブドメインを移動する可能性があるアカウント。
-
name
- サブドメインの名前。
-
parent
- 親ドメイン。
-
newSubdomain
- 次に設定されるドメイン。
戻り値
-
bool
- アカウントがサブドメインを更新できるかどうか。
canDeleteSubdomain
function canDeleteSubdomain(address updater, string memory name, IDomain parent) external view returns (bool);
概要
アカウントがこのドメインをサブドメインとして解除できるか取得する関数。
詳細
この関数は、あるアカウントが特定のサブドメインを削除(解除)できるかどうかを判断します。
これは、親ドメインのcanDeleteDomain
から呼び出される可能性があり、アクセス制御をここで実装する必要があります。
引数
-
updater
- サブドメインを削除する可能性があるアカウント。
-
name
- 削除するサブドメインの名前。
-
parent
- 親ドメイン。
戻り値
-
bool
- アカウントがサブドメインを削除できるかどうか。
補足
このEIPの主な目的は、名前解決のための簡単で使いやすいインターフェースを提供することです。
名前解決アルゴリズムの特徴
このEIPにおける名前解決は、ENS(Ethereum Name Service)とは異なり、解決過程にあるコントラクトによって完全にコントロールされます。
この方法はユーザーにとって理解しやすいです。
このアプローチにより、例えば時間帯に応じて解決先を変更するなど、より柔軟な設計が可能になります。
親ドメインのアクセス制御
一般的な「ownable
」インターフェースは使用されず、より汎用的な設計が採用されています。
もし特定の「ownable
」実装が必要な場合、それは別途実装可能です。
これにより親ドメインは、サブドメインのアクセス制御メソッドを呼び出すことができ、サブドメインも自由にアクセス制御メカニズムを選択できます。
サブドメインのアクセス制御
サブドメインは、必ずしも親ドメインのアクセス制御に従う必要はなく、これに対するメソッドが提供されています。
例えば、ルートドメインは譲渡不可能なトークンを持つDAOによって管理されることがあり、TLD(トップレベルドメイン)は持ち株を表すトークンを持つDAOによって管理されることがあります。
TLDの下のドメインは単一のオーナーによって、またそのサブドメインはNFTにリンクされた単一のオーナーによって管理されることもあります。
サブドメインのアクセス制御機能は、例えば鍵が失われた場合にサブドメインを回復するためのオーナーオーバーライドを含む提案です。
このEIPは、ブロックチェーン上でのドメインとサブドメインの管理において、より柔軟で直感的なアクセス制御と名前解決の方法を提供することを目指しています。
後方互換性
このEIPは、非常に汎用的な設計がされているため、ENSのような特定のシステムもサポートできるほど広範囲に対応しています。
一方で、ENSはこのEIPが提供するほどの幅広い機能や柔軟性を持っていないです。
つまり、このEIPはENSよりも多様な用途や機能に対応できるように作られています。
このEIPはENSを含む広い範囲のシステムやニーズに適応できる一方で、ENS自体はこのEIPのような広範な用途には対応できない、ということを意味しています。
セキュリティ考慮事項
悪意のあるcanMoveSubdomain
(ブラックホール)
説明
canMoveSubdomain
は、サブドメインをsetDomain
関数を使って移動する際の潜在的な危険性に関するものです。
親ドメインの実装次第で、新しいサブドメインがcanMoveSubdomain
で意図せずfalse
を返した場合、そのサブドメインはドメインの所有権を事実上固定することができます。
また、期待に反してtrue
を返す(バックドアのように)、コントラクトのオーナーがドメインを掌握する可能性もあります。
対策
クライアントは、新しいサブドメインのcanMoveSubdomain
やcanDeleteSubdomain
がfalse
に変わる場合に警告を出すことが望ましいです。
これらの関数は、状況によって結果が変わる可能性があるため、setDomain
を実行する前に新しいサブドメインのソースコードの監査が推奨されます。
親ドメインの名前解決
説明
親ドメインはサブドメインの名前解決に完全なコントロールを持ちます。
例えば、特定のドメインがa.b.cにリンクされている場合、b.cは、そのコードによってa.b.cを任意のドメインに設定することができ、cはb.c自体を任意のドメインに設定できます。
対策
既にリンクされているドメインを取得する前に、関連するコントラクトとその親ドメイン(ルートに至るまで)の監査を行うことが推奨されます。
これらの問題は、ブロックチェーン上でのドメイン操作を行う際の重要な安全上の考慮事項です。
ユーザーは、これらのリスクを理解し、適切な監査を通じて対策を講じる必要があります。
引用
Gavin John (@Pandapip1), "ERC-4834: Hierarchical Domains," Ethereum Improvement Proposals, no. 4834, February 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4834.
最後に
今回は「Ethereumのアドレスを簡単な名前に変換する、ENSと似た目的を持った提案している規格であるERC4834」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!