Develop with pleasure!

福岡でCloudとかBlockchainとか。

リモートピアの生存確認とレイテンシーの計測を可能にするBIP-031

2012年5月にリリースされたBitcoin Core 0.6.1から導入されたPeer Serviceレイヤーの拡張で、リモートピアの生存確認と、レイテンシーの測定を可能にするBIP-031↓

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

簡単に言うとよくあるping送ってその反応が来るまでの速度を計測する機能をBitcoinP2Pメッセージにも実装しようというもの。

動機

Bitcoinのユーザーエクスペリエンスを低下するネットワーク関連の問題がいくつかある↓

  1. 一部のBitcoinクライアントは、スリープ状態になり警告なしにいつでも実行が停止されるプラットフォームで動作している。これは特に携帯やノートPCでよくある(ふたを閉めるとか)。システムが復帰するとスリープ前のTCP接続は残っているが、IPアドレスが変わっていたり、リモートピアがオフラインになっていたり、他のシステムによりタイムアウトしているなりして正しく機能する状態にないことは充分考えられる。こういう状況が発生した場合、それに気づくまでにしばらく時間がかかることがある。
  2. Satoshiクライアントは大部分がシングルスレッドで、(ブロックチェーンデータのダウンロードなどで)負荷がかかると、ネットワークメッセージに応答するのが非常に遅くなる。これを検出する簡単な方法はなく、リモートピアからのブロードキャストを受動的に待っている場合は特に顕著になる。オーバーロードされたリモートピアを検出し回避する方法は、負荷のバランスとり、より応答性の優れたシステムを提供するのに役立つ。
  3. ブロックチェーンのような大規模なデータ構造をダウンロードする際は、ネットワーク的に近いピアを選択する方が効率的である。現状リモートピアへのレイテンシーを測定するのは難しいので、クライアントは気にせずランダムにピアを選択している。

これら全て、下位互換性のあるプロトコルの変更によって解決することができる。

仕様

verメッセージのプロトコルバージョンが60000より大きい場合、pingメッセージにはnonceと呼ばれるuint64フィールドが含まれている必要がある。pingメッセージを送信しているピアはnonceにランダムな値を設定し、受信者は(受け取ったnonceをセットした)単一のuint64フィールドを含む新しいpongメッセージをエコーバックする。

こうすることでクライアントはpingメッセージを送りpongメッセージが返ってくるまでの時間を計測することができる。クライアントが最初のpongメッセージを受け取る前に、pingメッセージを2つ送っている場合、その応答がどちらのpingに対するものかはnonceで判断することができる。クライアントが重複してpingを送らない場合は、nonceの値は0で良い。

後方互換

クライアントは自身のプロトコルバージョンが6000より大きいと宣言する場合は新機能にオプトインする必要がある。古いプロトコルバージョンのクライアントは、pingメッセージにnonceを提供するとは思われず、pongメッセージを返さない。

実装

github.com

これ書いたのマイクハーンだったのか。

ProtocolのバージョンとUser Agentを分離したBIP-014

作られたのは2011年とだいぶ前のBIPだけど、あまりPeer ServiceレイヤーのBIPを見てなかったので見てみる。
昔はプロトコルのバージョンとクライアントのバージョンが同じだったのね。

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

動機

BitcoinプロトコルはSatoshiクライアントから始まったが、現在ではコミュニティも多様化し、様々な言語(Java, Python, Javascript, C++)で書かれた独自のコードベースを持つクライアントが、独自の機能セットを急速に開発 している。

プロトコルにはバージョンが埋め込まれている。このバージョン番号は主にversionメッセージとgetblocksメッセージ、ブロックを作成したソフトウェアのバージョンを示すblockメッセージに含まれている。現在このバージョン番号はクライアントのバージョン番号と同じである。このBIPでは、プロトコルのバージョン番号とクライアントのバージョン番号を分離するための提案である。

論拠

