Cover photo

Account Abstractionの関連規格と関係性

ERC4337やEIP3074などのアカウント抽象化に関連する規格の解説とその関係性についてまとめている記事です。

初めまして。

普段ブロックチェーン関連の開発や発信をしているかるでねと申します。

https://twitter.com/cardene777

https://qiita.com/cardene

https://zenn.dev/heku

https://chaldene.net/

https://cardene.substack.com/

今回はアカウント抽象化に関連する規格の解説とその関係性についてまとめていきます。

Account Abstractionとは?

post image

まずは**アカウント抽象化(Account Abstraction)**が何かについて確認してきましょう。

Ethereumにおいてアカウントと呼ばれるものには以下の2つがあります。

  • EOAアカウント

  • コントラクトアカウント

EOAアカウントは秘密鍵を保有していてトランザクションを起こすことができます。

Metamaskなどのウォレットで管理している1つ1つのアドレスが、まさにEOAアカウントです。

一方、コントラクトアカウントは秘密鍵を持たないがコードを保有しています。

スマートコントラクトがこのコントラクトアカウントに該当します。

アカウント抽象化とは、ざっくりいってしまうと「EOAアカウントとコントラクトの差をなくす」ということです。

これにより以下のようなメリットがあります。

  • ユーザーにウォレットを意識させずに作成から使用まで可能。

  • 秘密鍵を持たないため不正な送金をコントラクトレベルでブロックできる。

  • 任意の署名方式を使用できる。

  • 署名に使用する鍵をユーザーが無くしたり流出した際に再作成可能。

  • 署名に加えて指紋認証などを組み合わせて多要素認証が可能。

  • 一定の期間や一定の資金までは署名不要でトランザクションを起こせる。

より詳しくは以下の記事にまとめています。

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58/-qoixMc413uT4FFM61uhXYUiL05Hw-wahq9hV_JtjJs

Account Abstractionの関連提案

post image

まずはAccount Abstractionに関する提案を一通り見ていきます。

EIP86

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

当時(現在)のEthereumでは、ECDSAにより署名検証とnonceによりリプレイ攻撃防止機能が直接プロトコルに組み込まれています。

nonceはそのアドレスから実行されたトランザクションの総数です。nonceがあることで同じトランザクションを2度通すことを防いでいます。

