aboutsummaryrefslogtreecommitdiff
path: root/src/js/entropy.js
diff options
context:
space:
mode:
authorIan Coleman <coleman.ian@gmail.com>2016-11-07 16:01:21 +1100
committerIan Coleman <coleman.ian@gmail.com>2016-11-07 16:01:21 +1100
commitadc8ce127d4f8ea0d7e5ede6a82c2791d60ff4d2 (patch)
treeda8a4bfe57f207d52822cab17ae7a8af150271bf /src/js/entropy.js
parent6606c50fd3af59483a5524170d9d2c3ec213a60f (diff)
downloadBIP39-adc8ce127d4f8ea0d7e5ede6a82c2791d60ff4d2.tar.gz
BIP39-adc8ce127d4f8ea0d7e5ede6a82c2791d60ff4d2.tar.zst
BIP39-adc8ce127d4f8ea0d7e5ede6a82c2791d60ff4d2.zip
Cards can be used for entropy
Format is [A2-9TJQK][CDHS]
Diffstat (limited to 'src/js/entropy.js')
-rw-r--r--src/js/entropy.js123
1 files changed, 87 insertions, 36 deletions
diff --git a/src/js/entropy.js b/src/js/entropy.js
index 5b687d8..92300af 100644
--- a/src/js/entropy.js
+++ b/src/js/entropy.js
@@ -36,6 +36,32 @@ window.Entropy = new (function() {
36 hex: function(str) { 36 hex: function(str) {
37 return str.match(/[0-9A-F]/gi) || []; 37 return str.match(/[0-9A-F]/gi) || [];
38 }, 38 },
39 card: function(str) {
40 // Format is NumberSuit, eg
41 // AH ace of hearts
42 // 8C eight of clubs
43 // TD ten of diamonds
44 // JS jack of spades
45 // QH queen of hearts
46 // KC king of clubs
47 return str.match(/([A2-9TJQK][CDHS])/gi) || [];
48 }
49 }
50
51 // Convert array of cards from ["ac", "4d", "ks"]
52 // to numbers between 0 and 51 [0, 16, 51]
53 function convertCardsToInts(cards) {
54 var ints = [];
55 var values = "a23456789tjqk";
56 var suits = "cdhs";
57 for (var i=0; i<cards.length; i++) {
58 var card = cards[i].toLowerCase();
59 var value = card[0];
60 var suit = card[1];
61 var asInt = 13 * suits.indexOf(suit) + values.indexOf(value);
62 ints.push(asInt);
63 }
64 return ints;
39 } 65 }
40 66
41 this.fromString = function(rawEntropyStr) { 67 this.fromString = function(rawEntropyStr) {
@@ -62,61 +88,61 @@ window.Entropy = new (function() {
62 if (base.parts.length == 0) { 88 if (base.parts.length == 0) {
63 return { 89 return {
64 binaryStr: "", 90 binaryStr: "",
65 hexStr: "",
66 cleanStr: "", 91 cleanStr: "",
67 base: base, 92 base: base,
68 }; 93 };
69 } 94 }
70 // Pull leading zeros off 95 // Pull leading zeros off
71 var leadingZeros = []; 96 var leadingZeros = [];
72 while (base.parts[0] == "0") { 97 while (base.ints[0] == "0") {
73 leadingZeros.push("0"); 98 leadingZeros.push("0");
74 base.parts.shift(); 99 base.ints.shift();
75 } 100 }
76 // Convert leading zeros to binary equivalent 101 // Convert leading zeros to binary equivalent
77 var numBinLeadingZeros = Math.ceil(Math.log2(base.asInt) * leadingZeros.length); 102 var numBinLeadingZeros = Math.floor(Math.log2(base.asInt) * leadingZeros.length);
78 var binLeadingZeros = ""; 103 var binLeadingZeros = "";
79 for (var i=0; i<numBinLeadingZeros; i++) { 104 for (var i=0; i<numBinLeadingZeros; i++) {
80 binLeadingZeros += "0"; 105 binLeadingZeros += "0";
81 } 106 }
82 // Convert leading zeros to hex equivalent
83 var numHexLeadingZeros = Math.floor(numBinLeadingZeros / 4);
84 var hexLeadingZeros = "";
85 for (var i=0; i<numHexLeadingZeros; i++) {
86 hexLeadingZeros += "0";
87 }
88 // Handle entropy of zero 107 // Handle entropy of zero
89 if (base.parts.length == 0) { 108 if (base.ints.length == 0) {
90 return { 109 return {
91 binaryStr: binLeadingZeros, 110 binaryStr: binLeadingZeros,
92 hexStr: hexLeadingZeros || "0",
93 cleanStr: leadingZeros, 111 cleanStr: leadingZeros,
94 base: base, 112 base: base,
95 } 113 }
96 } 114 }
97 // If using hex, should always be multiples of 4 bits, which can get 115 // If the first integer is small, it must be padded with zeros.
98 // out of sync if first number has leading 0 bits, eg 2 in hex is 0010 116 // Otherwise the chance of the first bit being 1 is 100%, which is
99 // which would show up as 10, thus missing 2 bits it should have. 117 // obviously incorrect.
100 if (base.asInt == 16) { 118 // This is not perfect for unusual bases, eg base 6 has 2.6 bits, so is
101 var firstDigit = parseInt(base.parts[0], 16); 119 // slightly biased toward having leading zeros, but it's still better
102 if (firstDigit >= 4 && firstDigit < 8) { 120 // than ignoring it completely.
103 binLeadingZeros += "0"; 121 // TODO: revise this, it seems very fishy. For example, in base 10, there are
104 } 122 // 8 opportunities to start with 0 but only 2 to start with 1
105 else if (firstDigit >= 2 && firstDigit < 4) { 123 var firstInt = base.ints[0];
106 binLeadingZeros += "00"; 124 var firstIntBits = Math.floor(Math.log2(firstInt))+1;
107 } 125 var maxFirstIntBits = Math.floor(Math.log2(base.asInt-1))+1;
108 else if (firstDigit >= 1 && firstDigit < 2) { 126 var missingFirstIntBits = maxFirstIntBits - firstIntBits;
109 binLeadingZeros += "000"; 127 var firstIntLeadingZeros = "";
110 } 128 for (var i=0; i<missingFirstIntBits; i++) {
129 binLeadingZeros += "0";
130 }
131 // Convert base.ints to BigInteger.
132 // Due to using unusual bases, eg cards of base52, this is not as simple as
133 // using BigInteger.parse()
134 var entropyInt = BigInteger.ZERO;
135 for (var i=base.ints.length-1; i>=0; i--) {
136 var thisInt = BigInteger.parse(base.ints[i]);
137 var power = (base.ints.length - 1) - i;
138 var additionalEntropy = BigInteger.parse(base.asInt).pow(power).multiply(thisInt);
139 entropyInt = entropyInt.add(additionalEntropy);
111 } 140 }
112 // Convert entropy to different foramts 141 // Convert entropy to different formats
113 var entropyInt = BigInteger.parse(base.parts.join(""), base.asInt);
114 var entropyBin = binLeadingZeros + entropyInt.toString(2); 142 var entropyBin = binLeadingZeros + entropyInt.toString(2);
115 var entropyHex = hexLeadingZeros + entropyInt.toString(16); 143 var entropyClean = base.parts.join("");
116 var entropyClean = leadingZeros.join("") + base.parts.join("");
117 var e = { 144 var e = {
118 binaryStr: entropyBin, 145 binaryStr: entropyBin,
119 hexStr: entropyHex,
120 cleanStr: entropyClean, 146 cleanStr: entropyClean,
121 base: base, 147 base: base,
122 } 148 }
@@ -129,17 +155,32 @@ window.Entropy = new (function() {
129 var binaryMatches = matchers.binary(str); 155 var binaryMatches = matchers.binary(str);
130 var hexMatches = matchers.hex(str); 156 var hexMatches = matchers.hex(str);
131 // Find the lowest base that can be used, whilst ignoring any irrelevant chars 157 // Find the lowest base that can be used, whilst ignoring any irrelevant chars
132 if (binaryMatches.length == hexMatches.length) { 158 if (binaryMatches.length == hexMatches.length && hexMatches.length > 0) {
159 var ints = binaryMatches.map(function(i) { return parseInt(i, 2) });
133 return { 160 return {
161 ints: ints,
134 parts: binaryMatches, 162 parts: binaryMatches,
135 matcher: matchers.binary, 163 matcher: matchers.binary,
136 asInt: 2, 164 asInt: 2,
137 str: "binary", 165 str: "binary",
138 } 166 }
139 } 167 }
168 var cardMatches = matchers.card(str);
169 if (cardMatches.length >= hexMatches.length / 2) {
170 var ints = convertCardsToInts(cardMatches);
171 return {
172 ints: ints,
173 parts: cardMatches,
174 matcher: matchers.card,
175 asInt: 52,
176 str: "card",
177 }
178 }
140 var diceMatches = matchers.dice(str); 179 var diceMatches = matchers.dice(str);
141 if (diceMatches.length == hexMatches.length) { 180 if (diceMatches.length == hexMatches.length && hexMatches.length > 0) {
181 var ints = diceMatches.map(function(i) { return parseInt(i) });
142 return { 182 return {
183 ints: ints,
143 parts: diceMatches, 184 parts: diceMatches,
144 matcher: matchers.dice, 185 matcher: matchers.dice,
145 asInt: 6, 186 asInt: 6,
@@ -147,8 +188,10 @@ window.Entropy = new (function() {
147 } 188 }
148 } 189 }
149 var base6Matches = matchers.base6(str); 190 var base6Matches = matchers.base6(str);
150 if (base6Matches.length == hexMatches.length) { 191 if (base6Matches.length == hexMatches.length && hexMatches.length > 0) {
192 var ints = base6Matches.map(function(i) { return parseInt(i) });
151 return { 193 return {
194 ints: ints,
152 parts: base6Matches, 195 parts: base6Matches,
153 matcher: matchers.base6, 196 matcher: matchers.base6,
154 asInt: 6, 197 asInt: 6,
@@ -156,15 +199,19 @@ window.Entropy = new (function() {
156 } 199 }
157 } 200 }
158 var base10Matches = matchers.base10(str); 201 var base10Matches = matchers.base10(str);
159 if (base10Matches.length == hexMatches.length) { 202 if (base10Matches.length == hexMatches.length && hexMatches.length > 0) {
203 var ints = base10Matches.map(function(i) { return parseInt(i) });
160 return { 204 return {
205 ints: ints,
161 parts: base10Matches, 206 parts: base10Matches,
162 matcher: matchers.base10, 207 matcher: matchers.base10,
163 asInt: 10, 208 asInt: 10,
164 str: "base 10", 209 str: "base 10",
165 } 210 }
166 } 211 }
212 var ints = hexMatches.map(function(i) { return parseInt(i, 16) });
167 return { 213 return {
214 ints: ints,
168 parts: hexMatches, 215 parts: hexMatches,
169 matcher: matchers.hex, 216 matcher: matchers.hex,
170 asInt: 16, 217 asInt: 16,
@@ -175,7 +222,11 @@ window.Entropy = new (function() {
175 // Polyfill for Math.log2 222 // Polyfill for Math.log2
176 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill 223 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill
177 Math.log2 = Math.log2 || function(x) { 224 Math.log2 = Math.log2 || function(x) {
178 return Math.log(x) * Math.LOG2E; 225 // The polyfill isn't good enough because of the poor accuracy of
226 // Math.LOG2E
227 // log2(8) gave 2.9999999999999996 which when floored causes issues.
228 // So instead use the BigInteger library to get it right.
229 return BigInteger.log(x) / BigInteger.log(2);
179 }; 230 };
180 231
181})(); 232})();