FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

「ブロックチェーン・プログラミング」を読む (ビットコイン送金を体験)

ブロックチェーン・プログラミング」第2章はビットコインの基本を復習しておく。 Mastering Bitcoinを読んでいるので、ある程度理解できるのでサクサク読み進めていく。

今回は第4章の途中まで。第3章は暗号技術でちょっと難しいのでとりあえず読み飛ばす。

ブロックチェーン・プログラミング 仮想通貨入門 (KS情報科学専門書)

ブロックチェーン・プログラミング 仮想通貨入門 (KS情報科学専門書)

4. ビットコイン・ワレット

4.1 ワレットの初期化

下記のコマンドにより、自分の秘密鍵と公開鍵が生成される。2つのビットコインアドレスを生成したのは、送受信をチェックするためだ。

$ bitcoin-cli getnewaddress
2N4b5AbK45czUSr9FeL2sXfivh49v9nanAy
$ bitcoin-cli getnewaddress
2N6bBDdqbAcXbD3fqD2jMcAMzcKfP1aUELj

次に、アカウントを設定する。送信元のビットコインアドレスについてはアカウントを設定しないと、送信ができないらしい。そんなもん?

$ bitcoin-cli setaccount 2N4b5AbK45czUSr9FeL2sXfivh49v9nanAy msyksphinz
  • ワレット秘密鍵の暗号化する。パスワードにより暗号化する。
$ bitcoin-cli encryptwallet <パスワード>
wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.

次に、testnetでビットコインを入手する。https://testnet.manu.backend.hamburg/faucet を参照する。

![1528119813642](C:\msys64\home\masayuki\work\manuscript\bitcoin\testnet_wallet.png)

ビットコインを入手する。これにより、0.275BTC が自分のワレットに転送された。

$ bitcoin-cli getbalance
0.27500000
$ bitcoin-cli listaccounts
{
  "": 0.00000000,
  "msyksphinz": 0.27500000
}

4.3 ビットコインの送金

まずはビットコインの送金のために、まずは取引手数料を設定する。

$ bitcoin-cli settxfee 0.00001
true

つぎに、受信先アドレスに対して送金を行った。送信額は0.1BTCだ。




このトランザクションを確認してみよう。

$ bitcoin-cli gettransaction b7b240c18ecabc9cdfcabbc6a2cfe407f52a1f15d4907504b04cf613441b44d3
{
  "amount": 0.00000000,
  "fee": -0.00000166,
  "confirmations": 5,
  "blockhash": "0000000000000015c537afbf8b6a4084fc690041506af2b2dec26015a01ed69e",
  "blockindex": 11,
  "blocktime": 1528120342,
  "txid": "b7b240c18ecabc9cdfcabbc6a2cfe407f52a1f15d4907504b04cf613441b44d3",
  "walletconflicts": [
  ],
  "time": 1528120295,
  "timereceived": 1528120295,
  "bip125-replaceable": "no",
  "details": [
    {
      "account": "msyksphinz",
      "address": "2N6bBDdqbAcXbD3fqD2jMcAMzcKfP1aUELj",
      "category": "send",
      "amount": -0.10000000,
      "label": "",
      "vout": 1,
      "fee": -0.00000166,
      "abandoned": false
    },
    {
      "account": "",
      "address": "2N6bBDdqbAcXbD3fqD2jMcAMzcKfP1aUELj",
      "category": "receive",
      "amount": 0.10000000,
      "label": "",
      "vout": 1
    }
  ],
  "hex": "0200000000010102e517bb9c31447838b5d84359da3df79f19665763fa3ff1c57f7ee3c5ba395e00000000171600146fc7265dad5654f1721c5945daf11e8fde5e7578feffffff02ba060b010000000017a9147778f63cb2d4c6f0326938e7a405fd304b60def787809698000000000017a914925fa46ef41f3d1b37683a73091764948d2261c98702483045022100aa6e36bbab10e8b3b4a95bd83711d02aaf7b2f1b4169012ebcdc1862e984851c02207b3880824a1fec98f3522e37b7b5378d42324b0e689139341f15700d0c3076f30121023cda393b2cd148239c138d1f7e01d74127c08c1eabd11f146ae40db92ed2c69a272c1400"
}

