From 760a1559bf1ee015d3a87b40f83c0d1c6b909d1d Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 11:02:53 +0100 Subject: [PATCH 01/15] Clean up and simplify fmpz_mod --- src/flint/types/fmpz_mod.pyx | 73 ++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 362a9161..65a430db 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -45,8 +45,8 @@ cdef class fmpz_mod_ctx: if mod < 1: raise ValueError("Modulus is expected to be positive") - # Init the context - fmpz_mod_ctx_init(self.val, (mod).val) + # Set the modulus + fmpz_mod_ctx_set_modulus(self.val, (mod).val) def modulus(self): """ @@ -224,22 +224,39 @@ cdef class fmpz_mod(flint_scalar): def __radd__(self, other): return self.__add__(other) - def __sub__(self, other): - other = self.any_as_fmpz_mod(other) - if other is NotImplemented: - return NotImplemented - + @staticmethod + def _sub_(left, right): cdef fmpz_mod res res = fmpz_mod.__new__(fmpz_mod) - res.ctx = self.ctx + + # Case when left and right are already fmpz_mod + if typecheck(left, fmpz_mod) and typecheck(right, fmpz_mod): + if not (left).ctx == (right).ctx: + raise ValueError("moduli must match") + # Case when right is not fmpz_mod, try to convert to fmpz + elif typecheck(left, fmpz_mod): + right = (left).any_as_fmpz_mod(right) + if right is NotImplemented: + return NotImplemented + + # Case when left is not fmpz_mod, try to convert to fmpz + else: + left = (right).any_as_fmpz_mod(left) + if left is NotImplemented: + return NotImplemented + + res.ctx = (left).ctx fmpz_mod_sub( - res.val, self.val, (other).val, self.ctx.val - ) + res.val, (left).val, (right).val, res.ctx.val + ) return res - def __rsub__(self, other): - return self.__sub__(other).__neg__() + def __sub__(s, t): + return fmpz_mod._sub_(s, t) + + def __rsub__(s, t): + return fmpz_mod._sub_(t, s) def __mul__(self, other): other = self.any_as_fmpz_mod(other) @@ -264,35 +281,27 @@ cdef class fmpz_mod(flint_scalar): cdef fmpz_mod res res = fmpz_mod.__new__(fmpz_mod) - # Division when left and right are fmpz_mod + # Case when left and right are already fmpz_mod if typecheck(left, fmpz_mod) and typecheck(right, fmpz_mod): - res.ctx = (left).ctx if not (left).ctx == (right).ctx: raise ValueError("moduli must match") - check = fmpz_mod_divides( - res.val, (left).val, (right).val, res.ctx.val - ) - - # Case when only left is fmpz_mod + + # Case when right is not fmpz_mod, try to convert to fmpz elif typecheck(left, fmpz_mod): - res.ctx = (left).ctx - right = any_as_fmpz(right) + right = (left).any_as_fmpz_mod(right) if right is NotImplemented: - return NotImplemented - check = fmpz_mod_divides( - res.val, (left).val, (right).val, res.ctx.val - ) + return NotImplemented - # Case when right is an fmpz_mod + # Case when left is not fmpz_mod, try to convert to fmpz else: - res.ctx = (right).ctx - left = any_as_fmpz(left) + left = (right).any_as_fmpz_mod(left) if left is NotImplemented: - return NotImplemented - check = fmpz_mod_divides( - res.val, (left).val, (right).val, res.ctx.val - ) + return NotImplemented + res.ctx = (left).ctx + check = fmpz_mod_divides( + res.val, (left).val, (right).val, res.ctx.val + ) if check == 0: raise ZeroDivisionError(f"{right} is not invertible modulo {res.ctx.modulus()}") From 3fe7a631a6bd5cb5da9b0b01951640fd947ca1d6 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 13:04:28 +0100 Subject: [PATCH 02/15] Add discrete log solver to fmpz_mod --- src/flint/flintlib/fmpz_mod.pxd | 47 ++++++++-- src/flint/test/test.py | 45 ++++++++++ src/flint/types/fmpz_mod.pyx | 149 ++++++++++++++++++++++++-------- 3 files changed, 199 insertions(+), 42 deletions(-) diff --git a/src/flint/flintlib/fmpz_mod.pxd b/src/flint/flintlib/fmpz_mod.pxd index a96a0e3a..b6d96f82 100644 --- a/src/flint/flintlib/fmpz_mod.pxd +++ b/src/flint/flintlib/fmpz_mod.pxd @@ -1,10 +1,11 @@ from flint.flintlib.flint cimport ulong, slong -from flint.flintlib.fmpz cimport fmpz_t, fmpz_preinvn_struct +from flint.flintlib.fmpz cimport fmpz_t, fmpz_struct, fmpz_preinvn_struct from flint.flintlib.nmod cimport nmod_t -# unimported types {'fmpz_mod_discrete_log_pohlig_hellman_t'} - cdef extern from "flint/fmpz_mod.h": + # + # fmpz_mod structs, a la Pohlig - Hellman + # ctypedef struct fmpz_mod_ctx_struct: fmpz_t n nmod_t mod @@ -13,6 +14,36 @@ cdef extern from "flint/fmpz_mod.h": fmpz_preinvn_struct * ninv_huge ctypedef fmpz_mod_ctx_struct fmpz_mod_ctx_t[1] + # + # discrete logs structs, a la Pohlig - Hellman + # + + ctypedef struct fmpz_mod_discrete_log_pohlig_hellman_table_entry_struct: + fmpz_t gammapow + ulong cm + + ctypedef struct fmpz_mod_discrete_log_pohlig_hellman_entry_struct: + slong exp + ulong prime + fmpz_t gamma + fmpz_t gammainv + fmpz_t startingbeta + fmpz_t co + fmpz_t startinge + fmpz_t idem + ulong cbound + ulong dbound + fmpz_mod_discrete_log_pohlig_hellman_table_entry_struct * table # length cbound */ + + ctypedef struct fmpz_mod_discrete_log_pohlig_hellman_struct: + fmpz_mod_ctx_t fpctx + fmpz_t pm1 # p - 1 */ + fmpz_t alpha # p.r. of p */ + fmpz_t alphainv + slong num_factors # factors of p - 1 + fmpz_mod_discrete_log_pohlig_hellman_entry_struct * entries + ctypedef fmpz_mod_discrete_log_pohlig_hellman_struct fmpz_mod_discrete_log_pohlig_hellman_t[1] + # Parsed from here void fmpz_mod_ctx_init(fmpz_mod_ctx_t ctx, const fmpz_t n) void fmpz_mod_ctx_clear(fmpz_mod_ctx_t ctx) @@ -37,9 +68,9 @@ cdef extern from "flint/fmpz_mod.h": int fmpz_mod_divides(fmpz_t a, const fmpz_t b, const fmpz_t c, const fmpz_mod_ctx_t ctx) void fmpz_mod_pow_ui(fmpz_t a, const fmpz_t b, ulong e, const fmpz_mod_ctx_t ctx) int fmpz_mod_pow_fmpz(fmpz_t a, const fmpz_t b, const fmpz_t e, const fmpz_mod_ctx_t ctx) - # void fmpz_mod_discrete_log_pohlig_hellman_init(fmpz_mod_discrete_log_pohlig_hellman_t L) - # void fmpz_mod_discrete_log_pohlig_hellman_clear(fmpz_mod_discrete_log_pohlig_hellman_t L) - # double fmpz_mod_discrete_log_pohlig_hellman_precompute_prime(fmpz_mod_discrete_log_pohlig_hellman_t L, const fmpz_t p) - # const fmpz_struct * fmpz_mod_discrete_log_pohlig_hellman_primitive_root(const fmpz_mod_discrete_log_pohlig_hellman_t L) - # void fmpz_mod_discrete_log_pohlig_hellman_run(fmpz_t x, const fmpz_mod_discrete_log_pohlig_hellman_t L, const fmpz_t y) + void fmpz_mod_discrete_log_pohlig_hellman_init(fmpz_mod_discrete_log_pohlig_hellman_t L) + void fmpz_mod_discrete_log_pohlig_hellman_clear(fmpz_mod_discrete_log_pohlig_hellman_t L) + double fmpz_mod_discrete_log_pohlig_hellman_precompute_prime(fmpz_mod_discrete_log_pohlig_hellman_t L, const fmpz_t p) + const fmpz_struct * fmpz_mod_discrete_log_pohlig_hellman_primitive_root(const fmpz_mod_discrete_log_pohlig_hellman_t L) + void fmpz_mod_discrete_log_pohlig_hellman_run(fmpz_t x, const fmpz_mod_discrete_log_pohlig_hellman_t L, const fmpz_t y) int fmpz_next_smooth_prime(fmpz_t a, const fmpz_t b) diff --git a/src/flint/test/test.py b/src/flint/test/test.py index 8121d1ed..3a5b7622 100644 --- a/src/flint/test/test.py +++ b/src/flint/test/test.py @@ -1770,6 +1770,50 @@ def test_fmpz_mod(): assert fmpz(test_y) / F_test(test_x) == (test_y * pow(test_x, -1, test_mod)) % test_mod assert test_y / F_test(test_x) == (test_y * pow(test_x, -1, test_mod)) % test_mod +def test_fmpz_mod_dlog(): + from flint import fmpz, fmpz_mod_ctx + + # Input modulus must be prime + F = fmpz_mod_ctx(4) + g, a = F(1), F(2) + assert raises(lambda: g.discrete_log(a, check=True), ValueError) + + # Moduli must match + F1, F2 = fmpz_mod_ctx(2), fmpz_mod_ctx(3) + g = F1(2) + a = F2(4) + assert raises(lambda: g.discrete_log(a, check=True), ValueError) + + # Need to use either fmpz_mod or something which can be case to + # fmpz + assert raises(lambda: g.discrete_log("A", check=True), TypeError) + + F = fmpz_mod_ctx(163) + g = F(2) + a = g**123 + + assert 123 == g.discrete_log(a) + + a_int = pow(2, 123, 163) + a_fmpz = fmpz(a_int) + assert 123 == g.discrete_log(a_int) + assert 123 == g.discrete_log(a_fmpz) + + # Randomised testing with smooth large modulus + e2, e3 = 92, 79 + p = 2**e2 * 3**e3 + 1 + F = fmpz_mod_ctx(p) + + import random + for _ in range(10): + g = F(random.randint(0,p)) + for _ in range(10): + i = random.randint(0,p) + a = g**i + x = g.discrete_log(a) + assert g**x == a + + all_tests = [ test_pyflint, @@ -1790,4 +1834,5 @@ def test_fmpz_mod(): test_nmod_mat, test_arb, test_fmpz_mod, + test_fmpz_mod_dlog ] diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 65a430db..f8cc3ca1 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -4,8 +4,16 @@ from flint.flintlib.fmpz cimport ( fmpz_set, fmpz_init, fmpz_clear, - fmpz_equal + fmpz_equal, + fmpz_is_probabprime, + fmpz_sub, + fmpz_mul, + fmpz_invmod, + fmpz_divexact, + fmpz_gcd ) +from flint.flintlib.fmpz cimport fmpz_mod as fmpz_type_mod + from flint.flintlib.fmpz_mod cimport * from flint.utils.typecheck cimport typecheck @@ -158,6 +166,112 @@ cdef class fmpz_mod(flint_scalar): res = fmpz_mod_is_one(self.val, self.ctx.val) return res == 1 + def inverse(self, check=True): + r""" + Computes :math:`a^{-1} \pmod N` + + When check=False, the solutions is assumed to exist and Flint will abort on + failure. + + >>> mod_ctx = fmpz_mod_ctx(163) + >>> mod_ctx(2).inverse() + fmpz_mod(82, 163) + >>> mod_ctx(2).inverse(check=False) + fmpz_mod(82, 163) + """ + cdef fmpz_mod res + res = fmpz_mod.__new__(fmpz_mod) + res.ctx = self.ctx + + if check is False: + fmpz_mod_inv(res.val, self.val, self.ctx.val) + return res + + cdef bint r + cdef fmpz one = fmpz.__new__(fmpz) + fmpz_one(one.val) + + r = fmpz_mod_divides( + res.val, one.val, self.val, self.ctx.val + ) + if r == 0: + raise ZeroDivisionError(f"{self} is not invertible modulo {self.ctx.modulus()}") + + return res + + def discrete_log(self, a, check=False): + """ + Solve the discrete logarithm problem, using `self = g` as a base. + Assumes a solution, :math:`a = g^x \pmod p` exists. + + NOTE: Requires that the context modulus is prime. + + TODO: This could instead be initalised as a class from a + given base and the precomputations could be stored to allow + faster computations for many discrete logs with the same base. + + >>> p = 163 + >>> F = fmpz_mod_ctx(p) + >>> g = F(2) + >>> x = 123 + >>> g.discrete_log(g**123) + 123 + """ + cdef fmpz_mod_discrete_log_pohlig_hellman_t L + cdef bint is_prime + + # Ensure that the modulus is prime + if check: + is_prime = fmpz_is_probabprime(self.ctx.val.n) + if not is_prime: + raise ValueError("modulus must be prime") + + # Then check the type of the input + if typecheck(a, fmpz_mod): + if self.ctx != (a).ctx: + raise ValueError("moduli must match") + else: + a = self.any_as_fmpz_mod(a) + if a is NotImplemented: + raise TypeError + + # Initalise the dlog data, all discrete logs are solved with an internally + # chosen base `y` + fmpz_mod_discrete_log_pohlig_hellman_init(L) + fmpz_mod_discrete_log_pohlig_hellman_precompute_prime(L, self.ctx.val.n) + + # Solve the discrete log for the chosen base and target + # g = y^x_g and a = y^x_a + # We want to find x such that a = g^x => + # (y^x_a) = (y^x_g)^x => x = (x_a / x_g) mod (p-1) + cdef fmpz_t x_a + cdef fmpz_t x_g + fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) + fmpz_mod_discrete_log_pohlig_hellman_run(x_a, L, (a).val) + + # If x_a, x_g share a common factor divide it out? + # TODO: this step seems annoying, but it solved the + # problem when I had even x_a, x_g so there was no + # inverse mod (p-1) + cdef fmpz_t g + fmpz_gcd(g, x_a, x_g) + fmpz_divexact(x_g, x_g, g) + fmpz_divexact(x_a, x_a, g) + + # Finally, compute output exponent + cdef fmpz x + x = fmpz.__new__(fmpz) + + # Compute (x_a / x_g) mod (p-1) + fmpz_invmod(x.val, x_g, L.pm1) + fmpz_mul(x.val, x.val, x_a) + fmpz_type_mod(x.val, x.val, L.pm1) + + # Clear the dlog struct + fmpz_mod_discrete_log_pohlig_hellman_clear(L) + + return x + def __richcmp__(self, other, int op): cdef bint res if op != 2 and op != 3: @@ -316,39 +430,6 @@ cdef class fmpz_mod(flint_scalar): def __floordiv__(self, other): return NotImplemented - def inverse(self, check=True): - r""" - Computes :math:`a^{-1} \pmod N` - - When check=False, the solutions is assumed to exist and Flint will abort on - failure. - - >>> mod_ctx = fmpz_mod_ctx(163) - >>> mod_ctx(2).inverse() - fmpz_mod(82, 163) - >>> mod_ctx(2).inverse(check=False) - fmpz_mod(82, 163) - """ - cdef fmpz_mod res - res = fmpz_mod.__new__(fmpz_mod) - res.ctx = self.ctx - - if check is False: - fmpz_mod_inv(res.val, self.val, self.ctx.val) - return res - - cdef bint r - cdef fmpz one = fmpz.__new__(fmpz) - fmpz_one(one.val) - - r = fmpz_mod_divides( - res.val, one.val, self.val, self.ctx.val - ) - if r == 0: - raise ZeroDivisionError(f"{self} is not invertible modulo {self.ctx.modulus()}") - - return res - def __invert__(self): return self.inverse() From 46b749a6ec6138b76867a567b14610534e88f20a Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 13:07:32 +0100 Subject: [PATCH 03/15] Clean up imports --- src/flint/types/fmpz_mod.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index f8cc3ca1..f46d39ce 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -6,7 +6,6 @@ from flint.flintlib.fmpz cimport ( fmpz_clear, fmpz_equal, fmpz_is_probabprime, - fmpz_sub, fmpz_mul, fmpz_invmod, fmpz_divexact, From 258b64b24bd2f2eafc98071d6d74e21b67876ea4 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 13:55:25 +0100 Subject: [PATCH 04/15] Clearer comments --- src/flint/types/fmpz_mod.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index f46d39ce..d771439d 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -245,13 +245,13 @@ cdef class fmpz_mod(flint_scalar): # (y^x_a) = (y^x_g)^x => x = (x_a / x_g) mod (p-1) cdef fmpz_t x_a cdef fmpz_t x_g + # TODO: should this value be stored for efficiency? fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) fmpz_mod_discrete_log_pohlig_hellman_run(x_a, L, (a).val) - # If x_a, x_g share a common factor divide it out? - # TODO: this step seems annoying, but it solved the - # problem when I had even x_a, x_g so there was no - # inverse mod (p-1) + # If g is not a primative root, then x_g and x_a will share + # a factor which divides (p-1). + # TODO: should we only divide if g != 1, might be faster? cdef fmpz_t g fmpz_gcd(g, x_a, x_g) fmpz_divexact(x_g, x_g, g) From 1050cb8ef0dda8e5cb4c1f6d6702fead38654224 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 14:08:40 +0100 Subject: [PATCH 05/15] Clearer mathematics for subgroup testing --- src/flint/types/fmpz_mod.pyx | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index d771439d..11f14e02 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -9,7 +9,8 @@ from flint.flintlib.fmpz cimport ( fmpz_mul, fmpz_invmod, fmpz_divexact, - fmpz_gcd + fmpz_gcd, + fmpz_is_one ) from flint.flintlib.fmpz cimport fmpz_mod as fmpz_type_mod @@ -249,22 +250,26 @@ cdef class fmpz_mod(flint_scalar): fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) fmpz_mod_discrete_log_pohlig_hellman_run(x_a, L, (a).val) - # If g is not a primative root, then x_g and x_a will share - # a factor which divides (p-1). - # TODO: should we only divide if g != 1, might be faster? - cdef fmpz_t g - fmpz_gcd(g, x_a, x_g) - fmpz_divexact(x_g, x_g, g) - fmpz_divexact(x_a, x_a, g) + # If g is not a primative root, then x_g and pm1 will share + # a common factor. We can use this to compute the order of + # g. + cdef fmpz_t g, g_order + fmpz_gcd(g, x_g, L.pm1) + if not fmpz_is_one(g): + fmpz_divexact(x_g, x_g, g) + fmpz_divexact(x_a, x_a, g) + fmpz_divexact(g_order, L.pm1, g) + else: + fmpz_set(g_order, L.pm1) # Finally, compute output exponent cdef fmpz x x = fmpz.__new__(fmpz) - # Compute (x_a / x_g) mod (p-1) - fmpz_invmod(x.val, x_g, L.pm1) + # Compute (x_a / x_g) mod g_order + fmpz_invmod(x.val, x_g, g_order) fmpz_mul(x.val, x.val, x_a) - fmpz_type_mod(x.val, x.val, L.pm1) + fmpz_type_mod(x.val, x.val, g_order) # Clear the dlog struct fmpz_mod_discrete_log_pohlig_hellman_clear(L) From 473315ab26e0d35163f5c40f4018b48813156158 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 14:32:50 +0100 Subject: [PATCH 06/15] modify fragile doctest --- src/flint/types/fmpz_mod.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 11f14e02..1cf3ee1d 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -210,11 +210,11 @@ cdef class fmpz_mod(flint_scalar): given base and the precomputations could be stored to allow faster computations for many discrete logs with the same base. - >>> p = 163 - >>> F = fmpz_mod_ctx(p) + >>> F = fmpz_mod_ctx(163) >>> g = F(2) >>> x = 123 - >>> g.discrete_log(g**123) + >>> a = g**123 + >>> g.discrete_log(a) 123 """ cdef fmpz_mod_discrete_log_pohlig_hellman_t L From e83e64929892122be7ae85046c6f2c62f2b796b9 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 15:13:28 +0100 Subject: [PATCH 07/15] Speed up conversion to fmpz_mod --- src/flint/types/fmpz_mod.pxd | 5 ++-- src/flint/types/fmpz_mod.pyx | 46 +++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/flint/types/fmpz_mod.pxd b/src/flint/types/fmpz_mod.pxd index 264d35d7..a2800a21 100644 --- a/src/flint/types/fmpz_mod.pxd +++ b/src/flint/types/fmpz_mod.pxd @@ -5,10 +5,9 @@ from flint.flintlib.fmpz_mod cimport fmpz_mod_ctx_t cdef class fmpz_mod_ctx: cdef fmpz_mod_ctx_t val - + cdef any_as_fmpz_mod(self, obj) + cdef class fmpz_mod(flint_scalar): cdef fmpz_mod_ctx ctx cdef fmpz_t val - cdef any_as_fmpz_mod(self, obj) - diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 1cf3ee1d..c5c53299 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -70,6 +70,30 @@ cdef class fmpz_mod_ctx: fmpz_set(n.val, (self.val.n)) return n + cdef any_as_fmpz_mod(self, obj): + # If `obj` is an `fmpz_mod`, just check moduli + # match + # TODO: we could allow conversion from one modulus to another? + if typecheck(obj, fmpz_mod): + if self != (obj).ctx: + raise ValueError("moduli must match") + return obj + + # Try and convert obj to fmpz + if not typecheck(obj, fmpz): + obj = any_as_fmpz(obj) + if obj is NotImplemented: + return NotImplemented + + # We have been able to cast `obj` to an `fmpz` so + # we create a new `fmpz_mod` and set the val + cdef fmpz_mod res + res = fmpz_mod.__new__(fmpz_mod) + res.ctx = self + fmpz_mod_set_fmpz(res.val, (obj).val, self.val) + + return res + def __eq__(self, other): if typecheck(other, fmpz_mod_ctx): return fmpz_equal(self.val.n, (other).val.n) @@ -133,12 +157,6 @@ cdef class fmpz_mod(flint_scalar): raise NotImplementedError fmpz_mod_set_fmpz(self.val, (val).val, self.ctx.val) - cdef any_as_fmpz_mod(self, obj): - try: - return self.ctx(obj) - except NotImplementedError: - return NotImplemented - def is_zero(self): """ Return whether an element is equal to zero @@ -231,7 +249,7 @@ cdef class fmpz_mod(flint_scalar): if self.ctx != (a).ctx: raise ValueError("moduli must match") else: - a = self.any_as_fmpz_mod(a) + a = self.ctx.any_as_fmpz_mod(a) if a is NotImplemented: raise TypeError @@ -282,7 +300,7 @@ cdef class fmpz_mod(flint_scalar): raise TypeError("fmpz_mod cannot be ordered") if not typecheck(other, fmpz_mod): - other = self.any_as_fmpz_mod(other) + other = self.ctx.any_as_fmpz_mod(other) if typecheck(self, fmpz_mod) and typecheck(other, fmpz_mod): res = fmpz_equal(self.val, (other).val) and \ @@ -327,7 +345,7 @@ cdef class fmpz_mod(flint_scalar): return res def __add__(self, other): - other = self.any_as_fmpz_mod(other) + other = self.ctx.any_as_fmpz_mod(other) if other is NotImplemented: return NotImplemented @@ -354,13 +372,13 @@ cdef class fmpz_mod(flint_scalar): # Case when right is not fmpz_mod, try to convert to fmpz elif typecheck(left, fmpz_mod): - right = (left).any_as_fmpz_mod(right) + right = (left).ctx.any_as_fmpz_mod(right) if right is NotImplemented: return NotImplemented # Case when left is not fmpz_mod, try to convert to fmpz else: - left = (right).any_as_fmpz_mod(left) + left = (right).ctx.any_as_fmpz_mod(left) if left is NotImplemented: return NotImplemented @@ -377,7 +395,7 @@ cdef class fmpz_mod(flint_scalar): return fmpz_mod._sub_(t, s) def __mul__(self, other): - other = self.any_as_fmpz_mod(other) + other = self.ctx.any_as_fmpz_mod(other) if other is NotImplemented: return NotImplemented @@ -406,13 +424,13 @@ cdef class fmpz_mod(flint_scalar): # Case when right is not fmpz_mod, try to convert to fmpz elif typecheck(left, fmpz_mod): - right = (left).any_as_fmpz_mod(right) + right = (left).ctx.any_as_fmpz_mod(right) if right is NotImplemented: return NotImplemented # Case when left is not fmpz_mod, try to convert to fmpz else: - left = (right).any_as_fmpz_mod(left) + left = (right).ctx.any_as_fmpz_mod(left) if left is NotImplemented: return NotImplemented From 3d2984828002e38ca1b1e357a2702cd382833bb9 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 16:44:02 +0100 Subject: [PATCH 08/15] insane print debugging --- src/flint/types/fmpz_mod.pyx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index c5c53299..ae2e2a2e 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -238,12 +238,17 @@ cdef class fmpz_mod(flint_scalar): cdef fmpz_mod_discrete_log_pohlig_hellman_t L cdef bint is_prime + print(f"[DEBUGGING!]") + print("\t[DEBUG]: Starting discrete log") + # Ensure that the modulus is prime if check: is_prime = fmpz_is_probabprime(self.ctx.val.n) if not is_prime: raise ValueError("modulus must be prime") + print("\t[DEBUG]: Checked Primality") + # Then check the type of the input if typecheck(a, fmpz_mod): if self.ctx != (a).ctx: @@ -253,11 +258,18 @@ cdef class fmpz_mod(flint_scalar): if a is NotImplemented: raise TypeError + print("\t[DEBUG]: Converted types") + # Initalise the dlog data, all discrete logs are solved with an internally # chosen base `y` fmpz_mod_discrete_log_pohlig_hellman_init(L) + + print("\t[DEBUG]: Initialised L") + fmpz_mod_discrete_log_pohlig_hellman_precompute_prime(L, self.ctx.val.n) + print("\t[DEBUG]: Precomputed prime for L") + # Solve the discrete log for the chosen base and target # g = y^x_g and a = y^x_a # We want to find x such that a = g^x => @@ -266,7 +278,9 @@ cdef class fmpz_mod(flint_scalar): cdef fmpz_t x_g # TODO: should this value be stored for efficiency? fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) + print("\t[DEBUG]: Solved dlog for base") fmpz_mod_discrete_log_pohlig_hellman_run(x_a, L, (a).val) + print("\t[DEBUG]: Solved dlog for input") # If g is not a primative root, then x_g and pm1 will share # a common factor. We can use this to compute the order of @@ -279,6 +293,7 @@ cdef class fmpz_mod(flint_scalar): fmpz_divexact(g_order, L.pm1, g) else: fmpz_set(g_order, L.pm1) + print("\t[DEBUG]: Fixed order") # Finally, compute output exponent cdef fmpz x @@ -286,11 +301,18 @@ cdef class fmpz_mod(flint_scalar): # Compute (x_a / x_g) mod g_order fmpz_invmod(x.val, x_g, g_order) + print("\t[DEBUG]: Computed Inverse") + fmpz_mul(x.val, x.val, x_a) + print("\t[DEBUG]: Computed multiplication") + fmpz_type_mod(x.val, x.val, g_order) + print("\t[DEBUG]: Computed modular reduction") # Clear the dlog struct fmpz_mod_discrete_log_pohlig_hellman_clear(L) + print("\t[DEBUG]: Cleared Struct") + return x From 679d609c1ffeb7cecdda1517b67d244b9d4662ee Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 17:11:16 +0100 Subject: [PATCH 09/15] Initalise memory --- src/flint/types/fmpz_mod.pyx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index ae2e2a2e..b0da1e59 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -276,6 +276,9 @@ cdef class fmpz_mod(flint_scalar): # (y^x_a) = (y^x_g)^x => x = (x_a / x_g) mod (p-1) cdef fmpz_t x_a cdef fmpz_t x_g + fmpz_init(x_a) + fmpz_init(x_g) + # TODO: should this value be stored for efficiency? fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) print("\t[DEBUG]: Solved dlog for base") @@ -286,6 +289,9 @@ cdef class fmpz_mod(flint_scalar): # a common factor. We can use this to compute the order of # g. cdef fmpz_t g, g_order + fmpz_init(g) + fmpz_init(g_order) + fmpz_gcd(g, x_g, L.pm1) if not fmpz_is_one(g): fmpz_divexact(x_g, x_g, g) @@ -296,8 +302,7 @@ cdef class fmpz_mod(flint_scalar): print("\t[DEBUG]: Fixed order") # Finally, compute output exponent - cdef fmpz x - x = fmpz.__new__(fmpz) + cdef fmpz x = fmpz.__new__(fmpz) # Compute (x_a / x_g) mod g_order fmpz_invmod(x.val, x_g, g_order) From 1da5e63830d00af344b9997c934695b468ddac11 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Mon, 18 Sep 2023 19:04:02 +0100 Subject: [PATCH 10/15] Cleanup --- src/flint/types/fmpz_mod.pyx | 82 +++++++++++++++--------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index b0da1e59..98e98fb7 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -1,4 +1,4 @@ -from flint.flintlib.fmpz cimport ( +from flint.flintlib.fmpz cimport( fmpz_t, fmpz_one, fmpz_set, @@ -13,18 +13,16 @@ from flint.flintlib.fmpz cimport ( fmpz_is_one ) from flint.flintlib.fmpz cimport fmpz_mod as fmpz_type_mod - from flint.flintlib.fmpz_mod cimport * from flint.utils.typecheck cimport typecheck from flint.flint_base.flint_base cimport flint_scalar -from flint.types.fmpz cimport ( +from flint.types.fmpz cimport( fmpz, any_as_fmpz, fmpz_get_intlong ) - cdef class fmpz_mod_ctx: r""" Context object for creating :class:`~.fmpz_mod` initalised @@ -47,7 +45,9 @@ cdef class fmpz_mod_ctx: if not typecheck(mod, fmpz): mod = any_as_fmpz(mod) if mod is NotImplemented: - raise TypeError("Context modulus must be able to be case to an `fmpz` type") + raise TypeError( + "Context modulus must be able to be case to an `fmpz` type" + ) # Ensure modulus is positive if mod < 1: @@ -55,7 +55,7 @@ cdef class fmpz_mod_ctx: # Set the modulus fmpz_mod_ctx_set_modulus(self.val, (mod).val) - + def modulus(self): """ Return the modulus from the context as an fmpz @@ -78,20 +78,20 @@ cdef class fmpz_mod_ctx: if self != (obj).ctx: raise ValueError("moduli must match") return obj - + # Try and convert obj to fmpz if not typecheck(obj, fmpz): obj = any_as_fmpz(obj) if obj is NotImplemented: return NotImplemented - - # We have been able to cast `obj` to an `fmpz` so + + # We have been able to cast `obj` to an `fmpz` so # we create a new `fmpz_mod` and set the val cdef fmpz_mod res res = fmpz_mod.__new__(fmpz_mod) res.ctx = self fmpz_mod_set_fmpz(res.val, (obj).val, self.val) - + return res def __eq__(self, other): @@ -168,7 +168,7 @@ cdef class fmpz_mod(flint_scalar): False """ return self == 0 - + def is_one(self): """ Return whether an element is equal to one @@ -213,7 +213,9 @@ cdef class fmpz_mod(flint_scalar): res.val, one.val, self.val, self.ctx.val ) if r == 0: - raise ZeroDivisionError(f"{self} is not invertible modulo {self.ctx.modulus()}") + raise ZeroDivisionError( + f"{self} is not invertible modulo {self.ctx.modulus()}" + ) return res @@ -221,7 +223,7 @@ cdef class fmpz_mod(flint_scalar): """ Solve the discrete logarithm problem, using `self = g` as a base. Assumes a solution, :math:`a = g^x \pmod p` exists. - + NOTE: Requires that the context modulus is prime. TODO: This could instead be initalised as a class from a @@ -238,17 +240,12 @@ cdef class fmpz_mod(flint_scalar): cdef fmpz_mod_discrete_log_pohlig_hellman_t L cdef bint is_prime - print(f"[DEBUGGING!]") - print("\t[DEBUG]: Starting discrete log") - # Ensure that the modulus is prime if check: is_prime = fmpz_is_probabprime(self.ctx.val.n) if not is_prime: raise ValueError("modulus must be prime") - print("\t[DEBUG]: Checked Primality") - # Then check the type of the input if typecheck(a, fmpz_mod): if self.ctx != (a).ctx: @@ -258,22 +255,17 @@ cdef class fmpz_mod(flint_scalar): if a is NotImplemented: raise TypeError - print("\t[DEBUG]: Converted types") - - # Initalise the dlog data, all discrete logs are solved with an internally - # chosen base `y` + # Initalise the dlog data, all discrete logs are solved with an + # internally chosen base `y` fmpz_mod_discrete_log_pohlig_hellman_init(L) - - print("\t[DEBUG]: Initialised L") - - fmpz_mod_discrete_log_pohlig_hellman_precompute_prime(L, self.ctx.val.n) - - print("\t[DEBUG]: Precomputed prime for L") + fmpz_mod_discrete_log_pohlig_hellman_precompute_prime( + L, self.ctx.val.n + ) # Solve the discrete log for the chosen base and target # g = y^x_g and a = y^x_a # We want to find x such that a = g^x => - # (y^x_a) = (y^x_g)^x => x = (x_a / x_g) mod (p-1) + # (y^x_a) = (y^x_g)^x => x = (x_a / x_g) mod (p-1) cdef fmpz_t x_a cdef fmpz_t x_g fmpz_init(x_a) @@ -281,12 +273,10 @@ cdef class fmpz_mod(flint_scalar): # TODO: should this value be stored for efficiency? fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) - print("\t[DEBUG]: Solved dlog for base") fmpz_mod_discrete_log_pohlig_hellman_run(x_a, L, (a).val) - print("\t[DEBUG]: Solved dlog for input") # If g is not a primative root, then x_g and pm1 will share - # a common factor. We can use this to compute the order of + # a common factor. We can use this to compute the order of # g. cdef fmpz_t g, g_order fmpz_init(g) @@ -299,26 +289,18 @@ cdef class fmpz_mod(flint_scalar): fmpz_divexact(g_order, L.pm1, g) else: fmpz_set(g_order, L.pm1) - print("\t[DEBUG]: Fixed order") # Finally, compute output exponent cdef fmpz x = fmpz.__new__(fmpz) # Compute (x_a / x_g) mod g_order fmpz_invmod(x.val, x_g, g_order) - print("\t[DEBUG]: Computed Inverse") - fmpz_mul(x.val, x.val, x_a) - print("\t[DEBUG]: Computed multiplication") - fmpz_type_mod(x.val, x.val, g_order) - print("\t[DEBUG]: Computed modular reduction") # Clear the dlog struct fmpz_mod_discrete_log_pohlig_hellman_clear(L) - print("\t[DEBUG]: Cleared Struct") - return x def __richcmp__(self, other, int op): @@ -349,7 +331,7 @@ cdef class fmpz_mod(flint_scalar): ) def __hash__(self): - return hash((int(self))) + return hash((int(self))) def __int__(self): return fmpz_get_intlong(self.val) @@ -391,7 +373,7 @@ cdef class fmpz_mod(flint_scalar): def _sub_(left, right): cdef fmpz_mod res res = fmpz_mod.__new__(fmpz_mod) - + # Case when left and right are already fmpz_mod if typecheck(left, fmpz_mod) and typecheck(right, fmpz_mod): if not (left).ctx == (right).ctx: @@ -412,7 +394,7 @@ cdef class fmpz_mod(flint_scalar): res.ctx = (left).ctx fmpz_mod_sub( res.val, (left).val, (right).val, res.ctx.val - ) + ) return res def __sub__(s, t): @@ -443,7 +425,7 @@ cdef class fmpz_mod(flint_scalar): cdef bint check cdef fmpz_mod res res = fmpz_mod.__new__(fmpz_mod) - + # Case when left and right are already fmpz_mod if typecheck(left, fmpz_mod) and typecheck(right, fmpz_mod): if not (left).ctx == (right).ctx: @@ -460,13 +442,15 @@ cdef class fmpz_mod(flint_scalar): left = (right).ctx.any_as_fmpz_mod(left) if left is NotImplemented: return NotImplemented - + res.ctx = (left).ctx check = fmpz_mod_divides( res.val, (left).val, (right).val, res.ctx.val - ) + ) if check == 0: - raise ZeroDivisionError(f"{right} is not invertible modulo {res.ctx.modulus()}") + raise ZeroDivisionError( + f"{right} is not invertible modulo {res.ctx.modulus()}" + ) return res @@ -498,6 +482,8 @@ cdef class fmpz_mod(flint_scalar): ) if check == 0: - raise ZeroDivisionError(f"{self} is not invertible modulo {self.ctx.modulus()}") + raise ZeroDivisionError( + f"{self} is not invertible modulo {self.ctx.modulus()}" + ) return res From 88d704ef71a3e39a82a5af6ebde1b4a341430c26 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Tue, 19 Sep 2023 15:51:24 +0100 Subject: [PATCH 11/15] Modify equality check for contexts --- src/flint/types/fmpz_mod.pyx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 98e98fb7..b8d04621 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -95,6 +95,13 @@ cdef class fmpz_mod_ctx: return res def __eq__(self, other): + # Most often, we expect both `fmpz_mod` to be pointing to the + # same ctx, so this seems the fastest way to check + if self is other: + return True + + # If they're not the same object in memory, they may have the + # same modulus, which is good enough if typecheck(other, fmpz_mod_ctx): return fmpz_equal(self.val.n, (other).val.n) return False From b87397958ffe7233f7344929fe7d043fa1e59e17 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Tue, 19 Sep 2023 15:52:03 +0100 Subject: [PATCH 12/15] Add TODO --- src/flint/types/fmpz_mod.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index b8d04621..ad9ac475 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -95,6 +95,10 @@ cdef class fmpz_mod_ctx: return res def __eq__(self, other): + # TODO: + # If we could cache contexts, then we would ensure that only + # the a is b check is needed for equality. + # Most often, we expect both `fmpz_mod` to be pointing to the # same ctx, so this seems the fastest way to check if self is other: From 20e5d498d452723608a6b947827d7f6661aea493 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Tue, 19 Sep 2023 16:05:05 +0100 Subject: [PATCH 13/15] Store ph-dlog precomputation on ctx --- src/flint/types/fmpz_mod.pxd | 8 ++++++- src/flint/types/fmpz_mod.pyx | 43 ++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/flint/types/fmpz_mod.pxd b/src/flint/types/fmpz_mod.pxd index a2800a21..f3f987f5 100644 --- a/src/flint/types/fmpz_mod.pxd +++ b/src/flint/types/fmpz_mod.pxd @@ -1,11 +1,17 @@ from flint.flint_base.flint_base cimport flint_scalar from flint.flintlib.fmpz cimport fmpz_t -from flint.flintlib.fmpz_mod cimport fmpz_mod_ctx_t +from flint.flintlib.fmpz_mod cimport ( + fmpz_mod_ctx_t, + fmpz_mod_discrete_log_pohlig_hellman_t +) cdef class fmpz_mod_ctx: cdef fmpz_mod_ctx_t val + cdef fmpz_mod_discrete_log_pohlig_hellman_t L + cdef bint _dlog_precomputed cdef any_as_fmpz_mod(self, obj) + cdef _precompute_dlog_prime(self) cdef class fmpz_mod(flint_scalar): cdef fmpz_mod_ctx ctx diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index ad9ac475..e400ec60 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -36,9 +36,11 @@ cdef class fmpz_mod_ctx: cdef fmpz one = fmpz.__new__(fmpz) fmpz_one(one.val) fmpz_mod_ctx_init(self.val, one.val) + fmpz_mod_discrete_log_pohlig_hellman_init(self.L) def __dealloc__(self): fmpz_mod_ctx_clear(self.val) + fmpz_mod_discrete_log_pohlig_hellman_clear(self.L) def __init__(self, mod): # Ensure modulus is fmpz type @@ -56,6 +58,10 @@ cdef class fmpz_mod_ctx: # Set the modulus fmpz_mod_ctx_set_modulus(self.val, (mod).val) + # Store whether the pohlig-hellman precomputation has + # been performed + self._dlog_precomputed = 0 + def modulus(self): """ Return the modulus from the context as an fmpz @@ -70,6 +76,16 @@ cdef class fmpz_mod_ctx: fmpz_set(n.val, (self.val.n)) return n + cdef _precompute_dlog_prime(self): + """ + Initalise the dlog data, all discrete logs are solved with an + internally chosen base `y` + """ + fmpz_mod_discrete_log_pohlig_hellman_precompute_prime( + self.L, self.val.n + ) + self._dlog_precomputed = 1 + cdef any_as_fmpz_mod(self, obj): # If `obj` is an `fmpz_mod`, just check moduli # match @@ -98,7 +114,7 @@ cdef class fmpz_mod_ctx: # TODO: # If we could cache contexts, then we would ensure that only # the a is b check is needed for equality. - + # Most often, we expect both `fmpz_mod` to be pointing to the # same ctx, so this seems the fastest way to check if self is other: @@ -248,7 +264,6 @@ cdef class fmpz_mod(flint_scalar): >>> g.discrete_log(a) 123 """ - cdef fmpz_mod_discrete_log_pohlig_hellman_t L cdef bint is_prime # Ensure that the modulus is prime @@ -266,13 +281,6 @@ cdef class fmpz_mod(flint_scalar): if a is NotImplemented: raise TypeError - # Initalise the dlog data, all discrete logs are solved with an - # internally chosen base `y` - fmpz_mod_discrete_log_pohlig_hellman_init(L) - fmpz_mod_discrete_log_pohlig_hellman_precompute_prime( - L, self.ctx.val.n - ) - # Solve the discrete log for the chosen base and target # g = y^x_g and a = y^x_a # We want to find x such that a = g^x => @@ -282,9 +290,13 @@ cdef class fmpz_mod(flint_scalar): fmpz_init(x_a) fmpz_init(x_g) + # Ensure that self.ctx.L has performed precomputations + if not self.ctx._dlog_precomputed: + self.ctx._precompute_dlog_prime() + # TODO: should this value be stored for efficiency? - fmpz_mod_discrete_log_pohlig_hellman_run(x_g, L, self.val) - fmpz_mod_discrete_log_pohlig_hellman_run(x_a, L, (a).val) + fmpz_mod_discrete_log_pohlig_hellman_run(x_g, self.ctx.L, self.val) + fmpz_mod_discrete_log_pohlig_hellman_run(x_a, self.ctx.L, (a).val) # If g is not a primative root, then x_g and pm1 will share # a common factor. We can use this to compute the order of @@ -293,13 +305,13 @@ cdef class fmpz_mod(flint_scalar): fmpz_init(g) fmpz_init(g_order) - fmpz_gcd(g, x_g, L.pm1) + fmpz_gcd(g, x_g, self.ctx.L.pm1) if not fmpz_is_one(g): fmpz_divexact(x_g, x_g, g) fmpz_divexact(x_a, x_a, g) - fmpz_divexact(g_order, L.pm1, g) + fmpz_divexact(g_order, self.ctx.L.pm1, g) else: - fmpz_set(g_order, L.pm1) + fmpz_set(g_order, self.ctx.L.pm1) # Finally, compute output exponent cdef fmpz x = fmpz.__new__(fmpz) @@ -309,9 +321,6 @@ cdef class fmpz_mod(flint_scalar): fmpz_mul(x.val, x.val, x_a) fmpz_type_mod(x.val, x.val, g_order) - # Clear the dlog struct - fmpz_mod_discrete_log_pohlig_hellman_clear(L) - return x def __richcmp__(self, other, int op): From e866add7236a1127c8b444c3e14934ddfbd3e308 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Tue, 19 Sep 2023 16:41:54 +0100 Subject: [PATCH 14/15] Allow base dlog to also be precomputed --- src/flint/types/fmpz_mod.pxd | 3 ++- src/flint/types/fmpz_mod.pyx | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/flint/types/fmpz_mod.pxd b/src/flint/types/fmpz_mod.pxd index f3f987f5..80e77723 100644 --- a/src/flint/types/fmpz_mod.pxd +++ b/src/flint/types/fmpz_mod.pxd @@ -16,4 +16,5 @@ cdef class fmpz_mod_ctx: cdef class fmpz_mod(flint_scalar): cdef fmpz_mod_ctx ctx cdef fmpz_t val - + cdef bint base_dlog_precomputed + cdef fmpz_t x_g diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index e400ec60..388e8cd2 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -158,9 +158,11 @@ cdef class fmpz_mod(flint_scalar): def __cinit__(self): fmpz_init(self.val) + fmpz_init(self.x_g) def __dealloc__(self): fmpz_clear(self.val) + fmpz_clear(self.x_g) def __init__(self, val, ctx): if not typecheck(ctx, fmpz_mod_ctx): @@ -184,6 +186,9 @@ cdef class fmpz_mod(flint_scalar): raise NotImplementedError fmpz_mod_set_fmpz(self.val, (val).val, self.ctx.val) + # Bool to say whether x_g has been computed before + self.base_dlog_precomputed = 0 + def is_zero(self): """ Return whether an element is equal to zero @@ -294,8 +299,9 @@ cdef class fmpz_mod(flint_scalar): if not self.ctx._dlog_precomputed: self.ctx._precompute_dlog_prime() - # TODO: should this value be stored for efficiency? - fmpz_mod_discrete_log_pohlig_hellman_run(x_g, self.ctx.L, self.val) + if not self.base_dlog_precomputed: + fmpz_mod_discrete_log_pohlig_hellman_run(self.x_g, self.ctx.L, self.val) + self.base_dlog_precomputed = 1 fmpz_mod_discrete_log_pohlig_hellman_run(x_a, self.ctx.L, (a).val) # If g is not a primative root, then x_g and pm1 will share @@ -305,13 +311,15 @@ cdef class fmpz_mod(flint_scalar): fmpz_init(g) fmpz_init(g_order) - fmpz_gcd(g, x_g, self.ctx.L.pm1) + fmpz_gcd(g, self.x_g, self.ctx.L.pm1) if not fmpz_is_one(g): - fmpz_divexact(x_g, x_g, g) + fmpz_divexact(x_g, self.x_g, g) fmpz_divexact(x_a, x_a, g) fmpz_divexact(g_order, self.ctx.L.pm1, g) else: fmpz_set(g_order, self.ctx.L.pm1) + fmpz_set(x_g, self.x_g) + # Finally, compute output exponent cdef fmpz x = fmpz.__new__(fmpz) From bb25d9d2087fdcf4c71e59cd878193dc5377e839 Mon Sep 17 00:00:00 2001 From: giacomopope Date: Tue, 19 Sep 2023 17:37:53 +0100 Subject: [PATCH 15/15] Change memory management for precomutations --- src/flint/types/fmpz_mod.pxd | 6 +-- src/flint/types/fmpz_mod.pyx | 82 +++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/flint/types/fmpz_mod.pxd b/src/flint/types/fmpz_mod.pxd index 80e77723..11b68928 100644 --- a/src/flint/types/fmpz_mod.pxd +++ b/src/flint/types/fmpz_mod.pxd @@ -8,13 +8,11 @@ from flint.flintlib.fmpz_mod cimport ( cdef class fmpz_mod_ctx: cdef fmpz_mod_ctx_t val - cdef fmpz_mod_discrete_log_pohlig_hellman_t L - cdef bint _dlog_precomputed + cdef fmpz_mod_discrete_log_pohlig_hellman_t *L cdef any_as_fmpz_mod(self, obj) cdef _precompute_dlog_prime(self) cdef class fmpz_mod(flint_scalar): cdef fmpz_mod_ctx ctx cdef fmpz_t val - cdef bint base_dlog_precomputed - cdef fmpz_t x_g + cdef fmpz_t *x_g diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 388e8cd2..686d21e4 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -22,6 +22,8 @@ from flint.types.fmpz cimport( any_as_fmpz, fmpz_get_intlong ) +cimport cython +cimport libc.stdlib cdef class fmpz_mod_ctx: r""" @@ -36,11 +38,13 @@ cdef class fmpz_mod_ctx: cdef fmpz one = fmpz.__new__(fmpz) fmpz_one(one.val) fmpz_mod_ctx_init(self.val, one.val) - fmpz_mod_discrete_log_pohlig_hellman_init(self.L) + self.L = NULL + def __dealloc__(self): fmpz_mod_ctx_clear(self.val) - fmpz_mod_discrete_log_pohlig_hellman_clear(self.L) + if self.L: + fmpz_mod_discrete_log_pohlig_hellman_clear(self.L[0]) def __init__(self, mod): # Ensure modulus is fmpz type @@ -58,10 +62,6 @@ cdef class fmpz_mod_ctx: # Set the modulus fmpz_mod_ctx_set_modulus(self.val, (mod).val) - # Store whether the pohlig-hellman precomputation has - # been performed - self._dlog_precomputed = 0 - def modulus(self): """ Return the modulus from the context as an fmpz @@ -81,10 +81,13 @@ cdef class fmpz_mod_ctx: Initalise the dlog data, all discrete logs are solved with an internally chosen base `y` """ + self.L = libc.stdlib.malloc( + cython.sizeof(fmpz_mod_discrete_log_pohlig_hellman_struct) + ) + fmpz_mod_discrete_log_pohlig_hellman_init(self.L[0]) fmpz_mod_discrete_log_pohlig_hellman_precompute_prime( - self.L, self.val.n + self.L[0], self.val.n ) - self._dlog_precomputed = 1 cdef any_as_fmpz_mod(self, obj): # If `obj` is an `fmpz_mod`, just check moduli @@ -158,11 +161,12 @@ cdef class fmpz_mod(flint_scalar): def __cinit__(self): fmpz_init(self.val) - fmpz_init(self.x_g) + self.x_g = NULL def __dealloc__(self): fmpz_clear(self.val) - fmpz_clear(self.x_g) + if self.x_g: + fmpz_clear(self.x_g[0]) def __init__(self, val, ctx): if not typecheck(ctx, fmpz_mod_ctx): @@ -186,9 +190,6 @@ cdef class fmpz_mod(flint_scalar): raise NotImplementedError fmpz_mod_set_fmpz(self.val, (val).val, self.ctx.val) - # Bool to say whether x_g has been computed before - self.base_dlog_precomputed = 0 - def is_zero(self): """ Return whether an element is equal to zero @@ -258,10 +259,6 @@ cdef class fmpz_mod(flint_scalar): NOTE: Requires that the context modulus is prime. - TODO: This could instead be initalised as a class from a - given base and the precomputations could be stored to allow - faster computations for many discrete logs with the same base. - >>> F = fmpz_mod_ctx(163) >>> g = F(2) >>> x = 123 @@ -286,45 +283,54 @@ cdef class fmpz_mod(flint_scalar): if a is NotImplemented: raise TypeError + # First, Ensure that self.ctx.L has performed precomputations + # This generates a `y` which is a primative root, and used as + # the base in `fmpz_mod_discrete_log_pohlig_hellman_run` + if not self.ctx.L: + self.ctx._precompute_dlog_prime() + # Solve the discrete log for the chosen base and target # g = y^x_g and a = y^x_a # We want to find x such that a = g^x => # (y^x_a) = (y^x_g)^x => x = (x_a / x_g) mod (p-1) - cdef fmpz_t x_a - cdef fmpz_t x_g - fmpz_init(x_a) - fmpz_init(x_g) - # Ensure that self.ctx.L has performed precomputations - if not self.ctx._dlog_precomputed: - self.ctx._precompute_dlog_prime() + # For repeated calls to discrete_log, it's more efficient to + # store x_g rather than keep computing it + if not self.x_g: + self.x_g = libc.stdlib.malloc( + cython.sizeof(fmpz_t) + ) + fmpz_mod_discrete_log_pohlig_hellman_run( + self.x_g[0], self.ctx.L[0], self.val + ) - if not self.base_dlog_precomputed: - fmpz_mod_discrete_log_pohlig_hellman_run(self.x_g, self.ctx.L, self.val) - self.base_dlog_precomputed = 1 - fmpz_mod_discrete_log_pohlig_hellman_run(x_a, self.ctx.L, (a).val) + # Then we need to compute x_a which will be different for each call + cdef fmpz_t x_a + fmpz_init(x_a) + fmpz_mod_discrete_log_pohlig_hellman_run( + x_a, self.ctx.L[0], (a).val + ) # If g is not a primative root, then x_g and pm1 will share # a common factor. We can use this to compute the order of # g. - cdef fmpz_t g, g_order + cdef fmpz_t g, g_order, x_g fmpz_init(g) fmpz_init(g_order) + fmpz_init(x_g) - fmpz_gcd(g, self.x_g, self.ctx.L.pm1) + fmpz_gcd(g, self.x_g[0], self.ctx.L[0].pm1) if not fmpz_is_one(g): - fmpz_divexact(x_g, self.x_g, g) + fmpz_divexact(x_g, self.x_g[0], g) fmpz_divexact(x_a, x_a, g) - fmpz_divexact(g_order, self.ctx.L.pm1, g) + fmpz_divexact(g_order, self.ctx.L[0].pm1, g) else: - fmpz_set(g_order, self.ctx.L.pm1) - fmpz_set(x_g, self.x_g) + fmpz_set(g_order, self.ctx.L[0].pm1) + fmpz_set(x_g, self.x_g[0]) - - # Finally, compute output exponent + # Finally, compute output exponent by computing + # (x_a / x_g) mod g_order cdef fmpz x = fmpz.__new__(fmpz) - - # Compute (x_a / x_g) mod g_order fmpz_invmod(x.val, x_g, g_order) fmpz_mul(x.val, x.val, x_a) fmpz_type_mod(x.val, x.val, g_order)