@hdwallet/core
Version:
A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.
154 lines • 6.83 kB
JavaScript
// SPDX-License-Identifier: MIT
import { Mnemonic } from '../mnemonic';
import { BIP39Entropy, BIP39_ENTROPY_STRENGTHS } from '../../entropies';
import { sha256 } from '../../crypto';
import { hexToBytes, bytesToBinaryString, integerToBinaryString, binaryStringToBytes, bytesToHex } from '../../utils';
import { MnemonicError, EntropyError, ChecksumError } from '../../exceptions';
import { BIP39_CHINESE_SIMPLIFIED_WORDLIST, BIP39_CHINESE_TRADITIONAL_WORDLIST, BIP39_CZECH_WORDLIST, BIP39_ENGLISH_WORDLIST, BIP39_FRENCH_WORDLIST, BIP39_ITALIAN_WORDLIST, BIP39_JAPANESE_WORDLIST, BIP39_KOREAN_WORDLIST, BIP39_PORTUGUESE_WORDLIST, BIP39_RUSSIAN_WORDLIST, BIP39_SPANISH_WORDLIST, BIP39_TURKISH_WORDLIST } from './wordlists';
export const BIP39_MNEMONIC_WORDS = {
TWELVE: 12,
FIFTEEN: 15,
EIGHTEEN: 18,
TWENTY_ONE: 21,
TWENTY_FOUR: 24
};
export const BIP39_MNEMONIC_LANGUAGES = {
CHINESE_SIMPLIFIED: 'chinese-simplified',
CHINESE_TRADITIONAL: 'chinese-traditional',
CZECH: 'czech',
ENGLISH: 'english',
FRENCH: 'french',
ITALIAN: 'italian',
JAPANESE: 'japanese',
KOREAN: 'korean',
PORTUGUESE: 'portuguese',
RUSSIAN: 'russian',
SPANISH: 'spanish',
TURKISH: 'turkish'
};
export class BIP39Mnemonic extends Mnemonic {
static wordBitLength = 11;
static wordsListNumber = 2048;
static wordsList = [
BIP39_MNEMONIC_WORDS.TWELVE,
BIP39_MNEMONIC_WORDS.FIFTEEN,
BIP39_MNEMONIC_WORDS.EIGHTEEN,
BIP39_MNEMONIC_WORDS.TWENTY_ONE,
BIP39_MNEMONIC_WORDS.TWENTY_FOUR
];
static wordsToEntropyStrength = {
12: BIP39_ENTROPY_STRENGTHS.ONE_HUNDRED_TWENTY_EIGHT,
15: BIP39_ENTROPY_STRENGTHS.ONE_HUNDRED_SIXTY,
18: BIP39_ENTROPY_STRENGTHS.ONE_HUNDRED_NINETY_TWO,
21: BIP39_ENTROPY_STRENGTHS.TWO_HUNDRED_TWENTY_FOUR,
24: BIP39_ENTROPY_STRENGTHS.TWO_HUNDRED_FIFTY_SIX
};
static languages = Object.values(BIP39_MNEMONIC_LANGUAGES);
static wordLists = {
[BIP39_MNEMONIC_LANGUAGES.CHINESE_SIMPLIFIED]: BIP39_CHINESE_SIMPLIFIED_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.CHINESE_TRADITIONAL]: BIP39_CHINESE_TRADITIONAL_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.CZECH]: BIP39_CZECH_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.ENGLISH]: BIP39_ENGLISH_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.FRENCH]: BIP39_FRENCH_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.ITALIAN]: BIP39_ITALIAN_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.JAPANESE]: BIP39_JAPANESE_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.KOREAN]: BIP39_KOREAN_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.PORTUGUESE]: BIP39_PORTUGUESE_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.RUSSIAN]: BIP39_RUSSIAN_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.SPANISH]: BIP39_SPANISH_WORDLIST,
[BIP39_MNEMONIC_LANGUAGES.TURKISH]: BIP39_TURKISH_WORDLIST
};
static getName() {
return 'BIP39';
}
static fromWords(words, language, options = {}) {
if (!this.wordsList.includes(words)) {
throw new MnemonicError(`Invalid words`, { expected: this.wordsList, got: words });
}
const strength = this.wordsToEntropyStrength[words];
const entropyHex = BIP39Entropy.generate(strength);
return this.encode(entropyHex, language, options);
}
static fromEntropy(entropy, language, options = {}) {
let hex;
if (typeof entropy === 'string') {
hex = entropy;
}
else if (entropy instanceof Uint8Array) {
hex = bytesToHex(entropy);
}
else {
hex = entropy.getEntropy();
}
return this.encode(hex, language, options);
}
static encode(entropyInput, language, options = {}) {
const entropyBytes = typeof entropyInput === 'string' ?
hexToBytes(entropyInput) : entropyInput;
const bitLen = entropyBytes.length * 8;
if (!Object.values(this.wordsToEntropyStrength).includes(bitLen)) {
throw new EntropyError(`Unsupported entropy length ${bitLen}`);
}
const hash = sha256(entropyBytes);
const csLen = bitLen / 32;
const entBits = bytesToBinaryString(entropyBytes, bitLen);
const hashBits = bytesToBinaryString(hash, 256).slice(0, csLen);
const bits = entBits + hashBits;
const wordList = this.getWordsListByLanguage(language, this.wordLists);
if (wordList.length !== this.wordsListNumber) {
throw new Error(`Loaded wordlist length ${wordList.length} !== ${this.wordsListNumber}`);
}
const words = [];
for (let i = 0; i < bits.length / this.wordBitLength; i++) {
const idx = parseInt(bits.slice(i * this.wordBitLength, (i + 1) * this.wordBitLength), 2);
words.push(wordList[idx]);
}
return words.join(' ');
}
static decode(mnemonic, options = { checksum: false }) {
const words = this.normalize(mnemonic);
if (!this.wordsList.includes(words.length)) {
throw new MnemonicError(`Invalid words ${words.length}`, { expected: this.wordsList, got: words.length });
}
let wordList;
let idxMap = {};
if (options.wordsList && options.wordsListWithIndex) {
idxMap = options.wordsListWithIndex;
}
else {
[wordList] = this.findLanguage(words, this.wordLists);
wordList.forEach((w, i) => idxMap[w] = i);
}
const bits = words
.map(w => {
const idx = idxMap[w];
if (idx === undefined) {
throw new MnemonicError(`Unknown word: ${w}`);
}
return integerToBinaryString(idx, this.wordBitLength);
})
.join('');
const checksumLen = bits.length / 33;
const entropyBits = bits.slice(0, -checksumLen);
const givenChecksum = bits.slice(-checksumLen);
const entropyBytes = binaryStringToBytes(entropyBits);
const hash = sha256(entropyBytes);
const hashBits = bytesToBinaryString(hash, 256).slice(0, checksumLen);
if (givenChecksum !== hashBits) {
throw new ChecksumError('Checksum mismatch', { expected: givenChecksum, got: hashBits });
}
if (options.checksum) {
const totalBits = bits.length;
const padBits = totalBits % 8 === 0
? totalBits
: totalBits + (8 - (totalBits % 8));
return bytesToHex(binaryStringToBytes(bits, padBits / 8));
}
return bytesToHex(entropyBytes);
}
static normalize(input) {
const arr = typeof input === 'string' ? input.trim().split(/\s+/) : input;
return arr.map(w => w.normalize('NFKD').toLowerCase());
}
}
//# sourceMappingURL=mnemonic.js.map