From f6a46311301c9b51e5805f2741dd07c2e388e627 Mon Sep 17 00:00:00 2001
From: p-leriche <7701190+p-leriche@users.noreply.github.com>
Date: Sat, 25 Jul 2026 08:00:06 +0100
Subject: [PATCH] Add Modular Exponentiation operation (#2149)
---
src/core/config/Categories.json | 1 +
src/core/operations/ModularExponentiation.mjs | 111 ++++++++++++++
.../tests/ModularExponentiation.mjs | 137 ++++++++++++++++++
3 files changed, 249 insertions(+)
create mode 100644 src/core/operations/ModularExponentiation.mjs
create mode 100644 tests/operations/tests/ModularExponentiation.mjs
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 4f4dedc2..6ad3adb7 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -246,6 +246,7 @@
"Divide",
"MOD",
"Extended GCD",
+ "Modular Exponentiation",
"Modular Inverse",
"Mean",
"Median",
diff --git a/src/core/operations/ModularExponentiation.mjs b/src/core/operations/ModularExponentiation.mjs
new file mode 100644
index 00000000..6a08a97d
--- /dev/null
+++ b/src/core/operations/ModularExponentiation.mjs
@@ -0,0 +1,111 @@
+/**
+ * @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 { parseBigInt, modPow } from "../lib/BigIntUtils.mjs";
+
+/* ---------- operation class ---------- */
+
+/**
+ * Modular Exponentiation operation
+ */
+class ModularExponentiation extends Operation {
+
+ /**
+ * ModularExponentiation constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Modular Exponentiation";
+ this.module = "Crypto";
+ this.description = "Performs modular exponentiation, as used in Diffie-Hellman and RSA.
" +
+ "Computes Base ^ Exponent mod Modulus.
" +
+ "Input handling: If either Base or Exponent is left blank, " +
+ "its value is taken from the Input field.";
+ this.infoURL = "https://wikipedia.org/wiki/Modular_exponentiation";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Base",
+ type: "string",
+ value: ""
+ },
+ {
+ name: "Modulus",
+ type: "string",
+ value: "1"
+ },
+ {
+ name: "Exponent",
+ type: "string",
+ value: ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [baseStr, modStr, expStr] = args;
+
+ // Trim everything so "" and " " count as empty
+ const baseParam = baseStr?.trim();
+ const expParam = expStr?.trim();
+ const modParam = modStr?.trim();
+ const inputVal = input?.trim();
+
+ const mod = modParam;
+ if (!mod) {
+ throw new OperationError("Modulus must be defined");
+ }
+
+ // Base *or* Exponent (but not both) are taken from the Input
+ // if their boxes are empty.
+ let base, exp;
+ if (baseParam && expParam) {
+ // Case 1: base and exponent both given as parameters
+ base = baseParam;
+ exp = expParam;
+ } else if (!baseParam && expParam) {
+ // Case 2: base missing - take from input
+ base = inputVal;
+ exp = expParam;
+ if (!base) {
+ throw new OperationError("Base must be defined");
+ }
+ } else if (baseParam && !expParam) {
+ // Case 3: exponent missing - take from input
+ base = baseParam;
+ exp = inputVal;
+ if (!exp) {
+ throw new OperationError("Exponent must be defined");
+ }
+ } else if (!inputVal) {
+ // Case 4: base and exponent both missing
+ throw new OperationError("Base and Exponent must be defined");
+ } else throw new OperationError("Ambiguous input: specify either Base or Exponent when using Input");
+
+ // Parse numbers
+ const baseBI = parseBigInt(base, "Base");
+ const expBI = parseBigInt(exp, "Exponent");
+ const modBI = parseBigInt(mod, "Modulus");
+
+ // Check for invalid modulus (parseBigInt eliminates negatives)
+ if (modBI === 0n) {
+ throw new OperationError("Modulus must be greater than zero");
+ }
+
+ return modPow(baseBI, expBI, modBI).toString();
+ }
+}
+
+export default ModularExponentiation;
diff --git a/tests/operations/tests/ModularExponentiation.mjs b/tests/operations/tests/ModularExponentiation.mjs
new file mode 100644
index 00000000..4912530f
--- /dev/null
+++ b/tests/operations/tests/ModularExponentiation.mjs
@@ -0,0 +1,137 @@
+/**
+ * Modular Exponentiation tests.
+ *
+ * @author p-leriche [philip.leriche@cantab.net]
+ *
+ * @copyright Crown Copyright 2025
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Modular Exponentiation: basic example (2^10 mod 1000)",
+ input: "",
+ expectedOutput: "24",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["2", "1000", "10"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: small values (3^5 mod 7)",
+ input: "",
+ expectedOutput: "5",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["3", "7", "5"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: exponent zero",
+ input: "",
+ expectedOutput: "1",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["999", "100", "0"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: base one",
+ input: "",
+ expectedOutput: "1",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["1", "1000", "999"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: hexadecimal input",
+ input: "",
+ expectedOutput: "256",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["0x10", "1000", "0x2"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: using input field for base",
+ input: "5",
+ expectedOutput: "6",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["", "7", "3"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: using input field for exponent",
+ input: "4",
+ expectedOutput: "5",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["2", "11", ""],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: RSA-like example (small)",
+ input: "",
+ expectedOutput: "561",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["123", "1000", "456"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: large base and exponent",
+ input: "",
+ expectedOutput: "560583526",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["123456789", "1000000007", "65537"],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: crypto-sized numbers (RSA-2048 simulation)",
+ input: "",
+ expectedOutput: "1",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: [
+ "12345678901234567890123456789012345678901234567890",
+ "99999999999999999999999999999999999999999999999999",
+ "0"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Modular Exponentiation: Fermat's Little Theorem (a^(p-1) mod p = 1)",
+ input: "",
+ expectedOutput: "1",
+ recipeConfig: [
+ {
+ op: "Modular Exponentiation",
+ args: ["3", "11", "10"],
+ },
+ ],
+ },
+]);