BIP32 拡張鍵の文字列から、秘密鍵・公開鍵やチェーンコードを抽出する

# 背景
各ビットコインウォレットサービスより、
拡張公開鍵や拡張秘密鍵を取得することができたりするが、
その鍵を分解して実際の公開鍵や秘密鍵、チェーンコードを取得する方法が不明だったので、
調べてみた。

# 拡張鍵の中身を分解していく

##拡張鍵について
例えば[bcwallet](https://github.com/blockcypher/bcwallet)等でウォレットを新規作成した場合、
BIP32に基づく拡張秘密鍵/拡張公開鍵が得られる。

拡張公開鍵の例は以下のようになる。

```
tpubD6NzVbkrYhZ4WsMXiXFSfYkBoJxysBZv1PD79BPS72LTEiCfnf3Qenh5Z173CwfEdw5keu4GAAWum6J4mu1suKL3CWCBCnTB7NFMf3DjNfS
```

なお、頭文字４文字（プレフィックス）で拡張鍵の種類が分かるようになっている。詳細は以下参照。

|プレフィックス|16進数表記 (※1)|鍵の種類|   
|:-----|:------------------|:------------------|
| xpub |0x0488B21E|拡張公開鍵（メインネット）|     
| xprv |0x0488ADE4|拡張秘密鍵（メインネット）|
| tpub |0x043587CF|拡張公開鍵（テストネット）|
| tprv |0x04358394|拡張秘密鍵（テストネット）| 

※1 ビットコインのアドレスは、16進数文字列をbase58checkという形式でエンコードしている。
エンコード前の表記を上記に乗せている。例えばxpubをbase58checkでデコードすると
0x0488B21E　になるということ。	

##拡張鍵=> 鍵情報(256bits)+チェーンコード(256bits)

ビットコインの鍵は公開鍵も秘密鍵も通常256bitsである。

先程の例の拡張公開鍵と公開鍵を比較すると、
以下のようになるが、明らかに拡張公開鍵の方が長い。

```
#公開鍵
217ZvQxj8hRf2VBBEgSAApC3555vj8jmvDMG61aCKepgN

#拡張公開鍵
tpubD6NzVbkrYhZ4WsMXiXFSfYkBoJxysBZv1PD79BPS72LTEiCfnf3Qenh5Z173CwfEdw5keu4GAAWum6J4mu1suKL3CWCBCnTB7NFMf3DjNfS
```

そこで、著名な本である[Mastering Bitcoin](https://github.com/bitcoinbook/bitcoinbook)を調べてみると、拡張公開鍵には、鍵情報に加えてチェーンコード(256bits)が含まれていることも分かる。

すなわち「拡張鍵 => 鍵情報(256bits) + チェーンコード(256bits)」となる。
ここにプレフィックス(4bytes)の情報を加えれば、拡張鍵は最低68bytesという長さになる。

##拡張鍵は68bytesではなく、82bytes、、、

前出の「※１」にも記載したが、
ビットコインのアドレスは、base58checkという方式でエンコードされている。

そのため、先程提示した拡張公開鍵をbase58checkでデコードしてみる。
プレフィックスが32bits,鍵情報が256bits、チェーンコードが256bitなので、
合計68bytesになることを期待したが、82bytesとなった。

```
0x043587cf00000000000000000035390470be22cc0d18aa6f6d53fd7900ff53446a2cb6075de1c65a5fc8432ed3035f748ce5c61c9991dd5451dbdcb2c664d1cf9a893829a6986c5180ec7fbbcc8f47b766b9
```

## 82bytes - 68bytes = 残りの14bytesは何か？

ここまでの情報だと14bytes分謎のbytesが存在する。
構成要素を調べたところ、[こちら](http://techmedia-think.hatenablog.com/entry/2017/01/07/142406)のサイトから以下の表を得られた。

![Screenshot from 2018-05-04 12-43-31.png](https://qiita-image-store.s3.amazonaws.com/0/131286/3912bff1-a148-7c7f-4c61-962bdd31b4ef.png)

鍵部分が33bytesになっているが、
おそらくcomporessed鍵となっており、1bytes増えているのだと思う。

それよりも、上記表のバイト数を合計しても
「4+1+4+4+32+33 = 78bytes」にしかならず、4bytes足りない。。。

## 最後の4bytesはチェックサム

82bytes中78bytesまでの正体はわかったが、残りの4bytesが何かわからない。。。
英語のサイトをあさりはじめ、[こちら](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format)のサイトに答えが載っていた。

該当の箇所のキャプチャを以下に抜粋する。
![Screenshot from 2018-05-04 12-49-09.png](https://qiita-image-store.s3.amazonaws.com/0/131286/511122d7-43d5-5e54-9ee9-e8584d1855f8.png)

箇条書きのすぐ下のパラグラフに、
78bytesに加えて32bitsのチェックサムが追加されているよ、と書いてある。
チェックサムの作り方は、78bytesのデータをSHA256で2回ハッシュしましょうね、ということ。

試しに、前半78bytesをSHA256でダブルハッシュしたところ、
ハッシュ値の上位4bytesと、拡張公開鍵をデコードした文字列の下位4bytesが見事に一致した。

```
//前半78bytesをSHA256でダブルハッシュした16進数値
0x47b766b959dfa60a0e2927b46052837d8cb3a6f988223845f18ced4c770db41c
→ (上位4bytes) 47b766b9

//拡張公開鍵をデコードした文字列
0x043587cf00000000000000000035390470be22cc0d18aa6f6d53fd7900ff53446a2cb6075de1c65a5fc8432ed3035f748ce5c61c9991dd5451dbdcb2c664d1cf9a893829a6986c5180ec7fbbcc8f47b766b9
→（下位4bytes) 47b766b9
```

日本語でまとまった情報がなく、回り道をしてしまったが、
最終的にこのように各要素を分解できた。

# 拡張鍵をインプットすると、各要素に分解してくれるコード書いた

最後に拡張鍵を各要素に分解するコードを書いたので、
以下に示す。言語はPHP。

## 補足
[bitwasp/bitcoin-php](https://github.com/Bit-Wasp/bitcoin-php)というパッケージを利用している。require('./vendor/autoload.php') で呼び出しているのがそれです。

```php:extendedKey.php

use BitWasp\Bitcoin\Base58;
use BitWasp\Bitcoin\Crypto\Hash;

require('./vendor/autoload.php');

class ExtendedKey {

  private $keyString;
  private $version;
  private $depth;
  private $fingerprint;
  private $childNumber;
  private $chainCode;
  private $key;
  private $checksum;

  public function __construct($keyString){

    $this->keyString = $keyString;

    try {

      if (strlen($keyString) > 112) {
        throw new \Exception('[ERROR]the key string exceeds maximum size'."\n");
      }

      if (!in_array(substr($keyString,0, 4), ['xpub', 'xprv', 'tpub', 'tprv'])) {
        throw new \Exception('[ERROR]the key string is invalid'."\n");
      }

      $decoded = (new Base58())->decode($keyString);
      $keyMain = $decoded->slice(0,78);
      $this->checksum = $decoded->slice(78,4);

      //$keyStringをbase58デコードした文字列において、
      //下位４バイトが上位78バイトのチェックサムになっているので比較。
      $hash        = (Hash::sha256(Hash::sha256($keyMain)));
      $checkBytes  = $hash->slice(0,4);

      print_r($hash);

      if(!$checkBytes->equals($this->checksum)){
        throw new \Exception('[ERROR]the key string is invalid'."\n");
      }

      $this->version     = $keyMain->slice(0,4);
      $this->depth       = $keyMain->slice(4,1);
      $this->fingerprint = $keyMain->slice(5,4);
      $this->childNumber = $keyMain->slice(9,4);
      $this->chainCode   = $keyMain->slice(13,32);
      $this->key         = $keyMain->slice(45,33);

    }catch(Exception $e){
      echo $e->getMessage();
      exit();
    }
  }
 
  //以下はただのゲッター
  public function getVersion(){
    return $this->version->getHex();
  }

  public function getDepth(){
    return $this->depth->getHex();
  }

  public function getFingerprint(){
    return $this->fingerprint->getHex();
  }

  public function getChildNumber(){
    return $this->childNumber->getHex();
  }

  public function getChainCode(){
    return $this->chainCode->getHex();
  }

  public function getKey(){
    return $this->key->getHex();
  }

  public function getChecksum(){
    return $this->checksum->getHex();
  }

}
```

```php:利用例.php
require('./extendedKey.php');

$key = 'tpubD6NzVbkrYhZ4WsMXiXFSfYkBoJxysBZv1PD79BPS72LTEiCfnf3Qenh5Z173CwfEdw5keu4GAAWum6J4mu1suKL3CWCBCnTB7NFMf3DjNfS';

$exKey = new ExtendedKey($key);

//043587cf
echo $exKey->getVersion()         . "\n";

//00
echo $exKey->getDepth()           . "\n";

//00000000
echo $exKey->getFingerprint()     . "\n";

//00000000
echo $exKey->getChildNumber()     . "\n";

//35390470be22cc0d18aa6f6d53fd7900ff53446a2cb6075de1c65a5fc8432ed3
echo $exKey->getChainCode()       . "\n";

//035f748ce5c61c9991dd5451dbdcb2c664d1cf9a893829a6986c5180ec7fbbcc8f
echo $exKey->getKey()             . "\n";

//47b766b9
echo $exKey->getChecksum()        . "\n";
```