はじめに

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

今回は、複数のエージェントが単一のブロックチェーン上で安全かつ検証可能に協調行動できるようにする最小限のフレームワークを提案しているERC8001についてまとめていきます!

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

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

概要

ERC8001は、複数の主体(エージェント)が単一のブロックチェーン上で足並みをそろえて行動するための、最小限のオンチェーン仕組みを定義する規格です。 発端となるユーザー(イニシエータ)が「インテント(やりたいことを機械可読なメッセージとして表したもの)」を投稿し、各参加者がそのインテントに対して「受諾の証明(アテステーション:検証可能な署名やコントラクト署名)」を提出します。必要な参加者分の受諾が揃っていて、かつ有効期限内(fresh=期限切れでないこと)であれば、そのインテントは実行可能になります。

この規格は、以下を明確に定義します。

  • Typed Data(型付きデータ) メッセージの構造を固定し、署名検証の互換性を確保するための形式です。
  • ライフサイクル インテントの投稿から受諾の収集、実行可能状態の判定までの流れです。
  • 必須イベント クライアントやインデクサが状態を正しく追跡できるようにするためのイベント群です。
  • 検証ルール 署名の正当性、参加者リストの正規化(ソート済み・重複排除)、有効期限の確認など、実行前に満たすべき条件です。

また、ウォレットやコントラクト署名との互換性のため、次の既存仕様に準拠します。

  • EIP712 型付きデータに対する署名標準です。
  • ERC1271 コントラクトアカウントが署名検証を提供するための標準です。
  • EIP2098 署名データの圧縮表現(コンパクト署名)です。
  • EIP5267 EIP712のドメイン情報をオンチェーンで取得するための標準です。

EIP712については以下の記事を参考にしてください。

ERC1271については以下の記事を参考にしてください。

EIP2098については以下の記事を参考にしてください。

EIP5267については以下の記事を参考にしてください。

一方で、プライバシー、評判(レピュテーション)、しきい値ポリシー(何人中何人で承認とみなすか等)、ボンディング(担保を積む仕組み)、クロスチェーンの意味付けは、ERC8001からは意図的に外されています。 これらはERC8001を参照する拡張モジュールとして実装する前提です。 最小限の中核を標準化し、拡張はモジュールに任せることで互換性と柔軟性を両立します。

互換性と非対象の整理

区分 具体項目 位置づけ
互換仕様 EIP712、ERC1271、EIP2098、EIP5267 規格が前提とする標準。ウォレット・コントラクト署名・圧縮署名・ドメイン取得に対応します。
コアで定義 型付きデータ、ライフサイクル、必須イベント、検証ルール、正規化された参加者リスト 反復可能で再現性のある実装を狙った最小集合です。
モジュールに委譲 プライバシー、評判、しきい値ポリシー、ボンディング、クロスチェーン 中核から除外し、用途に応じて組み合わせる設計です。

簡易フロー図

sequenceDiagram
    participant Initiator as イニシエータ
    participant Chain as チェーン(ERC-8001)
    participant P1 as 参加者1
    participant P2 as 参加者2
    Initiator->>Chain: インテントを投稿(EIP-712型付きデータ)
    P1->>Chain: 受諾アテステーション(署名/コントラクト署名)
    P2->>Chain: 受諾アテステーション(署名/コントラクト署名)
    Chain->>Chain: 必要受諾の充足+期限内か検証
    Chain-->>Initiator: 実行可能状態(executable)

動機

DeFiやMEVの領域では、複数の自律エージェントが信頼できる中央調整者なしで同時に動く必要があります。 ところが、既存のインテント系標準(例:ERC7521、ERC7683)は、基本的に単一のイニシエータを前提としたフローを定義しており、複数当事者の合意形成そのものは標準化していませんでした。

ERC7521については以下の記事を参考にしてください。

ERC7683については以下の記事を参考にしてください。

ERC8001は、このギャップを埋めるために最小限のオンチェーン原語(プリミティブ)として以下を提供します。 イニシエータがEIP712形式のインテントを示し、各参加者がEIP712/ERC1271に基づく受諾を提出します。 必要な受諾が揃い、期限切れでなければ実行可能になります。 参加者リストは正規形(ソート済み・重複排除)で扱い、リプレイ耐性(過去の署名を別の文脈で流用されないようにする性質)とウォレット互換性を確保します。

