はじめに
記事の内容
今の時流的に、技術者としてビットコインとかブロックチェーンなんもワカラン状態は気分が良くないので、周回遅れながらゼロから実装する経験を積みたい👨💻
と思っていたところ、上記のawesome-blockchainリポジトリのImplementation of Blockchainの項目に、フルスクラッチでライトなビットコインを作るチュートリアルがまとまっていまして、その中からNaivecoinを写経してみることにしました。
選定理由は、
- build-your-own-xでも紹介されていた
- ちょっと分かるTypeScriptで実装されてる
- パッとみた感じ実装量多くない割には網羅されてる感
等々です(結構古いリポジトリなので、cloneして動かす場合はnpm audit fix
とかした方が良さそうです!)。
Naivecoinを写経しながら「ブロックチェーンとはなんぞや?」レベルからの理解を積み重ねていきました。
記事の内容としては、ブロックチェーン学習のためのメモという極個人的な内容になっていることをご了承ください。
(以降、「ブロックチェーン」はビットコインにおけるブロックチェーンを指すこととします)
Naivecoin
Naivecoinの概要
TypeScript製の小さくてシンプルなブロックチェーンノードの実装です。
マイニング(ブロックの作成)やウォレット(秘密鍵の管理)等の機能が一通り実装されています。
ノードを立ち上げるとWebSocket(本家はノーマルなTCP)で別ノードと繋がり、トランザクションやブロックのやりとりを行います。
Naivecoinのノード上では、ユーザ操作用のインターフェースとしてTCPサーバが動作しており、curlで叩くことでトランザクションを発行したり、ウォレット残高を確認できます。
Bitcoin Coreにおけるbitcoin-cliやJSON-RPCの役割みたいな感じですね。
GitHubリポジトリの他に、作者自身による解説ブログも公開されています。
Naivecoinの簡単な動作
基本的な動作はビットコインと類似しています。
トランザクションを発行し、ブロックチェーン上に永続化されるまでのイメージを簡単にまとめます。
(1) ユーザがcurlでノードを叩き、トランザクション生成を依頼します(`sendTransaction`)
(2) ノード内でトランザクションを生成し、自身のトランザクションプールに追加しつつ、他ノードにブロードキャストします
(3) ブロックを採掘したいノードをcurlで叩き、ブロック生成を依頼します(`mineBlock`)
(4) ノードは、ブロックを採掘したご褒美(`Coinbase Transaction`)を含めたトランザクションの塊をもとにブロックを生成します
(5) 生成に成功したら自身のブロックチェーンにブロックを追加しつつ、他ノードにブロードキャストします
本家ビットコインであれば、(3)のブロック採掘はノードが自律的に行いますが、あくまでNaivecoinはチュートリアルなのでcurlの手動実行で動かします。
Naivecoinは、Bitcoin Core(本家ビットコインノードのリファレンス実装かつ仕様書)と同じように、ウォレット機能/ブロックチェーンの全データ保持/マイニング機能/ネットワークのルーティングといった、ノードとして必要なほぼ全ての機能を有しています。
Bitcoin Coreのように自身専用の秘密鍵を生成するので、個人毎にノードがあるイメージです。
以降では、Naivecoinを写経する中で、ブロックチェーンについて調べたメモを書き下していきます。
ブロックチェーンを全く知らない状態から入ったので、Naivecoin写経の内容というよりビットコイン概論的メモになっています。
写経しながら取っていたメモ
ブロックチェーン
- ブロックチェーンは改ざん耐性を持つ分散データベース
- 順序付けられたレコード(ブロック)をハッシュで繋いだデータ構造でデータを保持
- 世界中のノード(コンピュータ)が繋がり合うことでP2Pネットワークを形成(2019年時点では1万ノード程)
- 各ノードは仕様(BIP)で決まったプロトコルをTCP上で喋り繋がっている
- ノードのリファレンス実装かつ仕様書としてBitcoin Coreが公開されている(Satoshi Nakamoto作)
- 仕様に準拠すればノードは自作可能。そのため、Bitcoin Core以外にも多くの実装アリ。Go製のbtcd等
- プロトコルさえ合えば誰でも参加できるため、素人目線ではいくらでも不正なノードを作れる気がするが、「不正すると損するが従うとトクですよ!」という行動経済学で不正を防御している(ビザンチン将軍問題へのひとつの回答)
- 不正をしないノードが過半数いればうまくいく仕組み。もちろん攻撃手法がないわけではない
- 実装上、ブロックチェーンはBlockクラスの配列で表すデータ構造。前ブロックのデータのハッシュ値をプロパティに持たせることでチェーンのように繋がっている。ブロック内容を変更するとハッシュ値が変わるので、ブロック内容の改ざんチェックが容易
- 配列の先頭はどうするのかと言えば、ソースコード上でハードコードしたブロックを入れる(ジェネシスブロック)。余談だが、ビットコインでは、ハードコードされた値が他にもいくつか存在する模様(e.g. DNSサーバの位置)
P2Pネットワーク
- NaivecoinではWebSocketで各ノードを繋げる。本家ビットコインはノーマルなTCP上で繋げている
- メッセージの種類を定義することで、ノードから話しかけられたときにどのアクションを取れば良いのか判定できるようにする。例えば「最新のブロックをください!」「ブロックチェーンの全データをください!」
ユーザ操作API
- ユーザがノードを操作するためのAPIを作る
- ビットコインはP2Pネットワークなので、取引所等を使わない限り、自分用のノードを立ち上げて参加するイメージ
- P2Pでノード同士が話し合う口とは別に操作用のAPIを作成。公開鍵/秘密鍵作ったり、トランザクションを発行したり、様々な操作を行えるようにする
Proof of Work
- 本来的には単なる「作るのは困難で検証が簡単なデータ」のこと1
- ブロックのハッシュ値は「一定個数以上の0から始まるハッシュ値(ある一定以下のビット列)」しか受け入れないようにする
- 各ノードは、最新ブロックのデータと任意の文字列(nonce)をハッシュ関数に入れて「一定個数以上の0から始まるハッシュ値」を探す(マイニング)。最新ブロックのデータは定数なので、nonceを総当たりで変更し、出力されるハッシュ値を確認する。この試行錯誤に莫大な計算コストを投じた証明こそがnonceであり、Proof of Work
- nonceを見つけたノードがブロックを繋ぐ権利を持つ
- 技術の発展とともにコンピューターの処理速度は加速するため、ブロック生成時間を保つためには難易度を調整する必要がある2
- 難易度は前方に連続する0の個数によって調整する。多くなればなるほどもちろん難しい
- Naivecoinでは単にループの中でハッシュ値を計算して、先頭0の個数が難易度と一致するか確認している
- 累積難易度が最も高いチェーン即ち計算コストを最も要したチェーンが正史として選ばれる(中本コンセンサス)
- 同時にnonceを発見したノードが複数あった場合、ブロックチェーンに分岐が生じる場合がある
- Naivecoinでは別ノードから新規ブロックを受け取った際「分岐が起きた可能性がある」と思ったら、相手のブロックチェーン一覧を受け取り(ココでブロックチェーン全体を要求している)累積難易度の計算を行い、先方が上回っていた場合自身のブロックチェーンを入れ替える(もちろん先方のブロックチェーンが正しいか検証した上で)
トランザクション
- ビットコインではコインの送金履歴をトランザクションというデータ構造で表現する
- トランザクションにはインプット(コインの送信者Aさんと金額を指定)とアウトプット(コインの受信者Bさんと金額を指定)プロパティがあり「AさんがBさんにいくらコインを送った」という内容を表現可能
- ブロックチェーンはデータの改ざんを防ぐレイヤーなので、ブロックチェーン上の「データの正しさ」までは見れていない
- ビットコインでは、データの内容たるトランザクションに電子署名の仕組みを導入することで「正しいデータのみが追記される」ようにした
- ブロックチェーン上にはコインの実体はなく、ただ送金履歴たるトランザクションがあるのみ。では、どうやってコインを「送る」ことができるのか?
- 自分宛のアウトプットがあれば誰かにコインを送られたということ。つまり自分のコインがあるということ。そのアウトプットのIDを別トランザクションのインプットに指定して、別の宛先に送ることができる
- 自分宛を指定したアウトプットのうち、まだインプットとして利用していないものをUTXOと呼ぶ。UTXOの総和が自身が持っているコインの総和
- 本家ビットコインだとノード上で実行できる独自のScript言語3によってトランザクションの検証を行うようだが、Naivecoinでは署名を検証する程度のシンプルな実装になっている
- どちらにしても重要なのは、全てのコインは公開鍵(アドレス)宛に送るため、対応する秘密鍵を持っていると証明できれば、そのコインの所有権を主張できるということ
- トランザクションのアウトプットには公開鍵が含まれており、インプットとして利用する際に署名を加える。公開鍵と署名は対応関係にあるので、「アウトプットの公開鍵に対応する署名が入っている = 対応する秘密鍵を持っている = そのコインを所有している」と各ノードが判断できる
- 「このトランザクションは確かにコインの所有者であるAさんが作ったモノだ」という事実を公開鍵と署名を組み合わせて検証する
ウォレット
- そもそもウォレットとは何か?
- 厳密な定義はないがこちらによると下記特徴を持つもの
- 秘密鍵・公開鍵(アドレス)の管理
- トランザクションおよび電子署名の作成
- ブロックチェーン上のデータの参照・書込
- 厳密な定義はないがこちらによると下記特徴を持つもの
- NaivecoinはBitcoin Core等と同様なフルノードなのでノード内で秘密鍵を管理する
トランザクションプール
- マイニングには時間がかかるためトランザクション1件毎にブロックを作っていたら破滅する。一旦トランザクションを溜めて、まとめてブロックにする必要がある。バッファとしてトランザクションプールがある
- 各ノードが検証したトランザクションがトランザクションプールに追加され、定期的にブロック作成時に読み出されてブロックとしてまとめられる
- 実装上はトランザクションを要素に持つただの配列
さいごに
何も分からん状態から始めましたが、実際に手を動かすことで理解が進みました。
毎回思う月並みな感想ですが、実際に手を動かすか、或いはコードを読むことが仕様を理解する近道ですね。
あらゆる技術は人が作り出したモノなので、仕様を掴むことが重要です。
ブロックチェーンの理解のためには、各ノードが行なっている「検証」がキーポイントになると感じました。
例えばマイニング難易度やCoinbase Transactionについて、ノードが各々自分勝手に設定できそうな気がしますが、別のノードに伝播した際に「正しい難易度か?不正のないトランザクションか?」を簡易に検証できるようにブロックチェーンはデザインされています。
「こうやったら不正できるんじゃない?」と思いつくことに対する回答は、大体「検証によって防いでいる」が答えです4。
検証し、不正を防いだ方が経済的合理性があるため、みんな頑張って検証してくれる仕様になっているわけです。
当初はブロックチェーンのノードについて「ただのデータベースに過ぎずブロックチェーンを保存しているだけなのだろうか?」と考えていましたが、実際はデータの検証を行なったり、Scriptの実行環境を持っていたり等、不正のないデータを死守するために様々な検証ロジックが集積されたモノであることが分かりました。
実際に写経を行うことで、ブロックチェーン/ビットコインに対する解像度がひとつ上がりました。
ノードと話す仕様に準拠すれば一般的なWeb技術で作ることができる、例えばウォレットをReactで作ったりも可能だ、と感覚的に腹落ちしたのも大きかったです。
NFTやらメタバースやらで高まっている界隈ではありますが、技術者としては、ある程度静観しつつ、振り落とされないように定期的にキャッチアップを続けていきたいです。
参考
-
https://engineering.mercari.com/blog/entry/2017-12-08-095000/ ↩
-
もちろん例外もあるが。例えば「自身が作ったトランザクションをひとつのノードではなく知りうるノードにブロードキャストする」というP2Pネットワークに由来する強みもある。例えばノードをひとつしか知らなかった場合、対するノードが悪意あるモノだったら破棄されたりとかいじられたりとかされる場合はあり得ると思われる ↩