js-moi-bip39
Version:
Bitcoin BIP39 package for deterministic key generation using mnemonic code
269 lines • 9.77 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDefaultWordlist = exports.validateMnemonic = exports.generateMnemonic = exports.entropyToMnemonic = exports.mnemonicToEntropy = exports.mnemonicToSeed = exports.mnemonicToSeedSync = void 0;
/**
* This code is based on the bitcoinjs/bip39 by Wei Lu and Danieal Cousens
* Modifications have been made to adapt it to the needs of js-moi-bip39
* including enhancements for browser compatibility and TypeScript conversion.
*
* Original module available at: https://github.com/bitcoinjs/bip39
* Modified version available at: https://github.com/sarvalabs/js-moi-sdk/tree/main/packages/js-moi-bip39
*
* Copyright (c) 2014, Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com>
* Repository ISC license details can be found at https://github.com/bitcoinjs/bip39/blob/master/LICENSE
*
**/
const buffer_1 = require("buffer");
const sha256_1 = require("@noble/hashes/sha256");
const sha512_1 = require("@noble/hashes/sha512");
const utils_1 = require("@noble/hashes/utils");
const pbkdf2_1 = require("@noble/hashes/pbkdf2");
const _wordlists_1 = require("./_wordlists");
let DEFAULT_WORDLIST = _wordlists_1._default;
const INVALID_MNEMONIC = 'Invalid mnemonic';
const INVALID_ENTROPY = 'Invalid entropy';
const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
const WORDLIST_REQUIRED = 'A wordlist is required but a default could not be found.\n' +
'Please pass a 2048 word array explicitly.';
/**
* Normalizes a string by converting it to Unicode Normalization Form KD (NFKD).
*
* @param {string} str - The string to normalize.
* @returns {string} The normalized string.
*/
const normalize = (str) => {
return (str || '').normalize('NFKD');
};
/**
* Left pad a string with a padString to a specific length.
*
* @param {string} str - The string to pad.
* @param {string} padString - The string used for padding.
* @param {number} length - The target length of the padded string.
* @returns {string} The padded string.
*/
const lpad = (str, padString, length) => {
while (str.length < length) {
str = padString + str;
}
return str;
};
/**
* Convert a binary string to a byte (number).
*
* @param {string} bin - The binary string to convert.
* @returns {number} The converted byte.
*/
const binaryToByte = (bin) => {
return parseInt(bin, 2);
};
/**
* Convert an array of bytes to a binary string.
*
* @param {number[]} bytes - The array of bytes to convert.
* @returns {string} The converted binary string.
*/
const bytesToBinary = (bytes) => {
return bytes.map((x) => lpad(x.toString(2), '0', 8)).join('');
};
/**
* Derive the checksum bits from an entropy buffer.
*
* @param {Uint8Array} entropyBuffer - The entropy buffer.
* @returns {string} The derived checksum bits.
*/
const deriveChecksumBits = (entropyBuffer) => {
const ENT = entropyBuffer.length * 8;
const CS = ENT / 32;
const hash = (0, sha256_1.sha256)(Uint8Array.from(entropyBuffer));
return bytesToBinary(Array.from(hash)).slice(0, CS);
};
/**
* Generate a salt for PBKDF2 using a password.
*
* @param {string} password - The password.
* @returns {string} The generated salt.
*/
const salt = (password) => {
return 'mnemonic' + (password || '');
};
/**
* Synchronously convert a mnemonic to a seed.
*
* @param {string} mnemonic - The mnemonic phrase.
* @param {string} [password] - The optional password.
* @returns {Buffer} The generated seed.
*/
const mnemonicToSeedSync = (mnemonic, password) => {
const mnemonicBuffer = Uint8Array.from(buffer_1.Buffer.from(normalize(mnemonic), 'utf8'));
const saltBuffer = Uint8Array.from(buffer_1.Buffer.from(salt(normalize(password)), 'utf8'));
const res = (0, pbkdf2_1.pbkdf2)(sha512_1.sha512, mnemonicBuffer, saltBuffer, {
c: 2048,
dkLen: 64,
});
return buffer_1.Buffer.from(res);
};
exports.mnemonicToSeedSync = mnemonicToSeedSync;
/**
* Asynchronously convert a mnemonic to a seed.
*
* @param {string} mnemonic - The mnemonic phrase.
* @param {string} [password] - The optional password.
* @returns {Promise<Buffer>} The generated seed.
* @throws {Error} If an error occurs during the conversion.
*/
const mnemonicToSeed = async (mnemonic, password) => {
try {
const mnemonicBuffer = Uint8Array.from(buffer_1.Buffer.from(normalize(mnemonic), 'utf8'));
const saltBuffer = Uint8Array.from(buffer_1.Buffer.from(salt(normalize(password)), 'utf8'));
const res = await (0, pbkdf2_1.pbkdf2Async)(sha512_1.sha512, mnemonicBuffer, saltBuffer, {
c: 2048,
dkLen: 64,
});
return buffer_1.Buffer.from(res);
}
catch (e) {
throw new Error(e.message);
}
};
exports.mnemonicToSeed = mnemonicToSeed;
/**
* Convert a mnemonic to its corresponding entropy value.
*
* @param {string} mnemonic - The mnemonic phrase.
* @param {string[]} [wordlist] - The optional wordlist.
* @returns {string} The corresponding entropy.
* @throws {Error} If the mnemonic is invalid or a wordlist is required but not found.
*/
const mnemonicToEntropy = (mnemonic, wordlist) => {
wordlist = wordlist || DEFAULT_WORDLIST;
if (!wordlist) {
throw new Error(WORDLIST_REQUIRED);
}
const words = normalize(mnemonic).split(' ');
if (words.length % 3 !== 0) {
throw new Error(INVALID_MNEMONIC);
}
// convert word indices to 11 bit binary strings
const bits = words
.map((word) => {
const index = wordlist.indexOf(word);
if (index === -1) {
throw new Error(INVALID_MNEMONIC);
}
return lpad(index.toString(2), '0', 11);
})
.join('');
const dividerIndex = Math.floor(bits.length / 33) * 32;
const entropyBits = bits.slice(0, dividerIndex);
const checksumBits = bits.slice(dividerIndex);
const entropyBytes = entropyBits.match(/(.{1,8})/g).map(binaryToByte);
if (entropyBytes.length < 16) {
throw new Error(INVALID_ENTROPY);
}
if (entropyBytes.length > 32) {
throw new Error(INVALID_ENTROPY);
}
if (entropyBytes.length % 4 !== 0) {
throw new Error(INVALID_ENTROPY);
}
const entropy = buffer_1.Buffer.from(entropyBytes);
const newChecksum = deriveChecksumBits(entropy);
if (newChecksum !== checksumBits) {
throw new Error(INVALID_CHECKSUM);
}
return entropy.toString('hex');
};
exports.mnemonicToEntropy = mnemonicToEntropy;
/**
* Convert entropy to its corresponding mnemonic.
*
* @param {Buffer|string} entropy - The entropy value or buffer.
* @param {string[]} [wordlist] - The optional wordlist.
* @returns {string} The corresponding mnemonic phrase.
* @throws {Error} If the entropy is invalid or a wordlist is required but not found.
*/
const entropyToMnemonic = (entropy, wordlist) => {
if (!buffer_1.Buffer.isBuffer(entropy)) {
entropy = buffer_1.Buffer.from(entropy, 'hex');
}
wordlist = wordlist || DEFAULT_WORDLIST;
if (!wordlist) {
throw new Error(WORDLIST_REQUIRED);
}
if (entropy.length < 16) {
throw new TypeError(INVALID_ENTROPY);
}
if (entropy.length > 32) {
throw new TypeError(INVALID_ENTROPY);
}
if (entropy.length % 4 !== 0) {
throw new TypeError(INVALID_ENTROPY);
}
const entropyBits = bytesToBinary(Array.from(entropy));
const checksumBits = deriveChecksumBits(entropy);
const bits = entropyBits + checksumBits;
const chunks = bits.match(/(.{1,11})/g);
const words = chunks.map((binary) => {
const index = binaryToByte(binary);
return wordlist[index];
});
return wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093' // Japanese wordlist
? words.join('\u3000')
: words.join(' ');
};
exports.entropyToMnemonic = entropyToMnemonic;
/**
* Generate a mnemonic phrase with the specified strength (in bits).
*
* @param {number} strength - The strength of the mnemonic in bits.
* @param {Function} rng - The random number generator function.
* @param {string[]} [wordlist] - The optional wordlist.
* @returns {string} The generated mnemonic phrase.
* @throws {TypeError} If the strength is not divisible by 32.
*/
const generateMnemonic = (strength, rng, wordlist) => {
strength = strength || 128;
if (strength % 32 !== 0) {
throw new TypeError(INVALID_ENTROPY);
}
rng = rng || ((size) => buffer_1.Buffer.from((0, utils_1.randomBytes)(size)));
return (0, exports.entropyToMnemonic)(rng(strength / 8), wordlist);
};
exports.generateMnemonic = generateMnemonic;
/**
* Validate a mnemonic phrase.
*
* @param {string} mnemonic - The mnemonic phrase to validate.
* @param {string[]} [wordlist] - The optional wordlist.
* @returns {boolean} True if the mnemonic is valid, false otherwise.
*/
const validateMnemonic = (mnemonic, wordlist) => {
try {
(0, exports.mnemonicToEntropy)(mnemonic, wordlist);
}
catch (e) {
return false;
}
return true;
};
exports.validateMnemonic = validateMnemonic;
/**
* Get the currently set default wordlist.
*
* @returns {string} The language code of the default wordlist.
* @throws {Error} If the default wordlist is not set.
*/
const getDefaultWordlist = () => {
if (!DEFAULT_WORDLIST) {
throw new Error('No Default Wordlist set');
}
return Object.keys(_wordlists_1.wordlists).filter((lang) => {
if (lang === 'JA' || lang === 'EN') {
return false;
}
return _wordlists_1.wordlists[lang].every((word, index) => word === DEFAULT_WORDLIST[index]);
})[0];
};
exports.getDefaultWordlist = getDefaultWordlist;
//# sourceMappingURL=bip39.js.map