バージョン番号が分離されていないと、Satoshiクライアントのリリースの度に内部のバージョン番号がインクリメントされることになり、こうなると他の全てのクライアントもSatoshiのバージョン番号に追いつく必要が出てくる。Bitcoin開発者のあるグループのリリーススケジュールに全てのソフトウェアのリリースを合わせるといったことになり、これはBitcoinが本来持つ分散型の特性とは異なる。

バージョンのバンプは非互換性を生み、ネットワークを破壊する可能性もある。ネットワークの健全性が維持されるためには、プロトコルの開発とプロトコルの実装は分けて考える必要がある。全てのグループの代表者と共にプロトコルの舵をきるニュートラルな第3のエンティティの存在が、Bitcoinが最小のリスクで積極的に成長する機会を提供することになる。

プロトコルバージョンを利用することで、ネットワーク上の全ての実装に共通の標準を設定することができ、何がプロトコルで何が実装依存なのかの境界について誰もが同意できるようになる。User Agentの文字列はクライアントがネットワーク内で自分自身を識別するためのvanity-plateとして提供される。

ネットワークプロトコルを実装から分離し、参加者間の相互合意によりプロトコルを開発するということは、議論によって合意が困難な場合にはこの民主的な意思決定は不利益をもたらす。この問題を軽減するためには強力なコミュニケーションチャネルと迅速なリリーススケジュールが必要となるが、それについてはこのBIPの範囲外。

User Agentは、使っているクライアントの実装や共通のアーキテクチャ/OSなどのネットワークデータを把握するのに便利な余分なトラッキング情報を提供する。稀に、ネットワークの健全性を脅かすクライアントを排除するための緊急の方法として利用されることも考えられるが、これは基本的に推奨される使い方ではない。User Agentは異なるクライアントの実装をワークアラウンドに動作させるための方法ではない(そういう使い方をするとプロトコルの破壊に繋がる)。

まとめると

  • Protocol version
    異なる動作をするノードを区別する方法
  • User Agent
    シンプルな情報ツール(User Agentに応じてプロトコルを変更しないこと)

ブラウザのUser-Agents

RFC 1945ではUser Agentを製品の文字列にオプションのコメントを付けて曖昧に指定している。

Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.6) Gecko/20100127 Gentoo Shiretoko/3.5.6

User Agentは人間よりコンピュータによって解析されることの方が多い。スペースで区切るフォーマットでは簡単、高速にまた効率的に解析することができない。またデータにはその階層を示す構造は含まれていない。

情報の最も直接的な部分には、ブラウザの製品名、レンダリングエンジンおよびビルド(Gentoo Shiretoko)とバージョン番号がある。デスクトップ環境やプラットフォーム、言語、ビルドのリビジョン番号などさまざまな情報はコメントに含まれる。

仕様

versiongetblocksパケットのversionフィールドは、プロトコルのバージョン番号になる。blocksのバージョン番号は、そのブロックが作成されたときのプロトコルのバージョンを反映する。

versionパケットの現在使われていないsub_version_numフィールドが新しいUser Agentの文字列となる。

BitcoinのUser Agentは、パースしやすく一貫性のあるデータを構成するためブラウザのUser Agentに変更を加えたものになる。Bitcoinでは、ソフトウェアは通常、コアのcode-baseからGUIまでのスタックで構成されている。そのためUser Agentの文字列でこの関連をコード化する。

基本フォーマットが↓

/Name:Version/Name:Version/.../

サンプル

/Satoshi:5.64/bitcoin-qt:0.4/
/Satoshi:5.12/Spesmilo:0.8/

ここでbitcoin-qtとSpesmiloはプロトコルバージョン5.0を使うが、両者が使っている内部のコードベースは同じソフトウェアの異なるバージョンになっている。バージョン番号には厳密なフォーマットは定義されてないが、このガイドでは以下を推奨する。

  • バージョン番号は、メジャー.マイナー.リビジョン(例:2.6.41)というフォーマットを使う
  • リポジトリビルドにはYYYYMMDD(例:20110128)というフォーマットのデータを使う

