はじめに
初めまして。 『DApps開発入門』という本や色々記事を書いているかるでねです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回は「AIエージェントが使うツールをオンチェーンで発見・検証するAgent Tool Registryを定義するERC8257」についてまとめていきます。
ERC8257は、AIエージェント向けツールをオンチェーンのレジストリへ登録し、ツールのmanifest、アクセス条件、価格ヒント、実行環境の検証情報を発見できるようにする提案です。 ツールの実行や支払いはレジストリが扱わず、ツールを見つけて、正しいmanifestかを検証し、利用できるかを判定するための最小限の土台を定義します。
以下にまとめられているものを解説しながらまとめていきます。
概要
ERC8257は、AIエージェントが利用できるツールを登録するオンチェーンレジストリです。
登録情報は ToolConfig として保存され、ツールの説明本体はHTTPSで取得できるmanifestに置かれます。
レジストリがオンチェーンで持つ情報は、誰が登録したか、manifestをどこから取得するか、そのmanifestの keccak256 ハッシュ、アクセス判定を任せる外部コントラクトです。
ツールの入出力スキーマ、価格、アクセス条件、実行環境の説明はmanifestに入り、manifest全体の正しさをオンチェーンの manifestHash で検証します。
ERC8257の責務分担は以下です。

ツール提供者はmanifestを自分のドメイン配下で公開し、そのURIとハッシュを ToolRegistry へ登録します。
エージェントやウォレットは getToolConfig でオンチェーン設定を読み、manifestを取得してハッシュ検証とcreator検証を行ったうえで、必要なら hasAccess で利用可否を確認します。
この構成では、レジストリは支払いを処理しません。 価格情報はmanifestのヒントとして公開され、実際の支払い確認はツールのendpointや支払いプロトコルが担当します。 そのため、レジストリは「検索と検証の土台」に集中できます。
動機
AIエージェント向けツールは、各サービスや各プラットフォームのカタログに分散しがちです。 エージェントが自律的にツールを選ぶには、ツールの場所、入出力、価格、アクセス条件、実行環境を機械的に読める必要があります。
しかし、単なるWebカタログだけでは、どのツール情報が正しいかをオンチェーンの主体と結びつけにくくなります。 そこで ERC8257 は、manifestのハッシュをオンチェーンへ記録し、ツール情報が改ざんされていないことを確認できるようにします。
アクセス制御を外部コントラクトへ分ける理由
ツールごとのアクセス条件は、NFT保有、サブスクリプション、allowlist、DAO投票、評判スコアなどに広がります。 これらをレジストリ本体へ列挙すると、新しい条件を追加するたびにレジストリを変える必要があります。
ERC8257は accessPredicate という1つのアドレスにアクセス判定を委譲します。
accessPredicate が address(0) なら誰でも利用でき、外部コントラクトが設定されていれば、そのコントラクトへ staticcall して利用可否を判定します。
この「外部コントラクトに判定を任せる」設計は、ERC4337 のPaymasterや、Uniswap v4のhook、Seaportのzoneに近い考え方です。
ERC4337については以下の記事を参考にしてください。
価格情報をmanifestへ置く理由
エージェントが同じ目的のツールを比較する時、実行前に価格を知る必要があります。
ERC8257では、価格情報をmanifestの pricing 配列に置きます。
ただし、レジストリは資金を扱いません。
pricing は「いくら、どの資産で、誰へ、どのプロトコルで払うか」を示す発見用の情報で、支払いの強制はendpointや支払いプロトコル側で行います。
仕様
ToolConfig
ToolConfig は、登録済みツールのオンチェーン設定です。
struct ToolConfig {
address creator;
string metadataURI;
bytes32 manifestHash;
address accessPredicate;
}各フィールドの役割は以下です。
| フィールド | 役割 |
|---|---|
creator |
ツールを登録したアドレス。登録後も同じ値として扱われます |
metadataURI |
Tool Manifest JSONを取得するURI |
manifestHash |
JCSで正規化したmanifest bytesの keccak256 |
accessPredicate |
アクセス判定を任せるコントラクト。address(0) は公開ツール |
metadataURI は空ではない文字列で、2,048 bytes以内である必要があります。
manifestHash は bytes32(0) 以外である必要があります。
実在するコンテンツの keccak256 がゼロになる前提は置きにくいため、ゼロハッシュは意味を持たないコミットメントとして拒否されます。
IToolRegistry
IToolRegistry は、登録、更新、削除、参照、アクセス判定をまとめたインターフェースです。
interface IToolRegistry {
event ToolRegistered(
uint256 indexed toolId,
address indexed creator,
address indexed accessPredicate,
string metadataURI,
bytes32 manifestHash
);
event ToolMetadataUpdated(
uint256 indexed toolId,
string newURI,
bytes32 newHash
);
event AccessPredicateUpdated(
uint256 indexed toolId,
address indexed newPredicate
);
event ToolDeregistered(uint256 indexed toolId);
error ToolNotFound(uint256 toolId);
error NotToolCreator(uint256 toolId, address caller);
error InvalidMetadataURI();
error InvalidManifestHash();
error InvalidAccessPredicate(address predicate);
error ToolIsDeregistered(uint256 toolId);
function registerTool(
string calldata metadataURI,
bytes32 manifestHash,
address accessPredicate
) external returns (uint256 toolId);
function deregisterTool(uint256 toolId) external;
function updateToolMetadata(
uint256 toolId,
string calldata newURI,
bytes32 newHash
) external;
function setAccessPredicate(
uint256 toolId,
address newPredicate
) external;
function getToolConfig(
uint256 toolId
) external view returns (ToolConfig memory);
function hasAccess(
uint256 toolId,
address account,
bytes calldata data
) external view returns (bool);
function tryHasAccess(
uint256 toolId,
address account,
bytes calldata data
) external view returns (bool ok, bool granted);
function toolCount() external view returns (uint256);
function name() external view returns (string memory);
function version() external view returns (string memory);
}registerTool
function registerTool(
string calldata metadataURI,
bytes32 manifestHash,
address accessPredicate
) external returns (uint256 toolId);ツールを新規登録する関数です。
呼び出し元が creator として記録され、連番の toolId が割り当てられます。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
metadataURI |
string |
Tool Manifest JSONを取得するURI |
manifestHash |
bytes32 |
JCSで正規化したmanifest bytesの keccak256 |
accessPredicate |
address |
アクセス判定コントラクト。公開ツールなら address(0) |
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
登録されたツールID |
登録時には metadataURI と manifestHash の制約を確認し、accessPredicate も検証します。
accessPredicate がERC165に対応している場合は、IAccessPredicate をサポートしている必要があります。
ERC165に対応していないコントラクトや、まだコードが存在しないアドレスはbest-effortとして受け入れられますが、実行時のアクセス判定で失敗すればアクセス拒否として扱われます。
deregisterTool
function deregisterTool(uint256 toolId) external;ツールを恒久的に削除する関数です。
呼び出せるのは creator だけです。
削除された toolId は再利用されません。
削除後に getToolConfig、hasAccess、tryHasAccess、updateToolMetadata、setAccessPredicate、deregisterTool を呼ぶと ToolIsDeregistered でrevertします。
一時停止したい場合は、削除ではなく常に拒否するpredicateへ setAccessPredicate で切り替える想定です。
削除は戻せない操作として扱われます。
updateToolMetadata
function updateToolMetadata(
uint256 toolId,
string calldata newURI,
bytes32 newHash
) external;manifestのURIとハッシュを同時に更新する関数です。
呼び出せるのは creator だけです。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
更新するツールID |
newURI |
string |
新しいmanifest URI |
newHash |
bytes32 |
新しいmanifestのJCS正規化後ハッシュ |
manifestの入出力スキーマ、価格、アクセスヒント、検証情報を変えた時は、新しいmanifestを公開し、そのJCS正規化後ハッシュをオンチェーンへ反映します。
URIとハッシュのどちらかが変われば ToolMetadataUpdated イベントが発行されます。
setAccessPredicate
function setAccessPredicate(
uint256 toolId,
address newPredicate
) external;アクセス判定コントラクトを更新する関数です。
呼び出せるのは creator だけです。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
更新するツールID |
newPredicate |
address |
新しいアクセス判定コントラクト。公開ツールなら address(0) |
新しいpredicateへ変える時は、登録時と同じ検証ルールが適用されます。 同じpredicateを設定する呼び出しは状態が変わらないため、実装は検証やイベント発行を省略できます。
getToolConfig
function getToolConfig(
uint256 toolId
) external view returns (ToolConfig memory);登録済みツールの設定を返す参照関数です。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
参照するツールID |
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
ToolConfig |
ToolConfig |
登録済みツールのオンチェーン設定 |
未登録のIDなら ToolNotFound、削除済みなら ToolIsDeregistered でrevertします。
未登録、削除済み、アクセス不可は別々の状態として扱われます。
hasAccess
function hasAccess(
uint256 toolId,
address account,
bytes calldata data
) external view returns (bool);指定したアカウントがツールへアクセスできるかを返す参照関数です。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
判定するツールID |
account |
address |
アクセス可否を確認するアカウント |
data |
bytes |
predicateへ渡す文脈データ |
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
bool |
bool |
アクセスできるなら true |
accessPredicate が address(0) なら、predicateを呼ばずに true を返します。
predicateが設定されている場合は staticcall で呼び出し、正しいABIエンコードの true だけをアクセス許可として扱います。
predicateのrevert、ガス不足、戻り値の長さ不一致、0 と 1 以外のbool値、コードがないpredicateは、すべて false になります。
安全側へ倒すため、壊れたpredicateでアクセスが許可されることはありません。
tryHasAccess
function tryHasAccess(
uint256 toolId,
address account,
bytes calldata data
) external view returns (bool ok, bool granted);アクセス可否に加えて、predicate呼び出しが正常に解釈できたかを返す参照関数です。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
判定するツールID |
account |
address |
アクセス可否を確認するアカウント |
data |
bytes |
predicateへ渡す文脈データ |
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
ok |
bool |
predicate呼び出しを正しく解釈できたか |
granted |
bool |
アクセスが許可されたか |
戻り値の意味は以下です。
| 戻り値 | 意味 |
|---|---|
(true, true) |
公開ツール、またはpredicateが正しい true を返した |
(true, false) |
predicateが正しい false を返した |
(false, false) |
predicate呼び出しが失敗、または戻り値を解釈できない |
(false, true) は無効な組み合わせとして扱う必要があります。
ウォレットやエージェントは、tryHasAccess を使うことで「条件を満たしていない」のか「predicateが壊れている」のかを分けて表示できます。
アクセス判定の流れは以下です。

