#概要
Bitcoinをなんとなく知っているけど、なんとなくしか知らない人です。
今回、勉強のためにBitcoinフルノードの運用に取り組んでみたので備忘録として残しておきます。
各種参考にさせていただいた記事のうち代表的なものを随所にリンク張っておきます。
Bitcoin Core(v0.19.0.1)
を前提とした内容であることを念頭に置いてください。
あと、勉強しながら平行して記事を書き進めているので、まだ理解が進んでいないポイントは盛大に間違っている可能性があります。
##環境
今回使用するマシンのスペック
- CPU:8 core
- memory:16GB
- HDD:1TB
な仮想マシンをVirtualBoxで作成。
OSはCentOS 7
です。
#1.Bitcoin Coreインストール
$ cat /etc/redhat-release
CentOS Linux release 7.7.1908 (Core)
CentOSの初期設定が終わった状態からスタート。
#依存パッケージインストール
yum install -y yum-utils
#拡張リポジトリ(Extra Packages for Enterprise Linux)追加
yum install -y epel-release
#もろもろ追加(不要なものもあると思うが細かいことは気にしない)
yum install -y gcc gcc-c++ cpp libtool patch patchutils bzip2 zip unzip rsync wget ntp pciutils make autoconf automake libxslt openssl-devel zlib-devel curl-devel bind-utils ncurses ncurses-devel ncurses-static ncurses-term libevent libevent-devel libevent-doc
準備が整ったらrpmからbitcoin-server
とbitcoin-utils
をインストールする。
#リポジトリを設定
rpm -Uvh https://linux.ringingliberty.com/bitcoin/el7/x86_64/bitcoin-release-3-1.noarch.rpm
yum-config-manager --enable rhel-7-server-optional-rpms
# bitcoin coreインストール
yum -y install bitcoin-server bitcoin-utils
#bitcoin coreのバージョン確認
$ bitcoind -version
Bitcoin Core version v0.19.0.1-g1bc9988993ee84bc814e5a7f33cc90f670a19f6a
Copyright (C) 2009-2019 The Bitcoin Core developers
#bitcoin-cliのバージョン確認
$ bitcoin-cli -version
Bitcoin Core RPC client version v0.19.0.1-g1bc9988993ee84bc814e5a7f33cc90f670a19f6a
#2.初期設定
ホームディレクトリ配下に.bitcoin
ディレクトリを作成し、bitcoin.conf
ファイルを配置する。
$ mkdir ~/.bitcoin
### -- 同期するブロックチェーンを指定する -- ###
mainnet=1 ## 本番環境
# testnet=3 ## テストネット(3代目)で試験する場合
# regtest=1 ## オフラインでクローズドな試験をする場合
### -- 動作モード -- ###
server=1 ## JSON RPCサーバとして起動する
### -- RPCポート -- ###
### mainnetなら8332、testnetなら18332がデフォルト値
# rpcport=8332
# rpcport=18332
### -- 認証情報 -- ###
### localhostのみ許可するのであれば凝った設定は不要
rpcuser=user
rpcpassword=pass
### -- アクセス制御 -- ###
### RPCはlocalhostのみから受け付けるのが一般的だが
### 下記設定を追加することにより外部からRPC接続も可能。
rpcallowip=192.168.0.1/32
### -- トランザクション検索インデックスの有効化 -- ###
### トランザクション情報を参照したい場合は必要
txindex=1
設定項目は基本的には以上で完了。
メインネットに接続すると自動的にブロックの同期が始まるため初回はかなりの時間を要するので、もしもお試しや検証目的であればまずはテストネットを使用してみるとよいかと。
#3.起動(メインネット接続)
bitcoindを起動しメインネットへの接続を開始。
$ bitcoind -daemon
Bitcoin Core starting
デーモンを終了する場合は下記のコマンド
$ bitcoin-cli stop
Bitcoin Core stopping
もしうまく動作しないようなら下記の通りRPCまわりからチェックしてみるとよい。
##3.1 RPC稼働確認
$ systemctl status rpcbind
● rpcbind.service - RPC bind service
Loaded: loaded (/usr/lib/systemd/system/rpcbind.service; enabled; vendor preset: enabled)
Active: active (running) since 火 2019-12-17 11:57:56 JST; 3 weeks 1 days ago
Main PID: 1459 (rpcbind)
Tasks: 1
CGroup: /system.slice/rpcbind.service
└─1459 /sbin/rpcbind -w
12月 17 11:57:52 BTC systemd[1]: Starting RPC bind service...
12月 17 11:57:56 BTC systemd[1]: Started RPC bind service.
$ systemctl list-unit-files -t service | grep "rpcbind"
rpcbind.service enabled
$ systemctl list-unit-files | grep "rpcbind"
rpcbind.service enabled
rpcbind.socket enabled
rpcbind.target static
$ rpcinfo -p
program vers proto port service
100000 4 tcp 111 portmapper
100000 3 tcp 111 portmapper
100000 2 tcp 111 portmapper
100000 4 udp 111 portmapper
100000 3 udp 111 portmapper
100000 2 udp 111 portmapper
必要に応じてFWのポート開放も行う。
$sudo firewall-cmd --zone=manage --add-port=18832/tcp --permanent
$sudo firewall-cmd --zone=manage --add-port=18832/udp --permanent
$sudo firewall-cmd --reload
$sudo firewall-cmd --list-all-zones
##3.2 ネットワーク状況確認
bitcoinネットワークの接続状況を確認する。
$ netstat -pant | grep bitcoin
tcp 0 0 127.0.0.1:18332 0.0.0.0:* LISTEN 12531/bitcoind
tcp 0 0 0.0.0.0:18333 0.0.0.0:* LISTEN 12531/bitcoind
tcp 0 0 192.168.1.1:44054 xxx.xxx.xxx.xxx:18333 ESTABLISHED 12531/bitcoind
<snip>
tcp6 0 0 ::1:18332 :::* LISTEN 12531/bitcoind
tcp6 0 0 :::18333 :::* LISTEN 12531/bitcoind
ローカルノードの内部情報を参照するにはbitcoin-cli
コマンドを使用する。
$ bitcoin-cli getconnectioncount
10
$ bitcoin-cli getnetworkinfo
{
"version": 190001,
"subversion": "/Satoshi:0.19.0.1/",
"protocolversion": 70015,
"localservices": "0000000000000409",
"localservicesnames": [
"NETWORK",
"WITNESS",
"NETWORK_LIMITED"
],
"localrelay": true,
"timeoffset": 0,
"networkactive": true,
"connections": 10,
"networks": [
{
"name": "ipv4",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
},
{
"name": "ipv6",
"limited": false,
"reachable": true,
"proxy": "",
"proxy_randomize_credentials": false
},
{
"name": "onion",
"limited": true,
"reachable": false,
"proxy": "",
"proxy_randomize_credentials": false
}
],
"relayfee": 0.00001000,
"incrementalfee": 0.00001000,
"localaddresses": [
],
"warnings": ""
}
#3.3 ローカルノードの稼働状況確認
動作バージョンやブロックチェーン、ウォレット残高などの概況サマリーを確認する。
$ bitcoin-cli -getinfo
{
"version": 190001,
"protocolversion": 70015,
"blocks": 611853, ## 最新ブロック高
"timeoffset": 0,
"connections": 10,
"proxy": "",
"difficulty": 13798783827516.42, ## 採掘難易度
"chain": "main",
"walletversion": 169900,
"balance": 0.00000000, ## wallet残高
"keypoololdest": 1576991493,
"keypoolsize": 1000,
"unlocked_until": 0, ## wallet複合時の終了期限(暗号化済みの場合に項目表示)
"paytxfee": 0.00000000,
"relayfee": 0.00001000,
"warnings": "" ## 警告メッセージ
}
#3.4 ブロックチェーン同期状況の確認
Bitcoinネットワークと接続されれば自動的にネットワーク上の最新のブロックチェーンとのローカル同期が始まります。
初回起動時は初期ブロック(genesisブロック)から最新ブロックまでの全ブロックを順次ダウンロード&検証するのでだいたい20時間以上は見ておいたほうがいいです。
同期状況の進捗はgetblockchaininfo
というRPCコマンドにより確認可能。
blocks
とheaders
が同値になれば同期完了ということらしい。
$ bitcoin-cli getblockchaininfo
{
"chain": "main",
"blocks": 615023, ## ローカル同期が完了したブロック長
"headers": 615023, ## ネットワーク上の最新ブロック長
"bestblockhash": "0000000000000000000597fb45aa1f4ca78081352cb2d26487ba7d019bd76a08",
"difficulty": 15466098935554.65,
"mediantime": 1580285702, ## タイムスタンプ(中央値)
"verificationprogress": 0.9999997039397914,
"initialblockdownload": false,
"chainwork": "00000000000000000000000000000000000000000c2d3de90e0990276f7b9f70",
"size_on_disk": 295504268933,
"pruned": false,
"softforks": {
"bip34": {
"type": "buried",
"active": true,
"height": 227931
},
"bip66": {
"type": "buried",
"active": true,
"height": 363725
},
"bip65": {
"type": "buried",
"active": true,
"height": 388381
},
"csv": {
"type": "buried",
"active": true,
"height": 419328
},
"segwit": {
"type": "buried",
"active": true,
"height": 481824
}
},
"warnings": ""
}
進捗状況の確認はblocks
でもざっくりわかるが、より具体的に知りたい場合は、ブロックのタイムスタンプmediantime
を確認できる。タイムスタンプはUNIX時間で与えられるのでdate
コマンドを用いれば読める。
$ date --date @1580285702
2020年 1月 29日 水曜日 17:15:02 JST
mediantime
は同期が完了した最終ブロックに含まれるタイムスタンプの一つなのだが、厳密にはブロックの生成日時ではなく、当該ブロックを含む過去11ブロックの生成日時の中央値を示しているらしい。
https://bitcoin.stackexchange.com/questions/67618/difference-between-time-and-mediantime-in-getblock
通常1ブロックの生成間隔は凡そ10分なのでmediantime
はブロックの生成日時から概ね1時間程度過去の値として算出されることになる。Bitcoinネットワークのハッシュレートは割と変動するし、分岐も頻繁に発生しているので、そういった不確定要因を極力除外したいとの思想から平均値ではなく中央値を利用しているのかな?
多少余談になるが難易度調整アルゴリズムについては下記のレポートが勉強になった。
###同期完了後のディスク使用量
実際にサーバのディスク容量をどの程度使うのかは気になるところ。
.bitcoin
ディレクトリのディスク使用量を確認すると2020年1月執筆時点で300GB
を超えており、そのうち90%以上がブロックチェーン本体のバイナリデータで占められている模様だった。
$ du -h
93M ./blocks/index
274G ./blocks
3.7G ./chainstate
24G ./indexes/txindex
24G ./indexes
60K ./database
301G .
#4.ブロックやトランザクションの中身を調べてみる
同期が完了したので、とりあえず過去のブロックやトランザクションがどうなっているか覗いてみた。
ここでは他サイトで紹介されていた方法を真似しているが他にもやり方があるかもしれない。
#4.1 ブロックの中身を確認する
参照したいブロックを指定するには各ブロックに一意なブロックハッシュ値blockhash
を指定する必要がある。ハッシュ値が分からなければ(普通はそんなもの覚えているわけがない・・・)、ブロックチェーン上の位置height
を指定すれば、そのブロックに対応するブロックハッシュ値を返してくれるRPCコマンドが別に用意されているので、それを併用するようだ。具体的には
getblockhash <height>
:heightに対応するブロックのブロックハッシュ値を特定する
getblock <blockhash>
:ブロックハッシュ値に対応するブロック情報を表示する
という2段階を経ることになる。
例として100001番目に位置するブロックを取得した実行結果を示す。
(heightは0始まりであることに注意/ genesis=0)
# height=100000のブロックハッシュを特定する
$ bitcoin-cli getblockhash 100000
000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506
# 上記ブロックハッシュに対応するブロックの中身を表示する
$ bitcoin-cli getblock 000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506
{
"hash": "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506",
"confirmations": 511995,
"strippedsize": 957,
"size": 957,
"weight": 3828,
"height": 100000, ## ブロックHeight
"version": 1,
"versionHex": "00000001",
"merkleroot": "f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766",
"tx": [
"8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",
"fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4",
"6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4",
"e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d"
],
"time": 1293623863,
"mediantime": 1293622620,
"nonce": 274148111,
"bits": "1b04864c",
"difficulty": 14484.1623612254,
"chainwork": "0000000000000000000000000000000000000000000000000644cb7f5234089e",
"nTx": 4, ## トランザクション数
"previousblockhash": "000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250",
"nextblockhash": "00000000000080b66c911bd5ba14a74260057311eaeb1982802f7010f1a9f090"
}
これは2010年頃のブロックになるのでトランザクションは僅か4件しか記録されていない。こういった黎明期を維持してくれた先駆者に感謝の思いを馳せる。
一点補足すると、ここに出力されている情報のうち実際のブロックチェーン上のブロックには記載されていないものがある。分かりやすい例としては末尾のnextblockhash
がそうだ。これは次のブロックのブロックハッシュを示しているが、ブロックが実際に生成(マイニング)された時点でまだ存在しない未来のブロックのハッシュ値が確定できるはずもない。すなわちこれはビットコインソフトウェアがブロックチェーンを解析して独自に付与しているということを意味している。
TIPS
getblockhash
からgetblock
を実行するコンボはこれからもよく使いそうなのでシェルスクリプト化しておくと便利
#!/bin/bash
### heightを引数に指定しブロック情報を出力する
blockhash=`bitcoin-cli getblockhash $1`
bitcoin-cli getblock $blockhash
その他、最新ブロックの情報を取得するコマンド
## 最新ブロックの位置を特定する
$ bitcoin-cli getblockcount
615022
## (または)最新ブロックのブロックハッシュを特定する
$ bitcoin-cli getbestblockhash
0000000000000000000074bc934c66bccb7e1963d1376e46613f715b13077045
##4-2.トランザクションの中身を確認する
次にトランザクションの中身を覗いてみる。
先ほどブロック情報を表示した際にtx
セクションが含まれていたが、この文字列がそれぞれのトランザクションを特定するトランザクションIDtxid
なるものらしい。
"tx": [
"8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",
"fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4",
"6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4",
"e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d"
],
トランザクションの中身を表示したい場合はtxid
でインデックスを検索してrawデータ
を取得し、それをデコードするという手順となる。具体的には下記のRPCコマンドを用いる。
getrawtransaction <txid>
:txidからrawデータを取得する
decoderawtransaction <raw>
:rawデータをデコードする
## txidからrawデータを取得する
$ bitcoin-cli getrawtransaction 8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87
01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84ac00000000
## rawデータをデコードする
$ bitcoin-cli decoderawtransaction 01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84ac00000000
{
"txid": "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",
"hash": "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87",
"version": 1,
"size": 135,
"vsize": 135,
"weight": 540,
"locktime": 0,
"vin": [
{
"coinbase": "044c86041b020602",
"sequence": 4294967295
}
],
"vout": [
{
"value": 50.00000000,
"n": 0,
"scriptPubKey": {
"asm": "041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84 OP_CHECKSIG",
"hex": "41041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84ac",
"type": "pubkey"
}
}
]
}
vin
がインプット情報(出金元UTXO)
vout
がアウトプット情報(送金先UTXO)
この出力結果にはvin
セクションにcoinbase
という記述があるが、これはマイニングの報酬として新たに生み出されたコインであることを意味している。つまりこのトランザクションは『採掘報酬50BTCを採掘者に送付しますよ』という取引であることがわかる。
こういった情報はBlockchain Explorerと呼ばれるWEBサイトにも公開されているので併せて確認することで理解が深まると思う。
(https://www.blockchain.com/btc/tx/8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87)
TIPS
トランザクションについてもブロックと同じくgetrawtransaction
からdecoderawtransaction
を実行するコンボは有用なのでシェルスクリプト化しておくとよい。
#!/bin/bash
### トランザクションIDを引数に指定しトランザクション情報を出力する
txid=`bitcoin-cli getrawtransaction $1`
bitcoin-cli decoderawtransaction $txid
#5.ウォレットの管理
次はウォレットやアドレスの仕組みについて確認していきたい。この章あたりから、いよいよビットコインの真髄が見えてきそうだ。
ウォレットの実体は.bitcoin
ディレクトリ直下にあるwallet.dat
というバイナリファイルである。getwalletinfo
コマンドで内容の確認ができる。
$ bitcoin-cli getwalletinfo
{
"walletname": "",
"walletversion": 169900,
"balance": 0.00000000, ## BTC残高
"unconfirmed_balance": 0.00000000,
"immature_balance": 0.00000000,
"txcount": 0,
"keypoololdest": 1576991493,
"keypoolsize": 1000,
"keypoolsize_hd_internal": 1000,
"unlocked_until": 0, ## 暗号化のアンロック期限
"paytxfee": 0.00000000,
"hdseedid": "2d819a2d1265e61aeb1dfa80e53f1c0d07536ccb",
"private_keys_enabled": true,
"avoid_reuse": false,
"scanning": false
}
初期状態ではunlocked_until
の項目が存在しない。これは暗号化walletを一時的にアンロックした場合にその期限を表示するためのフィールドである(0の場合は暗号化されていることを意味する)。walletを暗号化すれば項目が出現する。
##5-1. wallet暗号化/複合化
walletを暗号化するためにはencryptwallet
を用いる
$ bitcoin-cli encryptwallet <passphrase>
暗号化することでパスフレーズによってそのウォレットは保護され秘密鍵が秘匿される。このサーバに不正アクセスされたとしてもパスフレーズが漏洩しなければ勝手に送金されてしまうといった事態が避けられる。
複合化したい場合は制限時間を設けて時限的に複合化することができる。
$ bitcoin-cli walletpassphrase <passphrase> 120
##5-2. ビットコインアドレスの作成
ビットコインアドレスはBTCを受け取るための口座番号みたいなもの。
これはいくらでも好きなだけ(!)自分で生成することができる。
アドレス生成にはgetnewaddress
コマンドを用いる。
またアドレスには識別子として任意のラベルlabel
を付けることができる。これはあくまでローカルだけで通用するものであり、アドレスとラベルを紐付けることで人間が管理しやすくするためのものである。一つのラベルに複数のアドレスを紐付けることも可能である。
## ラベル:hoge11に2つ、ラベル:hoge22に1つのアドレスをそれぞれ生成する
$ bitcoin-cli getnewaddress hoge11
3Lr2tGo21MLmR8De4Jw1dA8zhDdPy2wRbi
$ bitcoin-cli getnewaddress hoge11
35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss
$ bitcoin-cli getnewaddress hoge22
3Do3Uenc4WwZ5pgDBANhDfpZHbwN25hWFg
作成したアドレスはgetaddressbylabel
メソッドで確認できる。
$ bitcoin-cli getaddressesbylabel hoge11
{
"35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss": {
"purpose": "receive"
},
"3Lr2tGo21MLmR8De4Jw1dA8zhDdPy2wRbi": {
"purpose": "receive"
}
}
$ bitcoin-cli getaddressesbylabel hoge22
{
"3Do3Uenc4WwZ5pgDBANhDfpZHbwN25hWFg": {
"purpose": "receive"
}
}
ラベル一覧を表示する:listlabels
$ bitcoin-cli listlabels
[
"hoge11",
"hoge22",
]
アドレスを生成するということは、それに対応する秘密鍵と公開鍵も同時に生成しそれらを自らのウォレットで保管し管理していくということに等しい。
ここらへんはかなり重要なポイントになってくるのでしっかり勉強しておく。
秘密鍵と公開鍵とビットコインアドレスの関係
ブロックチェーンとトランザクションを銀行に例えて
- ブロックチェーン = 全口座の全ての入出金履歴
- トランザクション = 1回の取引記録
というイメージであるとすると、3者(秘密鍵、公開鍵、ビットコインアドレス)の関係性は
- 秘密鍵
= 暗証番号
- 公開鍵
= 口座番号(入出金兼用)
- ビットコインアドレス
= 口座番号(入金専用)
という表現が適切だろうか。公開鍵
とビットコインアドレス
はいずれも形を変えただけで本質は同一の口座を指し示す識別子として利用されるが以下の違いがある。公開鍵
またはビットコインアドレス
を指定すれば誰でも入金(その口座に送金)することはできるのに対し、出金(別の口座に送金)するためにはビットコインアドレス
ではなく必ず公開鍵
で指定する必要がある。
また出金する際には暗証番号として秘密鍵
を用いたデジタル署名で取引書類にサインする必要がある。
なお入金に
公開鍵
を指定する方法(P2PK
=Pay to Public Key
)はかなり初期こそ使われていたが、安全性などの理由から新たにビットコインアドレス
で指定する方法(P2PKH
=Pay to Public Key Hash
)が開発され、現在ではP2PK
を利用する動機はほとんど無い。ここらへんについては、別途P2SH
やSeqwit
などと併せて整理したい。
ビットコインソフトウェアがビットコインアドレスを生成する際、内部的には下記の処理が行われている。
1.疑似乱数を用いて秘密鍵(一般的に256bitの数値)
を生成する
2.ECDSA(楕円曲線デジタル署名アルゴリズム)を用いて秘密鍵
から公開鍵
を求める
3.ハッシュ関数を用いて公開鍵
からビットコインアドレス
を生成する
1から2、2から3の処理は不可逆的であり、アドレスから公開鍵を求めたり、公開鍵から秘密鍵を求めたりすることは数学的困難さを伴う。この特性を利用してビットコインを安全にやり取りする仕組みが作られている。
秘密鍵(Private Key)
秘密鍵はそれから生成される公開鍵ならびにビットコインアドレスの唯一の所有者であることを証明するもの。
秘密鍵はそれ自体は乱数などから生成された任意の数値だが、ビットコインソフトウェアにおいてはそこにいくつかの属性情報(プレフィックス、チェックサムなど)を付加して以下のいずれかの形式で定義される。
- WIF形式
・・・・・先頭文字が5
- WIF圧縮形式
・・・先頭文字がK
orL
WIFというのはWallet Import Format
というビットコイン独自のフォーマット。Base58
というビットコインのために定義された58種のキャラクタセットを用いて58進数の数値に変換することで、人間が誤読しにくいようにという配慮がされている。
圧縮形式というのは後述する公開鍵が非圧縮タイプ
であるか圧縮タイプ
であるかによって確定的に決まる。
試しに先ほど作成したアドレス35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss
の元となった秘密鍵を確認してみる。秘密鍵を表示するにはdumpprivkey
コマンドを用いる。もちろん自らの保有する秘密鍵でなければ確認することはできない。
## walletを60秒間だけ複合化する(※秘密鍵にアクセスする際には事前に暗号化を解いておく必要があるため)
$ bitcoin-cli walletpassphrase <passphrase> 60
## アドレスに対応する秘密鍵を表示
$ bitcoin-cli dumpprivkey 35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss
KzUwAsGmHLKrpp9dK4SNDiu6DucckKq4uLThjx8grZfaLe6be5he
K
から始まっているためWIF圧縮形式
であることがわかる。
この記事をご覧になる方にはもはや釈迦に説法だと思うが、秘密鍵は暗証番号に相当するものなので、通常は絶対に秘匿しておかなければならないし、仮に紛失してしまうと、二度とその口座にアクセスすることは叶わないと思った方が良い。実際に十分な強度の256bit秘密鍵を用いている場合、その値がとりうる選択肢は2^256≒10^77となり、これは観測可能な宇宙に存在する全ての素粒子の数(10^80)より少しばかり少ないくらいの数となると言われている。文字通り、万が一にも見つけようと思って見つけられるような次元の話ではないのである。
公開鍵(Public Key)
公開鍵は秘密鍵(32byte)を楕円曲線デジタル署名アルゴリズム(ECDSA)により変換した2軸の座標(整数X,整数Y)として求められる。ECDSAにも各種方式があり、ビットコインでは米国国立標準技術研究所(NIST)が標準化したSecp256k1という曲線ならびにパラメータを使用している。ビットコインプロトコルにおいて公開鍵は下記の2種類の形式のいずれかを用いることとされている。
- 非圧縮形式
・・・先頭文字が0x04
、65byte長、(X,Y)を利用
- 圧縮形式
・・・・先頭文字が0x02
or0x03
、33byte長、Xのみを利用
座標(X,Y)は楕円曲線上にあるので、多項式を計算すればXからYは簡単に導出できる関係にある。よって(X,Y)の組を公開鍵として扱うことと、Xのみを公開鍵として扱うことは暗号強度的には等価であり違いはない。当初のビットコインソフトウェアは(X,Y)の組を公開鍵として扱っていたが、トランザクションの容量を削減する目的で、公開鍵の長さが約半分で済むXのみを公開鍵として扱う方式も仕様追加された。これらの経緯から、前者を非圧縮形式
、後者を圧縮形式
と呼んで区別している。
(参考)【Mastering Bitcoin】秘密鍵から楕円曲線暗号を使って公開鍵を生成する
(参考)【Mastering Bitcoin】 ビットコインアドレスについて(圧縮形式など)
具体的に公開鍵を確認してみる。先ほどに引き続きアドレス35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss
に対応する公開鍵を表示するためgetaddressinfo
コマンドを使用する。このコマンドを利用するとアドレスの情報が一覧表示されるためここではひとまず公開鍵の部分だけをピックアップする。
$ bitcoin-cli getaddressinfo 35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss | grep "pubkey"
"pubkey": "022d0c1a116d24cef338f53071edae8177901010085f93f170f1d577dd3f6f06d6",
"pubkey": "022d0c1a116d24cef338f53071edae8177901010085f93f170f1d577dd3f6f06d6",
先頭文字が0x02
なので圧縮形式
を使用していることがわかる。これは先ほど確認した秘密鍵もWIF圧縮形式
となっていたことと連動している。 圧縮形式
においてはプレフィックス1byte(ここでは0x02
)の後に続く32byte(2d0c1a116d24cef338f53071edae8177901010085f93f170f1d577dd3f6f06d6
)が公開鍵の片割れである座標値Xである。
なお上記実行例で公開鍵が2回表示されている理由についてはここでは割愛する(後述のSegwitラッピングアドレスに関連)。
参考までに非圧縮形式
も確認しておく。非圧縮形式
がメインで使われていた最初期のトランザクションを出力してみたい。
先ほど作成したシェルスクリプトを使用する。
## 2番目(Height=1)のブロック情報を出力
$ ./getblk.sh 1
{
"hash": "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048",
"confirmations": 614991,
"strippedsize": 215,
"size": 215,
"weight": 860,
"height": 1,
"version": 1,
"versionHex": "00000001",
"merkleroot": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
"tx": [
"0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" ## トランザクションID
],
"time": 1231469665,
"mediantime": 1231469665,
"nonce": 2573394689,
"bits": "1d00ffff",
"difficulty": 1,
"chainwork": "0000000000000000000000000000000000000000000000000000000200020002",
"nTx": 1,
"previousblockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
"nextblockhash": "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd"
}
## トランザクションIDからトランザクション情報を出力
$ ./gettx.sh 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
{
"txid": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
"hash": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
"version": 1,
"size": 134,
"vsize": 134,
"weight": 536,
"locktime": 0,
"vin": [
{
"coinbase": "04ffff001d0104",
"sequence": 4294967295
}
],
"vout": [
{
"value": 50.00000000,
"n": 0,
"scriptPubKey": {
"asm": "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG",
"hex": "410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac",
"type": "pubkey"
}
}
]
}
これはgenesisブロックの次のブロックであり、まだまだ取引らしい取引はなく、coinbaseトランザクションだけが記録されている状況だ。このトランザクションのvout
の中にあるscriptPubKey
に送金先の情報が含まれている。詳しくは別途、トランザクションスクリプトという項目で整理したいと思っているが、ここではasm
の項目に注目すると、0x04
に続く長い64byte(96b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee
)が見つかる。これが非圧縮形式
で記述された公開鍵である。座標(X,Y)の両方が含まれているので1+32×2=65byteもの情報量になってしまうわけである。
トランザクション容量の圧縮はビットコインの処理性能に深く関わってくる課題なので、圧縮形式
の採用もそうだが、後述するビットコインアドレス
やSegwit
の発明へと繋がっていく。こういった歴史的背景を紐解きつつ、理解していくことは非常に重要なことだと思う。
ビットコインアドレス
ビットコインアドレス
は公開鍵をハッシュ演算して導出したものであり、その実体は公開鍵ハッシュ
と呼ばれることも多い。前項からの文脈で表現するならば、公開鍵の代わりに『公開鍵をハッシュ化したもの』をscriptPubKey
に指定しようぜ、という発想でできたものがであるから公開鍵ハッシュ
という呼び方は非常にしっくりくる。そして公開鍵ハッシュ
にプレフィックスやチェックサムなどの属性情報を付加してBase58
エンコードしたものを便宜的にビットコインアドレス
と呼んでいるわけである。
ハッシュ演算は具体的には下記の順序で行われる。
公開鍵 → SHA256(256bit) → RIPEMD160(160bit) → 公開鍵ハッシュ(160bit)
このように2回ハッシュ演算を行うことを一般的にダブルハッシュ
と呼び、ビットコインではHASH160
と表記したりする。なぜ2回もハッシュ化する必要があるのかという疑問が沸くが、採用した理由についてはどうやらサトシ・ナカモトも言及していないようだが、いくつかの参考資料ではLength Extension Attack (長さ拡張攻撃)に対する対策ではないかとの推測が見られた。つまるところハッシュ関数が将来的に部分的に破られたときのリスクに備えて、二重化することで強度を高めておくという事前の予防策、と考えるのが妥当なところだろうか。
公開鍵ハッシュ
ができたら、プレフィックスとチェックサムを付加するのだが、チェックサムは公開鍵ハッシュ
をさらにSHA256
とSHA256
でダブルハッシュ
する(これをSHA256d
と呼ぶ)。
{プレフィックス+公開鍵ハッシュ} → SHA256(256bit) → SHA256(256bit) → 先頭4byteをチェックサムとする
とにかく随所でハッシュ関数が活躍しまくっている。最後にまとめてBase58エンコードにかけて完成。
{プレフィックス(1byte)+公開鍵ハッシュ(20byte)+チェックサム(4byte)}→ Base58エンコード(27~34文字)
詳細については参考資料の方が100倍わかりやすいのでここでは概要を追いかける。
最終的にビットコインアドレス
は1
or3
で始まる27~34文字の英数字として表記される。
それでは実際に最初に作成したアドレス35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss
の詳細情報を確認してみたい。
getaddressinfo
コマンドを使うと
$ bitcoin-cli getaddressinfo 35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss
{
"address": "35YzatN8GgtGFBpjT7HWkZHNgcKQ2FmHss",
"scriptPubKey": "a9142a5c0ec9cefb797bdef84b3e57ee8561c40b450987",
"ismine": true,
"solvable": true,
"desc": "sh(wpkh([2f0ff834/0'/0'/9']022d0c1a116d24cef338f53071edae8177901010085f93f170f1d577dd3f6f06d6))#6hhgrklp",
"iswatchonly": false,
"isscript": true,
"iswitness": false,
"script": "witness_v0_keyhash",
"hex": "00140797b322026a926da788b9396240556e08ecb217",
"pubkey": "022d0c1a116d24cef338f53071edae8177901010085f93f170f1d577dd3f6f06d6",
"embedded": {
"isscript": false,
"iswitness": true,
"witness_version": 0,
"witness_program": "0797b322026a926da788b9396240556e08ecb217",
"pubkey": "022d0c1a116d24cef338f53071edae8177901010085f93f170f1d577dd3f6f06d6",
"address": "bc1qq7tmxgszd2fxmfughyukysz4dcywevshqpjf7w",
"scriptPubKey": "00140797b322026a926da788b9396240556e08ecb217"
},
"label": "hoge11",
"ischange": false,
"timestamp": 1576991492,
"hdkeypath": "m/0'/0'/9'",
"hdseedid": "2d819a2d1265e61aeb1dfa80e53f1c0d07536ccb",
"hdmasterfingerprint": "2f0ff834",
"labels": [
{
"name": "hoge11",
"purpose": "receive"
}
]
}
ここに記述されている内容を完全に理解するにはまだ学習ステップが足りていないことがよくわかる。。
とはいえ、こうして定義されたビットコインアドレス
は送金先情報としてトランザクションのscriptPubKey
に利用できる運びとなった(P2PKH(Pay to Public Key Hash)
)。これには主に2つのメリットがあったとサトシ・ナカモトが言及している。
①(受け取りの時点では)自身の公開鍵を開示する必要がなくなり、公開鍵暗号への攻撃耐性が向上した。
量子コンピュータでショアのアルゴリズムを用いると、多項式時間で公開鍵から秘密鍵の導出ができてしまうと言われており、公開鍵が公知になっている状態はそれ自体が将来的なリスク要因となり得る。送金先情報としてビットコインアドレスを用いると公開鍵は秘匿できる。また出金時は残高をお釣りアドレスに転送するような使い捨てタイプのウォレット実装も進んでいるので、これらを併用すれば、実質的に公開鍵暗号への攻撃を無力化できると言える。
②文字数圧縮(256bit→160bit)により、可読性の向上であったり、QRコードへの搭載が簡易になった。
主に実用面への寄与も大きく、ビットコインの普及拡大に貢献したことは間違いないだろう。
ところでここで一つの疑問が沸いてくる。見てきたようにビットコインアドレス
の実体は公開鍵ハッシュ
であり、これは言い換えれば公開鍵の情報量(圧縮形式
で256bit)を160bitに弱体化させただけのものだ。先ほど秘密鍵は宇宙の素粒子の数だけあると書いたが、100bit近くも減らしたということは、逆に考えると、同じハッシュを導出する秘密鍵・公開鍵ペアの数が100bit近く増加したと考えることができる。本来、全く無関係なはずの秘密鍵・公開鍵ペアにも関わらずそれらは同一のビットコインアドレスへのアクセス権を有している。こういった衝突が起こってしまう確率はどのくらいあるのだろうか?
検索してみる一応文献に辿り着いた(権利関係が微妙なのでリンクは割愛)。誕生日のパラドックス
という一度は聞いたことがあるアレと同じ問題と捉えると、『1.7×10^23個のアドレスを生成した時点で、いずれかのアドレスが衝突する確率が1%を超える』という試算結果らしい。これは桁数だけ見ればまだ現実的な脅威と感じるかもしれないが、それでも大概大きな数値であり、そうそう起こりえないことは確かである。また、衝突するのが『自分のアドレス』に限定するなら、その確率は天文学的なものになるため、暗号学的には十分な強度があると言えると思う。
いずれにせよこれに対する解決策は既に提示されており、scriptPubKey
に公開鍵やアドレスを直接指定するのではなく、スクリプトを指定してしまおうというP2SH(Pay to Script Hash)
というどこかの頭の良い人が考えた仕組みが既に実装されており、広く使われている。次章ではここらへんに踏み込んでいきたい。
というわけで、キリが良いのでここらで一度締めて投稿します。