diff options
author | Ian Coleman <coleman.ian@gmail.com> | 2016-11-25 14:24:58 +1100 |
---|---|---|
committer | Ian Coleman <coleman.ian@gmail.com> | 2016-11-25 14:24:58 +1100 |
commit | 886f06ee6bbca2e6e05109dbd7e264bdea35238f (patch) | |
tree | 89d7d36e913c497516347b20e7818ad9dd804f25 /src | |
parent | 9e97eb7684bf2c2d9c5276561f29fe50caf31f82 (diff) | |
download | BIP39-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')
-rw-r--r-- | src/js/entropy.js | 96 |
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 | ||
17 | window.Entropy = new (function() { | 17 | window.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. |