kryptools 0.2__tar.gz → 0.4__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.2 → kryptools-0.4}/PKG-INFO +3 -3
- {kryptools-0.2 → kryptools-0.4}/README.md +1 -1
- {kryptools-0.2 → kryptools-0.4}/kryptools/Zmod.py +52 -10
- kryptools-0.4/kryptools/__init__.py +13 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools/dlp.py +2 -3
- {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_ic.py +1 -1
- {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_qs.py +12 -13
- {kryptools-0.2 → kryptools-0.4}/kryptools/ec.py +77 -37
- {kryptools-0.2 → kryptools-0.4}/kryptools/factor.py +16 -19
- {kryptools-0.2 → kryptools-0.4}/kryptools/factor_dix.py +22 -5
- {kryptools-0.2 → kryptools-0.4}/kryptools/factor_ecm.py +4 -0
- kryptools-0.4/kryptools/factor_fmt.py +30 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools/factor_pm1.py +1 -1
- {kryptools-0.2 → kryptools-0.4}/kryptools/factor_qs.py +4 -4
- {kryptools-0.2 → kryptools-0.4}/kryptools/la.py +86 -24
- {kryptools-0.2 → kryptools-0.4}/kryptools/lat.py +9 -9
- {kryptools-0.2 → kryptools-0.4}/kryptools/nt.py +20 -30
- {kryptools-0.2 → kryptools-0.4}/kryptools/poly.py +52 -7
- {kryptools-0.2 → kryptools-0.4}/kryptools/primes.py +51 -9
- {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/PKG-INFO +3 -3
- {kryptools-0.2 → kryptools-0.4}/pyproject.toml +2 -2
- kryptools-0.2/kryptools/__init__.py +0 -13
- kryptools-0.2/kryptools/factor_fmt.py +0 -30
- {kryptools-0.2 → kryptools-0.4}/LICENSE +0 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_rho.py +0 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.2 → kryptools-0.4}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
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/doc
|
|
8
|
+
Project-URL: Docs, https://github.com/teschlg/kryptools/tree/main/doc
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
@@ -27,7 +27,7 @@ The tools contained are:
|
|
|
27
27
|
* number theory: sqrt modulo primes, crt, continued fractions, etc.
|
|
28
28
|
* primes: Sieve of Erathostenes, primality tests
|
|
29
29
|
* solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
|
|
30
|
-
* integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
|
|
30
|
+
* integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
|
|
31
31
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
32
32
|
* lattices: Babai rounding/nearest plane, lattice reduction
|
|
33
33
|
|
|
@@ -12,7 +12,7 @@ The tools contained are:
|
|
|
12
12
|
* number theory: sqrt modulo primes, crt, continued fractions, etc.
|
|
13
13
|
* primes: Sieve of Erathostenes, primality tests
|
|
14
14
|
* solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
|
|
15
|
-
* integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
|
|
15
|
+
* integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
|
|
16
16
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
17
17
|
* lattices: Babai rounding/nearest plane, lattice reduction
|
|
18
18
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Ring of intergers modulo `n`.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from math import gcd
|
|
6
|
+
from .factor import factorint
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
class Zmod:
|
|
6
10
|
"""
|
|
7
11
|
Ring of intergers modulo `n`.
|
|
@@ -20,9 +24,11 @@ class Zmod:
|
|
|
20
24
|
0 (mod 5)
|
|
21
25
|
"""
|
|
22
26
|
|
|
23
|
-
def __init__(self, n: int, short: bool =
|
|
27
|
+
def __init__(self, n: int, short: bool = True):
|
|
24
28
|
self.n = n
|
|
25
29
|
self.short = short
|
|
30
|
+
self.group_order = 0
|
|
31
|
+
self.factors = {} # factoring of the group order
|
|
26
32
|
|
|
27
33
|
def __call__(self, x: int):
|
|
28
34
|
return ZmodPoint(x, self)
|
|
@@ -35,15 +41,33 @@ class Zmod:
|
|
|
35
41
|
def __contains__(self, other: "ZmodPoint") -> bool:
|
|
36
42
|
return isinstance(other, ZmodPoint) and self.n == other.ring.n
|
|
37
43
|
|
|
44
|
+
def order(self) -> int:
|
|
45
|
+
"""Compute the order of the group Z_n^*."""
|
|
46
|
+
if self.group_order:
|
|
47
|
+
return self.group_order
|
|
48
|
+
# We compute euler_phi(n) and its factorization in one pass
|
|
49
|
+
for p, k in factorint(self.n).items(): # first factorize n
|
|
50
|
+
for pm, km in factorint(p - 1).items(): # factor p-1 and add the factors
|
|
51
|
+
if pm in self.factors:
|
|
52
|
+
self.factors[pm] += km
|
|
53
|
+
else:
|
|
54
|
+
self.factors[pm] = km
|
|
55
|
+
if k > 1: # if the multiplicity of of p is >1, then we need to add p**(k-1)
|
|
56
|
+
if p in self.factors:
|
|
57
|
+
self.factors[p] += k - 1
|
|
58
|
+
else:
|
|
59
|
+
self.factors[p] = k - 1
|
|
60
|
+
self.group_order = 1
|
|
61
|
+
for p, k in self.factors.items():
|
|
62
|
+
self.group_order *= p**k
|
|
63
|
+
return self.group_order
|
|
64
|
+
|
|
38
65
|
|
|
39
66
|
class ZmodPoint:
|
|
40
67
|
"Represents a point in the ring Zmod."
|
|
41
68
|
|
|
42
69
|
def __init__(self, x: int, ring: "Zmod"):
|
|
43
|
-
|
|
44
|
-
self.x = int(x)
|
|
45
|
-
else:
|
|
46
|
-
self.x = int(x) % ring.n
|
|
70
|
+
self.x = int(x) % ring.n
|
|
47
71
|
self.ring = ring
|
|
48
72
|
|
|
49
73
|
def __repr__(self):
|
|
@@ -62,11 +86,6 @@ class ZmodPoint:
|
|
|
62
86
|
def __int__(self):
|
|
63
87
|
return self.x
|
|
64
88
|
|
|
65
|
-
def sharp(self):
|
|
66
|
-
"Returns a symmetric (w.r.t. 0) representative."
|
|
67
|
-
tmp = (self.ring.n - 1) // 2
|
|
68
|
-
return (self.x + tmp) % self.ring.n - tmp
|
|
69
|
-
|
|
70
89
|
def __hash__(self):
|
|
71
90
|
return hash(self.x)
|
|
72
91
|
|
|
@@ -113,3 +132,26 @@ class ZmodPoint:
|
|
|
113
132
|
|
|
114
133
|
def __pow__(self, scalar: int) -> "ZmodPoint":
|
|
115
134
|
return self.__class__(pow(self.x, scalar, self.ring.n), self.ring)
|
|
135
|
+
|
|
136
|
+
def sharp(self):
|
|
137
|
+
"Returns a symmetric (w.r.t. 0) representative."
|
|
138
|
+
tmp = (self.ring.n - 1) // 2
|
|
139
|
+
return (self.x + tmp) % self.ring.n - tmp
|
|
140
|
+
|
|
141
|
+
def order(self) -> int:
|
|
142
|
+
"""Compute the order of the point in the group Z_n^*."""
|
|
143
|
+
if self.x == 0 or gcd(self.x, self.ring.n) != 1:
|
|
144
|
+
raise ValueError(f"{self.x} and {self.ring.n} are not coprime!")
|
|
145
|
+
order = self.ring.order() # use euler_phi(n) as our current guess
|
|
146
|
+
for p, k in self.ring.factors.items():
|
|
147
|
+
for _ in range(k):
|
|
148
|
+
order_try = order // p
|
|
149
|
+
if pow(self.x, order_try, self.ring.n) == 1:
|
|
150
|
+
order = order_try
|
|
151
|
+
else:
|
|
152
|
+
break
|
|
153
|
+
return order
|
|
154
|
+
|
|
155
|
+
def is_generator(self):
|
|
156
|
+
"""Test if the point is a generator of the group Z_n^*."""
|
|
157
|
+
return self.ring.order() == self.order()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implemenation of same basic algorithms used in cryptography.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .nt import egcd, crt, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
|
|
6
|
+
from .primes import sieve_eratosthenes, is_prime, next_prime, random_prime, random_strongprime, is_safeprime, random_safeprime
|
|
7
|
+
from .factor import factorint
|
|
8
|
+
from .dlp import dlog
|
|
9
|
+
from .ec import EC_Weierstrass
|
|
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
|
|
12
|
+
from .poly import Poly
|
|
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."""
|
|
@@ -69,7 +69,7 @@ def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int =
|
|
|
69
69
|
rinv = pow(ri, -1, m)
|
|
70
70
|
relation[i] = 1
|
|
71
71
|
for j in range(i+1, len_relations + 1):
|
|
72
|
-
|
|
72
|
+
relation[j] = rinv * relation[j] % m
|
|
73
73
|
relations[i] = relation
|
|
74
74
|
if verbose > 2:
|
|
75
75
|
print(n_relations, f"rel found (index={i}) :", relation)
|
|
@@ -50,18 +50,18 @@ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: in
|
|
|
50
50
|
while n % p == 0: # divide by p as many times as possible
|
|
51
51
|
factors[i] += 1
|
|
52
52
|
n = n // p
|
|
53
|
-
if pollard_k:
|
|
53
|
+
if pollard_k:
|
|
54
54
|
if gcd(pow(2, pollard_k, n)-1, n) == 1: # Pollard p-1 test
|
|
55
55
|
return None # most likely not smooth, give up
|
|
56
56
|
for i in range(smallprimes_len, factorbase_len):
|
|
57
57
|
p = factorbase[i]
|
|
58
58
|
while n % p == 0: # divide by p as many times as possible
|
|
59
59
|
factors[i] += 1
|
|
60
|
-
n = n // p
|
|
60
|
+
n = n // p
|
|
61
61
|
if n != 1:
|
|
62
62
|
return None # the number factors if at the end nothing is left
|
|
63
63
|
return factors
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int:
|
|
66
66
|
"""
|
|
67
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.
|
|
@@ -104,7 +104,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
104
104
|
relation += [ 0, x ]
|
|
105
105
|
relation = [0] * sieve_bound + relation # sieve values + primes + b + x
|
|
106
106
|
return relation
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
# this functions does the linear algebra
|
|
109
109
|
def process_relation(relation: list) -> None or int:
|
|
110
110
|
"""Add a new relation to the linear system and keep the system in echelon form."""
|
|
@@ -122,10 +122,10 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
122
122
|
relation[j] = (relation[j] - ri * relations[i][j]) % m
|
|
123
123
|
continue
|
|
124
124
|
# normalize the first nonzero entry
|
|
125
|
-
rinv = pow(ri, -1, m)
|
|
125
|
+
rinv = pow(ri, -1, m)
|
|
126
126
|
relation[i] = 1
|
|
127
127
|
for j in range(i+1, len_relations + 1):
|
|
128
|
-
|
|
128
|
+
relation[j] = rinv * relation[j] % m
|
|
129
129
|
relations[i] = relation
|
|
130
130
|
if verbose > 2:
|
|
131
131
|
print(n_relations, f"rel found (index={i}) :", relation)
|
|
@@ -143,8 +143,8 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
143
143
|
# if ri > 0:
|
|
144
144
|
# relations[i][index] = 0
|
|
145
145
|
# for j in range(index+1, len_relations + 1):
|
|
146
|
-
# relations[i][j] = (relations[i][j] - ri * relation[j]) % m
|
|
147
|
-
#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)
|
|
148
148
|
if index == len_relations - 1: # we found the solution
|
|
149
149
|
if verbose:
|
|
150
150
|
print(f"Success after {n_relations} relations out of {len_relations}.")
|
|
@@ -160,7 +160,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
160
160
|
# Determine the parameters
|
|
161
161
|
#
|
|
162
162
|
|
|
163
|
-
B, expected_trys, expected_trys2 = determine_factorbound(n)
|
|
163
|
+
B, expected_trys, expected_trys2 = determine_factorbound(n)
|
|
164
164
|
max_trys = 10 * expected_trys
|
|
165
165
|
factorbase = []
|
|
166
166
|
factorbase = tuple(p for p in sieve_eratosthenes(B) if gcd(p,n) == 1) # compute the factorbse
|
|
@@ -172,7 +172,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
172
172
|
if pollard: # should we speed up trial division with Pollard p-1
|
|
173
173
|
smallprimes_len, pollard_k = determine_trialdivison_bounds(B // 150, factorbase)
|
|
174
174
|
no_sieve_bound = 1 # We do not sieve for primes smaller than this bound (not worth the effort)
|
|
175
|
-
no_sieve_primes = [ ]
|
|
175
|
+
no_sieve_primes = [ ]
|
|
176
176
|
for i in range(factorbase_len):
|
|
177
177
|
if factorbase[i] > no_sieve_bound:
|
|
178
178
|
break
|
|
@@ -195,7 +195,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
195
195
|
#
|
|
196
196
|
# Do the sieving
|
|
197
197
|
#
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
sn = isqrt(n - 1) + 1 # ceil(sqrt(n))
|
|
200
200
|
d = sn**2 - n
|
|
201
201
|
sn2 = 2 * sn
|
|
@@ -203,7 +203,6 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
203
203
|
for j in range(sieve_bound):
|
|
204
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)
|
|
205
205
|
snj = sn2 + j
|
|
206
|
-
snj2 = snj**2
|
|
207
206
|
dj = d + j * sn
|
|
208
207
|
max_j = sieve_bound - j
|
|
209
208
|
for i in range(factorbase_len):
|
|
@@ -294,5 +293,5 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
294
293
|
res = process_relation(relation)
|
|
295
294
|
if res:
|
|
296
295
|
return(res)
|
|
297
|
-
|
|
296
|
+
|
|
298
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}")
|
|
@@ -7,6 +7,7 @@ from random import randint
|
|
|
7
7
|
from .factor import factorint
|
|
8
8
|
from .nt import legendre_symbol, sqrt_mod, crt
|
|
9
9
|
from .Zmod import Zmod
|
|
10
|
+
from .poly import Poly
|
|
10
11
|
|
|
11
12
|
class EC_Weierstrass():
|
|
12
13
|
"""
|
|
@@ -26,27 +27,29 @@ class EC_Weierstrass():
|
|
|
26
27
|
>>> P + Q
|
|
27
28
|
(195, 41)
|
|
28
29
|
"""
|
|
30
|
+
# pylint: disable=too-many-instance-attributes
|
|
29
31
|
|
|
30
32
|
def __init__(self, p: int, a: int, b: int, order: int = None):
|
|
31
33
|
if p < 3:
|
|
32
34
|
raise ValueError(f"{p} must be a prime larger than 2.")
|
|
33
35
|
if (4 * pow(a, 3, p) + 27 * pow(b, 2, p) ) % p == 0:
|
|
34
|
-
raise ValueError(
|
|
36
|
+
raise ValueError("Curve is singular!")
|
|
35
37
|
self.p = p
|
|
36
38
|
self.gf = Zmod(p, short = True)
|
|
37
39
|
self.a = self.gf(a % p)
|
|
38
40
|
self.b = self.gf(b % p)
|
|
39
41
|
self.group_order = order
|
|
40
42
|
self.group_order_factors = None
|
|
43
|
+
self.psi_list = [ Poly([ 0 ], ring = self.gf ) ] # division polynomials
|
|
41
44
|
self.short = False # display points in short format
|
|
42
45
|
self.hex = False # display points as hex values in compressed format
|
|
43
46
|
|
|
44
47
|
def __call__(self, x: int | str | None = None, y: int | None = None, short: bool = False):
|
|
45
48
|
if isinstance(x, str):
|
|
46
49
|
x = x.replace(" ", "")
|
|
47
|
-
|
|
50
|
+
coordinate_type = x[:2]
|
|
48
51
|
x = x[2:]
|
|
49
|
-
if
|
|
52
|
+
if coordinate_type in ('02', '03'):
|
|
50
53
|
short = 1
|
|
51
54
|
x = int(x, 16)
|
|
52
55
|
if type == '02':
|
|
@@ -70,15 +73,17 @@ class EC_Weierstrass():
|
|
|
70
73
|
return P.y**2 == P.x**3 + self.a * P.x + self.b
|
|
71
74
|
|
|
72
75
|
def info(self):
|
|
73
|
-
|
|
76
|
+
"Display some basic info on the curve"
|
|
77
|
+
out = "Weierstrass curve y^2 = x^3"
|
|
74
78
|
if int(self.a):
|
|
75
|
-
|
|
79
|
+
out += f" + {self.a} x"
|
|
76
80
|
if int(self.b):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
print(
|
|
81
|
+
out += f" + {self.b}"
|
|
82
|
+
out += f" over Z_{self.p}."
|
|
83
|
+
print(out)
|
|
80
84
|
|
|
81
85
|
def add(self, x1, y1, x2, y2):
|
|
86
|
+
"Point addition"
|
|
82
87
|
if x1 is None:
|
|
83
88
|
return x2, y2
|
|
84
89
|
if x2 is None:
|
|
@@ -93,6 +98,7 @@ class EC_Weierstrass():
|
|
|
93
98
|
return x3, y3
|
|
94
99
|
|
|
95
100
|
def dbl(self, x, y):
|
|
101
|
+
"Point doubling"
|
|
96
102
|
if x is None or not y:
|
|
97
103
|
return None, None
|
|
98
104
|
s = (3 * x**2 + self.a) / (2 * y)
|
|
@@ -101,6 +107,7 @@ class EC_Weierstrass():
|
|
|
101
107
|
return x3, y3
|
|
102
108
|
|
|
103
109
|
def mult(self, j: int, x, y): # Addition-subtraction ladder
|
|
110
|
+
"Point multiplication"
|
|
104
111
|
if j == 0:
|
|
105
112
|
return None, None
|
|
106
113
|
if j < 0:
|
|
@@ -126,6 +133,7 @@ class EC_Weierstrass():
|
|
|
126
133
|
return xx, yy
|
|
127
134
|
|
|
128
135
|
def random(self):
|
|
136
|
+
"Return a random point."
|
|
129
137
|
j = -1
|
|
130
138
|
while j == -1:
|
|
131
139
|
x = self.gf(randint(0, self.p - 1))
|
|
@@ -133,7 +141,30 @@ class EC_Weierstrass():
|
|
|
133
141
|
j = legendre_symbol(y2, self.p)
|
|
134
142
|
return ECPoint(x, randint(0, 1), self, short = True)
|
|
135
143
|
|
|
144
|
+
def psi(self, n: int):
|
|
145
|
+
"""The x-part of the n'th division polynomial."""
|
|
146
|
+
|
|
147
|
+
if len(self.psi_list) < 5:
|
|
148
|
+
self.psi_list = [ Poly([i], ring = self.gf) for i in range(3)]
|
|
149
|
+
self.psi_list += [ Poly([-self.a * self.a, 12 * self.b, 6 * self.a, 0, 3], ring = self.gf) ]
|
|
150
|
+
self.psi_list += [ Poly([-4 * self.a**3 - 32 * self.b * self.b, -16 * self.a * self.b, -20 * self.a * self.a, 80 * self.b, 20 * self.a, 0, 4], ring = self.gf) ]
|
|
151
|
+
if len(self.psi_list) < n + 1:
|
|
152
|
+
y2 = Poly([self.b, self.a, 0, 1], ring = self.gf)**2
|
|
153
|
+
ti = 1 / self.gf(2)
|
|
154
|
+
for m in range(len(self.psi_list), n + 1):
|
|
155
|
+
if m % 2: # odd
|
|
156
|
+
m = (m - 1) // 2
|
|
157
|
+
if m % 2:
|
|
158
|
+
self.psi_list += [ self.psi_list[m + 2] * self.psi_list[m]**3 - y2 * self.psi_list[m - 1] * self.psi_list[m + 1]**3]
|
|
159
|
+
else:
|
|
160
|
+
self.psi_list += [ y2 * self.psi_list[m + 2] * self.psi_list[m]**3 - self.psi_list[m - 1] * self.psi_list[m + 1]**3]
|
|
161
|
+
else: # even
|
|
162
|
+
m = m // 2
|
|
163
|
+
self.psi_list += [ ti * self.psi_list[m] * (self.psi_list[m + 2] * self.psi_list[m - 1]**2 - self.psi_list[m - 2] * self.psi_list[m + 1]**2) ]
|
|
164
|
+
return self.psi_list[n]
|
|
165
|
+
|
|
136
166
|
def order(self, order: int = None) -> int:
|
|
167
|
+
"Return the group order."
|
|
137
168
|
if order:
|
|
138
169
|
self.group_order = order
|
|
139
170
|
elif not self.group_order:
|
|
@@ -144,6 +175,7 @@ class EC_Weierstrass():
|
|
|
144
175
|
return self.group_order
|
|
145
176
|
|
|
146
177
|
def factor_order(self) -> dict:
|
|
178
|
+
"Factor the group order."
|
|
147
179
|
if self.group_order_factors:
|
|
148
180
|
return self.group_order_factors
|
|
149
181
|
if not self.group_order:
|
|
@@ -152,6 +184,7 @@ class EC_Weierstrass():
|
|
|
152
184
|
return self.group_order_factors
|
|
153
185
|
|
|
154
186
|
def order_naive(self) -> int:
|
|
187
|
+
"Return the order of the group by adding the Legendre symbols."
|
|
155
188
|
a1 = (int(self.a) + 1) % self.p
|
|
156
189
|
y = int(self.b)
|
|
157
190
|
order = self.p + 1 + legendre_symbol(y, self.p)
|
|
@@ -161,6 +194,7 @@ class EC_Weierstrass():
|
|
|
161
194
|
return order
|
|
162
195
|
|
|
163
196
|
def order_shanks_mestre(self) -> int:
|
|
197
|
+
"Return the order of the group using the Shanks-Mestre algorithm."
|
|
164
198
|
if self.p < 230:
|
|
165
199
|
return self.order_naive()
|
|
166
200
|
j = 1
|
|
@@ -215,7 +249,7 @@ class EC_Weierstrass():
|
|
|
215
249
|
|
|
216
250
|
class ECPoint:
|
|
217
251
|
"Point on an elliptic curve"
|
|
218
|
-
def __init__(self, x: int, y: int, curve: EC_Weierstrass, short:bool = False):
|
|
252
|
+
def __init__(self, x: int|None, y: int|None, curve: EC_Weierstrass, short:bool = False):
|
|
219
253
|
self.curve = curve
|
|
220
254
|
if x is None:
|
|
221
255
|
self.x = None
|
|
@@ -301,17 +335,23 @@ class ECPoint:
|
|
|
301
335
|
break
|
|
302
336
|
return order
|
|
303
337
|
|
|
304
|
-
def
|
|
338
|
+
def psi(self, n: int):
|
|
339
|
+
"""Value of the n'th division polynomial."""
|
|
340
|
+
if n % 2:
|
|
341
|
+
return self.curve.psi(n)(self.x)
|
|
342
|
+
return self.y * self.curve.psi(n)(self.x)
|
|
343
|
+
|
|
344
|
+
def dlog(self, other: "ECPoint") -> int:
|
|
305
345
|
"""Compute the discrete log_P(Q) in EC."""
|
|
306
|
-
m =
|
|
346
|
+
m = other.order()
|
|
307
347
|
mf = factorint(m)
|
|
308
|
-
assert m *
|
|
348
|
+
assert m * self == other.curve(None, None), "DLP not solvable."
|
|
309
349
|
# We first use Pohlig-Hellman to split m into powers of prime factors
|
|
310
350
|
mm = []
|
|
311
351
|
ll = []
|
|
312
352
|
for pj, kj in mf.items():
|
|
313
|
-
Pj = (m // pj**kj) *
|
|
314
|
-
Qj = (m // pj**kj) *
|
|
353
|
+
Pj = (m // pj**kj) * other
|
|
354
|
+
Qj = (m // pj**kj) * self
|
|
315
355
|
l = Qj.dlog_ph(Pj, pj, kj)
|
|
316
356
|
if l is None:
|
|
317
357
|
return None
|
|
@@ -319,60 +359,60 @@ class ECPoint:
|
|
|
319
359
|
ll += [l]
|
|
320
360
|
return crt(ll, mm)
|
|
321
361
|
|
|
322
|
-
def dlog_ph(
|
|
362
|
+
def dlog_ph(self, other: "ECPoint", q: int, k: int) -> int:
|
|
323
363
|
"""Compute the discrete log_P(Q) in EC if P has order q^k using Pohlig-Hellman reduction."""
|
|
324
364
|
if k == 1 or q**k < 10000:
|
|
325
|
-
return
|
|
326
|
-
Pj = q**(k - 1) *
|
|
365
|
+
return self.dlog_switch(other, q**k)
|
|
366
|
+
Pj = q**(k - 1) * self
|
|
327
367
|
P1 = Pj
|
|
328
|
-
Qj = q**(k - 1) *
|
|
368
|
+
Qj = q**(k - 1) * other
|
|
329
369
|
xj = Qj.dlog_switch(P1, q)
|
|
330
370
|
for j in range(2, k + 1):
|
|
331
|
-
Pj = q**(k - j) *
|
|
332
|
-
Qj = q**(k - j) *
|
|
371
|
+
Pj = q**(k - j) * self
|
|
372
|
+
Qj = q**(k - j) * other - xj * Pj
|
|
333
373
|
yj = Qj.dlog_switch(P1, q)
|
|
334
374
|
xj = xj + q ** (j - 1) * yj % q**j
|
|
335
375
|
return xj
|
|
336
376
|
|
|
337
|
-
def dlog_switch(
|
|
377
|
+
def dlog_switch(self, other: "ECPoint", m: int) -> int:
|
|
338
378
|
"""Compute the discrete log_P(Q) in EC if P has order m choosing an appropriate method."""
|
|
339
379
|
if m < 100:
|
|
340
|
-
return
|
|
341
|
-
return
|
|
380
|
+
return self.dlog_naive(other, m)
|
|
381
|
+
return self.dlog_bsgs(other, m)
|
|
342
382
|
|
|
343
|
-
def dlog_naive(
|
|
383
|
+
def dlog_naive(self, other: "ECPoint", m: int) -> int:
|
|
344
384
|
"""Compute the discrete log_P(Q) in EC using an exhaustive search."""
|
|
345
|
-
if not
|
|
346
|
-
raise ValueError(
|
|
385
|
+
if not self.curve == other.curve and not isinstance(self, other.__class__):
|
|
386
|
+
raise ValueError("Points must be on the same curve!")
|
|
347
387
|
j = 0
|
|
348
388
|
xx, yy = None, None
|
|
349
|
-
while xx !=
|
|
389
|
+
while xx != self.x:
|
|
350
390
|
j += 1
|
|
351
|
-
xx, yy =
|
|
391
|
+
xx, yy = self.curve.add(xx, yy, other.x, other.y)
|
|
352
392
|
if xx is None:
|
|
353
393
|
raise ValueError("DLP not solvabel!")
|
|
354
|
-
if yy ==
|
|
394
|
+
if yy == self.y:
|
|
355
395
|
return j
|
|
356
396
|
return m - j
|
|
357
397
|
|
|
358
|
-
def dlog_bsgs(
|
|
398
|
+
def dlog_bsgs(self, other: "ECPoint", m: int) -> int:
|
|
359
399
|
"""Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm."""
|
|
360
|
-
if not
|
|
361
|
-
raise ValueError(
|
|
400
|
+
if not self.curve == other.curve and not isinstance(other, self.__class__):
|
|
401
|
+
raise ValueError("Points must be on the same curve!")
|
|
362
402
|
mm = 1 + isqrt(m - 1)
|
|
363
403
|
m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps
|
|
364
404
|
# initialize baby_steps table
|
|
365
405
|
baby_steps = {}
|
|
366
|
-
baby_step =
|
|
406
|
+
baby_step = other
|
|
367
407
|
for j in range(1,m2+1):
|
|
368
408
|
baby_steps[int(baby_step.x)] = j, int(baby_step.y)
|
|
369
|
-
baby_step +=
|
|
409
|
+
baby_step += other
|
|
370
410
|
|
|
371
411
|
# now take the giant steps
|
|
372
|
-
giant_stride = -mm *
|
|
373
|
-
giant_step =
|
|
412
|
+
giant_stride = -mm * other
|
|
413
|
+
giant_step = self
|
|
374
414
|
for l in range(mm+1):
|
|
375
|
-
if giant_step.x
|
|
415
|
+
if giant_step.x is None:
|
|
376
416
|
return l * mm
|
|
377
417
|
if int(giant_step.x) in baby_steps:
|
|
378
418
|
j = baby_steps[int(giant_step.x)][0]
|
|
@@ -4,7 +4,10 @@ Factorization of integers:
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from math import isqrt, gcd
|
|
7
|
-
from .primes import sieve_eratosthenes,
|
|
7
|
+
from .primes import sieve_eratosthenes, is_prime
|
|
8
|
+
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
|
|
@@ -12,24 +15,18 @@ from .primes import sieve_eratosthenes, isprime
|
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
def _factor_fermat(n: int, steps: int = 10) -> list:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# return
|
|
18
|
+
"Fermat method"
|
|
19
|
+
parameters = {11: (12, 6), 23: (12, 0),
|
|
20
|
+
5: (6, 3), 17: (6, 3),
|
|
21
|
+
19: (4, 2), 7: (4, 0),
|
|
22
|
+
1: (2, 1), 13: (2, 1)}
|
|
23
|
+
start = isqrt(n - 1) + 1
|
|
24
|
+
step, mod = parameters[n % 24]
|
|
25
|
+
start += (mod - start) % step
|
|
26
|
+
for a in range(start, min(start + steps * step,(n + 9) // 6) + 1, step):
|
|
25
27
|
b = isqrt(a * a - n)
|
|
26
28
|
if b * b == a * a - n:
|
|
27
29
|
return a - b
|
|
28
|
-
a += step
|
|
29
|
-
|
|
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
30
|
|
|
34
31
|
def factorint(n: int, verbose: int = 0) -> list:
|
|
35
32
|
"Factor a number."
|
|
@@ -42,7 +39,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
42
39
|
for m in mm:
|
|
43
40
|
if m in prime_factors:
|
|
44
41
|
prime_factors[m] += k
|
|
45
|
-
elif
|
|
42
|
+
elif is_prime(m):
|
|
46
43
|
prime_factors[m] = k
|
|
47
44
|
else:
|
|
48
45
|
if m in remaining_factors:
|
|
@@ -66,7 +63,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
66
63
|
return prime_factors
|
|
67
64
|
if verbose:
|
|
68
65
|
print("Trial division found:", list(prime_factors))
|
|
69
|
-
if
|
|
66
|
+
if is_prime(n):
|
|
70
67
|
prime_factors[n] = 1
|
|
71
68
|
return prime_factors
|
|
72
69
|
remaining_factors = { n: 1 }
|
|
@@ -130,7 +127,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
130
127
|
else:
|
|
131
128
|
remaining_factors[m] = new_factors[m]
|
|
132
129
|
if verbose > 1: print("Remaining: ", remaining_factors)
|
|
133
|
-
|
|
130
|
+
|
|
134
131
|
if len(remaining_factors) == 0:
|
|
135
132
|
return prime_factors
|
|
136
133
|
|
|
@@ -4,9 +4,27 @@ Integer factorization: Dixon's method
|
|
|
4
4
|
|
|
5
5
|
from math import isqrt, gcd, sqrt, log, exp, ceil
|
|
6
6
|
from .primes import sieve_eratosthenes
|
|
7
|
-
from .nt import legendre_symbol
|
|
7
|
+
from .nt import legendre_symbol
|
|
8
8
|
from .factor_qs import bytexor, byteset, bytetest
|
|
9
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
|
|
10
28
|
|
|
11
29
|
def factor_dixon(n: int) -> list:
|
|
12
30
|
"""Find factors of n using the method of Dixon."""
|
|
@@ -19,7 +37,7 @@ def factor_dixon(n: int) -> list:
|
|
|
19
37
|
2 + 3 * u + 2 * u * log(u)
|
|
20
38
|
) # Newton iteration
|
|
21
39
|
B = int(exp(log(n) / u))
|
|
22
|
-
|
|
40
|
+
|
|
23
41
|
# B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
|
|
24
42
|
factorbase = []
|
|
25
43
|
for p in sieve_eratosthenes(B): # compute the factorbase
|
|
@@ -44,7 +62,7 @@ def factor_dixon(n: int) -> list:
|
|
|
44
62
|
index = lf # this will be the index of the first nonzero entry
|
|
45
63
|
# print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
|
|
46
64
|
for i in range(lf):
|
|
47
|
-
if bytetest(relation, i) and relations[i]
|
|
65
|
+
if bytetest(relation, i) and relations[i] is not None: # make this entry zero if we can (Gauss elimination)
|
|
48
66
|
bytexor(relation, relations[i])
|
|
49
67
|
if bytetest(relation, i) and index == lf: # is this the index of the first nonzero entry?
|
|
50
68
|
index = i
|
|
@@ -85,6 +103,5 @@ def factor_dixon(n: int) -> list:
|
|
|
85
103
|
continue
|
|
86
104
|
res = process_relation(j, relation)
|
|
87
105
|
if res:
|
|
88
|
-
return
|
|
106
|
+
return res
|
|
89
107
|
return n
|
|
90
|
-
|