UNPKG

3.1 kBJavaScriptView Raw
1import { stringToU8a, u8aToU8a } from '@polkadot/util';
2import { pbkdf2Encode } from '../pbkdf2/index.js';
3import { randomAsU8a } from '../random/index.js';
4import { sha256AsU8a } from '../sha/index.js';
5import DEFAULT_WORDLIST from './wordlists/en.js';
6const INVALID_MNEMONIC = 'Invalid mnemonic';
7const INVALID_ENTROPY = 'Invalid entropy';
8const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
9/** @internal */
10function normalize(str) {
11 return (str || '').normalize('NFKD');
12}
13/** @internal */
14function binaryToByte(bin) {
15 return parseInt(bin, 2);
16}
17/** @internal */
18function bytesToBinary(bytes) {
19 return bytes.map((x) => x.toString(2).padStart(8, '0')).join('');
20}
21/** @internal */
22function deriveChecksumBits(entropyBuffer) {
23 return bytesToBinary(Array.from(sha256AsU8a(entropyBuffer))).slice(0, (entropyBuffer.length * 8) / 32);
24}
25export function mnemonicToSeedSync(mnemonic, password) {
26 return pbkdf2Encode(stringToU8a(normalize(mnemonic)), stringToU8a(`mnemonic${normalize(password)}`)).password;
27}
28export function mnemonicToEntropy(mnemonic, wordlist = DEFAULT_WORDLIST) {
29 const words = normalize(mnemonic).split(' ');
30 if (words.length % 3 !== 0) {
31 throw new Error(INVALID_MNEMONIC);
32 }
33 // convert word indices to 11 bit binary strings
34 const bits = words
35 .map((word) => {
36 const index = wordlist.indexOf(word);
37 if (index === -1) {
38 throw new Error(INVALID_MNEMONIC);
39 }
40 return index.toString(2).padStart(11, '0');
41 })
42 .join('');
43 // split the binary string into ENT/CS
44 const dividerIndex = Math.floor(bits.length / 33) * 32;
45 const entropyBits = bits.slice(0, dividerIndex);
46 const checksumBits = bits.slice(dividerIndex);
47 // calculate the checksum and compare
48 const matched = entropyBits.match(/(.{1,8})/g);
49 const entropyBytes = matched?.map(binaryToByte);
50 if (!entropyBytes || (entropyBytes.length % 4 !== 0) || (entropyBytes.length < 16) || (entropyBytes.length > 32)) {
51 throw new Error(INVALID_ENTROPY);
52 }
53 const entropy = u8aToU8a(entropyBytes);
54 if (deriveChecksumBits(entropy) !== checksumBits) {
55 throw new Error(INVALID_CHECKSUM);
56 }
57 return entropy;
58}
59export function entropyToMnemonic(entropy, wordlist = DEFAULT_WORDLIST) {
60 // 128 <= ENT <= 256
61 if ((entropy.length % 4 !== 0) || (entropy.length < 16) || (entropy.length > 32)) {
62 throw new Error(INVALID_ENTROPY);
63 }
64 const matched = `${bytesToBinary(Array.from(entropy))}${deriveChecksumBits(entropy)}`.match(/(.{1,11})/g);
65 const mapped = matched?.map((b) => wordlist[binaryToByte(b)]);
66 if (!mapped || (mapped.length < 12)) {
67 throw new Error('Unable to map entropy to mnemonic');
68 }
69 return mapped.join(' ');
70}
71export function generateMnemonic(numWords, wordlist) {
72 return entropyToMnemonic(randomAsU8a((numWords / 3) * 4), wordlist);
73}
74export function validateMnemonic(mnemonic, wordlist) {
75 try {
76 mnemonicToEntropy(mnemonic, wordlist);
77 }
78 catch {
79 return false;
80 }
81 return true;
82}