Ameba Ownd

アプリで簡単、無料ホームページ作成

ハッタリからはじめよ - bluff driven -

少しだけ易しいMastering Bitcoin【第6章】

2018.08.26 14:45


Bitcoinの技術面を学ぶにはこれしかないと言われているほどの名著Mastering Bitcoin を今更読んだので、自分の中での復習を兼ねて、ところどころ解説付きでまとめを書いていきます。


元の本ではサラッと流されているところもしっかり解説するつもりでお節介に書いたので初学者の人には分かりやすいかもしれません。


日本語版PDFはここで無料公開されているので翻訳者の方々への感謝を常に感じながら読みました。

無料公開版ということもあって誤字脱字もたまにあったりするので、

しっかり製本されたものが読みたいという方はAmazonで買って読むことをお勧めします。


前回↓


第6章 Bitcoinネットワーク


⑴Peer-to-Peerネットワークとは

Bitcoinは、インターネット上のPeer-to-Peer(P2P)ネットワークとして構築されています。


P2Pネットワークとは、ネットワークに参加しているコンピュータがそれぞれ同等の立場を持ち、"特別な"ノードがなく、全てのノードがサービス負荷を負担し合っていることを指します。つまり、サーバ、どこかに中央を持つサービス、ネットワーク内の階層はありません。peer-to- peerネットワーク内のノードは、同時にサービスの提供者でもあり、消費者でもある(サーバでもクライアントでもある)のです。


P2Pネットワークの代表的な例は初期のインターネットそのもので、IPネットワーク上のノードは全て平等でした。現在のインターネットの構造は階層的になりましたが、インターネットプロトコル(IP)はまだフラットなトポロジー(ネットワークの構造のこと)を保っています。

Bitcoinが現れる前にP2Pテクノロジーを使った最も大きく最も成功したサービスはファイル共有であり、Napster、BitTorrent、Winnyなどがあります。


本題に入る前に、言葉の定義を確認しておきます。

"Bitcoinネットワーク"という言葉は、Bitcoin P2Pプロトコル(プロトコルは通信規格のことだと思ってもらえれば)で動作しているBitcoinノード全体を指します。

一方で、"拡張されたBitcoinネットワーク" という言葉も登場します。

実はBitcoinP2Pプロトコル以外にもマイニング、軽量orモバイルのウォレットに使われたりしているStratumなどの様々なプロトコルが存在しています。他のプロトコルで動くノードをBitcoin P2Pプロトコルネットワークに繋げることももちろん可能です。例えば、StratumサーバはStratumマイニングノードをStratumプロトコルを通してメインのBitcoinネットワークに接続させ、StratumプロトコルとBitcoin P2Pプロトコルの橋渡しをします。このようにBitcoin P2Pプロトコルに加えて、Stratumプロトコルやプールマイニングプロトコルなどの他のプロトコルを全て含む全ネットワークを"拡張されたBitcoinネットワーク"という言葉で表します。


⑵ノードのタイプと役割

Bitcoinネットワークのノードは上記の画像のようなルーティング、ブロックチェーンデータベース、マイニング、ウォレットという4つの機能の集合体です。


全てのノードはBitcoinネットワークに接続するためのルーティング機能を必ず持っていますが、その他の機能は 持っていたり持っていなかったりしており、それによってそのノードのタイプ・役割が変わります。


ノードのタイプには以下の画像にあるようなものが挙げられます。

各タイプについては以降の章で解説して行きます。

①Bitcoin core (リファレンスクライアント)

ルーティング、ブロックチェーンデータベース、マイニング、ウォレットの4つ全ての機能を持っています。


②フルブロックチェーンノード(フルノード)

ルーティング機能の他に、完全で最新のブロックチェーンデータベース(2018年初頭の時点で150GBを越えるサイズ)を持っています。

他のノードと独立に最初のブロック(genesisブロック)から最新のブロックまでを独自で保持し、独立してトランザクションを検証します。

フルブロックチェーンノードは新しいトランザクションのブロックをBitcoinネットワークから受け取り、それらを検証した後ブロックチェーンのローカルコピーに追加していきます。

これによってフルノードは外部への参照をすることなく閉じられた形で自律的にトランザクションを検証できます。

実際に動作しているフルブロックチェーンノードの90%以上は①のリファレンスクライアント(Satoshiクライアント)です。


