Develop with pleasure!

福岡でCloudとかBlockchainとか。

Segwitのアップグレードガイド

mainnetへのSegwitデプロイを有効にするBitcoin Core 0.13.1がリリースされ、それに合わせてSegwitをサポートしたい人、したくない人向けにアップグレードガイドも公開されてたので見てみる↓
malleabilityの問題や署名の前後でtxidが変わらないのはコントラクトを作る上でも必要な機能なので個人的にはSegwitのリリースは歓迎だけど、Segwitサポートしたくない人向けにも書いてるのが素晴らしい。

bitcoincore.org

Segwitのソフトフォークは、Segwitを採用するかどうか個人の決定にまかせていて、このガイドはSegwitを採用したい人、採用したくない人向けに両者に向けて書かれている。

ソフトフォークのアクティベーションに十分なマイナーの同意が得られたら、Segwitはアクティベーションされ、ユーザはsegregated witnessesなトランザクションを作ることができるようになる。Segwitのソフトフォークは一般的に使われているウォレットにとって前方・後方互換がある形で設計されており、ウォレットの開発者やユーザはそれぞれSegwitを採用するるか採用せずに今まで通りのトランザクションを作るか自由に決められる。

マイナー

このセクションはソロマイナーとマイニングプールのオペレータ向けに書かれている。プールマイナーはSegwitアップグレードするかしないかプールオペレータに問い合わせること。

2016年7月4日にアクティベートされたBIP 68/112/113と同様、BIP-9のソフトフォークのデプロイの仕組みがSegwitにも使われている。アップグレードするかしないか、アップグレードプロセスの重要な段階を理解する必要がある。

  • Started
    Segwitは2016年11月15日以降最初のretarget periodが始まる。これはアクティベートされるか、1年後までにlock-inステータスにならず失敗したと判断されるまで続く。この間、マイナーはブロックヘッダのversion bits 1 にフラグをセットすることでSegwitの新しいコンセンサスルールをサポートすることを表明することができる。
  • Locked-in
    2016ブロック内の95%のブロックがSegwitのサポートを表明すると、Segwitのソフトフォークはロックインされ、そこから2016ブロック(約2週間)後にアクティベートされる。
  • Activated
    ロックイン期間が終わると、マイナーは署名が分離されたトランザクションを含むsegwitスタイルのブロックを生成する。

アップグレードしない場合

startedフェーズでは、Segwitを採用したくない場合はBitcoin Core 0.13.1のようなSegwit互換のノードへのアップグレードをせずSegiwtのversion bits 1にフラグをセットするのを避ける。

もしソフトフォークがlocked-inフェーズに入ったら、すぐにアップグレードする必要は無いがノードのアップグレードを推奨する。Segwitのソフトフォークはsegwitスタイルのブロックの生成を強制するものではないので、今まで通りのブロックの生成を継続することができる。しかし、他のマイナー達の支持によりステータスがactivatedになると、あなたは有効と判断しているブロックもsegwitを支持する各ノードはリジェクトし、あなたの作ったブロックは無効であると判断される。

そのためsegwitがlocked-inになった後は、フルノードをBitcoin Core 0.13.1にアップグレードするか、後述するフルノードをアップグレードしない場合に記載されているようにsegwitのフィルタとしてBitcoin 0.13.1もしくはそれ以降のバージョンを使用することを推奨する。

アップグレードする場合

マイナーは2016年11月15日以降、BIP-9のルールに従ってSegwitのサポートを表明することができる。Segwitのサポートを表明するのは以下の作業が必要になる。

  • ブロックに含めるトランザクションの選択とブロックの構成をするのにBitcoin 0.13.1もしくはSegwit互換のフルノードにアップグレードする。
  • マイニングソフトウェアもしくはマイニングプールのソフトウェアをSegwit互換のバージョンにアップグレードする。
  • ブロックを生成する際に、BIP-9に従い、segwitのversion bits 1をセットする。

Segwitがアクティベートされると、Segwitスタイルのブロックのマイニング及びリレーができるようになる。

GetBlockTemplate (GBT)のRPCをサポートするソフトウェアは、BIP-9とBIP-145によるGBTの変更に対応するため必ずアップグレードする必要がある。

Segwitは既にtestnetではアクティベートされているため、testnetを使えば少ないハッシュレートでアップグレードしたインフラでマイニングのテストをすることができる。またBitcoin Core 0.13.1のregtestもデフォルトでSegwitをサポートしている。

