👛

AA(Account Abstraction)の先にある、コントラクトウォレット中心の世界

2021/12/20に公開約17,900字

SIVIRA Inc. 執行役員兼エンジニアの m0t0k1ch1 です。Ethereum Advent Calendar 2021 の 20 日目の記事として、本記事を書かせていただきます。

関連資料(2022.07.13, 2022.08.30 追記)

2022.07.12 に開催された TOKYO BLOCKCHAIN TECH MEETUP 2022 にて、本記事の導入に相当するお話をさせていただきました。

本記事の内容が難しいと感じた方はまず上記をご覧いただくのがよいかと思います。

はじめに

今年は、EIP4337 の登場によって Ethereum の Account Abstraction(以降、AA と表記することとします)が大きく進展し、その実現がようやく現実味を帯び始めた年となりました。

詳細は後述しますが、AA は Ethereum デベロッパーの長年の夢であり、Vitalik が 2016 年頃からその実現に向けて取り組んできたトピックでもあります(EIP4337 含め、以下で紹介する EIP の 1st author は全て Vitalik です)。

timelinehttps://etherworld.co/2021/10/06/an-overview-of-account-abstraction-in-ethereum-blockchain より引用

もちろん、AA はユーザーにとっても重要なトピックです。AA の進行によって、ユーザーのプライマリアカウントは EOA からコントラクトアカウントへと移行していくことになりますが、ユーザーにとって、これは特にセキュリティ面で大きな恩恵があります。

また、今後、コントラクトウォレットは EIP4337 への準拠を求められるようになっていくと考えられるため、unWallet というコントラクトウォレットを開発している弊社にとっても、AA は重要なトピックです。

そこで本記事では、AA の歴史を振り返りながら、その最新提案である EIP4337 によって Ethereum アカウントの常識がどのように変わろうとしているのかについて整理していきたいと思います。

要約(2021.12.22 追記)

以下ツイートに続くリプツリーをご参照ください🙏

Account Abstraction とは?

まず、そもそも AA とは何なのでしょうか?

EIP2938 では以下のように表現されています。

Account abstraction (AA) allows a contract to be the top-level account that pays fees and starts transaction execution.

そのまま翻訳すると、AA は「コントラクトを最上位アカウント(トランザクションの起点となり、その手数料を支払うことができるアカウント)にすること」 となります。これは「EOA とコントラクトアカウントの差をなくすこと」と言い換えることもできるでしょう。

なお、周知の事実ではありますが、現在 Ethereum においてトランザクションの起点となれるのは EOA のみです。なぜなら、「トランザクションは、EOA による ECDSA 電子署名を基準にした検証をパスしないとブロックに含めることができない」ということがプロトコルとして規定されているからです。ゆえに、例えばコントラクトウォレットを有していたとしても、それを利用するためには EOA が必須となりますし、EOA を介すことで gas も余分に消費されてしまいます。このような文脈を考慮すると、AA は「プロトコルで規定されたトランザクション検証処理を抽象化し、EOA なしでコントラクトウォレットを利用できるようにすること」とも言えるでしょう。

では、なぜ AA が必要なのでしょうか?

その理由はいくつかありますが、ウォレット(EIP2938 で言うところの single-tenant AA や EIP4337)の文脈で AA を考えた場合、最も重要な理由は「よりセキュアなトランザクション検証処理(を有するウォレット)を実装できるようにするため」だと考えられます。より端的に「Account Security Abstraction を実現するため」と表現することもできるでしょう。

Vitalik が こちらの記事 で以下のように述べていることからも分かる通り、現在、ブロックチェーン領域において、ウォレットのセキュリティ向上は非常に重要な課題となっています。

One analysis of the Bitcoin ecosystem suggests that 1500 BTC may be lost every day - over ten times more than what Bitcoin users spend on transaction fees, and over the years adding up to as much as 20% of the total supply. The stories and the numbers alike point to the same inescapable truth: the importance of the wallet security problem is great, and it should not be underestimated.

念のため強調しておきますが、これは決して他人事ではありません。例えば、Twitter で "MetaMask" という単語を含んだツイートをしてみるとよいでしょう。高確率で秘密鍵泥棒からのリプライが飛んできます。あなたの資産も狙われているのです。

