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"], + }, + ], + }, +]);