From 92fa4a898e042b5055535ff35611c70be02a3acd Mon Sep 17 00:00:00 2001
From: Philip Le Riche <7701190+p-leriche@users.noreply.github.com>
Date: Wed, 11 Mar 2026 21:46:08 +0000
Subject: [PATCH] Add operation RandomPrime.mjs and entry in Categories.json
---
src/core/operations/RandomPrime.mjs | 154 ++++++++++++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 src/core/operations/RandomPrime.mjs
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;