この規格では、上記2つの機能をコントラクトに委ねることを提案しています。これにより、ユーザーが柔軟にセキュリティモデルを定義でき、以下のことを可能にします。

  • 通常であれば無効なトランザクション署名を有効とみなす。(r = s = 0, v = CHAIN_ID

  • トランザクション内のガス代・nonce・送金額を0とし、送信アドレスも特殊な値である(NULL_SENDER)にする。

  • CREATE2(別提案で導入済み)というコントラクトデプロイ前にアドレスを決定できるオペコードを追加する。

  • コントラクト作成時、既にアドレスが存在する場合に作成を失敗させる。

これにより以下のようなメリットもあります。

  • コントラクトに全員の署名を含んだトランザクションを1つ送るだけで済むため、マルチシグウォレットの実装が簡素化。

  • 資金を引き出す時、引き出す資金の一部をガス代に使用可能。

  • 任意の署名方式を使用できる。

  • 有効期限付きのトランザクションなどいろいろカスタム可能。

また、マイナー(当時)がどのようにしてトランザクションを受け入れるかについてが課題であり、以下のように提案されています。

  • Serpent(スマートコントラクトをかけるプログラミング言語)で書かれたコードで、ユーザー固有の公開鍵ハッシュが埋め込まれているか。

  • トランザクションデータ内の署名の検証が可能か。

  • トランザクションデータ内とアカウント内のガス代チェック。

  • アカウントのnonceがトランザクションデータ内のnonceと一致する。

正直どれもイマイチな気がしています。プロトコルレベルでの変更も必要であり、時間・コスト・リスクが伴うのも課題です。

EIP2938

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

https://qiita.com/cardene/items/baf4c07631756b35dcf0#%E3%83%9E%E3%83%AB%E3%83%81%E3%83%86%E3%83%8A%E3%83%B3%E3%83%88

新しいオペコードであるPAYGASを提案していて、コントラクトが支払い可能なガス価格とガスリミットを指定し、トランザクションの有効性を示すことができます。

この提案では、アカウント抽象化を以下の2つの層に分けて提案していて、先にシングルテナントAAから実装しようとしています。

  • シングルテナントAA

    • 個人のウォレットや少人数での使用を想定。

  • マルチテナントAA

    • 多くのアドレス(DeFiなど)をサポートできる。

この2つのAAを実装することで以下のようなことが実現できます。

  • 様々な署名技術の使用。

  • マルチシグ検証やソーシャルリカバリー機能を含むコントラクトウォレット。

  • tornado.cashのようなプライバシー保護システム。

  • 条件を満たさないトランザクションを無効にすることによるガス効率化。

  • ガス代をETH以外で支払い可能。

シングルテナントAA

AA_TX_TYPEというトランザクションタイプ(AAトランザクション)を導入し、以下の変更が実装されます。

  • ベースガスコストは、実行されるコントラクトのコードの複雑さで決定されるAA_BASE_GAS_COSTに設定。

  • nonceの処理は既存のトランザクションと同様。

  • ガスリミットがなく、新オペコードであるPAYGASで調整。

  • トランザクション全体のグローバル変数である、transaction_fee_paidgas_pricegas_limitを導入し、AA_TX_TYPEの場合とそうでない場合でガス代の処理を分ける。

  • PAYGASでは、指定されたメモリからガス価格とガスリミットを取り出し、条件を満たす場合(アカウントに残高が十分にある、手数料未払い、トランザクションを送ったコントラクトからの実行か)、コントラクトの残高からガス価格 * ガスリミットを引く。

    • これが成功するとgas_pricegas_limitをメモリから読み取った値に更新。

  • 以下の方法によりリプレイ攻撃保護。

    • SELFDESTRUCTを無効にするオペコード(SET_INDESTRUCTIBLE**)**が設定されている。

    • SELFDESTRUCTを呼び出した時、nonce0にせず保存して再度実行を防ぐ。

  • 以下の3つのオペコードの処理を更新。

    • CALLERORIGINAA_ENTRY_POINTを返し、コントラクトアドレスからのトランザクションとわかるようにする。

    • GASPRICEは、PAYGASオペコードを通して動的に設定される。

  • ノードでは以下の検証を行いAAトランザクションの有効性をチェックする。

    • 対象コントラクトのコードがAA_PREFIX(AAトランザクションを受け入れ可能かの値)で始まっている。

    • トランザクションの実行をシミュレートし以下の条件のチェック。

      • 環境に依存するオペコード(BLOCKHASHCOINBASETIMESTAMPなど)とBALANCEオペコードを使用していないか。

        • ブロックチェーンに適用する前に検証が行われるため、オペコードの戻り値が正確に予測できない。

      • 外部コントラクトの呼び出しや外部コントラクトのコードを読み取るオペコード(EXTCODESIZEEXTCODEHASHなど)の使用をしていない。

        • 予期せぬ動作を引き起こす可能性があるため。

    • ガスの消費量か利用可能量が一定の制限(VERIFICATION_GAS_CAP)を超えていない。

    • コントラクトの残高がガス代を支払えるか。

  • ノードは以下の確認を行いながらメモリープールの管理を行う。

    • 現在の有効なnonceよりも大きなnonceのトランザクションを弾く。

    • 同じコントラクト宛の同じnonceのトランザクションが複数ある場合、ガス価格の高い方を残す。

    • 新しいブロックを処理するとき、メンプール内のトランザクションを全て破棄。

これも先ほど同様、プロトコルレベルの変更が必要になります。

ERC4337

以下の記事たちでわかりやすく・詳しく解説しています。ERC4337はここまでの2つの提案と異なり、プロトコルレベルでの変更が不要になるため、現在(2024年5月)様々な形で実装されています。

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58/-qoixMc413uT4FFM61uhXYUiL05Hw-wahq9hV_JtjJs

https://qiita.com/cardene/items/8c97ca7c93c4557ebfc2

EIP3074

ちょっとこの章は長いです。

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3074.md

https://qiita.com/cardene/items/f55a6d2df6d13030e20e

2つの新しいオペコードである「AUTH」と「AUTHCALL」を追加し、コントラクトにEOAアカウントの制御を委任する機能を追加する提案です。

  • AUTH

    • ESDSA署名の検証に基づいて「authorized」という変数を設定し、EVMにコントラクトによる制御が認証されたことを知らせます。

  • AUTHCALL

    • AUTH」で設定された認証情報をもとに、コントラクトからトランザクションの実行ができます。

これにより以下のことが実現できます。

  • ガス代をERC20トークンで支払う。

  • ガス代を別のアカウントに支払ってもらう「スポンサードトランザクション」が可能。

  • EOAを使用しつつ、上記の「スポンサードトランザクション」やトランザクションのバッチ処理などが実行できUXが向上。

スポンサードトランザクションについては以下の記事を参考にしてください。

https://chaldene.net/erc20permit#st-toc-h-7

authorizedという変数は実行フレームの中(コントラクトの一連の命令が実行される期間)のみ有効です。仮にこのコントラクトが別のコントラクトを呼び出すと新しい実行フレームが作成され、authorizedは初期日リセットされます。このように各実行フレームが独自のauthorized変数をもち、同じコントラクト内の別の部分の実行であっても引き継がれないことで安全性を保っています。

AUTH

以下の入力値とメモリ内の署名データ、出力値を持ちます。

入力

  • authority

    • 署名を生成したアカウントのアドレス。

  • offset

    • 署名データが格納されているメモリ開始位置。

  • length

    • 署名データの長さ。

メモリ内の署名データ

rscommitからなるそれぞれ32バイト、計96バイトのデータ。

出力値

  • success

    • 署名の検証に成功したかのbool値。

上記をもとに以下のように動作します。

  • authorityのアドレスが持つコード(EXTCODESIZE)が0でない場合(= EOAアドレスでない)、処理は失敗してauthorized変数は未設定になる。

  • yParity, r, s)という3つの値は以下の5つのデータを連結しkeccak256ハッシュ関数に通して生成されたESDSA署名です。

    • 固定値であるMAGIC

    • トランザクションが実行されているEthereumチェーンの識別子であるchainId

    • 署名者のアカウントが実行したトランザクションの数であるnonce

    • AUTH命令を実行するコントラクトのアドレスであるinvokerAddress

    • AUTH命令に渡された32バイト値であるcommit

  • 署名の検証と署名の復元はトランザクション署名と同様に処理される。

  • 署名が有効で、署名者のアドレスがauthorityと同じ場合authorized変数にauthorityが設定され、署名が無効または署名者のアドレスがauthorityと一致しない場合authorized変数は未設定になる。

  • authorized変数がセットされた場合は1を、そうでない場合は0を返す。

