UNPKG

3.59 kBJavaScriptView Raw
1// Copyright 2017-2023 @polkadot/util-crypto authors & contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Adapted from the bitcoinjs/bip39 source
5// https://github.com/bitcoinjs/bip39/blob/1d063b6a6aee4145b34d701037cd3e67f5446ff9/ts_src/index.ts
6// Copyright (c) 2014, Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com>
7// ISC Licence
8//
9// Change made in this version -
10// - Adjust formatting (just eslint differences)
11// - Only English wordlist (this aligns with the wasm-crypto implementation)
12// - Use util-crypto randomAsU8a (instead of randombytes)
13// - Remove setting of wordlist passing of wordlist in functions
14// - Remove mnemonicToSeed (we only use the sync variant)
15// - generateMnemonic takes number of words (instead of strength)
16
17import { stringToU8a, u8aToU8a } from '@polkadot/util';
18import { pbkdf2Encode } from "../pbkdf2/index.js";
19import { randomAsU8a } from "../random/index.js";
20import { sha256AsU8a } from "../sha/index.js";
21import DEFAULT_WORDLIST from "./bip39-en.js";
22const INVALID_MNEMONIC = 'Invalid mnemonic';
23const INVALID_ENTROPY = 'Invalid entropy';
24const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
25function normalize(str) {
26 return (str || '').normalize('NFKD');
27}
28function binaryToByte(bin) {
29 return parseInt(bin, 2);
30}
31function bytesToBinary(bytes) {
32 return bytes.map(x => x.toString(2).padStart(8, '0')).join('');
33}
34function deriveChecksumBits(entropyBuffer) {
35 return bytesToBinary(Array.from(sha256AsU8a(entropyBuffer))).slice(0, entropyBuffer.length * 8 / 32);
36}
37export function mnemonicToSeedSync(mnemonic, password) {
38 return pbkdf2Encode(stringToU8a(normalize(mnemonic)), stringToU8a(`mnemonic${normalize(password)}`)).password;
39}
40export function mnemonicToEntropy(mnemonic) {
41 const words = normalize(mnemonic).split(' ');
42 if (words.length % 3 !== 0) {
43 throw new Error(INVALID_MNEMONIC);
44 }
45
46 // convert word indices to 11 bit binary strings
47 const bits = words.map(word => {
48 const index = DEFAULT_WORDLIST.indexOf(word);
49 if (index === -1) {
50 throw new Error(INVALID_MNEMONIC);
51 }
52 return index.toString(2).padStart(11, '0');
53 }).join('');
54
55 // split the binary string into ENT/CS
56 const dividerIndex = Math.floor(bits.length / 33) * 32;
57 const entropyBits = bits.slice(0, dividerIndex);
58 const checksumBits = bits.slice(dividerIndex);
59
60 // calculate the checksum and compare
61 const matched = entropyBits.match(/(.{1,8})/g);
62 const entropyBytes = matched && matched.map(binaryToByte);
63 if (!entropyBytes || entropyBytes.length % 4 !== 0 || entropyBytes.length < 16 || entropyBytes.length > 32) {
64 throw new Error(INVALID_ENTROPY);
65 }
66 const entropy = u8aToU8a(entropyBytes);
67 if (deriveChecksumBits(entropy) !== checksumBits) {
68 throw new Error(INVALID_CHECKSUM);
69 }
70 return entropy;
71}
72export function entropyToMnemonic(entropy) {
73 // 128 <= ENT <= 256
74 if (entropy.length % 4 !== 0 || entropy.length < 16 || entropy.length > 32) {
75 throw new Error(INVALID_ENTROPY);
76 }
77 const matched = `${bytesToBinary(Array.from(entropy))}${deriveChecksumBits(entropy)}`.match(/(.{1,11})/g);
78 const mapped = matched && matched.map(binary => DEFAULT_WORDLIST[binaryToByte(binary)]);
79 if (!mapped || mapped.length < 12) {
80 throw new Error('Unable to map entropy to mnemonic');
81 }
82 return mapped.join(' ');
83}
84export function generateMnemonic(numWords) {
85 return entropyToMnemonic(randomAsU8a(numWords / 3 * 4));
86}
87export function validateMnemonic(mnemonic) {
88 try {
89 mnemonicToEntropy(mnemonic);
90 } catch (e) {
91 return false;
92 }
93 return true;
94}
\No newline at end of file