Develop with pleasure!

福岡でCloudとかBlockchainとか。

TREZOR導入してみた

TREZOR本体に付属のUSBケーブル使ってPCに接続する。

繋いだらブラウザ開いて、trezor.io/start にアクセス。

ページ下部で、TRZORのセキュリティニュースなどを配信してるみたいなので、メールアドレスを登録。

続いてTREZORウォレットのセットアップ。「Install Extension」を選択してChrome Extensionをインストールする。インストールを実行するとTREZORのファームウェアのアップデートが始まる。

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

続いてTREZOR側でアップデートが成功した旨メッセージが出るので、PCからTREZOR外して再度接続する。そうするとPCのブラウザでセットアップが始まる↓

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

バイス名を入力すると、続いてPINの設定を求められる↓

f:id:techmedia-think:20171201215226p:plain:w300

PC上に表示されているマスと同じ形のマスがTREZOR上に表示されそこには1〜9の数字が表示されているので、PINに設定する数値をTREZOR上のマスの数値を見ながら、PC上のマスを選択して設定する。

PINの設定が終わるとHDウォレットのマスタードシードの元になる24個のワードリストがTREZOR上に表示されるので、各ワードをメモっていく。24個のワードリストが表示されると、確認用にもういちどワードリストが表示される。それが終わればセットアップは完了。

受信用のタブを開いてみると受信用のアドレスが表示されているが、そのアドレスの導出パスはm/49'/0'/0'/0/0と記載されていて、SegwitのP2WPKHをP2SHでネストした3から始まるP2SHアドレスになってる。

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

BIP-49のアドレス導出の仕様は↓

techmedia-think.hatenablog.com

折角なのでbitcoinrbで、↑のアドレスを導出してみた↓

require 'bitcoin'

Bitcoin.chain_params = :mainnet

words = %w(↑のTREZORの24個のワードリスト).map(&:downcase)

m = Bitcoin::Mnemonic.new('english')

# シードを生成
seed = m.to_seed(words)

# マスターキーを生成
master_key = Bitcoin::ExtKey.generate_master(seed)

# m/49'/0'/0'/0/0 のパスの公開鍵を導出
pubkey = master_key.derive(2**31 + 49).derive(2**31).derive(2**31).derive(0).derive(0).pub

# 公開鍵からP2WPKHのスクリプト作ってそれをネストしたP2SHのアドレスを生成
bip49adrr = Bitcoin::Script.to_p2wpkh(Bitcoin.hash160(pubkey)).to_p2sh.to_addr

↑はアドレスの導出だけど、秘密鍵も導出できてるのでそれ使えばトランザクションの署名も可能。ただ秘密鍵導出してもプログラムで署名するとあまりTREZOR使ってる意味ないので、↓みたいに拡張公開鍵を導出して持っておくとそこから、受信用のアドレスを随時導出していくことは可能。

# m/49'/0'/0'/0 の拡張公開鍵
ext_pubkey = master_key.derive(2**31 + 49).derive(2**31).derive(2**31).derive(0).ext_pubkey

# 拡張公開鍵があれば、
xpub = ext_pubkey.to_base58
-> 'xpub...'

# 拡張公開鍵があればそこから子公開鍵を導出
ext_pubkey = Bitcoin::ExtPubkey.from_base58(xpub)
ext_pubkey.derive(1)
ext_pubkey.derive(2)

任意のデータに対する署名検証を行うOP_DATASIGVERIFY(BUIP-78)

OP_GROUPに加えてBitcoin Cashの中期開発計画に含まれているのが、Bitcoinトランザクションの署名検証の際に使われるECDSAと同じアルゴリズムを使って、任意のデータの署名を検証するOP_DATASIGVERIFYという新しいopcode。

https://github.com/BitcoinUnlimited/BUIP/blob/master/078.mediawiki

通常BitcoinスクリプトではOP_CHECKSIGを使って署名の検証をするけど、この時検証に使われるデータはあくまでトランザクションのデータ。この署名検証においてトランザクションに限らず任意のデータに対して署名検証できるようにするスクリプトOP_DATASIGVERIFYというわけだ。

任意のデータについて署名検証できると何が嬉しいの?という話だが、外部の情報をトランザクションに持ち込むオラクルを想定しているようだ。

相変わらずBUIP単体ではプロトコルの内容が分からないので、Andrew Stoneのブログ記事を読もう↓

medium.com

Bitcoinというのはプログラマブルなお金なので、シンプルなプログラマブルマネーということで考えると以下のようなifを使った条件が考えられる。

if (condition) then pay A else pay B

この時重要になるのが、条件を評価するところ、特に外部データをインプットして条件評価をする部分だ。

基本的にBitcoinもEthereumもスクリプトサンドボックス環境で動作するようになっているため、スクリプトを評価する際にスクリプトが外部API叩いて値を取得するといったことはできない。まぁAPIを叩いたところでそれが正しいデータという保証は無いし、中間者攻撃のようなリスクも考えられる。

そのため外部データをブロックチェーンに取り込む場合、オラクルと呼ばれる信頼できるエンティティが登場する。オラクルは現実世界のデータをBitcoinスクリプトで利用可能な形式に変換して提供する存在だ。もちろんオラクルが嘘を付けば破綻するのでオラクルへの信頼が必要になる。極力このオラクルへの信頼を取り除いていく仕組みやインセンティブ設計がいろいろと研究されており、Discreet Log Contractsなんかもそういった点が設計されている↓

techmedia-think.hatenablog.com

