Develop with pleasure!

福岡でCloudとかBlockchainとか。

HFリサーチ”BIP-MSMMHHF”

Welcome - Bitcoin Hard Fork Research

↑の2つめの、James Hilliardが書いたマルチステージマージマイニングヘッダハードフォーク↓について見てみる。

https://github.com/jameshilliard/bips/blob/bip-msmmhhf/bip-msmmhhf.mediawiki

簡単に言うと、ハードフォークを2段階で実施する提案で、初期フェーズでは新しいチェーンのブロックで、元のチェーンのブロックをマージマイニングする。
その間、元のチェーンでは空ブロックが生成され新しくコインが生成されることもない。一定期間経過したら、元のチェーンのマージマイニングをやめ、そのタイミングで元のチェーンとマージマイニングするために使っていたヘッダフォーマットも変更するという内容。

動機

プールマイニングにおいては、ヘッダフォーマットを大幅に変更することでしか修正できないブロック保留攻撃*1などの深刻な問題が存在する。

また他にも非マージマイニングと互換性のある方法でのみ可能な、多くの望ましいヘッダフォーマットの変更がある。

元のチェーンを永久に無効にできない場合、2つの実行可能なチェーンが存在するリスクが高くなる。

仕様

直近の4032ブロックのうち3900ブロック(約96.7%)にバージョンフラグがセットされていた場合、このフォークをアクティベートする。このフラグは以下の2つのステージ(マージマインステージ、ヘッダ変更ステージ)を同時にロックインする。

マージマインステージ

最初のハードフォークはマージマイニングを使用して実装する。元のチェーンではトランザクションを一切含まないことに加えて、新しいコインが生成されない。
さらに元のチェーン上でntimeを操作し意図的に難易度を上げ、アップグレードしていない全クライアントが現在の時刻に追い付かないようにするコンセンサスルールを追加する。意図的なntimeの操作は、新しいチェーンのブロックのコンセンサスルールとして実装される。

新しいヘッダ
new_header.prev = 前のブロックヘッダのハッシュ
new_header.small_nonce = 4バイトのnonce
new_header.big_nonce = 8バイトのnonce
new_header.... (新しいフィールドを追加可能)
フェイクブロック

元のチェーンに対して↓のフェイクブロックを作る。

block.version = 4
block.prev = new_header.prev
block.merkle = コインベースでマークルルートを計算(コインベースのみで他のトランザクションを含まない)
block.timestamp = 前のブロックのmedian time past + 1
block.bits = calculate_bits()
block.nonce = new_header.small_nonce
block.tx_count = 1

(過去11ブロックのタイムスタンプの中央値 + 1秒した時間が次のブロックのタイムスタンプになる)

コインベース

フェイクブロックのコインベースは↓

coinbase.version = 1
coinbase.tx_in_count = 0
coinbase.tx_out_count = 1
coinbase.tx_out[0].value = 0
coinbase.tx_out[0].pk_script = "OP_RETURN"

(コインの報酬を受け取るコインベースのコインの値が0なので、新しいコインは生成されていないことになる。)

これはメインチェーンを破壊する最後の手段となる攻撃である。median time pastは非常にゆっくりと増加する(6ブロックに1秒)。これはマイニングの難易度の更新タイミングであるretarget period(2016ブロック)の間に336秒しかブロックのタイムスタンが増えないことを意味し、こうなるとretarget peroid毎に難易度は(調整の上限である)4倍に上昇することになる。

Bitcoinのチェーンが一定の難易度にとどまっていると、4倍時間がかかるようになる。

2週間後: 4倍の難易度   (1期間につき2週間)
10週間後: 16倍の難易度 (1期間につき8週間)
42週間後: 256倍の難易度 (1期間につき32週間)

ヘッダ変更ステージ

これはハードフォークの最終段階で、ヘッダーフォーマットがマージマイニングと互換性がなくなる。マージマインステージから42,336ブロック後で、元のチェーンのretarget periodの境界ブロックがスタートしたタイミングでアクティベートされる。アクティベーションの条件は、タイムワープ後元のチェーンが6048ブロック経過したタイミング。

論拠

この解決策は、1つのロックイン期間を使った2段階のハードフォークをする。

最初の段階では、難易度の調整をするための2016ブロックのマイニングが非常に困難になるまで元のチェーンのネットワークの難易度を意図的に増加させるためntimeの増加を抑制し、元のチェーンを実質使えくするよう設計されている。これによりハードフォークに対応していないクライアントにとってハードフォークへの対応が必要なことが明らかになる。

2つのステージが同時にロックインすることで、クライアントのマージマイニングもヘッダ変更ステージでロックされ、ヘッダ変更ステージを迎えると元のチェーンも停止することを保証する。1年以上のマージマイニングを経て元のチェーンの難易度を大幅に上昇させ、現時点の時刻まで追いつくよう難易度を減少させるにはとてつもないコストがかかるようになる。

後方互換

このハードフォークが実施されると、ハードフォークをサポートしていないフルノード及び軽量ノードはどちらも無効になる。移行には全ノードのアップグレードが必要で、マイナーは圧倒的大多数のサポートを表明する必要がある。

所感

  • マージマイニングで、元のチェーンのタイムスタンプに細工して意図的に元のチェーンの難易度を上昇させていくアプローチ面白い。
  • 厳密には元チェーンでのマイニングの結果コインを生成する権利を得てるんだけど、コインベーストランザクションにOP_RETUNの出力で実質そのコイン捨ててる。
  • HFのためにマージマイニングを採用する提案で内容としてはシンプル。他の拡張については特に触れていない。

*1:プールの運営者に経済的な損失を与える攻撃で、マイナーがマイニングに成功して有効なハッシュを見つけてもプールに報告しないという攻撃

HFリサーチ”BIP-MMHF”

ブロックサイズの拡張(1M→2M)を目的としたハードフォークに注目が集まっているけど、ブロックサイズとは別に↓のような問題を解消するためにブロックヘッダの構造についてもそろそろ変更が必要じゃないかと思う。

  • extra nonceの確保
    現在のハッシュレートではとても既存のnonce領域では足りず、タイムスタンプやコインベースの値がextra nonceとして使われている。
  • segwitのコミットメント領域の確保
    BIP-141のsegwitはソフトフォークとして提案されており、segwitの署名を含むトランザクションのコミットメントはコインベーストランザクションの出力の1つにOP_RETUNRを使って追加する仕様になっている。もともとOP_RETUNRの導入やサイズ拡張については賛否あったのを考えると、それをコアとなる機能の一部として採用するのは皮肉な結果にも思える。本来は従来のマークルルートと同様、ブロックヘッダに専用の領域として定義すべきものだと思う。