4.4 ビットコイン・ワレットによる暗号鍵管理

ビットコイン・ワレットは日本語にすると財布だが、通常の財布よりもセキュリティを強化する必要がある。店舗のワレットのビットコインアドレスがただ1つだとすると、その1つの秘密鍵が漏洩すると、その店の資産がすべて盗まれてしまう。

ビットコインの送金履歴は誰でも読み取ることができるので、第三者が購入者の購入履歴を追跡することが可能になるため、店舗が受領の度にビットコインアドレスを使い捨てにする方式で、プライバシーを問題を解決される。

セキュリティを確保するために有名な手法として、2レベルセキュリティという手法がある。以下の2つのストレージに分けて管理することである。

  • ホットストレージ : オンライン状態のマシンで管理するストレージ
  • コールドストレージ : オフライン状態のマシンで管理するストレージ

ここでリスクを避けるためには、ホットストレージにおける公開鍵の管理とコールドストレージの秘密鍵の管理が完全に独立に運用できることが理想である。

ECDSAにおいて、秘密鍵と独立鍵を独立に生成する方法を考える。楕円曲線暗号の公開鍵の足し算は、火砲の定義から以下のようになる。

 (a+b)G=aG+bG

ここで  priv を最初の秘密鍵 privG を最初の公開鍵とする。このとき、 (priv+i) を新しい秘密鍵とすると、これに対応する公開鍵は (priv+i)G=privG+iG となり、 privG は既に明らかになっているため、パラメータGにより公開鍵を生成できることを意味する。

このような親の鍵から子の鍵を生成している方法を決定性ワレットと呼ぶ。

しかしこのままでは i を一つずつずらしながら試行することで公開鍵を推定することができるようになってしまう。したがって、インデックスに加えて256ビットのサイズを持つ巨大な乱数を1つ追加する。これにハッシュ関数を適用する。このインデックスと乱数を含んだハッシュ値チェーンコード(chain code)と呼ばれている。最初の秘密鍵 mマスター秘密鍵と呼ばれ、マスター秘密鍵から生成された M=mGマスター公開鍵 と呼ばれる。

 \text{chain_code}=h(i, c)

したがって、秘密鍵と公開鍵の関係は以下のようになる。

 (priv+\text{chain_code})G = privG + \text{chain_code}G

ビットコインの決定性ワレットでは、ルートシードというデータを秘密情報の起点にして、マスター秘密鍵と最初のチェーンコードを生成している。

これでも、ルートシードが漏洩してしまった場合は、すべての秘密鍵が芋づる式に漏洩してしまうので、一般ユーザのワレットのセキュリティとしてストレージのセキュリティレベルをより多くのレベルに分割する。この方法を階層的決定性ワレット(HDワレット) と呼ぶ。

4.5 HDワレットの実装例

HDワレットの実装例をまずは写経だ。

ルートシードとマスター鍵を作成する。

