]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/entropy.js
Remove bias from entropy in base 6 and base 10
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / entropy.js
CommitLineData
6606c50f
IC
1/*
2 * Detects entropy from a string.
3 *
4 * Formats include:
5 * binary [0-1]
6 * base 6 [0-5]
7 * dice 6 [1-6]
8 * decimal [0-9]
9 * hexadecimal [0-9A-F]
0fdcf2eb 10 * card [A2-9TJQK][CDHS]
6606c50f
IC
11 *
12 * Automatically uses lowest entropy to avoid issues such as interpretting 0101
13 * as hexadecimal which would be 16 bits when really it's only 4 bits of binary
14 * entropy.
15 */
16
c6624d51
IC
17window.Entropy = new (function() {
18
bf96267f
IC
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 }
886f06ee 149
6606c50f
IC
150 // matchers returns an array of the matched events for each type of entropy.
151 // eg
152 // matchers.binary("010") returns ["0", "1", "0"]
153 // matchers.binary("a10") returns ["1", "0"]
154 // matchers.hex("a10") returns ["a", "1", "0"]
c6624d51 155 var matchers = {
6606c50f
IC
156 binary: function(str) {
157 return str.match(/[0-1]/gi) || [];
158 },
159 base6: function(str) {
160 return str.match(/[0-5]/gi) || [];
161 },
162 dice: function(str) {
163 return str.match(/[1-6]/gi) || []; // ie dice numbers
164 },
165 base10: function(str) {
166 return str.match(/[0-9]/gi) || [];
167 },
168 hex: function(str) {
169 return str.match(/[0-9A-F]/gi) || [];
170 },
adc8ce12
IC
171 card: function(str) {
172 // Format is NumberSuit, eg
173 // AH ace of hearts
174 // 8C eight of clubs
175 // TD ten of diamonds
176 // JS jack of spades
177 // QH queen of hearts
178 // KC king of clubs
179 return str.match(/([A2-9TJQK][CDHS])/gi) || [];
180 }
181 }
182
516c16d7 183 this.fromString = function(rawEntropyStr, baseStr) {
c6624d51 184 // Find type of entropy being used (binary, hex, dice etc)
516c16d7 185 var base = getBase(rawEntropyStr, baseStr);
c6624d51 186 // Convert dice to base6 entropy (ie 1-6 to 0-5)
425b75a9 187 // This is done by changing all 6s to 0s
c6624d51 188 if (base.str == "dice") {
bf96267f
IC
189 var newEvents = [];
190 for (var i=0; i<base.events.length; i++) {
191 var c = base.events[i];
425b75a9 192 if ("12345".indexOf(c) > -1) {
bf96267f 193 newEvents[i] = base.events[i];
c6624d51
IC
194 }
195 else {
bf96267f 196 newEvents[i] = "0";
c6624d51
IC
197 }
198 }
c6624d51 199 base.str = "base 6 (dice)";
bf96267f 200 base.events = newEvents;
c6624d51
IC
201 base.matcher = matchers.base6;
202 }
c6624d51 203 // Detect empty entropy
bf96267f 204 if (base.events.length == 0) {
c6624d51
IC
205 return {
206 binaryStr: "",
c6624d51 207 cleanStr: "",
b54c1218 208 cleanHtml: "",
c6624d51
IC
209 base: base,
210 };
211 }
bf96267f
IC
212 // Convert entropy events to binary
213 var entropyBin = base.events.map(function(e) {
214 return eventBits[base.str][e.toLowerCase()];
215 }).join("");
216 // Get average bits per event
217 // which may be adjusted for bias if log2(base) is fractional
218 var bitsPerEvent = base.bitsPerEvent;
1cf1bbaf 219 // Supply a 'filtered' entropy string for display purposes
bf96267f
IC
220 var entropyClean = base.events.join("");
221 var entropyHtml = base.events.join("");
c193ff67 222 if (base.asInt == 52) {
bf96267f 223 entropyClean = base.events.join(" ").toUpperCase();
c193ff67
IC
224 entropyClean = entropyClean.replace(/C/g, "\u2663");
225 entropyClean = entropyClean.replace(/D/g, "\u2666");
226 entropyClean = entropyClean.replace(/H/g, "\u2665");
227 entropyClean = entropyClean.replace(/S/g, "\u2660");
bf96267f 228 entropyHtml = base.events.join(" ").toUpperCase();
b54c1218
IC
229 entropyHtml = entropyHtml.replace(/C/g, "<span class='card-suit club'>\u2663</span>");
230 entropyHtml = entropyHtml.replace(/D/g, "<span class='card-suit diamond'>\u2666</span>");
231 entropyHtml = entropyHtml.replace(/H/g, "<span class='card-suit heart'>\u2665</span>");
232 entropyHtml = entropyHtml.replace(/S/g, "<span class='card-suit spade'>\u2660</span>");
c193ff67 233 }
0fdcf2eb 234 // Return the result
c6624d51
IC
235 var e = {
236 binaryStr: entropyBin,
c6624d51 237 cleanStr: entropyClean,
b54c1218 238 cleanHtml: entropyHtml,
94959756 239 bitsPerEvent: bitsPerEvent,
c6624d51
IC
240 base: base,
241 }
242 return e;
243 }
244
516c16d7 245 function getBase(str, baseStr) {
c6624d51
IC
246 // Need to get the lowest base for the supplied entropy.
247 // This prevents interpreting, say, dice rolls as hexadecimal.
6606c50f
IC
248 var binaryMatches = matchers.binary(str);
249 var hexMatches = matchers.hex(str);
516c16d7 250 var autodetect = baseStr === undefined;
c6624d51 251 // Find the lowest base that can be used, whilst ignoring any irrelevant chars
516c16d7 252 if ((binaryMatches.length == hexMatches.length && hexMatches.length > 0 && autodetect) || baseStr === "binary") {
adc8ce12 253 var ints = binaryMatches.map(function(i) { return parseInt(i, 2) });
c6624d51 254 return {
adc8ce12 255 ints: ints,
bf96267f 256 events: binaryMatches,
c6624d51
IC
257 matcher: matchers.binary,
258 asInt: 2,
bf96267f 259 bitsPerEvent: 1,
c6624d51
IC
260 str: "binary",
261 }
262 }
adc8ce12 263 var cardMatches = matchers.card(str);
516c16d7 264 if ((cardMatches.length >= hexMatches.length / 2 && autodetect) || baseStr === "card") {
adc8ce12
IC
265 return {
266 ints: ints,
bf96267f 267 events: cardMatches,
adc8ce12
IC
268 matcher: matchers.card,
269 asInt: 52,
bf96267f 270 bitsPerEvent: (32*5 + 16*4 + 4*2) / 52, // see cardBits
adc8ce12
IC
271 str: "card",
272 }
273 }
6606c50f 274 var diceMatches = matchers.dice(str);
516c16d7 275 if ((diceMatches.length == hexMatches.length && hexMatches.length > 0 && autodetect) || baseStr === "dice") {
adc8ce12 276 var ints = diceMatches.map(function(i) { return parseInt(i) });
c6624d51 277 return {
adc8ce12 278 ints: ints,
bf96267f 279 events: diceMatches,
c6624d51
IC
280 matcher: matchers.dice,
281 asInt: 6,
bf96267f 282 bitsPerEvent: (4*2 + 2*1) / 6, // see diceBits
c6624d51
IC
283 str: "dice",
284 }
285 }
6606c50f 286 var base6Matches = matchers.base6(str);
516c16d7 287 if ((base6Matches.length == hexMatches.length && hexMatches.length > 0 && autodetect) || baseStr === "base 6") {
adc8ce12 288 var ints = base6Matches.map(function(i) { return parseInt(i) });
c6624d51 289 return {
adc8ce12 290 ints: ints,
bf96267f 291 events: base6Matches,
c6624d51
IC
292 matcher: matchers.base6,
293 asInt: 6,
bf96267f 294 bitsPerEvent: (4*2 + 2*1) / 6, // see diceBits
c6624d51
IC
295 str: "base 6",
296 }
297 }
6606c50f 298 var base10Matches = matchers.base10(str);
516c16d7 299 if ((base10Matches.length == hexMatches.length && hexMatches.length > 0 && autodetect) || baseStr === "base 10") {
adc8ce12 300 var ints = base10Matches.map(function(i) { return parseInt(i) });
c6624d51 301 return {
adc8ce12 302 ints: ints,
bf96267f 303 events: base10Matches,
c6624d51
IC
304 matcher: matchers.base10,
305 asInt: 10,
bf96267f 306 bitsPerEvent: (8*3 + 2*1) / 10, // see b10Bits
c6624d51
IC
307 str: "base 10",
308 }
309 }
adc8ce12 310 var ints = hexMatches.map(function(i) { return parseInt(i, 16) });
c6624d51 311 return {
adc8ce12 312 ints: ints,
bf96267f 313 events: hexMatches,
c6624d51
IC
314 matcher: matchers.hex,
315 asInt: 16,
bf96267f 316 bitsPerEvent: 4,
c6624d51
IC
317 str: "hexadecimal",
318 }
319 }
320
c6624d51 321})();