aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xdev_env_setup.sh58
-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
5 files changed, 274 insertions, 235 deletions
diff --git a/dev_env_setup.sh b/dev_env_setup.sh
new file mode 100755
index 0000000..68af109
--- /dev/null
+++ b/dev_env_setup.sh
@@ -0,0 +1,58 @@
1# this script is intended to be run in a VM
2# running ubuntu 20.04 server
3# from the root directory of this repo
4
5echo "This script is intended to be run in a VM."
6echo "It may do things to your OS that you don't want to be peristent."
7echo "Please type virtualmachine to continue, or Ctrl-C to quit."
8
9read passage
10
11if [ "$passage" = "virtualmachine" ]; then
12 echo "Installing dev environment"
13else
14 echo "Did not type virtualmachine, quitting with no changes applied"
15 exit
16fi
17
18# set up place for local binaries
19mkdir $HOME/.bin
20echo "export PATH=$PATH:$HOME/.bin" >> $HOME/.bashrc
21source $HOME/.bashrc
22
23# allow python3 to be run with python command
24ln -s /usr/bin/python3 $HOME/.bin/python
25
26# install firefox and other dependencies
27sudo apt-get -y install firefox unzip openjdk-11-jre-headless xvfb libxi6 libgconf-2-4
28# install chrome
29curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add
30sudo sh -c "echo \"deb https://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google-chrome.list"
31sudo apt-get -y update
32sudo apt-get -y install google-chrome-stable
33
34# install nodejs for running tests
35curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash
36# load nvm
37source $HOME/.bashrc
38export NVM_DIR="$HOME/.nvm"
39[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
40[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
41# install latest node
42nvm install node
43# install jasmine
44cd tests
45npm install --global jasmine
46npm install selenium-webdriver
47# install gecko webdriver for firefox
48wget https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-linux64.tar.gz --output-document=/tmp/geckodriver.tar.gz
49tar -xf /tmp/geckodriver.tar.gz -C $HOME/.bin
50# install chrome webdriver for chromium
51wget https://chromedriver.storage.googleapis.com/85.0.4183.87/chromedriver_linux64.zip --output-document=/tmp/chromedriver.zip
52unzip /tmp/chromedriver.zip -d $HOME/.bin
53
54# to run tests
55# cd tests
56# Xvfb :1 -screen 1 1024x768x24 & export DISPLAY=:1.1
57# BROWSER=firefox jasmine spec/tests.js
58# BROWSER=chrome jasmine spec/tests.js
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 cd3f769..dc24963 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 73e3087..58318db 100644
--- a/tests/spec/tests.js
+++ b/tests/spec/tests.js
@@ -3130,7 +3130,7 @@ it("Shows the number of bits of entropy for 4 bits of binary", function(done) {
3130 testEntropyBits(done, "0000", "4"); 3130 testEntropyBits(done, "0000", "4");
3131}); 3131});
3132it("Shows the number of bits of entropy for 1 character of base 6 (dice)", function(done) { 3132it("Shows the number of bits of entropy for 1 character of base 6 (dice)", function(done) {
3133 // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits) 3133 // 6 in card is 0 in base 6, 0 is mapped to 00 by entropy.js
3134 testEntropyBits(done, "6", "2"); 3134 testEntropyBits(done, "6", "2");
3135}); 3135});
3136it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits", function(done) { 3136it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits", function(done) {
@@ -3138,13 +3138,15 @@ it("Shows the number of bits of entropy for 1 character of base 10 with 3 bits",
3138 testEntropyBits(done, "7", "3"); 3138 testEntropyBits(done, "7", "3");
3139}); 3139});
3140it("Shows the number of bits of entropy for 1 character of base 10 with 4 bis", function(done) { 3140it("Shows the number of bits of entropy for 1 character of base 10 with 4 bis", function(done) {
3141 testEntropyBits(done, "8", "4"); 3141 // 8 in base 10 is mapped to 0 by entropy.js
3142 testEntropyBits(done, "8", "1");
3142}); 3143});
3143it("Shows the number of bits of entropy for 1 character of hex", function(done) { 3144it("Shows the number of bits of entropy for 1 character of hex", function(done) {
3144 testEntropyBits(done, "F", "4"); 3145 testEntropyBits(done, "F", "4");
3145}); 3146});
3146it("Shows the number of bits of entropy for 2 characters of base 10", function(done) { 3147it("Shows the number of bits of entropy for 2 characters of base 10", function(done) {
3147 testEntropyBits(done, "29", "6"); 3148 // 2 as base 10 is binary 010, 9 is mapped to binary 1 by entropy.js
3149 testEntropyBits(done, "29", "4");
3148}); 3150});
3149it("Shows the number of bits of entropy for 2 characters of hex", function(done) { 3151it("Shows the number of bits of entropy for 2 characters of hex", function(done) {
3150 testEntropyBits(done, "0A", "8"); 3152 testEntropyBits(done, "0A", "8");
@@ -3169,17 +3171,17 @@ it("Shows the number of bits of entropy for 4 characters of hex with leading zer
3169 testEntropyBits(done, "000A", "16"); 3171 testEntropyBits(done, "000A", "16");
3170}); 3172});
3171it("Shows the number of bits of entropy for 4 characters of base 6", function(done) { 3173it("Shows the number of bits of entropy for 4 characters of base 6", function(done) {
3172 testEntropyBits(done, "5555", "11"); 3174 // 5 in base 6 is mapped to binary 1
3175 testEntropyBits(done, "5555", "4");
3173}); 3176});
3174it("Shows the number of bits of entropy for 4 characters of base 6 dice", function(done) { 3177it("Shows the number of bits of entropy for 4 characters of base 6 dice", function(done) {
3175 // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of 3178 // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of
3176 // 2.58 bits, which is 10.32 bits (rounded down to 10 bits) 3179 // binary 00
3177 testEntropyBits(done, "6666", "10"); 3180 testEntropyBits(done, "6666", "8");
3178}); 3181});
3179it("Shows the number of bits of entropy for 4 charactes of base 10", function(done) { 3182it("Shows the number of bits of entropy for 4 charactes of base 10", function(done) {
3180 // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded 3183 // 2 in base 10 is binary 010 and 7 is binary 111 so is 4 events of 3 bits
3181 // down to 13) 3184 testEntropyBits(done, "2227", "12");
3182 testEntropyBits(done, "2227", "13");
3183}); 3185});
3184it("Shows the number of bits of entropy for 4 characters of hex with 2 leading zeros", function(done) { 3186it("Shows the number of bits of entropy for 4 characters of hex with 2 leading zeros", function(done) {
3185 testEntropyBits(done, "222F", "16"); 3187 testEntropyBits(done, "222F", "16");
@@ -3188,13 +3190,16 @@ it("Shows the number of bits of entropy for 4 characters of hex starting with F"
3188 testEntropyBits(done, "FFFF", "16"); 3190 testEntropyBits(done, "FFFF", "16");
3189}); 3191});
3190it("Shows the number of bits of entropy for 10 characters of base 10", function(done) { 3192it("Shows the number of bits of entropy for 10 characters of base 10", function(done) {
3191 // 10 events at 3.32 bits per event 3193 // 10 events with 3 bits for each event
3192 testEntropyBits(done, "0000101017", "33"); 3194 testEntropyBits(done, "0000101017", "30");
3195});
3196it("Shows the number of bits of entropy for 10 characters of base 10 account for bias", function(done) {
3197 // 9 events with 3 bits per event and 1 event with 1 bit per event
3198 testEntropyBits(done, "0000101018", "28");
3193}); 3199});
3194it("Shows the number of bits of entropy for a full deck of cards", function(done) { 3200it("Shows the number of bits of entropy for a full deck of cards", function(done) {
3195 // cards are not replaced, so a full deck is not 52^52 entropy which is 296 3201 // removing bias is 32*5 + 16*4 + 4*2
3196 // bits, it's 52!, which is 225 bits 3202 testEntropyBits(done, "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "232");
3197 testEntropyBits(done, "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225");
3198}); 3203});
3199 3204
3200it("Shows details about the entered entropy", function(done) { 3205it("Shows details about the entered entropy", function(done) {
@@ -3320,7 +3325,7 @@ it("Shows details about the entered entropy", function(done) {
3320 entropy: "7d", 3325 entropy: "7d",
3321 type: "card", 3326 type: "card",
3322 events: "1", 3327 events: "1",
3323 bits: "4", 3328 bits: "5",
3324 words: 0, 3329 words: 0,
3325 strength: "less than a second", 3330 strength: "less than a second",
3326 } 3331 }
@@ -3332,7 +3337,7 @@ it("Shows details about the entered entropy", function(done) {
3332 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3337 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3333 type: "card (full deck)", 3338 type: "card (full deck)",
3334 events: "52", 3339 events: "52",
3335 bits: "225", 3340 bits: "232",
3336 words: 21, 3341 words: 21,
3337 strength: "centuries", 3342 strength: "centuries",
3338 } 3343 }
@@ -3344,7 +3349,7 @@ it("Shows details about the entered entropy", function(done) {
3344 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d", 3349 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
3345 type: "card (full deck, 1 duplicate: 3d)", 3350 type: "card (full deck, 1 duplicate: 3d)",
3346 events: "53", 3351 events: "53",
3347 bits: "254", 3352 bits: "237",
3348 words: 21, 3353 words: 21,
3349 strength: "centuries", 3354 strength: "centuries",
3350 } 3355 }
@@ -3356,7 +3361,7 @@ it("Shows details about the entered entropy", function(done) {
3356 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d", 3361 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
3357 type: "card (2 duplicates: 3d 4d, 1 missing: KS)", 3362 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
3358 events: "53", 3363 events: "53",
3359 bits: "254", 3364 bits: "240",
3360 words: 21, 3365 words: 21,
3361 strength: "centuries", 3366 strength: "centuries",
3362 } 3367 }
@@ -3368,8 +3373,8 @@ it("Shows details about the entered entropy", function(done) {
3368 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d", 3373 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
3369 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)", 3374 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
3370 events: "55", 3375 events: "55",
3371 bits: "264", 3376 bits: "250",
3372 words: 24, 3377 words: 21,
3373 strength: "centuries", 3378 strength: "centuries",
3374 } 3379 }
3375 ); 3380 );
@@ -3377,13 +3382,12 @@ it("Shows details about the entered entropy", function(done) {
3377it("Shows details about the entered entropy", function(done) { 3382it("Shows details about the entered entropy", function(done) {
3378 testEntropyFeedback(done, 3383 testEntropyFeedback(done,
3379 // Next test was throwing uncaught error in zxcvbn 3384 // Next test was throwing uncaught error in zxcvbn
3380 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
3381 { 3385 {
3382 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3386 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3383 type: "card (full deck, 52 duplicates: ac 2c 3c...)", 3387 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
3384 events: "104", 3388 events: "104",
3385 bits: "499", 3389 bits: "464",
3386 words: 45, 3390 words: 42,
3387 strength: "centuries", 3391 strength: "centuries",
3388 } 3392 }
3389 ); 3393 );
@@ -3395,7 +3399,7 @@ it("Shows details about the entered entropy", function(done) {
3395 entropy: "asAS", 3399 entropy: "asAS",
3396 type: "card (1 duplicate: AS)", 3400 type: "card (1 duplicate: AS)",
3397 events: "2", 3401 events: "2",
3398 bits: "9", 3402 bits: "8",
3399 words: 0, 3403 words: 0,
3400 strength: "less than a second", 3404 strength: "less than a second",
3401 } 3405 }
@@ -3407,7 +3411,7 @@ it("Shows details about the entered entropy", function(done) {
3407 entropy: "ASas", 3411 entropy: "ASas",
3408 type: "card (1 duplicate: as)", 3412 type: "card (1 duplicate: as)",
3409 events: "2", 3413 events: "2",
3410 bits: "9", 3414 bits: "8",
3411 words: 0, 3415 words: 0,
3412 strength: "less than a second", 3416 strength: "less than a second",
3413 } 3417 }
@@ -3420,8 +3424,8 @@ it("Shows details about the entered entropy", function(done) {
3420 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3424 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3421 type: "card (1 missing: 9C)", 3425 type: "card (1 missing: 9C)",
3422 events: "51", 3426 events: "51",
3423 bits: "221", 3427 bits: "227",
3424 words: 18, 3428 words: 21,
3425 strength: "centuries", 3429 strength: "centuries",
3426 } 3430 }
3427 ); 3431 );
@@ -3432,7 +3436,7 @@ it("Shows details about the entered entropy", function(done) {
3432 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3436 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3433 type: "card (2 missing: 9C 5D)", 3437 type: "card (2 missing: 9C 5D)",
3434 events: "50", 3438 events: "50",
3435 bits: "216", 3439 bits: "222",
3436 words: 18, 3440 words: 18,
3437 strength: "centuries", 3441 strength: "centuries",
3438 } 3442 }
@@ -3444,7 +3448,7 @@ it("Shows details about the entered entropy", function(done) {
3444 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", 3448 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
3445 type: "card (4 missing: 9C 5D QD...)", 3449 type: "card (4 missing: 9C 5D QD...)",
3446 events: "48", 3450 events: "48",
3447 bits: "208", 3451 bits: "212",
3448 words: 18, 3452 words: 18,
3449 strength: "centuries", 3453 strength: "centuries",
3450 } 3454 }
@@ -3457,20 +3461,21 @@ it("Shows details about the entered entropy", function(done) {
3457 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks", 3461 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
3458 type: "card", 3462 type: "card",
3459 events: "45", 3463 events: "45",
3460 bits: "195", 3464 bits: "198",
3461 words: 18, 3465 words: 18,
3462 strength: "centuries", 3466 strength: "centuries",
3463 } 3467 }
3464 ); 3468 );
3465}); 3469});
3466it("Shows details about the entered entropy", function(done) { 3470it("Shows details about the entered entropy", function(done) {
3471 // multiple decks does not affect the bits per event
3472 // since the bits are hardcoded in entropy.js
3467 testEntropyFeedback(done, 3473 testEntropyFeedback(done,
3468 // Multiple decks of cards increases bits per event
3469 { 3474 {
3470 entropy: "3d", 3475 entropy: "3d",
3471 events: "1", 3476 events: "1",
3472 bits: "4", 3477 bits: "5",
3473 bitsPerEvent: "4.34", 3478 bitsPerEvent: "4.46",
3474 } 3479 }
3475 ); 3480 );
3476}); 3481});
@@ -3479,8 +3484,8 @@ it("Shows details about the entered entropy", function(done) {
3479 { 3484 {
3480 entropy: "3d3d", 3485 entropy: "3d3d",
3481 events: "2", 3486 events: "2",
3482 bits: "9", 3487 bits: "10",
3483 bitsPerEvent: "4.80", 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: "3d3d3d", 3495 entropy: "3d3d3d",
3491 events: "3", 3496 events: "3",
3492 bits: "15", 3497 bits: "15",
3493 bitsPerEvent: "5.01", 3498 bitsPerEvent: "4.46",
3494 } 3499 }
3495 ); 3500 );
3496}); 3501});
@@ -3500,7 +3505,7 @@ it("Shows details about the entered entropy", function(done) {
3500 entropy: "3d3d3d3d", 3505 entropy: "3d3d3d3d",
3501 events: "4", 3506 events: "4",
3502 bits: "20", 3507 bits: "20",
3503 bitsPerEvent: "5.14", 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: "3d3d3d3d3d", 3515 entropy: "3d3d3d3d3d",
3511 events: "5", 3516 events: "5",
3512 bits: "26", 3517 bits: "25",
3513 bitsPerEvent: "5.22", 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: "3d3d3d3d3d3d", 3525 entropy: "3d3d3d3d3d3d",
3521 events: "6", 3526 events: "6",
3522 bits: "31", 3527 bits: "30",
3523 bitsPerEvent: "5.28", 3528 bitsPerEvent: "4.46",
3524 } 3529 }
3525 ); 3530 );
3526}); 3531});
@@ -3529,8 +3534,8 @@ it("Shows details about the entered entropy", function(done) {
3529 { 3534 {
3530 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d", 3535 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
3531 events: "33", 3536 events: "33",
3532 bits: "184", 3537 bits: "165",
3533 bitsPerEvent: "5.59", 3538 bitsPerEvent: "4.46",
3534 strength: 'less than a second - Repeats like "abcabcabc" are only slightly harder to guess than "abc"', 3539 strength: 'less than a second - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
3535 } 3540 }
3536 ); 3541 );
@@ -3581,10 +3586,11 @@ it('Converts very long entropy to very long mnemonics', function(done) {
3581// https://bip32jp.github.io/english/index.html 3586// https://bip32jp.github.io/english/index.html
3582// NOTES: 3587// NOTES:
3583// Is incompatible with: 3588// Is incompatible with:
3589// base 6
3584// base 20 3590// base 20
3585it('Is compatible with bip32jp.github.io', function(done) { 3591it('Is compatible with bip32jp.github.io', function(done) {
3586 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543"; 3592 var entropy = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
3587 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"; 3593 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";
3588 driver.findElement(By.css('.use-entropy')) 3594 driver.findElement(By.css('.use-entropy'))
3589 .click(); 3595 .click();
3590 driver.findElement(By.css('.entropy')) 3596 driver.findElement(By.css('.entropy'))