@ecash/lib
Version:
Library for eCash transaction building
123 lines • 4.7 kB
JavaScript
;
// Copyright (c) 2025 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
Object.defineProperty(exports, "__esModule", { value: true });
exports.mnemonicToSeed = exports.mnemonicToEntropy = exports.entropyToMnemonic = void 0;
const hash_1 = require("./hash");
const str_js_1 = require("./io/str.js");
const pbkdf2_1 = require("./pbkdf2");
const BITS_PER_BYTE = 8;
const BITS_PER_WORD = 11;
const BITS_PER_CHECKSUM_BIT = 32;
// Calculate how many bits there are in the mnemonic
function calcNumChecksumBits(numEntropyBytes) {
return (numEntropyBytes * BITS_PER_BYTE) / BITS_PER_CHECKSUM_BIT;
}
// Normalize according to unicode standard
function normalize(str) {
return (str || '').normalize('NFKD');
}
// Turn the password into a salt for seed generation
function salt(password) {
return 'mnemonic' + (password || '');
}
/** Derive the mnemonic from entropy */
function entropyToMnemonic(entropy, wordlist) {
if (entropy.length < 16 || entropy.length > 32) {
throw new TypeError('Entropy must be between 16 and 32 bytes long');
}
if (entropy.length % 4 !== 0) {
throw new TypeError('Entropy length must be divisible by 4');
}
const checksum = (0, hash_1.sha256)(entropy);
const data = new Uint8Array(entropy.length + checksum.length);
data.set(entropy, 0);
data.set(checksum, entropy.length);
let nextBits = 0;
let numBits = 0;
let numLeftoverBits = entropy.length * BITS_PER_BYTE + calcNumChecksumBits(entropy.length);
const words = [];
for (const byte of data) {
nextBits = (nextBits << BITS_PER_BYTE) | byte;
numBits += BITS_PER_BYTE;
if (numBits >= BITS_PER_WORD) {
const wordIdx = nextBits >> (numBits - BITS_PER_WORD);
words.push(wordlist.words[wordIdx]);
if (numLeftoverBits <= BITS_PER_WORD) {
break;
}
numBits -= BITS_PER_WORD;
numLeftoverBits -= BITS_PER_WORD;
nextBits &= 0x7ff >> (BITS_PER_WORD - numBits);
}
}
return words.join(wordlist.separator);
}
exports.entropyToMnemonic = entropyToMnemonic;
/** Recover the entropy from the mnemonic */
function mnemonicToEntropy(phrase, wordlist) {
const words = normalize(phrase).split(' ');
if (words.length < 12 || words.length > 24) {
throw new Error('Number of words in mnemonic phrase must be between 12 and 24');
}
if (words.length % 3 !== 0) {
throw new Error('Number of words in mnemonic phrase must be divisible by 3');
}
const wordIndices = words.map(word => {
const idx = wordlist.indexOf(word);
if (idx === -1) {
throw new Error('Invalid mnemonic phrase word: ' + word);
}
return idx;
});
const numEntropyBytes = (wordIndices.length / 3) * 4;
let nextBits = 0;
let numBits = 0;
let idx = 0;
const entropy = new Uint8Array(numEntropyBytes);
let checksum = 0;
for (const wordIdx of wordIndices) {
nextBits = (nextBits << BITS_PER_WORD) | wordIdx;
numBits += BITS_PER_WORD;
while (numBits >= BITS_PER_BYTE) {
const byte = nextBits >> (numBits - BITS_PER_BYTE);
if (idx < entropy.length) {
entropy[idx] = byte;
}
else {
checksum = (checksum << BITS_PER_BYTE) | byte;
}
idx++;
numBits -= BITS_PER_BYTE;
nextBits &= 0xffff >> (16 - numBits);
}
}
if (numBits != 0) {
checksum = (checksum << BITS_PER_BYTE) | nextBits;
}
const entropyHash = (0, hash_1.sha256)(entropy);
const numChecksumBits = calcNumChecksumBits(numEntropyBytes);
const expectedChecksum = entropyHash[0] >> (BITS_PER_BYTE - numChecksumBits);
if (checksum != expectedChecksum) {
const expected = expectedChecksum.toString(16);
const actual = checksum.toString(16);
throw new Error(`Invalid checksum: expected ${expected}, got ${actual}`);
}
return entropy;
}
exports.mnemonicToEntropy = mnemonicToEntropy;
/** Derive the seed bytes from the mnemonic */
function mnemonicToSeed(phrase, password) {
return (0, pbkdf2_1.pbkdf2)({
hashFactory: hash_1.sha512Hasher,
password: (0, str_js_1.strToBytes)(normalize(phrase)),
salt: (0, str_js_1.strToBytes)(salt(normalize(password))),
blockLength: 128,
outputLength: 64,
dkLen: 64,
iterations: 2048,
});
}
exports.mnemonicToSeed = mnemonicToSeed;
//# sourceMappingURL=mnemonic.js.map