Electrum Seedバージョンシステム

このドキュメントではElectrum(バージョン2.0以上)で用いられているSeedバージョンシステムのことを説明しています。

説明

Electrumは自然言語で出来たSeedフレーズから秘密鍵とアドレスを導き出しています。バージョン2.0以降、Electrum Seedフレーズはバージョン番号を含み、その目的は秘密鍵とアドレスを導き出すためにはどのデリベーションをたどるべきかを示すことです。

固定単語リストへの依存を排除するために、マスター秘密鍵およびバージョン番号は両方とも、UTF8正規化されたSeedフレーズのハッシュによって得られます。バージョン番号は次の最初のビットから得られます。

hmac_sha_512("Seed version", seed_phrase)

バージョン番号はSeedの整合性を確かめるためにも使用されます。間違いのないように、Seedフレーズは登録済のバージョン番号を提示しなければなりません。

動機

旧バージョンのElectrum(2.0より前)はSeedフレーズとエントロピーの双方向エンコーディングを用いていました。このタイプのエンコーディングは固定の単語リストを必要とします。これが意味するのは将来のバージョンのElectrumが過去のSeedフレーズの解読を可能にするために全く同じ単語リストを備えなければならないということです。

BIP39はElectrumの二年後に発表されました。BIP39のSeedはチェックサムを含み、タイプミスを検出するのを補助しています。しかしながらBIP39にはかつてのElectrum Seedフレーズと同じ欠点があります:

  • 固定の単語リストが依然として要求されます。我々の勧告を受けて、BIP39の著者は鍵とアドレスを生成するのに単語リストには依存しない方法をとりました。しかしながら、BIP39はチェックサムを計算するためにその単語リストを必要とします。チェックサムには明らかに矛盾があり、我々の勧告の目的を台無しにさせています。この問題はBIP39が言語ごとに一つの単語リストを作成しようと提案したことでさらに悪化しました。これがBIP39 Seedフレーズの移植性を脅かしています。
  • BIP39 Seedフレーズはバージョン番号を含みません。つまりソフトウェアは常に鍵とアドレスを生成する方法を知っていなければなりません。BIP43ではウォレットソフトウェアはBIP32フレームワーク内に存在する様々な生成スキームを試行するようにすることを提案しています。これは著しく非効率的であり、今後のウォレットがそれまでに利用された生成メソッドのすべてをサポートするという仮定に基づいています。もし将来、ウォレットの開発者が特定の生成メソッドは廃止予定だからと、その実行をやめることに決めた場合、そのソフトウェアは、サポートしていない該当Seedフレーズを検出することができず、代わりに空のウォレットを返すでしょう。これはユーザーの資産を脅かします。

これらの理由から、ElectrumはBIP39 Seedの生成は行ないません。バージョン2.0から、Electrumは以下のSeedバージョンシステムを使用してこれらの問題に対処しています。

Electrum2.0はUTF8正規化された固定の単語リストに依存しないSeedフレーズのハッシュから鍵とアドレスを生成します。つまり単語リストはSeedは移植性を保ったままウォレット間で異なることができます。そして今日生成されたSeedをデコードするこために、将来のウォレットの実装では今日の単語リストを必要としません。これによって前方互換性のコストを減らしています。

バージョン番号

バージョン番号はSeedフレーズから生成されたハッシュのプレフィックスです。長さは4bitの倍数で、以下のように計算されます:

def version_number(seed_phrase):
  # normalize seed
  normalized = prepare_seed(seed_phrase)
  # compute hash
  h = hmac_sha_512("Seed version", normalized)
  # use hex encoding, because prefix length is a multiple of 4 bits
  s = h.encode('hex')
  # the length of the prefix is written on the fist 4 bits
  # for example, the prefix '101' is of length 4*3 bits = 4*(1+2)
  length = int(s[0]) + 2
  # read the prefix
  prefix = s[0:length]
  # return version number
  return hex(int(prefix, 16))

正規化関数(prepare_seed)は単語の間のスペース1つを除いてすべて削除します。また発音区別記号、アジアのCJK文字(CJK characters)も削除されます。

登録済み番号のリスト

以下のバージョン番号がElectrum Seedに使われています。

Number Type Description
0x01 Standard P2PKH and Multisig P2SH wallets
0x100 Segwit Segwit: P2WPKH and P2WSH wallets
0x101 2FA Two-factor authenticated wallets

加えて、マスター公開鍵/秘密鍵のバージョンバイトどのタイプのアウトプットスクリプトが使用されるべきかを示しています。 以下のプレフィックスがマスター公開鍵に使用されています。 here.

Seed生成

Seed生成においてSeedフレーズがハッシュ化されると、結果のハッシュ値は正しいバージョン番号プレフィックスで始まらなければいけません。これは望むバージョン番号が作成されるまでnonceを挙げてSeedフレーズをハッシュ化しなおすことで達成されます。この要求はSeedのセキュリティを下げることはありません。(秘密鍵を生成するのに要求されるキーストレッチングのコスト次第)

セキュリティ推測

Electrumは現在はBIP39(2048単語)と同じ単語リストを使用しています。代表的なSeedは12単語を有しており、Seedを選ぶ際には132bitのエントロピーとなるようにします。

BIP39に従って、キーストレッチングの2048ループがマスター秘密鍵の生成には追加されます。ハッシュに関して、これはSeedのセキュリティにさらなる11bitを追加することと同等です(2048=2^11)。

攻撃者の観点からすると、Seedバージョンハッシュのプレフィックスを課すことで追加された制約はSeedのエントロピーを減少させることはありません。なぜならSeedフレーズから得られる情報はないからです。攻撃者は2^nの候補Seedフレーズを列挙する必要があり、nはSeedを生成するのに使われたエントロピーのbit数です。

しかしながら、攻撃者によって作成されたテストは候補Seedが有効なSeedでない場合即座に返ってきます。なぜなら攻撃者は鍵を生成する必要がないからです。つまり付与されたプレフィックスはキーストレッチングの長さを削減しているということです。

nはSeedのエントロピーbitの数を示しており、mはキーストレッチングによって追加された難易度のbit数を示しています。m=log2(stretching_iterations)。kはビットの中のプレフィックスの長さを示しています。

攻撃の各反復における、有効なSeedを得る確率 p = 2^-k

候補Seedをテストするのに求められるハッシュの数 p * (1+2^m) + (1-p)*1 = 1 + 2^(m-k)

ゆえに、攻撃コストは 2^n * (1 + 2^(m-k))

これは2^(n + m - k)、または2^nと近似することができます。

Electrumに現在使用されている標準値は 2^(132 + 11 - 8) = 2^135 です。つまり標準のElectrum Seedはハッシュに関して135bitエントロピーに相当するということです。