1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import { stringToU8a, u8aToU8a } from '@polkadot/util';
|
18 | import { pbkdf2Encode } from "../pbkdf2/index.js";
|
19 | import { randomAsU8a } from "../random/index.js";
|
20 | import { sha256AsU8a } from "../sha/index.js";
|
21 | import DEFAULT_WORDLIST from "./bip39-en.js";
|
22 | const INVALID_MNEMONIC = 'Invalid mnemonic';
|
23 | const INVALID_ENTROPY = 'Invalid entropy';
|
24 | const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
|
25 | function normalize(str) {
|
26 | return (str || '').normalize('NFKD');
|
27 | }
|
28 | function binaryToByte(bin) {
|
29 | return parseInt(bin, 2);
|
30 | }
|
31 | function bytesToBinary(bytes) {
|
32 | return bytes.map(x => x.toString(2).padStart(8, '0')).join('');
|
33 | }
|
34 | function deriveChecksumBits(entropyBuffer) {
|
35 | return bytesToBinary(Array.from(sha256AsU8a(entropyBuffer))).slice(0, entropyBuffer.length * 8 / 32);
|
36 | }
|
37 | export function mnemonicToSeedSync(mnemonic, password) {
|
38 | return pbkdf2Encode(stringToU8a(normalize(mnemonic)), stringToU8a(`mnemonic${normalize(password)}`)).password;
|
39 | }
|
40 | export function mnemonicToEntropy(mnemonic) {
|
41 | const words = normalize(mnemonic).split(' ');
|
42 | if (words.length % 3 !== 0) {
|
43 | throw new Error(INVALID_MNEMONIC);
|
44 | }
|
45 |
|
46 |
|
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 |
|
56 | const dividerIndex = Math.floor(bits.length / 33) * 32;
|
57 | const entropyBits = bits.slice(0, dividerIndex);
|
58 | const checksumBits = bits.slice(dividerIndex);
|
59 |
|
60 |
|
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 | }
|
72 | export function entropyToMnemonic(entropy) {
|
73 |
|
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 | }
|
84 | export function generateMnemonic(numWords) {
|
85 | return entropyToMnemonic(randomAsU8a(numWords / 3 * 4));
|
86 | }
|
87 | export 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 |