kryptools 0.3__tar.gz → 0.5__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 (30) hide show
  1. {kryptools-0.3 → kryptools-0.5}/PKG-INFO +3 -3
  2. {kryptools-0.3 → kryptools-0.5}/README.md +1 -1
  3. kryptools-0.5/kryptools/Zmod.py +175 -0
  4. {kryptools-0.3 → kryptools-0.5}/kryptools/__init__.py +2 -2
  5. {kryptools-0.3 → kryptools-0.5}/kryptools/dlp.py +5 -4
  6. {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_qs.py +3 -3
  7. {kryptools-0.3 → kryptools-0.5}/kryptools/ec.py +56 -26
  8. {kryptools-0.3 → kryptools-0.5}/kryptools/factor.py +13 -16
  9. {kryptools-0.3 → kryptools-0.5}/kryptools/factor_ecm.py +2 -1
  10. kryptools-0.5/kryptools/factor_fmt.py +30 -0
  11. {kryptools-0.3 → kryptools-0.5}/kryptools/factor_pm1.py +1 -1
  12. {kryptools-0.3 → kryptools-0.5}/kryptools/factor_qs.py +4 -4
  13. {kryptools-0.3 → kryptools-0.5}/kryptools/la.py +27 -11
  14. {kryptools-0.3 → kryptools-0.5}/kryptools/lat.py +1 -0
  15. {kryptools-0.3 → kryptools-0.5}/kryptools/nt.py +15 -22
  16. {kryptools-0.3 → kryptools-0.5}/kryptools/poly.py +49 -29
  17. {kryptools-0.3 → kryptools-0.5}/kryptools/primes.py +49 -6
  18. {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/PKG-INFO +3 -3
  19. {kryptools-0.3 → kryptools-0.5}/pyproject.toml +2 -2
  20. kryptools-0.3/kryptools/Zmod.py +0 -115
  21. kryptools-0.3/kryptools/factor_fmt.py +0 -30
  22. {kryptools-0.3 → kryptools-0.5}/LICENSE +0 -0
  23. {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_bsgs.py +0 -0
  24. {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_ic.py +0 -0
  25. {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_rho.py +0 -0
  26. {kryptools-0.3 → kryptools-0.5}/kryptools/factor_dix.py +0 -0
  27. {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/SOURCES.txt +0 -0
  28. {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/dependency_links.txt +0 -0
  29. {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/top_level.txt +0 -0
  30. {kryptools-0.3 → kryptools-0.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.3
3
+ Version: 0.5
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.8
12
+ Requires-Python: >=3.9
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
 
@@ -27,7 +27,7 @@ The tools contained are:
27
27
  * number theory: sqrt modulo primes, crt, continued fractions, etc.
28
28
  * primes: Sieve of Erathostenes, primality tests
29
29
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
30
- * integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
30
+ * integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
31
31
  * linear algebra: Hermite normal form, Gram-Schmidt
32
32
  * lattices: Babai rounding/nearest plane, lattice reduction
33
33
 
@@ -12,7 +12,7 @@ The tools contained are:
12
12
  * number theory: sqrt modulo primes, crt, continued fractions, etc.
13
13
  * primes: Sieve of Erathostenes, primality tests
14
14
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
15
- * integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
15
+ * integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
16
16
  * linear algebra: Hermite normal form, Gram-Schmidt
17
17
  * lattices: Babai rounding/nearest plane, lattice reduction
18
18
 
@@ -0,0 +1,175 @@
1
+ """
2
+ Ring of intergers modulo `n`.
3
+ """
4
+
5
+ from math import gcd
6
+ from .factor import factorint
7
+
8
+
9
+ class Zmod:
10
+ """
11
+ Ring of intergers modulo `n`.
12
+
13
+ Example:
14
+
15
+ To define a finite Galois field modulo the prime 5 use
16
+ >>> gf=Zmod(5)
17
+
18
+ To declare 3 as an element of our Galois filed use
19
+ >>> gf(3)
20
+ 3 (mod 5)
21
+
22
+ The usual arithmetic operations are supported.
23
+ >>> gf(2) + gf(3)
24
+ 0 (mod 5)
25
+ """
26
+
27
+ def __init__(self, n: int, short: bool = True):
28
+ self.n = n
29
+ self.short = short
30
+ self.group_order = 0
31
+ self.factors = {} # factoring of the group order
32
+
33
+ def __call__(self, x: int):
34
+ return ZmodPoint(x, self)
35
+
36
+ def __eq__(self, other):
37
+ if isinstance(other, self.__class__):
38
+ return self.n == other.n
39
+ return False
40
+
41
+ def __contains__(self, other: "ZmodPoint") -> bool:
42
+ return isinstance(other, ZmodPoint) and self.n == other.ring.n
43
+
44
+ def order(self) -> int:
45
+ """Compute the order of the group Z_n^*."""
46
+ if self.group_order:
47
+ return self.group_order
48
+ # We compute euler_phi(n) and its factorization in one pass
49
+ for p, k in factorint(self.n).items(): # first factorize n
50
+ for pm, km in factorint(p - 1).items(): # factor p-1 and add the factors
51
+ if pm in self.factors:
52
+ self.factors[pm] += km
53
+ else:
54
+ self.factors[pm] = km
55
+ if k > 1: # if the multiplicity of of p is >1, then we need to add p**(k-1)
56
+ if p in self.factors:
57
+ self.factors[p] += k - 1
58
+ else:
59
+ self.factors[p] = k - 1
60
+ self.group_order = 1
61
+ for p, k in self.factors.items():
62
+ self.group_order *= p**k
63
+ return self.group_order
64
+
65
+
66
+ class ZmodPoint:
67
+ "Represents a point in the ring Zmod."
68
+
69
+ def __init__(self, x: int, ring: "Zmod"):
70
+ self.x = int(x) % ring.n
71
+ self.ring = ring
72
+
73
+ def __repr__(self):
74
+ if self.ring.short:
75
+ return str(self.x)
76
+ return f"{self.x} (mod {self.ring.n})"
77
+
78
+ def __eq__(self, other):
79
+ if not isinstance(other, self.__class__) or self.ring != other.ring:
80
+ return False
81
+ return self.x == other.x
82
+
83
+ def __bool__(self):
84
+ return self.x != 0
85
+
86
+ def __int__(self):
87
+ return self.x
88
+
89
+ def __hash__(self):
90
+ return hash(self.x)
91
+
92
+ def __add__(self, other: "ZmodPoint") -> "ZmodPoint":
93
+ if isinstance(other, self.__class__):
94
+ if self.ring != other.ring:
95
+ raise NotImplementedError("Cannot add elements from different rings.")
96
+ return self.__class__(self.x + other.x, self.ring)
97
+ if isinstance(other, int):
98
+ return self.__class__(self.x + other, self.ring)
99
+ return NotImplemented
100
+
101
+ def __radd__(self, scalar: int) -> "ZmodPoint":
102
+ if isinstance(scalar, int):
103
+ return self.__class__(scalar + self.x, self.ring)
104
+ return NotImplemented
105
+
106
+ def __neg__(self) -> "ZmodPoint":
107
+ return self.__class__(-self.x, self.ring)
108
+
109
+ def __sub__(self, other: "ZmodPoint") -> "ZmodPoint":
110
+ if isinstance(other, self.__class__):
111
+ if self.ring != other.ring:
112
+ raise NotImplementedError("Cannot subtract elements from different rings.")
113
+ return self.__class__(self.x - other.x, self.ring)
114
+ if isinstance(other, int):
115
+ return self.__class__(self.x - other, self.ring)
116
+ return NotImplemented
117
+
118
+ def __rsub__(self, scalar: int) -> "ZmodPoint":
119
+ if isinstance(scalar, int):
120
+ return self.__class__(scalar - self.x, self.ring)
121
+ return NotImplemented
122
+
123
+ def __mul__(self, other: "ZmodPoint") -> "ZmodPoint":
124
+ if isinstance(other, self.__class__):
125
+ if self.ring != other.ring:
126
+ raise NotImplementedError("Cannot multiply elements from different rings.")
127
+ return self.__class__(self.x * other.x, self.ring)
128
+ if isinstance(other, int):
129
+ return self.__class__(self.x * other, self.ring)
130
+ return NotImplemented
131
+
132
+ def __rmul__(self, scalar: int) -> "ZmodPoint":
133
+ if isinstance(scalar, int):
134
+ return self.__class__(scalar * self.x, self.ring)
135
+ return NotImplemented
136
+
137
+ def __truediv__(self, other: "ZmodPoint") -> "ZmodPoint":
138
+ if isinstance(other, self.__class__):
139
+ if self.ring != other.ring:
140
+ raise NotImplementedError("Cannot divide elements from different rings.")
141
+ return self.__class__(self.x * pow(other.x, -1, self.ring.n), self.ring)
142
+ return NotImplemented
143
+
144
+ def __rtruediv__(self, scalar: int) -> "ZmodPoint":
145
+ if isinstance(scalar, int):
146
+ return self.__class__(scalar * pow(self.x, -1, self.ring.n), self.ring)
147
+ return NotImplemented
148
+
149
+ def __pow__(self, scalar: int) -> "ZmodPoint":
150
+ if isinstance(scalar, int):
151
+ return self.__class__(pow(self.x, scalar, self.ring.n), self.ring)
152
+ return NotImplemented
153
+
154
+ def sharp(self):
155
+ "Returns a symmetric (w.r.t. 0) representative."
156
+ tmp = (self.ring.n - 1) // 2
157
+ return (self.x + tmp) % self.ring.n - tmp
158
+
159
+ def order(self) -> int:
160
+ """Compute the order of the point in the group Z_n^*."""
161
+ if self.x == 0 or gcd(self.x, self.ring.n) != 1:
162
+ raise ValueError(f"{self.x} and {self.ring.n} are not coprime!")
163
+ order = self.ring.order() # use euler_phi(n) as our current guess
164
+ for p, k in self.ring.factors.items():
165
+ for _ in range(k):
166
+ order_try = order // p
167
+ if pow(self.x, order_try, self.ring.n) == 1:
168
+ order = order_try
169
+ else:
170
+ break
171
+ return order
172
+
173
+ def is_generator(self):
174
+ """Test if the point is a generator of the group Z_n^*."""
175
+ return self.ring.order() == self.order()
@@ -2,8 +2,8 @@
2
2
  Implemenation of same basic algorithms used in cryptography.
3
3
  """
4
4
 
5
- from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
6
- from .primes import sieve_eratosthenes, isprime
5
+ from .nt import egcd, crt, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
6
+ from .primes import sieve_eratosthenes, is_prime, next_prime, random_prime, random_strongprime, is_safeprime, random_safeprime
7
7
  from .factor import factorint
8
8
  from .dlp import dlog
9
9
  from .ec import EC_Weierstrass
@@ -11,7 +11,7 @@ from .dlp_qs import dlog_qs
11
11
  from .factor import factorint
12
12
 
13
13
 
14
- def dlog_naive(a: int, b: int, n: int, m: int = None) -> int:
14
+ def dlog_naive(a: int, b: int, n: int, m: int = None) -> int|None:
15
15
  """Compute the discrete log_a(b) in Z_n of an element a of order m by exhaustive search."""
16
16
  a %= n
17
17
  b %= n
@@ -43,14 +43,14 @@ def _dlog_ph(a: int, b: int, n: int, q: int, k: int) -> int:
43
43
  for j in range(2, k + 1):
44
44
  aj = pow(a, q ** (k - j), n)
45
45
  bj = pow(b, q ** (k - j), n)
46
- yj = _dlog_switch(a1, bj * pow(aj, -xj, n) % n, n, q)
46
+ yj = _dlog_switch(a1, bj * pow(aj, -xj, n) % n, n, q) # pylint: disable=E1130
47
47
  if yj is None:
48
48
  return None
49
49
  xj = xj + q ** (j - 1) * yj % q**j
50
50
  return xj
51
51
 
52
52
 
53
- def dlog(a: int, b: int, n: int, m: int = None) -> int:
53
+ def dlog(a: int, b: int, n: int, m: int|None = None) -> int:
54
54
  """Compute the discrete log_a(b) in Z_n of an element a of order m using Pohlig-Hellman reduction."""
55
55
  a %= n
56
56
  b %= n
@@ -60,7 +60,8 @@ def dlog(a: int, b: int, n: int, m: int = None) -> int:
60
60
  mf = factorint(m)
61
61
  else:
62
62
  m, mf = order(a, n, True)
63
- assert pow(b, m, n) == 1, "DLP not solvable."
63
+ if pow(b, m, n) != 1:
64
+ raise ValueError("DLP not solvable.")
64
65
  # We first use Pohlig-Hellman to split m into powers of prime factors
65
66
  mm = []
66
67
  ll = []
@@ -227,7 +227,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
227
227
  if r == 0:
228
228
  roots = [ -(inv2 * aa) % p ] # one root
229
229
  else:
230
- roots = [ (inv2 * (r - aa)) % p, (inv2 * (-r - aa)) % p ] # two roots
230
+ roots = [ (inv2 * (r - aa)) % p, (inv2 * (-r - aa)) % p ] # two roots pylint: disable=E1130
231
231
  for r in roots:
232
232
  x = r # start value for x
233
233
  while x < max_j:
@@ -255,10 +255,10 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
255
255
  relation = find_relation(include_b)
256
256
  res = process_relation(relation)
257
257
  if res:
258
- return(res)
258
+ return res
259
259
 
260
260
  #
261
- # find the B-smooth numbers and add them to the system
261
+ # find the B-smooth numbers and add them to the system
262
262
  #
263
263
 
264
264
  for s in range(sieve_bound):
@@ -7,6 +7,7 @@ from random import randint
7
7
  from .factor import factorint
8
8
  from .nt import legendre_symbol, sqrt_mod, crt
9
9
  from .Zmod import Zmod
10
+ from .poly import Poly
10
11
 
11
12
  class EC_Weierstrass():
12
13
  """
@@ -39,6 +40,7 @@ class EC_Weierstrass():
39
40
  self.b = self.gf(b % p)
40
41
  self.group_order = order
41
42
  self.group_order_factors = None
43
+ self.psi_list = [ Poly([ 0 ], ring = self.gf ) ] # division polynomials
42
44
  self.short = False # display points in short format
43
45
  self.hex = False # display points as hex values in compressed format
44
46
 
@@ -139,6 +141,28 @@ class EC_Weierstrass():
139
141
  j = legendre_symbol(y2, self.p)
140
142
  return ECPoint(x, randint(0, 1), self, short = True)
141
143
 
144
+ def psi(self, n: int):
145
+ """The x-part of the n'th division polynomial."""
146
+
147
+ if len(self.psi_list) < 5:
148
+ self.psi_list = [ Poly([i], ring = self.gf) for i in range(3)]
149
+ self.psi_list += [ Poly([-self.a * self.a, 12 * self.b, 6 * self.a, 0, 3], ring = self.gf) ]
150
+ self.psi_list += [ Poly([-4 * self.a**3 - 32 * self.b * self.b, -16 * self.a * self.b, -20 * self.a * self.a, 80 * self.b, 20 * self.a, 0, 4], ring = self.gf) ]
151
+ if len(self.psi_list) < n + 1:
152
+ y2 = Poly([self.b, self.a, 0, 1], ring = self.gf)**2
153
+ ti = 1 / self.gf(2)
154
+ for m in range(len(self.psi_list), n + 1):
155
+ if m % 2: # odd
156
+ m = (m - 1) // 2
157
+ if m % 2:
158
+ self.psi_list += [ self.psi_list[m + 2] * self.psi_list[m]**3 - y2 * self.psi_list[m - 1] * self.psi_list[m + 1]**3]
159
+ else:
160
+ self.psi_list += [ y2 * self.psi_list[m + 2] * self.psi_list[m]**3 - self.psi_list[m - 1] * self.psi_list[m + 1]**3]
161
+ else: # even
162
+ m = m // 2
163
+ self.psi_list += [ ti * self.psi_list[m] * (self.psi_list[m + 2] * self.psi_list[m - 1]**2 - self.psi_list[m - 2] * self.psi_list[m + 1]**2) ]
164
+ return self.psi_list[n]
165
+
142
166
  def order(self, order: int = None) -> int:
143
167
  "Return the group order."
144
168
  if order:
@@ -296,7 +320,7 @@ class ECPoint:
296
320
  def __neg__(self) -> "ECPoint":
297
321
  if self.x is None or not self.y:
298
322
  return self
299
- return ECPoint(self.x, -self.y, self.curve)
323
+ return ECPoint(self.x, -self.y, self.curve) # pylint: disable=E1130
300
324
 
301
325
  def order(self) -> int:
302
326
  """Compute the order of an element."""
@@ -311,17 +335,23 @@ class ECPoint:
311
335
  break
312
336
  return order
313
337
 
314
- def dlog(Q, P: "ECPoint") -> int:
338
+ def psi(self, n: int):
339
+ """Value of the n'th division polynomial."""
340
+ if n % 2:
341
+ return self.curve.psi(n)(self.x)
342
+ return self.y * self.curve.psi(n)(self.x)
343
+
344
+ def dlog(self, other: "ECPoint") -> int:
315
345
  """Compute the discrete log_P(Q) in EC."""
316
- m = P.order()
346
+ m = other.order()
317
347
  mf = factorint(m)
318
- assert m * Q == P.curve(None, None), "DLP not solvable."
348
+ assert m * self == other.curve(None, None), "DLP not solvable."
319
349
  # We first use Pohlig-Hellman to split m into powers of prime factors
320
350
  mm = []
321
351
  ll = []
322
352
  for pj, kj in mf.items():
323
- Pj = (m // pj**kj) * P
324
- Qj = (m // pj**kj) * Q
353
+ Pj = (m // pj**kj) * other
354
+ Qj = (m // pj**kj) * self
325
355
  l = Qj.dlog_ph(Pj, pj, kj)
326
356
  if l is None:
327
357
  return None
@@ -329,58 +359,58 @@ class ECPoint:
329
359
  ll += [l]
330
360
  return crt(ll, mm)
331
361
 
332
- def dlog_ph(Q, P: "ECPoint", q: int, k: int) -> int:
362
+ def dlog_ph(self, other: "ECPoint", q: int, k: int) -> int:
333
363
  """Compute the discrete log_P(Q) in EC if P has order q^k using Pohlig-Hellman reduction."""
334
364
  if k == 1 or q**k < 10000:
335
- return Q.dlog_switch(P, q**k)
336
- Pj = q**(k - 1) * P
365
+ return self.dlog_switch(other, q**k)
366
+ Pj = q**(k - 1) * self
337
367
  P1 = Pj
338
- Qj = q**(k - 1) * Q
368
+ Qj = q**(k - 1) * other
339
369
  xj = Qj.dlog_switch(P1, q)
340
370
  for j in range(2, k + 1):
341
- Pj = q**(k - j) * P
342
- Qj = q**(k - j) * Q - xj * Pj
371
+ Pj = q**(k - j) * self
372
+ Qj = q**(k - j) * other - xj * Pj
343
373
  yj = Qj.dlog_switch(P1, q)
344
374
  xj = xj + q ** (j - 1) * yj % q**j
345
375
  return xj
346
376
 
347
- def dlog_switch(Q, P: "ECPoint", m: int) -> int:
377
+ def dlog_switch(self, other: "ECPoint", m: int) -> int:
348
378
  """Compute the discrete log_P(Q) in EC if P has order m choosing an appropriate method."""
349
379
  if m < 100:
350
- return Q.dlog_naive(P, m)
351
- return Q.dlog_bsgs(P, m)
380
+ return self.dlog_naive(other, m)
381
+ return self.dlog_bsgs(other, m)
352
382
 
353
- def dlog_naive(Q, P: "ECPoint", m: int) -> int:
383
+ def dlog_naive(self, other: "ECPoint", m: int) -> int:
354
384
  """Compute the discrete log_P(Q) in EC using an exhaustive search."""
355
- if not Q.curve == P.curve and not isinstance(Q, P.__class__):
385
+ if not self.curve == other.curve and not isinstance(self, other.__class__):
356
386
  raise ValueError("Points must be on the same curve!")
357
387
  j = 0
358
388
  xx, yy = None, None
359
- while xx != Q.x:
389
+ while xx != self.x:
360
390
  j += 1
361
- xx, yy = P.curve.add(xx, yy, P.x, P.y)
391
+ xx, yy = self.curve.add(xx, yy, other.x, other.y)
362
392
  if xx is None:
363
393
  raise ValueError("DLP not solvabel!")
364
- if yy == Q.y:
394
+ if yy == self.y:
365
395
  return j
366
396
  return m - j
367
397
 
368
- def dlog_bsgs(Q, P: "ECPoint", m: int) -> int:
398
+ def dlog_bsgs(self, other: "ECPoint", m: int) -> int:
369
399
  """Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm."""
370
- if not Q.curve == P.curve and not isinstance(P, Q.__class__):
400
+ if not self.curve == other.curve and not isinstance(other, self.__class__):
371
401
  raise ValueError("Points must be on the same curve!")
372
402
  mm = 1 + isqrt(m - 1)
373
403
  m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps
374
404
  # initialize baby_steps table
375
405
  baby_steps = {}
376
- baby_step = P
406
+ baby_step = other
377
407
  for j in range(1,m2+1):
378
408
  baby_steps[int(baby_step.x)] = j, int(baby_step.y)
379
- baby_step += P
409
+ baby_step += other
380
410
 
381
411
  # now take the giant steps
382
- giant_stride = -mm * P
383
- giant_step = Q
412
+ giant_stride = -mm * other
413
+ giant_step = self
384
414
  for l in range(mm+1):
385
415
  if giant_step.x is None:
386
416
  return l * mm
@@ -4,7 +4,7 @@ Factorization of integers:
4
4
  """
5
5
 
6
6
  from math import isqrt, gcd
7
- from .primes import sieve_eratosthenes, isprime
7
+ from .primes import sieve_eratosthenes, is_prime
8
8
  from .factor_pm1 import _pm1_parameters, factor_pm1
9
9
  from .factor_ecm import _ecm_parameters, factor_ecm
10
10
  #from .factor_qs import factor_qs
@@ -15,21 +15,18 @@ from .factor_ecm import _ecm_parameters, factor_ecm
15
15
 
16
16
 
17
17
  def _factor_fermat(n: int, steps: int = 10) -> list:
18
- a = isqrt(n - 1) + 1
19
- step =2
20
- if n % 3 == 2: # if n % 3 = 2, then a must be a multiple of 3
21
- a += 2 - ((a - 1) % 3)
22
- step = 3
23
- elif (n % 4 == 1) ^ (a & 1): # if n % 4 = 1,3 then a must be odd, even, respectively
24
- a += 1
25
- for _ in range(steps):
26
- #if a > (n + 9) // 6:
27
- # return
18
+ "Fermat method"
19
+ parameters = {11: (12, 6), 23: (12, 0),
20
+ 5: (6, 3), 17: (6, 3),
21
+ 19: (4, 2), 7: (4, 0),
22
+ 1: (2, 1), 13: (2, 1)}
23
+ start = isqrt(n - 1) + 1
24
+ step, mod = parameters[n % 24]
25
+ start += (mod - start) % step
26
+ for a in range(start, min(start + steps * step,(n + 9) // 6) + 1, step):
28
27
  b = isqrt(a * a - n)
29
28
  if b * b == a * a - n:
30
29
  return a - b
31
- a += step
32
-
33
30
 
34
31
  def factorint(n: int, verbose: int = 0) -> list:
35
32
  "Factor a number."
@@ -42,7 +39,7 @@ def factorint(n: int, verbose: int = 0) -> list:
42
39
  for m in mm:
43
40
  if m in prime_factors:
44
41
  prime_factors[m] += k
45
- elif isprime(m):
42
+ elif is_prime(m):
46
43
  prime_factors[m] = k
47
44
  else:
48
45
  if m in remaining_factors:
@@ -66,7 +63,7 @@ def factorint(n: int, verbose: int = 0) -> list:
66
63
  return prime_factors
67
64
  if verbose:
68
65
  print("Trial division found:", list(prime_factors))
69
- if isprime(n):
66
+ if is_prime(n):
70
67
  prime_factors[n] = 1
71
68
  return prime_factors
72
69
  remaining_factors = { n: 1 }
@@ -130,7 +127,7 @@ def factorint(n: int, verbose: int = 0) -> list:
130
127
  else:
131
128
  remaining_factors[m] = new_factors[m]
132
129
  if verbose > 1: print("Remaining: ", remaining_factors)
133
-
130
+
134
131
  if len(remaining_factors) == 0:
135
132
  return prime_factors
136
133
 
@@ -5,6 +5,7 @@ Integer factorization: Lentra's ECM
5
5
  from math import gcd, isqrt, log
6
6
  from random import randint, seed
7
7
  from .primes import sieve_eratosthenes
8
+ seed(0)
8
9
 
9
10
  # Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
10
11
  # Algorithm 7.2.7
@@ -83,7 +84,7 @@ def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None
83
84
  return D, stage_one, stage_two_deltas
84
85
 
85
86
 
86
- def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm_parameters: tuple = None):
87
+ def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm_parameters: tuple|None = None):
87
88
  "Factors a number n using Lentsta's ECM method."
88
89
 
89
90
  if ecm_parameters:
@@ -0,0 +1,30 @@
1
+ """
2
+ Integer factorization: Fermat's method
3
+ """
4
+
5
+ from math import isqrt
6
+
7
+
8
+ def factor_fermat(n: int) -> list:
9
+ """Find factors of n using the method of Fermat."""
10
+ factors = []
11
+ parameters = {11: (12, 6), 23: (12, 0),
12
+ 5: (6, 3), 17: (6, 3),
13
+ 19: (4, 2), 7: (4, 0),
14
+ 1: (2, 1), 13: (2, 1)}
15
+ # Speedup only works if n is neiter a multipl of 2 or 3
16
+ for p in (2, 3):
17
+ while n % p == 0:
18
+ factors.append(p)
19
+ n //= p
20
+ start = isqrt(n - 1) + 1
21
+ step, mod = parameters[n % 24]
22
+ start += (mod - start) % step
23
+ for a in range(start, (n + 9) // 6 + 1, step):
24
+ b = isqrt(a * a - n)
25
+ if b * b == a * a - n:
26
+ factors.append(a - b)
27
+ factors.append(a + b)
28
+ return factors
29
+ factors.append(n)
30
+ return factors
@@ -24,7 +24,7 @@ def _pm1_parameters(B1: int, B2: int = None, primes: tuple = None):
24
24
  continue
25
25
  if q > B2:
26
26
  break
27
- stage_two_deltas.append(q - p)
27
+ stage_two_deltas.append(q - p) # pylint: disable=W0631
28
28
 
29
29
  return stage_one, stage_two_deltas
30
30
 
@@ -54,11 +54,11 @@ def factor_qs(n: int) -> list:
54
54
  for i, p in enumerate(factorbase):
55
55
  r1 = sqrt_mod(n, p)
56
56
  assert r1 is not None # only quadratic residues
57
- r2 = -r1 % p
57
+ r2 = -r1 % p # pylint: disable=E1130
58
58
  if r1 == r2:
59
59
  factorbase_root[i] = [ r1 ]
60
60
  else:
61
- factorbase_root[i] = [ r1, -r1 % p ]
61
+ factorbase_root[i] = [ r1, -r1 % p ] # pylint: disable=E1130
62
62
 
63
63
  m = isqrt(n - 1) + 1
64
64
  d = m**2 - n
@@ -168,7 +168,7 @@ def factor_qs(n: int) -> list:
168
168
  res = process_relation(j, mask)
169
169
  if res:
170
170
  return res
171
- del(factors[j])
171
+ del factors[j]
172
172
  else:
173
- del(factors[j]) # the number is unlikely to factor
173
+ del factors[j] # the number is unlikely to factor
174
174
  sieve_bound += sieve_step # increase the sieve and continue
@@ -3,6 +3,8 @@ Linear algebra
3
3
  """
4
4
 
5
5
  from math import inf, sqrt, prod
6
+ from numbers import Number
7
+
6
8
 
7
9
  class Matrix:
8
10
  """
@@ -61,8 +63,10 @@ class Matrix:
61
63
  else:
62
64
  cols = range(self.cols)[j]
63
65
  return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
64
- i, j = divmod(item, self.cols)
65
- return self.matrix[i][j]
66
+ if isinstance(item, int):
67
+ i, j = divmod(item, self.cols)
68
+ 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]])
66
70
 
67
71
  def __setitem__(self, item, value):
68
72
  if isinstance(item, tuple):
@@ -147,12 +151,16 @@ class Matrix:
147
151
  return result
148
152
 
149
153
  def __add__(self, other) -> "Matrix":
150
- if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
154
+ if isinstance(other, Matrix):
155
+ if other.cols != self.cols or other.rows != self.rows:
156
+ raise NotImplementedError("Matrix dimensions do not match!")
151
157
  return Matrix([ [ x1 + y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
152
158
  return NotImplemented
153
159
 
154
160
  def __sub__(self, other) -> "Matrix":
155
- if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
161
+ if isinstance(other, Matrix):
162
+ if other.cols != self.cols or other.rows != self.rows:
163
+ raise NotImplementedError("Matrix dimensions do not match!")
156
164
  return Matrix([ [ x1 - y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
157
165
  return NotImplemented
158
166
 
@@ -162,12 +170,14 @@ class Matrix:
162
170
  def __mul__(self, other) -> "Matrix":
163
171
  if isinstance(other, Matrix):
164
172
  return self.multiply(other)
173
+ if isinstance(other, Number) or type(other) == type(self.matrix[0][0]):
174
+ return Matrix([ [item * other for item in row] for row in self.matrix ])
165
175
  return NotImplemented
166
176
 
167
177
  def __rmul__(self, other) -> "Matrix":
168
- if isinstance(other, Matrix):
169
- return self.multiply(other)
170
- return Matrix([ [item * other for item in row] for row in self.matrix ])
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 ])
180
+ return NotImplemented
171
181
 
172
182
  def rref(self) -> "Matrix":
173
183
  "Compute the reduced echelon form of a matrix M."
@@ -242,20 +252,26 @@ class Matrix:
242
252
  n, m = self.cols, self.rows
243
253
  elif not n:
244
254
  n = m
245
- zero = 0 * self[0]
255
+ try:
256
+ zero = 0 * self[0]
257
+ except:
258
+ zero = 0
246
259
  return Matrix([[ zero for j in range(n)] for i in range(m) ])
247
260
 
248
261
  def eye(self, m: int = None, n: int = None):
249
262
  "Returns an identity matrix of the same dimension"
250
263
  def delta(i, j):
251
264
  if i == j:
252
- return 1
253
- return 0
265
+ return one
266
+ return zero
254
267
  if not m and not n:
255
268
  n, m = self.cols, self.rows
256
269
  elif not n:
257
270
  n = m
258
- zero = 0 * self[0]
271
+ try:
272
+ zero = 0 * self[0]
273
+ except:
274
+ zero = 0
259
275
  one = 1 + zero
260
276
  return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
261
277
 
@@ -104,6 +104,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
104
104
  assert 0 < delta <= 1, f"LLL reqires 0 < delta={delta} <= 1"
105
105
  j = 1
106
106
  U = V[:, :]
107
+ U.map(int)
107
108
  Us = U[:, :]
108
109
  Us.map(Fraction)
109
110
  M = U.zeros()
@@ -1,29 +1,21 @@
1
1
  """
2
2
  Number theory tools:
3
- lcm(a, b) least common mutiple of a and b
4
3
  egcd(a,b) extended Euclidean agorithm
5
4
  crt([a1, a2, ...],[m1, m2, ...]) Chinese Remainder Theorem
6
5
  cf(Fraction(m,n)) continued fraction expansions
7
6
  convergents() convergents of a continued fraction
7
+ legendre_symbol(a, p) compute the Legendre symbol of a with respect to the prime p
8
+ jacobi_symbol(a, n) compute the Jacobi symbol of a with respect to n
8
9
  sqrt_mod(n, p) square root of n modulo a prime p
9
10
  order(a, n) oder of a in the multiplicative group Z_n^*
10
11
  """
11
- from math import gcd, prod
12
+ from math import gcd, lcm, prod
12
13
  from fractions import Fraction
13
14
 
14
- # Euclid and friends
15
-
16
- def lcm(a: int, b: int) -> int:
17
- """Compute the least common multiple of a and b."""
18
- if b == 0:
19
- return 0
20
- if bool(a > 0) != bool(b > 0):
21
- a = -a
22
- return (a // gcd(a, b)) * b
23
-
15
+ # extended Euclid
24
16
 
25
17
  def egcd(a: int, b: int) -> (int, int, int):
26
- """Perform the extended Euclidean agorithm. Returns gcd, x, y such that a x + b y = gcd."""
18
+ """Perform the extended Euclidean agorithm. Returns `gcd`, `x`, `y` such that `a x + b y = gcd`."""
27
19
  r0, r1 = a, b
28
20
  x0, x1, y0, y1 = 1, 0, 0, 1
29
21
  while r1 != 0:
@@ -36,8 +28,8 @@ def egcd(a: int, b: int) -> (int, int, int):
36
28
 
37
29
  # Chinese remainder theorem
38
30
 
39
- def crt(a: list, m: list) -> int:
40
- """Solve given linear congruences x[j] % m[j] == a[j] using the Chinese Remainder Theorem."""
31
+ def crt(a: list[int], m: list[int]) -> int:
32
+ """Solve given linear congruences x % m[j] == a[j] using the Chinese Remainder Theorem."""
41
33
  l = len(a)
42
34
  assert len(m) == l, "The lists of numbers and modules must have equal length."
43
35
  M = prod(m)
@@ -112,6 +104,7 @@ def crt(a: list, m: list) -> int:
112
104
 
113
105
 
114
106
  def fraction_repr(self):
107
+ "Representation of a fraction."
115
108
  if self.denominator == 1:
116
109
  return str(self.numerator)
117
110
  return str(self.numerator) + "/" + str(self.denominator)
@@ -178,8 +171,8 @@ def jacobi_symbol(a: int, n: int) -> int:
178
171
  return t
179
172
  return 0
180
173
 
181
- def sqrt_mod(a: int, p: int) -> list:
182
- "Compute a square root of a modulo p unsing Cipolla's algorithm."
174
+ def sqrt_mod(a: int, p: int) -> int:
175
+ "Compute a square root of `a` modulo `p` unsing Cipolla's algorithm."
183
176
  a %= p
184
177
  if a == 0 or a == 1:
185
178
  return a
@@ -211,13 +204,13 @@ from .factor import factorint
211
204
 
212
205
 
213
206
  def euler_phi(n: int) -> int:
214
- """Euler's phi function of n."""
207
+ """Euler's phi function of `n`."""
215
208
  k = factorint(n)
216
209
  return prod([(p - 1) * p ** (k[p] - 1) for p in k])
217
210
 
218
211
 
219
212
  def carmichael_lambda(n: int) -> int:
220
- """Carmichael's lambda function of n."""
213
+ """Carmichael's lambda function of `n`."""
221
214
  k = factorint(n)
222
215
  lam_all = [] # values corresponding to the prime factors
223
216
  for p in k:
@@ -233,10 +226,10 @@ def carmichael_lambda(n: int) -> int:
233
226
  # Order in Z_p^*
234
227
 
235
228
  def order(a: int, n: int, factor=False) -> int:
236
- """Compute the order of a in the group Z_n^*."""
229
+ """Compute the order of `a` in the group Z_n^*."""
237
230
  a %= n
238
231
  assert a != 0 and gcd(a, n) == 1, f"{a} and {n} are not coprime!"
239
- factors = dict() # We compute euler_phi(n) and its factorization in one pass
232
+ factors = {} # We compute euler_phi(n) and its factorization in one pass
240
233
  for p, k in factorint(n).items(): # first factorize n
241
234
  for pm, km in factorint(p - 1).items(): # factor p-1 and add the factors
242
235
  if pm in factors:
@@ -263,7 +256,7 @@ def order(a: int, n: int, factor=False) -> int:
263
256
  else:
264
257
  break
265
258
  if factor and i < k:
266
- factors_order[p] = k - i
259
+ factors_order[p] = k - i # pylint: disable=E0606
267
260
  if factor:
268
261
  return order_a, factors_order
269
262
  return order_a
@@ -2,6 +2,8 @@
2
2
  Polynomials
3
3
  """
4
4
 
5
+ from numbers import Number
6
+
5
7
  class Poly:
6
8
  """
7
9
  Represents a polynomial as a list of coefficients.
@@ -25,9 +27,18 @@ class Poly:
25
27
  if modulus:
26
28
  self.mod(modulus)
27
29
 
30
+ def __call__(self, x):
31
+ return sum(c * x**j for j, c in enumerate(self.coeff))
32
+
28
33
  def __getitem__(self, item):
29
34
  return self.coeff[item]
30
35
 
36
+ def __setitem__(self, item, value):
37
+ self.coeff[item] = value
38
+
39
+ def __len__(self):
40
+ return len(self.coeff)
41
+
31
42
  def __repr__(self):
32
43
  def prx(i: int):
33
44
  if i == 0:
@@ -37,7 +48,7 @@ class Poly:
37
48
  return "x^" + str(i)
38
49
 
39
50
  if len(self.coeff) == 1:
40
- return str(int(self.coeff[0]))
51
+ return str(self.coeff[0])
41
52
  plus = ""
42
53
  tmp = ""
43
54
  for i in reversed(range(len(self.coeff))):
@@ -76,19 +87,20 @@ class Poly:
76
87
  return bool(self.degree()) or bool(self.coeff[0])
77
88
 
78
89
  def degree(self):
90
+ "Return the degree."
79
91
  return len(self.coeff) - 1
80
92
 
81
93
  def map(self, func):
94
+ "Apply a given function to all coefficients."
82
95
  self.coeff = list(map(func, self.coeff))
83
96
 
84
97
  def __add__(self, other: "Poly") -> "Poly":
85
98
  if not isinstance(other, self.__class__):
86
- try:
87
- tmp = self.coeff[:]
99
+ tmp = self.coeff[:]
100
+ if isinstance(other, int) or type(other) == type(tmp[0]):
88
101
  tmp[0] += other
89
102
  return self.__class__(tmp, modulus=self.modulus)
90
- except:
91
- return NotImplemented
103
+ return NotImplemented
92
104
  ls, lo = len(self.coeff), len(other.coeff)
93
105
  if ls < lo:
94
106
  scoeff = self.coeff + (lo - ls) * [0]
@@ -105,12 +117,10 @@ class Poly:
105
117
 
106
118
  def __radd__(self, other: "Poly") -> "Poly":
107
119
  if not isinstance(other, self.__class__):
108
- try:
120
+ if isinstance(other, Number) or type(other) == type(self.coeff[0]):
109
121
  tmp = self.coeff[:]
110
122
  tmp[0] += other
111
123
  return self.__class__(tmp, modulus=self.modulus)
112
- except:
113
- pass
114
124
  return NotImplemented
115
125
 
116
126
  def __neg__(self) -> "Poly":
@@ -118,12 +128,11 @@ class Poly:
118
128
 
119
129
  def __sub__(self, other: "Poly") -> "Poly":
120
130
  if not isinstance(other, self.__class__):
121
- try:
131
+ if isinstance(other, int) or type(other) == type(self.coeff[0]):
122
132
  tmp = self.coeff[:]
123
133
  tmp[0] -= other
124
134
  return self.__class__(tmp, modulus=self.modulus)
125
- except:
126
- return NotImplemented
135
+ return NotImplemented
127
136
  ls, lo = len(self.coeff), len(other.coeff)
128
137
  if ls < lo:
129
138
  scoeff = self.coeff + (lo - ls) * [0]
@@ -136,24 +145,21 @@ class Poly:
136
145
  modulus = self.modulus
137
146
  if not modulus and other.modulus:
138
147
  modulus = other.modulus
139
- return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
148
+ return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus = modulus)
140
149
 
141
150
  def __rsub__(self, other: "Poly") -> "Poly":
142
151
  if not isinstance(other, self.__class__):
143
- try:
152
+ if isinstance(other, int) or type(other) == type(self.coeff[0]):
144
153
  tmp = self.coeff[:]
145
154
  tmp[0] -= other
146
155
  return self.__class__(tmp, modulus=self.modulus)
147
- except:
148
- pass
149
156
  return NotImplemented
150
157
 
151
158
  def __mul__(self, other: "Poly") -> "Poly":
152
159
  if not isinstance(other, self.__class__):
153
- try:
154
- return Poly([other * s for s in self.coeff])
155
- except:
156
- return NotImplemented
160
+ if isinstance(other, int) or type(other) == type(self.coeff[0]):
161
+ return Poly([other * s for s in self.coeff], modulus = self.modulus)
162
+ return NotImplemented
157
163
  ls, lo = len(self.coeff), len(other.coeff)
158
164
  coeff = [0] * (ls + lo - 1)
159
165
  for k in range(ls + lo - 1):
@@ -166,25 +172,38 @@ class Poly:
166
172
  modulus = self.modulus
167
173
  if not modulus and other.modulus:
168
174
  modulus = other.modulus
169
- return self.__class__(coeff, modulus=modulus)
175
+ return self.__class__(coeff, modulus = modulus)
170
176
 
171
177
  def __rmul__(self, other: int) -> "Poly":
172
- return self.__class__([other * s for s in self.coeff], modulus=self.modulus)
178
+ if isinstance(other, int) or type(other) == type(self.coeff[0]):
179
+ return self.__class__([other * s for s in self.coeff], modulus=self.modulus)
180
+ return NotImplemented
173
181
 
174
182
  def __pow__(self, i: int) -> "Poly":
175
- res = self.__class__([1], modulus=self.modulus)
183
+ if not isinstance(i, int):
184
+ return NotImplemented
185
+ zero = 0 * self.coeff[0]
186
+ one = zero + 1
187
+ res = self.__class__([one], modulus=self.modulus)
176
188
  if i < 0:
177
189
  if not self.modulus:
178
- raise NotImplementedError(f"Cannot divide.")
190
+ raise NotImplementedError("Cannot divide polynomials without modulus.")
179
191
  tmp = self.inv()
192
+ i *= -1
180
193
  else:
181
194
  tmp = self
182
195
  for _ in range(i):
183
196
  res *= tmp
184
197
  return res
185
198
 
199
+ def __floordiv__(self, other: "Poly") -> "Poly":
200
+ return self.divmod(other)[0]
201
+
202
+ def __mod__(self, other: "Poly") -> "Poly":
203
+ return self.divmod(other)[1]
204
+
186
205
  def divmod(self, other: "Poly") -> ("Poly", "Poly"):
187
- "Polynom division with remainder"
206
+ "Polynom division with remainder."
188
207
  if isinstance(other, list):
189
208
  other = self.__class__(other)
190
209
  elif not isinstance(other, self.__class__):
@@ -215,7 +234,7 @@ class Poly:
215
234
  )
216
235
 
217
236
  def mod(self, other: "Poly") -> None:
218
- "Remainder of polynom division"
237
+ "Reduce with respect to a given polynomial."
219
238
  if isinstance(other, list):
220
239
  other = self.__class__(other)
221
240
  elif not isinstance(other, self.__class__):
@@ -241,6 +260,7 @@ class Poly:
241
260
  self.coeff.pop(i)
242
261
 
243
262
  def inv(self, other: "Poly" = None) -> "Poly":
263
+ "Inverse modulo a given polynomial."
244
264
  if not other:
245
265
  other = self.modulus
246
266
  if isinstance(other, list):
@@ -249,10 +269,10 @@ class Poly:
249
269
  raise NotImplementedError(f"Cannot invert {self} modulo {other}.")
250
270
  if not other:
251
271
  raise NotImplementedError(f"{other} must be nonzero.")
272
+ zero = 0 * self.coeff[0]
273
+ one = zero +1
252
274
  r0, r1 = other, self
253
- y0, y1 = self.__class__([0], modulus=self.modulus), self.__class__(
254
- [1], modulus=self.modulus
255
- )
275
+ y0, y1 = self.__class__([zero], modulus=self.modulus), self.__class__([one], modulus=self.modulus)
256
276
  while r1:
257
277
  q, r = r0.divmod(r1)
258
278
  r0, r1 = r1, r
@@ -260,6 +280,6 @@ class Poly:
260
280
  if r0.degree() != 0:
261
281
  raise ValueError(f"{self} is not invertible mod {other}.")
262
282
  tmp = 1 / r0[0]
263
- for i in range(y0.degree()):
283
+ for i in range(len(y0)):
264
284
  y0.coeff[i] *= tmp
265
285
  return y0
@@ -1,10 +1,16 @@
1
1
  """
2
2
  Tools for prime numbers:
3
3
  sieve_eratosthenes(B) a tuple of all primes up to including B
4
- isprime(n) test if n is probably prime
4
+ is_prime(n) test if n is probably prime
5
+ next_prime(n) find the next prime larger or equal n
6
+ random_prime(l) find a random prime with bit length at least l
7
+ random_strongprime(l) find a random strong prime with bit length at least l
8
+ is_safeprime(n) test if n is a safe prime
9
+ random_safeprime(n) find a random safe prime with bit length at least l and ord(2)=(p-1)/2
5
10
  miller_rabin_test(n, b) Miller-Rabin primality test with base b
6
11
  """
7
12
  from math import isqrt, gcd
13
+ from random import randint
8
14
  from .nt import jacobi_symbol
9
15
 
10
16
  # Erathostenes
@@ -13,15 +19,15 @@ def sieve_eratosthenes(B: int) -> list:
13
19
  """ "Returns a list of all primes up to (including) max."""
14
20
  B1 = (isqrt(B) -1)//2
15
21
  B = (B - 1)//2
16
- is_prime = [True] * (B + 1) # to begin with, all numbers are potentially prime
22
+ isprime = [True] * (B + 1) # to begin with, all numbers are potentially prime
17
23
  # sieve out the primes p=2*q+1 starting at 3 in steps of 2 (ignoring even numbers)
18
24
  for q in range(1, B1 + 1):
19
- if is_prime[q]: # sieve out all multiples; numbers p*q with q<p were already sieved out previously
25
+ if isprime[q]: # sieve out all multiples; numbers p*q with q<p were already sieved out previously
20
26
  qq = (q << 1) * (q + 1)
21
27
  p = (q << 1) | 1
22
- is_prime[qq :: p] = [False] * ((B - qq) // p + 1)
28
+ isprime[qq :: p] = [False] * ((B - qq) // p + 1)
23
29
 
24
- return tuple([2] + [2 * q + 1 for q in range(1, B + 1) if is_prime[q]])
30
+ return tuple([2] + [2 * q + 1 for q in range(1, B + 1) if isprime[q]])
25
31
 
26
32
  # Primality testing
27
33
 
@@ -51,7 +57,7 @@ def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
51
57
  return True
52
58
  return False
53
59
 
54
- def isprime(n: int) -> bool:
60
+ def is_prime(n: int) -> bool:
55
61
  """Test if an integer n if probable prime."""
56
62
  if n < 18446744073709551616: # https://miller-rabin.appspot.com
57
63
  return miller_rabin_test(n, [2, 325, 9375, 28178, 450775, 9780504, 1795265022])
@@ -59,6 +65,43 @@ def isprime(n: int) -> bool:
59
65
  return miller_rabin_test(n, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41])
60
66
  return miller_rabin_test(n, [2]) and _is_strong_lucas_prp(n) # Baillie–PSW primality test
61
67
 
68
+ def next_prime(n: int) -> int:
69
+ """Find the next prime larger or equal n."""
70
+ n |= 1 # make sure n is odd
71
+ while not is_prime(n):
72
+ n += 2
73
+ return n
74
+
75
+ def random_prime(l: int) -> int:
76
+ """Find a random prime with bit length at least l."""
77
+ return next_prime(randint(2 ** (l - 1), 2**l - 1))
78
+
79
+ def random_strongprime(l: int) -> int:
80
+ """Find a random strong prime with bit length at least l using Gordon's algorithm."""
81
+ t = random_prime(l)
82
+ s = random_prime(l)
83
+ u = 2 * t
84
+ uu = u * randint(1, 100)
85
+ while not is_prime(uu + 1):
86
+ uu += u
87
+ r = uu + 1
88
+ u = 2 * r * s
89
+ uu = u * randint(1, 100) + 2 * s * pow(s, r - 2, r) - 1
90
+ while not is_prime(uu):
91
+ uu += u
92
+ return t, s, r, uu
93
+
94
+ def is_safeprime(p: int) -> bool:
95
+ """Tests if a number is a safe prime."""
96
+ return is_prime(p) and is_prime((p - 1) // 2)
97
+
98
+ def random_safeprime(l: int) -> int:
99
+ """Find a random safe prime with bit length at least l and ord(2)=(p-1)/2."""
100
+ p = randint(2 ** (l - 1), 2**l - 1)
101
+ p = p - (p % 24) + 23
102
+ while not is_safeprime(p):
103
+ p += 24
104
+ return p
62
105
 
63
106
  def _lucas_sequence(n, D, k):
64
107
  """Evaluate a Lucas sequence."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.3
3
+ Version: 0.5
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.8
12
+ Requires-Python: >=3.9
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
 
@@ -27,7 +27,7 @@ The tools contained are:
27
27
  * number theory: sqrt modulo primes, crt, continued fractions, etc.
28
28
  * primes: Sieve of Erathostenes, primality tests
29
29
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
30
- * integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
30
+ * integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
31
31
  * linear algebra: Hermite normal form, Gram-Schmidt
32
32
  * lattices: Babai rounding/nearest plane, lattice reduction
33
33
 
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "0.3"
3
+ version = "0.5"
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.8"
9
+ requires-python = ">=3.9"
10
10
  classifiers = [
11
11
  "Programming Language :: Python :: 3",
12
12
  "License :: OSI Approved :: MIT License",
@@ -1,115 +0,0 @@
1
- """
2
- Ring of intergers modulo `n`.
3
- """
4
-
5
- class Zmod:
6
- """
7
- Ring of intergers modulo `n`.
8
-
9
- Example:
10
-
11
- To define a finite Galois field modulo the prime 5 use
12
- >>> gf=Zmod(5)
13
-
14
- To declare 3 as an element of our Galois filed use
15
- >>> gf(3)
16
- 3 (mod 5)
17
-
18
- The usual arithmetic operations are supported.
19
- >>> gf(2) + gf(3)
20
- 0 (mod 5)
21
- """
22
-
23
- def __init__(self, n: int, short: bool = False):
24
- self.n = n
25
- self.short = short
26
-
27
- def __call__(self, x: int):
28
- return ZmodPoint(x, self)
29
-
30
- def __eq__(self, other):
31
- if isinstance(other, self.__class__):
32
- return self.n == other.n
33
- return False
34
-
35
- def __contains__(self, other: "ZmodPoint") -> bool:
36
- return isinstance(other, ZmodPoint) and self.n == other.ring.n
37
-
38
-
39
- class ZmodPoint:
40
- "Represents a point in the ring Zmod."
41
-
42
- def __init__(self, x: int, ring: "Zmod"):
43
- if isinstance(x, self.__class__) and x.ring.n == ring.n:
44
- self.x = int(x)
45
- else:
46
- self.x = int(x) % ring.n
47
- self.ring = ring
48
-
49
- def __repr__(self):
50
- if self.ring.short:
51
- return str(self.x)
52
- return f"{self.x} (mod {self.ring.n})"
53
-
54
- def __eq__(self, other):
55
- if not isinstance(other, self.__class__) or self.ring != other.ring:
56
- return False
57
- return self.x == other.x
58
-
59
- def __bool__(self):
60
- return self.x != 0
61
-
62
- def __int__(self):
63
- return self.x
64
-
65
- def sharp(self):
66
- "Returns a symmetric (w.r.t. 0) representative."
67
- tmp = (self.ring.n - 1) // 2
68
- return (self.x + tmp) % self.ring.n - tmp
69
-
70
- def __hash__(self):
71
- return hash(self.x)
72
-
73
- def __add__(self, other: "ZmodPoint") -> "ZmodPoint":
74
- if isinstance(other, self.__class__) and self.ring == other.ring:
75
- return self.__class__(self.x + other.x, self.ring)
76
- if isinstance(other, int):
77
- return self.__class__(self.x + other, self.ring)
78
- return NotImplemented
79
-
80
- def __radd__(self, scalar: int) -> "ZmodPoint":
81
- return self.__class__(scalar + self.x, self.ring)
82
-
83
- def __neg__(self) -> "ZmodPoint":
84
- return self.__class__(-self.x, self.ring)
85
-
86
- def __sub__(self, other: "ZmodPoint") -> "ZmodPoint":
87
- if isinstance(other, self.__class__) and self.ring == other.ring:
88
- return self.__class__(self.x - other.x, self.ring)
89
- if isinstance(other, int):
90
- return self.__class__(self.x - other, self.ring)
91
- return NotImplemented
92
-
93
- def __rsub__(self, scalar: int) -> "ZmodPoint":
94
- return self.__class__(scalar - self.x, self.ring)
95
-
96
- def __mul__(self, other: "ZmodPoint") -> "ZmodPoint":
97
- if isinstance(other, self.__class__) and self.ring == other.ring:
98
- return self.__class__(self.x * other.x, self.ring)
99
- if isinstance(other, int):
100
- return self.__class__(self.x * other, self.ring)
101
- return NotImplemented
102
-
103
- def __rmul__(self, scalar: int) -> "ZmodPoint":
104
- return self.__class__(scalar * self.x, self.ring)
105
-
106
- def __truediv__(self, other: "ZmodPoint") -> "ZmodPoint":
107
- if not isinstance(other, self.__class__) or self.ring != other.ring:
108
- return NotImplemented
109
- return self.__class__(self.x * pow(other.x, -1, self.ring.n), self.ring)
110
-
111
- def __rtruediv__(self, scalar: int) -> "ZmodPoint":
112
- return self.__class__(scalar * pow(self.x, -1, self.ring.n), self.ring)
113
-
114
- def __pow__(self, scalar: int) -> "ZmodPoint":
115
- return self.__class__(pow(self.x, scalar, self.ring.n), self.ring)
@@ -1,30 +0,0 @@
1
- """
2
- Integer factorization: Fermat's method
3
- """
4
-
5
- from math import isqrt
6
-
7
-
8
- def factor_fermat(n: int) -> list:
9
- """Find factors of n using the method of Fermat."""
10
- factors = []
11
- # Fermat only works if n has two factors which are either both even or both odd
12
- while n % 2 == 0:
13
- factors.append(2)
14
- n //= 2
15
- a = isqrt(n - 1) + 1
16
- step =2
17
- if n % 3 == 2: # if n % 3 = 2, then a must be a multiple of 3
18
- a += 2 - ((a - 1) % 3)
19
- step = 3
20
- elif (n % 4 == 1) ^ (a & 1): # if n % 4 = 1,3 then a must be odd, even, respectively
21
- a += 1
22
- while a <= (n + 9) // 6:
23
- b = isqrt(a * a - n)
24
- if b * b == a * a - n:
25
- factors.append(a - b)
26
- factors.append(a + b)
27
- return factors
28
- a += step
29
- factors.append(n)
30
- return factors
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes