Develop with pleasure!

福岡でCloudとかBlockchainとか。

UTXOのサイズ削減のアプローチTXO Commitments

BitcoinのUTXOのデータサイズは増加する一方で、フルノードを運用する際のボトルネックにもなっているが、そのUTXOの成長の問題を解決する方法としてBitcoinのコア開発者の1人であるPerter Toddが昨年 TXO Commitmentsという仕組みについてブログに投稿してた↓ので仕様部分だけまとめてみた。

Making UTXO Set Growth Irrelevant With Low-Latency Delayed TXO Commitments

現状の課題

現状Bitcoinにはブロックサイズの制限以外に、各種データのサイズを制限するようなコンセンサスルールは存在せず、UTXOのサイズも増えている。UTXOのセットを保持するのがフルノードの最小要件なので、UTXOのサイズが大きくなるほどフルノードの実行のハードルが上がる。

またUTXOのディスク上のサイズは圧縮しシリアライズされた状態であり、UTXOをモリ上に展開するとそのサイズは大幅に増える。複数の入力をマージして1つのUTXOを作るなどすればUTXOのサイズを減らせるが、現状そういったことをするインセンティブも無いので、サイズ削減も期待できない。それ以外に、小額過ぎて実際に使うことのないUTXOや秘密鍵の紛失により使用できないUTXO、コインの送付を目的としていないタイムスタンプサービスなどの使い方など、多くの要因によってUTXOサイズは増加している。特にタイムスタンプサービスなどはデータを記録するのが目的なので、通常のBitcoin送付の手数料よりもずっと高額な手数料を支払うことも許容される。

今のところUTXOの成長を防ぐための良いツールはなく、Segregated Witnessには、UTXOのサイズの削減する効果があるが、UTXOの成長を無視できるほどの効果は無い。

UTXOセットのサイズをハードリミットするというアイディアもあるが、これはBitcoinノードを効率的に動かすための最小要件であって、UTXOのスパム問題の根本解決にならない。

TXO Commitments

このUTXOのサイズの問題を解消しようと提案されているのがTXO Commitments。

(使用済み・未使用両方の出力を含む)全てのトランザクション出力の状態にコミットするマークルツリーを構築する仕組みで、出力の現在の状態をコンパクトに証明する方法を提供できる。これによりUTXOセットのアクセスの低い部分をアーカイブし、フルノードで関連データを破棄しても、それらの出力が実際に使用されていないことをノードに証明することで、アーカイブされた出力が使用する仕組みを提供する。

具体的には、TXO Commitmentsでは決定性のあるインデックス可能な挿入型マークルツリーの一種であるMerkle Mountain Range (MMR)を提案しており、ストレージ要件を最小限に抑え、新しいアイテムを安価にツリーに追加することができる。出力がTXO MMRに追加されると、その出力は決して削除されることはない。出力が使用された場合は、その場でステータスが更新される。MMRの特定のアイテムの状態と、MMRのアイテムの変更の有効性は両方ともツリーの先端までのマークルパスからなる { \displaystyle\log{2} n}サイズのプルーフで証明できる。

TXO commitment proofsは署名者の関与無しに生成及びトランザクションへの追加ができる。TXO commitment proofsは自体は署名される必要はなく、トランザクションハッシュの一部でもないので、TXO MMRのデータにアクセスできる人であれば誰でも、不足しているproofを再生成することができ、TXO commitmentsを使用するためにウォレットのソフトウェアに最小限の変更が必要となる。

TXO Commitmentsの実装

コミットメントフルノード実装では次のデータを格納する必要がある。

  1. UTXOセット
    未使用であることが明らかなtxoutsのキー:バリューのMap。既存のUTXOの実装と同様だが、古い出力のUTXOはこのセットから除外されるのが大きく違う。
  2. STXOセット
    直近のTXO commitmentの後に使用されたトランザクション出力のセット。ただしTXO commitmentより前に作成されたもの
  3. TXOジャーナル
    TXO MMRに使用済みとマークする必要がある出力。追加は低レイテンシーで、削除は高レイテンシーで行われる必要がある。
  4. TXO MMRリスト
    pruning可能な順序付けられたTXO MMRのリストで、主にペンディングされてるコミットメント。
Fast-Path: ブロック内で使用されているTxOutの検証

トランザクション出力がブロック内のトランザクションで使われた場合、以下の2ケースが考えられる。

  1. 最近作られた出力の場合
    最新のTXO commitmentの後に作られた出力なので、出力はUTXOセット内に存在する。そのためその出力を使用するトランザクションにはTXO commitment proofは不要。出力をUTXOセットから削除し、TXOジャーナルに追加する。
  2. アーカイブされた出力の場合
    最新のTXO commitmentより前に作られた出力なので、出力がUTXOセット内に存在するとは限らない。トランザクションはその出力が未使用であることを示す最新のTXO commitmentに対するTXO commitment proofを持つ。出力がSTXOに存在していないことを確認し、STXO内に無ければSTXOに追加する。出力と TXO commitment proofをTXOジャーナルに追加する。

どちらのケースでも、出力を使用済みとして記録する際は2つのキー・バリューの更新と1つのジャーナルの追加が必要。既存のUTXOセットは使用の都度1つのキー:バリューの更新が必要で、最悪ブロック内のトランザクション出力の使用が全てアーカイブされた出力であったとしてもブロックの検証レイテンシーは現状の2倍以内になると期待される。

Slow-Path:保留中のTXO Commitmentsの計算

優先順位の低いバックグラウンドタスクでTXOジャーナルをフラッシュし、TXO MMRに各ブロックが使用した出力を記録し、MMRデータをハッシュしTXO commitmentsダイジェストを取得する。さらにこのバックグラウンドタスクはTXO commitmentsに記録された出力をSTXOから削除し、不要になったTXO commitment を剪定する。

TXO commitmentの計算のスループットは既存のUTXOのみのスキームより悪くなる。

TXO MMRの詳細な実装

各TXO MMRの状態は、ほとんどの情報が共有されている前のものの修正で、多数の TXO commitmentsの状態を効率的に保存している。マークル化されたデータ構造では循環参照は不可能なので、ガベージコレクションは単純な参照カウンタで充分。不要になったデータはデータベースから削除することで剪定できる。

実際に構成も見たほうが理解しやすいと思うkので具体例で考えてみる。

