UNPKG

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
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;