kryptools 0.3__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.3 → kryptools-0.4}/PKG-INFO +2 -2
- {kryptools-0.3 → kryptools-0.4}/README.md +1 -1
- {kryptools-0.3 → kryptools-0.4}/kryptools/Zmod.py +52 -10
- {kryptools-0.3 → kryptools-0.4}/kryptools/__init__.py +2 -2
- {kryptools-0.3 → kryptools-0.4}/kryptools/ec.py +55 -25
- {kryptools-0.3 → kryptools-0.4}/kryptools/factor.py +13 -16
- {kryptools-0.3 → kryptools-0.4}/kryptools/factor_ecm.py +1 -0
- kryptools-0.4/kryptools/factor_fmt.py +30 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/factor_pm1.py +1 -1
- {kryptools-0.3 → kryptools-0.4}/kryptools/factor_qs.py +4 -4
- {kryptools-0.3 → kryptools-0.4}/kryptools/la.py +15 -7
- {kryptools-0.3 → kryptools-0.4}/kryptools/lat.py +1 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/nt.py +12 -20
- {kryptools-0.3 → kryptools-0.4}/kryptools/poly.py +16 -4
- {kryptools-0.3 → kryptools-0.4}/kryptools/primes.py +49 -6
- {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/PKG-INFO +2 -2
- {kryptools-0.3 → kryptools-0.4}/pyproject.toml +1 -1
- kryptools-0.3/kryptools/factor_fmt.py +0 -30
- {kryptools-0.3 → kryptools-0.4}/LICENSE +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/dlp.py +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_ic.py +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_qs.py +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_rho.py +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools/factor_dix.py +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.3 → kryptools-0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
@@ -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()
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Implemenation of same basic algorithms used in cryptography.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
|
|
6
|
-
from .primes import sieve_eratosthenes,
|
|
5
|
+
from .nt import egcd, crt, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
|
|
6
|
+
from .primes import sieve_eratosthenes, is_prime, next_prime, random_prime, random_strongprime, is_safeprime, random_safeprime
|
|
7
7
|
from .factor import factorint
|
|
8
8
|
from .dlp import dlog
|
|
9
9
|
from .ec import EC_Weierstrass
|
|
@@ -7,6 +7,7 @@ from random import randint
|
|
|
7
7
|
from .factor import factorint
|
|
8
8
|
from .nt import legendre_symbol, sqrt_mod, crt
|
|
9
9
|
from .Zmod import Zmod
|
|
10
|
+
from .poly import Poly
|
|
10
11
|
|
|
11
12
|
class EC_Weierstrass():
|
|
12
13
|
"""
|
|
@@ -39,6 +40,7 @@ class EC_Weierstrass():
|
|
|
39
40
|
self.b = self.gf(b % p)
|
|
40
41
|
self.group_order = order
|
|
41
42
|
self.group_order_factors = None
|
|
43
|
+
self.psi_list = [ Poly([ 0 ], ring = self.gf ) ] # division polynomials
|
|
42
44
|
self.short = False # display points in short format
|
|
43
45
|
self.hex = False # display points as hex values in compressed format
|
|
44
46
|
|
|
@@ -139,6 +141,28 @@ class EC_Weierstrass():
|
|
|
139
141
|
j = legendre_symbol(y2, self.p)
|
|
140
142
|
return ECPoint(x, randint(0, 1), self, short = True)
|
|
141
143
|
|
|
144
|
+
def psi(self, n: int):
|
|
145
|
+
"""The x-part of the n'th division polynomial."""
|
|
146
|
+
|
|
147
|
+
if len(self.psi_list) < 5:
|
|
148
|
+
self.psi_list = [ Poly([i], ring = self.gf) for i in range(3)]
|
|
149
|
+
self.psi_list += [ Poly([-self.a * self.a, 12 * self.b, 6 * self.a, 0, 3], ring = self.gf) ]
|
|
150
|
+
self.psi_list += [ Poly([-4 * self.a**3 - 32 * self.b * self.b, -16 * self.a * self.b, -20 * self.a * self.a, 80 * self.b, 20 * self.a, 0, 4], ring = self.gf) ]
|
|
151
|
+
if len(self.psi_list) < n + 1:
|
|
152
|
+
y2 = Poly([self.b, self.a, 0, 1], ring = self.gf)**2
|
|
153
|
+
ti = 1 / self.gf(2)
|
|
154
|
+
for m in range(len(self.psi_list), n + 1):
|
|
155
|
+
if m % 2: # odd
|
|
156
|
+
m = (m - 1) // 2
|
|
157
|
+
if m % 2:
|
|
158
|
+
self.psi_list += [ self.psi_list[m + 2] * self.psi_list[m]**3 - y2 * self.psi_list[m - 1] * self.psi_list[m + 1]**3]
|
|
159
|
+
else:
|
|
160
|
+
self.psi_list += [ y2 * self.psi_list[m + 2] * self.psi_list[m]**3 - self.psi_list[m - 1] * self.psi_list[m + 1]**3]
|
|
161
|
+
else: # even
|
|
162
|
+
m = m // 2
|
|
163
|
+
self.psi_list += [ ti * self.psi_list[m] * (self.psi_list[m + 2] * self.psi_list[m - 1]**2 - self.psi_list[m - 2] * self.psi_list[m + 1]**2) ]
|
|
164
|
+
return self.psi_list[n]
|
|
165
|
+
|
|
142
166
|
def order(self, order: int = None) -> int:
|
|
143
167
|
"Return the group order."
|
|
144
168
|
if order:
|
|
@@ -311,17 +335,23 @@ class ECPoint:
|
|
|
311
335
|
break
|
|
312
336
|
return order
|
|
313
337
|
|
|
314
|
-
def
|
|
338
|
+
def psi(self, n: int):
|
|
339
|
+
"""Value of the n'th division polynomial."""
|
|
340
|
+
if n % 2:
|
|
341
|
+
return self.curve.psi(n)(self.x)
|
|
342
|
+
return self.y * self.curve.psi(n)(self.x)
|
|
343
|
+
|
|
344
|
+
def dlog(self, other: "ECPoint") -> int:
|
|
315
345
|
"""Compute the discrete log_P(Q) in EC."""
|
|
316
|
-
m =
|
|
346
|
+
m = other.order()
|
|
317
347
|
mf = factorint(m)
|
|
318
|
-
assert m *
|
|
348
|
+
assert m * self == other.curve(None, None), "DLP not solvable."
|
|
319
349
|
# We first use Pohlig-Hellman to split m into powers of prime factors
|
|
320
350
|
mm = []
|
|
321
351
|
ll = []
|
|
322
352
|
for pj, kj in mf.items():
|
|
323
|
-
Pj = (m // pj**kj) *
|
|
324
|
-
Qj = (m // pj**kj) *
|
|
353
|
+
Pj = (m // pj**kj) * other
|
|
354
|
+
Qj = (m // pj**kj) * self
|
|
325
355
|
l = Qj.dlog_ph(Pj, pj, kj)
|
|
326
356
|
if l is None:
|
|
327
357
|
return None
|
|
@@ -329,58 +359,58 @@ class ECPoint:
|
|
|
329
359
|
ll += [l]
|
|
330
360
|
return crt(ll, mm)
|
|
331
361
|
|
|
332
|
-
def dlog_ph(
|
|
362
|
+
def dlog_ph(self, other: "ECPoint", q: int, k: int) -> int:
|
|
333
363
|
"""Compute the discrete log_P(Q) in EC if P has order q^k using Pohlig-Hellman reduction."""
|
|
334
364
|
if k == 1 or q**k < 10000:
|
|
335
|
-
return
|
|
336
|
-
Pj = q**(k - 1) *
|
|
365
|
+
return self.dlog_switch(other, q**k)
|
|
366
|
+
Pj = q**(k - 1) * self
|
|
337
367
|
P1 = Pj
|
|
338
|
-
Qj = q**(k - 1) *
|
|
368
|
+
Qj = q**(k - 1) * other
|
|
339
369
|
xj = Qj.dlog_switch(P1, q)
|
|
340
370
|
for j in range(2, k + 1):
|
|
341
|
-
Pj = q**(k - j) *
|
|
342
|
-
Qj = q**(k - j) *
|
|
371
|
+
Pj = q**(k - j) * self
|
|
372
|
+
Qj = q**(k - j) * other - xj * Pj
|
|
343
373
|
yj = Qj.dlog_switch(P1, q)
|
|
344
374
|
xj = xj + q ** (j - 1) * yj % q**j
|
|
345
375
|
return xj
|
|
346
376
|
|
|
347
|
-
def dlog_switch(
|
|
377
|
+
def dlog_switch(self, other: "ECPoint", m: int) -> int:
|
|
348
378
|
"""Compute the discrete log_P(Q) in EC if P has order m choosing an appropriate method."""
|
|
349
379
|
if m < 100:
|
|
350
|
-
return
|
|
351
|
-
return
|
|
380
|
+
return self.dlog_naive(other, m)
|
|
381
|
+
return self.dlog_bsgs(other, m)
|
|
352
382
|
|
|
353
|
-
def dlog_naive(
|
|
383
|
+
def dlog_naive(self, other: "ECPoint", m: int) -> int:
|
|
354
384
|
"""Compute the discrete log_P(Q) in EC using an exhaustive search."""
|
|
355
|
-
if not
|
|
385
|
+
if not self.curve == other.curve and not isinstance(self, other.__class__):
|
|
356
386
|
raise ValueError("Points must be on the same curve!")
|
|
357
387
|
j = 0
|
|
358
388
|
xx, yy = None, None
|
|
359
|
-
while xx !=
|
|
389
|
+
while xx != self.x:
|
|
360
390
|
j += 1
|
|
361
|
-
xx, yy =
|
|
391
|
+
xx, yy = self.curve.add(xx, yy, other.x, other.y)
|
|
362
392
|
if xx is None:
|
|
363
393
|
raise ValueError("DLP not solvabel!")
|
|
364
|
-
if yy ==
|
|
394
|
+
if yy == self.y:
|
|
365
395
|
return j
|
|
366
396
|
return m - j
|
|
367
397
|
|
|
368
|
-
def dlog_bsgs(
|
|
398
|
+
def dlog_bsgs(self, other: "ECPoint", m: int) -> int:
|
|
369
399
|
"""Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm."""
|
|
370
|
-
if not
|
|
400
|
+
if not self.curve == other.curve and not isinstance(other, self.__class__):
|
|
371
401
|
raise ValueError("Points must be on the same curve!")
|
|
372
402
|
mm = 1 + isqrt(m - 1)
|
|
373
403
|
m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps
|
|
374
404
|
# initialize baby_steps table
|
|
375
405
|
baby_steps = {}
|
|
376
|
-
baby_step =
|
|
406
|
+
baby_step = other
|
|
377
407
|
for j in range(1,m2+1):
|
|
378
408
|
baby_steps[int(baby_step.x)] = j, int(baby_step.y)
|
|
379
|
-
baby_step +=
|
|
409
|
+
baby_step += other
|
|
380
410
|
|
|
381
411
|
# now take the giant steps
|
|
382
|
-
giant_stride = -mm *
|
|
383
|
-
giant_step =
|
|
412
|
+
giant_stride = -mm * other
|
|
413
|
+
giant_step = self
|
|
384
414
|
for l in range(mm+1):
|
|
385
415
|
if giant_step.x is None:
|
|
386
416
|
return l * mm
|
|
@@ -4,7 +4,7 @@ Factorization of integers:
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from math import isqrt, gcd
|
|
7
|
-
from .primes import sieve_eratosthenes,
|
|
7
|
+
from .primes import sieve_eratosthenes, is_prime
|
|
8
8
|
from .factor_pm1 import _pm1_parameters, factor_pm1
|
|
9
9
|
from .factor_ecm import _ecm_parameters, factor_ecm
|
|
10
10
|
#from .factor_qs import factor_qs
|
|
@@ -15,21 +15,18 @@ from .factor_ecm import _ecm_parameters, factor_ecm
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def _factor_fermat(n: int, steps: int = 10) -> list:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# return
|
|
18
|
+
"Fermat method"
|
|
19
|
+
parameters = {11: (12, 6), 23: (12, 0),
|
|
20
|
+
5: (6, 3), 17: (6, 3),
|
|
21
|
+
19: (4, 2), 7: (4, 0),
|
|
22
|
+
1: (2, 1), 13: (2, 1)}
|
|
23
|
+
start = isqrt(n - 1) + 1
|
|
24
|
+
step, mod = parameters[n % 24]
|
|
25
|
+
start += (mod - start) % step
|
|
26
|
+
for a in range(start, min(start + steps * step,(n + 9) // 6) + 1, step):
|
|
28
27
|
b = isqrt(a * a - n)
|
|
29
28
|
if b * b == a * a - n:
|
|
30
29
|
return a - b
|
|
31
|
-
a += step
|
|
32
|
-
|
|
33
30
|
|
|
34
31
|
def factorint(n: int, verbose: int = 0) -> list:
|
|
35
32
|
"Factor a number."
|
|
@@ -42,7 +39,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
42
39
|
for m in mm:
|
|
43
40
|
if m in prime_factors:
|
|
44
41
|
prime_factors[m] += k
|
|
45
|
-
elif
|
|
42
|
+
elif is_prime(m):
|
|
46
43
|
prime_factors[m] = k
|
|
47
44
|
else:
|
|
48
45
|
if m in remaining_factors:
|
|
@@ -66,7 +63,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
66
63
|
return prime_factors
|
|
67
64
|
if verbose:
|
|
68
65
|
print("Trial division found:", list(prime_factors))
|
|
69
|
-
if
|
|
66
|
+
if is_prime(n):
|
|
70
67
|
prime_factors[n] = 1
|
|
71
68
|
return prime_factors
|
|
72
69
|
remaining_factors = { n: 1 }
|
|
@@ -130,7 +127,7 @@ def factorint(n: int, verbose: int = 0) -> list:
|
|
|
130
127
|
else:
|
|
131
128
|
remaining_factors[m] = new_factors[m]
|
|
132
129
|
if verbose > 1: print("Remaining: ", remaining_factors)
|
|
133
|
-
|
|
130
|
+
|
|
134
131
|
if len(remaining_factors) == 0:
|
|
135
132
|
return prime_factors
|
|
136
133
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integer factorization: Fermat's method
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import isqrt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def factor_fermat(n: int) -> list:
|
|
9
|
+
"""Find factors of n using the method of Fermat."""
|
|
10
|
+
factors = []
|
|
11
|
+
parameters = {11: (12, 6), 23: (12, 0),
|
|
12
|
+
5: (6, 3), 17: (6, 3),
|
|
13
|
+
19: (4, 2), 7: (4, 0),
|
|
14
|
+
1: (2, 1), 13: (2, 1)}
|
|
15
|
+
# Speedup only works if n is neiter a multipl of 2 or 3
|
|
16
|
+
for p in (2, 3):
|
|
17
|
+
while n % p == 0:
|
|
18
|
+
factors.append(p)
|
|
19
|
+
n //= p
|
|
20
|
+
start = isqrt(n - 1) + 1
|
|
21
|
+
step, mod = parameters[n % 24]
|
|
22
|
+
start += (mod - start) % step
|
|
23
|
+
for a in range(start, (n + 9) // 6 + 1, step):
|
|
24
|
+
b = isqrt(a * a - n)
|
|
25
|
+
if b * b == a * a - n:
|
|
26
|
+
factors.append(a - b)
|
|
27
|
+
factors.append(a + b)
|
|
28
|
+
return factors
|
|
29
|
+
factors.append(n)
|
|
30
|
+
return factors
|
|
@@ -54,11 +54,11 @@ def factor_qs(n: int) -> list:
|
|
|
54
54
|
for i, p in enumerate(factorbase):
|
|
55
55
|
r1 = sqrt_mod(n, p)
|
|
56
56
|
assert r1 is not None # only quadratic residues
|
|
57
|
-
r2 = -r1 % p
|
|
57
|
+
r2 = -r1 % p # pylint: disable=E1130
|
|
58
58
|
if r1 == r2:
|
|
59
59
|
factorbase_root[i] = [ r1 ]
|
|
60
60
|
else:
|
|
61
|
-
factorbase_root[i] = [ r1, -r1 % p ]
|
|
61
|
+
factorbase_root[i] = [ r1, -r1 % p ] # pylint: disable=E1130
|
|
62
62
|
|
|
63
63
|
m = isqrt(n - 1) + 1
|
|
64
64
|
d = m**2 - n
|
|
@@ -168,7 +168,7 @@ def factor_qs(n: int) -> list:
|
|
|
168
168
|
res = process_relation(j, mask)
|
|
169
169
|
if res:
|
|
170
170
|
return res
|
|
171
|
-
del
|
|
171
|
+
del factors[j]
|
|
172
172
|
else:
|
|
173
|
-
del
|
|
173
|
+
del factors[j] # the number is unlikely to factor
|
|
174
174
|
sieve_bound += sieve_step # increase the sieve and continue
|
|
@@ -61,8 +61,10 @@ class Matrix:
|
|
|
61
61
|
else:
|
|
62
62
|
cols = range(self.cols)[j]
|
|
63
63
|
return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if isinstance(item, int):
|
|
65
|
+
i, j = divmod(item, self.cols)
|
|
66
|
+
return self.matrix[i][j]
|
|
67
|
+
return Matrix([self.matrix[k // self.cols][k % self.cols] for k in range(self.cols * self.rows)[item]])
|
|
66
68
|
|
|
67
69
|
def __setitem__(self, item, value):
|
|
68
70
|
if isinstance(item, tuple):
|
|
@@ -162,7 +164,7 @@ class Matrix:
|
|
|
162
164
|
def __mul__(self, other) -> "Matrix":
|
|
163
165
|
if isinstance(other, Matrix):
|
|
164
166
|
return self.multiply(other)
|
|
165
|
-
return
|
|
167
|
+
return Matrix([ [item * other for item in row] for row in self.matrix ])
|
|
166
168
|
|
|
167
169
|
def __rmul__(self, other) -> "Matrix":
|
|
168
170
|
if isinstance(other, Matrix):
|
|
@@ -242,20 +244,26 @@ class Matrix:
|
|
|
242
244
|
n, m = self.cols, self.rows
|
|
243
245
|
elif not n:
|
|
244
246
|
n = m
|
|
245
|
-
|
|
247
|
+
try:
|
|
248
|
+
zero = 0 * self[0]
|
|
249
|
+
except:
|
|
250
|
+
zero = 0
|
|
246
251
|
return Matrix([[ zero for j in range(n)] for i in range(m) ])
|
|
247
252
|
|
|
248
253
|
def eye(self, m: int = None, n: int = None):
|
|
249
254
|
"Returns an identity matrix of the same dimension"
|
|
250
255
|
def delta(i, j):
|
|
251
256
|
if i == j:
|
|
252
|
-
return
|
|
253
|
-
return
|
|
257
|
+
return one
|
|
258
|
+
return zero
|
|
254
259
|
if not m and not n:
|
|
255
260
|
n, m = self.cols, self.rows
|
|
256
261
|
elif not n:
|
|
257
262
|
n = m
|
|
258
|
-
|
|
263
|
+
try:
|
|
264
|
+
zero = 0 * self[0]
|
|
265
|
+
except:
|
|
266
|
+
zero = 0
|
|
259
267
|
one = 1 + zero
|
|
260
268
|
return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
|
|
261
269
|
|
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Number theory tools:
|
|
3
|
-
lcm(a, b) least common mutiple of a and b
|
|
4
3
|
egcd(a,b) extended Euclidean agorithm
|
|
5
4
|
crt([a1, a2, ...],[m1, m2, ...]) Chinese Remainder Theorem
|
|
6
5
|
cf(Fraction(m,n)) continued fraction expansions
|
|
7
6
|
convergents() convergents of a continued fraction
|
|
7
|
+
legendre_symbol(a, p) compute the Legendre symbol of a with respect to the prime p
|
|
8
|
+
jacobi_symbol(a, n) compute the Jacobi symbol of a with respect to n
|
|
8
9
|
sqrt_mod(n, p) square root of n modulo a prime p
|
|
9
10
|
order(a, n) oder of a in the multiplicative group Z_n^*
|
|
10
11
|
"""
|
|
11
|
-
from math import gcd, prod
|
|
12
|
+
from math import gcd, lcm, prod
|
|
12
13
|
from fractions import Fraction
|
|
13
14
|
|
|
14
|
-
# Euclid
|
|
15
|
-
|
|
16
|
-
def lcm(a: int, b: int) -> int:
|
|
17
|
-
"""Compute the least common multiple of a and b."""
|
|
18
|
-
if b == 0:
|
|
19
|
-
return 0
|
|
20
|
-
if bool(a > 0) != bool(b > 0):
|
|
21
|
-
a = -a
|
|
22
|
-
return (a // gcd(a, b)) * b
|
|
23
|
-
|
|
15
|
+
# extended Euclid
|
|
24
16
|
|
|
25
17
|
def egcd(a: int, b: int) -> (int, int, int):
|
|
26
|
-
"""Perform the extended Euclidean agorithm. Returns gcd
|
|
18
|
+
"""Perform the extended Euclidean agorithm. Returns `gcd`, `x`, `y` such that `a x + b y = gcd`."""
|
|
27
19
|
r0, r1 = a, b
|
|
28
20
|
x0, x1, y0, y1 = 1, 0, 0, 1
|
|
29
21
|
while r1 != 0:
|
|
@@ -36,8 +28,8 @@ def egcd(a: int, b: int) -> (int, int, int):
|
|
|
36
28
|
|
|
37
29
|
# Chinese remainder theorem
|
|
38
30
|
|
|
39
|
-
def crt(a: list, m: list) -> int:
|
|
40
|
-
"""Solve given linear congruences x
|
|
31
|
+
def crt(a: list[int], m: list[int]) -> int:
|
|
32
|
+
"""Solve given linear congruences x % m[j] == a[j] using the Chinese Remainder Theorem."""
|
|
41
33
|
l = len(a)
|
|
42
34
|
assert len(m) == l, "The lists of numbers and modules must have equal length."
|
|
43
35
|
M = prod(m)
|
|
@@ -178,8 +170,8 @@ def jacobi_symbol(a: int, n: int) -> int:
|
|
|
178
170
|
return t
|
|
179
171
|
return 0
|
|
180
172
|
|
|
181
|
-
def sqrt_mod(a: int, p: int) ->
|
|
182
|
-
"Compute a square root of a modulo p unsing Cipolla's algorithm."
|
|
173
|
+
def sqrt_mod(a: int, p: int) -> int:
|
|
174
|
+
"Compute a square root of `a` modulo `p` unsing Cipolla's algorithm."
|
|
183
175
|
a %= p
|
|
184
176
|
if a == 0 or a == 1:
|
|
185
177
|
return a
|
|
@@ -211,13 +203,13 @@ from .factor import factorint
|
|
|
211
203
|
|
|
212
204
|
|
|
213
205
|
def euler_phi(n: int) -> int:
|
|
214
|
-
"""Euler's phi function of n
|
|
206
|
+
"""Euler's phi function of `n`."""
|
|
215
207
|
k = factorint(n)
|
|
216
208
|
return prod([(p - 1) * p ** (k[p] - 1) for p in k])
|
|
217
209
|
|
|
218
210
|
|
|
219
211
|
def carmichael_lambda(n: int) -> int:
|
|
220
|
-
"""Carmichael's lambda function of n
|
|
212
|
+
"""Carmichael's lambda function of `n`."""
|
|
221
213
|
k = factorint(n)
|
|
222
214
|
lam_all = [] # values corresponding to the prime factors
|
|
223
215
|
for p in k:
|
|
@@ -233,7 +225,7 @@ def carmichael_lambda(n: int) -> int:
|
|
|
233
225
|
# Order in Z_p^*
|
|
234
226
|
|
|
235
227
|
def order(a: int, n: int, factor=False) -> int:
|
|
236
|
-
"""Compute the order of a in the group Z_n^*."""
|
|
228
|
+
"""Compute the order of `a` in the group Z_n^*."""
|
|
237
229
|
a %= n
|
|
238
230
|
assert a != 0 and gcd(a, n) == 1, f"{a} and {n} are not coprime!"
|
|
239
231
|
factors = dict() # We compute euler_phi(n) and its factorization in one pass
|
|
@@ -25,9 +25,18 @@ class Poly:
|
|
|
25
25
|
if modulus:
|
|
26
26
|
self.mod(modulus)
|
|
27
27
|
|
|
28
|
+
def __call__(self, x):
|
|
29
|
+
return sum(c * x**j for j, c in enumerate(self.coeff))
|
|
30
|
+
|
|
28
31
|
def __getitem__(self, item):
|
|
29
32
|
return self.coeff[item]
|
|
30
33
|
|
|
34
|
+
def __setitem__(self, item, value):
|
|
35
|
+
self.coeff[item] = value
|
|
36
|
+
|
|
37
|
+
def __len__(self):
|
|
38
|
+
return len(self.coeff)
|
|
39
|
+
|
|
31
40
|
def __repr__(self):
|
|
32
41
|
def prx(i: int):
|
|
33
42
|
if i == 0:
|
|
@@ -37,7 +46,7 @@ class Poly:
|
|
|
37
46
|
return "x^" + str(i)
|
|
38
47
|
|
|
39
48
|
if len(self.coeff) == 1:
|
|
40
|
-
return str(
|
|
49
|
+
return str(self.coeff[0])
|
|
41
50
|
plus = ""
|
|
42
51
|
tmp = ""
|
|
43
52
|
for i in reversed(range(len(self.coeff))):
|
|
@@ -76,9 +85,11 @@ class Poly:
|
|
|
76
85
|
return bool(self.degree()) or bool(self.coeff[0])
|
|
77
86
|
|
|
78
87
|
def degree(self):
|
|
88
|
+
"Return the degree."
|
|
79
89
|
return len(self.coeff) - 1
|
|
80
90
|
|
|
81
91
|
def map(self, func):
|
|
92
|
+
"Apply a given function to all coefficients."
|
|
82
93
|
self.coeff = list(map(func, self.coeff))
|
|
83
94
|
|
|
84
95
|
def __add__(self, other: "Poly") -> "Poly":
|
|
@@ -175,7 +186,7 @@ class Poly:
|
|
|
175
186
|
res = self.__class__([1], modulus=self.modulus)
|
|
176
187
|
if i < 0:
|
|
177
188
|
if not self.modulus:
|
|
178
|
-
raise NotImplementedError(
|
|
189
|
+
raise NotImplementedError("Cannot divide.")
|
|
179
190
|
tmp = self.inv()
|
|
180
191
|
else:
|
|
181
192
|
tmp = self
|
|
@@ -184,7 +195,7 @@ class Poly:
|
|
|
184
195
|
return res
|
|
185
196
|
|
|
186
197
|
def divmod(self, other: "Poly") -> ("Poly", "Poly"):
|
|
187
|
-
"Polynom division with remainder"
|
|
198
|
+
"Polynom division with remainder."
|
|
188
199
|
if isinstance(other, list):
|
|
189
200
|
other = self.__class__(other)
|
|
190
201
|
elif not isinstance(other, self.__class__):
|
|
@@ -215,7 +226,7 @@ class Poly:
|
|
|
215
226
|
)
|
|
216
227
|
|
|
217
228
|
def mod(self, other: "Poly") -> None:
|
|
218
|
-
"
|
|
229
|
+
"Reduce with respect to a given polynomial."
|
|
219
230
|
if isinstance(other, list):
|
|
220
231
|
other = self.__class__(other)
|
|
221
232
|
elif not isinstance(other, self.__class__):
|
|
@@ -241,6 +252,7 @@ class Poly:
|
|
|
241
252
|
self.coeff.pop(i)
|
|
242
253
|
|
|
243
254
|
def inv(self, other: "Poly" = None) -> "Poly":
|
|
255
|
+
"Inverse modulo a given polynomial."
|
|
244
256
|
if not other:
|
|
245
257
|
other = self.modulus
|
|
246
258
|
if isinstance(other, list):
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Tools for prime numbers:
|
|
3
3
|
sieve_eratosthenes(B) a tuple of all primes up to including B
|
|
4
|
-
|
|
4
|
+
is_prime(n) test if n is probably prime
|
|
5
|
+
next_prime(n) find the next prime larger or equal n
|
|
6
|
+
random_prime(l) find a random prime with bit length at least l
|
|
7
|
+
random_strongprime(l) find a random strong prime with bit length at least l
|
|
8
|
+
is_safeprime(n) test if n is a safe prime
|
|
9
|
+
random_safeprime(n) find a random safe prime with bit length at least l and ord(2)=(p-1)/2
|
|
5
10
|
miller_rabin_test(n, b) Miller-Rabin primality test with base b
|
|
6
11
|
"""
|
|
7
12
|
from math import isqrt, gcd
|
|
13
|
+
from random import randint
|
|
8
14
|
from .nt import jacobi_symbol
|
|
9
15
|
|
|
10
16
|
# Erathostenes
|
|
@@ -13,15 +19,15 @@ def sieve_eratosthenes(B: int) -> list:
|
|
|
13
19
|
""" "Returns a list of all primes up to (including) max."""
|
|
14
20
|
B1 = (isqrt(B) -1)//2
|
|
15
21
|
B = (B - 1)//2
|
|
16
|
-
|
|
22
|
+
isprime = [True] * (B + 1) # to begin with, all numbers are potentially prime
|
|
17
23
|
# sieve out the primes p=2*q+1 starting at 3 in steps of 2 (ignoring even numbers)
|
|
18
24
|
for q in range(1, B1 + 1):
|
|
19
|
-
if
|
|
25
|
+
if isprime[q]: # sieve out all multiples; numbers p*q with q<p were already sieved out previously
|
|
20
26
|
qq = (q << 1) * (q + 1)
|
|
21
27
|
p = (q << 1) | 1
|
|
22
|
-
|
|
28
|
+
isprime[qq :: p] = [False] * ((B - qq) // p + 1)
|
|
23
29
|
|
|
24
|
-
return tuple([2] + [2 * q + 1 for q in range(1, B + 1) if
|
|
30
|
+
return tuple([2] + [2 * q + 1 for q in range(1, B + 1) if isprime[q]])
|
|
25
31
|
|
|
26
32
|
# Primality testing
|
|
27
33
|
|
|
@@ -51,7 +57,7 @@ def miller_rabin_test(n: int, bases: list[int] | int) -> bool:
|
|
|
51
57
|
return True
|
|
52
58
|
return False
|
|
53
59
|
|
|
54
|
-
def
|
|
60
|
+
def is_prime(n: int) -> bool:
|
|
55
61
|
"""Test if an integer n if probable prime."""
|
|
56
62
|
if n < 18446744073709551616: # https://miller-rabin.appspot.com
|
|
57
63
|
return miller_rabin_test(n, [2, 325, 9375, 28178, 450775, 9780504, 1795265022])
|
|
@@ -59,6 +65,43 @@ def isprime(n: int) -> bool:
|
|
|
59
65
|
return miller_rabin_test(n, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41])
|
|
60
66
|
return miller_rabin_test(n, [2]) and _is_strong_lucas_prp(n) # Baillie–PSW primality test
|
|
61
67
|
|
|
68
|
+
def next_prime(n: int) -> int:
|
|
69
|
+
"""Find the next prime larger or equal n."""
|
|
70
|
+
n |= 1 # make sure n is odd
|
|
71
|
+
while not is_prime(n):
|
|
72
|
+
n += 2
|
|
73
|
+
return n
|
|
74
|
+
|
|
75
|
+
def random_prime(l: int) -> int:
|
|
76
|
+
"""Find a random prime with bit length at least l."""
|
|
77
|
+
return next_prime(randint(2 ** (l - 1), 2**l - 1))
|
|
78
|
+
|
|
79
|
+
def random_strongprime(l: int) -> int:
|
|
80
|
+
"""Find a random strong prime with bit length at least l using Gordon's algorithm."""
|
|
81
|
+
t = random_prime(l)
|
|
82
|
+
s = random_prime(l)
|
|
83
|
+
u = 2 * t
|
|
84
|
+
uu = u * randint(1, 100)
|
|
85
|
+
while not is_prime(uu + 1):
|
|
86
|
+
uu += u
|
|
87
|
+
r = uu + 1
|
|
88
|
+
u = 2 * r * s
|
|
89
|
+
uu = u * randint(1, 100) + 2 * s * pow(s, r - 2, r) - 1
|
|
90
|
+
while not is_prime(uu):
|
|
91
|
+
uu += u
|
|
92
|
+
return t, s, r, uu
|
|
93
|
+
|
|
94
|
+
def is_safeprime(p: int) -> bool:
|
|
95
|
+
"""Tests if a number is a safe prime."""
|
|
96
|
+
return is_prime(p) and is_prime((p - 1) // 2)
|
|
97
|
+
|
|
98
|
+
def random_safeprime(l: int) -> int:
|
|
99
|
+
"""Find a random safe prime with bit length at least l and ord(2)=(p-1)/2."""
|
|
100
|
+
p = randint(2 ** (l - 1), 2**l - 1)
|
|
101
|
+
p = p - (p % 24) + 23
|
|
102
|
+
while not is_safeprime(p):
|
|
103
|
+
p += 24
|
|
104
|
+
return p
|
|
62
105
|
|
|
63
106
|
def _lucas_sequence(n, D, k):
|
|
64
107
|
"""Evaluate a Lucas sequence."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.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
|
|
@@ -27,7 +27,7 @@ The tools contained are:
|
|
|
27
27
|
* number theory: sqrt modulo primes, crt, continued fractions, etc.
|
|
28
28
|
* primes: Sieve of Erathostenes, primality tests
|
|
29
29
|
* solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
|
|
30
|
-
* integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
|
|
30
|
+
* integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve)
|
|
31
31
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
32
32
|
* lattices: Babai rounding/nearest plane, lattice reduction
|
|
33
33
|
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Integer factorization: Fermat's method
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from math import isqrt
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def factor_fermat(n: int) -> list:
|
|
9
|
-
"""Find factors of n using the method of Fermat."""
|
|
10
|
-
factors = []
|
|
11
|
-
# Fermat only works if n has two factors which are either both even or both odd
|
|
12
|
-
while n % 2 == 0:
|
|
13
|
-
factors.append(2)
|
|
14
|
-
n //= 2
|
|
15
|
-
a = isqrt(n - 1) + 1
|
|
16
|
-
step =2
|
|
17
|
-
if n % 3 == 2: # if n % 3 = 2, then a must be a multiple of 3
|
|
18
|
-
a += 2 - ((a - 1) % 3)
|
|
19
|
-
step = 3
|
|
20
|
-
elif (n % 4 == 1) ^ (a & 1): # if n % 4 = 1,3 then a must be odd, even, respectively
|
|
21
|
-
a += 1
|
|
22
|
-
while a <= (n + 9) // 6:
|
|
23
|
-
b = isqrt(a * a - n)
|
|
24
|
-
if b * b == a * a - n:
|
|
25
|
-
factors.append(a - b)
|
|
26
|
-
factors.append(a + b)
|
|
27
|
-
return factors
|
|
28
|
-
a += step
|
|
29
|
-
factors.append(n)
|
|
30
|
-
return factors
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|