2つのtxoutsを持つ以下のTXO MMRを考える。(これを状態#0と呼ぶ)

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

別のエントリーを追加すると状態#1になる↓

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

commitment #1で状態#0のデータがどのように再利用されたかに注目してほしい。状態#2を得るためさらに2つのエントリーを追加する。

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

このケースでは状態#1のデータは再利用されていない。

ここで状態#2が最新のブロックによってブロックチェーンにコミットされたとする。状態#2で作られた出力を使おうとする将来のトランザクションは、それが未使用であることを証明する責務がある。基本的には状態#2のMMRのデータを提供しなければならない。これによりTXO MMRに新しいtxoutを追加するために必要最低限のデータを残し、データを剪定することができる。

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

実装の詳細に応じて、ノード"2"と"e"に必要なデータはそのハッシュダイジェストだけである。

さらに3つのtxoutを追加すると状態#3になる。

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

ここで最近作られたtxout fが使用されたとする。MMRを更新するための全てのデータがあり、状態#4になる。ここでは2つの内部ノードと1つのリーフノードを変更する。

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

もしアーカイブされたtxoutが使われると、トランザクションは最近コミットされたTXO(このケースでは状態#2)にマークルパスを提供するよう要求する。txout bが使用された場合であれば、トランザクションは状態#2から以下のデータを提供する必要がある。

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

ローカルのTXO MMRをベースに上記データを復元すると↓

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

この時点でまだ状態#4は変更していない。txout bが使用されたことを示すようデータを更新し状態#5にする。

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

続いて、現在状態#3がチェーンにコミットされている状態で、状態#3で作られたtxoutを使用するトランザクションを作りたい場合、状態#3のデータからなるTXO proofを提供する必要がある。出力gとhのリーフノードとその内部ノードが状態#3の一部であるため、それらを剪定する↓

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

最後にa、c、gの3つのtxoutを使って、新しい3つのtxout i、j、kを作る。状態#3が最新のコミットされた状態だったので、aとgを使用するトランザクションはそこまでのマークルパスを提供する必要がある。これには状態#2のデータの一部が含まれる↓

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

unpruningすると、状態#5のデータは以下のようになる。

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

a、c、gの3つの出力を使用し、新しい出力i、j、kを追加した結果以下のの状態#6になる。

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

再度、状態#4の関連データを剪定することができる。さらにSTXOセットの実装方法によっては、この状態以降の使用済みtxtoutに関連するデータも剪定することができる(配下のノードが使用済みとなった内部ノードを含む)。

まとめ&所感

  • 様々な要因によってBitcoinのUTXOのサイズは増加する一方で、フルノードを運用する際のボトルネックにもなっている。
  • 現状の実装ではUTXOセット1つのみの構成で、そこに全UTXOのデータが保持されているけど、アクセス頻度の低いUTXOをアーカイブして、高速にアクセスする必要のあるUTXOのサイズを削減しようというアプローチ。
  • アーカイブされたUTXOは、それがまだ未使用である証拠=TXO commitment proofsを提供することでUTXOとしての正当性を評価して使えるようにする。
  • TXO commitment では、全てのトランザクション出力(未使用・使用済み両方含む)からなるマークルツリー = Merkle Mountain Range (MMR)を構成する。
  • MMRは決定性があるインデックス可能な挿入型のマークルツリー。
  • 未使用・使用済み両方含んでるからUTXOじゃなくTXO Commitmentsなんだろう。
  • トランザクションでは、そのトランザクションで使用しようとしているUTXOが未使用であることをTXO commitment proofsを添付することで証明する。
  • TXO commitment proofsの提供が必要になるのはアーカイブ済みのtxoutsを使用するトランザクションの場合のみ。
  • アーカイブ済みのtxoutを使用するトランザクションは、最新のTXO commitmentへのマークルパスを提供する。
  • UTXOの削減以外の用途でもMMR使ったコミットメントの仕組み利用できるんじゃないかなー。

BIPについて定義したBIP-002

BIPについて定義していたBIP-001が、2016年11月にBIP-002にリプレースされてたので見とく↓

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

概要

BIPはBitcoin Improvement Proposalの略で、Bitcoinコミュニティに情報提供したり、Bitcoinの新しい機能やプロセス、環境について記述した設計ドキュメント。BIPは機能の技術仕様やその機能の根拠について完結に記述する必要がある。

BIPは新しい機能の提案や、コミュニティにおける課題の収集、Bitcoinに取り入れられる設計上の意思決定を文書化するための主要な仕組みであると考えている。BIPの著者にはコミュニティ内でコンセンサスをとり、反対意見を文書化する責任がある。

BIPはバージョン管理されたリポジトリでテキストファイルとして管理されているので、そのリビジョン履歴は機能提案の履歴になる。

BIPのワークフロー

BIPのプロセスはBitcoinの新しいアイディアから始まる。BIP候補には、後述するスタイルとフォーマットを使用してBIPを書き、適切なフォーラムでディスカッションを開催し、アイディアに関するコンセンサスを構築する著者が必要となる。BIPの著者は、そのアイディアがBIP化可能かまず確かめる必要がある。ちょっとした拡張やパッチは、多くの場合複数のプロジェクト間で標準化を行う必要は無い。そういったものにBIPは必要なく適切なイシュートラッカーにパッチを送ると共に、関連するプロジェクトの開発ワークフローに従えばいい。またさまざまな理由でリジェクトされたBitcoinの変更に関するアイディアが今まで多数提出されているので、BIPを作成する際は、まず最初に過去のディスカッションを検索し、アイディアが以前に検討されていないかどうかを確認し、もしあればどのような問題がその時生じたか調べること。過去のディスカッションを調査した後、新しいアイディアを提案する良い方法は、Bitcoinの開発メーリングリストにポストすることである。

BIPを書く前にアイディアを公開するこで、著者とコミュニティの時間の節約になる。オリジナルのアイディアが以前の議論に基づいて明らかにリジェクトされる場合、Bitcoinコミュニティに最初に相談することで無駄な時間を使うことがなくなる。また、アイディアが著者だけでなくコミュニティ全体にとってメリットになるか確認することにもなる。アイディが著者にとってだけ有益なものでBitcoinを使っているほとんどの人々にとっては有益ではないかもしれない。

著者がBitcoinコミュニティにアイディが受け入れられるかどうかを尋ねた後は、bitcoin-devのメーリングリストにBIPのドラフトを提出する必要がある。これは著者にとって、BIPのドラフトを適切なフォーマットで、高品質にし、提案に関する懸念事項に対処するためBIPのドラフトを完成させるチャンスになる。ディスカッションの後、プロポーザルはBIPのgitリポジトリにプルリクエストで提出する。このドラフトは後述するBIPスタイルで書かなければならない。編集者によりBIPに番号が割り当てられるまでは、”bip-johndoe-infinitebitcoins”のように著者の名前/ニックネームと件名を含むエイリアスを使う必要がある。

著者はレビューのためにBIPを提出する前に、初期のアイディアとBIPについてコミュニティからのフィードバックを集める責任がある。しかし可能であれば公開メーリングリストで長い議論は避けるべきである。ディスカッションを効率的にするためには、トピックのための個別のメーリングリストをセットアップし、著者に初期の設計段階のプライベートコメントを受けさせたり、wikiページやgitリポジトリのセットアップなどをするのが効果的である。どれを利用するかはBIPの著者が決めればいい。

1つのBIPに含めるのは、1つのプロポーザルにすることを推奨する。BIPの焦点を絞るほど採用される可能性があがる。必要であればBIPを複数に分割すると良い。

BIPのドラフトが完成すると、編集者はBIPに番号を割り当て、Standards TrackかInformationalかProcessかのラベルを付け、gitリポジトリにプルリクエストをマージする。編集者はBIPを不当に拒否することはない。BIPのステータスを拒否する理由としては、何度も努力したけど、フォーマットルールの無視や、あまりにも範囲が広いケース、技術的に不適切、適切な動機ではない、下位互換性に対処しない、Bitcoinの考え方に従わない、などが挙げられる。BIPが受け入れられるためには、一定の最小基準を満たさなければならない。提案の拡張内容について明確で完全に記述されている必要がある。適用された場合、その提案の実装は堅牢でプロトコルを過度に複雑にしてはならない。

BIPの著者は必要に応じてgitリポジトリ内のドラフトを更新することができる。ドラフトの更新は著者からのプルリクエストで提出される。

BIPの所有権の移転

BIPの所有権を新しい著者に移す必要がある場合がある。一般的に、移転する場合、元の著者を共同著者として残したいと思うが、それはオリジナルの著者次第である。所有権を移転する正当な理由には、元の著者がBIPを更新したりフォローしたりするのに時間や関心を持たなくなったり、(メールが届かなくなったりレスが無いなど)ネット上でコミュニケーションが取れないといった場合である。所有権を移転する悪い理由は、BIPの方向性に同意しない場合で、BIPについてコンセンサスを構築しようとするが、不可能な場合にはいつでも競合するBIPを提出することができる。

BIPの所有権を引き継ぐのに興味があれば、元の著者とBIPの編集者の両方に引き継ぐ意思があることを示すメッセージを送信する。元の著者がタイムリーに返信しない場合、BIPの編集者が一方的な決定を下す(このような決定が行われると元には戻らない)。

BIPの編集者

現在のBIPの編集者はLuke Dashjrで、luke_bipeditor@dashjr.orgでコンタクト可能。

BIPの編集者の責任とワークフロー

BIPの編集者はBitcoinの開発メーリングリストを購読している。メーリングリスト外でのBIP関連の対応はluke_bipeditor@dashjr.org宛(もしくはCc)に送る必要がある。

新しいBIPが届くと編集者は以下のチェックを行う

  • BIPを読み、準備が整っているか(アイディアは受け入れられそうになくても、技術的意味を持っているか)
  • タイトルが内容を正確に記述しているか
  • BIPのドラフトがBitcoinの開発メーリングリストに投稿されているか
  • 動機と後方互換性について言及されているか
  • 定義されたLayerヘッダが仕様通りに正しく割り当てられているか
  • BIPとして受け入れ可能なライセンス条項になっているか

BIPの準備ができていない場合は、編集者が特定の手順で著者に送り返す。

BIPの準備ができたら、BIPのgitリポジトリにプルリクエストを送り、さらなるフィードバックを得る。

そうすると編集者は、

  • プルリクエストに対してBIPの番号を割り当てる
  • プルリクエストをマージする
  • README.mediawikiのリストに加える

BIPの編集者は、管理上及び編集上の責務を果たすこと。また、BIPの編集者はBIPの変更を監視し、必要に応じてBIPのヘッダを更新する。

BIPのフォーマットと構造

仕様

BIPはmediawiki形式で記述する。

各BIPには以下のパートが必要

  • Preamble
    BIPに関するメタデータを含むヘッダ(後述)
  • Abstract
    このBIPで対処する技術的な問題についての記述(200ワードくらい)。
  • Copyright
    許容される著作権下(後述)の明示的な使用許諾
  • Specification
    技術仕様で、新機能の構文と意味について記述する。仕様は現行のBitcoinの各プラットフォームで相互運用可能な実装について詳細に記述する必要がある。
  • Motivation
    動機はBitcoinプロトコルを変更したいBIPにとって重要なもの。既存のBitcoinプロトコルがBIPが解決しようとしている問題に対して不十分である理由を明確に記述すること。充分な動機のないBIPの提出は拒否される場合がある。
  • Rationale
    論理的根拠は、設計上の動機となぜそのような設計上の決定をしたのかを記述することで、仕様を完成させる。ここには考慮した別の設計や関連する作業についても(機能が他の言語でどのようにサポートされているかなど)記述する必要がある。論理的根拠では、ディスカッションの中で提起された重要な異論や議論についてコミュニティ内でのコンセンサスの証拠を提供する必要がある。
  • Backwards Compatibility
    下位非互換性を導入する全てのBIPは、非互換性とその重要度の記述が含まれるセクションが必要。BIPでは著者が非互換性をどのように扱うべきか説明しなければならない。下位互換性について充分な説明のないBIPは完全にリジェクトされる可能性がある。
  • Reference Implementation
    BIPのステータスが"Final"になる前に参照実装は完成させる必要があるが、BIPが受け入れられる前に完成させる必要は無い。コードを書く前にSpecificationとRationaleを最初に完成させ合意する方が良い。最終的な実装には、Bitcoinプロトコルに適したテストコードとドキュメントが含まれていなければならない。
BIP Header Preamble

各BIPはRFC 822 スタイルのヘッダPreambleで始まる。ヘッダは以下の順序で記述する。* の付いたヘッダはオプションで、付いてない項目は必須。

  BIP: <BIP number, or "?" before being assigned>
* Layer: <Consensus (soft fork) | Consensus (hard fork) | Peer Services | API/RPC | Applications>
  Title: <BIP title; maximum 44 characters>
  Author: <list of authors' real names and email addrs>
* Discussions-To: <email address>
* Comments-Summary: <summary tone>
  Comments-URI: <links to wiki page for comments>
  Status: <Draft | Active | Proposed | Deferred | Rejected |
           Withdrawn | Final | Replaced | Obsolete>
  Type: <Standards Track | Informational | Process>
  Created: <date created on, in ISO 8601 (yyyy-mm-dd) format>
  License: <abbreviation for approved license(s)>
* License-Code: <abbreviation for code under different approved license(s)>
* Post-History: <dates of postings to bitcoin mailing list, or link to thread in mailing list archive>
* Requires: <BIP number(s)>
* Replaces: <BIP number>
* Superseded-By: <BIP number>

(Standards Track BIPのみに含まれる)Layerヘッダは、このBIPがBitcoinのどのレイヤーに適用されるか文書化したもの。各BIPレイヤーについてはBIP-123を参照。

AuthorヘッダにはBIPの全ての著者/所有者の名前とメールアドレスが記載されている。Authorヘッダは以下の形式で書く。

  Random J. User <address@dom.ain>

著者が複数いる場合は、RFC 2822の継続行の規則に従って別々の行に記述すること。

通常初期のドラフトフェーズではBIPはプライベートで議論が行われているため、Discussions-ToヘッダにはそのBIPが議論されているメーリングリストもしくはURLを記述する。BIPが著者とプライベートで議論されている場合やBitcoinメーリングリスト上で行われている場合はDiscussions-Toヘッダは不要。

TypeヘッダはBIPの種類を指定する(Standards Track、Informational、Processのいずれか)。

CreatedヘッダにはBIPに番号が割り当てられた日付が記録され、Post-HistoryヘッダにはBIPの新しいバージョンがBitcoinメーリングリストに投稿された日付を記録する。2つともyyyy-mm-ddの形式で記述すること。Post-Historにはメーリングリストアーカイブ内の特定のスレッドのリンクを設定することが許可されている。

BIPにはこのBIPが依存するBIPの番号を示すRequiresヘッダもある。

BIPには後のドキュメントによってBIPが廃止されたことを示すSuperseded-Byヘッダを記述することも可能。設定する値は現在のドキュメントを置換するBIPの番号。置換した側のBIPには、廃止したBIPの番号をReplacesヘッダに記載する必要がある。

補助ファイル

BIPには図などの補助ファイルが含まれれる場合がある。補助ファイルはそのBIPのサブディレクトリに含めるか、BIP-XXXX-Y.extというファイル名にすること(XXXXはBIP番号で、Yは1から始まるシリアル番号、extは実際の拡張子)。

BIPの種類

BIPは以下の3種類に分けられる

  • Standards Track BIPは、ネットワークプロトコルの変更やブロック、トランザクションの検証ルールの変更、Bitcoinを使用するアプリケーションの相互運用性影響する追加・変更など、Bitcoinの実装のほとんどもしくは全てに影響を与える記述をする。Standards Track BIPは設計ドキュメントと参照実装の2つのパートで構成される。
  • Informational BIPは、Bitcoinの設計上の問題や、一般的なガイドラインや情報をBitcoinコミュニティに提供するために記述されるもので、新しい機能を提供するものではない。Informational BIPは、必ずしもBitcoinコミュニティのコンセンサスや勧告を示すものではないので、Informational BIPに従うか、無視するかはユーザー及び実装者の自由である。
  • Process BIPは、Bitconをとりまくプロセスや、プロセスの変更について記述する。Process BIPはStandards Track BIPに似ているが、Bitcoinプロトコル以外の領域も対象とする。Bitcoinのコードベースではなく実装を提案するかもしれない。これらはコミュニティのコンセンサスが必要で、Informational BIPと異なり、ユーザーは通常この提案を無視することはできない。例としては、手順やガイドライン、意思決定プロセスの変更やBitcoinの開発に使われるツールや環境の変更などが含まれる。どのmeta-BIPもProcess BIPとみなされる。

BIPのステータスフィールド

仕様

BIPのステータスの典型的なパスは以下の通り

https://github.com/bitcoin/bips/raw/master/bip-0002/process.png

BIPの著者は、DraftとDeferred、Withdrawn間のステータスの変更は自身で決定することができる。BIPの進捗がない場合、編集者はステータスをDeferredに変更することもできる。

著者が作業を完了し、有効な実装があり、Finalステータスに進めるためのコミュニティ計画があるのみ、ステータスをDraft(もしくはRejected)からProposedに変更される。

BIPに3年間なんの進捗もない場合、誰かから要求があれば、DraftもしくはProposedからRejectedにステータスが変更される。Rejectedに変更されたBIPは、著者が提案の批判に対して意味のある修正を加えた場合はDraftに、また前の段落で説明したように要求された基準を満たす修正である場合はProposedにステータスを変更することができる。

ProposedステータスのBIPは、実世界で特定の基準を満たす採用の動きがあった場合のみFinalに進む。これは提案された変更の性質に応じてBIP毎に異なる(後述)。このステータス変更の評価は、客観的に検証可能で、また開発メーリングリストで議論されるべきである。

FinalのBIPが適切でない場合、そのステータスはReplacedもしくはObsoleteに変更されることがある。この変更は客観的に検証可能で、また議論される必要がある。

Process BIPはメーリングリストで大まかな合意に達したらDraftからActiveにステータスが変わる。大まかな合意というのは、開発メーリングリストでディスカッションを開始して、最低でも1ヶ月、誰も新たに対応が必要な異論が無い場合を言う。充分な対処がされているにも関らず異議があがるような状況では、反対する明確な理由がなければならない。

Finalステータスへの移行

ソフトフォークが必要なBIPは(BIP-9を利用した)ブロックチェーン上の投票によりマイナーの大部分の賛成(95%)が必要になる。さらに(PoWのアルゴリズムを変更するような)ハードフォークを行う意思があると思われる場合には、そのようなハードフォークが多数によってサポートされる限りソフトフォークはFinalにならない。ソフトフォークのBIPはその採用のため追加の要件を設定することがある。マイニングプールなどで行われる代理投票などによって、マイナーのパワーバランスが変わる可能性を考慮すると、BIPの適用には95%という大多数の投票を必要とすることを強く推奨する。(低い閾値でも大丈夫な理論的な根拠がない限り)

ハードフォークが必要なBIPは、Bitcoinエコノミー全体からの合意を必要とする。特にBitcoinの支払いと引き換えに商品やサービスを販売する事業者やコインの所有者など。合意は実際にハードフォークしたバージョンを使用するということである。この経済的な合意は、圧倒的多数を取ればいいという訳ではなく、少数派にもハードフォークの受け入れを強制させない限り(そういったことが可能かどうかはこのドキュメントの範囲外)実現できない。

Peer services BIPは、1ヶ月間少なくとも公開ノードの1%が採用するか監視する必要がある。

API/RPC及びapplicationレイヤーのBIPは、少なくとも2つ以上の独立した互換性のあるソフトウェアアプリケーションで実装される必要がある。

ソフトウェアの作成者は、ステータス変更の検証を支援するため、ソフトウェアがサポートするBIPの概要を公開することが推奨される。良いサンプルはBitcoin Coreのdoc/bips.mdファイルや、Bitcoin Wallet for Androidのwallet/README.specsである。

これらの基準はBIPの事実上の採用を観察する客観的な方法として、BIPをリジェクトする理由として使用されることはない。ここに記載した基準を満たしていないにも関らず、BIPが明確に採用された場合ははFinalステータスに更新される。

論理的根拠

このBIPは何故必要なのか?

  • BIP-1ではBIPのステータスフィールドの基準の定義が曖昧で、よく混乱の原因となった。その結果、実用性のある多くのBIPが長い間DraftやProposedステータスのまま残っている。この提案ではBIPの進行を判断する適切な基準を与えることで、ステータスを正確かつ最新に保つことを目的としている。

Bitcoinのエコノミー全体というのは、モノやサービスを販売する事業者やBitcoinの所有者によって定義されている?

  • Bitcoinが通貨として機能するためには、決済方法として受け入れられる必要がある。Bitcoinと引き換えに何かを手に得ることができないのであれば、そこに価値は無い。そのような決済を受け入れる全員が特定のコンセンサスルールを必要とする場合、"bitcoins"は事実上そのルールによって定義されている。このコンセンサスルールが(ハードフォークのように)拡大すると予想される場合、新しいルールセットの元ので行われた決済を受け入れる必要がある。Bitcoin保有者は、彼らがBitcoinを使った決済ができる事業者を選択するという点で関係がある。

xがエコノミーに含まれないのはなぜ?

  • Bitcoinと引き換えに商品やサービスを提供するエンティティもあり、それらはエコノミーに関与している。
  • マイナーはエコノミーには含まれない。なぜなら彼らは、他の人にとっては価値の無いマイニング成果物を売ったり買ったりしてるだけだから。従って合意のルールを決定する際に、他の人の指示を受け入れる必要がある。
  • 交換所は単にコインを売りたい人と買いたい人をマッチングするサービスを提供してるだけなので、エコノミーには含まれない。例え全ての交換所がBitcoinから撤退しても、ユーザー同士はいつでも直接取引したり独自取引を確立できる。
  • 開発者はコードを書くだけなのでエコノミーには含まれない。そのコードを使うかどうかは他の人にまかせている。

しかし、彼らは何か重要なことをしていて、Bitcoinに多くの投資をしている。重要な決定に彼らを含めるべきでは?

  • このBIPは何を意思決定のベースにすべきかについて書かれたものではない。そのような声明はどんなに完璧に正当化されていても、他者にそれに従うよう強制させる方法が無ければ無駄になるだろう。BIPのプロセスはBitcoinの強力なガバナンスを目指すものではなく、人々が自発的に採用するかどうかについて情報を提供するための共同のリポジトリを提供するだけのもの。”彼らはどのようにすべき”ではなく、”ものごとが実際どのような”状況にあるのか正確に把握することのみを目的としている。

単一の事業者のみがハードフォークしたい場合はどうなる?

  • このBIPはハードフォーク自体のデプロイについてではなく、BIPのステータスフィールドの進捗にのみ対応する。
  • いずれにしても1店舗だけ孤立してオペレーションすることはできない。本当に1事業者のみであれば、彼らの店舗でBitcoinの決済が行われることはなくなるだろう。誰もそんなチェーンでの決済をしたいとは思わない。そしてBitcoinでの決済がなくなるということは、ここでいうエコノミーでも無くなるということになる。

お互いに商品を販売しあう少数の(多分2人)の売り手ではどう?

  • このシナリオでは、以前のBitcoinは動作していてハードフォークは失敗しているようだ。そのような分割を解決する方法は本BIPの対象外である。

経済的な合意はどうやってソフトフォークを拒否できる?

  • マイナーのグループを動的メンバーシップマルチパーティ署名(BitcoinではPoW)のコンセンサスルールによって決めるよう、ハードフォークすることができる。このグループを変更するのに必要な同じ条件がソフトフォークとは反対に満たされる場合、エコノミーがソフトフォークをサポートしないマイナーグループを選択すると、ソフトフォークをサポートしている大部分のマイナーは無効になる。

エコノミーが議論の余地のあるソフトフォークから3ヶ月後にハードフォークをすることを決めたらどうなる?

  • この状況では、議論の余地のあるソフトフォークのステータスは、決まったソフトフォークを置き換えるハードフォークの性質から、FinalからReplacedに変わる。

ピアサービスのプロポーザルを採択するのに必要な理想的なリスニングノードの割合は?

  • これについては未知で、現時点ではむしろ任意に設定される。ピアをランダムに選択し、その拡張を持つ他のピア1つ以上とつながるには13%以上必要だが、ノードはそのようなピアをスキャンし続けることができる。さらにそういったピアを識別するためのservice bitが存在する。

API/RPCとapplicationレイヤーのBIPの実装がすくなくとも2つのプロジェクトでリリースする必要があるのはなぜ?

  • 仕様の実装が1つしかない場合、標準インターフェースを必要としないから。
  • 2つだけのプロジェクトでも、それらの間で標準に関する調整が必要になる。

1つの特定のプロジェクトのみを対象にしたBIPが提案された場合どうなる?

  • BIPのプロセスは独立したプロジェクト間の標準化のために存在する。何かが1つのプロジェクトにしか影響を与えないのであれば、そのプロジェクトの内部プロセスで実行し、BIPとして提案することはできない。

BIPのコメント

仕様

各BIPはそのpreambleに、コメントの要約を含む公開Wikiページのリンクを記載する必要があり、自身がそのBIPの審査員だと思う人はそのWikiページに自分のコメントを投稿する必要がある。コメントページは通常完了したBIPの最終コメントを投稿するためのみに使う。BIPがまだ完成してない場合、レビューアはBIPの著者がレビューによって指摘された問題に対処できるよう、適切な開発メーリングリストのスレッドにポストすべき。

一部のBIPは完了する前に開発コミュニティ外に公開され、他のBIPは全く完了していない場合がある。期間中に批判的なBIPのレビューが気付かれないといったことがないよう、レビューアはコメントページにレビューを投稿してもいいし、最初にメーリングリストにポストし、完成したバージョンを元に後で必要に応じて削除・修正してもいい。このような改訂の場合、前回のレビューを編集しタイムスタンプを更新する必要がある。完成版より前に行われたレビューは、タイムリー(1ヶ月以内)に更新されない場合、削除される可能性がある。

ページはネームスペースとして"Comments"の後に(”BIP 0001”のような)完全なBIP番号を付与した名前にする必要がある。例えばBIP 1ののコメントページのリンクは以下のようになる。

https://github.com/bitcoin/bips/wiki/Comments:BIP-0001

このWikiにコメントを投稿する際は以下のフォーマットで投稿すること。

    <Your opinion> --<Your name>, <Date of posting, as YYYY-MM-DD>

BIPはWikiに加えてコメントのための第二のフォーラムを設置することも可能で、その場合第二のフォーラムのURIはpreambleのプライマリWikiURIの下に掲載される。

しばらくすると、BIPの自体のコメントのサマリの傾向が更新されることがある。サマリの傾向は以下から選択できるが、これでBIPの全てのニュアンスをカバーするつもりはなく、必要に応じて他のサマリを使用することもできる。

  • No comments yet.
  • Unanimously Recommended for implementation
  • Unanimously Discourage for implementation
  • Mostly Recommended for implementation, with some Discouragement
  • Mostly Discouraged for implementation, with some Recommendation

例えばBIP 1のpreambleは以下の行を含むよう更新される。

    Comments-Summary: No comments yet.
    Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0001
                                https://some-other-wiki.org/BIP_1_Comments

これらのフィールドは、BIP-1で定義されているDiscussions-Toヘッダに従う必要がある。(Discussions-Toヘッダが存在しない場合は本来存在する位置に配置する。一般的にStatusヘッダの上)

疑義が生じないよう補足しておくと、BIPを判断するためにコメントとステータスには何の関連もなく、どちらも一方に直接影響を与えてはいけない。

論理的根拠

BIPのコメントの目的は?

  • 一般的に不適切と思われながらも様々なBIPが採用されている。現在BIPは単純にBIP番号が割り当てられているだけで”良いアイディア”とみなされている。新しいBIPを提出するエントリーの敷居が低いため、レビューアの意見を表現する方法がある方が望ましいため。

BIPにコメントをするのは特定の参加者/専門家に制限されてる?

  • 参加者は自分の知識の専門外であっても自由にコメントすべき。コメントは検閲されるべきではないし、参加は一般に公開されるべき。

BIPのライセンス

仕様

新しいBIPは後述するライセンスで受け入れられる。新しい各BIPはpreambleですくなくとも1つ許容するライセンスを分かるようにすること。preambleのLicenseヘッダはCreatedヘッダの後に配置する。各ライセンスは次のいずれかの略語で参照される。

例えばpreambleに以下のライセンスが含まれる場合

    License: BSD-2-Clause
                   GNU-All-Permissive

この場合、BIPのテキストはOSI-approved BSD 2-clauseライセンスとGNU All-Permissiveライセンスの両方のライセンスの下に認可されており、いずれかのライセンス条件に従う限り、誰でもテキストを修正し再配布することができる。言い換えると、リストアップされているライセンスには、AND条件ではなくOR条件が適用される。

BIPのライセンスとは異なるソースコードのライセンスを交付することも可能である。オプションのLicense-CodeヘッダはLicenseヘッダの後に配置する。ここでも後述する各ライセンスの略語を記載する。

例えばオプションのLicense-Codeヘッダを指定するpreambleは以下のようになる。

    License: BSD-2-Clause
                   GNU-All-Permissive
    License-Code: GPL-2.0+

この場合、BIPのコードはBSDもしくはAll-Permissiveライセンスではなく、 GNU General Public License (GPL)のバージョン2以降のライセンスでのみ利用可能になる。コードをバージョン2のみで利用可能とする場合は+のシンボルをライセンスから削除する。以降のバージョン(GPL 3.0など)については、バージョンをインクリメントする(必要に応じて+を付与・削除する)。

    License-Code: GPL-2.0    # This refers to GPL v2.0 *only*, no later license versions are acceptable.
    License-Code: GPL-2.0+  # This refers to GPL v2.0 *or later*.
    License-Code: GPL-3.0    # This refers to GPL v3.0 *only*, no later license versions are acceptable.
    License-Code: GPL-3.0+  # This refers to GPL v3.0 *or later*.

テキストやコードのライセンスが複雑過ぎて簡単に表現できない場合、リストアップせず"Complex"という単一の用語に置き換える必要がある。全ての場合において、ライセンス条項の詳細はBIPのCopyrightのセクションに記載されなければならない。

BIPは承認された条件のもとで独占的に認可される必要はなく、少なくとも1つの許容ライセンスに加えて容認できないライセンスの下で認可される場合もある。この場合、受け入れ可能なライセンスのみがLicense及びLicense-Codeヘッダに記載される。

推奨ライセンス

さらにBIP上に含まれるリテラルコードは、プロジェクトと同じライセンス条項とする二重ライセンスとすることを推奨する。例えば、Bitcoin Coreを対象にしたリテラルコードは、BIPのテキストのライセンスと同様、MIT ライセンス条項の2重ライセンスを取得することになる。

非推奨だが受け入れ可能なライセンス
受け入れ不可能なライセンス

上記のライセンスリストに含まれていないライセンスはBitcoin Improvement Proposal の条件として受け入れられない(後にこのBIPが更新されライセンスが追加された場合は別)。ただし、このBIPの承認以前のBIPは他の条件で許可されており、他のライセンスが明記されていない場合、以下の略語を使用する必要がある。

論理的根拠

BIP 1ではOpen Publication Licenseもしくは public domainを許可していたがこれでは不十分だったのか?

  • OPLは廃止されたものであり、新しい出版物に適したライセンスではない
  • 多くの人はOPLの用語に馴染みがなく、そういった不確実な条件よりpublic domainを好むかもしれない
  • OPLのライセンス条項により著者は出版や派生物の作成を防止できるが、これはBitcoinの標準としては不適切だと考えられてきた。
  • public domainは法的なアクションが普遍的に認められていないため、推奨できない。

ソフトウェアライセンスが含まれているのはなぜ?

  • 一部のBIP(特にコンセンサスレイヤー)は、BIP内にリテラルコードが含まれていることがあり、それはBIPの正確なライセンス条件では利用できない場合があるため。
  • ただ、BIP内の全てのコンテンツに対してソフトウェアライセンスが適用される訳ではない

新しいBIPでPublic Domainが受け入れられないのはなぜ?

  • いくつかの国では、Public Domainは合法的な法的措置であると認識されず、BIPは著作権で保護され、改変や再配布が一切許可されないため。

BIP 1からの変更点

  • BIPとして受け入れ可能なライセンスについて再選択し、多種多様なオープンライセンスを許容すると共に、問題のある古いライセンスを禁止
  • AcceptedステータスをProposedにリネーム
  • ステータスがProposedに進む前に、実装が必要
  • BIPのコメントを新しく導入
  • ライセンスのpreambleヘッダを追加
  • BIP-123からLayerヘッダを追加
  • bip-XXXXサブフォルダに画像ではない補助ファイルを配置可能に
  • 著者のメールアドレスが必須に
  • Post-Historyヘッダは単純な日付に代わって、リンクが記載される場合がある
  • BIPを書くフォーマットとしてmarkdownは不許可に
  • Resolutionヘッダは、最終決定を下す権限がない分散システムには不適切なので削除

所感

  • BIP-001ではProposed(旧Accepted)からFinalになる基準が曖昧だったので、変更の性質に合わせて客観的に移行可能かどうか判断する基準が設けられたのね。
  • 選択できるライセンスがガッツリ変わってる。
  • BIPのリポジトリWikiページみると、数はそんなにないけどたしかにコメントページが作られてる。
  • このBIPとは直接関係ないけど、ピアサービスに関する拡張の場合その拡張をサポートするピアを識別するためのservice bitが存在するのね。2ndレイヤーのピア作る場合とかに仕組みが参考になるかも。
  • BIPはBitcoinのガバナンスを定義したものではなく、自発的な提案とコミュニケーションを促す場であるって認識大事。

Lightning Networkで古いCommitment Txを取り消すRevocation Key

以前、Lightning Networkを構築する際のPayment Channelの仕組みについて書いた↓

techmedia-think.hatenablog.com

プリイメージを使ったコミットメントトランザクションの取消

このPayment Chanelを使ったオフチェーンの決済には、悪意あるユーザによって最新の残高ではなく自分の残高が多い古いコミットメントトランザクションをブロードキャストする不正が行われた際に、チャネルにデポジットされている全資金を取り戻す仕組みが存在する。

この仕組みはHTLCsで使われている仕組みと似ていて、新しいコミットメントトランザクションに両者が合意した際、古いコミットメントトランザクションに含まれているハッシュの元となったプリイメージを交換する。そのプリイメージがあれば、相手によって古いコミットメントトランザクションがブロードキャストされても、自分の署名とそのプリイメージで全資金を自分のものにできるというもの。

具体的には、アリスとボブがいてアリスは以下のようなコミットメントトランザクション作成してボブに送る(事前にボブからボブが生成したrevokehashを受け取っておく)。

# 入力
アリスとボブのマルチシグにデポジットされたコイン

# 出力1(アリスの残高をアリスに送る出力)
OP_DUP OP_HASH160 <アリスの公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

# 出力2(ボブの残高で、24時間経てばボブの公開鍵でコインが入手可能、もしくはrevokehashのプリイメージが分かればアリスの公開鍵でコインが入手可能)
OP_HASH160 <ボブが生成したrevokehash> OP_EQUAL
OP_IF
    <アリスの公開鍵>
OP_ELSE
    "24h" OP_CSV OP_DROP
    <ボブの公開鍵>
OP_ENDIF
OP_CHECKSIG

新しいコミットメントトランザクションを作成したら、アリスとボブがお互いに古くなったコミットメントトランザクションに使ったrevokehashのプリイメージを相手に公開する。もしボブが裏切って↑の古いコミットメントトランザクションに署名してブロードキャストしてもアリスはボブが明かしたプリイメージを使って、出力1と出力2のコイン全てを入手することができる。

プリイメージを使わずRevocation Keyを利用

ただ、最近のLNの実装ではこのプリイメージを使った仕組みは使われておらず、代わりにRevocation Keyが使われている。

↑の出力2のスクリプトが↓のスクリプトになる。

OP_IF
    <revocation-pubkey>
OP_ELSE
    "24h" OP_CSV OP_DROP
    <ボブの公開鍵>
OP_ENDIF
OP_CHECKSIG

見ての通り、ハッシュとそのプリイメージを使う形から、ある公開鍵<revocation-pubkey>による署名があればコインを取り戻せるようになっている。

この<revocation-pubkey>を導出する方法だが、まずLNのチャネルをオープンする際にそれぞれ自分のベースポイントを相手に伝える。ここでいうベースポイントは楕円曲線上のポイントなので言ってしまえば公開鍵。ただこのポイント自体はチャネルのセットアップに使うだけで決済に使う公開鍵ではない。

このベースポイントを使って<revocation-pubkey>を導出する。
コミットメントトランザクションを新しく生成する際、ボブがプリイメージを新しく生成しそのハッシュをアリスに伝える代わりに、ボブは新しく生成したハッシュに楕円曲線のGを乗算し新しいポイントを作成しそのポイントをアリスに伝える。アリスはそのポイントをアリスのベースポイントに加算し<revocation-pubkey>を生成する。ちなみに(正確には元のハッシュが異なるが)アリスがボブから受け取ったポイントをボブのベースポイントに加算したのがタイムロックされたブランチのボブの公開鍵になる。
新しいコミットメントトランザクションを作成する際は、このコミットメントトランザクションの<revocation-pubkey>を作るのに使ったポイントの元のハッシュを交換する。(Payment Channelは鏡像となるトランザクションをお互いに作るのでアリスはその逆を行う)

※ LNの仕様は現在標準化されていないので、この <revocation-pubkey>の計算方法はLNの実装によって異なる。lndとかはもう少し算出方法が複雑。

プリイメージを使うパターンでなくRevocation Keyを使うメリットとしては、redeem scriptのデータ量ががOP_HASH160 <revokehash> OP_EQUALの分だけ少なくてすむ。

P2WSHを使うトランザクションの作成と署名

P2WPKHの署名方法について書いた↓ので

techmedia-think.hatenablog.com

今回はP2WSHの署名について。

単純なマルチシグでもつまならいのでOP_CSVのBIPに出てくるLNのトランザクションサンプルのP2WSHにしてみる↓

https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki#Lightning_Network

※ LNのホワイトペーパーでは古いコミットメントトランザクションの取消に↑のようなシークレットのプリイメージを使う方法が書かれてるけど、現在の実装ではプリイメージのハッシュにロックする方法は使われておらず、チャネル設立時のベースポイントに対してシークレットを加算して生成した鍵でロックする形に変わっている。

P2WSHにBitcoinを送付する

今回は↑の例に出てくる以下のスクリプトBitcoinを送る。

HASH160 <revokehash> EQUAL
IF
    <Bob's pubkey>
ELSE
    "24h" CHECKSEQUENCEVERIFY DROP
    <Alice's pubkey>
ENDIF
CHECKSIG

このスクリプトは、revokehashの元のプリイメージを知っていればボブの秘密鍵による署名でコインが入手でき、それ以外のケースではこのUTXOがブロックに入れられて24時間経過したらアリスの秘密鍵による署名でコインが入手できるというものになる。

このスクリプトをベースにしたP2WSHのscriptPubkeyを作る。P2WSHのwitness programは以下の形式になる。

0 <↑のスクリプトの32バイトハッシュ>

実際にbitcoin-rubyで↑のP2WSHに支払う処理は以下のように書ける。

require 'bitcoin'

Bitcoin.network = :testnet3

alice_key = Bitcoin::Key.from_base58('アリスの秘密鍵')
bob_key = Bitcoin::Key.from_base58('ボブの秘密鍵')

# シークレットの値
preimage = "apple"

# シークレットのハッシュ値
hash = Bitcoin.hash160(preimage)

# 24時間 = 10分ブロック計算で 24 * 6 ブロック
block_count = 24 * 6

# ロックスクリプト
redeem_script = Bitcoin::Script.from_string("OP_HASH160 #{hash} OP_EQUAL OP_IF #{bob_key.pub} OP_ELSE #{block_count} OP_NOP3 OP_DROP #{alice_key.pub} OP_ENDIF OP_CHECKSIG")

# P2WSHのscriptPubkey
script_pubkey = Bitcoin::Script.from_string("0 #{Bitcoin.sha256(redeem_script.to_payload.bth)}")

# 適当なUTXOを入力にしてP2WSH宛に送る。
tx = Bitcoin::Protocol::Tx.new
tx.add_in(Bitcoin::Protocol::TxIn.from_hex_hash('07b09f22abd2d78c41b2984f05d3dd83a3c5905771f0ee290b04f933e0c13d59', 0))
# P2WSHの出力
tx.add_out(Bitcoin::Protocol::TxOut.new(100000, script_pubkey.to_payload))
# おつり
tx.add_out(Bitcoin::Protocol::TxOut.value_to_address(760000, 'mt1hZLajqyc63NkWy7qvgiuum5nuTBdVZ6'))

# (これは非segwitなTxなので)to_payloadしたデータをsignrawtransactionなどで署名してブロードキャストする
tx.to_payload.bth

実際にtestnetにブロードキャストしたデータ
(txid = 4e70cfb5ca582aeb88924fe74dbcb49503699f0f09adb2493c7439d051668af7)↓

{
  "hex": "0100000001593dc1e033f9040b29eef0715790c5a383ddd3054f98b2418cd7d2ab229fb007000000006a47304402202ecef6c319d65d408d953d9f4dc6c8882e638d6493751c6728448fc14c8d98920220684a2c222c7b5529e6f290ae08ebe07b893de69c3f92b68fc9a9b7f804e027c9012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefaffffffff02a086010000000000220020ee6e25ff1fcf33396396cc82bb3702533fb1ad5289711262ed75e0dea34d84e4c0980b00000000001976a9148911455a265235b2d356a1324af000d4dae0326288ac00000000",
  "txid": "4e70cfb5ca582aeb88924fe74dbcb49503699f0f09adb2493c7439d051668af7",
  "hash": "4e70cfb5ca582aeb88924fe74dbcb49503699f0f09adb2493c7439d051668af7",
  "size": 234,
  "vsize": 234,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "07b09f22abd2d78c41b2984f05d3dd83a3c5905771f0ee290b04f933e0c13d59",
      "vout": 0,
      "scriptSig": {
        "asm": "304402202ecef6c319d65d408d953d9f4dc6c8882e638d6493751c6728448fc14c8d98920220684a2c222c7b5529e6f290ae08ebe07b893de69c3f92b68fc9a9b7f804e027c9[ALL] 02effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa",
        "hex": "47304402202ecef6c319d65d408d953d9f4dc6c8882e638d6493751c6728448fc14c8d98920220684a2c222c7b5529e6f290ae08ebe07b893de69c3f92b68fc9a9b7f804e027c9012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00100000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 ee6e25ff1fcf33396396cc82bb3702533fb1ad5289711262ed75e0dea34d84e4",
        "hex": "0020ee6e25ff1fcf33396396cc82bb3702533fb1ad5289711262ed75e0dea34d84e4",
        "type": "witness_v0_scripthash"
      }
    }, 
    {
      "value": 0.00760000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 8911455a265235b2d356a1324af000d4dae03262 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9148911455a265235b2d356a1324af000d4dae0326288ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mt1hZLajqyc63NkWy7qvgiuum5nuTBdVZ6"
        ]
      }
    }
  ]
}

