7

How do you generate cryptographically secure floats in Javascript?

This should be a plug-in for Math.random, with range (0, 1), but cryptographically secure. Example usage

cryptoFloat.random();
0.8083966837153522

Secure random numbers in javascript? shows how to create a cryptographically secure Uint32Array. Maybe this could be converted to a float somehow?

Community
  • 1
  • 1
serv-inc
  • 35,772
  • 9
  • 166
  • 188
  • 1
    Do you need only to support browsers that support `window.crypto`? – T.J. Crowder Jan 03 '16 at 10:52
  • 1
    @T.J.Crowder: Actually, for my use case, Firefox would suffice. The more general, the merrier. – serv-inc Jan 03 '16 at 11:02
  • 1
    Just to flag it up at a high-level: JavaScript's floats are 64-bit IEEE-754 numbers (well, you can get a 32-bit one if you want), which means they only have 53 (effective) significant binary digits. That's not enough for nearly any cryptographic purpose. – T.J. Crowder Jan 03 '16 at 12:01
  • @T.J.Crowder: the warning is definitely right. Yet, for some cases (statistical distributions), it is hopefully enough. – serv-inc Jan 03 '16 at 13:05

1 Answers1

8

Since the following code is quite simple and functionally equivalent to the division method, here is the alternate method of altering the bits. (This code is copied and modified from @T.J. Crowder's very helpful answer).

// A buffer with just the right size to convert to Float64
let buffer = new ArrayBuffer(8);

// View it as an Int8Array and fill it with 8 random ints
let ints = new Int8Array(buffer);
window.crypto.getRandomValues(ints);

// Set the sign (ints[7][7]) to 0 and the
// exponent (ints[7][6]-[6][5]) to just the right size 
// (all ones except for the highest bit)
ints[7] = 63;
ints[6] |= 0xf0;

// Now view it as a Float64Array, and read the one float from it
let float = new DataView(buffer).getFloat64(0, true) - 1; 
document.body.innerHTML = "The number is " + float;

Explanation:

The format of a IEEE754 double is 1 sign bit (ints[7][7]), 11 exponent bits (ints[7][6] to ints[6][5]), and the rest as mantissa (which holds the values). The formula to compute is

(-1)<sup>sign</sup> (1 + Σ<sub>i=1</sub><sup>52</sup> b<sub>52-i</sub> 2<sup>i</sup>) * 2<sup>e-1023</sup>

To set the factor to 1, the exponent needs to be 1023. It has 11 bits, thus the highest-order bit gives 2048. This needs to be set to 0, the other bits to 1.

serv-inc
  • 35,772
  • 9
  • 166
  • 188
  • Cool. Did you determine that the subnormal thing wasn't an issue, or...? – T.J. Crowder Jan 03 '16 at 15:15
  • @T.J.Crowder: This uses the maximum number of bits. Consider that each bit of the mantissa "splits the interval" by 2: The first either adds 1/2 or 0, the second adds 1/4 or 0, the third 1/8 or 0, ... It seems as though the problem of subnormal numbers only occurs if you include the exponent. (Then the numbers are clearly logarithmically spaced). Thank you. I probably would not have made it without your help. (had not worked with JS FloatArrays etc before) – serv-inc Jan 03 '16 at 15:28
  • 1
    You're welcome, I'm glad that helped. Re the subnormals, now I've read it again a few hours later, I don't think it's a problem; your method should evenly distribute the values across the range of numbers that `Number` can represent precisely. Subnormals extend the range by compromising precision. I don't think you're losing anything from a randomness perspective by leaving them out. – T.J. Crowder Jan 03 '16 at 15:35
  • 1
    I like the solution! Thanks much! Still, while almost the whole universe is little-endian these days, it might be worth being explicit: `let float = new DataView(buffer).getFloat64(0, true) - 1;` – Tom Palmer Sep 19 '17 at 12:31