kryptools 0.7__tar.gz → 0.9__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.7 → kryptools-0.9}/PKG-INFO +9 -6
- {kryptools-0.7 → kryptools-0.9}/README.md +8 -5
- {kryptools-0.7 → kryptools-0.9}/kryptools/__init__.py +1 -1
- {kryptools-0.7 → kryptools-0.9}/kryptools/dlp.py +2 -6
- {kryptools-0.7 → kryptools-0.9}/kryptools/dlp_bsgs.py +1 -1
- {kryptools-0.7 → kryptools-0.9}/kryptools/dlp_qs.py +1 -1
- {kryptools-0.7 → kryptools-0.9}/kryptools/dlp_rho.py +1 -1
- {kryptools-0.7 → kryptools-0.9}/kryptools/ec.py +4 -3
- {kryptools-0.7 → kryptools-0.9}/kryptools/factor.py +13 -6
- {kryptools-0.7 → kryptools-0.9}/kryptools/factor_ecm.py +3 -3
- {kryptools-0.7 → kryptools-0.9}/kryptools/la.py +6 -3
- {kryptools-0.7 → kryptools-0.9}/kryptools/lat.py +1 -1
- {kryptools-0.7 → kryptools-0.9}/kryptools/nt.py +43 -79
- {kryptools-0.7 → kryptools-0.9}/kryptools/poly.py +12 -7
- {kryptools-0.7 → kryptools-0.9}/kryptools/primes.py +11 -11
- {kryptools-0.7 → kryptools-0.9}/kryptools.egg-info/PKG-INFO +9 -6
- {kryptools-0.7 → kryptools-0.9}/pyproject.toml +1 -1
- {kryptools-0.7 → kryptools-0.9}/LICENSE +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools/Zmod.py +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools/dlp_ic.py +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools/factor_dix.py +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools/factor_fmt.py +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools/factor_pm1.py +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools/factor_qs.py +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.7 → kryptools-0.9}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.7 → kryptools-0.9}/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
|
|
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
|
-
* integer factorization (Fermat, Pollard p-1,
|
|
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
|
-
* integer factorization (Fermat, Pollard p-1,
|
|
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,7 +2,7 @@
|
|
|
2
2
|
Implemenation of same basic algorithms used in cryptography.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .nt import egcd,
|
|
5
|
+
from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, order, crt
|
|
6
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
|
|
@@ -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]
|
|
@@ -88,7 +95,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
88
95
|
D = isqrt(B2)
|
|
89
96
|
primes = sieve_eratosthenes(B1 - 1 + ((B2 - B1 + 1) // (2 * D) + 1) * 2 * D)
|
|
90
97
|
pm1_parameters = _pm1_parameters(10 * B1, B2, primes = primes)
|
|
91
|
-
ecm_parameters = tuple([num_curves] + list(_ecm_parameters(B1, B2, D, primes = primes)))
|
|
98
|
+
ecm_parameters = tuple([B1, B2, num_curves] + list(_ecm_parameters(B1, B2, D, primes = primes)))
|
|
92
99
|
|
|
93
100
|
methods = {_factor_fermat: "fm", factor_pm1: "pm1", factor_ecm: "ecm"} #, factor_qs: "qs"}
|
|
94
101
|
while remaining_factors:
|
|
@@ -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)
|
|
@@ -57,7 +57,7 @@ def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None
|
|
|
57
57
|
else:
|
|
58
58
|
B2 += B2 & 1
|
|
59
59
|
if not D:
|
|
60
|
-
D = isqrt(B2)
|
|
60
|
+
D = min(isqrt(B2), B1 // 2 - 1)
|
|
61
61
|
if not primes:
|
|
62
62
|
primes = sieve_eratosthenes(B1 - 1 + ((B2 - B1 + 1) // (2 * D) + 1) * 2 * D)
|
|
63
63
|
|
|
@@ -71,7 +71,7 @@ def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None
|
|
|
71
71
|
r = B1 - 1
|
|
72
72
|
stage_two_deltas[r] = []
|
|
73
73
|
for p in primes:
|
|
74
|
-
if p
|
|
74
|
+
if p < r:
|
|
75
75
|
continue
|
|
76
76
|
if p <= r + 2 * D:
|
|
77
77
|
stage_two_deltas[r].append((p - r) // 2)
|
|
@@ -88,7 +88,7 @@ def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm
|
|
|
88
88
|
"Factors a number n using Lentsta's ECM method."
|
|
89
89
|
|
|
90
90
|
if ecm_parameters:
|
|
91
|
-
curves, D, stage_one, stage_two_deltas = ecm_parameters
|
|
91
|
+
B1, B2, curves, D, stage_one, stage_two_deltas = ecm_parameters
|
|
92
92
|
else:
|
|
93
93
|
D, stage_one, stage_two_deltas = _ecm_parameters(B1, B2)
|
|
94
94
|
|
|
@@ -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)
|
|
@@ -268,7 +271,7 @@ class Matrix:
|
|
|
268
271
|
zero = 0 * self[0]
|
|
269
272
|
except:
|
|
270
273
|
zero = 0
|
|
271
|
-
return Matrix([[ zero for j in range(n)] for i in range(m) ])
|
|
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
277
|
"Returns an identity matrix of the same dimension"
|
|
@@ -288,11 +291,11 @@ class Matrix:
|
|
|
288
291
|
return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
|
|
289
292
|
|
|
290
293
|
|
|
291
|
-
def zeros(m: int, n: int = None) -> "Matrix":
|
|
294
|
+
def zeros(m: int, n: int = None, zero = 0) -> "Matrix":
|
|
292
295
|
"Returns a zero matrix of the given dimension"
|
|
293
296
|
if not n:
|
|
294
297
|
n = m
|
|
295
|
-
return Matrix([[
|
|
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
301
|
"Returns an identity matrix of the given dimension"
|
|
@@ -173,7 +173,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
173
173
|
return U
|
|
174
174
|
|
|
175
175
|
def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = None) -> Matrix:
|
|
176
|
-
"Create a
|
|
176
|
+
"Create a pseudorandom unimodular matrix of dimension n."
|
|
177
177
|
W = zeros(n)
|
|
178
178
|
for i in range(n):
|
|
179
179
|
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
|
|
@@ -10,7 +10,7 @@ class Poly:
|
|
|
10
10
|
|
|
11
11
|
Example:
|
|
12
12
|
|
|
13
|
-
To define a polynomial as
|
|
13
|
+
To define a polynomial as a list of coefficients use
|
|
14
14
|
>>> Poly([1, 2, 3])
|
|
15
15
|
3 x^2 + 2 x + 1
|
|
16
16
|
"""
|
|
@@ -203,21 +203,26 @@ class Poly:
|
|
|
203
203
|
raise NotImplementedError("Cannot invert polynomials without modulus.")
|
|
204
204
|
return other * self.inv()
|
|
205
205
|
|
|
206
|
-
def __pow__(self,
|
|
207
|
-
if not isinstance(
|
|
206
|
+
def __pow__(self, j: int) -> "Poly":
|
|
207
|
+
if not isinstance(j, int):
|
|
208
208
|
return NotImplemented
|
|
209
209
|
zero = 0 * self.coeff[0]
|
|
210
210
|
one = zero + 1
|
|
211
211
|
res = self.__class__([one], modulus=self.modulus)
|
|
212
|
-
if
|
|
212
|
+
if j < 0:
|
|
213
213
|
if not self.modulus:
|
|
214
214
|
raise NotImplementedError("Cannot divide polynomials without modulus.")
|
|
215
215
|
tmp = self.inv()
|
|
216
|
-
|
|
216
|
+
j *= -1
|
|
217
217
|
else:
|
|
218
218
|
tmp = self
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
while j > 0:
|
|
220
|
+
# If j is odd, multiply
|
|
221
|
+
if j & 1:
|
|
222
|
+
res *= tmp
|
|
223
|
+
# Now square
|
|
224
|
+
j = j >> 1 # j= j//2
|
|
225
|
+
tmp *= tmp
|
|
221
226
|
return res
|
|
222
227
|
|
|
223
228
|
def max(self) -> int:
|
|
@@ -3,10 +3,10 @@ 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
|
-
random_prime(l) find a
|
|
7
|
-
random_strongprime(l) find a
|
|
6
|
+
random_prime(l) find a pseudorandom prime with bit length at least l
|
|
7
|
+
random_strongprime(l) find a pseudorandom strong prime with bit length at least l
|
|
8
8
|
is_safeprime(n) test if n is a safe prime
|
|
9
|
-
random_safeprime(n) find a
|
|
9
|
+
random_safeprime(n) find a pseudorandom safe prime with bit length at least l and ord(2)=(p-1)/2
|
|
10
10
|
miller_rabin_test(n, b) Miller-Rabin primality test with base b
|
|
11
11
|
"""
|
|
12
12
|
from math import isqrt, gcd
|
|
@@ -15,8 +15,8 @@ from .nt import jacobi_symbol
|
|
|
15
15
|
|
|
16
16
|
# Erathostenes
|
|
17
17
|
|
|
18
|
-
def sieve_eratosthenes(B: int) ->
|
|
19
|
-
"""
|
|
18
|
+
def sieve_eratosthenes(B: int) -> tuple:
|
|
19
|
+
"""Returns a tuple of all primes up to (including) `B`."""
|
|
20
20
|
B1 = (isqrt(B) -1)//2
|
|
21
21
|
B = (B - 1)//2
|
|
22
22
|
isprime = [True] * (B + 1) # to begin with, all numbers are potentially prime
|
|
@@ -33,7 +33,7 @@ def sieve_eratosthenes(B: int) -> list:
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
|
|
36
|
-
"""Run a Miller-Rabin test with given bases on n
|
|
36
|
+
"""Run a Miller-Rabin test with given bases on `n`."""
|
|
37
37
|
if n < 2:
|
|
38
38
|
return False
|
|
39
39
|
if n % 2 == 0:
|
|
@@ -58,7 +58,7 @@ def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
|
|
|
58
58
|
return False
|
|
59
59
|
|
|
60
60
|
def is_prime(n: int) -> bool:
|
|
61
|
-
"""Test if an integer n if probable prime."""
|
|
61
|
+
"""Test if an integer `n` if probable prime."""
|
|
62
62
|
if n < 18446744073709551616: # https://miller-rabin.appspot.com
|
|
63
63
|
return miller_rabin_test(n, [2, 325, 9375, 28178, 450775, 9780504, 1795265022])
|
|
64
64
|
if n < 3317044064679887385961981:
|
|
@@ -66,18 +66,18 @@ def is_prime(n: int) -> bool:
|
|
|
66
66
|
return miller_rabin_test(n, [2]) and _is_strong_lucas_prp(n) # Baillie–PSW primality test
|
|
67
67
|
|
|
68
68
|
def next_prime(n: int) -> int:
|
|
69
|
-
"""Find the next prime larger or equal n
|
|
69
|
+
"""Find the next prime larger or equal `n`."""
|
|
70
70
|
n |= 1 # make sure n is odd
|
|
71
71
|
while not is_prime(n):
|
|
72
72
|
n += 2
|
|
73
73
|
return n
|
|
74
74
|
|
|
75
75
|
def random_prime(l: int) -> int:
|
|
76
|
-
"""Find a
|
|
76
|
+
"""Find a pseudorandom prime with bit length at least `l`."""
|
|
77
77
|
return next_prime(randint(2 ** (l - 1), 2**l - 1))
|
|
78
78
|
|
|
79
79
|
def random_strongprime(l: int) -> int:
|
|
80
|
-
"""Find a
|
|
80
|
+
"""Find a pseudorandom strong prime with bit length at least `l` using Gordon's algorithm."""
|
|
81
81
|
t = random_prime(l)
|
|
82
82
|
s = random_prime(l)
|
|
83
83
|
u = 2 * t
|
|
@@ -96,7 +96,7 @@ def is_safeprime(p: int) -> bool:
|
|
|
96
96
|
return is_prime(p) and is_prime((p - 1) // 2)
|
|
97
97
|
|
|
98
98
|
def random_safeprime(l: int) -> int:
|
|
99
|
-
"""Find a
|
|
99
|
+
"""Find a pseudorandom safe prime with bit length at least `l` and `ord(2)=(p-1)/2`."""
|
|
100
100
|
p = randint(2 ** (l - 1), 2**l - 1)
|
|
101
101
|
p = p - (p % 24) + 23
|
|
102
102
|
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
|
|
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
|
-
* integer factorization (Fermat, Pollard p-1,
|
|
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
|