0番めの出力がP2WSHの出力で、typewitness_v0_scripthashになっており、アドレスも表示されていないことが分かる。

witnessなトランザクションの作成と署名

↑でP2WSH宛に送ったBitcoinを入力にしたトランザクションの作成と署名を行う。

↑のスクリプトはアンロック条件が2つあるが、今回はシークレットを知ったボブがボブの秘密鍵を使った署名でコインをアンロックするパターンで実装する。 この条件でアンロックする際のwitnessのスタックは

<ボブの秘密鍵を使った署名> <revokehashのプリイメージ> <witness script>

になる。

version 0のwitness programで0の後に32バイトのデータがプッシュされているスクリプトをP2WSHのwitness programと認識し、以下のようなプロセスが実行される。

  1. witnessスタックの先頭から最後から1つ前までのスタックのアイテムをピックアップしてスタックを生成する。
  2. witnessスタックの最後のアイテムをredeem scriptとしてピックアップする。
  3. 1のスタックに対して2のredeem scriptを実行してスクリプトを評価する。

※ なおwitnessスタックにセットされたwitness scriptから計算したハッシュが元のscriptPubkeyの32バイトのハッシュ値と異なる場合はエラー。

この辺の処理は↓あたりで実装されている。

https://github.com/bitcoin/bitcoin/blob/v0.13.2/src/script/interpreter.cpp#L1356-L1407

