*/\r
export class Bip39Mnemonic {\r
static #isInternal: boolean = false\r
- #bip44Seed?: Uint8Array<ArrayBuffer>\r
+ #bip39Seed?: Uint8Array<ArrayBuffer>\r
#blake2bSeed?: Uint8Array<ArrayBuffer>\r
- #phrase: string = ''\r
- get phrase (): string { return this.#phrase.normalize('NFKD') }\r
+ #phrase?: string[]\r
+ get phrase (): string | undefined { return this.#phrase?.join(' ').normalize('NFKD') }\r
\r
private constructor () {\r
if (!Bip39Mnemonic.#isInternal) {\r
const self = new this()\r
const isValid = await this.validate(phrase)\r
if (isValid) {\r
- self.#phrase = phrase.normalize('NFKD')\r
+ self.#phrase = phrase.normalize('NFKD').split(' ')\r
return self\r
} else {\r
throw new Error('Invalid mnemonic phrase.')\r
* collection.\r
*/\r
destroy () {\r
- bytes.erase(this.#bip44Seed)\r
+ bytes.erase(this.#bip39Seed)\r
bytes.erase(this.#blake2bSeed)\r
- this.#bip44Seed = undefined\r
+ this.#bip39Seed = undefined\r
this.#blake2bSeed = undefined\r
- this.#phrase = ''\r
+ this.#phrase = undefined\r
}\r
\r
/**\r
*/\r
async toBip39Seed (passphrase: string, format: 'hex'): Promise<string>\r
async toBip39Seed (passphrase: string, format?: 'hex'): Promise<string | Uint8Array> {\r
- if (this.#bip44Seed == null) {\r
+ if (this.phrase == null) {\r
+ throw new Error('BIP-39 mnemonic phrase not found')\r
+ }\r
+ if (this.#bip39Seed == null) {\r
if (passphrase == null || typeof passphrase !== 'string') {\r
passphrase = ''\r
}\r
}\r
const seedKey = await globalThis.crypto.subtle.deriveKey(algorithm, phraseKey, derivedKeyType, true, ['sign'])\r
const seedBuffer = await globalThis.crypto.subtle.exportKey('raw', seedKey)\r
- this.#bip44Seed = new Uint8Array(seedBuffer)\r
+ this.#bip39Seed = new Uint8Array(seedBuffer)\r
}\r
return format === 'hex'\r
- ? bytes.toHex(this.#bip44Seed)\r
- : this.#bip44Seed\r
+ ? bytes.toHex(this.#bip39Seed)\r
+ : this.#bip39Seed\r
}\r
\r
+ /**\r
+ * Converts the mnemonic phrase to a BLAKE2b seed.\r
+ *\r
+ * @returns {Promise<Uint8Array<ArrayBuffer>>} Promise for seed as bytes\r
+ */\r
async toBlake2bSeed (): Promise<Uint8Array<ArrayBuffer>>\r
/**\r
* Converts the mnemonic phrase to a BLAKE2b seed.\r
*\r
- * @returns {string} Hexadecimal seed\r
+ * @returns {Promise<string>} Promise for seed as hexadecimal string\r
*/\r
async toBlake2bSeed (format: 'hex'): Promise<string>\r
async toBlake2bSeed (format?: 'hex'): Promise<Key> {\r
+ if (this.#phrase?.length !== 24) {\r
+ throw new Error('BIP-39 mnemonic phrase must be 24 words to convert to BLAKE2b seed')\r
+ }\r
if (this.#blake2bSeed == null) {\r
- const wordArray = this.phrase.split(' ')\r
- const bits = wordArray.map((w: string) => {\r
- const wordIndex = Bip39Words.indexOf(w)\r
+ let bits = 0n\r
+ const words = this.#phrase\r
+ for (const word of this.#phrase) {\r
+ const wordIndex = Bip39Words.indexOf(word)\r
if (wordIndex === -1) {\r
- return false\r
+ throw new RangeError('Word not found in BIP-39 list')\r
}\r
- return dec.toBin(wordIndex, 11)\r
- }).join('')\r
-\r
- const dividerIndex = Math.floor(bits.length / 33) * 32\r
- const entropyBits = bits.slice(0, dividerIndex)\r
- const entropyBytes = entropyBits.match(/(.{1,8})/g)?.map((bin: string) => parseInt(bin, 2))\r
- if (entropyBytes == null) {\r
- throw new Error('Invalid mnemonic phrase')\r
+ bits = (bits << 11n) | BigInt(Bip39Words.indexOf(word))\r
+ }\r
+ bits >>= 8n\r
+ this.#blake2bSeed = new Uint8Array(32)\r
+ for (let i = 31; i >= 0; i--) {\r
+ this.#blake2bSeed[i] = Number(bits & 255n)\r
+ bits >> 8n\r
}\r
- this.#blake2bSeed = new Uint8Array(entropyBytes)\r
}\r
return format === 'hex'\r
? bytes.toHex(this.#blake2bSeed)\r