👛

【RIP-7560】L2 での Native Account Abstraction (AA) の標準化

2023/12/23に公開

こんにちは。SIVIRA Inc. で unWallet の開発をしている taxio です。

先日 ERC-4337 の Author 達から "RIP-7560 Native Account Abstraction" という Proposal が提出されました。

https://github.com/ethereum/RIPs/pull/3

description を見比べると分かりやすいですが、コンセンサスレイヤに手を入れて本格的に Native Account Abstraction を達成するための Proposal になっています。

An account abstraction proposal which completely avoids consensus-layer protocol changes, instead relying on higher-layer infrastructure.
-- ERC-4337

An account abstraction proposal that introduces consensus-layer protocol changes, instead of relying on higher-layer infrastructure.
-- RIP-7560

RIP という聞き慣れない prefix がありますが、これは Rollup Improvement Proposals の略で、Ethereum エコシステムにおけるロールアップの標準化を提供することを目的としています。

ただし標準とはいっても常にオプションであり、Ethereum のエコシステムとしてこれを強制するものではありません。

https://github.com/ethereum/RIPs

本記事では RIP-7560 で提案された AA の仕様を追いながら、ERC-4337 と何が違うのかを見ていきます。

Account Abstraction おさらい

In short, account abstraction means that not only the execution of a transaction can be arbitrarily complex computation logic as specified by the EVM, but also the authorization logic of a transaction would be opened up so that users could create accounts with whatever authorization logic they want.
-- https://ethereum-magicians.org/t/implementing-account-abstraction-as-part-of-eth1-x/4020

Account Abstraction はユーザが Tx の認可ロジックを任意に設定できるようにすることを目標としています。

これを達成するためにいくつかの角度から Proposal が提案されています。

  • EIP-86, EIP-2938
    • Core に変更を入れて AA 対応の Transaction Type や opcode を追加する Proposal
  • ERC-4337
    • Core には変更を入れず、Contract レイヤで完結する AA を実現するための Proposal

詳しくは以下の記事を読んでみてください。

https://zenn.dev/sivira_inc/articles/d041f1ac44ca1e

https://zenn.dev/sivira_inc/articles/34e8147f206ff5

RIP-7560 の仕様と ERC-4337 との差分

ざっくり一言でいうと、Bundler と EntryPoint が Core にネイティブ実装されます。

ユーザはいつも通り RPC エンドポイントに対して通常の EOA 起点の Tx を投げるように、AA Tx(詳細は下記)のリクエストを送ればそれが Block に格納されます。

EIP-86 や EIP-2938 で達成しようとしていた Core での AA を ERC-4337 のアイデアを軸にやり直したという感じです。

AA Transactions

EIP-2718 に則って Account Abstraction 用の新しい Transaction Type (AA_TX_TYPE = 0x04) が定義されました。

payload は以下のとおりです。

0x04 || 0x00 || rlp([
  chainId, sender, nonce, builderFee,
  callData, paymasterData, deployerData,
  maxPriorityFeePerGas, maxFeePerGas,
  validationGasLimit, paymasterGasLimit, callGasLimit,
  accessList, signature
])

Transaction Hash は以下のように計算されます。

