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.
Files changed (29) hide show
  1. {kryptools-0.3 → kryptools-0.4}/PKG-INFO +2 -2
  2. {kryptools-0.3 → kryptools-0.4}/README.md +1 -1
  3. {kryptools-0.3 → kryptools-0.4}/kryptools/Zmod.py +52 -10
  4. {kryptools-0.3 → kryptools-0.4}/kryptools/__init__.py +2 -2
  5. {kryptools-0.3 → kryptools-0.4}/kryptools/ec.py +55 -25
  6. {kryptools-0.3 → kryptools-0.4}/kryptools/factor.py +13 -16
  7. {kryptools-0.3 → kryptools-0.4}/kryptools/factor_ecm.py +1 -0
  8. kryptools-0.4/kryptools/factor_fmt.py +30 -0
  9. {kryptools-0.3 → kryptools-0.4}/kryptools/factor_pm1.py +1 -1
  10. {kryptools-0.3 → kryptools-0.4}/kryptools/factor_qs.py +4 -4
  11. {kryptools-0.3 → kryptools-0.4}/kryptools/la.py +15 -7
  12. {kryptools-0.3 → kryptools-0.4}/kryptools/lat.py +1 -0
  13. {kryptools-0.3 → kryptools-0.4}/kryptools/nt.py +12 -20
  14. {kryptools-0.3 → kryptools-0.4}/kryptools/poly.py +16 -4
  15. {kryptools-0.3 → kryptools-0.4}/kryptools/primes.py +49 -6
  16. {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/PKG-INFO +2 -2
  17. {kryptools-0.3 → kryptools-0.4}/pyproject.toml +1 -1
  18. kryptools-0.3/kryptools/factor_fmt.py +0 -30
  19. {kryptools-0.3 → kryptools-0.4}/LICENSE +0 -0
  20. {kryptools-0.3 → kryptools-0.4}/kryptools/dlp.py +0 -0
  21. {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_bsgs.py +0 -0
  22. {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_ic.py +0 -0
  23. {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_qs.py +0 -0
  24. {kryptools-0.3 → kryptools-0.4}/kryptools/dlp_rho.py +0 -0
  25. {kryptools-0.3 → kryptools-0.4}/kryptools/factor_dix.py +0 -0
  26. {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/SOURCES.txt +0 -0
  27. {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/dependency_links.txt +0 -0
  28. {kryptools-0.3 → kryptools-0.4}/kryptools.egg-info/top_level.txt +0 -0
  29. {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
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 = False):
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
- if isinstance(x, self.__class__) and x.ring.n == ring.n:
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, isprime
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 dlog(Q, P: "ECPoint") -> int:
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 = P.order()
346
+ m = other.order()
317
347
  mf = factorint(m)
318
- assert m * Q == P.curve(None, None), "DLP not solvable."
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) * P
324
- Qj = (m // pj**kj) * Q
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(Q, P: "ECPoint", q: int, k: int) -> int:
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 Q.dlog_switch(P, q**k)
336
- Pj = q**(k - 1) * P
365
+ return self.dlog_switch(other, q**k)
366
+ Pj = q**(k - 1) * self
337
367
  P1 = Pj
338
- Qj = q**(k - 1) * Q
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) * P
342
- Qj = q**(k - j) * Q - xj * Pj
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(Q, P: "ECPoint", m: int) -> int:
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 Q.dlog_naive(P, m)
351
- return Q.dlog_bsgs(P, m)
380
+ return self.dlog_naive(other, m)
381
+ return self.dlog_bsgs(other, m)
352
382
 
353
- def dlog_naive(Q, P: "ECPoint", m: int) -> int:
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 Q.curve == P.curve and not isinstance(Q, P.__class__):
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 != Q.x:
389
+ while xx != self.x:
360
390
  j += 1
361
- xx, yy = P.curve.add(xx, yy, P.x, P.y)
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 == Q.y:
394
+ if yy == self.y:
365
395
  return j
366
396
  return m - j
367
397
 
368
- def dlog_bsgs(Q, P: "ECPoint", m: int) -> int:
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 Q.curve == P.curve and not isinstance(P, Q.__class__):
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 = P
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 += P
409
+ baby_step += other
380
410
 
381
411
  # now take the giant steps
382
- giant_stride = -mm * P
383
- giant_step = Q
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, isprime
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
- a = isqrt(n - 1) + 1
19
- step =2
20
- if n % 3 == 2: # if n % 3 = 2, then a must be a multiple of 3
21
- a += 2 - ((a - 1) % 3)
22
- step = 3
23
- elif (n % 4 == 1) ^ (a & 1): # if n % 4 = 1,3 then a must be odd, even, respectively
24
- a += 1
25
- for _ in range(steps):
26
- #if a > (n + 9) // 6:
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 isprime(m):
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 isprime(n):
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
 
@@ -5,6 +5,7 @@ Integer factorization: Lentra's ECM
5
5
  from math import gcd, isqrt, log
6
6
  from random import randint, seed
7
7
  from .primes import sieve_eratosthenes
8
+ seed(0)
8
9
 
9
10
  # Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
10
11
  # Algorithm 7.2.7
@@ -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
@@ -24,7 +24,7 @@ def _pm1_parameters(B1: int, B2: int = None, primes: tuple = None):
24
24
  continue
25
25
  if q > B2:
26
26
  break
27
- stage_two_deltas.append(q - p)
27
+ stage_two_deltas.append(q - p) # pylint: disable=W0631
28
28
 
29
29
  return stage_one, stage_two_deltas
30
30
 
@@ -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(factors[j])
171
+ del factors[j]
172
172
  else:
173
- del(factors[j]) # the number is unlikely to factor
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
- i, j = divmod(item, self.cols)
65
- return self.matrix[i][j]
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 NotImplemented
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
- zero = 0 * self[0]
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 1
253
- return 0
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
- zero = 0 * self[0]
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
 
@@ -104,6 +104,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
104
104
  assert 0 < delta <= 1, f"LLL reqires 0 < delta={delta} <= 1"
105
105
  j = 1
106
106
  U = V[:, :]
107
+ U.map(int)
107
108
  Us = U[:, :]
108
109
  Us.map(Fraction)
109
110
  M = U.zeros()
@@ -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 and friends
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, x, y such that a x + b y = 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[j] % m[j] == a[j] using the Chinese Remainder Theorem."""
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) -> list:
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(int(self.coeff[0]))
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(f"Cannot divide.")
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
- "Remainder of polynom division"
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
- isprime(n) test if n is probably prime
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
- is_prime = [True] * (B + 1) # to begin with, all numbers are potentially prime
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 is_prime[q]: # sieve out all multiples; numbers p*q with q<p were already sieved out previously
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
- is_prime[qq :: p] = [False] * ((B - qq) // p + 1)
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 is_prime[q]])
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 isprime(n: int) -> bool:
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
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "0.3"
3
+ version = "0.4"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -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