ルノードユーザ

ルノードはBitcoinのコンセンサスルールに違反するブロックからユーザを保護する。Segwitのようなソフトフォークを使ったアップグレードでは、新しいルールが追加されても、アップグレードしていないノードはそのルールについて知らない。これは問題ではなく、Segwitのソフトフォークはアップグレードしていないユーザもソフトフォークの前と同じ方法で引き続きBitcoinを利用できるよう設計されている。

しかし、Segwitのソフトフォークで有効になった機能を使いたい人は、充分な数のフルノードユーザがSegwitのルールに違反するブロックやトランザクションを拒否するためノードをアップグレードしたか知りたいだろう。それによってマイナーにSegwitによって更新されたコンセンサスルールに従うインセンティブを提供する。

アップグレードしない場合

マイナーではなくSegwitのアップグレードをしたくない場合、単純に今のフルノードをそのまま使い続ければ良い。Segwitはソフトフォークで実装されているので、アップグレードする必要は無い。またフルノードに接続しているウォレットもアップグレードする必要はなく以前と変わらず利用できる。

しかし、承認数の少ない(1とか2の)トランザクションを受け入れる場合、ソフトフォークがアクティベートされた後は、アップグレードしていないノードが一時的に無効ブロックを受け入れる小さなリスクがあることに注意する必要がある(一部のアップグレードしていないマイナーが作ったブロックが中継されてくる可能性があるため)。その状況はアップグレードしたマイナーがSegwitのコンセンサスルールを継続して強制するため数ブロックで解消するが、無効ブロックで承認されたトランザクションが、その後の有効なブロックで承認されている保証は無い。

この問題を防ぐ一番簡単な方法は、 Bitcoin Core 0.13.1もしくはSegwit互換のフルノードにアップグレードすること。それでもアップグレードしたくない場合は、以下のように、古いBitcoin Coreのフィルタとして新しいBitcoin Coreを使うことができる。

https://bitcoincore.org/assets/images/filtering-by-upgraded-node.svg

この構成では、現在のBitcoin Coreのノード(古いノード)をBitcoin Core 0.13.1以降のノード(新しいノード)にのみ接続するよう設定する。新しいノードの方は通常通りBitcoinP2Pネットワークに接続する。新しいノードはSegwitのコンセンサスルールの変更の中身を知ってるので、古いノードによって作られた無効ブロックを中継することは無い。

この構成にする際は、Bitcoin Coreのデフォルトを利用している場合、古いノードはSegwitの機能を使ったトランザクションをブロックに入るまで見れないという点に注意する必要がある。

ノードの設定

新しいノードは、普通に起動してブロックチェーンを同期する。現時点でprunedノードは中継ノードとして使えないので、この構成ではprunedノードは使えない。古いノードを特別なノードとして扱うため、以下のパラメータを付けて起動する(もしくは設定ファイルに記述)。

  -whitebind=<addr>
       Bind to given address and whitelist peers connecting to it. Use
       [host]:port notation for IPv6

  -whitelist=<netmask>
       Whitelist peers connecting from the given netmask or IP address. Can be
       specified multiple times. Whitelisted peers cannot be DoS banned
       and their transactions are always relayed, even if they are
       already in the mempool, useful e.g. for a gateway

古いノードは新しいノードのブロックチェーンの同期が終わったら、以下のコマンドラインパラメータを付与して再起動する。

-connect=<新しいノードのIPもしくはDNS名>

これで古いノードは新しいノードにのみ接続され、全てのブロックとトランザクションは新しいノードでフィルタリングされる。

アップグレードする場合

Segwit互換にアップグレードするには、Bitcoin Core 0.13.1のようなSegwit互換のフルノードをダウンロードし、ダウンロードしたファイルが正しいものか(PGPなどで)確認し、旧バージョンのノードを停止し、新しいバージョンのノードを起動する。Segwitがアクティベートされた後にアップグレードした場合、(旧ノードではダウンロードしていないデータがあるため)アクティベーションされた時点からのブロックを再同期する必要があるので注意すること。

Bitcoin CoreのRPCgetblockchaininfoを使うとSegwitのソフトフォークの状況が確認できる。この情報から、最近のブロックのどれくらいがSegwitのコンセンサスルールを適用しようとしているかが分かる。またgetblockchaininfoの結果から、Segwitのソフトフォークがいつlocked-inactivatedになったのか分かる。

