# [EIP8130] Account ConfigurationでネイティブAAを実現する仕組みを理解しよう！

> EIP8130は、アカウント抽象化をネイティブトランザクションとして扱うための提案です。

- URL: https://cardene.dev/articles/6c030864f527b9dfc24b
- 公開日: 2026-05-26
- 更新日: 2026-05-26
- トピック: Blockchain
- タグ: eip, smartcontract, ブロックチェーン, accountabstraction

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

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

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は「**オンチェーンのアカウント設定と新しいトランザクションタイプでアカウント抽象化を実現するEIP8130**」についてまとめていきます。

**EIP8130**は、ウォレットコードを毎回シミュレーションしてトランザクションを受け入れるのではなく、署名検証を担う `Verifier` と所有者設定をオンチェーンで明示する提案です。
署名方式の追加、バッチ実行、ガススポンサー、既存EOAからの移行を、プロトコルが事前検証しやすいトランザクション構造に組み込みます。

:::message
以下にまとめられているものを解説しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-8130

提案では `AA_TX_TYPE` や主要なシステムアドレスが `TBD` のままです。
値そのものではなく、どの役割をどこへ分けているかを中心に見ていきます。

## 概要

**EIP8130**は、アカウント抽象化をネイティブトランザクションとして扱うための提案です。
新しい **EIP2718** 型トランザクションと、アカウントごとの所有者設定を管理する `Account Configuration Contract` を組み合わせます。

:::message
**EIP2718**については以下の記事を参考にしてください。
:::

[\[EIP2718\] Ethereumのトランザクションを拡張するための仕組みを理解しよう！](/articles/1d273e5564158eebe463)

この提案の中心は、署名検証をウォレット実装から切り離すことです。
トランザクションは「どの `Verifier` で検証するか」を明示し、ノードは任意のウォレットコードを実行する前に、検証コストや受け入れ可否を判断できます。

![EIP8130 の全体像](/images/qiita-c226e68a-39b4-45b2-8343-a130ecb98ad0.webp)

AAトランザクションは、`Account Configuration` と `Verifier` で所有者を確認し、`Nonce Manager` と `Transaction Context` を経由して `calls` を実行します。
ウォレットコードだけに責務を集めず、所有者設定、署名検証、nonce、実行文脈をプロトコル上の別々の要素として扱います。

この分離によって、`secp256k1` だけでなく、P-256、パスキー、マルチシグ、将来の耐量子署名のような方式を `Verifier` として追加できます。
一方で、ノードが受け入れる `Verifier` を共通セットに制限することで、メモリプールの検証負荷を予測しやすくします。

## 動機

既存のアカウント抽象化では、ウォレットコードへ検証ロジックを任せる設計が多くなります。
この方式では、ノードがトランザクションを受け入れる前に任意のEVM実行をシミュレーションする必要があります。
そのため、無効な送信を弾くためにも、状態アクセス、実行トレース、評判管理のような追加の仕組みが必要になります。

**EIP8130**は、検証を「ウォレットの任意コード」から「明示された `Verifier`」へ移します。
トランザクションに使う検証方式が先に分かるため、ノードは許可された `Verifier` だけを受け入れ、許可リスト外の検証方式を実行前に拒否できます。

#### 署名方式の拡張

Ethereumの通常トランザクションは `secp256k1` のECDSA署名を前提にしています。
しかし、ウォレットの認証方式はすでにそれだけではありません。
パスキー、モバイル端末の安全領域、マルチシグ、組織アカウント、将来の耐量子署名まで考えると、アカウントごとに署名方式を選べる必要があります。

この提案では、署名方式を `Verifier` コントラクトとして導入します。
`Verifier` は署名データから `ownerId` を返し、プロトコルはその `ownerId` が対象アカウントの所有者として登録されているかを確認します。
署名方式ごとの公開鍵形式や検証手順を、プロトコル本体へ増やさない設計です。

#### ウォレットとノードの境界

ウォレットは自由な認証体験を作りたい一方で、ノードはメモリプールを守るために検証コストを読める必要があります。
`Verifier` を明示する構成では、ウォレットは使う認証方式を選び、ノードは共通の `Verifier` セットを基準に受け入れ可否を判断します。

この境界があることで、ウォレットの柔軟性とノードの予測可能性を両立しやすくなります。
アカウント抽象化をプロトコル内へ入れる時に、任意コード実行をそのままメモリプール検証へ持ち込まないことが狙いです。

## 仕様

### 定数

提案で定義される主な値は以下です。
`TBD` の値は、提案が進む中で確定する前提です。

