- // Assume cards are NOT replaced.
- // Additional entropy decreases as more cards are used. This means
- // total possible entropy is measured using n!, not base^n.
- // eg the second last card can be only one of two, not one of fifty two
- // so the added entropy for that card is only one bit at most
- function processCardEntropy(cards) {
- // Track how many instances of each card have been used, and thus
- // how many decks are in use.
- var cardCounts = {};
- var numberOfDecks = 0;
- // Work out number of decks by max(duplicates)
- for (var i=0; i<cards.length; i++) {
- // Get the card that was drawn
- var cardLower = cards[i];
- var card = cardLower.toUpperCase();
- // Initialize the count for this card if needed
- if (!(card in cardCounts)) {
- cardCounts[card] = 0;
- }
- cardCounts[card] += 1;
- // See if this is max(duplicates)
- if (cardCounts[card] > numberOfDecks) {
- numberOfDecks = cardCounts[card];
- }
- }
- // Work out the total number of bits for this many decks
- // See http://crypto.stackexchange.com/q/41886
- var gainedBits = Math.log2(factorial(52 * numberOfDecks));
- var lostBits = 52 * Math.log2(factorial(numberOfDecks));
- var maxBits = gainedBits - lostBits;
- // Convert the drawn cards to a binary representation.
- // The exact technique for doing this is unclear.
- // See
- // http://crypto.stackexchange.com/a/41896
- // "I even doubt that this is well defined (only the average entropy
- // is, I believe)."
- // See
- // https://github.com/iancoleman/bip39/issues/33#issuecomment-263021856
- // "The binary representation can be the first log(permutations,2) bits
- // of the sha-2 hash of the normalized deck string."
- //
- // In this specific implementation, the first N bits of the hash of the
- // normalized cards string is being used. Uppercase, no spaces; eg
- // sha256("AH8DQSTC2H")
- var totalCards = numberOfDecks * 52;
- var percentUsed = cards.length / totalCards;
- // Calculate the average number of bits of entropy for the number of
- // cards drawn.
- var numberOfBits = Math.floor(maxBits * percentUsed);
- // Create a normalized string of the selected cards
- var normalizedCards = cards.join("").toUpperCase();
- // Convert to binary using the SHA256 hash of the normalized cards.
- // If the number of bits is more than 256, multiple rounds of hashing
- // are used until the required number of bits is reached.
- var entropyBin = "";
- var iterations = 0;
- while (entropyBin.length < numberOfBits) {
- var hashedCards = sjcl.hash.sha256.hash(normalizedCards);
- for (var j=0; j<iterations; j++) {
- hashedCards = sjcl.hash.sha256.hash(hashedCards);
- }
- var hashHex = sjcl.codec.hex.fromBits(hashedCards);
- for (var i=0; i<hashHex.length; i++) {
- var decimal = parseInt(hashHex[i], 16);
- var binary = decimal.toString(2);
- while (binary.length < 4) {
- binary = "0" + binary;
- }
- entropyBin = entropyBin + binary;
- }
- iterations = iterations + 1;
- }
- // Truncate to the appropriate number of bits.
- entropyBin = entropyBin.substring(0, numberOfBits);
- // Get the number of bits per event
- bitsPerEvent = maxBits / totalCards;
- return {
- binaryStr: entropyBin,
- bitsPerEvent: bitsPerEvent,
- }
- }
-
- // Polyfill for Math.log2
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill
- Math.log2 = Math.log2 || function(x) {
- // The polyfill isn't good enough because of the poor accuracy of
- // Math.LOG2E
- // log2(8) gave 2.9999999999999996 which when floored causes issues.
- // So instead use the BigInteger library to get it right.
- return BigInteger.log(x) / BigInteger.log(2);
- };
-
- // Depends on BigInteger
- function factorial(n) {
- if (n == 0) {
- return 1;
- }
- f = BigInteger.ONE;
- for (var i=1; i<=n; i++) {
- f = f.multiply(new BigInteger(i));
- }
- return f;
- }
-