kryptools 0.8__tar.gz → 0.9.1__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.8 → kryptools-0.9.1}/PKG-INFO +8 -5
- {kryptools-0.8 → kryptools-0.9.1}/README.md +7 -4
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/__init__.py +2 -2
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/dlp.py +2 -6
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/dlp_bsgs.py +1 -1
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/dlp_qs.py +1 -1
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/dlp_rho.py +1 -1
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/ec.py +4 -3
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/factor.py +12 -5
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/la.py +10 -7
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/lat.py +3 -1
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/nt.py +43 -79
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/primes.py +29 -17
- {kryptools-0.8 → kryptools-0.9.1}/kryptools.egg-info/PKG-INFO +8 -5
- {kryptools-0.8 → kryptools-0.9.1}/pyproject.toml +1 -1
- {kryptools-0.8 → kryptools-0.9.1}/LICENSE +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/Zmod.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/dlp_ic.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/factor_dix.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/factor_ecm.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/factor_fmt.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/factor_pm1.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/factor_qs.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools/poly.py +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.8 → kryptools-0.9.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
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
|
|
@@ -22,18 +22,21 @@ you are welcome to read (and modify) the source. Any suggestions on how to make
|
|
|
22
22
|
code more readable or make it better are welcome. However, my main goal is to keep
|
|
23
23
|
it simple and readability will be preferred over small speed improvements.
|
|
24
24
|
|
|
25
|
+
It does not require any external libraries.
|
|
26
|
+
|
|
25
27
|
The tools contained are:
|
|
26
28
|
|
|
27
|
-
* number theory: sqrt modulo primes,
|
|
28
|
-
* primes: Sieve of Erathostenes, primality tests
|
|
29
|
+
* number theory: sqrt modulo primes, Jacobi/Legendre symbol, Chinese Remainder Theorem, continued fractions, etc.
|
|
30
|
+
* primes: Sieve of Erathostenes, primality tests, generation of random safe/strong primes
|
|
29
31
|
* solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
|
|
30
32
|
* integer factorization (Fermat, Pollard p-1, Lenstra's ECM, Dixon, basic quadratic sieve)
|
|
33
|
+
* elliptic curves (Weierstrass form), group operations, order, discrete logarithms
|
|
31
34
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
32
|
-
* lattices: Babai rounding/nearest plane, lattice reduction
|
|
35
|
+
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
33
36
|
|
|
34
37
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
35
38
|
* Poly: a class for polynomials (division, modulo)
|
|
36
39
|
* Zmod: a class for the ring of integers modulo an integer
|
|
37
40
|
|
|
38
41
|
Documentation can be found in the jupyter notebook
|
|
39
|
-
(
|
|
42
|
+
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -7,18 +7,21 @@ you are welcome to read (and modify) the source. Any suggestions on how to make
|
|
|
7
7
|
code more readable or make it better are welcome. However, my main goal is to keep
|
|
8
8
|
it simple and readability will be preferred over small speed improvements.
|
|
9
9
|
|
|
10
|
+
It does not require any external libraries.
|
|
11
|
+
|
|
10
12
|
The tools contained are:
|
|
11
13
|
|
|
12
|
-
* number theory: sqrt modulo primes,
|
|
13
|
-
* primes: Sieve of Erathostenes, primality tests
|
|
14
|
+
* number theory: sqrt modulo primes, Jacobi/Legendre symbol, Chinese Remainder Theorem, continued fractions, etc.
|
|
15
|
+
* primes: Sieve of Erathostenes, primality tests, generation of random safe/strong primes
|
|
14
16
|
* solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
|
|
15
17
|
* integer factorization (Fermat, Pollard p-1, Lenstra's ECM, Dixon, basic quadratic sieve)
|
|
18
|
+
* elliptic curves (Weierstrass form), group operations, order, discrete logarithms
|
|
16
19
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
17
|
-
* lattices: Babai rounding/nearest plane, lattice reduction
|
|
20
|
+
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
18
21
|
|
|
19
22
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
20
23
|
* Poly: a class for polynomials (division, modulo)
|
|
21
24
|
* Zmod: a class for the ring of integers modulo an integer
|
|
22
25
|
|
|
23
26
|
Documentation can be found in the jupyter notebook
|
|
24
|
-
(
|
|
27
|
+
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Implemenation of same basic algorithms used in cryptography.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .nt import egcd,
|
|
6
|
-
from .primes import sieve_eratosthenes, is_prime, next_prime, random_prime, random_strongprime, is_safeprime, random_safeprime
|
|
5
|
+
from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, order, crt
|
|
6
|
+
from .primes import sieve_eratosthenes, is_prime, next_prime, previous_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
|
|
@@ -22,7 +22,7 @@ def dlog_naive(a: int, b: int, n: int, m: int = None) -> int|None:
|
|
|
22
22
|
aa = (aa * a) % n
|
|
23
23
|
if aa == b:
|
|
24
24
|
return i + 1
|
|
25
|
-
|
|
25
|
+
raise ValueError("DLP not solvable.")
|
|
26
26
|
|
|
27
27
|
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."""
|
|
@@ -44,14 +44,12 @@ def _dlog_ph(a: int, b: int, n: int, q: int, k: int) -> int:
|
|
|
44
44
|
aj = pow(a, q ** (k - j), n)
|
|
45
45
|
bj = pow(b, q ** (k - j), n)
|
|
46
46
|
yj = _dlog_switch(a1, bj * pow(aj, -xj, n) % n, n, q) # pylint: disable=E1130
|
|
47
|
-
if yj is None:
|
|
48
|
-
return None
|
|
49
47
|
xj = xj + q ** (j - 1) * yj % q**j
|
|
50
48
|
return xj
|
|
51
49
|
|
|
52
50
|
|
|
53
51
|
def dlog(a: int, b: int, n: int, m: int|None = None) -> int:
|
|
54
|
-
"""Compute the discrete log_a(b) in Z_n of an element a of order m using Pohlig-Hellman reduction."""
|
|
52
|
+
"""Compute the discrete log_a(b) in Z_n of an element `a` of order `m` using Pohlig-Hellman reduction."""
|
|
55
53
|
a %= n
|
|
56
54
|
b %= n
|
|
57
55
|
assert gcd(a, n) == 1, "a and n must be coprime."
|
|
@@ -69,8 +67,6 @@ def dlog(a: int, b: int, n: int, m: int|None = None) -> int:
|
|
|
69
67
|
aj = pow(a, m // pj**kj, n)
|
|
70
68
|
bj = pow(b, m // pj**kj, n)
|
|
71
69
|
l = _dlog_ph(aj, bj, n, pj, kj)
|
|
72
|
-
if l is None:
|
|
73
|
-
return None
|
|
74
70
|
mm += [pj**kj]
|
|
75
71
|
ll += [l]
|
|
76
72
|
return crt(ll, mm)
|
|
@@ -292,6 +292,6 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
292
292
|
relation[x + j] = 1
|
|
293
293
|
res = process_relation(relation)
|
|
294
294
|
if res:
|
|
295
|
-
return
|
|
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}")
|
|
@@ -55,7 +55,7 @@ def dlog_rho(a: int, b: int, n: int, m: int = None, brent=0) -> int:
|
|
|
55
55
|
l = (((alpha_x - alpha_y) // d) * pow((beta_y - beta_x) // d, -1, mm)) % mm
|
|
56
56
|
while pow(a, l, n) != b:
|
|
57
57
|
if l >= m:
|
|
58
|
-
|
|
58
|
+
raise ValueError("DLP not solvable.")
|
|
59
59
|
l += mm
|
|
60
60
|
return l
|
|
61
61
|
trys -= 1
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Elliptic curves
|
|
2
|
+
Elliptic curves:
|
|
3
|
+
EC_Weierstrass() constructor for a Weierstrass curve y^2 = x^3 + ax + b mod p
|
|
3
4
|
"""
|
|
4
5
|
|
|
5
6
|
from math import isqrt, floor, sqrt
|
|
@@ -11,11 +12,11 @@ from .poly import Poly
|
|
|
11
12
|
|
|
12
13
|
class EC_Weierstrass():
|
|
13
14
|
"""
|
|
14
|
-
Elliptic curve in Weierstrass normal form y^2 = x^3 + ax + b mod p
|
|
15
|
+
Elliptic curve in Weierstrass normal form `y^2 = x^3 + ax + b mod p`.
|
|
15
16
|
|
|
16
17
|
Example:
|
|
17
18
|
|
|
18
|
-
To define an elliptic curve use
|
|
19
|
+
To define an elliptic curve y^2 = x^3 + 3x + 1 mod 239 use
|
|
19
20
|
>>> ec = EC_Weierstrass(239, 3, 1)
|
|
20
21
|
|
|
21
22
|
To declare a point on the elliptic curve use
|
|
@@ -28,9 +28,16 @@ def _factor_fermat(n: int, steps: int = 10) -> list:
|
|
|
28
28
|
if b * b == a * a - n:
|
|
29
29
|
return a - b
|
|
30
30
|
|
|
31
|
-
def factorint(n: int, verbose: int = 0) ->
|
|
32
|
-
"Factor a
|
|
31
|
+
def factorint(n: int, verbose: int = 0) -> dict:
|
|
32
|
+
"Factor a the ineger `n`."
|
|
33
33
|
prime_factors = {}
|
|
34
|
+
if not isinstance(n, int):
|
|
35
|
+
raise ValueError("Number to be factored must be an integer!")
|
|
36
|
+
if n == 0:
|
|
37
|
+
return prime_factors
|
|
38
|
+
if n < 0:
|
|
39
|
+
n *= -1
|
|
40
|
+
prime_factors[-1] = 1
|
|
34
41
|
|
|
35
42
|
def add_factors(m: int, mm: tuple) -> None:
|
|
36
43
|
k = remaining_factors[m]
|
|
@@ -96,7 +103,8 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
96
103
|
for method in [ _factor_fermat, factor_pm1, factor_ecm ]: # , factor_qs ]:
|
|
97
104
|
factors = list(remaining_factors)
|
|
98
105
|
for m in factors:
|
|
99
|
-
if verbose > 1:
|
|
106
|
+
if verbose > 1:
|
|
107
|
+
print("Factoring: ",m, "method", methods[method])
|
|
100
108
|
if method == factor_pm1:
|
|
101
109
|
tmp = factor_pm1(m, pm1_parameters = pm1_parameters)
|
|
102
110
|
elif method == factor_ecm:
|
|
@@ -131,5 +139,4 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
131
139
|
if len(remaining_factors) == 0:
|
|
132
140
|
return prime_factors
|
|
133
141
|
|
|
134
|
-
|
|
135
|
-
return prime_factors, remaining_factors, new_factors
|
|
142
|
+
raise ValueError("Incomplete factorization:", prime_factors, remaining_factors, new_factors)
|
|
@@ -169,6 +169,9 @@ class Matrix:
|
|
|
169
169
|
def __neg__(self) -> "Matrix":
|
|
170
170
|
return -1 * self
|
|
171
171
|
|
|
172
|
+
def __pos__(self) -> "Matrix":
|
|
173
|
+
return self
|
|
174
|
+
|
|
172
175
|
def __mul__(self, other) -> "Matrix":
|
|
173
176
|
if isinstance(other, Matrix):
|
|
174
177
|
return self.multiply(other)
|
|
@@ -208,7 +211,7 @@ class Matrix:
|
|
|
208
211
|
|
|
209
212
|
def det(self) -> int:
|
|
210
213
|
"Compute the determinant of a matrix M."
|
|
211
|
-
if self.rows != self.
|
|
214
|
+
if self.rows != self.cols:
|
|
212
215
|
raise ValueError("Matrix must be square!")
|
|
213
216
|
n, m = self.cols, self.rows
|
|
214
217
|
R = self[:, :]
|
|
@@ -236,7 +239,7 @@ class Matrix:
|
|
|
236
239
|
|
|
237
240
|
def inv(self) -> "Matrix":
|
|
238
241
|
"Compute the inverse of a square matrix M."
|
|
239
|
-
if self.rows != self.
|
|
242
|
+
if self.rows != self.cols:
|
|
240
243
|
raise ValueError("Matrix must be square!")
|
|
241
244
|
n = self.cols
|
|
242
245
|
MM = Matrix([[0 for _ in range(2*n)] for _ in range(n)])
|
|
@@ -250,7 +253,7 @@ class Matrix:
|
|
|
250
253
|
|
|
251
254
|
def is_unimodular(self) -> bool:
|
|
252
255
|
"Test if the matrix is unimodular."
|
|
253
|
-
if self.rows != self.
|
|
256
|
+
if self.rows != self.cols:
|
|
254
257
|
return False
|
|
255
258
|
def is_integer(i):
|
|
256
259
|
if isinstance(i, int) or (isinstance(i, Fraction) and i.denominator == 1):
|
|
@@ -259,7 +262,7 @@ class Matrix:
|
|
|
259
262
|
return all([is_integer(i) for i in self]) and self.det()**2 == 1
|
|
260
263
|
|
|
261
264
|
def zeros(self, m: int = None, n: int = None):
|
|
262
|
-
"Returns a zero matrix of the same dimension"
|
|
265
|
+
"Returns a zero matrix of the same dimension."
|
|
263
266
|
if not m and not n:
|
|
264
267
|
n, m = self.cols, self.rows
|
|
265
268
|
elif not n:
|
|
@@ -271,7 +274,7 @@ class Matrix:
|
|
|
271
274
|
return Matrix([[ zero for j in range(n) ] for i in range(m) ])
|
|
272
275
|
|
|
273
276
|
def eye(self, m: int = None, n: int = None):
|
|
274
|
-
"Returns an identity matrix of the same dimension"
|
|
277
|
+
"Returns an identity matrix of the same dimension."
|
|
275
278
|
def delta(i, j):
|
|
276
279
|
if i == j:
|
|
277
280
|
return one
|
|
@@ -289,13 +292,13 @@ class Matrix:
|
|
|
289
292
|
|
|
290
293
|
|
|
291
294
|
def zeros(m: int, n: int = None, zero = 0) -> "Matrix":
|
|
292
|
-
"Returns a zero matrix of the given dimension"
|
|
295
|
+
"Returns a zero matrix of the given dimension."
|
|
293
296
|
if not n:
|
|
294
297
|
n = m
|
|
295
298
|
return Matrix([[ zero for j in range(n) ] for i in range(m) ])
|
|
296
299
|
|
|
297
300
|
def eye(m:int, n: int = None) -> "Matrix":
|
|
298
|
-
"Returns an identity matrix of the given dimension"
|
|
301
|
+
"Returns an identity matrix of the given dimension."
|
|
299
302
|
def delta(i, j):
|
|
300
303
|
if i == j:
|
|
301
304
|
return 1
|
|
@@ -42,6 +42,8 @@ def hermite_nf(M: Matrix) -> Matrix:
|
|
|
42
42
|
H[:, jj] -= tmp * H[:, j]
|
|
43
43
|
#print(H)
|
|
44
44
|
j -= 1
|
|
45
|
+
if j < 0:
|
|
46
|
+
break
|
|
45
47
|
while H.cols> 1 and all(not H[i, 0] for i in range(m)): # remove zero columns
|
|
46
48
|
H = H[:,1:]
|
|
47
49
|
return H
|
|
@@ -173,7 +175,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
173
175
|
return U
|
|
174
176
|
|
|
175
177
|
def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = None) -> Matrix:
|
|
176
|
-
"Create a
|
|
178
|
+
"Create a pseudorandom unimodular matrix of dimension n."
|
|
177
179
|
W = zeros(n)
|
|
178
180
|
for i in range(n):
|
|
179
181
|
for j in range(i, n):
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Number theory tools:
|
|
3
3
|
egcd(a,b) extended Euclidean agorithm
|
|
4
|
-
crt([a1, a2, ...],[m1, m2, ...]) Chinese Remainder Theorem
|
|
5
4
|
cf(Fraction(m,n)) continued fraction expansions
|
|
6
5
|
convergents() convergents of a continued fraction
|
|
7
6
|
legendre_symbol(a, p) compute the Legendre symbol of a with respect to the prime p
|
|
8
7
|
jacobi_symbol(a, n) compute the Jacobi symbol of a with respect to n
|
|
9
8
|
sqrt_mod(n, p) square root of n modulo a prime p
|
|
9
|
+
euler_phi(n) Euler phi function of n
|
|
10
|
+
carmichael_lambda(n) Carmichael lambda function of n
|
|
10
11
|
order(a, n) oder of a in the multiplicative group Z_n^*
|
|
12
|
+
crt([a1, a2, ...],[m1, m2, ...]) Chinese Remainder Theorem
|
|
11
13
|
"""
|
|
12
14
|
from math import gcd, lcm, prod
|
|
13
15
|
from fractions import Fraction
|
|
@@ -26,83 +28,8 @@ def egcd(a: int, b: int) -> (int, int, int):
|
|
|
26
28
|
return r0, x0, y0
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
# Chinese remainder theorem
|
|
30
|
-
|
|
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."""
|
|
33
|
-
l = len(a)
|
|
34
|
-
assert len(m) == l, "The lists of numbers and modules must have equal length."
|
|
35
|
-
M = prod(m)
|
|
36
|
-
Mi = [M // m[i] for i in range(l)]
|
|
37
|
-
MNi = [Mi[i] * pow(Mi[i], -1, m[i]) % M for i in range(l)]
|
|
38
|
-
return sum([a[i] * MNi[i] % M for i in range(l)]) % M
|
|
39
|
-
|
|
40
31
|
# Continued fractions
|
|
41
32
|
|
|
42
|
-
# class Fraction:
|
|
43
|
-
# "Rationl number"
|
|
44
|
-
#
|
|
45
|
-
# def __init__(self, numerator: int, denominator: int = 1):
|
|
46
|
-
# assert denominator, "Denominator must be nozero."
|
|
47
|
-
# tmp = gcd(denominator, numerator)
|
|
48
|
-
# if tmp != 1:
|
|
49
|
-
# denominator //= tmp
|
|
50
|
-
# numerator //= tmp
|
|
51
|
-
# self.numerator = numerator
|
|
52
|
-
# self.denominator = denominator
|
|
53
|
-
#
|
|
54
|
-
# def __repr__(self):
|
|
55
|
-
# if self.denominator == 1:
|
|
56
|
-
# return str(self.numerator)
|
|
57
|
-
# else:
|
|
58
|
-
# return str(self.numerator) + "/" + str(self.denominator)
|
|
59
|
-
#
|
|
60
|
-
# def __eq__(self, other):
|
|
61
|
-
# if not isinstance(other, self.__class__):
|
|
62
|
-
# return False
|
|
63
|
-
# return self.denominator == other.denominator and self.numerator == other.numerator
|
|
64
|
-
#
|
|
65
|
-
# def __bool__(self):
|
|
66
|
-
# return self.numerator != 0
|
|
67
|
-
#
|
|
68
|
-
# def __add__(self, other: "Fraction") -> "Fraction":
|
|
69
|
-
# if isinstance(other, self.__class__):
|
|
70
|
-
# return Rational(self.numerator * other.denominator + other.numerator * self.denominator, self.denominator * other.denominator)
|
|
71
|
-
# if isinstance(other, int):
|
|
72
|
-
# return Rational(self.numerator + other * self.denominator, self.denominator)
|
|
73
|
-
# raise ValueError(f"Cannot add {self} and {other}.")
|
|
74
|
-
#
|
|
75
|
-
# def __neg__(self) -> "Fraction":
|
|
76
|
-
# return Rational(- self.numerator, self.denominator)
|
|
77
|
-
#
|
|
78
|
-
# def __sub__(self, other: "Fraction") -> "Fraction":
|
|
79
|
-
# if isinstance(other, self.__class__):
|
|
80
|
-
# return Rational(self.numerator * other.denominator - other.numerator * self.denominator, self.denominator * other.denominator)
|
|
81
|
-
# if isinstance(other, int):
|
|
82
|
-
# return Rational(self.numerator - other * self.denominator, self.denominator)
|
|
83
|
-
# raise ValueError(f"Cannot subtract {self} and {other}.")
|
|
84
|
-
#
|
|
85
|
-
# def __mul__(self, other: "Fraction") -> "Fraction":
|
|
86
|
-
# if isinstance(other, self.__class__):
|
|
87
|
-
# return Rational(self.numerator * other.numerator, self.denominator * other.denominator)
|
|
88
|
-
# if isinstance(other, int):
|
|
89
|
-
# return Rational(self.numerator * other, self.denominator)
|
|
90
|
-
# raise ValueError(f"Cannot multiply {self} and {other}.")
|
|
91
|
-
#
|
|
92
|
-
# def __rmul__(self, other: int) -> "Fraction":
|
|
93
|
-
# return Rational(self.denominator * other, self.numerator)
|
|
94
|
-
#
|
|
95
|
-
# def __truediv__(self, other: "Fraction") -> "Fraction":
|
|
96
|
-
# if isinstance(other, self.__class__):
|
|
97
|
-
# return Rational(self.numerator * other.denominator, self.denominator * other.numerator)
|
|
98
|
-
# if isinstance(other, int):
|
|
99
|
-
# return Rational(self.numerator, self.denominator * other)
|
|
100
|
-
# raise ValueError(f"Cannot divide {self} and {other}.")
|
|
101
|
-
#
|
|
102
|
-
# def __pow__(self, scalar: int) -> "Fraction":
|
|
103
|
-
# return Rational(self.numerator**scalar, self.denominator**scalar)
|
|
104
|
-
|
|
105
|
-
|
|
106
33
|
def fraction_repr(self):
|
|
107
34
|
"Representation of a fraction."
|
|
108
35
|
if self.denominator == 1:
|
|
@@ -113,7 +40,7 @@ Fraction.__repr__ = fraction_repr
|
|
|
113
40
|
|
|
114
41
|
|
|
115
42
|
def cf(x: Fraction) -> list[int]:
|
|
116
|
-
"Compute the continued fraction expansion of the rational
|
|
43
|
+
"Compute the continued fraction expansion of the rational fraction `x`."
|
|
117
44
|
res = []
|
|
118
45
|
m = x.numerator
|
|
119
46
|
n = x.denominator
|
|
@@ -144,7 +71,7 @@ def convergents(cont_frac: list[int]) -> float:
|
|
|
144
71
|
# Legendre symbol
|
|
145
72
|
|
|
146
73
|
def legendre_symbol(a: int, p: int) -> int:
|
|
147
|
-
"""Compute the Legendre symbol of a with respect to the prime p
|
|
74
|
+
"""Compute the Legendre symbol of `a` with respect to the prime `p`."""
|
|
148
75
|
a = a % p
|
|
149
76
|
if a == 0:
|
|
150
77
|
return 0
|
|
@@ -153,7 +80,7 @@ def legendre_symbol(a: int, p: int) -> int:
|
|
|
153
80
|
return -1
|
|
154
81
|
|
|
155
82
|
def jacobi_symbol(a: int, n: int) -> int:
|
|
156
|
-
"""Compute the Jacobi symbol of a with respect to the integer n
|
|
83
|
+
"""Compute the Jacobi symbol of `a` with respect to the integer `n`."""
|
|
157
84
|
# Crandall/Pomerance Algorithm 2.3.5
|
|
158
85
|
a = a % n
|
|
159
86
|
t = 1
|
|
@@ -260,3 +187,40 @@ def order(a: int, n: int, factor=False) -> int:
|
|
|
260
187
|
if factor:
|
|
261
188
|
return order_a, factors_order
|
|
262
189
|
return order_a
|
|
190
|
+
|
|
191
|
+
# Chinese remainder theorem
|
|
192
|
+
|
|
193
|
+
def crt(a: list[int], m: list[int], coprime = True) -> int:
|
|
194
|
+
"""Solve given linear congruences `x % m[j] == a[j]` using the Chinese Remainder Theorem."""
|
|
195
|
+
l = len(m)
|
|
196
|
+
if l != len(a):
|
|
197
|
+
raise ValueError("The lists of numbers and modules must have equal length.")
|
|
198
|
+
|
|
199
|
+
if not coprime: # make a copy
|
|
200
|
+
m = [ mi for mi in m ]
|
|
201
|
+
a = [ ai for ai in a ]
|
|
202
|
+
for i in range(l):
|
|
203
|
+
for j in range(i + 1,l):
|
|
204
|
+
g = gcd(m[i], m[j])
|
|
205
|
+
if g == 1: # moduli are coprime, nothing to be done
|
|
206
|
+
continue
|
|
207
|
+
if (a[i] - a[j]) % g:
|
|
208
|
+
raise ValueError(f"Congruences not solvable!")
|
|
209
|
+
for p, k in factorint(g).items():
|
|
210
|
+
p = p**k # remove this factor from one of the equations
|
|
211
|
+
if gcd(m[i] // p, p) == 1:
|
|
212
|
+
m[i] //= p
|
|
213
|
+
a[i] %= m[i]
|
|
214
|
+
else:
|
|
215
|
+
m[j] //= p
|
|
216
|
+
a[j] %= m[j]
|
|
217
|
+
for i in reversed(range(l)):
|
|
218
|
+
if m[i] == 1:
|
|
219
|
+
del m[i]
|
|
220
|
+
del a[i]
|
|
221
|
+
l =len(m)
|
|
222
|
+
|
|
223
|
+
M = prod(m)
|
|
224
|
+
Mi = [M // m[i] for i in range(l)]
|
|
225
|
+
MNi = [Mi[i] * pow(Mi[i], -1, m[i]) % M for i in range(l)]
|
|
226
|
+
return sum([a[i] * MNi[i] % M for i in range(l)]) % M
|
|
@@ -3,10 +3,11 @@ 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
5
|
next_prime(n) find the next prime larger or equal n
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
previous_prime(n) find the previous prime smaller or equal n
|
|
7
|
+
random_prime(l) find a pseudorandom prime with bit length at least l
|
|
8
|
+
random_strongprime(l) find a pseudorandom strong prime with bit length at least l
|
|
8
9
|
is_safeprime(n) test if n is a safe prime
|
|
9
|
-
random_safeprime(n) find a
|
|
10
|
+
random_safeprime(n) find a pseudorandom safe prime with bit length at least l and ord(2)=(p-1)/2
|
|
10
11
|
miller_rabin_test(n, b) Miller-Rabin primality test with base b
|
|
11
12
|
"""
|
|
12
13
|
from math import isqrt, gcd
|
|
@@ -15,16 +16,16 @@ from .nt import jacobi_symbol
|
|
|
15
16
|
|
|
16
17
|
# Erathostenes
|
|
17
18
|
|
|
18
|
-
def sieve_eratosthenes(B: int) ->
|
|
19
|
-
"""
|
|
20
|
-
B1 = (isqrt(B) -1)//2
|
|
19
|
+
def sieve_eratosthenes(B: int) -> tuple:
|
|
20
|
+
"""Returns a tuple of all primes up to (including) `B`."""
|
|
21
|
+
B1 = (isqrt(B) - 1)//2
|
|
21
22
|
B = (B - 1)//2
|
|
22
|
-
isprime = [True] * (B + 1) # to begin with, all numbers are potentially prime
|
|
23
|
-
# sieve out the primes p=2*q+1 starting at
|
|
23
|
+
isprime = [True] * (B + 1) # to begin with, all odd numbers are potentially prime
|
|
24
|
+
# sieve out the primes p=2*q+1 starting at q = 1
|
|
24
25
|
for q in range(1, B1 + 1):
|
|
25
|
-
if isprime[q]: # sieve out all multiples; numbers p*
|
|
26
|
-
qq =
|
|
27
|
-
p =
|
|
26
|
+
if isprime[q]: # sieve out all multiples; numbers p*r with r<p were already sieved out previously
|
|
27
|
+
qq = 2 * q * (q + 1)
|
|
28
|
+
p = 2 * q + 1
|
|
28
29
|
isprime[qq :: p] = [False] * ((B - qq) // p + 1)
|
|
29
30
|
|
|
30
31
|
return tuple([2] + [2 * q + 1 for q in range(1, B + 1) if isprime[q]])
|
|
@@ -33,7 +34,7 @@ def sieve_eratosthenes(B: int) -> list:
|
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
|
|
36
|
-
"""Run a Miller-Rabin test with given bases on n
|
|
37
|
+
"""Run a Miller-Rabin test with given bases on `n`."""
|
|
37
38
|
if n < 2:
|
|
38
39
|
return False
|
|
39
40
|
if n % 2 == 0:
|
|
@@ -58,7 +59,7 @@ def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
|
|
|
58
59
|
return False
|
|
59
60
|
|
|
60
61
|
def is_prime(n: int) -> bool:
|
|
61
|
-
"""Test if an integer n if probable prime."""
|
|
62
|
+
"""Test if an integer `n` if probable prime."""
|
|
62
63
|
if n < 18446744073709551616: # https://miller-rabin.appspot.com
|
|
63
64
|
return miller_rabin_test(n, [2, 325, 9375, 28178, 450775, 9780504, 1795265022])
|
|
64
65
|
if n < 3317044064679887385961981:
|
|
@@ -66,18 +67,29 @@ def is_prime(n: int) -> bool:
|
|
|
66
67
|
return miller_rabin_test(n, [2]) and _is_strong_lucas_prp(n) # Baillie–PSW primality test
|
|
67
68
|
|
|
68
69
|
def next_prime(n: int) -> int:
|
|
69
|
-
"""Find the next prime larger or equal n
|
|
70
|
+
"""Find the next prime larger or equal `n`."""
|
|
71
|
+
if n < 3:
|
|
72
|
+
return 2
|
|
70
73
|
n |= 1 # make sure n is odd
|
|
71
74
|
while not is_prime(n):
|
|
72
75
|
n += 2
|
|
73
76
|
return n
|
|
74
77
|
|
|
78
|
+
def previous_prime(n: int) -> int:
|
|
79
|
+
"""Find the previous prime smaller or equal `n`."""
|
|
80
|
+
if n < 3:
|
|
81
|
+
return 2
|
|
82
|
+
n += (n & 1) - 1 # make sure n is odd
|
|
83
|
+
while not is_prime(n):
|
|
84
|
+
n -= 2
|
|
85
|
+
return n
|
|
86
|
+
|
|
75
87
|
def random_prime(l: int) -> int:
|
|
76
|
-
"""Find a
|
|
88
|
+
"""Find a pseudorandom prime with bit length at least `l`."""
|
|
77
89
|
return next_prime(randint(2 ** (l - 1), 2**l - 1))
|
|
78
90
|
|
|
79
91
|
def random_strongprime(l: int) -> int:
|
|
80
|
-
"""Find a
|
|
92
|
+
"""Find a pseudorandom strong prime with bit length at least `l` using Gordon's algorithm."""
|
|
81
93
|
t = random_prime(l)
|
|
82
94
|
s = random_prime(l)
|
|
83
95
|
u = 2 * t
|
|
@@ -96,7 +108,7 @@ def is_safeprime(p: int) -> bool:
|
|
|
96
108
|
return is_prime(p) and is_prime((p - 1) // 2)
|
|
97
109
|
|
|
98
110
|
def random_safeprime(l: int) -> int:
|
|
99
|
-
"""Find a
|
|
111
|
+
"""Find a pseudorandom safe prime with bit length at least `l` and `ord(2)=(p-1)/2`."""
|
|
100
112
|
p = randint(2 ** (l - 1), 2**l - 1)
|
|
101
113
|
p = p - (p % 24) + 23
|
|
102
114
|
while not is_safeprime(p):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
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
|
|
@@ -22,18 +22,21 @@ you are welcome to read (and modify) the source. Any suggestions on how to make
|
|
|
22
22
|
code more readable or make it better are welcome. However, my main goal is to keep
|
|
23
23
|
it simple and readability will be preferred over small speed improvements.
|
|
24
24
|
|
|
25
|
+
It does not require any external libraries.
|
|
26
|
+
|
|
25
27
|
The tools contained are:
|
|
26
28
|
|
|
27
|
-
* number theory: sqrt modulo primes,
|
|
28
|
-
* primes: Sieve of Erathostenes, primality tests
|
|
29
|
+
* number theory: sqrt modulo primes, Jacobi/Legendre symbol, Chinese Remainder Theorem, continued fractions, etc.
|
|
30
|
+
* primes: Sieve of Erathostenes, primality tests, generation of random safe/strong primes
|
|
29
31
|
* solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
|
|
30
32
|
* integer factorization (Fermat, Pollard p-1, Lenstra's ECM, Dixon, basic quadratic sieve)
|
|
33
|
+
* elliptic curves (Weierstrass form), group operations, order, discrete logarithms
|
|
31
34
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
32
|
-
* lattices: Babai rounding/nearest plane, lattice reduction
|
|
35
|
+
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
33
36
|
|
|
34
37
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
35
38
|
* Poly: a class for polynomials (division, modulo)
|
|
36
39
|
* Zmod: a class for the ring of integers modulo an integer
|
|
37
40
|
|
|
38
41
|
Documentation can be found in the jupyter notebook
|
|
39
|
-
(
|
|
42
|
+
(mostly done, but might not contain everything: use the force, read the source).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|