gitリポジトリのビルドの場合、リビジョンビルドにgitのcommitを使用することも可能だが、先行するリポジトリがなければすぐには明らかにならないという問題もある。そのため上記の日付のフォーマットを推奨するが、これは決して要件ではない。

オプションで-r1-r2をUser Agentのバージョン番号に付与できる。これは推奨事項だが必須要件ではない。User Agentの構文エラーとなる():/を含めない限り、バージョン番号は任意のフォーマットで指定できる。

バージョン番号の後にオプションのコメントフィールドを設けることも可能。その際コメントは()で区切ること。コメント内の区切り文字としてはセミコロンの使用を推奨している。

サンプル

/BitcoinJ:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/

従って予約シンボルは/:()

  • /
    code-stackのデリミタ
  • :
    特定のスタックの実装バージョンを指定するのに使用
  • ()
    コメントのデリミタ(オプションでコメント内のデータのデリミタとして;

タイムライン

このBIPが公開された時点でBitcoinプロトコルとSatoshiクライアントのバージョンは0.5で、開発が行われている。この変更の対応における影響を最小限にするため、次のプロトコルバージョン0.6はクライアントバージョンから除外された。2012年1月からプロトコルと実装(クライアント)のバージョン番号はそれぞれ異なる値になっている。

プロトコルのバージョン遷移は↓に記載されていて、0.6.0からこのBIPのルールがて適用されている。

https://bitcoin.org/en/developer-reference#protocol-versions

現在のバージョンはBIP-152をサポートにより70014が最新みたい。

311→31402にガッツリ上がってるのは何があったのか?

Bitcoin Core 0.14.1のリリース

Bitcoin Core 0.14.1のマイナーアップデートがリリースされてたので主な変更内容を見ておく。

bitcoin/release-notes-0.14.1.md at master · bitcoin/bitcoin · GitHub

RPCの変更

  • createrawtransactionの最初の引数がtransactionsからinputsにリネームされた。
  • disconnectnodeの引数がnodeからaddressにリネームされた。

0.14.0からRPCコールする際に名前付き引数をサポートしているため、↑のインターフェースの変更はその点について0.14.0との互換性は無い。もし名前付き引数を使ってRPCを呼んでいるクライアントがあれば、更新する必要がある。

マイニング

以前のバージョンでは、getblocktemplateはネットワーク上でSegwitがアクティベートされたら、そのテンプレートを使用するクライアントやマイナーもSegwitをサポートする必要があった。0.14.1では、返されたブロックテンプレートから全てのSegwitトランザクションを削除することで、ネットワーク上でSegwitがアクティベートされた後もSegwitに対応していないクライアントをサポートするようになった。これによりSegwitがアクティベートされた後でも、非Segwitのマイナーは、非Segwitのトランザクションのみをマイニングし続けることができるようになる。

以前のバージョンまでの↑の制約により、getblocktemplateは非SegwitのクライアントにはSegwitのversion-bitをシグナリングをしないよう推奨してきた。今回のアップデートで、この必要は無くなり、getblocktemplateは、全てのマイナーにsegwitのシグナリングを推奨するようになっている。安全なアクティベーションにとって唯一必要な基準は、実際にSegwit対応のブロックを生成することではなく、ルールを実行する能力であるため、これは安全な取り組みである。

UTXOメモリの計算

Bitcoin CoreはUTXOのデータにより速くアクセスできるようUTXOをメモリ上にキャッシュしている。このUTXOキャッシュのフラッシュ時にメモリ使用量がピークになっても-dbcacheで設定された制限値を順守できるよう、UTXOキャッシュのメモリ使用量がより正確に計算されるようになった。(以前のリリースバージョンでは、実際のピーク使用率の半分しか使われていないと見積もられている。)

今回のリリースででデフォルトの-dbcacheのサイズも300MiBから450MiBに変更されている。現在-dbcacheを高い値に設定しているユーザーは、以前のリリースと同じキャッシュパフォーマンスを実現するためには、この設定値の増加を検討する必要がある。逆に低メモリシステム(1GB以下)のユーザーは、このパラメータの値を小さくすることを検討する必要がある。

低メモリシステムでの実行に関する追加情報は、reducing-bitcoind-memory-usage.mdを参照。

この変更に伴うプルリクは↓あたり

github.com

github.com

署名に厳密なDERエンコーディングを適用したBIP-066

techmedia-think.hatenablog.com

でECDSAの署名データの改竄に遭遇したのもあり、関連して現在のBitcoinプロトコルに適用されている署名のエンコーディングルールについて定義したBIP-066の内容もおさえておく。

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

動機

Bitcoinの参照実装では、署名検証をOpenSSLに依存しており(現在は独自実装したlibsecp256k1)、これがBitcoinのブロックの有効性を検証する暗黙的なルールになっている。しかし残念ながらOpenSSLはコンセンサスクリティカルな動作をするよう設計されておらず、その変更がBitcoinのソフトウェアに影響を及ぼす可能性がある。

特に重要な機能の1つが署名のエンコードである。最近までOpenSSLはDER標準から外れた署名であっても有効な署名として受け入れてきた。この仕様がOpenSSL 1.0.0p と 1.0.1kで変更されると、いくつかのノードがチェーンをリジェクトするようになった。

このBIPでは、コンセンサスルールがOpenSSLの署名解析に依存しないよう、DERが要求するものを正確に制限することを提案している。コンセンサスコードの実装から全てのOpenSSLを削除する場合、この変更が必要になる。

仕様

OP_CHECKSIGOP_CHECKSIGVERIFYOP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYに渡される全ての署名は、ECDSAの検証がされる。これらの署名には後述する厳密なDERエンコーディングエンコードされなければならない。

上記opcodeは全て、公開鍵/署名のペアに対してECDSAの検証を実行する。この検証では、署名が以下のIsValidSignatureEncodingチェックをパスしないと、そのスクリプトは即falseと評価される。署名は有効なDERだけど、ECDSAの検証でエラーになった場合、opcodeはこれまでと同様opcodeの実行を停止しスタックにfalseをプッシュする(ただしこの場合スクリプトはすぐにエラーにはならない)。

DERエンコーディング仕様

以下のコードが厳密なDERチェックの内容になる。この関数は1バイトのsighashフラグを含む署名のbyte vectorをテストする(sighashフラグはDERの仕様外でこの提案の影響は受けない)。この関数は署名の長さが0の場合は呼び出されない。

bool static IsValidSignatureEncoding(const std::vector<unsigned char> &sig) {
    // フォーマット: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]
    // * total-length: sighashを除くこの後に続くデータのデータ長を示す1バイトのデータ
    // * R-length: Rの長さを示す1バイトのデータ
    // * R: 任意の長さでビッグエンディアンでエンコードされたRの値。正の整数の最小エンコーディングになる。(次のバイト値が最上位bitでない限りnullバイトで始まってはならない。)
    // * S-length: Sの長さを示す1バイトのデータ
    // * S: 任意の長さでビッグエンディアンでエンコードされたRの値。(次のバイト値が最上位bitでない限りnullバイトで始まってはならない。)
    // * sighash: sihash typeを示す1バイトのデータ

    // データ長の最小・最大値チェック
    if (sig.size() < 9) return false;
    if (sig.size() > 73) return false;

    // A signature is of type 0x30 (compound).
    if (sig[0] != 0x30) return false;

    // 署名のトータルサイズを検証
    if (sig[1] != sig.size() - 3) return false;

    // Rの値の長さを取得
    unsigned int lenR = sig[3];

    // 残りの署名データにSの値が格納されている余地があるか検証
    if (5 + lenR >= sig.size()) return false;

    // Sの値の長さを取得
    unsigned int lenS = sig[5 + lenR];

    // 各要素の長さの合計が署名のデータサイズと一致するか検証
    if ((size_t)(lenR + lenS + 7) != sig.size()) return false;
 
    // Rの値がintegerか検証
    if (sig[2] != 0x02) return false;

    // Rの値の長さが0の場合はエラー
    if (lenR == 0) return false;

    // Rの値がマイナス値の場合はエラー
    if (sig[4] & 0x80) return false;

    // Rがマイナスの値として解釈されない限り、Rの先頭がnull byteは許可されない。
    if (lenR > 1 && (sig[4] == 0x00) && !(sig[5] & 0x80)) return false;

    // Sの値がintegerか検証
    if (sig[lenR + 4] != 0x02) return false;

    // Sの値の長さが0の場合はエラー
    if (lenS == 0) return false;

    //  Sの値がマイナス値の場合はエラー
    if (sig[lenR + 6] & 0x80) return false;

    // Sがマイナスの値として解釈されない限り、Sの先頭がnull byteは許可されない。
    if (lenS > 1 && (sig[lenR + 6] == 0x00) && !(sig[lenR + 7] & 0x80)) return false;

    return true;
}

表記:P1とP2はシリアライズされた公開鍵で、S1及びS2はP1とP2に対する有効な署名。S1' と S2' は非DERだがP1とP2の有効な署名。FはDER準拠の無効な署名で、F'はDERに準拠しない無効な署名。

  1. S1' P1 CHECKSIG fails (changed)
  2. S1' P1 CHECKSIG NOT fails (unchanged)
  3. F P1 CHECKSIG fails (unchanged)
  4. F P1 CHECKSIG NOT can succeed (unchanged)
  5. F' P1 CHECKSIG fails (unchanged)
  6. F' P1 CHECKSIG NOT fails (changed)
  7. 0 S1' S2 2 P1 P2 2 CHECKMULTISIG fails (changed)
  8. 0 S1' S2 2 P1 P2 2 CHECKMULTISIG NOT fails (unchanged)
  9. 0 F S2' 2 P1 P2 2 CHECKMULTISIG fails (unchanged)
  10. 0 F S2' 2 P1 P2 2 CHECKMULTISIG NOT fails (changed)
  11. 0 S1' F 2 P1 P2 2 CHECKMULTISIG fails (unchanged)
  12. 0 S1' F 2 P1 P2 2 CHECKMULTISIG NOT can succeed (unchanged)

デプロイメント

BIP-034と同じ2つの閾値(詳細はBIP-034参照)を使ったデプロイを利用する。ただし、ブロックのnVersionに3をセットする。1000ブロック中750ブロックのnVersionが3になるとこのルールは有効になる。さらに1000ブロック中950ブロックのnVersionが3になるとnVersionに2をセットしたブロックは無効になり、全てのブロックがこの新しいルールを採用する。

実際にBitcoin Core 0.10.0(2015年2月)で導入され、2015年7月時点で完全に有効になったみたい。

互換性

DERに厳密に準拠した署名を作成する本要件は、参照クライアントのv0.8.0以降でリレーポリシーとして実装され、違反するトランザクションは2015年1月時点でチェーンに追加されていない。さらに、準拠していない署名は準拠した署名に変換できるため、この要件によって機能が失われることはない。またこの提案にはトランザクションのmalleabilityを軽減するというメリットもある(詳細はBIP-062参照)。

実装

参照クライアントの実装↓

github.com

所感

署名の検証ルールがOpenSSL依存というのは確かに危ないので、明示的に検証ルールを定義するのは大事。

BIPとして登録されたHTLC(BIP-199)

HTLCの仕様がBIPとして登録されてたので見てみる↓

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

概要

Hashed Time-Locked Contract (HTLC) は、あるユーザー間(売り手と買い手)の決済を行うコントラクトで、売り手がハッシュのプリイメージを公開することで資金を入手することを可能にし、買い手が指定期間内に売り手がプリイメージを公開せずタイムアウトになった際に、資金を取り戻すことを可能にするスクリプトである。

このスクリプトは以下のような形式になる

OP_IF
    [HASHOP] <digest> OP_EQUALVERIFY OP_DUP OP_HASH160 <seller pubkey hash>            
OP_ELSE
    <num> [TIMEOUTOP] OP_DROP OP_DUP OP_HASH160 <buyer pubkey hash>
OP_ENDIF
OP_EQUALVERIFY
OP_CHECKSIG

[HASHOP]にセットするopcodeは、OP_SHA256OP_HASH160のどちらか。

[TIMEOUTOP]にセットするopcodeは、OP_CHECKSEQUENCEVERIFYOP_CHECKLOCKTIMEVERIFYのどちらか。

HTLCの流れ

  • ビクター(買い手)とペギー(売り手)は、お互いに公開鍵を交換し、タイムアウト閾値に合意する。続いてペギーはビクターにハッシュダイジェストを提供する。この段階で、両者はHTLCのスクリプトを構築し、そのP2SHアドレスを生成することができる。
  • ビクターはそのP2SHアドレスに資金を送る。
  • ビクターが送った資金は以下のいずれかで入手できる
    • べギーはビクターが送った資金のUTXOについて、自分が生成したハッシュのプリイメージを記載したトランザクションを作ることで資金を入手する(↑のOP_IFブランチの条件)
    • ビクターはタイムアウト時間を過ぎたので、資金を回収する(↑のOP_ELSEブランチの条件)

ビクターは、べギーがプリイメージを公開しない場合、資金がロックされている時間を減らすためタイムアウトを短く設定したい。逆にペギーの方は、タイムアウト閾値によって資金の入手できないといったリスクを減らすためタイムアウトを長く設定したい。

動機

多くのオフチェーンプロトコルでは、シークレットの開示が決済メカニズムの一部として使われている。HTLCのトランザクションは、回収前であればシークレットを公開することで確実に資金を入手でき、非協力的な取引相手から資金を回収する仕組みと合わせて、ブロックチェーンを介してシークレットとお金を交換する安全で安価な方法である。

Lightning Network

Lightning Networkでは、HTLCスクリプトを使ってペイメントチャネル間のアトミックスワップを実現している。

アリスはシークレットKを生成し、そのハッシュLを生成する。アリスはそのLのプリイメージ対するHTLCの支払いをボブに送る。ボブは同じプリイメージと金額へのHTLC支払いをキャロルに送る。アリスがプリイメージKをリリースした場合のみ価値の交換が発生する。シークレットは各ホップ毎に明らかになるので、全ての当事者の取引が補償される。いずれかの当事者が取引に非協力的になった場合は、払い戻し条件によって処理を中止することができる。

この辺の仕組みの話は↓の方が分かりやすいと思う

techmedia-think.hatenablog.com

ゼロ知識の条件付き支払い

ハッシュのプリイメージが期待する情報を導出することを保証するために使用されるさまざまな実験的なゼロ知識証明システムが存在する。一例として、ゼロ知識証明が、ハッシュのプリイメージが暗号化された数独パズルの解の復号鍵として機能することを証明するために利用されていたりする。(プロトコルの具体例は、pay-to-sudokuを参照)

HTLCトランザクションは、そのような復号鍵をリスク無しにお金と交換することができ、ゼロ知識証明で必要となる大規模で高価な検証トランザクションを必要としない。

実装

Bitcoin CoreでこのHTLCをサポートするプルリク↓が出てる。

github.com

所感

  • HTLC自体は以前からある仕組みで今回のBIPで特に新しい概念や機能が加わったものではない。
  • 今回のはBIP化というより、Bitcoin CoreがHTLCをサポートすることが目的かな?
  • Lightning Networkはホワイトペーパーではハッシュのプリイメージの交換だったけど、現状はRevocation Keyという公開鍵を使う方式になってるのでこのBIPとはあまり関係なくなってる。