irb(main):001:0> require 'bitcoin'
=> true
irb(main):002:0> require 'securerandom'
=> false
irb(main):004:0> root_seed = SecureRandom.hex (32)
=> "a16f2eb626481c1fe2e7d80f7b9fa7c980107d0173a5bc170618a6c741fa1f5b"
irb(main):005:0> master_key = Bitcoin::ExtKey.generate_master (root_seed.htb)
=> #<Bitcoin::ExtKey:0x00005570693cea68 @number=0, @depth=0, @parent_fingerprint="00000000", @priv_key=#<Bitcoin::Key:0x0000557069403b28 @key=#<OpenSSL::PKey::EC:0x0000557069403a88>, @pubkey_compressed=true>, @chain_code="\xD1\xDD\x1C\xB9&IAs\xB5\xD2i8:\xD3\xD3Tb\x83\xE7\xF2\xF8i\xF7\xC0H\xDF\xD6\xE2\x9B\xAB\xE1Z">
irb(main):006:0> m = master_key.priv
=> "b00b62ff659fee0d8f517cc8d80f21b59ccb354eaaefac85bccb777ee90c137c"
irb(main):007:0> M = master_key.pub
=> "03e96f71630c1a138fc5f7fe06c4bb42024c0b6506aa6d62168251479b1e678d92"
irb(main):008:0> master_key.chain_code
=> "\xD1\xDD\x1C\xB9&IAs\xB5\xD2i8:\xD3\xD3Tb\x83\xE7\xF2\xF8i\xF7\xC0H\xDF\xD6\xE2\x9B\xAB\xE1Z"
irb(main):009:0> master_key.depth
=> 0
irb(main):010:0> key44 = master_key.derive (2*31+44)
=> #<Bitcoin::ExtKey:0x00005570694817f8 @number=106, @depth=1, @parent_fingerprint="f86ba5b0", @priv_key=#<Bitcoin::Key:0x0000557069480d30 @key=#<OpenSSL::PKey::EC:0x0000557069480cb8>, @pubkey_compressed=true>, @chain_code="\x1EL\xBE\xBE\xEA\x9El\x18\b\x01\x16\x03\xA7\xA7;i\xDC\xFF\xC6\x15}\x8A\x87\x8C1\x01>\xC2\xEE\x8B^V">
irb(main):011:0> key44.depth
=> 1
irb(main):012:0> key440=key44.derive (2*31+0)
=> #<Bitcoin::ExtKey:0x000055706946d078 @number=62, @depth=2, @parent_fingerprint="a6af8e02", @priv_key=#<Bitcoin::Key:0x000055706946bd18 @key=#<OpenSSL::PKey::EC:0x000055706946bca0>, @pubkey_compressed=true>, @chain_code="\x8B\x9E\rG08\xF3\x7F\xA1\xFE\xED\e\xB9JRS\xDB\x05d2\x92\xC0\x94@\xEB.\xB9\xCF\xF8\x03\x1E\x1F">
irb(main):013:0> key440.depth
=> 2
irb(main):014:0> key4400 = key440.derive (2*31+0)
=> #<Bitcoin::ExtKey:0x000055706944f5f0 @number=62, @depth=3, @parent_fingerprint="80f1d049", @priv_key=#<Bitcoin::Key:0x000055706944e4c0 @key=#<OpenSSL::PKey::EC:0x000055706944e448>, @pubkey_compressed=true>, @chain_code="0\x88\xFD\xA4\xAF\xA9N\xD2;\x82d\vr\f\x8E\x9F\x8D\xEFc_\xF7|\xEE\x142\x9Eo=3\t\x9Bb">
irb(main):015:0> key4400.depth
=> 3
irb(main):016:0> key4401=key440.derive (2*31+1)
=> #<Bitcoin::ExtKey:0x000055706940cc00 @number=63, @depth=3, @parent_fingerprint="80f1d049", @priv_key=#<Bitcoin::Key:0x000055706940ba08 @key=#<OpenSSL::PKey::EC:0x000055706940b990>, @pubkey_compressed=true>, @chain_code="\xBEF\xA1\x05\xD1|p-f\x7F\x1A\xEF\xC8\n\\\x1FB\xD1C\x0F\xBC\x0F\x99\x10\xC0c\xC4i\x97\xF3\xC9A">
irb(main):017:0> key4401.depth
=> 3
irb(main):018:0> key4400.to_base58
=> "xprv9ycPRjdqs6AJMwofjNTG43myRsFXwZwQNWffouUBXDww32up9iRwZAhYd1UhM5nCFkD76BnCGXEzoATrEAoEeqoSga1U95S9MF14G1C4Uoo"
irb(main):019:0> key4401.ext_pubkey.to_base58
=> "xpub6CbjqFAjhTibePZJexeY27NunEQtQiMYWStBgB4Z6eSuzpRZTzqXAaaJWCbB41QejM9wBUZUBxb1GpiDwQbuspGWxaSgx35GBRMLXNtWRkS"
irb(main):020:0> ext_privkey = Bitcoin::ExtKey.from_base58 ("xprv9ycPRjdqs6AJMwofjNTG43myRsFXwZwQNWffouUBXDww32up9iRwZAhYd1UhM5nCFkD76BnCGXEzoATrEAoEeqoSga1U95S9MF14G1C4Uoo")
=> #<Bitcoin::ExtKey:0x0000557069382758 @number=62, @depth=3, @parent_fingerprint="80f1d049", @priv_key=#<Bitcoin::Key:0x00005570693811f0 @key=#<OpenSSL::PKey::EC:0x00005570693806d8>, @pubkey_compressed=true>, @chain_code="0\x88\xFD\xA4\xAF\xA9N\xD2;\x82d\vr\f\x8E\x9F\x8D\xEFc_\xF7|\xEE\x142\x9Eo=3\t\x9Bb">
irb(main):021:0> xt_pubkey = Bitcoin::ExtPubkey.from_base58 ("xpub6CbjqFAjhTibePZJexeY27NunEQtQiMYWStBgB4Z6eSuzpRZTzqXAaaJWCbB41QejM9wBUZUBxb1GpiDwQbuspGWxaSgx35GBRMLXNtWRkS")
=> #<Bitcoin::ExtPubkey:0x0000557068f91838 @depth=3, @number=63, @parent_fingerprint="80f1d049", @chain_code="\xBEF\xA1\x05\xD1|p-f\x7F\x1A\xEF\xC8\n\\\x1FB\xD1C\x0F\xBC\x0F\x99\x10\xC0c\xC4i\x97\xF3\xC9A", @pub_key=#<OpenSSL::PKey::EC::Point:0x0000557068f8aba0 @group=#<OpenSSL::PKey::EC::Group:0x0000557068f8baa0>>>

