import { WORDLIST } from "./bip39_wordlist.ts"; export async function entropyToMnemonic(entropy: Uint8Array): Promise { if (entropy.length < 16 || entropy.length > 32 || entropy.length % 4 !== 0) { throw new Error("Invalid entropy length"); } const entropyBits = Array.from(entropy) .map((b) => b.toString(2).padStart(8, "0")) .join(""); const entropyBuffer = new Uint8Array(entropy.length); entropyBuffer.set(entropy); const hashBuffer = await crypto.subtle.digest("SHA-256", entropyBuffer); const hashBits = Array.from(new Uint8Array(hashBuffer)) .map((b) => b.toString(2).padStart(8, "0")) .join(""); const checksumLength = entropy.length / 4; const checksum = hashBits.slice(0, checksumLength); const bits = entropyBits + checksum; const chunks = bits.match(/(.{1,11})/g) || []; const mnemonic = chunks.map((binaryStr) => { const index = parseInt(binaryStr, 2); return WORDLIST[index]; }); return mnemonic.join(" "); } export async function mnemonicToEntropy(mnemonic: string): Promise { const words = mnemonic.normalize("NFKD").trim().split(/\s+/); if (words.length % 3 !== 0) { throw new Error("Invalid mnemonic length"); } const bits = words .map((word) => { const index = WORDLIST.indexOf(word); if (index === -1) { throw new Error(`Invalid word in mnemonic: ${word}`); } return index.toString(2).padStart(11, "0"); }) .join(""); const dividerIndex = Math.floor(bits.length / 33) * 32; const entropyBits = bits.slice(0, dividerIndex); const checksumBits = bits.slice(dividerIndex); const entropy = new Uint8Array(entropyBits.length / 8); for (let i = 0; i < entropy.length; i++) { entropy[i] = parseInt(entropyBits.slice(i * 8, (i + 1) * 8), 2); } const hashBuffer = await crypto.subtle.digest("SHA-256", entropy); const hashBits = Array.from(new Uint8Array(hashBuffer)) .map((b) => b.toString(2).padStart(8, "0")) .join(""); const expectedChecksum = hashBits.slice(0, checksumBits.length); if (expectedChecksum !== checksumBits) { // Explicitly zeroize on failure entropy.fill(0); throw new Error("Invalid mnemonic checksum"); } return entropy; }