ちなみにこのBUIP自体ではオラクルへの信頼の問題を解消する方法については特に触れてはいない。

Andrew Stoneのブログでは株価などのデータを提供するオラクルサービスを仮定し、単純なif文を使ってバイナリオプションがサンプルのユースケースとして挙げて、OP_DATASIGVERIFYの使い方を説明している。

オラクルのデータ提供方法

OP_RETURNの利用

データを提供するオラクルは、OP_RETURNを使ってデータをブロックチェーンに記録できるが、他のスクリプトがそのデータを参照する方法は無い。そこで新しいopcodedata = OP_GET_DATA(address)を追加する。このopcodeはaddressによって署名されたブロックチェーンの履歴を調べ、そのOP_RETURNのデータをスタックにプッシュする。そのアドレスで署名されているトランザクションが無い場合、opcodeは失敗して終了する。そのUTXOは現在使用不可能となる。まだオラクルが嘘をつくという問題はあるが、オラクルだけがそのアドレスのお金を使える(そのアドレスで署名できる)ため、それが提供元(オラクルよる)データであるという真正性は確認できる。

この方法の問題点は、オラクルがたくさんのトランザクションで情報をブロックチェーンに記録しても、それを使用するユーザーがおらず未使用に終わる可能性がある点だ。

OP_DATASIGVERIFYの利用

より現実的なオプションは、オラクルがバイナリオプションを使用するスクリプトの一部として使用できる署名付きにステートメントを個別に公開する方法だ。しかし、これはスクリプトがそのステートメントの真正性を検証しなければならないことを意味する。Bitcoinは簡単に署名を検証できるが、CHECKSIGopcodeはトランザクションデータへの署名のみを検証し、任意のデータの署名は検証できない。そこで

data = OP_DATASIGVERIFY(signature, data, pubkeyhash)

というopcodeを追加する必要がある。

このopcodeはトランザクションではなくスタック上のデータに対してOP_CHECKSIGVERIFYを行う。具体的には↓

  1. スタックからトップ3アイテム(公開鍵ハッシュ、署名、任意のデータ)をポップする。
  2. 署名が任意のデータに対する有効な署名か検証する。
  3. 署名の公開鍵のハッシュが公開鍵ハッシュと一致するか検証する。

この検証のいずれかで失敗するとトランザクションの検証は失敗する。検証が終わるとデータはスタックにプッシュバックされ残りのスクリプトで使われる。

オラクルが署名したデータが、1日の株価の中央値だったとする(証券コードと日付も含めて、データを一意にする)。このメタデータの検証をブロックチェーン上で行う場合、データを証券名、日付、金額の3つのパートに分け独立して評価されるよう、文字列を操作するopcodeを再度有効化する必要がある。

バイナリオプションのスクリプト構成例

とりあえずデータの一意性(証券名とか日付とか)を無視してバイナリオプションスクリプトを作成すると、バイナリオプションに勝ったユーザーが資金を入手する際のscriptSigは次のようなスタックを構成する。

オラクルの署名
オラクルのデータ
勝者の公開鍵 (標準のP2PKHの場合)
勝者のトランザクションの署名 (標準のP2PKHの場合)

参照されるscriptPubkeyは以下のような構成になる。

# オラクルの公開鍵をスタックにプッシュ
OP_PUSHDATA(オラクルの公開鍵ハッシュ)

# 上記のスクリプトのデータと署名を検証
OP_DATASIGVERIFY

# 検証が成功したら、オラクルのデータはスタック上に残り、バイナリオプションのストライクプライスをスタックにプッシュ
OP_PUSHDATA(バイナリオプションのストライクプライス)

# プッシュしたストライクプライスとオラクルのデータを比較
OP_LESSTHAN
# データの比較結果によって、参加者のいずれかへの支払いを許可する
OP_IF
  # 通常のP2PKHトランザクションの始まり
  OP_DUP  # 勝者の公開鍵をコピーする
  OP_HASH160 # 公開鍵をアドレスに変換
  OP_DATA(参加者Aのアドレス)
OP_ELSE
  OP_DUP
  OP_HASH160
  OP_DATA(参加者Bのアドレス)
OP_ENDIF
# 通常のP2PKHトランザクション終わり
OP_EQUALVERIFY # アドレスをハッシュされた公開鍵と比較
OP_CHECKSIGVERIFY # トランザクションと署名が一致するか検証

↑は単純な条件分岐だけど、MASTなんかと組み合わせるとより複雑な条件構成を持ち秘匿性のあるスクリプトにもできそう。

所感

  • 任意のデータについての署名検証ができると確かにコントラクトの応用は広がりそう。そういうユースケースが増えるとオラクルへの信頼問題の研究も進むかもね。
  • でもOP_GET_DATAブロックチェーン上のデータ検索してOP_RETURN探すとか流石に無謀だろう…。
  • ElementsやScaling BitcoinBitcoin Script 2.0なんかでも言われているけど、現在無効化されている文字列やビット列を操作するopcodeについては再有効化を求める声が多い。

Bitcoin Cash上でネイティブにカラードコインをサポートするOP_GROUP

Bitcoin CashブロックチェーンでBCH以外にネイティブにカラードコインをサポートするためにOP_GROUPという新しいopcodeが提案されている↓

https://github.com/BitcoinUnlimited/BUIP/blob/master/077.mediawiki

OP_GROUPとは?

