Bitcoinをフォークして、君だけの、最強の暗号通貨を作ろう!
Kusacoin
Bitcoinをフォークして、Kusacoinという暗号通貨を作った。
2018年10月8日開催の技術書典5の「い25」で頒布する本はKusacoinでも購入できるので、よろしくお願いします。
ソースコード中の「Bitcoin」を「Kusacoin」に書き換えるだけでしょ?と思うかもしれない。まあ、だいたいその通りなのだけど、他にも細々と修正しないといけないところがあるので、修正内容を書き連ねておく。
修正内容
GitHubリポジトリ
当然そうするべきだと思って、GitHub上の操作で本家をフォークしてリポジトリを作った。リポジトリに「forked from bitcoin/bitcoin」と表示される状態。
これが扱いづらい。まず、ソースコードの検索ができない。
Sorry, forked repositories are not currently searchable.
You could try searching the parent repository.
と言われる。
また、プルリクを送るときの送り先がデフォルトで「bitcoin/bitcoin」になっていて、変更するのが面倒だし、うっかり本家にプルリクを送りそうになってしまう。
GitHubのフォークは本家にプルリクを送るために使う機能なのかもしれない。
サポートに連絡すれば、切り離してくれるらしい。どこかの設定一つで切り離せるかと思っていたけれど、そんなことはなかった。
ForkリポジトリをFork元から切り離してスタンドアローンなリポジトリにする
.git/を消してソースコードだけ持ってくるのは、バージョンアップ時の追従が面倒になるので止めたほうが良いと思う。
デフォルトのデータディレクトリの変更
src/util.cppのGetDefaultDataDir
。これを書き換えておかないとBitcoinと衝突してしまう。
シードノード
- https://github.com/kusacoin/kusacoin/commit/87d199cdfcbf38755d08bc137851ae0be22d4ca7
- https://github.com/kusacoin/kusacoin/commit/380353712df601edd848c5e3c360dccee83c4e0a
Bitcoinのプログラムを対処に立ち上げたときに繋ぎに行くアドレスがハードコードされている。src/chainparams.cppのvSeeds
に代入されるアドレスを書き換えれば良い。
IPアドレスのほうは、contrib/seeds/nodes_main.txtとnodes_test.txtから(手動で)変換したアドレスがsrc/chainparamsseeds.hに書き込まれ、vSeeds
のアドレスに繋がらなかったときに使われるらしい。とりあえず消しておいた。ノードが増えてきたら、ノードを取得してファイルを作ると良いのだろうか。
開発中に複数のノードを繋ぎたいときは、コンソールでaddnode "example.com:1234" "add"
を実行すれば良い。
Magic bytes
通信やブロックのヘッダで、Bitcoinのものであることを識別するための4バイトのシグネチャ。Bitcoinはfp be b4 d9
。何でも良いのだろうけど、Bitcoinはソースコードに、
/**
* The message start string is designed to be unlikely to occur in normal data.
* The characters are rarely used upper ASCII, not valid as UTF-8, and produce
* a large 32-bit integer with any alignment.
*/
と書かれていて、そのようなバイト列になっている。また、小ネタとして、ピタゴラス数になっている。
protocol - How was the magic network ID value chosen? - Bitcoin Stack Exchange
ポート番号
- https://github.com/kusacoin/kusacoin/commit/cf56296003e373cf3652ced437544988b750dc28
- https://github.com/kusacoin/kusacoin/commit/2a9e45a9f7a202757867ffdd26a0358bc4230288
外部との通信用と、ローカルの通信用の2個のポートを使う。Bitcoinはそれぞれ8333と8332。また、テストネットとリグレッションテスト用でも異なる。起動引数で変えられるけれど、そのまま立ち上げたときにBitcoinと衝突して不便なのでデフォルトを変えておく。
プログラム中の文字列の変更
- https://github.com/kusacoin/kusacoin/commit/26ba4600a91d34811a3838f7ad102fbe70a0c3ab
- https://github.com/kusacoin/kusacoin/commit/97173c80ecf3498134dc8dc39ca69f8e86e2a43e
- https://github.com/kusacoin/kusacoin/commit/a94bc7bbf6f1be17ca2d96ae5cf16dba84e82433
- https://github.com/kusacoin/kusacoin/commit/98b2d7669758522f72508dcbfafa3e1101c0faf8
- https://github.com/kusacoin/kusacoin/commit/a19c42180236dab0f4e7de4edad0cfeece1bd6a6
プログラムの文字列中のBitcoin
、BTC
、satoshi
を置換する。Qtの他言語の文字列はsrc/qt/locale/*.tsにある。文字列ならば動作には影響が無いので、そのままコンパイルも通る。
プログラム中の識別子の変更
クラス名や変数名にbitcoin
を含んでいるものがあるので、書き換える。利用者にはバレないから、面倒なら書き換えなくても良いかもしれない。
ソースコードのファイル名の変更
- https://github.com/kusacoin/kusacoin/commit/f908d1de91a581ab21f60ebc3d974d690079861d
- https://github.com/kusacoin/kusacoin/commit/367a510698ec02ffdcb082057389a03a83667b1d
- https://github.com/kusacoin/kusacoin/commit/133d7e4e4460a5eee29851542bcee69d74fee89d
- https://github.com/kusacoin/kusacoin/commit/95a81ad24d6fad3580e8b6356714f01d476f7a10
- https://github.com/kusacoin/kusacoin/commit/0113ff8b712c373e5fe7bd9fa0b212a6a8da7131
これも変更しなくても、ビルド済みのファイルを使うだけならば問題が無い。変更箇所が多いと、本家の修正をマージするときにgitが追ってくれなくてちょっと面倒。ファイル名を変えたら参照しているMakefileや#include
なども書き換える必要がある。インクルードガードはファイル名を使っているので書き換えたほうが丁寧。
Qtのアプリ名の変更
Windowsだとレジストリのフォルダ名に使われるので変更が必要。
機械的にBitcoinを???coinに置換すればいいんじゃないの?
ファイル名とテキストファイル中の文字列を単に変換すれば良さそうだけど、置換してはいけないものもある。ソースコード先頭の著作権表記を書き換えるわけにはいかない。また、コメントでBitcoinのことを指しているものもある。仕方がないので、面倒だけれど、検索して確認しながら手で書き換えた。
著作権表記は、contrib/devtools/copyright_header.pyにツールがあるので、これを改造して何とかする手もあるかもしれない。
アイコンの変更
- https://github.com/kusacoin/kusacoin/commit/0591851b4aee152bccd04125fd24f80cc5b03776
- https://github.com/kusacoin/kusacoin/commit/a164044ef5ee5e58e3b3e461b2705e1d7fe7dc0c
- https://github.com/kusacoin/kusacoin/commit/af6a3ec9e1a826b4b607b119bbc52aa38ead85a6
- https://github.com/kusacoin/kusacoin/commit/0c1a3117414baa74d8b57552b3d731d2666449db
- https://github.com/kusacoin/kusacoin/commit/f92e7af08133e98cc17d0004c4982986d502512d
あちこちにあるのでひたすら置き換える。.xpmはImageMagick(convert)で、Windowsの.icoは@icon変換で、Macの.icnsはiconutilで変換した。
Proof of Work(PoW)のアルゴリズム
- https://github.com/kusacoin/kusacoin/commit/e703ca7c0d5ea9e69a288cc5ba123207df76e27c
- https://github.com/kusacoin/kusacoin/commit/8768bcd9aaf4b3165f3a57f6ffa652659051bd80
- https://github.com/kusacoin/kusacoin/commit/e14bbba8b012eff4a474ea0a1d597f9bcc519d41
- https://github.com/kusacoin/kusacoin/commit/aef46899207a8b94e1fd441a7a89a2147e69093c
PoWの暗号通貨はハッシュ値の小さいブロックが有効なブロックとしてチェーンに繋ぐことができて、採掘した人は報酬を得られる。Bitcoinはこのハッシュ値のアルゴリズムがSHA-256。
ASICが開発されているアルゴリズムはASICが強すぎる。GMO miner B2は公称33 TH/s。手元のCore i7-4790 3.6 GHzで試してみたら、約50 MH/s。6桁違う。これではASICを持っている人以外は採掘ができない。
ということで、Monacoinも採用しているLyra2REv2にしてみた。まあ、ASICが無くても高いGPUを持っている人とはだいぶ差が出る。
Kusacoin、誰がマイニングを始めたのかネットワークハッシュレートが600倍になり、それが突然止まったことにより24時間ブロックが採掘されなくて死にかけていた。手元でGPUマイナーを動かすことで何とか採掘が間に合った。危ないところだった。https://t.co/X6jQaXzfRg https://t.co/ILZmtsRI2A
— kusanoさん@がんばらない (@kusano_k) 2018年7月18日
BitZenyのYescryptはGPUにも耐性があることを謳っているので、そういうアルゴリズムを選ぶ手はある。まあ、GPUが強くなってきたからどうしよう?という話はあるようだけど。
GPUMinerの登場と今後の対応 · Issue #24 · BitzenyCoreDevelopers/others
マイナーなアルゴリズムや独自のアルゴリズムにすると、マイニングプログラムが無いのでマイニングプログラムも用意しないといけない。
Monacoinはブロックのハッシュ値はSHA-256のままで、ブロックのハッシュ値とは別にPoW用のハッシュ値を計算するようになっているので、ブロックのハッシュ値は小さな値にはならない。暗号通貨のブロックのハッシュ値は小さな値になっているべきでは?と思い、Lyra2REv2のハッシュ値をそのままブロックのハッシュ値にしてみた。(それを目的にしているから)SHA-256よりは計算が重いので、問題になるかもしれない。今のところ手元で動かしている限りでは大丈夫だけど。
そういえば、ハッシュ値の計算法を変えたことでこけるようになったテストを無効化して、後から何とかしようと思って忘れていた
難易度調整アルゴリズム
- https://github.com/kusacoin/kusacoin/commit/e198de9593116a711a9e6966ea5b44343660f8de
- https://github.com/kusacoin/kusacoin/commit/2831afb60687f0866e1c02a0a4ce84e768fa579f
ブロックが採掘される時間の間隔が一定になるように、過去の採掘間隔から目標とするハッシュ値を調整する必要がある。Bitcoinは2016ブロック(2週間)に1回、調整が入る。マイニングする人がある程度多ければ変化も緩やかになるだろうから、これで構わないかもしれないが、どうせ数人しか採掘する人はいないだろうと思うと、これでは遅すぎる。ハッシュレート600倍で2週間マイニングした後その人が飽きてマイニングを止めると、残った人が2016ブロックをマイニングするまで難易度が変更されない。23年掛かる。
アルゴリズムは色々ある。
過去のアルゴリズムを見ながら考えられた新しいアルゴリズムのようなので、LWMAを採用した。直近の採掘間隔に重みを付けて今のハッシュレートを推測して、毎ブロック難易度を調整+急激な変化に備えてキャップ。
そういえば、ここでも落ちるテストを潰して、忘れてた
採掘間隔の調整
Bitcoinのブロック採掘間隔は10分間。Kusacoinでは変えていない。
src/chainparams.cppを書き換えれば変更できる。短くすれば、取引がブロックに取り込まれるまでの時間が短くなる。短くするデメリットは、
- 採掘したブロックが他のノードに伝わるまでに多少の時間が掛かるので、一度採掘した人が次のブロックを採掘しやすくなる
- チェーンの分岐が起こりやすくなる
- ブロック1個あたりの計算量が小さくなるので、攻撃を受けやすくなる
- これは使う側が、支払ったとみなす承認数を増やせば良い
- (ブロックサイズを変えない場合)取引が増えると、ブロックチェーンのサイズが大きくなる
あたりだろうか。短くしている暗号通貨を見るに、1分間くらいまでなら大丈夫そう。
総発行量の調整
変更していない。
Bitcoinは、1ブロックあたりの報酬は最初は50 BTCで、210,000ブロック(4年)ごとに半分になっていって、指数関数の和を計算すると総発行量は2.1億 BTCになる。GetBlockSubsidy関数を変更すれば変えられる。
1単位が法定通貨の1円か1ドルくらいになると使い勝手が良さそうだが、暗号通貨を狙った価値にすることは難しそう。大きくしたほうが楽しいけど、調子に乗ると、コインの数を扱うのに使っている64 bit整数が溢れてバグる。内部的には10-8を1単位としているので、総発行量を108倍した値が64 bitで表せないとマズい。
あれ? 総発行量が2,800億XPのXP Coinは大丈夫? と気になったけど、内部的には10-6を最小単位として扱うようになっていた。
いきなり半分になると影響が大きいので、4年に1回ではなく、徐々に減らしていくという手はどうだろうか?
ソフトフォークへの対応
- https://github.com/kusacoin/kusacoin/commit/b219fd94a31bdf90637aec9aa72c61b062151f60
- https://github.com/kusacoin/kusacoin/commit/7b23c707be081b7364ced44010b2d96c32850ce8
Bitcoinには何度も機能が追加されている。これらの新しい機能は、昔はブロック高がハードコードされた高さ以上になると有効になっていた。最近の追加は、開始時刻と終了時刻がハードコードされていて、この期間内の2016ブロック(2週間)で、機能を有効にするというフラグを立てて採掘されたブロックが95%以上になったら自動的に有効化される。
そのままだと、指定のブロック高になるまで有効にならないので、最初から全て有効になるようにした。
Segwitの有効無効を切り替える機能を丸ごと消したら、Segwitに対応していないテストが通らなくなったので、修正した。
BIP 34への対応
- https://github.com/kusacoin/kusacoin/commit/f42d4749ac93cf20ea4186a94e148fde79aebeeb
- https://github.com/kusacoin/cpuminer/pull/2/commits/d3f10efd404b3cf85549f5abaadeb5b906c71f21
BIP 34で、conibase transactionにブロック高を含めるというルールが追加された。
- https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki
- ブロックのバージョンをアップグレードする際のルール(BIP-34) - Develop with pleasure!
何の意味があるのかと思ったけど、このルールが無いと、取引のID(ハッシュ値)が等しい取引がブロックチェーンに複数現れてしまうかららしい。coinbase transaction以外は支払い元の取引のIDが取引に含まれるのでこの心配は要らない。
このブロック高はBitcoinのスクリプトの整数リテラルとして書き込み、可変長になる。とはいえ、Bitcoinの場合は有効になるのが227,931からなので、有効になって以降、はるか先(223-1)まで、3バイト。問題は最初から有効にした場合で、一部のテストとマイニングプログラムが3バイト以外に対応していないので修正が必要。
アドレス
- https://github.com/kusacoin/kusacoin/commit/0a4110e0b703f9d0e888e801b0419f917606f39c
- https://github.com/kusacoin/kusacoin/commit/2553c2f8e550fbc87ec0c800108945d53d43dfd8
- https://github.com/kusacoin/kusacoin/commit/841097fc13da2db65669a5e422d122c8d67d70c6
- https://github.com/kusacoin/kusacoin2bitcoin
Bitcoinの各種アドレスは公開鍵や秘密鍵やそのハッシュ値に適当なプレフィックスを付けたバイト列をBase58エンコードしたもの。↓のページの図が分かりやすい。
間違いを防ぐためにも互換性を無くしておいたほうが良い。(1箇所だけ修正が必要だったけれど)基本的には1バイトでなくても良いので、被らないように適当なバイト列に書き換える。
MonacoinのMNpZNpHiTKhS5QStejVWCiKK8J5ZKwEVeL
や、LitecoinのLWWTXiKk26zuUNTxoehYfpaHBNXXxbprQW
のように、値を上手く調節してBase58でエンコードしたときに先頭がその通貨を表すようにするのを良く見る。
今ではBech32というフォーマットがあって、これならばプレフィックスを素直に文字列で指定できるので、デフォルトのアドレスをBech32にしてしまえば良いのではないだろうか。Bitcoinのスマホアプリをフォークして、対応させようとしたときにBech32に対応していなくて面倒なことになる可能性はあるかもしれない。
Segwitのアドレスフォーマットを定義したBIP-173 - Develop with pleasure!
マイニングプログラムに送金先のアドレスを指定したとき、アドレスから公開鍵のハッシュ値などを取り出すのはマイニングプログラムの仕事。なぜ、マイニングプログラムは色々な暗号通貨に対応できているのだろう?と気になったけど、BitcoinのP2SH以外のプレフィックスだったらP2PKHと見なしていた。
プレフィックスが1バイトならば良いのだけど、それ以外では動かない。CPU向けとかGPU向けとか色々あるマイニングプログラムを書き換えるのは面倒なので、プレフィックスをビットコインのものに差し替えるスクリプトを用意した。
Genesis Block
Genesis Block(最初のブロック)は、プログラム中で生成している。BIP 34を最初から有効にしているならば、Genesis Blockもcoinbase transactionにブロック高を含めるなどの対応が必要。
Bitcoinの場合、新聞記事のタイトルが含まれていて、Bitcoinの開始がこの日以降であることを示すとともに、このタイトルが既存の通貨を扱う銀行に対する批判(?)で、とてもカッコイイ。
ハッシュを小さくするためのnonceは、ソースコードにnonceを増やしながらバリデーションが通るかどうかをチェックするコードを一時的に追加して求めた。このブロックが難易度が難易度調整の基準になるので、PoWのアルゴリズムを重いものにしている場合は、難易度を減らしておかないと大変。
nTime
はちゃんと現在時刻にしておかないと、採掘ができない。
Bitcoinのソースコードを読んでいて知ったのだけど、公式実装はIsInitialBlockDownload()がfalseにならないとマイニング用のブロックを返さず、「最新のブロックが24時間以内」という条件あるので、24時間ブロックが採掘されないと、誰もブロックが採掘できなくなってブロックチェーンが死ぬ。
— kusanoさん@がんばらない (@kusano_k) 2018年7月10日
checkpointとchain tx data
一旦消して、ある程度ブロックが採掘された後で、実際のブロックチェーンの情報を書き込んだ。
checkpointData
はある高さのブロックのハッシュ値を固定するもの。「より計算力が注ぎ込まれたチェーンが正しいのであって、ソースコードで指定するのは中央集権的では?」という批判があるからか、Bitcoinではブロック高295,000(2014年4月)以降追加されていない。一方、Monacoinでは攻撃対策として動的にcheckpointを配信している。何が良いのか分からないけど、とりあえず追加しておいた。
chainTxData
は同期がどこまで進んだかを計算するために使われる。
man
Linuxのman
で表示されるもの。contrib/devtools/gen-manpages.shで、--help
の実行結果から生成される。