hasAccess は単純なboolを返す便利な関数です。
一方で、tryHasAccess はpredicateの正常応答とアクセス可否を分けるため、UIやエージェントが原因を表示しやすくなります。
toolCount
function toolCount() external view returns (uint256);登録されたツールIDの最大値を返す参照関数です。
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
uint256 |
uint256 |
これまでに割り当てられた最大 toolId |
toolId は1から始まる連番です。
削除済みIDは再利用されないため、toolCount() は現在利用可能な件数ではなく、割り当て済みIDの上限を表します。
name() / version()
function name() external view returns (string memory);
function version() external view returns (string memory);レジストリ実装を識別するための参照関数です。
どちらも空文字列以外を返す必要があります。
参照実装では name() が "ToolRegistry" を返し、version() は "0.1" や "1.0" のような文字列を返します。
この値は厳密なsemverではなく、デプロイ済みレジストリの粗い識別子として使われます。
IAccessPredicate
IAccessPredicate は、アクセス判定を外部化するためのインターフェースです。
struct AccessRequirement {
bytes4 kind;
bytes data;
string label;
}
enum RequirementLogic {
AND,
OR
}
interface IAccessPredicate {
function hasAccess(
uint256 toolId,
address account,
bytes calldata data
) external view returns (bool);
function name() external view returns (string memory);
function getRequirements(
uint256 toolId
)
external
view
returns (
AccessRequirement[] memory requirements,
RequirementLogic logic
);
}hasAccess
function hasAccess(
uint256 toolId,
address account,
bytes calldata data
) external view returns (bool);predicateが、指定された account が toolId のツールを利用できるかを返す関数です。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
判定対象のツールID |
account |
address |
利用可否を確認するアカウント |
data |
bytes |
predicate実装へ渡す文脈データ |
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
bool |
bool |
利用できるなら true |
ToolRegistry はこの関数を staticcall で呼び出します。
戻り値は、正しいboolとして解釈できる必要があります。
解釈に失敗した場合、アクセスは拒否として扱われます。
name
function name() external view returns (string memory);predicate実装の表示名を返す関数です。
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
string |
string |
predicate実装の表示名 |
ウォレットやエージェントは、この値をアクセス条件の説明に使えます。
getRequirements
function getRequirements(
uint256 toolId
)
external
view
returns (
AccessRequirement[] memory requirements,
RequirementLogic logic
);エージェントが事前に必要条件を把握するための機械可読なヒントを返す関数です。
- 引数
| 引数 | 型 | 説明 |
|---|---|---|
toolId |
uint256 |
条件を確認するツールID |
- 戻り値
| 戻り値 | 型 | 説明 |
|---|---|---|
requirements |
AccessRequirement[] |
必要条件の一覧 |
logic |
RequirementLogic |
条件をANDで見るかORで見るか |
AccessRequirement.kind はERC165風の4 bytes識別子です。
既知の種類は以下です。
| 種類 | kind |
data |
|---|---|---|
IERC721Holding |
0xbdf8c428 |
abi.encode(address collection) |
IERC1155Holding |
0xcb429230 |
abi.encode(address collection, uint256 tokenId) |
ISubscription |
0x44387cc2 |
abi.encode(address collection, uint8 minTier) |
ERC165は、コントラクトが特定のインターフェースに対応しているかを確認するための規格です。
ERC8257では、IToolRegistry と IAccessPredicate のinterface IDも固定されています。
ERC165については以下の記事を参考にしてください。
| インターフェース | interface ID |
|---|---|
IToolRegistry |
0xf1dc8075 |
IAccessPredicate |
0xbdf9dc18 |
getRequirements は発見と準備のためのヒントです。
最終的なアクセス可否は hasAccess や tryHasAccess で確認します。
predicateの設定が変わると、manifestの access ヒントとオンチェーンpredicateの状態がずれることがあるためです。
ツールIDの範囲
toolId は、単体ではグローバルな識別子ではありません。
同じ toolId が、別チェーンや別レジストリで異なるツールを指すことがあります。
そのため、ツール参照は chainId と registryAddress と toolId を合わせて扱います。
推奨される文字列表現は以下です。
eip155:<chainId>/erc8257:<registryAddress>/<toolId>erc8257 のCAIP namespaceは、提案時点ではまだCAIP側へ登録されていません。
ただし、将来の登録を見越して、相互運用しやすい表記としてこの文字列が推奨されています。
Tool Manifest
metadataURI が指すmanifestはJSONです。
ツールの名前、説明、endpoint、入出力スキーマ、creatorアドレス、価格、アクセスヒント、実行環境の検証情報を含みます。
manifestに載る発見情報は以下です。