ここでのポイントはauthorized変数に直接コントラクトアドレスを指定することはできず、生成される署名データ内に実行権限を委任するコントラクトアドレスを指定できるようになっています。

AUTHCALL

以下の入力値と出力値を持ちます。

入力値

  • gas

    • サブコール(EOAから委任された権限を使用して他コントラクトを呼び出す)に提供するガス量。

  • addr

    • コール先のアドレス。

  • value

    • コールと一緒に送るWeiの量。

  • argsOffset

    • コールのデータ部分の開始位置(メモリ内)。

  • argsLength

    • コールのデータ部分の長さ(バイト数)。

  • retOffset

    • コールの戻り値を格納するメモリの開始位置。

  • retLength

    • コールの戻り値を格納するメモリの長さ。

出力値

  • success

    • コールが成功したかのbool値。

基本的にCALLと同じように動作します。

  • authorized変数が未設定の場合は実行が無効になり、設定されている場合はコールの呼び出しもとアドレスがauthorizedの値に設定されます。

    • CALL時には実行フレームが変わってしまうため、改めて設定しています。

  • サブコールに必要なガスがgasよりも少ない場合は実行が無効。

  • valueが残っていてもガス代の補助には使用できない。

  • valueauthorizedの残高から差し引かれ、足りない場合は実行が無効。

  • AUTHCALLはコールの深さは1つだけ増加させる。

    • CALLの場合は呼び出し元のアドレスに対してコールを行い、その後呼び出し先のアドレスにコールを行うため2段階のコールが必要。

    • AUTHCALLは直接呼び出し先のアドレスにコールを行い、authorizedで指定されたアドレスからAUTHCALLの引数で指定されたアドレスへの直接コールが行われる。

  • AUTHCALLでは、authorizedの値を変更したりリセットしてはいけず、AUTH命令によってのみ設定される。

AUTH命令によって設定された署名を使用して、コントラクトアドレスから処理を実行します。

以下の例で、「ユーザーがEOAを使ってコントラクトAに権限を委任し、コントラクトAがその権限を使って別のコントラクトBを呼び出す」フローを見ていきます。

  • ユーザーは、自分のEOA(0xUSER)から、以下のトランザクションAを送信。

    • コントラクトA(0xCONTRACT_A)を指定してAUTH命令を呼び出します。

    • AUTH命令の引数として、ユーザーは自分のEOAのアドレス(0xUSER)と、コントラクトAを呼び出すための情報(コントラクトアドレス、関数シグネチャ、パラメータなど)に署名したデータを提供。

  • AUTH命令を実行。

    • AUTH命令は、ユーザーの署名を検証し、署名が有効であることを確認。

    • 署名が有効な場合、AUTH命令はコントラクトAの現在の実行コンテキストのauthorized変数に0xUSERを設定。

  • 同じトランザクションA内で、コントラクトAはAUTHCALL命令を実行。

    • AUTHCALL命令は、現在の実行コンテキストのauthorized変数(0xUSER)を使って、コントラクトB(0xCONTRACT_B)の関数を呼び出す。

    • この呼び出しは、0xUSERのアドレスから行われたものとして扱われる。

  • コントラクトBは、コントラクトAからの呼び出しを受け取る。

    • コントラクトBは、呼び出し元のアドレス(msg.sender)が0xUSERであることを確認。

    • コントラクトBは、0xUSERが所有するERC20トークンを別のアドレスに送付するなどの処理を実行。

  • コントラクトBの実行が完了し、制御がコントラクトAに戻る。

  • コントラクトAの実行が完了し、トランザクションAの実行が終了。

    • トランザクションAの結果がブロードキャストされ、新しいブロックに取り込まれる。

    • コントラクトAの実行コンテキストは破棄され、authorized変数の値が未設定になる。