ただ、このようなブロックヘッダの構造に変更を加える場合は当然ながらハードフォークが発生する。
ハードフォークが行われるとチェーンの分岐が考えられ、チェーンの分岐を技術的に防ぐことは現状不可能で、社会的なコンセンサスの形成が必要になる。

というのを考慮するとハードフォークは気軽に行えないので、個人的にはサイズ拡張だけでなく↑のようなブロックヘッダの拡張も合わせて計画し、実施した方が現実的なんじゃないかと思う。(まぁ何を加える加えないで政治的対立がまた発生しそうな気もする)

ではどういう拡張が考えられるのか、ハードフォークに関するリサーチが↓のサイトにまとめられている。

Welcome - Bitcoin Hard Fork Research

今回は1つめの、ブロックサイズとnonceのスペースを拡張し、Native merge-miningをサポートするLuke-jrによる提案(2016年2月に提案されたけどまだBIPにはなってない)↓について見てみる。(残念ながらマージマイニングのサポート方法についてはまだ詳しく記載されていない)

https://github.com/luke-jr/bips/blob/bip-mmhf/bip-mmhf.mediawiki

動機

Bitcoinのマイニングは、有効なブロックを見つけるためにデータを変更してハッシュ値を計算する必要がある。32bitのnonceはブロックヘッダに直接設定する。このnonceのスペースを使い尽くしても目的のハッシュが見つからないと、マイナーはトランザクションデータを変更しなければならない。その場合、コインベーストランザクションのscriptSigを変更することで、マークルルートの値が別の値に変わり、新しいマークルルートで再びnonceを変えながらハッシュ計算を続ける。しかしこのプロセスは複雑だしハードウェアにアウトソースするのに適していないが、現在のハッシュレートではそうせざるを得ない。この問題についてはハードウェアにアウトソースできるようnonceのスペースを拡張することで解決できる。

※他のブロックサイズの制限や、マージマイニングについての動機はまだ書かれていない

仕様

ブロックヘッダのフィールド

各ブロックは↓のヘッダフィールドで構成されるようになる。

  • 最大600bitのnonceフィールド
    • PoW計算の最終段階で直前に変更可能な32bitのclass 1 nonce。
    • 24bitのclass 2 nonce
    • 32bit〜544bitの可変長のclass 3 nonce
  • 前のブロックのハッシュ
  • 32bitのタイムスタンプ
  • ブロック高
  • 8bitのmerge-mining-hardforkのデプロイのビットフィールド
  • 16bitのハードフォークのデプロイのビットフィールド
  • 32bitのソフトフォークのデプロイに使用するビットフィールド(現在のBIP-9のようなもの)

これに加えてブロックにはトランザクションのセットが含まれる。

トランザクションは前のブロックからUTXO stateを更新する必要がある。UTXO stateに存在しないデータを使用しようとするトランザクションは無効となる。

有効なUTXOを使用する入力のwitnessは、その入力が参照するpubkey scriptに対して有効なものかチェックする。このチェックに失敗すると、そのブロックは無効である。

ブロック内のトランザクションの総コストはXXXを超えてはならず、またsigopsの合計がXXXを超えてはならない。 ( XXXの値はまだ決まってないみたい。)

マークルツリーのアルゴリズム

マークルツリーの構成にも変更が加わる。各オブジェクトについて、マークルツリーでそのダイジェスト値を計算する。各ダイジェストはハッシュとそのノードに含まれる値の合計値で構成される。

例えば、トランザクションのマークルツリーのダイジェストは↓の3つのフィールドで構成されるようになる。

uint256 hash;  // SHA-256ハッシュ
uint64 fees;   // このノード以下のトランザクションで支払われる総手数料
uint64 size;   // このノード以下のトランザクションの合計サイズ

witnessのマークルツリーのダイジェストは↓の3つのフィールドで構成される。

uint256 hash;  // SHA-256ハッシュ
uint32 sigops; // このノード以下のトランザクションのsigopsの合計値
uint64 size;   // このノード以下のトランザクションの合計サイズ

以下の3つのbitがflagsバイトとして定義される。

FLAG_LEAF = 0x80
FLAG_PLURAL = 0x40
FLAG_MERKLE_ROOT = 0x20

残りのbitは予約されており、現在は全て0。

  • FLAG_LEAFはツリーの最下位レベルにのみセットする。
  • FLAG_PLURALは2つのハッシュを1つに圧縮する際にセットする。
  • FLAG_MERKLE_ROOTはマークルルートを計算するための最終的なハッシュステップにセットする。

トランザクションデータとwitnessのダイジェストは↓のように生成する。

トランザクションツリーを生成する際は、最初に↓のようにリーフダイジェストを生成する。

 uint256 hash = txid
unit64 fees = 入力のコインの合計 - 出力のコインの合計
uint64 size = witnessデータを除外してシリアライズしたトランザクションのサイズ

witnessツリーを生成する際は、最初に↓のようにリーフダイジェストを生成する。

uint256 hash = txid
uint32 sigops = トランザクションのsigop数
uint64 size = witnessデータを含むBIP-144でシリアライズしたトランザクションのサイズ

各ダイジェストのペア(左と右)から、その親ノードを↓で計算する。

flags = FLAG_PLURAL (or FLAG_PLURAL | FLAG_LEAF for the first pass)
    
compressed.hash = SHA256d(left.hash | left.fees | left.size | right.hash | right.fees | right.size | flags)
compressed.fees = left.fees + right.fees
compressed.size = left.size + right.size

ノードの数が奇数の場合、↓のようにして親ノードを計算する。(今までと同じように左ノードのデータをコピーして使うと手数料やデータサイズの計算がおかしくなるため、ゼロ値を使ってると思われる)

flags = 0 (or FLAG_LEAF for the first pass)
    
compressed.hash = SHA256d(left.hash | left.fees | left.size | 256 zeros | 64 zeros | 64 zeros | flags)
compressed.fees = left.fees
compressed.size = left.size

このダイジェストの数が1つになる(ルートノードになる)まで計算ステップを繰り返す。

最後にマークルツリーのルートを作成するためルートハッシュを計算する。(今までのようにルートノードハッシュがそのままマークルルートになる訳ではない)

flags = FLAG_MERKLE (or FLAG_MERKLE | FLAG_LEAF)
    
final_root_hash = SHA256d(root.hash | root.fees | root.size | element_count | 192 zeros | 64 zeros | 64 zeros | flags)

element_countは、8バイトの数値で、このツリーのリーフノード(トランザクション)の数

