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.
Files changed (57) hide show
  1. {kryptools-1.5 → kryptools-1.5.1}/PKG-INFO +1 -1
  2. {kryptools-1.5 → kryptools-1.5.1}/kryptools/GF2.py +4 -0
  3. {kryptools-1.5 → kryptools-1.5.1}/kryptools/__init__.py +1 -1
  4. {kryptools-1.5 → kryptools-1.5.1}/kryptools/code.py +4 -3
  5. {kryptools-1.5 → kryptools-1.5.1}/kryptools/keccak.py +25 -4
  6. {kryptools-1.5 → kryptools-1.5.1}/kryptools/poly.py +83 -57
  7. {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/PKG-INFO +1 -1
  8. {kryptools-1.5 → kryptools-1.5.1}/pyproject.toml +1 -1
  9. {kryptools-1.5 → kryptools-1.5.1}/tests/test_GF2.py +11 -12
  10. {kryptools-1.5 → kryptools-1.5.1}/tests/test_Zmod.py +0 -1
  11. {kryptools-1.5 → kryptools-1.5.1}/tests/test_poly.py +30 -5
  12. {kryptools-1.5 → kryptools-1.5.1}/LICENSE +0 -0
  13. {kryptools-1.5 → kryptools-1.5.1}/README.md +0 -0
  14. {kryptools-1.5 → kryptools-1.5.1}/kryptools/Zmod.py +0 -0
  15. {kryptools-1.5 → kryptools-1.5.1}/kryptools/aes.py +0 -0
  16. {kryptools-1.5 → kryptools-1.5.1}/kryptools/blockcipher.py +0 -0
  17. {kryptools-1.5 → kryptools-1.5.1}/kryptools/conway_polynomials.py +0 -0
  18. {kryptools-1.5 → kryptools-1.5.1}/kryptools/des.py +0 -0
  19. {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp.py +0 -0
  20. {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_bsgs.py +0 -0
  21. {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_ic.py +0 -0
  22. {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_qs.py +0 -0
  23. {kryptools-1.5 → kryptools-1.5.1}/kryptools/dlp_rho.py +0 -0
  24. {kryptools-1.5 → kryptools-1.5.1}/kryptools/ec.py +0 -0
  25. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor.py +0 -0
  26. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_dix.py +0 -0
  27. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_ecm.py +0 -0
  28. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_fmt.py +0 -0
  29. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_pm1.py +0 -0
  30. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_qs.py +0 -0
  31. {kryptools-1.5 → kryptools-1.5.1}/kryptools/factor_rho.py +0 -0
  32. {kryptools-1.5 → kryptools-1.5.1}/kryptools/intfuncs.py +0 -0
  33. {kryptools-1.5 → kryptools-1.5.1}/kryptools/la.py +0 -0
  34. {kryptools-1.5 → kryptools-1.5.1}/kryptools/lat.py +0 -0
  35. {kryptools-1.5 → kryptools-1.5.1}/kryptools/nt.py +0 -0
  36. {kryptools-1.5 → kryptools-1.5.1}/kryptools/primes.py +0 -0
  37. {kryptools-1.5 → kryptools-1.5.1}/kryptools/sha1.py +0 -0
  38. {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/SOURCES.txt +0 -0
  39. {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/dependency_links.txt +0 -0
  40. {kryptools-1.5 → kryptools-1.5.1}/kryptools.egg-info/top_level.txt +0 -0
  41. {kryptools-1.5 → kryptools-1.5.1}/setup.cfg +0 -0
  42. {kryptools-1.5 → kryptools-1.5.1}/tests/test_aes.py +0 -0
  43. {kryptools-1.5 → kryptools-1.5.1}/tests/test_code.py +0 -0
  44. {kryptools-1.5 → kryptools-1.5.1}/tests/test_des.py +0 -0
  45. {kryptools-1.5 → kryptools-1.5.1}/tests/test_dlog.py +0 -0
  46. {kryptools-1.5 → kryptools-1.5.1}/tests/test_ec.py +0 -0
  47. {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor.py +0 -0
  48. {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor_fmt.py +0 -0
  49. {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor_pm1.py +0 -0
  50. {kryptools-1.5 → kryptools-1.5.1}/tests/test_factor_qs.py +0 -0
  51. {kryptools-1.5 → kryptools-1.5.1}/tests/test_intfuncs.py +0 -0
  52. {kryptools-1.5 → kryptools-1.5.1}/tests/test_keccak.py +0 -0
  53. {kryptools-1.5 → kryptools-1.5.1}/tests/test_la.py +0 -0
  54. {kryptools-1.5 → kryptools-1.5.1}/tests/test_lat.py +0 -0
  55. {kryptools-1.5 → kryptools-1.5.1}/tests/test_nt.py +0 -0
  56. {kryptools-1.5 → kryptools-1.5.1}/tests/test_primes.py +0 -0
  57. {kryptools-1.5 → kryptools-1.5.1}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.5
3
+ Version: 1.5.1
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  License: MIT
@@ -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
- x = Poly([ -y(self.alpha**(-j)) for j in range(self.n) ])
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([[ x[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
314
- b = [ -x[self.k + t + l ] for l in range(t) ]
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.hash_length = n // 8 # in bytes
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.hash_length)
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.hash_length)
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
- #from .factor import factorint
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
- zero = 0 * self.coeff[0]
40
- if not type(x) == type(zero):
41
- raise ValueError("Evaluation point must be an element of the field.")
42
- return sum((c * x**j for j, c in enumerate(self.coeff)), start = zero)
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 = False):
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 = True) + "$"
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("The polynomial does not seem to be over a known finite field.")
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("The polynomial does not seem to be over a finite field.")
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__([ s + o for s, o in zip(self.coeff, other.coeff) ] + other.coeff[ls:], modulus=modulus)
176
- return self.__class__([ s + o for s, o in zip(self.coeff, other.coeff) ] + self.coeff[lo:], modulus=modulus)
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 = [ sum((self.coeff[j] * other.coeff[k - j] for j in range(max(0, k - lo + 1), min(ls, k + 1))), start=zero) for k in range(ls + lo - 1) ]
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("Cannot invert polynomials without modulus.")
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("Cannot invert polynomials without modulus.")
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("Cannot divide polynomials without modulus.")
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("Coefficients cannot be converted to bits.")
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] # "* 1" is here to make sure we get a copy
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 # "* 1" is here to make sure we get a copy
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): # strip leading zeros
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(f"{other} must be a list of coefficients or a polynomial.")
334
- other = Poly(other, ring = ring)
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: {other} must be a polynomial.")
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: {other} must be a polynomial.")
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 = [ j * self.coeff[j] for j in range(1, l) ]
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 # x^pr will be the p'th root of x
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 { self / self.coeff[-1]: 1}
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 = [ zero ] * (d + 1)
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
- xq = Poly(q * [zero] + [one], modulus = self.coeff) # x^q modulo self to keep the polynomials small
466
+ x = Poly([zero, one], modulus=self.coeff) # x
467
+ b = x
444
468
  k = 1
445
469
  while k <= w.degree()//2:
446
- if k == 1:
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 = True)
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 = True)
492
+ return self.distinct_degree_factors(test=True)
472
493
 
473
- def equal_degree_factors(self ,k:int) -> list:
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(f"The polynomial is no product of irreducibel polynomials of degree {k}.")
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 = [ self ]
510
+
511
+ factors = [self]
490
512
  while len(factors) < r:
491
- g = Poly([randint(0,q-1) for _ in range(d+1)], ring = field, modulus = self)
492
- if p == 2: # Gathen-Shoup
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 **= qk
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(): # irreducible
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
- def lagrange_interpolation(x_coordinates:list, y_coordinates:list) -> "Poly":
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('List of x and y values must be nonempty of equal length.')
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 = one)
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 / prod([x -xx for xx in x_coordinates if xx != x], start = one)) * L
577
+ poly += (y /
578
+ prod([x - xx for xx in x_coordinates if xx != x], start=one)) * L
553
579
  return poly
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.5
3
+ Version: 1.5.1
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "1.5"
3
+ version = "1.5.1"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -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 (gf(a) + gf(b)).poly() == gf(a).poly() + gf(b).poly()
24
- assert (gf(a) - gf(b)).poly() == gf(a).poly() - gf(b).poly()
25
- assert (gf(a) * gf(b)).poly() == gf(a).poly() * gf(b).poly()
26
- if not (b):
27
- continue
28
- assert (gf(a) / gf(b)).poly() == gf(a).poly() / gf(b).poly()
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 test_Zmod_generator():
55
+ def test_GF2_generator():
57
56
  for n in range(1, 9):
58
57
  gf = GF2(n)
59
58
  a = gf.generator()
@@ -89,7 +89,6 @@ def test_ZmodP_order():
89
89
 
90
90
  def test_ZmodP_sqrt():
91
91
  for n in range(1,100):
92
- print(n)
93
92
  ring = Zmod(n)
94
93
  one = ring(1)
95
94
  roots = {} # compute a dict of all roots
@@ -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
- Z_5 = Zmod(5)
15
- p.map(Z_5)
16
- q.map(Z_5)
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), 11]]:
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([randint(0,order-1) for _ in range(t)], ring =gf)
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