| 名前 | 値 | 役割 |
|---|---:|---|
| `AA_TX_TYPE` | `TBD` | 新しいEIP2718トランザクションタイプ |
| `AA_PAYER_TYPE` | `TBD` | `payer` 署名のドメイン分離に使う識別バイト |
| `AA_BASE_COST` | `15000` | AAトランザクションの基本intrinsic gas |
| `ACCOUNT_CONFIG_ADDRESS` | `TBD` | アカウント設定コントラクトのアドレス |
| `ECRECOVER_VERIFIER` | `address(1)` | ネイティブECDSA検証を表す予約アドレス |
| `REVOKED_VERIFIER` | `type(uint160).max` | 暗黙EOA所有者を無効化する印 |
| `NONCE_MANAGER_ADDRESS` | `TBD` | nonce管理プリコンパイルのアドレス |
| `TX_CONTEXT_ADDRESS` | `TBD` | トランザクション文脈プリコンパイルのアドレス |
| `DEFAULT_ACCOUNT_ADDRESS` | `TBD` | EOA自動委任先のデフォルト実装 |
| `DEPLOYMENT_HEADER_SIZE` | `14` | デプロイ用ヘッダのバイト数 |
| `NONCE_KEY_MAX` | `2^256 - 1` | nonceなしモードを表す特別なキー |

`ECRECOVER_VERIFIER` は通常のコントラクトではなく、プロトコルがECDSA検証を直接行うための予約値です。
既存EOAが最初からAAトランザクションを使えるようにするため、暗黙のEOA所有者ルールと組み合わせて使われます。

### アカウント設定

各アカウントは、`ACCOUNT_CONFIG_ADDRESS` にあるアカウント設定コントラクトを通じて所有者を登録します。
所有者は `ownerId` で識別されます。
`ownerId` は32バイトの識別子で、どの公開鍵データからどう導くかは `Verifier` ごとに決めます。

所有者設定は `owner_config` という1スロットに入ります。

| フィールド | バイト位置 | 役割 |
|---|---:|---|
| `verifier` | `0-19` | 署名検証を担当する `Verifier` アドレス |
| `scope` | `20` | その所有者が使える認証文脈 |
| `reserved` | `21-31` | 将来拡張用。ゼロでなければなりません |

1所有者を1スロットへ収めることで、プロトコルは所有者確認と権限確認に必要な値を1回の `SLOAD` で読めます。
公開鍵そのものは保存せず、署名時に `Verifier` 固有の `data` として渡します。
大きな公開鍵を永続ストレージへ置かないため、耐量子署名のように公開鍵が大きい方式でも状態肥大を抑えられます。

#### 暗黙のEOA所有者

登録がないアカウントでも、`ownerId == bytes32(bytes20(account))` であれば、そのEOAは暗黙に所有者として扱われます。
この場合の `scope` は `0x00` で、すべての文脈で使えます。

このルールにより、既存EOAは事前登録なしでAAトランザクションを送れます。
一方で、暗黙EOA所有者を無効化する場合は、対象スロットへ `REVOKED_VERIFIER` を書き込みます。
空スロットと無効化済みスロットを区別できるようにするためです。

#### `Owner Scope`

`scope` は、所有者がどの認証文脈で使えるかを表すビットマスクです。
`0x00` は制限なしを表し、非ゼロの場合は該当ビットが立っている文脈だけで有効です。

| ビット | 値 | 名前 | 使われる文脈 |
|---:|---:|---|---|
| 0 | `0x01` | `SIGNATURE` | `verifySignature()` によるERC1271互換の署名確認 |
| 1 | `0x02` | `SENDER` | `sender_auth` による送信者認証 |
| 2 | `0x04` | `PAYER` | `payer_auth` によるガス支払い認証 |
| 3 | `0x08` | `CONFIG` | 所有者変更の認可 |

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

[\[ERC6492\] デプロイされていないコントラクトアカウントで署名検証する仕組みを理解しよう！](/articles/019072f96b73939a047d)

![Owner Scope の役割](/images/qiita-cb066367-681f-4e13-9a7c-d070417f930e.webp)

同じアカウントに複数の所有者がいても、`scope` によって署名確認用、送信者用、`payer` 用、設定変更用の権限を分けられます。
`Verifier` は `ownerId` を返すだけで、最終的な権限判定はプロトコルが `owner_config` と `scope` で行います。

例えば、ガススポンサー用の所有者には `PAYER` だけを与えられます。
その所有者はガス支払いを承認できますが、アカウント本人として `calls` を実行したり、所有者設定を変更したりできません。

### 2次元nonce

nonceは `NONCE_MANAGER_ADDRESS` のプリコンパイルで管理されます。
AAトランザクションは `nonce_key` と `nonce_sequence` の2つを持ちます。

| `nonce_key` | 名前 | 役割 |
|---:|---|---|
| `0` | 標準チャネル | 通常の連番。メモリプールで通常使うチャネル |
| `1` から `NONCE_KEY_MAX - 1` | ユーザー定義チャネル | ウォレットが独立した並列レーンとして使う |
| `NONCE_KEY_MAX` | nonceなしモード | nonce状態を読まず、短い `expiry` とトランザクションハッシュ重複排除で再利用を抑える |

`nonce_key` と `nonce_sequence` の関係は以下です。

![2次元nonce](/images/qiita-9f668ebd-3087-4cda-825f-b088e8743665.webp)

`nonce_key` は独立したチャネルを選び、`nonce_sequence` はそのチャネル内の順序を管理します。
nonceなしモードでは、順序管理を使わない代わりに短い有効期限とハッシュ重複排除で再利用を抑えます。

`nonce_key` を分けると、用途ごとのnonce競合を避けられます。
例えば、定期支払いと通常の署名操作を別チャネルへ分けることで、一方の未確定トランザクションがもう一方の送信順序を止めにくくなります。