Bitcoin Core 0.13.1のウォレットは、デフォルトでBitcoinの支払いを受け取るため非SegwitのP2PKHのアドレスを生成する。今後のリリースでユーザがSegwitなアドレスで支払いを受け取るか選択できるようになることが期待されている。

もし開発者やエキスパートでテストのためにアドレスを生成したい場合はSegwitの開発ガイドを参照。

ウォレットのユーザ

アップグレードしない場合

Segwitにアップグレードしたくない場合は、Segwitサポートを追加していない任意のウォレットを使えば良い。アップグレードしなくても、Segwitにアップグレードしたユーザとも、アップグレードしてないユーザとも取引をすることはできる。

アップグレードしない場合に唯一違うのは、Segwitに対応したユーザからの支払いを受ける場合、そのトランザクションがブロックに含まれるまであなたのウォレットには表示されない。これはマイナーによって承認されるまで未確認の取引からあなたのウォレットを保護する安全機能である。

アップグレードする場合

Segwitにアップグレードしたい場合は、マイナーがSegwitをアクティベースするのを待ち、Segwitスタイルの支払いや受け取りをサポートするウォレットが必要になる。Bitcoin Coreのウォレットや軽量ウォレット、サードパーティが管理しているウォレット全般に言える。Bitcoin Coreもしくは別のフルノードのユーザは、上記のフルノードのセクション参照。

Segwitをサポートするウォレットにアップグレードしたら、Bitcoinの受け取りようのアドレスとして3で始まるアドレスが生成される(P2SHアドレス)。

一般的なウォレットはほとんどP2SHアドレスへの支払いができるため、他のユーザがSegwitにアップグレードしているかどうかに関係なく、P2SHを使って支払いを受け取ることができる。Segwitにアップグレード後にBitcoinの支払いをする場合、元々の1から始まるBitcoinアドレス(P2PKHアドレス)に支払いをすることができる。

Segwit対応のウォレットを使ってBitcoinを送ると、次のような点が分かる。

  • アップグレード前に受信したBitcoinを使う時に作られるトランザクションはSegwitのアップグレード前に作られるトランザクションとなんら変わりはない。
  • アップグレード後に受信したBitcoinをSegwitのアップグレードしていないユーザに送る場合、そのトランザクションはブロックに入れられるまで相手には表示されない。これはトランザクションがブロックに含まれるまで、このトランザクションを完全に理解できないウォレットにそのトランザクションを示すことを回避する安全機能である。トランザクションが承認されると受領したBitcoinが見れるようになりいつもどおりに使うことができる。
  • アップグレード後に新しいP2SHアドレスで受信したBitcoinを使う際、非SegwitなUTXOを使った時より若干手数料が安いことに気付くだろう。これは、署名が含まれているトランザクションの一部のデータにBitcoinのフルノードが迅速にアクセスする必要が無いため、Segwitによりマイナーは非witnessなデータの4倍のwitnessデータを保存できるようになっている。そのためブロック作成コストとの調整で手数料が安くなっている。

Bitcoinのソフトウェア開発者

全てのBitcoinのソフトウェアは以前と同様に動作するべきで、Segwitの新しい機能のアドバンテージを利用したい場合はソフトウェアをアップグレードする必要がある。

Segwitの開発者のためのドキュメントが↓

Segwitのアドレスフォーマットを定義したBIP-142のステータスはdeferredで、標準ではないので注意すること。代わりにbitcoin-devのMLで現在のBase58エンコードしたアドレスより使いやすい新しいBitcoinのアドレスフォーマットについて議論されている。

BIP141、143、144、および145のほとんど実装はBitcoin CoreのPR#8149で確認できる。また、BIP-147の実装はPR#8636

Segwit対応のテストは、testnetがSegwitのサポートをしており、ここ数ヶ月の間で多数のSegwitのブロックができている。またSegwitのブロックの最大サイズ(4MB)に近いサイズのブロックも作られている。Bitcoin Core 0.13.0と0.13.1のregtestもデフォルトでSegwitをサポートしている。

参考

参考までに関連するBIPを以前意訳したもの↓

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

