kryptools 0.9.4__tar.gz → 0.9.6__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 (29) hide show
  1. {kryptools-0.9.4 → kryptools-0.9.6}/PKG-INFO +1 -1
  2. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor.py +36 -16
  3. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_ecm.py +40 -8
  4. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_fmt.py +17 -3
  5. kryptools-0.9.6/kryptools/factor_pm1.py +77 -0
  6. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_qs.py +34 -9
  7. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/poly.py +19 -4
  8. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/primes.py +74 -12
  9. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/PKG-INFO +1 -1
  10. {kryptools-0.9.4 → kryptools-0.9.6}/pyproject.toml +1 -1
  11. kryptools-0.9.4/kryptools/factor_pm1.py +0 -58
  12. {kryptools-0.9.4 → kryptools-0.9.6}/LICENSE +0 -0
  13. {kryptools-0.9.4 → kryptools-0.9.6}/README.md +0 -0
  14. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/Zmod.py +0 -0
  15. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/__init__.py +0 -0
  16. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp.py +0 -0
  17. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_bsgs.py +0 -0
  18. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_ic.py +0 -0
  19. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_qs.py +0 -0
  20. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_rho.py +0 -0
  21. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/ec.py +0 -0
  22. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_dix.py +0 -0
  23. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/la.py +0 -0
  24. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/lat.py +0 -0
  25. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/nt.py +0 -0
  26. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/SOURCES.txt +0 -0
  27. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/dependency_links.txt +0 -0
  28. {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/top_level.txt +0 -0
  29. {kryptools-0.9.4 → kryptools-0.9.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.9.4
3
+ Version: 0.9.6
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
@@ -4,7 +4,7 @@ Factorization of integers:
4
4
  divisors(n) unsorted list of all divisors of the integer n
5
5
  """
6
6
 
7
- from math import isqrt, gcd, prod
7
+ from math import isqrt, gcd, prod, floor, log10
8
8
  from .primes import sieve_eratosthenes, is_prime
9
9
  from .factor_pm1 import _pm1_parameters, factor_pm1
10
10
  from .factor_ecm import _ecm_parameters, factor_ecm
@@ -14,8 +14,10 @@ from .factor_ecm import _ecm_parameters, factor_ecm
14
14
  # Factoring
15
15
 
16
16
 
17
- def _factor_fermat(n: int, steps: int = 10) -> list:
17
+ def _factor_fermat(n: int, maxsteps: int = 10, verbose: int = 0) -> list:
18
18
  "Fermat method"
19
+ if verbose:
20
+ print(f"Factoring (Fermat, steps={maxsteps}): {n} ({floor(log10(n)) + 1} digits)")
19
21
  parameters = {11: (12, 6), 23: (12, 0),
20
22
  5: (6, 3), 17: (6, 3),
21
23
  19: (4, 2), 7: (4, 0),
@@ -23,12 +25,23 @@ def _factor_fermat(n: int, steps: int = 10) -> list:
23
25
  start = isqrt(n - 1) + 1
24
26
  step, mod = parameters[n % 24]
25
27
  start += (mod - start) % step
26
- for a in range(start, min(start + steps * step,(n + 9) // 6) + 1, step):
28
+ if verbose > 1:
29
+ print("Working ", end= "")
30
+ maxa = (n + 9) // 6 + 1
31
+ if maxsteps:
32
+ maxa = min(maxa, start + maxsteps * step + 1)
33
+ for a in range(start, maxa, step):
34
+ if verbose > 1:
35
+ print(".", end= "")
27
36
  b = isqrt(a * a - n)
28
37
  if b * b == a * a - n:
38
+ if verbose > 1:
39
+ print("")
29
40
  return a - b
41
+ if verbose > 1:
42
+ print("")
30
43
 
31
- def factorint(n: int, verbose: int = 0) -> dict:
44
+ def factorint(n: int, verbose: int = 0, trial_bnd: int = 2500) -> dict:
32
45
  "Factor an ineger `n` into prime factors."
33
46
  prime_factors = {}
34
47
  if not isinstance(n, int):
@@ -46,7 +59,7 @@ def factorint(n: int, verbose: int = 0) -> dict:
46
59
  for m in mm:
47
60
  if m in prime_factors:
48
61
  prime_factors[m] += k
49
- elif is_prime(m):
62
+ elif is_prime(m, trialdivision = False):
50
63
  prime_factors[m] = k
51
64
  else:
52
65
  if m in remaining_factors:
@@ -56,8 +69,10 @@ def factorint(n: int, verbose: int = 0) -> dict:
56
69
  else:
57
70
  new_factors[m] = k
58
71
 
72
+ if verbose:
73
+ print(f"Factoring: {n} ({floor(log10(n)) + 1} digits)")
59
74
  # trial division
60
- B = 2500
75
+ B = max(3, trial_bnd)
61
76
  factorbase = sieve_eratosthenes(B)
62
77
  for p in factorbase:
63
78
  k = 0
@@ -70,13 +85,14 @@ def factorint(n: int, verbose: int = 0) -> dict:
70
85
  return prime_factors
71
86
  if verbose:
72
87
  print("Trial division found:", list(prime_factors))
73
- if is_prime(n):
88
+ if is_prime(n, trialdivision = False):
74
89
  prime_factors[n] = 1
75
90
  return prime_factors
76
91
  remaining_factors = { n: 1 }
77
92
 
78
93
  # https://gitlab.inria.fr/zimmerma/ecm/
79
94
  ECM_PARAMETERS = [
95
+ [ 5000, 130000, 54],
80
96
  [ 11000, 1900000, 74],
81
97
  [ 50000, 13000000, 221],
82
98
  [ 250000, 130000000, 453],
@@ -88,8 +104,12 @@ def factorint(n: int, verbose: int = 0) -> dict:
88
104
  [260000000, 3200000000000, 47173],
89
105
  [850000000, 16000000000000, 77666]
90
106
  ]
107
+ fermat_steps = 10
91
108
 
92
- for parameters in ECM_PARAMETERS:
109
+ for round, parameters in enumerate(ECM_PARAMETERS):
110
+ if verbose:
111
+ print(f"Round {round+1}")
112
+ fermat_steps += 10
93
113
  B1, B2, num_curves = parameters
94
114
  num_curves *= 2
95
115
  D = isqrt(B2)
@@ -97,20 +117,20 @@ def factorint(n: int, verbose: int = 0) -> dict:
97
117
  pm1_parameters = _pm1_parameters(10 * B1, B2, primes = primes)
98
118
  ecm_parameters = tuple([B1, B2, num_curves] + list(_ecm_parameters(B1, B2, D, primes = primes)))
99
119
 
100
- methods = {_factor_fermat: "fm", factor_pm1: "pm1", factor_ecm: "ecm"} #, factor_qs: "qs"}
120
+ methods = {_factor_fermat: "fmt", factor_pm1: "pm1", factor_ecm: "ecm"} #, factor_qs: "qs"}
101
121
  while remaining_factors:
102
122
  new_factors = {}
103
123
  for method in [ _factor_fermat, factor_pm1, factor_ecm ]: # , factor_qs ]:
104
124
  factors = list(remaining_factors)
105
125
  for m in factors:
106
- if verbose > 1:
107
- print("Factoring: ",m, "method", methods[method])
108
- if method == factor_pm1:
109
- tmp = factor_pm1(m, pm1_parameters = pm1_parameters)
126
+ if method == _factor_fermat:
127
+ tmp = _factor_fermat(m, maxsteps = fermat_steps, verbose = max(0, verbose - 1))
128
+ elif method == factor_pm1:
129
+ tmp = factor_pm1(m, pm1_parameters = pm1_parameters, verbose = max(0, verbose - 1))
110
130
  elif method == factor_ecm:
111
- tmp = factor_ecm(m, ecm_parameters = ecm_parameters)
131
+ tmp = factor_ecm(m, ecm_parameters = ecm_parameters, verbose = max(0, verbose - 1))
112
132
  else:
113
- tmp = method(m)
133
+ tmp = method(m, verbose = max(0, verbose - 1))
114
134
  if tmp:
115
135
  tmp2 = m // tmp
116
136
  g = gcd(tmp, tmp2)
@@ -125,7 +145,7 @@ def factorint(n: int, verbose: int = 0) -> dict:
125
145
  tmp3.append(x)
126
146
  else:
127
147
  tmp3 = [tmp, tmp2]
128
- if verbose > 0: print("Factors found (", methods[method] ,"): ", tmp3)
148
+ if verbose > 0: print(f"Factors found ({methods[method]}): ", tmp3)
129
149
  add_factors(m, tmp3)
130
150
  if not new_factors:
131
151
  break
@@ -2,7 +2,7 @@
2
2
  Integer factorization: Lentra's ECM
3
3
  """
4
4
 
5
- from math import gcd, isqrt, log
5
+ from math import gcd, isqrt, log, floor, log10
6
6
  from random import randint, seed
7
7
  from .primes import sieve_eratosthenes
8
8
  seed(0)
@@ -35,19 +35,21 @@ def mult(k, P, c2, p):
35
35
  return dbl(P, c2, p)
36
36
  Q = dbl(P, c2, p)
37
37
  R = P
38
- for i in bin(k)[3:]:
38
+ for i in bin(k)[3:-1]:
39
39
  if i == "1":
40
40
  R = add(Q, R, P, p)
41
41
  Q = dbl(Q, c2, p)
42
42
  else:
43
43
  Q = add(R, Q, P, p)
44
44
  R = dbl(R, c2, p)
45
- return R
45
+ if k % 2:
46
+ return add(Q, R, P, p)
47
+ return dbl(R, c2, p)
46
48
 
47
49
  # Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
48
50
  # Algorithm 7.4.4 (Inversionless ECM)
49
51
 
50
- def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None):
52
+ def _ecm_parameters(B1: int, B2: int|None = None, D: int|None = None, primes: tuple|None = None):
51
53
  "Precompute parameters for the ECM method."
52
54
 
53
55
  # Stage-one/two limits must be even
@@ -84,36 +86,59 @@ def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None
84
86
  return D, stage_one, stage_two_deltas
85
87
 
86
88
 
87
- def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm_parameters: tuple|None = None):
89
+ def factor_ecm(n: int, B1: int|None = None, B2: int|None = None, curves: int = 150, ecm_parameters: tuple|None = None, verbose: int = 0):
88
90
  "Factors a number n using Lentsta's ECM method."
