@hdwallet/core
Version:
A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.
209 lines • 9.43 kB
JavaScript
;
// SPDX-License-Identifier: MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.ElectrumV2Mnemonic = exports.ELECTRUM_V2_MNEMONIC_TYPES = exports.ELECTRUM_V2_MNEMONIC_LANGUAGES = exports.ELECTRUM_V2_MNEMONIC_WORDS = void 0;
const mnemonic_1 = require("../../mnemonic");
const mnemonic_2 = require("../../bip39/mnemonic");
const mnemonic_3 = require("../v1/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.ELECTRUM_V2_MNEMONIC_WORDS = {
TWELVE: 12,
TWENTY_FOUR: 24
};
exports.ELECTRUM_V2_MNEMONIC_LANGUAGES = {
CHINESE_SIMPLIFIED: 'chinese-simplified',
ENGLISH: 'english',
PORTUGUESE: 'portuguese',
SPANISH: 'spanish'
};
exports.ELECTRUM_V2_MNEMONIC_TYPES = {
STANDARD: 'standard',
SEGWIT: 'segwit',
STANDARD_2FA: 'standard-2fa',
SEGWIT_2FA: 'segwit-2fa'
};
class ElectrumV2Mnemonic extends mnemonic_1.Mnemonic {
static wordBitLength = 11;
static wordsList = [
exports.ELECTRUM_V2_MNEMONIC_WORDS.TWELVE,
exports.ELECTRUM_V2_MNEMONIC_WORDS.TWENTY_FOUR
];
static wordsToEntropyStrength = {
12: entropies_1.ELECTRUM_V2_ENTROPY_STRENGTHS.ONE_HUNDRED_THIRTY_TWO,
24: entropies_1.ELECTRUM_V2_ENTROPY_STRENGTHS.TWO_HUNDRED_SIXTY_FOUR
};
static languages = Object.values(exports.ELECTRUM_V2_MNEMONIC_LANGUAGES);
static wordLists = {
[exports.ELECTRUM_V2_MNEMONIC_LANGUAGES.CHINESE_SIMPLIFIED]: wordlists_1.ELECTRUM_V2_CHINESE_SIMPLIFIED_WORDLIST,
[exports.ELECTRUM_V2_MNEMONIC_LANGUAGES.ENGLISH]: wordlists_1.ELECTRUM_V2_ENGLISH_WORDLIST,
[exports.ELECTRUM_V2_MNEMONIC_LANGUAGES.PORTUGUESE]: wordlists_1.ELECTRUM_V2_PORTUGUESE_WORDLIST,
[exports.ELECTRUM_V2_MNEMONIC_LANGUAGES.SPANISH]: wordlists_1.ELECTRUM_V2_SPANISH_WORDLIST
};
static mnemonicTypes = {
[exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD]: '01',
[exports.ELECTRUM_V2_MNEMONIC_TYPES.SEGWIT]: '100',
[exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD_2FA]: '101',
[exports.ELECTRUM_V2_MNEMONIC_TYPES.SEGWIT_2FA]: '102'
};
static getName() {
return 'Electrum-V2';
}
static fromWords(count, language, option = {
mnemonicType: exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD,
maxAttempts: BigInt('1' + '0'.repeat(60))
}) {
if (!this.wordsList.includes(count)) {
throw new exceptions_1.MnemonicError('Invalid mnemonic words number', {
expected: this.wordsList,
got: count,
});
}
const entropyBytes = entropies_1.ElectrumV2Entropy.generate(this.wordsToEntropyStrength[count]);
return this.fromEntropy(entropyBytes, language, option);
}
static fromEntropy(entropy, language, option = {
mnemonicType: exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD,
maxAttempts: BigInt('1' + '0'.repeat(60))
}) {
if (!option.mnemonicType) {
throw new exceptions_1.MnemonicError('mnemonicType is required');
}
if (!option.maxAttempts) {
option.maxAttempts = BigInt('1' + '0'.repeat(60));
}
let raw;
if (typeof entropy === 'string') {
raw = (0, utils_1.getBytes)(entropy);
}
else if (entropy instanceof Uint8Array) {
raw = entropy;
}
else {
raw = (0, utils_1.getBytes)(entropy.getEntropy());
}
if (!entropies_1.ElectrumV2Entropy.areEntropyBitsEnough(raw)) {
throw new exceptions_1.EntropyError('Entropy bit length is not enough for generating a valid mnemonic');
}
const wordsList = this.normalize(this.getWordsListByLanguage(language, this.wordLists));
const bip39List = this.normalize(this.getWordsListByLanguage(language, mnemonic_2.BIP39Mnemonic.wordLists));
const bip39Index = Object.fromEntries(bip39List.map((w, i) => [w, i]));
let ev1List = [];
let ev1Index = {};
try {
ev1List = this.normalize(this.getWordsListByLanguage(language, mnemonic_3.ElectrumV1Mnemonic.wordLists));
ev1Index = Object.fromEntries(ev1List.map((w, i) => [w, i]));
}
catch {
}
const baseEnt = (0, utils_1.bytesToInteger)(raw, false);
// try offsets 0,1,2… up to maxAttempts
for (let offset = BigInt(0); offset < option.maxAttempts; offset++) {
const candidate = (0, utils_1.integerToBytes)(baseEnt + offset, raw.length, 'big');
try {
return this.encode(candidate, language, {
mnemonicType: option.mnemonicType,
wordsList: wordsList,
bip39List: bip39List,
bip39Index: bip39Index,
ev1List: ev1List,
ev1Index: ev1Index
});
}
catch (err) {
if (err instanceof exceptions_1.EntropyError) {
continue;
}
throw err;
}
}
throw new exceptions_1.MnemonicError('Unable to generate a valid mnemonic');
}
static encode(entropy, language, option = {
mnemonicType: exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD
}) {
if (!option.mnemonicType) {
throw new exceptions_1.MnemonicError('mnemonicType is required');
}
const entropyBytes = (0, utils_1.getBytes)(entropy);
let ent = (0, utils_1.bytesToInteger)(entropyBytes, false);
if (!entropies_1.ElectrumV2Entropy.areEntropyBitsEnough(entropyBytes)) {
throw new exceptions_1.EntropyError('Invalid entropy strength for V2');
}
const wl = option.wordsList ?? this.normalize(this.getWordsListByLanguage(language, this.wordLists));
const mnemonic = [];
// repeatedly mod/divide
while (ent > BigInt(0)) {
const idx = Number(ent % BigInt(wl.length));
ent = ent / BigInt(wl.length);
mnemonic.push(wl[idx]);
}
if (mnemonic_2.BIP39Mnemonic.isValid(mnemonic, { wordsList: option.bip39List, wordsListWithIndex: option.bip39Index }) ||
mnemonic_3.ElectrumV1Mnemonic.isValid(mnemonic, { wordsList: option.ev1List, wordsListWithIndex: option.ev1Index })) {
throw new exceptions_1.EntropyError('Entropy bytes are not suitable for generating a valid mnemonic');
}
if (!this.isType(mnemonic, option.mnemonicType)) {
throw new exceptions_1.EntropyError(`Could not generate a '${option.mnemonicType}' mnemonic`);
}
return this.normalize(mnemonic).join(' ');
}
static decode(mnemonic, option = {
mnemonicType: exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD
}) {
if (!option.mnemonicType) {
throw new exceptions_1.MnemonicError('mnemonicType is required');
}
const words = this.normalize(mnemonic);
if (!this.wordsList.includes(words.length)) {
throw new exceptions_1.MnemonicError('Invalid mnemonic words count', {
expected: this.wordsList, got: words.length,
});
}
if (!this.isValid(words, option)) {
throw new exceptions_1.MnemonicError(`Invalid ${option.mnemonicType} mnemonic words`);
}
const [wordsList] = this.findLanguage(words, this.wordLists);
const idxMap = Object.fromEntries(wordsList.map((w, i) => [w, i]));
let ent = BigInt(0);
// reverse process: from last word to first
for (const w of words.slice().reverse()) {
ent = ent * BigInt(wordsList.length) + BigInt(idxMap[w]);
}
// convert bigint -> bytes -> hex
const byteLen = Math.ceil(words.length * this.wordBitLength / 8);
const buf = (0, utils_1.integerToBytes)(ent, byteLen, 'big');
return (0, utils_1.bytesToString)(buf);
}
static isValid(input, option = {
mnemonicType: exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD
}) {
if (mnemonic_2.BIP39Mnemonic.isValid(input, {
wordsList: option.bip39List, wordsListWithIndex: option.bip39Index
}) ||
mnemonic_3.ElectrumV1Mnemonic.isValid(input, {
wordsList: option.ev1List, wordsListWithIndex: option.ev1Index
})) {
return false;
}
return this.isType(input, option.mnemonicType ?? exports.ELECTRUM_V2_MNEMONIC_TYPES.STANDARD);
}
static isType(input, mnemonicType) {
const tag = (0, utils_1.bytesToString)((0, crypto_1.hmacSha512)((0, utils_1.toBuffer)('Seed version'), this.normalize(input).join(' ')));
return tag.startsWith(this.mnemonicTypes[mnemonicType]);
}
getMnemonicType() {
if (!this.options?.mnemonicType) {
throw new exceptions_1.MnemonicError('mnemonicType is not found');
}
return this.options?.mnemonicType;
}
static normalize(input) {
const arr = typeof input === 'string' ? input.trim().split(/\s+/) : input;
return arr.map(w => w.normalize('NFKD').toLowerCase());
}
}
exports.ElectrumV2Mnemonic = ElectrumV2Mnemonic;
//# sourceMappingURL=mnemonic.js.map