実際にbitcoin-rubyで実装してみる。

P2WSHの場合も署名は、BIP-143↓で定義された新しいSIGHASHの生成方法に従う。
P2WPKHと異なるのは、scriptCodeの生成方法くらいかな。

techmedia-think.hatenablog.com

alice_key = Bitcoin::Key.from_base58('アリスの秘密鍵')
bob_key = Bitcoin::Key.from_base58('ボブの秘密鍵')

# シークレットの値
preimage = "apple"

# シークレットのハッシュ値
hash = Bitcoin.hash160(preimage)

# 24時間 = 10分ブロック計算で 24 * 6 ブロック
block_count = 24 * 6
# ロックスクリプト
redeem_script = Bitcoin::Script.from_string("OP_HASH160 #{hash} OP_EQUAL OP_IF #{bob_key.pub} OP_ELSE #{block_count} OP_NOP3 OP_DROP #{alice_key.pub} OP_ENDIF OP_CHECKSIG")

prev_tx = Bitcoin::Protocol::Tx.new('↑でブロードキャストしたトランザクションデータ'.htb)

tx = Bitcoin::Protocol::Tx.new

tx.add_in(Bitcoin::Protocol::TxIn.from_hex_hash('4e70cfb5ca582aeb88924fe74dbcb49503699f0f09adb2493c7439d051668af7', 0))
tx.add_out(Bitcoin::Protocol::TxOut.value_to_address(90000, bob_key.addr))