マスター秘密鍵$m$とマスター公開鍵 $M$ を生成する。

irb(main):005:0> master_key = Bitcoin::ExtKey.generate_master (root_seed.htb)
=> #<Bitcoin::ExtKey:0x00005570693cea68 @number=0, @depth=0, @parent_fingerprint="00000000", @priv_key=#<Bitcoin::Key:0x0000557069403b28 @key=#<OpenSSL::PKey::EC:0x0000557069403a88>, @pubkey_compressed=true>, @chain_code="\xD1\xDD\x1C\xB9&IAs\xB5\xD2i8:\xD3\xD3Tb\x83\xE7\xF2\xF8i\xF7\xC0H\xDF\xD6\xE2\x9B\xAB\xE1Z">
irb(main):006:0> m = master_key.priv
=> "b00b62ff659fee0d8f517cc8d80f21b59ccb354eaaefac85bccb777ee90c137c"
irb(main):007:0> M = master_key.pub
=> "03e96f71630c1a138fc5f7fe06c4bb42024c0b6506aa6d62168251479b1e678d92"
irb(main):008:0> master_key.chain_code
=> "\xD1\xDD\x1C\xB9&IAs\xB5\xD2i8:\xD3\xD3Tb\x83\xE7\xF2\xF8i\xF7\xC0H\xDF\xD6\xE2\x9B\xAB\xE1Z"
irb(main):009:0> master_key.depth
=> 0

第2階層の m/44'を導出する。

irb(main):010:0> key44 = master_key.derive (2*31+44)
=> #<Bitcoin::ExtKey:0x00005570694817f8 @number=106, @depth=1, @parent_fingerprint="f86ba5b0", @priv_key=#<Bitcoin::Key:0x0000557069480d30 @key=#<OpenSSL::PKey::EC:0x0000557069480cb8>, @pubkey_compressed=true>, @chain_code="\x1EL\xBE\xBE\xEA\x9El\x18\b\x01\x16\x03\xA7\xA7;i\xDC\xFF\xC6\x15}\x8A\x87\x8C1\x01>\xC2\xEE\x8B^V">
irb(main):011:0> key44.depth
=> 1

第3階層m/44'/0' を導出する。

irb(main):012:0> key440=key44.derive (2*31+0)
=> #<Bitcoin::ExtKey:0x000055706946d078 @number=62, @depth=2, @parent_fingerprint="a6af8e02", @priv_key=#<Bitcoin::Key:0x000055706946bd18 @key=#<OpenSSL::PKey::EC:0x000055706946bca0>, @pubkey_compressed=true>, @chain_code="\x8B\x9E\rG08\xF3\x7F\xA1\xFE\xED\e\xB9JRS\xDB\x05d2\x92\xC0\x94@\xEB.\xB9\xCF\xF8\x03\x1E\x1F">
irb(main):013:0> key440.depth
=> 2