所感

  • Segwit対応したBitcoin Coreのウォレットでは、受信用のアドレスはP2SHになるみたい(P2WPKHをネストしたP2SH)。
  • Segwitのアドレスを定義したBIP-142は結局実装に含まれてなく、支払いの受け取りアドレスもP2PKHアドレスを引き続き生成してるので、Segwitなアドレスが定義されるまではP2WPKHやP2WSHとかのトランザクションは少なく、基本はP2SHでネストしたアドレスが主流か?
  • まだブロックに含まれていないトランザクションも、そのトランザクションデータを受信すれば0 confirmationのUTXOとして認識できるけど、Segwit互換のノードにアップグレードしていない場合は、Segwitのトランザクションはブロックに入れられるまで認識できなくなると。
    0 confirmationで決済してる事業者とかは事前にSegwitへのアップグレードが必須。
  • Segwitのリリースだけじゃなく、BIP-147も一緒にデプロイされると。

マルチシグ検証の際のmalleabilityを解消するNULLDUMMYルール(BIP-147)

Segwitと一緒にデプロイされるBIP-147を見てみる。

bips/bip-0147.mediawiki at master · bitcoin/bips · GitHub

このBIPでは、OP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYで使われる余計なスタック要素を使ったmalleabilityを解消するため、Bitcoinトランザクションの検証ルールの変更を提案している。

動機

署名を使ったmalleabilityは、作られた署名のベースとなる秘密鍵へアクセスすることなく、ネットワーク上の任意の中継ノードがトランザクションの署名の改変をする能力によるものである。Segwitによる署名が分離されていないトランザクションでは、このmalleabilityを利用してtxidを変更し、任意の未承認の子トランザクションが無効にすることができる。BIP-141のSegregated Witnessにより第三者によるtxidの変更はできなくなるが、分離された署名を含むwtxidを変更することで、BIP-152のCompact Blockを使った中継の効率を低下させることができる。

OP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYにおける設計上の欠陥は、署名検証の後に余分なスタック要素("ダミー要素")を使用することにある。このダミー要素は特に検証されないので任意の値に置き換えることができる。このドキュメントではこの署名のmalleabilityを解消するための新しいルールを提案する。

仕様

ダミー要素を利用したmalleabilityを解消するため、新しいコンセンサスルールNULLDUMMYを追加する。このルールはダミー要素は必ず空のバイト配列でないといけないというもの。空のバイト配列以外の場合、スクリプトはすぐにfalseと判定する。NULLDUMMYのルールは、BIP-141で説明されているpre-segregatedスクリプトとpay-to-witness-script-hashのスクリプト内のOP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYに適用される。

デプロイメント

このBIPはBIP-9のversion bitを使ってデプロイされる。その際使われるデプロイのパラメータはBIP-141、BIP-143と同じ"segwit"で、使用するbitは1。

mainnetでは、starttimeは2016年11月15日の深夜(Epociタイムは1479168000)でtimeoutは1年後。

testnetでは、starttimeは2016年05月01日の深夜(Epociタイムは1462060800)でtimeoutは1年後。

互換性

参照クライアントは最初から互換性のある署名を生成しており、NULLDUMMYルールは v0.10.0以降の参照クライアントの中継ポリシーとして施行された。そのため2015年8月以降、要件に違反するトランザクションはチェーンに追加されていない。

実際に使われている全てのscriptPubkeyタイプについて、非準拠の署名もちょっとした変換で準拠したものになるため、この要件によって機能が失われることは無い。特殊なスクリプトを設計する際は、ユーザはこの新しいルールに注意をはらう必要がある。

実装

参照クライアントの実装は以下のプルリクを参照。

github.com

謝辞

Peter ToddがNULLDUMMYの原作者で、この文書はPieter WuilleによってBIP-62から抽出された。

参考

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

Bitcoin Core自体は以前から対応してたみたいなので、特に新要素という感じでは無いかなー。

コントラクトの関数で例外を投げる

Solidityで作成するコントラクトの関数から例外を投げる方法について。

http://solidity.readthedocs.io/en/develop/control-structures.html#exceptions

コントラクトの関数内で手動で例外を投げたい場合throwを使う。例外が投げられると現在実行中の処理が停止し、状態や残高に関する全ての変更は取り消され元に戻る。

ちなみに発生した例外はキャッチすることはまだできない。

throwの例↓

pragma solidity ^0.4.0;

contract Sharer {
    function sendHalf(address addr) payable returns (uint balance) {
        if (!addr.send(msg.value / 2))
            throw; // Sharerへの転送を元に戻す
        return this.balance;
    }
}

Solidityで自動的に例外が発生するケース

