Develop with pleasure!

福岡でCloudとかBlockchainとか。

手計算でBIP-32シードを生成/検証可能な新しいシードエンコード方式Codex32

Bitcoinの主要ウォレットはBIP-32 HDウォレットをサポートしており、単一のマスターシードから、支払いや受け取りに使用するすべての鍵を導出するようになっている。オンチェーンの資金は、基本的にこのマスターシードだけ保持していれば、そこからリカバリーができる。

既存のエンコード方法

このマスターシードのエンコード方法として現在最も主流なのが、BIP-39ニーモニックワードとしてエンコードする方法。12個や24個の単語として覚えておく方法。

もう1つはSLIP-39。こちらは、BIP-39が単一のシードを単語に変換するのに対し

  • シャミアの秘密分散法を利用してマスターシードを複数のシェアに分割し、
  • 各シェアをニーモニックワードにエンコード
  • 予め設定した閾値数分のシェアを集めたら、マスターシードを復元できる

TrezorのShamir Bakupなどで採用されている。

Codex32

↑のような方式を使わずに、マスターシードそのまま保存するということも可能だけど、データの欠落やコピーミスなんかで、誤ったデータになっていないか確認できる機能は欲しい。

最近このマスターシードについて、Codex32という新しいエンコード方式のBIPドラフトが提案された↓

https://github.com/apoelstra/bips/blob/2023-02--volvelles/bip-0000.mediawiki

Codex32の機能については、↑のBIPよりこのウェブサイトの方が分かりやすいかも。

Codex32では、マスターシードをBech32のアルファベット(BIP-173参照)を使って

2種類のエンコード方法をサポートしている。

既存のエンコード方法との違いは、

  • 単語リストを使わずに、Bech32のアルファベットを使用するので、エンコード結果はコンパクトになる。
  • ソフトウェアを使わずに手計算で、シェアやシードの生成や検証ができる。

特に後者が特徴的で、シェアの正しさの検証のためにデジタル機器にシェアを入力する機会をなくすことができる。

一方で、手計算をサポートするため、BIP-39でサポートされるようなシードに対するパスフレーズの設定はできない。

データフォーマット

Codex32のフォーマットは、BIP-173のBech32フォーマットと似ており、以下のデータで構成される。

  • 人が識別可能な部分:ms
  • セパレーター: 1
  • データ部:
    • 閾値パラメーター(k):2〜9までの1桁の閾値。もしくは0。このため、Codex32でサポートされる閾値の最大値は9。
    • 識別子(ID):4つのbach32文字で構成される、このシェアを識別するための任意の値。
    • シェアインデックス:シェアのインデックスを表す値で、任意のbech32文字。
    • ペイロード:最大74文字のbech32文字列で、シェアやシードの値
    • チェックサム:13文字のbech32文字列のチェックサム

シードもシェアも同じこのフォーマットを使用する。シェアインデックスがsの場合、シードを直接エンコードしたもので、それ以外の場合はシェアをエンコードしたデータになる。

新しいマスターシードの生成

新しくマスターシードを生成する場合、まず先に閾値分のシェアを生成して、そのデータからマスターシードを生成する。具体的な手順は、

  1. マスターシードのビットサイズを決める。128 bit〜512 bitの範囲で、8の倍数であること。
  2. 閾値kを決める(2〜9)
  3. 4文字の識別子を決める。
  4. k回分、ランダムにシェアを生成する↓
    1. bech32のアルファベットの先頭から順次文字をピックアップしてシェアインデックスとする。
    2. ここまでで、プレフィックスms1閾値k、4文字の識別子、シェアインデックスの9文字が揃う。
    3. シードのビットサイズ / 5を切り上げた個数分、ランダムなbech32文字を選択する(128 bitの場合26文字)。
    4. 13文字のチェックサムを計算し、追加する。

結果、k個のシェアをエンコードしたリストができる。シェアのランダムなbech32文字の選択方法については、特に明記されてないけど、サイコロとワークシート使った導出方法とかは紹介されてる。

マスターシードは、このk個のシェアからシャミアの秘密分散法によって計算される。↑のシェアインデックスをxの値として、ランダムなシェアの値と合わせてラグランジュ補間でマスターシークレットを導出するみたい。

マスターシードは、x = 16として多項式を評価した値で、bech32文字の16番目の要素がsなので、マスターシードのシェアインデックスはsになってるっぽい。

補間したデータのシークレット値は、8bit単位に変換し、過剰分を切り詰めるとマスターシードになる。

新しいシェアの生成

閾値分のシェアが準備できたら、他のシェアも生成することができる。シェアの生成は↑のマスターシードの計算方法と同様で、異なるのはラグランジュ補間で多項式を評価する際の値だけ。マスターシードの場合はsだったけど、代わりに新しく生成するシェアのインデックスをx値として評価することで、シェアが導出できる。

既存のマスタシードを使用する場合

既にあるマスタシードを利用する場合は、まず最初にそのシードデータを以下の手順でCodex32フォーマットに変換する。

  1. 閾値kを決める。
  2. 4文字の識別子を決める。
  3. シェアインデックスをsとする。
  4. マスターシードを8 bit 単位から5 bit単位に変換し、Bech32文字に変換する。
  5. チェックサムを計算する。

これでマスターシードのCodex32文字列ができるので、続いて、k-1個のシェアを生成する。この時、シェアの生成方法は↑の新しいマスターシードの生成方法と同じでランダムにシェアを生成する。

k-1個ランダムに選択したシェアとx = s = 16となるシードデータがあれば、後はそれを使っていくつでもシェアを生成できる。k個のシェアが作れたらマスターシードのCodex32データはマスターシードごと削除してもいい。

チェックサムの計算

チェックサムは、

を元に計算される13文字のbech32文字で、最大4文字の誤り、最大8文字の消失を訂正できる機能を持つ。BIP-173と同じ文字セットを使用したBCH符号ベースのチェックサムだが、文字数やパラメーター、計算対象(hrpは含まない)、bit変換時の不要データの扱い(切り詰め)などは異なる。

ロングCodex32

↑の13文字のチェックサムでサポートされるデータ部は最大80文字(ペイロードは74文字、46バイトまで)で、それを超えるサイズのシードデータを扱う場合には、15文字のロングチェックサムを持つ、ロングCodex32フォーマットを用いることになる。

BIP-32のシードは32バイトで通常のCodex32フォーマットで十分なものの、BIP-32ではこのシードを最大64バイトにすることもでき、この場合は通常のCodex32ではエンコードできない。このようなシードをエンコードする場合にロングCodex32が使用され、この場合

シェアの生成方法やマスターシードのリカバリー方法は通常のCodex32と同様。

Rubyで実装

Pythonコードスニペットを参考にRubyで実装してみた↓

github.com