Last active
January 5, 2026 10:21
-
-
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().
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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