エージェントはmanifestを読むことで、ツールの目的、呼び出し方、価格、アクセス条件、信頼判断に必要な情報を実行前に確認できます。 レジストリはこれらの詳細をオンチェーンへ直接保存せず、manifestのハッシュを保存して改ざん検知に使います。
manifestの正規化bytes
manifestHash は、JSONをそのままハッシュした値ではありません。
RFC8785 のJSON Canonicalization Scheme(JCS)で正規化したbytesに対する keccak256 です。
JCSは、オブジェクトキー順、空白、数値表現、文字列エスケープを揃えます。 そのうえで ERC8257 は、JCSの前に以下のルールも求めます。
| ルール | 内容 |
|---|---|
| Unicode | すべてのJSON文字列はNFCである必要があります |
| BOM | UTF-8 BOMを含まないbytesである必要があります |
| hex | manifest内のhex文字列は小文字である必要があります |
これらを満たさないmanifestは検証失敗です。 コンシューマは、勝手にNFCへ直したり、BOMを剥がしたり、hexを小文字化したりせず、失敗として扱います。 黙った補正をすると、オンチェーンにコミットされたbytesと別のbytesを検証したことになるためです。
必須フィールド
manifestの主な必須フィールドは以下です。
| フィールド | 型 | 説明 |
|---|---|---|
type |
string |
schema version identifier |
name |
string |
ツール名。1から128 Unicode code points |
description |
string |
ツール説明。1から500 Unicode code points |
endpoint |
string |
ツールがホストされるHTTPS URL |
inputs |
object |
入力パラメータのJSON Schema |
outputs |
object |
出力パラメータのJSON Schema |
creatorAddress |
string |
このmanifestを登録できるオンチェーンアドレス |
endpoint は https:// で始まるURLである必要があります。
creatorAddress は ^0x[0-9a-f]{40}$ に一致する小文字hexアドレスで、オンチェーンの creator と一致する必要があります。
任意フィールド
manifestの主な任意フィールドは以下です。
| フィールド | 役割 |
|---|---|
version |
ツールのsemver表記 |
image |
ツールアイコンURL |
tags |
発見用タグ |
pricing |
支払い方法の候補 |
access |
事前確認用のアクセス条件 |
verifiability |
実行環境やデータ保持の自己申告と検証情報 |
定義されていないトップレベルフィールドは無視します。 将来拡張のため、拡張フィールドにはreverse DNS表記の名前空間を付けることが推奨されています。
価格情報
pricing は、ツール実行前にエージェントが価格を知るための配列です。
レジストリは支払いを処理せず、支払いの検証も行いません。
pricing の各要素は以下です。
| フィールド | 必須 | 説明 |
|---|---|---|
amount |
はい | 最小単位での金額。10進文字列 |
asset |
はい | CAIP-19表記の資産識別子 |
recipient |
はい | CAIP-10表記の受取先 |
protocol |
はい | 支払いプロトコル識別子 |
asset と recipient は同じチェーンを参照する必要があります。
pricing がある場合は、1件以上の配列である必要があります。
null は拒否されます。
配列の順序はcreatorの優先順として扱われ、エージェントは対応できる最初の protocol を選びます。
amount が "0" の場合、ウォレットの支出制御では無料として扱います。
ただし、endpointが特定プロトコルの交渉を期待していることを示せるため、pricing が無い無料ツールとは意味が少し違います。
アクセス条件
manifestの access は、predicateの条件を事前に読むためのヒントです。
オンチェーンの IAccessPredicate.getRequirements と役割が近いですが、最終的な判定はpredicateの hasAccess が担います。
access の主なフィールドは以下です。
| フィールド | 説明 |
|---|---|
logic |
"AND" または "OR" |
requirements |
条件オブジェクトの配列 |
各条件オブジェクトは以下です。
| フィールド | 説明 |
|---|---|
kind |
4 bytes selector。小文字hex |
data |
ABIエンコードされたpayload。小文字hex |
label |
表示用の短い説明 |
links |
関連URLのmap。値はHTTPS URLのみ |
access はmanifestに含まれるため、manifestHash によってオンチェーンへコミットされます。
ただし、predicateのオンチェーン設定がmanifest更新なしに変わることがあります。
そのため、access は準備用のヒントであり、実際の利用直前には hasAccess か tryHasAccess で確認します。
検証可能性
verifiability は、ツールの実行環境やデータの扱いについて、エージェントが事前に信頼判断するための情報です。
これもオンチェーンの ToolConfig ではなくmanifestに入ります。
主なフィールドは以下です。
| フィールド | 役割 |
|---|---|
tier |
信頼段階の要約 |
execution |
実行環境 |
description |
説明 |
dataRetention |
データ保持方針 |
sourceVisibility |
ソースコードの公開状況 |
attestation |
TEEやE2EE向けの検証情報 |
reproducibleBuild |
再現可能ビルドの情報 |
tier の値は以下です。
| 値 | 意味 |
|---|---|
self-attested |
すべて自己申告。ハードウェア隔離や再現可能ビルドはありません |
hardware-attested |
TEEのattestationを検証できます |
verifiable |
TEE attestationと再現可能ビルドを組み合わせて検証できます |
execution の値は以下です。
| 値 | 意味 |
|---|---|
standard |
通常のサーバー実行 |
tee |
Trusted Execution Environment上で実行 |
e2ee |
入力を端末側で暗号化し、検証済みTEE内で復号 |
tier は自己申告です。
エージェントは tier だけで高い信頼を与えず、attestation や reproducibleBuild の構造がその主張を支えているかを確認します。
例えば、tier が verifiable なのに attestation や reproducibleBuild が無いmanifestは一貫していません。
indexerや発見レイヤーは、一貫しないmanifestをフラグし、実際に確認できる低い信頼段階として扱うことが推奨されています。
originとの結びつき
manifestは、ツールの endpoint と同じoriginのwell-known pathで取得できる必要があります。
<origin>/.well-known/ai-tool/<slug>.jsonslug は以下に一致します。
[a-z0-9]([a-z0-9-]*[a-z0-9])?長さは1から64文字です。
metadataURI はクエリ文字列やfragmentを含まないURIである必要があります。
endpoint はpathやqueryを含められますが、origin比較ではscheme、host、portだけを使います。
このルールにより、endpointのoriginを管理している主体だけが、そのendpointに対応するmanifestを配置できます。 攻撃者が他人のAPI endpointを勝手に登録しても、同じoriginのwell-known pathへmanifestを配置できる主体ではないため、検証で落とせます。
creatorとの結びつき
Origin Bindingは、manifestがendpointのoriginから出ていることを確認します。
しかし、registerTool はpermissionlessなので、別のアドレスが正しいmanifest URLを読み取り、自分の creator として登録する攻撃が残ります。
そこでmanifestには creatorAddress を入れます。
コンシューマは、manifest内の creatorAddress とオンチェーンの ToolConfig.creator が一致するかを確認します。
{
"creatorAddress": "0xabcdefabcdef1234567890abcdefabcdef123456"
}creatorAddress は小文字hexの20 bytesアドレスで、ゼロアドレス以外である必要があります。
ゼロアドレスは msg.sender になれないため、正当な登録者として一致しないからです。
コンシューマ検証
エージェント、ウォレット、indexerは、ツールを解決する時に以下の順で検証します。