# BIP-143の仕様でSIGHASHを生成
sig_hash = tx.signature_hash_for_witness_input(0, prev_tx.out[0].script, 100000, redeem_script.to_payload)

# ボブの秘密鍵で署名
sig = bob_key.sign(sig_hash)+ [Bitcoin::Script::SIGHASH_TYPE[:all]].pack("C")

tx.in[0].script_witness.stack << sig # ボブの署名
tx.in[0].script_witness.stack << preimage.htb # revokehashのプリイメージ
tx.in[0].script_witness.stack << redeem_script.to_payload # redeem scrpt(witness script)

# トランザクションのrawデータが出力されるので、これをsendrawtransactionする。
tx.to_witness_payload.bth

実際にtestnetにブロードキャストしたデータ
(txid = fc45cf999310987860cf7d287d1f0118322ac31212aef1b279c7a3f8b55c83e0)が↓

{
  "hex": "01000000000101f78a6651d039743c49b2ad090f9f690395b4bc4de74f9288eb2a58cab5cf704e0000000000ffffffff01905f0100000000001976a914f8ae1df3f73d7d2e197b467b509dfedbf1b4cdc288ac0347304402203fd393fb54584fbcd89e24aff756696edd8d1889307030a6e97c7fcc7dcb4cee02207e484fca226d4d752ee69c0a9e972f58ff0f7debd8c1209911403837847c5efb0103a995e064a9146a25f59f6c9ebe7415c640c915bd48c4b7138a62876321038c811a50fa3d7a68426fef93d92c6897d7f0a1c16c6fb17e9b183ef868d8d16f67021440b2752103f50e3fdd76870c21581af66af261ffed186b780c024f8f2fc9daeac58bf8356368ac00000000",
  "txid": "fc45cf999310987860cf7d287d1f0118322ac31212aef1b279c7a3f8b55c83e0",
  "hash": "6dcce7541bf1e89698d7f616e09b5510fe75464947cab5fb3388cf0be96aaf76",
  "size": 265,
  "vsize": 130,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "4e70cfb5ca582aeb88924fe74dbcb49503699f0f09adb2493c7439d051668af7",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "304402203fd393fb54584fbcd89e24aff756696edd8d1889307030a6e97c7fcc7dcb4cee02207e484fca226d4d752ee69c0a9e972f58ff0f7debd8c1209911403837847c5efb01", 
        "a995e0", 
        "a9146a25f59f6c9ebe7415c640c915bd48c4b7138a62876321038c811a50fa3d7a68426fef93d92c6897d7f0a1c16c6fb17e9b183ef868d8d16f67021440b2752103f50e3fdd76870c21581af66af261ffed186b780c024f8f2fc9daeac58bf8356368ac"
      ],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00090000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 f8ae1df3f73d7d2e197b467b509dfedbf1b4cdc2 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914f8ae1df3f73d7d2e197b467b509dfedbf1b4cdc288ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "n4BrTVLsr1PUfqAneRpVqZt3kAYJRQY6Zf"
        ]
      }
    }
  ]
}

