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.
Files changed (30) hide show
  1. {kryptools-0.2 → kryptools-0.4}/PKG-INFO +3 -3
  2. {kryptools-0.2 → kryptools-0.4}/README.md +1 -1
  3. {kryptools-0.2 → kryptools-0.4}/kryptools/Zmod.py +52 -10
  4. kryptools-0.4/kryptools/__init__.py +13 -0
  5. {kryptools-0.2 → kryptools-0.4}/kryptools/dlp.py +2 -3
  6. {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_ic.py +1 -1
  7. {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_qs.py +12 -13
  8. {kryptools-0.2 → kryptools-0.4}/kryptools/ec.py +77 -37
  9. {kryptools-0.2 → kryptools-0.4}/kryptools/factor.py +16 -19
  10. {kryptools-0.2 → kryptools-0.4}/kryptools/factor_dix.py +22 -5
  11. {kryptools-0.2 → kryptools-0.4}/kryptools/factor_ecm.py +4 -0
  12. kryptools-0.4/kryptools/factor_fmt.py +30 -0
  13. {kryptools-0.2 → kryptools-0.4}/kryptools/factor_pm1.py +1 -1
  14. {kryptools-0.2 → kryptools-0.4}/kryptools/factor_qs.py +4 -4
  15. {kryptools-0.2 → kryptools-0.4}/kryptools/la.py +86 -24
  16. {kryptools-0.2 → kryptools-0.4}/kryptools/lat.py +9 -9
  17. {kryptools-0.2 → kryptools-0.4}/kryptools/nt.py +20 -30
  18. {kryptools-0.2 → kryptools-0.4}/kryptools/poly.py +52 -7
  19. {kryptools-0.2 → kryptools-0.4}/kryptools/primes.py +51 -9
  20. {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/PKG-INFO +3 -3
  21. {kryptools-0.2 → kryptools-0.4}/pyproject.toml +2 -2
  22. kryptools-0.2/kryptools/__init__.py +0 -13
  23. kryptools-0.2/kryptools/factor_fmt.py +0 -30
  24. {kryptools-0.2 → kryptools-0.4}/LICENSE +0 -0
  25. {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_bsgs.py +0 -0
  26. {kryptools-0.2 → kryptools-0.4}/kryptools/dlp_rho.py +0 -0
  27. {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/SOURCES.txt +0 -0
  28. {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/dependency_links.txt +0 -0
  29. {kryptools-0.2 → kryptools-0.4}/kryptools.egg-info/top_level.txt +0 -0
  30. {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.2
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 = 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()
@@ -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
- elif 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
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
- else:
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
- relation[j] = rinv * relation[j] % m
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
- relation[j] = rinv * relation[j] % m
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(f"Curve is singular!")
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
- type = x[:2]
50
+ coordinate_type = x[:2]
48
51
  x = x[2:]
49
- if type == '02' or type == '03':
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
- format = "Weierstrass curve y^2 = x^3"
76
+ "Display some basic info on the curve"
77
+ out = "Weierstrass curve y^2 = x^3"
74
78
  if int(self.a):
75
- format += f" + {self.a} x"
79
+ out += f" + {self.a} x"
76
80
  if int(self.b):
77
- format += f" + {self.b}"
78
- format += f" over Z_{self.p}."
79
- print(format)
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 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:
305
345
  """Compute the discrete log_P(Q) in EC."""
306
- m = P.order()
346
+ m = other.order()
307
347
  mf = factorint(m)
308
- assert m * Q == P.curve(None, None), "DLP not solvable."
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) * P
314
- Qj = (m // pj**kj) * Q
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(Q, P: "ECPoint", q: int, k: int) -> int:
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 Q.dlog_switch(P, q**k)
326
- Pj = q**(k - 1) * P
365
+ return self.dlog_switch(other, q**k)
366
+ Pj = q**(k - 1) * self
327
367
  P1 = Pj
328
- Qj = q**(k - 1) * Q
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) * P
332
- Qj = q**(k - j) * Q - xj * Pj
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(Q, P: "ECPoint", m: int) -> int:
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 Q.dlog_naive(P, m)
341
- return Q.dlog_bsgs(P, m)
380
+ return self.dlog_naive(other, m)
381
+ return self.dlog_bsgs(other, m)
342
382
 
343
- def dlog_naive(Q, P: "ECPoint", m: int) -> int:
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 Q.curve == P.curve and not isinstance(Q, P.__class__):
346
- raise ValueError(f"Points must be on the same curve!")
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 != Q.x:
389
+ while xx != self.x:
350
390
  j += 1
351
- xx, yy = P.curve.add(xx, yy, P.x, P.y)
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 == Q.y:
394
+ if yy == self.y:
355
395
  return j
356
396
  return m - j
357
397
 
358
- def dlog_bsgs(Q, P: "ECPoint", m: int) -> int:
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 Q.curve == P.curve and not isinstance(P, Q.__class__):
361
- raise ValueError(f"Points must be on the same curve!")
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 = P
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 += P
409
+ baby_step += other
370
410
 
371
411
  # now take the giant steps
372
- giant_stride = -mm * P
373
- giant_step = Q
412
+ giant_stride = -mm * other
413
+ giant_step = self
374
414
  for l in range(mm+1):
375
- if giant_step.x == None:
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, isprime
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
- 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
- for _ in range(steps):
23
- #if a > (n + 9) // 6:
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 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
 
@@ -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, sqrt_mod
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] != None: # make this entry zero if we can (Gauss elimination)
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(res)
106
+ return res
89
107
  return n
90
-