OP_GROUPBitcoinスクリプト言語に追加する単一のopcodeで、ブロックチェーン上でトークン(カラードコイン)を効果的に表現する。OP_GROUPは既存のカラードコインの提案および実装とは異なり、通常のトランザクションの検証の一環でマイナーがカラードコインを検証する。これはビットコイン(BCH)と同様、無効なカラードコインのトランザクションブロックチェーンに現れないことを意味する(他のほとんどのカラードコインの提案はカラードコインのプロトコルとして無効なトランザクションブロックチェーン上に現れるため全てのクライアントがカラードコインのtxを検証する)。さらにこのOP_GROUPの提案では、全てのUTXOにカラーを明示する。これらの機能を一緒に追加することで以下が可能になる。

  1. ユーザーが誤ってカラードコインを通常のビットコインとして使用することがなくなる。
  2. SPVウォレットはビットコインと同じ容易さとセキュリティモデルでカラーリングされたコインを処理できる。

OP_GROUPの初期バージョンでは、他のカラードコインがサポートしている高度な機能の一部は含まれていないが、(SPVウォレットで有効など)カラードコインに必要な主要な機能を含んでいると思われる。この機能は他のカラードコインの提案にはない。

というのがBUIPに書かれてる内容だが、これだけだとどうやって実現するのか全く分からないので、リンクされている↓の記事を見てみる。(本筋とは関係ないけど、プロトコルの内容はちゃんとBUIP内に書こうよ。。)

medium.com https://www.yours.org/content/colored-coins-in-bitcoin-cash-b26804e05964/

OP_GROUPの挙動

スクリプト内にOP_GROUPが現れると、スタックの一番上のデータがグループ識別子として使われ、インプット/アウトプットの値がそのグループに関連付けられた整数に加算/減算される。この整数はgroup balanceと呼ばれる。OP_GROUPが無い場合、そのインプット/アウトプットはデフォルトのbitcoin cashグループとして扱われる。トランザクションの処理の最後に、bitcoin cashグループを除く各グループのバランスは0にならなければならない(bitcoin cashグループのインプットは手数料分アウトプットより多いためバランスは0にはならない)。言い換えると、全カラードコイングループ内で、satoshiのインプットとアウトプットのバランスは一致する。

通常のP2PKHのスクリプトは以下のような形式だが

OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

カラーリングされたアウトプットの公開鍵ハッシュへの支払スクリプトは以下のような形式になる。

<グループ識別子> OP_GROUP OP_DROP OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

OP_DUP以降はP2PKHと同じで、先頭にグループ識別子とOP_GROUPOP_DROPが付加されている。グループ識別子がscriptPubkey内にあるため、SPVウォレットでもUTXOがどのグループなのかを識別できるという仕組みだ。OP_GROUP命令はスタックの頂上からグループ識別子をポップしないので、その後のOP_DROPが必要になる。これは不便だが、アップグレードされていないクライアントはOP_GROUPをno-opとして解釈するため、OP_GROUPを理解しないクライアントでもトランザクションを有効と判断するようになる。このためこのスクリプトの変更でハードフォークは必要ない。

また今のところグループ識別子のデータフォーマットはまだ定義されてないみたい。

カラードコイントークンの作成と破壊

カラーリングされたコインを持つトランザクションのインプットとアウトプットのバランスは0になるということだったが、2つだけ例外がある。カラードコインのトークンを発行するmintと破壊するburnの場合だ。

mint トランザクション

以下のようなscriptPubkeyを持つインプットと

OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

以下のscriptPubkeyを持つアウトプット

<グループ識別子> OP_GROUP OP_DROP OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

が含まれるトランザクションを考える。インプットにはOP_GROUPがないため、通常ではグループのバランスが取れておらずトランザクションの検証に失敗するが、mintトランザクションであれば例外となる。アウトプット内のOP_GROUPのグループ識別子がインプットのデータ要素と一致する場合、mintトランザクションであると認識され、このインプットの値はアウトプット内のグループ識別子のグループの値として扱われる。

インプットのデータ要素というのはまだプロトコルで正式に定められている訳ではないようだが、アドレスやアドレスの元となる公開鍵などを利用する模様。公開鍵やそこから派生したアドレスを利用することで、その公開鍵に対応する秘密鍵を持つユーザーのみが同じ識別子のコインを作り出すことができる。これは既存のカラードコインのプロトコルでもよく採用されているアプローチだ。

最初のmintトランザクションはオプションでOP_RETURNを含めることができ、これには人間が識別できるグループの短い名前やグループの詳細について説明したドキュメント/コントラクトのハッシュが記録されている。

ちなみに↑のインプット1つカラーリングされたアウトプット1つのmintトランザクションだと手数料が設定できないので、実際には手数料分のインプットが追加されることになる。

↑のルールの場合、発行者は同じ公開鍵を使えば同じグループ識別子のコインを何度でも追加発行できる。そのため追加発行を許可しないようなコインの発行をどう制御するかは検討が必要。

burn トランザクション

burnトランザクションはカラーリングされたコインを通常のBitcoin Cashに戻す際に使用するトランザクション

トランザクションのインプットは↓のようなカラーリングされたscriptPubkeyのUTXOを参照していて

<グループ識別子> OP_GROUP OP_DROP OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

アウトプットは以下のようにカラーリングされていないscriptPubkey

OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

全アウトプットにOP_GROUPが含まれていない場合、burnトランザクションとみなされる。

グループを識別しやすくするためのルール

このアプローチでは、OP_GROUPが複雑なスクリプト内にある場合、トランザクションの検証時にどのグループのコインに属するか決定することができないかもしれないという問題があり、このためスクリプトのスタンダードルールとして以下の制約が設けようとしているみたい。

  • スクリプト内にOP_GROUPは1つのみ
  • OP_GROUPは条件付きのロジック(OP_IF、OP_ELSEとか)の外側であること
  • OP_GROUPの直前にグループ識別子がプッシュされていること

