alterdot-lib
Version:
A pure and powerful JavaScript Alterdot library.
308 lines (272 loc) • 8.12 kB
JavaScript
/* eslint-disable */
// TODO: Remove previous line and work through linting issues at next edit
;
var $ = require('../util/preconditions');
var errors = require('./errors');
var BN = require('../crypto/bn');
var unorm = require('unorm');
var _ = require('lodash');
var pbkdf2 = require('./pbkdf2');
var Hash = require('../crypto/hash');
var Random = require('../crypto/random');
var HDPrivateKey = require('../hdprivatekey');
/**
* This is an immutable class that represents a BIP39 Mnemonic code.
* See BIP39 specification for more info: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
* A Mnemonic code is a a group of easy to remember words used for the generation
* of deterministic wallets. A Mnemonic can be used to generate a seed using
* an optional passphrase, for later generate a HDPrivateKey.
*
* @example
* // generate a random mnemonic
* var mnemonic = new Mnemonic();
* var phrase = mnemonic.phrase;
*
* // use a different language
* var mnemonic = new Mnemonic(Mnemonic.Words.SPANISH);
* var xprivkey = mnemonic.toHDPrivateKey();
*
* @param {*=} data - a seed, phrase, or entropy to initialize (can be skipped)
* @param {Array=} wordlist - the wordlist to generate mnemonics from
* @returns {Mnemonic} A new instance of Mnemonic
* @constructor
*/
var Mnemonic = function (data, wordlist) {
if (!(this instanceof Mnemonic)) {
return new Mnemonic(data, wordlist);
}
if (_.isArray(data)) {
wordlist = data;
data = null;
}
// handle data overloading
var ent, phrase, seed;
if (Buffer.isBuffer(data)) {
seed = data;
} else if (_.isString(data)) {
phrase = unorm.nfkd(data);
} else if (_.isNumber(data)) {
ent = data;
} else if (data) {
throw new errors.InvalidArgument(
'data',
'Must be a Buffer, a string or an integer'
);
}
ent = ent || 128;
// check and detect wordlist
wordlist = wordlist || Mnemonic._getDictionary(phrase);
if (phrase && !wordlist) {
throw new errors.UnknownWordlist(phrase);
}
wordlist = wordlist || Mnemonic.Words.ENGLISH;
if (seed) {
phrase = Mnemonic._entropy2mnemonic(seed, wordlist);
}
// validate phrase and ent
if (phrase && !Mnemonic.isValid(phrase, wordlist)) {
throw new errors.InvalidMnemonic(phrase);
}
if (ent % 32 !== 0 || ent < 128) {
throw new errors.InvalidArgument(
'ENT',
'Values must be ENT > 128 and ENT % 32 == 0'
);
}
phrase = phrase || Mnemonic._mnemonic(ent, wordlist);
Object.defineProperty(this, 'wordlist', {
configurable: false,
value: wordlist,
});
Object.defineProperty(this, 'phrase', {
configurable: false,
value: phrase,
});
};
Mnemonic.Words = require('./words');
/**
* Will return a boolean if the mnemonic is valid
*
* @example
*
* var valid = Mnemonic.isValid('lab rescue lunch elbow recall phrase perfect donkey biology guess moment husband');
* // true
*
* @param {String} mnemonic - The mnemonic string
* @param {String} [wordlist] - The wordlist used
* @returns {boolean}
*/
Mnemonic.isValid = function (mnemonic, wordlist) {
mnemonic = unorm.nfkd(mnemonic);
wordlist = wordlist || Mnemonic._getDictionary(mnemonic);
if (!wordlist) {
return false;
}
var words = mnemonic.split(' ');
var bin = '';
for (var i = 0; i < words.length; i++) {
var ind = wordlist.indexOf(words[i]);
if (ind < 0) return false;
bin = bin + ('00000000000' + ind.toString(2)).slice(-11);
}
var cs = bin.length / 33;
var hash_bits = bin.slice(-cs);
var nonhash_bits = bin.slice(0, bin.length - cs);
var buf = Buffer.alloc(nonhash_bits.length / 8);
for (i = 0; i < nonhash_bits.length / 8; i++) {
buf.writeUInt8(parseInt(bin.slice(i * 8, (i + 1) * 8), 2), i);
}
var expected_hash_bits = Mnemonic._entropyChecksum(buf);
return expected_hash_bits === hash_bits;
};
/**
* Internal function to check if a mnemonic belongs to a wordlist.
*
* @param {String} mnemonic - The mnemonic string
* @param {String} wordlist - The wordlist
* @returns {boolean}
*/
Mnemonic._belongsToWordlist = function (mnemonic, wordlist) {
var words = unorm.nfkd(mnemonic).split(' ');
for (var i = 0; i < words.length; i++) {
var ind = wordlist.indexOf(words[i]);
if (ind < 0) return false;
}
return true;
};
/**
* Internal function to detect the wordlist used to generate the mnemonic.
*
* @param {String} mnemonic - The mnemonic string
* @returns {Array} the wordlist or null
*/
Mnemonic._getDictionary = function (mnemonic) {
if (!mnemonic) return null;
var dicts = Object.keys(Mnemonic.Words);
for (var i = 0; i < dicts.length; i++) {
var key = dicts[i];
if (Mnemonic._belongsToWordlist(mnemonic, Mnemonic.Words[key])) {
return Mnemonic.Words[key];
}
}
return null;
};
/**
* Will generate a seed based on the mnemonic and optional passphrase.
*
* @param {String} [passphrase]
* @returns {Buffer}
*/
Mnemonic.prototype.toSeed = function (passphrase) {
passphrase = passphrase || '';
return pbkdf2(
unorm.nfkd(this.phrase),
unorm.nfkd('mnemonic' + passphrase),
2048,
64
);
};
/**
* Will generate a Mnemonic object based on a seed.
*
* @param {Buffer} [seed]
* @param {string} [wordlist]
* @returns {Mnemonic}
*/
Mnemonic.fromSeed = function (seed, wordlist) {
$.checkArgument(Buffer.isBuffer(seed), 'seed must be a Buffer.');
$.checkArgument(
_.isArray(wordlist) || _.isString(wordlist),
'wordlist must be a string or an array.'
);
return new Mnemonic(seed, wordlist);
};
/**
*
* Generates a HD Private Key from a Mnemonic.
* Optionally receive a passphrase and bitcoin network.
*
* @param {String=} [passphrase]
* @param {Network|String|number=} [network] - The network: 'livenet' or 'testnet'
* @returns {HDPrivateKey}
*/
Mnemonic.prototype.toHDPrivateKey = function (passphrase, network) {
var seed = this.toSeed(passphrase);
return HDPrivateKey.fromSeed(seed, network);
};
/**
* Will return a the string representation of the mnemonic
*
* @returns {String} Mnemonic
*/
Mnemonic.prototype.toString = function () {
return this.phrase;
};
/**
* Will return a string formatted for the console
*
* @returns {String} Mnemonic
*/
Mnemonic.prototype.inspect = function () {
return '<Mnemonic: ' + this.toString() + ' >';
};
/**
* Internal function to generate a random mnemonic
*
* @param {Number} ENT - Entropy size, defaults to 128
* @param {Array} wordlist - Array of words to generate the mnemonic
* @returns {String} Mnemonic string
*/
Mnemonic._mnemonic = function (ENT, wordlist) {
var buf = Random.getRandomBuffer(ENT / 8);
return Mnemonic._entropy2mnemonic(buf, wordlist);
};
/**
* Internal function to generate mnemonic based on entropy
*
* @param {Number} entropy - Entropy buffer
* @param {Array} wordlist - Array of words to generate the mnemonic
* @returns {String} Mnemonic string
*/
Mnemonic._entropy2mnemonic = function (entropy, wordlist) {
var bin = '';
for (var 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 errors.InvalidEntropy(bin);
}
var mnemonic = [];
for (i = 0; i < bin.length / 11; i++) {
var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2);
mnemonic.push(wordlist[wi]);
}
var ret;
if (wordlist === Mnemonic.Words.JAPANESE) {
ret = mnemonic.join('\u3000');
} else {
ret = mnemonic.join(' ');
}
return ret;
};
/**
* Internal function to create checksum of entropy
*
* @param entropy
* @returns {string} Checksum of entropy length / 32
* @private
*/
Mnemonic._entropyChecksum = function (entropy) {
var hash = Hash.sha256(entropy);
var bits = entropy.length * 8;
var cs = bits / 32;
var hashbits = new BN(hash.toString('hex'), 16).toString(2);
// zero pad the hash bits
while (hashbits.length % 256 !== 0) {
hashbits = '0' + hashbits;
}
var checksum = hashbits.slice(0, cs);
return checksum;
};
module.exports = Mnemonic;