witnessツリーの場合は、合計部分は含まれずflagsは同じで計算はシンプル。手数料の部分をsigop数に置き換えれば良い。

動的メンバーシップマルチパーティ署名(DDMS)アルゴリズム

ハッシュTMRトランザクションマークルルート)は、ブロックに含まれる全トランザクションのidを使ったマークルツリーアルゴリズムを実行することで生成される。

ハッシュWMR(Witnessマークルルート)は、ブロックに含まれる全トランザクションの(witnessデータを含む)完全なハッシュを使用したマークルツリーアルゴリズムを実行することで生成される。

ハッシュH-C(Header-C)は、↓のデータをSHA256して生成される(エンディアンについては未FIX)。

ハッシュCMR(コミットメントマークルルート)は、最大232個の要素を持つハッシュH-Cのマークルツリーアルゴリズムを実行することで生成されるが、ハッシュH-Cにつながる部分木としてのみ検査される。ノードはマークルツリーの他のブランチに関して何も気にせず、H-Cの位置は以下のように計算する。

uint32_t lrot(uint32_t x, uint32_t n) {
  return (x << n) | (x >> (32 - n));
}
// nonceはclass 3 nonceの先頭4バイトでビッグエンディアンとして解釈される。
uint32_t vector_position_for_hc(uint32_t nonce, uint32_t vector_size) {
  const uint32_t chain_id = 0x62697463;  // "bitc"
  uint32_t a, b, c;
  a = (0xb14c0121 ^ chain_id) - lrot(chain_id, 14);
  b = (nonce ^ a) - lrot(a, 11);
  c = (chain_id ^ b) - lrot(b, 25);
  a = (a ^ c) - lrot(c, 16);
  b = (b ^ a) - lrot(a, 4);
  c = (c ^ b) - lrot(b, 14);
  a = (a ^ c) - lrot(c, 24);
  return a % vector_size;
}

ハッシュH-B(Header-B)は、↓のデータをSHA256して生成される。

  • 41バイトの定数:
77 77 77 77  01 00 00 00  00 00 00 00  00 00 00 00
00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
00 00 00 00  00 ff ff ff  ff
  • 1バイト:以下の41〜103バイトのデータ長 - 3
  • 41〜103バイト:
    • シリアライズしたブロック高(BIP-34)
    • 1バイト:マージマイニングハードフォークのデプロイ用のビットフィールド
    • ハッシュCMR
    • class 3 nonce
  • 1バイト:前のデータ長(midstate圧縮用)
  • 14バイトの定数:
01 00 00 00  00 00 00 00  00 00 00 00 00 00

ハッシュH-A(Header-A)は、↓のデータをSHA256して生成される。

  • 3バイト:class 2 nonce
  • 1バイト:定数0x60
  • 32バイト:前のブロックのハッシュ
  • 32バイト:ハッシュH-B
  • 4バイト:タイムスタンプ
  • 4バイト:現在のブロックのターゲット(difficulty)
  • 4バイト:class 1 nonce

ハッシュH-Aはリトルエンディアンの数値として解釈され、現在のブロックターゲットと比較される。ハッシュ値がターゲット以下であればDDMS署名は有効。

ハードフォークのデプロイ用ビットフィールド

↓のハードフォークの性質上、ハードフォークのデプロイ用ビットフィールドはBIP-9のように機能する。

  • ノードは、ハードフォークに関してネットワーク全体の同意が示されるまで、starttimeを延期しなければならない。
  • マイニングノードは、ネットワーク全体がハードフォークに合意し、ノードのサポートをを確信するまでstarttimeを延期しなければならない。
  • 認識していないハードフォークがネットワークの最長のチェーンでアクティベートされた場合、ノードのオペレーターがハードフォークを受け入れるか、拒否するか選択するまでノードは旧ルールの最後のブロックで停止する必要がある。
  • 拒否されたハードフォークのビットがアクティベートされると、ノードはフォークポイントより前進するため代わりのPoWアルゴリズムに切り替える必要がある。
  • ハードフォークはそれが認識/実装されるまでノードはそれを受け入れない。ノードがアップグレードされるまで、ノードは認識していない、不参加状態になる。
  • シグナリング期間中XXX%以上がハードフォークを拒否する場合、ハードフォークはアクティベートしてはならない。

論拠

nonceのサイズ制限の1つの解決策は、ブロックヘッダ内のnonceのスペースを拡張することで、現在80バイトに固定されているブロックヘッダの長さを可変にするか、未使用のセクションを追加のnonceフィールドとして再利用できるようにするかのどちらかになる。ブロックサイズを拡張するのは、単純により多くのトランザクションを許容するようにするだけで充分。

しかし、こういったハードフォークは、旧ノードを壊すだけでなく、新しいブロックの受け入れを拒否し、新しいチェーンより短くても古いルールを守るチェーンを有効なチェーンとして受け入れることができてしまう。これはこのハードフォークを実装する際に、旧ノードが有効な空ブロックとして認識するハッシュを使用することで対処することができる。

後方互換

このハードフォークは、明示的にこのハードフォークに対応していないノードでは(フルノードも軽量ノードも)無効になる。移行するには、全ノードがアップグレードを選択する必要があり、マイナーは圧倒的大多数がサポートを表明する必要がある。

まとめ&所感

  • nonceは現在32bitなので、600bitになると19倍弱増えることになる。
  • BIP-9のようなソフトフォークのシグナリングを今はブロックのnVersionをビット列として解釈して使用しているが、ソフトフォーク、ハードフォーク、マージマイニング用の各デプロイ用、シグナリング専用のヘッダフィールドが設けられる。
  • マークルツリーを構成するアイテムも変わる。従来リーフノードにTXIDをセットし、2つのノードのハッシュを連結して親ノードのハッシュを計算し、それをルートノードまで繰り返すことでツリーを構成していたけど、そのノードにハッシュだけではなく、用途に応じた値(手数料やsigop数など)がセットされるようになる。
  • 複数のアイテムを組み合わせて↑のようなマークルツリーのアルゴリズム考えるのも面白そう。
  • nonceが3つのパートで構成されるよう拡張され、そのハッシュの計算方法も新たに定義されるが、結構複雑になってる感がある。
  • 旧ノードにとって空のブロックとして認識させる仕組みの部分が詳しく知りたい。

ソフトフォークを確実にアクティベートするBIP-8

2020/07/04追記 その後BIP-8の内容は更新されたので、内容については以下の記事を参照↓

techmedia-think.hatenablog.com

現在、ソフトフォークのアクティベートと方法はBIP-9のversion bitsを使った方法が採用されている↓

