lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
165 lines (164 loc) • 5.94 kB
JavaScript
import { BN } from '../crypto/bn.js';
import { Hash } from '../crypto/hash.js';
import { Random } from '../crypto/random.js';
import { Preconditions } from '../util/preconditions.js';
import { HDPrivateKey } from '../hdprivatekey.js';
import { pbkdf2 } from './pbkdf2.js';
import { Words } from './words/index.js';
function normalizeUnicode(str) {
return str.normalize('NFKD');
}
export class Mnemonic {
wordlist;
phrase;
constructor(data, wordlist) {
if (Array.isArray(data)) {
wordlist = data;
data = undefined;
}
let ent;
let phrase;
let seed;
if (Buffer.isBuffer(data)) {
seed = data;
ent = seed.length * 8;
}
else if (typeof data === 'string') {
phrase = normalizeUnicode(data);
}
else if (typeof data === 'number') {
ent = data;
}
else if (data) {
throw new Error('Invalid data: Must be a Buffer, a string or an integer');
}
ent = ent || 128;
const detectedWordlist = Mnemonic._getDictionary(phrase);
wordlist = wordlist || detectedWordlist || Words.ENGLISH;
if (phrase && !detectedWordlist) {
throw new Error(`Could not detect the used word list: ${phrase}`);
}
if (seed) {
phrase = Mnemonic._entropy2mnemonic(seed, wordlist);
}
if (phrase && !Mnemonic.isValid(phrase, wordlist)) {
throw new Error(`Mnemonic string is invalid: ${phrase}`);
}
if (!seed && ent && (ent % 32 !== 0 || ent < 128 || ent > 256)) {
throw new Error('Values must be ENT > 128 and ENT < 256 and ENT % 32 == 0');
}
phrase = phrase || Mnemonic._mnemonic(ent, wordlist);
phrase = normalizeUnicode(phrase);
this.wordlist = wordlist;
this.phrase = phrase;
}
static Words = Words;
static isValid(mnemonic, wordlist) {
mnemonic = normalizeUnicode(mnemonic);
const detectedWordlist = Mnemonic._getDictionary(mnemonic);
wordlist = wordlist || detectedWordlist;
if (!wordlist) {
return false;
}
const words = mnemonic.split(' ');
let bin = '';
for (let i = 0; i < words.length; i++) {
const ind = wordlist.indexOf(words[i]);
if (ind < 0)
return false;
bin = bin + ('00000000000' + ind.toString(2)).slice(-11);
}
const cs = bin.length / 33;
const hash_bits = bin.slice(-cs);
const nonhash_bits = bin.slice(0, bin.length - cs);
const buf = Buffer.alloc(nonhash_bits.length / 8);
for (let i = 0; i < nonhash_bits.length / 8; i++) {
buf.writeUInt8(parseInt(bin.slice(i * 8, (i + 1) * 8), 2), i);
}
const expected_hash_bits = Mnemonic._entropyChecksum(buf);
return expected_hash_bits === hash_bits;
}
static _belongsToWordlist(mnemonic, wordlist) {
const words = normalizeUnicode(mnemonic).split(' ');
for (let i = 0; i < words.length; i++) {
const ind = wordlist.indexOf(words[i]);
if (ind < 0)
return false;
}
return true;
}
static _getDictionary(mnemonic) {
if (!mnemonic)
return undefined;
const dicts = Object.keys(Words);
for (let i = 0; i < dicts.length; i++) {
const key = dicts[i];
const wordlist = Words[key];
if (Mnemonic._belongsToWordlist(mnemonic, wordlist)) {
return wordlist;
}
}
return undefined;
}
toSeed(passphrase) {
passphrase = passphrase || '';
return pbkdf2(normalizeUnicode(this.phrase), normalizeUnicode('mnemonic' + passphrase), 2048, 64);
}
static fromSeed(seed, wordlist) {
Preconditions.checkArgument(Buffer.isBuffer(seed), 'seed must be a Buffer.');
if (wordlist !== undefined) {
Preconditions.checkArgument(Array.isArray(wordlist) || typeof wordlist === 'string', 'wordlist must be a string or an array.');
}
return new Mnemonic(seed, wordlist);
}
toHDPrivateKey(passphrase, network) {
const seed = this.toSeed(passphrase);
return HDPrivateKey.fromSeed(seed, network);
}
toString() {
return this.phrase;
}
inspect() {
return '<Mnemonic: ' + this.toString() + ' >';
}
static _mnemonic(ENT, wordlist) {
const buf = Random.getRandomBuffer(ENT / 8);
return Mnemonic._entropy2mnemonic(buf, wordlist);
}
static _entropy2mnemonic(entropy, wordlist) {
let bin = '';
for (let i = 0; i < entropy.length; i++) {
bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8);
}
bin = bin + Mnemonic._entropyChecksum(entropy);
if (bin.length % 11 !== 0) {
throw new Error(`Entropy length must be an even multiple of 11 bits: ${bin}`);
}
const mnemonic = [];
for (let i = 0; i < bin.length / 11; i++) {
const wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2);
mnemonic.push(wordlist[wi]);
}
let ret;
if (wordlist === Words.ENGLISH) {
ret = mnemonic.join(' ');
}
else {
ret = mnemonic.join(' ');
}
return ret;
}
static _entropyChecksum(entropy) {
const hash = Hash.sha256(entropy);
const bits = entropy.length * 8;
const cs = bits / 32;
const hashbits = new BN(hash.toString('hex'), 16).toString(2);
let paddedHashbits = hashbits;
while (paddedHashbits.length % 256 !== 0) {
paddedHashbits = '0' + paddedHashbits;
}
const checksum = paddedHashbits.slice(0, cs);
return checksum;
}
}
export default Mnemonic;