`NONCE_KEY_MAX` を使う場合、`nonce_sequence` は `0` でなければなりません。
また、`expiry` は非ゼロである必要があります。
nonce状態を使わない代わりに、短い有効期限とメモリプール内の同一ハッシュ排除でリプレイを抑えます。

### アカウントロック

アカウントは所有者設定をロックできます。
ロック中は、`account_changes` による設定変更も、EVM実行中の `applySignedOwnerChanges()` も拒否されます。

ロック状態は `locked`、`unlock_delay`、`unlocks_at` を含む1つのスロットにまとめられます。
解除は即時ではなく、まず `initiateUnlock()` で解除予定時刻を作り、`block.timestamp >= unlocks_at` になってから設定変更が許可されます。

ロックはメモリプール検証でも意味があります。
所有者集合が固定されていれば、検証済みトランザクションを無効化する主な要因はnonce消費に寄ります。
そのため、ノードはロック済みアカウントに対して高めのメモリプール保持上限を設定しやすくなります。

### 委任インジケータ

**EIP8130**は、**EIP7702** と同じ委任インジケータを使います。
アカウントのコードが `0xef0100 || target` であれば、そのアカウントは `target` のコードへ委任していると扱われます。

:::message
**EIP7702**については以下の記事を参考にしてください。
:::

[\[EIP7702\] EOAをコントラクトアカウントに変換する仕組みを理解しよう！](/articles/13a828c97786aab953e4)

委任済みアカウントへコード実行を伴う操作が来ると、実際には `target` のコードが読み込まれます。
EOAがAAトランザクションを送り、コードがまだ空で、作成や委任の指定もなければ、プロトコルは `DEFAULT_ACCOUNT_ADDRESS` へ自動委任します。
この自動委任は永続します。

### `Verifier` コントラクト

`Verifier` は署名検証を行うコントラクトです。
すべての `Verifier` は、ハッシュと署名データを受け取り、認証された `ownerId` を返します。

`Verifier` の最小インターフェースは以下です。

```js
interface IVerifier {
    function verify(
        bytes32 hash,
        bytes calldata data
    ) external view returns (bytes32 ownerId);
}
```

戻り値が `bytes32(0)` であれば、署名は無効として扱われます。
プロトコルは返された `ownerId` を `owner_config` と照合し、保存されている `verifier` と一致するかを確認します。

`Verifier` は `STATICCALL` で実行されます。
また、委任アカウントを `Verifier` として使うことはできません。
`Verifier` のアドレスにあるコードが `0xef0100` で始まる場合、プロトコルは拒否します。

#### 共通 `Verifier` セット

ノードが何でも受け入れると、ネットワークごとに受け入れ可能な署名方式がばらつきます。
そこで提案では、準拠ノードが必ず受け入れる `Verifier` の共通セットを定義します。

初期セットは以下です。

| 名前 | アルゴリズム | `Verifier` |
|---|---|---|
| `k1` | `secp256k1` | `ECRECOVER_VERIFIER` |
| `p256` | P-256 | オンチェーンコントラクト |
| `passkey` | WebAuthn / FIDO2 | オンチェーンコントラクト |
| `delegate` | 署名委任 | オンチェーンコントラクト |

`secp256k1` の場合、プロトコルは `address(1)` を見て `ecrecover` を直接実行します。
それ以外の `Verifier` は、コントラクトの `verify()` を呼び出します。

### 対応するアカウントタイプ

**EIP8130**は、既存EOA、新規アカウント、既存スマートアカウントの3つを扱います。

| アカウント | 使い方 | 鍵復旧や移行 |
|---|---|---|
| 既存スマートコントラクト | アカウント設定コントラクトに所有者を登録する | ウォレット実装側の復旧設計を使う |
| EOA | 既存の `secp256k1` 鍵でAAトランザクションを送る | 標準トランザクションやEIP7702トランザクションも使える |
| 新規アカウント | `account_changes` の `create` 項目で作る | 初期所有者と `Verifier` を同時に設定する |

既存の **ERC4337** スマートアカウントは、再デプロイなしで移行できます。
`importAccount()` で `ERC1271` 署名を確認して初期所有者を登録し、ウォレットロジックから `verifySignature()` や `getOwnerId()` を使うように更新します。
その後も、既存の `EntryPoint` 経由の `UserOperation` とAAトランザクションを併用できます。

:::message
**ERC4337**については以下の記事を参考にしてください。
:::

[\[ERC4337\] プロトコルの変更をせずにAAを実装する仕組みを理解しよう！](/articles/8c97ca7c93c4557ebfc2)

### AAトランザクション形式

AAトランザクションは `AA_TX_TYPE` で始まるEIP2718型トランザクションです。
構造は以下です。

```js
AA_TX_TYPE || rlp([
  chain_id,
  sender,
  nonce_key,
  nonce_sequence,
  expiry,
  max_priority_fee_per_gas,
  max_fee_per_gas,
  gas_limit,
  account_changes,
  calls,
  payer,
  sender_auth,
  payer_auth
])

call = rlp([to, data])
```

主なフィールドの役割は以下です。

