仮想通貨の相場は大きく変動する場合がございます。また、レバレッジ取引を行う場合は投資額を上回る損失が生じる可能性がございます。

前回の記事(https://bitcoinlab.jp/knowledge/1937/)で「Ethereumそのものを深く理解しなければ、高度な攻撃方法の理解はできず、スマートコントラクトの高いセキュリティは実現不可能です。」と書いて締めくくりました。今回はその続編として、「EVMを理解する」をテーマに解説します。スマートコントラクトセキュリティのコンテキストでは、特にEVMを深く理解することが大事だと筆者は考えています。本記事では、コントラクト生成と関数呼び出しを通して、EVMの概要といかにしてEVMを学習するかについて解説します。

※「- -XXX」オプションなどの表記について、本サイトの仕様上「-」と「-」の間にスペースを入れている箇所がございますが、実際は「-」が2つ連続しています。

目次

  • 1、EVMとは
  • 2、emvコマンド
  • 3、コントラクト生成
  • 4、関数の実行
  • 5、如何に学習するか

1、EVMとは

EVMはEthereum Virtual Machineの略で、Ethereum上のスマートコントラクトを実行するための仮想マシンです。スマートコントラクトは各ノード上のEVMで実行され、その結果としてスマートコントラクトの状態が変更されます。Ethereumでスマートコントラクトを開発する場合は、一般的にSolidityなどの専用のプログラミング言語で開発しますが、そのソースコードがそのままEthereumにデプロイされるのではなく、EVMが実行可能な形式にコンパイルしてデプロイします。EVMで実行可能なプログラムはOPCODEと呼ばれる命令セットで成り立っています。

2、evmコマンド

スマートコントラクトを開発する方は、開発したスマートコントラクトをtruffleやgethなどにデプロイして実行していると思いますが、これだとEVMの真の理解は進みません。そこで、今回はEVMを理解するために有効だと考える、evmコマンドをベースに解説します。evmコマンドはgethと一緒にダウンロード可能なプログラムで、gethとは独立して機能しコマンドとして、EVMを起動できます。evmコマンドは次のサイトでダウンロードしてください。

・Go Ethereum Downloads

https://geth.ethereum.org/downloads/

「Geth & Tools 1.8.20」のように、Toolsが含まれているものをダウンロードしてください。evmコマンドが含まれています。なお、本記事ではバージョン 1.8.20 を利用します。これとは異なるバージョンだと、本記事とは異なる挙動をする可能性がある点、ご留意ください。また、Solidityのコンパイラであるsolcはバージョン 0.5.2を利用しています。

3、コントラクト生成

はじめにコントラクト生成を実施してみましょう。コントラクト生成用のトランザクションを発行してプログラムをデプロイします。トランザクションは、インプットとしてデータをセットでき、ここにデプロイ対象のプログラムをセットします。デモ用のSolidityのプログラムファイルは次の通りです。

・EVMDemo.sol

まずはsolcコマンドでコンパイルしましょう。

・コンパイル

EVMDemo.solと同じディレクトリに、EVMDemo.binというファイルが出力されます。evmコマンドでこれを使って、コントラクトを生成してみましょう。

・コントラクトデプロイ

「- -codefile」オプションでは、デプロイするプログラムファイル(EVMDemo.bin)を指定します。「- -create」オプションはコントラクト生成のためトランザクションを表し、「- -dump」オプションは実行後のステートの状態を表示するためのオプションです。「run」はトランザクション実行という意味だと思ってください。

実行結果のうち、注目してほしいのは次の箇所です。

一番上の「1f2a98889594024bffda3311cbe69728d392c06d」 は、生成されたコントラクトのアドレスです。生成されたコントラクトのプログラムはcodeに設定されていて、storageには当該コントラクトに紐づくストレージの値が表示されます。当該コントラクトではコンストラクタでxに0x11を設定しているので、ストレージのindex=0に0x11が保存されていることがわかります。

さて、先程生成したEVMDemo.binとcodeに設定された値を見比べてみてください。codeにはEVMDemo.binと完全一致しないものが設定されています。コントラクト生成では生成トランザクションで指定したプログラムがそのままデプロイされるわけではなく、生成トランザクションで指定したプログラムをEVMが実行することで、コントラクト生成時に設定したプログラムとは別のものをデプロイしているのです。次のコマンドを実行してみましょう。

・bin-runtime

先程、「- -bin」オプションにしていたところを、「- -bin-runtime」に変更しています。実行すると、EVMDemo.bin-rutime というファイルが出力されます。この中身と先程のcodeを比べると完全一致します。普段、solcを使っている方でも、馴染みのないオプションかと思いますが、これはコントラクト生成トランザクションによってデプロイされるプログラムを生成するためのオプションだと思ってください。「- -bin」オプションで生成されたものをEVM上で実行すると、最終的に「- -bin-runtime」で生成されるものと同じプログラムが戻り値として返ってきて、それをEthereum上にデプロイするという流れなのです。「- -bin」オプションの方は簡単に説明すると、コンストラクタを実行して、デプロイするプログラムをリターンするプログラムと思っていただくとよいでしょう。リターンされるプログラムには以降で不要なコンストラクタ部分などは含まれていません。従って、コントラクト生成によってデプロイされるプログラムは、コントラクト生成のためのプログラムのサブセットだと思っていただいてもよいでしょう。

さて、このあたりの処理を理解するには、EVM上での処理の流れを見るのが一番です。ここでevmコマンドが活きてきます。次の通り実行してください。

・debugオプションで実行

TRACE部分には各OPCODE実行とその後の、スタック、メモリ、ストレージ、プログラムカウンタが表示されます。次の部分を例に上げましょう。

当該処理は、コントラクト生成のプログラムの次の部分の処理を表しています。

最初の pc はプログラムカウンタを表現しており、0からスタートします。プログラムの0x60はPUSH1を表現しており、続く1byteをスタックにプッシュせよの意味です。ここでは0x80がスタックにプッシュされます。evmコマンドではOPCODEの実行後の状態を次のOPCODEの時に表示するようになっているのですが、2つ目のPUSH1のStackパートを見ると、最初のPUSH1の後にスタックに0x80がプッシュされていることがわかります。なお、pcは通常は1ずつカウントアップされますが、今回はPUSH1で1byteプッシュしたのでこの分もカウントアップされ、2つめのPUSH1の時には 1 ではなく 2 となります。TRACEにはMemoryやStorageの状態も出力されますが、見方はStackと概ね同じです。

このように、debugオプションをつければ、各OPCODEの処理内容を可視化でき、OPCODEを理解するための一助となります。

4、関数の実行

それでは、デプロイしたコントラクトの関数をトランザクションで呼び出してみましょう。最初に次のようなファイルを用意します。

・prestate.json

当該ファイルは先程のコントラクト生成後の状態を初期状態として表現しているgenesisファイルです。ハイライトしている3,6,8行目(コントラクトアドレス、デプロイ後のプログラム、ストレージ)を実態にあわせて修正すればOKです。

evmコマンドは一度実行するとその結果を保存しないため、このようなファイルを必要に応じて用意します。

・fallback関数呼び出し

「- -prestate」オプションには作成したprestate.jsonファイル、 「- -receiver」オプションには先程生成したコントラクトのアドレスを設定します。このコマンドでは、先程デプロイしたコントラクトに対して、データをセットせずにトランザクションを発行しているだけです。当該コントラクトはfallback関数が定義されているため、データがない場合はfallback関数が呼ばれます。ステレージ上のxはデプロイ後は0x11でしたが、fallback関数で0x22が加算されて、0x33になっていることが確認できます。これも「- -debug」オプションをつければどのように処理されているかが理解できます。

続いてadd関数を呼び出してみましょう。関数を指定する場合は、トランザクションのデータ部分に呼び出す関数と引数の情報をセットする必要があります。まず、どのように関数を指定するですが、次の通りのコマンドで関数のfingerprintを取得してください。

・fingerprint取得

呼び出す関数のfingerprintを出力しています。fingerprintは関数のシグネチャのkeccak256ハッシュ値の最初の4byteとなります。今回呼び出すadd関数のシグネチャは「add(uint256)」であり(Solidityファイルでは引数をuintの型にしていますが、uintはuint256のエイリアスであるため、uint256を設定する必要があります)、そのfingerprintが0x1003e2d2であることがわかります。gethやtruffleのコンソール上などで実行できるweb3のコマンドでも試してみましょう。次はtruffleコンソール上で実行しています。

・truffleコンソール上でfingerprintを計算

同じ結果になりましたね。さて、add関数ではuint型の引数を与えると、それをxに加算します。今回は0x33を引数としてあたえて実行してみましょう。「- -input」オプションにfingerprint、その後に32byteの0パディングした引数を設定します。

・add関数呼び出し

初期値の0x11に0x33が加算されて、0x44にセットされているのが確認できましたね。

5、如何に学習するか

ここまでで、evmコマンドの使い方、コントラクト生成、関数呼び出しの仕組みについて解説しました。OPCODE実行の仕組みなどは、是非、evmコマンドでdebugオプションを付与して流れを理解してもらえればと思います。しかし、ここまでの解説はあくまで概要レベルですし、debugオプションで表示したとしてもなんとなく実行後の状態が理解できるようになっても腹落ちしないというのが現実だと筆者は考えます。EVMを理解するには、Ethereumの公式クライアントであるgethのソースコードを読んでいくのが一番だと考えています。Ethereumのyellow pagerには処理内容は詳しく解説されていますが、数式が多く、敷居が高いです。その他の噛み砕いた説明をしている書籍やネットリソースも簡素化しすぎていて、逆に腹落ちできないという問題があると筆者は考えるからです。そのため、gethのプログラムをデバッカでデバックしたり、プログラム内にprint関数などを仕込んでデバックするという方法でひとつひとつ丁寧に腹落ちさせていくのが大事だと思います。一方で、gethの場合は、EVMを動かすために、gethを起動して、トランザクションを生成したりする手間があるに加え、余計な処理も動いており、デバックを手軽にやるには適していません。しかし、evmコマンドはコマンド単位でトランザクションをデバックでき、デバックが捗るため、今回紹介させてもらいました。EVMの処理はgethとevmコマンドで共通ですので、gethで実装されたEVMを理解することが可能です。筆者がお勧めするのはデバックもしかりですが、実際にソースコードを写経してみることです。evmコマンドのエントリーポイントから写経していき、コンパイルして実行、mainから呼ばれている別のプログラムファイルを写経してコンパイルして実行と言ったぐあいに、自身が理解したい箇所を順に写経しながら理解していくイメージです。Ethereumに限らずですが、まだまだ世の中に十分な解説がなされていない領域の技術を深く理解したい場合は、筆者がお勧めしている学習法の一つです。もちろん時間はかかりますが、その分、深く理解できるので、かえって合理的だと筆者は考えています。

最後に学習していく上でポイントとなるキーワードをあらためて列挙しておきます。EVMを理解するためには、OPCODE、スタック、メモリ、ストレージ、プログラムカウンタがポイントです。プログラムは頭からOPCODEが実行されていき、プログラムカウンタがいま実行しているOPCODEの場所を指します。各OPCODEによって、スタック、メモリ、ストレージへのRead/Writeが発生します。スタックは、スタック構造で動くコントラクトを実行するためのスタックそのもので、メモリは一時保存領域、ストレージはハードディスクのようなイメージをもっていただくと理解が捗ると思います。

evmコマンドが利用しているプログラムのうち、読んでいただきたい主要プログラムは次の通りです。以下のプログラムから呼ばれている別のプログラムも適宜読み込んでみてください。

GitHub – ethereum/go-ethereum at v1.8.20

gethやevmのリポジトリです。本記事ではバージョン1.8.20を利用したので、このバージョンのタグをリンクしています。

main.go

evmコマンドのエントリポイント。当該ファイルがあるディレクトリで「go build」と実行するとevmという実行ファイルが同じディレクトリに生成されます。

runner.go

初期化処理を行う箇所。実質的なエントリーポイント。

evm.go

gethと共通のプログラム。コントラクト生成や関数呼び出しなどが実装されており、EVMを深く理解する上で重要なプログラム。

instructions.go

gethと共通のプログラム。OPCODEの処理を実装しており、EVMを深く理解する上で重要なプログラム。

 

是非、仕様レベルでEVMが理解できるように突き詰めてみてください。

この記事が気に入ったら
いいね!しよう
最新情報をお届けします

この記事を書いた人:田篭 照博

田篭 照博

「堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門」「堅牢なシステム開発/運用を実現するための ビットコイン[技術]入門」などの著者である田篭照博氏によるコラム。日本で初めてブロックチェーン診断としてスマートコントラクトのセキュリティ診断サービスを開発・リリースしたブロックチェーンセキュリティの第一人者が最新のセキュリティと脅威動向を紹介します。

主要仮想通貨

国内人気取引所一覧