この例からも分かるように、毎回AUTH命令とAUTHCALL命令をセットで実行する必要があります。一度設定したらOKというわけではないため注意が必要です。

以下の図のフローなども理解しやすいです。

https://www.odaily.news/ja/post/5194511
https://www.odaily.news/ja/post/5194511

補足

この提案では以下のような細かい仕組みを実装しています。

AUTH命令のコントラクトからの署名

署名のフォーマットは動的なメモリ範囲を受け取るため、ECDSA署名以外の署名方式をサポートして、コントラクトからの署名データを受け取れる余地を残しています。

AUTH命令の引数でコントラクトアドレスを受け取る

AUTH命令の引数にauthorityという値を受け取ることで、将来コントラクトをこの値に設定できる余地を残しています。

コントラクト呼び出し時のガス代の制限

以下のEIP150と同じ理由で、コントラクトを呼び出す時(サブコール)のガス代をコントラクトが持つ残高の1/64しか渡せないようにします。

https://qiita.com/cardene/items/38750e8f3708f86bbb7a#%E8%A3%9C%E8%B6%B3

これによりガスの枯渇の防止や無限ループを防ぐことができます。

authorizedの未設定エラー

AUTHCALL実行時にauthorizedに何も設定されていない場合は、即座に実行フレームを終了させることで安全性を保ちます。

スポンサードトランザクション実現のための新しいメカニズムの導入

スポンサードトランザクションの実装には以下の2つのアプローチがあり、複数の理由から後者を選択しています。

  • 新しいトランザクションタイプの導入

    • クライアントの大幅のな変更が必要。

    • アップグレードが難しい。

    • Account Abstractionと互換性がない。

  • 新しいメカニズムの導入(AUTH、AUTHCALL)

    • EOAの権限を委任できる。

    • Account Abstractionとの互換性が高い。

    • 既存のウォレットやツールの変更がほとんど不要。

invokerコントラクトへの信頼