なお、プライバシー、しきい値合意、担保、クロスチェーンといった設計論点は、用途ごとに要件が大きく変わるため、モジュール化して本体の外に切り出しています。 こうすることで、コアは小さく安定させつつ、必要な場面で拡張を選んで組み合わせられるようにしています。

用語の補足

用語 説明
インテント(Intent) 実行したいアクションを、署名可能な型付きデータとして表現したものです。
受諾アテステーション(Acceptance Attestation) 参加者がインテントに同意したことを示す検証可能な証明です。EOA署名や、コントラクトが提供する署名検証(ERC1271)を含みます。
fresh(新鮮/期限内) 受諾が有効期限切れではないことを指します。期限管理の明確化により、古い合意の流用を防ぎます。
正規化された参加者リスト 参加者の並び順を決め、重複を除いた確定リストです。検証の一貫性とリプレイ耐性に寄与します。
リプレイ耐性 ある文脈での署名を、別の文脈や時点で不正再利用されないようにする性質です。
モジュール 規格本体の外側で拡張できる付加機能のまとまりです。プライバシーやしきい値などを後付けで選択できます。

仕様

ステータスコード

実装では、getCoordinationStatus関数が返す標準ステータスコードを定義する必要があります。

Status

javascript
enum Status { None, Proposed, Ready, Executed, Cancelled, Expired }

インテントの現在の状態を示す列挙型です。 以下の値を持ちます。

状態 説明
None デフォルトのゼロ状態。インテントが存在しない状態。
Proposed インテントが提案されたが、すべての受諾がまだ揃っていない状態。
Ready すべての参加者が受諾し、インテントが実行可能な状態。
Executed インテントが正常に実行された状態。
Cancelled インテントが明示的にキャンセルされた状態。
Expired 実行前に有効期限切れになった状態。

概要

ERC8001では、以下の要素を標準化しています。

項目 内容
EIP712ドメイン エージェント間協調のための共通ドメイン定義。
型付きデータ構造 AgentIntent, CoordinationPayload, AcceptanceAttestation。
決定的ハッシュルール データの一貫したハッシュ生成ルール。
標準インターフェース IAgentCoordinationを定義。
ライフサイクルの意味付け 提案 → 受諾 → 実行またはキャンセルの流れ。
エラーとステータスコード 明確な失敗理由と状態定義。

EIP712ドメイン

EIP712準拠のドメイン情報を以下の形式で定義します。

javascript
{name: "ERC-8001", version: "1", chainId, verifyingContract}

インテントと署名を検証するための共通ドメイン識別子です。 すべての実装はこの形式を使用し、EIP712署名の一貫性を保ちます。 さらに、ERC5267を通してこのドメインを公開することが推奨されています。

主な型定義

AgentIntent

javascript
struct AgentIntent {
    bytes32 payloadHash;
    uint64  expiry;
    uint64  nonce;
    address agentId;
    bytes32 coordinationType;
    uint256 coordinationValue;
    address[] participants;
}

エージェントが提案するインテントを表す構造体です。 この構造体は、協調に必要な参加者や期限などを含みます。

パラメータ

  • payloadHash
    • CoordinationPayloadのkeccak256ハッシュ値。
  • expiry
    • インテントの有効期限(UNIX秒)。提案時に現在時刻より未来でなければなりません。
  • nonce
    • エージェントごとの一意な番号。
    • agentNonces[agentId]より大きい必要があります。
  • agentId
    • 提案者(イニシエータ)かつ署名者。
  • coordinationType
    • 協調タイプの識別子(例: keccak256("MEV_SANDWICH_COORD_V1"))。
  • coordinationValue
    • 情報目的の値。
    • モジュールがこの値に基づいて制約を設けることもあります。
  • participants
    • 参加者のリスト。
    • 重複なく昇順で並べられ、agentIdも含まなければなりません。

CoordinationPayload

javascript
struct CoordinationPayload {
    bytes32 version;
    bytes32 coordinationType;
    bytes   coordinationData;
    bytes32 conditionsHash;
    uint256 timestamp;
    bytes   metadata;
}

協調の具体的なデータ内容を保持します。 この構造体は、オンチェーン外の詳細データやメタ情報を格納します。

