kryptools 0.1__tar.gz → 0.3__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.1 → kryptools-0.3}/PKG-INFO +5 -1
- {kryptools-0.1 → kryptools-0.3}/README.md +3 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/__init__.py +5 -5
- {kryptools-0.1 → kryptools-0.3}/kryptools/dlp.py +2 -3
- {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_ic.py +3 -2
- {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_qs.py +15 -15
- {kryptools-0.1 → kryptools-0.3}/kryptools/ec.py +22 -12
- kryptools-0.3/kryptools/factor_dix.py +107 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/factor_ecm.py +3 -0
- kryptools-0.3/kryptools/factor_fmt.py +30 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/la.py +75 -21
- {kryptools-0.1 → kryptools-0.3}/kryptools/lat.py +28 -15
- {kryptools-0.1 → kryptools-0.3}/kryptools/nt.py +8 -10
- {kryptools-0.1 → kryptools-0.3}/kryptools/poly.py +38 -5
- {kryptools-0.1 → kryptools-0.3}/kryptools/primes.py +2 -3
- {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/PKG-INFO +5 -1
- {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/SOURCES.txt +2 -0
- {kryptools-0.1 → kryptools-0.3}/pyproject.toml +3 -2
- {kryptools-0.1 → kryptools-0.3}/LICENSE +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/Zmod.py +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_rho.py +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/factor.py +3 -3
- {kryptools-0.1 → kryptools-0.3}/kryptools/factor_pm1.py +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools/factor_qs.py +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.1 → kryptools-0.3}/setup.cfg +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/teschlg/kryptools/issues
|
|
8
|
+
Project-URL: Docs, https://github.com/teschlg/kryptools/tree/main/doc
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
11
|
Classifier: Operating System :: OS Independent
|
|
@@ -33,3 +34,6 @@ The tools contained are:
|
|
|
33
34
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
34
35
|
* Poly: a class for polynomials (division, modulo)
|
|
35
36
|
* Zmod: a class for the ring of integers modulo an integer
|
|
37
|
+
|
|
38
|
+
Documentation can be found in the jupyter notebook
|
|
39
|
+
(currently incomplete: use the force, read the source).
|
|
@@ -19,3 +19,6 @@ The tools contained are:
|
|
|
19
19
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
20
20
|
* Poly: a class for polynomials (division, modulo)
|
|
21
21
|
* Zmod: a class for the ring of integers modulo an integer
|
|
22
|
+
|
|
23
|
+
Documentation can be found in the jupyter notebook
|
|
24
|
+
(currently incomplete: use the force, read the source).
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Implemenation of same basic algorithms used in cryptography.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
|
|
6
|
+
from .primes import sieve_eratosthenes, isprime
|
|
7
|
+
from .factor import factorint
|
|
5
8
|
from .dlp import dlog
|
|
6
9
|
from .ec import EC_Weierstrass
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
9
|
-
from .lat import hermite_nf, gram_schmidt, lll
|
|
10
|
-
from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
|
|
10
|
+
from .la import Matrix, zeros, eye
|
|
11
|
+
from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_plane_cvp, lagrange_lr, lll, random_unimodular_matrix
|
|
11
12
|
from .poly import Poly
|
|
12
|
-
from .primes import sieve_eratosthenes, isprime
|
|
13
13
|
from .Zmod import Zmod
|
|
@@ -28,10 +28,9 @@ def _dlog_switch(a: int, b: int, n: int, m: int) -> int:
|
|
|
28
28
|
"""Compute the discrete log_a(b) in Z_n of an element a of order m choosing an appropriate method."""
|
|
29
29
|
if m < 1000:
|
|
30
30
|
return dlog_naive(a, b, n, m)
|
|
31
|
-
|
|
31
|
+
if 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
32
|
return dlog_bsgs(a, b, n, m)
|
|
33
|
-
|
|
34
|
-
return dlog_qs(a, b, n, m)
|
|
33
|
+
return dlog_qs(a, b, n, m)
|
|
35
34
|
|
|
36
35
|
def _dlog_ph(a: int, b: int, n: int, q: int, k: int) -> int:
|
|
37
36
|
"""Compute the discrete log_a(b) in Z_n of an element a of order q^k using Pohlig-Hellman reduction."""
|
|
@@ -3,9 +3,10 @@ Discrete log solvers: Index calculus
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from math import gcd
|
|
6
|
-
from random import randint
|
|
6
|
+
from random import randint, seed
|
|
7
7
|
from .primes import sieve_eratosthenes
|
|
8
8
|
from .dlp_qs import determine_factorbound, determine_trialdivison_bounds, is_smooth
|
|
9
|
+
seed(0)
|
|
9
10
|
|
|
10
11
|
def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int = 0) -> int:
|
|
11
12
|
"""Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus."""
|
|
@@ -68,7 +69,7 @@ def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int =
|
|
|
68
69
|
rinv = pow(ri, -1, m)
|
|
69
70
|
relation[i] = 1
|
|
70
71
|
for j in range(i+1, len_relations + 1):
|
|
71
|
-
|
|
72
|
+
relation[j] = rinv * relation[j] % m
|
|
72
73
|
relations[i] = relation
|
|
73
74
|
if verbose > 2:
|
|
74
75
|
print(n_relations, f"rel found (index={i}) :", relation)
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
Discrete log solvers: Quadratic sieve
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from math import exp, log, sqrt, gcd, isqrt
|
|
6
|
+
from random import randint, seed
|
|
5
7
|
from .nt import sqrt_mod
|
|
6
8
|
from .primes import sieve_eratosthenes
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
seed(0)
|
|
9
10
|
|
|
10
11
|
def determine_factorbound(n: int) -> (int, int):
|
|
11
12
|
"""Determines the optimal factor bound and the expected number of trys until a for a given n."""
|
|
@@ -49,18 +50,18 @@ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: in
|
|
|
49
50
|
while n % p == 0: # divide by p as many times as possible
|
|
50
51
|
factors[i] += 1
|
|
51
52
|
n = n // p
|
|
52
|
-
if pollard_k:
|
|
53
|
+
if pollard_k:
|
|
53
54
|
if gcd(pow(2, pollard_k, n)-1, n) == 1: # Pollard p-1 test
|
|
54
55
|
return None # most likely not smooth, give up
|
|
55
56
|
for i in range(smallprimes_len, factorbase_len):
|
|
56
57
|
p = factorbase[i]
|
|
57
58
|
while n % p == 0: # divide by p as many times as possible
|
|
58
59
|
factors[i] += 1
|
|
59
|
-
n = n // p
|
|
60
|
+
n = n // p
|
|
60
61
|
if n != 1:
|
|
61
62
|
return None # the number factors if at the end nothing is left
|
|
62
63
|
return factors
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int:
|
|
65
66
|
"""
|
|
66
67
|
Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus with a quadratic sieve.
|
|
@@ -103,7 +104,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
103
104
|
relation += [ 0, x ]
|
|
104
105
|
relation = [0] * sieve_bound + relation # sieve values + primes + b + x
|
|
105
106
|
return relation
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
# this functions does the linear algebra
|
|
108
109
|
def process_relation(relation: list) -> None or int:
|
|
109
110
|
"""Add a new relation to the linear system and keep the system in echelon form."""
|
|
@@ -121,10 +122,10 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
121
122
|
relation[j] = (relation[j] - ri * relations[i][j]) % m
|
|
122
123
|
continue
|
|
123
124
|
# normalize the first nonzero entry
|
|
124
|
-
rinv = pow(ri, -1, m)
|
|
125
|
+
rinv = pow(ri, -1, m)
|
|
125
126
|
relation[i] = 1
|
|
126
127
|
for j in range(i+1, len_relations + 1):
|
|
127
|
-
|
|
128
|
+
relation[j] = rinv * relation[j] % m
|
|
128
129
|
relations[i] = relation
|
|
129
130
|
if verbose > 2:
|
|
130
131
|
print(n_relations, f"rel found (index={i}) :", relation)
|
|
@@ -142,8 +143,8 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
142
143
|
# if ri > 0:
|
|
143
144
|
# relations[i][index] = 0
|
|
144
145
|
# 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)
|
|
146
|
+
# relations[i][j] = (relations[i][j] - ri * relation[j]) % m
|
|
147
|
+
#print(n_relations, f"i={i}={index} ({len_relations})", relations)
|
|
147
148
|
if index == len_relations - 1: # we found the solution
|
|
148
149
|
if verbose:
|
|
149
150
|
print(f"Success after {n_relations} relations out of {len_relations}.")
|
|
@@ -159,7 +160,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
159
160
|
# Determine the parameters
|
|
160
161
|
#
|
|
161
162
|
|
|
162
|
-
B, expected_trys, expected_trys2 = determine_factorbound(n)
|
|
163
|
+
B, expected_trys, expected_trys2 = determine_factorbound(n)
|
|
163
164
|
max_trys = 10 * expected_trys
|
|
164
165
|
factorbase = []
|
|
165
166
|
factorbase = tuple(p for p in sieve_eratosthenes(B) if gcd(p,n) == 1) # compute the factorbse
|
|
@@ -171,7 +172,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
171
172
|
if pollard: # should we speed up trial division with Pollard p-1
|
|
172
173
|
smallprimes_len, pollard_k = determine_trialdivison_bounds(B // 150, factorbase)
|
|
173
174
|
no_sieve_bound = 1 # We do not sieve for primes smaller than this bound (not worth the effort)
|
|
174
|
-
no_sieve_primes = [ ]
|
|
175
|
+
no_sieve_primes = [ ]
|
|
175
176
|
for i in range(factorbase_len):
|
|
176
177
|
if factorbase[i] > no_sieve_bound:
|
|
177
178
|
break
|
|
@@ -194,7 +195,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
194
195
|
#
|
|
195
196
|
# Do the sieving
|
|
196
197
|
#
|
|
197
|
-
|
|
198
|
+
|
|
198
199
|
sn = isqrt(n - 1) + 1 # ceil(sqrt(n))
|
|
199
200
|
d = sn**2 - n
|
|
200
201
|
sn2 = 2 * sn
|
|
@@ -202,7 +203,6 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
202
203
|
for j in range(sieve_bound):
|
|
203
204
|
# 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
205
|
snj = sn2 + j
|
|
205
|
-
snj2 = snj**2
|
|
206
206
|
dj = d + j * sn
|
|
207
207
|
max_j = sieve_bound - j
|
|
208
208
|
for i in range(factorbase_len):
|
|
@@ -293,5 +293,5 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
293
293
|
res = process_relation(relation)
|
|
294
294
|
if res:
|
|
295
295
|
return(res)
|
|
296
|
-
|
|
296
|
+
|
|
297
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}")
|
|
@@ -26,12 +26,13 @@ class EC_Weierstrass():
|
|
|
26
26
|
>>> P + Q
|
|
27
27
|
(195, 41)
|
|
28
28
|
"""
|
|
29
|
+
# pylint: disable=too-many-instance-attributes
|
|
29
30
|
|
|
30
31
|
def __init__(self, p: int, a: int, b: int, order: int = None):
|
|
31
32
|
if p < 3:
|
|
32
33
|
raise ValueError(f"{p} must be a prime larger than 2.")
|
|
33
34
|
if (4 * pow(a, 3, p) + 27 * pow(b, 2, p) ) % p == 0:
|
|
34
|
-
raise ValueError(
|
|
35
|
+
raise ValueError("Curve is singular!")
|
|
35
36
|
self.p = p
|
|
36
37
|
self.gf = Zmod(p, short = True)
|
|
37
38
|
self.a = self.gf(a % p)
|
|
@@ -44,9 +45,9 @@ class EC_Weierstrass():
|
|
|
44
45
|
def __call__(self, x: int | str | None = None, y: int | None = None, short: bool = False):
|
|
45
46
|
if isinstance(x, str):
|
|
46
47
|
x = x.replace(" ", "")
|
|
47
|
-
|
|
48
|
+
coordinate_type = x[:2]
|
|
48
49
|
x = x[2:]
|
|
49
|
-
if
|
|
50
|
+
if coordinate_type in ('02', '03'):
|
|
50
51
|
short = 1
|
|
51
52
|
x = int(x, 16)
|
|
52
53
|
if type == '02':
|
|
@@ -70,15 +71,17 @@ class EC_Weierstrass():
|
|
|
70
71
|
return P.y**2 == P.x**3 + self.a * P.x + self.b
|
|
71
72
|
|
|
72
73
|
def info(self):
|
|
73
|
-
|
|
74
|
+
"Display some basic info on the curve"
|
|
75
|
+
out = "Weierstrass curve y^2 = x^3"
|
|
74
76
|
if int(self.a):
|
|
75
|
-
|
|
77
|
+
out += f" + {self.a} x"
|
|
76
78
|
if int(self.b):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
print(
|
|
79
|
+
out += f" + {self.b}"
|
|
80
|
+
out += f" over Z_{self.p}."
|
|
81
|
+
print(out)
|
|
80
82
|
|
|
81
83
|
def add(self, x1, y1, x2, y2):
|
|
84
|
+
"Point addition"
|
|
82
85
|
if x1 is None:
|
|
83
86
|
return x2, y2
|
|
84
87
|
if x2 is None:
|
|
@@ -93,6 +96,7 @@ class EC_Weierstrass():
|
|
|
93
96
|
return x3, y3
|
|
94
97
|
|
|
95
98
|
def dbl(self, x, y):
|
|
99
|
+
"Point doubling"
|
|
96
100
|
if x is None or not y:
|
|
97
101
|
return None, None
|
|
98
102
|
s = (3 * x**2 + self.a) / (2 * y)
|
|
@@ -101,6 +105,7 @@ class EC_Weierstrass():
|
|
|
101
105
|
return x3, y3
|
|
102
106
|
|
|
103
107
|
def mult(self, j: int, x, y): # Addition-subtraction ladder
|
|
108
|
+
"Point multiplication"
|
|
104
109
|
if j == 0:
|
|
105
110
|
return None, None
|
|
106
111
|
if j < 0:
|
|
@@ -126,6 +131,7 @@ class EC_Weierstrass():
|
|
|
126
131
|
return xx, yy
|
|
127
132
|
|
|
128
133
|
def random(self):
|
|
134
|
+
"Return a random point."
|
|
129
135
|
j = -1
|
|
130
136
|
while j == -1:
|
|
131
137
|
x = self.gf(randint(0, self.p - 1))
|
|
@@ -134,6 +140,7 @@ class EC_Weierstrass():
|
|
|
134
140
|
return ECPoint(x, randint(0, 1), self, short = True)
|
|
135
141
|
|
|
136
142
|
def order(self, order: int = None) -> int:
|
|
143
|
+
"Return the group order."
|
|
137
144
|
if order:
|
|
138
145
|
self.group_order = order
|
|
139
146
|
elif not self.group_order:
|
|
@@ -144,6 +151,7 @@ class EC_Weierstrass():
|
|
|
144
151
|
return self.group_order
|
|
145
152
|
|
|
146
153
|
def factor_order(self) -> dict:
|
|
154
|
+
"Factor the group order."
|
|
147
155
|
if self.group_order_factors:
|
|
148
156
|
return self.group_order_factors
|
|
149
157
|
if not self.group_order:
|
|
@@ -152,6 +160,7 @@ class EC_Weierstrass():
|
|
|
152
160
|
return self.group_order_factors
|
|
153
161
|
|
|
154
162
|
def order_naive(self) -> int:
|
|
163
|
+
"Return the order of the group by adding the Legendre symbols."
|
|
155
164
|
a1 = (int(self.a) + 1) % self.p
|
|
156
165
|
y = int(self.b)
|
|
157
166
|
order = self.p + 1 + legendre_symbol(y, self.p)
|
|
@@ -161,6 +170,7 @@ class EC_Weierstrass():
|
|
|
161
170
|
return order
|
|
162
171
|
|
|
163
172
|
def order_shanks_mestre(self) -> int:
|
|
173
|
+
"Return the order of the group using the Shanks-Mestre algorithm."
|
|
164
174
|
if self.p < 230:
|
|
165
175
|
return self.order_naive()
|
|
166
176
|
j = 1
|
|
@@ -215,7 +225,7 @@ class EC_Weierstrass():
|
|
|
215
225
|
|
|
216
226
|
class ECPoint:
|
|
217
227
|
"Point on an elliptic curve"
|
|
218
|
-
def __init__(self, x: int, y: int, curve: EC_Weierstrass, short:bool = False):
|
|
228
|
+
def __init__(self, x: int|None, y: int|None, curve: EC_Weierstrass, short:bool = False):
|
|
219
229
|
self.curve = curve
|
|
220
230
|
if x is None:
|
|
221
231
|
self.x = None
|
|
@@ -343,7 +353,7 @@ class ECPoint:
|
|
|
343
353
|
def dlog_naive(Q, P: "ECPoint", m: int) -> int:
|
|
344
354
|
"""Compute the discrete log_P(Q) in EC using an exhaustive search."""
|
|
345
355
|
if not Q.curve == P.curve and not isinstance(Q, P.__class__):
|
|
346
|
-
raise ValueError(
|
|
356
|
+
raise ValueError("Points must be on the same curve!")
|
|
347
357
|
j = 0
|
|
348
358
|
xx, yy = None, None
|
|
349
359
|
while xx != Q.x:
|
|
@@ -358,7 +368,7 @@ class ECPoint:
|
|
|
358
368
|
def dlog_bsgs(Q, P: "ECPoint", m: int) -> int:
|
|
359
369
|
"""Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm."""
|
|
360
370
|
if not Q.curve == P.curve and not isinstance(P, Q.__class__):
|
|
361
|
-
raise ValueError(
|
|
371
|
+
raise ValueError("Points must be on the same curve!")
|
|
362
372
|
mm = 1 + isqrt(m - 1)
|
|
363
373
|
m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps
|
|
364
374
|
# initialize baby_steps table
|
|
@@ -372,7 +382,7 @@ class ECPoint:
|
|
|
372
382
|
giant_stride = -mm * P
|
|
373
383
|
giant_step = Q
|
|
374
384
|
for l in range(mm+1):
|
|
375
|
-
if giant_step.x
|
|
385
|
+
if giant_step.x is None:
|
|
376
386
|
return l * mm
|
|
377
387
|
if int(giant_step.x) in baby_steps:
|
|
378
388
|
j = baby_steps[int(giant_step.x)][0]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integer factorization: Dixon's method
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import isqrt, gcd, sqrt, log, exp, ceil
|
|
6
|
+
from .primes import sieve_eratosthenes
|
|
7
|
+
from .nt import legendre_symbol
|
|
8
|
+
from .factor_qs import bytexor, byteset, bytetest
|
|
9
|
+
|
|
10
|
+
def is_smooth(n: int, factorbase: list, lfb: int) -> bytes or None:
|
|
11
|
+
"""Try to factor n with respect to a given factorbase.
|
|
12
|
+
Upon success a bytestring, whose bits are the exponents with repect to the factorbase mod 2, is returned.
|
|
13
|
+
Otherwise None."""
|
|
14
|
+
factors = bytearray(b"\x00") * lfb # we store the exponents mod 2 as bits
|
|
15
|
+
if n < 0:
|
|
16
|
+
byteset(factors, 0)
|
|
17
|
+
n *= -1
|
|
18
|
+
for i, p in enumerate(factorbase):
|
|
19
|
+
k = 0
|
|
20
|
+
while n % p == 0: # divide by p as many times as possible
|
|
21
|
+
k = (k + 1) % 2 # we only need the exponents mod 2
|
|
22
|
+
n = n // p
|
|
23
|
+
if k:
|
|
24
|
+
byteset(factors, i + 1)
|
|
25
|
+
if n != 1:
|
|
26
|
+
return None # the number factors if at the end nothing is left
|
|
27
|
+
return factors
|
|
28
|
+
|
|
29
|
+
def factor_dixon(n: int) -> list:
|
|
30
|
+
"""Find factors of n using the method of Dixon."""
|
|
31
|
+
# first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
|
|
32
|
+
# 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
|
|
33
|
+
# we use Newton
|
|
34
|
+
u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
|
|
35
|
+
for _ in range(3):
|
|
36
|
+
u = (2 * log(n) + u * u * (2 + log(u))) / (
|
|
37
|
+
2 + 3 * u + 2 * u * log(u)
|
|
38
|
+
) # Newton iteration
|
|
39
|
+
B = int(exp(log(n) / u))
|
|
40
|
+
|
|
41
|
+
# B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
|
|
42
|
+
factorbase = []
|
|
43
|
+
for p in sieve_eratosthenes(B): # compute the factorbase
|
|
44
|
+
ls = legendre_symbol(n, p)
|
|
45
|
+
if ls == 0: # we already found a factor;-)
|
|
46
|
+
if n == p:
|
|
47
|
+
return n
|
|
48
|
+
return p
|
|
49
|
+
if ls == 1: # we only take primes such that n is quadratic residue
|
|
50
|
+
factorbase.append(p)
|
|
51
|
+
lf = len(factorbase) + 1 # length of the factorbase (including -1)
|
|
52
|
+
lfb = ceil(lf / 8) # the number of bytes we need to store a relation
|
|
53
|
+
m = isqrt(n - 1) + 1
|
|
54
|
+
def process_relation(j: int, relation: bytes):
|
|
55
|
+
nonlocal relation_no, values, relations
|
|
56
|
+
relation_no += 1
|
|
57
|
+
rhs = bytearray(b"\x00") * lfb # construct the k'th row of the identity matrix
|
|
58
|
+
byteset(rhs, relation_no)
|
|
59
|
+
relation += rhs # extend the relation with this row
|
|
60
|
+
values[relation_no] = j # store the value which lead to the relation
|
|
61
|
+
# do the Gauss elimination
|
|
62
|
+
index = lf # this will be the index of the first nonzero entry
|
|
63
|
+
# print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
|
|
64
|
+
for i in range(lf):
|
|
65
|
+
if bytetest(relation, i) and relations[i] is not None: # make this entry zero if we can (Gauss elimination)
|
|
66
|
+
bytexor(relation, relations[i])
|
|
67
|
+
if bytetest(relation, i) and index == lf: # is this the index of the first nonzero entry?
|
|
68
|
+
index = i
|
|
69
|
+
# print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
|
|
70
|
+
if index == lf: # the new relation is linearly dependent: we have found a linear combination of the 0 vector
|
|
71
|
+
# now we need to determine the factors
|
|
72
|
+
u = 1 # product over all values m - j such that f(m-j) is B-smooth
|
|
73
|
+
v = 1 # sqrt of the product over all f(m-j) which are B-smooth
|
|
74
|
+
w = 1 # save nonsquare terms for the next round
|
|
75
|
+
for i in range(lf):
|
|
76
|
+
if bytetest(relation, lfb * 8 + i): # select the relations which sum to the 0 vector
|
|
77
|
+
ui = m + values[i]
|
|
78
|
+
u = (u * ui) % n
|
|
79
|
+
vi = ui ** 2 - n
|
|
80
|
+
d = gcd(w, vi)
|
|
81
|
+
v = (v * d) % n # sqrt of the part which is already square
|
|
82
|
+
w = (w // d) * (vi // d) # this part is not square yet
|
|
83
|
+
v = v * isqrt(w) % n
|
|
84
|
+
res = gcd(u - v, n)
|
|
85
|
+
if 1 < res < n:
|
|
86
|
+
return res
|
|
87
|
+
relation_no -= 1 # this one did not work, try again
|
|
88
|
+
else:
|
|
89
|
+
relations[index] = relation
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
relation_no = -1
|
|
93
|
+
relations = [None] * lf # here we will store the relations in case we found a B-smooth number
|
|
94
|
+
values = [None] * lf # here we will store the values leading to the B-smooth numbers
|
|
95
|
+
for j in range(1,m): # loop over j=0,1,-1,2,-2,...
|
|
96
|
+
if j & 1 == 0:
|
|
97
|
+
j >>= 1
|
|
98
|
+
else:
|
|
99
|
+
j >>= 1
|
|
100
|
+
j *= -1
|
|
101
|
+
relation = is_smooth((j + m) ** 2 - n, factorbase, lfb) # test if f(j) is B-smooth
|
|
102
|
+
if relation is None:
|
|
103
|
+
continue
|
|
104
|
+
res = process_relation(j, relation)
|
|
105
|
+
if res:
|
|
106
|
+
return res
|
|
107
|
+
return n
|
|
@@ -11,6 +11,7 @@ from .primes import sieve_eratosthenes
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def dbl(P, c2, p):
|
|
14
|
+
"EC Montgommery point doubling"
|
|
14
15
|
# c2 = c - 2
|
|
15
16
|
t1 = (P[0] + P[1]) % p
|
|
16
17
|
t2 = (P[0] - P[1]) % p
|
|
@@ -19,12 +20,14 @@ def dbl(P, c2, p):
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def add(P1, P2, P3, p):
|
|
23
|
+
"EC Montgommery point addition"
|
|
22
24
|
t1 = pow(P1[0] * P2[0] - P1[1] * P2[1], 2, p)
|
|
23
25
|
t2 = pow(P1[0] * P2[1] - P2[0] * P1[1], 2, p)
|
|
24
26
|
return P3[1] * t1 % p, P3[0] * t2 % p
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def mult(k, P, c2, p):
|
|
30
|
+
"EC Montgommery point multiplication"
|
|
28
31
|
if k == 1:
|
|
29
32
|
return P
|
|
30
33
|
if k == 2:
|
|
@@ -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
|
+
# 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
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Linear algebra
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from math import sqrt, prod
|
|
5
|
+
from math import inf, sqrt, prod
|
|
6
6
|
|
|
7
7
|
class Matrix:
|
|
8
8
|
"""
|
|
@@ -48,12 +48,18 @@ class Matrix:
|
|
|
48
48
|
i, j = item
|
|
49
49
|
if isinstance(i, int) and isinstance(j, int):
|
|
50
50
|
return self.matrix[i][j]
|
|
51
|
-
rows = range(self.rows)[i]
|
|
52
51
|
if isinstance(i, int):
|
|
53
|
-
rows = [
|
|
54
|
-
|
|
52
|
+
rows = [ i ]
|
|
53
|
+
elif isinstance(i, list):
|
|
54
|
+
rows = i
|
|
55
|
+
else:
|
|
56
|
+
rows = range(self.rows)[i]
|
|
55
57
|
if isinstance(j, int):
|
|
56
|
-
cols = [
|
|
58
|
+
cols = [ j ]
|
|
59
|
+
elif isinstance(j, list):
|
|
60
|
+
cols = i
|
|
61
|
+
else:
|
|
62
|
+
cols = range(self.cols)[j]
|
|
57
63
|
return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
|
|
58
64
|
i, j = divmod(item, self.cols)
|
|
59
65
|
return self.matrix[i][j]
|
|
@@ -64,12 +70,18 @@ class Matrix:
|
|
|
64
70
|
if isinstance(i, int) and isinstance(j, int):
|
|
65
71
|
self.matrix[i][j] = value
|
|
66
72
|
return
|
|
67
|
-
rows = range(self.rows)[i]
|
|
68
73
|
if isinstance(i, int):
|
|
69
|
-
rows = [
|
|
70
|
-
|
|
74
|
+
rows = [ i ]
|
|
75
|
+
elif isinstance(i, list):
|
|
76
|
+
rows = i
|
|
77
|
+
else:
|
|
78
|
+
rows = range(self.rows)[i]
|
|
71
79
|
if isinstance(j, int):
|
|
72
|
-
cols = [
|
|
80
|
+
cols = [ j ]
|
|
81
|
+
elif isinstance(j, list):
|
|
82
|
+
cols = i
|
|
83
|
+
else:
|
|
84
|
+
cols = range(self.cols)[j]
|
|
73
85
|
for i, ii in zip(cols,range(len(cols))):
|
|
74
86
|
for j, jj in zip(rows,range(len(rows))):
|
|
75
87
|
self.matrix[j][i] = value[jj,ii]
|
|
@@ -97,11 +109,20 @@ class Matrix:
|
|
|
97
109
|
"Squared Frobenius/Euclidean norm."
|
|
98
110
|
return sum( sum(x*x for x in row) for row in self.matrix )
|
|
99
111
|
|
|
100
|
-
def norm(self) -> float:
|
|
101
|
-
"
|
|
102
|
-
|
|
112
|
+
def norm(self, p: int = 2) -> float:
|
|
113
|
+
"p-norm of a matrix regarded as a vector."
|
|
114
|
+
if p == 2:
|
|
115
|
+
return sqrt(self.norm2())
|
|
116
|
+
if p == 1:
|
|
117
|
+
return sum( sum(abs(x) for x in row) for row in self.matrix )
|
|
118
|
+
if p == inf:
|
|
119
|
+
return max( max(abs(x) for x in row) for row in self.matrix )
|
|
120
|
+
tmp = sum( sum(abs(x)**p for x in row) for row in self.matrix )
|
|
121
|
+
return tmp**(1/p)
|
|
122
|
+
|
|
103
123
|
|
|
104
124
|
def dot(self, other) -> int:
|
|
125
|
+
"Dot product of two vectors."
|
|
105
126
|
if self.rows == 1 and other.rows == 1 and self.cols == other.cols:
|
|
106
127
|
return sum(x * y for x, y in zip(self.matrix[0], other.matrix[0]))
|
|
107
128
|
if self.cols == 1 and other.cols == 1 and self.rows == other.rows:
|
|
@@ -109,17 +130,21 @@ class Matrix:
|
|
|
109
130
|
return NotImplemented
|
|
110
131
|
|
|
111
132
|
def transpose(self) -> "Matrix":
|
|
133
|
+
"Transpose of a matrix."
|
|
112
134
|
return Matrix([list(i) for i in zip(*self.matrix)])
|
|
113
135
|
|
|
114
136
|
def multiply(self, other) -> "Matrix":
|
|
115
|
-
|
|
137
|
+
"Matrix multiplication."
|
|
138
|
+
if not isinstance(other, Matrix):
|
|
116
139
|
return NotImplemented
|
|
117
|
-
|
|
140
|
+
if self.cols != other.rows:
|
|
141
|
+
raise NotImplementedError("Matrix dimensions do not match!")
|
|
142
|
+
result = self.zeros(self.rows, other.cols)
|
|
118
143
|
for i in range(self.rows):
|
|
119
144
|
for j in range(other.cols):
|
|
120
145
|
for k in range(other.rows):
|
|
121
|
-
result[i][j] += self.matrix[i][k] * other.matrix[k][j]
|
|
122
|
-
return
|
|
146
|
+
result.matrix[i][j] += self.matrix[i][k] * other.matrix[k][j]
|
|
147
|
+
return result
|
|
123
148
|
|
|
124
149
|
def __add__(self, other) -> "Matrix":
|
|
125
150
|
if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
|
|
@@ -150,7 +175,7 @@ class Matrix:
|
|
|
150
175
|
R = self[:, :]
|
|
151
176
|
i = 0
|
|
152
177
|
for j in range(n):
|
|
153
|
-
if not R[i, j]: # search for
|
|
178
|
+
if not R[i, j]: # search for a nonzero entry in the present column
|
|
154
179
|
for ii in range(i+1,m):
|
|
155
180
|
if R[ii, j]:
|
|
156
181
|
R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows
|
|
@@ -173,12 +198,12 @@ class Matrix:
|
|
|
173
198
|
"Compute the determinant of a matrix M."
|
|
174
199
|
if self.rows != self.rows:
|
|
175
200
|
raise ValueError("Matrix must be square!")
|
|
176
|
-
n = self.cols
|
|
201
|
+
n, m = self.cols, self.rows
|
|
177
202
|
R = self[:, :]
|
|
178
203
|
D = 1
|
|
179
204
|
i = 0
|
|
180
205
|
for j in range(n):
|
|
181
|
-
if not R[i, j]: # search for
|
|
206
|
+
if not R[i, j]: # search for a nonzero entry in the present column
|
|
182
207
|
for ii in range(i+1,m):
|
|
183
208
|
if R[ii, j]:
|
|
184
209
|
D *= -1
|
|
@@ -211,13 +236,42 @@ class Matrix:
|
|
|
211
236
|
raise ValueError("Matrix is not invertible!")
|
|
212
237
|
return MM[:,n:]
|
|
213
238
|
|
|
239
|
+
def zeros(self, m: int = None, n: int = None):
|
|
240
|
+
"Returns a zero matrix of the same dimension"
|
|
241
|
+
if not m and not n:
|
|
242
|
+
n, m = self.cols, self.rows
|
|
243
|
+
elif not n:
|
|
244
|
+
n = m
|
|
245
|
+
zero = 0 * self[0]
|
|
246
|
+
return Matrix([[ zero for j in range(n)] for i in range(m) ])
|
|
247
|
+
|
|
248
|
+
def eye(self, m: int = None, n: int = None):
|
|
249
|
+
"Returns an identity matrix of the same dimension"
|
|
250
|
+
def delta(i, j):
|
|
251
|
+
if i == j:
|
|
252
|
+
return 1
|
|
253
|
+
return 0
|
|
254
|
+
if not m and not n:
|
|
255
|
+
n, m = self.cols, self.rows
|
|
256
|
+
elif not n:
|
|
257
|
+
n = m
|
|
258
|
+
zero = 0 * self[0]
|
|
259
|
+
one = 1 + zero
|
|
260
|
+
return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
|
|
261
|
+
|
|
214
262
|
|
|
215
|
-
def zeros(m:int, n: int) -> "Matrix":
|
|
263
|
+
def zeros(m: int, n: int = None) -> "Matrix":
|
|
264
|
+
"Returns a zero matrix of the given dimension"
|
|
265
|
+
if not n:
|
|
266
|
+
n = m
|
|
216
267
|
return Matrix([[ 0 for j in range(n)] for i in range(m) ])
|
|
217
268
|
|
|
218
|
-
def eye(m:int, n: int) -> "Matrix":
|
|
269
|
+
def eye(m:int, n: int = None) -> "Matrix":
|
|
270
|
+
"Returns an identity matrix of the given dimension"
|
|
219
271
|
def delta(i, j):
|
|
220
272
|
if i == j:
|
|
221
273
|
return 1
|
|
222
274
|
return 0
|
|
275
|
+
if not n:
|
|
276
|
+
n = m
|
|
223
277
|
return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
|
|
@@ -4,13 +4,8 @@ Lattice tools
|
|
|
4
4
|
|
|
5
5
|
from math import prod
|
|
6
6
|
from fractions import Fraction
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
def babai_round_cvp(x: Matrix, U: Matrix) -> Matrix:
|
|
10
|
-
"Babai's rounding algorithm for solving the CVP."
|
|
11
|
-
s = U.inv() * x
|
|
12
|
-
k = s.map(round)
|
|
13
|
-
return U * k
|
|
7
|
+
from random import choice, sample
|
|
8
|
+
from .la import Matrix, zeros
|
|
14
9
|
|
|
15
10
|
def hermite_nf(M: Matrix) -> Matrix:
|
|
16
11
|
"Compute the Hermite normal form of a matrix M."
|
|
@@ -57,7 +52,7 @@ def norm2(v: Matrix) -> float:
|
|
|
57
52
|
|
|
58
53
|
def gram_schmidt(U: Matrix) -> (Matrix, Matrix):
|
|
59
54
|
"Compute the Gram-Schmidt orthogonalization of the column vectors of a matrix M."
|
|
60
|
-
M = eye(
|
|
55
|
+
M = U.eye()
|
|
61
56
|
Us = U[:, :]
|
|
62
57
|
for j in range(1, U.rows):
|
|
63
58
|
tmp = U[:, j]
|
|
@@ -68,13 +63,21 @@ def gram_schmidt(U: Matrix) -> (Matrix, Matrix):
|
|
|
68
63
|
return Us, M
|
|
69
64
|
|
|
70
65
|
def gram_det(U: Matrix) -> float:
|
|
66
|
+
"Compute the Gram determinant of a matrix."
|
|
71
67
|
Us = gram_schmidt(U)[0]
|
|
72
68
|
return prod([Us[:, i].norm() for i in range(U.rows)])
|
|
73
69
|
|
|
74
70
|
def hadamard_ratio(M: Matrix) -> float:
|
|
71
|
+
"Compute the Hadamard ratio of a matrix."
|
|
75
72
|
m = M.rows
|
|
76
73
|
return (gram_det(M) / prod([M[:, i].norm() for i in range(m)])) ** (1 / m)
|
|
77
74
|
|
|
75
|
+
def babai_round_cvp(x: Matrix, U: Matrix) -> Matrix:
|
|
76
|
+
"Babai's rounding algorithm for solving the CVP."
|
|
77
|
+
s = U.inv() * x
|
|
78
|
+
k = s.applyfunc(round)
|
|
79
|
+
return U * k
|
|
80
|
+
|
|
78
81
|
def babai_plane_cvp(x: Matrix, U: Matrix) -> Matrix:
|
|
79
82
|
"Babai's closest plane algorithm for solving the CVP."
|
|
80
83
|
Us = gram_schmidt(U)[0]
|
|
@@ -83,6 +86,18 @@ def babai_plane_cvp(x: Matrix, U: Matrix) -> Matrix:
|
|
|
83
86
|
y = y - round(y.dot(Us[:, k]) / norm2(Us[:, k])) * U[:, k]
|
|
84
87
|
return (x - y).applyfunc(round)
|
|
85
88
|
|
|
89
|
+
def lagrange_lr(V: Matrix) -> Matrix:
|
|
90
|
+
"Lagrange lattice reduction."
|
|
91
|
+
assert (V.rows, V.cols) == (2, 2)
|
|
92
|
+
v1, v2 = V[:, 0], V[:, 1]
|
|
93
|
+
if norm2(v1) > norm2(v2):
|
|
94
|
+
v1, v2 = v2, v1
|
|
95
|
+
v3 = v2 - round(v1.dot(v2) / norm2(v1)) * v1
|
|
96
|
+
while norm2(v3) < norm2(v1):
|
|
97
|
+
v2, v1 = v1, v3
|
|
98
|
+
v3 = v2 - round(v1.dot(v2) / norm2(v1)) * v1
|
|
99
|
+
return Matrix([list(v1), list(v3)]).transpose()
|
|
100
|
+
|
|
86
101
|
def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
87
102
|
"lll algorithm for lattice reduction"
|
|
88
103
|
|
|
@@ -91,7 +106,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
91
106
|
U = V[:, :]
|
|
92
107
|
Us = U[:, :]
|
|
93
108
|
Us.map(Fraction)
|
|
94
|
-
M = zeros(
|
|
109
|
+
M = U.zeros()
|
|
95
110
|
M.map(Fraction)
|
|
96
111
|
M[0, 0] = norm2(Us[:, 0]) # we store the squared norms on the diagonal
|
|
97
112
|
for l in range(1, U.rows): # Gram-Schmidt decomposition
|
|
@@ -147,11 +162,9 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
147
162
|
U[:, j] = tmp[j]
|
|
148
163
|
return U
|
|
149
164
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) -> Matrix:
|
|
165
|
+
def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = None) -> Matrix:
|
|
153
166
|
"Create a random unimodular matrix of dimension n."
|
|
154
|
-
W =
|
|
167
|
+
W = zeros(n)
|
|
155
168
|
for i in range(n):
|
|
156
169
|
for j in range(i, n):
|
|
157
170
|
W[i, j] = choice([-1, 1])
|
|
@@ -159,10 +172,10 @@ def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) ->
|
|
|
159
172
|
for _ in range(iterations):
|
|
160
173
|
i, j = sample(range(n), 2)
|
|
161
174
|
tmp = W[i, :] + choice([-1, 1]) * W[j, :]
|
|
162
|
-
if max([abs(x) for x in tmp]) <= max_val:
|
|
175
|
+
if not max_val or max([abs(x) for x in tmp]) <= max_val:
|
|
163
176
|
W[i, :] = tmp
|
|
164
177
|
i, j = sample(range(n), 2)
|
|
165
178
|
tmp = W[:, i] + choice([-1, 1]) * W[:, j]
|
|
166
|
-
if max([abs(x) for x in tmp]) <= max_val:
|
|
179
|
+
if not max_val or max([abs(x) for x in tmp]) <= max_val:
|
|
167
180
|
W[:, i] = tmp
|
|
168
181
|
return W
|
|
@@ -10,7 +10,6 @@ Number theory tools:
|
|
|
10
10
|
"""
|
|
11
11
|
from math import gcd, prod
|
|
12
12
|
from fractions import Fraction
|
|
13
|
-
from .factor import factorint
|
|
14
13
|
|
|
15
14
|
# Euclid and friends
|
|
16
15
|
|
|
@@ -115,8 +114,7 @@ def crt(a: list, m: list) -> int:
|
|
|
115
114
|
def fraction_repr(self):
|
|
116
115
|
if self.denominator == 1:
|
|
117
116
|
return str(self.numerator)
|
|
118
|
-
|
|
119
|
-
return str(self.numerator) + "/" + str(self.denominator)
|
|
117
|
+
return str(self.numerator) + "/" + str(self.denominator)
|
|
120
118
|
|
|
121
119
|
Fraction.__repr__ = fraction_repr
|
|
122
120
|
|
|
@@ -209,7 +207,7 @@ def sqrt_mod(a: int, p: int) -> list:
|
|
|
209
207
|
|
|
210
208
|
# Euler phi and Carmichael function
|
|
211
209
|
|
|
212
|
-
from
|
|
210
|
+
from .factor import factorint
|
|
213
211
|
|
|
214
212
|
|
|
215
213
|
def euler_phi(n: int) -> int:
|
|
@@ -250,22 +248,22 @@ def order(a: int, n: int, factor=False) -> int:
|
|
|
250
248
|
factors[p] += k - 1
|
|
251
249
|
else:
|
|
252
250
|
factors[p] = k - 1
|
|
253
|
-
|
|
251
|
+
order_a = 1 # compute the group order euler_phi(n) as our current guess
|
|
254
252
|
for p, k in factors.items():
|
|
255
|
-
|
|
253
|
+
order_a *= p**k
|
|
256
254
|
if factor: # we compute the factorization of the order along the way
|
|
257
255
|
factors_order = {} # factorization of the order
|
|
258
256
|
for p, k in factors.items():
|
|
259
257
|
i = 0
|
|
260
258
|
for _ in range(k):
|
|
261
|
-
order_try =
|
|
259
|
+
order_try = order_a // p
|
|
262
260
|
if pow(a, order_try, n) == 1:
|
|
263
|
-
|
|
261
|
+
order_a = order_try
|
|
264
262
|
i += 1
|
|
265
263
|
else:
|
|
266
264
|
break
|
|
267
265
|
if factor and i < k:
|
|
268
266
|
factors_order[p] = k - i
|
|
269
267
|
if factor:
|
|
270
|
-
return
|
|
271
|
-
return
|
|
268
|
+
return order_a, factors_order
|
|
269
|
+
return order_a
|
|
@@ -83,7 +83,12 @@ class Poly:
|
|
|
83
83
|
|
|
84
84
|
def __add__(self, other: "Poly") -> "Poly":
|
|
85
85
|
if not isinstance(other, self.__class__):
|
|
86
|
-
|
|
86
|
+
try:
|
|
87
|
+
tmp = self.coeff[:]
|
|
88
|
+
tmp[0] += other
|
|
89
|
+
return self.__class__(tmp, modulus=self.modulus)
|
|
90
|
+
except:
|
|
91
|
+
return NotImplemented
|
|
87
92
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
88
93
|
if ls < lo:
|
|
89
94
|
scoeff = self.coeff + (lo - ls) * [0]
|
|
@@ -98,12 +103,27 @@ class Poly:
|
|
|
98
103
|
modulus = other.modulus
|
|
99
104
|
return self.__class__([s + o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
|
|
100
105
|
|
|
106
|
+
def __radd__(self, other: "Poly") -> "Poly":
|
|
107
|
+
if not isinstance(other, self.__class__):
|
|
108
|
+
try:
|
|
109
|
+
tmp = self.coeff[:]
|
|
110
|
+
tmp[0] += other
|
|
111
|
+
return self.__class__(tmp, modulus=self.modulus)
|
|
112
|
+
except:
|
|
113
|
+
pass
|
|
114
|
+
return NotImplemented
|
|
115
|
+
|
|
101
116
|
def __neg__(self) -> "Poly":
|
|
102
117
|
return Poly([-s for s in self.coeff], modulus=self.modulus)
|
|
103
118
|
|
|
104
119
|
def __sub__(self, other: "Poly") -> "Poly":
|
|
105
120
|
if not isinstance(other, self.__class__):
|
|
106
|
-
|
|
121
|
+
try:
|
|
122
|
+
tmp = self.coeff[:]
|
|
123
|
+
tmp[0] -= other
|
|
124
|
+
return self.__class__(tmp, modulus=self.modulus)
|
|
125
|
+
except:
|
|
126
|
+
return NotImplemented
|
|
107
127
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
108
128
|
if ls < lo:
|
|
109
129
|
scoeff = self.coeff + (lo - ls) * [0]
|
|
@@ -118,11 +138,22 @@ class Poly:
|
|
|
118
138
|
modulus = other.modulus
|
|
119
139
|
return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
|
|
120
140
|
|
|
141
|
+
def __rsub__(self, other: "Poly") -> "Poly":
|
|
142
|
+
if not isinstance(other, self.__class__):
|
|
143
|
+
try:
|
|
144
|
+
tmp = self.coeff[:]
|
|
145
|
+
tmp[0] -= other
|
|
146
|
+
return self.__class__(tmp, modulus=self.modulus)
|
|
147
|
+
except:
|
|
148
|
+
pass
|
|
149
|
+
return NotImplemented
|
|
150
|
+
|
|
121
151
|
def __mul__(self, other: "Poly") -> "Poly":
|
|
122
|
-
if isinstance(other, int):
|
|
123
|
-
return Poly([other * s for s in self.coeff])
|
|
124
152
|
if not isinstance(other, self.__class__):
|
|
125
|
-
|
|
153
|
+
try:
|
|
154
|
+
return Poly([other * s for s in self.coeff])
|
|
155
|
+
except:
|
|
156
|
+
return NotImplemented
|
|
126
157
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
127
158
|
coeff = [0] * (ls + lo - 1)
|
|
128
159
|
for k in range(ls + lo - 1):
|
|
@@ -153,6 +184,7 @@ class Poly:
|
|
|
153
184
|
return res
|
|
154
185
|
|
|
155
186
|
def divmod(self, other: "Poly") -> ("Poly", "Poly"):
|
|
187
|
+
"Polynom division with remainder"
|
|
156
188
|
if isinstance(other, list):
|
|
157
189
|
other = self.__class__(other)
|
|
158
190
|
elif not isinstance(other, self.__class__):
|
|
@@ -183,6 +215,7 @@ class Poly:
|
|
|
183
215
|
)
|
|
184
216
|
|
|
185
217
|
def mod(self, other: "Poly") -> None:
|
|
218
|
+
"Remainder of polynom division"
|
|
186
219
|
if isinstance(other, list):
|
|
187
220
|
other = self.__class__(other)
|
|
188
221
|
elif not isinstance(other, self.__class__):
|
|
@@ -4,7 +4,8 @@ Tools for prime numbers:
|
|
|
4
4
|
isprime(n) test if n is probably prime
|
|
5
5
|
miller_rabin_test(n, b) Miller-Rabin primality test with base b
|
|
6
6
|
"""
|
|
7
|
-
from math import isqrt
|
|
7
|
+
from math import isqrt, gcd
|
|
8
|
+
from .nt import jacobi_symbol
|
|
8
9
|
|
|
9
10
|
# Erathostenes
|
|
10
11
|
|
|
@@ -86,8 +87,6 @@ def _lucas_sequence(n, D, k):
|
|
|
86
87
|
|
|
87
88
|
def _is_strong_lucas_prp(n: int) -> bool:
|
|
88
89
|
"""Strong Lucas primality test."""
|
|
89
|
-
from math import gcd
|
|
90
|
-
from .nt import jacobi_symbol
|
|
91
90
|
|
|
92
91
|
# remove powers of 2 from n+1 (= k * 2**s)
|
|
93
92
|
k = (n + 1) // 2
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/teschlg/kryptools/issues
|
|
8
|
+
Project-URL: Docs, https://github.com/teschlg/kryptools/tree/main/doc
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
11
|
Classifier: Operating System :: OS Independent
|
|
@@ -33,3 +34,6 @@ The tools contained are:
|
|
|
33
34
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
34
35
|
* Poly: a class for polynomials (division, modulo)
|
|
35
36
|
* Zmod: a class for the ring of integers modulo an integer
|
|
37
|
+
|
|
38
|
+
Documentation can be found in the jupyter notebook
|
|
39
|
+
(currently incomplete: use the force, read the source).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kryptools"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
|
|
6
6
|
]
|
|
@@ -15,4 +15,5 @@ classifiers = [
|
|
|
15
15
|
|
|
16
16
|
[project.urls]
|
|
17
17
|
Homepage = "https://github.com/teschlg/kryptools"
|
|
18
|
-
Issues = "https://github.com/teschlg/kryptools/issues"
|
|
18
|
+
Issues = "https://github.com/teschlg/kryptools/issues"
|
|
19
|
+
Docs = "https://github.com/teschlg/kryptools/tree/main/doc"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -5,6 +5,9 @@ Factorization of integers:
|
|
|
5
5
|
|
|
6
6
|
from math import isqrt, gcd
|
|
7
7
|
from .primes import sieve_eratosthenes, isprime
|
|
8
|
+
from .factor_pm1 import _pm1_parameters, factor_pm1
|
|
9
|
+
from .factor_ecm import _ecm_parameters, factor_ecm
|
|
10
|
+
#from .factor_qs import factor_qs
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
# Factoring
|
|
@@ -27,9 +30,6 @@ def _factor_fermat(n: int, steps: int = 10) -> list:
|
|
|
27
30
|
return a - b
|
|
28
31
|
a += step
|
|
29
32
|
|
|
30
|
-
from .factor_pm1 import _pm1_parameters, factor_pm1
|
|
31
|
-
from .factor_ecm import _ecm_parameters, factor_ecm
|
|
32
|
-
#from .factor_qs import factor_qs
|
|
33
33
|
|
|
34
34
|
def factorint(n: int, verbose: int = 0) -> list:
|
|
35
35
|
"Factor a number."
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|