kryptools 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kryptools/Zmod.py +115 -0
- kryptools/__init__.py +13 -0
- kryptools/dlp.py +76 -0
- kryptools/dlp_bsgs.py +29 -0
- kryptools/dlp_ic.py +136 -0
- kryptools/dlp_qs.py +297 -0
- kryptools/dlp_rho.py +62 -0
- kryptools/ec.py +383 -0
- kryptools/factor.py +138 -0
- kryptools/factor_ecm.py +140 -0
- kryptools/factor_pm1.py +58 -0
- kryptools/factor_qs.py +174 -0
- kryptools/la.py +223 -0
- kryptools/lat.py +168 -0
- kryptools/nt.py +271 -0
- kryptools/poly.py +232 -0
- kryptools/primes.py +123 -0
- kryptools-0.1.dist-info/LICENSE +21 -0
- kryptools-0.1.dist-info/METADATA +35 -0
- kryptools-0.1.dist-info/RECORD +22 -0
- kryptools-0.1.dist-info/WHEEL +5 -0
- kryptools-0.1.dist-info/top_level.txt +1 -0
kryptools/lat.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lattice tools
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import prod
|
|
6
|
+
from fractions import Fraction
|
|
7
|
+
from .la import Matrix, zeros, eye
|
|
8
|
+
|
|
9
|
+
def babai_round_cvp(x: Matrix, U: Matrix) -> Matrix:
|
|
10
|
+
"Babai's rounding algorithm for solving the CVP."
|
|
11
|
+
s = U.inv() * x
|
|
12
|
+
k = s.map(round)
|
|
13
|
+
return U * k
|
|
14
|
+
|
|
15
|
+
def hermite_nf(M: Matrix) -> Matrix:
|
|
16
|
+
"Compute the Hermite normal form of a matrix M."
|
|
17
|
+
n, m = M.cols, M.rows
|
|
18
|
+
H = M[:, :]
|
|
19
|
+
j = n - 1
|
|
20
|
+
for i in reversed(range(m)):
|
|
21
|
+
j0 = j
|
|
22
|
+
minimum = abs(H[i, j]) # search for the pivot in the present row
|
|
23
|
+
for jj in range(j):
|
|
24
|
+
tmp = abs(H[i, jj])
|
|
25
|
+
if tmp > 0 and (tmp < minimum or minimum == 0):
|
|
26
|
+
minimum = tmp
|
|
27
|
+
j0 = jj
|
|
28
|
+
if minimum == 0:
|
|
29
|
+
continue # all entries are zero
|
|
30
|
+
if j0 < j:
|
|
31
|
+
H[:, j], H[:, j0] = (
|
|
32
|
+
H[:, j0],
|
|
33
|
+
H[:, j],
|
|
34
|
+
) # swap columns, to move the pivot in place
|
|
35
|
+
if H[i, j] < 0:
|
|
36
|
+
H[:, j] *= -1 # make the pivot positive
|
|
37
|
+
jj = j - 1
|
|
38
|
+
while jj >= 0: # make the row entries left to the pivot zero
|
|
39
|
+
tmp = H[i, jj] // H[i, j]
|
|
40
|
+
H[:, jj] -= tmp * H[:, j]
|
|
41
|
+
if H[i, jj]:
|
|
42
|
+
H[:, j], H[:, jj] = H[:, jj], H[:, j] # swap columns
|
|
43
|
+
else:
|
|
44
|
+
jj -= 1
|
|
45
|
+
for jj in range(j + 1, n): # reduce the row entries right to the pivot
|
|
46
|
+
tmp = H[i, jj] // H[i, j]
|
|
47
|
+
H[:, jj] -= tmp * H[:, j]
|
|
48
|
+
#print(H)
|
|
49
|
+
j -= 1
|
|
50
|
+
while H.cols> 1 and all(not H[i, 0] for i in range(m)): # remove zero columns
|
|
51
|
+
H = H[:,1:]
|
|
52
|
+
return H
|
|
53
|
+
|
|
54
|
+
def norm2(v: Matrix) -> float:
|
|
55
|
+
"Square of the Euclidean norm of a vector v."
|
|
56
|
+
return sum(map(lambda x: x * x, v))
|
|
57
|
+
|
|
58
|
+
def gram_schmidt(U: Matrix) -> (Matrix, Matrix):
|
|
59
|
+
"Compute the Gram-Schmidt orthogonalization of the column vectors of a matrix M."
|
|
60
|
+
M = eye(U.cols, U.rows)
|
|
61
|
+
Us = U[:, :]
|
|
62
|
+
for j in range(1, U.rows):
|
|
63
|
+
tmp = U[:, j]
|
|
64
|
+
for i in range(j):
|
|
65
|
+
M[i, j] = U[:, j].dot(Us[:, i]) / norm2(Us[:, i])
|
|
66
|
+
tmp -= M[i, j] * Us[:, i]
|
|
67
|
+
Us[:, j] = tmp
|
|
68
|
+
return Us, M
|
|
69
|
+
|
|
70
|
+
def gram_det(U: Matrix) -> float:
|
|
71
|
+
Us = gram_schmidt(U)[0]
|
|
72
|
+
return prod([Us[:, i].norm() for i in range(U.rows)])
|
|
73
|
+
|
|
74
|
+
def hadamard_ratio(M: Matrix) -> float:
|
|
75
|
+
m = M.rows
|
|
76
|
+
return (gram_det(M) / prod([M[:, i].norm() for i in range(m)])) ** (1 / m)
|
|
77
|
+
|
|
78
|
+
def babai_plane_cvp(x: Matrix, U: Matrix) -> Matrix:
|
|
79
|
+
"Babai's closest plane algorithm for solving the CVP."
|
|
80
|
+
Us = gram_schmidt(U)[0]
|
|
81
|
+
y = x
|
|
82
|
+
for k in range(U.cols - 1, -1, -1):
|
|
83
|
+
y = y - round(y.dot(Us[:, k]) / norm2(Us[:, k])) * U[:, k]
|
|
84
|
+
return (x - y).applyfunc(round)
|
|
85
|
+
|
|
86
|
+
def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
87
|
+
"lll algorithm for lattice reduction"
|
|
88
|
+
|
|
89
|
+
assert 0 < delta <= 1, f"LLL reqires 0 < delta={delta} <= 1"
|
|
90
|
+
j = 1
|
|
91
|
+
U = V[:, :]
|
|
92
|
+
Us = U[:, :]
|
|
93
|
+
Us.map(Fraction)
|
|
94
|
+
M = zeros(U.cols, U.rows)
|
|
95
|
+
M.map(Fraction)
|
|
96
|
+
M[0, 0] = norm2(Us[:, 0]) # we store the squared norms on the diagonal
|
|
97
|
+
for l in range(1, U.rows): # Gram-Schmidt decomposition
|
|
98
|
+
tmp = U[:, l]
|
|
99
|
+
for i in range(l):
|
|
100
|
+
M[i, l] = U[:, l].dot(Us[:, i]) / M[i, i]
|
|
101
|
+
tmp -= M[i, l] * Us[:, i]
|
|
102
|
+
Us[:, l] = tmp
|
|
103
|
+
M[l, l] = norm2(Us[:, l])
|
|
104
|
+
|
|
105
|
+
while j < U.rows:
|
|
106
|
+
for i in range(j - 1, -1, -1): # reduce the weights of the basis vectors
|
|
107
|
+
r = round(M[i, j])
|
|
108
|
+
if r:
|
|
109
|
+
U[:, j] -= r * U[:, i]
|
|
110
|
+
for k in range(j):
|
|
111
|
+
if k == i:
|
|
112
|
+
M[k, j] -= r
|
|
113
|
+
else:
|
|
114
|
+
M[k, j] -= r * M[k, i]
|
|
115
|
+
|
|
116
|
+
newM11 = M[j, j] + M[j - 1, j] ** 2 * M[j - 1, j - 1]
|
|
117
|
+
if (delta * M[j - 1, j - 1] <= newM11): # Lovasz condition
|
|
118
|
+
j += 1
|
|
119
|
+
continue # nothing to be done
|
|
120
|
+
# else swap vectors
|
|
121
|
+
U[:, j], U[:, j - 1] = U[:, j - 1], U[:, j]
|
|
122
|
+
# update the Gram-Schmidt decomposition
|
|
123
|
+
oldM11 = M[j - 1, j - 1]
|
|
124
|
+
oldM10 = M[j - 1, j]
|
|
125
|
+
oldM00 = M[j, j]
|
|
126
|
+
oldUs = Us[:, j - 1]
|
|
127
|
+
Us[:, j - 1] = Us[:, j] + M[j - 1, j] * Us[:, j - 1]
|
|
128
|
+
M[j - 1, j - 1] = newM11
|
|
129
|
+
M[j - 1, j] *= oldM11 / M[j - 1, j - 1]
|
|
130
|
+
Us[:, j] = oldUs - M[j - 1, j] * Us[:, j - 1]
|
|
131
|
+
M[j, j] = oldM11 - M[j - 1, j] ** 2 * M[j - 1, j - 1]
|
|
132
|
+
for l in range(j - 1):
|
|
133
|
+
M[l, j], M[l, j - 1] = M[l, j - 1], M[l, j]
|
|
134
|
+
tmp1 = oldM00 / M[j - 1, j - 1]
|
|
135
|
+
tmp2 = oldM10 * oldM11 / M[j - 1, j - 1]
|
|
136
|
+
for l in range(j + 1, U.rows):
|
|
137
|
+
M[j - 1, l], M[j, l] = (
|
|
138
|
+
tmp1 * M[j, l] + tmp2 * M[j - 1, l],
|
|
139
|
+
M[j - 1, l] - oldM10 * M[j, l],
|
|
140
|
+
)
|
|
141
|
+
j = max(j - 1, 1) # redo the last step
|
|
142
|
+
|
|
143
|
+
if sort: # sort the vectors according to their norm
|
|
144
|
+
tmp = [U[:, j] for j in range(U.rows)]
|
|
145
|
+
tmp.sort(key=norm2)
|
|
146
|
+
for j in range(U.rows):
|
|
147
|
+
U[:, j] = tmp[j]
|
|
148
|
+
return U
|
|
149
|
+
|
|
150
|
+
from random import choice, sample
|
|
151
|
+
|
|
152
|
+
def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) -> Matrix:
|
|
153
|
+
"Create a random unimodular matrix of dimension n."
|
|
154
|
+
W = Matrix.zeros(n, n)
|
|
155
|
+
for i in range(n):
|
|
156
|
+
for j in range(i, n):
|
|
157
|
+
W[i, j] = choice([-1, 1])
|
|
158
|
+
W = W[sample(range(n), n), sample(range(n), n)]
|
|
159
|
+
for _ in range(iterations):
|
|
160
|
+
i, j = sample(range(n), 2)
|
|
161
|
+
tmp = W[i, :] + choice([-1, 1]) * W[j, :]
|
|
162
|
+
if max([abs(x) for x in tmp]) <= max_val:
|
|
163
|
+
W[i, :] = tmp
|
|
164
|
+
i, j = sample(range(n), 2)
|
|
165
|
+
tmp = W[:, i] + choice([-1, 1]) * W[:, j]
|
|
166
|
+
if max([abs(x) for x in tmp]) <= max_val:
|
|
167
|
+
W[:, i] = tmp
|
|
168
|
+
return W
|
kryptools/nt.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Number theory tools:
|
|
3
|
+
lcm(a, b) least common mutiple of a and b
|
|
4
|
+
egcd(a,b) extended Euclidean agorithm
|
|
5
|
+
crt([a1, a2, ...],[m1, m2, ...]) Chinese Remainder Theorem
|
|
6
|
+
cf(Fraction(m,n)) continued fraction expansions
|
|
7
|
+
convergents() convergents of a continued fraction
|
|
8
|
+
sqrt_mod(n, p) square root of n modulo a prime p
|
|
9
|
+
order(a, n) oder of a in the multiplicative group Z_n^*
|
|
10
|
+
"""
|
|
11
|
+
from math import gcd, prod
|
|
12
|
+
from fractions import Fraction
|
|
13
|
+
from .factor import factorint
|
|
14
|
+
|
|
15
|
+
# Euclid and friends
|
|
16
|
+
|
|
17
|
+
def lcm(a: int, b: int) -> int:
|
|
18
|
+
"""Compute the least common multiple of a and b."""
|
|
19
|
+
if b == 0:
|
|
20
|
+
return 0
|
|
21
|
+
if bool(a > 0) != bool(b > 0):
|
|
22
|
+
a = -a
|
|
23
|
+
return (a // gcd(a, b)) * b
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def egcd(a: int, b: int) -> (int, int, int):
|
|
27
|
+
"""Perform the extended Euclidean agorithm. Returns gcd, x, y such that a x + b y = gcd."""
|
|
28
|
+
r0, r1 = a, b
|
|
29
|
+
x0, x1, y0, y1 = 1, 0, 0, 1
|
|
30
|
+
while r1 != 0:
|
|
31
|
+
q, r = divmod(r0, r1)
|
|
32
|
+
r0, r1 = r1, r
|
|
33
|
+
x0, x1 = x1, x0 - q * x1
|
|
34
|
+
y0, y1 = y1, y0 - q * y1
|
|
35
|
+
return r0, x0, y0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Chinese remainder theorem
|
|
39
|
+
|
|
40
|
+
def crt(a: list, m: list) -> int:
|
|
41
|
+
"""Solve given linear congruences x[j] % m[j] == a[j] using the Chinese Remainder Theorem."""
|
|
42
|
+
l = len(a)
|
|
43
|
+
assert len(m) == l, "The lists of numbers and modules must have equal length."
|
|
44
|
+
M = prod(m)
|
|
45
|
+
Mi = [M // m[i] for i in range(l)]
|
|
46
|
+
MNi = [Mi[i] * pow(Mi[i], -1, m[i]) % M for i in range(l)]
|
|
47
|
+
return sum([a[i] * MNi[i] % M for i in range(l)]) % M
|
|
48
|
+
|
|
49
|
+
# Continued fractions
|
|
50
|
+
|
|
51
|
+
# class Fraction:
|
|
52
|
+
# "Rationl number"
|
|
53
|
+
#
|
|
54
|
+
# def __init__(self, numerator: int, denominator: int = 1):
|
|
55
|
+
# assert denominator, "Denominator must be nozero."
|
|
56
|
+
# tmp = gcd(denominator, numerator)
|
|
57
|
+
# if tmp != 1:
|
|
58
|
+
# denominator //= tmp
|
|
59
|
+
# numerator //= tmp
|
|
60
|
+
# self.numerator = numerator
|
|
61
|
+
# self.denominator = denominator
|
|
62
|
+
#
|
|
63
|
+
# def __repr__(self):
|
|
64
|
+
# if self.denominator == 1:
|
|
65
|
+
# return str(self.numerator)
|
|
66
|
+
# else:
|
|
67
|
+
# return str(self.numerator) + "/" + str(self.denominator)
|
|
68
|
+
#
|
|
69
|
+
# def __eq__(self, other):
|
|
70
|
+
# if not isinstance(other, self.__class__):
|
|
71
|
+
# return False
|
|
72
|
+
# return self.denominator == other.denominator and self.numerator == other.numerator
|
|
73
|
+
#
|
|
74
|
+
# def __bool__(self):
|
|
75
|
+
# return self.numerator != 0
|
|
76
|
+
#
|
|
77
|
+
# def __add__(self, other: "Fraction") -> "Fraction":
|
|
78
|
+
# if isinstance(other, self.__class__):
|
|
79
|
+
# return Rational(self.numerator * other.denominator + other.numerator * self.denominator, self.denominator * other.denominator)
|
|
80
|
+
# if isinstance(other, int):
|
|
81
|
+
# return Rational(self.numerator + other * self.denominator, self.denominator)
|
|
82
|
+
# raise ValueError(f"Cannot add {self} and {other}.")
|
|
83
|
+
#
|
|
84
|
+
# def __neg__(self) -> "Fraction":
|
|
85
|
+
# return Rational(- self.numerator, self.denominator)
|
|
86
|
+
#
|
|
87
|
+
# def __sub__(self, other: "Fraction") -> "Fraction":
|
|
88
|
+
# if isinstance(other, self.__class__):
|
|
89
|
+
# return Rational(self.numerator * other.denominator - other.numerator * self.denominator, self.denominator * other.denominator)
|
|
90
|
+
# if isinstance(other, int):
|
|
91
|
+
# return Rational(self.numerator - other * self.denominator, self.denominator)
|
|
92
|
+
# raise ValueError(f"Cannot subtract {self} and {other}.")
|
|
93
|
+
#
|
|
94
|
+
# def __mul__(self, other: "Fraction") -> "Fraction":
|
|
95
|
+
# if isinstance(other, self.__class__):
|
|
96
|
+
# return Rational(self.numerator * other.numerator, self.denominator * other.denominator)
|
|
97
|
+
# if isinstance(other, int):
|
|
98
|
+
# return Rational(self.numerator * other, self.denominator)
|
|
99
|
+
# raise ValueError(f"Cannot multiply {self} and {other}.")
|
|
100
|
+
#
|
|
101
|
+
# def __rmul__(self, other: int) -> "Fraction":
|
|
102
|
+
# return Rational(self.denominator * other, self.numerator)
|
|
103
|
+
#
|
|
104
|
+
# def __truediv__(self, other: "Fraction") -> "Fraction":
|
|
105
|
+
# if isinstance(other, self.__class__):
|
|
106
|
+
# return Rational(self.numerator * other.denominator, self.denominator * other.numerator)
|
|
107
|
+
# if isinstance(other, int):
|
|
108
|
+
# return Rational(self.numerator, self.denominator * other)
|
|
109
|
+
# raise ValueError(f"Cannot divide {self} and {other}.")
|
|
110
|
+
#
|
|
111
|
+
# def __pow__(self, scalar: int) -> "Fraction":
|
|
112
|
+
# return Rational(self.numerator**scalar, self.denominator**scalar)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def fraction_repr(self):
|
|
116
|
+
if self.denominator == 1:
|
|
117
|
+
return str(self.numerator)
|
|
118
|
+
else:
|
|
119
|
+
return str(self.numerator) + "/" + str(self.denominator)
|
|
120
|
+
|
|
121
|
+
Fraction.__repr__ = fraction_repr
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def cf(x: Fraction) -> list[int]:
|
|
125
|
+
"Compute the continued fraction expansion of the rational number m/n."
|
|
126
|
+
res = []
|
|
127
|
+
m = x.numerator
|
|
128
|
+
n = x.denominator
|
|
129
|
+
while True:
|
|
130
|
+
d, m = divmod(m, n)
|
|
131
|
+
res.append(d)
|
|
132
|
+
if not m:
|
|
133
|
+
return res
|
|
134
|
+
m, n = n, m
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def convergents(cont_frac: list[int]) -> float:
|
|
138
|
+
"Compute the convergents of a continued fraction expansion."
|
|
139
|
+
res = []
|
|
140
|
+
|
|
141
|
+
def recursion(x: int, m: int, mm: int) -> (int, int):
|
|
142
|
+
return x * m + mm, m
|
|
143
|
+
|
|
144
|
+
m, mm = 1, 0
|
|
145
|
+
n, nn = 0, 1
|
|
146
|
+
for x in cont_frac:
|
|
147
|
+
m, mm = recursion(x, m, mm)
|
|
148
|
+
n, nn = recursion(x, n, nn)
|
|
149
|
+
res.append(Fraction(m, n))
|
|
150
|
+
return res
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Legendre symbol
|
|
154
|
+
|
|
155
|
+
def legendre_symbol(a: int, p: int) -> int:
|
|
156
|
+
"""Compute the Legendre symbol of a with respect to the prime p."""
|
|
157
|
+
a = a % p
|
|
158
|
+
if a == 0:
|
|
159
|
+
return 0
|
|
160
|
+
if pow(a, (p - 1) // 2, p) == 1:
|
|
161
|
+
return 1
|
|
162
|
+
return -1
|
|
163
|
+
|
|
164
|
+
def jacobi_symbol(a: int, n: int) -> int:
|
|
165
|
+
"""Compute the Jacobi symbol of a with respect to the integer n."""
|
|
166
|
+
# Crandall/Pomerance Algorithm 2.3.5
|
|
167
|
+
a = a % n
|
|
168
|
+
t = 1
|
|
169
|
+
while a:
|
|
170
|
+
while a % 2 == 0:
|
|
171
|
+
a //= 2
|
|
172
|
+
tmp = n % 8
|
|
173
|
+
if tmp == 3 or tmp == 5:
|
|
174
|
+
t *= -1
|
|
175
|
+
a, n = n, a
|
|
176
|
+
if a % 4 == 3 and n % 4 == 3:
|
|
177
|
+
t *= -1
|
|
178
|
+
a %= n
|
|
179
|
+
if n == 1:
|
|
180
|
+
return t
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
def sqrt_mod(a: int, p: int) -> list:
|
|
184
|
+
"Compute a square root of a modulo p unsing Cipolla's algorithm."
|
|
185
|
+
a %= p
|
|
186
|
+
if a == 0 or a == 1:
|
|
187
|
+
return a
|
|
188
|
+
if pow(a, (p - 1) // 2, p) != 1: # Legendre symbol must be equal one
|
|
189
|
+
return None
|
|
190
|
+
if p % 4 == 3: # easy case
|
|
191
|
+
return pow(a, (p + 1) // 4, p)
|
|
192
|
+
# Cipolla's algorithm
|
|
193
|
+
for c in range(1, p): # find a field extension
|
|
194
|
+
tmp = (c * c - a) % p
|
|
195
|
+
if tmp == 0:
|
|
196
|
+
return c
|
|
197
|
+
if pow(tmp, (p - 1) // 2, p) == p - 1:
|
|
198
|
+
break
|
|
199
|
+
r = (c * c - a) % p
|
|
200
|
+
i = (p + 1) // 2
|
|
201
|
+
x1, x2 = c, 1 # compute x^i in Z_p(sqrt(r))
|
|
202
|
+
y1, y2 = 1, 0
|
|
203
|
+
while i > 0:
|
|
204
|
+
if i & 1: # if i is odd, multiply with x
|
|
205
|
+
y1, y2 = (x1 * y1 + x2 * y2 * r) % p, (x1 * y2 + x2 * y1) % p
|
|
206
|
+
x1, x2 = (pow(x1, 2, p) + pow(x2, 2, p) * r) % p, 2 * (x1 * x2) % p # now square
|
|
207
|
+
i = i >> 1 # i= i/2
|
|
208
|
+
return y1
|
|
209
|
+
|
|
210
|
+
# Euler phi and Carmichael function
|
|
211
|
+
|
|
212
|
+
from math import prod
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def euler_phi(n: int) -> int:
|
|
216
|
+
"""Euler's phi function of n."""
|
|
217
|
+
k = factorint(n)
|
|
218
|
+
return prod([(p - 1) * p ** (k[p] - 1) for p in k])
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def carmichael_lambda(n: int) -> int:
|
|
222
|
+
"""Carmichael's lambda function of n."""
|
|
223
|
+
k = factorint(n)
|
|
224
|
+
lam_all = [] # values corresponding to the prime factors
|
|
225
|
+
for p in k:
|
|
226
|
+
lam = (p - 1) * p ** (k[p] - 1)
|
|
227
|
+
if p == 2 and k[p] > 2:
|
|
228
|
+
lam = lam // 2
|
|
229
|
+
lam_all += [lam]
|
|
230
|
+
lam = lam_all[0] # now take the least common multiple of all values
|
|
231
|
+
for l in lam_all[1:]:
|
|
232
|
+
lam = lcm(lam, l)
|
|
233
|
+
return lam
|
|
234
|
+
|
|
235
|
+
# Order in Z_p^*
|
|
236
|
+
|
|
237
|
+
def order(a: int, n: int, factor=False) -> int:
|
|
238
|
+
"""Compute the order of a in the group Z_n^*."""
|
|
239
|
+
a %= n
|
|
240
|
+
assert a != 0 and gcd(a, n) == 1, f"{a} and {n} are not coprime!"
|
|
241
|
+
factors = dict() # We compute euler_phi(n) and its factorization in one pass
|
|
242
|
+
for p, k in factorint(n).items(): # first factorize n
|
|
243
|
+
for pm, km in factorint(p - 1).items(): # factor p-1 and add the factors
|
|
244
|
+
if pm in factors:
|
|
245
|
+
factors[pm] += km
|
|
246
|
+
else:
|
|
247
|
+
factors[pm] = km
|
|
248
|
+
if k > 1: # if the multiplicity of of p is >1, then we need to add p**(k-1)
|
|
249
|
+
if p in factors:
|
|
250
|
+
factors[p] += k - 1
|
|
251
|
+
else:
|
|
252
|
+
factors[p] = k - 1
|
|
253
|
+
order = 1 # compute the group order euler_phi(n) as our current guess
|
|
254
|
+
for p, k in factors.items():
|
|
255
|
+
order *= p**k
|
|
256
|
+
if factor: # we compute the factorization of the order along the way
|
|
257
|
+
factors_order = {} # factorization of the order
|
|
258
|
+
for p, k in factors.items():
|
|
259
|
+
i = 0
|
|
260
|
+
for _ in range(k):
|
|
261
|
+
order_try = order // p
|
|
262
|
+
if pow(a, order_try, n) == 1:
|
|
263
|
+
order = order_try
|
|
264
|
+
i += 1
|
|
265
|
+
else:
|
|
266
|
+
break
|
|
267
|
+
if factor and i < k:
|
|
268
|
+
factors_order[p] = k - i
|
|
269
|
+
if factor:
|
|
270
|
+
return order, factors_order
|
|
271
|
+
return order
|
kryptools/poly.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polynomials
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
class Poly:
|
|
6
|
+
"""
|
|
7
|
+
Represents a polynomial as a list of coefficients.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
|
|
11
|
+
To define a polynomial as alist of coefficients use
|
|
12
|
+
>>> Poly([1, 2, 3])
|
|
13
|
+
3 x^2 + 2 x + 1
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, coeff: list, ring = None, modulus: list = None):
|
|
17
|
+
for i in range(len(coeff) - 1, 0, -1):
|
|
18
|
+
if coeff[i]:
|
|
19
|
+
break
|
|
20
|
+
coeff.pop(i)
|
|
21
|
+
self.coeff = coeff
|
|
22
|
+
self.modulus = modulus
|
|
23
|
+
if ring:
|
|
24
|
+
self.map(ring)
|
|
25
|
+
if modulus:
|
|
26
|
+
self.mod(modulus)
|
|
27
|
+
|
|
28
|
+
def __getitem__(self, item):
|
|
29
|
+
return self.coeff[item]
|
|
30
|
+
|
|
31
|
+
def __repr__(self):
|
|
32
|
+
def prx(i: int):
|
|
33
|
+
if i == 0:
|
|
34
|
+
return ""
|
|
35
|
+
if i == 1:
|
|
36
|
+
return "x"
|
|
37
|
+
return "x^" + str(i)
|
|
38
|
+
|
|
39
|
+
if len(self.coeff) == 1:
|
|
40
|
+
return str(int(self.coeff[0]))
|
|
41
|
+
plus = ""
|
|
42
|
+
tmp = ""
|
|
43
|
+
for i in reversed(range(len(self.coeff))):
|
|
44
|
+
s = self.coeff[i]
|
|
45
|
+
if not s:
|
|
46
|
+
continue
|
|
47
|
+
if not s - 1 and i != 0:
|
|
48
|
+
tmp += plus + prx(i)
|
|
49
|
+
plus = " + "
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
if s == -1 and i != 0:
|
|
53
|
+
if plus:
|
|
54
|
+
plus = " "
|
|
55
|
+
tmp += plus + "- " + prx(i)
|
|
56
|
+
plus = " + "
|
|
57
|
+
continue
|
|
58
|
+
except:
|
|
59
|
+
pass
|
|
60
|
+
try:
|
|
61
|
+
if plus and s < 0:
|
|
62
|
+
tmp += " - " + str(-s) + " " + prx(i)
|
|
63
|
+
continue
|
|
64
|
+
except:
|
|
65
|
+
pass
|
|
66
|
+
tmp += plus + str(s) + " " + prx(i)
|
|
67
|
+
plus = " + "
|
|
68
|
+
return tmp.strip()
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other):
|
|
71
|
+
if not isinstance(other, self.__class__):
|
|
72
|
+
return False
|
|
73
|
+
return self.coeff == other.coeff
|
|
74
|
+
|
|
75
|
+
def __bool__(self):
|
|
76
|
+
return bool(self.degree()) or bool(self.coeff[0])
|
|
77
|
+
|
|
78
|
+
def degree(self):
|
|
79
|
+
return len(self.coeff) - 1
|
|
80
|
+
|
|
81
|
+
def map(self, func):
|
|
82
|
+
self.coeff = list(map(func, self.coeff))
|
|
83
|
+
|
|
84
|
+
def __add__(self, other: "Poly") -> "Poly":
|
|
85
|
+
if not isinstance(other, self.__class__):
|
|
86
|
+
raise NotImplementedError(f"Cannot add {self} and {other}.")
|
|
87
|
+
ls, lo = len(self.coeff), len(other.coeff)
|
|
88
|
+
if ls < lo:
|
|
89
|
+
scoeff = self.coeff + (lo - ls) * [0]
|
|
90
|
+
else:
|
|
91
|
+
scoeff = self.coeff
|
|
92
|
+
if ls > lo:
|
|
93
|
+
ocoeff = other.coeff + (ls - lo) * [0]
|
|
94
|
+
else:
|
|
95
|
+
ocoeff = other.coeff
|
|
96
|
+
modulus = self.modulus
|
|
97
|
+
if not modulus and other.modulus:
|
|
98
|
+
modulus = other.modulus
|
|
99
|
+
return self.__class__([s + o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
|
|
100
|
+
|
|
101
|
+
def __neg__(self) -> "Poly":
|
|
102
|
+
return Poly([-s for s in self.coeff], modulus=self.modulus)
|
|
103
|
+
|
|
104
|
+
def __sub__(self, other: "Poly") -> "Poly":
|
|
105
|
+
if not isinstance(other, self.__class__):
|
|
106
|
+
raise NotImplementedError(f"Cannot subtract {self} and {other}.")
|
|
107
|
+
ls, lo = len(self.coeff), len(other.coeff)
|
|
108
|
+
if ls < lo:
|
|
109
|
+
scoeff = self.coeff + (lo - ls) * [0]
|
|
110
|
+
else:
|
|
111
|
+
scoeff = self.coeff
|
|
112
|
+
if ls > lo:
|
|
113
|
+
ocoeff = other.coeff + (ls - lo) * [0]
|
|
114
|
+
else:
|
|
115
|
+
ocoeff = other.coeff
|
|
116
|
+
modulus = self.modulus
|
|
117
|
+
if not modulus and other.modulus:
|
|
118
|
+
modulus = other.modulus
|
|
119
|
+
return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
|
|
120
|
+
|
|
121
|
+
def __mul__(self, other: "Poly") -> "Poly":
|
|
122
|
+
if isinstance(other, int):
|
|
123
|
+
return Poly([other * s for s in self.coeff])
|
|
124
|
+
if not isinstance(other, self.__class__):
|
|
125
|
+
raise NotImplementedError(f"Cannot multiply {self} and {other}.")
|
|
126
|
+
ls, lo = len(self.coeff), len(other.coeff)
|
|
127
|
+
coeff = [0] * (ls + lo - 1)
|
|
128
|
+
for k in range(ls + lo - 1):
|
|
129
|
+
coeff[k] = sum(
|
|
130
|
+
[
|
|
131
|
+
self.coeff[j] * other.coeff[k - j]
|
|
132
|
+
for j in range(max(0, k - lo + 1), min(ls, k + 1))
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
modulus = self.modulus
|
|
136
|
+
if not modulus and other.modulus:
|
|
137
|
+
modulus = other.modulus
|
|
138
|
+
return self.__class__(coeff, modulus=modulus)
|
|
139
|
+
|
|
140
|
+
def __rmul__(self, other: int) -> "Poly":
|
|
141
|
+
return self.__class__([other * s for s in self.coeff], modulus=self.modulus)
|
|
142
|
+
|
|
143
|
+
def __pow__(self, i: int) -> "Poly":
|
|
144
|
+
res = self.__class__([1], modulus=self.modulus)
|
|
145
|
+
if i < 0:
|
|
146
|
+
if not self.modulus:
|
|
147
|
+
raise NotImplementedError(f"Cannot divide.")
|
|
148
|
+
tmp = self.inv()
|
|
149
|
+
else:
|
|
150
|
+
tmp = self
|
|
151
|
+
for _ in range(i):
|
|
152
|
+
res *= tmp
|
|
153
|
+
return res
|
|
154
|
+
|
|
155
|
+
def divmod(self, other: "Poly") -> ("Poly", "Poly"):
|
|
156
|
+
if isinstance(other, list):
|
|
157
|
+
other = self.__class__(other)
|
|
158
|
+
elif not isinstance(other, self.__class__):
|
|
159
|
+
raise NotImplementedError(f"Cannot divide {self} and {other}.")
|
|
160
|
+
if not other:
|
|
161
|
+
raise ValueError(f"{other} must be nonzero.")
|
|
162
|
+
sd, od = self.degree(), other.degree()
|
|
163
|
+
if sd < od:
|
|
164
|
+
return self.__class__([0]), self
|
|
165
|
+
div = [0] * (sd - od + 1)
|
|
166
|
+
lco = other.coeff[-1]
|
|
167
|
+
if bool(lco - 1):
|
|
168
|
+
tmp = 1 / lco
|
|
169
|
+
oth = [c * tmp for c in other.coeff]
|
|
170
|
+
rem = [c * tmp for c in self.coeff]
|
|
171
|
+
else:
|
|
172
|
+
oth = other.coeff
|
|
173
|
+
rem = [c for c in self.coeff]
|
|
174
|
+
for i in range(sd - od + 1):
|
|
175
|
+
tmp = rem[sd - i]
|
|
176
|
+
div[sd - od - i] = tmp
|
|
177
|
+
for j in range(od + 1):
|
|
178
|
+
rem[sd - i - j] -= tmp * oth[od - j]
|
|
179
|
+
if bool(lco - 1):
|
|
180
|
+
rem = [c * lco for c in rem]
|
|
181
|
+
return self.__class__(div, modulus=self.modulus), self.__class__(
|
|
182
|
+
rem, modulus=self.modulus
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def mod(self, other: "Poly") -> None:
|
|
186
|
+
if isinstance(other, list):
|
|
187
|
+
other = self.__class__(other)
|
|
188
|
+
elif not isinstance(other, self.__class__):
|
|
189
|
+
raise NotImplementedError(f"Cannot divide {self} and {other}.")
|
|
190
|
+
if not other:
|
|
191
|
+
raise NotImplementedError(f"{other} must be nonzero.")
|
|
192
|
+
sd, od = self.degree(), other.degree()
|
|
193
|
+
if sd < od:
|
|
194
|
+
return self
|
|
195
|
+
lco = other.coeff[-1]
|
|
196
|
+
if bool(lco - 1):
|
|
197
|
+
tmp = 1 / lco
|
|
198
|
+
oth = [c * tmp for c in other.coeff]
|
|
199
|
+
else:
|
|
200
|
+
oth = other.coeff
|
|
201
|
+
for i in range(sd - od + 1):
|
|
202
|
+
tmp = self.coeff[sd - i]
|
|
203
|
+
for j in range(od + 1):
|
|
204
|
+
self.coeff[sd - i - j] -= tmp * oth[od - j]
|
|
205
|
+
for i in range(len(self.coeff) - 1, 0, -1):
|
|
206
|
+
if self.coeff[i]:
|
|
207
|
+
break
|
|
208
|
+
self.coeff.pop(i)
|
|
209
|
+
|
|
210
|
+
def inv(self, other: "Poly" = None) -> "Poly":
|
|
211
|
+
if not other:
|
|
212
|
+
other = self.modulus
|
|
213
|
+
if isinstance(other, list):
|
|
214
|
+
other = self.__class__(other)
|
|
215
|
+
elif not isinstance(other, self.__class__):
|
|
216
|
+
raise NotImplementedError(f"Cannot invert {self} modulo {other}.")
|
|
217
|
+
if not other:
|
|
218
|
+
raise NotImplementedError(f"{other} must be nonzero.")
|
|
219
|
+
r0, r1 = other, self
|
|
220
|
+
y0, y1 = self.__class__([0], modulus=self.modulus), self.__class__(
|
|
221
|
+
[1], modulus=self.modulus
|
|
222
|
+
)
|
|
223
|
+
while r1:
|
|
224
|
+
q, r = r0.divmod(r1)
|
|
225
|
+
r0, r1 = r1, r
|
|
226
|
+
y0, y1 = y1, y0 - q * y1
|
|
227
|
+
if r0.degree() != 0:
|
|
228
|
+
raise ValueError(f"{self} is not invertible mod {other}.")
|
|
229
|
+
tmp = 1 / r0[0]
|
|
230
|
+
for i in range(y0.degree()):
|
|
231
|
+
y0.coeff[i] *= tmp
|
|
232
|
+
return y0
|