Skip to content

Instantly share code, notes, and snippets.

@lichrot
Last active January 5, 2026 10:21
Show Gist options
  • Select an option

  • Save lichrot/a01f6c7bc6b6334ff823290c3aadf15c to your computer and use it in GitHub Desktop.

Select an option

Save lichrot/a01f6c7bc6b6334ff823290c3aadf15c to your computer and use it in GitHub Desktop.
Cryptographically secure randomness in JavaScript/TypeScript. Implementation is ~17x slower than regular Math.random().
const EXP_BIAS = 2 ** 10 - 2;
const EXP_SHL_OFFSET = 20;
const QUERY_BIT = 2 ** EXP_SHL_OFFSET;
const MANT_BITS = QUERY_BIT - 1;
const UNION_BYTES = new ArrayBuffer(64 / 8);
const UNION_U32 = new Uint32Array(UNION_BYTES);
const UNION_F64 = new Float64Array(UNION_BYTES);
/**
* Same as {@link Math.random}, but cryptographically secure.
*
* Based on the algorithm described in [Downey, A. B. (2007). Generating Pseudo-random Floating-Point Values.](https://allendowney.com/research/rand/downey07randfloat.pdf)
*
* The general steps are as follows:
* 1) Generate an exponent by taking `exponent bias - 1` and successively substracting `1`
* for each leading zero in random sequence of bits up to `exponent bias - 1` number of bits.
* The generated exponent thus should be in the range from `0` to `exponent bias - 1`,
* where `0` indicates a [denormal/subnormal](https://en.wikipedia.org/wiki/Subnormal_number) float.
* 2) Randomly generate mantissa/significand (as any regular integer).
* 3) If mantissa is equal to `0`, then half the time (e.g. check a random bit)
* exponent should be increased by `1`.
* 4) Finally, pack generated mantissa and exponent together as integer,
* and reinterpret the integer as float.
*/
function random(): number {
let exp = EXP_BIAS;
main: while (true) {
for (const num of crypto.getRandomValues(UNION_U32)) {
exp = (exp - Math.clz32(num)) >>> 0;
// very extremely astranomically likely
if (num !== 0) break main;
}
}
crypto.getRandomValues(UNION_U32);
const mant = UNION_U32[1] & MANT_BITS;
// @ts-ignore: branchless addition
exp += (UNION_U32[0] === 0 && mant === 0 && (UNION_U32[1] & QUERY_BIT));
UNION_U32[1] = (exp << EXP_SHL_OFFSET) | mant;
return UNION_F64[0];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment