/** * Audited & minimal JS implementation of * [BIP39 mnemonic phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). * @module * @example ```js import * as bip39 from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; const mn = bip39.generateMnemonic(wordlist); console.log(mn); const ent = bip39.mnemonicToEntropy(mn, wordlist) bip39.entropyToMnemonic(ent, wordlist); bip39.validateMnemonic(mn, wordlist); await bip39.mnemonicToSeed(mn, 'password'); bip39.mnemonicToSeedSync(mn, 'password'); // Wordlists import { wordlist as czech } from '@scure/bip39/wordlists/czech'; import { wordlist as english } from '@scure/bip39/wordlists/english'; import { wordlist as french } from '@scure/bip39/wordlists/french'; import { wordlist as italian } from '@scure/bip39/wordlists/italian'; import { wordlist as japanese } from '@scure/bip39/wordlists/japanese'; import { wordlist as korean } from '@scure/bip39/wordlists/korean'; import { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese'; import { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese'; import { wordlist as spanish } from '@scure/bip39/wordlists/spanish'; import { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese'; ``` */ /*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */ import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2'; import { sha256, sha512 } from '@noble/hashes/sha2'; import { abytes, anumber, randomBytes } from '@noble/hashes/utils'; import { utils as baseUtils } from '@scure/base'; // Japanese wordlist const isJapanese = (wordlist) => wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093'; // Normalization replaces equivalent sequences of characters // so that any two texts that are equivalent will be reduced // to the same sequence of code points, called the normal form of the original text. // https://tonsky.me/blog/unicode/#why-is-a---- function nfkd(str) { if (typeof str !== 'string') throw new TypeError('invalid mnemonic type: ' + typeof str); return str.normalize('NFKD'); } function normalize(str) { const norm = nfkd(str); const words = norm.split(' '); if (![12, 15, 18, 21, 24].includes(words.length)) throw new Error('Invalid mnemonic'); return { nfkd: norm, words }; } function aentropy(ent) { abytes(ent, 16, 20, 24, 28, 32); } /** * Generate x random words. Uses Cryptographically-Secure Random Number Generator. * @param wordlist imported wordlist for specific language * @param strength mnemonic strength 128-256 bits * @example * generateMnemonic(wordlist, 128) * // 'legal winner thank year wave sausage worth useful legal winner thank yellow' */ export function generateMnemonic(wordlist, strength = 128) { anumber(strength); if (strength % 32 !== 0 || strength > 256) throw new TypeError('Invalid entropy'); return entropyToMnemonic(randomBytes(strength / 8), wordlist); } const calcChecksum = (entropy) => { // Checksum is ent.length/4 bits long const bitsLeft = 8 - entropy.length / 4; // Zero rightmost "bitsLeft" bits in byte // For example: bitsLeft=4 val=10111101 -> 10110000 return new Uint8Array([(sha256(entropy)[0] >> bitsLeft) << bitsLeft]); }; function getCoder(wordlist) { if (!Array.isArray(wordlist) || wordlist.length !== 2048 || typeof wordlist[0] !== 'string') throw new Error('Wordlist: expected array of 2048 strings'); wordlist.forEach((i) => { if (typeof i !== 'string') throw new Error('wordlist: non-string element: ' + i); }); return baseUtils.chain(baseUtils.checksum(1, calcChecksum), baseUtils.radix2(11, true), baseUtils.alphabet(wordlist)); } /** * Reversible: Converts mnemonic string to raw entropy in form of byte array. * @param mnemonic 12-24 words * @param wordlist imported wordlist for specific language * @example * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; * mnemonicToEntropy(mnem, wordlist) * // Produces * new Uint8Array([ * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f * ]) */ export function mnemonicToEntropy(mnemonic, wordlist) { const { words } = normalize(mnemonic); const entropy = getCoder(wordlist).decode(words); aentropy(entropy); return entropy; } /** * Reversible: Converts raw entropy in form of byte array to mnemonic string. * @param entropy byte array * @param wordlist imported wordlist for specific language * @returns 12-24 words * @example * const ent = new Uint8Array([ * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f * ]); * entropyToMnemonic(ent, wordlist); * // 'legal winner thank year wave sausage worth useful legal winner thank yellow' */ export function entropyToMnemonic(entropy, wordlist) { aentropy(entropy); const words = getCoder(wordlist).encode(entropy); return words.join(isJapanese(wordlist) ? '\u3000' : ' '); } /** * Validates mnemonic for being 12-24 words contained in `wordlist`. */ export function validateMnemonic(mnemonic, wordlist) { try { mnemonicToEntropy(mnemonic, wordlist); } catch (e) { return false; } return true; } const psalt = (passphrase) => nfkd('mnemonic' + passphrase); /** * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password. * @param mnemonic 12-24 words * @param passphrase string that will additionally protect the key * @returns 64 bytes of key data * @example * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; * await mnemonicToSeed(mnem, 'password'); * // new Uint8Array([...64 bytes]) */ export function mnemonicToSeed(mnemonic, passphrase = '') { return pbkdf2Async(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 }); } /** * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password. * @param mnemonic 12-24 words * @param passphrase string that will additionally protect the key * @returns 64 bytes of key data * @example * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; * mnemonicToSeedSync(mnem, 'password'); * // new Uint8Array([...64 bytes]) */ export function mnemonicToSeedSync(mnemonic, passphrase = '') { return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 }); }