Add operation RandomPrime.mjs and entry in Categories.json

This commit is contained in:
Philip Le Riche 2026-03-11 21:46:08 +00:00
parent 881d89d30d
commit 92fa4a898e

View file

@ -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.<br><br>" +
"<b>Primality guarantee:</b><br>" +
"For numbers . 3,317, the result is guaranteed prime (deterministic test).<br>" +
"For larger numbers, uses probabilistic testing:<br>" +
"- <b>Standard (7 rounds):</b> Probability of composite approx 1 in 16,000)<br><br>" +
"- <b>Crypto grade (40 rounds):</b> Probability of composite(approx 1 in 10^24)<br>" +
"Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).<br><br>" ;
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;