このような状況を考慮すると尚更、EOA に依存した資産管理は安全とは言い難いでしょう。基本的には、秘密鍵を紛失したり盗難されたりしたら、その時点でゲームオーバーです。しかし、「EOA はユーザーに対応し、コントラクトアカウントはアプリケーションに対応する」という想定で設計されてしまった Ethereum プロトコルの上で、多くのユーザーが EOA を利用するのは当然のことです。厳しい言い方をすれば、当初の想定がアマかったのです。

また、前述した通り、EOA ベースの世界では ECDSA 電子署名が必須となってしまいますが、セキュリティの観点だと、今後、その代わりにポスト量子暗号ベースの電子署名アルゴリズムなどを採用できることが望ましいでしょう。

ウォレットの文脈における AA は、これらの課題を解決するため、前述したような EOA 縛りを解放し、よりセキュアな資産管理が可能な世界への扉を開こうとしているのです。

なお、そのような世界で活用されるだろう技術の一例として、上でも引用した Vitalik の記事では、ソーシャルリカバリー という、コントラクトウォレットならではのアカウント保護技術が紹介されています。EOA ベースのハードウェアウォレットなどと比較した場合の利点についても整理されていますので、一読を強くオススメします。

https://vitalik.ca/general/2021/01/11/recovery.html

...それでは、ここまでの話を頭の片隅に置きながら、EIP4337 に至るまでの AA の歴史を振り返ってみましょう。これは、EIP4337 の理解を深めるためにも重要なプロセスです。

EIP86

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

前述したような背景を踏まえて 2017.02.20 に提案された、AA の始まりとも言える EIP です。

以下のような新しいトランザクション形式と新しい opcode を導入し、プロトコルの代わりにコントラクトがトランザクション検証処理(電子署名や nonce の検証)や手数料徴収処理を担えるようにすることで、AA を実現しようとしました。

が、現在のステータスは Stagnant となっています。

新しいトランザクション形式

  • 電子署名が (CHAIN_ID, 0, 0)(すなわち、r = s = 0 v = CHAIN_ID)の場合、それを許容し、送信者を NULL_SENDER2**160 - 1)とみなす
    • NULL_SENDERnonce はインクリメントしない
  • gasPrice = 0 nonce = 0 value = 0 でなければならない

上記仕様を見れば分かる通り、この形式のトランザクションは EOA なしで作成することができます。トランザクションがこの形式だった場合、プロトコルはトランザクション検証処理や手数料徴収処理を行わず、それをコントラクトに任せます。

新しい opcode:CREATE20xfb

  • value salt memStart memSize を引数とし、コントラクトアカウントの生成を行う
  • これによって生成されるコントラクトアカウントのアドレスは sha3(sender + salt + sha3(initCode)) とする

既存のアドレス決定は nonce に依存したロジックとなっているのですが、新しいトランザクション形式では常に nonce = 0 となってしまうため、nonce に依存しない新しいアドレス決定ロジックが必要となったのでしょう。

なお、CREATE2EIP1014 として改めて提案され、既に導入されています。

課題

非常にシンプルな提案に見えますが、まず大前提として、これを実装するためにはプロトコルレイヤーの改修が必要(EIP のカテゴリは Core)であり、これには大きなコストとリスクが伴います。

また、AA を実現する際に非常に重要となるマイニング戦略の問題(マイナーがブロックに含めるトランザクションをどのように判断するのか、すなわち、トランザクションの正当性をどのように再定義するのか、という問題)について、現実的なソリューションがないように見えます。これを怠ってしまうと、例えば EIP86(トランザクションは gasPrice = 0 nonce = 0)を前提とした場合、マイナーは手数料を支払わないトランザクションや何の効果もないトランザクションを受け入れてしまう可能性があります。また、トランザクション検証処理の負荷が大きい場合、DoS 攻撃ベクトルが生じてしまいます。

EIP86 の中では、トランザクション検証処理や手数料徴収処理を実行する標準的なコードを定義し、マイナーはコントラクトアカウントがそれを有していることを正規表現によって確認、それに沿って検証処理を行うようにする、といった方法が提案されてはいますが、プロトコル改修に加え、デベロッパー・マイナー双方と足並みを揃えながら標準規格策定や実装を進めるのは簡単なことではないでしょう。

