aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Coleman <ian@iancoleman.io>2020-10-01 23:44:38 +0000
committerIan Coleman <ian@iancoleman.io>2020-10-01 23:44:38 +0000
commitbf96267f89d18f278e78cf02c97ab1e7513fb871 (patch)
treef832e39596ae0f356a4ec1fe0f4f1e9e837b5a27
parent920f7aa0785f3d2fb7b08667ea371f349eb4bced (diff)
downloadBIP39-bf96267f89d18f278e78cf02c97ab1e7513fb871.tar.gz
BIP39-bf96267f89d18f278e78cf02c97ab1e7513fb871.tar.zst
BIP39-bf96267f89d18f278e78cf02c97ab1e7513fb871.zip
Remove bias from entropy in base 6 and base 10
-rw-r--r--src/index.html2
-rw-r--r--src/js/entropy.js345
-rw-r--r--src/js/index.js8
-rw-r--r--tests/spec/tests.js96
4 files changed, 216 insertions, 235 deletions
diff --git a/src/index.html b/src/index.html
index 7eb123c..f66d4ed 100644
--- a/src/index.html
+++ b/src/index.html
@@ -86,7 +86,7 @@
86 <div class="row"> 86 <div class="row">
87 <label class="col-sm-3 control-label">Entropy Type</label> 87 <label class="col-sm-3 control-label">Entropy Type</label>
88 <div class="type col-sm-3 form-control-static"></div> 88 <div class="type col-sm-3 form-control-static"></div>
89 <label class="col-sm-3 control-label">Bits Per Event</label> 89 <label class="col-sm-3 control-label">Avg Bits Per Event</label>
90 <div class="bits-per-event col-sm-3 form-control-static"></div> 90 <div class="bits-per-event col-sm-3 form-control-static"></div>
91 </div> 91 </div>
92 <div class="row"> 92 <div class="row">
diff --git a/src/js/entropy.js b/src/js/entropy.js
index 62b2711..3b62e10 100644
--- a/src/js/entropy.js
+++ b/src/js/entropy.js
@@ -16,7 +16,136 @@
16 16
17window.Entropy = new (function() { 17window.Entropy = new (function() {
18 18
19 var TWO = new libs.BigInteger.BigInteger(2); 19 let eventBits = {
20
21 "binary": {
22 "0": "0",
23 "1": "1",
24 },
25
26 // log2(6) = 2.58496 bits per roll, with bias
27 // 4 rolls give 2 bits each
28 // 2 rolls give 1 bit each
29 // Average (4*2 + 2*1) / 6 = 1.66 bits per roll without bias
30 "base 6": {
31 "0": "00",
32 "1": "01",
33 "2": "10",
34 "3": "11",
35 "4": "0",
36 "5": "1",
37 },
38
39 // log2(6) = 2.58496 bits per roll, with bias
40 // 4 rolls give 2 bits each
41 // 2 rolls give 1 bit each
42 // Average (4*2 + 2*1) / 6 = 1.66 bits per roll without bias
43 "base 6 (dice)": {
44 "0": "00", // equivalent to 0 in base 6
45 "1": "01",
46 "2": "10",
47 "3": "11",
48 "4": "0",
49 "5": "1",
50 },
51
52 // log2(10) = 3.321928 bits per digit, with bias
53 // 8 digits give 3 bits each
54 // 2 digits give 1 bit each
55 // Average (8*3 + 2*1) / 10 = 2.6 bits per digit without bias
56 "base 10": {
57 "0": "000",
58 "1": "001",
59 "2": "010",
60 "3": "011",
61 "4": "100",
62 "5": "101",
63 "6": "110",
64 "7": "111",
65 "8": "0",
66 "9": "1",
67 },
68
69 "hexadecimal": {
70 "0": "0000",
71 "1": "0001",
72 "2": "0010",
73 "3": "0011",
74 "4": "0100",
75 "5": "0101",
76 "6": "0110",
77 "7": "0111",
78 "8": "1000",
79 "9": "1001",
80 "a": "1010",
81 "b": "1011",
82 "c": "1100",
83 "d": "1101",
84 "e": "1110",
85 "f": "1111",
86 },
87
88 // log2(52) = 5.7004 bits per card, with bias
89 // 32 cards give 5 bits each
90 // 16 cards give 4 bits each
91 // 4 cards give 2 bits each
92 // Average (32*5 + 16*4 + 4*2) / 52 = 4.46 bits per card without bias
93 "card": {
94 "ac": "00000",
95 "2c": "00001",
96 "3c": "00010",
97 "4c": "00011",
98 "5c": "00100",
99 "6c": "00101",
100 "7c": "00110",
101 "8c": "00111",
102 "9c": "01000",
103 "tc": "01001",
104 "jc": "01010",
105 "qc": "01011",
106 "kc": "01100",
107 "ad": "01101",
108 "2d": "01110",
109 "3d": "01111",
110 "4d": "10000",
111 "5d": "10001",
112 "6d": "10010",
113 "7d": "10011",
114 "8d": "10100",
115 "9d": "10101",
116 "td": "10110",
117 "jd": "10111",
118 "qd": "11000",
119 "kd": "11001",
120 "ah": "11010",
121 "2h": "11011",
122 "3h": "11100",
123 "4h": "11101",
124 "5h": "11110",
125 "6h": "11111",
126 "7h": "0000",
127 "8h": "0001",
128 "9h": "0010",
129 "th": "0011",
130 "jh": "0100",
131 "qh": "0101",
132 "kh": "0110",
133 "as": "0111",
134 "2s": "1000",
135 "3s": "1001",
136 "4s": "1010",
137 "5s": "1011",
138 "6s": "1100",
139 "7s": "1101",
140 "8s": "1110",
141 "9s": "1111",
142 "ts": "00",
143 "js": "01",
144 "qs": "10",
145 "ks": "11",
146 },
147
148 }
20 149
21 // matchers returns an array of the matched events for each type of entropy. 150 // matchers returns an array of the matched events for each type of entropy.
22 // eg 151 // eg
@@ -51,48 +180,28 @@ window.Entropy = new (function() {
51 } 180 }
52 } 181 }
53 182
54 // Convert array of cards from ["ac", "4d", "ks"]
55 // to numbers between 0 and 51 [0, 16, 51]
56 function convertCardsToInts(cards) {
57 var ints = [];
58 var values = "a23456789tjqk";
59 var suits = "cdhs";
60 for (var i=0; i<cards.length; i++) {
61 var card = cards[i].toLowerCase();
62 var value = card[0];
63 var suit = card[1];
64 var asInt = 13 * suits.indexOf(suit) + values.indexOf(value);
65 ints.push(asInt);
66 }
67 return ints;
68 }
69
70 this.fromString = function(rawEntropyStr, baseStr) { 183 this.fromString = function(rawEntropyStr, baseStr) {
71 // Find type of entropy being used (binary, hex, dice etc) 184 // Find type of entropy being used (binary, hex, dice etc)
72 var base = getBase(rawEntropyStr, baseStr); 185 var base = getBase(rawEntropyStr, baseStr);
73 // Convert dice to base6 entropy (ie 1-6 to 0-5) 186 // Convert dice to base6 entropy (ie 1-6 to 0-5)
74 // This is done by changing all 6s to 0s 187 // This is done by changing all 6s to 0s
75 if (base.str == "dice") { 188 if (base.str == "dice") {
76 var newParts = []; 189 var newEvents = [];
77 var newInts = []; 190 for (var i=0; i<base.events.length; i++) {
78 for (var i=0; i<base.parts.length; i++) { 191 var c = base.events[i];
79 var c = base.parts[i];
80 if ("12345".indexOf(c) > -1) { 192 if ("12345".indexOf(c) > -1) {
81 newParts[i] = base.parts[i]; 193 newEvents[i] = base.events[i];
82 newInts[i] = base.ints[i];
83 } 194 }
84 else { 195 else {
85 newParts[i] = "0"; 196 newEvents[i] = "0";
86 newInts[i] = 0;
87 } 197 }
88 } 198 }
89 base.str = "base 6 (dice)"; 199 base.str = "base 6 (dice)";
90 base.ints = newInts; 200 base.events = newEvents;
91 base.parts = newParts;
92 base.matcher = matchers.base6; 201 base.matcher = matchers.base6;
93 } 202 }
94 // Detect empty entropy 203 // Detect empty entropy
95 if (base.parts.length == 0) { 204 if (base.events.length == 0) {
96 return { 205 return {
97 binaryStr: "", 206 binaryStr: "",
98 cleanStr: "", 207 cleanStr: "",
@@ -100,44 +209,23 @@ window.Entropy = new (function() {
100 base: base, 209 base: base,
101 }; 210 };
102 } 211 }
103 // Convert base.ints to BigInteger. 212 // Convert entropy events to binary
104 // Due to using unusual bases, eg cards of base52, this is not as simple as 213 var entropyBin = base.events.map(function(e) {
105 // using BigInteger.parse() 214 return eventBits[base.str][e.toLowerCase()];
106 var entropyInt = libs.BigInteger.BigInteger.ZERO; 215 }).join("");
107 for (var i=base.ints.length-1; i>=0; i--) { 216 // Get average bits per event
108 var thisInt = libs.BigInteger.BigInteger.parse(base.ints[i]); 217 // which may be adjusted for bias if log2(base) is fractional
109 var power = (base.ints.length - 1) - i; 218 var bitsPerEvent = base.bitsPerEvent;
110 var additionalEntropy = libs.BigInteger.BigInteger.parse(base.asInt).pow(power).multiply(thisInt);
111 entropyInt = entropyInt.add(additionalEntropy);
112 }
113 // Convert entropy to binary
114 var entropyBin = entropyInt.toString(2);
115 // If the first integer is small, it must be padded with zeros.
116 // Otherwise the chance of the first bit being 1 is 100%, which is
117 // obviously incorrect.
118 // This is not perfect for non-2^n bases.
119 var expectedBits = Math.floor(base.parts.length * Math.log2(base.asInt));
120 while (entropyBin.length < expectedBits) {
121 entropyBin = "0" + entropyBin;
122 }
123 // Calculate the number of bits per event
124 var bitsPerEvent = Math.log2(base.asInt);
125 // Cards binary must be handled differently, since they're not replaced
126 if (base.asInt == 52) {
127 var cardEntropy = processCardEntropy(base.parts);
128 entropyBin = cardEntropy.binaryStr;
129 bitsPerEvent = cardEntropy.bitsPerEvent;
130 }
131 // Supply a 'filtered' entropy string for display purposes 219 // Supply a 'filtered' entropy string for display purposes
132 var entropyClean = base.parts.join(""); 220 var entropyClean = base.events.join("");
133 var entropyHtml = base.parts.join(""); 221 var entropyHtml = base.events.join("");
134 if (base.asInt == 52) { 222 if (base.asInt == 52) {
135 entropyClean = base.parts.join(" ").toUpperCase(); 223 entropyClean = base.events.join(" ").toUpperCase();
136 entropyClean = entropyClean.replace(/C/g, "\u2663"); 224 entropyClean = entropyClean.replace(/C/g, "\u2663");
137 entropyClean = entropyClean.replace(/D/g, "\u2666"); 225 entropyClean = entropyClean.replace(/D/g, "\u2666");
138 entropyClean = entropyClean.replace(/H/g, "\u2665"); 226 entropyClean = entropyClean.replace(/H/g, "\u2665");
139 entropyClean = entropyClean.replace(/S/g, "\u2660"); 227 entropyClean = entropyClean.replace(/S/g, "\u2660");
140 entropyHtml = base.parts.join(" ").toUpperCase(); 228 entropyHtml = base.events.join(" ").toUpperCase();
141 entropyHtml = entropyHtml.replace(/C/g, "<span class='card-suit club'>\u2663</span>"); 229 entropyHtml = entropyHtml.replace(/C/g, "<span class='card-suit club'>\u2663</span>");
142 entropyHtml = entropyHtml.replace(/D/g, "<span class='card-suit diamond'>\u2666</span>"); 230 entropyHtml = entropyHtml.replace(/D/g, "<span class='card-suit diamond'>\u2666</span>");
143 entropyHtml = entropyHtml.replace(/H/g, "<span class='card-suit heart'>\u2665</span>"); 231 entropyHtml = entropyHtml.replace(/H/g, "<span class='card-suit heart'>\u2665</span>");
@@ -154,18 +242,6 @@ window.Entropy = new (function() {
154 return e; 242 return e;
155 } 243 }
156 244
157 function getSortedDeck() {
158 var s = [];
159 var suits = "CDHS";
160 var values = "A23456789TJQK";
161 for (var i=0; i<suits.length; i++) {
162 for (var j=0; j<values.length; j++) {
163 s.push(values[j]+suits[i]);
164 }
165 }
166 return s;
167 }
168
169 function getBase(str, baseStr) { 245 function getBase(str, baseStr) {
170 // Need to get the lowest base for the supplied entropy. 246 // Need to get the lowest base for the supplied entropy.
171 // This prevents interpreting, say, dice rolls as hexadecimal. 247 // This prevents interpreting, say, dice rolls as hexadecimal.
@@ -177,20 +253,21 @@ window.Entropy = new (function() {
177 var ints = binaryMatches.map(function(i) { return parseInt(i, 2) }); 253 var ints = binaryMatches.map(function(i) { return parseInt(i, 2) });
178 return { 254 return {
179 ints: ints, 255 ints: ints,
180 parts: binaryMatches, 256 events: binaryMatches,
181 matcher: matchers.binary, 257 matcher: matchers.binary,
182 asInt: 2, 258 asInt: 2,
259 bitsPerEvent: 1,
183 str: "binary", 260 str: "binary",
184 } 261 }
185 } 262 }
186 var cardMatches = matchers.card(str); 263 var cardMatches = matchers.card(str);
187 if ((cardMatches.length >= hexMatches.length / 2 && autodetect) || baseStr === "card") { 264 if ((cardMatches.length >= hexMatches.length / 2 && autodetect) || baseStr === "card") {
188 var ints = convertCardsToInts(cardMatches);
189 return { 265 return {
190 ints: ints, 266 ints: ints,
191 parts: cardMatches, 267 events: cardMatches,
192 matcher: matchers.card, 268 matcher: matchers.card,
193 asInt: 52, 269 asInt: 52,
270 bitsPerEvent: (32*5 + 16*4 + 4*2) / 52, // see cardBits
194 str: "card", 271 str: "card",
195 } 272 }
196 } 273 }
@@ -199,9 +276,10 @@ window.Entropy = new (function() {
199 var ints = diceMatches.map(function(i) { return parseInt(i) }); 276 var ints = diceMatches.map(function(i) { return parseInt(i) });
200 return { 277 return {
201 ints: ints, 278 ints: ints,
202 parts: diceMatches, 279 events: diceMatches,
203 matcher: matchers.dice, 280 matcher: matchers.dice,
204 asInt: 6, 281 asInt: 6,
282 bitsPerEvent: (4*2 + 2*1) / 6, // see diceBits
205 str: "dice", 283 str: "dice",
206 } 284 }
207 } 285 }
@@ -210,9 +288,10 @@ window.Entropy = new (function() {
210 var ints = base6Matches.map(function(i) { return parseInt(i) }); 288 var ints = base6Matches.map(function(i) { return parseInt(i) });
211 return { 289 return {
212 ints: ints, 290 ints: ints,
213 parts: base6Matches, 291 events: base6Matches,
214 matcher: matchers.base6, 292 matcher: matchers.base6,
215 asInt: 6, 293 asInt: 6,
294 bitsPerEvent: (4*2 + 2*1) / 6, // see diceBits
216 str: "base 6", 295 str: "base 6",
217 } 296 }
218 } 297 }
@@ -221,126 +300,22 @@ window.Entropy = new (function() {
221 var ints = base10Matches.map(function(i) { return parseInt(i) }); 300 var ints = base10Matches.map(function(i) { return parseInt(i) });
222 return { 301 return {
223 ints: ints, 302 ints: ints,
224 parts: base10Matches, 303 events: base10Matches,
225 matcher: matchers.base10, 304 matcher: matchers.base10,
226 asInt: 10, 305 asInt: 10,
306 bitsPerEvent: (8*3 + 2*1) / 10, // see b10Bits
227 str: "base 10", 307 str: "base 10",
228 } 308 }
229 } 309 }
230 var ints = hexMatches.map(function(i) { return parseInt(i, 16) }); 310 var ints = hexMatches.map(function(i) { return parseInt(i, 16) });
231 return { 311 return {
232 ints: ints, 312 ints: ints,
233 parts: hexMatches, 313 events: hexMatches,
234 matcher: matchers.hex, 314 matcher: matchers.hex,
235 asInt: 16, 315 asInt: 16,
316 bitsPerEvent: 4,
236 str: "hexadecimal", 317 str: "hexadecimal",
237 } 318 }
238 } 319 }
239 320
240 // Assume cards are NOT replaced.
241 // Additional entropy decreases as more cards are used. This means
242 // total possible entropy is measured using n!, not base^n.
243 // eg the second last card can be only one of two, not one of fifty two
244 // so the added entropy for that card is only one bit at most
245 function processCardEntropy(cards) {
246 // Track how many instances of each card have been used, and thus
247 // how many decks are in use.
248 var cardCounts = {};
249 var numberOfDecks = 0;
250 // Work out number of decks by max(duplicates)
251 for (var i=0; i<cards.length; i++) {
252 // Get the card that was drawn
253 var cardLower = cards[i];
254 var card = cardLower.toUpperCase();
255 // Initialize the count for this card if needed
256 if (!(card in cardCounts)) {
257 cardCounts[card] = 0;
258 }
259 cardCounts[card] += 1;
260 // See if this is max(duplicates)
261 if (cardCounts[card] > numberOfDecks) {
262 numberOfDecks = cardCounts[card];
263 }
264 }
265 // Work out the total number of bits for this many decks
266 // See http://crypto.stackexchange.com/q/41886
267 var gainedBits = 0;
268 // Equivalent of Math.log2(factorial(52*numberOfDecks))
269 // which becomes infinity for numberOfDecks > 4
270 for (var i=1; i<=52*numberOfDecks; i++) {
271 gainedBits = gainedBits + Math.log2(i);
272 }
273 var lostBits = 52 * Math.log2(factorial(numberOfDecks));
274 var maxBits = gainedBits - lostBits;
275 // Convert the drawn cards to a binary representation.
276 // The exact technique for doing this is unclear.
277 // See
278 // http://crypto.stackexchange.com/a/41896
279 // "I even doubt that this is well defined (only the average entropy
280 // is, I believe)."
281 // See
282 // https://github.com/iancoleman/bip39/issues/33#issuecomment-263021856
283 // "The binary representation can be the first log(permutations,2) bits
284 // of the sha-2 hash of the normalized deck string."
285 //
286 // In this specific implementation, the first N bits of the hash of the
287 // normalized cards string is being used. Uppercase, no spaces; eg
288 // sha256("AH8DQSTC2H")
289 var totalCards = numberOfDecks * 52;
290 var percentUsed = cards.length / totalCards;
291 // Calculate the average number of bits of entropy for the number of
292 // cards drawn.
293 var numberOfBits = Math.floor(maxBits * percentUsed);
294 // Create a normalized string of the selected cards
295 var normalizedCards = cards.join("").toUpperCase();
296 // Convert to binary using the SHA256 hash of the normalized cards.
297 // If the number of bits is more than 256, multiple hashes
298 // are used until the required number of bits is reached.
299 var entropyBin = "";
300 var iterations = 0;
301 while (entropyBin.length < numberOfBits) {
302 var hashedCards = sjcl.hash.sha256.hash(normalizedCards + ":" + iterations);
303 var hashHex = sjcl.codec.hex.fromBits(hashedCards);
304 for (var i=0; i<hashHex.length; i++) {
305 var decimal = parseInt(hashHex[i], 16);
306 var binary = decimal.toString(2);
307 while (binary.length < 4) {
308 binary = "0" + binary;
309 }
310 entropyBin = entropyBin + binary;
311 }
312 iterations = iterations + 1;
313 }
314 // Truncate to the appropriate number of bits.
315 entropyBin = entropyBin.substring(0, numberOfBits);
316 // Get the number of bits per event
317 bitsPerEvent = maxBits / totalCards;
318 return {
319 binaryStr: entropyBin,
320 bitsPerEvent: bitsPerEvent,
321 }
322 }
323
324 // Polyfill for Math.log2
325 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill
326 Math.log2 = Math.log2 || function(x) {
327 // The polyfill isn't good enough because of the poor accuracy of
328 // Math.LOG2E
329 // log2(8) gave 2.9999999999999996 which when floored causes issues.
330 // So instead use the BigInteger library to get it right.
331 return libs.BigInteger.BigInteger.log(x) / libs.BigInteger.BigInteger.log(2);
332 };
333
334 // Depends on BigInteger
335 function factorial(n) {
336 if (n == 0) {
337 return 1;
338 }
339 f = libs.BigInteger.BigInteger.ONE;
340 for (var i=1; i<=n; i++) {
341 f = f.multiply(new libs.BigInteger.BigInteger(i));
342 }
343 return f;
344 }
345
346})(); 321})();
diff --git a/src/js/index.js b/src/js/index.js
index 252eec1..b070006 100644
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -1726,7 +1726,7 @@
1726 var numberOfBits = entropy.binaryStr.length; 1726 var numberOfBits = entropy.binaryStr.length;
1727 var timeToCrack = "unknown"; 1727 var timeToCrack = "unknown";
1728 try { 1728 try {
1729 var z = libs.zxcvbn(entropy.base.parts.join("")); 1729 var z = libs.zxcvbn(entropy.base.events.join(""));
1730 timeToCrack = z.crack_times_display.offline_fast_hashing_1e10_per_second; 1730 timeToCrack = z.crack_times_display.offline_fast_hashing_1e10_per_second;
1731 if (z.feedback.warning != "") { 1731 if (z.feedback.warning != "") {
1732 timeToCrack = timeToCrack + " - " + z.feedback.warning; 1732 timeToCrack = timeToCrack + " - " + z.feedback.warning;
@@ -1745,7 +1745,7 @@
1745 DOM.entropyFiltered.html(entropy.cleanHtml); 1745 DOM.entropyFiltered.html(entropy.cleanHtml);
1746 DOM.entropyType.text(entropyTypeStr); 1746 DOM.entropyType.text(entropyTypeStr);
1747 DOM.entropyCrackTime.text(timeToCrack); 1747 DOM.entropyCrackTime.text(timeToCrack);
1748 DOM.entropyEventCount.text(entropy.base.ints.length); 1748 DOM.entropyEventCount.text(entropy.base.events.length);
1749 DOM.entropyBits.text(numberOfBits); 1749 DOM.entropyBits.text(numberOfBits);
1750 DOM.entropyWordCount.text(wordCount); 1750 DOM.entropyWordCount.text(wordCount);
1751 DOM.entropyBinary.text(spacedBinaryStr); 1751 DOM.entropyBinary.text(spacedBinaryStr);
@@ -1770,8 +1770,8 @@
1770 // Detect duplicates 1770 // Detect duplicates
1771 var dupes = []; 1771 var dupes = [];
1772 var dupeTracker = {}; 1772 var dupeTracker = {};
1773 for (var i=0; i<entropy.base.parts.length; i++) { 1773 for (var i=0; i<entropy.base.events.length; i++) {
1774 var card = entropy.base.parts[i]; 1774 var card = entropy.base.events[i];
1775 var cardUpper = card.toUpperCase(); 1775 var cardUpper = card.toUpperCase();
1776 if (cardUpper in dupeTracker) { 1776 if (cardUpper in dupeTracker) {
1777 dupes.push(card); 1777 dupes.push(card);
diff --git a/tests/spec/tests.js b/tests/spec/tests.js
index 14cdb8e..92df64a 100644
--- a/tests/spec/tests.js
+++ b/tests/spec/tests.js
@@ -3120,7 +3120,7 @@ it("Shows the number of bits of entropy for 4 bits of binary", function(done) {
3120 testEntropyBits(done, "0000", "4"); 3120 testEntropyBits(done, "0000", "4");
3121}); 3121});
3122it("Shows the number of bits of entropy for 1 character of base 6 (dice)", function(done) { 3122it("Shows the number of bits of entropy for 1 character of base 6 (dice)", function(done) {
3123 // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits) 3123 // 6 in card is 0 in base 6, 0 is mapped to 00 by entropy.js
3124 testEntropyBits(done, "6", "2"); 3124 testEntropyBits(done, "6", "2");
3125}); 3125});
3126it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits", function(done) { 3126it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits", function(done) {
@@ -3128,13 +3128,15 @@ it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits",
3128 testEntropyBits(done, "7", "3"); 3128 testEntropyBits(done, "7", "3");
3129}); 3129});
3130it("Shows the number of bits of entropy for 1 character of base 10 with 4 bis", function(done) { 3130it("Shows the number of bits of entropy for 1 character of base 10 with 4 bis", function(done) {
3131 testEntropyBits(done, "8", "4"); 3131 // 8 in base 10 is mapped to 0 by entropy.js
3132 testEntropyBits(done, "8", "1");
3132}); 3133});
3133it("Shows the number of bits of entropy for 1 character of hex", function(done) { 3134it("Shows the number of bits of entropy for 1 character of hex", function(done) {
3134 testEntropyBits(done, "F", "4"); 3135 testEntropyBits(done, "F", "4");
3135}); 3136});
3136it("Shows the number of bits of entropy for 2 characters of base 10", function(done) { 3137it("Shows the number of bits of entropy for 2 characters of base 10", function(done) {
3137 testEntropyBits(done, "29", "6"); 3138 // 2 as base 10 is binary 010, 9 is mapped to binary 1 by entropy.js
3139 testEntropyBits(done, "29", "4");
3138}); 3140});
3139it("Shows the number of bits of entropy for 2 characters of hex", function(done) { 3141it("Shows the number of bits of entropy for 2 characters of hex", function(done) {
3140 testEntropyBits(done, "0A", "8"); 3142 testEntropyBits(done, "0A", "8");
@@ -3159,17 +3161,17 @@ it("Shows the number of bits of entropy for 4 characters of hex with leading zer
3159 testEntropyBits(done, "000A", "16"); 3161 testEntropyBits(done, "000A", "16");
3160}); 3162});
3161it("Shows the number of bits of entropy for 4 characters of base 6", function(done) { 3163it("Shows the number of bits of entropy for 4 characters of base 6", function(done) {
3162 testEntropyBits(done, "5555", "11"); 3164 // 5 in base 6 is mapped to binary 1
3165 testEntropyBits(done, "5555", "4");
3163}); 3166});
3164it("Shows the number of bits of entropy for 4 characters of base 6 dice", function(done) { 3167it("Shows the number of bits of entropy for 4 characters of base 6 dice", function(done) {
3165 // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of 3168 // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of
3166 // 2.58 bits, which is 10.32 bits (rounded down to 10 bits) 3169 // binary 00
3167 testEntropyBits(done, "6666", "10"); 3170 testEntropyBits(done, "6666", "8");
3168}); 3171});
3169it("Shows the number of bits of entropy for 4 charactes of base 10", function(done) { 3172it("Shows the number of bits of entropy for 4 charactes of base 10", function(done) {
3170 // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded 3173 // 2 in base 10 is binary 010 and 7 is binary 111 so is 4 events of 3 bits
3171 // down to 13) 3174 testEntropyBits(done, "2227", "12");
3172 testEntropyBits(done, "2227", "13");
3173}); 3175});
3174it("Shows the number of bits of entropy for 4 characters of hex with 2 leading zeros", function(done) { 3176it("Shows the number of bits of entropy for 4 characters of hex with 2 leading zeros", function(done) {
3175 testEntropyBits(done, "222F", "16"); 3177 testEntropyBits(done, "222F", "16");
@@ -3178,13 +3180,16 @@ it("Shows the number of bits of entropy for 4 characters of hex starting with F"
3178 testEntropyBits(done, "FFFF", "16"); 3180 testEntropyBits(done, "FFFF", "16");
3179}); 3181});
3180it("Shows the number of bits of entropy for 10 characters of base 10", function(done) { 3182it("Shows the number of bits of entropy for 10 characters of base 10", function(done) {
3181 // 10 events at 3.32 bits per event 3183 // 10 events with 3 bits for each event
3182 testEntropyBits(done, "0000101017", "33"); 3184 testEntropyBits(done, "0000101017", "30");
3185});
3186it("Shows the number of bits of entropy for 10 characters of base 10 account for bias", function(done) {
3187 // 9 events with 3 bits per event and 1 event with 1 bit per event
3188 testEntropyBits(done, "0000101018", "28");
3183}); 3189});
3184it("Shows the number of bits of entropy for a full deck of cards", function(done) { 3190it("Shows the number of bits of entropy for a full deck of cards", function(done) {
3185 // cards are not replaced, so a full deck is not 52^52 entropy which is 296 3191 // removing bias is 32*5 + 16*4 + 4*2
3186 // bits, it's 52!, which is 225 bits 3192 testEntropyBits(done, "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "232");
3187 testEntropyBits(done, "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225");
3188}); 3193});
3189 3194
3190it("Shows details about the entered entropy", function(done) { 3195it("Shows details about the entered entropy", function(done) {
@@ -3310,7 +3315,7 @@ it("Shows details about the entered entropy", function(done) {
3310 entropy: "7d", 3315 entropy: "7d",
3311 type: "card", 3316 type: "card",
3312 events: "1", 3317 events: "1",
3313 bits: "4", 3318 bits: "5",
3314 words: 0, 3319 words: 0,
3315 strength: "less than a second", 3320 strength: "less than a second",
3316 } 3321 }
@@ -3322,7 +3327,7 @@ it("Shows details about the entered entropy", function(done) {
3322 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3327 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3323 type: "card (full deck)", 3328 type: "card (full deck)",
3324 events: "52", 3329 events: "52",
3325 bits: "225", 3330 bits: "232",
3326 words: 21, 3331 words: 21,
3327 strength: "centuries", 3332 strength: "centuries",
3328 } 3333 }
@@ -3334,7 +3339,7 @@ it("Shows details about the entered entropy", function(done) {
3334 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d", 3339 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
3335 type: "card (full deck, 1 duplicate: 3d)", 3340 type: "card (full deck, 1 duplicate: 3d)",
3336 events: "53", 3341 events: "53",
3337 bits: "254", 3342 bits: "237",
3338 words: 21, 3343 words: 21,
3339 strength: "centuries", 3344 strength: "centuries",
3340 } 3345 }
@@ -3346,7 +3351,7 @@ it("Shows details about the entered entropy", function(done) {
3346 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d", 3351 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
3347 type: "card (2 duplicates: 3d 4d, 1 missing: KS)", 3352 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
3348 events: "53", 3353 events: "53",
3349 bits: "254", 3354 bits: "240",
3350 words: 21, 3355 words: 21,
3351 strength: "centuries", 3356 strength: "centuries",
3352 } 3357 }
@@ -3358,8 +3363,8 @@ it("Shows details about the entered entropy", function(done) {
3358 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d", 3363 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
3359 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)", 3364 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
3360 events: "55", 3365 events: "55",
3361 bits: "264", 3366 bits: "250",
3362 words: 24, 3367 words: 21,
3363 strength: "centuries", 3368 strength: "centuries",
3364 } 3369 }
3365 ); 3370 );
@@ -3367,13 +3372,12 @@ it("Shows details about the entered entropy", function(done) {
3367it("Shows details about the entered entropy", function(done) { 3372it("Shows details about the entered entropy", function(done) {
3368 testEntropyFeedback(done, 3373 testEntropyFeedback(done,
3369 // Next test was throwing uncaught error in zxcvbn 3374 // Next test was throwing uncaught error in zxcvbn
3370 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
3371 { 3375 {
3372 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3376 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3373 type: "card (full deck, 52 duplicates: ac 2c 3c...)", 3377 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
3374 events: "104", 3378 events: "104",
3375 bits: "499", 3379 bits: "464",
3376 words: 45, 3380 words: 42,
3377 strength: "centuries", 3381 strength: "centuries",
3378 } 3382 }
3379 ); 3383 );
@@ -3385,7 +3389,7 @@ it("Shows details about the entered entropy", function(done) {
3385 entropy: "asAS", 3389 entropy: "asAS",
3386 type: "card (1 duplicate: AS)", 3390 type: "card (1 duplicate: AS)",
3387 events: "2", 3391 events: "2",
3388 bits: "9", 3392 bits: "8",
3389 words: 0, 3393 words: 0,
3390 strength: "less than a second", 3394 strength: "less than a second",
3391 } 3395 }
@@ -3397,7 +3401,7 @@ it("Shows details about the entered entropy", function(done) {
3397 entropy: "ASas", 3401 entropy: "ASas",
3398 type: "card (1 duplicate: as)", 3402 type: "card (1 duplicate: as)",
3399 events: "2", 3403 events: "2",
3400 bits: "9", 3404 bits: "8",
3401 words: 0, 3405 words: 0,
3402 strength: "less than a second", 3406 strength: "less than a second",
3403 } 3407 }
@@ -3410,8 +3414,8 @@ it("Shows details about the entered entropy", function(done) {
3410 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3414 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3411 type: "card (1 missing: 9C)", 3415 type: "card (1 missing: 9C)",
3412 events: "51", 3416 events: "51",
3413 bits: "221", 3417 bits: "227",
3414 words: 18, 3418 words: 21,
3415 strength: "centuries", 3419 strength: "centuries",
3416 } 3420 }
3417 ); 3421 );
@@ -3422,7 +3426,7 @@ it("Shows details about the entered entropy", function(done) {
3422 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3426 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3423 type: "card (2 missing: 9C 5D)", 3427 type: "card (2 missing: 9C 5D)",
3424 events: "50", 3428 events: "50",
3425 bits: "216", 3429 bits: "222",
3426 words: 18, 3430 words: 18,
3427 strength: "centuries", 3431 strength: "centuries",
3428 } 3432 }
@@ -3434,7 +3438,7 @@ it("Shows details about the entered entropy", function(done) {
3434 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3438 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3435 type: "card (4 missing: 9C 5D QD...)", 3439 type: "card (4 missing: 9C 5D QD...)",
3436 events: "48", 3440 events: "48",
3437 bits: "208", 3441 bits: "212",
3438 words: 18, 3442 words: 18,
3439 strength: "centuries", 3443 strength: "centuries",
3440 } 3444 }
@@ -3447,20 +3451,21 @@ it("Shows details about the entered entropy", function(done) {
3447 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks", 3451 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
3448 type: "card", 3452 type: "card",
3449 events: "45", 3453 events: "45",
3450 bits: "195", 3454 bits: "198",
3451 words: 18, 3455 words: 18,
3452 strength: "centuries", 3456 strength: "centuries",
3453 } 3457 }
3454 ); 3458 );
3455}); 3459});
3456it("Shows details about the entered entropy", function(done) { 3460it("Shows details about the entered entropy", function(done) {
3461 // multiple decks does not affect the bits per event
3462 // since the bits are hardcoded in entropy.js
3457 testEntropyFeedback(done, 3463 testEntropyFeedback(done,
3458 // Multiple decks of cards increases bits per event
3459 { 3464 {
3460 entropy: "3d", 3465 entropy: "3d",
3461 events: "1", 3466 events: "1",
3462 bits: "4", 3467 bits: "5",
3463 bitsPerEvent: "4.34", 3468 bitsPerEvent: "4.46",
3464 } 3469 }
3465 ); 3470 );
3466}); 3471});
@@ -3469,8 +3474,8 @@ it("Shows details about the entered entropy", function(done) {
3469 { 3474 {
3470 entropy: "3d3d", 3475 entropy: "3d3d",
3471 events: "2", 3476 events: "2",
3472 bits: "9", 3477 bits: "10",
3473 bitsPerEvent: "4.80", 3478 bitsPerEvent: "4.46",
3474 } 3479 }
3475 ); 3480 );
3476}); 3481});
@@ -3480,7 +3485,7 @@ it("Shows details about the entered entropy", function(done) {
3480 entropy: "3d3d3d", 3485 entropy: "3d3d3d",
3481 events: "3", 3486 events: "3",
3482 bits: "15", 3487 bits: "15",
3483 bitsPerEvent: "5.01", 3488 bitsPerEvent: "4.46",
3484 } 3489 }
3485 ); 3490 );
3486}); 3491});
@@ -3490,7 +3495,7 @@ it("Shows details about the entered entropy", function(done) {
3490 entropy: "3d3d3d3d", 3495 entropy: "3d3d3d3d",
3491 events: "4", 3496 events: "4",
3492 bits: "20", 3497 bits: "20",
3493 bitsPerEvent: "5.14", 3498 bitsPerEvent: "4.46",
3494 } 3499 }
3495 ); 3500 );
3496}); 3501});
@@ -3499,8 +3504,8 @@ it("Shows details about the entered entropy", function(done) {
3499 { 3504 {
3500 entropy: "3d3d3d3d3d", 3505 entropy: "3d3d3d3d3d",
3501 events: "5", 3506 events: "5",
3502 bits: "26", 3507 bits: "25",
3503 bitsPerEvent: "5.22", 3508 bitsPerEvent: "4.46",
3504 } 3509 }
3505 ); 3510 );
3506}); 3511});
@@ -3509,8 +3514,8 @@ it("Shows details about the entered entropy", function(done) {
3509 { 3514 {
3510 entropy: "3d3d3d3d3d3d", 3515 entropy: "3d3d3d3d3d3d",
3511 events: "6", 3516 events: "6",
3512 bits: "31", 3517 bits: "30",
3513 bitsPerEvent: "5.28", 3518 bitsPerEvent: "4.46",
3514 } 3519 }
3515 ); 3520 );
3516}); 3521});
@@ -3519,8 +3524,8 @@ it("Shows details about the entered entropy", function(done) {
3519 { 3524 {
3520 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d", 3525 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
3521 events: "33", 3526 events: "33",
3522 bits: "184", 3527 bits: "165",
3523 bitsPerEvent: "5.59", 3528 bitsPerEvent: "4.46",
3524 strength: 'less than a second - Repeats like "abcabcabc" are only slightly harder to guess than "abc"', 3529 strength: 'less than a second - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
3525 } 3530 }
3526 ); 3531 );
@@ -3571,10 +3576,11 @@ it('Converts very long entropy to very long mnemonics', function(done) {
3571// https://bip32jp.github.io/english/index.html 3576// https://bip32jp.github.io/english/index.html
3572// NOTES: 3577// NOTES:
3573// Is incompatible with: 3578// Is incompatible with:
3579// base 6
3574// base 20 3580// base 20
3575it('Is compatible with bip32jp.github.io', function(done) { 3581it('Is compatible with bip32jp.github.io', function(done) {
3576 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543"; 3582 var entropy = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
3577 var expectedPhrase = "train then jungle barely whip fiber purpose puppy eagle cloud clump hospital robot brave balcony utility detect estate old green desk skill multiply virus"; 3583 var expectedPhrase = "primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary foster";
3578 driver.findElement(By.css('.use-entropy')) 3584 driver.findElement(By.css('.use-entropy'))
3579 .click(); 3585 .click();
3580 driver.findElement(By.css('.entropy')) 3586 driver.findElement(By.css('.entropy'))