diff options
author | Ian Coleman <coleman.ian@gmail.com> | 2016-11-07 16:01:21 +1100 |
---|---|---|
committer | Ian Coleman <coleman.ian@gmail.com> | 2016-11-07 16:01:21 +1100 |
commit | adc8ce127d4f8ea0d7e5ede6a82c2791d60ff4d2 (patch) | |
tree | da8a4bfe57f207d52822cab17ae7a8af150271bf /src | |
parent | 6606c50fd3af59483a5524170d9d2c3ec213a60f (diff) | |
download | BIP39-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')
-rw-r--r-- | src/js/entropy.js | 123 | ||||
-rw-r--r-- | src/js/index.js | 21 |
2 files changed, 99 insertions, 45 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 | })(); |
diff --git a/src/js/index.js b/src/js/index.js index 1cc77d3..a717a9e 100644 --- a/src/js/index.js +++ b/src/js/index.js | |||
@@ -738,33 +738,36 @@ | |||
738 | // Show entropy details | 738 | // Show entropy details |
739 | var extraBits = 32 - (entropy.binaryStr.length % 32); | 739 | var extraBits = 32 - (entropy.binaryStr.length % 32); |
740 | var extraChars = Math.ceil(extraBits * Math.log(2) / Math.log(entropy.base.asInt)); | 740 | var extraChars = Math.ceil(extraBits * Math.log(2) / Math.log(entropy.base.asInt)); |
741 | var words = Math.floor(entropy.binaryStr.length / 32) * 3; | ||
741 | var strength = "an extremely weak"; | 742 | var strength = "an extremely weak"; |
742 | if (entropy.hexStr.length >= 8) { | 743 | if (words >= 3) { |
743 | strength = "a very weak"; | 744 | strength = "a very weak"; |
744 | } | 745 | } |
745 | if (entropy.hexStr.length >= 12) { | 746 | if (words >= 6) { |
746 | strength = "a weak"; | 747 | strength = "a weak"; |
747 | } | 748 | } |
748 | if (entropy.hexStr.length >= 24) { | 749 | if (words >= 9) { |
749 | strength = "a strong"; | 750 | strength = "a strong"; |
750 | } | 751 | } |
751 | if (entropy.hexStr.length >= 32) { | 752 | if (words >= 12) { |
752 | strength = "a very strong"; | 753 | strength = "a very strong"; |
753 | } | 754 | } |
754 | if (entropy.hexStr.length >= 40) { | 755 | if (words >= 15) { |
755 | strength = "an extremely strong"; | 756 | strength = "an extremely strong"; |
756 | } | 757 | } |
757 | if (entropy.hexStr.length >=48) { | 758 | if (words >= 18) { |
758 | strength = "an even stronger" | 759 | strength = "an even stronger" |
759 | } | 760 | } |
760 | var msg = "Have " + entropy.binaryStr.length + " bits of entropy, " + extraChars + " more " + entropy.base.str + " chars required to generate " + strength + " mnemonic: " + entropy.cleanStr; | 761 | var msg = "Have " + entropy.binaryStr.length + " bits of entropy, " + extraChars + " more " + entropy.base.str + " chars required to generate " + strength + " mnemonic: " + entropy.cleanStr; |
761 | showEntropyError(msg); | 762 | showEntropyError(msg); |
762 | // Discard trailing entropy | 763 | // Discard trailing entropy |
763 | var hexStr = entropy.hexStr.substring(0, Math.floor(entropy.hexStr.length / 8) * 8); | 764 | var bitsToUse = Math.floor(entropy.binaryStr.length / 32) * 32; |
765 | var binaryStr = entropy.binaryStr.substring(0, bitsToUse); | ||
764 | // Convert entropy string to numeric array | 766 | // Convert entropy string to numeric array |
765 | var entropyArr = []; | 767 | var entropyArr = []; |
766 | for (var i=0; i<hexStr.length / 2; i++) { | 768 | for (var i=0; i<binaryStr.length / 8; i++) { |
767 | var entropyByte = parseInt(hexStr[i*2].concat(hexStr[i*2+1]), 16); | 769 | var byteAsBits = binaryStr.substring(i*8, i*8+8); |
770 | var entropyByte = parseInt(byteAsBits, 2); | ||
768 | entropyArr.push(entropyByte) | 771 | entropyArr.push(entropyByte) |
769 | } | 772 | } |
770 | // Convert entropy array to mnemonic | 773 | // Convert entropy array to mnemonic |