techmedia-think.hatenablog.com

この方法を使ってOP_CSVのソフトフォークがリリースされ(シグナリング開始から約2ヶ月でアクティベート)、Segwitのデプロイも行われている。ただ、BIP-141ベースのSegwitは2016年11月にデプロイが始まったものの、Segwitのシグナリングを発信しているブロックは長らく25%〜30%くらいで、2017年7月11日時点で46%ほどで、アクティベートの閾値である95%にはほど遠い。

このSegwitのアクティベートを促すためにUASFや、Segwit2xなどの提案もされているが、そもそもBIP-9のソフトフォークのアクティベート方法を見直そうという提案がBIP-8になる↓

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

概要

このBIPでは、BIP-9の時間ベースのアクティベートをブロック高ベースに置き換えるとともに、ソフトフォークのアクティベーションを保証する変更について規程している。

動機

BIP-9ではブロックのnVersionフィールドを再利用して、ソフトフォークの並行デプロイを行うための仕組みが導入された。アクティベーションの際は95%というハッシュレートのほぼ全てのシグナリングが必要になっているが、これは非実用的で、少数のハッシュレートさえあればソフトフォークを拒否することができる。過半数のハッシュレートをベースにしたアクティベーショントリガーであれば、フルノードのアップグレードに代わって過半数のハッシュパワーが新しいルールを適用することでアクティベーションが可能。全てのコンセンサスルールはフルノードによって最終的に強制されるため、最終的に新しいソフトフォークがエコノミーによって強制される。この提案は、これら2つの側面を組み合わせて合理的な期間後にフラグデイによるアクティベーションを提供すると共に、フラグデイ以前でも大部分のハッシュレートによるアクティベーションを有効にする。

ブロック時間についてはいささか信頼性が低く、意図的にもしくは意図せず不正確な時間になる可能性があるため、ブロック時間に基づく閾値は理想的ではない。第二に、BIP-9では指定した時間後の最初のretarget periodに基づいてトリガーがを指定しているが、これは直感的ではない。新しいブロックはそれぞれ1つずつ高さが増えていくので、ブロック高に基づく閾値の方がはるかに信頼性が高く直感的で、難しいretargetを正確に計算することができる。

仕様

概要

この仕様は、MTP(Median Time Past)を利用した時間ベースの閾値をブロック高に置き換え、デプロイのステートマシンにFAILEDが無いという2点を除いてBIP-9と同様である。STARTEDからLOCKED_INへの状態遷移は以下の2つの条件のいずれかで行われる。

パラメータ

各ソフトフォークのデプロイの際は、以下のチェーン毎のパラメータを指定する。

  1. name
    ソフトフォークの識別子として使用するのに適切な名称を指定する。単一のBIPをデプロイする場合は、「bipN」という名称を推奨(NはBIP番号)。
  2. bit
    ブロックのnVersionフィールドのどのbitをソフトフォークのロックインやアクティベーションを知らせるのに使用するか指定する。0〜28の範囲内で指定。
  3. startheight
    ソフトフォークのシグナリングを開始するブロック高を指定する。
  4. timeoutheight
    まだLOCKED_INもしくはアクティベートされていない場合に、ソフトフォークをアクティベーションするブロック高を指定する。

パラメータ選択のガイドライン

上記のパラメータを選択する際は、以下のガイドラインを推奨する。

  1. name
    他のソフトフォークと被らない名称を指定する。
  2. bit
    並行でデプロイされているソフトフォークと同じbitを使用しないこと。
  3. startheight
    ソフトフォークを実装したソフトウェアをリリースした日から30日後(もしくは4320ブロック後)のブロック高を指定する。これによりプレリリースしたソフトウェアを使用しているグループによるトリガーを防止する。簡単にするためretarget periodのブロック高にする。
  4. timeoutheight
    startheightの1年もしくは52416ブロック後を指定する。

既にアクティベート済み、もしくはtimeoutheightのブロック高を過ぎていれば、前のソフトフォークで使われたbitを使って新しいソフトフォークをデプロイが可能だが、そういった必要性が出るまで使われていないbitを選択した方がいい。

状態

各ソフトフォークとブロックには、以下の状態が関連付けられる。

  1. DEFINED
    各ソフトフォークが開始した最初の状態
  2. STARTED
    startheightを過ぎたブロック
  3. LOCKED_IN
    STARTEDブロックの最初のretarget periodより後で、ブロックのnVersionフィールドに閾値以上のビットが関連付けられたブロック
  4. ACTIVE
    LOCKED_IN状態のretarget periodが過ぎた後のブロック
  5. FAILED
    DEFINED状態で、ブロックチェーンのブロック高がtimeoutheightより大きくなった状態

Bit flags

ブロックヘッダのnVersionフィールドは、32 bitのリトルエンディアン整数として解釈され、bitはこの整数内のビット値として選択される。

STARTED状態にあるブロックは、1がセットされているビットのnVersionを取得する。ブロックの上位3ビットは001になる必要があるため、nVersionの値は[0x20000000…0x3FFFFFFF]の範囲内となる。

BIP-34、BIP-66、BIP-65の制約により、使用可能なnVersionの値は0x7FFFFFFB個のみ。このため独立して並行デプロイできるソフトフォークの数は30個までという制約が付く。上位3 bitを001に制限するため、実際の同時デプロイは29個までとなる。将来この仕組みとは異なる仕組みを使ったアップデートをサポートするのに上位3bitの値の内、010011を確保している。現状ブロックのnVersionの上位ビットが001でない場合、デプロイの全てのbitは0であると解釈される。

マイナーはLOCKED_INフェーズ中はbitをずっとセットし続ける必要があるので、どういう状況かは確認できる。

新しいコンセンサスルール

各ソフトフォークの新しいコンセンサスルールは、ACTIVE状態の各ブロックで適用される。

状態遷移

https://github.com/bitcoin/bips/raw/fcd34618d7ce594db2a7e9badc4e70f1a2fab6db/bip-0008/states.png

ジェネシスブロックは各ソフトフォークのデプロイについてDEFINED状態にある。