第4階層のアカウントの導出。

irb(main):014:0> key4400 = key440.derive (2*31+0)
=> #<Bitcoin::ExtKey:0x000055706944f5f0 @number=62, @depth=3, @parent_fingerprint="80f1d049", @priv_key=#<Bitcoin::Key:0x000055706944e4c0 @key=#<OpenSSL::PKey::EC:0x000055706944e448>, @pubkey_compressed=true>, @chain_code="0\x88\xFD\xA4\xAF\xA9N\xD2;\x82d\vr\f\x8E\x9F\x8D\xEFc_\xF7|\xEE\x142\x9Eo=3\t\x9Bb">
irb(main):015:0> key4400.depth
=> 3
irb(main):016:0> key4401=key440.derive (2*31+1)
=> #<Bitcoin::ExtKey:0x000055706940cc00 @number=63, @depth=3, @parent_fingerprint="80f1d049", @priv_key=#<Bitcoin::Key:0x000055706940ba08 @key=#<OpenSSL::PKey::EC:0x000055706940b990>, @pubkey_compressed=true>, @chain_code="\xBEF\xA1\x05\xD1|p-f\x7F\x1A\xEF\xC8\n\\\x1FB\xD1C\x0F\xBC\x0F\x99\x10\xC0c\xC4i\x97\xF3\xC9A">
irb(main):017:0> key4401.depth
=> 3
irb(main):018:0> key4400.to_base58
=> "xprv9ycPRjdqs6AJMwofjNTG43myRsFXwZwQNWffouUBXDww32up9iRwZAhYd1UhM5nCFkD76BnCGXEzoATrEAoEeqoSga1U95S9MF14G1C4Uoo"
irb(main):019:0> key4401.ext_pubkey.to_base58
=> "xpub6CbjqFAjhTibePZJexeY27NunEQtQiMYWStBgB4Z6eSuzpRZTzqXAaaJWCbB41QejM9wBUZUBxb1GpiDwQbuspGWxaSgx35GBRMLXNtWRkS"
irb(main):020:0> ext_privkey = Bitcoin::ExtKey.from_base58 ("xprv9ycPRjdqs6AJMwofjNTG43myRsFXwZwQNWffouUBXDww32up9iRwZAhYd1UhM5nCFkD76BnCGXEzoATrEAoEeqoSga1U95S9MF14G1C4Uoo")
=> #<Bitcoin::ExtKey:0x0000557069382758 @number=62, @depth=3, @parent_fingerprint="80f1d049", @priv_key=#<Bitcoin::Key:0x00005570693811f0 @key=#<OpenSSL::PKey::EC:0x00005570693806d8>, @pubkey_compressed=true>, @chain_code="0\x88\xFD\xA4\xAF\xA9N\xD2;\x82d\vr\f\x8E\x9F\x8D\xEFc_\xF7|\xEE\x142\x9Eo=3\t\x9Bb">
irb(main):021:0> xt_pubkey = Bitcoin::ExtPubkey.from_base58 ("xpub6CbjqFAjhTibePZJexeY27NunEQtQiMYWStBgB4Z6eSuzpRZTzqXAaaJWCbB41QejM9wBUZUBxb1GpiDwQbuspGWxaSgx35GBRMLXNtWRkS")
=> #<Bitcoin::ExtPubkey:0x0000557068f91838 @depth=3, @number=63, @parent_fingerprint="80f1d049", @chain_code="\xBEF\xA1\x05\xD1|p-f\x7F\x1A\xEF\xC8\n\\\x1FB\xD1C\x0F\xBC\x0F\x99\x10\xC0c\xC4i\x97\xF3\xC9A", @pub_key=#<OpenSSL::PKey::EC::Point:0x0000557068f8aba0 @group=#<OpenSSL::PKey::EC::Group:0x0000557068f8baa0>>>