ERC4337やEIP3074などのアカウント抽象化に関連する規格の解説とその関係性についてまとめている記事です。
初めまして。
普段ブロックチェーン関連の開発や発信をしているかるでねと申します。
今回はアカウント抽象化に関連する規格の解説とその関係性についてまとめていきます。
まずは**アカウント抽象化(Account Abstraction)**が何かについて確認してきましょう。
Ethereumにおいてアカウントと呼ばれるものには以下の2つがあります。
EOAアカウント
コントラクトアカウント
EOAアカウントは秘密鍵を保有していてトランザクションを起こすことができます。
Metamaskなどのウォレットで管理している1つ1つのアドレスが、まさにEOAアカウントです。
一方、コントラクトアカウントは秘密鍵を持たないがコードを保有しています。
スマートコントラクトがこのコントラクトアカウントに該当します。
アカウント抽象化とは、ざっくりいってしまうと「EOAアカウントとコントラクトの差をなくす」ということです。
これにより以下のようなメリットがあります。
ユーザーにウォレットを意識させずに作成から使用まで可能。
秘密鍵を持たないため不正な送金をコントラクトレベルでブロックできる。
任意の署名方式を使用できる。
署名に使用する鍵をユーザーが無くしたり流出した際に再作成可能。
署名に加えて指紋認証などを組み合わせて多要素認証が可能。
一定の期間や一定の資金までは署名不要でトランザクションを起こせる。
より詳しくは以下の記事にまとめています。
まずはAccount Abstractionに関する提案を一通り見ていきます。
当時(現在)のEthereumでは、ECDSAにより署名検証とnonce
によりリプレイ攻撃防止機能が直接プロトコルに組み込まれています。
nonceはそのアドレスから実行されたトランザクションの総数です。nonceがあることで同じトランザクションを2度通すことを防いでいます。
この規格では、上記2つの機能をコントラクトに委ねることを提案しています。これにより、ユーザーが柔軟にセキュリティモデルを定義でき、以下のことを可能にします。
通常であれば無効なトランザクション署名を有効とみなす。(r = s = 0
, v = CHAIN_ID
)
トランザクション内のガス代・nonce
・送金額を0
とし、送信アドレスも特殊な値である(NULL_SENDER
)にする。
CREATE2
(別提案で導入済み)というコントラクトデプロイ前にアドレスを決定できるオペコードを追加する。
コントラクト作成時、既にアドレスが存在する場合に作成を失敗させる。
これにより以下のようなメリットもあります。
コントラクトに全員の署名を含んだトランザクションを1つ送るだけで済むため、マルチシグウォレットの実装が簡素化。
資金を引き出す時、引き出す資金の一部をガス代に使用可能。
任意の署名方式を使用できる。
有効期限付きのトランザクションなどいろいろカスタム可能。
また、マイナー(当時)がどのようにしてトランザクションを受け入れるかについてが課題であり、以下のように提案されています。
Serpent(スマートコントラクトをかけるプログラミング言語)で書かれたコードで、ユーザー固有の公開鍵ハッシュが埋め込まれているか。
トランザクションデータ内の署名の検証が可能か。
トランザクションデータ内とアカウント内のガス代チェック。
アカウントのnonce
がトランザクションデータ内のnonce
と一致する。
正直どれもイマイチな気がしています。プロトコルレベルでの変更も必要であり、時間・コスト・リスクが伴うのも課題です。
新しいオペコードであるPAYGAS
を提案していて、コントラクトが支払い可能なガス価格とガスリミットを指定し、トランザクションの有効性を示すことができます。
この提案では、アカウント抽象化を以下の2つの層に分けて提案していて、先にシングルテナントAAから実装しようとしています。
シングルテナントAA
マルチテナントAA
この2つのAAを実装することで以下のようなことが実現できます。
様々な署名技術の使用。
マルチシグ検証やソーシャルリカバリー機能を含むコントラクトウォレット。
tornado.cashのようなプライバシー保護システム。
条件を満たさないトランザクションを無効にすることによるガス効率化。
ガス代をETH以外で支払い可能。
AA_TX_TYPE
というトランザクションタイプ(AAトランザクション)を導入し、以下の変更が実装されます。
ベースガスコストは、実行されるコントラクトのコードの複雑さで決定されるAA_BASE_GAS_COST
に設定。
nonce
の処理は既存のトランザクションと同様。
ガスリミットがなく、新オペコードであるPAYGAS
で調整。
トランザクション全体のグローバル変数である、transaction_fee_paid
、gas_price
、gas_limit
を導入し、AA_TX_TYPE
の場合とそうでない場合でガス代の処理を分ける。
PAYGAS
では、指定されたメモリからガス価格とガスリミットを取り出し、条件を満たす場合(アカウントに残高が十分にある、手数料未払い、トランザクションを送ったコントラクトからの実行か)、コントラクトの残高からガス価格 * ガスリミット
を引く。
gas_price
とgas_limit
をメモリから読み取った値に更新。以下の方法によりリプレイ攻撃保護。
SELFDESTRUCT
を無効にするオペコード(SET_INDESTRUCTIBLE
**)**が設定されている。
SELFDESTRUCT
を呼び出した時、nonce
を0
にせず保存して再度実行を防ぐ。
以下の3つのオペコードの処理を更新。
CALLER
とORIGIN
はAA_ENTRY_POINT
を返し、コントラクトアドレスからのトランザクションとわかるようにする。
GASPRICE
は、PAYGAS
オペコードを通して動的に設定される。
ノードでは以下の検証を行いAAトランザクションの有効性をチェックする。
対象コントラクトのコードがAA_PREFIX
(AAトランザクションを受け入れ可能かの値)で始まっている。
トランザクションの実行をシミュレートし以下の条件のチェック。
環境に依存するオペコード(BLOCKHASH
、COINBASE
、TIMESTAMP
など)とBALANCE
オペコードを使用していないか。
外部コントラクトの呼び出しや外部コントラクトのコードを読み取るオペコード(EXTCODESIZE
、EXTCODEHASH
など)の使用をしていない。
ガスの消費量か利用可能量が一定の制限(VERIFICATION_GAS_CAP
)を超えていない。
コントラクトの残高がガス代を支払えるか。
ノードは以下の確認を行いながらメモリープールの管理を行う。
現在の有効なnonce
よりも大きなnonce
のトランザクションを弾く。
同じコントラクト宛の同じnonce
のトランザクションが複数ある場合、ガス価格の高い方を残す。
新しいブロックを処理するとき、メンプール内のトランザクションを全て破棄。
これも先ほど同様、プロトコルレベルの変更が必要になります。
以下の記事たちでわかりやすく・詳しく解説しています。ERC4337はここまでの2つの提案と異なり、プロトコルレベルでの変更が不要になるため、現在(2024年5月)様々な形で実装されています。
ちょっとこの章は長いです。
2つの新しいオペコードである「AUTH
」と「AUTHCALL
」を追加し、コントラクトにEOAアカウントの制御を委任する機能を追加する提案です。
AUTH
authorized
」という変数を設定し、EVMにコントラクトによる制御が認証されたことを知らせます。AUTHCALL
AUTH
」で設定された認証情報をもとに、コントラクトからトランザクションの実行ができます。これにより以下のことが実現できます。
ガス代をERC20トークンで支払う。
ガス代を別のアカウントに支払ってもらう「スポンサードトランザクション」が可能。
EOAを使用しつつ、上記の「スポンサードトランザクション」やトランザクションのバッチ処理などが実行できUXが向上。
スポンサードトランザクションについては以下の記事を参考にしてください。
authorized
という変数は実行フレームの中(コントラクトの一連の命令が実行される期間)のみ有効です。仮にこのコントラクトが別のコントラクトを呼び出すと新しい実行フレームが作成され、authorized
は初期日リセットされます。このように各実行フレームが独自のauthorized
変数をもち、同じコントラクト内の別の部分の実行であっても引き継がれないことで安全性を保っています。
以下の入力値とメモリ内の署名データ、出力値を持ちます。
authority
offset
length
r
、s
、commit
からなるそれぞれ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変数に直接コントラクトアドレスを指定することはできず、生成される署名データ内に実行権限を委任するコントラクトアドレスを指定できるようになっています。
以下の入力値と出力値を持ちます。
gas
addr
value
argsOffset
argsLength
retOffset
retLength
success
bool
値。基本的にCALL
と同じように動作します。
authorized
変数が未設定の場合は実行が無効になり、設定されている場合はコールの呼び出しもとアドレスがauthorized
の値に設定されます。
CALL
時には実行フレームが変わってしまうため、改めて設定しています。サブコールに必要なガスがgas
よりも少ない場合は実行が無効。
value
が残っていてもガス代の補助には使用できない。
value
はauthorized
の残高から差し引かれ、足りない場合は実行が無効。
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というわけではないため注意が必要です。
以下の図のフローなども理解しやすいです。
この提案では以下のような細かい仕組みを実装しています。
署名のフォーマットは動的なメモリ範囲を受け取るため、ECDSA署名以外の署名方式をサポートして、コントラクトからの署名データを受け取れる余地を残しています。
AUTH
命令の引数にauthority
という値を受け取ることで、将来コントラクトをこの値に設定できる余地を残しています。
以下のEIP150と同じ理由で、コントラクトを呼び出す時(サブコール)のガス代をコントラクトが持つ残高の1/64しか渡せないようにします。
これによりガスの枯渇の防止や無限ループを防ぐことができます。
AUTHCALL
実行時にauthorized
に何も設定されていない場合は、即座に実行フレームを終了させることで安全性を保ちます。
スポンサードトランザクションの実装には以下の2つのアプローチがあり、複数の理由から後者を選択しています。
新しいトランザクションタイプの導入
クライアントの大幅のな変更が必要。
アップグレードが難しい。
Account Abstractionと互換性がない。
新しいメカニズムの導入(AUTH、AUTHCALL)
EOAの権限を委任できる。
Account Abstractionとの互換性が高い。
既存のウォレットやツールの変更がほとんど不要。
実行を委任するコントラクトを信頼する必要があり、以下の情報を署名データに含むことでリプレイ攻撃を防ぎ安全にスポンサードトランザクションを実行できるようにしています。
チェーンID(chainId
)
トランザクション数(nonce
)
委任するコントラクトアドレス(invoker
)
任意のデータ(commit
)
署名データ内のcommit
値部分には、invoker(委任する)コントラクトに実行してもらいたい内容を含んでいます。具体的にcommit
値には、操作内容を表すデータのハッシュ値が格納されています。実行時には、同じ署名アルゴリズムでハッシュ値を計算し、同じであれば実行します。
以下のような処理をcommit
値にしているすることも可能です。
複数のnonce
を管理し、複数の操作を同時に実行。
以下のようにnonceをうまく管理し、トランザクション内で送金と投票の処理を並行に走らせることができる。
送金(送金用のnonce = 0
)
投票(投票用のnonce = 0
)
送金(送金用のnonce = 1
)
投票(投票用のnonce = 1
)
投票(投票用のnonce = 2
)
複数の操作を1つの署名にまとめる(ERC20のapprove
とtransfer
を1つのトランザクションで実行)。
invoker(委任する)コントラクトを選ぶとき、レビュー・テストがされておりコミュニティで安全と認められているものを選ぶ必要があります。また、アップグレードできてはいけません。ユーザーが気付かぬうちに実装内容が変わってしまうため安全ではありません。
トランザクション実行中にEOAの残高を変更することは「トランザクションプールに入っているトランザクションの有効性は、そのトランザクションが実際に実行されるまで変化しない」という理由から問題と考えていました。これはセキュリティの観点から問題視されていましたが、EIP3074の導入前の現在においても同じような攻撃が可能なため「やってもやんなくても特に変わらんじゃん」ということで容認されています。
具体的な攻撃方法は以下になります。
多数のアカウントを用意し、それぞれのアカウントから多数のトランザクションを送信。
これらのトランザクションは、トランザクションプールにたまる。
攻撃者は、これらのアカウントの残高を全て別のアカウントに移動するトランザクションを含むブロックを作成。
このブロックが承認されると、トランザクションプールに滞留していたトランザクションは全て無効になる。
複数の処理(例:ERC20のapprove
とtrasnfer
)を1つのトランザクションで実行するために、tx.originによる署名を許可しています。
tx.origin
- トランザクションを最初に実行したアドレス(EOA)。
msg.sender
- コントラクトなどを呼び出しているアドレス。
ただし、msg.sender == tx.origin
というチェックをしているコントラクトには以下のような影響を与える可能性はあります。
EOAであるかどうかの確認。
同一トランザクション内の前後でデータを変更して攻撃する(アトミックサンドイッチ攻撃)の防止。
セキュリティ観点で気をつけるべきことがいくつかあります。
invoker(委任する)コントラクトでは以下を確認する必要があります。
リプレイ攻撃保護の実装。
value
をcommit
値に含める。
gas
をcommit
値に含める。
addr
とcalldata
をcommit
値に含める。
以下のようにスポンサードトランザクションが故意に失敗させられる場合があります。
承認の無効化
nonce
(トランザクションカウンタ)を増やすことで、元々の承認が無効になりトランザクションが失敗し、リレーヤーはガス代を無駄に消費する。資産の一掃
上記の対策としては以下が挙げられます。
保証金の預け入れ
評判システムの実装
ユーザーからするとどちらも微妙なので、システムとして対策を実装して欲しいところではあります。
もちろん良いことだけでなく懸念点もあります。
例えば、AUTH命令実行時にユーザーが処理の内容をしっかり確認しないと悪意ある処理が実行されてしまう危険性があります。要は権限を委任するコントラクトが本当に安全なのかが重要になるため、ユーザーがそれを見極めるのは非常に難しいと思います。
また、commit
値に指定したEOAアドレスから送ることができるETH以上のETHを送ることができないため、複雑な処理や追加のETHを送る処理ができなくなります。
別の署名方式のサポートについても、プロトコルのアップデートが必要になるため時間とコスト、リスクの検討が必要になります。
最も大きいのがEIP3074の実装にはチェーンをハードフォーク(互換性のないチェーンのアップデート)する必要があるため、EVM系の各チェーンでアップデートを行う必要があります。
EOAのセキュリティと機能を向上させるために新オペコードである「AUTHUSURP
」を導入し、EOAアドレスにコードをデプロイする機能を実装し以下の機能を実行できるようにします。
EOAのセキュリティキーのローテーション(交換)
トランザクションのバッチ処理
スポンサードトランザクション
EIP3074の場合はコントラクトに権限を委任できますが、依然としてEOAの管理が必要なため攻撃リスクが残ってしまいます。
このEIP5003では秘密鍵の完全な権限を放棄し、コントラクトに権限を委譲する仕組みを提案しています。これにより、秘密鍵の漏洩やフィッシングのリスクからユーザーを守ることができます。
また、EIP3607と組み合わせることで、元のEOAアドレスに存在した秘密鍵を無効にすることができます。
新オペコードである「AUTHUSURP
」は以下の入力値と出力値を持ちます。
offset
length
address
「AUTHUSURP
」は以下の条件のもと処理が実行されます。
EIP3074のauthorized
に設定されているアドレス(この場合はEOA)以外の実行は失敗する。
nonce
のチェックを行わない。
初期化コード(initcode
)はauthorized
に設定しているアドレス(この場合はEOA)から実行される。
コードがブロックチェーンに保存されていない(初期化コードから戻り値がない)、もしくはアドレスがコードを保有していた場合は処理が失敗する。
コードはauthorized
に設定されているアドレス(この場合はEOA)からデプロイされる。
0バイトのコントラクトをデプロイしてもアカウントの状態は変更しない。
もちろんこちらもセキュリティ上の懸念点は存在します。
トランザクションでは元の秘密鍵を使用できなくなりますが、トランザクション外での署名では引き続き秘密鍵の署名が有効になります。例えば、コントラクトでの署名検証やPermitなどのトランザクションを実行する前の検証の部分です。
Permitについては以下の記事を参考にしてください。
また、ecrecoverという署名検証のコントラクトがありますが、こちを更新し「AUTHUSURP」が実行された後は秘密鍵による署名を無効にする変更を加えることが望ましいです。
仮にEthereumで秘密鍵を無効にしてもEVM互換の他チェーンでもEIP5003を有効(「AUTHUSURP」の実装)をしていないと、引き続き秘密鍵を使用できてしまい、クロスチェーン取引で資金を移転できてしまいます。
この規格は、将来のAccount Abstractionへの移行をスムーズに行うことを視野に入れつつ、EIP3074の機能を実装する仕組みを提案しています。
「将来のAccount Abstractionへの移行」というのは、EOAアカウントからコントラクトアカウントへ完全に移行することを指しています。
EIP3074の課題点として、コントラクトアカウントへ完全移行した際に、「AUTH
」と「AUTHCALL
」が使用されなくなり、invokerコントラクトの仕組みも使われなくなるという課題点がありました。そのため、この規格では上記の仕組みを使わずにEIP3074を実現しようとしています。
この提案では以下の4つのパラメータが定義されています。
FORK_BLKNUM
TX_TYPE
新しいトランザクションタイプを識別する番号。
過去のトランザクションと判別するために使用されます。
MAGIC
contract_code
の署名を検証する時に使用される定数。
他の署名との混合を防ぎます。
PER_CONTRACT_CODE_BASE_COST
contract_code
の基本コスト。contract_code
とは、その名前の通りスマートコントラクトのコードです。EIP7702では、このcontract_code
を使用してEOAアカウントを一時的にコントラクトアカウントにします。このフローを見ていきます。
署名者(signer)の特定
MAGIC
とcontract_code
を連結した文字列のKeccakハッシュを計算し、y_parity
、r
、s
(ECDSAデジタル署名アルゴリズムにおける署名の構成要素)を使ってECDSAの署名検証を実行。署名者のコントラクトコードの検証
署名者のアドレスに現在設定されているコントラクトコードが空であることを確認。
署名者のアカウントがEOA(外部所有アカウント)であることを保証。
署名者のコントラクトコードの更新
contract_code
に指定されたコードに設定し、署名者のアカウントを一時的にコントラクトアカウントに変換。トランザクションの実行が完了すると、各署名者のコントラクトコードが空になりEOAアカウントに戻ります。
また、contract_code
に署名した署名者と、トランザクションを実際に送信したアカウント(tx.origin
)が異なっていても問題がなく、これにより他のアカウントがガス代を肩代わりできます。
contract_code
には以下の関数が定義されていて、EIP3074の機能を実現しています。
verify
関数
EIP3074の「AUTH
」機能で、関数を呼び出したアドレスに権限を付与。
特定の操作(コントラクト実行や資金送付など)の実行権を持つ。
execute
関数
AUTHCALL
」機能で、呼び出し元のアドレスが権限を持っている場合に処理が実行。この2つの関数は権限管理や実行制御としてコントラクトウォレットでも使用可能です。
最後にEIP7702の課題点を確認しましょう。
基本的にEIP3074と同じ懸念点として、コントラクトを信用する必要があることが挙げられます。EIP7702だと、contract_code
がまさにそれで、コントラクトコードを信頼する必要があります。
変なコードや脆弱性があると大問題なので、このコードをどのようにして見極めるかが重要です。
前章ではAccount Abstractionに関連する様々な提案をまとめてきました。それぞれの提案の関係性を改めて確認しましょう。
この3つは「EIP86→EIP2938→ERC4337」と懸念点の改善版やアップデート版というような関係性です。
ERC4337とEIP3074は一見やっていることが同じに見えますが、細かい部分として異なるためそれぞれの特徴をまとめてみましょう。
ERC4337
ユーザーはコントラクトウォレットを保有。
1つのEOAに対してトランザクションを投げて代わりに実行してもらう。
秘密鍵の管理から解放。
ECDSA以外の様々な署名方式のみの使用が可能。
鍵のローテーションが可能。
EIP3074
ユーザーはEOAアカウントを保有。
特定のコントラクトに対してトランザクションの実行権限を委任。
秘密鍵はEOAが保有しているため、セキュリティのコントロールはEOA側でできる。
秘密鍵を盗まれたら終わりなため、依然として秘密鍵の管理から解放されない。
EOAがECDSAを使用している限り、ECDSAの使用を防ぐことができない。
鍵のローテーションを行なっても有効な秘密鍵が残ってしまう。
ユーザーがいきなりコントラクトウォレットに移行するのは難しいですが、EIP3074というステップを踏むことでユーザーがコントラクトウォレットに移行しやすくなると述べている記事もあります。
また、コントラクトウォレットがEOAアカウントを制御できるようになるため、ユーザーは2つのアカウントを組み合わせて使用することもできます。
ソーシャルリカバリーと言って、秘密鍵を無くしたり流出した場合に変更することができる仕組みがあります。ERC4337ではこのソーシャルリカバリーを実装でき、EIP3074でも実装できるのですがあらかじめコントラクトにその機能を実装する必要があるのと、事前に別の認証方法を提供する必要があるため、実装のハードルは高いと感じました。
まとめると、この2つの規格は競争関係にあるわけではなくむしろ協力関係であり、コントラクトアカウントへの移行へのアプローチをEIP3074でサポートしていることがわかります。
一方、EIP3074についてERC4337の作者(Vitalikも含む)が反発しているようです。
ERC4337、ERC7560はAAのゴールの1つであり、他の方法があっても良い。
Ethereumはすでに十分な検閲耐性を持ってるから、これ以上UXの改善を遅らせる必要がない。
「検証と実行の分離」、「検証フェーズにてストレージにアクセスするルールの設定」などの検閲耐性を実現したい場合は、ERC4337、ERC7560が望ましいです。
EIP3074には以下の記事で3つの課題があると述べられています。
リレーへの集中化
EIP3074を使用する場合、リレーなしで使用すると「AUTH
」用とトランザクション用で2回署名が必要になり、ほとんどのケースでリレーが使用される。
Permissioned Mempool(トランザクションをメモリープールに追加する前に、特定の条件や基準を満たす必要がある)の使用ではなく、Permissioneless Mempool(トランザクションをメモリープールに追加する前に、特定の条件や基準を満たす必要がない)を使用するinvokerコントラクトの実装は簡単ではない。
トランザクションの検証に多くの計算リソースが必要なのと、コントラクトからチェーンの状態を読み取ることができないため、チェーンの状態が変更するとトランザクションが無効になる可能性がある。
また、トランザクションが失敗するとガス代も一部持ってかれるため、Dos攻撃に弱い。
ウォレットによるコントロール
ユーザー保護のために、信頼できるコントラクトのみ委任できるようにウォレット側でホワイトリスト機能(許可したアドレスのみ特定の処理が実行される)の導入が考えれる。
これによりウォレットによって実装できる機能がコントロールされてしまう。
検閲耐性よりUX
EIP3074の実装の優先により、より緊急性が高いEIP7547の実装が後回しなった。
EIP7547という、トランザクションを通すときにビルダーではなくプロポーザーによって決定されるようにして、トランザクションのセットを強制的に含めるようにする「Inclusion lists」という仕組みを提案している規格。
EIP7547は、EOAに適している仕組みなため、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のコア開発者の中でも意見が割れているのは分散性の観点から良いことだと思います。
この2つの規格は組み合わせることでパワーアップするというイメージです。
EIP3074はEOAアドレスの制御権をコントラクトアドレスに委任する仕組みを提案しています。
一方、EIP5003はEOAアドレスをコントラクトアドレスに変換して、秘密鍵を無効にする提案をしています。しかもEIP3074の機能を一部使用しています。
これによりERC4337とほとんど同じ機能の実装が可能になります。
ただ、コントラクトアカウントへの移行後以下が課題になります。
コントラクトへの変換後、トランザクションを起こすために別のEOAが必要になりUXが低下する。
トランザクションの送信を中継するリレーを使用する方法もあるが、DoS攻撃や妨害のリスクがあるため、特定のユーザーやグループのみのアクセスなどの許可制を採用する必要があるが、「分散化」、「検閲体制」を損なう。
移行するコントラクトの規格があればこの部分は解決するため、上記を改善する規格が必要ですね。
EIP3074とEIP5003を組み合わせることでほとんどERC4337と同じ機能の実装ができるのであれば、ERC4337でいいじゃんと思うかもしれません。
これはアプローチの違いであり、まずEIP3074でEOAからコントラクトに権限を委任し、EIP5003でコントラクトアカウントに変換します。これにより既存アカウントをコントラクトをアカウントに変換できます。
一方、ERC4337は初めからコントラクトウォレットを生成するため、既存ユーザーの移行ハードルが高いのが現状です。
より既存ユーザーがコントラクトウォレットの世界に移行するにはEIP3074とEIP5003のアプローチの方が自然な移行になるということです。
まず、EIP7702とEIP3074はほとんど同じ規格です。そのため、EIP7702にEIP5003を実装することは可能です。
ただ、異なる点としてはEIP7702の方がEIP5003の実行がしやすいという点です。なぜなら、EIP7702では一時的にEOAアカウントをコントラクトに切り替えるため、この切り替えを一時的ではなく永続化すれば良いためです。
何らかのフラグを持っていて、そのフラグをずっと「ON」にしておけばずっとコントラクトウォレットでいるということは簡単に実装できそうです。
今回はアカウント抽象化に関連する規格の解説とその関係性についてまとめてきました。
だいぶ内容としては重いですが読んでいただきありがとうございます。
「ここ違くね?」、「ここがわからないです」などあれば以下のTwitterのDMなどから連絡ください!
また、まだ他に関連規格はあるので今後追記していきます。
今後も技術的な部分を中心にわかりやすくまとめていこうと思います。
他の媒体でも情報発信しているのでよければ見ていってください!