State GetStateForBlock(block) {
  if (block.height == 0) {
    return DEFINED;
  }

同じretarget period内であれば状態は変化しない。つまり、floor(block1.height / 2016) = floor(block2.height / 2016) の場合、各デプロイは同じ状態を持つことが保証されている。

  if ((block.height % 2016) != 0) {
    return GetStateForBlock(block.parent);
  }

それ以外の場合、次の状態が何になるかは前の状態によって決まる。

  switch (GetStateForBlock(GetAncestorAtHeight(block, block.height - 2016))) {

ブロック高がstartheightもしくはtimeoutheightを迎えるまでは、最初の状態(DEFINED)のまま。

 case DEFINED:
   if (block.height >= timeoutheight) {
     return FAILED;
   }
   if (block.height >= startheight) {
     return STARTED;
   }
   return DEFINED;

STARTED状態になった後、タイムアウトを過ぎた場合はLOCKED_INに切り替わる。まだタイムアウトになっていない場合はbitをセットする。過去のperiodで充分な数のbitがセットされた場合はLOCKED_INに遷移する。この時の閾値は、mainnetが2016ブロック中1916ブロック以上(95%以上)で、testnetが2016ブロック中1512ブロック以上(75%以上)である。

この時、(そうそうないと思われるが)オーバーラップしない形で2つのデプロイに同じbitが使われている可能性もある。この場合、最初のデプロイはLOCKED_INに遷移し、同時にその次のデプロイはSTARTEDに遷移する。この直近のperiodでbitをセットしているということは2つのデプロイに対してbitをセットしていることになる。(1つのデプロイはLOCKED_INになってるので、次のデプロイが同じタイミングでSTARTEDになる分にはデプロイ自体はオーバーラップしていないという認識)

ブロックの状態は、そのブロック自身のnVersionには依存せず、その前のperiodのブロックのnVersionに依存していることに注意すること。

  case STARTED:
    if (block.height >= timeoutheight)
      return LOCKED_IN;
  int count = 0;
  walk = block;
  for (i = 0; i < 2016; i++) {
    walk = walk.parent;
    if (walk.nVersion & 0xE0000000 == 0x20000000 && (walk.nVersion >> bit) & 1 == 1) {
      count++;
    }
  }
  if (count >= threshold) {
    return LOCKED_IN;
  }
  return STARTED;

LOCKED_INのretarget periodが過ぎたら、自動的にACTIVEに遷移する。

  case LOCKED_IN:
    return ACTIVE;

ACTIVEは最終状態なので、このソフトフォークのデプロイはこれで終了になる。

  case ACTIVE:
    return ACTIVE;
}

実装上の注意:状態はブロックのチェーンの分岐に沿って維持されるが、ブロックチェーンの再編成が行われた際は再計算が必要な場合がある。

特定のブロックやデプロイの組み合わせの状態は、現状のretarget期間の前にその祖先によって決められることを考慮すると、各multiple-of-2016ブロックの状態の結果をキャッシュすることで、親によってインデックス化され、上記の仕組みを効率的かつ安全に実装することが可能。

警告の仕組み

アップグレードの警告をサポートするため、"implicit bit" mask = (block.nVersion & ~expectedVersion) != 0を使って、"未知のアップグレード"がトラッキングされる。nVersionにソフトウェアが予期せぬbitがセットされると、maskは0以外の値になる。"未知のアップグレード"のLOCKED_INが検出したら、ソフトウェアはこれから有効になるソフトフォークについて大きく警告する必要がある。さらに次のretarget peroidになって状態がACTIVEになった場合はさらに大きく警告する必要がある。

getblocktemplateの変更

block templateを要求するオブジェクトは以下の新しい項目を含むよう拡張される。

template request

キー 必須 タイプ 定義
rules no 文字列の配列 サポートされているソフトフォークのnameのリスト

templateオブジェクトは以下のように拡張される。

template

キー 必須 タイプ 定義
rules yes 文字列の配列 アクティブになっているソフトフォークのnameのリスト
vbavailable yes オブジェクト 保留中のサポートされているソフトフォークのセット。キーがソフトフォークのnameで値にそのソフトフォークのbitをセットしたオブジェクト。
vbrequired no 数値 ソフトフォークのversion bitのビットマスク

templateversionキーは保持され、サーバーのデプロイメントの設定を示すのに使われる。versionbitsが使われている場合、versionはversionbitsの範囲内([0x20000000...0x3FFFFFFF])でなければならない。マイナーは、テンプレートのvbavailableにリストされており、vbrequiredのbitに含まれていないソフトフォークのbitについては、特別なmutableキー無しにブロックバージョンのbitをセットしたりクリアしたりすることができる。

ソフトフォークのデプロイメント名は、rules内にリストされているかvbavailableのキーに含まれており、プレフィックスとして!を付けることができる。このプレフィックスが無いと、GBTクライアントはそのルールがテンプレートに影響を与えることはないと判定してもいい。この典型的な例はBIPの16, 65, 66, 68, 112, や 113で以前のトランザクションが有効でなくなった時である。クライアントがプレフィックスの無いルールを理解していない場合、クライアントはマイニングのため変更されていないものを使用する可能性がある。一方、このプレフィックスを使用するとブロックの構造や生成トランザクションに微妙な変更があることを示す。この例はBIP-34(コミットメント構造の変更)やBIP-141(txidのハッシュの変更とコインベーストランザクションへのコミットメントの追加)。プレフィックス!のルールを理解していないクライアントは、テンプレートを処理しようとしてはならず、変更されていないくてもマイニングに使用しないこと。

参照実装

Comparing bitcoin:master...shaolinfry:bip8-height · bitcoin/bitcoin · GitHub

後方互換

BIP-8とBIP-9のデプロイメントでは、同時デプロイメントの際にbitを共有しないこと。BIP-9のみを実装しているノードは、タイムアウトまでにハッシュパワーが閾値に達しない場合BIP-8のソフトフォークをアクティベートしないが、アクティベートされたノードによって作られたブロックは引き続き受け入れる。

デプロイメント

BIP-8を使ったデプロイメントの対象は以下で確認できる。

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

所感

ソフトフォークのアクティベートの歴史

BIP-9以前は、ブロックのnVersionのバージョンをインクリメントしてデプロイする方法でソフトフォークのデプロイが行われてきた。

techmedia-think.hatenablog.com

それより前はP2SHのように、ソフトフォークを適用する日(フラグデイ)を決めてアクティベートする方法も取られていた。P2SHのアクティベートは、フラグデイ(2012年2月1日)から過去7日間のブロックを対象に、ブロックのコインベースに/P2SH/という文字列が含まれているブロックが550個以上あれば、2月15日以降のタイムスタンプを持つブロックは全てP2SHの検証が行われるようになるというものだった。1週間に作られるブロックが約1000個なので、550という数字はネットワークの過半数と言える。もちろん逆に大多数のハッシュパワーがP2SHをサポートしない場合は、P2SHのリリースは延期されることになっていた。

P2SHを見るとソフトフォークのアクティベートの閾値は初期は過半数であったのが、BIP-34やBIP-9などによって、75%、最終的にハッシュパワーの95%というほぼ全てにまで上がるようになった。ネットワークを安定に保つという視点であれば、95%という閾値も本来あるべき閾値であると思う。ただ、同時にソフトフォークを採用するかどうかの投票の側面が強くなってきたとも言える。

ソフトフォークの採用は誰が決める?

以下の記事では、コア開発者の1人であるEric Lombrozoが、そもそもBIP-9はマイナーがソフトフォークに対して何の準備もなくアクティベートされて無効なブロックを作ることがないよう導入された機能であって、ソフトフォーク採用の投票に使うためではないとしている。

bitcoinmagazine.com

現状のSegwitのアクティベートを巡る混乱を思えば、正しく聞こえる。

ただ、BIP-8はタイムアウト期間を過ぎれば無条件にアクティベートされる訳で、実質ネットワークのほとんどが反対するような提案であっても必ずアクティベートできるということになる。もちろんネットワークの大多数が反対しているのであれば、誰もそのソフトフォークを採用しようとせず、そのソフトフォークはアクティベートはされたけど実質使えない状態になる。P2SHのリリース実績をもとにフラグデイでのアクティベートを謳ってはいるけど、P2SHも過半数が賛成しない場合はリリースを延期するとしていたのを考えると、無条件のアクティベートというのも如何なものかと思う。

BIP-9からシフトするか?

BIP-8を使ってSegwitをデプロイしようとBIP-149が定義されているが、現状はBIP-148が主流であるため、BIP-149がデプロイされることはないと思う。 またBitcoin Coreにもまだマージされてはいないので、BIP-9に代わってBIP-8が使われるようになるかどうかは現時点では分からない。

未来の価格に基づいた決済を可能にするDiscreet Log Contracts

Lightning Networkのホワイトペーパーを書いたThaddeus DryjaがこないだDiscreet Log Contracts(直訳すると離散対数コントラクト?)というホワイトペーパーを公開していたので↓見てみる。

https://adiabat.github.io/dlc.pdf

アブストラクト

スマートコントラクトはよくBitcoinのような暗号通貨のシステムで目にするが、金融分野ではまだ広く使われていない。スマートコントラクトを実装、採用にするにあたって、最大のハードルは、スケーラビリティと通貨に関する外部データをスマートコントラクト内から取得する難しさにある。これまでコントラクトのプライバシーは別の問題だった。Discreet Log Contractsは、スケーラビリティとプライバシーの問題に対処し、外部データを提供するオラクル*1への信頼を最小限に抑えるシステムを提案している。またこのコントラクトは外部のオブザーバーがトランザクションログからコントラクトの存在を検出できないように設計されている。

モデル

コントラクトのプロセスに、アリスとボブとオリビアの3者が参加しているとする。このうちアリスとボブはそれぞれ契約相手で、オリビアがオラクルになる。アリスとボブの間に信頼関係はなく、互いに法的な識別情報を知る必要はないが、認証されたチャネルを介して通信でき、互いを永続的に認識できる必要がある。
またアリスとボブはオリビアが署名しブロードキャストしたメッセージを受信できる必要がある。オリビアはアリスとボブを意識する必要はなく、理想的には情報をブロードキャストする以外の接触はない。ブロードキャストする情報は、Bitcoinネットワークでブロードキャストできるほどコンパクトだが、必ずしもBitcoinネットワークにブロードキャストする必要性はない。

DLCプロトコルは多種多様なコントラクトで使用できる。以下の例では未来の通貨価格をベースにしたコントラクトを作り実行する。

コントラクトは水曜日から始まり、水曜日時点では日本円の価値は1000satoshi。コントラクトは金曜日に終了し、その時点の日本円の価値は1050satoshiだったとする。

コミットされたRポイント署名

Discreet Log Contractsでは、Schnorr署名を使うが通常の使い方とは違う。通常ユーザーは秘密のスカラーaを生成し、そのaとベースポイントGからA = aGを計算し、Aを公開鍵として公開する。あるメッセージに署名する際は、aとは別にランダムな秘密のスカラーkを生成しR = kGを計算する。続いて

s = k -h(m, R)a

を計算する。ここでh()ハッシュ関数で、mは署名対象のメッセージ。この署名データは(R, s)になる。

この署名の検証は、与えられた (A, m, R, s)で以下の計算式が成立するか検証することである。

sG = R − h(m, R)A
   = kG − h(m, R)aG

Discreet Log Contractsでは、(A, R)を公開鍵と呼び、署名はsのみとする。方程式は同じだが、Rは署名の一部ではなく公開鍵の一部として使われる。未承認のトランザクションの二重使用を防ぐ仕組みに同様の構造を使用する方法が最近発表されている*2techmedia-think.hatenablog.com

リビアは公開鍵vGであるポイントVを公開している。オリビアはこの公開鍵を複数回使用し、秘密鍵であるvを安全に保つ必要がある。vは異なる当事者が保持する異なる鍵で構成することができる。またオリビアVとは別のポイントR = kGも公開している。kはランダムなnonceでRは1回限りの署名鍵である。これは通常のSchnorr署名で使われているkRと同じだが、Rは署名が計算される前にコミットされる。つまり署名するメッセージはまだ分かっていないが、事前にnonceは選択されそのRは公開されている。

合わせてオリビアRに関するメタデータも公開する。全てのRにはアセットタイプとそれに関する終了時刻がメタデータとして存在する(例:Rを金曜日のマーケットの終値の日本円に関連付ける)。Rは33バイトの長さなので、メタデータRより大きい可能性がある。

Rは公開され既知であるため、署名者がsを計算するより前に任意のmについてsGを計算することができる。

コントラクトの作成

コントラクトはブロックチェーン上に単一の出力として存在し、その出力には契約期間中にコントラクト実行時に支払われる全ての資金がセットされている。(この出力は多くの場合最適化されたものになるが、最適化については後述)

コントラクトをセットアップする前にアリスとボブはまずお互いを見つけて、コントラクトの条件に合意する必要がある。

アリスは金曜日に日本円を買い、ボブは日本円を売る決済を行う。

契約を結ぶには、両者のマルチシグアドレスに資金を入れる(funding output)必要がある。このマルチシグに資金を投入する仕組みはLightning Networkのチャネルのセットアップと大きく変わらない。アリスとボブはマルチシグにロックされた資金のOutPoint(txidと出力のインデックス)に同意する。このトランザクションをブロードキャストする前に、そのトランザクションを入力にした後続のトランザクションを作ることもできる。

続いて、アリスとボブはfunding outputを使用する多数のトランザクションで構成されるコントラクトの作成に移る。この出力は当然ながら1回しか使えないので、作成したコントラクトを構成するトランザクションの内1つのみがブロックチェーン上に記録されることになる。アリスとボブがこの段階ではどのトランザクションがブロードキャストされるものかまだ知らないので、お互い全てのトランザクションについて署名し保存しておく必要がある。

クロージングトランザクションは、契約当初とは異なるクローズ時の価格に基づいて作られる。今回の例では、価格は日本円の価格で、satoshiで表される。そのため、アリスとボブは終値の異なる何千ものトランザクションを作る。

現在開発中のLightning Networkのソフトウェアと同様、当事者はコントラクトの状態について合意するが、双方が作成した各トランザクションを保持する。アリスはボブが署名したトランザクションを保持しており、このトランザクションは2つの出力を持つ。1つはボブへの支払いで、もう1つはアリスもしくはボブへの支払い(アリスが不正を働いた場合のみボブがアリス分も入手する)の出力になる。ボブはこの逆で、アリスが署名したトランザクションで、1つの出力は直接アリスへの支払いをする出力で、もう1つはアリスもしくはボブのどちかに支払うスクリプト(ボブが不正を働いた場合のみボブ分もアリスが入手する)の出力になる。

Lightning Networkではこれらのスクリプトを使ってペイメントチャネルの一貫性を維持し、古い状態のトランザクションをブロードキャストすると相手に全ての資金を支払うことになる。DLCでも同様の仕組みを使うが、ペイメントチャネルと異なるのは、アリスとボブがお互いに過去のコミットメントに使ったシークレットを明らかににするのではなく、オラクルとであるオリビアがシークレットを明らかにする。

コントラクト内のトランザクション

アリスとボブは何千もの署名済みのトランザクションを持ち、それは両者のコンピューター内に保存されている。これらのトランザクションは、入力にfunding outputを持ち、出力は取引相手へのP2PKHと独自のスクリプトハッシュの2つで構成されている。このスクリプトはLightning Networkのチャネルで使われているのと同じスクリプトで、アリスが持つスクリプト

PubAi ∨ (PubB ∧ TimeDelay)

で(∨はorで∧はand)、ボブが持つスクリプト

PubBi ∨ (PubA ∧ TimeDelay)

後者のスクリプトでは、PrivBiを持つユーザーはすぐに出力を使用でき、 PrivAを持つユーザーはある時間経過したら出力を使用できるようになる。Lightning Networkではこれを以前の状態のトランザクションがブロードキャストされた際にそれを取り消すのに使われるが、DLCではオラクルが間接的に正しい状態であると承認したトランザクションのみをユーザーがブロードキャストするよう強制する。

アリスが保持する↑のスクリプトのPubAiはアリスの公開鍵にsiGを加算した以下のスクリプトになる。

PubAi = PubAlice + siG

ここでsiGは↓

siG = R - h(i, R)V

ボブはこの逆で、一定時間待ってアリスの公開鍵PubAに送る出力と待ち時間の無い以下の出力宛に送るトランザクションを保持する。

PubBi = PubBob + siG

検証者はポイントsiGを計算できるが、与えられたiだけではsiの値が何かは分からない。
リビアが署名すると、アリスとボブが既に計算している点の離散対数が明らかになる。

オラクルの署名

リビアの仕事は簡単で、金曜日にマーケットが閉じるのを待って、日本円の終値を観測し予めコミットしたnonceを使ってその数値に署名する。

オラクルは観測した価格をメッセージmとしてセット(この例では1050)し、以下の計算をする。

s = k − h(1050, R)v

水曜日に1000satoshiだった日本円が金曜には1050satoshiに上昇したことになる。
(実際のmの値はハッシュ値だが、ここでは分かりやすくするため、数値をそのまま使用)

ここでアリスとボブが以前トランザクションの鍵を導出するために使ったs1050が明らかになる。

s1050G = R − h(1050, R)V

オラクルがs1050を明らかにすると、PubB1050秘密鍵b + s1050と同じなのでボブはトランザクションの署名に必要な秘密鍵を知り、アリスも同様にPubA1050秘密鍵を知ることになる。

コントラクトの実行

アリスとボブはTX1050をブロードキャストすることで、金曜日の終値に基づく正しい状態でコントラクトを一方的に閉じることができるようになる。トランザクションがブロードキャストされると、すぐその出力の資金を自身のコントロール化のアドレスに送るトランザクションを作成する。
結果、2つのオンチェーントランザクションブロックチェーン上に公開されるが、直接両者の公開鍵ハッシュにコントラクトの実行トランザクションと同額を分配する新しいトランザクションTXggを両者で合意して作成する方がより効率的になる。
またスクリプトハッシュに定義されている期間(TimeDelay)より前に資金を回収しないと相手に全て資金を持っていかれるので注意すること。

アリスとボブのいずれかが途中で実行トランザクションをブロードキャストしたり、(TX1050でなくTX950とか)間違ったトランザクションをブロードキャストすると、スクリプト内のPubAiやPubBiの条件で資金を償還することはできなくなり、もう1つの条件であるTimeDelay経過した後に相手方の公開鍵ロックの条件でしか資金を償還できなくなる。つまり契約ルールに違反すると全ての資金が相手に渡ることになる。

オラクルへの信頼リスク

オラクルであるオリビアが価格を誤って報告する可能性がある。オラクルが誤った報告をした場合、システムの全ユーザーはエラーを識別し、オラクルの使用を停止できる。もしオリビアが2つの異なる価格を公表した場合、オリビアの持つ秘密鍵と二重報告をしようとした特定のコントラクトkの値が明らかになる。
またオリビア自身が契約相手(例えばアリス)だった場合、オリビア秘密鍵を明らかにすること無く、任意のコントラクトを実行できる。オリビアが実行したいTX2をブロードキャストし、続いて以下を計算し

PrivA2 = PrivA + s2

PubA2の出力に署名する。オリビア秘密鍵vを維持しながら、s2を公開せずに署名することになる。これは検知が可能で、詐欺にあったボブは、他の全ユーザーにオリビアのコミットメントと署名の使用を中止するよう、詐欺の証拠を提出することができる。オリビアは参加しているコントラクトを1つ騙すことができるが、同時に全てのユーザーの信頼を失うことになる。

リビアRを公開した後、終値を報告する前にいなくなることも考えられる。こういうケースに対応するため、予めコントラクトが実行される予定日の数日後であれば(タイムアウトすれば)、マルチシグにロックされた資金をそれぞれに払い戻すトランザクションを作っておく必要がある。

複数のオラクルを使用することもできる。2つのオラクルの署名が必要な場合であれば、各当事者は単純にオラクルのsGポイントを各公開鍵に追加する。複数のオラクルを使用することで、オラクルの不正リスクを減らすことができるが、複数のオラクルが報告するデータの一致しないパターンのリスクについて考慮する必要がある。例えばオラクル1は終値を1050と報告し、オラクル2は1049と報告した場合、実行トランザクションを安全に使用することはできず、タイムアウト後に両者に払い戻しが行われる。

もう1つのリスクは、コントラクト内の実質的に全てのポジションを失った当事者が、正当な所有者への資金の移動を遅らせるため、わざと無効なコントラクト実行トランザクションをブロードキャストする可能性があることである。最終的には正しい所有者が資金を回収することができるが、タイムアウトの期間まで待たなくてはならない。ただ、実際に行われることはほとんどないケースだと思われる。

最適化

いくつかの最適化によりデータ量と計算量を減らすことができる。システムを実装する際にはまた多くの最適化方法が見出されると思うが、以下にいくつかの基本的なアイディアをリストアップする。

R値の基数と指数

アリスとボブはコントラクトを作る際、オリビアが署名する可能性がある全てのメッセージmiについて署名を互いに送信し保持する必要がある。価格の候補は非常に多く、何千もの署名を検証し保存する必要がでてくる。ほどんどの場合、ノックインとノックアウトの価格が、それを下回るもしくは上回るとどちらか一方がコントラクトの全ての資金を受け取ることになる。例えば日本円の価格が10 satoshi以下になった場合、アリスとボブは価格が4 satoshiであろうが5 satoshiであろうが関係なくボブが全ての資金を手にすることに合意する。同様に価格が5000を超えると、それが6000か7000かに関わらず全ての資金をアリスが手にする。

価格のレンジは数桁以上にわたるため、アリスとボブはこれらの極端な範囲での取引を少なくすることで、ノックインとノックアウトの価格を最適化することができる。これはオリビアがRmantissaとRexponentという2つのRの値にコミットすることで可能になる。オリビアは価格を表す2つのメッセージに署名することを約束する。1050に署名する代わりにRmantissa仮数部)を使用して.050に署名し、Rexponent(指数部)を使用して3に署名する。小数点の基数と仮数には暗黙的に1が指定される。1.050 ∗ 103 = 1050

