Skip to main content

proof_engine/number_theory/
galois.rs

1//! Galois fields GF(p^n): arithmetic, generators, and multiplication tables.
2
3/// Descriptor for a Galois field GF(p^n).
4/// For n=1 this is simply Z/pZ. For n>1 we use a fixed irreducible polynomial
5/// (automatically selected for small cases).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct GaloisField {
8    pub prime: u64,
9    pub power: u32,
10}
11
12/// An element of a Galois field.
13/// For GF(p) (power=1), value is simply an integer mod p.
14/// For GF(p^n), value encodes a polynomial with coefficients in GF(p),
15/// packed as value = c0 + c1*p + c2*p^2 + ... where ci < p.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct GfElement {
18    pub value: u64,
19    pub field: GaloisField,
20}
21
22impl GaloisField {
23    pub fn new(prime: u64, power: u32) -> Self {
24        assert!(prime >= 2);
25        assert!(power >= 1);
26        Self { prime, power }
27    }
28
29    /// Order of the field: p^n.
30    pub fn order(&self) -> u64 {
31        self.prime.pow(self.power)
32    }
33
34    /// Create the zero element.
35    pub fn zero(self) -> GfElement {
36        GfElement { value: 0, field: self }
37    }
38
39    /// Create the one (multiplicative identity).
40    pub fn one(self) -> GfElement {
41        GfElement { value: 1, field: self }
42    }
43
44    /// Create an element from a value.
45    pub fn element(self, value: u64) -> GfElement {
46        GfElement { value: value % self.order(), field: self }
47    }
48
49    /// Get a fixed irreducible polynomial for GF(p^n).
50    /// Returns coefficients [c0, c1, ..., cn] of the polynomial c0 + c1*x + ... + cn*x^n.
51    /// Only supports small cases.
52    fn irreducible_poly(&self) -> Vec<u64> {
53        if self.power == 1 {
54            return vec![0, 1]; // x (unused for GF(p))
55        }
56        // Some known irreducible polynomials
57        match (self.prime, self.power) {
58            (2, 2) => vec![1, 1, 1],           // x^2 + x + 1
59            (2, 3) => vec![1, 1, 0, 1],        // x^3 + x + 1
60            (2, 4) => vec![1, 1, 0, 0, 1],     // x^4 + x + 1
61            (2, 8) => vec![1, 0, 1, 1, 1, 0, 0, 0, 1], // x^8 + x^4 + x^3 + x^2 + 1
62            (3, 2) => vec![1, 0, 1],           // x^2 + 1 over GF(3)
63            (3, 3) => vec![2, 1, 0, 1],        // x^3 + x + 2 over GF(3)
64            (5, 2) => vec![2, 0, 1],           // x^2 + 2 over GF(5)
65            (7, 2) => vec![3, 0, 1],           // x^2 + 3 over GF(7)
66            _ => {
67                // Brute-force search for an irreducible polynomial
68                self.find_irreducible()
69            }
70        }
71    }
72
73    fn find_irreducible(&self) -> Vec<u64> {
74        let p = self.prime;
75        let n = self.power as usize;
76        // Try all monic polynomials of degree n over GF(p)
77        let total = p.pow(n as u32); // number of lower-degree coefficient combos
78        for combo in 0..total {
79            let mut coeffs = Vec::with_capacity(n + 1);
80            let mut c = combo;
81            for _ in 0..n {
82                coeffs.push(c % p);
83                c /= p;
84            }
85            coeffs.push(1); // monic
86            if self.is_irreducible_poly(&coeffs) {
87                return coeffs;
88            }
89        }
90        // Fallback (should not happen for valid prime/power)
91        let mut coeffs = vec![1; n + 1];
92        coeffs[n] = 1;
93        coeffs
94    }
95
96    fn is_irreducible_poly(&self, coeffs: &[u64]) -> bool {
97        let p = self.prime;
98        let n = coeffs.len() - 1;
99        // Check: no roots in GF(p) (necessary for degree 2,3; not sufficient for higher)
100        // For simplicity: check no polynomial factor of degree 1..n/2
101        if n <= 1 {
102            return n == 1;
103        }
104        // Quick check: no roots
105        for x in 0..p {
106            let mut val = 0u64;
107            let mut xpow = 1u64;
108            for &c in coeffs {
109                val = (val + c * xpow) % p;
110                xpow = xpow * x % p;
111            }
112            if val == 0 {
113                return false;
114            }
115        }
116        if n <= 3 {
117            return true; // for degree 2,3 no-root test is sufficient
118        }
119        true // approximate for higher degrees
120    }
121}
122
123impl GfElement {
124    /// Extract polynomial coefficients from packed value.
125    fn to_poly(&self) -> Vec<u64> {
126        let p = self.field.prime;
127        let n = self.field.power as usize;
128        let mut coeffs = Vec::with_capacity(n);
129        let mut v = self.value;
130        for _ in 0..n {
131            coeffs.push(v % p);
132            v /= p;
133        }
134        coeffs
135    }
136
137    /// Pack polynomial coefficients into a value.
138    fn from_poly(coeffs: &[u64], field: GaloisField) -> Self {
139        let p = field.prime;
140        let mut value = 0u64;
141        let mut base = 1u64;
142        for &c in coeffs.iter().take(field.power as usize) {
143            value += (c % p) * base;
144            base *= p;
145        }
146        GfElement { value, field }
147    }
148
149    /// Addition in the field.
150    pub fn add(self, other: Self) -> Self {
151        assert_eq!(self.field, other.field);
152        let p = self.field.prime;
153        if self.field.power == 1 {
154            return GfElement {
155                value: (self.value + other.value) % p,
156                field: self.field,
157            };
158        }
159        let a = self.to_poly();
160        let b = other.to_poly();
161        let n = self.field.power as usize;
162        let mut c = vec![0u64; n];
163        for i in 0..n {
164            c[i] = (a.get(i).copied().unwrap_or(0) + b.get(i).copied().unwrap_or(0)) % p;
165        }
166        Self::from_poly(&c, self.field)
167    }
168
169    /// Subtraction in the field.
170    pub fn sub(self, other: Self) -> Self {
171        assert_eq!(self.field, other.field);
172        let p = self.field.prime;
173        if self.field.power == 1 {
174            return GfElement {
175                value: (self.value + p - other.value % p) % p,
176                field: self.field,
177            };
178        }
179        let a = self.to_poly();
180        let b = other.to_poly();
181        let n = self.field.power as usize;
182        let mut c = vec![0u64; n];
183        for i in 0..n {
184            c[i] = (a.get(i).copied().unwrap_or(0) + p
185                - b.get(i).copied().unwrap_or(0) % p)
186                % p;
187        }
188        Self::from_poly(&c, self.field)
189    }
190
191    /// Multiplication in the field.
192    pub fn multiply(self, other: Self) -> Self {
193        assert_eq!(self.field, other.field);
194        let p = self.field.prime;
195        if self.field.power == 1 {
196            return GfElement {
197                value: (self.value * other.value) % p,
198                field: self.field,
199            };
200        }
201        let a = self.to_poly();
202        let b = other.to_poly();
203        let n = self.field.power as usize;
204        // Multiply polynomials
205        let mut prod = vec![0u64; 2 * n];
206        for i in 0..n {
207            for j in 0..n {
208                prod[i + j] = (prod[i + j] + a[i] * b[j]) % p;
209            }
210        }
211        // Reduce modulo the irreducible polynomial
212        let irr = self.field.irreducible_poly();
213        poly_mod(&mut prod, &irr, p);
214        prod.truncate(n);
215        while prod.len() < n {
216            prod.push(0);
217        }
218        Self::from_poly(&prod, self.field)
219    }
220
221    /// Multiplicative inverse (via extended Euclidean in the polynomial ring, or Fermat's little theorem).
222    pub fn inverse(self) -> Self {
223        assert!(self.value != 0, "cannot invert zero");
224        let field = self.field;
225        // a^{-1} = a^{p^n - 2} by Fermat's little theorem in GF(p^n)
226        let order = field.order();
227        self.pow(order - 2)
228    }
229
230    /// Exponentiation by squaring.
231    pub fn pow(self, mut exp: u64) -> Self {
232        let mut result = self.field.one();
233        let mut base = self;
234        while exp > 0 {
235            if exp & 1 == 1 {
236                result = result.multiply(base);
237            }
238            base = base.multiply(base);
239            exp >>= 1;
240        }
241        result
242    }
243
244    /// Check if this element is zero.
245    pub fn is_zero(self) -> bool {
246        self.value == 0
247    }
248}
249
250/// Polynomial modular reduction: reduce `poly` mod `modulus` in GF(p)[x].
251fn poly_mod(poly: &mut Vec<u64>, modulus: &[u64], p: u64) {
252    let deg_mod = modulus.len() - 1;
253    let lead_inv = mod_inv(modulus[deg_mod], p);
254    while poly.len() > deg_mod {
255        let coeff = poly[poly.len() - 1];
256        if coeff == 0 {
257            poly.pop();
258            continue;
259        }
260        let factor = coeff * lead_inv % p;
261        let offset = poly.len() - modulus.len();
262        for (i, &m) in modulus.iter().enumerate() {
263            poly[offset + i] = (poly[offset + i] + p - factor * m % p) % p;
264        }
265        poly.pop();
266    }
267}
268
269fn mod_inv(a: u64, p: u64) -> u64 {
270    // Simple for prime p: a^{p-2} mod p
271    let mut result = 1u64;
272    let mut base = a % p;
273    let mut exp = p - 2;
274    while exp > 0 {
275        if exp & 1 == 1 {
276            result = result * base % p;
277        }
278        base = base * base % p;
279        exp >>= 1;
280    }
281    result
282}
283
284/// Find a generator (primitive element) of the multiplicative group of GF(p^n).
285pub fn find_generator(field: GaloisField) -> GfElement {
286    let order = field.order() - 1; // multiplicative group order
287    let factors = super::primes::prime_factorization(order);
288
289    for v in 1..field.order() {
290        let g = field.element(v);
291        let mut is_generator = true;
292        for &(p, _) in &factors {
293            if g.pow(order / p).value == 1 {
294                is_generator = false;
295                break;
296            }
297        }
298        if is_generator {
299            return g;
300        }
301    }
302    field.one() // fallback
303}
304
305/// Build the full multiplication table for a Galois field.
306pub fn multiplication_table(field: GaloisField) -> Vec<Vec<u64>> {
307    let n = field.order() as usize;
308    let mut table = vec![vec![0u64; n]; n];
309    for i in 0..n {
310        let a = field.element(i as u64);
311        for j in 0..n {
312            let b = field.element(j as u64);
313            table[i][j] = a.multiply(b).value;
314        }
315    }
316    table
317}
318
319// ─── Tests ──────────────────────────────────────────────────────────────────
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn gf5_basic() {
327        let f = GaloisField::new(5, 1);
328        let a = f.element(3);
329        let b = f.element(4);
330        assert_eq!(a.add(b).value, 2); // 3+4 = 7 mod 5 = 2
331        assert_eq!(a.multiply(b).value, 2); // 3*4 = 12 mod 5 = 2
332    }
333
334    #[test]
335    fn gf7_inverse() {
336        let f = GaloisField::new(7, 1);
337        for v in 1..7 {
338            let a = f.element(v);
339            let inv = a.inverse();
340            assert_eq!(a.multiply(inv).value, 1, "{}^-1 failed", v);
341        }
342    }
343
344    #[test]
345    fn gf5_additive_identity() {
346        let f = GaloisField::new(5, 1);
347        for v in 0..5 {
348            let a = f.element(v);
349            assert_eq!(a.add(f.zero()).value, a.value);
350        }
351    }
352
353    #[test]
354    fn gf5_multiplicative_identity() {
355        let f = GaloisField::new(5, 1);
356        for v in 0..5 {
357            let a = f.element(v);
358            assert_eq!(a.multiply(f.one()).value, a.value);
359        }
360    }
361
362    #[test]
363    fn gf4_arithmetic() {
364        // GF(2^2) with irreducible x^2 + x + 1
365        let f = GaloisField::new(2, 2);
366        assert_eq!(f.order(), 4);
367        // Elements: 0, 1, x (=2), x+1 (=3)
368        let a = f.element(2); // x
369        let b = f.element(3); // x+1
370        let sum = a.add(b);
371        // x + (x+1) = 1 in GF(2)
372        assert_eq!(sum.value, 1);
373        // x * (x+1) = x^2 + x. Mod (x^2+x+1): x^2+x ≡ 1 (since x^2+x+1≡0 => x^2+x≡1)
374        let prod = a.multiply(b);
375        // Wait: x^2 = x+1 (from x^2+x+1=0), so x*(x+1) = x^2+x = (x+1)+x = 1
376        assert_eq!(prod.value, 1, "x * (x+1) should be 1 in GF(4)");
377    }
378
379    #[test]
380    fn gf4_inverse() {
381        let f = GaloisField::new(2, 2);
382        for v in 1..4 {
383            let a = f.element(v);
384            let inv = a.inverse();
385            assert_eq!(
386                a.multiply(inv).value,
387                1,
388                "inverse of {} failed (got {})",
389                v,
390                inv.value
391            );
392        }
393    }
394
395    #[test]
396    fn gf8_all_inverses() {
397        let f = GaloisField::new(2, 3);
398        assert_eq!(f.order(), 8);
399        for v in 1..8 {
400            let a = f.element(v);
401            let inv = a.inverse();
402            assert_eq!(a.multiply(inv).value, 1, "inverse of {} failed", v);
403        }
404    }
405
406    #[test]
407    fn generator_gf5() {
408        let f = GaloisField::new(5, 1);
409        let g = find_generator(f);
410        // g should generate all non-zero elements
411        let mut seen = std::collections::HashSet::new();
412        let mut current = f.one();
413        for _ in 0..4 {
414            seen.insert(current.value);
415            current = current.multiply(g);
416        }
417        assert_eq!(seen.len(), 4, "generator should produce all 4 non-zero elements");
418    }
419
420    #[test]
421    fn generator_gf4() {
422        let f = GaloisField::new(2, 2);
423        let g = find_generator(f);
424        let mut seen = std::collections::HashSet::new();
425        let mut current = f.one();
426        for _ in 0..3 {
427            seen.insert(current.value);
428            current = current.multiply(g);
429        }
430        assert_eq!(seen.len(), 3);
431    }
432
433    #[test]
434    fn multiplication_table_gf3() {
435        let f = GaloisField::new(3, 1);
436        let table = multiplication_table(f);
437        assert_eq!(table.len(), 3);
438        assert_eq!(table[0], vec![0, 0, 0]); // 0 * anything = 0
439        assert_eq!(table[1], vec![0, 1, 2]); // 1 * x = x
440        assert_eq!(table[2], vec![0, 2, 1]); // 2 * 2 = 4 mod 3 = 1
441    }
442
443    #[test]
444    fn distributive_law() {
445        let f = GaloisField::new(5, 1);
446        for a in 0..5 {
447            for b in 0..5 {
448                for c in 0..5 {
449                    let ea = f.element(a);
450                    let eb = f.element(b);
451                    let ec = f.element(c);
452                    // a * (b + c) = a*b + a*c
453                    let lhs = ea.multiply(eb.add(ec));
454                    let rhs = ea.multiply(eb).add(ea.multiply(ec));
455                    assert_eq!(lhs.value, rhs.value, "distributive failed for {},{},{}", a, b, c);
456                }
457            }
458        }
459    }
460
461    #[test]
462    fn gf9_order() {
463        let f = GaloisField::new(3, 2);
464        assert_eq!(f.order(), 9);
465        // Every non-zero element satisfies a^8 = 1
466        for v in 1..9 {
467            let a = f.element(v);
468            assert_eq!(a.pow(8).value, 1, "a^8 != 1 for a={}", v);
469        }
470    }
471}