③軽量ウォレット(SPVウォレット)

ルーティングとウォレット機能、またブロックチェーンデータベースの一部のみ管理しており、 simplified payment verification(簡略化された支払いの検証という意味)または SPV と呼ばれている方法でトランザクションを検証します。

ユーザウォレットは、主にデスクトップBitcoinクライアントなどはフルノードになっていますが、スマートフォンなどリソースが限られているデバイスでは多くのユーザウォレットが SPVノードになっています。


④独立したマイニングノード

ルーティング、マイニング、フルブロックチェーンデータベースを持っています。

マイニングノードの中でも独立してマイニングが可能なノードはフルノードでもあり、ブロックチェーンの完全なコピーを管理しています。


④軽量マイニングノード

マイニング機能とプールマイニングプロトコルのルーティングを持っています。

マイニングプ ールに参加している軽量ノードであり、フルノードを管理しているプールサーバに依存しています。


⑤Pool Protocol servers

Bitcoin P2Pプロトコルとその他のプロトコルで動いているノードとを結ぶ架け橋の役割をするものです。


⑥軽量(SPV) Stratumウォレット

軽量(SPV)ウォレットのルーティング機能がStratumプロトコルverです。


このような様々なタイプのノードが以下の画像のように繋がって”拡張されたBitcoinネットワーク“を形成しています。


⑶Bitcoinノード同士の接続

もしあなたが新しいノードを立ち上げた時、Bitcoinネットワークに参加するには、すでにBitcoinネットワークに参加しているノード(ピア)を少なくとも一つ見つけて接続しなくてはなりません。


以下に新しいノードがBitcoinネットワークに参加するまでの手順を書いて行きます。


①新しいノードが最初に接続するピアを見つける

・あらかじめBitcoin core クライアントに含まれている5つのDNSシードに接続してBitcoinノードのIPアドレスリストを得ます。

・または、このDNSシードを使用せず、「少なくとも一つのBitcoinノードのIPアドレス」を与える方法もあります。


DNSシードはBitcoinノードのIPアドレスリストを提供するDNSサーバで、安定的にリクエストを受け付けているBitcoinノードの静的なIPアドレスを返すものと、クローラや長期的に稼働しているBitcoinノードによって集められ たBitcoinノードのリストからランダムにいくつかを選んで返却するカスタマイズされたBIND(Berkeley Internet Name Daemon)で実装されているものがあります。


②そのピアに接続する

IPアドレスを得たピアに対して、ノードはTCPコネクションを確立し、通常8333番ポート(一般にBitcoin によって使われているポート)または指定されているなら代替のポートを使って接続します。

その後、以下の画像のような"挨拶"をします。

あなたが立てた新規ノード(画像ではNode A)は、最初に以下の要素を含むversion messageをピアノード(Node B)に対して送ります。自己紹介みたいなものですね。



これを受け取ったピアノードはコネクションを承認し確立するためにverackというメッセージを返します。

さらに場合によっては、もしコネクションのお返しにピアとして接続し直す場合は自身の version messageを送ります。


③Bitcoinネットワーク全体に接続していく


新しいノード(画像ではNode A)は一度1つのコネクションを確立すると、addr messageという自身の IPアドレスが含まれた情報をコネクションを確立したノード隣接ノード(画像ではNode B)に送信します。

これを受け取った隣接ノードは次々にこのaddr messageを彼らの近くのノードに転送して行きます。

また、新しく接続されたノード(Node A)は getaddrを隣接ノードに送ることで他のピアのIPアドレスリストを要求することもできます。そうやって受け取ったIPアドレスリストから、新しいノードは接続するピアを新たに見つけることができ、addr messageを送って自身の存在を知らせることができるのです。


注意点として、ノードは2、3個の異なったピアと接続し、Bitcoinネットワークへの多様な接続を確立しておかなければいけません。この理由は、それぞれのノードは連絡なく通信が切れたり復活したりするためです。

初期動作プロセスを終えた後ノードは直近でうまくコネクションを張れたピアを 覚えておきます。

コネクション上に何もトラフィックがない場合、ノードは定期的にコネクション維持のためメッセージを送ります。90分以上何の通信もしなかったコネクションがあった場合、ノードはコネクションが切れたとみなすのです。