EIP2938

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

EIP86 の提案から約 3 年半後の 2020.09.04 に提案されました。目指すところは EIP86 とほぼ同様ですが、EIP2718 を考慮した、より具体的な提案となっています。

が、EIP86 同様、現在のステータスは Stagnant です。

EIP2938 では、AA を以下のように分類した上で、まずは前者から始めましょう、という方針がとられています。

  • single-tenant AA
    • ごく少数のユーザーによる単一アカウントのステート更新のみを扱えばよいコントラクト(コントラクトウォレットなど)の AA
  • multi-tenant AA
    • 大量のユーザーによる単一アカウントのステート更新を扱わなければならないコントラクト(DEX など)の AA

後者は前者に比べて要件が複雑であり、一括りにして実現を考えることは難しいと考えられるため、これは適切な判断でしょう。この方針は、EIP4337 という現実的な着地にも繋がっているように思います。

以下、特に重要なポイントに絞って、提案の内容を説明します。

新しいトランザクション形式:AA_TX_TYPE2

  • EIP2718 に準拠する
  • ペイロードは rlp([nonce, target, data]) とする
    • valuegasPrice gasLimit、電子署名(v r s)を有さない
    • nonce は既存のトランザクションと同様に処理される
    • gasLimit は、単純にブロック内の残 gas(既にブロックに含められたトランザクションが消費した gas を block.gasLimit から差し引いたもの)に設定される
    • 以下で説明する PAYGAS によって gasLimit を引き下げることが可能
  • 基本 gas コストは AA_BASE_GAS_COST15,000) とする
    • 電子署名の検証が必須ではないため、現行の 21,000 より少ない

single-tenant AA を念頭に置いているため、EIP86 と比べると nonce に関する方針が変わっていますが、大方針は変わっていません。

以降、この形式のトランザクションを AA トランザクション と呼ぶこととします。

新しい(トランザクション単位での)グローバル変数

変数名 初期値
globals.transactionFeePaid bool false if type(tx) == AA_TX_TYPE else true
globals.gasPrice int 0 if type(tx) == AA_TX_TYPE else tx.gasPrice
globals.gasLimit int 0 if type(tx) == AA_TX_TYPE else tx.gasLimit

以下で説明する PAYGAS の実行以降、従来形式のトランザクションと AA トランザクションを同様に扱えるようにするため、これらの変数が導入されたと考えられます。

新しい opcode:PAYGAS0x49

  • versionNumbermemoryStart を引数にとる
  • memoryStart で指定されたメモリ領域から gasPricegasLimit を読み出す
  • 以下の条件が全て満たされていることを確認する
    • アカウントの残高が gasPrice * gasLimit 以上である
    • globals.transactionFeePaid == false
    • 最上位 AA 処理フレーム(現在実行している処理が exit や revert した場合、トランザクション処理全体が終了するようなフレーム)の中にいる
      • おそらく「それ以前に PAYGAS が実行されていないこと」と言い換えることができる
  • 上記条件が全て満たされていた場合、以下の処理を行う
    • アカウントの残高から gasPrice * gasLimit を差し引く
    • globals.transactionFeePaidtrue を設定する
    • globals.gasPrice に、上で読み出した gasPrice を設定する
    • globals.gasLimit に、上で読み出した gasLimit を設定する
    • 現処理コンテキストの remainingGas を更新する
      • gasLimit から既に消費した分の gas を差し引いた値を設定する

上記仕様を読んだだけだとよく分からないかもしれませんが、PAYGAS がやろうとしているのは、その実行を境として、以下のように処理フェーズを切り分けることです。

  • PAYGAS 前:verification phase
    • その名の通り、現行プロトコルにおけるトランザクション検証処理(と手数料徴収処理)に相当する
    • ここで例外が発生した場合、トランザクションは不正とみなされる
      • 現行プロトコルでトランザクションの電子署名が不正だった場合などと同様の扱い
  • PAYGAS 後:execution phase
    • トランザクションのメイン処理に相当する
      • PAYGAS によって設定された globals.gasPriceglobals.gasLimit を基準として、従来と同様に処理が行われる
    • ここでは、例外が発生した場合でもトランザクション手数料が発生する

