@hdwallet/core
Version:
A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.
158 lines • 7.12 kB
JavaScript
// SPDX-License-Identifier: MIT
import { Mnemonic } from '../mnemonic';
import { MoneroEntropy, MONERO_ENTROPY_STRENGTHS } from '../../entropies';
import { crc32 } from '../../crypto';
import { hexToBytes, bytesToHex, bytesToInteger, bytesChunkToWords, wordsToBytesChunk, getBytes, concatBytes } from '../../utils';
import { MnemonicError, EntropyError, ChecksumError } from '../../exceptions';
import { MONERO_CHINESE_SIMPLIFIED_WORDLIST, MONERO_DUTCH_WORDLIST, MONERO_ENGLISH_WORDLIST, MONERO_FRENCH_WORDLIST, MONERO_GERMAN_WORDLIST, MONERO_ITALIAN_WORDLIST, MONERO_JAPANESE_WORDLIST, MONERO_PORTUGUESE_WORDLIST, MONERO_RUSSIAN_WORDLIST, MONERO_SPANISH_WORDLIST } from './wordlists';
export const MONERO_MNEMONIC_WORDS = {
TWELVE: 12,
THIRTEEN: 13,
TWENTY_FOUR: 24,
TWENTY_FIVE: 25,
};
export const MONERO_MNEMONIC_LANGUAGES = {
CHINESE_SIMPLIFIED: 'chinese-simplified',
DUTCH: 'dutch',
ENGLISH: 'english',
FRENCH: 'french',
GERMAN: 'german',
ITALIAN: 'italian',
JAPANESE: 'japanese',
PORTUGUESE: 'portuguese',
RUSSIAN: 'russian',
SPANISH: 'spanish',
};
export class MoneroMnemonic extends Mnemonic {
static wordBitLength = 11;
static wordsList = [
MONERO_MNEMONIC_WORDS.TWELVE,
MONERO_MNEMONIC_WORDS.THIRTEEN,
MONERO_MNEMONIC_WORDS.TWENTY_FOUR,
MONERO_MNEMONIC_WORDS.TWENTY_FIVE
];
static wordsToStrength = {
12: MONERO_ENTROPY_STRENGTHS.ONE_HUNDRED_TWENTY_EIGHT,
13: MONERO_ENTROPY_STRENGTHS.ONE_HUNDRED_TWENTY_EIGHT,
24: MONERO_ENTROPY_STRENGTHS.TWO_HUNDRED_FIFTY_SIX,
25: MONERO_ENTROPY_STRENGTHS.TWO_HUNDRED_FIFTY_SIX
};
static checksumWordCounts = [
MONERO_MNEMONIC_WORDS.THIRTEEN,
MONERO_MNEMONIC_WORDS.TWENTY_FIVE
];
static languages = Object.values(MONERO_MNEMONIC_LANGUAGES);
static languageUniquePrefixLengths = {
[MONERO_MNEMONIC_LANGUAGES.CHINESE_SIMPLIFIED]: 1,
[MONERO_MNEMONIC_LANGUAGES.DUTCH]: 4,
[MONERO_MNEMONIC_LANGUAGES.ENGLISH]: 3,
[MONERO_MNEMONIC_LANGUAGES.FRENCH]: 4,
[MONERO_MNEMONIC_LANGUAGES.GERMAN]: 4,
[MONERO_MNEMONIC_LANGUAGES.ITALIAN]: 4,
[MONERO_MNEMONIC_LANGUAGES.JAPANESE]: 4,
[MONERO_MNEMONIC_LANGUAGES.PORTUGUESE]: 4,
[MONERO_MNEMONIC_LANGUAGES.RUSSIAN]: 4,
[MONERO_MNEMONIC_LANGUAGES.SPANISH]: 4,
};
static wordLists = {
[MONERO_MNEMONIC_LANGUAGES.CHINESE_SIMPLIFIED]: MONERO_CHINESE_SIMPLIFIED_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.DUTCH]: MONERO_DUTCH_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.ENGLISH]: MONERO_ENGLISH_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.FRENCH]: MONERO_FRENCH_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.GERMAN]: MONERO_GERMAN_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.ITALIAN]: MONERO_ITALIAN_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.JAPANESE]: MONERO_JAPANESE_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.PORTUGUESE]: MONERO_PORTUGUESE_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.RUSSIAN]: MONERO_RUSSIAN_WORDLIST,
[MONERO_MNEMONIC_LANGUAGES.SPANISH]: MONERO_SPANISH_WORDLIST
};
static getName() {
return 'Monero';
}
static fromWords(count, language) {
if (!this.wordsList.includes(count)) {
throw new MnemonicError('Invalid word count', { expected: this.wordsList, got: count });
}
let options = {};
if (this.checksumWordCounts.includes(count)) {
options = { checksum: true };
}
const strength = this.wordsToStrength[count];
const entropyBytes = MoneroEntropy.generate(strength);
return this.encode(entropyBytes, language, options);
}
static fromEntropy(entropy, language, options = {}) {
let raw;
if (typeof entropy === 'string') {
raw = hexToBytes(entropy);
}
else if (entropy instanceof Uint8Array) {
raw = entropy;
}
else {
raw = hexToBytes(entropy.getEntropy());
}
return this.encode(raw, language, options);
}
static encode(entropy, language, options = {}) {
const entropyBytes = getBytes(entropy);
if (!MoneroEntropy.isValidBytesStrength(entropyBytes.length)) {
throw new EntropyError('Wrong entropy strength', { expected: MoneroEntropy.strengths, got: entropyBytes.length * 8 });
}
const rawList = this.getWordsListByLanguage(language, this.wordLists);
const wordList = this.normalize(rawList);
if (wordList.length !== 1626) {
throw new Error(`Expected 1626 words in list for '${language}', got ${wordList.length}`);
}
const mnemonic = [];
for (let i = 0; i < entropyBytes.length; i += 4) {
const chunk = entropyBytes.slice(i, i + 4);
mnemonic.push(...bytesChunkToWords(chunk, wordList, 'little'));
}
if (options.checksum) {
const prefixLen = this.languageUniquePrefixLengths[language];
const prefixes = mnemonic.map(w => w.slice(0, prefixLen)).join('');
const lenBig = BigInt(mnemonic.length);
const idxBig = bytesToInteger(crc32(prefixes)) % lenBig;
const idx = Number(idxBig);
mnemonic.push(mnemonic[idx]);
}
return this.normalize(mnemonic).join(' ');
}
static decode(input, options = {}) {
const words = this.normalize(input);
const count = words.length;
if (!this.wordsList.includes(count)) {
throw new MnemonicError('Invalid word count', { expected: this.wordsList, got: count });
}
const [wordsList, language] = this.findLanguage(words, this.wordLists);
if (wordsList.length !== 1626) {
throw new Error(`Expected 1626 words in list for '${language}', got ${wordsList.length}`);
}
const phraseWords = [...words];
if (this.checksumWordCounts.includes(count)) {
const last = phraseWords.pop();
const prefixLen = this.languageUniquePrefixLengths[language];
const prefixes = phraseWords.map(w => w.slice(0, prefixLen)).join('');
const lenBig = BigInt(phraseWords.length);
const idxBig = bytesToInteger(crc32(prefixes)) % lenBig;
const idx = Number(idxBig);
const expected = phraseWords[idx];
if (last !== expected) {
throw new ChecksumError('Invalid checksum', { expected, got: last });
}
}
const buffers = [];
for (let i = 0; i < phraseWords.length; i += 3) {
const [w1, w2, w3] = phraseWords.slice(i, i + 3);
const chunk = wordsToBytesChunk(w1, w2, w3, wordsList, 'little');
buffers.push(getBytes(chunk));
}
return bytesToHex(concatBytes(...buffers), false);
}
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