そしてそれら以前繋がっていたピアの全てがコネクションリクエストに答えなければ、そのノードは再度DNSシードノードを使って初期動作プロセスを行います。

このように、Bitcoinネットワークは常に一時的なノードやネットワークの問題を調整しながら、中央のコントロールなしに必要に応じて有機的に成長または縮小を繰り返していくのです。



⑷新しいノードのブロックチェーンとの同期


新しいノード(今回はBitcoin core==フルブロックチェーンノードとする)は、ブロックチェーンを持っていないので、Bitcoin Coreに埋め込まれている一番最初のブロック(genesisブロック)しか知りません。


このノードがBitcoinネットワークと接続して始めにやることは、Bitcoinネットワークからブロックチェーンをダウンロードしてローカルに再構築することです。


この手順を以下の画像で説明して行きます。


このブロックチェーンの同期作業では送るブロック数が多すぎてデータ量が膨大になってしまいかねません。そこで「1ピアに対する送信中状態最大ブロック数(MAX_BLOCKS_IN_TRANSIT_PER_PEER)」という形で制限が設けられている点もポイントです。


⑸SPVノードとそのトランザクション検証


①Simplified Payment Verification (SPV) ノードとは

全てのBitcoinノードがフルノードであれば理想的かもしれませんが、多くのBitcoinクライアントはディスク容量や計算スピードが限られているスマートフォンやタブレット、組み込みシステム(IoTとかイメージしてもらえば)などのデバイス上で動作する必要があります。

そのようにフルブロックチェーンデータを保持するのが現実的に不可能な場合に、使われるのがsimplified payment verification(SPV)という「ブロックとその中のトランザクション全ての情報を保持するのではなく、各ブロックの軽い情報だけ保持していて、データが必要になったら他のフルノードから関心のあるトランザクションだけが入ったブロックだけをもらう」手法です。


この手法を用いているクライアントを、軽量クライアント・SPVクライアントと言います。

また、ほとんどのBitcoinウォレットはこのSPVノードになっており、それらをSPVウォレットと言います。


②新しいSPVノードのデータ取得

SPVノードはブロックヘッダだけをダウンロードし、トランザクション自体のデータはダウンロードしません。


ブロックヘッダとは、主にそのブロックの



を含むデータです。(細かい構造は[第8章前半]で解説)

(以下画像のイメージ,Bitcoin white paperから引用)

※これまでトランザクション単位での話が多かったと思いますが、ブロックヘッダは「ブロック」単位で存在するものです(トランザクションが取引データの1行だとすると、ブロックはその台帳の1ページのようなものでした)


トランザクションデータがないブロックヘッダだけのブロックチェーンはフルブロックチェーンの約1/1000の大きさになります。

 SPVノードはBitcoinネットワーク上の全てのトランザクションデータを持っているわけではないため、使用可能な全てのUTXOセットを構築できません。SPVノードは、必要に応じてブロックチェーンの関連した部分のみを提供する他のピアに頼るという方法を用いてトランザクションを検証します。


新しいSPVノードがこのブロックヘッダデータを取得する手順は以下の画像のようになっています。

新しいフルノードがブロックチェーンデータを同期するのとほとんど同じ手順になっています。

ブロックヘッダを得るために、SPVノードは getblocks messageの代わりに getheaders message を使います。getheaders message を受け取ったピアは2,000個までのブロックヘッダを1個の headers message で返送します。


③SPVノードのトランザクション検証

SPVノードは多くの場合、ウォレットなので、マイナーのようにブロックの生成(第8章で説明)をしたりはしません。


そのためSPVウォレットがトランザクションのデータを必要とする場合は

です(3については補足あり)


1の場合は、自分が作成するトランザクションのInputに入れる「自分のアドレスへのUTXO」のデータを持っていなくてはいけませんし、2の場合は、自分のウォレットに紐づいたUTXOをかき集めてこなければなりませんね。


3の場合は、第8章(2)-①の「トランザクションプールorメインブランチブロックチェーンのブロックに同じトランザクションがあるか確認する」「それぞれのインプットに対して、メインブランチブロックチェーンかトランザクションプールにインプットが参照しているトランザクションアウトプットが見つかるかを確認する」というという部分でトランザクションデータを確認しなくてはいけません。