カラードコインの量

↑のルールでグループ識別子によってカラードコインを識別する仕組みが提供される。識別は可能だがカラーリングされたコインの量はそのままアウトプットのコインの量(satoshiの量)を使ってる。2ndレイヤーで実装するカラードコインの場合、カラーリングされたコインには専用の量があり、UTXOとして認識するために各アウトプットに少額のBitcoinが乗っているが、OP_GROUPの場合はBitcoinの量とカラードコインの量を分離してはいないのが特徴的でもある。ただいずれもカラーリングされている間はBitcoinとしては使えないという点は同じだ。

所感

  • インプットとアウトプットの各グループのコイン量のバランスのチェックは、各スクリプト単体の評価時には結果が分からないので、全インプット/アウトプットを考慮して計算しないといけない。つまりスクリプトインタプリタ単位では検証できないわけで、そういった評価の類をスクリプトのopcodeとして導入していいものか?本来スクリプトのランタイムはscriptSig + scriptPubkeyを組み合わせて実行して結果が出るもの。Covenantsの仕組みとかでその中でトランザクションデータをスタックに入れて評価するような拡張の提案もあるけど、OP_GROUPトランザクションの他のインプットも含めた上で評価する必要があり、検証のスコープが複数のインプットのスクリプトにまたがることになるけど、いいのか?。
  • 最初のmintトランザクションにはOP_RETURNメタデータを付与できると記載されているけど、これは最初のmintトランザクションかどうかをチェックしないといけないという意味になると考えると、どのトランザクションが最初の発行トランザクションであるか管理しておかなければならないということになる?
  • インプット/アウトプットでグループのコインのバランスがゼロになる必要があることから、おそらくP2SHは使えないと思われる。P2SHの場合、そのredeem scriptが明かされるのは、そのスクリプトを使用するトランザクションのscriptSigなので、アウトプットがP2SHだとそのredeem script内にOP_GROUPがあることをトランザクションの検証時に確認する術がない。
  • Bitcoinではネイティブにこういったトークンをサポートしてなくて、やる場合は2ndレイヤーで実装してたのもあり1stレイヤーのコンセンサスで保護されるわけではない。そのためトークンプラットフォームとしてはEthereumが人気な訳だけど、Cashが対応するとスクリプトの堅牢性という意味も含めてBitcoinベースのトークンプラットフォームも進化していくかもね。
  • ちなみにBTCでは、コア開発者の1人であるJohnson Lauがソフトフォークでカラードコインをネイティブに対応する提案を書いているが↓、実際に導入されることは無いだろうな。 btcnews.jp

ペイメントチャネル間の資金移動を可能にするChannel Factory

トラストレスな双方向のオフチェーン決済を可能にするペイメントチャネルだが実用に向けてはまだ課題も多い。

techmedia-think.hatenablog.com

課題の1つにペイメントチャネルのキャパシティ(=チャネル内で決済できる金額の上限)が固定されている点がある。このためキャパシティを超える金額を送金したい場合や、2者間のチャネル決済において片一方に全ての資金が渡った後にさらに決済したい場合には、一度チャネルを閉じて、大きめのキャパシティや追加の資金を投入して新しいペイメントチャネルを構築する必要がある。チャネルを閉じて再度チャネルを開くということはクローズとオープンの2つのトランザクションをブロックに入れるためにブロードキャストする必要があり、当然各トランザクションには手数料が必要になる。

またキャパシティ以外にも取引相手のノードがクラッシュしたりオフラインになると、最終残高の資金を確保するためチャネルをクローズする必要があり、チャネルが不安定であればその機会も増え、これも手数料負担の増加につながる。

この問題を改善するマイクロペイメントチャネルのプロトコルを提案しているのが↓のホワイトペーパーだ。

Scalable Funding of Bitcoin Micropayment Channel Networks

Channel Factory

ホワイトペーパーでは、Bitcoinブロックチェーンとそのオフチェーンのペイメントチャネルの間にChannel Factoryと呼ばれるレイヤーを追加することで、二者間のペイメントチャネルのキャパシティを動的に調整できるようにしている。

今までのペイメントチャネルだと、ペイメントチャネルを構築する2者がそれぞれのコインをインプットにセットし、両者のマルチシグアウトプットにそのコインを送るトランザクションを作ってオンチェーンにコインをロックし、そのコインをインプットにして決済毎に両者の残高を更新するコミットメントトランザクションを交換してオフチェーン決済を行う。

f:id:techmedia-think:20171124223500p:plain
Channel Factoryの構成

注釈

サークルはトランザクションアウトプットで、ボックスはトランザクション。サークルの各色は各アウトプットの所有者を表している。1つのサークルに複数の色があるのは、そのアウトプットを使用するのにサークルの色の所有者の署名が必要であることを示している。鍵マークはチャネルがオープンしている間、ブロックチェーン上では未使用のアウトプットになっていることを表す。

Channel Factoryの構成要素

今までのペイメントチャネルではチャネルの残高を管理するCommitmentトランザクションが直接オンチェーン上にロックされている2者間のマルチシグをインプットとして参照していたが、その間にオフチェーンのAllocationトランザクションを挟み、CommitmentトランザクションがインプットとしてAllocationトランザクションのアウトプットを参照するようにし、Allocationトランザクションの内容をオフチェーンで更新していくことで、サブチャネルの各マイクロペイメントチャネルのキャパシティもオフチェーンで動的に変更できるようにしようというのがChannel Factoryのアプローチ。

