Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
43 changes: 43 additions & 0 deletions algebra/extensions/extension_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from abc import ABC, abstractmethod
from typing import List, Union


class ExtensionField(ABC):
"""Abstract base class for field extensions"""

base_field = None
degree = None

def __init__(self, coeffs: Union[List, 'ExtensionField']):
"""Initialize with coefficients or copy from another element"""
if isinstance(coeffs, ExtensionField):
self.coeffs = coeffs.coeffs[:]
else:
self.coeffs = list(coeffs)
# Pad with zeros if needed
while len(self.coeffs) < self.degree:
self.coeffs.append(self.base_field(0))

@abstractmethod
def __add__(self, other):
"""Addition in the extension field"""
pass

@abstractmethod
def __mul__(self, other):
"""Multiplication in the extension field"""
pass

def __repr__(self):
return f"{self.__class__.__name__}({self.coeffs})"

def __eq__(self, other):
if not isinstance(other, ExtensionField):
return False
return self.coeffs == other.coeffs

def __radd__(self, other):
return self.__add__(other)

def __rmul__(self, other):
return self.__mul__(other)
46 changes: 46 additions & 0 deletions algebra/extensions/gf2n.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from algebra.extensions.extension_field import ExtensionField
from algebra.ff.gf2 import GF2


class GF2n(ExtensionField):
base_field = GF2
degree = None
irreducible_poly = None

def __add__(self, other):
if isinstance(other, int):
other = type(self)([GF2(other)])

result = [self.coeffs[i] + other.coeffs[i] for i in range(self.degree)]
return type(self)(result)

def __mul__(self, other):
if isinstance(other, int):
other = type(self)([GF2(other)])

result = [GF2(0)] * (2 * self.degree - 1)
for i in range(len(self.coeffs)):
for j in range(len(other.coeffs)):
result[i + j] += self.coeffs[i] * other.coeffs[j]

return type(self)(self._reduce(result))

def _reduce(self, coeffs):
while len(coeffs) > self.degree and coeffs[-1] == GF2(1):
for i, coeff in enumerate(self.irreducible_poly):
idx = len(coeffs) - len(self.irreducible_poly) + i
if idx >= 0:
coeffs[idx] += coeff
coeffs.pop()

return coeffs[:self.degree] + [GF2(0)] * (self.degree - len(coeffs))


class GF4(GF2n):
degree = 2
irreducible_poly = [GF2(1), GF2(1), GF2(1)]


class GF8(GF2n):
degree = 3
irreducible_poly = [GF2(1), GF2(1), GF2(0), GF2(1)]
3 changes: 2 additions & 1 deletion algebra/ff/bigint_field.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""BigInt-based prime field implementation that avoids tinygrad tensor operations with large constants"""

from algebra.bigint.bigint import BigInt
from algebra.ff.prime_field import PrimeField
from tinygrad.tensor import Tensor
from tinygrad import dtypes


class BigIntPrimeField:
class BigIntPrimeField(PrimeField):
"""Prime field implementation using BigInt for all arithmetic"""

P: int = None
Expand Down
16 changes: 16 additions & 0 deletions algebra/ff/gf2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from algebra.ff.prime_field import PrimeField

class GF2(PrimeField):
P = 2

def __init__(self, x):
self.value = self.t32(int(x) & 1)

def __add__(self, other):
return GF2(self.value.item() ^ GF2(other).value.item())

def __mul__(self, other):
return GF2(self.value.item() & GF2(other).value.item())

__radd__ = __add__
__rmul__ = __mul__
34 changes: 34 additions & 0 deletions tests/test_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from algebra.extensions.gf2n import GF4, GF8
from algebra.ff.gf2 import GF2


def test_gf4():
# Test elements in GF(4)
a = GF4([GF2(1), GF2(0)]) # 1
b = GF4([GF2(0), GF2(1)]) # x
c = GF4([GF2(1), GF2(1)]) # 1 + x

# Test addition (XOR)
assert a + b == c
assert b + b == GF4([GF2(0), GF2(0)]) # x + x = 0

# Test multiplication
# x * x = x^2, but x^2 = x + 1 in GF(4) with irreducible x^2 + x + 1
result = b * b
expected = GF4([GF2(1), GF2(1)]) # x + 1
assert result == expected


def test_gf8():
# Test elements in GF(8)
a = GF8([GF2(1), GF2(0), GF2(0)]) # 1
b = GF8([GF2(0), GF2(1), GF2(0)]) # x

# Test addition
result = a + b
expected = GF8([GF2(1), GF2(1), GF2(0)]) # 1 + x
assert result == expected

# Test that x + x = 0
assert b + b == GF8([GF2(0), GF2(0), GF2(0)])

32 changes: 32 additions & 0 deletions tests/test_gf2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from algebra.ff.gf2 import GF2

def test_basic_ops():
assert GF2(0) + GF2(0) == GF2(0)
assert GF2(0) + GF2(1) == GF2(1)
assert GF2(1) + GF2(0) == GF2(1)
assert GF2(1) + GF2(1) == GF2(0)

assert GF2(0) * GF2(0) == GF2(0)
assert GF2(0) * GF2(1) == GF2(0)
assert GF2(1) * GF2(0) == GF2(0)
assert GF2(1) * GF2(1) == GF2(1)

def test_polynomials():
p_coeffs = [GF2(1), GF2(1)] # 1 + x
q_coeffs = [GF2(0), GF2(1)] # x

max_len = max(len(p_coeffs), len(q_coeffs))
p_padded = p_coeffs + [GF2(0)] * (max_len - len(p_coeffs))
q_padded = q_coeffs + [GF2(0)] * (max_len - len(q_coeffs))

result = [p_padded[i] + q_padded[i] for i in range(max_len)]
expected = [GF2(1), GF2(0)] # 1 + 0x = 1
assert result == expected

p_mul_q = [GF2(0), GF2(1), GF2(1)] # 0 + 1x + 1x^2 = x + x^2

c0 = p_coeffs[0] * q_coeffs[0] # 1 * 0 = 0
c1 = p_coeffs[0] * q_coeffs[1] + p_coeffs[1] * q_coeffs[0] # 1*1 + 1*0 = 1
c2 = p_coeffs[1] * q_coeffs[1] # 1 * 1 = 1

assert [c0, c1, c2] == p_mul_q
Loading