(もしかしたら3の場合は「SPVノードが他人の新規トランザクションを渡された場合はそのままどこかのフルノードに横流しする」or「そもそもSPVノードに新規トランザクションを未検証プールに入れる要請は来ない」という仕様になってるかもしれないです。以降は1,2にフォーカスして説明して行きます。)


以下でSPVが自分で持たないトランザクションデータをどのように参照するかを説明して行きます。


SPVとは、「ブロックとその中のトランザクション全ての情報を保持するのではなく、各ブロックのブロックヘッダだけ保持していて、データが必要になったら他のフルノードから欲しいトランザクションだけが入ったブロックだけをもらう」手法でした。


SPVは、必要となったトランザクションデータ[A](1の場合は「自分のアドレスにoutputされているUTXO」,2の場合は「その新規トランザクションに入れられているUTXO」)を、フルブロックチェーンノードに「教えて!」と要求します。


しかし、そうして返ってきた[A]が含まれるブロックは正しいかどうか分かりません。そのフルノードが「持っているブロックチェーン」にあるデータと全く違う嘘っぱちのブロックデータを返してきている可能性もあります。


しかし、SPVノードは全てのブロックのブロックヘッダを持っており、その中には各ブロックの「マークルルート」を持っているのでした。マークルルートは「そのブロックに含まれる全てのトランザクションを(markle treeで)ハッシュ化したもの」でしたね。


SPVがフルブロックチェーンノードに必要なトランザクションデータを要求する際に、「そのトランザクションが含まれるブロックの他のトランザクションデータのダブルハッシュ値[B](authentication path, merkle pathと呼ばれる[第7章で解説])」も一緒にブロックに入れて返してもらいます。


それを受け取ったSPVは「[A]をダブルハッシュ化したもの」と[B] をmarkle treeにかけていき、返ってきたブロックのマークルルートを求めます。

そしてこの導出したマークルルートと自分が保持しているそのブロックのマークルルートが一致すれば、そのブロックには間違いなく[A]が含まれている、つまり正しいデータだということが検証できます。


しかし、一点問題があります。

上記のやり方で検証できるのはあくまで「そのブロックにそのトランザクションが存在する」ということのみです。

これによって例えばUTXOの存在を確認した際に「そのUTXOが二重使用(double-spend)されたものではないのか」を確認することはできません。(二重使用は二重支払いとも言い、この場合は「そのUTXOが[A => B, A=>Cと二重で送られた、お金が複製された]場合のC である」ということ)

そしてフルノードから返ってきたUTXOが実は二重支払いされたものだったとしたら、そのUTXOは使えませんよね。

通常はそのブロックがある程度の深さにあれば二重払いされたUTXOが存在する可能性は低いので「block depthが6以上のブロック」に存在するUTXOかどうかをを確認する必要があります。


(通常、Bitcoinの二重支払いが起こったとしてもノード全員の検証によって不正だと発見されそのトランザクションが拒否されますが、もしブロック自体が同時に作られた場合にはどちらかのブロックが伸びていくまでは、そのトランザクションが二重支払いである可能性が消えません[第8章で詳しく説明]。なので、その受け取ったUTXOの存在するブロックの上に6つ以上のブロックが積み重なれば、それは二重支払いされたものではないと断定できるとされています。)


また、そもそも上記の検証でSPVノードが”拠り所”にしているブロックヘッダのデータを提供してくれたフルノードとトランザクションデータを要求したフルノードがグルだった場合は、騙されてしまいます(ネットワーク分割攻撃、シビル(Sybil)攻撃[攻撃者が沢山のID(ユーザ,ノード)を作り、(複数の人物だと思わせて)攻撃すること]という)。


(これらによって起こる被害は「SPVノードが誤ったUTXOしか得られず、永遠に送金できないようにする」「ネットワーク分割攻撃をすることでSPVノードに対して本当のメインブロックチェーンとは全く違うブロックチェーンを本物だと思わせ、送金したように見せて現実世界での商品を貰っていく」くらいしか思いつかない…)


これらの問題の対策として、SPVノードはランダムにいくつかのノードと接続するようにしておく必要があるということも覚えておきましょう。


SPVノードはこのような仕組みで、自分自身はフルブロックチェーンデータを持たず、かつ代わりにデータについて教えてもらうフルノードを信用することもなく、必要なトランザクションデータが用意できるというわけです。