以下のケースでは、自動的に例外が発生する

  • 配列にアクセスする際のインデックの値が大きすぎるかマイナスの値の場合
  • 固定長のbytesNに大きすぎるもしくはマイナスのインデックスでアクセスした場合
  • メッセージ呼び出しを使って関数を呼び出したが(gas不足や合致する関数が無い、その関数自体が例外を投げるなど)その関数が正常に終了しない場合。ただし、callsenddelegatecallといった低レベルの操作の場合は例外は発生せず、falseを返すことにより障害が発生したことを示す。
  • 新しいキーワードを使ってコントラクトを作成したがそのコントラクトが正しく作成されない場合(前述のような理由で)。
  • 0を使った除算、剰余を行った場合(5 / 0とか23 % 0とか)
  • 何のコードも含まれていないをターゲットにした外部関数呼び出しを実行した場合
  • payable修飾子が付いていない関数を介してetherを受け取った場合
  • コントラクトがpublicなアクセッサ関数を介してetherを受け取った場合

内部的には、Solidityは例外が発生するとinvalid jumpを実行することで、EVMが全ての変更を元に戻している。

実際に例外を発生させたトランザクションデバッグトレースを見ると

...
{
      depth: 1,
      error: "invalid jump destination (PUSH1) 2",
      gas: 2977807,
      gasCost: 8,
      memory: ["0000000000000000000000000bcfa0dd51b7be1081cd1b82b2e3cb17f7fa1d56", "a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb51", "0000000000000000000000000000000000000000000000000000000000000060"],
      op: "JUMP",
      pc: 970,
      stack: ["000000000000000000000000000000000000000000000000000000004b5f4b1d", "00000000000000000000000000000000000000000000000000000000000000f1", "0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000000000000000000000000000000001f4", "a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "61f469589cff8db864e34dcbe0959d5c4658d7fc34600773b9ffb8ae1321930e"],
      storage: {}
  }]
}

invalid jumpで終わってるのが分かる。

Rubyで書かれたテスティングフレームワークtethでコントラクトをテストする

Ethereum使ったアプリケーションをRubyで書けないかなーと思ってRubyのライブラリを調べてみた。

EthereumのRubyライブラリ

公式ドキュメントで紹介されているのは以下の3つ。

ruby-ethereum

Rubyで書かれたEVM(Ethereum Virtual Machine)の実装

github.com

ruby-serpent

EthereumのSerpent*1コンパイラへのRubyバインディングライブラリ

github.com

ethereum-ruby

EthereumのノードへのJSON-RPCをラップしたライブラリ

github.com

teth

またその他にtethというrubyで書かれたテスティングフレームワークがある。

Solidityのコントラクトのテストをgethもしくはruby EVMで実行することができるみたい。
ruby EVMは↑のruby-ethereum。

tethのセットアップ

コントラクトの記述もTDDでということで、minitestベースのテストが書けるtethを使ってみる。

libserpent.soのビルド

tethの依存ライブラリにserpentというgemがあり、libserpent.soが無いとそのgemのビルドに失敗するので、事前にserpentをビルドする。

$ git clone git@github.com:ethereum/serpent.git
..
$ cd serpent
$ make
...
$ sudo make install
cp serpent /usr/local/bin
cp libserpent.a /usr/local/lib
cp libserpent.so /usr/local/lib
rm -rf /usr/local/include/libserpent
mkdir -p /usr/local/include/libserpent
cp bignum.h util.h keccak-tiny-wrapper.h tokenize.h lllparser.h parser.h opcodes.h functions.h optimize.h rewriteutils.h preprocess.h rewriter.h compiler.h funcs.h /usr/local/include/libserpent

ruby-bitcoin-secp256k1のセットアップ

tethはruby-bitcoin-secp256k1にも依存しており、ruby-bitcoin-secp256k1はlibsecp256k1に依存している。libsecp256k1はBitcoin Coreが独自実装した楕円曲線暗号のCライブラリの最適化実装。

そのためruby-bitcoin-secp256k1をクローンしてlibsecp256k1をビルドする。

$ git clone git@github.com:cryptape/ruby-bitcoin-secp256k1.git
...
$ ruby-bitcoin-secp256k1 
$ git submodule update --init --recursive
...
$ ./install_lib.sh
...
途中でsudoのパスワード聞かれるので入力
...

実行が終わると/usr/local/lib/libsecp256k1.soができてる。あとは生成されたライブラリをちゃんと読みこむようsudo ldconfigしておく。

