aboutsummaryrefslogtreecommitdiff
path: root/src/js/entropy.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/entropy.js')
-rw-r--r--src/js/entropy.js169
1 files changed, 79 insertions, 90 deletions
diff --git a/src/js/entropy.js b/src/js/entropy.js
index 8a0c799..d04d861 100644
--- a/src/js/entropy.js
+++ b/src/js/entropy.js
@@ -120,97 +120,9 @@ window.Entropy = new (function() {
120 while (entropyBin.length < expectedBits) { 120 while (entropyBin.length < expectedBits) {
121 entropyBin = "0" + entropyBin; 121 entropyBin = "0" + entropyBin;
122 } 122 }
123 // Assume cards are NOT replaced. 123 // Cards binary must be handled differently, since they're not replaced
124 // Additional entropy decreases as more cards are used. This means
125 // total possible entropy is measured using n!, not base^n.
126 // eg the second last card can be only one of two, not one of fifty two
127 // so the added entropy for that card is only one bit at most
128 if (base.asInt == 52) { 124 if (base.asInt == 52) {
129 var totalDecks = Math.ceil(base.parts.length / 52); 125 entropyBin = getCardBinary(base.parts);
130 var totalCards = totalDecks * 52;
131 var totalCombos = factorial(52).pow(totalDecks);
132 var totalRemainingCards = totalCards - base.parts.length;
133 var remainingDecks = Math.floor(totalRemainingCards / 52);
134 var remainingCards = totalRemainingCards % 52;
135 var remainingCombos = factorial(52).pow(remainingDecks).multiply(factorial(remainingCards));
136 var currentCombos = totalCombos.divide(remainingCombos);
137 var numberOfBits = Math.log2(currentCombos);
138 var maxWithoutReplace = BigInteger.pow(2, numberOfBits);
139 // Use a bunch of sorted decks to measure entropy from, populated
140 // as needed.
141 var sortedDecks = [];
142 // Initialize the final entropy value for these cards
143 var entropyInt = BigInteger.ZERO;
144 // Track how many instances of each card have been used, and thus
145 // how many decks are in use.
146 var cardCounts = {};
147 // Track the total bits of entropy that remain, which diminishes as
148 // each card is drawn.
149 var totalBitsLeft = numberOfBits;
150 // Work out entropy contribution of each card drawn
151 for (var i=0; i<base.parts.length; i++) {
152 // Get the card that was drawn
153 var cardLower = base.parts[i];
154 var card = cardLower.toUpperCase();
155 // Initialize the deck for this card if needed, to track how
156 // much entropy it adds.
157 if (!(card in cardCounts)) {
158 cardCounts[card] = 0;
159 }
160 // Get the deck this card is from
161 var deckIndex = cardCounts[card];
162 while (deckIndex > sortedDecks.length-1) {
163 sortedDecks.push(getSortedDeck());
164 }
165 // See how many bits this card contributes (depends on how many
166 // are left in the deck it's from)
167 var deckForCard = sortedDecks[deckIndex];
168 var cardsLeftInDeck = deckForCard.length;
169 var additionalBits = Math.log2(cardsLeftInDeck);
170 // Work out the min and max value for this card
171 var nextTotalBitsLeft = totalBitsLeft - additionalBits;
172 var minPossibleNewEntropy = TWO.pow(nextTotalBitsLeft).subtract(1);
173 var maxPossibleNewEntropy = TWO.pow(totalBitsLeft).subtract(1);
174 var diff = maxPossibleNewEntropy.subtract(minPossibleNewEntropy);
175 // BigInteger aggresively floors numbers which greatly affects
176 // the small numbers. In that case, use native Math library
177 var useBigInt = totalBitsLeft >= 32;
178 if (!useBigInt) {
179 minPossibleNewEntropy = Math.round(Math.pow(2, nextTotalBitsLeft)-1);
180 maxPossibleNewEntropy = Math.round(Math.pow(2, totalBitsLeft)-1);
181 diff = maxPossibleNewEntropy - minPossibleNewEntropy;
182 }
183 // Scale the value between possible min and max depending on
184 // this card value
185 var thisCardIndex = deckForCard.indexOf(card);
186 var toAdd = BigInteger.ZERO;
187 if (cardsLeftInDeck > 1) {
188 if (useBigInt) {
189 toAdd = diff.multiply(thisCardIndex)
190 .divide(deckForCard.length - 1)
191 .add(minPossibleNewEntropy);
192 }
193 else {
194 var ratio = thisCardIndex / (deckForCard.length -1);
195 var f = diff * ratio;
196 toAdd = new BigInteger(f).add(minPossibleNewEntropy);
197 }
198 }
199 // Add this card entropy to existing entropy
200 entropyInt = entropyInt.add(toAdd);
201 // Remove this card from the deck it comes from
202 deckForCard.splice(thisCardIndex,1);
203 // Ensure the next insance of this card uses the next deck
204 cardCounts[card] = cardCounts[card] + 1;
205 // Next card drawn has less total remaining bits to work with
206 totalBitsLeft = nextTotalBitsLeft;
207 }
208 // Convert to binary
209 var entropyBin = entropyInt.toString(2);
210 var numberOfBitsInt = Math.floor(numberOfBits);
211 while (entropyBin.length < numberOfBitsInt) {
212 entropyBin = "0" + entropyBin;
213 }
214 } 126 }
215 // Supply a 'filtered' entropy string for display purposes 127 // Supply a 'filtered' entropy string for display purposes
216 var entropyClean = base.parts.join(""); 128 var entropyClean = base.parts.join("");
@@ -319,6 +231,83 @@ window.Entropy = new (function() {
319 } 231 }
320 } 232 }
321 233
234 // Assume cards are NOT replaced.
235 // Additional entropy decreases as more cards are used. This means
236 // total possible entropy is measured using n!, not base^n.
237 // eg the second last card can be only one of two, not one of fifty two
238 // so the added entropy for that card is only one bit at most
239 function getCardBinary(cards) {
240 // Track how many instances of each card have been used, and thus
241 // how many decks are in use.
242 var cardCounts = {};
243 var numberOfDecks = 0;
244 // Work out number of decks by max(duplicates)
245 for (var i=0; i<cards.length; i++) {
246 // Get the card that was drawn
247 var cardLower = cards[i];
248 var card = cardLower.toUpperCase();
249 // Initialize the count for this card if needed
250 if (!(card in cardCounts)) {
251 cardCounts[card] = 0;
252 }
253 cardCounts[card] += 1;
254 // See if this is max(duplicates)
255 if (cardCounts[card] > numberOfDecks) {
256 numberOfDecks = cardCounts[card];
257 }
258 }
259 // Work out the total number of bits for this many decks
260 // See http://crypto.stackexchange.com/q/41886
261 var gainedBits = Math.log2(factorial(52 * numberOfDecks));
262 var lostBits = 52 * Math.log2(factorial(numberOfDecks));
263 var maxBits = gainedBits - lostBits;
264 // Convert the drawn cards to a binary representation.
265 // The exact technique for doing this is unclear.
266 // See
267 // http://crypto.stackexchange.com/a/41896
268 // "I even doubt that this is well defined (only the average entropy
269 // is, I believe)."
270 // See
271 // https://github.com/iancoleman/bip39/issues/33#issuecomment-263021856
272 // "The binary representation can be the first log(permutations,2) bits
273 // of the sha-2 hash of the normalized deck string."
274 //
275 // In this specific implementation, the first N bits of the hash of the
276 // normalized cards string is being used. Uppercase, no spaces; eg
277 // sha256("AH8DQSTC2H")
278 var totalCards = numberOfDecks * 52;
279 var percentUsed = cards.length / totalCards;
280 // Calculate the average number of bits of entropy for the number of
281 // cards drawn.
282 var numberOfBits = Math.floor(maxBits * percentUsed);
283 // Create a normalized string of the selected cards
284 var normalizedCards = cards.join("").toUpperCase();
285 // Convert to binary using the SHA256 hash of the normalized cards.
286 // If the number of bits is more than 256, multiple rounds of hashing
287 // are used until the required number of bits is reached.
288 var entropyBin = "";
289 var iterations = 0;
290 while (entropyBin.length < numberOfBits) {
291 var hashedCards = sjcl.hash.sha256.hash(normalizedCards);
292 for (var j=0; j<iterations; j++) {
293 hashedCards = sjcl.hash.sha256.hash(hashedCards);
294 }
295 var hashHex = sjcl.codec.hex.fromBits(hashedCards);
296 for (var i=0; i<hashHex.length; i++) {
297 var decimal = parseInt(hashHex[i], 16);
298 var binary = decimal.toString(2);
299 while (binary.length < 4) {
300 binary = "0" + binary;
301 }
302 entropyBin = entropyBin + binary;
303 }
304 iterations = iterations + 1;
305 }
306 // Truncate to the appropriate number of bits.
307 entropyBin = entropyBin.substring(0, numberOfBits);
308 return entropyBin;
309 }
310
322 // Polyfill for Math.log2 311 // Polyfill for Math.log2
323 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill 312 // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill
324 Math.log2 = Math.log2 || function(x) { 313 Math.log2 = Math.log2 || function(x) {