mirror of
https://github.com/gchq/CyberChef.git
synced 2026-03-12 02:09:39 +01:00
Add BigInt utility functions for number theory operations (#2205)
Co-authored-by: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> (initial tests)
This commit is contained in:
@@ -222,6 +222,9 @@
|
||||
"Subtract",
|
||||
"Multiply",
|
||||
"Divide",
|
||||
"Modular Exponentiation",
|
||||
"Modular Inverse",
|
||||
"Extended GCD",
|
||||
"Mean",
|
||||
"Median",
|
||||
"Standard Deviation",
|
||||
|
||||
73
src/core/lib/BigIntUtils.mjs
Normal file
73
src/core/lib/BigIntUtils.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @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
|
||||
* - Modular Exponentiation
|
||||
*
|
||||
* 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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Modular exponentiation
|
||||
*/
|
||||
export function modPow(base, exponent, modulus) {
|
||||
let result = 1n;
|
||||
base %= modulus;
|
||||
|
||||
while (exponent > 0n) {
|
||||
if (exponent & 1n) {
|
||||
result = (result * base) % modulus;
|
||||
}
|
||||
base = (base * base) % modulus;
|
||||
exponent >>= 1n;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import "./tests/Dish.mjs";
|
||||
import "./tests/NodeDish.mjs";
|
||||
import "./tests/Utils.mjs";
|
||||
import "./tests/Categories.mjs";
|
||||
import "./tests/lib/BigIntUtils.mjs";
|
||||
|
||||
const testStatus = {
|
||||
allTestsPassing: true,
|
||||
|
||||
150
tests/node/tests/lib/BigIntUtils.mjs
Normal file
150
tests/node/tests/lib/BigIntUtils.mjs
Normal file
@@ -0,0 +1,150 @@
|
||||
import TestRegister from "../../../lib/TestRegister.mjs";
|
||||
import { parseBigInt, egcd, modPow } from "../../../../src/core/lib/BigIntUtils.mjs";
|
||||
import it from "../../assertionHandler.mjs";
|
||||
import assert from "assert";
|
||||
|
||||
TestRegister.addApiTests([
|
||||
// ===== parseBigInt tests =====
|
||||
it("BigIntUtils: parseBigInt - decimal number", () => {
|
||||
const value = parseBigInt("1", "test value");
|
||||
assert.deepStrictEqual(value, BigInt("1"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - large decimal", () => {
|
||||
const value = parseBigInt("123456789012345678901234567890", "test value");
|
||||
assert.deepStrictEqual(value, BigInt("123456789012345678901234567890"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - hexadecimal lowercase", () => {
|
||||
const value = parseBigInt("0xff", "test value");
|
||||
assert.deepStrictEqual(value, BigInt("255"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - hexadecimal uppercase", () => {
|
||||
const value = parseBigInt("0xFF", "test value");
|
||||
assert.deepStrictEqual(value, BigInt("255"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - large hexadecimal", () => {
|
||||
const value = parseBigInt("0x123456789ABCDEF", "test value");
|
||||
assert.deepStrictEqual(value, BigInt("0x123456789ABCDEF"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - whitespace trimming", () => {
|
||||
const value = parseBigInt(" 42 ", "test value");
|
||||
assert.deepStrictEqual(value, BigInt("42"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - invalid input (text)", () => {
|
||||
assert.throws(() => parseBigInt("test", "test value"), {
|
||||
name: "Error",
|
||||
message: "test value must be decimal or hex (0x...)"
|
||||
});
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - invalid input (hex without prefix)", () => {
|
||||
assert.throws(() => parseBigInt("FF", "test value"), {
|
||||
name: "Error",
|
||||
message: "test value must be decimal or hex (0x...)"
|
||||
});
|
||||
}),
|
||||
|
||||
it("BigIntUtils: parseBigInt - invalid input (mixed)", () => {
|
||||
assert.throws(() => parseBigInt("12abc", "test value"), {
|
||||
name: "Error",
|
||||
message: "test value must be decimal or hex (0x...)"
|
||||
});
|
||||
}),
|
||||
|
||||
// ===== egcd tests =====
|
||||
it("BigIntUtils: egcd - basic coprime", () => {
|
||||
const a = BigInt("36");
|
||||
const b = BigInt("48");
|
||||
const gcd = BigInt("12");
|
||||
const bezout1 = BigInt("-1");
|
||||
const bezout2 = BigInt("1");
|
||||
assert.deepStrictEqual(egcd(a, b), [gcd, bezout1, bezout2]);
|
||||
}),
|
||||
|
||||
it("BigIntUtils: egcd - coprime numbers", () => {
|
||||
const [g, x, y] = egcd(BigInt("3"), BigInt("11"));
|
||||
assert.strictEqual(g, BigInt("1"));
|
||||
// Verify Bézout identity: a*x + b*y = gcd
|
||||
assert.strictEqual(BigInt("3") * x + BigInt("11") * y, g);
|
||||
}),
|
||||
|
||||
it("BigIntUtils: egcd - non-coprime numbers", () => {
|
||||
const [g, x, y] = egcd(BigInt("240"), BigInt("46"));
|
||||
assert.strictEqual(g, BigInt("2"));
|
||||
// Verify Bézout identity
|
||||
assert.strictEqual(BigInt("240") * x + BigInt("46") * y, g);
|
||||
}),
|
||||
|
||||
it("BigIntUtils: egcd - with zero", () => {
|
||||
const [g, x, y] = egcd(BigInt("17"), BigInt("0"));
|
||||
assert.strictEqual(g, BigInt("17"));
|
||||
assert.strictEqual(x, BigInt("1"));
|
||||
assert.strictEqual(y, BigInt("0"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: egcd - identical numbers", () => {
|
||||
const [g, x, y] = egcd(BigInt("42"), BigInt("42"));
|
||||
assert.strictEqual(g, BigInt("42"));
|
||||
// Verify Bézout identity
|
||||
assert.strictEqual(BigInt("42") * x + BigInt("42") * y, g);
|
||||
}),
|
||||
|
||||
it("BigIntUtils: egcd - large numbers", () => {
|
||||
const a = BigInt("123456789012345678901234567890");
|
||||
const b = BigInt("987654321098765432109876543210");
|
||||
const [g, x, y] = egcd(a, b);
|
||||
// Verify Bézout identity
|
||||
assert.strictEqual(a * x + b * y, g);
|
||||
}),
|
||||
|
||||
// ===== modPow tests =====
|
||||
it("BigIntUtils: modPow - basic", () => {
|
||||
// 2^10 mod 1000 = 1024 mod 1000 = 24
|
||||
const result = modPow(BigInt("2"), BigInt("10"), BigInt("1000"));
|
||||
assert.strictEqual(result, BigInt("24"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: modPow - RSA-like example", () => {
|
||||
// Common RSA public exponent
|
||||
const base = BigInt("123456789");
|
||||
const exp = BigInt("65537");
|
||||
const mod = BigInt("999999999999");
|
||||
const result = modPow(base, exp, mod);
|
||||
// Result should be less than modulus
|
||||
assert(result < mod);
|
||||
assert(result >= BigInt("0"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: modPow - exponent zero", () => {
|
||||
// Any number^0 = 1
|
||||
const result = modPow(BigInt("999"), BigInt("0"), BigInt("100"));
|
||||
assert.strictEqual(result, BigInt("1"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: modPow - base zero", () => {
|
||||
// 0^n = 0
|
||||
const result = modPow(BigInt("0"), BigInt("5"), BigInt("100"));
|
||||
assert.strictEqual(result, BigInt("0"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: modPow - large exponent", () => {
|
||||
// Test with very large exponent (efficient algorithm should handle this)
|
||||
const result = modPow(BigInt("3"), BigInt("1000000"), BigInt("1000000007"));
|
||||
assert(result >= BigInt("0"));
|
||||
assert(result < BigInt("1000000007"));
|
||||
}),
|
||||
|
||||
it("BigIntUtils: modPow - modular inverse verification", () => {
|
||||
// If a*x . 1 (mod m), then modPow(a, 1, m) * x . 1 (mod m)
|
||||
const a = BigInt("3");
|
||||
const m = BigInt("11");
|
||||
const x = BigInt("4"); // inverse of 3 mod 11
|
||||
const result = modPow(a, BigInt("1"), m) * x % m;
|
||||
assert.strictEqual(result, BigInt("1"));
|
||||
}),
|
||||
]);
|
||||
Reference in New Issue
Block a user