Bundleインストール

あとはGemfileに

gem 'teth'

と書いてbundleすれば良い。

expectのインストール

tethのスクリプトが内部でexpectを使っているので、expectがインストールされていない場合はインストールしておく。aptやyumでインストールできる。

tethの使い方

プロジェクトの作成

newコマンドでSmart Contractアプリケーションを新規作成し、プロジェクトのテンプレートが作成される。

$ teth n sample
Creating project tethsample...
Resolving dependencies...
Using ffi 1.9.10
Using little-plugger 1.1.4
Using multi_json 1.11.2
Using digest-sha3 1.1.0
Using ethash 0.2.0
Using fiddler-rb 0.1.2
Using lru_redux 1.1.0
Using minitest 5.8.4
Using rlp 0.7.3
Using serpent 0.3.0
Using bundler 1.11.2
Using bitcoin-secp256k1 0.4.0
Using logging 2.1.0
Using leveldb 0.1.9
Using block_logger 0.1.2
Using ruby-ethereum 0.9.6
Using teth 0.2.2
Bundle complete! 1 Gemfile dependency, 17 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
Done.

そうすると以下のようなディレクトリ/ファイルが生成される。

f:id:techmedia-think:20161012154255p:plain

コントラクトの新規作成

generateコマンドでコントラクトを新規作成する。

$ teth g token
Creating Token contract file...
Create Token.sol contract file...
Creating token test files...
Done.

コントラクトのファイルとしてcontracts/Token.solが、テストファイルとしてgtests/Token_test.jstests/token_test.rbが生成される。

README.mdに記載されているサンプルのようにコントラクトとテストを実装する。

テストの実行

testコマンドで対象のコントラクトのテストを実行する。この際のテストはrubyのEVMで実行される。

$ teth t token
Test Token contract...
Run options: --seed 44085

# Running:

....

Finished in 1.966287s, 2.0343 runs/s, 3.5600 assertions/s.

4 runs, 7 assertions, 0 failures, 0 errors, 0 skips
Done.

gethと連携したプライベートチェーンでのテスト

testコマンドはrubyのEVMで実行したが、続いてgethを使ってテストを実行する。

プライベートチェーンの初期化

initコマンドを使うと新しいgenesis blockを初期化し、プライベートチェーンの新しいデータディレクトリを作成する。

$ teth init
Initialising a new genesis block...
***** Using geth at: geth
I1012 16:55:00.860131 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to /home/azuchi/projects/ethereum/tethsample/data/chaindata
I1012 16:55:00.870799 cmd/geth/main.go:353] successfully wrote genesis block and/or chain rule set: 611596e7979cd4e7ca1531260fa706093a5492ecbdf58f20a39545397e424d04

data/chaindataディレクトリが新規に作られる。

gethへキーのインポート

import_keysコマンドを実行するとprivate_keysディレクトリ内の*.keyファイルがプライベートチェーンにインポートされる。

$ teth ik
Importing keys, this will take a while, please be patient...
***** Using geth at: geth
***** Import all pre-funded private keys
Notice: No need to input your password. The default password is 123456
spawn geth --datadir data account import ./private_keys/9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat passphrase: 
Address: {9da26fc2e1d6ad9fdd46138906b0104ae68a65d8}
Notice: No need to input your password. The default password is 123456
spawn geth --datadir data account import ./private_keys/81063419f13cab5ac090cd8329d8fff9feead4a0.key
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat passphrase: 
Address: {81063419f13cab5ac090cd8329d8fff9feead4a0}
Notice: No need to input your password. The default password is 123456
spawn geth --datadir data account import ./private_keys/3ae88fe370c39384fc16da2c9e768cf5d2495b48.key
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat passphrase: 
Address: {3ae88fe370c39384fc16da2c9e768cf5d2495b48}
***** Done.
コントラクトのビルド

buildコマンドでコントラクトをビルドする。(コントラクト名を省略した場合は全てのコントラクトがビルドされる)

$ teth build token 
Building contract Token

======= Token =======
Gas estimation:
construction:
   20201 + 73400 = 93601
external:
   issue(address,uint256):  21716
   transfer(address,uint256):   42187
   getBalance(address): 318
internal:


-------------------------------------
Enter Gas: 
400000
Enter Value To Be Transferred: 

Enter Input: 

Done.
gethの起動

serverコマンドでgeth serverを起動

