diff --git a/lib/mnemonic.js b/lib/mnemonic.js index 82f90e764..09b3723e2 100644 --- a/lib/mnemonic.js +++ b/lib/mnemonic.js @@ -62,6 +62,10 @@ var Mnemonic = function(data, wordlist) { // check and detect wordlist wordlist = wordlist || Mnemonic._getDictionary(phrase); + // _getDictionary returns an array of wordlists, so if length is 1, remove the outer array + if (wordlist) { + if (wordlist.length == 1) wordlist = wordlist[0]; + } if (phrase && !wordlist) { throw new errors.UnknownWordlist(phrase); } @@ -71,14 +75,24 @@ var Mnemonic = function(data, wordlist) { phrase = Mnemonic._entropy2mnemonic(seed, wordlist); } - + // if multiple wordlists matched (highly unlikely) + if (phrase && wordlist.length > 1 && wordlist.length < 1000) { + var isvalid = false; + for (var i = 0; i < wordlist.length; i++) { + if (Mnemonic.isValid(phrase, wordlist[i])) { + isvalid = true; + wordlist = wordlist[i]; + break; + }; + }; + if (!isvalid) throw new errors.InvalidMnemonic(phrase); // validate phrase and ent - if (phrase && !Mnemonic.isValid(phrase, wordlist)) { + } else if (phrase && !Mnemonic.isValid(phrase, wordlist)) { throw new errors.InvalidMnemonic(phrase); - } + }; if (ent % 32 !== 0 || ent < 128) { throw new bitcore.errors.InvalidArgument('ENT', 'Values must be ENT > 128 and ENT % 32 == 0'); - } + }; phrase = phrase || Mnemonic._mnemonic(ent, wordlist); @@ -114,24 +128,31 @@ Mnemonic.isValid = function(mnemonic, wordlist) { if (!wordlist) { return false; } + + // turn wordlist into an array of wordlists, always + if (wordlist.length > 1000) wordlist = [wordlist]; 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; + // loop through wordlists and check if one is true + var isvalid = false; + for (var j = 0; j < wordlist.length; j++) { + var bin = ''; + for (var i = 0; i < words.length; i++) { + var ind = wordlist[j].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); + if (expected_hash_bits === hash_bits) isvalid = true; + }; + return isvalid; }; /** @@ -160,12 +181,14 @@ Mnemonic._getDictionary = function(mnemonic) { if (!mnemonic) return null; var dicts = Object.keys(Mnemonic.Words); + var matches = []; for (var i = 0; i < dicts.length; i++) { var key = dicts[i]; if (Mnemonic._belongsToWordlist(mnemonic, Mnemonic.Words[key])) { - return Mnemonic.Words[key]; + matches.push(Mnemonic.Words[key]); } } + if (matches.length > 0) return matches; return null; }; diff --git a/test/mnemonic.unit.js b/test/mnemonic.unit.js index e04c66338..57a78e791 100644 --- a/test/mnemonic.unit.js +++ b/test/mnemonic.unit.js @@ -67,6 +67,11 @@ describe('Mnemonic', function() { mnemonic.wordlist.should.equal(Mnemonic.Words.SPANISH); }); + it('constructor should detect standard wordlist even if all words contained in 2 wordlists', function() { + var mnemonic = new Mnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon brave'); + mnemonic.wordlist.should.equal(Mnemonic.Words.FRENCH); + }); + }); @@ -130,6 +135,17 @@ describe('Mnemonic', function() { var valid2 = Mnemonic.isValid('caution opprimer époque belote devenir ficeler filleul caneton apologie nectar frapper fouiller'); valid2.should.equal(true); }); + + it('validates phrases that match multiple wordlists', function() { + var valid = Mnemonic.isValid('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon brave'); + valid.should.equal(true); + + var invalid = Mnemonic.isValid('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon brave', Mnemonic.Words.ENGLISH); + invalid.should.equal(false); + + var valid2 = Mnemonic.isValid('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon brave', Mnemonic.Words.FRENCH); + valid2.should.equal(true); + }); it('has a toString method', function() { var mnemonic = new Mnemonic();