はじめに
こんにちは。bitbankでビットコインなどの仮想通貨やバックエンドシステムを担当しています、ゆあ といいます。
この記事は僕が調べた仕様とBIP39・Electrumに当時関わっていた歴史的背景を知っているブロックチェーン大学校校長のジョナサンに聞いてなんでそうなってるの?という疑問と好奇心を解決するために書いた記事となっています。
自分と同じようになんでそうなってんの?って思った人にもそうでない人にもわかるように書いていきます。
BIP39の考案された理由
秘密鍵には完全な乱数と、ある値を元に楕円曲線の計算式で派生させて作る再現性のある派生鍵があります。
完全な乱数を扱う場合には生成した鍵ごとにバラバラにすべて保管する必要があります。この場合、鍵の管理が大変です。(データベースに保管する必要があるでしょう。)派生鍵を使った場合は元となる鍵を保管しておけば他の鍵は生成に使った式の値(例えばIDをインクリメントした最大値)だけ保管しておけばいつでも復元可能です。
この元となる値はシード鍵といいます。
通常、シード鍵と言われるものは単なる数値であり、バイナリ値として表現されるのですが、シード鍵は無くしたらそこから生成された派生鍵をすべて失うことになります。
そこでバックアップをとることになるのですが大量の鍵が集約されて単一の鍵で管理できるのでUSBメモリや特定のコンピュータ上に保管しておくと丸ごと盗まれたときの盗難リスクが大きくなります。(マルウェアなど場所と関係がない盗難が起こり得る。)
そこでコンピューターに保管しなくてもよいように人間がわかる単語を紙と鉛筆でメモを取ることでバックアップができるような仕組みが考案されました。それがBIP39です。
ワードリストからMnemonicを生成する仕組み
ワードリストと呼ばれる2048個の言葉からなるリストが各言語(英語、日本語など)ごとに用意されています。
この言葉をランダムに選んだリストをMnemonic(ニーモニック)といいます。Mnemonicは日本語では記憶を助けるという意味があります。
Mnimonicによく使われる言葉の数は12ワード、24ワードです。
12ワードは128bitのエントロピーから、24ワードは256bitのエントロピーから生成されます。この生成時に利用されるエントロピーは暗号学的にランダムな値が用いられます。
このエントロピーの作成を適当にしてしまうと推測され盗まれる原因になるので細心の注意が必要です。
このエントロピーからチェックサムを生成し足し合わせバイト列に変換し11bit(最大2048)ごとに分割する。その分割した値をindex値としてワードリストの索引とします。
このロジックで生成した言葉のリスト(Mnemonic)をバックアップとして紙に書き出すことで秘密鍵の束を保管する手がかりとなります。(Mnemonicはシード鍵ではない、シード鍵を生成するためのエントロピーを表現している。)
Mnemonicからシード鍵を生成する仕組み
PBKDF2-SHA512というパスワードを安全にハッシュ化するために作られたハッシュ関数を通して生成します。この関数は指定回数分ストレッチと呼ばれる複数回ハッシュ関数を通す処理が行われます。BIP39では2048回ストレッチを行います。
Mnemonicをインプットとします。
このとき追加のPassphraseと呼ばれる追加の文字列を入力しそれをSALTとして入力することができます。Passphraseを追加で入力した場合にはMnemonicだけではシード鍵は復元できなくなります。なお、Mnemonicにはチェックサムがついているので間違いの検出が可能です。(ただし全体のチェックサムなのでどれを間違えたかわからないためスペルチェックで検出する以外の方法はとれない。)
BIP39で使われるハッシュ関数はHMAC-SHA512なので最終的に512bit=64バイトのシード鍵が生成されます。
このシード鍵からマスター鍵を生成し、そこから派生鍵を生成します。BIP39ではシード鍵の生成までが対象範囲となります。
BIP39の派生バージョン
実はBIP39には複数の派生バージョンが存在します。
BIP39はsatoshilabsが中心となりハードウェアウォレットtrezorを開発するために仕様化を進めましたが、元々はElectrumというウォレットに実装されたシード鍵生成+派生鍵生成(BIP32の元ネタでもある)アイデアが元になっています。
Electrum作者はBIP39には問題点があるとして、BIP39に賛同はしなかったのでElectrumで生成されるMnemonicには互換性がありません。
BIP39はシード鍵の算出時にワードリストを必要としない設計となっていますが、実際にMnemonicからシード鍵を復元するときはバックアップにとった紙から手入力の場合がほとんどです。
その場合手書きのことが多いので打ち間違いが多発します。打ち間違いが発生したかどうかチェックするのにチェックサムを検証する必要がありますが、それを計算するのにワードリストが必要になります。(このとき問題になるのはそのリストが各言語ごと(英語、日本語、その他)に必要ということ。)
Electrum作者はMnemonicからシード鍵生成時の検証にワードリストを必要としない設計が良いと提言しています。
Electrum Seed Version 1系
Version1系はBIP39の元になった初期のアイデアでハッシュから生成するシードではなくシード鍵とMnemonicをエンコード、デコード、相互変換できる形になっています。
そもそもワードリストがBIP39と違うのでBIP39のMnemonicとして扱うとワードが存在しないエラーを出すか、チェックを通ってしまった場合、間違ったシードを算出します。ちなみにこのバージョンのワードの数は1626個です。(BIP39は2048個である。)
Electrum以外のプロジェクトでも(CounterpartyやDarkwalletなど)利用されています。
以下はそのソースコードです。
wordlist(https://github.com/spesmilo/electrum/blob/1.9.8/lib/mnemonic.py)とエンコード・デコードロジック
Electrum Seed Version 2系
Electrum作者の言うBIP39の問題点を改善した仕様になっています。BIP39のワードリストと同じものを使っていますがロジックが違うのでElectrumのMnemonicからBIP39のシード鍵は生成できません。
ElectrumのMnemonicにバージョン情報が埋め込まれています。
シード生成時にこれらの値がhmac_sha_512(b"Seed version”, mnemonic)をしたときにバージョン情報が入っているMnemonicを見つけるまでマイニングします。(最後のワードがチェックサムのような役目をしています。)
HMACのハッシュ値のバージョンヘッダとして以下のような値が定義されています。
'01' # Standard wallet
'101' # Two-factor authentication
'100' # Segwit wallet
Mnemonicが正しいかの検証はこのハッシュのバージョンチェックで行っています。間違ったMnemonicを入力すると上記のバージョン情報は入っていないため間違いを検証できます。
このやり方であれば単語リストなしに間違っていることが検証できます。ですがこの方式であってもどの単語が間違っているかを知るためには単語リストが必要になります。
ちなみにこのチェックサムですが2系と3系でチェックサムの取り方が後方互換性をもったまま少し変更が入っています。Electrum 2.xでは13ワードが有り得たのですが3.xでは12ワードに統一しようという変更がありました。
https://github.com/spesmilo/electrum/blob/2.9.x/lib/mnemonic.py
https://github.com/spesmilo/electrum/blob/3.2.2/lib/mnemonic.py
ここまでがエントロピーの生成方法と確認方法で、あとはBIP39のようにPBKDF2関数を通すのですがPassphraseにelectrumという文字列を追加しているのでelectrumのシード鍵はBIP39とは同じになりません。
おわりに
BIP39は商用化を急いだsatoshilabsと、より良いものを作ろうと考えたElectrum開発チームによって
派生仕様が生まれた形になり、ユーザーからすると見分けがつかない別物が出来上がり混乱することになったと思います。
こういった背景を知っておくとなんでこんな仕組みになっているんだと頭を抱えなくなりコードに味わいを感じるようになり、より楽しいOSSライフが楽しめるんじゃないかなと思います。
以上になります。
PS このドキュメントを書き終えてから日本語でElectrumのドキュメントを読めるサイトが有りました。(当然Electrumのシードシステムについてもより詳しく書いてあります。)
https://docs.electrum-mona.org/ja/japanese-monacoin/seedphrase.html
すばらしいですね。