$ teth server
***** Using geth at: geth
Start geth server...
I1012 17:32:18.222462 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to data/chaindata
I1012 17:32:18.233830 ethdb/database.go:169] closed db:data/chaindata
I1012 17:32:18.235163 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to data/chaindata
I1012 17:32:18.277175 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to data/dapp
I1012 17:32:18.281127 eth/backend.go:170] Protocol Versions: [63 62 61], Network Id: 31415926
I1012 17:32:18.281287 eth/backend.go:199] Blockchain DB Version: 3
I1012 17:32:18.281863 core/blockchain.go:206] Last header: #0 [611596e7…] TD=131072
I1012 17:32:18.281887 core/blockchain.go:207] Last block: #0 [611596e7…] TD=131072
I1012 17:32:18.281899 core/blockchain.go:208] Fast block: #0 [611596e7…] TD=131072
I1012 17:32:18.282866 p2p/server.go:311] Starting Server
I1012 17:32:18.283063 p2p/server.go:554] Listening on [::]:30303
I1012 17:32:18.284900 node/node.go:298] IPC endpoint opened: data/geth.ipc
I1012 17:32:18.285096 node/node.go:368] HTTP endpoint opened: http://localhost:8545
I1012 17:32:19.556940 cmd/geth/accountcmd.go:187] Unlocked account 3ae88fe370c39384fc16da2c9e768cf5d2495b48
デプロイ

migrateコマンドでコントラクトをgethにデプロイする。(コントラクト名を省略した場合は全てのコントラクトがデプロイされる)

$ teth m
Migrating all contracts
***** Using geth at: geth
null
Contract transaction send: TransactionHash: 0xc8cba6ded9720d2db8c69551bf1ee1ddee1fe2c7b82a7adda1442da4d3362e1a waiting to be mined...
Compiled Object : TokenCompiled
Contract : TokenContract
Contract Instance : Token
true
Contract mined! Address: 0x3ff58e2e4f9ab2d47f0cd84bead42dd0369846c9
Done.

アカウントがロックされている場合はエラーが発生するので、teth consoleでgethのコンソールにアクセスしアカウントをアンロックする。(初期パスワードはimport.shに書いてあるが123456)

2つのファイルが生成され、1つはabiとアドレスを保持するtemp/db/Token.jsonで、もう1つがtemp/migrations/Token.js

javascriptのテストを書く

gtests以下にgethの環境で実行するテストを記述する。↓はgtests/Token_test.js

loadScript('temp/migrations/Token.js');

var balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] })

console.log("balance is: ", balance);

Token.issue.sendTransaction(web3.eth.accounts[0], 10000, { from: web3.eth.accounts[0] }, function(err, tx){
  if(err){
    console.log("issue error!");
  } else {
    console.log("issue success. tx: ", tx);
  }
})

miner.start();admin.sleepBlocks(2);miner.stop();

balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] })

console.log("balance is: ", balance);

作成したテストはgtestコマンドで実行する。

$ teth gt
***** Using geth at: geth
Testing all contracts on geth...
balance is:  0
issue success. tx:  0x4fb32aaf2c9272478dbb549374349654b3785eee3cef8d671603efb54ee62e95
balance is:  10000
true
Done.
gethのコンソールアクセス

consoleコマンドでgethのコンソールにアタッチできる。

$ teth console
***** Using geth at: geth
Starting geth attach...
instance: Geth/v1.5.0-unstable/linux/go1.5.1
coinbase: 0x9da26fc2e1d6ad9fdd46138906b0104ae68a65d8
at block: 13 (Wed, 12 Oct 2016 18:16:38 JST)
 datadir: data

rubyのEVMでサクッとテストが実行できるのは便利だなー。

*1:SerpentはContractを記述する言語の一つでPythonライクな言語。

witnessデータを含むブロックのwitness rootとcommitment hashの計算

Segwitの導入にあたってブロック作成時にwitness トランザクションの署名が確かにブロックに含まれていることを保証するために新しいCommitment構造が導入されている。

techmedia-think.hatenablog.com

コインベーストランザクションに追加されたCommitmentのデータ構造

ブロック作成時に作られるコインベーストランザクションにそのCommitmentデータが新しく追加されているので、実際にどう変わったのか見てみる。

↓がtestnetで確認できたwitnessなトランザクションを含むブロックのコインベーストランザクション

