diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index aac00ca1ca..e0b8e8dd40 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -222,6 +222,7 @@
"Subtract",
"Multiply",
"Divide",
+ "Extended GCD",
"Mean",
"Median",
"Standard Deviation",
diff --git a/src/core/lib/BigIntUtils.mjs b/src/core/lib/BigIntUtils.mjs
new file mode 100644
index 0000000000..dce23f2670
--- /dev/null
+++ b/src/core/lib/BigIntUtils.mjs
@@ -0,0 +1,53 @@
+/**
+ * @author p-leriche [philip.leriche@cantab.net]
+ * @copyright Crown Copyright 2025
+ * @license Apache-2.0
+ */
+
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * Number theory utilities used by cryptographic operations.
+ *
+ * Currently provides:
+ * - parseBigInt
+ * - Extended Euclidean Algorithm
+ *
+ * Additional algorithms may be added as required.
+ */
+
+/**
+ * parseBigInt helper operation
+ */
+export function parseBigInt(value, param) {
+ const v = (value ?? "").trim();
+ if (/^0x[0-9a-f]+$/i.test(v)) return BigInt(v);
+ if (/^[+-]?[0-9]+$/.test(v)) return BigInt(v);
+ throw new OperationError(param + " must be decimal or hex (0x...)");
+}
+
+/**
+ * Extended Euclidean Algorithm
+ *
+ * Returns [g, x, y] such that:
+ * a*x + b*y = g = gcd(a, b)
+ *
+ * (Uses an iterative algorithm to avoid possible stack overflow)
+ */
+export function egcd(a, b) {
+ let oldR = a, r = b;
+ let oldS = 1n, s = 0n;
+ let oldT = 0n, t = 1n;
+
+ while (r !== 0n) {
+ const quotient = oldR / r;
+
+ [oldR, r] = [r, oldR - quotient * r];
+ [oldS, s] = [s, oldS - quotient * s];
+ [oldT, t] = [t, oldT - quotient * t];
+ }
+
+ // oldR is the gcd
+ // oldS and oldT are the Bézout coefficients
+ return [oldR, oldS, oldT];
+}
diff --git a/src/core/operations/ExtendedGCD.mjs b/src/core/operations/ExtendedGCD.mjs
new file mode 100644
index 0000000000..88069c7459
--- /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;
diff --git a/tests/operations/tests/ExtendedGCD.mjs b/tests/operations/tests/ExtendedGCD.mjs
new file mode 100644
index 0000000000..aeefe5e4d2
--- /dev/null
+++ b/tests/operations/tests/ExtendedGCD.mjs
@@ -0,0 +1,126 @@
+/**
+ * Extended GCD 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: "Extended GCD: coprime numbers (3, 11)",
+ input: "",
+ expectedOutput: "Extended Euclidean Algorithm Results:\n" +
+ "=====================================\n\n" +
+ "gcd(a, b) = 1\n\n" +
+ "Béut coefficients:\n" +
+ " x = 4\n" +
+ " y = -1\n\n" +
+ "Verification:\n" +
+ " a·x + b·y = gcd(a, b)\n" +
+ " (3) ×(4) + (11) ×(-1) = 1",
+ recipeConfig: [
+ {
+ op: "Extended GCD",
+ args: ["3", "11"],
+ },
+ ],
+ },
+ {
+ name: "Extended GCD: non-coprime numbers (240, 46)",
+ input: "",
+ expectedOutput: "Extended Euclidean Algorithm Results:\n" +
+ "=====================================\n\n" +
+ "gcd(a, b) = 2\n\n" +
+ "Béut coefficients:\n" +
+ " x = -9\n" +
+ " y = 47\n\n" +
+ "Verification:\n" +
+ " a·x + b·y = gcd(a, b)\n" +
+ " (240) ×(-9) + (46) ×(47) = 2",
+ recipeConfig: [
+ {
+ op: "Extended GCD",
+ args: ["240", "46"],
+ },
+ ],
+ },
+ {
+ name: "Extended GCD: with zero (17, 0)",
+ input: "",
+ expectedOutput: "Extended Euclidean Algorithm Results:\n" +
+ "=====================================\n\n" +
+ "gcd(a, b) = 17\n\n" +
+ "Béut coefficients:\n" +
+ " x = 1\n" +
+ " y = 0\n\n" +
+ "Verification:\n" +
+ " a·x + b·y = gcd(a, b)\n" +
+ " (17) ×(1) + (0) ×(0) = 17",
+ recipeConfig: [
+ {
+ op: "Extended GCD",
+ args: ["17", "0"],
+ },
+ ],
+ },
+ {
+ name: "Extended GCD: hexadecimal input (0xFF, 0x11)",
+ input: "",
+ expectedOutput: "Extended Euclidean Algorithm Results:\n" +
+ "=====================================\n\n" +
+ "gcd(a, b) = 17\n\n" +
+ "Béut coefficients:\n" +
+ " x = 1\n" +
+ " y = -15\n\n" +
+ "Verification:\n" +
+ " a·x + b·y = gcd(a, b)\n" +
+ " (255) ×(1) + (17) ×(-15) = 17",
+ recipeConfig: [
+ {
+ op: "Extended GCD",
+ args: ["0xFF", "0x11"],
+ },
+ ],
+ },
+ {
+ name: "Extended GCD: using input field for value a",
+ input: "42",
+ expectedOutput: "Extended Euclidean Algorithm Results:\n" +
+ "=====================================\n\n" +
+ "gcd(a, b) = 7\n\n" +
+ "Béut coefficients:\n" +
+ " x = -2\n" +
+ " y = 3\n\n" +
+ "Verification:\n" +
+ " a·x + b·y = gcd(a, b)\n" +
+ " (42) ×(-2) + (35) ×(3) = 7",
+ recipeConfig: [
+ {
+ op: "Extended GCD",
+ args: ["", "35"],
+ },
+ ],
+ },
+ {
+ name: "Extended GCD: large numbers",
+ input: "",
+ expectedOutput: "Extended Euclidean Algorithm Results:\n" +
+ "=====================================\n\n" +
+ "gcd(a, b) = 1\n\n" +
+ "Béut coefficients:\n" +
+ " x = -80538738812075595\n" +
+ " y = 10000000000000000\n\n" +
+ "Verification:\n" +
+ " a·x + b·y = gcd(a, b)\n" +
+ " (123456789012345678901234567890) ×(-80538738812075595) + (994064509324197316) ×(10000000000000000) = 1",
+ recipeConfig: [
+ {
+ op: "Extended GCD",
+ args: ["123456789012345678901234567890", "994064509324197316"],
+ },
+ ],
+ },
+]);