shuffrand
Version:
Cryptographically secure randomizer and shuffler.
165 lines (164 loc) • 11.1 kB
JavaScript
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
};