aboutsummaryrefslogtreecommitdiff
path: root/src/js
diff options
context:
space:
mode:
authorIan Coleman <coleman.ian@gmail.com>2016-11-25 14:24:58 +1100
committerIan Coleman <coleman.ian@gmail.com>2016-11-25 14:24:58 +1100
commit886f06ee6bbca2e6e05109dbd7e264bdea35238f (patch)
tree89d7d36e913c497516347b20e7818ad9dd804f25 /src/js
parent9e97eb7684bf2c2d9c5276561f29fe50caf31f82 (diff)
downloadBIP39-886f06ee6bbca2e6e05109dbd7e264bdea35238f.tar.gz
BIP39-886f06ee6bbca2e6e05109dbd7e264bdea35238f.tar.zst
BIP39-886f06ee6bbca2e6e05109dbd7e264bdea35238f.zip
Card entropy calculation bugfix
See https://github.com/iancoleman/bip39/issues/33
Diffstat (limited to 'src/js')
-rw-r--r--src/js/entropy.js96
1 files changed, 84 insertions, 12 deletions
diff --git a/src/js/entropy.js b/src/js/entropy.js
index f41cf80..8a0c799 100644
--- a/src/js/entropy.js
+++ b/src/js/entropy.js
@@ -16,6 +16,8 @@
16 16
17window.Entropy = new (function() { 17window.Entropy = new (function() {
18 18
19 var TWO = new BigInteger(2);
20
19 // matchers returns an array of the matched events for each type of entropy. 21 // matchers returns an array of the matched events for each type of entropy.
20 // eg 22 // eg
21 // matchers.binary("010") returns ["0", "1", "0"] 23 // matchers.binary("010") returns ["0", "1", "0"]
@@ -124,7 +126,6 @@ window.Entropy = new (function() {
124 // eg the second last card can be only one of two, not one of fifty two 126 // eg the second last card can be only one of two, not one of fifty two
125 // so the added entropy for that card is only one bit at most 127 // so the added entropy for that card is only one bit at most
126 if (base.asInt == 52) { 128 if (base.asInt == 52) {
127 // Get the maximum value WITHOUT replacement
128 var totalDecks = Math.ceil(base.parts.length / 52); 129 var totalDecks = Math.ceil(base.parts.length / 52);
129 var totalCards = totalDecks * 52; 130 var totalCards = totalDecks * 52;
130 var totalCombos = factorial(52).pow(totalDecks); 131 var totalCombos = factorial(52).pow(totalDecks);
@@ -135,18 +136,77 @@ window.Entropy = new (function() {
135 var currentCombos = totalCombos.divide(remainingCombos); 136 var currentCombos = totalCombos.divide(remainingCombos);
136 var numberOfBits = Math.log2(currentCombos); 137 var numberOfBits = Math.log2(currentCombos);
137 var maxWithoutReplace = BigInteger.pow(2, numberOfBits); 138 var maxWithoutReplace = BigInteger.pow(2, numberOfBits);
138 // aggresive flooring of numberOfBits by BigInteger.pow means a 139 // Use a bunch of sorted decks to measure entropy from, populated
139 // more accurate result can be had for small numbers using the 140 // as needed.
140 // built-in Math.pow function. 141 var sortedDecks = [];
141 if (numberOfBits < 32) { 142 // Initialize the final entropy value for these cards
142 maxWithoutReplace = BigInteger(Math.round(Math.pow(2, numberOfBits))); 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;
143 } 207 }
144 // Get the maximum value WITH replacement 208 // Convert to binary
145 var maxWithReplace = BigInteger.pow(52, base.parts.length); 209 var entropyBin = entropyInt.toString(2);
146 // Calculate the new value by scaling the original value down
147 var withoutReplace = entropyInt.multiply(maxWithoutReplace).divide(maxWithReplace);
148 // Left pad with zeros based on number of bits
149 var entropyBin = withoutReplace.toString(2);
150 var numberOfBitsInt = Math.floor(numberOfBits); 210 var numberOfBitsInt = Math.floor(numberOfBits);
151 while (entropyBin.length < numberOfBitsInt) { 211 while (entropyBin.length < numberOfBitsInt) {
152 entropyBin = "0" + entropyBin; 212 entropyBin = "0" + entropyBin;
@@ -177,6 +237,18 @@ window.Entropy = new (function() {
177 return e; 237 return e;
178 } 238 }
179 239
240 function getSortedDeck() {
241 var s = [];
242 var suits = "CDHS";
243 var values = "A23456789TJQK";
244 for (var i=0; i<suits.length; i++) {
245 for (var j=0; j<values.length; j++) {
246 s.push(values[j]+suits[i]);
247 }
248 }
249 return s;
250 }
251
180 function getBase(str) { 252 function getBase(str) {
181 // Need to get the lowest base for the supplied entropy. 253 // Need to get the lowest base for the supplied entropy.
182 // This prevents interpreting, say, dice rolls as hexadecimal. 254 // This prevents interpreting, say, dice rolls as hexadecimal.