From: Chris Duncan Date: Tue, 29 Jul 2025 09:34:04 +0000 (-0700) Subject: Refactor BIP-39 mnemonic generation from strings to bitwise operations. X-Git-Tag: v0.10.5~48^2~10 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=23caf4905453a7e83b3bb433a267504df8aaaed5;p=libnemo.git Refactor BIP-39 mnemonic generation from strings to bitwise operations. --- diff --git a/src/lib/bip39-mnemonic.ts b/src/lib/bip39-mnemonic.ts index 46a36d1..785f743 100644 --- a/src/lib/bip39-mnemonic.ts +++ b/src/lib/bip39-mnemonic.ts @@ -3,7 +3,7 @@ import { Bip39Words } from './bip39-wordlist' import { BIP39_ITERATIONS } from './constants' -import { bin, bytes, dec, utf8 } from './convert' +import { bin, bytes, dec, hex, utf8 } from './convert' import { Entropy } from './entropy' import { Key } from '#types' @@ -54,16 +54,26 @@ export class Bip39Mnemonic { * @param {string} entropy - Hexadecimal string * @returns {string} Mnemonic phrase created using the BIP-39 wordlist */ - static async fromEntropy (entropy: string): Promise { - const e = await Entropy.import(entropy) - const checksum = await this.checksum(e) - let concatenation = `${e.bits}${checksum}` + static async fromEntropy (entropy: Key): Promise { + if (typeof entropy === 'string') entropy = hex.toBytes(entropy) + if (![16, 20, 24, 28, 32].includes(entropy.byteLength)) { + throw new RangeError('Invalid entropy byte length for BIP-39') + } + const phraseLength = 0.75 * entropy.byteLength + const checksum = await this.checksum(entropy) + + let e = 0n + for (let i = 0; i < entropy.byteLength; i++) { + e = e << 8n | BigInt(entropy[i]) + } + + let concatenation = (e << BigInt(entropy.byteLength) / 4n) | checksum const words: string[] = [] - while (concatenation.length > 0) { - const wordBits = concatenation.substring(0, 11) - const wordIndex = parseInt(wordBits, 2) - words.push(Bip39Words[wordIndex]) - concatenation = concatenation.substring(11) + for (let i = 0; i < phraseLength; i++) { + const wordBits = concatenation & 2047n + const wordIndex = Number(wordBits) + words.unshift(Bip39Words[wordIndex]) + concatenation >>= 11n } const sentence = words.join(' ') return this.fromPhrase(sentence) @@ -76,12 +86,10 @@ export class Bip39Mnemonic { * @param {Entropy} entropy - Cryptographically strong pseudorandom data of length N bits * @returns {Promise} First N/32 bits of the hash as a hexadecimal string */ - static async checksum (entropy: Entropy): Promise { - const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', entropy.bytes) - const hashBytes = new Uint8Array(hashBuffer) - const hashBits = bytes.toBin(hashBytes) - const checksumLength = entropy.bits.length / 32 - const checksum = hashBits.substring(0, checksumLength) + static async checksum (entropy: Uint8Array): Promise { + const sha256sum = new Uint8Array(await crypto.subtle.digest('SHA-256', entropy))[0] + const checksumBitLength = BigInt(entropy.byteLength) / 4n + const checksum = BigInt(sha256sum) >> (8n - checksumBitLength) return checksum } @@ -131,9 +139,9 @@ export class Bip39Mnemonic { } const entropy = await Entropy.import(bin.toBytes(entropyBits)) - const expectedChecksum = await this.checksum(entropy) + const expectedChecksum = await this.checksum(entropy.bytes) - if (expectedChecksum !== checksumBits) { + if (Number(expectedChecksum) !== parseInt(checksumBits, 2)) { return false }