2020-02-28 19:35:21 +00:00
|
|
|
import numbers
|
|
|
|
import itertools
|
2020-02-28 20:14:28 +00:00
|
|
|
import unittest
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:13:06 +00:00
|
|
|
def invert(a, b):
|
|
|
|
"Multiplicative inverse of a mod b. a,b must be coprime."
|
|
|
|
A = (a, 1, 0)
|
|
|
|
B = (b, 0, 1)
|
|
|
|
while B[0]:
|
|
|
|
q = A[0] // B[0]
|
|
|
|
A, B = B, tuple(Ai - q*Bi for Ai, Bi in zip(A, B))
|
|
|
|
assert abs(A[0]) == 1
|
|
|
|
return A[1]*A[0] % b
|
|
|
|
|
2020-02-28 19:35:21 +00:00
|
|
|
def jacobi(n,m):
|
|
|
|
"""Compute the Jacobi symbol.
|
|
|
|
|
|
|
|
The special case of this when m is prime is the Legendre symbol,
|
|
|
|
which is 0 if n is congruent to 0 mod m; 1 if n is congruent to a
|
|
|
|
non-zero square number mod m; -1 if n is not congruent to any
|
|
|
|
square mod m.
|
|
|
|
|
|
|
|
"""
|
|
|
|
assert m & 1
|
|
|
|
acc = 1
|
|
|
|
while True:
|
|
|
|
n %= m
|
|
|
|
if n == 0:
|
|
|
|
return 0
|
|
|
|
while not (n & 1):
|
|
|
|
n >>= 1
|
|
|
|
if (m & 7) not in {1,7}:
|
|
|
|
acc *= -1
|
|
|
|
if n == 1:
|
|
|
|
return acc
|
|
|
|
if (n & 3) == 3 and (m & 3) == 3:
|
|
|
|
acc *= -1
|
|
|
|
n, m = m, n
|
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
class CyclicGroupRootFinder(object):
|
|
|
|
"""Class for finding rth roots in a cyclic group. r must be prime."""
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
# Basic strategy:
|
|
|
|
#
|
|
|
|
# We write |G| = r^k u, with u coprime to r. This gives us a
|
|
|
|
# nested sequence of subgroups G = G_0 > G_1 > ... > G_k, each
|
|
|
|
# with index 3 in its predecessor. G_0 is the whole group, and the
|
|
|
|
# innermost G_k has order u.
|
|
|
|
#
|
|
|
|
# Within G_k, you can take an rth root by raising an element to
|
|
|
|
# the power of (r^{-1} mod u). If k=0 (so G = G_0 = G_k) then
|
|
|
|
# that's all that's needed: every element has a unique rth root.
|
|
|
|
# But if k>0, then things go differently.
|
|
|
|
#
|
|
|
|
# Define the 'rank' of an element g as the highest i such that
|
|
|
|
# g \in G_i. Elements of rank 0 are the non-rth-powers: they don't
|
|
|
|
# even _have_ an rth root. Elements of rank k are the easy ones to
|
|
|
|
# take rth roots of, as above.
|
|
|
|
#
|
|
|
|
# In between, you can follow an inductive process, as long as you
|
|
|
|
# know one element z of index 0. Suppose we're trying to take the
|
|
|
|
# rth root of some g with index i. Repeatedly multiply g by
|
|
|
|
# z^{r^i} until its index increases; then take the root of that
|
|
|
|
# (recursively), and divide off z^{r^{i-1}} once you're done.
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
def __init__(self, r, order):
|
|
|
|
self.order = order # order of G
|
|
|
|
self.r = r
|
|
|
|
self.k = next(k for k in itertools.count()
|
|
|
|
if self.order % (r**(k+1)) != 0)
|
|
|
|
self.u = self.order // (r**self.k)
|
|
|
|
self.z = next(z for z in self.iter_elements()
|
|
|
|
if self.index(z) == 0)
|
|
|
|
self.zinv = self.inverse(self.z)
|
|
|
|
self.root_power = invert(self.r, self.u) if self.u > 1 else 0
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
self.roots_of_unity = {self.identity()}
|
|
|
|
if self.k > 0:
|
|
|
|
exponent = self.order // self.r
|
|
|
|
for z in self.iter_elements():
|
|
|
|
root_of_unity = self.pow(z, exponent)
|
|
|
|
if root_of_unity not in self.roots_of_unity:
|
|
|
|
self.roots_of_unity.add(root_of_unity)
|
|
|
|
if len(self.roots_of_unity) == r:
|
|
|
|
break
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
def index(self, g):
|
|
|
|
h = self.pow(g, self.u)
|
|
|
|
for i in range(self.k+1):
|
|
|
|
if h == self.identity():
|
|
|
|
return self.k - i
|
|
|
|
h = self.pow(h, self.r)
|
|
|
|
assert False, ("Not a cyclic group! Raising {} to u r^k should give e."
|
|
|
|
.format(g))
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
def all_roots(self, g):
|
|
|
|
try:
|
|
|
|
r = self.root(g)
|
|
|
|
except ValueError:
|
|
|
|
return []
|
|
|
|
return {r * rou for rou in self.roots_of_unity}
|
2020-02-28 19:35:21 +00:00
|
|
|
|
2020-02-28 20:14:28 +00:00
|
|
|
def root(self, g):
|
|
|
|
i = self.index(g)
|
|
|
|
if i == 0 and self.k > 0:
|
|
|
|
raise ValueError("{} has no {}th root".format(g, self.r))
|
|
|
|
out = self.root_recurse(g, i)
|
|
|
|
assert self.pow(out, self.r) == g
|
|
|
|
return out
|
|
|
|
|
|
|
|
def root_recurse(self, g, i):
|
|
|
|
if i == self.k:
|
|
|
|
return self.pow(g, self.root_power)
|
|
|
|
z_in = self.pow(self.z, self.r**i)
|
|
|
|
z_out = self.pow(self.zinv, self.r**(i-1))
|
|
|
|
adjust = self.identity()
|
|
|
|
while True:
|
|
|
|
g = self.mul(g, z_in)
|
|
|
|
adjust = self.mul(adjust, z_out)
|
|
|
|
i2 = self.index(g)
|
|
|
|
if i2 > i:
|
|
|
|
return self.mul(self.root_recurse(g, i2), adjust)
|
|
|
|
|
|
|
|
class AdditiveGroupRootFinder(CyclicGroupRootFinder):
|
|
|
|
"""Trivial test subclass for CyclicGroupRootFinder.
|
|
|
|
|
|
|
|
Represents a cyclic group of any order additively, as the integers
|
|
|
|
mod n under addition. This makes root-finding trivial without
|
|
|
|
having to use the complicated algorithm above, and therefore it's
|
|
|
|
a good way to test the complicated algorithm under conditions
|
|
|
|
where the right answers are obvious."""
|
|
|
|
|
|
|
|
def __init__(self, r, order):
|
|
|
|
super().__init__(r, order)
|
|
|
|
|
|
|
|
def mul(self, x, y):
|
|
|
|
return (x + y) % self.order
|
|
|
|
def pow(self, x, n):
|
|
|
|
return (x * n) % self.order
|
|
|
|
def inverse(self, x):
|
|
|
|
return (-x) % self.order
|
|
|
|
def identity(self):
|
|
|
|
return 0
|
|
|
|
def iter_elements(self):
|
|
|
|
return range(self.order)
|
|
|
|
|
|
|
|
class TestCyclicGroupRootFinder(unittest.TestCase):
|
|
|
|
def testRootFinding(self):
|
|
|
|
for order in 10, 11, 12, 18:
|
|
|
|
grf = AdditiveGroupRootFinder(3, order)
|
|
|
|
for i in range(order):
|
|
|
|
try:
|
|
|
|
r = grf.root(i)
|
|
|
|
except ValueError:
|
|
|
|
r = None
|
|
|
|
|
|
|
|
if order % 3 == 0 and i % 3 != 0:
|
|
|
|
self.assertEqual(r, None)
|
|
|
|
else:
|
|
|
|
self.assertEqual(r*3 % order, i)
|
|
|
|
|
|
|
|
class RootModP(CyclicGroupRootFinder):
|
|
|
|
"""The live class that can take rth roots mod a prime."""
|
|
|
|
|
|
|
|
def __init__(self, r, p):
|
|
|
|
self.modulus = p
|
|
|
|
super().__init__(r, p-1)
|
|
|
|
|
|
|
|
def mul(self, x, y):
|
|
|
|
return (x * y) % self.modulus
|
|
|
|
def pow(self, x, n):
|
|
|
|
return pow(x, n, self.modulus)
|
|
|
|
def inverse(self, x):
|
|
|
|
return invert(x, self.modulus)
|
|
|
|
def identity(self):
|
|
|
|
return 1
|
|
|
|
def iter_elements(self):
|
|
|
|
return range(1, self.modulus)
|
|
|
|
|
|
|
|
def root(self, g):
|
|
|
|
return 0 if g == 0 else super().root(g)
|
2020-02-28 19:35:21 +00:00
|
|
|
|
|
|
|
class ModP(object):
|
|
|
|
"""Class that represents integers mod p as a field.
|
|
|
|
|
|
|
|
All the usual arithmetic operations are supported directly,
|
|
|
|
including division, so you can write formulas in a natural way
|
|
|
|
without having to keep saying '% p' everywhere or call a
|
|
|
|
cumbersome modular_inverse() function.
|
|
|
|
|
|
|
|
"""
|
|
|
|
def __init__(self, p, n=0):
|
|
|
|
self.p = p
|
|
|
|
if isinstance(n, type(self)):
|
|
|
|
self.check(n)
|
|
|
|
n = n.n
|
|
|
|
self.n = n % p
|
|
|
|
def check(self, other):
|
|
|
|
assert isinstance(other, type(self))
|
|
|
|
assert isinstance(self, type(other))
|
|
|
|
assert self.p == other.p
|
|
|
|
def coerce_to(self, other):
|
|
|
|
if not isinstance(other, type(self)):
|
|
|
|
other = type(self)(self.p, other)
|
|
|
|
else:
|
|
|
|
self.check(other)
|
|
|
|
return other
|
|
|
|
def __int__(self):
|
|
|
|
return self.n
|
|
|
|
def __add__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return type(self)(self.p, (self.n + rhs.n) % self.p)
|
|
|
|
def __neg__(self):
|
|
|
|
return type(self)(self.p, -self.n % self.p)
|
|
|
|
def __radd__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return type(self)(self.p, (self.n + rhs.n) % self.p)
|
|
|
|
def __sub__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return type(self)(self.p, (self.n - rhs.n) % self.p)
|
|
|
|
def __rsub__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return type(self)(self.p, (rhs.n - self.n) % self.p)
|
|
|
|
def __mul__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return type(self)(self.p, (self.n * rhs.n) % self.p)
|
|
|
|
def __rmul__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return type(self)(self.p, (self.n * rhs.n) % self.p)
|
|
|
|
def __div__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
2020-02-28 20:13:06 +00:00
|
|
|
return type(self)(self.p, (self.n * invert(rhs.n, self.p)) % self.p)
|
2020-02-28 19:35:21 +00:00
|
|
|
def __rdiv__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
2020-02-28 20:13:06 +00:00
|
|
|
return type(self)(self.p, (rhs.n * invert(self.n, self.p)) % self.p)
|
2020-02-28 19:35:21 +00:00
|
|
|
def __truediv__(self, rhs): return self.__div__(rhs)
|
|
|
|
def __rtruediv__(self, rhs): return self.__rdiv__(rhs)
|
|
|
|
def __pow__(self, exponent):
|
|
|
|
assert exponent >= 0
|
|
|
|
n, b_to_n = 1, self
|
|
|
|
total = type(self)(self.p, 1)
|
|
|
|
while True:
|
|
|
|
if exponent & n:
|
|
|
|
exponent -= n
|
|
|
|
total *= b_to_n
|
|
|
|
n *= 2
|
|
|
|
if n > exponent:
|
|
|
|
break
|
|
|
|
b_to_n *= b_to_n
|
|
|
|
return total
|
|
|
|
def __cmp__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return cmp(self.n, rhs.n)
|
|
|
|
def __eq__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return self.n == rhs.n
|
|
|
|
def __ne__(self, rhs):
|
|
|
|
rhs = self.coerce_to(rhs)
|
|
|
|
return self.n != rhs.n
|
|
|
|
def __lt__(self, rhs):
|
|
|
|
raise ValueError("Elements of a modular ring have no ordering")
|
|
|
|
def __le__(self, rhs):
|
|
|
|
raise ValueError("Elements of a modular ring have no ordering")
|
|
|
|
def __gt__(self, rhs):
|
|
|
|
raise ValueError("Elements of a modular ring have no ordering")
|
|
|
|
def __ge__(self, rhs):
|
|
|
|
raise ValueError("Elements of a modular ring have no ordering")
|
|
|
|
def __str__(self):
|
|
|
|
return "0x{:x}".format(self.n)
|
|
|
|
def __repr__(self):
|
|
|
|
return "{}(0x{:x},0x{:x})".format(type(self).__name__, self.p, self.n)
|
2020-02-28 20:13:46 +00:00
|
|
|
def __hash__(self):
|
|
|
|
return hash((type(self).__name__, self.p, self.n))
|
2020-02-28 20:14:28 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
|
|
|
if sys.argv[1:] == ["--test"]:
|
|
|
|
sys.argv[1:2] = []
|
|
|
|
unittest.main()
|