verification phase はコントラクトごとに定義可能なので、これによって AA が実現されます。

課題

EIP86 と同様、

  • プロトコルレイヤーの改修
  • マイニング戦略のアップデート

が大きな課題ではありますが、EIP2938 の中にはマイニング戦略についての具体的な提案も含まれているため、後者についてはどうにかなりそうな道が見えてきたように思います。また、single-tenant AA に焦点を当て、multi-tenant AA で取り扱う必要がある複雑な問題を(想定はしつつも解決は)先延ばしにしたことによって、第 1 歩を踏み出しやすくなってもいます。

しかしやはり、第 1 歩を踏み出すにあたっても前者が必要という事実はクリティカルです。現状、Ethereum のプロトコルレイヤーの開発は the Merge やその後のスケーラビリティに焦点が当てられているため、今後数年の間、それ以上にプロトコルレイヤーを改修する機会は巡ってこないかもしれません。

ここでやっと、冒頭でご紹介した EIP4337 が登場する準備が整いました。

EIP4337

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

EIP2938 の提案から約 1 年後の 2021.09.29 に提案された、プロトコルレイヤーの改修を必要としない AA です。

重要なのでもう一度言います。プロトコルレイヤーの改修を必要としない AA です

実際、EIP4337 のカテゴリは ERC です(EIP と ERC の区別が曖昧な方は EIP1 を参照してください)。Core ではありません。

概要については以下の Vitalik の記事にもまとめられていますので、EIP の原文を読むことにハードルを感じる方は、こちらに目を通してもよいでしょう。

https://medium.com/infinitism/erc-4337-account-abstraction-without-ethereum-protocol-changes-d75c9d94dc4a

https://medium.com/infinitism/erc-4337-account-abstraction-without-ethereum-protocol-changes-d75c9d94dc4a より引用

上の概念図からもイメージできると思いますが、EIP4337 がやろうとしていることは、メタトランザクションの標準規格化と、その実行を担うシステム体系の整備を行い、コントラクトレイヤーをベースとした AA を実現すること です。よって、メタトランザクションの原理を理解している方は、それと照らし合わせて考えると理解が早まると思います。

メタトランザクションがよく分からない方は、まずは極端に、EOA を有している人が 1 人だけの世界をイメージしてみてください(その人をバンドラーと呼ぶこととします)。

バンドラー以外の人は、EOA ではなくコントラクトウォレットを有しており、各コントラクトウォレットには何らかの検証ロジック(極端に言えば「1 + 1 の答えを知っていれば OK」などでもよい)が定義されていることとします。自身のコントラクトウォレットを起点に何か処理を行いたくなった場合は、その処理に必要なデータをバンドラーに伝えます(このデータを UserOperation と呼ぶこととします)。

バンドラーは、複数の UserOperation をまとめて 1 つのトランザクションとし、特別なコントラクトを実行します(このトランザクションをバンドルトランザクション、特別なコントラクトをエントリーポイントコントラクトと呼ぶこととします)。

エントリーポイントコントラクトは、バンドルトランザクションに含まれる UserOperation を指定されたコントラクトウォレットに伝え、コントラクトウォレットは自身に定義された検証ロジックをパスした UserOperation のみを実行します。

...と、これで例え話は終わりです。

途中で気づかれた方も多いと思いますが、上記プロセスにおいて EOA に縛られているのはバンドラーのみです。すなわち、バンドラー以外の人に関しては AA が実現されています

バンドラーが AA されていないため完璧とは言えませんが、EIP4337 はこのような原理で AA を実現しようとしているわけです。

以下、その原理の中でも特に重要な UserOperation とエントリーポイントコントラクトの仕様について説明します。

UserOperation

ABI エンコードされた構造体であり、以下のフィールドを有します。