| フィールド | 役割 |
|---|---|
| `chain_id` | **EIP155** に基づくチェーンID |
| `sender` | 送信アカウント。EOA署名の処理では空値にし、署名から復元する |
| `nonce_key` | nonceチャネル |
| `nonce_sequence` | 対象チャネルの期待シーケンス |
| `expiry` | 有効期限。`0` は期限なし |
| `gas_limit` | `sender` 側の検証、intrinsic cost、設定変更、`calls` 実行に使う上限 |
| `account_changes` | 作成、所有者変更、委任の配列 |
| `calls` | 実行する呼び出しのフェーズ配列 |
| `payer` | 空値なら `sender` 支払い。アドレスならスポンサー支払い |
| `sender_auth` | `sender` の認証データ |
| `payer_auth` | `payer` の認証データ |

:::message
**EIP155**については以下の記事を参考にしてください。
:::

[\[EIP155\] Ethereumネットワークでリプレイ攻撃を防ぐ仕組みを理解しよう！](/articles/2526432899353e4a0225)

`sender` が空値の場合、`sender_auth` は65バイトのECDSA署名です。
プロトコルは `ecrecover` で `sender` を復元し、`ownerId = bytes32(bytes20(sender))` として扱います。

`sender` が入っている場合、`sender_auth` は以下のバイト列です。

```js
sender_auth = verifier(20 bytes) || data
```

先頭20バイトが使う `Verifier` を示し、残りの `data` は `Verifier` ごとの署名データです。
`ECRECOVER_VERIFIER` の場合は、`data` がECDSA署名になります。

### 署名検証

検証は以下の順に行われます。

1. `sender` を解決する
    `sender` が空値なら `sender_auth` から `ecrecover` で復元する。
    `sender` がある場合は、`sender_auth` の先頭20バイトを `Verifier` として読む。
2. トランザクション文脈を設定する
    `TX_CONTEXT_ADDRESS` に `sender`、`payer`、`calls` を読める状態として持たせる。
3. 署名を検証する
    EOA署名の処理と `ECRECOVER_VERIFIER` では、プロトコルが直接 `ecrecover` を行う。
    それ以外は `verifier.verify(hash, data)` を `STATICCALL` で呼ぶ。
4. 所有者を認可する
    `owner_config(sender, ownerId)` を読み、保存された `verifier` と一致するか確認する。
    暗黙EOA所有者の条件を満たす場合だけ、空スロットでも認可する。
5. `scope` を確認する
    `sender_auth` では `SENDER`、`payer_auth` では `PAYER`、`verifySignature()` では `SIGNATURE`、設定変更では `CONFIG` を要求する。

暗黙EOA所有者ルールは、ネイティブECDSA署名の処理でだけ使えます。
任意の `Verifier` が `bytes32(bytes20(sender))` を返しただけでは、未登録EOAの所有者として扱われません。
この制限により、悪意ある `Verifier` が未設定EOAを勝手に認証する処理を防ぎます。

#### 署名ハッシュ

`sender` 署名と `payer` 署名は、異なる識別バイトでドメイン分離されます。
同じ署名が `sender` 認証と `payer` 認証で使い回されないようにするためです。

`sender` 署名は、`sender_auth` と `payer_auth` を除いたトランザクション本体を対象にします。

```js
keccak256(AA_TX_TYPE || rlp([
  chain_id,
  sender,
  nonce_key,
  nonce_sequence,
  expiry,
  max_priority_fee_per_gas,
  max_fee_per_gas,
  gas_limit,
  account_changes,
  calls,
  payer
]))
```

`payer` 署名は、`AA_PAYER_TYPE` を使い、`payer` と両方の認証データを除外します。

```js
keccak256(AA_PAYER_TYPE || rlp([
  chain_id,
  sender,
  nonce_key,
  nonce_sequence,
  expiry,
  max_priority_fee_per_gas,
  max_fee_per_gas,
  gas_limit,
  account_changes,
  calls
]))
```

EOA署名の処理では、トランザクションにエンコードされた `sender` フィールドが空値です。
しかし `payer` 署名ハッシュを作る時は、復元済みの `sender` アドレスを `sender` 位置へ入れます。
復元済み `sender` を入れないと、別のEOAが同じ `nonce_key`、`nonce_sequence`、手数料、`calls` を持つトランザクションを作り、`payer` 署名を流用できてしまいます。

`sender` 認証と `payer` 認証の分担は以下です。

![sender認証とpayer認証](/images/qiita-606d26bb-7f65-4fb1-a0c0-2d9316776081.webp)

`sender_auth` は `calls` 実行の承認に使われ、`payer_auth` はガス支払いの承認に使われます。
それぞれ別の `scope` と署名ハッシュを使うことで、実行権限と支払い権限を混同しないようにします。

### ガス支払い

ガス支払いは `payer` と `payer_auth` で決まります。

| `payer` | `payer_auth` | 支払い元 |
|---|---|---|
| 空値 | 空値 | `sender` が自分で払う |
| 20バイトアドレス | `verifier || data` | 指定された `payer` がスポンサーとして払う |

