diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 7dd5aa76..afde473e 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -104,7 +104,7 @@ var unorm = require('../../vendor/unorm'); * be generated if not present). * @param {String?} options.passphrase - Optional salt for * key stretching (empty string if not present). - * @param {String?} options.lang - Language. + * @param {String?} options.language - Language. */ function Mnemonic(options) { @@ -118,7 +118,7 @@ function Mnemonic(options) { this.entropy = options.entropy; this.phrase = options.phrase; this.passphrase = options.passphrase || ''; - this.lang = options.lang || 'english'; + this.language = options.language || 'english'; this.seed = null; assert(this.bits >= 128); @@ -154,29 +154,40 @@ Mnemonic.prototype.toSeed = function toSeed() { */ Mnemonic.prototype.createMnemonic = function createMnemonic() { - var bin = ''; var mnemonic = []; - var wordlist = Mnemonic.getWordlist(this.lang); - var i, wi, hash, bits; + var bits = this.entropy.length * 8; + var wordlist = Mnemonic.getWordlist(this.language); + var i, j, word, entropy, oct, bit; - for (i = 0; i < this.entropy.length; i++) - bin += ('00000000' + this.entropy[i].toString(2)).slice(-8); + // Append the hash to the entropy to + // make things easy when grabbing + // the checksum bits. + entropy = Buffer.concat([ + this.entropy, + utils.sha256(this.entropy) + ]); - hash = utils.sha256(this.entropy); - bits = new bn(hash).toString(2); - while (bits.length % 256 !== 0) - bits = '0' + bits; + // Include the first `ENT / 32` bits + // of the hash (the checksum). + bits += bits / 32; - bin += bits.slice(0, (this.entropy.length * 8) / 32); - - assert(bin.length % 11 === 0); - - for (i = 0; i < bin.length / 11; i++) { - wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); - mnemonic.push(wordlist[wi]); + // Build the mnemonic by reading + // 11 bit indexes from the entropy. + for (i = 0; i < bits; i++) { + i--; + word = 0; + for (j = 0; j < 11; j++) { + i++; + oct = i / 8 | 0; + bit = i % 8; + word <<= 1; + word |= (entropy[oct] >>> (7 - bit)) & 1; + } + mnemonic.push(wordlist[word]); } - if (this.lang === 'japanese') + // Japanese likes double-width spaces. + if (this.language === 'japanese') return mnemonic.join('\u3000'); return mnemonic.join(' '); @@ -184,12 +195,12 @@ Mnemonic.prototype.createMnemonic = function createMnemonic() { /** * Retrieve the wordlist for a language. - * @param {String} lang + * @param {String} language * @returns {String[]} */ -Mnemonic.getWordlist = function getWordlist(lang) { - switch (lang) { +Mnemonic.getWordlist = function getWordlist(language) { + switch (language) { case 'simplified chinese': return require('../../etc/chinese-simplified.js'); case 'traditional chinese': @@ -203,7 +214,7 @@ Mnemonic.getWordlist = function getWordlist(lang) { case 'japanese': return require('../../etc/japanese.js'); default: - assert(false, 'Unknown language: ' + lang); + assert(false, 'Unknown language: ' + language); } }; @@ -656,7 +667,8 @@ HDPrivateKey.prototype.derivePath = function derivePath(path) { /** * Create an hd private key from a seed. - * @param {Buffer|Mnemonic|Object} options - A buffer, HD seed, or HD seed options. + * @param {Buffer|Mnemonic|Object} options - A buffer, + * HD seed, or HD seed options. * @param {String?} networkType * @returns {Object} A "naked" key (a * plain javascript object which is suitable diff --git a/test/mnemonic-test.js b/test/mnemonic-test.js index fc52884e..92a39a70 100644 --- a/test/mnemonic-test.js +++ b/test/mnemonic-test.js @@ -13,7 +13,7 @@ describe('Mnemonic', function() { var xpriv = data[3]; it('should create an english mnemonic (' + i + ')', function() { var mnemonic = new bcoin.hd.mnemonic({ - lang: 'english', + language: 'english', entropy: entropy, passphrase: 'TREZOR' }); @@ -32,7 +32,7 @@ describe('Mnemonic', function() { var xpriv = data.bip32_xprv; it('should create a japanese mnemonic (' + i + ')', function() { var mnemonic = new bcoin.hd.mnemonic({ - lang: 'japanese', + language: 'japanese', entropy: entropy, passphrase: passphrase });