@hdwallet/core
Version:
A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.
220 lines • 9.48 kB
JavaScript
;
// SPDX-License-Identifier: MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.BIP39Mnemonic = exports.BIP39_MNEMONIC_LANGUAGES = exports.BIP39_MNEMONIC_WORDS = void 0;
const mnemonic_1 = require("../mnemonic");
const entropies_1 = require("../../entropies");
const crypto_1 = require("../../crypto");
const utils_1 = require("../../utils");
const exceptions_1 = require("../../exceptions");
const wordlists_1 = require("./wordlists");
exports.BIP39_MNEMONIC_WORDS = {
TWELVE: 12,
FIFTEEN: 15,
EIGHTEEN: 18,
TWENTY_ONE: 21,
TWENTY_FOUR: 24
};
exports.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'
};
/**
* Implements the BIP-39 mnemonic standard.
*
* BIP-39 mnemonics are human-readable sequences of words that encode entropy
* with a checksum, making it easier to back up and restore HD wallets.
*
* Supported word counts: 12, 15, 18, 21, 24
* Supported languages: Chinese (Simplified/Traditional), Czech, English, French,
* Italian, Japanese, Korean, Portuguese, Russian, Spanish, Turkish.
*
* Features:
* - Generate mnemonics from entropy or word count.
* - Encode entropy into mnemonic phrases.
* - Decode mnemonic phrases back to entropy.
* - Verify and normalize mnemonics across languages.
*/
class BIP39Mnemonic extends mnemonic_1.Mnemonic {
static wordBitLength = 11;
static wordsListNumber = 2048;
static wordsList = [
exports.BIP39_MNEMONIC_WORDS.TWELVE,
exports.BIP39_MNEMONIC_WORDS.FIFTEEN,
exports.BIP39_MNEMONIC_WORDS.EIGHTEEN,
exports.BIP39_MNEMONIC_WORDS.TWENTY_ONE,
exports.BIP39_MNEMONIC_WORDS.TWENTY_FOUR
];
static wordsToEntropyStrength = {
12: entropies_1.BIP39_ENTROPY_STRENGTHS.ONE_HUNDRED_TWENTY_EIGHT,
15: entropies_1.BIP39_ENTROPY_STRENGTHS.ONE_HUNDRED_SIXTY,
18: entropies_1.BIP39_ENTROPY_STRENGTHS.ONE_HUNDRED_NINETY_TWO,
21: entropies_1.BIP39_ENTROPY_STRENGTHS.TWO_HUNDRED_TWENTY_FOUR,
24: entropies_1.BIP39_ENTROPY_STRENGTHS.TWO_HUNDRED_FIFTY_SIX
};
static languages = Object.values(exports.BIP39_MNEMONIC_LANGUAGES);
static wordLists = {
[exports.BIP39_MNEMONIC_LANGUAGES.CHINESE_SIMPLIFIED]: wordlists_1.BIP39_CHINESE_SIMPLIFIED_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.CHINESE_TRADITIONAL]: wordlists_1.BIP39_CHINESE_TRADITIONAL_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.CZECH]: wordlists_1.BIP39_CZECH_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.ENGLISH]: wordlists_1.BIP39_ENGLISH_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.FRENCH]: wordlists_1.BIP39_FRENCH_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.ITALIAN]: wordlists_1.BIP39_ITALIAN_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.JAPANESE]: wordlists_1.BIP39_JAPANESE_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.KOREAN]: wordlists_1.BIP39_KOREAN_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.PORTUGUESE]: wordlists_1.BIP39_PORTUGUESE_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.RUSSIAN]: wordlists_1.BIP39_RUSSIAN_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.SPANISH]: wordlists_1.BIP39_SPANISH_WORDLIST,
[exports.BIP39_MNEMONIC_LANGUAGES.TURKISH]: wordlists_1.BIP39_TURKISH_WORDLIST
};
/**
* Get the human-readable name of this mnemonic standard.
* @returns `"BIP39"`
*/
static getName() {
return 'BIP39';
}
/**
* Generate a new mnemonic phrase by word count.
*
* @param words Number of words (12, 15, 18, 21, or 24).
* @param language Language of the wordlist.
* @param options Additional encoding options.
* @throws {MnemonicError} If word count is invalid.
* @returns Mnemonic phrase as a string.
*/
static fromWords(words, language, options = {}) {
if (!this.wordsList.includes(words)) {
throw new exceptions_1.MnemonicError(`Invalid words`, { expected: this.wordsList, got: words });
}
const strength = this.wordsToEntropyStrength[words];
const entropyHex = entropies_1.BIP39Entropy.generate(strength);
return this.encode(entropyHex, language, options);
}
/**
* Generate a mnemonic phrase from entropy.
*
* @param entropy Hex string, byte array, or Entropy instance.
* @param language Language of the wordlist.
* @param options Additional encoding options.
* @returns Mnemonic phrase as a string.
*/
static fromEntropy(entropy, language, options = {}) {
let hex;
if (typeof entropy === 'string') {
hex = entropy;
}
else if (entropy instanceof Uint8Array) {
hex = (0, utils_1.bytesToHex)(entropy);
}
else {
hex = entropy.getEntropy();
}
return this.encode(hex, language, options);
}
/**
* Encode entropy into a BIP-39 mnemonic phrase.
*
* @param entropyInput Hex string or byte array.
* @param language Wordlist language.
* @param options Additional options.
* @throws {EntropyError} If entropy length is invalid.
* @throws {Error} If wordlist size is not 2048.
* @returns Mnemonic phrase as a string.
*/
static encode(entropyInput, language, options = {}) {
const entropyBytes = typeof entropyInput === 'string' ?
(0, utils_1.hexToBytes)(entropyInput) : entropyInput;
const bitLen = entropyBytes.length * 8;
if (!Object.values(this.wordsToEntropyStrength).includes(bitLen)) {
throw new exceptions_1.EntropyError(`Unsupported entropy length ${bitLen}`);
}
const hash = (0, crypto_1.sha256)(entropyBytes);
const csLen = bitLen / 32;
const entBits = (0, utils_1.bytesToBinaryString)(entropyBytes, bitLen);
const hashBits = (0, utils_1.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(' ');
}
/**
* Decode a mnemonic phrase back into entropy.
*
* @param mnemonic Mnemonic phrase as string or array of words.
* @param options Options for checksum verification and custom wordlists.
* @throws {MnemonicError} If word count or words are invalid.
* @throws {ChecksumError} If checksum does not match.
* @returns Hex string of entropy.
*/
static decode(mnemonic, options = { checksum: false }) {
const words = this.normalize(mnemonic);
if (!this.wordsList.includes(words.length)) {
throw new exceptions_1.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 exceptions_1.MnemonicError(`Unknown word: ${w}`);
}
return (0, utils_1.integerToBinaryString)(idx, this.wordBitLength);
})
.join('');
const checksumLen = bits.length / 33;
const entropyBits = bits.slice(0, -checksumLen);
const givenChecksum = bits.slice(-checksumLen);
const entropyBytes = (0, utils_1.binaryStringToBytes)(entropyBits);
const hash = (0, crypto_1.sha256)(entropyBytes);
const hashBits = (0, utils_1.bytesToBinaryString)(hash, 256).slice(0, checksumLen);
if (givenChecksum !== hashBits) {
throw new exceptions_1.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 (0, utils_1.bytesToHex)((0, utils_1.binaryStringToBytes)(bits, padBits / 8));
}
return (0, utils_1.bytesToHex)(entropyBytes);
}
/**
* Normalize a mnemonic phrase into lowercase NFKD words.
*
* @param input Mnemonic as string or array of words.
* @returns Array of normalized words.
*/
static normalize(input) {
const arr = typeof input === 'string' ? input.trim().split(/\s+/) : input;
return arr.map(w => w.normalize('NFKD').toLowerCase());
}
}
exports.BIP39Mnemonic = BIP39Mnemonic;
//# sourceMappingURL=mnemonic.js.map