跳至主要内容

種子產生與隨機性

錢包的強度取決於建立它的隨機性。BitTool Signer 的所有隨機性都取自作業系統的密碼學安全產生器,從不使用 JavaScript 內建的偽隨機函式。

Android 版

本頁以 iPhone 版為準說明。BitTool Signer Android 版使用 Android 中的對應機制取得隨機數,請參閱 Android 版。

熵的來源​

新的助記詞起始於 Apple SecRandomCopyBytes(kSecRandomDefault, …) 所產生的隨機位元組,這是 iOS 以硬體熵作為種子的系統 CSPRNG。此呼叫在原生 Swift 程式碼中進行:

BitToolSigner/Core/VaultCrypto.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()
}
}

如果產生器回報任何錯誤,建立錢包的流程就會以錯誤中止。不會退回使用較弱的來源。

從熵到單字​

App 提供 24 個單字(預設,256 位元熵)或 12 個單字(128 位元)。這些位元組會以標準 BIP39 演算法轉換為單字:

  1. 取得熵:24 個單字需要 32 個位元組,12 個單字需要 16 個位元組。
  2. 將 SHA-256(entropy) 的前 entropy bits ÷ 32 個位元附加在後作為校驗碼(24 個單字為 8 位元,12 個單字為 4 位元)。
  3. 將結果分割為每組 11 位元。每一組都是 2,048 字英文單字表中的一個索引。

轉換在內建引擎(@scure/bip39)中執行,引擎會從原生程式碼以 hex 形式接收熵:

// 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 };
}

新錢包一律使用所有錢包都支援的 英文 單字表。其他九種 BIP39 語言的助記詞可以匯入。

例如,32 個零位元組(切勿使用)會產生:

熵 0x00…00(32 位元組)→ 24 個單字
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 種子是以 CommonCrypto 在原生程式碼中計算,而非在 JavaScript 中計算,因為引擎在 iOS 上是在沒有 JIT 的情況下執行,在那裡執行 PBKDF2 會花上將近一秒:

seed = PBKDF2-HMAC-SHA512(
password = NFKD(phrase words joined by single spaces),
salt = NFKD("mnemonic" + passphrase),
iterations = 2048,
length = 64 bytes)

引擎只會收到用於衍生與簽名的 64 位元組種子(以 hex 表示),從不接觸助記詞,而原生緩衝區在使用後會以 memset_s 覆寫。BIP39 passphrase 頁面列出了測試助記詞在使用與不使用 passphrase 時的種子。

其他所有隨機值​

值來源
助記詞的熵SecRandomCopyBytes,16 或 32 位元組
保險庫 salt(PBKDF2)SecRandomCopyBytes,32 位元組,每次加密都重新產生
裝置金鑰SecRandomCopyBytes,32 位元組,每個錢包一把
AES-GCM nonceCryptoKit,96 位元,每次加密都重新產生
引擎內的 crypto.getRandomValues()在引擎載入前橋接到 SecRandomCopyBytes
引擎內的 Math.random()替換為以 SecRandomCopyBytes 為基礎的函式(部分函式庫會呼叫它來產生內部 ID)
Taproot(BIP340)輔助隨機數每次簽名使用 32 個全新的隨機位元組
ECDSA nonce(Bitcoin、EVM、TRON)確定性,RFC 6979
Ed25519(Solana)設計上即為確定性(RFC 8032)

引擎的 crypto 物件會在任何函式庫程式碼執行之前安裝並凍結,因此相依套件無法替換它:

Installed by SigningEngine.swift before the bundle is evaluated
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 版​

數值來源
助記詞熵、保險庫鹽值、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,由 App 在引擎之外完成,引擎同樣只接收種子。

安全審查的發現​

1.0.0 版的兩次安全審查都追蹤了這條路徑並加以確認:熵只來自 SecRandomCopyBytes,錯誤狀態會中止錢包建立,沒有任何基於時間或其他較弱的備援方式,而且引擎的 crypto 物件與 Math.random 會在任何函式庫程式碼之前安裝,且無法被替換。GPT-6 Astra 報告補充了一項中肯的但書:在 iOS 與裝置值得信任的前提下,這是一個密碼學安全的產生器,而不是經過認證的物理隨機數串流;而從其他地方匯入的助記詞,其隨機性只會和產生它的錢包一樣好。

測試方式​

原生測試套件會檢查熵函式的邊界、一個以 Python 獨立產生的 PBKDF2 向量,並透過真實的 JavaScriptCore 橋接檢查公開的 BIP39 向量。引擎測試則會檢查從助記詞衍生種子與從原生種子衍生,是否會得到相同的金鑰。