はじめに

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

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

Agentがオンチェーンで資産を動かす時、「そのAgentが存在する」と分かるだけでは足りません。 資金の送付や交換では、そのAgentに実行を任せてよいかを、操作ごとに確認する必要があります。

ERC8273は、信頼する確認者が評価した結果を使い、1回のトランザクションの間だけAgentの操作を認可するためのinterfaceです。 この記事では、認可が残る場所、実行時に何を照合するか、DAppがどの呼び出しで認可を確認するかを順に説明します。

仕様は以下です。

概要

提案の簡単な理解

ERC8273では、AttestorがAgentの操作を事前に評価します。 評価に通ったら、AttestorはRegistryコントラクトのattestAndCallを呼びます。 Registryコントラクトは、監査用の記録を残し、今のトランザクションだけ有効な認可を一時ストレージへ書き込みます。 続いてAgentのウォレットからDAppを呼び出します。

DAppは、呼び出し元のウォレットと、許可された操作を指定してRegistryコントラクトへ照会します。 認可があれば処理を続けます。 認可がなければトランザクション全体をrevertします。 トランザクションが終わると一時ストレージはEVMによって消去されます。 監査用の記録だけがオンチェーンに残ります。

erc8273-overview.png

この仕組みは、長期間使える権限を発行するものではありません。 評価した1回の操作だけを通すための認可です。

アテステーションと認可の違い

attestationは、確認者が「この対象は条件を満たす」と記録するオンチェーンの証跡です。 ERC8273のAttestationRecordは、誰を評価したか、誰が評価したか、何を許可したかを永続ストレージに残します。

一方で、DAppが今すぐ操作を許可するかは、永続レコードだけでは判断しません。 現在のトランザクションに限って作られた一時認可を確認します。

flowchart LR
  A[Attestorの評価] --> B[監査レコードを保存]
  B --> C[一時認可をTSTORE]
  C --> D[DAppが認可を照会]
  D --> E[操作を実行]
  E --> F[トランザクション終了]
  F --> G[一時認可を自動消去]
  B --> H[監査レコードは残る]

動機

AgentのIDは、どのAgentを指すかを示します。 しかし、IDだけでは今回のスワップ、mint、管理操作を実行してよいかは判断できません。

ERC8273は、対象のIDと、操作ごとの認可を分けます。 capabilityは「DeFiの操作を許可する」のような大きな権限区分です。 actionDigestは、どのコントラクトのどの関数を、どの引数で呼ぶかをハッシュ化した値です。

erc8273-scope.png

例えば、1,000USDCまでをWETHへ交換する操作を許可する時は、対象コントラクト、関数セレクタ、交換額、受取先、nonceをactionDigestへ含めます。 金額や受取先を変えるとハッシュ値が変わるため、DAppは別の操作として拒否できます。

actionDigestが0の場合は、具体的な呼び出しを結び付けない認可です。 この形式は「認証済みAgentか」のような粗い確認には使えます。 送付、特権的な状態変更、トランザクション中の再入を利用できる金融操作には使えません。

仕様

認可に使うデータ

認可範囲は、対象、capability、actionDigestの組で決まります。 RegistryコントラクトとDAppは同じ組を使って書き込みと照会を行います。

js
// 交換を1回だけ認可する時の値
subjectId: 42
subjectType: keccak256("ERC8004_AGENT")
capability: keccak256("DEFI_SWAP_V1")
actionDigest: keccak256(abi.encode(<交換コントラクト>, <呼び出しデータ>, <nonce>))
wallet: <Agentウォレット>

subjectIdとsubjectTypeからは、検索用のsubjectHashを計算します。 walletはDAppがmsg.senderとして確認するウォレットです。 evidenceHashには、Attestorが参照した評価報告書や署名付き証跡のハッシュ値を入れられます。

erc8273-storage-boundary.png

AttestationStatus

js
enum AttestationStatus { None, Recorded }

Noneは、存在しないレコードを読む時の初期値です。 標準のレコードはすべてRecordedです。 この値は、現在の操作が認可されているかを表しません。 操作中の認可は一時ストレージで確認します。

SubjectRef

js
struct SubjectRef {
    uint256 subjectId;
    bytes32 subjectType;
}

SubjectRefは、評価対象を指定する入力値です。 subjectIdだけではIDの発行元が分からないため、subjectTypeで名前空間も指定します。

  • フィールド