また今までのペイメントチャネルでは1つのチャネルの参加者は2人でチャネルをオープン/クローズする際も2人で協力すればよかったが、Channel Factoryでは実際のマイクロペイメントチャネルの参加者は2者だが、Channel Factoryには多数のユーザーが参加するため、Channel Factoryをセットアップする際やAllocationトランザクションを更新する際は、Channel Factoryに参加しているメンバー全員の協力が必要になる。

↑の図では、3人のユーザーがChannel Factoryに参加し、3人の内2人ずつ組み合わせた計3つのペイメントチャネルを作っている。

Channel Factoryのセットアップ

Channel Factoryを初期セットアップするには、まずHook、Allocation、各Commitmentの一連のトランザクションを作成する(Hook→Commitmentまで未署名で作成するので当然Segwitが必須)。続いて、全参加者でAllocation、各Commitmentトランザクションの署名を完成させる。全ての署名が揃ったら最後にHookトランザクションに署名する。署名済みのHookトランザクションをブロードキャストしブロックに格納されてから十分時間が経ったらチャネルが使用できるようになる。

Allocationの更新

サブチャネルのペイメントチャネルで資金が不足した場合、Allocationトランザクションの各アウトプットの残高を変更した新しいAllocationトランザクションを作成し置き換えることサブチャネル内の別のチャネルから資金を移動してくることができる。Allocationが置き換わると当然それを参照してるサブチャネルのCommitmentも更新が必要になる。Allocationの更新は以下の手順で行う。

  1. 他のペイメントチャネルに資金を移動したくなったユーザーは、新しいAllocationの作成をグループ内の全ノードに通知する。
  2. Allocationの更新リクエストを受信したノードは、自身が参加している全サブチャネルの取引相手に現在の各チャネルの状態(残高)で、新しいAllocationに対応するようリクエストする。
  3. 各サブチャネルの2人の参加者は協力して、新しいAllocationをベースにしたサブチャネルの初期状態を決め、それをグループに通知する。
  4. 各ノードはそれぞれ新しいAllocationトランザクションを作成する。このトランザクションはサブチャネルに資金を供給するものなので、全チャネルを通して1つの同じトランザクションでなければならない。
  5. 各サブチャネルは新しいAllocationトランザクションをインプットにしたCommitmentトランザクションを作成し、署名する。これ以降サブチャネルの参加者はサブチャネルの決済を更新する(Commitmentトランザクションを更新する)場合、新旧のAllocationトランザクション両方についてCommitmentトランザクションを作成することになる。
  6. 全ノードが新しいAllocationトランザクションに署名し、その署名を交換する。
  7. 新しいAllocationの署名が全て揃ったら、各ノードは古いAllocationに基づいたサブチャネルの更新を止めることができる。

また常に合意した新しいAllocationが適用される(=古いAllocationがブロードキャストされない)よう、各Allocationトランザクションには相対的なタイムロックが施されている。

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

新しいAllocationトランザクションを作る際は必ず古いAllocationよりも短いロックタイムが設定される。これによりAllocationトランザクションのインプットが参照するトランザクションがブロードキャストされた後、一番速いタイミングでブロードキャストできるのは最新のAllocationトランザクションであることを保証している。

2者間のペイメントチャネルに比べて、グループの参加者が増えるほど新しいAllocationを作成するための調整コストは増える。ただ、自身が資金移動する側ではないノードにとっては、新しいAllocationの通知が届いたら新しいAllocationベースのサブチャネルを開いて、新旧両方のサブチャネルを一緒に更新していけば、Allocationトランザクションの署名が全部揃うまで待つことなく、オフチェーンの決済自体は継続して行える。

尚、このAllocationの更新には参加者の総数をpとした場合、O(p2)回の通信のオーバーヘッドが発生するが、これ自体はリーダーを置くことでO(p)まで減少できるみたい。

セトルメント

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

Channel Factoryを閉じたい場合は、今までのペイメントチャネルと同じように、各サブチャネルの状態を考慮して全参加者の最終状態(残高)を決め、Hookトランザクションをインプットとし、各参加者毎の残高をセットしたアウトプットを持つSettlement トランザクションを作成し、全員で署名しブロードキャストする。ブロックチェーン上にはHookトランザクションとSettlementトランザクションのみが現れる。

スプライスアウト

ペイメントチャネルの課題として相手のノードがクラッシュしたりオフラインとなったままで決済が継続できないケースがある。通常であれば最新の残高でCommitmentトランザクションをブロードキャストしチャネルをクローズする。Channel Factoryにおいても応答の無いノードがいると、新しいAllocationへの更新や応答の無いノードが参加しているサブチャネルのCommitmentの更新ができなくなる。この問題の解決方法の1つとして、以下のようにまだアクティブな参加者が集まってその全アウトプットを使って新しい共有アカウントを作る方法がある。

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

↑では4人が参加するChannel Factoryで、そのうちの2名ずつで計6個のサブチャネルを持っている。その状況で赤いノードが応答がなくなった場合、残りの3ノードはそれらで構成されるチャネルのインプットとなっているAllocationのアウトプットを全て集め、そのアウトプットをインプットにした新しいHookトランザクションを作り、それ以下にさらにChannel Factoryを構成する。この時作成するHookにはもちろん応答の無かった赤いノードは除外する。

