Add Modular Exponentiation operation (#2149)
This commit is contained in:
parent
c7d20d8601
commit
f6a4631130
@ -246,6 +246,7 @@
|
|||||||
"Divide",
|
"Divide",
|
||||||
"MOD",
|
"MOD",
|
||||||
"Extended GCD",
|
"Extended GCD",
|
||||||
|
"Modular Exponentiation",
|
||||||
"Modular Inverse",
|
"Modular Inverse",
|
||||||
"Mean",
|
"Mean",
|
||||||
"Median",
|
"Median",
|
||||||
|
|||||||
111
src/core/operations/ModularExponentiation.mjs
Normal file
111
src/core/operations/ModularExponentiation.mjs
Normal file
@ -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.<br><br>" +
|
||||||
|
"Computes Base ^ Exponent mod Modulus.<br><br>" +
|
||||||
|
"<b>Input handling:</b> If <i>either</i> Base <i>or</i> 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;
|
||||||
137
tests/operations/tests/ModularExponentiation.mjs
Normal file
137
tests/operations/tests/ModularExponentiation.mjs
Normal file
@ -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"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
Loading…
x
Reference in New Issue
Block a user