フィールド 詳細
subjectId 評価対象を識別する数値です。
subjectType IDの種類を区別するハッシュ値です。

ExecutionRequest

js
struct ExecutionRequest {
    bytes32 profileId;
    bytes32 actionDigest;
    bytes data;
}

ExecutionRequestは、RegistryコントラクトがAgentウォレットからDAppを呼ぶ方法と、実行する操作を指定します。 dataの形式はprofileIdごとに決まります。

  • フィールド
フィールド 詳細
profileId 実行プロファイルを選ぶ識別子です。
actionDigest 具体的な操作を固定するハッシュ値です。0は権限区分だけを使います。
data 実行プロファイルが復号する呼び出しデータです。

AttestationRecord

js
struct AttestationRecord {
    uint256 subjectId;
    bytes32 subjectType;
    address attestor;
    bytes32 capability;
    bytes32 actionDigest;
    uint64 issuedAt;
    AttestationStatus status;
    bytes32 evidenceHash;
    address wallet;
}

AttestationRecordは、発行済みアテステーションを監査するための永続レコードです。 現在有効な認可を保存する場所ではありません。

  • フィールド
フィールド 詳細
subjectId、subjectType 評価した対象とIDの種類です。
attestor レコードを発行した確認者です。
capability、actionDigest 許可する権限区分と、必要な時は具体的な操作です。
issuedAt 発行時刻です。
status 永続レコードの状態です。標準レコードはRecordedです。
evidenceHash 評価に用いたオフチェーン証跡を識別するハッシュ値です。
wallet DAppが認可対象として照会するAgentウォレットです。

IERC165

supportsInterface

js
function supportsInterface(bytes4 interfaceId) external view returns (bool);

supportsInterfaceは、RegistryコントラクトがどのERC8273 interfaceを実装しているかを確認する呼び出しです。 DAppは、認可を照会する前に必要なinterfaceがあるかを確認します。

  1. interface IDを渡す

    DAppが必要とするIERC8273WalletAttestationなどのinterface IDを渡す。

  2. 対応可否を受け取る

    Registryコントラクトが実装していればtrueを返す。

  • 引数
引数 詳細
interfaceId 確認したいinterfaceを表す4バイトの識別子です。
  • 戻り値
戻り値 詳細
bool interfaceを実装している時にtrueです。
  • 実行条件

  • 呼び出し元

    認可照会を使うDAppや、対応状況を表示するアプリケーションです。

IERC8273

IERC8273は、監査レコード、イベントログ、参照用の呼び出しをまとめるコアinterfaceです。

Attested

js
event Attested(
    uint256 indexed attestationId,
    address indexed wallet,
    bytes32 indexed capability,
    bytes32 actionDigest,
    bytes32 subjectHash,
    address attestor,
    uint256 subjectId,
    bytes32 subjectType,
    bytes32 evidenceHash
);

Attestedイベントは、アテステーションを発行した時にイベントログへ残ります。 Indexerはwalletやcapabilityでログを検索し、監査画面を更新できます。

  • フィールド
フィールド 詳細
attestationId Registryコントラクト内で増えるレコードIDです。
wallet 認可に結び付くAgentウォレットです。
capability 許可した権限区分です。
actionDigest 具体的な操作を固定するハッシュ値です。
subjectHash subjectIdとsubjectTypeから計算する検索用ハッシュ値です。
attestor 発行した確認者です。
subjectId、subjectType 評価対象とIDの種類です。
evidenceHash オフチェーン証跡を識別するハッシュ値です。

attestationIdはチェーンやRegistryコントラクトをまたいで一意ではありません。 IndexerはchainId、Registryアドレス、attestationIdを組にして記録します。

isAttested

js
function isAttested(
    SubjectRef calldata subject,
    bytes32 capability,
    bytes32 actionDigest
) external view returns (bool);

isAttestedは、対象をキーにして、現在のトランザクションに一時認可があるかをboolで返します。 画面表示やオフチェーンの確認に使います。

  1. 対象のキーを計算する

    subject、capability、actionDigestから一時ストレージのキーを決める。

  2. 一時認可を読む

    値があればtrue、なければfalseを返す。

  • 引数
引数 詳細
subject 評価対象を指定するSubjectRefです。
capability 権限区分です。
actionDigest 具体的な操作のハッシュ値、または0です。
  • 戻り値
