// Thin wrappers over @scure/bip39 (a small, audited BIP39 implementation that // ships the English wordlist and the mnemonic<->entropy conversions). We do not // roll our own checksum logic — getting the BIP39 checksum wrong silently is a // classic footgun, so the conversion stays in the library. import { generateMnemonic, validateMnemonic, mnemonicToEntropy, } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english.js"; // MNEMONIC_STRENGTH_BITS = 128 bits of entropy => exactly 12 words. export const MNEMONIC_STRENGTH_BITS = 128; export const MNEMONIC_WORD_COUNT = 12; // newMnemonic returns a fresh 12-word mnemonic from a CSPRNG (crypto.getRandomValues // inside @scure). The caller must show it to the user once and never persist it. export function newMnemonic(): string { return generateMnemonic(wordlist, MNEMONIC_STRENGTH_BITS); } // normalizeMnemonic lowercases, trims and collapses whitespace so a phrase the // user typed (extra spaces, trailing newline, mixed case) validates the same way // it would have been generated. export function normalizeMnemonic(input: string): string { return input.trim().toLowerCase().split(/\s+/).filter(Boolean).join(" "); } // mnemonicWords splits a phrase into its individual words (normalized). export function mnemonicWords(input: string): string[] { const n = normalizeMnemonic(input); return n ? n.split(" ") : []; } // isValidMnemonic checks word count, that every word is in the wordlist, and the // BIP39 checksum. A phrase that fails this must not be used to derive an identity. export function isValidMnemonic(input: string): boolean { const n = normalizeMnemonic(input); if (mnemonicWords(n).length !== MNEMONIC_WORD_COUNT) return false; try { return validateMnemonic(n, wordlist); } catch { return false; } } // entropyHex returns the underlying entropy (hex) of a valid mnemonic. Used only // for diagnostics / tests, never sent anywhere. export function entropyHex(input: string): string { const bytes = mnemonicToEntropy(normalizeMnemonic(input), wordlist); return Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); }