と正常にブロードキャストできた。

前回のP2WPKHとP2WSHのトランザクションが作れればLNのペイメントチャネルを実装をする上で必要なものは揃う。

P2SHでネストしたP2WPKHや、P2SHでネストしたP2WSHなんかもあるけど、基本的にSegwit非対応なウォレットとの互換のためだけの仕様なので、普通にペイメントチャネル実装する際に使うことはないだろう。

P2WPKHを使うトランザクションの作成と署名

だいぶ前に書き始めて放置してたのでちゃんと実装してみた。

以前↓のようにwitnessなトランザクションのパース方法について書いたので、

techmedia-think.hatenablog.com

続いてwitnessなトランザクションを作ってみる。

P2WPKHにBitcoinを送付

まず最初にP2WPKH(Pay-to-Witness-Public-Key-Hash)のscriptPubkeyに対してBitcoinの支払いをするトランザクションを作成する。

P2WPKHのscriptPubkeyは以下のフォーマットの22バイトのデータになる。

0 < 20-byte-pubkey-hash >

実際にtestnetでP2WPKHの支払いをしたトランザクションが↓

{
  "hex": "0100000001f55cb86d8d04d4759fb8b05a198cf4d48d790e6c64d00e072aed98281d0ebff1010000006b483045022100fe718c5f0bb58d86225e1d9370f858b8c864f00112c6a33f9910fa7ea8e9c34b02203cef1a73a0a8e210c6a264446bbb90c86b7b6f6e6f411aa505f0a835ff147204012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefaffffffff02806d0d00000000001600148911455a265235b2d356a1324af000d4dae0326250a1c917000000001976a9142c159d64daa0de5ae6abac61a9416c8a54e834bd88ac00000000",
  "txid": "fdb55428ed5a1949cad4732fcb1be031a9790e7e0f651fd33129909065511580",
  "hash": "fdb55428ed5a1949cad4732fcb1be031a9790e7e0f651fd33129909065511580",
  "size": 223,
  "vsize": 223,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "f1bf0e1d2898ed2a070ed0646c0e798dd4f48c195ab0b89f75d4048d6db85cf5",
      "vout": 1,
      "scriptSig": {
        "asm": "3045022100fe718c5f0bb58d86225e1d9370f858b8c864f00112c6a33f9910fa7ea8e9c34b02203cef1a73a0a8e210c6a264446bbb90c86b7b6f6e6f411aa505f0a835ff147204[ALL] 02effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa",
        "hex": "483045022100fe718c5f0bb58d86225e1d9370f858b8c864f00112c6a33f9910fa7ea8e9c34b02203cef1a73a0a8e210c6a264446bbb90c86b7b6f6e6f411aa505f0a835ff147204012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00880000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 8911455a265235b2d356a1324af000d4dae03262",
        "hex": "00148911455a265235b2d356a1324af000d4dae03262",
        "type": "witness_v0_keyhash"
      }
    }, 
    {
      "value": 3.99090000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 2c159d64daa0de5ae6abac61a9416c8a54e834bd OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9142c159d64daa0de5ae6abac61a9416c8a54e834bd88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mjY3vKRzyHkpB5kbEwCxFNmkFb4wKVDVab"
        ]
      }
    }
  ]
}