アリスとボブはsmantissaGとsexponentGのポイントを加算することで、同じようにトランザクションを構築する。オリビア仮数と基数のメッセージのペアに署名すると、sの値が明らかになる。Rexponentが4,5,6のような場合であれば、仮数部は無視できるレベルの数値になる。その場合Rexponentのみでコントラクトを作ればいいし、詳細な値まで含める場合はRexponentとRmantissa両者を必要とするコントラクトになる。どちらの粒度でコントラクトを作成するかは、コントラクト作成前に合意しておく必要がある。

↑の例ではRが2つだが、Rの数を3,4と増やすことで粒度を細かくすることもできる。また10は最適な基底ではなく、基底には2を使うのが良い選択になるだろう。

チャネル内のコントラクト

コントラクトはLightning Networkのチャネル内で作ることもできる。コミットメントトランザクション内の直接の送金とHTLCの送金の2つの出力に加えて、コントラクトの出力を追加する。あとはコントラクトの結果についてオンラインで両当事者が合意すれば、コントラクトの実行結果に従いお互いの残高を更新した新しいコミットメントトランザクションを作成し交換すれば良い。取引相手が非協力的な場合は親チャネルを閉じ、オラクルが提供した値を使ってすぐにコントラクトを終了させることができる。

所感

  • 予測される未来の値をベースにした決済を執行できるという意味で、暗号通貨を使用するユースケースの拡張につながる面白いアイディアだと思う。
  • トラストポイントとしてオラクルの存在があるモデルで、オラクルが厳密に不正をできない仕組みにはなっていないが、不正の検知は可能で、不正を行ったオラクルは以降信頼されずオラクルとして使われることはない。