戻り値 詳細
bool 現在のトランザクションに認可がある時にtrueです。

重要なオンチェーン操作の認可には使いません。 認可がない時にrevertするgetActiveAttestationを使います。

latestAttestationId

js
function latestAttestationId(
    SubjectRef calldata subject,
    bytes32 capability,
    bytes32 actionDigest
) external view returns (uint256);

latestAttestationIdは、同じ対象と認可範囲で最後に発行した監査レコードIDを返します。 現在有効な認可を返す呼び出しではありません。

  1. 永続レコードを検索する

    対象、権限区分、操作ハッシュが同じレコードを探す。

  2. 最後のIDを返す

    レコードがなければ0を返す。

  • 引数
引数 詳細
subject 評価対象です。
capability 権限区分です。
actionDigest 操作ハッシュ、または0です。
  • 戻り値
戻り値 詳細
uint256 最後の監査レコードIDです。

getAttestation

js
function getAttestation(uint256 attestationId)
    external view returns (AttestationRecord memory record);

この呼び出しは、IDを指定して監査レコード全体を返します。

  1. IDで永続ストレージを読む

    attestationIdに対応するAttestationRecordを取得する。

  • 引数
引数 詳細
attestationId 取得する監査レコードIDです。
  • 戻り値
戻り値 詳細
record 発行者、対象、認可範囲、証跡ハッシュ、ウォレットを含む監査レコードです。

IERC8273ActiveAttestation

getActiveAttestation

js
function getActiveAttestation(
    SubjectRef calldata subject,
    bytes32 capability,
    bytes32 actionDigest
) external view returns (AttestationRecord memory record);

getActiveAttestationは、対象をキーにして現在のトランザクションの認可を取得します。 認可がなければrevertするため、DAppの認可チェックに使えます。

  1. 一時ストレージを照会する

    対象、権限区分、操作ハッシュに対応する認可を読む。

  2. レコードを返すかrevertする

    認可IDがあれば永続レコードを返す。 なければDAppの呼び出しをrevertする。

  • 引数
引数 詳細
subject 評価対象です。
capability 権限区分です。
actionDigest 操作ハッシュ、または0です。
  • 戻り値
戻り値 詳細
record 現在有効な認可に対応する監査レコードです。

IERC8273WalletAttestation

isAttestedAddress

js
function isAttestedAddress(
    address wallet,
    bytes32 capability,
    bytes32 actionDigest
) external view returns (bool);

isAttestedAddressは、ウォレットをキーにしたboolの照会です。 画面表示やIndexerの処理には使えますが、DAppの重要操作だけをこの戻り値に任せません。

  1. ウォレットの一時認可を読む

    wallet、capability、actionDigestに対応するスロットを読む。

  2. 有無を返す

    スロットに認可IDがあればtrueを返す。

  • 引数
引数 詳細
wallet Agentウォレットです。
capability 権限区分です。
actionDigest 操作ハッシュ、または0です。
  • 戻り値
戻り値 詳細
bool 現在のトランザクションに認可があればtrueです。

getActiveAttestationByWallet

js
function getActiveAttestationByWallet(
    address wallet,
    bytes32 capability,
    bytes32 actionDigest
) external view returns (AttestationRecord memory record);

getActiveAttestationByWalletは、DAppがmsg.senderをAgentウォレットとして確認する時に使う認可チェックです。 認可がなければrevertします。

  1. DAppが操作内容をハッシュ化する

    action-bound modeでは、DAppが呼び出し先、関数、引数、nonceから同じactionDigestを再計算する。

  2. ウォレットをキーに照会する

    msg.sender、capability、再計算したハッシュ値でRegistryコントラクトを照会する。

  3. 認可がなければ処理を中止する

    空のスロットならrevertする。

  • 引数
引数 詳細
wallet DAppがmsg.senderとして確認するウォレットです。
capability 権限区分です。
actionDigest DAppが再計算した操作ハッシュです。
  • 戻り値
戻り値 詳細
record 現在有効な監査レコードです。

erc8273-gating.png

IERC8273AtomicAttestation

attestAndCall

js
function attestAndCall(
    SubjectRef calldata subject,
    bytes32 capability,
    bytes32 evidenceHash,
    address wallet,
    ExecutionRequest calldata exec
) external payable returns (uint256 attestationId, bytes memory result);

