123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- /*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */
- import { bytes as assertBytes, number as assertNumber } from '@noble/hashes/_assert';
- import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';
- import { sha256 } from '@noble/hashes/sha256';
- import { sha512 } from '@noble/hashes/sha512';
- import { 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 assertEntropy(entropy) {
- assertBytes(entropy, 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) {
- assertNumber(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);
- assertEntropy(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) {
- assertEntropy(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 salt = (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, salt(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, salt(passphrase), { c: 2048, dkLen: 64 });
- }
|