0番目の出力がP2WPKHへの支払いで、scriptPubKeyのtypewitness_v0_keyhashになっているのがわかる。

witnessなトランザクションの作成と署名

↑でP2WPKH宛に送ったBitcoinを入力にしたトランザクションの作成と署名をする。

ここで注意するのが、署名の方法も従来とは変わっているということ。
署名時に計算するトランザクションのSIG_HASHの新しい計算方法がBIP-143で定義されている↓

techmedia-think.hatenablog.com

BIP-143の仕様をbitcoin-rubyで実装してみたのが↓

まだプルリクは出してないけど、bitcoin-rubyにsegwit対応のコードを実装してるのが↓

作成したプルリクがbitcoin-rubyの0.0.11で取り込まれたのでそのバージョンからは以下のコードで署名可能

(仕様が変わる可能性はあるけど)現状の↑を使ってP2WPKHのUTXOを使用するコードは以下のように書ける↓ P2WPKHのwitness<signature> <pubkey>の構成になる。

from_key = Bitcoin::Key.from_base58('P2WPKHのUTXOの秘密鍵')
prev_tx = Bitcoin::Protocol::Tx.new('P2WPKHのUTXOを持つトランザクションのrawデータ')

tx = Bitcoin::Protocol::Tx.new
tx.add_in(Bitcoin::Protocol::TxIn.from_hex_hash(prev_tx.hash, 0))
tx.add_out(Bitcoin::Protocol::TxOut.value_to_address(880000 - 10000, from_key.addr))