{
  "hex": "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3603cbd20e00048681fc5704bacfd5370cc69bfb57b25c0000000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542fffffffff02c0eba012000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9ed670436c55de638c8100326d72998157a61aab2af1a8d4c5785f9093134b78e330120000000000000000000000000000000000000000000000000000000000000000000000000",
  "txid": "f5b1b47c99ab40fa765582dae0f4346eecbe9b77ce99c66bc765378d315441f3",
  "hash": "944c9305710c116ddccec64762ed642315e48a7c279c36e11756b74c8c5984e7",
  "size": 222,
  "vsize": 195,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "coinbase": "03cbd20e00048681fc5704bacfd5370cc69bfb57b25c0000000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542f",
      "txinwitness": [
        "0000000000000000000000000000000000000000000000000000000000000000"
      ],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 3.12536000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d6 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mss5NFyX96ix4erFMamR1gK3SsvUSMWcjE"
        ]
      }
    }, 
    {
      "value": 0.00000000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_RETURN aa21a9ed670436c55de638c8100326d72998157a61aab2af1a8d4c5785f9093134b78e33",
        "hex": "6a24aa21a9ed670436c55de638c8100326d72998157a61aab2af1a8d4c5785f9093134b78e33",
        "type": "nulldata"
      }
    }
  ],
  "blockhash": "000000000000002b2fcf202c54a21cf792a66592a4f8b655b026f5e3d419d656",
  "confirmations": 15,
  "time": 1476170550,
  "blocktime": 1476170550
}

Commitmentの追加

出力を見ると今までのコインベーストランザクションには無かったOP_RETURNを含む出力が追加されているのが分かる。Segwitでは新たにcommitment用のデータフィールドを設けるのではなく、OP_RETURNを使うことでcommitmentのデータ領域を確保している。

"asm": "OP_RETURN aa21a9ed670436c55de638c8100326d72998157a61aab2af1a8d4c5785f9093134b78e33",
"hex": "6a24aa21a9ed670436c55de638c8100326d72998157a61aab2af1a8d4c5785f9093134b78e33",

このcommitmentデータは以下のような内容で構成されている。

バイト数 内容
1 0x6a OP_RETURN
1 0x24 36バイト(0x24)のデータを続いてプッシュする
4 0xaa21a9ed commitmentヘッダ
32 67043...78e33 commitment hash=Double-SHA256(witness root hash|witness reserved value)
39バイト目以降 コンセンサスとは関係ない任意のデータ(オプション)

witnessなトランザクションの署名がブロックチェーンに記録されていることを確認するためブロック内の全てのwitnessを含むデータからマークルツリーが作られていて、そのマークルルートがwitness rootになり、commitment hashの計算に使われている。
(従来のトランザクションのマークルツリーはtxidを元に計算されているのでwitnessデータは含まれない)

※ ブロック内のトランザクションが全てwitnessなトランザクションではない場合、commitmentはオプションとなる。

では、実際witness rootとコミットメントハッシュはどのように計算されているのか?

witness rootとcommitment hashの計算

witness rootはwtxid*1のデータから計算されている

Bitcoin Coreで実際にwitness rootを計算しているのは、merkle.cppの↓の部分

uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated)
{
    std::vector<uint256> leaves;
    leaves.resize(block.vtx.size());
    leaves[0].SetNull(); // The witness hash of the coinbase is 0.
    for (size_t s = 1; s < block.vtx.size(); s++) {
        leaves[s] = block.vtx[s].GetWitnessHash();
    }
    return ComputeMerkleRoot(leaves, mutated);
}

コインベースの場合はそれ自身が有効なwitnessデータを持たないので、コインベースのwtxidはwitness reserved valueの値で構成され、現在は0x00...00となる。

こうして算出したwitness rootwitness reserved valueを付加してSHA256ダブルハッシュしたのがcommitment hashになる。

bitcoin-rubyで↑のコインベーストランザクションを含むブロックのwitness rootcommitment hashを計算したのが↓

witness rootを計算する際にwtxidのエンディアンを変換しておく必要がある。

実行すると

witness_root = 50187168cf7fb4fa39db8fadefb032fb28e109bf22732a589144f108276945f4
commitment = 670436c55de638c8100326d72998157a61aab2af1a8d4c5785f9093134b78e33

となり、コインベーストランザクションcommitment hashと同じ値になっているのが分かる。

*1:従来のtxidの計算にはwitnessデータは含まれないのに対し、witnessデータを含めて計算したハッシュ値。具体的な計算内容はBIP-144内で定義。