UNPKG

shuffrand

Version:

Cryptographically secure randomizer and shuffler.

165 lines (164 loc) 11.1 kB
import { type as e } from "arktype"; const r = "0123456789", t = "abcdefghijklmnopqrstuvwxyz", n = t.toUpperCase(), a = t + n, o = t + n + r, i = r + t.slice(0, 6), s = { MAX_SAFE_INT: Number.MAX_SAFE_INTEGER, MIN_SAFE_INT: Number.MIN_SAFE_INTEGER, MAX_SAFE_DOUBLE: 1e15, MIN_SAFE_DOUBLE: -1e15, UINT32_MAX_VALUE: 4294967295, UINT32_RANGE: 4294967296, MAX_FRACTIONAL_DIGITS: 15, MIN_FRACTIONAL_DIGITS: 1, MAX_ATTEMPTS_TO_GENERATE_NUM: 30, DIGITS: r, LATIN_LOWERCASE_LETTERS: t, LATIN_UPPERCASE_LETTERS: n, LATIN_LETTERS: a, ALPHANUMERIC_CHARS: o, HEX_CHARS: i }, l = e("number").atLeast(s.MIN_SAFE_INT).atMost(s.MAX_SAFE_INT), u = e({ lowerBound: l.optional(), upperBound: l.optional(), typeOfNum: "'integer'|'double'?", exclusion: "'none'|'lower bound'|'upper bound'|'both'?", maxFracDigits: "0 <= number.integer <= 15?" }), c = e({ arr: "unknown[]?", isDestructive: "boolean?", preventIdentical: "boolean?", startIndex: "unknown?", endIndex: "unknown?" }), p = e({ length: "number.integer>=0?", characterSet: "string | 'alphanumeric' | 'numeric' | 'alpha' | 'hex' | 'uppercase' | 'lowercase' ?", noRepeat: "boolean?" }); function d(e2 = {}) { const r2 = e2.maxFracDigits ?? 3; if ("number" != typeof r2 || !Number.isInteger(r2) || r2 < s.MIN_FRACTIONAL_DIGITS || r2 > s.MAX_FRACTIONAL_DIGITS) throw new TypeError(`maxFracDigits (currently ${r2}) must be an integer between ${s.MIN_FRACTIONAL_DIGITS} and ${s.MAX_FRACTIONAL_DIGITS} (inclusive) to ensure reliable precision.`); u.assert(e2); const t2 = { lowerBound: e2.lowerBound ?? 0, upperBound: e2.upperBound ?? 2, typeOfNum: e2.typeOfNum ?? "integer", exclusion: e2.exclusion ?? "none", maxFracDigits: e2.maxFracDigits ?? 3 }; if ("double" === t2.typeOfNum && 0 === t2.maxFracDigits) throw new TypeError(`Invalid cryptoRandom parameters: 'maxFracDigits' cannot be 0 when 'typeOfNum' is 'double'. Use 'typeOfNum: "integer"' for whole numbers.`); if (void 0 === globalThis.crypto || !globalThis.crypto.getRandomValues) throw new Error("Cryptographically secure random number generator (WebCrypto API) is not available in this environment."); const { lowerBound: n2, upperBound: a2, typeOfNum: o2, exclusion: i2, maxFracDigits: l2 } = t2, c2 = Math.min(n2, a2), p2 = Math.max(n2, a2); if (c2 === p2) { if ("double" === o2 && "both" === i2) throw new TypeError(`Invalid range for double with 'both' exclusion: lowerBound (${n2}) equals upperBound (${a2}).`); return c2; } let d2, h2 = 0; const m2 = s.MAX_ATTEMPTS_TO_GENERATE_NUM; do { let e3 = c2, r3 = p2; if ("integer" === o2) { if (e3 = Math.ceil(c2), r3 = Math.floor(p2), "lower bound" !== i2 && "both" !== i2 || e3++, "upper bound" !== i2 && "both" !== i2 || r3--, e3 > r3) throw new TypeError(`Invalid integer range after exclusions: the original range of [${n2}${a2}] with exclusion '${i2}' results in an empty integer range.`); const t4 = r3 - e3 + 1, o3 = Math.ceil(Math.log2(t4) / 8), s2 = Math.pow(256, o3) - Math.pow(256, o3) % t4; let l3; const u2 = new Uint8Array(o3); do { globalThis.crypto.getRandomValues(u2), l3 = 0; for (let e4 = 0; e4 < o3; e4++) l3 = 256 * l3 + u2[e4]; } while (l3 >= s2); d2 = e3 + l3 % t4; } else { const t4 = 8, n3 = 2 ** (8 * t4); let a3; const o3 = new Uint8Array(t4); do { globalThis.crypto.getRandomValues(o3); let e4 = 0; for (let r4 = 0; r4 < t4; r4++) e4 = 256 * e4 + o3[r4]; a3 = e4 / n3; } while (1 === a3); d2 = e3 + a3 * (r3 - e3); const i3 = Number(l2); if (!isNaN(i3) && i3 >= 0) { const e4 = Math.pow(10, i3); d2 = Math.round(d2 * e4) / e4; } } let t3 = false; if ("double" === o2 ? ("lower bound" === i2 && Math.abs(d2 - c2) < Number.EPSILON || "upper bound" === i2 && Math.abs(d2 - p2) < Number.EPSILON || "both" === i2 && (Math.abs(d2 - c2) < Number.EPSILON || Math.abs(d2 - p2) < Number.EPSILON)) && (t3 = true) : "lower bound" === i2 && d2 === c2 || "upper bound" === i2 && d2 === p2 ? t3 = true : "both" !== i2 || d2 !== c2 && d2 !== p2 || (t3 = true), t3) h2++; else { if ("double" !== o2 || !Number.isInteger(d2)) break; h2++; } } while (h2 < m2); if (h2 >= m2) { let e3 = `the exclusion constraint: '${i2}'`; throw "double" === o2 && (e3 += " or the non-integer requirement"), new Error(`Unable to generate a random number within the range [${c2}${p2}] that satisfies ${e3}. Max attempts (${m2}) reached.`); } return d2; } function h(e2 = [], r2 = {}) { if (null === r2) throw new TypeError("Invalid cryptoShuffle parameters: 'options' cannot be null. Please provide an object or omit it."); if (void 0 !== r2.startIndex) { if ("number" != typeof r2.startIndex) throw new TypeError(`Invalid cryptoShuffle parameters: startIndex must be a number (was ${typeof r2.startIndex})`); if (!Number.isInteger(r2.startIndex)) throw new TypeError(`Invalid cryptoShuffle parameters: startIndex must be an integer (was ${r2.startIndex})`); } if (void 0 !== r2.endIndex) { if ("number" != typeof r2.endIndex) throw new TypeError(`Invalid cryptoShuffle parameters: endIndex must be a number (was ${typeof r2.endIndex})`); if (!Number.isInteger(r2.endIndex)) throw new TypeError(`Invalid cryptoShuffle parameters: endIndex must be an integer (was ${r2.endIndex})`); } const t2 = { arr: e2, isDestructive: r2.isDestructive ?? false, preventIdentical: r2.preventIdentical ?? false, startIndex: r2.startIndex, endIndex: r2.endIndex }; let n2; try { c.assert(t2), n2 = { arr: t2.arr ?? [], isDestructive: t2.isDestructive ?? false, preventIdentical: t2.preventIdentical ?? false, startIndex: t2.startIndex ?? 0, endIndex: t2.endIndex ?? t2.arr?.length ?? 0 }; } catch (e3) { throw e3 instanceof Error ? new TypeError(`Invalid cryptoShuffle parameters: ${e3.message}`) : new TypeError("Invalid cryptoShuffle parameters: An unknown error occurred during validation."); } const { arr: a2, isDestructive: o2, preventIdentical: i2, startIndex: s2, endIndex: l2 } = n2, u2 = a2.length; if (s2 < 0 || s2 > u2) throw new TypeError(`Invalid cryptoShuffle parameters: 'startIndex' (${s2}) must be between 0 and the array length (${u2}), inclusive.`); if (l2 < 0 || l2 > u2) throw new TypeError(`Invalid cryptoShuffle parameters: 'endIndex' (${l2}) must be between 0 and the array length (${u2}), inclusive.`); const p2 = o2 ? a2 : [...a2], h2 = p2.length; if (i2 && u2 < 2) throw new TypeError("Invalid cryptoShuffle parameters: 'preventIdentical' requires an array with at least 2 elements to guarantee a different result."); const m2 = Math.max(0, Math.min(s2, h2)), f2 = Math.max(m2, Math.min(l2, h2)); if (o2 && f2 <= m2 + 1) throw new TypeError("Invalid cryptoShuffle parameters: 'isDestructive' requires a shuffle range (defined by startIndex/endIndex) with at least 2 elements to potentially modify the original array."); let I2 = null; if (i2) try { I2 = JSON.stringify(a2); } catch (e3) { throw e3 instanceof Error ? new TypeError(`Invalid cryptoShuffle parameters: ${e3.message}`) : new TypeError("Invalid cryptoShuffle parameters: An unknown error occurred during validation."); } for (let e3 = f2 - 1; e3 > m2; e3--) { const r3 = d({ lowerBound: m2, upperBound: e3, typeOfNum: "integer", exclusion: "none" }); [p2[e3], p2[r3]] = [p2[r3], p2[e3]]; } if (i2 && null !== I2 && h2 > 1) { let e3; try { e3 = JSON.stringify(p2); } catch (e4) { throw e4 instanceof Error ? new TypeError(`Invalid cryptoShuffle parameters: ${e4.message}`) : new TypeError("Invalid cryptoShuffle parameters: An unknown error occurred during validation."); } e3 === I2 && ([p2[0], p2[h2 - 1]] = [p2[h2 - 1], p2[0]]); } return p2; } const m = { alphanumeric: s.ALPHANUMERIC_CHARS, numeric: s.DIGITS, alpha: s.LATIN_LETTERS, hex: s.HEX_CHARS, uppercase: s.LATIN_UPPERCASE_LETTERS, lowercase: s.LATIN_LOWERCASE_LETTERS }; function f(e2 = {}) { if (null === e2) throw new TypeError("Invalid cryptoString parameters: 'rawParams' cannot be null. Please provide an object or omit it."); try { p.assert(e2); } catch (e3) { throw e3 instanceof Error ? new TypeError(`Invalid cryptoString parameters: ${e3.message}`) : new TypeError("Invalid cryptoString parameters: An unknown error occurred during validation."); } const r2 = e2.length ?? 16, t2 = e2.characterSet ?? "alphanumeric", n2 = e2.noRepeat ?? false; if (r2 > 1e6) throw new TypeError("Invalid cryptoString parameters: 'length' exceeds maximum safe limit of 1,000,000 characters."); let a2; if ("string" == typeof t2 && Object.hasOwn(m, t2)) a2 = m[t2]; else { a2 = t2; const e3 = Array.from(a2); if (new Set(e3).size !== e3.length) throw new TypeError("Invalid cryptoString parameters: Custom character set contains duplicate characters, which would skew randomness distribution."); } if (0 === a2.length && r2 > 0) throw new TypeError("Invalid cryptoString parameters: The resolved 'characterSet' cannot be empty."); const o2 = Array.from(new Set(Array.from(a2))); if (o2.length < 2 && r2 > 1) throw new TypeError("Invalid cryptoString parameters: Character set must contain at least 2 unique characters to generate a string longer than 1."); if (n2 && r2 > o2.length) throw new TypeError(`Invalid cryptoString parameters: Cannot generate a string of length ${r2} with no repeats from a character set with only ${o2.length} unique characters.`); if (0 === r2) return ""; if (n2) return h(o2).slice(0, r2).join(""); { const e3 = new Array(r2), t3 = Array.from(a2), n3 = t3.length; for (let a3 = 0; a3 < r2; a3++) { const r3 = d({ lowerBound: 0, upperBound: n3 - 1, typeOfNum: "integer", exclusion: "none" }); e3[a3] = t3[r3]; } return e3.join(""); } } function I(e2 = {}) { try { p.assert(e2); } catch (e3) { throw e3 instanceof Error ? new TypeError(`Invalid cryptoString parameters: ${e3.message}`) : new TypeError("Invalid cryptoString parameters: An unknown error occurred during validation."); } const r2 = e2.length ?? 16, t2 = e2.characterSet ?? "alphanumeric", n2 = e2.noRepeat ?? false; let a2; a2 = "string" == typeof t2 && Object.hasOwn(m, t2) ? m[t2] : t2; const o2 = new Set(Array.from(a2)).size; if (n2) { if (r2 > o2) throw new TypeError(`Invalid calculateStringEntropy parameters: Cannot calculate entropy for a length of ${r2} with no repeats from a character set with only ${o2} unique characters.`); let e3 = 0; for (let t3 = 0; t3 < r2; t3++) { const r3 = o2 - t3; r3 > 0 && (e3 += Math.log2(r3)); } return e3; } if (o2 < 2 && r2 > 1) throw new TypeError("Invalid calculateStringEntropy parameters: Character set must contain at least 2 unique characters to calculate meaningful entropy."); return 0 === o2 ? 0 : Math.log2(o2) * r2; } export { s as Constants, I as calculateStringEntropy, d as cryptoRandom, h as cryptoShuffle, f as cryptoString };