# BIP-143の仕様でSIGHASHを生成
sig_hash = tx.signature_hash_for_witness_input(0, prev_tx.out[0].script, 880000)

sig = from_key.sign(sig_hash) + [Bitcoin::Script::SIGHASH_TYPE[:all]].pack("C")

# witnessを構成(P2WPKHなのでwitnessは、 "<署名> <公開鍵>" の構成)
tx.in[0].script_witness.stack << sig
tx.in[0].script_witness.stack << from_key.pub.htb

# witnessなペイロード
puts tx.to_witness_payload.bth

生成したwitnessペイロードを実際にtestnetでブロードキャストしたトランザクションが↓

{
  "hex": "010000000001018015516590902931d31f650f7e0e79a931e01bcb2f73d4ca49195aed2854b5fd0000000000ffffffff0170460d00000000001976a9148911455a265235b2d356a1324af000d4dae0326288ac02473044022009ea34cf915708efa8d0fb8a784d4d9e3108ca8da4b017261dd029246c857ebc02201ae570e2d8a262bd9a2a157f473f4089f7eae5a8f54ff9f114f624557eda7420012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa00000000",
  "txid": "07b09f22abd2d78c41b2984f05d3dd83a3c5905771f0ee290b04f933e0c13d59",
  "hash": "ac6506dab409881178f4d1b416188f903e85a5d588f175d930911532cb53ac58",
  "size": 194,
  "vsize": 113,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "fdb55428ed5a1949cad4732fcb1be031a9790e7e0f651fd33129909065511580",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "3044022009ea34cf915708efa8d0fb8a784d4d9e3108ca8da4b017261dd029246c857ebc02201ae570e2d8a262bd9a2a157f473f4089f7eae5a8f54ff9f114f624557eda742001", 
        "02effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa"
      ],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00870000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 8911455a265235b2d356a1324af000d4dae03262 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a9148911455a265235b2d356a1324af000d4dae0326288ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mt1hZLajqyc63NkWy7qvgiuum5nuTBdVZ6"
        ]
      }
    }
  ]
}

次はP2WSHの署名を。