diff --git a/src/core/operations/RandomPrime.mjs b/src/core/operations/RandomPrime.mjs new file mode 100644 index 000000000..3509045f0 --- /dev/null +++ b/src/core/operations/RandomPrime.mjs @@ -0,0 +1,154 @@ +/** + * @author p-leriche [philip.leriche@cantab.net] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { modPow } from "../lib/BigIntUtils.mjs"; + +/* ---------- helper functions ---------- */ + +/** + * Generate random BigInt with specified bit length + */ +function randBigInt(bits) { + const bytes = Math.ceil(bits / 8); + const a = new Uint8Array(bytes); + crypto.getRandomValues(a); + + // Set high bit to ensure correct bit length + a[0] |= 1 << (7 - ((8 * bytes - bits))); + // Set low bit to ensure odd (primes > 2 are odd) + a[bytes - 1] |= 1; + + let h = ""; + for (const b of a) h += b.toString(16).padStart(2, "0"); + return BigInt("0x" + h); +} + +/** + * Miller-Rabin primality test + */ +function isProbablePrime(n, rounds) { + if (n < 2n) return false; + if (n === 2n || n === 3n) return true; + if (n % 2n === 0n) return false; + + // Write n-1 as 2^r * d + let d = n - 1n; + let r = 0n; + while (d % 2n === 0n) { + d /= 2n; + r++; + } + + // Witness loop + for (let i = 0; i < rounds; i++) { + const a = randBigInt(n.toString(2).length - 1) % (n - 3n) + 2n; + let x = modPow(a, d, n); + + if (x === 1n || x === n - 1n) continue; + + let composite = true; + for (let j = 0n; j < r - 1n; j++) { + x = modPow(x, 2n, n); + if (x === n - 1n) { + composite = false; + break; + } + } + + if (composite) return false; + } + + return true; +} + +/* ---------- operation class ---------- */ + +/** + * Generate Prime Number operation + */ +class GeneratePrime extends Operation { + /** + * GeneratePrime constructor + */ + constructor() { + super(); + + this.name = "Pseudo-Random Prime Generator"; + this.module = "Crypto"; + this.description = + "Generates a random probable prime number of specified bit length using the Miller-Rabin primality test.

" + + "Primality guarantee:
" + + "For numbers . 3,317, the result is guaranteed prime (deterministic test).
" + + "For larger numbers, uses probabilistic testing:
" + + "- Standard (7 rounds): Probability of composite approx 1 in 16,000)

" + + "- Crypto grade (40 rounds): Probability of composite(approx 1 in 10^24)
" + + "Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).

" ; + this.infoURL = "https://wikipedia.org/wiki/Miller-Rabin_primality_test"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Bit length", + type: "number", + value: 512, + min: 2 + }, + { + name: "Crypto grade", + type: "boolean", + value: false + }, + { + name: "Output format", + type: "option", + value: ["Decimal", "Hexadecimal"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [bits, cryptoGrade, outputFormat] = args; + + if (bits < 2) { + throw new OperationError("Bit length must be at least 2"); + } + + if (bits > 4096) { + throw new OperationError("Bit length limited to 4096 bits for performance reasons"); + } + + const rounds = cryptoGrade ? 40 : 7; + let attempts = 0; + const maxAttempts = 10000; + + let n = randBigInt(bits); + + while (!isProbablePrime(n, rounds)) { + n = randBigInt(bits); + attempts++; + + if (attempts > maxAttempts) { + throw new OperationError(`Failed to generate prime after ${maxAttempts} attempts. Try a different bit length.`); + } + } + + // Return only the prime for pipeability + if (outputFormat === "Hexadecimal") { + return "0x" + n.toString(16); + } else { + return n.toString(); + } + } +} + +export default GeneratePrime;