新しく作成したHookトランザクションはブロードキャストする必要はなく、赤いノードが復帰したら新しいAllocationを作成してそこからチャネルを継続したり、通常のSettlementを作成してチャネルを閉じることもできる。赤いノードからずっと応答が無い場合は最後のAllocationをブロードキャストする必要があり、そうすると全てのサブチャネルの状態もブロックチェーン上に記録されることとなる。

このスプライスアウトの仕組みによって、クラッシュしたノードをある一定期間まったり、たまにオフラインになる取引相手などを許容することができる。

Channel Factoryの多層化

グループが大きければ大きいほど、オフチェーン決済の量が増え、ブロックチェーンのスペースは節約できる(当然、大きくなるほどその調整コストは上がるが)。大きなグループにするがAllocationの更新時の協調作業は少なくしたいといった場合に以下のようにChannel Factoryを多層化する方法がある。

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

↑では参加者が8人いて、それを1つめのAllocationトランザクションで4-of-4のマルチシグアウトプット3つに、その3つのアウトプットをインプットにした2層目のAllocationトランザクションを作る。この2層目のAllocationの配下に従来のサブチャネルを配置していく。これだと、1層目のAllocationを更新するのには8人の署名が必要だが、2層目の範囲内でAllocationを更新する分には4人の署名だけあれば済む。より大きなグループを構成する場合はこういった多層化が効果的になる。ただ大きくなるほどチャネルクローズ時のインパクトも大きい。

所感

  • ペイメントチャネルに動的に資金追加するアプローチとして面白い。
  • 2者間からマルチマーティのチャネル構成になるので、参加者が増えるほど調整コストはあがるけど、ある程度参加者いないと資金移動の流動性も上がらないので、導入する際ははそういったトレードオフの関係も含めユースケースとして妥当か判断する必要がありそう。
  • 直接の取引相手ではない第三者によってチャネルが閉じられるリスクは増える。ブロードキャストは手数料が発生するのでそれがマイナスインセンティブとして働きはするけど、別の用途で資金が必要になるケースもあると思うので、実際にどういうコスト削減効果が働くか気になる。

MASTで選択されたスクリプトを実行するtail-callの仕組みを定義したBIP-117

MASTの機能を実現するのに新しくBIP-98とBIP-116とBIP-117の3種類のBIPが提案されている。

BIP-116はスクリプトの条件分岐をフラットにしてMASTのマークツリーを構成した結果、スクリプトで実際に使用する条件(サブスクリプト)がそのマークルツリー内に含まれていることを検証するための新しいopcodeMERKLEBRANCHVERIFYを定義した↓

techmedia-think.hatenablog.com

BIP-98はそのMASTのマークツリーを構成する際に使用する効率的なハッシュ関数やマークルパスを指定する際のエンコード方法を新たに定義した↓

techmedia-think.hatenablog.com

最後のBIP-117は、MERKLEBRANCHVERIFYでマークルツリーへの包含が確認できたあと、実際に使用する条件(サブスクリプト)をスクリプトとして実行するためのプロトコルを定義している↓

https://github.com/bitcoin/bips/blob/master/bip-0117.mediawiki

概要

BIP16 (Pay to Script Hash)とBIP141 (Segregated Witness)は、そこにロックされたコインを使用する際にwitnessのデータの一部としてそのスクリプトの内容を公開するようになっている。どちらの場合も1つのスクリプトだけがコミットされる。これはこのBIPの目標を達成するのには便利だが、コインを使用する際に必要かどうかに関係なく全ての内容を1つのスクリプト内に記載する必要がある。

このBIPはBIP116(MERKLEBRANCHVERIFY)と組み合わせて、実質無制限の条件を持つスクリプトにコミットしつつ、コインを使用する際にその条件のうちコインのアンロックに使用する条件のみを公表するようにする。これにより複雑な条件分岐を持つスクリプトを分岐のないフラットな実行パスに分解し、可能なパスのセット全体にコミットし、使用するパスのみを明らかにする一般化されたMASTの形式を実現する。

動機

BIP16 (Pay to Script Hash)とBIP141 (Segregated Witness)は、ポリシーの公開を使用時まで遅延させることができる。しかしこれらのアプローチは、特定のトランザクションのアウトプットに対してコミットできるのは1つのポリシーのみという点で制限されている。複数のポリシーにコミットすることはできず、使用時にどちらを公開するか選択することはできない。

BIP116(MERKLEBRANCHVERIFY)は複数のデータ要素にコミットし、使用時に必要な要素のみを明らかにすることを可能にする。MERKLEBRANCHVERIFY opcodeは予め選択されたデータのセットに対してのみコミットを提供でき、コード自体を実行することはできない。

このBIPでは、redeem scriptがスタックにポリシースクリプトを配置するために必要なあらゆるタイプの計算を実行できるようにすることで、これら従来の方法のアプローチを一般化する。ポリシースクリプトは、BIP16やBIP141でredeem scriptがwitnessスタックの上から実行されるのと同じように、データスタックの先頭から実行される。特に、scriptPubkeyやredeem scriptでMERKLEBRANCHVERIFYを使うとコイン使用の検証に必要な条件のみを含むポリシースクリプトを選択することができる。これは、事前計算の段階でシンタックスツリーを実行可能なパスに分割し、それらを列挙してポリシースクリプトのマークルツリーハッシュにする一般化されたMASTの形式だ。ポリシースクリプトを実行する前に、コインの使用時に提供されたポリシースクリプトがこのツリーの一部であることは証明される。

仕様