keccak256(AA_TX_TYPE || 0x00 || rlp(transaction_payload)

コントラクト上で渡ってくるデータ構造は以下のとおりです。

struct TransactionType4 {
    address sender;
    uint256 nonce;
    uint256 validationGasLimit;
    uint256 paymasterGasLimit;
    uint256 callGasLimit;
    uint256 maxFeePerGas;
    uint256 maxPriorityFeePerGas;
    uint256 builderFee;
    bytes paymasterData;
    bytes deployerData;
    bytes callData;
    bytes signature;
}

UserOperation とほぼ同じような構造になっています。

以前は maxFeePerGasmaxPriorityFeePerGas は Bundler に対するガス支払いのパラメータになっていましたが、今回はネイティブ実装になるため通常のガスパラメータと同様に扱われます。

builderFee が追加されていますが、これはガス代とは別で Block builder (Block header の coinbase) に支払われるチップです。
L2 Rollup などの offchain 作業では L1 に対する費用は L2 側のガス変動とは別軸で発生するため、パラメータも分離されているようです。[1]

実行の流れ

大枠は ERC-4337 と変わってい無さそうです。
以下の記事で触れています。

https://zenn.dev/taxio/articles/834e6a04bd6b80

ただし Core に実装された影響で、Contract 上で見える Transaction Property が以下のように変化します。

  • msg.sender: AA_ENTRY_POINT (address(7560)).
    • Sender deployment frame では AA_SENDER_CREATOR (address(ffff7560))
  • tx.origin: sender address

tx.origin がちゃんと Contract Account のアドレスを指すようになったのが、Native Account Abstraction っぽくていいですね。
ERC-4337 では Bundler の EOA address が入っていました。

次に、ERC-4337 にも存在した Validation (Verification) Phase と Execution Phase でそれぞれ何が起きるのかを見ていきます。

Validation Phase

このフェーズでは全ての処理は revert することなく正常に完了する必要があります。
revert する場合、Block には取り込まれません。(そもそも Node RPC 側で弾かれるはず)
次の4つの frame から構成されます。

Nonce validation frame

AA_ENTRY_POINTsender の nonce チェックを行います。
nonce のフォーマットは ERC-4337 の EntryPoint と同じ Two-dimensional nonce [2] です。
NonceManager がどう運用されるかは議論中のようです。

Sender deployment frame

AA_SENDER_CREATORdeployerData に従って Account Contract を deploy します。
UserOperation の initCode と構造は同じなので、Factory を用意してそれを実行させる流れは変わりません。

この frame での gas は validationGasLimit に含まれます。

Sender validation frame

AA_ENTRY_POINT が Contract Account に実装されている以下の関数を呼び出します。

function validateTransaction(
  uint256 version,
  bytes32 txHash,
  bytes transaction
) external returns (uint256 validationData);

transactionTransactionType4 が ABI Encode されたものです。
将来的に Transaction の構造が変わった時にシグネチャが変更されないようになっていて、version がその構造体の revision を表しています。

この frame での gas は validationGasLimit に含まれます。

Paymaster validation frame

AA_ENTRY_POINT が Paymaster に実装されている以下の関数を呼び出します。

function validatePaymasterTransaction(
  uint256 version,
  bytes32 txHash,
  bytes transaction
) external returns (bytes context, uint256 validationData);

ここでの gas は paymasterGasLimit に含まれます。

Execution Phase

Sender execution frame

AA_ENTRY_POINT が Contract Account に対して callData を invoke します。
ここで revert が起きても、Validation phase の処理は revert されません。
ただし、Transaction Receipt の status はちゃんと 0 (failure) になります。

この frame での gas は callGasLimit に含まれます。

Paymaster post-transaction frame

AA_ENTRY_POINT が Paymaster の以下の関数を呼び出します。

function postPaymasterTransaction(
  bool success, // Sender execution frame が revert せずに完了したか
  uint256 actualGasCost,
  bytes context
) external;

Sender execution frame が revert されても、この frame は実行されます。

この frame での gas は paymasterGasLimit に含まれます。

以上が AA Transaction 実行時の流れです。

Validation, Execution Tx の分離

先程も触れた通り、Validation phase は必ず revert されずに完了する必要があります。

特定の AA Tx の Execution による State 変化で他の AA Tx の Validation が revert される状態になる可能性があるので、Block 構築の難易度緩和のために Block builder は Validation phase と Execution phase で Tx を分割できます。

Validation phase 内で悪いことができそうな気もしますが、そこに関しては ERC-7562 の中で議論されているようです。
筆者はまだちゃんと読めてないですが、Validation に関して Storage アクセス等の制限を設けているようです。
これに関してはとても重要そうなので別で掘り下げたいですね。

block overview
-- https://github.com/eth-infinitism/RIPs/blob/e8af8009251f24e67311e0d955e7a951284717fc/RIPS/rip-7560.md#block-structure-diagram

分割時にできた Validation phase 側の Tx は Virtual Transaction と呼ばれ、Tx Hash は AA_TX_TYPE transaction hash + 1、つまり AA Transaction のハッシュ値に1を足したものになります。
Execution phase は AA Transaction の扱いのままです。

Block 内で Tx が2つに分かれるので、Tx Receipt を見ている場合は注意が必要です。
Proposal では eth_getTransactionReceipt で AA Transaction Hash に加えて Virtual Transaction Hash も受け取れるようになっていました。
つまり、Validation phase で発生する Event なんかは明示的に Virtual Transaction Hash を指定して取りに行く必要があります。

所感

RIP-7560 では ERC-4337 をベースにシンプルに Account Abstraction を Core に実装していました。
UserOperation が TransactionType4 に変わっただけで、Account Contract, Paymaster がやることも特に変わっていません。
ただ、ERC-4337 では EntryPoint に対して Gas 代を deposit していましたが、Core 実装のおかげでそこの処理がいらなくなりました。
署名集約に関してはこの Proposal のスコープには入れておらず、今後導入していくようです。[3]

また vitalik が 2023/09 に投稿した記事では非常に納得感のある一文がありました。

ERC-4337 introduced a large amount of globally shared code, and in the long term it makes more sense for bugs in that code to be fixed by hard forks than to lead to users losing a large amount of ETH.
-- https://vitalik.eth.limo/general/2023/09/30/enshrinement.html

ERC-4337 では Contract Account は1つの EntryPoint をフルトラストします。
もしこの EntryPoint にバグがあった場合、多くのアカウントが資産を失うことになります。
しかし、Core 側に EntryPoint の機能を実装していた場合、それはプロトコルのバグとしてハードフォークによって修正できます。
確かにこれは Contract レイヤで AA を実現している ERC-4337 ではできないことです。

既に Native AA をサポートしている Chain

ところで、この Proposal が出されるより前に既に Account Abstraction のネイティブ実装を入れている Chain が存在します。
RIP-7560 と比べて先発の実装はどうなっているのか、いくつか見てみましょう。

StarkNet

https://docs.starknet.io/documentation/architecture_and_concepts/Accounts/approach/

Contract Account は以下のメソッドを持ちます。

  • __validate__
  • __execute__

Sequencer が mempool から Tx を選択して __validate____execute__ を実行し、全てが通れば Block に含まれるようです。
大枠の方針は RIP-7560 と同じっぽいですね。(言語は cairo ですが)

下記の記事から始まる連載では ERC-4337 を取り上げながら Starknet の AA に関する方針が書かれています。

https://medium.com/starknet-edu/account-abstraction-on-starknet-part-i-2ff84c6a3c30

zkSync Era

https://era.zksync.io/docs/reference/concepts/account-abstraction.html

Contract Account は以下の Interface を実装しておく必要があります。

https://github.com/matter-labs/v2-testnet-contracts/blob/b8449bf9c819098cc8bfee0549ff5094456be51d/l2/system-contracts/interfaces/IAccount.sol

これも validate → execute の順で呼ばれるのは同じです。

Chain 上には System Contract という概念が存在し、その中の Bootloader コントラクトが実質的に Core の EntryPoint と同じ働きをします。

今後の L2 の AA 対応

そもそも ERC-4337 をベースにカスタマイズされているので当たり前ではありますが、StarkNet と zkSync Era のどちらも Core 側が Bundler と EntryPoint に相当する機能を持ち、Contract Account では validate と execute の関数を実装していました。

Core の Tx 処理に大きく手を入れなければならない概念なので、こうやって RIP で標準が示されるのは大きく価値があるのではないかと思います。

Wallet 提供者としては Chain 毎に Interface や RPC の叩き方を調整しなくてはならないので、この RIP-7560 を軸に1つのパターンに収束してほしいです(笑)

終わりに

RIP-7560 の仕様を見ていきました。ERC-4337 の慣れ親しんだ(?)アーキテクチャでシンプルに Core に実装されていて、これならギャップも少なく現実的な解なのではないかと思えます。

また、途中で気がついた人もいるかもしれませんが、この RIP-7560 はそもそも L1/L2 の区別が必要な仕様は入っていません。
もしかしたらそのまま EIP として L1 Core に入る可能性もあるかもしれませんね。[4]

ちなみに既存の EOA に関しても Contract Account として AA Transaction を開始できないか、以下の EIP で議論が進んでいるようです。

Account Abstraction は Blockchain を一般ユーザに使ってもらうための UX 改善に必須の概念です。
これからの議論にも要注目ですね。

参考文献

脚注
  1. あくまで offchain に関する fee で、onchain で計算可能な fee に関しては別途パラメータを追加することを検討しているそうです。https://github.com/ethereum/RIPs/pull/3#discussion_r1421828225 ↩︎

  2. https://docs.google.com/document/d/1MywdH_TCkyEjD3QusLZ_kUZg4ZEI00qp97mBze9JI4k/edit ↩︎

  3. https://hackmd.io/@alexforshtat/native-account-abstraction-roadmap#Aggregators ↩︎

  4. tx.origin が Contract Account の address になるケースができるため、L1 → L2 の address aliasing において、まだ議論が残っているようです。ここは筆者も理解できていないので、誰か知ってる人がいたら教えて欲しいです。https://github.com/ethereum/RIPs/pull/3#discussion_r1412269009 ↩︎

GitHubで編集を提案
SIVIRA Inc.

Discussion