スポンサー支払いでは、`payer` 側の `owner_config` を読み、`payer_auth` を `PAYER` 文脈で検証します。
`sender` 側の `gas_limit` とは別に `payer` 認証コストを計測するため、`payer` が選んだ `Verifier` によって `calls` に使えるgasが減ることはありません。

`sender` は、どの `payer` に紐づけるかを自分の署名対象に含めます。
そのため、誰でも好きな `payer` を後から差し替えられるわけではありません。

### `account_changes`

`account_changes` は、実行前にアカウント状態を変更するための配列です。
種類は3つあります。

| type | 名前 | 役割 |
|---:|---|---|
| `0x00` | create | 新規アカウントを作り、初期所有者を登録する |
| `0x01` | config change | 所有者の追加や削除を行う |
| `0x02` | delegation | EIP7702形式のコード委任を設定する |

![account_changes と calls の流れ](/images/qiita-ead7b6fa-c781-4649-bb0b-141acd48a8ef.webp)

`account_changes` は `calls` より先に処理されます。
新規作成、所有者変更、委任、コード配置を終えたあとで、フェーズ分けされた `calls` が実行されます。

#### `create` 項目

`create` 項目は、新しいスマートアカウントを1回のトランザクションで作成します。
フォーマットは以下です。

```js
rlp([
  0x00,
  user_salt,
  runtimeBytecode,
  initial_owners
])
```

`initial_owners` は `[verifier, ownerId, scope]` の配列です。
アドレスは、`ACCOUNT_CONFIG_ADDRESS` をデプロイヤーとした `CREATE2` 形式で計算されます。
この計算では、初期所有者を `ownerId` でソートしてからハッシュに含めます。
同じ所有者集合なら、指定順が違っても同じアドレスになるようにするためです。

```js
sorted_owners = sort(initial_owners, by: ownerId)
owners_commitment = keccak256(ownerId_0 || verifier_0 || scope_0 || ...)
effective_salt = keccak256(user_salt || owners_commitment)
address = keccak256(
  0xff || ACCOUNT_CONFIG_ADDRESS || effective_salt || keccak256(deployment_code)
)[12:]
```

`runtimeBytecode` はそのままアカウントアドレスへ配置されます。
デプロイ時に初期化コードを実行するのではなく、初期化が必要なら後続の `calls` で行います。

#### `config change` 項目

`config change` 項目は、所有者の追加や削除を表します。
フォーマットは以下です。

```js
rlp([
  0x01,
  chain_id,
  sequence,
  owner_changes,
  auth
])
```

`chain_id` が `0` の場合、同じ署名済み変更を複数チェーンで使えます。
特定のチェーンIDを入れると、そのチェーン専用の変更になります。
どちらの適用方法でも `sequence` が増えるため、所有者変更の順序を決定できます。

`owner_changes` の各要素は以下です。

| `change_type` | 名前 | 使う値 |
|---:|---|---|
| `0x01` | `authorizeOwner` | `verifier`、`ownerId`、`scope` |
| `0x02` | `revokeOwner` | `ownerId` |

`auth` は、変更適用時点の所有者設定に対して検証されます。
複数の `config change` 項目が並ぶ場合、前の項目を適用したあとの状態で、後ろの項目の `auth` を検証します。
署名者には `CONFIG` scope が必要です。

#### `delegation` 項目

`delegation` 項目は、`sender` アカウントへEIP7702形式のコード委任を設定します。

```js
rlp([
  0x02,
  target
])
```

`target` が `address(0)` なら委任を解除し、アカウントを純粋なEOAへ戻します。
委任できるのは、アカウントにコードがない場合、または既存コードが `0xef0100` で始まる委任インジケータである場合だけです。
通常のコントラクトコードを、この項目で上書きすることはできません。

この項目は `sender_auth` で認可されます。
ただし、認可できるのは `sender` の暗黙EOA所有者で、かつ `CONFIG` scope を持つ場合です。
非8130チェーンでは、標準のEIP7702トランザクションで同じ委任を行います。

### `calls` 実行

`account_changes` を処理したあと、プロトコルは `calls` を実行します。
各 `call` は `sender` 名義で対象アドレスへ直接ディスパッチされます。

| パラメータ | 値 |
|---|---|
| `from` | `sender` |
| `to` | `call.to` |
| `tx.origin` | `sender` |
| 対象側の `msg.sender` | `sender` |
| `msg.value` | `0` |
| `data` | `call.data` |

`calls` にはETH `value` がありません。
ETH送金が必要な場合は、ウォレットコードが `CALL` オペコードを使って送金します。
プロトコルが `sender` の代わりにETHを直接動かさない設計です。

`calls` はフェーズ配列です。
フェーズ内の `call` はアトミックに実行され、1つでもrevertすると、そのフェーズ内の変更はすべて破棄されます。
さらに、revertしたフェーズ以降は実行されません。
ただし、成功済みの前フェーズの状態変更は残ります。

よく使う実行パターンは以下です。

| 実行単位 | 例 | 挙動 |
|---|---|---|
| 単純な呼び出し | `[[{to, data}]]` | 1フェーズで1callを実行 |
| アトミックバッチ | `[[call_a, call_b, call_c]]` | すべて成功するか、すべて戻す |
| スポンサー支払い + ユーザー操作 | `[[sponsor_payment], [user_action_a, user_action_b]]` | phase 0の支払いが成功した後、phase 1をまとめて実行 |

