UNPKG

@h0llyw00dzz/crypto-rand

Version:

Cryptographically secure random utilities for Node.js and browsers

819 lines 37.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.randBigIntAsync = exports.randPrimeAsync = exports.randVersionAsync = exports.randSeedAsync = exports.randBase64Async = exports.randHexAsync = exports.randBytesAsync = exports.randAsync = exports.randExponential = exports.randBigInt = exports.randPrime = exports.randLattice = exports.randPassword = exports.randWalk = exports.randGaussian = exports.randSubset = exports.randSeeded = exports.randNormal = exports.randFloat = exports.randVersion = exports.randSeed = exports.randFormat = exports.randUUID = exports.randBytes = exports.randBool = exports.randBase64 = exports.randHex = exports.randString = exports.shuffle = exports.randWeighted = exports.randChoice = exports.randIndex = exports.randN = exports.randInt = exports.rand = exports.Crypto = void 0; const crypto = __importStar(require("crypto")); const util_1 = require("util"); const const_1 = require("./const"); const math_helper_1 = require("./math_helper"); // Promisified version of crypto.randomBytes - only created if crypto.randomBytes exists const randomBytesAsync = typeof crypto !== 'undefined' && crypto.randomBytes ? (0, util_1.promisify)(crypto.randomBytes) : undefined; /** * Cryptographically secure random utilities * Uses crypto module to replace Math.random() and Math.floor() for security. * Math.random() is not suitable for security-sensitive operations because it is not truly random and can be predictable. * This class provides methods that utilize cryptographic randomness, ensuring unpredictability and security. */ class Crypto { /** * Check if running in browser environment */ static isBrowser() { return typeof window !== 'undefined' && typeof window.crypto !== 'undefined'; } /** * Check if Web Crypto API is available with getRandomValues */ static hasWebCrypto() { return Crypto.isBrowser() && typeof window.crypto.getRandomValues === 'function'; } /** * Throw error for Node.js-only methods when running in browser */ static throwBrowserError(methodName) { throw new Error(`${methodName} is not available in browser environment. This method requires Node.js crypto module.`); } /** * Generate a cryptographically secure random float between 0 and 1. * Replacement for Math.random(). */ static rand() { if (Crypto.hasWebCrypto()) { // Browser environment const array = new Uint32Array(1); window.crypto.getRandomValues(array); return array[0] / 0x100000000; } else if (typeof crypto !== 'undefined' && crypto.randomBytes) { // Node.js environment const randomBytes = crypto.randomBytes(4); const randomUint32 = randomBytes.readUInt32BE(0); return randomUint32 / 0x100000000; } else { throw new Error('No secure random number generator available. Please use in Node.js environment or modern browser with Web Crypto API.'); } } /** * Generate a cryptographically secure random float between 0 and 1 asynchronously. * Async version of rand(). * * @returns Promise that resolves to a random number between 0 and 1 */ static async randAsync() { if (Crypto.hasWebCrypto()) { // Browser environment const array = new Uint32Array(1); window.crypto.getRandomValues(array); return array[0] / 0x100000000; } else if (typeof crypto !== 'undefined' && randomBytesAsync) { // Node.js environment const randomBytes = await randomBytesAsync(4); const randomUint32 = randomBytes.readUInt32BE(0); return randomUint32 / 0x100000000; } else { throw new Error('No secure random number generator available. Please use in Node.js environment or modern browser with Web Crypto API.'); } } /** * Generate a cryptographically secure random integer between min (inclusive) and max (exclusive). * Replacement for Math.floor(Math.random() * (max - min)) + min. */ static randInt(min = 0, max = 100) { if (min >= max) { throw new Error('min must be less than max'); } const range = max - min; const randomFloat = Crypto.rand(); return Math.floor(randomFloat * range) + min; } /** * Generate a cryptographically secure random integer between 0 and max (exclusive). * Direct replacement for Math.floor(Math.random() * max). */ static randN(max) { return Crypto.randInt(0, max); } /** * Generate random array index. * Replacement for Math.floor(Math.random() * array.length). */ static randIndex(array) { if (array.length === 0) { throw new Error('Array cannot be empty'); } return Crypto.randN(array.length); } /** * Pick random element from array. */ static randChoice(array) { const index = Crypto.randIndex(array); return array[index]; } /** * Generate weighted random choice. */ static randWeighted(items, weights) { if (items.length !== weights.length) { throw new Error('Items and weights arrays must have the same length'); } const totalWeight = weights.reduce((sum, weight) => sum + weight, 0); const random = Crypto.rand() * totalWeight; let sum = 0; for (let i = 0; i < weights.length; i++) { sum += weights[i]; if (random <= sum) { return items[i]; } } // Fallback to last item return items[items.length - 1]; } /** * Shuffle array using [Fisher-Yates algorithm](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) with crypto random. */ static shuffle(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Crypto.randN(i + 1); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } /** * Generate cryptographically secure random string. */ static randString(length, charset) { const chars = charset || const_1.DEFAULT_CHARSET; let result = ''; for (let i = 0; i < length; i++) { const randomIndex = Crypto.randN(chars.length); result += chars.charAt(randomIndex); } return result; } /** * Generate random hex string. * Note: Only available in Node.js environment. */ static randHex(length) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randHex'); } const bytes = crypto.randomBytes(Math.ceil(length / 2)); return bytes.toString('hex').substring(0, length); } /** * Generate random hex string asynchronously. * Async version of randHex(). * Note: Only available in Node.js environment. * * @param length - Length of the hex string to generate * @returns Promise that resolves to a random hex string */ static async randHexAsync(length) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randHexAsync'); } const bytes = await randomBytesAsync(Math.ceil(length / 2)); return bytes.toString('hex').substring(0, length); } /** * Generate random base64 string. * Note: Only available in Node.js environment. */ static randBase64(length) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randBase64'); } const bytes = crypto.randomBytes(Math.ceil(length * 3 / 4)); return bytes.toString('base64').substring(0, length); } /** * Generate random base64 string asynchronously. * Async version of randBase64(). * Note: Only available in Node.js environment. * * @param length - Length of the base64 string to generate * @returns Promise that resolves to a random base64 string */ static async randBase64Async(length) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randBase64Async'); } const bytes = await randomBytesAsync(Math.ceil(length * 3 / 4)); return bytes.toString('base64').substring(0, length); } /** * Generate random boolean with optional probability. */ static randBool(probability = 0.5) { return Crypto.rand() < probability; } /** * Generate random bytes. */ static randBytes(size) { if (Crypto.hasWebCrypto()) { // Browser environment const array = new Uint8Array(size); window.crypto.getRandomValues(array); return array; } else if (typeof crypto !== 'undefined' && crypto.randomBytes) { // Node.js environment return crypto.randomBytes(size); } else { throw new Error('No secure random bytes generator available. Please use in Node.js environment or modern browser with Web Crypto API.'); } } /** * Generate random bytes asynchronously. * Async version of randBytes(). * * @param size - Number of bytes to generate * @returns Promise that resolves to random bytes */ static async randBytesAsync(size) { if (Crypto.hasWebCrypto()) { // Browser environment const array = new Uint8Array(size); window.crypto.getRandomValues(array); return array; } else if (typeof crypto !== 'undefined' && randomBytesAsync) { // Node.js environment return randomBytesAsync(size); // Adding await here is redundant } else { throw new Error('No secure random bytes generator available. Please use in Node.js environment or modern browser with Web Crypto API.'); } } /** * Generate UUID v4. */ static randUUID() { if (Crypto.isBrowser() && window.crypto.randomUUID) { // Modern browsers with randomUUID support return window.crypto.randomUUID(); } else if (Crypto.hasWebCrypto()) { // Older browsers with crypto support but no randomUUID // Implement UUID v4 using getRandomValues const rnds = new Uint8Array(16); window.crypto.getRandomValues(rnds); // Set version bits (V4) rnds[6] = (rnds[6] & 0x0f) | 0x40; // Set variant bits (RFC4122) rnds[8] = (rnds[8] & 0x3f) | 0x80; // Convert to hex string return [ ...Array.from(rnds.subarray(0, 4)).map(b => b.toString(16).padStart(2, '0')), '-', ...Array.from(rnds.subarray(4, 6)).map(b => b.toString(16).padStart(2, '0')), '-', ...Array.from(rnds.subarray(6, 8)).map(b => b.toString(16).padStart(2, '0')), '-', ...Array.from(rnds.subarray(8, 10)).map(b => b.toString(16).padStart(2, '0')), '-', ...Array.from(rnds.subarray(10)).map(b => b.toString(16).padStart(2, '0')), ].join(''); } else if (typeof crypto !== 'undefined' && crypto.randomUUID) { // Node.js environment return crypto.randomUUID(); } else { throw new Error('UUID generation not available. Please use in Node.js environment or modern browser with Web Crypto API.'); } } /** * Generate random numbers for sensor data format. */ static randFormat(count = 6, maxValue = 100, delimiter = ',') { const numbers = []; for (let i = 0; i < count; i++) { const randomNum = Crypto.randN(maxValue); numbers.push(randomNum); } return numbers.join(delimiter); } /** * Generate cryptographically secure seed. * Note: Only available in Node.js environment. */ static randSeed() { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randSeed'); } const bytes = crypto.randomBytes(4); return bytes.readUInt32BE(0); } /** * Generate cryptographically secure seed asynchronously. * Async version of randSeed(). * Note: Only available in Node.js environment. * * @returns Promise that resolves to a random seed number */ static async randSeedAsync() { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randSeedAsync'); } const bytes = await randomBytesAsync(4); return bytes.readUInt32BE(0); } /** * Generate random version string (44 characters base64-like). * Note: Only available in Node.js environment. */ static randVersion() { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randVersion'); } const randomBytes = crypto.randomBytes(32); let base64Version; base64Version = randomBytes.toString('base64'); return base64Version; } /** * Generate random version string (44 characters base64-like) asynchronously. * Async version of randVersion(). * Note: Only available in Node.js environment. * * @returns Promise that resolves to a random version string */ static async randVersionAsync() { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randVersionAsync'); } const randomBytes = await randomBytesAsync(32); let base64Version; base64Version = randomBytes.toString('base64'); return base64Version; } /** * Generate random float in range. */ static randFloat(min = 0, max = 1) { return Crypto.rand() * (max - min) + min; } /** * Generate random number with normal distribution using [Box-Muller transform](https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform). * * This method is similar to randGaussian but ensures that the logarithm function * never receives zero by converting [0,1) to (0,1). * * Steps: * * 1. Generate two independent uniform random numbers: * - (u) is adjusted to be in the interval (0, 1) by using 1 - Crypto.rand(). * - (v) is a standard uniform random number in [0, 1). * 2. Apply the [Box-Muller transform](https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform): * - Compute z = √(-2 × ln(u)) × cos(2π × v) * - This gives z, a standard normally distributed random number (mean = 0, std dev = 1). * 3. Scale and shift z to have the desired mean and standard deviation: * - Return z × stdDev + mean */ static randNormal(mean = 0, stdDev = 1) { const u = 1 - Crypto.rand(); // Converting [0,1) to (0,1) const v = Crypto.rand(); const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); return z * stdDev + mean; } /** * Secure replacement for Math.random() with seed support. * Note: Seeded functionality only available in Node.js environment. */ static randSeeded(seed) { if (seed !== undefined) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randSeeded (with seed parameter)'); } // Create deterministic but secure random from seed const hash = crypto.createHash('sha256'); hash.update(seed.toString()); const hashBytes = hash.digest(); const value = hashBytes.readUInt32BE(0); return value / 0x100000000; } return Crypto.rand(); } /** * Check if current environment supports all features. */ static isFullySupported() { return !Crypto.isBrowser(); } /** * Get list of methods that are not supported in current environment. */ static getUnsupportedMethods() { if (Crypto.isBrowser()) { return [ 'randHex', 'randBase64', 'randSeed', 'randVersion', 'randSeeded (with seed parameter)', 'randLattice', 'randPrime', 'randBigInt', 'randHexAsync', 'randBase64Async', 'randSeedAsync', 'randVersionAsync', 'randPrimeAsync', 'randBigIntAsync' ]; } return []; } /** * Get environment info. */ static getEnvironmentInfo() { const isBrowser = Crypto.isBrowser(); const hasWebCrypto = Crypto.hasWebCrypto(); const hasRandomUUID = isBrowser ? (window.crypto && typeof window.crypto.randomUUID === 'function') : (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'); const allMethods = [ 'rand', 'randInt', 'randN', 'randIndex', 'randChoice', 'randWeighted', 'shuffle', 'randString', 'randHex', 'randBase64', 'randBool', 'randBytes', 'randUUID', 'randFormat', 'randSeed', 'randVersion', 'randFloat', 'randNormal', 'randSeeded', 'randSubset', 'randGaussian', 'randWalk', 'randPassword', 'randLattice', 'randPrime', 'randBigInt', 'randExponential', // Async versions 'randAsync', 'randBytesAsync', 'randHexAsync', 'randBase64Async', 'randSeedAsync', 'randVersionAsync', 'randPrimeAsync', 'randBigIntAsync' ]; const unsupportedMethods = Crypto.getUnsupportedMethods(); const supportedMethods = allMethods.filter(method => !unsupportedMethods.includes(method)); return { isBrowser, hasWebCrypto, hasRandomUUID, supportedMethods, unsupportedMethods }; } /** * Generate a random subset of a given size from an array. */ static randSubset(array, size) { if (size > array.length) { throw new Error('Subset size cannot be larger than the array size'); } const shuffled = Crypto.shuffle(array); return shuffled.slice(0, size); } /** * Generate random number with Gaussian distribution using [Box-Muller transform](https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform). * * Steps: * * 1. Generate two independent uniform random numbers (u₁) and (u₂) in the interval [0, 1). * 2. Apply the [Box-Muller transform](https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform): * - Compute z₀ = √(-2 × ln(u₁)) × cos(2π × u₂) * - This gives z₀, a standard normally distributed random number (mean = 0, std dev = 1). * 3. Scale and shift z₀ to have the desired mean and standard deviation: * - Return z₀ × stdDev + mean */ static randGaussian(mean = 0, stdDev = 1) { // Uniform random number in [0, 1) const u1 = Crypto.rand(); // Uniform random number in [0, 1) const u2 = Crypto.rand(); // Theoretically could encounter Math.log(0) if returns exactly 0 (though this is extremely unlikely with cryptographic randomness 🤪) Crypto.rand() const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2); return z0 * stdDev + mean; } /** * Generate random walk sequence. */ static randWalk(steps, stepSize = 1) { const walk = [0]; let position = 0; for (let i = 0; i < steps; i++) { const direction = Crypto.randBool() ? 1 : -1; position += direction * stepSize; walk.push(position); } return walk; } /** * Generate cryptographically secure password with configurable requirements. * * Note: This method is different from randString as it focuses specifically on password generation * with built-in character type controls, password-specific features like excluding similar-looking * characters (0O1lI), and ensuring proper character distribution for strong passwords. * While randString is a general-purpose string generator, randPassword is optimized for creating * secure passwords with common password policy requirements. */ static randPassword(options) { const { length, includeUppercase = true, includeLowercase = true, includeNumbers = true, includeSymbols = false, excludeSimilar = false, customChars } = options; if (customChars) { return Crypto.randString(length, customChars); } // Define the pattern for similar-looking characters once const similarCharsPattern = /[0O1lI]/g; // Build the charset let charset = ''; let uppercaseChars = const_1.UPPERCASE_CHARSET; let lowercaseChars = const_1.LOWERCASE_CHARSET; let numericChars = const_1.NUMERIC_CHARSET; let specialChars = const_1.SPECIAL_CHARSET; // Remove similar-looking characters if requested if (excludeSimilar) { // Consistently remove all similar-looking characters: 0, O, 1, l, I uppercaseChars = uppercaseChars.replace(similarCharsPattern, ''); lowercaseChars = lowercaseChars.replace(similarCharsPattern, ''); numericChars = numericChars.replace(similarCharsPattern, ''); specialChars = specialChars.replace(similarCharsPattern, ''); } // Add character sets to the full charset if (includeUppercase) charset += uppercaseChars; if (includeLowercase) charset += lowercaseChars; if (includeNumbers) charset += numericChars; if (includeSymbols) charset += specialChars; if (!charset) { throw new Error('At least one character type must be enabled'); } // Make sure the combined charset also has similar-looking characters removed if requested if (excludeSimilar) { // Ensure all similar-looking characters are removed from the combined charset charset = charset.replace(similarCharsPattern, ''); } // If length is too short to include all required character types, just use random const requiredTypes = [ includeUppercase && uppercaseChars, includeLowercase && lowercaseChars, includeNumbers && numericChars, includeSymbols && specialChars ].filter(Boolean); if (length < requiredTypes.length) { return Crypto.randString(length, charset); } // Ensure at least one character from each required character set const requiredChars = []; if (includeUppercase) { requiredChars.push(uppercaseChars.charAt(Crypto.randN(uppercaseChars.length))); } if (includeLowercase) { requiredChars.push(lowercaseChars.charAt(Crypto.randN(lowercaseChars.length))); } if (includeNumbers) { requiredChars.push(numericChars.charAt(Crypto.randN(numericChars.length))); } if (includeSymbols) { requiredChars.push(specialChars.charAt(Crypto.randN(specialChars.length))); } // Generate the remaining characters randomly const remainingLength = length - requiredChars.length; let password = requiredChars.join('') + Crypto.randString(remainingLength, charset); // Shuffle the password to ensure the required characters are not in predictable positions password = Crypto.shuffle(password.split('')).join(''); return password; } /** * Generate cryptographically secure random number using lattice-based mathematical operations. * It uses lattice operations combined with Gaussian error distribution to produce cryptographically secure randomness. * * **Note:** This method is currently only available in Node.js environment due to its * dependency on the native crypto module for secure random number generation. */ static randLattice(dimension = 512, modulus = 3329) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randLattice'); } // Input validation if (!Number.isInteger(dimension) || !Number.isInteger(modulus)) { throw new Error('Dimension and modulus must be integers'); } // 1. Generate secret vector with small coefficients (security requirement) const secret = Array.from({ length: dimension }, () => crypto.randomInt(-1, 2)); // 2. Generate random matrix A (uniform random) - single row for one LWE sample const matrixRow = Array.from({ length: dimension }, () => crypto.randomInt(0, modulus)); // 3. Compute inner product <A, s> mod q let innerProduct = matrixRow.reduce((sum, a, i) => sum + a * secret[i], 0); innerProduct = ((innerProduct % modulus) + modulus) % modulus; // 4. Add Gaussian error (critical for security!) // // TODO: This should be correct; however, if incorrect, it will be improved/fixed later. const alpha = 1 / (Crypto.SQRT_2PI * dimension); const sigma = alpha * modulus; const gaussianError = Crypto.randNormal(0, sigma); const error = Math.round(gaussianError); // 5. LWE sample: b = <A, s> + e (mod q) const lweSample = innerProduct + error; const normalizedSample = ((lweSample % modulus) + modulus) % modulus; return normalizedSample / modulus; } /** * Generate a cryptographically secure random prime number within a specified bit length. * * This method uses the [Miller-Rabin primality test](https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) to verify that the generated number is prime. * The implementation follows cryptographic best practices for generating prime numbers securely. * * **Note:** This method is currently only available in Node.js environment due to its * dependency on the native crypto module for secure random number generation. * * @param bits - The bit length of the prime number to generate (default: 1024) * @param iterations - The number of iterations for the [Miller-Rabin primality test](https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) (a.k.a accuracy 🎯, default: 10) * @param enhanced - Whether to use the enhanced [FIPS](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standards) version (default: false) * @returns A bigint representing a probable prime number of the specified bit length */ static randPrime(bits = 1024, iterations = 10, enhanced = false) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randPrime'); } // Input validation if (!Number.isInteger(bits) || bits < 2) { throw new Error('Bit length must be an integer greater than or equal to 2'); } if (!Number.isInteger(iterations) || iterations < 1) { throw new Error('Number of iterations must be a positive integer'); } // Generate prime candidates until a probable prime is found, // with a 100% guarantee of eventual success using this method. // // Note: This variable-time loop is safe from timing attacks because: // 1. The timing only reveals information about how many candidates were tested before finding a prime // 2. It does NOT leak information about the specific value of the final prime that is returned // 3. Each candidate is generated independently using secure random number generation // 4. The statistical distribution of the search process is independent of the specific prime value let candidate; do { candidate = Crypto.randBigInt(bits); } while (!(0, math_helper_1.isProbablePrime)(candidate, iterations, Crypto.randBytes, enhanced)); return candidate; } /** * Generate a cryptographically secure random prime number within a specified bit length asynchronously. * Async version of randPrime(). * * This method uses the [Miller-Rabin primality test](https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) to verify that the generated number is prime. * The implementation follows cryptographic best practices for generating prime numbers securely. * * **Note:** This method is currently only available in Node.js environment due to its * dependency on the native crypto module for secure random number generation. * * @param bits - The bit length of the prime number to generate (default: 1024) * @param iterations - The number of iterations for the [Miller-Rabin primality test](https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) (a.k.a accuracy 🎯, default: 10) * @param enhanced - Whether to use the enhanced [FIPS](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standards) version (default: false) * @returns A Promise that resolves to a bigint representing a probable prime number of the specified bit length */ static async randPrimeAsync(bits = 1024, iterations = 10, enhanced = false) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randPrimeAsync'); } // Input validation if (!Number.isInteger(bits) || bits < 2) { throw new Error('Bit length must be an integer greater than or equal to 2'); } if (!Number.isInteger(iterations) || iterations < 1) { throw new Error('Number of iterations must be a positive integer'); } // Generate prime candidates until a probable prime is found, // with a 100% guarantee of eventual success using this method. // // Note: This variable-time loop is safe from timing attacks because: // 1. The timing only reveals information about how many candidates were tested before finding a prime // 2. It does NOT leak information about the specific value of the final prime that is returned // 3. Each candidate is generated independently using secure random number generation // 4. The statistical distribution of the search process is independent of the specific prime value let candidate; let isPrime; do { candidate = await Crypto.randBigIntAsync(bits); isPrime = await (0, math_helper_1.isProbablePrimeAsync)(candidate, iterations, Crypto.randBytesAsync, enhanced); } while (!isPrime); return candidate; } /** * Generate a cryptographically secure random bigint with a specified bit length. * * This method generates a random bigint with exactly the specified number of bits. * It ensures the most significant bit is set to 1 and the least significant bit is set to 1 (making it odd). * * **Note:** This method is currently only available in Node.js environment due to its * dependency on the native crypto module for secure random number generation. * * @param bits - The bit length of the bigint to generate (default: 1024) * @returns A bigint with the specified bit length */ static randBigInt(bits = 1024) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randBigInt'); } // Input validation if (!Number.isInteger(bits) || bits < 2) { throw new Error('Bit length must be an integer greater than or equal to 2'); } // Calculate bytes needed (bits / 8, rounded up) const byteLength = Math.ceil(bits / 8); const randomBytes = Crypto.randBytes(byteLength); // Convert to bigint let num = BigInt('0x' + randomBytes.toString('hex')); // Ensure the number has exactly 'bits' bits // Set the most significant bit to 1 to ensure the number has the right bit length num = num | (1n << BigInt(bits - 1)); // Ensure the number is odd (all primes except 2 are odd) num = num | 1n; return num; } /** * Generate a cryptographically secure random bigint with a specified bit length asynchronously. * Async version of randBigInt(). * * This method generates a random bigint with exactly the specified number of bits. * It ensures the most significant bit is set to 1 and the least significant bit is set to 1 (making it odd). * * **Note:** This method is currently only available in Node.js environment due to its * dependency on the native crypto module for secure random number generation. * * @param bits - The bit length of the bigint to generate (default: 1024) * @returns A Promise that resolves to a bigint with the specified bit length */ static async randBigIntAsync(bits = 1024) { if (Crypto.isBrowser()) { Crypto.throwBrowserError('randBigIntAsync'); } // Input validation if (!Number.isInteger(bits) || bits < 2) { throw new Error('Bit length must be an integer greater than or equal to 2'); } // Calculate bytes needed (bits / 8, rounded up) const byteLength = Math.ceil(bits / 8); const randomBytes = await Crypto.randBytesAsync(byteLength); // Convert to bigint let num = BigInt('0x' + randomBytes.toString('hex')); // Ensure the number has exactly 'bits' bits // Set the most significant bit to 1 to ensure the number has the right bit length num = num | (1n << BigInt(bits - 1)); // Ensure the number is odd (all primes except 2 are odd) num = num | 1n; return num; } /** * Generate random number with exponential distribution */ static randExponential(lambda = 1) { const u = Crypto.rand(); return -Math.log(1 - u) / lambda; } } exports.Crypto = Crypto; /** * Pre-calculated constant for the square root of 2π. * * This constant is a component of the normalization factor for the Gaussian probability density function (PDF), * and is used in calculating the standard deviation for Gaussian error in lattice-based cryptography. */ Crypto.SQRT_2PI = Math.sqrt(2 * Math.PI); // Convenience exports - Go-style short names exports.rand = Crypto.rand, exports.randInt = Crypto.randInt, exports.randN = Crypto.randN, exports.randIndex = Crypto.randIndex, exports.randChoice = Crypto.randChoice, exports.randWeighted = Crypto.randWeighted, exports.shuffle = Crypto.shuffle, exports.randString = Crypto.randString, exports.randHex = Crypto.randHex, exports.randBase64 = Crypto.randBase64, exports.randBool = Crypto.randBool, exports.randBytes = Crypto.randBytes, exports.randUUID = Crypto.randUUID, exports.randFormat = Crypto.randFormat, exports.randSeed = Crypto.randSeed, exports.randVersion = Crypto.randVersion, exports.randFloat = Crypto.randFloat, exports.randNormal = Crypto.randNormal, exports.randSeeded = Crypto.randSeeded, exports.randSubset = Crypto.randSubset, exports.randGaussian = Crypto.randGaussian, exports.randWalk = Crypto.randWalk, exports.randPassword = Crypto.randPassword, exports.randLattice = Crypto.randLattice, exports.randPrime = Crypto.randPrime, exports.randBigInt = Crypto.randBigInt, exports.randExponential = Crypto.randExponential, // Async versions exports.randAsync = Crypto.randAsync, exports.randBytesAsync = Crypto.randBytesAsync, exports.randHexAsync = Crypto.randHexAsync, exports.randBase64Async = Crypto.randBase64Async, exports.randSeedAsync = Crypto.randSeedAsync, exports.randVersionAsync = Crypto.randVersionAsync, exports.randPrimeAsync = Crypto.randPrimeAsync, exports.randBigIntAsync = Crypto.randBigIntAsync; //# sourceMappingURL=rand.js.map