フィールド名 説明
sender address 処理を実行するウォレットのアドレス
nonce uint256 リプレイアタック防止のためのパラメータ(ウォレット生成の際は salt として利用される)
initCode bytes ウォレットの初期化コード(ウォレット生成の際のみ必要)
callData bytes sender に渡す、メイン処理を担うデータ
callGas uint256 メイン処理に割り当てられる gas の量
verificationGas uint256 検証処理に割り当てられる gas の量
preVerificationGas uint256 検証の前に行われる処理に割り当てられる gas の量(バンドラーに補償する gas の量)
maxFeePerGas uint256 EIP1559maxFeePerGas と同様の意味合いを持つガス価格
maxPriorityFeePerGas uint256 EIP1559maxPriorityFeePerGas と同様の意味合いを持つガス価格
paymaster address トランザクション手数料を代払いするアカウントのアドレス(代払いの必要がない場合は指定しない)
paymasterData bytes paymaster に渡すデータ
signature bytes senderUserOperation の正当性を検証するために使用するデータ(電子署名など)

ユーザーは、自身のコントラクトウォレットに実行させたい処理を UserOperation として表現し、バンドラーが待ち受ける UserOperation mempool に送信します。バンドラーは、mempool 内にある UserOperation をまとめてバンドルトランザクションを作成し、エントリーポイントコントラクトの handleOps をコールします。

エントリーポイントコントラクト

以下のようなインターフェースが実装されたコントラクト。

function handleOps
    (UserOperation[] calldata ops, address payable redeemer)
    external;

op.paymaster が指定されていない場合、handleOps は以下のような処理を行います。

https://eips.ethereum.org/EIPS/eip-4337 より引用

  • verification loop(上図の ② ③ に対応)
    • op.sender に対応するコントラクトウォレットが存在しない場合、op.initCode を使用して作成する
    • コントラクトウォレットの validateOp を実行する
      • コントラクトウォレットは、validateOp の中で op.nonceop.signature を検証し、処理が正当だとみなされる場合は手数料を支払い、自身の nonce をインクリメントする
function validateUserOp
    (UserOperation calldata userOp, uint requiredPrefund)
    external;
  • execution loop(上図の ④ ⑤ に対応)
    • op.callData を用いてコントラクトウォレットをコールする
      • op.callData をどう解釈するかはコントラクトウォレット次第
        • 例えば、いわゆる execute のような関数を介して外部コントラクトをコールするなど
      • 消費されなかった gas 分をコントラクトウォレットに返金する

op.paymaster が指定されている場合、handleOps は以下のような処理を行います。

https://eips.ethereum.org/EIPS/eip-4337 より引用

  • 事前準備
    • paymaster になりたいコントラクトは、ETH をエントリーポイントコントラクトにステークしてロックすることで、自身を paymaster として登録する
      • 他の "ユーザー" のコントラクトウォレットが op.paymaster として指定された悪意のある UserOperation による攻撃を防ぐため

なお、このような事前準備を行うため、エントリーポイントコントラクトは以下のようなインターフェースを実装する必要があります。

function addStake() external payable
function lockStake() external
function unlockStake(address paymaster) external
function withdrawStake(address payable withdrawAddress) external

verification loop と execution loop では、op.paymaster が指定されていない場合に行う処理に加えて、以下の処理を行います。

  • verification loop
    • op.paymaster が十分な ETH をエントリーポイントコントラクトにステークしていることを確認する
    • op.paymastervalidatePaymasterUserOp をコールし、この UserOperation に関して代払いが可能かどうかを確認する
function validatePaymasterUserOp
    (UserOperation calldata userOp, uint maxcost)
    external view returns (bytes memory context);
  • execution loop
    • メイン処理を終えた後、op.paymasterpostOp をコールする
      • 代払いした手数料(ETH)を ERC20 トークンなどで徴収する場合、ここでそれが行われる
function postOp
    (PostOpMode mode, bytes calldata context, uint actualGasCost)
    external;
       
enum PostOpMode {
    opSucceeded,     // user op succeeded
    opReverted,       // user op reverted. still has to pay for gas.
    postOpReverted // user op succeeded, but caused postOp to revert
}