89
91
 
90
92
  if ecm_parameters:
91
93
  B1, B2, curves, D, stage_one, stage_two_deltas = ecm_parameters
92
94
  else:
95
+ if not B1:
96
+ B1 = max(2, isqrt(n//200))
97
+ if not B2:
98
+ B2 = 100 * B1
93
99
  D, stage_one, stage_two_deltas = _ecm_parameters(B1, B2)
94
100
 
101
+ if verbose:
102
+ print(f"Factoring (ECM, B1={B1}, B2={B2}): {n} ({floor(log10(n)) + 1} digits)")
103
+ if verbose > 1:
104
+ print("Working ", end= "")
95
105
  for _ in range(curves):
96
106
  # find a random curve
107
+ if verbose > 1:
108
+ print("C", end= "") # stage one
97
109
  sigma = randint(6, n - 1)
98
110
  u = (sigma**2 - 5) % n
99
111
  v = (4 * sigma) % n
100
112
  try:
101
113
  c = (pow(v - u, 3, n) * (3 * u + v) * pow(4 * u**3 * v, -1, n) - 2) % n
102
114
  except:
103
- m = gcd(4 * u**3 * v, n)
104
- return m
105
- c2 = c - 2 % n
115
+ if verbose > 1:
116
+ print("\nFactor found.")
117
+ m = gcd(u, n)
118
+ if m > 1:
119
+ return m
120
+ m = gcd(v, n)
121
+ if m > 1:
122
+ return m
123
+ return 2
124
+ c2 = (c - 2) % n
106
125
  # and a point on the curve (or its twist)
107
126
  Q = (pow(u, 3, n), pow(v, 3, n))
108
127
  # Stage one
128
+ if verbose > 1:
129
+ print("1", end= "")
109
130
  Q = mult(stage_one, Q, c2, n)
110
131
  if Q[1] == 0:
111
132
  continue
112
133
  g = gcd(Q[1], n)
113
134
  if g > 1:
135
+ if verbose > 1:
136
+ print("\nFactor found.")
114
137
  return g
115
138
 
116
139
  # Stage two
140
+ if verbose > 1:
141
+ print("2", end= "")
117
142
  S = [dbl(Q, c2, n)]
118
143
  S.append(dbl(S[0], c2, n))
119
144
 
@@ -136,9 +161,16 @@ def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm
136
161
  break
137
162
  g =gcd(g,n)
138
163
  if 1 < g:
164
+ if verbose > 1:
165
+ print("\nFactor found.")
139
166
  return g
140
167
  R, T = add(R, S[D - 1], T, n), R
141
168
 
142
169
  g = gcd(g, n)
143
170
  if 1 < g < n:
171
+ if verbose > 1:
172
+ print("\nFactor found.")
144
173
  return g
174
+ if verbose > 1:
175
+ print(f"\nNo factor after trying {curves} curves.")
176
+
@@ -2,11 +2,13 @@
2
2
  Integer factorization: Fermat's method
3
3
  """
4
4
 
5
- from math import isqrt
5
+ from math import isqrt, log10, floor, log10
6
6
 
7
7
 
8
- def factor_fermat(n: int) -> list:
8
+ def factor_fermat(n: int, maxsteps: int|None = None, verbose: int = 0) -> list:
9
9
  """Find factors of n using the method of Fermat."""
10
+ if verbose:
11
+ print(f"Factoring (Fermat): {n} ({floor(log10(n)) + 1} digits)")
10
12
  factors = []
11
13
  parameters = {11: (12, 6), 23: (12, 0),
12
14
  5: (6, 3), 17: (6, 3),
@@ -20,11 +22,23 @@ def factor_fermat(n: int) -> list:
20
22
  start = isqrt(n - 1) + 1
21
23
  step, mod = parameters[n % 24]
22
24
  start += (mod - start) % step
23
- for a in range(start, (n + 9) // 6 + 1, step):
25
+ if verbose > 1:
26
+ print("Working ", end= "")
27
+ maxa = (n + 9) // 6 + 1
28
+ if maxsteps:
29
+ maxsteps = max(1, maxsteps)
30
+ maxa = min(maxa, start + maxsteps * step + 1)
31
+ for a in range(start, maxa, step):
32
+ if verbose > 1:
33
+ print(".", end= "")
24
34
  b = isqrt(a * a - n)
25
35
  if b * b == a * a - n:
26
36
  factors.append(a - b)
27
37
  factors.append(a + b)
38
+ if verbose > 1:
39
+ print("")
28
40
  return factors
29
41
  factors.append(n)
42
+ if verbose > 1:
43
+ print("")
30
44
  return factors
@@ -0,0 +1,77 @@
1
+ """
2
+ Integer factorization: Pollard's p-1 method
3
+ """
4
+
5
+ from math import gcd, log, floor, log10
6
+ from .primes import sieve_eratosthenes
7
+
8
+ def _pm1_parameters(B1: int, B2: int|None = None, primes: tuple = None):
9
+ "Precompute parameters for the p-1 method."
10
+ if not B2:
11
+ B2 = 100 * B1
12
+ if not primes:
13
+ primes = sieve_eratosthenes(B2)
14
+
15
+ stage_one = 1
16
+ stage_two_deltas = []
17
+ pp = 0
18
+ for p in primes:
19
+ if p <= B1:
20
+ stage_one *= p**int(log(B1, p))
21
+ elif p <= B2:
22
+ stage_two_deltas.append(p - pp) # pylint: disable=W0631
23
+ pp = p
24
+ if p > B2:
25
+ break
26
+
27
+ return stage_one, stage_two_deltas
28
+
29
+
30
+ def factor_pm1(n: int, B1: int = 10000, B2: int|None = None, x: int = 2, pm1_parameters: tuple = None, verbose: int = 0):
31
+ "Factors a number n using Pollard's p-1 method."
32
+ if verbose:
33
+ print(f"Factoring (Pollard p-1, B1={B1}, B2={B2}): {n} ({floor(log10(n)) + 1} digits)")
34
+
35
+ if pm1_parameters:
36
+ stage_one, stage_two_deltas = pm1_parameters
37
+ else:
38
+ stage_one, stage_two_deltas = _pm1_parameters(B1, B2)
39
+
40
+ # stage one
41
+ if verbose > 1:
42
+ print("Working 1", end= "")
43
+ x = pow(x, stage_one, n)
44
+ g = gcd(x - 1, n)
45
+ if g == n:
46
+ return None
47
+ if 1 < g:
48
+ return g
49
+
50
+ if verbose > 1:
51
+ print("2", end= "")
52
+ saved = {}
53
+ y = pow(x, stage_two_deltas[0], n)
54
+ D = (y - 1) % n
55
+ for k, d in enumerate(stage_two_deltas[1:]):
56
+ if not d in saved:
57
+ saved[d] = pow(x, d, n)
58
+ y = y * saved[d] % n
59
+ D = D * (y - 1) % n
60
+ if k % 20 == 0:
61
+ g = gcd(D, n)
62
+ if g == n:
63
+ if verbose > 1:
64
+ print("")
65
+ return None
66
+ if 1 < g:
67
+ if verbose > 1:
68
+ print("\nFactor found.")
69
+ return g
70
+ g = gcd(D, n)
71
+ if 1 < g < n:
72
+ if verbose > 1:
73
+ print("\nFactor found.")
74
+ return g
75
+ if verbose > 1:
76
+ print("")
77
+ return None
@@ -2,7 +2,7 @@
2
2
  Integer factorization: Quadratic sieve
3
3
  """
4
4
 
5
- from math import isqrt, gcd, sqrt, log, exp, ceil
5
+ from math import isqrt, gcd, sqrt, log, exp, ceil, floor, log10
6
6
  from .primes import sieve_eratosthenes
7
7
  from .nt import legendre_symbol, sqrt_mod
8
8
 
@@ -24,7 +24,7 @@ def bytetest(a: bytes, i: int) -> bytes:
24
24
  return (a[i1] & 2**i0) != 0
25
25
 
26
26
 
27
- def factor_qs(n: int) -> list:
27
+ def factor_qs(n: int, verbose: int = 0) -> list:
28
28
  """Find factors of n using the quadratic sieve."""
29
29
  # first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
30
30
  # the expected running time |B|^2 u^u = u^(u+2) p^(2/u)/log(n). There is no explicit expression for the optimum, hence
@@ -37,10 +37,16 @@ def factor_qs(n: int) -> list:
37
37
  B = int(exp(log(n) / u))
38
38
  # B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
39
39
 
40
+ if verbose:
41
+ print(f"Factoring (QS, B={B}): {n} ({floor(log10(n)) + 1} digits)")
42
+ if verbose > 1:
43
+ print(f"Setting up factorbase: B={B}", end="")
40
44
  factorbase = []
41
45
  for p in sieve_eratosthenes(B): # compute the factorbase
42
46
  ls = legendre_symbol(n, p)
43
47
  if ls == 0: # we already found a factor;-)
48
+ if verbose > 1:
49
+ print("\nFound a small prime factor.")
44
50
  if n == p:
45
51
  return n
46
52
  return p
@@ -49,6 +55,8 @@ def factor_qs(n: int) -> list:
49
55
  lf = len(factorbase) # length of the factorbase (including -1)
50
56
  lfb = ceil((lf+1) / 8) # the number of bytes we need to store a relation
51
57
 
58
+ if verbose > 1:
59
+ print(f" len={lf}", end ="")
52
60
  factorbase_log = [log(p) for p in factorbase] # we add these up to test if a number will probably factor
53
61
  factorbase_root = [0] * lf # compute the roots of n mod p
54
62
  for i, p in enumerate(factorbase):
@@ -66,13 +74,18 @@ def factor_qs(n: int) -> list:
66
74
  return m
67
75
  m2 = 2 * m
68
76
 
77
+ if verbose > 1:
78
+ print(" done.")
79
+
69
80
  relation_no = -1
70
81
  relations = [None] * (lf + 1) # here we will store the relations in case we found a B-smooth number
71
82
  values = [None] * (lf + 1) # here we will store the values leading to the B-smooth numbers
72
83
 
73
84
  # set up the sieve
74
- sieve_step = 100
75
- sieve_bound = sieve_step
85
+ expected_trys = int(exp(u*log(u/2)/2))+1 # expected number of trys to find a relation for sieved values
86
+ sieve_bound = max(100, expected_trys * lf)
87
+ sieve_step = sieve_bound // 10
88
+
76
89
  # start values for the iterators in the positive/negative direction
77
90
  iterator_p = [ [0, 0] for _ in range(lf) ]
78
91
  iterator_m = [ [0, 0] for _ in range(lf) ]
@@ -122,10 +135,10 @@ def factor_qs(n: int) -> list:
122
135
  # print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
123
136
  if index == lf: # the new relation is linearly dependent: we have found a linear combination of the 0 vector
124
137
  # now we need to determine the factors
125
- u = 1 # product over all values m - j such that f(m-j) is B-smooth
126
- v = 1 # sqrt of the product over all f(m-j) which are B-smooth
138
+ u = 1 # product over all values m + j such that f(m+j) is B-smooth
139
+ v = 1 # sqrt of the product over all f(m+j) which are B-smooth
127
140
  w = 1 # save nonsquare terms for the next round
128
- for i in range(lf):
141
+ for i in range(lf + 1):
129
142
  if bytetest(relation, lfb * 8 + i): # select the relations which sum to the 0 vector
130
143
  ui = m + values[i]
131
144
  u = (u * ui) % n
@@ -137,14 +150,20 @@ def factor_qs(n: int) -> list:
137
150
  res = gcd(u - v, n)
138
151
  if 1 < res < n:
139
152
  return res
153
+ if verbose > 1:
154
+ print("X", end="")
140
155
  relation_no -= 1 # this one did not work, try again
141
156
  else:
142
157
  relations[index] = relation
158
+ if verbose > 1:
159
+ print(".", end="")
143
160
  return None
144
161
 
145
- while True:
162
+ for _ in range(expected_trys * lf * 100):
146
163
  factors = {} # store the index of the primes dividing j
147
164
 
165
+ if verbose > 1:
166
+ print("S", end="")
148
167
  do_sieve()
149
168
 
150
169
  for j in sorted(factors.keys(),key=abs):
@@ -157,7 +176,7 @@ def factor_qs(n: int) -> list:
157
176
  v *= -1
158
177
  for i in primes:
159
178
  p = factorbase[i]
160
- k = 1 # per our sieve we already know that p devides v
179
+ k = 1 # per our sieve we already know that p divides v
161
180
  v //= p
162
181
  while v % p == 0: # divide by p as many times as possible
163
182
  k = (k + 1) % 2 # we only want to know if the exponent is even or odd
@@ -167,8 +186,14 @@ def factor_qs(n: int) -> list:
167
186
  if v == 1:
168
187
  res = process_relation(j, mask)
169
188
  if res:
189
+ if verbose:
190
+ print(f"\nFactor found after {relation_no} relations.")
170
191
  return res
171
192
  del factors[j]
172
193
  else:
173
194
  del factors[j] # the number is unlikely to factor
174
195
  sieve_bound += sieve_step # increase the sieve and continue
196
+ if verbose > 1:
197
+ print("")
198
+ if verbose:
199
+ print("QS failed.")
@@ -4,6 +4,14 @@ Polynomials
4
4
 
5
5
  from numbers import Number
6
6
 
7
+ display_reversed = True
8
+
9
+ def poly_style_reversed(value: bool = True) -> None:
10
+ "Change the order of the coefficients when displaying polynomials."
11
+ global display_reversed
12
+ display_reversed = value
13
+
14
+
7
15
  class Poly:
8
16
  """
9
17
  Represents a polynomial as a list of coefficients.
@@ -40,7 +48,7 @@ class Poly:
40
48
  return len(self.coeff)
41
49
 
42
50
  def __repr__(self):
43
- def prx(i: int):
51
+ def prx(i: int) -> str:
44
52
  if i == 0:
45
53
  return ""
46
54
  if i == 1:
@@ -51,7 +59,10 @@ class Poly:
51
59
  return str(self.coeff[0])
52
60
  plus = ""
53
61
  tmp = ""
54
- for i in reversed(range(len(self.coeff))):
62
+ coef_range = range(len(self.coeff))
63
+ if display_reversed:
64
+ coef_range = reversed(coef_range)
65
+ for i in coef_range:
55
66
  s = self.coeff[i]
56
67
  if not s:
57
68
  continue
@@ -70,11 +81,15 @@ class Poly:
70
81
  pass
71
82
  try:
72
83
  if plus and s < 0:
73
- tmp += " - " + str(-s) + " " + prx(i)
84
+ tmp += " - " + str(-s)
85
+ if i != 0:
86
+ tmp += " " + prx(i)
74
87
  continue
75
88
  except:
76
89
  pass
77
- tmp += plus + str(s) + " " + prx(i)
90
+ tmp += plus + str(s)
91
+ if i != 0:
92
+ tmp += " " + prx(i)
78
93
  plus = " + "
79
94
  return tmp.strip()
80
95
 
@@ -13,6 +13,9 @@ Tools for prime numbers:
13
13
  random_blumprime(l) find a pseudorandom Blum prime with bit length at least l
14
14
  miller_rabin_test(n, b) Miller-Rabin primality test with base b
15
15
  lucas_test(n) strong Lucas test
16
+ perfect_square(n) test if a number is a perfect square
17
+ perfect_power(n) test if a number is a perfect power
18
+ iroot(k, n) integer k'th root of n
16
19
  """
17
20
  from math import isqrt, gcd, floor, ceil
18
21
  from random import randint
@@ -51,26 +54,37 @@ def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
51
54
  """Run a Miller-Rabin test with given bases on `n`."""
52
55
  if n < 2:
53
56
  return False
54
- if n % 2 == 0:
57
+ if n % 2 == 0: # make sure n is odd
55
58
  return n == 2
59
+
60
+ # write n = m 2^k + 1
56
61
  m = (n - 1) // 2
57
62
  k = 1
58
63
  while m % 2 == 0:
59
64
  m //= 2
60
65
  k += 1
66
+
61
67
  if isinstance(bases, int):
62
68
  bases = [bases]
69
+
63
70
  for a in bases:
71
+ a %= n
72
+ if a == 0:
73
+ continue
64
74
  b = pow(a, m, n)
65
75
  if b == 1 or b == n - 1:
66
- return True
76
+ continue
67
77
  for _ in range(1, k):
68
78
  b = pow(b, 2, n)
69
- if b == 1:
79
+ if b == 0: # n and a are not coprime
80
+ return False
81
+ if b == 1: # 1 before -1
70
82
  return False
71
83
  if b == n - 1:
72
- return True
73
- return False
84
+ break
85
+ else: # no -1 found
86
+ return False
87
+ return True
74
88
 
75
89
  def _lucas_sequence(n, D, k):
76
90
  """Evaluate a Lucas sequence."""
@@ -97,16 +111,59 @@ def _lucas_sequence(n, D, k):
97
111
  return U % n, V % n, Qk
98
112
 
99
113
 
114
+ def perfect_square(n: int) -> int|None:
115
+ """Returns the root if `n` is a perfect square and None else."""
116
+ s = isqrt(n)
117
+ if s*s == n:
118
+ return s
119
+
120
+ def iroot(k: int, n: int) -> int:
121
+ """Compute the integer k'th root of n."""
122
+ if not isinstance(k, int) or n < 1:
123
+ raise ValueError(f"{k} is not a positive integer")
124
+ if k == 1:
125
+ return n
126
+ if k == 2:
127
+ return isqrt(n)
128
+ r = isqrt(n)
129
+ while r**k >= n:
130
+ rr = r
131
+ r = isqrt(r)
132
+ r =rr + 1
133
+ k1 = k-1
134
+ while rr < r:
135
+ r = rr
136
+ rr = (k1 * rr + n // rr ** k1) // k # Newton iteration
137
+ return r
138
+
139
+ def perfect_power(n: int) -> tuple|None:
140
+ """Returns integers `(m, p)` with `m**p == n` if `n` is a perfect power and None else."""
141
+ for p in sieve_eratosthenes(n.bit_length() - 1):
142
+ m = iroot(p, n)
143
+ if pow(m, p) == n:
144
+ return (m, p)
145
+
146
+
147
+ # https://en.wikipedia.org/wiki/Lucas_pseudoprime#Implementing_a_Lucas_probable_prime_test
148
+
100
149
  def lucas_test(n: int) -> bool:
101
150
  """Run a strong Lucas primality test on `n`."""
102
-
103
- # remove powers of 2 from n+1 (= k * 2**s)
151
+ if n < 2:
152
+ return False
153
+ if n % 2 == 0:
154
+ return n == 2
155
+ if perfect_square(n) is not None: # the search for D will not succeed in this case
156
+ return False
157
+
158
+ # write n = k 2^s - 1
104
159
  k = (n + 1) // 2
105
160
  s = 1
106
161
  while k % 2 == 0:
107
162
  k //= 2
108
163
  s += 1
109
- # gernerate parameters
164
+
165
+ # Selfridge method for choosing D
166
+
110
167
  D = 5
111
168
  while True:
112
169
  g = gcd(abs(D), n)
@@ -119,9 +176,6 @@ def lucas_test(n: int) -> bool:
119
176
  else:
120
177
  D = -D + 2
121
178
 
122
- if D == 0:
123
- return False
124
-
125
179
  U, V, Qk = _lucas_sequence(n, D, k)
126
180
 
127
181
  if U == 0 or V == 0:
@@ -133,8 +187,16 @@ def lucas_test(n: int) -> bool:
133
187
  Qk = pow(Qk, 2, n)
134
188
  return False
135
189
 
136
- def is_prime(n: int) -> bool:
190
+ def is_prime(n: int, trialdivision: bool = True) -> bool:
137
191
  """Test if an integer `n` if probable prime."""
192
+ # Test small primes
193
+ if trialdivision:
194
+ for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
195
+ 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199,
196
+ 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293,
197
+ 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397):
198
+ if n % p == 0:
199
+ return n == p
138
200
  if n < 18446744073709551616: # https://miller-rabin.appspot.com
139
201
  return miller_rabin_test(n, [2, 325, 9375, 28178, 450775, 9780504, 1795265022])
140
202
  if n < 3317044064679887385961981:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.9.4
3
+ Version: 0.9.6
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "0.9.4"
3
+ version = "0.9.6"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -1,58 +0,0 @@
1
- """
2
- Integer factorization: Pollard's p-1 method
3
- """
4
-
5
- from math import gcd, log
6
- from .primes import sieve_eratosthenes
7
-
8
- def _pm1_parameters(B1: int, B2: int = None, primes: tuple = None):
9
- "Precompute parameters for the P-1 method."
10
- if not B2:
11
- B2 = 100 * B1
12
- if not primes:
13
- primes = sieve_eratosthenes(B2)
14
-
15
- stage_one = 1
16
- for p in primes:
17
- if p > B1:
18
- break
19
- stage_one *= p**int(log(B1, p))
20
-
21
- stage_two_deltas = []
22
- for q in primes:
23
- if q <= B1:
24
- continue
25
- if q > B2:
26
- break
27
- stage_two_deltas.append(q - p) # pylint: disable=W0631
28
-
29
- return stage_one, stage_two_deltas
30
-
31
-
32
- def factor_pm1(n: int, B1: int = 11000, B2: int = 1900000, x: int = 2, pm1_parameters: tuple = None):
33
- "Factors a number n using Pollard's P-1 method."
34
-
35
- if pm1_parameters:
36
- stage_one, stage_two_deltas = pm1_parameters
37
- else:
38
- stage_one, stage_two_deltas = _pm1_parameters(B1, B2)
39
-
40
- g = gcd(pow(x, stage_one, n) - 1, n)
41
- if 1 < g < n:
42
- return g
43
- k = 0
44
- for d in stage_two_deltas:
45
- saved = {}
46
- if not d in saved:
47
- y = pow(x, d, n)
48
- saved[d] = y
49
- x = (x * saved[d]) % n
50
- k += 1
51
- if k % 20 == 0:
52
- g = gcd(x-1, n)
53
- if 1 < g < n:
54
- return g
55
- g = gcd(x-1, n)
56
- if 1 < g < n:
57
- return g
58
- return None
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