From c5025dc1fdc919c214746cfc553e4860733c49c1 Mon Sep 17 00:00:00 2001 From: dabura667 Date: Tue, 22 Sep 2015 09:00:13 +0900 Subject: [PATCH 1/4] Support multiple wordlist matches (rare case) fixes https://github.com/bitpay/copay/issues/3211 --- lib/mnemonic.js | 62 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/lib/mnemonic.js b/lib/mnemonic.js index 82f90e764..c787f182b 100644 --- a/lib/mnemonic.js +++ b/lib/mnemonic.js @@ -62,6 +62,8 @@ 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.length == 1) wordlist = wordlist[0]; if (phrase && !wordlist) { throw new errors.UnknownWordlist(phrase); } @@ -71,14 +73,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); @@ -110,28 +122,34 @@ Mnemonic.Words = require('./words'); Mnemonic.isValid = function(mnemonic, wordlist) { mnemonic = unorm.nfkd(mnemonic); wordlist = wordlist || Mnemonic._getDictionary(mnemonic); + // turn wordlist into an array of wordlists, always + if (wordlist.length > 1000) wordlist = [wordlist]; 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; + // 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 +178,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; }; From f9e761398d65dba00a0fa0cd6c3a1854cd9a5c93 Mon Sep 17 00:00:00 2001 From: dabura667 Date: Tue, 22 Sep 2015 09:48:00 +0900 Subject: [PATCH 2/4] Fix errors for when wordlist is null --- lib/mnemonic.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/mnemonic.js b/lib/mnemonic.js index c787f182b..af8fef5f3 100644 --- a/lib/mnemonic.js +++ b/lib/mnemonic.js @@ -63,7 +63,9 @@ 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.length == 1) wordlist = wordlist[0]; + if (wordlist) { + if (wordlist.length == 1) wordlist = wordlist[0]; + } if (phrase && !wordlist) { throw new errors.UnknownWordlist(phrase); } @@ -74,16 +76,18 @@ var Mnemonic = function(data, 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 (phrase && wordlist) { + if (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); }; - if (!isvalid) throw new errors.InvalidMnemonic(phrase); // validate phrase and ent } else if (phrase && !Mnemonic.isValid(phrase, wordlist)) { throw new errors.InvalidMnemonic(phrase); @@ -122,12 +126,13 @@ Mnemonic.Words = require('./words'); Mnemonic.isValid = function(mnemonic, wordlist) { mnemonic = unorm.nfkd(mnemonic); wordlist = wordlist || Mnemonic._getDictionary(mnemonic); - // turn wordlist into an array of wordlists, always - if (wordlist.length > 1000) wordlist = [wordlist]; if (!wordlist) { return false; } + + // turn wordlist into an array of wordlists, always + if (wordlist.length > 1000) wordlist = [wordlist]; var words = mnemonic.split(' '); // loop through wordlists and check if one is true From 2bcfc8eba0e0bf0641e3585a71e0dc6a144b3cf4 Mon Sep 17 00:00:00 2001 From: dabura667 Date: Tue, 22 Sep 2015 09:57:30 +0900 Subject: [PATCH 3/4] Fix missing InvalidMnemonic error from tests --- lib/mnemonic.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/mnemonic.js b/lib/mnemonic.js index af8fef5f3..09b3723e2 100644 --- a/lib/mnemonic.js +++ b/lib/mnemonic.js @@ -76,18 +76,16 @@ var Mnemonic = function(data, wordlist) { } // if multiple wordlists matched (highly unlikely) - if (phrase && wordlist) { - if (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 (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); }; + if (!isvalid) throw new errors.InvalidMnemonic(phrase); // validate phrase and ent } else if (phrase && !Mnemonic.isValid(phrase, wordlist)) { throw new errors.InvalidMnemonic(phrase); From 648ca6e0a1f76ec318f85784110347ed40ad74f2 Mon Sep 17 00:00:00 2001 From: dabura667 Date: Tue, 22 Sep 2015 10:20:50 +0900 Subject: [PATCH 4/4] Add unit tests for multiple wordlist matches --- test/mnemonic.unit.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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();