スクリプトの実行が終了した際に

  • 実行した状態がクリーンでない場合、つまり
    1. メインスタック上に複数の要素がある、もしくは
    2. メインスタックに1つの要素があり、アルトスタックが空でない
  • メインスタックの一番上の要素がboolで解釈した際にtrueと評価される、かつ
  • メインスタックの一番上の要素が1バイトでないか、0x51(OP_1)0x60(OP_16)の範囲外の場合

メインスタックの一番上の要素がポップされ、シリアライズされたスクリプトとして解釈され実行される。その際メインスタックとアルトスタックに残っている残りの要素はインプットとしてそのまま残る。

最後の1つを残して上記の条件を満たした場合以下のようになる。

  • 一番上の要素は、0x51OP_1つまりN=2)から0x60OP_16つまりN=17)の範囲のシングルバイトである。

そして、

  • メインスタック上の次のN-1の要素(メインスタック上の要素が全て使われていた場合はアルトスタック上で継続)は520バイトのプッシュで、
  • 続くメインスタック上のN番目の要素(もしくはアルトスタック上)は1〜520バイトのデータ

となる。続いてメインスタック上の一番上の要素がドロップされ、N=2(0x51)〜N=17(0x60)の追加要素がメインスタックからポップされ(メインスタックの要素を全て使い果たした場合はアルトスタックで継続)、逆順でシリアライズされたスクリプトを形成し、両方のスタックの要素をそのインプットとしてスクリプトを実行する。

サブスクリプト内のCHECKSIGCHECKMULTISIGはグローバルなMAX_BLOCK_SIGOPS_COST制限にカウントされず、サブスクリプトで実行される非プッシュopcodeの数はMAX_OPS_PER_SCRIPTで制限されない。上記の例外を除いて、実行状態はサブスクリプトに引き継がれ、サブスクリプトの終了(terminate)はスクリプト全体の実行の終了(terminate)になる。これはtail-callセマンティクスによる実行と呼ばれる。

このようなサブスクリプトのtail-callはスクリプトの実行コンテキスト毎に1つだけ許可され、かつsegwitのredeem script内からのみ許可される。その他のwitnessスタックの評価やscriptPubkeyやscriptSig、P2SHのredeem scriptの実行の際にtail-callセマンティクスが実行されることはない。

論拠

現状、スクリプトを実行した結果スタック上にシリアライズしたスクリプトが残っているとデータ的にはtrueであると評価されスクリプトの検証結果はtrueになる。そのためこのBIPはこのBitcoinのコンセンサスルールを変更するソフトフォークの提案である。サブスクリプトに実行終了の機会を与えるのは、検証ルールをさらに制約することに過ぎない。唯一falseと評価されるスクリプトは空のスクリプト、もしくはスタックに空/ゼロをプッシュするだけのスクリプトだ。これらのスクリプトにはいずれも現実的な効用はないので、ソフトフォークの互換性のためそれらを除外しても欠点はない。

より一般的なEVAL opcodeではなくtail-callの評価に限定することで、実装を大幅に簡略化する。tail-callセマンティクスとは実行結果が呼び出し元のスクリプトのコンテキストに戻らないこと意味し、このため状態を保存したり後で復元する必要もない。実装は本当にシンプルで、スタックからサブスクリプトを引っ張り出して、いくつかの状態変数をリセットし、スクリプトインタプリタの先頭にジャンプバックするだけである。

tail-callの再帰を1レイヤーのみ許可するという制限はあるが、マルチレイヤーにおけるtail-callの再帰をサポートする技術的課題は重要だ。スクリプトデータの使用状況を追跡するためには、トランザクションデータとwitnessサイズの2つファクターについて新しいメトリックを開発する必要がある。この新しいweightは、トランザクションを使って中継され、手数料計算のベースとして使用され、トランザクションの実行でインラインで検証され、違反するトランザクションを伝搬するDoS禁止ピアの方針を決定する。

ただこれらの問題を克服する場合、単一の再帰の制約を解除すること自体もソフトフォークが必要になる。tail-callの再帰を1レイヤーのみ許可することで、マルチポリシーコミットメント/一般化したMASTの主な利点を享受できるが、いつか必要な変更がリソースアカウンティングとP2Pトランザクションの配布に行われた場合の将来の一般化されたtail-call再帰への道は残る。

グローバルSIGOP制限やスクリプト単位のopcode制限はポリシースクリプトには適用されない。これはポリシースクリプトを動的に選択すると一般的に静的解析ツールでこれらの制限を検証することができなくなり、libsecp256k1やBitcoin Coreのパフォーマンスが向上したためこれらの制限は必要なくなった(また検証は一度だけ行えばいい)。検証コストはブロックサイズの制限内でエンコード可能な署名操作の数によって依然として制限されており、インプット単位のスクリプトサイズもまだ10,000バイト以下と制限されている。

これらのグローバルやスクリプト単位の制限を外すため、scriptPubKeyで直接tail-callの評価を実行することは許可されておらず、そういったスクリプトはUTXOからフェッチされ検証されるブロックのブロックサイズの制限にはカウントされない。同様にP2SHのredeem scriptのtail-callも、segwitで修正された二次的な脆弱性のためサポートされない。

汎用的なMAST

tail-callをBIP116 (MERKLEBRANCHVERIFY)と組み合わせると汎用的なMASTの機能を利用できるようになる。スクリプトの作成者はまず使用時に検証する完全なコントラクトの記述から始める。記述したコントラクトの条件分岐を条件の検証とブランチに置き換え、スクリプトを実行する際に通る可能性があるパスを列挙する。実行可能性のあるパスのリストはマークルツリーに入れられ、フラット化されたポリシースクリプトがこのツリーのリーフになる。最後に、資金を送金するredeem scriptは以下のようになる。

