diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index ab1dafb0..7a65c054 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -228,8 +228,6 @@
"Subtract",
"Multiply",
"Divide",
- "Modular Exponentiation",
- "Modular Inverse",
"Extended GCD",
"Mean",
"Median",
diff --git a/src/core/operations/ExtendedGCD.mjs b/src/core/operations/ExtendedGCD.mjs
new file mode 100644
index 00000000..88069c74
--- /dev/null
+++ b/src/core/operations/ExtendedGCD.mjs
@@ -0,0 +1,101 @@
+/**
+ * @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, egcd } from "../lib/BigIntUtils.mjs";
+
+/* ---------- operation class ---------- */
+
+/**
+ * Extended GCD operation
+ */
+class ExtendedGCD extends Operation {
+ /**
+ * ExtendedGCD constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Extended GCD";
+ this.module = "Crypto";
+ this.description =
+ "Computes the Extended Euclidean Algorithm for integers a and b.
" +
+ "Finds integers x and y (Bezout coefficients) such that:
" +
+ "a*x + b*y = gcd(a, b)
" +
+ "This is fundamental to many number theory algorithms including modular inverse, " +
+ "solving linear Diophantine equations, and cryptographic operations.
" +
+ "Input handling: If either a or b is left blank, " +
+ "its value is taken from the Input field.";
+ this.infoURL = "https://wikipedia.org/wiki/Extended_Euclidean_algorithm";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Value a",
+ type: "string",
+ value: ""
+ },
+ {
+ name: "Value b",
+ type: "string",
+ value: ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [aStr, bStr] = args;
+
+ // Trim everything so "" and " " count as empty
+ const aParam = aStr?.trim();
+ const bParam = bStr?.trim();
+ const inputVal = input?.trim();
+
+ let a, b;
+
+ if (aParam && bParam) {
+ // Case 1: both values given as parameters
+ a = aParam;
+ b = bParam;
+ } else if (!aParam && bParam) {
+ // Case 2: a missing - take from input
+ a = inputVal;
+ b = bParam;
+ if (!a) throw new OperationError("Value a must be defined");
+ } else if (aParam && !bParam) {
+ // Case 3: b missing - take from input
+ a = aParam;
+ b = inputVal;
+ if (!b) throw new OperationError("Value b must be defined");
+ } else if (!aParam && !bParam) {
+ // Case 4: both values missing
+ throw new OperationError("Values a and b must be defined");
+ }
+
+ const aBI = parseBigInt(a, "Value a");
+ const bBI = parseBigInt(b, "Value b");
+
+ const [g, x, y] = egcd(aBI, bBI);
+ const gcd = g < 0n ? -g : g;
+
+ // Format output string bearing in mind that crypto-grade numbers
+ // may greatly exceed the line length.
+ let output = "gcd: " + gcd.toString() + "\n\n";
+ output += "Bezout coefficients:\n";
+ output += "x = " + x.toString() + "\n";
+ output += "y = " + y.toString() + "\n\n";
+
+ return output;
+ }
+}
+
+export default ExtendedGCD;