しかし、SPVノードではこのように主に自分がトランザクションを作るときのUTXOつまり自分に関連あるデータばかりを取得するため、取得しているデータからウォレットのBitcoinアドレスがもれてしまうというプライバシー問題があります。

これに対する解決策を次の節で見て行きます。


(6)Bloom filter

SPVウォレットがフルノードに対して、自分のアドレスに関係するトランザクションだけを送ってもらおうとして自分のBitcoinアドレスを直接渡してしまうと、そのアドレスとノードが紐付けられて匿名性が下がる(「このアドレスとこのアドレスはこのノードのアドレスなんだ」と分かってしまうとそのノードが持っている財産やBitcoinの使用履歴が筒抜けになってしまう可能性がある)という危険があります。


そこでBitcoinアドレスの代わりにBloom Filter を送ることでプライバシーをある程度保護するという方法が取られています。Bloom Filterの実装はBIP0037(Bitcoin Improvement Proposal 37)に書かれています。


具体例を交えながらBloom Filter を説明していきます。


えたろうのSPVウォレットの持つ全てのBitcoinアドレスが「nakatumaisgreat」(Aとする)「nakakitaisgreat」(Bとする)だったとします。(Bitcoinアドレスは一つのウォレットに対して複数あるのでした[詳しくは第4章のウォレット参照])


えたろうはSPVノードを運営しており、これらの自分のBitcoinアドレスに関係したトランザクションをフルノードに要請したいと思っています。


この時、以下の画像のように

を用意します。これがBloom Filterです。


今回の例では N = 16, M = 3とします。

この用意したBloom Filterにまずは「nakatumaisgreat」(A)(正確には「Bitcoinアドレス(A)をBase58decodeしてpublic key hash」)を掛けます。


具体的には

という操作をします。すると、以下の画像のようになります。

次に、もう一つのBitcoinアドレスである「nakakitaisgreat」(B)(正確には「Bitcoinアドレス(B)をBase58decodeしてpublic key hash」)を同様に同じBloom Filterに通します。


この時、Bit列の既に1になっている部分を1に変える場合は、そのまま1のままになります。

以下の画像のようになりますね。

このように自分がデータを要求したいBitcoinアドレスを全て通して出来上がったBloom Filter(Bit列と使用したハッシュ関数達)を含むfilterload messageをフルノードに送信するのです。


ちなみにこの時、nFlagsというパラメーターを設定することでデフォルトで要求する「自分のBitcoinアドレスが送り先(output)になっているトランザクション」だけではなく、「自分のBitcoinアドレスに関係しているトランザクションがインプットに使われているトランザクション」も同時に要求することができます。


このBloom Filter を受け取ったフルノードは以下の画像のように、全てのトランザクションのアウトプット(にあるP2PKH scriptの中の公開鍵ハッシュ)をBloom Filterに通し、その結果が受け取ったBit列と同じになるもののトランザクションデータを見つけます。


そしてbloom filterにマッチしたトランザクションを含む tx message 、そのトランザクションが含まれるブロックのヘッダ、そのブロック内の他のトランザクションのハッシュ値を含む merkleblock message をSPVノードに返却するのです。


一連の流れが以下の図(参考文献(1)から引用)にまとまっています。

SPVノードがfilterload messageとgetblocks messageを同時にフルノードに送ったのち、フルノードが送られてきたbloom filter からブロックを特定し、「ブロック一覧リスト」であるinv(Inventory)をSPVノードに返却し、その後、SPVノードがinvに基づいてget data message を送り、ブロックの中身データが入ったmerkleblockが返って来るという流れです。

この仕組みにより「nakatumaisgreat」(A)「nakakitaisgreat」(B)に関するトランザクションを通した時には必ずBit列の出力結果が同じになるので、自分の欲しいトランザクションデータは必ず返って来ることがわかります。


一方で、全く別のBitcoinアドレスを通した時にも出力結果のBit列が同じになる可能性があります。

「watanabeisgreat」(C)というBitcoinアドレスを通した時に同じBit列が出力されたとすると、この(C)に関するトランザクションデータもSPVノードに返って来ることになります。


しかし、余分なデータが返ってきた場合には、SPVノードがその中から自分の必要なデータを選べばいいだけですね。