*1:外部データを提供するエンティティのこと

*2:http://eprint.iacr.org/2017/394.pdf

Compact Blockバージョン2

BIP-152として定義されているCompact Blockについて以前ブログを書いた↓

techmedia-think.hatenablog.com

簡単にいうと新しいブロックデータを受信する際、既存のblockメッセージを使ってデータを受け取ると、そのメッセージにはブロック内の全トランザクションのRAWデータも含まれている。ただ、ブロックに入っているトランザクションの大半は既にメモリプールにあるので、重複したデータを受け取ることになり、ネットワークの帯域幅的にも無駄なので既に保持しているトランザクションデータについては受け取らないようにしようと言うもの。詳細な仕様についてや↑参照。

このCompact Block、久しぶりにBIPを見たらバージョン2が追記されてた↓

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

内容的には、SegwitのトランザクションをサポートするCompact Blockをバージョン2としてるみたい。

バージョン2の仕様

バージョン2はほとんどバージョン1と同じだが、Segregated Witness(Segwit)のトランザクション(BIP-141、BIP-144)をサポートする。バージョン1からの変更点は以下の通り。

  1. sendcmpctメッセージの2つめの値(= バージョン)に1ではなく2をセットする。
  2. cmpctblockメッセージとblocktxnメッセージ内のトランザクションには、BIP-144で定義されているwitnessデータを含むトランザクションフォーマットが適用される。(このフォーマットはinventory typeにMSG_WITNESS_TXを指定したgetdataメッセージのレスポンスで使われている。)
  3. cmpctblockメッセージとgetblocktxnメッセージにセットされるShort transaction IDsは、バージョン1と同じプロセスで計算されるが、txidではなくBIP-141で定義されたwtxidが使われる。またノードは通常コインベーストランザクションは持ってないので(生成されたブロックのコインベーストランザクションは基本的にその時点で他のノードのメモリプールは存在しない)、当然それはShort transaction IDsに含まれるが、そのトランザクションはBIP-141で定義されているwitnessフォーマットのトランザクションエンコードしたトランザクションから計算されなければならない。
  4. cmpctblockメッセージを期待するinventory typeにMSG_CMPCT_BLOCKを指定したgetdataメッセージがレスポンスとして帰ってこず、非Compact形式のブロックを要求するblockメッセージは、cmpctblockメッセージをエンコードするのに使われたバージョンが2であればwitnessを含めてエンコードし、バージョン1であればwitness無しでエンコードされる。

バージョン2の参照実装

https://github.com/bitcoin/bitcoin/pull/8393