なお、マイニング(バンドル)戦略問題を解決するため、validateUserOp には以下のような制約があります。

  • GASPRICE GASLIMIT DIFFICULTY TIMESTAMP BASEFEE BLOCKHASH NUMBER SELFBALANCE BALANCE ORIGIN GAS といった、実際に実行した際と結果が異なる可能性がある opcode は使えない
  • 基本的に外部コントラクトにアクセスすることはできないが、外部コントラクトのコードが不変であること(SELFDESTRUCT または DELEGATECALL が含まれていないこと)が保証されている場合のみ、外部コントラクトのコードを参照することはできる
    • コード ≠ ストレージであることに注意
    • 手数料を支払う必要があるので、外部アカウントの残高を更新することは可能

このような制約により、バンドラーはローカル環境で validateUserOp をシミュレーションできるようになるため、UserOperation をバンドルトランザクションに含めてよいかを低コストで判断することができます。

課題

冒頭で述べた通り、EIP4337 はプロトコルレイヤーの改修を必要としない AA であるため、EIP86 や EIP2938 に立ちはだかった大きな課題が 1 つ解消されています。マイニング(バンドル)戦略については、そもそもマイナーではなくバンドラーの問題にすり替えられているため、既存のマイナーに変更を強要する必要がなくなるという点で現実的です。提案されている戦略自体も十分現実的なように思えます。

また、バンドラーという新しいステークホルダーが登場してしまっていますが、これは INFURAAlchemy などのサービスが担える範囲のように思えますので、インセンティブ設計をそこまでシビアに行わなくとも機能しそうです。

しかし、エントリーポイントコントラクトというトラストポイントが生まれたことによって、新しい課題が生じていることに注意しなければなりません。EIP4337 ウォレットから実行される処理は全てエントリーポイントコントラクトを経由するため、ここにバグや脆弱性が潜んでいた場合、それと接続している EIP4337 ウォレットが有する全ての資産が攻撃の危険に晒されることになります。よって、エントリーポイントコントラクトには、非常に厳しい監査や形式検証が求められるでしょう。EIP4337 の中では、特に以下のような機構が正しく動作することを保証する必要があると述べられています。

  • ハイジャック防止機構
    • エントリーポイントコントラクトは、コールしようとしているコントラクトウォレットの validateUserOp が成功した場合のみ、そのコントラクトウォレットをコールする
      • 他人のコントラクトウォレットを勝手にコールできてしまってはいけない
    • そのコールは op.callData を用いて行われなければならない
  • 手数料浪費防止機構
    • validateUserOp が成功した場合、エントリーポイントコントラクトはコントラクトウォレットに対するコールを行わなければならない
      • validateUserOp だけが実行され、検証に要した gas が浪費されてしまってはいけない
    • そのコールは op.callData を用いて行われなければならない

また、(他に大きな課題があったため)ここまでは目を瞑ってきましたが、コントラクトウォレットの生成には gas が必要です(一方、EOA の生成に gas は必要ありません)。コントラクトウォレットの普及に際しては、これも大きな課題となるでしょう。

まとめ

EIP4337 は、AA における大きな課題だったプロトコルレイヤーの改修を回避しているという点で、EIP86 や EIP2938 と比べて非常に現実的な提案となっているため、いくつかの課題は残っているものの、AA の実現を推し進めていくための重要な材料となるでしょう。

また、EIP4337 に準拠した周辺システムが整備されていくということは、EOA 中心の世界がコントラクトウォレット中心の世界へ移行していくことを意味します。特に、その流れとともに進行する Account Security Abstraction は、デベロッパーにとってもユーザーにとっても非常に重要な "常識のアップデート" と言えるため、引き続き、両者の観点からその動向をキャッチアップしていくことが重要でしょう。

なお、EIP4337 のサンプル実装は こちらのリポジトリ で公開されていますので、より具体的な実装が気になるエンジニアの方はこちらも参照してみてください。詳細については、今後、弊社テックブログの中でも言及していく予定です。

また、弊社では、冒頭で述べた通り、実際に unWallet というコントラクトウォレットの開発を行なっています(PoC の中で検証を続けながら、皆さんに使っていただくための準備を整えているところです)。本記事を読んで、AA やコントラクトウォレットについて興味が湧いたエンジニアの方、ぜひ一度お話しましょう ↓

https://meety.net/matches/FHQNZgHvWgSh

参照

Discussion

ログインするとコメントできます