3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

背景

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

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

拡張鍵について

例えばbcwallet等でウォレットを新規作成した場合、
BIP32に基づく拡張秘密鍵/拡張公開鍵が得られる。

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

tpubD6NzVbkrYhZ4WsMXiXFSfYkBoJxysBZv1PD79BPS72LTEiCfnf3Qenh5Z173CwfEdw5keu4GAAWum6J4mu1suKL3CWCBCnTB7NFMf3DjNfS

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

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

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

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

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

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

# 公開鍵
217ZvQxj8hRf2VBBEgSAApC3555vj8jmvDMG61aCKepgN

# 拡張公開鍵
tpubD6NzVbkrYhZ4WsMXiXFSfYkBoJxysBZv1PD79BPS72LTEiCfnf3Qenh5Z173CwfEdw5keu4GAAWum6J4mu1suKL3CWCBCnTB7NFMf3DjNfS

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

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

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

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

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

0x043587cf00000000000000000035390470be22cc0d18aa6f6d53fd7900ff53446a2cb6075de1c65a5fc8432ed3035f748ce5c61c9991dd5451dbdcb2c664d1cf9a893829a6986c5180ec7fbbcc8f47b766b9

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

ここまでの情報だと14bytes分謎のbytesが存在する。
構成要素を調べたところ、こちらのサイトから以下の表を得られた。

Screenshot from 2018-05-04 12-43-31.png

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

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

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

82bytes中78bytesまでの正体はわかったが、残りの4bytesが何かわからない。。。
英語のサイトをあさりはじめ、こちらのサイトに答えが載っていた。

該当の箇所のキャプチャを以下に抜粋する。
Screenshot from 2018-05-04 12-49-09.png

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

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

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

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

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

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

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

補足

bitwasp/bitcoin-phpというパッケージを利用している。require('./vendor/autoload.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デコードした文字列において、
      //下位4バイトが上位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
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";
3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?