Cybersecurity-Projects/PROJECTS/advanced/zig-stateless-scanner/src/numtheory.zig

149 lines
4.7 KiB
Zig

// ©AngelaMos | 2026
// numtheory.zig
const std = @import("std");
pub fn mulMod(a: u64, b: u64, m: u64) u64 {
return @intCast((@as(u128, a) * @as(u128, b)) % m);
}
pub fn modExp(base: u64, exp: u64, modulus: u64) u64 {
if (modulus == 1) return 0;
var result: u64 = 1;
var b: u64 = base % modulus;
var e: u64 = exp;
while (e > 0) {
if (e & 1 == 1) result = mulMod(result, b, modulus);
b = mulMod(b, b, modulus);
e >>= 1;
}
return result;
}
pub fn isPrime(n: u64) bool {
if (n < 2) return false;
const small = [_]u64{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
for (small) |p| {
if (n == p) return true;
if (n % p == 0) return false;
}
var d: u64 = n - 1;
var r: u32 = 0;
while (d & 1 == 0) : (d >>= 1) r += 1;
for (small) |a| {
var x = modExp(a, d, n);
if (x == 1 or x == n - 1) continue;
var i: u32 = 1;
var composite = true;
while (i < r) : (i += 1) {
x = mulMod(x, x, n);
if (x == n - 1) {
composite = false;
break;
}
}
if (composite) return false;
}
return true;
}
pub fn smallestPrimeAbove(n: u64) u64 {
var candidate = n + 1;
if (candidate <= 2) return 2;
if (candidate & 1 == 0) candidate += 1;
while (!isPrime(candidate)) : (candidate += 2) {}
return candidate;
}
pub fn distinctPrimeFactors(value: u64, buf: []u64) []u64 {
var n = value;
var count: usize = 0;
var f: u64 = 2;
while (f * f <= n) {
if (n % f == 0) {
buf[count] = f;
count += 1;
while (n % f == 0) n /= f;
}
f += if (f == 2) 1 else 2;
}
if (n > 1) {
buf[count] = n;
count += 1;
}
return buf[0..count];
}
pub fn isPrimitiveRoot(candidate: u64, prime: u64, prime_factors: []const u64) bool {
for (prime_factors) |q| {
if (modExp(candidate, (prime - 1) / q, prime) == 1) return false;
}
return true;
}
pub fn findPrimitiveRoot(prime: u64, rand: std.Random) u64 {
if (prime == 2) return 1;
var buf: [64]u64 = undefined;
const factors = distinctPrimeFactors(prime - 1, &buf);
while (true) {
const candidate = rand.intRangeAtMost(u64, 2, prime - 1);
if (isPrimitiveRoot(candidate, prime, factors)) return candidate;
}
}
test "modExp known values" {
try std.testing.expectEqual(@as(u64, 1), modExp(3, 4, 5));
try std.testing.expectEqual(@as(u64, 24), modExp(2, 10, 1000));
try std.testing.expectEqual(@as(u64, 0), modExp(10, 3, 1000));
try std.testing.expectEqual(@as(u64, 445), modExp(4, 13, 497));
}
test "isPrime classifies small and large values" {
const primes = [_]u64{ 2, 3, 5, 7, 11, 13, 65537, 1009, 4294967311, 281474976710677 };
for (primes) |p| try std.testing.expect(isPrime(p));
const composites = [_]u64{ 0, 1, 4, 9, 15, 561, 1105, 4294967296, 281474976710676 };
for (composites) |c| try std.testing.expect(!isPrime(c));
}
test "smallestPrimeAbove" {
try std.testing.expectEqual(@as(u64, 257), smallestPrimeAbove(256));
try std.testing.expectEqual(@as(u64, 65537), smallestPrimeAbove(65536));
try std.testing.expectEqual(@as(u64, 1009), smallestPrimeAbove(1000));
try std.testing.expectEqual(@as(u64, 4294967311), smallestPrimeAbove(4294967296));
}
test "distinctPrimeFactors" {
var buf: [16]u64 = undefined;
try std.testing.expectEqualSlices(u64, &.{2}, distinctPrimeFactors(256, &buf));
try std.testing.expectEqualSlices(u64, &.{ 2, 3 }, distinctPrimeFactors(12, &buf));
try std.testing.expectEqualSlices(u64, &.{ 2, 5 }, distinctPrimeFactors(100, &buf));
try std.testing.expectEqualSlices(u64, &.{ 2, 3, 5 }, distinctPrimeFactors(30, &buf));
}
test "isPrimitiveRoot for p=7 (roots are 3 and 5)" {
var buf: [16]u64 = undefined;
const factors = distinctPrimeFactors(7 - 1, &buf);
try std.testing.expect(isPrimitiveRoot(3, 7, factors));
try std.testing.expect(isPrimitiveRoot(5, 7, factors));
try std.testing.expect(!isPrimitiveRoot(2, 7, factors));
try std.testing.expect(!isPrimitiveRoot(4, 7, factors));
}
test "findPrimitiveRoot returns a generator that walks the whole group" {
var prng = std.Random.DefaultPrng.init(0xA11CE_2026);
const rand = prng.random();
const primes = [_]u64{ 7, 257, 65537, 1009 };
for (primes) |p| {
const g = findPrimitiveRoot(p, rand);
var seen = [_]bool{false} ** 65537;
var cur: u64 = 1;
var k: u64 = 0;
while (k < p - 1) : (k += 1) {
cur = mulMod(cur, g, p);
try std.testing.expect(!seen[cur]);
seen[cur] = true;
}
try std.testing.expectEqual(@as(u64, 1), cur);
}
}