convert gfp* instructions to python and add tests
authorJacob Lifshay <programmerjake@gmail.com>
Fri, 18 Mar 2022 04:34:30 +0000 (21:34 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Fri, 18 Mar 2022 04:34:30 +0000 (21:34 -0700)
gfpadd.py [new file with mode: 0644]
gfpinv.py [new file with mode: 0644]
gfpmadd.py [new file with mode: 0644]
gfpmsub.py [new file with mode: 0644]
gfpmsubr.py [new file with mode: 0644]
gfpmul.py [new file with mode: 0644]
gfpsub.py [new file with mode: 0644]
test_cl_gfb_gfp.py

diff --git a/gfpadd.py b/gfpadd.py
new file mode 100644 (file)
index 0000000..f00bf8c
--- /dev/null
+++ b/gfpadd.py
@@ -0,0 +1,5 @@
+from .state import ST
+
+
+def gfpadd(a, b):
+    return (a + b) % ST.GFPRIME
diff --git a/gfpinv.py b/gfpinv.py
new file mode 100644 (file)
index 0000000..e75773a
--- /dev/null
+++ b/gfpinv.py
@@ -0,0 +1,47 @@
+from .state import ST
+
+
+def gfpinv(a):
+    # based on Algorithm ExtEucdInv from:
+    # https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.90.5233&rep=rep1&type=pdf
+    p = ST.GFPRIME
+    assert p >= 2, "GFPRIME isn't a prime"
+    assert a != 0, "TODO: decide what happens for division by zero"
+    assert isinstance(a, int) and 0 < a < p, "a out of range"
+    if p == 2:
+        return 1  # the only value possible
+
+    u = p
+    v = a
+    r = 0
+    s = 1
+    while v > 0:
+        if u & 1 == 0:
+            u >>= 1
+            if r & 1 == 0:
+                r >>= 1
+            else:
+                r = (r + p) >> 1
+        elif v & 1 == 0:
+            v >>= 1
+            if s & 1 == 0:
+                s >>= 1
+            else:
+                s = (s + p) >> 1
+        else:
+            x = u - v
+            if x > 0:
+                u = x
+                r -= s
+                if r < 0:
+                    r += p
+            else:
+                v = -x
+                s -= r
+                if s < 0:
+                    s += p
+    if r > p:
+        r -= p
+    if r < 0:
+        r += p
+    return r
diff --git a/gfpmadd.py b/gfpmadd.py
new file mode 100644 (file)
index 0000000..e7159a2
--- /dev/null
@@ -0,0 +1,5 @@
+from .state import ST
+
+
+def gfpmadd(a, b, c):
+    return (a * b + c) % ST.GFPRIME
diff --git a/gfpmsub.py b/gfpmsub.py
new file mode 100644 (file)
index 0000000..fd09124
--- /dev/null
@@ -0,0 +1,5 @@
+from .state import ST
+
+
+def gfpmsub(a, b, c):
+    return (a * b - c) % ST.GFPRIME
diff --git a/gfpmsubr.py b/gfpmsubr.py
new file mode 100644 (file)
index 0000000..2d349f8
--- /dev/null
@@ -0,0 +1,5 @@
+from .state import ST
+
+
+def gfpmsubr(a, b, c):
+    return (c - a * b) % ST.GFPRIME
diff --git a/gfpmul.py b/gfpmul.py
new file mode 100644 (file)
index 0000000..42c43a2
--- /dev/null
+++ b/gfpmul.py
@@ -0,0 +1,5 @@
+from .state import ST
+
+
+def gfpmul(a, b):
+    return (a * b) % ST.GFPRIME
diff --git a/gfpsub.py b/gfpsub.py
new file mode 100644 (file)
index 0000000..44e5798
--- /dev/null
+++ b/gfpsub.py
@@ -0,0 +1,5 @@
+from .state import ST
+
+
+def gfpsub(a, b):
+    return (a - b) % ST.GFPRIME
index 787deab2a54ce9579e52791d3bcd446088dad3cd..34133cbe3a4a65a7c281f02b1e39a0d458adb2e5 100644 (file)
@@ -4,6 +4,13 @@ from .clmul import clmul
 from .gfbmul import gfbmul
 from .gfbmadd import gfbmadd
 from .gfbinv import gfbinv
+from .gfpadd import gfpadd
+from .gfpsub import gfpsub
+from .gfpmul import gfpmul
+from .gfpinv import gfpinv
+from .gfpmadd import gfpmadd
+from .gfpmsub import gfpmsub
+from .gfpmsubr import gfpmsubr
 from .pack_poly import pack_poly, unpack_poly
 import unittest
 
@@ -375,6 +382,133 @@ class TestGFBClass(unittest.TestCase):
         self.assertEqual(v, expected)
 
 
+class GFP:
+    def __init__(self, value, size):
+        assert isinstance(value, int)
+        assert isinstance(size, int) and size >= 2, "size is not a prime"
+        self.value = value % size
+        self.size = size
+
+    def __repr__(self):
+        return f"GFP({self.value}, {self.size})"
+
+    def __eq__(self, rhs):
+        if isinstance(rhs, GFP):
+            return self.value == rhs.value and self.size == rhs.size
+        return NotImplemented
+
+    def __add__(self, rhs):
+        assert isinstance(rhs, GFP)
+        assert self.size == rhs.size
+        return GFP((self.value + rhs.value) % self.size, self.size)
+
+    def __sub__(self, rhs):
+        assert isinstance(rhs, GFP)
+        assert self.size == rhs.size
+        return GFP((self.value - rhs.value) % self.size, self.size)
+
+    def __mul__(self, rhs):
+        assert isinstance(rhs, GFP)
+        assert self.size == rhs.size
+        return GFP((self.value * rhs.value) % self.size, self.size)
+
+    def __div__(self, rhs):
+        assert isinstance(rhs, GFP)
+        assert self.size == rhs.size
+        return self * rhs ** -1
+
+    @property
+    def __pow_period(self):
+        period = self.size - 1
+        assert period > 0, "internal logic error"
+        return period
+
+    def __pow__(self, exponent):
+        assert isinstance(exponent, int)
+        if self.value == 0:
+            if exponent < 0:
+                raise ZeroDivisionError
+            else:
+                return GFP(self.value, self.size)
+        exponent %= self.__pow_period
+        return GFP(pow(self.value, exponent, self.size), self.size)
+
+
+PRIMES = 2, 3, 5, 7, 11, 13, 17, 19
+"""handy list of small primes for testing"""
+
+
+class TestGFPClass(unittest.TestCase):
+    def test_add_sub(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    with self.subTest(av=av, bv=bv, prime=prime):
+                        a = GFP(av, prime)
+                        b = GFP(bv, prime)
+                        expected = GFP((av + bv) % prime, prime)
+                        c = a + b
+                        self.assertEqual(a, GFP(av, prime))
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(c, expected)
+                        c = b + a
+                        self.assertEqual(a, GFP(av, prime))
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(c, expected)
+                        expected = GFP((av - bv) % prime, prime)
+                        c = a - b
+                        self.assertEqual(a, GFP(av, prime))
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(c, expected)
+                        expected = GFP((bv - av) % prime, prime)
+                        c = b - a
+                        self.assertEqual(a, GFP(av, prime))
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(c, expected)
+
+    def test_mul(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    with self.subTest(av=av, bv=bv, prime=prime):
+                        a = GFP(av, prime)
+                        b = GFP(bv, prime)
+                        expected = GFP((av * bv) % prime, prime)
+                        c = a * b
+                        self.assertEqual(a, GFP(av, prime))
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(c, expected)
+                        c = b * a
+                        self.assertEqual(a, GFP(av, prime))
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(c, expected)
+
+    def test_pow(self):
+        for prime in PRIMES:
+            for bv in range(prime):
+                with self.subTest(bv=bv, prime=prime):
+                    b = GFP(bv, prime)
+                    period = prime - 1
+                    e = period - 1
+                    expected = GFP(pow(bv, e, prime) if bv != 0 else 0, prime)
+                    v = b ** e
+                    self.assertEqual(b, GFP(bv, prime))
+                    self.assertEqual(v, expected)
+                    e = -1
+                    if bv != 0:
+                        v = b ** e
+                        self.assertEqual(b, GFP(bv, prime))
+                        self.assertEqual(v, expected)
+
+                    # test that pow doesn't take inordinately long when given
+                    # a modulus. adding a multiple of `period` should leave
+                    # results unchanged.
+                    e += period * 10 ** 15
+                    v = b ** e
+                    self.assertEqual(b, GFP(bv, prime))
+                    self.assertEqual(v, expected)
+
+
 class TestCL(unittest.TestCase):
     def test_cldivrem(self):
         n_width = 8
@@ -463,5 +597,105 @@ class TestGFBInstructions(unittest.TestCase):
                 self.assertEqual(result, expectedv)
 
 
+class TestGFPInstructions(unittest.TestCase):
+    def test_gfpadd(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    a = GFP(av, prime)
+                    b = GFP(bv, prime)
+                    expected = a + b
+                    with self.subTest(a=str(a), b=str(b),
+                                      expected=str(expected)):
+                        ST.reinit(GFPRIME=prime)
+                        v = gfpadd(av, bv)
+                        self.assertEqual(v, expected.value)
+
+    def test_gfpsub(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    a = GFP(av, prime)
+                    b = GFP(bv, prime)
+                    expected = a - b
+                    with self.subTest(a=str(a), b=str(b),
+                                      expected=str(expected)):
+                        ST.reinit(GFPRIME=prime)
+                        v = gfpsub(av, bv)
+                        self.assertEqual(v, expected.value)
+
+    def test_gfpmul(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    a = GFP(av, prime)
+                    b = GFP(bv, prime)
+                    expected = a * b
+                    with self.subTest(a=str(a), b=str(b),
+                                      expected=str(expected)):
+                        ST.reinit(GFPRIME=prime)
+                        v = gfpmul(av, bv)
+                        self.assertEqual(v, expected.value)
+
+    def test_gfpinv(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                a = GFP(av, prime)
+                if av == 0:
+                    # TODO: determine what's expected for division by zero
+                    continue
+                else:
+                    expected = a ** -1
+                with self.subTest(a=str(a), expected=str(expected)):
+                    ST.reinit(GFPRIME=prime)
+                    v = gfpinv(av)
+                    self.assertEqual(v, expected.value)
+
+    def test_gfpmadd(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    for cv in range(prime):
+                        a = GFP(av, prime)
+                        b = GFP(bv, prime)
+                        c = GFP(cv, prime)
+                        expected = a * b + c
+                        with self.subTest(a=str(a), b=str(b), c=str(c),
+                                          expected=str(expected)):
+                            ST.reinit(GFPRIME=prime)
+                            v = gfpmadd(av, bv, cv)
+                            self.assertEqual(v, expected.value)
+
+    def test_gfpmsub(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    for cv in range(prime):
+                        a = GFP(av, prime)
+                        b = GFP(bv, prime)
+                        c = GFP(cv, prime)
+                        expected = a * b - c
+                        with self.subTest(a=str(a), b=str(b), c=str(c),
+                                          expected=str(expected)):
+                            ST.reinit(GFPRIME=prime)
+                            v = gfpmsub(av, bv, cv)
+                            self.assertEqual(v, expected.value)
+
+    def test_gfpmsubr(self):
+        for prime in PRIMES:
+            for av in range(prime):
+                for bv in range(prime):
+                    for cv in range(prime):
+                        a = GFP(av, prime)
+                        b = GFP(bv, prime)
+                        c = GFP(cv, prime)
+                        expected = c - a * b
+                        with self.subTest(a=str(a), b=str(b), c=str(c),
+                                          expected=str(expected)):
+                            ST.reinit(GFPRIME=prime)
+                            v = gfpmsubr(av, bv, cv)
+                            self.assertEqual(v, expected.value)
+
+
 if __name__ == "__main__":
     unittest.main()