From: Jacob Lifshay Date: Fri, 18 Mar 2022 04:34:30 +0000 (-0700) Subject: convert gfp* instructions to python and add tests X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=1bae1d6192fe746fbd3b5a5691625b290f453f39;p=nmutil.git convert gfp* instructions to python and add tests --- diff --git a/gfpadd.py b/gfpadd.py new file mode 100644 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 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 index 0000000..e7159a2 --- /dev/null +++ b/gfpmadd.py @@ -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 index 0000000..fd09124 --- /dev/null +++ b/gfpmsub.py @@ -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 index 0000000..2d349f8 --- /dev/null +++ b/gfpmsubr.py @@ -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 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 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 diff --git a/test_cl_gfb_gfp.py b/test_cl_gfb_gfp.py index 787deab..34133cb 100644 --- a/test_cl_gfb_gfp.py +++ b/test_cl_gfb_gfp.py @@ -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()