実行を委任するコントラクトを信頼する必要があり、以下の情報を署名データに含むことでリプレイ攻撃を防ぎ安全にスポンサードトランザクションを実行できるようにしています。

  • チェーンID(chainId

  • トランザクション数(nonce

  • 委任するコントラクトアドレス(invoker

    • コントラクトを指定することで他のコントラクトでの署名の使い回しを防ぐ。

  • 任意のデータ(commit

commit値

署名データ内のcommit値部分には、invoker(委任する)コントラクトに実行してもらいたい内容を含んでいます。具体的にcommit値には、操作内容を表すデータのハッシュ値が格納されています。実行時には、同じ署名アルゴリズムでハッシュ値を計算し、同じであれば実行します。

以下のような処理をcommit値にしているすることも可能です。

  • 複数のnonceを管理し、複数の操作を同時に実行。

    • 以下のようにnonceをうまく管理し、トランザクション内で送金と投票の処理を並行に走らせることができる。

      • 送金(送金用のnonce = 0

      • 投票(投票用のnonce = 0

      • 送金(送金用のnonce = 1

      • 投票(投票用のnonce = 1

      • 投票(投票用のnonce = 2

  • 複数の操作を1つの署名にまとめる(ERC20のapprovetransferを1つのトランザクションで実行)。

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3074.md
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3074.md
  • EOAの制御権を他の任意の鍵に委任。

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3074.md
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3074.md

invokerコントラクトの選択

invoker(委任する)コントラクトを選ぶとき、レビュー・テストがされておりコミュニティで安全と認められているものを選ぶ必要があります。また、アップグレードできてはいけません。ユーザーが気付かぬうちに実装内容が変わってしまうため安全ではありません。

トランザクション実行中にEOA残高を変更

トランザクション実行中にEOAの残高を変更することは「トランザクションプールに入っているトランザクションの有効性は、そのトランザクションが実際に実行されるまで変化しない」という理由から問題と考えていました。これはセキュリティの観点から問題視されていましたが、EIP3074の導入前の現在においても同じような攻撃が可能なため「やってもやんなくても特に変わらんじゃん」ということで容認されています。

具体的な攻撃方法は以下になります。

  1. 多数のアカウントを用意し、それぞれのアカウントから多数のトランザクションを送信。

  2. これらのトランザクションは、トランザクションプールにたまる。

  3. 攻撃者は、これらのアカウントの残高を全て別のアカウントに移動するトランザクションを含むブロックを作成。

  4. このブロックが承認されると、トランザクションプールに滞留していたトランザクションは全て無効になる。

tx.originを署名者として許可

複数の処理(例:ERC20のapprovetrasnfer)を1つのトランザクションで実行するために、tx.originによる署名を許可しています。

  • tx.origin

    • トランザクションを最初に実行したアドレス(EOA)。

  • msg.sender

    • コントラクトなどを呼び出しているアドレス。

ただし、msg.sender == tx.originというチェックをしているコントラクトには以下のような影響を与える可能性はあります。

  • EOAであるかどうかの確認。

  • 同一トランザクション内の前後でデータを変更して攻撃する(アトミックサンドイッチ攻撃)の防止。

セキュリティ

セキュリティ観点で気をつけるべきことがいくつかあります。

invokerコントラクトの注意点

invoker(委任する)コントラクトでは以下を確認する必要があります。

  • リプレイ攻撃保護の実装。

    • 署名の再利用を防ぐ。

  • valuecommit値に含める。

    • 好きなだけETHを抜かれてしまう可能性がある。

  • gascommit値に含める。

    • スポンサーに多大なガス負担が及ぶ。

  • addrcalldatacommit値に含める。

    • 任意のコントラクトの任意の関数を呼び出せてしまう。

スポンサードトランザクションの悪用

以下のようにスポンサードトランザクションが故意に失敗させられる場合があります。

  • 承認の無効化

    • 承認されたトランザクションを送信する前に、ユーザーが別のトランザクションを送信して自身のnonce(トランザクションカウンタ)を増やすことで、元々の承認が無効になりトランザクションが失敗し、リレーヤーはガス代を無駄に消費する。

  • 資産の一掃

    • 承認されたトランザクション実行前に、ユーザーが自分のアカウントから関連資産を移動することで、トランザクションが無効になり、リレーヤーがガスを無駄に消費する。

上記の対策としては以下が挙げられます。

  • 保証金の預け入れ

    • リレーヤーサービス実行前に保証金を預け入れ、損害を与えた場合は補償金から補填。

  • 評判システムの実装

    • ユーザーのこれまでの行動をもとに信頼性を評価するシステムの導入。

ユーザーからするとどちらも微妙なので、システムとして対策を実装して欲しいところではあります。

懸念点

もちろん良いことだけでなく懸念点もあります。

例えば、AUTH命令実行時にユーザーが処理の内容をしっかり確認しないと悪意ある処理が実行されてしまう危険性があります。要は権限を委任するコントラクトが本当に安全なのかが重要になるため、ユーザーがそれを見極めるのは非常に難しいと思います。

また、commit値に指定したEOAアドレスから送ることができるETH以上のETHを送ることができないため、複雑な処理や追加のETHを送る処理ができなくなります。

別の署名方式のサポートについても、プロトコルのアップデートが必要になるため時間とコスト、リスクの検討が必要になります。

最も大きいのがEIP3074の実装にはチェーンをハードフォーク(互換性のないチェーンのアップデート)する必要があるため、EVM系の各チェーンでアップデートを行う必要があります。

EIP5003

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

https://qiita.com/cardene/items/e65613d99a2a243c878b

EOAのセキュリティと機能を向上させるために新オペコードである「AUTHUSURP」を導入し、EOAアドレスにコードをデプロイする機能を実装し以下の機能を実行できるようにします。

  • EOAのセキュリティキーのローテーション(交換)

  • トランザクションのバッチ処理

  • スポンサードトランザクション

EIP3074の場合はコントラクトに権限を委任できますが、依然としてEOAの管理が必要なため攻撃リスクが残ってしまいます。

このEIP5003では秘密鍵の完全な権限を放棄し、コントラクトに権限を委譲する仕組みを提案しています。これにより、秘密鍵の漏洩やフィッシングのリスクからユーザーを守ることができます。

また、EIP3607と組み合わせることで、元のEOAアドレスに存在した秘密鍵を無効にすることができます。

https://qiita.com/cardene/items/b7a12a2848d2b2022761

AUTHUSURP

新オペコードである「AUTHUSURP」は以下の入力値と出力値を持ちます。

入力

  • offset

    • 新しくデプロイするコードの開始位置。

  • length

    • デプロイするコードの長さ。

出力

  • address

    • コードがデプロイされたアカウントのアドレス。

AUTHUSURP」は以下の条件のもと処理が実行されます。

  • EIP3074のauthorizedに設定されているアドレス(この場合はEOA)以外の実行は失敗する。

  • nonceのチェックを行わない。

    • コードをデプロイする処理のためnonceの更新は不要。

  • 初期化コード(initcode)はauthorizedに設定しているアドレス(この場合はEOA)から実行される。

    • 初期化コードが実行されるとコードがデプロイされる。

  • コードがブロックチェーンに保存されていない(初期化コードから戻り値がない)、もしくはアドレスがコードを保有していた場合は処理が失敗する。

  • コードはauthorizedに設定されているアドレス(この場合はEOA)からデプロイされる。

  • 0バイトのコントラクトをデプロイしてもアカウントの状態は変更しない。

懸念点

もちろんこちらもセキュリティ上の懸念点は存在します。

ECDSA署名

トランザクションでは元の秘密鍵を使用できなくなりますが、トランザクション外での署名では引き続き秘密鍵の署名が有効になります。例えば、コントラクトでの署名検証やPermitなどのトランザクションを実行する前の検証の部分です。

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

https://chaldene.net/erc20permit

また、ecrecoverという署名検証のコントラクトがありますが、こちを更新し「AUTHUSURP」が実行された後は秘密鍵による署名を無効にする変更を加えることが望ましいです。

秘密鍵を他チェーンで使用

仮にEthereumで秘密鍵を無効にしてもEVM互換の他チェーンでもEIP5003を有効(「AUTHUSURP」の実装)をしていないと、引き続き秘密鍵を使用できてしまい、クロスチェーン取引で資金を移転できてしまいます。

EIP7702

https://qiita.com/cardene/items/13a828c97786aab953e4

この規格は、将来のAccount Abstractionへの移行をスムーズに行うことを視野に入れつつ、EIP3074の機能を実装する仕組みを提案しています。

将来のAccount Abstractionへの移行」というのは、EOAアカウントからコントラクトアカウントへ完全に移行することを指しています。

EIP3074の課題点として、コントラクトアカウントへ完全移行した際に、「AUTH」と「AUTHCALL」が使用されなくなり、invokerコントラクトの仕組みも使われなくなるという課題点がありました。そのため、この規格では上記の仕組みを使わずにEIP3074を実現しようとしています。

この提案では以下の4つのパラメータが定義されています。

  • FORK_BLKNUM

    • このEIPが有効になるブロック番号。

  • TX_TYPE

    • 新しいトランザクションタイプを識別する番号。

    • 過去のトランザクションと判別するために使用されます。

  • MAGIC

    • contract_codeの署名を検証する時に使用される定数。

    • 他の署名との混合を防ぎます。

  • PER_CONTRACT_CODE_BASE_COST

    • contract_codeの基本コスト。

contract_codeとは、その名前の通りスマートコントラクトのコードです。EIP7702では、このcontract_codeを使用してEOAアカウントを一時的にコントラクトアカウントにします。このフローを見ていきます。

  • 署名者(signer)の特定

    • MAGICcontract_codeを連結した文字列のKeccakハッシュを計算し、y_parityrs(ECDSAデジタル署名アルゴリズムにおける署名の構成要素)を使ってECDSAの署名検証を実行。

  • 署名者のコントラクトコードの検証

    • 署名者のアドレスに現在設定されているコントラクトコードが空であることを確認。

    • 署名者のアカウントがEOA(外部所有アカウント)であることを保証。

  • 署名者のコントラクトコードの更新

    • 署名者のアドレスのコントラクトコードを、contract_codeに指定されたコードに設定し、署名者のアカウントを一時的にコントラクトアカウントに変換。

トランザクションの実行が完了すると、各署名者のコントラクトコードが空になりEOAアカウントに戻ります。

また、contract_codeに署名した署名者と、トランザクションを実際に送信したアカウント(tx.origin)が異なっていても問題がなく、これにより他のアカウントがガス代を肩代わりできます。

contract_codeには以下の関数が定義されていて、EIP3074の機能を実現しています。

  • verify関数

    • EIP3074の「AUTH」機能で、関数を呼び出したアドレスに権限を付与。

    • 特定の操作(コントラクト実行や資金送付など)の実行権を持つ。

  • execute関数

    • EIP3074の「AUTHCALL」機能で、呼び出し元のアドレスが権限を持っている場合に処理が実行。

この2つの関数は権限管理や実行制御としてコントラクトウォレットでも使用可能です。

課題点

最後にEIP7702の課題点を確認しましょう。

基本的にEIP3074と同じ懸念点として、コントラクトを信用する必要があることが挙げられます。EIP7702だと、contract_codeがまさにそれで、コントラクトコードを信頼する必要があります。

変なコードや脆弱性があると大問題なので、このコードをどのようにして見極めるかが重要です。

EIPの関係性

post image

前章ではAccount Abstractionに関連する様々な提案をまとめてきました。それぞれの提案の関係性を改めて確認しましょう。

EIP86・EIP2938・ERC4337

この3つは「EIP86→EIP2938→ERC4337」と懸念点の改善版やアップデート版というような関係性です。

ERC4337・EIP3074

ERC4337とEIP3074は一見やっていることが同じに見えますが、細かい部分として異なるためそれぞれの特徴をまとめてみましょう。

  • ERC4337

    • ユーザーはコントラクトウォレットを保有。

    • 1つのEOAに対してトランザクションを投げて代わりに実行してもらう。

    • 秘密鍵の管理から解放。

    • ECDSA以外の様々な署名方式のみの使用が可能。

    • 鍵のローテーションが可能。

  • EIP3074

    • ユーザーはEOAアカウントを保有。

    • 特定のコントラクトに対してトランザクションの実行権限を委任。

    • 秘密鍵はEOAが保有しているため、セキュリティのコントロールはEOA側でできる。

    • 秘密鍵を盗まれたら終わりなため、依然として秘密鍵の管理から解放されない。

    • EOAがECDSAを使用している限り、ECDSAの使用を防ぐことができない。

    • 鍵のローテーションを行なっても有効な秘密鍵が残ってしまう。

ユーザーがいきなりコントラクトウォレットに移行するのは難しいですが、EIP3074というステップを踏むことでユーザーがコントラクトウォレットに移行しやすくなると述べている記事もあります。

https://hackmd.io/@matt/note-on-3074

また、コントラクトウォレットがEOAアカウントを制御できるようになるため、ユーザーは2つのアカウントを組み合わせて使用することもできます。

ソーシャルリカバリーと言って、秘密鍵を無くしたり流出した場合に変更することができる仕組みがあります。ERC4337ではこのソーシャルリカバリーを実装でき、EIP3074でも実装できるのですがあらかじめコントラクトにその機能を実装する必要があるのと、事前に別の認証方法を提供する必要があるため、実装のハードルは高いと感じました。

まとめると、この2つの規格は競争関係にあるわけではなくむしろ協力関係であり、コントラクトアカウントへの移行へのアプローチをEIP3074でサポートしていることがわかります。

ERC4337 vs EIP3074

一方、EIP3074についてERC4337の作者(Vitalikも含む)が反発しているようです。

ERC4337側の主張

  • ERC4337RIP7560(こちらは今後まとめますが、めちゃくちゃざっくりまとめるとBundlerとEntryPointがEthereumチェーンにネイティブ実装する提案)の方が「公式」としての最終目標。

https://zenn.dev/sivira_inc/articles/540723258483eb

  • 例えUXの改善が遅れても検閲耐性を優先することの方が重要。

EIP3074側の主張

  • ERC4337ERC7560はAAのゴールの1つであり、他の方法があっても良い。

  • Ethereumはすでに十分な検閲耐性を持ってるから、これ以上UXの改善を遅らせる必要がない。

検証と実行の分離」、「検証フェーズにてストレージにアクセスするルールの設定」などの検閲耐性を実現したい場合は、ERC4337ERC7560が望ましいです。

3つの課題

EIP3074には以下の記事で3つの課題があると述べられています。

https://notes.ethereum.org/@yoav/3074-implications#Potential-centralization-vectors

  • リレーへの集中化

    • EIP3074を使用する場合、リレーなしで使用すると「AUTH」用とトランザクション用で2回署名が必要になり、ほとんどのケースでリレーが使用される。

    • Permissioned Mempool(トランザクションをメモリープールに追加する前に、特定の条件や基準を満たす必要がある)の使用ではなく、Permissioneless Mempool(トランザクションをメモリープールに追加する前に、特定の条件や基準を満たす必要がない)を使用するinvokerコントラクトの実装は簡単ではない。

      • トランザクションの検証に多くの計算リソースが必要なのと、コントラクトからチェーンの状態を読み取ることができないため、チェーンの状態が変更するとトランザクションが無効になる可能性がある。

      • また、トランザクションが失敗するとガス代も一部持ってかれるため、Dos攻撃に弱い。

  • ウォレットによるコントロール

    • ユーザー保護のために、信頼できるコントラクトのみ委任できるようにウォレット側でホワイトリスト機能(許可したアドレスのみ特定の処理が実行される)の導入が考えれる。

    • これによりウォレットによって実装できる機能がコントロールされてしまう。

  • 検閲耐性よりUX

    • EIP3074の実装の優先により、より緊急性が高いEIP7547の実装が後回しなった。

    • EIP7547という、トランザクションを通すときにビルダーではなくプロポーザーによって決定されるようにして、トランザクションのセットを強制的に含めるようにする「Inclusion lists」という仕組みを提案している規格。

    • EIP7547は、EOAに適している仕組みなため、EIP3074よりも先に実装しておくべき。

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

https://twitter.com/decentrek/status/1784919543036719251

課題への対応策

以下の記事では「リレーへの集中化」と「ウォレットによるコントロール」についての対応策が述べられています。

https://docs.zerodev.app/blog/3074-pitfalls

  • EIP3074では検証と実行を分離することで「リレーへの集中化」を解決する。

    • EIP3074で委任するコントラクトで、検証と実行の関数を分離することで実現。

  • Metamaskでは、セキュリティ上重要な処理をするMetamaskカーネルと、セキュリティ上そこまで問題ではないタスクを処理する「モジュール」があるため、「モジュール」の方にガスの支払いなどの機能を入れることで、セキュリティを考慮した上で任意の処理をウォレットから走らせることが可能になる。

    • このモジュール式のinvokerをホワイトリストに登録してもらう必要がある。

    • あと思うのは、Metamask以外のウォレットでうまくこの機能を使えるのか。

3つ目のEIP7547については、EIP3074を採用する場合設計の見直しが必要になるという状態です。例えばEIP3074を使用して、1つのトランザクションセットがアドレス内のガス代を全て使い果たししまうと、同じブロック内の同じアドレスからの他のトランザクションが無効になります。これにより「Inclusion lists」の仕組みが成り立たなくなります。

これは、CR(Consensus Robustness:コンセンサスの堅牢性、EIP7547)とUX(EIP3074)のどちらがより重要視されているかという話にもつながります。

CRはMEVの問題に関連する部分ですが、緊急の課題ではありません。一方、UXの方もトランザクションをより簡単に行えますが、こちらも緊急ではありません。個人的にも参考にしている記事でも意見は同じで、これはどちらかではなくバランスだと思っています。

前章で述べているように、自分としてはEIP3074もAccount Abstractionへのゴールには必要な道だと考えています。やはり、既存ウォレットをコントラクトアカウントに移行するステップは必要だからです。かといって、ERC4337のルートの方が主要になるとも思うので、その時「AUTH」と「AUTHCALL」があまり使われなくなる、もしくは不要になる場合も考えられます。この部分を解決しようとしているEIP7702には非常に期待しているところです。

また、この章は以下の記事を参考にまとめています。この記事の最後の方で述べられているように、Ethereumのコア開発者の中でも意見が割れているのは分散性の観点から良いことだと思います。

https://twitter.com/decentrek/status/1787700637914021892

https://docs.zerodev.app/blog/4337-and-3074-disagreements

EIP3074・EIP5003

この2つの規格は組み合わせることでパワーアップするというイメージです。

EIP3074はEOAアドレスの制御権をコントラクトアドレスに委任する仕組みを提案しています。

一方、EIP5003はEOAアドレスをコントラクトアドレスに変換して、秘密鍵を無効にする提案をしています。しかもEIP3074の機能を一部使用しています。

これによりERC4337とほとんど同じ機能の実装が可能になります。

ただ、コントラクトアカウントへの移行後以下が課題になります。

  • コントラクトへの変換後、トランザクションを起こすために別のEOAが必要になりUXが低下する。

  • トランザクションの送信を中継するリレーを使用する方法もあるが、DoS攻撃や妨害のリスクがあるため、特定のユーザーやグループのみのアクセスなどの許可制を採用する必要があるが、「分散化」、「検閲体制」を損なう。

移行するコントラクトの規格があればこの部分は解決するため、上記を改善する規格が必要ですね。

ERC4337とEIP3074・EIP5003

EIP3074とEIP5003を組み合わせることでほとんどERC4337と同じ機能の実装ができるのであれば、ERC4337でいいじゃんと思うかもしれません。

これはアプローチの違いであり、まずEIP3074でEOAからコントラクトに権限を委任し、EIP5003でコントラクトアカウントに変換します。これにより既存アカウントをコントラクトをアカウントに変換できます。

一方、ERC4337は初めからコントラクトウォレットを生成するため、既存ユーザーの移行ハードルが高いのが現状です。

より既存ユーザーがコントラクトウォレットの世界に移行するにはEIP3074とEIP5003のアプローチの方が自然な移行になるということです。

EIP7702とEIP3074・EIP5003

まず、EIP7702とEIP3074はほとんど同じ規格です。そのため、EIP7702にEIP5003を実装することは可能です。

ただ、異なる点としてはEIP7702の方がEIP5003の実行がしやすいという点です。なぜなら、EIP7702では一時的にEOAアカウントをコントラクトに切り替えるため、この切り替えを一時的ではなく永続化すれば良いためです。

何らかのフラグを持っていて、そのフラグをずっと「ON」にしておけばずっとコントラクトウォレットでいるということは簡単に実装できそうです。

最後に

今回はアカウント抽象化に関連する規格の解説とその関係性についてまとめてきました。

だいぶ内容としては重いですが読んでいただきありがとうございます。

「ここ違くね?」、「ここがわからないです」などあれば以下のTwitterのDMなどから連絡ください!

また、まだ他に関連規格はあるので今後追記していきます。

今後も技術的な部分を中心にわかりやすくまとめていこうと思います。

他の媒体でも情報発信しているのでよければ見ていってください!

https://twitter.com/cardene777

https://qiita.com/cardene

https://zenn.dev/heku

https://chaldene.net/

https://cardene.substack.com/

参考記事

https://zenn.dev/sivira_inc/articles/d041f1ac44ca1e?redirected=1

https://www.coinlive.com/ja/news/can-eip-3074-make-ethereum-simple-again

https://www.linkedin.com/pulse/understanding-eip-3074-paradigm-shift-u8wyc?trk=public_post_main-feed-card_reshare_feed-article-content

https://hackmd.io/@matt/note-on-3074

https://www.odaily.news/ja/post/5194511

https://www.dynamic.xyz/blog/eip-3074-is-this-the-end-of-account-abstraction

https://blog.ambire.com/eip-3074-explained/

https://twitter.com/lightclients/status/1778823652584120497

https://smartcontract.tips/articoli/ethereums-wallet-evolution-comparing-eip-3074-and-erc-4337/

https://safe.global/blog/eip-3074-risks-opportunities-for-smart-account-adoption

https://www.gate.io/learn/articles/erc-4337-vs-eip-3074/1481

https://twitter.com/VitalikButerin/status/1576199517434949634

https://www.alchemy.com/blog/account-abstraction