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.
- {kryptools-0.3 → kryptools-0.5}/PKG-INFO +3 -3
- {kryptools-0.3 → kryptools-0.5}/README.md +1 -1
- kryptools-0.5/kryptools/Zmod.py +175 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/__init__.py +2 -2
- {kryptools-0.3 → kryptools-0.5}/kryptools/dlp.py +5 -4
- {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_qs.py +3 -3
- {kryptools-0.3 → kryptools-0.5}/kryptools/ec.py +56 -26
- {kryptools-0.3 → kryptools-0.5}/kryptools/factor.py +13 -16
- {kryptools-0.3 → kryptools-0.5}/kryptools/factor_ecm.py +2 -1
- kryptools-0.5/kryptools/factor_fmt.py +30 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/factor_pm1.py +1 -1
- {kryptools-0.3 → kryptools-0.5}/kryptools/factor_qs.py +4 -4
- {kryptools-0.3 → kryptools-0.5}/kryptools/la.py +27 -11
- {kryptools-0.3 → kryptools-0.5}/kryptools/lat.py +1 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/nt.py +15 -22
- {kryptools-0.3 → kryptools-0.5}/kryptools/poly.py +49 -29
- {kryptools-0.3 → kryptools-0.5}/kryptools/primes.py +49 -6
- {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/PKG-INFO +3 -3
- {kryptools-0.3 → kryptools-0.5}/pyproject.toml +2 -2
- kryptools-0.3/kryptools/Zmod.py +0 -115
- kryptools-0.3/kryptools/factor_fmt.py +0 -30
- {kryptools-0.3 → kryptools-0.5}/LICENSE +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_ic.py +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/dlp_rho.py +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools/factor_dix.py +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.3 → kryptools-0.5}/kryptools.egg-info/top_level.txt +0 -0
- {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
|
+
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
346
|
+
m = other.order()
|
|
317
347
|
mf = factorint(m)
|
|
318
|
-
assert m *
|
|
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) *
|
|
324
|
-
Qj = (m // pj**kj) *
|
|
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(
|
|
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
|
|
336
|
-
Pj = q**(k - 1) *
|
|
365
|
+
return self.dlog_switch(other, q**k)
|
|
366
|
+
Pj = q**(k - 1) * self
|
|
337
367
|
P1 = Pj
|
|
338
|
-
Qj = q**(k - 1) *
|
|
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) *
|
|
342
|
-
Qj = q**(k - j) *
|
|
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(
|
|
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
|
|
351
|
-
return
|
|
380
|
+
return self.dlog_naive(other, m)
|
|
381
|
+
return self.dlog_bsgs(other, m)
|
|
352
382
|
|
|
353
|
-
def dlog_naive(
|
|
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
|
|
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 !=
|
|
389
|
+
while xx != self.x:
|
|
360
390
|
j += 1
|
|
361
|
-
xx, yy =
|
|
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 ==
|
|
394
|
+
if yy == self.y:
|
|
365
395
|
return j
|
|
366
396
|
return m - j
|
|
367
397
|
|
|
368
|
-
def dlog_bsgs(
|
|
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
|
|
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 =
|
|
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 +=
|
|
409
|
+
baby_step += other
|
|
380
410
|
|
|
381
411
|
# now take the giant steps
|
|
382
|
-
giant_stride = -mm *
|
|
383
|
-
giant_step =
|
|
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,
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
|
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
|
|
@@ -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
|
|
171
|
+
del factors[j]
|
|
172
172
|
else:
|
|
173
|
-
del
|
|
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
|
-
|
|
65
|
-
|
|
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)
|
|
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)
|
|
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,
|
|
169
|
-
|
|
170
|
-
return
|
|
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
|
-
|
|
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
|
|
253
|
-
return
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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) ->
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
return Poly([other * s for s in self.coeff])
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
"
|
|
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__([
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
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.
|
|
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
|
+
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.
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Programming Language :: Python :: 3",
|
|
12
12
|
"License :: OSI Approved :: MIT License",
|
kryptools-0.3/kryptools/Zmod.py
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|