### `Transaction Context`

`TX_CONTEXT_ADDRESS` のプリコンパイルは、現在のAAトランザクションに関する読み取り専用情報を返します。
`Verifier` やウォレットコードは、必要な情報だけをこのプリコンパイルから読み取れます。

主な関数は以下です。

```js
struct Call {
    address to;
    bytes data;
}

interface ITxContext {
    function getSender() external view returns (address);
    function getPayer() external view returns (address);
    function getOwnerId() external view returns (bytes32);
    function getCalls() external view returns (Call[][] memory);
    function getMaxCost() external view returns (uint256);
    function getGasLimit() external view returns (uint256);
}
```

`getSender()`、`getPayer()`、`getCalls()`、`getMaxCost()`、`getGasLimit()` は検証時と実行時の両方で使えます。
`getOwnerId()` は、認証済み所有者が決まったあとの実行時だけ使えます。

非8130チェーンでは、このアドレスにコードがありません。
そのため、`STATICCALL` はゼロ値を返す前提になり、8130文脈に依存する `Verifier` は自然に失敗します。

### 移植性

この提案は、8130チェーンと非8130チェーンの両方を意識しています。
構成要素ごとの扱いは以下です。

| 要素 | 8130チェーン | 非8130チェーン |
|---|---|---|
| `Account Configuration Contract` | プロトコルがストレージを直接読む。EVMインターフェースも使える | 標準コントラクトとして動く |
| `Verifier` | プロトコルが `STATICCALL` で呼ぶ | ウォレットや設定コントラクトから呼ぶ |
| コード委任 | `account_changes` の `delegation` 項目 | 標準EIP7702トランザクション |
| `Transaction Context` | プリコンパイルが文脈を返す | ゼロ値を返す |
| `Nonce Manager` | プリコンパイルで管理 | ERC4337のEntryPointなど既存の仕組み |

全コントラクトを決定的な `CREATE2` アドレスへデプロイすることで、複数チェーンで同じ管理基盤を使いやすくします。

### メモリプールでの検証

ノードがメモリプールで受け入れる時の流れは以下です。

1. 構造を確認する
    `sender_auth` をパースし、`create` 項目が最大1つで先頭にあること、`delegation` 項目が最大1つであることを確認する。
2. `sender` を解決する
    `sender` がある場合はその値を使い、空値の場合は `sender_auth` から `ecrecover` で復元する。
3. 有効な所有者状態を決める
    `create` 項目があれば初期所有者を使い、なければアカウント設定ストレージを読む。
4. 設定変更と委任を確認する
    アカウントがロックされていないか、委任先が置き換え可能なコード状態かを確認する。
5. `sender` 認証を検証する
    解決した所有者に `SENDER` scope があることを確認する。
    `delegation` 項目があれば、暗黙EOA所有者かつ `CONFIG` scope も必要になる。
6. `payer` を解決する
    自己支払いなら `sender` 残高を確認し、スポンサー支払いなら `payer` 側の `owner_config` と `PAYER` scope を確認する。
7. nonce、残高、有効期限を確認する
    標準nonceでは現在値と `nonce_sequence` の一致を要求する。
    nonceなしモードでは `expiry` を短く要求し、同一ハッシュを重複排除する。
8. メモリプール保持上限を確認する
    ガス支払い元ごとの未確定トランザクション数が、ノード設定の上限内かを見る。

ノードは共通 `Verifier` セットを含む許可リストを持ちます。
共通セット以外の `Verifier` を追加で受け入れると、ノードごとの受け入れ差が生まれるため、提案では拡張しない方向を推奨しています。

### ブロック実行

ブロックへ含まれたAAトランザクションは以下の順で処理されます。

1. ロック状態と委任条件を確認する
    `account_changes` に設定変更や委任があれば、`sender` がロックされていないことを確認する。
2. `payer` からガスを差し引く
    自己支払いなら `sender`、スポンサー支払いなら指定 `payer` から差し引く。
3. nonceを進める
    `nonce_key != NONCE_KEY_MAX` の場合、`(sender, nonce_key)` のnonceをインクリメントする。
4. 必要なら自動委任する
    `sender` にコードがなく、`create` 項目も `delegation` 項目もなければ、`DEFAULT_ACCOUNT_ADDRESS` へ自動委任する。
5. `account_changes` を処理する
    `create`、`config change`、`delegation`、コード配置を順に適用する。
6. トランザクション文脈を設定する
    `sender`、`payer`、`ownerId`、`calls` を `Transaction Context` から読めるようにする。
7. `calls` を実行する
    フェーズ単位のrevertルールに従って実行する。

未使用gasは `payer` へ返金されます。
設定変更やアカウント作成、委任が成功した場合、プロトコルが `OwnerAuthorized`、`AccountCreated`、`DelegationApplied` のようなログをreceiptへ注入します。
このログは8130トランザクションでだけ発行されます。

### RPC拡張

`eth_getTransactionCount` は、任意の `nonceKey` を指定できるように拡張されます。
この拡張により、標準チャネル以外のnonceも問い合わせできます。

receiptには以下が追加されます。

| 項目 | 役割 |
|---|---|
| `payer` | ガス支払い元。自己支払いなら `sender`、スポンサー支払いなら指定 `payer` |
| `status` | 全フェーズ成功なら `0x01`、どれかがrevertすれば `0x00` |
| `phaseStatuses` | 各フェーズの成功・失敗を表す配列 |

既存ツールが `status == 1` を成功として見る挙動は、全フェーズ成功の場合に維持されます。
フェーズごとの詳細は `phaseStatuses` で確認できます。

## 補足

#### なぜ `Verifier` コントラクトなのか

`Verifier` は署名方式の追加点です。
プロトコルは「どのアルゴリズムで公開鍵から署名を検証するか」を知らず、`Verifier` から返る `ownerId` だけを扱います。
そのため、署名方式を追加しても、プロトコルにアルゴリズム固有の分岐を増やしません。

また、権限分離は `Verifier` ではなく `scope` で行います。
`Verifier` が協力しなくても、プロトコルが `SENDER`、`PAYER`、`CONFIG` などの文脈を強制できます。

#### なぜ `Nonce Manager` がプリコンパイルなのか

nonceはほぼすべてのAAトランザクションで更新されます。
一方、所有者設定は頻繁には変わりません。
そこで、nonceをアカウント設定コントラクトの通常ストレージへ混ぜず、専用の `Nonce Manager` プリコンパイルへ分けます。

EVMからは読み取り用の `getNonce()` だけを公開します。
書き込みはプロトコルがAAトランザクション処理の中で直接行います。

```js
interface INonceManager {
    function getNonce(address account, uint256 nonceKey) external view returns (uint64);
}
```

#### なぜ `calls` に `value` がないのか

AAトランザクションの `calls` は `to` と `data` だけを持ち、`msg.value` は常に `0` です。
ETHを送る場合は、アカウントのウォレットコードが `CALL` で送金します。

この設計では、プロトコルが `sender` のETHを直接移動しません。
ETH移動のポリシーはウォレットコード側へ残し、AAトランザクション本体は呼び出しの列と認証の仕組みに集中します。

#### なぜ `Owner Scope` が必要なのか

`scope` がないと、登録された所有者はすべて同じ権限を持ちます。
この場合、署名確認用の所有者、トランザクション送信用の所有者、ガススポンサー用の所有者、所有者変更用の所有者を分けられません。

実際のウォレットでは、パスキーで日常操作を行い、ハードウェアキーで設定変更を行い、別の `payer` 鍵でガス支払いだけを許可する、といった構成にできます。
`scope` は、その役割分担をプロトコルレベルで強制するためのビットマスクです。

#### なぜ公開鍵を保存しないのか

公開鍵をオンチェーンに保存すると、所有者ごとに永続ストレージが増えます。
P-256程度ならまだ小さくても、耐量子署名では公開鍵が大きくなり、1所有者で大量のストレージを使います。

**EIP8130**では、保存するのは `verifier`、`ownerId`、`scope` だけです。
公開鍵材料は署名時の `data` に含め、`Verifier` がそこから `ownerId` を導きます。
公開鍵材料を一時的なcalldataに置くことで、永続ストレージの増加を抑えます。

## 互換性

既存EOAと既存スマートコントラクトは、そのまま動作します。
AA機能は opt-in です。

EOAは通常トランザクションを送り続けられます。
AAトランザクションを初めて送る時は、必要に応じて `DEFAULT_ACCOUNT_ADDRESS` へ自動委任されます。
自分で委任先を選ぶ場合は、`account_changes` の `delegation` 項目や標準EIP7702トランザクションを使えます。

ERC4337インフラも継続利用できます。
既存スマートアカウントは、所有者登録と `verifySignature()` への委譲を追加することで、ネイティブAAトランザクションとERC4337 `UserOperation` の両方を扱えます。

非8130チェーンでも、アカウント設定コントラクトや `Verifier` コントラクト自体は使えます。
ただし、`Transaction Context` と `Nonce Manager` はプロトコル側の機能なので、同じ挙動を完全に再現するには8130対応チェーンが必要です。

## 参考実装

提案では、`IAccountConfiguration`、`IVerifier`、`ITxContext`、`INonceManager` が示されています。
ここでは主要なインターフェースを抜粋します。

`IAccountConfiguration` は、アカウント作成、所有者変更、ロック、署名確認、設定参照をまとめて扱います。

```js
interface IAccountConfiguration {
    struct OwnerConfig {
        address verifier;
        uint8 scopes;
    }

    struct Owner {
        bytes32 ownerId;
        OwnerConfig config;
    }

    struct OwnerChange {
        bytes32 ownerId;
        uint8 changeType;
        bytes configData;
    }

    event OwnerAuthorized(address indexed account, bytes32 indexed ownerId, OwnerConfig config);
    event OwnerRevoked(address indexed account, bytes32 indexed ownerId);
    event AccountCreated(address indexed account, bytes32 userSalt, bytes32 codeHash);
    event DelegationApplied(address indexed account, address target);

    function createAccount(
        bytes32 userSalt,
        bytes calldata bytecode,
        Owner[] calldata initialOwners
    ) external returns (address);

    function applySignedOwnerChanges(
        address account,
        uint64 chainId,
        OwnerChange[] calldata ownerChanges,
        bytes calldata auth
    ) external;

    function verifySignature(
        address account,
        bytes32 hash,
        bytes calldata signature
    ) external view returns (bool verified);

    function getOwnerConfig(
        address account,
        bytes32 ownerId
    ) external view returns (OwnerConfig memory);
}
```

`createAccount()` は、初期所有者を含めてアカウントアドレスを決定し、ランタイムバイトコードを配置します。
`applySignedOwnerChanges()` は、署名済みの所有者変更をEVM経由で適用する移植可能な実行方法です。
`verifySignature()` は、ERC1271互換の署名確認に使われます。

`IVerifier` は非常に小さいです。
署名方式ごとの差分は `data` の中に閉じ込められます。

```js
interface IVerifier {
    function verify(
        bytes32 hash,
        bytes calldata data
    ) external view returns (bytes32 ownerId);
}
```

この小ささが、署名方式を追加しやすくしている部分です。
新しい認証方式を使いたい場合、`verify()` で同じ戻り値形式に合わせれば、プロトコルは `owner_config` と `scope` の確認だけで済みます。

## セキュリティ

#### 検証面

純粋な `Verifier` では、検証済みトランザクションを無効化する主な要因は `owner_config` の取り消しとnonce消費です。
状態に依存する `Verifier` では、読んだ状態が変わることも無効化要因になります。
その追跡はメモリプール側の課題です。

#### リプレイ保護

AAトランザクションは `chain_id`、2次元nonce、`expiry` を持ちます。
通常nonceでは `(sender, nonce_key)` ごとに `nonce_sequence` を確認します。
nonceなしモードではnonce状態を使わないため、短い `expiry` と同一ハッシュの重複排除が重要になります。

ブロックビルダーは、同じハッシュのnonceなしトランザクションを重複して含めてはいけません。
ノードも、nonceなしモードの有効期限が長すぎるトランザクションをメモリプールで拒否する前提です。

#### `Owner Scope`

`scope` は `Verifier` 実行後にプロトコルが確認します。
そのため、`Verifier` がどの `ownerId` を返しても、登録された `scope` を越えた権限は得られません。

特に `payer` 用の所有者と `sender` 用の所有者を分けられる点が重要です。
`PAYER` だけの所有者はガス支払いを承認できますが、`sender` として操作を実行できません。
`SENDER` だけの所有者は操作を実行できますが、`payer` として他者のガス支払いを承認できません。

#### 暗黙EOAルール

暗黙EOA所有者として認められるのは、ネイティブ `secp256k1` 署名の処理だけです。
`sender` を `sender_auth` から `ecrecover` で復元する場合、または `ECRECOVER_VERIFIER` を使う場合に限られます。

任意の `Verifier` が `bytes32(bytes20(sender))` を返しただけで暗黙EOA所有者として認めると、未登録EOAを勝手に認証できる危険があります。
この提案は、暗黙EOAルールをECDSA署名の処理へ限定して、その攻撃面を閉じています。

#### `payer` 署名の再利用

`sender` 署名と `payer` 署名は `AA_TX_TYPE` と `AA_PAYER_TYPE` で分けられます。
さらに、`payer` 署名ハッシュには解決済み `sender` アドレスを入れます。

EOA署名の処理ではトランザクションにエンコードされた `sender` フィールドが空値ですが、`payer` 署名ハッシュでは復元済み `sender` を使います。
復元済み `sender` をハッシュに含めないと、別のEOAが同じ `nonce_key`、`nonce_sequence`、手数料、`calls` を持つトランザクションを作り、`payer` 署名を再利用できてしまいます。
復元済み `sender` をハッシュへ入れることで、`payer` の承認は特定 `sender` 専用になります。

#### アカウント作成

`create` 項目では、`initial_owners` をsaltへコミットします。
そのため、攻撃者が同じ `user_salt` とコードで、異なる所有者を持つアカウントを先に作ることを防げます。

ウォレットバイトコードは、初期化前でも安全に置かれる必要があります。
`create` 項目ではランタイムバイトコードが直接配置されるため、初期化が必要な処理は後続の `calls` で行う設計になります。

## 最後に

今回は「**オンチェーンのアカウント設定と新しいトランザクションタイプでアカウント抽象化を実現するEIP8130**」についてまとめてきました。

**EIP8130**は、アカウント抽象化の検証をウォレットコードから切り離し、`Verifier`、`owner_config`、`scope`、2次元nonce、`payer` 認証として分解する提案です。
ノードが受け入れ前に検証コストを読みやすくしつつ、ウォレット側にはパスキーや耐量子署名のような認証方式を追加できる余地を残しています。
既存EOAやERC4337スマートアカウントからの移行手順も含んでいるため、アカウント抽象化をプロトコルの標準機能へ近づける大きな設計案として理解しておきたい内容です。

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

https://amzn.asia/d/gxvJ0Pw

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://twitter.com/cardene777

https://cardene.substack.com/
