UNPKG

shuffrand

Version:

Cryptographically secure randomness and shuffling — with soul.

101 lines (100 loc) 3.94 kB
import { randomParamsSchema as x } from "./types.es.js"; import { Constants as h } from "./constants.es.js"; /** * shuffrand - Cryptographically Secure Random Number Generation * * This file contains the core logic for generating cryptographically secure random numbers, * adhering to a flat, dot-categorized structure for clarity. * * @author Doron Brayer <doronbrayer@outlook.com> * @license MIT */ function _(u = {}) { const b = u.maxFracDigits ?? 3; if (typeof b != "number" || !Number.isInteger(b) || b < h.MIN_FRACTIONAL_DIGITS || b > h.MAX_FRACTIONAL_DIGITS) throw new TypeError( `maxFracDigits (currently ${b}) must be an integer between ${h.MIN_FRACTIONAL_DIGITS} and ${h.MAX_FRACTIONAL_DIGITS} (inclusive) to ensure reliable precision.` ); x.assert(u); const w = { lowerBound: u.lowerBound ?? 0, upperBound: u.upperBound ?? 2, // Corrected default upperBound to 2 as per test plan typeOfNum: u.typeOfNum ?? "integer", exclusion: u.exclusion ?? "none", maxFracDigits: u.maxFracDigits ?? 3 }; if (w.typeOfNum === "double" && w.maxFracDigits === 0) throw new TypeError( `Invalid cryptoRandom parameters: 'maxFracDigits' cannot be 0 when 'typeOfNum' is 'double'. Use 'typeOfNum: "integer"' for whole numbers.` ); if (typeof globalThis.crypto > "u" || !globalThis.crypto.getRandomValues) throw new Error( "Cryptographically secure random number generator (WebCrypto API) is not available in this environment." ); const { lowerBound: f, upperBound: g, typeOfNum: d, exclusion: t, maxFracDigits: M } = w, n = Math.min(f, g), i = Math.max(f, g); if (n === i) { if (d === "double" && t === "both") throw new TypeError( `Invalid range for double with 'both' exclusion: lowerBound (${f}) equals upperBound (${g}).` ); return n; } let e, N = 0; const T = h.MAX_ATTEMPTS_TO_GENERATE_NUM; do { let r = n, m = i; if (d === "integer") { if (r = Math.ceil(n), m = Math.floor(i), (t === "lower bound" || t === "both") && r++, (t === "upper bound" || t === "both") && m--, r > m) throw new TypeError( `Invalid integer range after exclusions: the original range of [${f}${g}] with exclusion '${t}' results in an empty integer range.` ); const y = m - r + 1, s = Math.ceil(Math.log2(y) / 8), p = Math.pow(256, s) - Math.pow(256, s) % y; let a; const c = new Uint8Array(s); do { globalThis.crypto.getRandomValues(c), a = 0; for (let o = 0; o < s; o++) a = a * 256 + c[o]; } while (a >= p); e = r + a % y; } else { const s = 18446744073709552e3; let p; const a = new Uint8Array(8); do { globalThis.crypto.getRandomValues(a); let o = 0; for (let I = 0; I < 8; I++) o = o * 256 + a[I]; p = o / s; } while (p === 1); e = r + p * (m - r); const c = Number(M); if (!isNaN(c) && c >= 0) { const o = Math.pow(10, c); e = Math.round(e * o) / o; } } let l = !1; if (d === "double" ? (t === "lower bound" && Math.abs(e - n) < Number.EPSILON || t === "upper bound" && Math.abs(e - i) < Number.EPSILON || t === "both" && (Math.abs(e - n) < Number.EPSILON || Math.abs(e - i) < Number.EPSILON)) && (l = !0) : (t === "lower bound" && e === n || t === "upper bound" && e === i || t === "both" && (e === n || e === i)) && (l = !0), l) { N++; continue; } if (d === "double" && Number.isInteger(e)) { N++; continue; } break; } while (N < T); if (N >= T) { let r = `the exclusion constraint: '${t}'`; throw d === "double" && (r += " or the non-integer requirement"), new Error( `Unable to generate a random number within the range [${n}${i}] that satisfies ${r}. Max attempts (${T}) reached.` ); } return e; } export { _ as cryptoRandom };