パラメータ

  • version
    • ペイロード形式のバージョン識別子。
  • coordinationType
    • AgentIntent.coordinationTypeと一致する必要があります。
  • coordinationData
    • コアでは不透明データ。
    • モジュールが自由に解釈可能です。
  • conditionsHash
    • 条件ハッシュ。
    • 用途固有の制約を表します。
  • timestamp
    • 作成時刻(情報目的)。
  • metadata
    • 任意のメタデータ。

AcceptanceAttestation

javascript
struct AcceptanceAttestation {
    bytes32 intentHash;
    address participant;
    uint64  nonce;
    uint64  expiry;
    bytes32 conditionsHash;
    bytes   signature;
}

参加者がインテントを受諾したことを証明するデータです。 受諾は署名またはコントラクト署名で表現されます。

パラメータ

  • intentHash
    • getIntentHash(intent)で得られる構造体ハッシュ。
  • participant
    • 署名した参加者アドレス。
  • nonce
    • 任意の連番。
    • 実装によっては省略可能です。
  • expiry
    • 受諾の有効期限。
    • 受諾時および実行時に現在時刻より未来である必要があります。
  • conditionsHash
    • 参加者固有の条件ハッシュ。
  • signature
    • 署名データ。
    • 65バイトまたは64バイトのECDSA署名、またはERC1271準拠の署名。

型付きデータハッシュ

インテントや受諾をハッシュ化して署名・検証するためのルールです。

AGENT_INTENT_TYPEHASH

javascript
bytes32 constant AGENT_INTENT_TYPEHASH = keccak256(
  "AgentIntent(bytes32 payloadHash,uint64 expiry,uint64 nonce,address agentId,bytes32 coordinationType,uint256 coordinationValue,address[] participants)"
);

ACCEPTANCE_TYPEHASH

javascript
bytes32 constant ACCEPTANCE_TYPEHASH = keccak256(
  "AcceptanceAttestation(bytes32 intentHash,address participant,uint64 nonce,uint64 expiry,bytes32 conditionsHash)"
);

_participantsHash

javascript
function _participantsHash(address[] memory ps) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(ps));
}

参加者リストのハッシュを計算する関数。 アドレス配列をkeccak256でハッシュ化します。 アドレスは昇順に並び、重複がない必要があります。

_agentIntentStructHash

javascript
function _agentIntentStructHash(AgentIntent calldata i) internal pure returns (bytes32) {
    return keccak256(abi.encode(
      AGENT_INTENT_TYPEHASH,
      i.payloadHash,
      i.expiry,
      i.nonce,
      i.agentId,
      i.coordinationType,
      i.coordinationValue,
      _participantsHash(i.participants)
    ));
}

AgentIntent構造体のハッシュを作成する関数。

_agentIntentDigest

javascript
function _agentIntentDigest(bytes32 domainSeparator, AgentIntent calldata i) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked("\x19\x01", domainSeparator, _agentIntentStructHash(i)));
}

EIP712形式の署名対象となる最終的なダイジェストを生成します。

_acceptanceStructHash と _acceptanceDigest

javascript
function _acceptanceStructHash(AcceptanceAttestation calldata a) internal pure returns (bytes32) {
    return keccak256(abi.encode(
      ACCEPTANCE_TYPEHASH,
      a.intentHash,
      a.participant,
      a.nonce,
      a.expiry,
      a.conditionsHash
    ));
}

function _acceptanceDigest(bytes32 domainSeparator, AcceptanceAttestation calldata a) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked("\x19\x01", domainSeparator, _acceptanceStructHash(a)));
}

受諾アテステーションの署名検証に使用されるハッシュおよびダイジェストを生成します。

インターフェース

IAgentCoordination

javascript
interface IAgentCoordination {
    event CoordinationProposed(...);
    event CoordinationAccepted(...);
    event CoordinationExecuted(...);
    event CoordinationCancelled(...);

    function proposeCoordination(...) external returns (...);
    function acceptCoordination(...) external returns (...);
    function executeCoordination(...) external returns (...);
    function cancelCoordination(...) external;
    function getCoordinationStatus(...) external view returns (...);
    function getRequiredAcceptances(...) external view returns (...);
    function getAgentNonce(...) external view returns (...);
}

エージェント間のインテント提案・承認・実行・キャンセルを管理する標準インターフェースです。

proposeCoordination

javascript
function proposeCoordination(AgentIntent calldata intent, bytes calldata signature, CoordinationPayload calldata payload) external returns (bytes32 intentHash);

