akachain-bip39
Version:
Akachain bip39 library for web browser
300 lines (260 loc) • 7.63 kB
JavaScript
;
var BN = require('./bignumber');
var unorm = require('unorm');
var pbkdf2 = require('./pbkdf2');
var crypto = require('crypto-js');
var Buffer = require('buffer').Buffer;
var isNumber = function(n) {
return (typeof n === 'number' || n instanceof Number)
}
var isString = function(str) {
return (typeof str === 'string' || str instanceof String)
}
var isArray = function(a) {
return (a instanceof Array);
}
var getRandomBuffer = function(size) {
var b32 = 0x100000000;
var b = new Buffer(size);
var r;
for (var i = 0; i <= size; i++) {
var j = Math.floor(i / 4);
var k = i - j * 4;
if (k === 0) {
r = Math.random() * b32;
b[i] = r & 0xff;
} else {
b[i] = (r = r >>> 8) & 0xff;
}
}
return b;
}
/**
* 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 (isString(data)) {
phrase = unorm.nfkd(data);
} else if (isNumber(data)) {
ent = data;
} else if (data) {
throw 'Data must be a string or an integer';
}
ent = ent || 128;
// check and detect wordlist
wordlist = wordlist || Mnemonic._getDictionary(phrase);
if (phrase && !wordlist) {
throw 'Unknow word list';
}
wordlist = wordlist || Mnemonic.Words.ENGLISH;
if (seed) {
phrase = Mnemonic._entropy2mnemonic(seed, wordlist);
}
// validate phrase and ent
if (phrase && !Mnemonic.isValid(phrase, wordlist)) {
throw 'Invalid mnemonic';
}
if (ent % 32 !== 0 || ent < 128 || ent > 256) {
throw 'Values must be ENT > 128 and ENT < 256 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 = new Buffer(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) {
return new Mnemonic(seed, wordlist);
};
/**
* 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 = getRandomBuffer(ENT / 8);
return Mnemonic._entropy2mnemonic(buf, wordlist);
};
/**
* Internal function to generate mnemonic based on entropy
*
* @param {Buffer} 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 'Invalid entropy';
}
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 {Buffer} entropy
* @returns {string} Checksum of entropy length / 32
* @private
*/
Mnemonic._entropyChecksum = function(entropy) {
var hash = crypto.SHA256(entropy);
var bits = entropy.length * 8;
var cs = bits / 32;
var hashbits = new BN(hash.toString(), 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;