redeemScript: <root> 2 MERKLEBRANCHVERIFY 2DROP DROP
witness: <argN> ... <arg1> <policyScript> <proof>

policyScriptがフラット化されたポリシースクリプトで、proofシリアライズされたマークルブランチとパスでpolicyScriptがマークルツリーrootを構成するのに使われセットの一部であることを証明する。arg1argNpolicyScriptが必要とする引数。2は1つのリーフ(1 << 1)が続き、リーフの値が事前にハッシュされていないことを示す。2DROP DROPMERKLEBRANCHVERIFYの検証を終えた際にその検証に必要だった引数をスタックから削除するのに使われる。

上の例は分かりやすくするために設計されているが、実際はsegwit v0スクリプト実行の際のCLEANSTACKルールに違反している。CLEANSTACKルールが新しいsegwitのアウトプットバージョンで削除もしくは変更されない限り、以下のようにアルトスタックを使用する形にスクリプトを変更する必要がある。

redeemScript: [TOALTSTACK]*N <root> 2 MERKLEBRANCHVERIFY 2DROP DROP
witness: <policyScript> <proof> <arg1> ... <argN>

[TOALTSTACK]*NTOALTSTACKopcodeがN回繰り返されたことを意味する。これはarg1argNをアルトスタックに逆順で移動させ、policyScriptが実行される時にarg1がアルトスタックの先頭に配置する。当然policyScriptもアルトスタックから引数をフェッチするよう変更する必要がある。

ポリシースクリプトのセットの中にさまざまなパラメータ数をとるスクリプトが含まれている場合、合理的な範囲内でサポートすることができる。以下のredeem scriptではポリシースクリプトとマークルプルーフに加え1〜3個のwitness引数を指定できる。

 witness: <policyScript> <proof> <arg1> ... <argN> // N は1〜3
redeemScript: DEPTH TOALTSTACK                    // witness要素の数をアウトスタックに保存
              TOALTSTACK                          // 最初の必須要素をアルトスタックに保存
              DEPTH 2 SUB                         // policyScriptとプルーフを無視して、オプションの数を計算
              DUP IF SWAP TOALTSTACK 1SUB ENDIF   // もし引数の要素が現れたら、オプションの2つ目の要素としてアウトスタックに保存
              IF TOALTSTACK ENDIF                 // もし引数の要素が現れたら、オプションの3つ目の要素としてアウトスタックに保存
              <root> 2 MERKLEBRANCHVERIFY 2DROP DROP
alt-stack: <N+2> <argN> ... <arg1>

witness要素の数がアルトスタックにプッシュされるため、アルトスタックが通常のスクリプトにアクセスできない場合でも、ポリシースクリプトは渡された引数の数を検証できる。上記のredeem scriptを使用する以下のポリシースクリプトは、アルトスタック上の2つのwitness 要素のみを受け入れ、witnessのmalleabilityを防ぐ。

policyScript: FROMALTSTACK ...check arg1... FROMALTSTACK ...check&consume arg2/arg1&2... FROMALTSTACK 4 EQUAL

数値4にはpolicyScriptproofが含まれていると予想される。

上記の例のような冗長性は、全てのポリシーサブスクリプトのパラメータとして同じ数のwitness要素を使用し、条件及びスタックサイズ数を削除することで回避することができる。将来のスクリプトのバージョンのアップグレードでは、witness/redeem scriptからメインスタックのポリシースクリプトに引数を直接渡せるようにCLEANSTACKルールの緩和も検討する必要がある。

BIP114との比較

BIP114 (Merkelized Abstract Syntax Tree)はBIP141のスクリプトのバージョニングを使って導入するMASTスキームについて定義している。BIP114と異なり、BIP116 (MERKLEBRANCHVERIFY)と共にこのBIPで提案するスキームは、MAST内のポリシースクリプトのメンバーシップを検証するのにスクリプト自体を使ってMAST構造を暗黙的に有効にする。これはMAST自体がプログラマブルであるため、コンセンサスコードの変更が大幅に少なくなるだけでなく、コンセンサスコードの変更を全く必要としない将来のスクリプトベースのイノベーションを可能にする可能性がある。

さらに、NOP拡張スペースを使用する全てのスクリプトMERKLEBRANCHVERIFYとtail-callセマンティクスを追加することで、BIP141のスクリプトバージョニングは不要となる。これによりこの機能をデプロイする際の、アドレスフォーマットをどうするかいった問題、スクリプトのバージョンアップの展開方法、別の機能がv1アップグレードを使用する際の合意、といったハードルは無くなる。

実装

コンセンサスコードの変更とテストを含むこのBIPの実装は以下のリポジトリで確認できる。

https://github.com/maaku/bitcoin/tree/tail-call-semantics

デプロイ

このBIPはBIP-8を使ってデプロイされ、tailcallという名前でbit 3を使用する。

Bitcoinのmainnetでは、BIP8のstartheightがM(未決定)で、timeoutはM+50,400ブロック後。

Bitcoinのtestnetでは、BIP8のstartheightがT(未決定)で、timeoutはT+50,400ブロック後。

CLEANSTACKは、この機能を使用するトランザクションが既にネットワークルールで非標準とみなされているため、例えばBIP68の時よりデプロイは容易になる。

互換性

v0 segwitのルールではスタックに何らかの要素を残すことは禁止されているので、互換性の理由からv0のパラメータはアルトスタックに渡す必要がある。