インテントを提案する関数。 無効な署名、期限切れのインテント、ノンス不整合、誤った参加者構成の場合は拒否します。 有効な署名を受け入れ、CoordinationProposedイベントを発行し、ステートをProposedに設定します。

引数

  • intent
    • 提案するAgentIntentデータ。
  • signature
    • イニシエータの署名。
  • payload
    • 実行内容のCoordinationPayload。

戻り値

  • intentHash
    • 登録されたインテントのハッシュ。

acceptCoordination

javascript
function acceptCoordination(bytes32 intentHash, AcceptanceAttestation calldata attestation) external returns (bool allAccepted);

インテントを受諾する関数。 存在しないインテントや期限切れ、非参加者、重複受諾、署名不正な場合は拒否します。 有効な場合はCoordinationAcceptedイベントを発行し、受諾者を登録します。 全員の受諾が揃ったらtrueを返します。

戻り値

  • allAccepted
    • すべての受諾が揃った場合にtrue。

executeCoordination

javascript
function executeCoordination(bytes32 intentHash, CoordinationPayload calldata payload, bytes calldata executionData) external returns (bool success, bytes memory result);

承認済みインテントを実行する関数。 未準備・期限切れ・ペイロード不一致の場合は拒否します。 実行成功時にCoordinationExecutedを発行します。

戻り値

  • success
    • 実行成功フラグ。
  • result
    • 実行結果データ。

cancelCoordination

javascript
function cancelCoordination(bytes32 intentHash, string calldata reason) external;

インテントをキャンセルする関数。 提案者は実行前にキャンセル可能で、誰でも有効期限後はキャンセル可能です。 CoordinationCancelledイベントを発行します。

getCoordinationStatus

javascript
function getCoordinationStatus(bytes32 intentHash) external view returns (Status status, address proposer, address[] memory participants, address[] memory acceptedBy, uint256 expiry);

インテントの状態を取得する関数。 返り値は定義済みのステータスのいずれかになります。

getRequiredAcceptances / getAgentNonce

javascript
function getRequiredAcceptances(bytes32 intentHash) external view returns (uint256);
function getAgentNonce(address agent) external view returns (uint64);

必要受諾数およびエージェントノンスを取得します。

セマンティクス

  • proposeCoordination 提案処理と署名検証、ノンス単調性を保証。
  • acceptCoordination 重複・不正署名を排除し、受諾記録を管理。
  • executeCoordination すべての受諾が有効かつ整合している場合にのみ実行。
  • cancelCoordination 状態をCancelledに遷移。

ノンス(Nonces)

各エージェントはagentNonces[agentId]で一意なノンスを管理します。 受諾ノンスは任意実装ですが、実装する場合は単調増加である必要があります。

エラー

以下のエラーの実装は推奨されています。

エラー名 説明
ERC8001_NotProposer 提案者以外のキャンセル。
ERC8001_ExpiredIntent インテントの有効期限切れ。
ERC8001_ExpiredAcceptance 受諾が実行時に期限切れ。
ERC8001_BadSignature 不正な署名。
ERC8001_NotParticipant 非参加者の受諾試行。
ERC8001_DuplicateAcceptance 重複受諾。
ERC8001_ParticipantsNotCanonical 参加者リストが昇順・重複なしでない。
ERC8001_NonceTooLow ノンスが前回より小さい。
ERC8001_PayloadHashMismatch ペイロードのハッシュ不一致。
ERC8001_NotReady すべての受諾が揃っていない状態で実行。

補足

ERC8001の設計は、複数参加者による合意を安全かつ実装しやすくするための最小構成を狙っています。 まず、参加者リストを昇順かつ重複なしで正規化することで、ハッシュ値が署名後に変えられてしまう「ハッシュ可鍛性(malleability)」を避けます。 これにより、同じ内容であれば常に同じハッシュが得られ、オフチェーン側での重複排除もしやすくなります。

次に、インテント(実行したい内容の宣言)と受諾アテステーション(参加者の同意の証明)を分離することで、受諾の収集をオフチェーンでまとめてオンチェーンでは最終チェックを1回行うだけにできます。 これにより、ガスコストを抑えつつ、検証可能性は維持できます。

また、単一チェーン前提に留めることで、ブリッジの仕様や安全性に依存せず、監査の対象も明確になります。 将来的にクロスチェーン連携が必要な場合は、拡張モジュールで扱う設計にすることで、コアの単純さを保ちます。

