├── LICENSE ├── README.md ├── index.js ├── package.json ├── test.js └── verify.js /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Emil Bay 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `secure-random-double` 2 | 3 | > Generate secure, random doubles that are unbiased and equidistant 4 | 5 | This module generate doubles (`Number`) in the range `[0, 1)`, with all points 6 | being equidistant and unbiased. 7 | 8 | ## Usage 9 | 10 | ```js 11 | const double = require('secure-random-double') 12 | 13 | const n = double() 14 | ``` 15 | 16 | ## API 17 | 18 | ### `const n = double()` 19 | 20 | Returns a `Number` (double-floating point) in the range `[0, 1)` from a CSPRNG. 21 | 22 | ### `const points = double.POINTS` 23 | 24 | Number of representable points as a `BigInt` 25 | 26 | ### `const points = double.BASE` 27 | 28 | The basis representation of `1` as a `BigInt` 29 | 30 | ### `const points = double.DISTANCE` 31 | 32 | Distance between consecutive points as a `BigInt` 33 | 34 | ## Math 35 | 36 | Random doubles are generated in the range `[1, 2)` and then `1` is subtracted to 37 | generate numbers in `[0, 1)`. In this range all points are 38 | `2.220446049250313080847263336181640625E-16` apart, with equal probability, 39 | meaning no numbers are biased or have multiple representations. 40 | There are `2^52 = 4503599627370496` points in the range, including 0. 41 | 42 | Heres a list of fun points: 43 | 44 | ``` 45 | 0% 0 46 | 0% + ε 0.0000000000000002220446049250313080847263336181640625 47 | 50% 0.5000000000000000000000000000000000000000000000000000 48 | 100% 0.9999999999999997779553950749686919152736663818359375 49 | ``` 50 | 51 | ## Install 52 | 53 | ```sh 54 | npm install secure-random-double 55 | ``` 56 | 57 | ## License 58 | 59 | [ISC](LICENSE) 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const sodium = require('sodium-universal') 2 | 3 | const write = Buffer.alloc(8) 4 | const read = new Float64Array(write.buffer) 5 | // s|exponent 6 | write[7] = 0b00111111 7 | 8 | module.exports = function () { 9 | sodium.randombytes_buf(write.subarray(0, 7)) 10 | // exponent| 11 | write[6] = write[6] | 0b11110000 12 | return read[0] - 1 13 | } 14 | 15 | module.exports.BASE = 10000000000000000000000000000000000000000000000000000n 16 | module.exports.DISTANCE = 2220446049250313080847263336181640625n 17 | module.exports.POINTS = 4503599627370496n // 2 ** 52 - 1 points and '0' 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secure-random-double", 3 | "version": "1.0.2", 4 | "description": "Generate secure, random doubles that are unbiased and equidistant", 5 | "main": "index.js", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "dependencies": { 10 | "sodium-universal": "^2.0.0" 11 | }, 12 | "devDependencies": { 13 | "standard": "^14.3.1", 14 | "tape": "^4.11.0" 15 | }, 16 | "scripts": { 17 | "pretest": "standard", 18 | "test": "tape test.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/emilbayes/secure-random-double.git" 23 | }, 24 | "keywords": [], 25 | "author": "Emil Bay ", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/emilbayes/secure-random-double/issues" 29 | }, 30 | "homepage": "https://github.com/emilbayes/secure-random-double#readme" 31 | } 32 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* global BigInt */ 2 | const double = require('.') 3 | const assert = require('assert') 4 | 5 | for (var i = 0; i < 10000; i++) { 6 | var a = BigInt(double().toFixed(52).replace('.', '')) 7 | var b = BigInt(double().toFixed(52).replace('.', '')) 8 | 9 | assert.ok(a / double.DISTANCE < double.POINTS) 10 | assert.ok(b / double.DISTANCE < double.POINTS) 11 | assert.ok(a % double.DISTANCE === 0n) 12 | assert.ok(b % double.DISTANCE === 0n) 13 | assert.ok((a - b) % double.DISTANCE === 0n) 14 | } 15 | -------------------------------------------------------------------------------- /verify.js: -------------------------------------------------------------------------------- 1 | const write = new Uint8Array(8) 2 | const read = new Float64Array(write.buffer) 3 | function gen (frac) { 4 | const sign = 0b0 5 | const exp = 0b11110011001 6 | 7 | const lower = frac / 0x100000000 8 | 9 | write[0] = frac & 0xff // 8 10 | write[1] = (frac & 0xff00) >> 8 // 16 11 | write[2] = (frac & 0xff0000) >> 16// 24 12 | write[3] = (frac & 0xff000000) >> 24 // 32 13 | write[4] = lower & 0xff // 40 14 | write[5] = (lower & 0xff00) >> 8 // 48 15 | write[6] = ((exp & 0b00000001111) << 4) | (lower & 0b000011111000000000000000) >> 16 // 53 16 | write[7] = (sign << 7) | (exp & 0b11111110000) >>> 4 17 | 18 | return read[0] 19 | } 20 | 21 | var min = gen(0b0000000000000000000000000000000000000000000000000001) - 1 22 | print(min) 23 | var min1 = gen(0b0000000000000000000000000000000000000000000000000010) 24 | print(min1) 25 | var half = gen(0b1000000000000000000000000000000000000000000000000000) 26 | print(half) 27 | var max1 = gen(0b0111111111111111111111111111111111111111111111111111) 28 | print(max1) 29 | var max = gen(0b1111111111111111111111111111111111111111111111111111) 30 | print(max) 31 | 32 | function print (float) { 33 | read[0] = float 34 | 35 | console.log(byte(write[7]) + 36 | byte(write[6]) + 37 | byte(write[5]) + 38 | byte(write[4]) + 39 | byte(write[3]) + 40 | byte(write[2]) + 41 | byte(write[1]) + 42 | byte(write[0]) 43 | ) 44 | console.log('s' + '⎣ exponent⎦' + '⎣ fraction ⎦') 45 | } 46 | 47 | function sign (float) { 48 | read[0] = float 49 | return (write[7] >>> 7) & 0b1 50 | } 51 | 52 | function exp (float) { 53 | read[0] = float 54 | return (write[7] & 0b01111111) << 4 | (write[6] & 0b11110000) >>> 4 55 | } 56 | 57 | function frac (float) { 58 | read[0] = float 59 | 60 | return ( 61 | (write[0] | write[1] << 8 | write[2] << 16 | write[3] << 24) + 62 | (write[4] | write[5] << 8 | (write[6] & 0b00001111) << 16) * 0x100000000 63 | ) 64 | } 65 | 66 | function byte (b) { 67 | return b.toString(2).padStart(8, '0') 68 | } 69 | --------------------------------------------------------------------------------