シードの生成と乱数
ウォレットの強度は、それを生成した乱数の強度で決まります。BitTool Signer は、すべての乱数をオペレーティングシステムの暗号論的に安全な生成器から取得し、JavaScript 組み込みの擬似乱数関数は一切使いません。
このページは iPhone 版を基準に説明しています。Android 版 BitTool Signer の乱数の取得元は、Android 版にまとめています。
エントロピー源
新しいリカバリーフレーズは、Apple の SecRandomCopyBytes(kSecRandomDefault, …) から得たランダムなバイト列から始まります。これは、iOS がハードウェアのエントロピーでシードするシステムの CSPRNG です。呼び出しはネイティブの Swift コードで行われます。
enum SecureRandom {
static func bytes(count: Int) throws -> Data {
guard (1...65_536).contains(count) else { throw VaultError.randomFailure }
var data = Data(count: count)
let status = data.withUnsafeMutableBytes { buffer in
guard let address = buffer.baseAddress else { return errSecAllocate }
return SecRandomCopyBytes(kSecRandomDefault, count, address)
}
guard status == errSecSuccess else { throw VaultError.randomFailure }
return data
}
static func entropyHex(wordCount: Int = 24) throws -> String {
guard [12, 15, 18, 21, 24].contains(wordCount) else { throw VaultError.invalidSecret }
return try bytes(count: wordCount / 3 * 4).map { String(format: "%02x", $0) }.joined()
}
}
生成器が何らかのエラーを報告した場合、ウォレットの作成はエラーで中断されます。より弱い乱数源へのフォールバックはありません。
エントロピーから単語へ
アプリでは 24 単語(デフォルト、256 ビットのエントロピー)または 12 単語(128 ビット)を選べます。バイト列は、標準の BIP39 アルゴリズムで単語に変換されます。
- エントロピーを用意します。24 単語なら 32 バイト、12 単語なら 16 バイトです。
SHA-256(entropy)の先頭entropy bits ÷ 32ビットをチェックサムとして末尾に付加します(24 単語なら 8 ビット、12 単語なら 4 ビット)。- 結果を 11 ビットずつのグループに分割します。各グループが、2,048 語の英語単語リストのインデックスになります。
変換は同梱のエンジン(@scure/bip39)で行われ、エンジンはネイティブコードからエントロピーを 16 進で受け取ります。
// engine/src/keys.js
export function mnemonic(entropy) {
const b = hexToBytes(entropy, 32);
check([16, 20, 24, 28, 32].includes(b.length), 'ENTROPY', 'Entropy must contain 128–256 bits in 32-bit steps.');
return { mnemonic: entropyToMnemonic(b, wordlist), wordCount: b.length * 3 / 4 };
}
新しいウォレットには、すべてのウォレットが対応している 英語 の単語リストを常に使います。他の 9 言語の BIP39 フレーズは インポート できます。
たとえば、32 個のゼロバイト(絶対に使わないでください)からは次のフレーズが得られます。
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art
フレーズからシードへ
64 バイトの BIP39 シードは、JavaScript ではなく、CommonCrypto を使ってネイティブで計算されます。iOS ではエンジンが JIT なしで動作するため、JavaScript で PBKDF2 を実行すると 1 秒近くかかってしまうからです。
seed = PBKDF2-HMAC-SHA512(
password = NFKD(phrase words joined by single spaces),
salt = NFKD("mnemonic" + passphrase),
iterations = 2048,
length = 64 bytes)
エンジンが導出と署名のために受け取るのは 64 バイトのシード(16 進)だけで、フレーズを受け取ることはありません。ネイティブ側のバッファーは使用後に memset_s で上書きされます。BIP39 パスフレーズ のページで、パスフレーズあり・なしそれぞれのテストフレーズのシードを示しています。
その他すべての乱数値
| 値 | 生成元 |
|---|---|
| リカバリーフレーズのエントロピー | SecRandomCopyBytes、16 または 32 バイト |
| Vault のソルト(PBKDF2) | SecRandomCopyBytes、32 バイト、暗号化のたびに新規生成 |
| デバイス鍵 | SecRandomCopyBytes、32 バイト、ウォレットごとに 1 つ |
| AES-GCM の nonce | CryptoKit、96 ビット、暗号化のたびに新規生成 |
エンジン内の crypto.getRandomValues() | エンジンの読み込み前に SecRandomCopyBytes へブリッジ |
エンジン内の Math.random() | SecRandomCopyBytes を使う関数に置き換え(一部のライブラリが内部 ID のために呼び出すため) |
| Taproot(BIP340)の補助乱数 | 署名ごとに新しいランダムな 32 バイト |
| ECDSA の nonce(Bitcoin、EVM、TRON) | 決定的、RFC 6979 |
| Ed25519(Solana) | 設計上決定的(RFC 8032) |
エンジンの crypto オブジェクトは、ライブラリのコードが実行される前にインストールされて凍結されるため、依存ライブラリがこれを置き換えることはできません。
Object.defineProperty(globalThis, 'crypto', {value: Object.freeze({
getRandomValues(array) { /* fills the array from native SecRandomCopyBytes */ }
}), writable: false, configurable: false});
Object.defineProperty(Math, 'random', {value: /* also SecRandomCopyBytes */, writable: false, configurable: false});
署名デバイスの中には、ユーザー自身のエントロピー(サイコロやコイン投げ)を混ぜ込めるものもあります。BitTool Signer はそうしていません。人間が生成するエントロピーは誤りが起きやすく、一方で iOS の生成器は十分に研究されているからです。自分のエントロピーを使いたいユーザーは、別の場所でフレーズを生成して インポート できます。
Android 版
| 値 | 取得元 |
|---|---|
| リカバリーフレーズのエントロピー、Vault のソルト、AES-GCM のナンス | java.security.SecureRandom:BoringSSL の生成器(Conscrypt 経由)で、Linux カーネルがシードを与えます。エラーが起きれば処理は止まり、代替の取得元はありません。 |
| デバイス鍵 | Android Keystore の内部で生成され、エクスポートされることはありません(端末にあれば StrongBox) |
エンジン内の crypto.getRandomValues() と Math.random() | エンジンの読み込み前に設定されて固定され、カーネルがシードを与える Android の C ライブラリの arc4random_buf を使います |
| 署名 | iPhone 版と同じエンジン:ECDSA は RFC 6979、Ed25519 は RFC 8032、Taproot には新しい補助乱数 |
フレーズからシードを求める処理も同じ BIP39 の PBKDF2-HMAC-SHA512 で、アプリがエンジンの外で計算し、エンジンが受け取るのはやはりシードだけです。
セキュリティレビューでの確認結果
バージョン 1.0.0 に対する 2 件の セキュリティレビュー はどちらもこの経路を追跡し、次のことを確認しました。エントロピーは SecRandomCopyBytes からのみ得られること、エラーのステータスが返るとウォレットの作成が中止されること、時刻に基づくものなどの弱いフォールバックがないこと、そしてエンジンの crypto オブジェクトと Math.random は、どのライブラリのコードよりも先にインストールされ、置き換えられないことです。GPT-6 Astra のレポートは、妥当な注意点を付け加えています。これは iOS と端末が信頼できるという前提のもとでの暗号論的に安全な生成器であって、認証を受けた物理乱数のストリームではないこと、そして別の場所からインポートしたフレーズのランダム性は、それを生成したウォレットのランダム性と同程度にとどまることです。
テスト方法
ネイティブのテストスイートでは、エントロピー関数の境界値、Python で独立に生成した PBKDF2 のベクター、そして公開されている BIP39 のベクターを、実際の JavaScriptCore ブリッジを通して検証しています。エンジンのテストでは、フレーズからのシード導出とネイティブで計算したシードから、同じ鍵が得られることを確認しています。