最後に、EIP712の配列サポートを活用することで、ウォレットの署名画面に実際の参加者アドレス一覧を表示できます。 これにより、署名者は誰と協調しようとしているのかを直感的に確認でき、ユーザー体験の面でも安全性の面でも有利です。

互換性

ERC8001は新しいインターフェースを導入しますが、既存のアカウント種別に対して互換性を保っています。 具体的には、外部所有アカウント(EOA)とはECDSA署名で、スマートウォレット(コントラクトウォレット)とはERC1271でそれぞれ連携できます。 既存の標準を改変せず、EIP712の型付きデータ署名を前提とするため、ウォレットやインデクサとの相互運用性を損ないません。

参考実装

リポジトリのcontracts/AgentCoordination.solに寛容なライセンスの参照実装が提供されています。

実装は最小限のECDSAヘルパーを用い、ERC1271署名者もサポートします。 さらに、以下のコア要件を強制します。

  • 参加者の正規化(昇順・重複なし)。
  • インテントのノンス単調性の検証。
  • 受諾アテステーションの有効期限(freshness)検証。
  • 全参加者受諾が揃っていない限り実行不可(All-participants policy)。

参考として、ライフサイクルの最小フローを図示します。

flowchart LR
  A[proposeCoordination] -->|署名とノンス検証| B[Proposed]
  B -->|acceptCoordination| C{全員受諾?}
  C -- いいえ --> B
  C -- はい --> D[Ready]
  D -->|executeCoordination| E[Executed]
  B -->|cancelCoordination| F[Cancelled]
  D -->|期限切れ| G[Expired]

セキュリティ

リプレイ耐性(Replay)については、EIP712のドメイン結合とノンスの単調増加により、コントラクトやネットワークを跨いだ再利用を防ぎます。 異なるコントラクトやチェーンではドメインが異なり、さらにノンスが前回以下であれば拒否されるため、同一署名の使い回しが困難になります。

可鍛性(Malleability)は、ECDSA署名におけるs値を下半分に制限するLow-s強制と、64/65バイト署名形式(EIP2098含む)の両対応を前提にすることで抑制します。 これにより、「同じ意味だが異なる署名バイト列**」を悪用する攻撃を避けます。

矛盾署名(Equivocation)、つまり参加者が同時に競合する複数のインテントへ受諾してしまう問題は、コアでは禁止しません。 実利用では、スラッシング(違反時の罰則)や評判管理を行うモジュール側で抑止する設計が推奨です。 例えば、同一ロールの二重受諾を検出した場合にステークを没収する、といった拡張が考えられます。

可用性(Liveness)の確保には、インテントと各受諾のTTL(有効期限)を明確に設定します。 実行者は、実行時点で十分な残り時間があるかを確認し、ギリギリのタイミングを避けると安全です。 期限切れ直前は、オンチェーン混雑の影響などで実行に失敗するリスクが高まります。

MEV耐性の観点では、coordinationDataに戦略が含まれている場合、その公開がフロントランなどの攻撃につながる可能性があります。 必要に応じて、コミット&リビール(先にコミットを公開して後で内容を明かす)や暗号化を用いるプライバシーモジュールを適用し、実行時まで戦略を秘匿することが推奨です。

リスクと対策

リスク 説明 推奨対策
リプレイ 署名を別コンテキストで再利用される。 EIP712ドメイン結合、ノンス単調増加の厳格化。
署名可鍛性 同値な別署名での置換攻撃。 Low-s強制、64/65バイト署名対応(EIP-2098含む)。
矛盾署名 同一参加者が競合インテントに受諾。 モジュールでのスラッシング・評判管理。
可用性低下 期限切れや混雑で実行不能。 インテント/受諾のTTL設定、十分な実行余裕の確保。
MEV漏えい 戦略がcoordinationDataで露出。 プライバシーモジュール(コミット&リビール、暗号化)。

引用

Kwame Bryan (@KBryan), "ERC-8001: Agent Coordination Framework [DRAFT]," Ethereum Improvement Proposals, no. 8001, August 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-8001.

最後に

今回は「複数のエージェントが単一のブロックチェーン上で安全かつ検証可能に協調行動できるようにする最小限のフレームワークを提案しているERC8001」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!