metadataURIからmanifestを取得する オンチェーンのToolConfig.metadataURIを使ってmanifestを取得します。 HTTPリダイレクトは追わず、3xxは検証失敗として扱います。metadataURIがendpointと同じoriginのwell-known pathにあることを確認する URLを正規化し、scheme、host、port、path、slugを確認します。metadataURIはqueryやfragmentを含まない必要があります。- JCSで正規化し、
keccak256がmanifestHashと一致することを確認する UTF-8 BOMなし、NFC文字列、小文字hexの条件を満たす必要があります。 ハッシュが合わない時は、更新トランザクションとの競合を考慮してgetToolConfigを1回だけ読み直せます。 creatorAddressがオンチェーンのcreatorと一致することを確認する 一致しなければ、正しいoriginのmanifestを別アドレスが登録したものとして拒否します。
4つの検証をすべて通ったツールだけがcanonicalな登録として扱われます。 エージェントは、canonicalな登録だけをユーザーに提示し、実行対象として扱う必要があります。
補足
hashによるコミットメント
manifestHash は、mutableな metadataURI に対するオンチェーンのコミットメントです。
manifestの内容が変わる場合は、updateToolMetadata で新しいURIとハッシュを登録し、ToolMetadataUpdated イベントを発行します。
indexerはイベントを追うことでmanifestの変化を検出できます。
特定の manifestHash をpinしているコンシューマは、新しいmanifestを明示的に再承認するまで古いコミットメントに留まれます。
predicateの検証
IToolRegistry.hasAccess(uint256,address,bytes) と IAccessPredicate.hasAccess(uint256,address,bytes) は同じselectorを持ちます。
そのため、同名同引数の関数を持つ別コントラクトを誤ってpredicateに設定すると、意図しないアクセス判定が起きます。
ERC8257は、ERC165に対応しているpredicateについて IAccessPredicate 対応を確認します。
supportsInterface(type(IERC165).interfaceId) が true のコントラクトが IAccessPredicate をサポートしていない場合、登録や更新は InvalidAccessPredicate でrevertします。
一方で、ERC165に対応していないコントラクトはbest-effortで受け入れます。
この場合の最後の防御は、staticcall と厳密なbool decodeです。
正しく true を返せないpredicateはアクセス拒否として扱われます。
EIP712によるAccessProof
account 引数は、呼び出し元が「このアカウントについて確認したい」と申告する値です。
レジストリは account と msg.sender を結びつけません。
そのため、hasAccess が true を返しても、現在のリクエスト主体が本当に account を操作できるとは限りません。
この問題に対して、提案では EIP712 のtyped dataを使った AccessProof パターンを示しています。
toolId、account、deadline を含むメッセージに署名し、predicateまたはendpointが署名者を検証します。
EIP712については以下の記事を参考にしてください。
deadline を入れることで再利用期間を制限し、chainId と verifyingContract をdomainに入れることで、別チェーンや別predicateへの再利用を防ぎます。
互換性
ERC8257は新しい IToolRegistry と IAccessPredicate を追加する提案で、既存のERCを変更しません。
インターフェース検出にはERC165を使いますが、ERC165自体の変更は不要です。
predicateは独立したコントラクトです。
既存のコントラクトでも、IAccessPredicate の関数シグネチャと挙動に合っていればpredicateとして使えます。
参考実装
Foundryベースの参照実装が、この提案と同じリポジトリで管理されています。
参照実装は ToolRegistry、IToolRegistry、IAccessPredicate を含み、ERC165 interface ID、manifest URI長制限、predicate連携、戻り値の厳密decode、ゼロコードpredicateの扱いをテストで固定しています。
実装者は、IToolRegistry のfunction setを変えてinterface IDが変わらないようにする必要があります。
function setを変える場合、それは別インターフェースとして新しい識別子を持つべきです。
セキュリティ
predicateのガスと失敗時の扱い
レジストリはpredicateを staticcall で呼び出します。
predicateがrevertしたり、ガスを使い切ったり、不正な戻り値を返したりした場合、hasAccess は false を返し、tryHasAccess は (false, false) を返します。
predicate呼び出しの最大ガス値は、提案では固定しません。
SLOADや署名検証などのコストはハードフォークやチェーンごとの料金体系で変わるためです。
オンチェーンで hasAccess を組み込むコントラクトは、staticcall{gas: budget} のように明示的な予算を与え、外部predicateに払う最悪コストを制限することが推奨されています。
upgrade可能なpredicate
accessPredicate がERC1967 proxyやUUPS proxyの場合、predicateの管理者は setAccessPredicate を呼ばずにアクセスロジックを変えられます。
そのため、アクセス条件の変更を追跡するには、AccessPredicateUpdated イベントだけでなく、predicate側の状態やイベントも確認する必要があります。
コンシューマやindexerは、predicateアドレスにproxyパターンがないかを確認し、proxyなら高リスクとして扱うことが推奨されています。
ERC1967の Upgraded(address) イベントを監視し、predicateの実装変更をユーザーに見せることも重要です。
registryのデプロイ
レジストリ自体がproxyでデプロイされている場合、proxy管理者は実装を差し替えて、hasAccess、getToolConfig、toolCount の挙動を変えられます。
canonicalなレジストリとして扱うアドレスでは、bytecode hashやデプロイトランザクションをpinすることが推奨されています。
discovery layerは、レジストリアドレスだけでなく、そのbytecode hashも公開することで、下流のウォレットやエージェントが同じ実装を見ているか確認できます。
読み取り専用のreentrancy
predicate呼び出しは staticcall なので、predicateは状態を書き換えられません。
ただし、predicateが他のコントラクトの状態を読むことはできます。
外部プロトコルの状態が途中まで更新されたタイミングで hasAccess を呼ぶと、predicateが一貫しない状態を読んでしまうことがあります。
hasAccess の結果で状態変更を制御するコントラクトは、外部view呼び出しとしてのread-only reentrancyを考慮する必要があります。
同じトランザクション内で同じ (toolId, account) の判定を複数回使う場合は、EIP1153の一時ストレージに最初の結果をキャッシュできます。
これはレジストリの挙動を変えるものではなく、組み込み側のコスト削減策です。
data に秘密情報を入れない
hasAccess と tryHasAccess の data は、predicateへそのまま渡されます。
これはオンチェーンview呼び出しの入力なので、RPC trace、ノードログ、監視基盤から見えることがあります。
data には、公開してよい文脈データだけを入れる必要があります。
秘密鍵、API token、パスワード、session cookie、bearer tokenのような秘密情報は、data ではなく別の安全な手段で扱います。
正当な用途は、Merkle proof、token ID、EIP712署名、公開challengeへの署名など、公開されても問題ない値です。
秘密情報が必要なアクセス方式では、オンチェーンpredicateへ渡さず、HTTPS endpoint側で検証します。
コードがないpredicate
登録時や更新時に、まだコードが存在しないpredicateアドレスは受け入れられます。 CREATE2で後からデプロイする設計を許すためです。
ただし、実行時にゼロコードアドレスへ staticcall した場合、戻り値を正しいboolとして解釈できる必要があります。
解釈に失敗した時はアクセス拒否になります。
counterfactual deploymentを使うcreatorは、CREATE2 saltとinit-code hashが意図したデプロイを指していることを確認し、利用者へ公開する前にpredicateをデプロイすることが推奨されています。
引用
最後に
今回は「AIエージェントが使うツールをオンチェーンで発見・検証するAgent Tool Registryを定義するERC8257」についてまとめてきました。
ERC8257は、AIエージェント向けツールを「オンチェーンで登録し、オフチェーンmanifestで詳しく説明し、hashとoriginとcreatorで検証する」ための提案です。 レジストリ本体は最小限に保ち、アクセス条件はpredicate、価格や実行環境の説明はmanifestへ分けることで、新しいツールや支払い方式を追加しやすくしています。
他でも色々記事を書いているのでぜひよろしければ読んでいってください!


