kryptools 0.9.7__tar.gz → 0.9.9__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 (38) hide show
  1. {kryptools-0.9.7 → kryptools-0.9.9}/PKG-INFO +3 -3
  2. {kryptools-0.9.7 → kryptools-0.9.9}/README.md +1 -1
  3. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/Zmod.py +9 -11
  4. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/__init__.py +6 -0
  5. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/ec.py +5 -1
  6. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor.py +1 -1
  7. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor_ecm.py +0 -1
  8. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor_fmt.py +1 -1
  9. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor_pm1.py +1 -1
  10. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor_rho.py +0 -1
  11. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/la.py +31 -16
  12. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/nt.py +3 -1
  13. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/poly.py +44 -63
  14. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/primes.py +3 -3
  15. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools.egg-info/PKG-INFO +3 -3
  16. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools.egg-info/SOURCES.txt +10 -1
  17. {kryptools-0.9.7 → kryptools-0.9.9}/pyproject.toml +2 -2
  18. kryptools-0.9.9/tests/test_Zmod.py +51 -0
  19. kryptools-0.9.9/tests/test_dlog.py +18 -0
  20. kryptools-0.9.9/tests/test_ec.py +25 -0
  21. kryptools-0.9.9/tests/test_factor.py +18 -0
  22. kryptools-0.9.9/tests/test_la.py +15 -0
  23. kryptools-0.9.9/tests/test_lat.py +9 -0
  24. kryptools-0.9.9/tests/test_nt.py +38 -0
  25. kryptools-0.9.9/tests/test_poly.py +15 -0
  26. kryptools-0.9.9/tests/test_primes.py +27 -0
  27. {kryptools-0.9.7 → kryptools-0.9.9}/LICENSE +0 -0
  28. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/dlp.py +0 -0
  29. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/dlp_bsgs.py +0 -0
  30. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/dlp_ic.py +0 -0
  31. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/dlp_qs.py +0 -0
  32. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/dlp_rho.py +0 -0
  33. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor_dix.py +0 -0
  34. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/factor_qs.py +0 -0
  35. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools/lat.py +0 -0
  36. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools.egg-info/dependency_links.txt +0 -0
  37. {kryptools-0.9.7 → kryptools-0.9.9}/kryptools.egg-info/top_level.txt +0 -0
  38. {kryptools-0.9.7 → kryptools-0.9.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.9.7
3
+ Version: 0.9.9
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  Project-URL: Homepage, https://github.com/teschlg/kryptools
@@ -9,7 +9,7 @@ Project-URL: Docs, https://github.com/teschlg/kryptools/tree/main/doc
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
12
+ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
 
@@ -29,7 +29,7 @@ The tools contained are:
29
29
  * number theory: sqrt modulo primes, Jacobi/Legendre symbol, Chinese Remainder Theorem, continued fractions, etc.
30
30
  * primes: Sieve of Erathostenes, primality tests, generation of random safe/strong primes
31
31
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
32
- * integer factorization (Fermat, Pollard p-1, Lenstra's ECM, Dixon, basic quadratic sieve)
32
+ * integer factorization (Fermat, Pollard p-1, Pollard rho, Lenstra's ECM, Dixon, basic quadratic sieve)
33
33
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
34
34
  * linear algebra: Hermite normal form, Gram-Schmidt
35
35
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
@@ -14,7 +14,7 @@ The tools contained are:
14
14
  * number theory: sqrt modulo primes, Jacobi/Legendre symbol, Chinese Remainder Theorem, continued fractions, etc.
15
15
  * primes: Sieve of Erathostenes, primality tests, generation of random safe/strong primes
16
16
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
17
- * integer factorization (Fermat, Pollard p-1, Lenstra's ECM, Dixon, basic quadratic sieve)
17
+ * integer factorization (Fermat, Pollard p-1, Pollard rho, Lenstra's ECM, Dixon, basic quadratic sieve)
18
18
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
19
19
  * linear algebra: Hermite normal form, Gram-Schmidt
20
20
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
@@ -30,7 +30,11 @@ class Zmod:
30
30
  self.group_order = 0
31
31
  self.factors = {} # factoring of the group order
32
32
 
33
- def __call__(self, x: int):
33
+ def __call__(self, x: int|list|tuple):
34
+ if isinstance(x, list):
35
+ return [ZmodPoint(xx, self) for xx in x]
36
+ elif isinstance(x, tuple):
37
+ return (ZmodPoint(xx, self) for xx in x)
34
38
  return ZmodPoint(x, self)
35
39
 
36
40
  def __eq__(self, other):
@@ -76,7 +80,7 @@ class Zmod:
76
80
 
77
81
  def star(self) -> int:
78
82
  """Return a list of all elements of the group Z_n^*."""
79
- elements = [ 1 ]
83
+ elements = [ self(1) ]
80
84
  for a in range(2, self.n):
81
85
  if gcd(a, self.n) > 1:
82
86
  continue
@@ -119,9 +123,7 @@ class ZmodPoint:
119
123
  return NotImplemented
120
124
 
121
125
  def __radd__(self, scalar: int) -> "ZmodPoint":
122
- if isinstance(scalar, int):
123
- return self.__class__(scalar + self.x, self.ring)
124
- return NotImplemented
126
+ return self + scalar
125
127
 
126
128
  def __neg__(self) -> "ZmodPoint":
127
129
  return self.__class__(-self.x, self.ring)
@@ -139,9 +141,7 @@ class ZmodPoint:
139
141
  return NotImplemented
140
142
 
141
143
  def __rsub__(self, scalar: int) -> "ZmodPoint":
142
- if isinstance(scalar, int):
143
- return self.__class__(scalar - self.x, self.ring)
144
- return NotImplemented
144
+ return (- self) + scalar
145
145
 
146
146
  def __mul__(self, other: "ZmodPoint") -> "ZmodPoint":
147
147
  if isinstance(other, self.__class__):
@@ -153,9 +153,7 @@ class ZmodPoint:
153
153
  return NotImplemented
154
154
 
155
155
  def __rmul__(self, scalar: int) -> "ZmodPoint":
156
- if isinstance(scalar, int):
157
- return self.__class__(scalar * self.x, self.ring)
158
- return NotImplemented
156
+ return self * scalar
159
157
 
160
158
  def __truediv__(self, other: "ZmodPoint") -> "ZmodPoint":
161
159
  if isinstance(other, self.__class__):
@@ -2,6 +2,12 @@
2
2
  Implemenation of same basic algorithms used in cryptography.
3
3
  """
4
4
 
5
+ __author__ = "Gerald Teschl"
6
+ __copyright__ = "Copyright 2024, Gerald Teschl"
7
+ __license__ = "MIT License"
8
+ __version__ = "0.9.9"
9
+ __email__ = "Gerald.Teschl@univie.ac.at"
10
+
5
11
  from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, order, crt
6
12
  from .primes import sieve_eratosthenes, prime_pi, is_prime, next_prime, previous_prime, random_prime, random_strongprime, is_safeprime, random_safeprime, is_blumprime, random_blumprime, miller_rabin_test, lucas_test
7
13
  from .factor import factorint, divisors
@@ -71,6 +71,10 @@ class EC_Weierstrass():
71
71
  return False
72
72
 
73
73
  def __contains__(self, P: "ECPoint") -> bool:
74
+ if not isinstance(P, ECPoint) or P.curve != self:
75
+ return False
76
+ if P.x is None and P.y is None:
77
+ return True
74
78
  return P.y**2 == P.x**3 + self.a * P.x + self.b
75
79
 
76
80
  def info(self):
@@ -268,7 +272,7 @@ class ECPoint:
268
272
  else:
269
273
  y = (curve.p - y1) % curve.p
270
274
  self.y = curve.gf(y)
271
- if not self in curve:
275
+ if self not in curve:
272
276
  raise ValueError("Point not on curve!")
273
277
 
274
278
  def __repr__(self):
@@ -116,7 +116,7 @@ def factorint(n: int, verbose: int = 0, trial_bnd: int = 2500) -> dict:
116
116
  B2 += B2 & 1
117
117
  num_curves *= 2
118
118
  D = isqrt(B2)
119
-
119
+
120
120
  if verbose:
121
121
  print(f"Round {round+1} (B1={B1})")
122
122
 
@@ -177,4 +177,3 @@ def factor_ecm(n: int, B1: int|None = None, B2: int|None = None, curves: int = 1
177
177
 
178
178
  if verbose > 1:
179
179
  print(f"\nNo factor after trying {curves} curves.")
180
-
@@ -2,7 +2,7 @@
2
2
  Integer factorization: Fermat's method
3
3
  """
4
4
 
5
- from math import isqrt, log10, floor, log10
5
+ from math import isqrt, floor, log10
6
6
 
7
7
 
8
8
  def factor_fermat(n: int, maxsteps: int|None = None, verbose: int = 0) -> list:
@@ -59,7 +59,7 @@ def factor_pm1(n: int, B1: int|None = None, B2: int|None = None, x: int = 2, pm1
59
59
  y = pow(x, stage_two_deltas[0], n)
60
60
  D = (y - 1) % n
61
61
  for k, d in enumerate(stage_two_deltas[1:]):
62
- if not d in saved:
62
+ if d not in saved:
63
63
  saved[d] = pow(x, d, n)
64
64
  y = y * saved[d] % n
65
65
  D = D * (y - 1) % n
@@ -55,4 +55,3 @@ def factor_rho(n: int, x: int = 2, maxinit: int = 10, maxiter: int = 1000, brent
55
55
  x = randint(1, n - 1) # new x_0
56
56
  if verbose > 1:
57
57
  print("")
58
-
@@ -23,6 +23,9 @@ class Matrix:
23
23
  self.matrix = matrix
24
24
  self.cols = len(matrix[0])
25
25
  self.rows = len(matrix)
26
+ for i in range(1, self.rows):
27
+ if len(matrix[i]) != self.cols:
28
+ raise ValueError("All matrix rows must have equal length!")
26
29
  if ring:
27
30
  self.map(ring)
28
31
 
@@ -62,11 +65,11 @@ class Matrix:
62
65
  cols = i
63
66
  else:
64
67
  cols = range(self.cols)[j]
65
- return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
68
+ return self.__class__([[self.matrix[i][j] for j in cols] for i in rows])
66
69
  if isinstance(item, int):
67
70
  i, j = divmod(item, self.cols)
68
71
  return self.matrix[i][j]
69
- return Matrix([self.matrix[k // self.cols][k % self.cols] for k in range(self.cols * self.rows)[item]])
72
+ return self.__class__([self.matrix[k // self.cols][k % self.cols] for k in range(self.cols * self.rows)[item]])
70
73
 
71
74
  def __setitem__(self, item, value):
72
75
  if isinstance(item, tuple):
@@ -89,15 +92,24 @@ class Matrix:
89
92
  for i, ii in zip(cols,range(len(cols))):
90
93
  for j, jj in zip(rows,range(len(rows))):
91
94
  self.matrix[j][i] = value[jj,ii]
92
- return
93
- i, j = divmod(item, self.cols)
94
- self.matrix[i][j] = value
95
+ elif isinstance(item, int):
96
+ i, j = divmod(item, self.cols)
97
+ self.matrix[i][j] = value
98
+ else:
99
+ for k in range(self.cols * self.rows)[item]:
100
+ i, j = divmod(k, self.cols)
101
+ self.matrix[i][j] = value[k]
95
102
 
96
103
  def __eq__(self, other):
97
104
  if not isinstance(other, self.__class__):
98
105
  return False
99
- return self.matrix == other.matrix
106
+ if self.rows != other.rows or self.cols != other.cols:
107
+ return False
108
+ return any([ self[i] == other[i] for i in range(self.rows * self.cols) ])
100
109
 
110
+ def __bool__(self):
111
+ return any([ bool(self[i]) for i in range(self.rows * self.cols) ])
112
+
101
113
  def map(self, func):
102
114
  "Apply a function to all elements in place."
103
115
  for row in self.matrix:
@@ -156,14 +168,14 @@ class Matrix:
156
168
  if isinstance(other, Matrix):
157
169
  if other.cols != self.cols or other.rows != self.rows:
158
170
  raise NotImplementedError("Matrix dimensions do not match!")
159
- return Matrix([ [ x1 + y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
171
+ return self.__class__([ [ x1 + y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
160
172
  return NotImplemented
161
173
 
162
174
  def __sub__(self, other) -> "Matrix":
163
175
  if isinstance(other, Matrix):
164
176
  if other.cols != self.cols or other.rows != self.rows:
165
177
  raise NotImplementedError("Matrix dimensions do not match!")
166
- return Matrix([ [ x1 - y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
178
+ return self.__class__([ [ x1 - y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
167
179
  return NotImplemented
168
180
 
169
181
  def __neg__(self) -> "Matrix":
@@ -175,13 +187,13 @@ class Matrix:
175
187
  def __mul__(self, other) -> "Matrix":
176
188
  if isinstance(other, Matrix):
177
189
  return self.multiply(other)
178
- if isinstance(other, Number) or type(other) == type(self.matrix[0][0]):
179
- return Matrix([ [item * other for item in row] for row in self.matrix ])
190
+ if isinstance(other, Number) or type(other) is type(self.matrix[0][0]):
191
+ return self.__class__([ [item * other for item in row] for row in self.matrix ])
180
192
  return NotImplemented
181
193
 
182
194
  def __rmul__(self, other) -> "Matrix":
183
- if isinstance(other, Number) or type(other) == type(self.matrix[0][0]):
184
- return Matrix([ [item * other for item in row] for row in self.matrix ])
195
+ if isinstance(other, Number) or type(other) is type(self.matrix[0][0]):
196
+ return self.__class__([ [item * other for item in row] for row in self.matrix ])
185
197
  return NotImplemented
186
198
 
187
199
  def rref(self) -> "Matrix":
@@ -242,7 +254,7 @@ class Matrix:
242
254
  if self.rows != self.cols:
243
255
  raise ValueError("Matrix must be square!")
244
256
  n = self.cols
245
- MM = Matrix([[0 for _ in range(2*n)] for _ in range(n)])
257
+ MM = self.__class__([[0 for _ in range(2*n)] for _ in range(n)])
246
258
  for i in range(n):
247
259
  MM[i,n+i] = 1
248
260
  MM[:,0:n] = self
@@ -271,7 +283,7 @@ class Matrix:
271
283
  zero = 0 * self[0]
272
284
  except:
273
285
  zero = 0
274
- return Matrix([[ zero for j in range(n) ] for i in range(m) ])
286
+ return self.__class__([[ zero for j in range(n) ] for i in range(m) ])
275
287
 
276
288
  def eye(self, m: int = None, n: int = None):
277
289
  "Returns an identity matrix of the same dimension."
@@ -287,8 +299,11 @@ class Matrix:
287
299
  zero = 0 * self[0]
288
300
  except:
289
301
  zero = 0
290
- one = 1 + zero
291
- return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
302
+ try:
303
+ one = zero**0
304
+ except:
305
+ one = 1
306
+ return self.__class__([[ delta(i, j) for j in range(n) ] for i in range(m) ])
292
307
 
293
308
 
294
309
  def zeros(m: int, n: int = None, zero = 0) -> "Matrix":
@@ -143,6 +143,8 @@ def carmichael_lambda(n: int) -> int:
143
143
  """Carmichael's lambda function of `n`."""
144
144
  if not isinstance(n, int) or n < 1:
145
145
  raise ValueError(f"{n} is not a positive integer")
146
+ if n == 1:
147
+ return 1
146
148
  k = factorint(n)
147
149
  lam_all = [] # values corresponding to the prime factors
148
150
  for p in k:
@@ -224,7 +226,7 @@ def crt(a: list[int], m: list[int], coprime = True) -> int:
224
226
  if g == 1: # moduli are coprime, nothing to be done
225
227
  continue
226
228
  if (a[i] - a[j]) % g:
227
- raise ValueError(f"Congruences not solvable!")
229
+ raise ValueError("Congruences not solvable!")
228
230
  for p, k in factorint(g).items():
229
231
  p = p**k # remove this factor from one of the equations
230
232
  if gcd(m[i] // p, p) == 1:
@@ -55,8 +55,9 @@ class Poly:
55
55
  return "x"
56
56
  return "x^" + str(i)
57
57
 
58
- if len(self.coeff) == 1:
58
+ if not self:
59
59
  return str(self.coeff[0])
60
+ one = self.coeff[0]**0
60
61
  plus = ""
61
62
  tmp = ""
62
63
  coef_range = range(len(self.coeff))
@@ -66,12 +67,12 @@ class Poly:
66
67
  s = self.coeff[i]
67
68
  if not s:
68
69
  continue
69
- if not s - 1 and i != 0:
70
+ if not s - one and i != 0:
70
71
  tmp += plus + prx(i)
71
72
  plus = " + "
72
73
  continue
73
74
  try:
74
- if s == -1 and i != 0:
75
+ if not s + one and i != 0:
75
76
  if plus:
76
77
  plus = " "
77
78
  tmp += plus + "- " + prx(i)
@@ -96,7 +97,7 @@ class Poly:
96
97
  def __eq__(self, other):
97
98
  if not isinstance(other, self.__class__):
98
99
  return False
99
- return self.coeff == other.coeff
100
+ return not bool(self - other)
100
101
 
101
102
  def __bool__(self):
102
103
  return bool(self.degree()) or bool(self.coeff[0])
@@ -116,7 +117,18 @@ class Poly:
116
117
  def _check_type(self, other):
117
118
  return isinstance(other, int) or (isinstance(other, Number) and isinstance(self.coeff[0], Number)) or type(other) == type(self.coeff[0])
118
119
 
120
+ def _guess_ring(self):
121
+ zero = 0 * self.coeff[0]
122
+ try:
123
+ ring = type(zero)
124
+ one = ring(1)
125
+ except:
126
+ ring = None
127
+ one = zero**0 # zero + 1
128
+ return zero, one, ring
129
+
119
130
  def __add__(self, other: "Poly") -> "Poly":
131
+ zero = 0 * self.coeff[0]
120
132
  if not isinstance(other, self.__class__):
121
133
  if self._check_type(other):
122
134
  tmp = self.coeff[:]
@@ -125,11 +137,11 @@ class Poly:
125
137
  return NotImplemented
126
138
  ls, lo = len(self.coeff), len(other.coeff)
127
139
  if ls < lo:
128
- scoeff = self.coeff + (lo - ls) * [0]
140
+ scoeff = self.coeff + (lo - ls) * [zero]
129
141
  else:
130
142
  scoeff = self.coeff
131
143
  if ls > lo:
132
- ocoeff = other.coeff + (ls - lo) * [0]
144
+ ocoeff = other.coeff + (ls - lo) * [zero]
133
145
  else:
134
146
  ocoeff = other.coeff
135
147
  modulus = self.modulus
@@ -138,12 +150,7 @@ class Poly:
138
150
  return self.__class__([s + o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
139
151
 
140
152
  def __radd__(self, other: "Poly") -> "Poly":
141
- if not isinstance(other, self.__class__):
142
- if self._check_type(other):
143
- tmp = self.coeff[:]
144
- tmp[0] += other
145
- return self.__class__(tmp, modulus=self.modulus)
146
- return NotImplemented
153
+ return self + other
147
154
 
148
155
  def __neg__(self) -> "Poly":
149
156
  return Poly([-s for s in self.coeff], modulus=self.modulus)
@@ -152,57 +159,33 @@ class Poly:
152
159
  return self
153
160
 
154
161
  def __sub__(self, other: "Poly") -> "Poly":
155
- if not isinstance(other, self.__class__):
156
- if self._check_type(other):
157
- tmp = self.coeff[:]
158
- tmp[0] -= other
159
- return self.__class__(tmp, modulus=self.modulus)
160
- return NotImplemented
161
- ls, lo = len(self.coeff), len(other.coeff)
162
- if ls < lo:
163
- scoeff = self.coeff + (lo - ls) * [0]
164
- else:
165
- scoeff = self.coeff
166
- if ls > lo:
167
- ocoeff = other.coeff + (ls - lo) * [0]
168
- else:
169
- ocoeff = other.coeff
170
- modulus = self.modulus
171
- if not modulus and other.modulus:
172
- modulus = other.modulus
173
- return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus = modulus)
162
+ return self + (- other)
174
163
 
175
164
  def __rsub__(self, other: "Poly") -> "Poly":
176
- if not isinstance(other, self.__class__):
177
- if self._check_type(other):
178
- tmp = self.coeff[:]
179
- tmp[0] -= other
180
- return self.__class__(tmp, modulus=self.modulus)
181
- return NotImplemented
165
+ return (- self) + other
182
166
 
183
167
  def __mul__(self, other: "Poly") -> "Poly":
184
168
  if not isinstance(other, self.__class__):
185
169
  if self._check_type(other):
186
170
  return Poly([other * s for s in self.coeff], modulus = self.modulus)
187
171
  return NotImplemented
172
+ zero = 0 * self.coeff[0]
188
173
  ls, lo = len(self.coeff), len(other.coeff)
189
- coeff = [0] * (ls + lo - 1)
174
+ coeff = [None] * (ls + lo - 1)
190
175
  for k in range(ls + lo - 1):
191
176
  coeff[k] = sum(
192
177
  [
193
178
  self.coeff[j] * other.coeff[k - j]
194
179
  for j in range(max(0, k - lo + 1), min(ls, k + 1))
195
180
  ]
196
- )
181
+ , start = zero)
197
182
  modulus = self.modulus
198
183
  if not modulus and other.modulus:
199
184
  modulus = other.modulus
200
185
  return self.__class__(coeff, modulus = modulus)
201
186
 
202
187
  def __rmul__(self, other) -> "Poly":
203
- if self._check_type(other):
204
- return self.__class__([other * s for s in self.coeff], modulus=self.modulus)
205
- return NotImplemented
188
+ return self * other
206
189
 
207
190
  def __truediv__(self, other) -> "Poly":
208
191
  if self._check_type(other):
@@ -221,8 +204,7 @@ class Poly:
221
204
  def __pow__(self, j: int) -> "Poly":
222
205
  if not isinstance(j, int):
223
206
  return NotImplemented
224
- zero = 0 * self.coeff[0]
225
- one = zero + 1
207
+ one = self.coeff[0]**0
226
208
  res = self.__class__([one], modulus=self.modulus)
227
209
  if j < 0:
228
210
  if not self.modulus:
@@ -256,39 +238,39 @@ class Poly:
256
238
 
257
239
  def divmod(self, other: "Poly") -> ("Poly", "Poly"):
258
240
  "Polynom division with remainder."
241
+ zero, one, ring = self._guess_ring()
259
242
  if isinstance(other, list):
260
- other = self.__class__(other)
243
+ other = self.__class__(other , ring = ring)
261
244
  elif not isinstance(other, self.__class__):
262
245
  raise NotImplementedError(f"Cannot divide {self} and {other}.")
263
246
  if not other:
264
247
  raise ValueError(f"{other} must be nonzero.")
265
248
  sd, od = self.degree(), other.degree()
266
249
  if sd < od:
267
- return self.__class__([0]), self
268
- div = [0] * (sd - od + 1)
250
+ return self.__class__([zero]), self
251
+ div = [zero] * (sd - od + 1)
269
252
  lco = other.coeff[-1]
270
- if bool(lco - 1):
271
- tmp = 1 / lco
253
+ if bool(lco - one):
254
+ tmp = one / lco
272
255
  oth = [c * tmp for c in other.coeff]
273
256
  rem = [c * tmp for c in self.coeff]
274
257
  else:
275
258
  oth = other.coeff
276
- rem = [c for c in self.coeff]
259
+ rem = [c * one for c in self.coeff] # "* 1" is here to make sure we get a copy
277
260
  for i in range(sd - od + 1):
278
- tmp = rem[sd - i]
261
+ tmp = rem[sd - i] * one # "* 1" is here to make sure we get a copy
279
262
  div[sd - od - i] = tmp
280
263
  for j in range(od + 1):
281
264
  rem[sd - i - j] -= tmp * oth[od - j]
282
- if bool(lco - 1):
265
+ if bool(lco - one):
283
266
  rem = [c * lco for c in rem]
284
- return self.__class__(div, modulus=self.modulus), self.__class__(
285
- rem, modulus=self.modulus
286
- )
267
+ return self.__class__(div, modulus=self.modulus), self.__class__(rem, modulus=self.modulus)
287
268
 
288
269
  def mod(self, other: "Poly") -> None:
289
270
  "Reduce with respect to a given polynomial."
271
+ one, ring = self._guess_ring()[1:]
290
272
  if isinstance(other, list):
291
- other = self.__class__(other)
273
+ other = self.__class__(other , ring = ring)
292
274
  elif not isinstance(other, self.__class__):
293
275
  raise NotImplementedError(f"Cannot divide {self} and {other}.")
294
276
  if not other:
@@ -297,13 +279,13 @@ class Poly:
297
279
  if sd < od:
298
280
  return self
299
281
  lco = other.coeff[-1]
300
- if bool(lco - 1):
301
- tmp = 1 / lco
282
+ if bool(lco - one):
283
+ tmp = one / lco
302
284
  oth = [c * tmp for c in other.coeff]
303
285
  else:
304
286
  oth = other.coeff
305
287
  for i in range(sd - od + 1):
306
- tmp = self.coeff[sd - i]
288
+ tmp = self.coeff[sd - i] * one # "* 1" is here to make sure we get a copy
307
289
  for j in range(od + 1):
308
290
  self.coeff[sd - i - j] -= tmp * oth[od - j]
309
291
  for i in range(len(self.coeff) - 1, 0, -1):
@@ -315,14 +297,13 @@ class Poly:
315
297
  "Inverse modulo a given polynomial."
316
298
  if not other:
317
299
  other = self.modulus
300
+ zero, one, ring = self._guess_ring()
318
301
  if isinstance(other, list):
319
- other = self.__class__(other)
302
+ other = self.__class__(other , ring = ring)
320
303
  elif not isinstance(other, self.__class__):
321
304
  raise NotImplementedError(f"Cannot invert {self} modulo {other}.")
322
305
  if not other:
323
306
  raise NotImplementedError(f"{other} must be nonzero.")
324
- zero = 0 * self.coeff[0]
325
- one = zero +1
326
307
  r0, r1 = other, self
327
308
  y0, y1 = self.__class__([zero], modulus=self.modulus), self.__class__([one], modulus=self.modulus)
328
309
  while r1:
@@ -331,7 +312,7 @@ class Poly:
331
312
  y0, y1 = y1, y0 - q * y1
332
313
  if r0.degree() != 0:
333
314
  raise ValueError(f"{self} is not invertible mod {other}.")
334
- tmp = 1 / r0[0]
315
+ tmp = one / r0[0]
335
316
  for i in range(len(y0)):
336
317
  y0.coeff[i] *= tmp
337
318
  return y0
@@ -72,7 +72,7 @@ def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
72
72
  if a == 0:
73
73
  continue
74
74
  b = pow(a, m, n)
75
- if b == 1 or b == n - 1:
75
+ if b in (1, n - 1):
76
76
  continue
77
77
  for _ in range(1, k):
78
78
  b = pow(b, 2, n)
@@ -154,7 +154,7 @@ def lucas_test(n: int) -> bool:
154
154
  return n == 2
155
155
  if perfect_square(n) is not None: # the search for D will not succeed in this case
156
156
  return False
157
-
157
+
158
158
  # write n = k 2^s - 1
159
159
  k = (n + 1) // 2
160
160
  s = 1
@@ -163,7 +163,7 @@ def lucas_test(n: int) -> bool:
163
163
  s += 1
164
164
 
165
165
  # Selfridge method for choosing D
166
-
166
+
167
167
  D = 5
168
168
  while True:
169
169
  g = gcd(abs(D), n)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.9.7
3
+ Version: 0.9.9
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  Project-URL: Homepage, https://github.com/teschlg/kryptools
@@ -9,7 +9,7 @@ Project-URL: Docs, https://github.com/teschlg/kryptools/tree/main/doc
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
12
+ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
 
@@ -29,7 +29,7 @@ The tools contained are:
29
29
  * number theory: sqrt modulo primes, Jacobi/Legendre symbol, Chinese Remainder Theorem, continued fractions, etc.
30
30
  * primes: Sieve of Erathostenes, primality tests, generation of random safe/strong primes
31
31
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
32
- * integer factorization (Fermat, Pollard p-1, Lenstra's ECM, Dixon, basic quadratic sieve)
32
+ * integer factorization (Fermat, Pollard p-1, Pollard rho, Lenstra's ECM, Dixon, basic quadratic sieve)
33
33
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
34
34
  * linear algebra: Hermite normal form, Gram-Schmidt
35
35
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
@@ -24,4 +24,13 @@ kryptools/primes.py
24
24
  kryptools.egg-info/PKG-INFO
25
25
  kryptools.egg-info/SOURCES.txt
26
26
  kryptools.egg-info/dependency_links.txt
27
- kryptools.egg-info/top_level.txt
27
+ kryptools.egg-info/top_level.txt
28
+ tests/test_Zmod.py
29
+ tests/test_dlog.py
30
+ tests/test_ec.py
31
+ tests/test_factor.py
32
+ tests/test_la.py
33
+ tests/test_lat.py
34
+ tests/test_nt.py
35
+ tests/test_poly.py
36
+ tests/test_primes.py
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "0.9.7"
3
+ version = "0.9.9"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
7
7
  description = "Implemenation of same basic algorithms used in cryptography."
8
8
  readme = "README.md"
9
- requires-python = ">=3.9"
9
+ requires-python = ">=3.10"
10
10
  classifiers = [
11
11
  "Programming Language :: Python :: 3",
12
12
  "License :: OSI Approved :: MIT License",
@@ -0,0 +1,51 @@
1
+ import pytest
2
+ from math import gcd
3
+ from kryptools import Zmod
4
+
5
+ def test_Zmod():
6
+ for n in [5, 6]:
7
+ Z_n = Zmod(n)
8
+ for a in range(n):
9
+ aa = Z_n(a)
10
+ bool(aa) == bool(a)
11
+ for i in range(4):
12
+ assert aa ** i == Z_n(pow(a, i, n))
13
+ if i == 0 or gcd(a, n) == 1:
14
+ assert aa ** -i == Z_n(pow(a, -i, n))
15
+ else:
16
+ with pytest.raises(ValueError):
17
+ aa ** -i
18
+ if gcd(a, n) == 1:
19
+ assert aa ** aa.order() == Z_n(1)
20
+ else:
21
+ with pytest.raises(ValueError):
22
+ aa.order()
23
+ for b in range(n):
24
+ bb = Z_n(b)
25
+ assert aa + bb == Z_n(a + b)
26
+ assert aa - bb == Z_n(a - b)
27
+ assert aa * bb == Z_n(a * b)
28
+ if gcd(b, n) == 1:
29
+ assert aa / bb == Z_n( a * pow(b, -1, n) )
30
+
31
+ Z_5 = Zmod(5)
32
+ assert Z_5(3).order() == 4
33
+ assert Z_5(1).is_generator() == False
34
+ assert Z_5(3).is_generator() == True
35
+ assert Z_5.generator(all = True) == Z_5([2, 3])
36
+ assert Z_5.star() == Z_5([1, 2, 3, 4])
37
+ assert [Z_5(i).sharp() for i in range(Z_5.n)] == [0, 1, 2, -2, -1]
38
+ assert [abs(Z_5(i)) for i in range(Z_5.n)] == [0, 1, 2, 2, 1]
39
+ assert str(Z_5(3)) == "3"
40
+ Z_5.short = False
41
+ assert str(Z_5(6)) == "1 (mod 5)"
42
+
43
+ Z_6 = Zmod(6)
44
+ assert Z_6(5).order() == 2
45
+ with pytest.raises(ValueError):
46
+ Z_6(3).order()
47
+ assert Z_6(1).is_generator() == False
48
+ assert Z_6(5).is_generator() == True
49
+ assert Z_6.generator(all = True) == Z_6([5])
50
+ assert Z_6.star() == Z_6([1, 5])
51
+ assert str(Z_6(3)) == "3"
@@ -0,0 +1,18 @@
1
+ import pytest
2
+ from random import randint, seed
3
+ from kryptools import dlog
4
+
5
+ seed(0)
6
+
7
+ with pytest.raises(ValueError):
8
+ dlog(1, 3, 10)
9
+
10
+ def test_dlogt():
11
+ for data in (
12
+ [557639, 278819, 2],
13
+ [24570203447, 12285101723, 2],
14
+ [28031135240181527, 14015567620090763, 2] ):
15
+ p, m, a = data # m is the order of a in Z_p
16
+ x = randint(2, m - 1)
17
+ b = pow(a, x, p)
18
+ assert dlog(a, b, p) == x
@@ -0,0 +1,25 @@
1
+ import pytest
2
+ from random import randint, seed
3
+ from kryptools import EC_Weierstrass
4
+ seed(0)
5
+
6
+
7
+ def test_EC():
8
+ ec = EC_Weierstrass(239, 3, 1)
9
+ O = ec(None, None) # point at infinity
10
+ assert O in ec
11
+ P = ec.random()
12
+ assert P in ec
13
+ Q = ec.random()
14
+ assert Q in ec
15
+ assert P + O == P
16
+ assert O + P == P
17
+ R = O
18
+ for i in range(5):
19
+ assert i * P == R
20
+ R += P
21
+ assert P + Q == Q + P
22
+ assert Q.order() * Q == O
23
+ k = randint(1, ec.order())
24
+ R = k * Q
25
+ assert R.dlog(Q) == k
@@ -0,0 +1,18 @@
1
+ import pytest
2
+ from kryptools import factorint, is_prime
3
+
4
+ def myprod(factors: dict) -> int:
5
+ n = 1
6
+ for p in factors:
7
+ assert is_prime(p)
8
+ n *= p**factors[p]
9
+ return n
10
+
11
+
12
+ def test_factorint():
13
+ assert factorint(0) == {0: 1}
14
+ assert factorint(1) == {}
15
+ for n in ( 1489576198567193874913874619387459183543154617315437135656,
16
+ 2**128 - 1,
17
+ 4521089809**7 ):
18
+ assert n == myprod(factorint(n))
@@ -0,0 +1,15 @@
1
+ import pytest
2
+ from fractions import Fraction
3
+ from kryptools import Matrix, Zmod
4
+
5
+
6
+ def test_Matrix():
7
+ M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 12]])
8
+ M.map(Fraction)
9
+ Mi = M.inv()
10
+ assert M * Mi == M.eye()
11
+
12
+ Z_11 = Zmod(11)
13
+ M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 12]], ring=Z_11)
14
+ Mi = M.inv()
15
+ assert M * Mi == M.eye()
@@ -0,0 +1,9 @@
1
+ import pytest
2
+ from fractions import Fraction
3
+ from kryptools import Matrix, gram_schmidt
4
+
5
+
6
+ def test_gram_schmidt():
7
+ V = Matrix([[5, 8], [0, 1]], ring=Fraction)
8
+ Vs, M = gram_schmidt(V)
9
+ assert V == Vs * M
@@ -0,0 +1,38 @@
1
+ import pytest
2
+ from kryptools import carmichael_lambda, euler_phi, moebius_mu
3
+
4
+ #https://oeis.org/A000010
5
+ OEIS_A000010 = [
6
+ 1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8, 12, 10, 22, 8, 20,
7
+ 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18, 24, 16, 40, 12, 42, 20, 24, 22,
8
+ 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36, 28, 58, 16, 60, 30, 36, 32, 48, 20,
9
+ 66, 32, 44 ]
10
+
11
+ def test_euler_phi():
12
+ for n in range(1, len(OEIS_A000010)+1):
13
+ assert euler_phi(n) == OEIS_A000010[n - 1]
14
+
15
+ #https://oeis.org/A002322
16
+ OEIS_A002322 = [
17
+ 1, 1, 2, 2, 4, 2, 6, 2, 6, 4, 10, 2, 12, 6, 4, 4, 16, 6, 18, 4, 6, 10, 22, 2, 20,
18
+ 12, 18, 6, 28, 4, 30, 8, 10, 16, 12, 6, 36, 18, 12, 4, 40, 6, 42, 10, 12, 22, 46,
19
+ 4, 42, 20, 16, 12, 52, 18, 20, 6, 18, 28, 58, 4, 60, 30, 6, 16, 12, 10, 66, 16,
20
+ 22, 12, 70, 6, 72, 36, 20, 18, 30, 12, 78, 4, 54 ]
21
+
22
+ def test_carmichael_lambda():
23
+ for n in range(1, len(OEIS_A002322)+1):
24
+ assert carmichael_lambda(n) == OEIS_A002322[n - 1]
25
+
26
+ #https://oeis.org/A008683
27
+ OEIS_A008683 = [
28
+ 1, -1, -1, 0, -1, 1, -1, 0, 0, 1, -1, 0, -1, 1, 1, 0, -1, 0, -1, 0, 1, 1, -1, 0,
29
+ 0, 1, 0, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, 1, 1, 0, -1, -1, -1, 0, 0, 1, -1, 0, 0,
30
+ 0, 1, 0, -1, 0, 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 1, -1, -1, 0, 1, -1, -1, 0, -1, 1,
31
+ 0, 0, 1, -1 ]
32
+
33
+ def test_moebius_mu():
34
+ for n in range(1, len(OEIS_A008683)+1):
35
+ assert moebius_mu(n) == OEIS_A008683[n - 1]
36
+
37
+
38
+
@@ -0,0 +1,15 @@
1
+ import pytest
2
+ from kryptools import Poly, Zmod
3
+
4
+
5
+ def test_Poly():
6
+ p = Poly([2, 0, 2])
7
+ q = Poly([1, 2, 3, 4])
8
+ d, m = q.divmod(p)
9
+ assert d * p + m == q
10
+
11
+ Z_5 = Zmod(5)
12
+ p.map(Z_5)
13
+ q.map(Z_5)
14
+ d, m = q.divmod(p)
15
+ assert d * p + m == q
@@ -0,0 +1,27 @@
1
+ import pytest
2
+ from kryptools import sieve_eratosthenes, prime_pi, next_prime, previous_prime
3
+
4
+
5
+ #https://oeis.org/A000040
6
+ OEIS_A000010 = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
7
+ 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163,
8
+ 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263,
9
+ 269, 271 ]
10
+
11
+ def test_sieve_eratosthenes():
12
+ for i, p in enumerate(sieve_eratosthenes(OEIS_A000010[-1])):
13
+ assert p == OEIS_A000010[i]
14
+ if i > 0:
15
+ assert previous_prime(p-1) == OEIS_A000010[i-1]
16
+ if i < len(OEIS_A000010) - 1:
17
+ assert next_prime(p+1) == OEIS_A000010[i+1]
18
+
19
+ #https://oeis.org/A000040
20
+ OEIS_A000720 = [ 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,
21
+ 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, 14, 14,
22
+ 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 18, 18, 18, 19,
23
+ 19, 19, 19, 20, 20, 21, 21, 21, 21, 21, 21 ]
24
+
25
+ def test_prime_pi():
26
+ for n in range(1, len(OEIS_A000720)+1):
27
+ assert prime_pi(n) == OEIS_A000720[n - 1]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes