kryptools 0.1__py3-none-any.whl

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/Zmod.py ADDED
@@ -0,0 +1,115 @@
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)
kryptools/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """
2
+ Implemenation of same basic algorithms used in cryptography.
3
+ """
4
+
5
+ from .dlp import dlog
6
+ from .ec import EC_Weierstrass
7
+ from .factor import factorint
8
+ from .la import Matrix
9
+ from .lat import hermite_nf, gram_schmidt, lll
10
+ from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
11
+ from .poly import Poly
12
+ from .primes import sieve_eratosthenes, isprime
13
+ from .Zmod import Zmod
kryptools/dlp.py ADDED
@@ -0,0 +1,76 @@
1
+ """
2
+ Discrete Log Problem:
3
+ dlog(a, b, n) solve the discrete log problem a^x = b mod n
4
+ """
5
+
6
+
7
+ from math import gcd, log, sqrt
8
+ from .nt import crt, order
9
+ from .dlp_bsgs import dlog_bsgs
10
+ from .dlp_qs import dlog_qs
11
+ from .factor import factorint
12
+
13
+
14
+ def dlog_naive(a: int, b: int, n: int, m: int = None) -> int:
15
+ """Compute the discrete log_a(b) in Z_n of an element a of order m by exhaustive search."""
16
+ a %= n
17
+ b %= n
18
+ if not m:
19
+ m = n - 1
20
+ aa = 1
21
+ for i in range(m):
22
+ aa = (aa * a) % n
23
+ if aa == b:
24
+ return i + 1
25
+ return None # no solution
26
+
27
+ def _dlog_switch(a: int, b: int, n: int, m: int) -> int:
28
+ """Compute the discrete log_a(b) in Z_n of an element a of order m choosing an appropriate method."""
29
+ if m < 1000:
30
+ return dlog_naive(a, b, n, m)
31
+ elif log(m) - 6 < 2 * sqrt(log(n) * log(log(n))): # compare the theoreticaly expected running times ob bsgs and ic; the constant 6 is determined experimentally
32
+ return dlog_bsgs(a, b, n, m)
33
+ else:
34
+ return dlog_qs(a, b, n, m)
35
+
36
+ def _dlog_ph(a: int, b: int, n: int, q: int, k: int) -> int:
37
+ """Compute the discrete log_a(b) in Z_n of an element a of order q^k using Pohlig-Hellman reduction."""
38
+ if k == 1 or q**k < 10000:
39
+ return _dlog_switch(a, b, n, q**k)
40
+ aj = pow(a, q ** (k - 1), n)
41
+ a1 = aj
42
+ bj = pow(b, q ** (k - 1), n)
43
+ xj = _dlog_switch(a1, bj, n, q)
44
+ for j in range(2, k + 1):
45
+ aj = pow(a, q ** (k - j), n)
46
+ bj = pow(b, q ** (k - j), n)
47
+ yj = _dlog_switch(a1, bj * pow(aj, -xj, n) % n, n, q)
48
+ if yj is None:
49
+ return None
50
+ xj = xj + q ** (j - 1) * yj % q**j
51
+ return xj
52
+
53
+
54
+ def dlog(a: int, b: int, n: int, m: int = None) -> int:
55
+ """Compute the discrete log_a(b) in Z_n of an element a of order m using Pohlig-Hellman reduction."""
56
+ a %= n
57
+ b %= n
58
+ assert gcd(a, n) == 1, "a and n must be coprime."
59
+ assert gcd(b, n) == 1, "b and n must be coprime."
60
+ if m:
61
+ mf = factorint(m)
62
+ else:
63
+ m, mf = order(a, n, True)
64
+ assert pow(b, m, n) == 1, "DLP not solvable."
65
+ # We first use Pohlig-Hellman to split m into powers of prime factors
66
+ mm = []
67
+ ll = []
68
+ for pj, kj in mf.items():
69
+ aj = pow(a, m // pj**kj, n)
70
+ bj = pow(b, m // pj**kj, n)
71
+ l = _dlog_ph(aj, bj, n, pj, kj)
72
+ if l is None:
73
+ return None
74
+ mm += [pj**kj]
75
+ ll += [l]
76
+ return crt(ll, mm)
kryptools/dlp_bsgs.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ Discrete log solvers: Shanks' baby-step-giant-step algorithm
3
+ """
4
+
5
+ from math import isqrt
6
+
7
+
8
+ def dlog_bsgs(a: int, b: int, n: int, m: int = None) -> int:
9
+ """Compute the discrete log_a(b) in Z_n of an element a of order m using Shanks' baby-step-giant-step algorithm."""
10
+ a %= n
11
+ b %= n
12
+ if not m:
13
+ m = n - 1
14
+ mm = 1 + isqrt(m - 1)
15
+ # initialize baby_steps table
16
+ baby_steps = {}
17
+ baby_step = 1
18
+ for k in range(mm):
19
+ baby_steps[baby_step] = k
20
+ baby_step = baby_step * a % n
21
+
22
+ # now take the giant steps
23
+ giant_stride = pow(a, -mm, n)
24
+ giant_step = b
25
+ for l in range(mm):
26
+ if giant_step in baby_steps:
27
+ return l * mm + baby_steps[giant_step]
28
+ giant_step = giant_step * giant_stride % n
29
+ return None # no solution
kryptools/dlp_ic.py ADDED
@@ -0,0 +1,136 @@
1
+ """
2
+ Discrete log solvers: Index calculus
3
+ """
4
+
5
+ from math import gcd
6
+ from random import randint
7
+ from .primes import sieve_eratosthenes
8
+ from .dlp_qs import determine_factorbound, determine_trialdivison_bounds, is_smooth
9
+
10
+ def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int = 0) -> int:
11
+ """Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus."""
12
+
13
+ # assert isprime(m), "The order of a must be prime."
14
+ # assert n_order(a, n) == m, "The order of a is incorrect."
15
+ # assert pow(b, m, n) == 1, "The DLP is not solvable."
16
+
17
+ def find_relation(include_b: bool) -> None or list:
18
+ """Find a relation for b * a^x or a^x"""
19
+ nonlocal a, b, m, n, max_trys, factorbase, factorbase_len, smallprimes_len, pollard_k
20
+
21
+ x_max = m - 1
22
+ x_min = 0
23
+ if include_b:
24
+ x_min = 1
25
+ trys = 0
26
+ while trys < max_trys:
27
+ x = randint(x_min, x_max)
28
+ if include_b:
29
+ bax = b * pow(a ,x, n) % n
30
+ else:
31
+ bax = pow(a ,x, n)
32
+ relation = is_smooth(bax, factorbase, factorbase_len, smallprimes_len, pollard_k)
33
+ if relation:
34
+ break
35
+ trys += 1
36
+ else:
37
+ raise Exception(f"Sorry, Index Calculus failed to find a relation after {trys} trys.")
38
+ if verbose > 2:
39
+ if include_b:
40
+ print(f"rel found: b*a^{x}=", b * pow(a, x, n) % n, relation)
41
+ else:
42
+ print(f"rel found: a^{x}=", pow(a, x, n), relation)
43
+ relation.reverse() # the linear algebra is slightly faster if we take the large primes first
44
+ if include_b:
45
+ relation += [ 1, x ]
46
+ else:
47
+ relation += [ 0, x ]
48
+ return relation
49
+
50
+
51
+ # this functions does the linear algebra
52
+ def process_relation(relation: list) -> None or int:
53
+ """Add a new relation to the linear system and keep the system in echelon form."""
54
+ nonlocal a, b, m, n, relations, len_relations, n_relations
55
+
56
+ n_relations += 1
57
+ # Gauss elimination
58
+ for i in range(len_relations):
59
+ ri = relation[i] % m
60
+ if ri == 0:
61
+ continue
62
+ if relations[i] is not None: # subtract the current relations
63
+ relation[i] = 0
64
+ for j in range(i + 1, len_relations + 1):
65
+ relation[j] = (relation[j] - ri * relations[i][j]) % m
66
+ continue
67
+ # normalize the first nonzero entry
68
+ rinv = pow(ri, -1, m)
69
+ relation[i] = 1
70
+ for j in range(i+1, len_relations + 1):
71
+ relation[j] = rinv * relation[j] % m
72
+ relations[i] = relation
73
+ if verbose > 2:
74
+ print(n_relations, f"rel found (index={i}) :", relation)
75
+ elif verbose > 1:
76
+ print(n_relations, f"rel found (index={i})")
77
+ index = i
78
+ break # we don't need a reduced echelon form
79
+ else: # the relation contains no new information
80
+ if verbose > 1:
81
+ print(n_relations,"redundant rel found")
82
+ return None
83
+ #for i in range(index): # reduced echelon form
84
+ # if relations[i] != None:
85
+ # ri = relations[i][index] # make this entry zero
86
+ # if ri > 0:
87
+ # relations[i][index] = 0
88
+ # for j in range(index+1, len_relations + 1):
89
+ # relations[i][j] = (relations[i][j] - ri * relation[j]) % m
90
+ #print(n_relations, f"i={i}={index} ({len_relations})", relations)
91
+ if index == len_relations - 1: # we found the solution
92
+ if verbose:
93
+ print(f"Success after {n_relations} relations out of {len_relations}.")
94
+ #for i in range(lf):
95
+ # p = factorbase[i]
96
+ # print(f'{p:d}',pow(p,m,n)==1,relations[i])
97
+ x = (m - relations[index][len_relations]) % m
98
+ if pow(a, x, n) == b:
99
+ return x
100
+ raise Exception("Sorry, Index Calculus failed! Either the DLP is not solvable, or the order is not prime or incorrect.")
101
+
102
+ #
103
+ # Determine the parameters
104
+ #
105
+ B, expected_trys = determine_factorbound(n)[:2]
106
+ max_trys = 10 * expected_trys
107
+ factorbase = tuple(p for p in sieve_eratosthenes(B) if gcd(p,n) == 1) # compute the factorbse
108
+ factorbase_len = len(factorbase) # length of the factorbase
109
+ if factorbase_len == 0:
110
+ raise Exception("Sorry, Index Calculus could not find a factorbase!")
111
+ smallprimes_len, pollard_k = factorbase_len, None
112
+ if pollard: # should we speed up trial division with Pollard p-1
113
+ smallprimes_len, pollard_k = determine_trialdivison_bounds(B // 150, factorbase)
114
+ if verbose > 0:
115
+ print(f"Factorbase: bound = {B}, size = {factorbase_len}, max_trys = {max_trys}")
116
+
117
+ # Start the work
118
+ n_relations = 0 # number of relations found
119
+
120
+ # first find a relation involving b
121
+ relation = find_relation(True)
122
+
123
+ # Set up the linear system
124
+ len_relations = factorbase_len + 1
125
+ relations = [None] * len_relations
126
+ res = process_relation(relation)
127
+ if res:
128
+ return res
129
+
130
+ while n_relations < 5 * factorbase_len: # find relations for all primes in our factor base
131
+ relation = find_relation(False)
132
+ res = process_relation(relation)
133
+ if res:
134
+ return res
135
+
136
+ raise Exception("Sorry, Index Calculus could not find enough relations! ({n_relations} - {n_relations_redundant} = {n_relations - n_relations_redundant} out of {len_relations})")
kryptools/dlp_qs.py ADDED
@@ -0,0 +1,297 @@
1
+ """
2
+ Discrete log solvers: Quadratic sieve
3
+ """
4
+
5
+ from .nt import sqrt_mod
6
+ from .primes import sieve_eratosthenes
7
+ from math import exp, log, sqrt, gcd
8
+
9
+
10
+ def determine_factorbound(n: int) -> (int, int):
11
+ """Determines the optimal factor bound and the expected number of trys until a for a given n."""
12
+ # Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us the expected running time |factorbase|^2 u^u = u^(u+2) n^(2/u)/log(n).
13
+ # There is no explicit expression for the optimum, hence we use Newton
14
+ u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
15
+ for _ in range(3):
16
+ u = (2 * log(n) + u * u * (2 + log(u))) / (
17
+ 2 + 3 * u + 2 * u * log(u)
18
+ ) # Newton iteration
19
+ B = int(exp(log(n) / u))
20
+ #B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
21
+ expected_trys = int(exp(u*log(u)))+1 # expected number of trys to find a relation for random values
22
+ expected_trys2 = int(exp(u*log(u/2)/2))+1 # expected number of trys to find a relation for sieved values
23
+ return B, expected_trys, expected_trys2
24
+
25
+ def determine_trialdivison_bounds(B: int, factorbase: list) -> (int, int):
26
+ """Determine the parameters for speeing up trial division."""
27
+ if B < 12:
28
+ return len(factorbase), None
29
+ pollard_k = 1 # guess for the order to be used in the Pollard p-1 test
30
+ smallprimes_len = 1 # number of primes we try first during trial division
31
+ for p in factorbase:
32
+ if p >= B:
33
+ break
34
+ k = p
35
+ kk = k * p
36
+ while kk < B:
37
+ k = kk
38
+ kk *= p
39
+ pollard_k *= k
40
+ smallprimes_len += 1
41
+ return smallprimes_len, pollard_k
42
+
43
+ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: int, pollard_k: int = None) -> list or None:
44
+ """Try to factor n with respect to a a given factorbase. Upon success a list of exponents with repect to the factorbase is returned. Otherwise None."""
45
+ # factorbase_len = len(factorbase)
46
+ factors = [0] * factorbase_len
47
+ for i in range(smallprimes_len):
48
+ p = factorbase[i]
49
+ while n % p == 0: # divide by p as many times as possible
50
+ factors[i] += 1
51
+ n = n // p
52
+ if pollard_k:
53
+ if gcd(pow(2, pollard_k, n)-1, n) == 1: # Pollard p-1 test
54
+ return None # most likely not smooth, give up
55
+ for i in range(smallprimes_len, factorbase_len):
56
+ p = factorbase[i]
57
+ while n % p == 0: # divide by p as many times as possible
58
+ factors[i] += 1
59
+ n = n // p
60
+ if n != 1:
61
+ return None # the number factors if at the end nothing is left
62
+ return factors
63
+
64
+ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int:
65
+ """
66
+ Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus with a quadratic sieve.
67
+ The problem is assumed solvable.
68
+ """
69
+ # assert isprime(m), "The order of a must be prime."
70
+ # assert n_order(a, n) == m, "The order of a is incorrect."
71
+ # assert pow(b, m, n) == 1, "The DLP is not solvable."
72
+
73
+ def find_relation(include_b: bool) -> None or list:
74
+ """Find a relation for b * a^x or a^x"""
75
+ nonlocal a, b, m, n, max_trys, factorbase, factorbase_len, smallprimes_len, pollard_k, sieve_bound
76
+
77
+ x_max = m - 1
78
+ x_min = 0
79
+ if include_b:
80
+ x_min = 1
81
+ trys = 0
82
+ while trys < max_trys: # first find a relation involving b
83
+ x = randint(x_min, x_max)
84
+ if include_b:
85
+ bax = b * pow(a ,x, n) % n
86
+ else:
87
+ bax = pow(a ,x, n)
88
+ relation = is_smooth(bax, factorbase, factorbase_len, smallprimes_len, pollard_k)
89
+ if relation:
90
+ break
91
+ trys += 1
92
+ else:
93
+ raise Exception(f"Sorry, Quadratic Sieve failed failed to find a new relation after {trys} trys.")
94
+ if verbose > 2:
95
+ if include_b:
96
+ print(f"rel found: b*a^{x}=", b * pow(a, x, n) % n, relation)
97
+ else:
98
+ print(f"rel found: a^{x}=", pow(a, x, n), relation)
99
+ relation.reverse() # the linear algebra is slightly faster if we take the large primes first
100
+ if include_b:
101
+ relation += [ 1, x ]
102
+ else:
103
+ relation += [ 0, x ]
104
+ relation = [0] * sieve_bound + relation # sieve values + primes + b + x
105
+ return relation
106
+
107
+ # this functions does the linear algebra
108
+ def process_relation(relation: list) -> None or int:
109
+ """Add a new relation to the linear system and keep the system in echelon form."""
110
+ nonlocal a, b, m, n, relations, len_relations, n_relations
111
+
112
+ n_relations += 1
113
+ # Gauss elimination
114
+ for i in range(len_relations):
115
+ ri = relation[i] % m
116
+ if ri == 0:
117
+ continue
118
+ if relations[i] is not None: # subtract the current relations
119
+ relation[i] = 0
120
+ for j in range(i + 1, len_relations + 1):
121
+ relation[j] = (relation[j] - ri * relations[i][j]) % m
122
+ continue
123
+ # normalize the first nonzero entry
124
+ rinv = pow(ri, -1, m)
125
+ relation[i] = 1
126
+ for j in range(i+1, len_relations + 1):
127
+ relation[j] = rinv * relation[j] % m
128
+ relations[i] = relation
129
+ if verbose > 2:
130
+ print(n_relations, f"rel found (index={i}) :", relation)
131
+ elif verbose > 1:
132
+ print(n_relations, f"rel found (index={i})")
133
+ index = i
134
+ break # we don't need a reduced echelon form
135
+ else: # the relation contains no new information
136
+ if verbose > 1:
137
+ print(n_relations,"redundant rel found")
138
+ return None
139
+ #for i in range(index): # reduced echelon form
140
+ # if relations[i] != None:
141
+ # ri = relations[i][index] # make this entry zero
142
+ # if ri > 0:
143
+ # relations[i][index] = 0
144
+ # for j in range(index+1, len_relations + 1):
145
+ # relations[i][j] = (relations[i][j] - ri * relation[j]) % m
146
+ #print(n_relations, f"i={i}={index} ({len_relations})", relations)
147
+ if index == len_relations - 1: # we found the solution
148
+ if verbose:
149
+ print(f"Success after {n_relations} relations out of {len_relations}.")
150
+ #for i in range(lf):
151
+ # p = factorbase[i]
152
+ # print(f'{p:d}',pow(p,m,n)==1,relations[i])
153
+ x = (m - relations[index][len_relations]) % m
154
+ if pow(a, x, n) == b:
155
+ return x
156
+ raise Exception("Sorry, Quadratic Sieve failed! Either the DLP is not solvable or the order is not prime or incorrect.")
157
+
158
+ #
159
+ # Determine the parameters
160
+ #
161
+
162
+ B, expected_trys, expected_trys2 = determine_factorbound(n)
163
+ max_trys = 10 * expected_trys
164
+ factorbase = []
165
+ factorbase = tuple(p for p in sieve_eratosthenes(B) if gcd(p,n) == 1) # compute the factorbse
166
+ factorbase_log = [log(p) for p in factorbase] # we add these up to test if a number will probably factor
167
+ factorbase_len = len(factorbase) # length of the factorbase
168
+ if factorbase_len == 0:
169
+ raise Exception("Sorry, Index Calculus could not find a factorbase!")
170
+ smallprimes_len, pollard_k = factorbase_len, None
171
+ if pollard: # should we speed up trial division with Pollard p-1
172
+ smallprimes_len, pollard_k = determine_trialdivison_bounds(B // 150, factorbase)
173
+ no_sieve_bound = 1 # We do not sieve for primes smaller than this bound (not worth the effort)
174
+ no_sieve_primes = [ ]
175
+ for i in range(factorbase_len):
176
+ if factorbase[i] > no_sieve_bound:
177
+ break
178
+ no_sieve_primes.append(i)
179
+
180
+ if sieve_factor is None: # Smaller numbers seem to require a larger sieve range
181
+ if n.bit_length() < 30:
182
+ sieve_factor = 3
183
+ elif n.bit_length() < 50:
184
+ sieve_factor = 2
185
+ elif n.bit_length() < 70:
186
+ sieve_factor = 1.5
187
+ else:
188
+ sieve_factor = 1.2
189
+ sieve_bound = int(sieve_factor*(2*expected_trys2 + factorbase_len))+1
190
+ if verbose:
191
+ print(f"Factorbase: bound = {B}, size = {factorbase_len} + {sieve_bound} = {factorbase_len + sieve_bound}, max_trys = {max_trys}")
192
+ factors = [ {} for j in range(sieve_bound) ] # here we will store the prime factors for the points we are sieving
193
+
194
+ #
195
+ # Do the sieving
196
+ #
197
+
198
+ sn = isqrt(n - 1) + 1 # ceil(sqrt(n))
199
+ d = sn**2 - n
200
+ sn2 = 2 * sn
201
+
202
+ for j in range(sieve_bound):
203
+ # we sieve with respect to the quadratic polynomial f_j(x) = (x+sn)*(x+j+sn) - n = x^2 + (2*sn+j)*x + (d+j*sn)
204
+ snj = sn2 + j
205
+ snj2 = snj**2
206
+ dj = d + j * sn
207
+ max_j = sieve_bound - j
208
+ for i in range(factorbase_len):
209
+ p = factorbase[i]
210
+ if p <= no_sieve_bound:
211
+ continue
212
+ aa = snj % p
213
+ bb = dj % p
214
+ # determine the roots of f_j(x) mod p
215
+ if p == 2:
216
+ if aa == 0:
217
+ roots = [ bb ]
218
+ elif bb == 0:
219
+ roots = [ 0, 1 ]
220
+ else:
221
+ continue # no root
222
+ else:
223
+ r = sqrt_mod(aa**2 - 4 * bb, p)
224
+ if r is None: # no roots
225
+ continue
226
+ inv2 = pow(2, -1, p)
227
+ if r == 0:
228
+ roots = [ -(inv2 * aa) % p ] # one root
229
+ else:
230
+ roots = [ (inv2 * (r - aa)) % p, (inv2 * (-r - aa)) % p ] # two roots
231
+ for r in roots:
232
+ x = r # start value for x
233
+ while x < max_j:
234
+ # record which primes divide x and sum up the logs
235
+ if x in factors[j]:
236
+ factors[j][x][0].append(i)
237
+ factors[j][x][1] += factorbase_log[i]
238
+ else:
239
+ factors[j][x] = [[i], factorbase_log[i]]
240
+ x += p
241
+ if verbose:
242
+ print("Done sieving.")
243
+
244
+ #
245
+ # Set up the linear system
246
+ #
247
+ len_relations = factorbase_len + sieve_bound + 1
248
+ relations = [None] * len_relations
249
+ n_relations = 0
250
+ n_relations_redundant = 0
251
+ #
252
+ # Find the relation for b plus another relation for a (I don't know why, but with this extra relation we find a solution much faster)
253
+ #
254
+ for include_b in (True, False):
255
+ relation = find_relation(include_b)
256
+ res = process_relation(relation)
257
+ if res:
258
+ return(res)
259
+
260
+ #
261
+ # find the B-smooth numbers and add them to the system
262
+ #
263
+
264
+ for s in range(sieve_bound):
265
+ for j in range(s+1):
266
+ x = s - j
267
+ if x not in factors[j]:
268
+ continue
269
+ v = (x + sn) * (x + j + sn) - n
270
+ if factors[j][x][1] < 0.49* log(v):
271
+ continue # the number is unlikely to factor
272
+ primes = factors[j][x][0]
273
+ primes.extend(no_sieve_primes)
274
+ factors_jx = [ 0 ] * factorbase_len
275
+ for i in primes:
276
+ p = factorbase[i]
277
+ if p <= no_sieve_bound and v % p != 0:
278
+ continue
279
+ k = 1 # per our sieve we already know that p divides v
280
+ v //= p
281
+ while v % p == 0: # divide by p as many times as possible
282
+ k += 1
283
+ v //= p
284
+ factors_jx[i] = k
285
+ if v == 1:
286
+ factors_jx.reverse()
287
+ relation = [0] * sieve_bound + factors_jx + [0, 0]
288
+ if j == 0:
289
+ relation[x] = 2
290
+ else:
291
+ relation[x] = 1
292
+ relation[x + j] = 1
293
+ res = process_relation(relation)
294
+ if res:
295
+ return(res)
296
+
297
+ raise Exception(f"Sorry, Quadratic sieve could not find enough relations! ({n_relations} - {n_relations_redundant} = {n_relations - n_relations_redundant} out of {len_relations}). Try to increase sieve_factor={sieve_factor}")