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.
- {kryptools-0.9.4 → kryptools-0.9.6}/PKG-INFO +1 -1
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor.py +36 -16
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_ecm.py +40 -8
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_fmt.py +17 -3
- kryptools-0.9.6/kryptools/factor_pm1.py +77 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_qs.py +34 -9
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/poly.py +19 -4
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/primes.py +74 -12
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/PKG-INFO +1 -1
- {kryptools-0.9.4 → kryptools-0.9.6}/pyproject.toml +1 -1
- kryptools-0.9.4/kryptools/factor_pm1.py +0 -58
- {kryptools-0.9.4 → kryptools-0.9.6}/LICENSE +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/README.md +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/Zmod.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/__init__.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_ic.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_qs.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/dlp_rho.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/ec.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/factor_dix.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/la.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/lat.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools/nt.py +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.9.4 → kryptools-0.9.6}/setup.cfg +0 -0
|
@@ -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,
|
|
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
|
-
|
|
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 =
|
|
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: "
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
sieve_bound =
|
|
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
|
|
126
|
-
v = 1 # sqrt of the product over all f(m
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
76
|
+
continue
|
|
67
77
|
for _ in range(1, k):
|
|
68
78
|
b = pow(b, 2, n)
|
|
69
|
-
if b ==
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|