attestAndCallは、アテステーションの発行、一時認可の書き込み、DAppの呼び出しを1つにまとめた唯一の発行入口です。 許可されたAttestorだけが呼び出します。

  1. 入力と権限を確認する

    呼び出し元がAttestorであること、wallet、capability、profileIdがゼロでないことを確認する。

  2. 監査レコードを作る

    永続ストレージへAttestationRecordを保存し、Attestedイベントを発行する。

  3. 現在のトランザクションだけ認可する

    対象用とウォレット用の一時ストレージへattestationIdを書き込む。

  4. AgentウォレットからDAppを呼ぶ

    profileIdに対応する実行プロファイルで、DAppから見たmsg.senderがwalletになるよう実行する。

  5. 失敗を全体へ戻す

    実行、プロファイル検証、成功確認のどれかが失敗したら、監査レコードと一時認可を含むトランザクション全体をrevertする。

  • 引数
引数 詳細
subject 評価対象です。
capability 権限区分です。
evidenceHash 評価証跡を識別するハッシュ値です。
wallet 認可とDApp呼び出しに結び付くAgentウォレットです。
exec 実行プロファイル、操作ハッシュ、実行データです。
  • 戻り値
戻り値 詳細
attestationId 作成した監査レコードIDです。
result 実行プロファイルが返した結果です。
  • 実行条件

  • 呼び出し元

    Registryコントラクトが許可したAttestorです。

  • 操作ハッシュ

    msg.valueを送る場合は、金額を結び付けるためactionDigestをゼロにしません。

IAgentExecute

AgentCall

js
struct AgentCall {
    address target;
    uint256 value;
    bytes data;
}

AgentCallは、Agentウォレットが呼び出すDApp操作を表します。 複数の呼び出しを配列にしてexecuteFromRelayerへ渡せます。

  • フィールド
フィールド 詳細
target 呼び出すDAppコントラクトです。
value 呼び出しとともに送るネイティブトークン量です。
data 関数セレクタと引数を含む呼び出しデータです。

executeFromRelayer

js
function executeFromRelayer(
    AgentCall[] calldata calls,
    bytes calldata authData
) external payable returns (bytes[] memory results);

executeFromRelayerは、直接実行プロファイルでRegistryコントラクトから呼ばれるAgentウォレット側の入口です。 ウォレット自身がtargetへ呼び出すため、DAppはmsg.senderとしてウォレットを受け取ります。

  1. 認可データを検証する

    ウォレットがauthDataでチェーン、ウォレット、Registry、プロファイル、操作ハッシュ、nonce、有効期間を確認する。

  2. 呼び出し配列を実行する

    ウォレットが各targetへvalueとdataを渡して呼び出す。

  3. 結果を返す

    各呼び出しの戻り値を配列として返す。

  • 引数
引数 詳細
calls ウォレットから実行するDApp呼び出しの配列です。
authData 呼び出しを承認する署名やセッション情報です。
  • 戻り値
戻り値 詳細
results 各呼び出しの戻り値です。
  • 実行条件

  • 認可データ

    ウォレットがauthDataを検証して、呼び出し元、操作ハッシュ、nonce、有効期間を結び付けます。

  • 呼び出し元

    対応する実行プロファイルを選んだRegistryコントラクトです。

実行プロファイル

実行プロファイルは、RegistryコントラクトがAgentウォレットからDAppを呼び出す方法を定めます。 認可の寿命はどちらも同じです。

erc8273-profiles.png

直接ウォレット実行

AGENT_EXECUTE_V1は、IAgentExecuteを実装したスマートウォレットやERC7702のウォレットで使います。 RegistryコントラクトがexecuteFromRelayerを呼び、ウォレットがDAppを呼びます。

actionDigestがゼロ以外なら、少なくとも呼び出し配列とattestationIdを含めて計算します。 authDataは、チェーンID、ウォレット、Registry、プロファイル、操作ハッシュ、nonce、有効期間を結び付けます。

ERC4337のUserOperation実行

ERC4337_USEROP_V1は、既存のERC4337ウォレットを使うための実行プロファイルです。 RegistryコントラクトはEntryPointへUserOperationを渡します。 EntryPointはvalidateUserOpとexecuteを実行します。

プロファイルは、UserOperationのsenderがwalletと一致することを確認します。 さらに、低レベルのhandleOpsがrevertしなかっただけで成功と判断せず、DAppの受領結果やイベント証明などで対象操作が成功したことを確認します。

sequenceDiagram
  participant A as Attestor
  participant R as Registry
  participant W as Agentウォレット
  participant D as DApp
  A->>R: attestAndCall
  R->>R: 監査レコードを保存
  R->>R: 一時認可をTSTORE
  R->>W: 実行を依頼
  W->>D: 操作を呼び出す
  D->>R: getActiveAttestationByWallet
  R-->>D: レコードを返す
  D-->>W: 操作結果
  Note over R: トランザクション終了時に一時認可を消去

補足

永続の監査記録と一時認可を分ける理由

永続レコードは、後から誰が何を評価したかを確認するために残します。 一時認可は、同じ操作を後のトランザクションで再利用できないように、トランザクション終了時に消します。

例外ではなくrevertで認可を止める理由

getActiveAttestationとgetActiveAttestationByWalletは、認可がなければrevertします。 重要操作でboolを受け取り、実装側が確認を忘れる事故を減らすためです。

Registryコントラクトを信頼する理由

ERC8273は、どのRegistryコントラクトも同じ信頼性を持つとは定めません。 DAppは、対応するinterfaceだけでなく、信頼するRegistryアドレス、Attestorの集合、認可方針を明示します。

互換性

既存の規格へ加える役割

ERC8273は既存のID、トークン、アカウント規格の動作を変更しません。 DAppはERC165で必要なinterfaceを確認してから、対応する認可照会を呼びます。

ERC8004はAgentのID登録や非同期の検証結果を扱います。 ERC8273は、1回のオンチェーン操作を止めるための同期的な認可を補います。

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

EIP1153は、トランザクションが終わると消える一時ストレージを提供します。 ERC8273はこの仕組みで、現在のトランザクションだけの認可を保持します。

参考実装

発行と実行を1つにする流れ

参考実装は、監査レコードを保存してから、対象用とウォレット用の一時スロットへ同じattestationIdを書き込みます。 その後で実行プロファイルを選びます。

js
function attestAndCall(...) external payable returns (uint256 attestationId, bytes memory result) {
    attestationId = _nextId++;
    _records[attestationId] = AttestationRecord(...);
    emit Attested(...);
    _attestTransient(...);
    result = _executeProfile(...);
}

実行に失敗した時はトランザクション全体がrevertします。 そのため、操作だけ失敗して認可や監査レコードだけが残る状態にはなりません。

セキュリティ

actionDigestに操作とnonceを入れる

actionDigestがゼロ以外なら、対象コントラクト、関数セレクタ、引数、nonceなどの一意な値を含めます。 ネイティブトークンを送る場合は金額も含めます。 DAppは同じ規則で再計算し、違う値なら認可を通しません。

再入を別に防ぐ

一時認可はトランザクションの間は残ります。 特にactionDigest == 0では、認可チェックだけで同一トランザクション中の再入を防げません。 重要な操作を行うDAppコントラクトはreentrancy guardを持ちます。

Attestorの侵害を監視する

侵害されたAttestorは、権限範囲内で何度も新しい認可を発行できます。 1回の認可が後のトランザクションへ残らないことと、侵害期間の総被害は別の問題です。 運用者はAttestedイベントを監視し、Attestorの権限を狭くし、停止と削除の手順を用意します。

チェーンをまたぐIDを混同しない

attestationIdは、1つのRegistryコントラクトと1つのチェーンの中でだけ増加します。 監査ツールやブリッジは、chainId、Registryアドレス、attestationIdを一組で記録します。

証跡の参照先を変えない

evidenceHashはオフチェーンの評価証跡を識別する値です。 内容が変わるHTTP URLだけを証跡の唯一の参照先にしません。 IPFSのように内容から識別できる保存先や、署名済みの証跡を使います。

最後に

ERC8273は、AgentのIDそのものではなく、1回のオンチェーン操作を許可してよいかを確認するためのinterfaceです。 Attestorが評価し、Registryコントラクトが一時認可を作り、AgentウォレットがDAppを呼び出す流れを1つのトランザクションで完結させます。

長く残る権限を発行せず、操作と結び付いた認可だけを使える点が、この提案の中心です。

他でも色々記事を書いているのでぜひよろしければ読んでいってください!