kryptools 1.5__tar.gz → 1.5.1__tar.gz
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-1.5 → kryptools-1.5.1}/PKG-INFO +1 -1
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/GF2.py +4 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/__init__.py +1 -1
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/code.py +4 -3
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/keccak.py +25 -4
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/poly.py +83 -57
- {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/PKG-INFO +1 -1
- {kryptools-1.5 → kryptools-1.5.1}/pyproject.toml +1 -1
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_GF2.py +11 -12
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_Zmod.py +0 -1
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_poly.py +30 -5
- {kryptools-1.5 → kryptools-1.5.1}/LICENSE +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/README.md +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/Zmod.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/aes.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/blockcipher.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/des.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/ec.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_dix.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_qs.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_rho.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/intfuncs.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/la.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/lat.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/nt.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/primes.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools/sha1.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/setup.cfg +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_aes.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_code.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_des.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_dlog.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_ec.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor_qs.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_intfuncs.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_keccak.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_la.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_lat.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_nt.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_primes.py +0 -0
- {kryptools-1.5 → kryptools-1.5.1}/tests/test_sha1.py +0 -0
|
@@ -261,6 +261,8 @@ class GF2nPoint:
|
|
|
261
261
|
return NotImplemented
|
|
262
262
|
if not (self.x) and j < 0:
|
|
263
263
|
raise ValueError("Division by zero.")
|
|
264
|
+
if self.field.order == 2:
|
|
265
|
+
return self
|
|
264
266
|
if self.field.bitreversed:
|
|
265
267
|
res = self.field(self.field.order >> 1)
|
|
266
268
|
else:
|
|
@@ -290,6 +292,8 @@ class GF2nPoint:
|
|
|
290
292
|
|
|
291
293
|
def sqrt(self) -> "GF2nPoint":
|
|
292
294
|
"Compute the square root."
|
|
295
|
+
if self.field.order == 2:
|
|
296
|
+
return self
|
|
293
297
|
return self**(self.field.order//2)
|
|
294
298
|
|
|
295
299
|
def order(self) -> int:
|
|
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
|
|
|
5
5
|
__author__ = "Gerald Teschl"
|
|
6
6
|
__copyright__ = "Copyright 2024-2025, Gerald Teschl"
|
|
7
7
|
__license__ = "MIT License"
|
|
8
|
-
__version__ = "1.5"
|
|
8
|
+
__version__ = "1.5.1"
|
|
9
9
|
__email__ = "Gerald.Teschl@univie.ac.at"
|
|
10
10
|
|
|
11
11
|
from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, is_carmichael_number, order, crt
|
|
@@ -307,11 +307,12 @@ class ReedSolomonCode(CyclicCode):
|
|
|
307
307
|
y = Poly(y, ring = self.gf)
|
|
308
308
|
if y.degree() >= self.n:
|
|
309
309
|
raise ValueError(f"Degree can be at most {self.n-1}.")
|
|
310
|
-
|
|
310
|
+
xx = [ -y(self.alpha**(-j)) for j in range(self.n) ]
|
|
311
|
+
x = Poly(xx)
|
|
311
312
|
if x.degree() >= self.k: # No codewort
|
|
312
313
|
t = (self.n - self.k) // 2
|
|
313
|
-
A = Matrix([[
|
|
314
|
-
b = [ -
|
|
314
|
+
A = Matrix([[ xx[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
|
|
315
|
+
b = [ -xx[self.k + t + l ] for l in range(t) ]
|
|
315
316
|
lam = Poly([one] + list(A.solve(b)))
|
|
316
317
|
x_coordinates = []
|
|
317
318
|
y_coordinates = []
|
|
@@ -161,16 +161,21 @@ class SHA3():
|
|
|
161
161
|
def __init__(self, n: int = 256):
|
|
162
162
|
if n not in (224, 256, 384, 512):
|
|
163
163
|
raise ValueError(f"n must be one of: 224, 256, 384, 512")
|
|
164
|
-
self.keccak = Keccak(rate=1600-2*n)
|
|
164
|
+
self.keccak = Keccak(rate = 1600 - 2*n)
|
|
165
165
|
self.keccak.pad = [b'\x86', b'\x06', b'\x80']
|
|
166
|
-
self.
|
|
166
|
+
self.digest_size = n // 8 # in bytes
|
|
167
|
+
self.block_size = (1600 - 2*n) // 8 # in bytes
|
|
168
|
+
self.name = f"SHA3-{n}"
|
|
167
169
|
|
|
168
170
|
def __call__(self, msg: bytes | str) -> bytes:
|
|
169
171
|
"Compute the hash of the message given or absorbed."
|
|
170
172
|
self.keccak.reset()
|
|
171
173
|
self.keccak.absorb(msg)
|
|
172
|
-
return self.keccak.squeeze(self.
|
|
174
|
+
return self.keccak.squeeze(self.digest_size)
|
|
173
175
|
|
|
176
|
+
def __repr__(self):
|
|
177
|
+
return self.name
|
|
178
|
+
|
|
174
179
|
def update(self, msg: bytes | str) -> None:
|
|
175
180
|
"Absorb the given message into the sponge."
|
|
176
181
|
self.keccak.absorb(msg)
|
|
@@ -179,10 +184,14 @@ class SHA3():
|
|
|
179
184
|
"Compute the hash of the message given or absorbed."
|
|
180
185
|
if msg is not None:
|
|
181
186
|
self.keccak.absorb(msg)
|
|
182
|
-
digest = self.keccak.squeeze(self.
|
|
187
|
+
digest = self.keccak.squeeze(self.digest_size)
|
|
183
188
|
self.keccak.reset()
|
|
184
189
|
return digest
|
|
185
190
|
|
|
191
|
+
def hexdigest(self, msg: bytes | str | None = None) -> bytes:
|
|
192
|
+
"Compute the hash of the message given or absorbed. Return as hex value."
|
|
193
|
+
return self.digest(msg).hex()
|
|
194
|
+
|
|
186
195
|
|
|
187
196
|
class SHAKE():
|
|
188
197
|
"""
|
|
@@ -201,7 +210,19 @@ class SHAKE():
|
|
|
201
210
|
self.keccak = Keccak(rate=1600-2*n)
|
|
202
211
|
self.keccak.pad = [b'\x9f', b'\x1f', b'\x80']
|
|
203
212
|
self.keccak.absorb(msg)
|
|
213
|
+
self.name = f"SHAKE-{n}"
|
|
214
|
+
|
|
215
|
+
def __repr__(self):
|
|
216
|
+
return self.name
|
|
204
217
|
|
|
205
218
|
def __call__(self, r: int) -> bytes:
|
|
206
219
|
"Extract `r` bytes."
|
|
207
220
|
return self.keccak.squeeze(r)
|
|
221
|
+
|
|
222
|
+
def digest(self, r: int) -> bytes:
|
|
223
|
+
"Extract `r` bytes."
|
|
224
|
+
return self(r)
|
|
225
|
+
|
|
226
|
+
def hexdigest(self, r: int) -> bytes:
|
|
227
|
+
"Extract `r` bytes as hex values."
|
|
228
|
+
return self(r).hex()
|
|
@@ -6,7 +6,7 @@ from math import prod
|
|
|
6
6
|
from numbers import Number
|
|
7
7
|
from random import randint
|
|
8
8
|
from .primes import is_prime
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
class Poly:
|
|
12
12
|
"""
|
|
@@ -36,31 +36,45 @@ class Poly:
|
|
|
36
36
|
self.mod(modulus)
|
|
37
37
|
|
|
38
38
|
def __call__(self, x):
|
|
39
|
-
|
|
40
|
-
if not type(x) == type(
|
|
41
|
-
raise ValueError(
|
|
42
|
-
|
|
39
|
+
result = 0 * self.coeff[0]
|
|
40
|
+
if not type(x) == type(result):
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"Evaluation point must be an element of the ring.")
|
|
43
|
+
# Horner's method
|
|
44
|
+
for c in reversed(self.coeff):
|
|
45
|
+
result = result * x + c
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
return sum((c * x**j for j, c in enumerate(self.coeff)), start=zero)
|
|
43
49
|
|
|
44
50
|
def __getitem__(self, item):
|
|
51
|
+
if item >= len(self.coeff):
|
|
52
|
+
return self.coeff[0] * 0
|
|
45
53
|
return self.coeff[item]
|
|
46
54
|
|
|
47
55
|
def __setitem__(self, item, value):
|
|
56
|
+
if item >= len(self.coeff):
|
|
57
|
+
zero = self.coeff[0] * 0
|
|
58
|
+
self.coeff += [zero] * (item - len(self.coeff) + 1)
|
|
48
59
|
self.coeff[item] = value
|
|
49
60
|
|
|
61
|
+
def __iter__(self):
|
|
62
|
+
yield from self.coeff
|
|
63
|
+
|
|
50
64
|
def __len__(self):
|
|
51
65
|
return len(self.coeff)
|
|
52
66
|
|
|
53
67
|
def __hash__(self):
|
|
54
68
|
return hash(tuple(self.coeff))
|
|
55
69
|
|
|
56
|
-
def __repr__(self, latex
|
|
70
|
+
def __repr__(self, latex=False):
|
|
57
71
|
def prx(i: int) -> str:
|
|
58
72
|
if i == 0:
|
|
59
73
|
return ""
|
|
60
74
|
if i == 1:
|
|
61
75
|
return self.__class__.print_x
|
|
62
76
|
if latex:
|
|
63
|
-
return self.__class__.print_x + "^{" + str(i) +"}"
|
|
77
|
+
return self.__class__.print_x + "^{" + str(i) + "}"
|
|
64
78
|
return self.__class__.print_x + self.__class__.print_pow + str(i)
|
|
65
79
|
|
|
66
80
|
if not self:
|
|
@@ -105,7 +119,7 @@ class Poly:
|
|
|
105
119
|
def _repr_mimebundle_(self, **kwargs):
|
|
106
120
|
return {
|
|
107
121
|
"text/plain": repr(self),
|
|
108
|
-
"text/latex": "$" + self.__repr__(latex
|
|
122
|
+
"text/latex": "$" + self.__repr__(latex=True) + "$"
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
def __eq__(self, other):
|
|
@@ -149,17 +163,19 @@ class Poly:
|
|
|
149
163
|
def _guess_field(self):
|
|
150
164
|
zero, one, field = self._guess_ring()
|
|
151
165
|
if not field:
|
|
152
|
-
raise ValueError(
|
|
166
|
+
raise ValueError(
|
|
167
|
+
"The polynomial does not seem to be over a known finite field.")
|
|
153
168
|
if hasattr(field, 'n'): # Zmod
|
|
154
169
|
p = field.n
|
|
155
170
|
q = field.n
|
|
156
171
|
if not field.is_field():
|
|
157
|
-
raise ValueError(
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"The polynomial does not seem to be over a finite field.")
|
|
158
174
|
else: # G2 or galois
|
|
159
175
|
p = field.characteristic
|
|
160
176
|
q = field.order
|
|
161
177
|
return field, zero, one, q, p
|
|
162
|
-
|
|
178
|
+
|
|
163
179
|
def __add__(self, other: "Poly") -> "Poly":
|
|
164
180
|
if not isinstance(other, self.__class__):
|
|
165
181
|
if self._check_type(other):
|
|
@@ -172,8 +188,8 @@ class Poly:
|
|
|
172
188
|
modulus = other.modulus
|
|
173
189
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
174
190
|
if ls < lo:
|
|
175
|
-
return self.__class__([
|
|
176
|
-
return self.__class__([
|
|
191
|
+
return self.__class__([s + o for s, o in zip(self.coeff, other.coeff)] + other.coeff[ls:], modulus=modulus)
|
|
192
|
+
return self.__class__([s + o for s, o in zip(self.coeff, other.coeff)] + self.coeff[lo:], modulus=modulus)
|
|
177
193
|
|
|
178
194
|
def __radd__(self, other: "Poly") -> "Poly":
|
|
179
195
|
return self + other
|
|
@@ -197,7 +213,8 @@ class Poly:
|
|
|
197
213
|
return NotImplemented
|
|
198
214
|
zero = 0 * self.coeff[0]
|
|
199
215
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
200
|
-
coeff = [
|
|
216
|
+
coeff = [sum((self.coeff[j] * other.coeff[k - j] for j in range(max(0, k - lo + 1), min(ls, k + 1))), start=zero)
|
|
217
|
+
for k in range(ls + lo - 1)]
|
|
201
218
|
modulus = self.modulus
|
|
202
219
|
if not modulus and other.modulus:
|
|
203
220
|
modulus = other.modulus
|
|
@@ -211,13 +228,15 @@ class Poly:
|
|
|
211
228
|
return self.__class__([s / other for s in self.coeff], modulus=self.modulus)
|
|
212
229
|
if isinstance(other, self.__class__):
|
|
213
230
|
if not other.modulus:
|
|
214
|
-
raise NotImplementedError(
|
|
231
|
+
raise NotImplementedError(
|
|
232
|
+
"Cannot invert polynomials without modulus.")
|
|
215
233
|
return self * other.inv()
|
|
216
234
|
return NotImplemented
|
|
217
235
|
|
|
218
236
|
def __rtruediv__(self, other) -> "Poly":
|
|
219
237
|
if not self.modulus:
|
|
220
|
-
raise NotImplementedError(
|
|
238
|
+
raise NotImplementedError(
|
|
239
|
+
"Cannot invert polynomials without modulus.")
|
|
221
240
|
return other * self.inv()
|
|
222
241
|
|
|
223
242
|
def __pow__(self, j: int) -> "Poly":
|
|
@@ -227,7 +246,8 @@ class Poly:
|
|
|
227
246
|
res = self.__class__([one], modulus=self.modulus)
|
|
228
247
|
if j < 0:
|
|
229
248
|
if not self.modulus:
|
|
230
|
-
raise NotImplementedError(
|
|
249
|
+
raise NotImplementedError(
|
|
250
|
+
"Cannot divide polynomials without modulus.")
|
|
231
251
|
tmp = self.inv()
|
|
232
252
|
j *= -1
|
|
233
253
|
else:
|
|
@@ -245,7 +265,8 @@ class Poly:
|
|
|
245
265
|
"List of bits of all coefficients."
|
|
246
266
|
ring = self.coeff[0].__class__
|
|
247
267
|
if not (hasattr(ring, 'bits') and callable(ring.bits)):
|
|
248
|
-
raise NotImplementedError(
|
|
268
|
+
raise NotImplementedError(
|
|
269
|
+
"Coefficients cannot be converted to bits.")
|
|
249
270
|
out = []
|
|
250
271
|
for c in self.coeff:
|
|
251
272
|
out += c.bits()
|
|
@@ -284,8 +305,8 @@ class Poly:
|
|
|
284
305
|
oth = [c * tmp for c in other.coeff]
|
|
285
306
|
rem = [c * tmp for c in self.coeff]
|
|
286
307
|
else:
|
|
287
|
-
oth = other.coeff
|
|
288
|
-
rem = [c * one for c in self.coeff]
|
|
308
|
+
oth = other.coeff # "* 1" is here to make sure we get a copy
|
|
309
|
+
rem = [c * one for c in self.coeff]
|
|
289
310
|
for i in range(sd - od + 1):
|
|
290
311
|
tmp = rem[sd - i] * one # "* 1" is here to make sure we get a copy
|
|
291
312
|
div[sd - od - i] = tmp
|
|
@@ -314,10 +335,10 @@ class Poly:
|
|
|
314
335
|
else:
|
|
315
336
|
oth = other.coeff
|
|
316
337
|
for i in range(sd - od + 1):
|
|
317
|
-
tmp = self.coeff[sd - i] * one
|
|
338
|
+
tmp = self.coeff[sd - i] * one # "* 1" is here to make sure we get a copy
|
|
318
339
|
for j in range(od + 1):
|
|
319
340
|
self.coeff[sd - i - j] -= tmp * oth[od - j]
|
|
320
|
-
for i in range(len(self.coeff) - 1, 0, -1):
|
|
341
|
+
for i in range(len(self.coeff) - 1, 0, -1): # strip leading zeros
|
|
321
342
|
if self.coeff[i]:
|
|
322
343
|
break
|
|
323
344
|
self.coeff.pop(i)
|
|
@@ -330,8 +351,9 @@ class Poly:
|
|
|
330
351
|
if isinstance(other, self.__class__):
|
|
331
352
|
other = other.coeff
|
|
332
353
|
elif not isinstance(other, list):
|
|
333
|
-
raise NotImplementedError(
|
|
334
|
-
|
|
354
|
+
raise NotImplementedError(
|
|
355
|
+
f"{other} must be a list of coefficients or a polynomial.")
|
|
356
|
+
other = Poly(other, ring=ring)
|
|
335
357
|
if not other:
|
|
336
358
|
raise NotImplementedError(f"{other} must be nonzero.")
|
|
337
359
|
r0, r1 = other, self
|
|
@@ -349,7 +371,8 @@ class Poly:
|
|
|
349
371
|
"Greates common divisor with a given polynomial."
|
|
350
372
|
ring = self._guess_ring()[-1]
|
|
351
373
|
if not isinstance(other, self.__class__):
|
|
352
|
-
raise NotImplementedError(f"Cannot compute gcd: {
|
|
374
|
+
raise NotImplementedError(f"Cannot compute gcd: {
|
|
375
|
+
other} must be a polynomial.")
|
|
353
376
|
if not self:
|
|
354
377
|
if not other:
|
|
355
378
|
return Poly([self.coeff[0]])
|
|
@@ -366,7 +389,8 @@ class Poly:
|
|
|
366
389
|
"""Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
|
|
367
390
|
zero, one, ring = self._guess_ring()
|
|
368
391
|
if not isinstance(other, self.__class__):
|
|
369
|
-
raise NotImplementedError(f"Cannot perform egcd: {
|
|
392
|
+
raise NotImplementedError(f"Cannot perform egcd: {
|
|
393
|
+
other} must be a polynomial.")
|
|
370
394
|
if not self:
|
|
371
395
|
if not other:
|
|
372
396
|
return Poly([zero]), Poly([zero]), Poly([zero])
|
|
@@ -388,23 +412,23 @@ class Poly:
|
|
|
388
412
|
if l == 1:
|
|
389
413
|
tmp = 0 * self.coeff[0]
|
|
390
414
|
else:
|
|
391
|
-
tmp = [
|
|
415
|
+
tmp = [j * self.coeff[j] for j in range(1, l)]
|
|
392
416
|
return self.__class__(tmp, modulus=self.modulus)
|
|
393
417
|
|
|
394
418
|
def square_free_factors(self) -> dict:
|
|
395
419
|
"Determine the square free factors of a polynomial over a Galois field."
|
|
396
420
|
_, zero, _, q, p = self._guess_field()
|
|
397
|
-
pr = q // p
|
|
421
|
+
pr = q // p # x^pr will be the p'th root of x
|
|
398
422
|
if self.degree() < 1:
|
|
399
423
|
return {}
|
|
400
424
|
if self.degree() == 1:
|
|
401
|
-
return {
|
|
425
|
+
return {self / self.coeff[-1]: 1}
|
|
402
426
|
factors = {}
|
|
403
427
|
w = self / self.coeff[-1]
|
|
404
428
|
c = w.gcd(w.derivative())
|
|
405
429
|
w = w // c
|
|
406
430
|
|
|
407
|
-
# first we find all factors which are not a power of p
|
|
431
|
+
# first we find all factors which are not a power of p
|
|
408
432
|
i = 1
|
|
409
433
|
while w.degree() > 0:
|
|
410
434
|
y = w.gcd(c)
|
|
@@ -418,11 +442,11 @@ class Poly:
|
|
|
418
442
|
if c.degree() > 0:
|
|
419
443
|
# compute the p'th root of c
|
|
420
444
|
d = c.degree() // p
|
|
421
|
-
root = [
|
|
445
|
+
root = [zero] * (d + 1)
|
|
422
446
|
while d >= 0:
|
|
423
447
|
root[d] = c.coeff[d * p]**pr
|
|
424
448
|
d -= 1
|
|
425
|
-
#assert Poly(root)**p == c, root
|
|
449
|
+
# assert Poly(root)**p == c, root
|
|
426
450
|
for fac, mult in Poly(root).square_free_factors().items():
|
|
427
451
|
factors[fac] = p * mult
|
|
428
452
|
return factors
|
|
@@ -432,21 +456,18 @@ class Poly:
|
|
|
432
456
|
multiplicities = self.square_free_factors().values()
|
|
433
457
|
return len(multiplicities) == 1 and multiplicities[0] == 1
|
|
434
458
|
|
|
435
|
-
def distinct_degree_factors(self, test:bool = False) -> bool|dict:
|
|
459
|
+
def distinct_degree_factors(self, test: bool = False) -> bool | dict:
|
|
436
460
|
"Factors the polynomial over a Galois field into distinct products of irredicible factors of the same degree."
|
|
437
461
|
_, zero, one, q, _ = self._guess_field()
|
|
438
462
|
if self.degree() < 1:
|
|
439
463
|
raise ValueError("The polynomial must be non-constant.")
|
|
440
464
|
factors = {}
|
|
441
465
|
w = self
|
|
442
|
-
x = Poly([zero, one]) # x
|
|
443
|
-
|
|
466
|
+
x = Poly([zero, one], modulus=self.coeff) # x
|
|
467
|
+
b = x
|
|
444
468
|
k = 1
|
|
445
469
|
while k <= w.degree()//2:
|
|
446
|
-
|
|
447
|
-
b = xq
|
|
448
|
-
else:
|
|
449
|
-
b **= q # we compute x^{q^k} mod a recursively
|
|
470
|
+
b **= q # we compute x^{q^k} mod a recursively
|
|
450
471
|
g = w.gcd(b - x) # gcd with x^{q^k} - x
|
|
451
472
|
if g.degree():
|
|
452
473
|
if test:
|
|
@@ -462,43 +483,45 @@ class Poly:
|
|
|
462
483
|
|
|
463
484
|
def rabin_test(self) -> bool:
|
|
464
485
|
"Determine if a polynomial is irreducible over a Galois field using a Rabin test."
|
|
465
|
-
return self.distinct_degree_factors(test
|
|
486
|
+
return self.distinct_degree_factors(test=True)
|
|
466
487
|
|
|
467
488
|
def is_irreducible(self) -> bool:
|
|
468
489
|
"Test if the polynomial over a Galois field is irreducibel."
|
|
469
490
|
if self.degree() == 0:
|
|
470
491
|
return False
|
|
471
|
-
return self.distinct_degree_factors(test
|
|
492
|
+
return self.distinct_degree_factors(test=True)
|
|
472
493
|
|
|
473
|
-
def equal_degree_factors(self
|
|
494
|
+
def equal_degree_factors(self, k: int) -> list:
|
|
474
495
|
"Factors a square free product of irreducibel polynomials of degree `k` over a Galois field."
|
|
475
496
|
field, zero, one, q, p = self._guess_field()
|
|
476
497
|
if hasattr(field, "degree"):
|
|
477
|
-
degree= field.degree
|
|
498
|
+
degree = field.degree
|
|
478
499
|
else:
|
|
479
500
|
degree = 1
|
|
480
501
|
d = self.degree()
|
|
481
|
-
if not isinstance(k, int) or k < 1
|
|
502
|
+
if not isinstance(k, int) or k < 1:
|
|
482
503
|
raise ValueError(f"The degree {k} must be a positive integer.")
|
|
483
504
|
if d < k or d % k:
|
|
484
|
-
raise ValueError(
|
|
505
|
+
raise ValueError(
|
|
506
|
+
f"The polynomial is no product of irreducibel polynomials of degree {k}.")
|
|
485
507
|
r = d // k
|
|
486
508
|
qk = (q**k - 1) // 2
|
|
487
509
|
poly = [0] * k + [1]
|
|
488
|
-
|
|
489
|
-
factors = [
|
|
510
|
+
|
|
511
|
+
factors = [self]
|
|
490
512
|
while len(factors) < r:
|
|
491
|
-
g = Poly([randint(0,q-1)
|
|
492
|
-
|
|
513
|
+
g = Poly([randint(0, q-1)
|
|
514
|
+
for _ in range(d+1)], ring=field, modulus=self)
|
|
515
|
+
if p == 2: # Gathen-Shoup
|
|
493
516
|
h = g
|
|
494
|
-
for i in range(1,k):
|
|
517
|
+
for i in range(1, k):
|
|
495
518
|
g += h**q
|
|
496
519
|
if degree > 1:
|
|
497
520
|
h = g
|
|
498
|
-
for i in range(1,degree):
|
|
521
|
+
for i in range(1, degree):
|
|
499
522
|
g += g**2
|
|
500
523
|
else: # Cantor–Zassenhaus
|
|
501
|
-
g
|
|
524
|
+
g = g**qk - one
|
|
502
525
|
factors2 = [] # list for the next round (to avoid the the new factors are tested a second time with the same g)
|
|
503
526
|
for fac in factors:
|
|
504
527
|
if fac.degree() == k: # this one is already irreducible
|
|
@@ -526,18 +549,20 @@ class Poly:
|
|
|
526
549
|
factors = {}
|
|
527
550
|
for fac1, m in self.square_free_factors().items():
|
|
528
551
|
for k, fac2 in fac1.distinct_degree_factors().items():
|
|
529
|
-
if k == fac2.degree():
|
|
552
|
+
if k == fac2.degree(): # irreducible
|
|
530
553
|
factors[fac2] = m
|
|
531
554
|
else:
|
|
532
555
|
for fac3 in fac2.equal_degree_factors(k):
|
|
533
556
|
factors[fac3] = m
|
|
534
557
|
return dict(sorted(factors.items(), key=lambda item: item[0].degree()))
|
|
535
558
|
|
|
536
|
-
|
|
559
|
+
|
|
560
|
+
def lagrange_interpolation(x_coordinates: list, y_coordinates: list) -> "Poly":
|
|
537
561
|
"Compute the Lagrange interpolation polynomial from a given list of x and y values."
|
|
538
562
|
l = len(x_coordinates)
|
|
539
563
|
if l == 0 or l != len(y_coordinates):
|
|
540
|
-
raise ValueError(
|
|
564
|
+
raise ValueError(
|
|
565
|
+
'List of x and y values must be nonempty of equal length.')
|
|
541
566
|
if l != len(set(x_coordinates)):
|
|
542
567
|
raise ValueError('List of x values must not contain duplicate values.')
|
|
543
568
|
# guess zero and one
|
|
@@ -545,9 +570,10 @@ def lagrange_interpolation(x_coordinates:list, y_coordinates:list) -> "Poly":
|
|
|
545
570
|
one = zero**0
|
|
546
571
|
|
|
547
572
|
poly = Poly([zero])
|
|
548
|
-
L_all = prod([Poly([-x, one]) for x in x_coordinates], start
|
|
573
|
+
L_all = prod([Poly([-x, one]) for x in x_coordinates], start=one)
|
|
549
574
|
|
|
550
575
|
for x, y in zip(x_coordinates, y_coordinates):
|
|
551
576
|
L = L_all // Poly([-x, one])
|
|
552
|
-
poly += (y /
|
|
577
|
+
poly += (y /
|
|
578
|
+
prod([x - xx for xx in x_coordinates if xx != x], start=one)) * L
|
|
553
579
|
return poly
|
|
@@ -18,21 +18,20 @@ def test_GF2_ops():
|
|
|
18
18
|
assert len(list(gf.star())) == gf.order - 1
|
|
19
19
|
assert len(list(gf.generators())) == euler_phi(gf.order - 1)
|
|
20
20
|
for _ in range(num_tests):
|
|
21
|
-
a = randint(0, gf.order-1)
|
|
22
|
-
b = randint(0, gf.order-1)
|
|
23
|
-
assert (
|
|
24
|
-
assert (
|
|
25
|
-
assert (
|
|
26
|
-
if
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
a = gf(randint(0, gf.order-1))
|
|
22
|
+
b = gf(randint(0, gf.order-1))
|
|
23
|
+
assert (a + b).poly() == a.poly() + b.poly()
|
|
24
|
+
assert (a - b).poly() == a.poly() - b.poly()
|
|
25
|
+
assert (a * b).poly() == a.poly() * b.poly()
|
|
26
|
+
if a:
|
|
27
|
+
assert (a**-1).poly() == a.poly().inv()
|
|
28
|
+
if b:
|
|
29
|
+
assert (a / b).poly() == a.poly() / b.poly()
|
|
30
|
+
assert a.sqrt()**2 == a
|
|
29
31
|
with pytest.raises(ValueError):
|
|
30
32
|
gf(1) / gf(0)
|
|
31
33
|
with pytest.raises(ValueError):
|
|
32
34
|
gf(0)**-1
|
|
33
|
-
for _ in range(num_tests):
|
|
34
|
-
a = randint(1, gf.order-1)
|
|
35
|
-
assert (gf(a)**-1).poly() == gf(a).poly().inv()
|
|
36
35
|
|
|
37
36
|
def test_GF2_order():
|
|
38
37
|
for n in range(1, 8):
|
|
@@ -53,7 +52,7 @@ def test_GF2P_order():
|
|
|
53
52
|
xx *=x
|
|
54
53
|
assert o == x.order()
|
|
55
54
|
|
|
56
|
-
def
|
|
55
|
+
def test_GF2_generator():
|
|
57
56
|
for n in range(1, 9):
|
|
58
57
|
gf = GF2(n)
|
|
59
58
|
a = gf.generator()
|
|
@@ -4,23 +4,48 @@ from random import sample, choices, randint, seed
|
|
|
4
4
|
from kryptools import Poly, Zmod, GF2, lagrange_interpolation
|
|
5
5
|
from kryptools import divisors, moebius_mu
|
|
6
6
|
seed(0)
|
|
7
|
+
num_tests = 10
|
|
7
8
|
|
|
8
9
|
def test_Poly():
|
|
9
10
|
p = Poly([2, 0, 2])
|
|
10
11
|
q = Poly([1, 2, 3, 4])
|
|
11
12
|
d, m = q.divmod(p)
|
|
12
13
|
assert d * p + m == q
|
|
14
|
+
for _ in range(num_tests):
|
|
15
|
+
p = Poly([randint(0,100) for _ in range(10)])
|
|
16
|
+
q = Poly([randint(0,100) for _ in range(12)])
|
|
17
|
+
x = randint(0,100)
|
|
18
|
+
assert p(x) == sum((c * x**j for j, c in enumerate(p.coeff)), start=0)
|
|
19
|
+
assert (p + q)(x) == p(x) + q(x)
|
|
20
|
+
assert (p - q)(x) == p(x) - q(x)
|
|
21
|
+
assert (p*q)(x) == p(x) * q(x)
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
p
|
|
16
|
-
q
|
|
23
|
+
gf = Zmod(5)
|
|
24
|
+
p = Poly([2, 0, 2], ring = gf)
|
|
25
|
+
q = Poly([1, 2, 3, 4], ring = gf)
|
|
17
26
|
d, m = q.divmod(p)
|
|
18
27
|
assert d * p + m == q
|
|
28
|
+
for _ in range(num_tests):
|
|
29
|
+
p = Poly(gf.random(10))
|
|
30
|
+
q = Poly(gf.random(12))
|
|
31
|
+
x = gf.random()
|
|
32
|
+
assert p(x) == sum((c * x**j for j, c in enumerate(p.coeff)), start=gf(0))
|
|
33
|
+
assert (p + q)(x) == p(x) + q(x)
|
|
34
|
+
assert (p - q)(x) == p(x) - q(x)
|
|
35
|
+
assert (p * q)(x) == p(x) * q(x)
|
|
19
36
|
|
|
20
37
|
gf = GF2(8, modulus=0b100011011)
|
|
21
38
|
p = Poly([2, 1, 1, 3], modulus=[1, 0, 0, 0, 1], ring=gf)
|
|
22
39
|
q = p.inv()
|
|
23
40
|
assert p * q == Poly([1], modulus=[1, 0, 0, 0, 1], ring=gf)
|
|
41
|
+
for _ in range(num_tests):
|
|
42
|
+
p = Poly(gf.random(10))
|
|
43
|
+
q = Poly(gf.random(12))
|
|
44
|
+
x = gf.random()
|
|
45
|
+
assert p(x) == sum((c * x**j for j, c in enumerate(p.coeff)), start=gf(0))
|
|
46
|
+
assert (p + q)(x) == p(x) + q(x)
|
|
47
|
+
assert (p - q)(x) == p(x) - q(x)
|
|
48
|
+
assert (p * q)(x) == p(x) * q(x)
|
|
24
49
|
|
|
25
50
|
def test_rabin():
|
|
26
51
|
for gf,t in [ [Zmod(7), 4], [GF2(4), 3]]:
|
|
@@ -38,13 +63,13 @@ def test_rabin():
|
|
|
38
63
|
assert count == sum([moebius_mu(d) * order ** (t // d) for d in divisors(t)]) // t
|
|
39
64
|
|
|
40
65
|
def test_factor():
|
|
41
|
-
for gf,t in [ [Zmod(7), 14], [GF2(4),
|
|
66
|
+
for gf, t in [ [Zmod(7), 14], [GF2(4), 9]]:
|
|
42
67
|
order = len(list(gf))
|
|
43
68
|
one = Poly([gf(1)])
|
|
44
69
|
for _ in range(20):
|
|
45
70
|
p = one
|
|
46
71
|
while p.degree() < 1:
|
|
47
|
-
p = Poly(
|
|
72
|
+
p = Poly(gf.random(t), ring =gf)
|
|
48
73
|
factors = p.factor()
|
|
49
74
|
assert prod([f**k for f,k in factors.items()], start = one) == p, p
|
|
50
75
|
for fac in factors.keys():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|