From b62ca1099ff996e333015142cb712687737d4637 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 17 Aug 2026 08:12:34 -0700 Subject: The seed takes dice: an optional entropy field folds open on the generate step, hashed with the device's own randomness so it can only strengthen the phrase — and the forget option now says all data, which is what it takes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz --- src/wallet.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/wallet.js') diff --git a/src/wallet.js b/src/wallet.js index 662ef0f..2ede69d 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -9,7 +9,7 @@ // transactions, only the (txid, vout, value, address) of each UTXO. import { HDKey } from '@scure/bip32'; -import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39'; +import { generateMnemonic, mnemonicToSeedSync, validateMnemonic, entropyToMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; import { hex, base64urlnopad, base32nopad, base58check } from '@scure/base'; import { sha256 } from '@noble/hashes/sha256'; @@ -64,8 +64,16 @@ export const NETS = { regtest: { net: { ...btc.TEST_NETWORK, bech32: 'bcrt' }, coin: 1 }, }; -export function newMnemonic(strengthBits = 128) { - return generateMnemonic(wordlist, strengthBits); +export function newMnemonic(strengthBits = 128, extraEntropy = '') { + if (!extraEntropy) return generateMnemonic(wordlist, strengthBits); + // User entropy is MIXED with the CSPRNG, never used alone: hashing the two + // together can only add randomness — weak dice can't weaken the seed, and + // a distrusted device RNG can't fully determine it either. + const txt = new TextEncoder().encode(extraEntropy); + const rnd = crypto.getRandomValues(new Uint8Array(strengthBits / 8)); + const mix = new Uint8Array(txt.length + rnd.length); + mix.set(txt); mix.set(rnd, txt.length); + return entropyToMnemonic(sha256(mix).slice(0, strengthBits / 8), wordlist); } // The base localStorage cache key for a wallet identity (xpub, xprv, or -- cgit v1.2.3