つまり、このBloom Filterという手法を使えば、自分のBitcoinアドレスをフルノードに明かすことなく「少なくとも自分が必要なトランザクションデータ」だけは必ず返って来ることになります。


上記の仕組みからも分かる通り

Bloom Filterでは、

を用意しますが、

このMとNが大きくすると、より精密なBloom Filterとなり「自分の欲しいデータ以外を極力少なく(つまり効率的に)」します)が、一方でそれだけBloom Filterが自分のBitcoinアドレスだけのFilterに近づくのでプライバシーが失われていきます。

逆にこのMとNを小さくすると、より粗いBloom Filterとなり「自分の欲しいデータ以外のデータもたくさん含まれる」ようになりますが、プライバシーはより守られます。


このようにBloom Filter ではSPVノード自身が MとNを設定することで「どれくらいプライバシーを犠牲にして効率をとるか」を決定できるようになっているのです。


ちなみに一度送ったBloom Filterに新たにパターンを増やす場合は filteradd message をフルノードに送ることでパターンをbloom filterに追加できます。またbloom filterを削除するためには、 filterclear message をピアに送ります。bloom filterからあるパターンだけを削除することはできないので、この場合 SPVノードは一度bloom filterを削除してから新しいbloom filterを送り直します。


(7)トランザクションプール

Bitcoinネットワーク上のほとんどのノードは 「メモリプール 」または 「トランザクションプール 」と呼ばれる未検証トランザクションの一時リストを持っています。


ノードはこのプールを使って、Bitcoinネ ットワークに伝わっていてもまだブロックチェーンに含まれていないトランザクションをトラッキングしています。例えば、ウォレットを持っているノードは、Bitcoinネットワークに伝わっていてもまだ承認されてい ないウォレットへの入金トランザクションを一時的にこのトランザクションプールに保持しています。


いくつかのノードはorphan(孤児)トランザクションを入れておく別のプール(orphanトランザクションプール)も持っています。

もし新しいトランザクションのインプットがまだノードが知らないトランザクションを参照していた場合、親トランザクションが到着するまでorphanトランザクションは一時的にorphanプールに保存されます。

新しいトランザクションがノードに届いて、トランザクションプールに追加される際に、ノードはorphanプールにあるorphanトランザクションがこの新しいトランザクションのアウトプットを参照していないかチェックします。もし参照していれば、orphanプールから削除してトランザクションプールに追加されます。


トランザクションプールもorphanプールも、常に変更されていくためローカルメモリに保持され、永続的なストレージには保存されません。

そしてこれら二つのプールはノードの初期起動時には空になっており、起動したその瞬間から新規トランザクションを受け取るたびに増えます。


また、いくつかのBitcoinクライアントの実装では「UTXOデータベース」「UTXOプール」も管理しています。

このプールはブロックチェーン上の全てのUTXO(未使用アウトプット)を集めたものです。

トランザクションプールやorphanプールと違って、UTXOプールの初期状態は空ではなく最初から数百万個の未使用トランザクションアウトプット(2009年からのトランザクションアウトプット)を持っています。


UTXOプールはローカルメモリまたは永続ストレージのデータベースに保持されています。


(8)アラートメッセージ

アラートメッセージはBitcoinの"緊急放送システム"で、コアのBitcoin開発者たちが緊急メッセージを全てのBitcoinノードに送れる仕組みです。

重大なバグなどが発生した時に使われます。


この緊急放送は、以下の要素を含むalert messageによって伝搬されます。


このalert messageを受け取ったノードはそれを検証し、有効期間をチェックし、他の全てのピアにアラートメッセージを伝搬します。このため、Bitcoinネットワーク上をすばやく伝搬することができるようになっています。

そしてこのalert message は公開鍵で暗号学的に署名されており、公開鍵に対応した秘密鍵は何人かの選ばれたコア開発メンバーによって保持されています。このデジタル署名によってBitcoinネットワークを嘘のアラートが伝搬しないようになっているのです。


<感想>

・PoWとかマイニング関係は8章だから、まだ全く出てきてないんだった。。。

・デジタル署名が何か相変わらず出てこない


<参考文献>

(1) https://qiita.com/lotz/items/1aa6cf18aa193f40c647



続き↓