はじめに
初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回は、Ethereum L1では実行できないままにして、L2や独自EVMチェーンが拡張命令の目印として使えるEXTENSION (0xae)を予約する「EIP8163」についてまとめていきます。
以下にまとめられているものを解説しながらまとめていきます。
EIP8163は、Ethereum L1のEVMには新しい実行機能を足さず、他のEVMが独自拡張を入れるための「ぶつからない目印」だけを用意する提案です。
要点は、Ethereum L1では0xaeをINVALID (0xfe)と同じように失敗させる一方で、非L1のEVMではその後ろのバイト列を使って独自命令を表現できるようにする点です。
概要
EIP8163は、0xaeという1バイトのオペコードをEXTENSIONとして予約する提案です。
Ethereum L1では、このEXTENSIONを有効な命令にしません。
実行された場合はINVALID (0xfe)と同じように例外停止し、現在の実行フレームを終了し、残りのガスを消費します。
一方で、Ethereum L1以外のEVMチェーンでは、0xaeを「ここから独自拡張命令が始まる」というプレフィックスとして使えます。
例えば、あるL2が新しい計算命令を試したい時、既存のEthereum L1オペコードと衝突しない場所として0xaeから始まる命令空間を使えるようになります。
この提案の狙いは、EVMの互換性を保ちながら、Ethereum L1以外のチェーンがEVM内で実験しやすくすることです。
独自チェーンが勝手に空いているオペコードを使うと、後からEthereum L1が同じ番号を別の意味で採用した時に互換性が崩れます。
EIP8163は、Ethereum L1が0xaeを今後も有効命令として使わないと決めることで、非L1 EVMが安心して拡張用の名前空間として扱えるようにします。
オペコードとは
EVMが実行する命令を表す1バイトの値です。
例えば、ADDやPUSH1のような命令にも番号が割り当てられており、スマートコントラクトのバイトコードはこの命令列として実行されます。
EXTENSION (0xae)は、その命令列の中で拡張命令の始まりを示すために予約される値です。
全体像は以下の図です。
図の左側は、同じ0xaeを含むバイトコードでも実行環境によって扱いが分かれることを示しています。
Ethereum L1では失敗する命令として固定されます。
非L1のEVMでは拡張命令の起点として使えますが、ジャンプ可能な場所の判定はEthereum L1と同じ結果でなければなりません。
動機
現在のEVM互換チェーンは、EthereumのEVMと互換性を保つことで、既存のツール、開発者体験、スマートコントラクト資産を利用できます。
しかし、EVM互換性を保とうとすると、新しい命令を自由に追加しにくくなります。
未使用のオペコードを独自に使うこと自体はできます。
ただし、その番号が将来Ethereum L1で別の用途に割り当てられると、同じバイトコードがチェーンごとに別の意味を持つようになります。
同じ番号に複数の意味が生まれると、コントラクトの移植性、監査、トレース、デバッグのすべてが難しくなります。
未使用オペコードをそのまま使う場合と、0xaeを予約する場合の違いは以下の図です。
左側は、独自チェーンが空き番号を使った後にEthereum L1が同じ番号を採用し、同じバイトコードが別の意味を持つ流れです。
右側は、Ethereum L1では0xaeをINVALID扱いに固定し、非L1では拡張命令の目印として使う流れです。
この分離によって、非L1 EVMはEthereum L1との衝突を避けながら実験できます。
非L1 EVMの実験余地
Ethereum L1以外のEVMチェーンは、実行環境を特化させることで新しい命令や処理モデルを試せます。
例えば、あるL2が特定の暗号処理を安くしたい場合や、アプリケーション特化チェーンが専用命令を追加したい場合があります。
その時に課題になるのは、どのオペコード番号を使うかです。
空いている番号を自由に使うだけでは、後からEthereum L1や他のEVMチェーンと衝突する可能性があります。
EIP8163は、0xaeをEthereum L1では永久に有効命令にしないことで、非L1 EVMが拡張命令の共通プレフィックスとして使える余地を作ります。
Ethereum L1への影響を増やさない設計
EIP8163は、Ethereum L1の実行レイヤークライアントにコンセンサス上の変更を求めません。
0xaeは現在も有効な命令ではないため、実行すると例外停止します。
提案が定めるのは、将来もこの挙動を変えず、拡張命令としてEthereum L1に導入しないという約束です。
この設計により、Ethereum L1の安全性や既存コントラクトの挙動を変えずに、EVMエコシステム全体の実験余地を広げられます。
非L1チェーンで成功した拡張があれば、将来的に別のEIPでEthereum L1へ持ち込むこともできます。
その場合でも、0xaeをそのままL1の多バイト命令プレフィックスにはせず、別のオペコードとして提案する前提です。
仕様
仕様は、Ethereum L1でのEXTENSIONの扱い、非L1 EVMでの扱い、そしてJUMPDEST解析を変えない制約に分かれます。
EXTENSION自体は新しいL1機能ではなく、L1ではINVALIDと同じ失敗動作をする予約済みの値です。
EXTENSION (0xae)
EXTENSIONは0xaeに割り当てられます。
Ethereum Mainnetやテストネットなど、すべてのEthereum L1チェーンではINVALID (0xfe)と同じように動作する必要があります。
EIP141は、INVALID (0xfe)を実行した時に例外停止する無効命令として定義した提案です。
[EIP141] INVALIDオペコードの仕組みについて理解しよう!
Ethereum L1でのEXTENSIONの要件は以下です。
| 項目 | 内容 |
|---|---|
| オペコード | 0xae |
| 名前 | EXTENSION |
| Ethereum L1での実行結果 |
INVALID (0xfe)と同じく例外停止し、現在の実行フレームを終了して残りのガスを消費する。 |
JUMPDEST解析への影響 |
影響してはならず、0xaeがあってもジャンプ先として有効な位置の判定は変わらない。 |
| 静的解析や事前処理への影響 |
INVALIDと同じ扱いを超えて、追加の影響を与えてはならない。 |
ここで重要なのは、0xaeを「将来のL1命令候補として予約する」のではなく、「L1では有効命令にしない拡張用の印として予約する」点です。
そのため、Ethereum L1のスマートコントラクトが0xaeを実行できるようになるわけではありません。
非L1 EVMでの扱い
Ethereum L1以外のEVMでは、EXTENSIONに意味のある実行動作を与えられます。
ただし、どのような動作にするかは各EVM実装が決めます。
EIP8163は、拡張命令そのもののエンコード方式や命令一覧までは定義しません。
非L1 EVMがEXTENSIONを実行する場合、後続のバイト列を即値引数として使い、拡張命令の意味を決める必要があります。
例えば、0xae 0x01をある拡張命令、0xae 0x02を別の拡張命令として解釈するような設計が考えられます。
ただし、後続の即値引数にJUMPDEST (0x5b)が含まれ、それがジャンプ先として有効なまま残る場合は、実行は例外停止する必要があります。
これは、Ethereum L1と非L1 EVMでJUMPDEST解析の結果がずれないようにするためです。
JUMPDEST解析
EVMでは、JUMPやJUMPIで移動できる先がJUMPDEST (0x5b)の位置に限定されています。
そのため、バイトコード中のどの0x5bが有効なジャンプ先かを事前に解析する必要があります。
もし非L1 EVMがEXTENSIONの後続バイトを自由に隠してしまうと、同じバイトコードでもEthereum L1と非L1 EVMで有効なジャンプ先が変わる可能性があります。
これが起きると、コードの移植性が失われます。
そこでEIP8163は、EXTENSIONがあってもJUMPDEST解析の結果をEthereum L1と一致させることを強く求めています。
処理の考え方は以下です。
-
Ethereum L1では
0xaeをINVALIDとして読む
0xaeの後ろにあるバイトは、通常のEVMバイトコードとして解析されます。 -
非L1 EVMでは
0xaeを拡張命令の始まりとして読める
ただし、後続バイトの扱いによって有効なJUMPDESTの集合を変えてはいけません。 -
有効な
JUMPDESTが即値引数に残る場合は例外停止する
拡張命令として実行するとジャンプ解析がずれるバイト列は、拡張命令として成立しません。
0x5bが命令として読まれる場合と、PUSH命令のデータとして読まれる場合の違いは以下の図です。
上段の0xae 5bでは、5bが命令としてのJUMPDESTです。
この場合、拡張命令の即値引数に有効なジャンプ先が残るため、拡張命令としては無効です。
一方、0xae 60 5bや0xae 61 60 5bでは、5bがPUSH1またはPUSH2のデータとして扱われます。
この場合、5bはジャンプ先ではないため、Ethereum L1と同じ解析結果を保てます。
バイト列の例
提案では、0xaeの後ろに来るバイト列とJUMPDEST解析の関係を例で示しています。
日本語で整理すると以下です。
| バイト列 |
JUMPDESTの扱い |
拡張命令としての扱い |
|---|---|---|
0xae5b... |
0x5bは有効なJUMPDESTです。 |
即値引数に有効なJUMPDESTが残るため、拡張命令としては無効です。 |
0xae015b... |
0x5bは有効なJUMPDESTです。 |
0x01を引数とする拡張命令として有効にできます。 |
0xae60 |
判定対象になる後続データが足りません。 |
PUSH1の即値が足りないため無効です。 |
0xae605b... |
0x5bはPUSH1のデータで、ジャンプ先ではありません。 |
PUSH1のデータとして0x5bを渡す拡張命令として有効にできます。 |
0xae60605b... |
最後の0x5bは有効なJUMPDESTです。 |
PUSH1で0x60を引数にする拡張命令として有効にできます。 |
0xae61605b... |
0x5bはPUSH2のデータで、ジャンプ先ではありません。 |
PUSH2で0x605bを引数にする拡張命令として有効にできます。 |
0xae615b |
PUSH2のデータが足りません。 |
命令が途中で切れているため無効です。 |
この表のポイントは、0x5bが「命令としてのJUMPDEST」なのか、「PUSH命令のデータの一部」なのかで扱いが変わることです。
EVMの既存ルールでは、PUSH1からPUSH32の直後にあるデータは命令として解釈されません。
そのため、0xae605b...の0x5bはPUSH1の即値データとして扱われ、ジャンプ先にはなりません。
設計根拠
JUMPDEST解析を変えない理由
別案として、EXTENSIONの後ろにある0x5bをジャンプ先として無効化したり、PUSHx命令を飛ばして即値引数のサイズを決めたりする設計も考えられました。
しかし、この方法ではEXTENSIONの即値引数のサイズをEIP8163側で決める必要があります。
また、将来別のEIPで即値引数を持つオペコードが導入された場合に、解析ルールが衝突する可能性もあります。
さらに、同じバイトコードをEthereum L1と非L1 EVMで解析した時、有効なジャンプ先が異なると、コードの移植性が下がります。
そのため、この提案ではEXTENSIONがJUMPDEST解析に影響しない設計を採用しています。
0xae5bのように0x5bが有効なジャンプ先として残るバイト列は、拡張命令としては無効です。
一方、0xae605bのように0x5bがPUSH1のデータとして扱われる場合は、ジャンプ解析を変えずに即値引数として使えます。
エンコード方式を定義しない理由
EIP8163は、EXTENSIONの後ろをどうエンコードするかまでは定めません。
この提案の範囲は、拡張命令のための安全なプレフィックスを予約することに絞られています。
もし具体的なエンコード方式まで決めると、非L1 EVMの実験余地が狭くなります。
また、拡張命令の形式や命令一覧は、それ自体が独立した議論になります。
そのため、必要であれば別の提案で議論する形になっています。
Ethereum L1で多バイト命令を使わない理由
EXTENSIONは、Ethereum L1で多バイト命令を作るための仕組みではありません。
提案では、Ethereum L1 EVMがEXTENSIONを使って多バイト命令をサポートすることを明確に禁止しています。
将来Ethereum L1に同じ機能を取り込みたい場合は、0xaeプレフィックスをそのまま使うのではなく、別のEIPで別のオペコードを選ぶ形になります。
これにより、非L1 EVMでの実験とEthereum L1の仕様進化を切り分けられます。
L1への取り込み方
非L1 EVMでEXTENSIONを使った機能が成功し、Ethereum L1にも取り込みたい場合があります。
その時は、別のEIPで専用のオペコードを選び、L1向けの仕様として提案します。
元の非L1 EVMがそのL1向けEIPも採用した場合、そのEVMでは同じ機能にアクセスする方法が2つ残ります。
1つはEXTENSIONを使った独自拡張命令で、もう1つはEthereum L1と共通の新しいオペコードです。
この二重経路を許容することで、既存の非L1向けコードを壊さずに、L1側へ機能を持ち込めます。
非L1で成功した拡張機能をEthereum L1へ取り込む時の流れは以下です。
EXTENSIONは非L1での実験用の経路です。
成功した機能をEthereum L1へ持ち込む時は、別EIPでL1向けに議論し、L1では別オペコードとして採用します。
元の非L1 EVMがL1側の新オペコードも採用すると、0xae経由とL1共通オペコード経由の2つが残ります。
互換性
EXTENSION (0xae)は、現在のEthereumでは有効な命令ではありません。
そのため、既存のEthereum L1コントラクトが0xaeを実行しても、今までどおり無効命令として失敗します。
また、0xaeはJUMPDEST解析に影響しないため、既存バイトコードのジャンプ先判定も変わりません。
この提案は、Ethereum L1に新しい実行動作を追加するのではなく、将来も0xaeをL1の有効命令にしないことを決めるものです。
テストケース
提案では、テストケースはEthereum Execution Layer Specs形式で提供予定とされています。
現時点で具体的なテストコードは含まれていません。
テストで確認すべき中心は、Ethereum L1で0xaeがINVALIDと同じように例外停止することと、0xaeを含むバイトコードでもJUMPDEST解析の結果が変わらないことです。
非L1 EVMでEXTENSIONを実装する場合は、即値引数に有効なJUMPDESTが残るバイト列を拡張命令として認めないことも確認対象になります。
参考実装
提案では、参考実装はありません。
Ethereum L1の実行レイヤークライアントのコンセンサス動作は変わらないためです。
実装上の変更があるとしても、トレースや表示など、コンセンサスに関係しない見せ方の部分に限られます。
例えば、ディスアセンブラやデバッグツールが0xaeをEXTENSIONという名前で表示するかどうかは、実行結果には影響しません。
セキュリティ
Ethereum L1では、EXTENSIONはINVALIDと同じ扱いです。
そのため、Ethereum L1の実行レイヤークライアントに新しいコンセンサス上のリスクは追加されません。
一方で、非L1 EVMがEXTENSIONを実装する場合は注意が必要です。
特に、拡張命令の即値引数によってJUMPDEST解析がEthereum L1とずれると、同じバイトコードの制御フローがチェーンごとに変わります。
このずれは、監査や移植、ツールによる解析を難しくします。
解析がずれる時のリスクは以下の図です。
同じバイトコードでも、Ethereum L1と非L1 EVMで有効なJUMPDESTの集合が変わると、ジャンプできる場所が変わります。
ジャンプできる場所が変わると、実行される経路も変わるため、監査結果や移植時の前提が合わなくなります。
そのため、非L1 EVMでEXTENSIONを実装する場合でも、JUMPDEST解析はEthereum L1と同じ結果に保つ必要があります。
また、各EVM実装が独自に拡張命令を増やしすぎると、EVM互換と呼ばれる範囲が読みにくくなります。
提案では、非L1 EVMの実装者がEIPの議論スレッドで具体的なEXTENSIONの使い方を調整することを推奨しています。
同じ0xaeプレフィックスを使う以上、命令空間の衝突を避けるための調整が重要です。
引用
最後に
今回は「EIP8163」についてまとめてきました。
EIP8163は、Ethereum L1で新しい命令を増やす提案ではありません。
むしろ、0xaeをL1では使わない値として固定し、L2や独自EVMチェーンが安全に拡張命令を試せる場所を作る提案です。
重要なのは、Ethereum L1の挙動を変えないことと、JUMPDEST解析を変えないことです。
この2つを守ることで、非L1 EVMは実験しやすくなり、同時にEVMバイトコードの移植性や解析可能性も保ちやすくなります。
他でも色々記事を書いているのでぜひよろしければ読んでいってください!




