kryptools 0.1__tar.gz → 0.3__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 (28) hide show
  1. {kryptools-0.1 → kryptools-0.3}/PKG-INFO +5 -1
  2. {kryptools-0.1 → kryptools-0.3}/README.md +3 -0
  3. {kryptools-0.1 → kryptools-0.3}/kryptools/__init__.py +5 -5
  4. {kryptools-0.1 → kryptools-0.3}/kryptools/dlp.py +2 -3
  5. {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_ic.py +3 -2
  6. {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_qs.py +15 -15
  7. {kryptools-0.1 → kryptools-0.3}/kryptools/ec.py +22 -12
  8. kryptools-0.3/kryptools/factor_dix.py +107 -0
  9. {kryptools-0.1 → kryptools-0.3}/kryptools/factor_ecm.py +3 -0
  10. kryptools-0.3/kryptools/factor_fmt.py +30 -0
  11. {kryptools-0.1 → kryptools-0.3}/kryptools/la.py +75 -21
  12. {kryptools-0.1 → kryptools-0.3}/kryptools/lat.py +28 -15
  13. {kryptools-0.1 → kryptools-0.3}/kryptools/nt.py +8 -10
  14. {kryptools-0.1 → kryptools-0.3}/kryptools/poly.py +38 -5
  15. {kryptools-0.1 → kryptools-0.3}/kryptools/primes.py +2 -3
  16. {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/PKG-INFO +5 -1
  17. {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/SOURCES.txt +2 -0
  18. {kryptools-0.1 → kryptools-0.3}/pyproject.toml +3 -2
  19. {kryptools-0.1 → kryptools-0.3}/LICENSE +0 -0
  20. {kryptools-0.1 → kryptools-0.3}/kryptools/Zmod.py +0 -0
  21. {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_bsgs.py +0 -0
  22. {kryptools-0.1 → kryptools-0.3}/kryptools/dlp_rho.py +0 -0
  23. {kryptools-0.1 → kryptools-0.3}/kryptools/factor.py +3 -3
  24. {kryptools-0.1 → kryptools-0.3}/kryptools/factor_pm1.py +0 -0
  25. {kryptools-0.1 → kryptools-0.3}/kryptools/factor_qs.py +0 -0
  26. {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/dependency_links.txt +0 -0
  27. {kryptools-0.1 → kryptools-0.3}/kryptools.egg-info/top_level.txt +0 -0
  28. {kryptools-0.1 → kryptools-0.3}/setup.cfg +0 -0
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.1
3
+ Version: 0.3
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/tree/main/doc
8
9
  Classifier: Programming Language :: Python :: 3
9
10
  Classifier: License :: OSI Approved :: MIT License
10
11
  Classifier: Operating System :: OS Independent
@@ -33,3 +34,6 @@ The tools contained are:
33
34
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
34
35
  * Poly: a class for polynomials (division, modulo)
35
36
  * Zmod: a class for the ring of integers modulo an integer
37
+
38
+ Documentation can be found in the jupyter notebook
39
+ (currently incomplete: use the force, read the source).
@@ -19,3 +19,6 @@ The tools contained are:
19
19
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
20
20
  * Poly: a class for polynomials (division, modulo)
21
21
  * Zmod: a class for the ring of integers modulo an integer
22
+
23
+ Documentation can be found in the jupyter notebook
24
+ (currently incomplete: use the force, read the source).
@@ -2,12 +2,12 @@
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
7
+ from .factor import factorint
5
8
  from .dlp import dlog
6
9
  from .ec import EC_Weierstrass
7
- from .factor import factorint
8
- from .la import Matrix
9
- from .lat import hermite_nf, gram_schmidt, lll
10
- from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda
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
11
12
  from .poly import Poly
12
- from .primes import sieve_eratosthenes, isprime
13
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."""
@@ -3,9 +3,10 @@ Discrete log solvers: Index calculus
3
3
  """
4
4
 
5
5
  from math import gcd
6
- from random import randint
6
+ from random import randint, seed
7
7
  from .primes import sieve_eratosthenes
8
8
  from .dlp_qs import determine_factorbound, determine_trialdivison_bounds, is_smooth
9
+ seed(0)
9
10
 
10
11
  def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int = 0) -> int:
11
12
  """Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus."""
@@ -68,7 +69,7 @@ def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int =
68
69
  rinv = pow(ri, -1, m)
69
70
  relation[i] = 1
70
71
  for j in range(i+1, len_relations + 1):
71
- relation[j] = rinv * relation[j] % m
72
+ relation[j] = rinv * relation[j] % m
72
73
  relations[i] = relation
73
74
  if verbose > 2:
74
75
  print(n_relations, f"rel found (index={i}) :", relation)
@@ -2,10 +2,11 @@
2
2
  Discrete log solvers: Quadratic sieve
3
3
  """
4
4
 
5
+ from math import exp, log, sqrt, gcd, isqrt
6
+ from random import randint, seed
5
7
  from .nt import sqrt_mod
6
8
  from .primes import sieve_eratosthenes
7
- from math import exp, log, sqrt, gcd
8
-
9
+ seed(0)
9
10
 
10
11
  def determine_factorbound(n: int) -> (int, int):
11
12
  """Determines the optimal factor bound and the expected number of trys until a for a given n."""
@@ -49,18 +50,18 @@ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: in
49
50
  while n % p == 0: # divide by p as many times as possible
50
51
  factors[i] += 1
51
52
  n = n // p
52
- if pollard_k:
53
+ if pollard_k:
53
54
  if gcd(pow(2, pollard_k, n)-1, n) == 1: # Pollard p-1 test
54
55
  return None # most likely not smooth, give up
55
56
  for i in range(smallprimes_len, factorbase_len):
56
57
  p = factorbase[i]
57
58
  while n % p == 0: # divide by p as many times as possible
58
59
  factors[i] += 1
59
- n = n // p
60
+ n = n // p
60
61
  if n != 1:
61
62
  return None # the number factors if at the end nothing is left
62
63
  return factors
63
-
64
+
64
65
  def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int:
65
66
  """
66
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.
@@ -103,7 +104,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
103
104
  relation += [ 0, x ]
104
105
  relation = [0] * sieve_bound + relation # sieve values + primes + b + x
105
106
  return relation
106
-
107
+
107
108
  # this functions does the linear algebra
108
109
  def process_relation(relation: list) -> None or int:
109
110
  """Add a new relation to the linear system and keep the system in echelon form."""
@@ -121,10 +122,10 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
121
122
  relation[j] = (relation[j] - ri * relations[i][j]) % m
122
123
  continue
123
124
  # normalize the first nonzero entry
124
- rinv = pow(ri, -1, m)
125
+ rinv = pow(ri, -1, m)
125
126
  relation[i] = 1
126
127
  for j in range(i+1, len_relations + 1):
127
- relation[j] = rinv * relation[j] % m
128
+ relation[j] = rinv * relation[j] % m
128
129
  relations[i] = relation
129
130
  if verbose > 2:
130
131
  print(n_relations, f"rel found (index={i}) :", relation)
@@ -142,8 +143,8 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
142
143
  # if ri > 0:
143
144
  # relations[i][index] = 0
144
145
  # for j in range(index+1, len_relations + 1):
145
- # relations[i][j] = (relations[i][j] - ri * relation[j]) % m
146
- #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)
147
148
  if index == len_relations - 1: # we found the solution
148
149
  if verbose:
149
150
  print(f"Success after {n_relations} relations out of {len_relations}.")
@@ -159,7 +160,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
159
160
  # Determine the parameters
160
161
  #
161
162
 
162
- B, expected_trys, expected_trys2 = determine_factorbound(n)
163
+ B, expected_trys, expected_trys2 = determine_factorbound(n)
163
164
  max_trys = 10 * expected_trys
164
165
  factorbase = []
165
166
  factorbase = tuple(p for p in sieve_eratosthenes(B) if gcd(p,n) == 1) # compute the factorbse
@@ -171,7 +172,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
171
172
  if pollard: # should we speed up trial division with Pollard p-1
172
173
  smallprimes_len, pollard_k = determine_trialdivison_bounds(B // 150, factorbase)
173
174
  no_sieve_bound = 1 # We do not sieve for primes smaller than this bound (not worth the effort)
174
- no_sieve_primes = [ ]
175
+ no_sieve_primes = [ ]
175
176
  for i in range(factorbase_len):
176
177
  if factorbase[i] > no_sieve_bound:
177
178
  break
@@ -194,7 +195,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
194
195
  #
195
196
  # Do the sieving
196
197
  #
197
-
198
+
198
199
  sn = isqrt(n - 1) + 1 # ceil(sqrt(n))
199
200
  d = sn**2 - n
200
201
  sn2 = 2 * sn
@@ -202,7 +203,6 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
202
203
  for j in range(sieve_bound):
203
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)
204
205
  snj = sn2 + j
205
- snj2 = snj**2
206
206
  dj = d + j * sn
207
207
  max_j = sieve_bound - j
208
208
  for i in range(factorbase_len):
@@ -293,5 +293,5 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
293
293
  res = process_relation(relation)
294
294
  if res:
295
295
  return(res)
296
-
296
+
297
297
  raise Exception(f"Sorry, Quadratic sieve could not find enough relations! ({n_relations} - {n_relations_redundant} = {n_relations - n_relations_redundant} out of {len_relations}). Try to increase sieve_factor={sieve_factor}")
@@ -26,12 +26,13 @@ class EC_Weierstrass():
26
26
  >>> P + Q
27
27
  (195, 41)
28
28
  """
29
+ # pylint: disable=too-many-instance-attributes
29
30
 
30
31
  def __init__(self, p: int, a: int, b: int, order: int = None):
31
32
  if p < 3:
32
33
  raise ValueError(f"{p} must be a prime larger than 2.")
33
34
  if (4 * pow(a, 3, p) + 27 * pow(b, 2, p) ) % p == 0:
34
- raise ValueError(f"Curve is singular!")
35
+ raise ValueError("Curve is singular!")
35
36
  self.p = p
36
37
  self.gf = Zmod(p, short = True)
37
38
  self.a = self.gf(a % p)
@@ -44,9 +45,9 @@ class EC_Weierstrass():
44
45
  def __call__(self, x: int | str | None = None, y: int | None = None, short: bool = False):
45
46
  if isinstance(x, str):
46
47
  x = x.replace(" ", "")
47
- type = x[:2]
48
+ coordinate_type = x[:2]
48
49
  x = x[2:]
49
- if type == '02' or type == '03':
50
+ if coordinate_type in ('02', '03'):
50
51
  short = 1
51
52
  x = int(x, 16)
52
53
  if type == '02':
@@ -70,15 +71,17 @@ class EC_Weierstrass():
70
71
  return P.y**2 == P.x**3 + self.a * P.x + self.b
71
72
 
72
73
  def info(self):
73
- format = "Weierstrass curve y^2 = x^3"
74
+ "Display some basic info on the curve"
75
+ out = "Weierstrass curve y^2 = x^3"
74
76
  if int(self.a):
75
- format += f" + {self.a} x"
77
+ out += f" + {self.a} x"
76
78
  if int(self.b):
77
- format += f" + {self.b}"
78
- format += f" over Z_{self.p}."
79
- print(format)
79
+ out += f" + {self.b}"
80
+ out += f" over Z_{self.p}."
81
+ print(out)
80
82
 
81
83
  def add(self, x1, y1, x2, y2):
84
+ "Point addition"
82
85
  if x1 is None:
83
86
  return x2, y2
84
87
  if x2 is None:
@@ -93,6 +96,7 @@ class EC_Weierstrass():
93
96
  return x3, y3
94
97
 
95
98
  def dbl(self, x, y):
99
+ "Point doubling"
96
100
  if x is None or not y:
97
101
  return None, None
98
102
  s = (3 * x**2 + self.a) / (2 * y)
@@ -101,6 +105,7 @@ class EC_Weierstrass():
101
105
  return x3, y3
102
106
 
103
107
  def mult(self, j: int, x, y): # Addition-subtraction ladder
108
+ "Point multiplication"
104
109
  if j == 0:
105
110
  return None, None
106
111
  if j < 0:
@@ -126,6 +131,7 @@ class EC_Weierstrass():
126
131
  return xx, yy
127
132
 
128
133
  def random(self):
134
+ "Return a random point."
129
135
  j = -1
130
136
  while j == -1:
131
137
  x = self.gf(randint(0, self.p - 1))
@@ -134,6 +140,7 @@ class EC_Weierstrass():
134
140
  return ECPoint(x, randint(0, 1), self, short = True)
135
141
 
136
142
  def order(self, order: int = None) -> int:
143
+ "Return the group order."
137
144
  if order:
138
145
  self.group_order = order
139
146
  elif not self.group_order:
@@ -144,6 +151,7 @@ class EC_Weierstrass():
144
151
  return self.group_order
145
152
 
146
153
  def factor_order(self) -> dict:
154
+ "Factor the group order."
147
155
  if self.group_order_factors:
148
156
  return self.group_order_factors
149
157
  if not self.group_order:
@@ -152,6 +160,7 @@ class EC_Weierstrass():
152
160
  return self.group_order_factors
153
161
 
154
162
  def order_naive(self) -> int:
163
+ "Return the order of the group by adding the Legendre symbols."
155
164
  a1 = (int(self.a) + 1) % self.p
156
165
  y = int(self.b)
157
166
  order = self.p + 1 + legendre_symbol(y, self.p)
@@ -161,6 +170,7 @@ class EC_Weierstrass():
161
170
  return order
162
171
 
163
172
  def order_shanks_mestre(self) -> int:
173
+ "Return the order of the group using the Shanks-Mestre algorithm."
164
174
  if self.p < 230:
165
175
  return self.order_naive()
166
176
  j = 1
@@ -215,7 +225,7 @@ class EC_Weierstrass():
215
225
 
216
226
  class ECPoint:
217
227
  "Point on an elliptic curve"
218
- def __init__(self, x: int, y: int, curve: EC_Weierstrass, short:bool = False):
228
+ def __init__(self, x: int|None, y: int|None, curve: EC_Weierstrass, short:bool = False):
219
229
  self.curve = curve
220
230
  if x is None:
221
231
  self.x = None
@@ -343,7 +353,7 @@ class ECPoint:
343
353
  def dlog_naive(Q, P: "ECPoint", m: int) -> int:
344
354
  """Compute the discrete log_P(Q) in EC using an exhaustive search."""
345
355
  if not Q.curve == P.curve and not isinstance(Q, P.__class__):
346
- raise ValueError(f"Points must be on the same curve!")
356
+ raise ValueError("Points must be on the same curve!")
347
357
  j = 0
348
358
  xx, yy = None, None
349
359
  while xx != Q.x:
@@ -358,7 +368,7 @@ class ECPoint:
358
368
  def dlog_bsgs(Q, P: "ECPoint", m: int) -> int:
359
369
  """Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm."""
360
370
  if not Q.curve == P.curve and not isinstance(P, Q.__class__):
361
- raise ValueError(f"Points must be on the same curve!")
371
+ raise ValueError("Points must be on the same curve!")
362
372
  mm = 1 + isqrt(m - 1)
363
373
  m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps
364
374
  # initialize baby_steps table
@@ -372,7 +382,7 @@ class ECPoint:
372
382
  giant_stride = -mm * P
373
383
  giant_step = Q
374
384
  for l in range(mm+1):
375
- if giant_step.x == None:
385
+ if giant_step.x is None:
376
386
  return l * mm
377
387
  if int(giant_step.x) in baby_steps:
378
388
  j = baby_steps[int(giant_step.x)][0]
@@ -0,0 +1,107 @@
1
+ """
2
+ Integer factorization: Dixon's method
3
+ """
4
+
5
+ from math import isqrt, gcd, sqrt, log, exp, ceil
6
+ from .primes import sieve_eratosthenes
7
+ from .nt import legendre_symbol
8
+ from .factor_qs import bytexor, byteset, bytetest
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
28
+
29
+ def factor_dixon(n: int) -> list:
30
+ """Find factors of n using the method of Dixon."""
31
+ # first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
32
+ # the expected running time |B|^2 u^u = u^(u+2) p^(2/u)/log(n). There is no explicit expression for the optimum, hence
33
+ # we use Newton
34
+ u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
35
+ for _ in range(3):
36
+ u = (2 * log(n) + u * u * (2 + log(u))) / (
37
+ 2 + 3 * u + 2 * u * log(u)
38
+ ) # Newton iteration
39
+ B = int(exp(log(n) / u))
40
+
41
+ # B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
42
+ factorbase = []
43
+ for p in sieve_eratosthenes(B): # compute the factorbase
44
+ ls = legendre_symbol(n, p)
45
+ if ls == 0: # we already found a factor;-)
46
+ if n == p:
47
+ return n
48
+ return p
49
+ if ls == 1: # we only take primes such that n is quadratic residue
50
+ factorbase.append(p)
51
+ lf = len(factorbase) + 1 # length of the factorbase (including -1)
52
+ lfb = ceil(lf / 8) # the number of bytes we need to store a relation
53
+ m = isqrt(n - 1) + 1
54
+ def process_relation(j: int, relation: bytes):
55
+ nonlocal relation_no, values, relations
56
+ relation_no += 1
57
+ rhs = bytearray(b"\x00") * lfb # construct the k'th row of the identity matrix
58
+ byteset(rhs, relation_no)
59
+ relation += rhs # extend the relation with this row
60
+ values[relation_no] = j # store the value which lead to the relation
61
+ # do the Gauss elimination
62
+ index = lf # this will be the index of the first nonzero entry
63
+ # print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
64
+ for i in range(lf):
65
+ if bytetest(relation, i) and relations[i] is not None: # make this entry zero if we can (Gauss elimination)
66
+ bytexor(relation, relations[i])
67
+ if bytetest(relation, i) and index == lf: # is this the index of the first nonzero entry?
68
+ index = i
69
+ # print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
70
+ if index == lf: # the new relation is linearly dependent: we have found a linear combination of the 0 vector
71
+ # now we need to determine the factors
72
+ u = 1 # product over all values m - j such that f(m-j) is B-smooth
73
+ v = 1 # sqrt of the product over all f(m-j) which are B-smooth
74
+ w = 1 # save nonsquare terms for the next round
75
+ for i in range(lf):
76
+ if bytetest(relation, lfb * 8 + i): # select the relations which sum to the 0 vector
77
+ ui = m + values[i]
78
+ u = (u * ui) % n
79
+ vi = ui ** 2 - n
80
+ d = gcd(w, vi)
81
+ v = (v * d) % n # sqrt of the part which is already square
82
+ w = (w // d) * (vi // d) # this part is not square yet
83
+ v = v * isqrt(w) % n
84
+ res = gcd(u - v, n)
85
+ if 1 < res < n:
86
+ return res
87
+ relation_no -= 1 # this one did not work, try again
88
+ else:
89
+ relations[index] = relation
90
+ return None
91
+
92
+ relation_no = -1
93
+ relations = [None] * lf # here we will store the relations in case we found a B-smooth number
94
+ values = [None] * lf # here we will store the values leading to the B-smooth numbers
95
+ for j in range(1,m): # loop over j=0,1,-1,2,-2,...
96
+ if j & 1 == 0:
97
+ j >>= 1
98
+ else:
99
+ j >>= 1
100
+ j *= -1
101
+ relation = is_smooth((j + m) ** 2 - n, factorbase, lfb) # test if f(j) is B-smooth
102
+ if relation is None:
103
+ continue
104
+ res = process_relation(j, relation)
105
+ if res:
106
+ return res
107
+ return n
@@ -11,6 +11,7 @@ from .primes import sieve_eratosthenes
11
11
 
12
12
 
13
13
  def dbl(P, c2, p):
14
+ "EC Montgommery point doubling"
14
15
  # c2 = c - 2
15
16
  t1 = (P[0] + P[1]) % p
16
17
  t2 = (P[0] - P[1]) % p
@@ -19,12 +20,14 @@ def dbl(P, c2, p):
19
20
 
20
21
 
21
22
  def add(P1, P2, P3, p):
23
+ "EC Montgommery point addition"
22
24
  t1 = pow(P1[0] * P2[0] - P1[1] * P2[1], 2, p)
23
25
  t2 = pow(P1[0] * P2[1] - P2[0] * P1[1], 2, p)
24
26
  return P3[1] * t1 % p, P3[0] * t2 % p
25
27
 
26
28
 
27
29
  def mult(k, P, c2, p):
30
+ "EC Montgommery point multiplication"
28
31
  if k == 1:
29
32
  return P
30
33
  if k == 2:
@@ -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
+ # 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
@@ -2,7 +2,7 @@
2
2
  Linear algebra
3
3
  """
4
4
 
5
- from math import sqrt, prod
5
+ from math import inf, sqrt, prod
6
6
 
7
7
  class Matrix:
8
8
  """
@@ -48,12 +48,18 @@ class Matrix:
48
48
  i, j = item
49
49
  if isinstance(i, int) and isinstance(j, int):
50
50
  return self.matrix[i][j]
51
- rows = range(self.rows)[i]
52
51
  if isinstance(i, int):
53
- rows = [ rows ]
54
- cols = range(self.cols)[j]
52
+ rows = [ i ]
53
+ elif isinstance(i, list):
54
+ rows = i
55
+ else:
56
+ rows = range(self.rows)[i]
55
57
  if isinstance(j, int):
56
- cols = [ cols ]
58
+ cols = [ j ]
59
+ elif isinstance(j, list):
60
+ cols = i
61
+ else:
62
+ cols = range(self.cols)[j]
57
63
  return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
58
64
  i, j = divmod(item, self.cols)
59
65
  return self.matrix[i][j]
@@ -64,12 +70,18 @@ class Matrix:
64
70
  if isinstance(i, int) and isinstance(j, int):
65
71
  self.matrix[i][j] = value
66
72
  return
67
- rows = range(self.rows)[i]
68
73
  if isinstance(i, int):
69
- rows = [ rows ]
70
- cols = range(self.cols)[j]
74
+ rows = [ i ]
75
+ elif isinstance(i, list):
76
+ rows = i
77
+ else:
78
+ rows = range(self.rows)[i]
71
79
  if isinstance(j, int):
72
- cols = [ cols ]
80
+ cols = [ j ]
81
+ elif isinstance(j, list):
82
+ cols = i
83
+ else:
84
+ cols = range(self.cols)[j]
73
85
  for i, ii in zip(cols,range(len(cols))):
74
86
  for j, jj in zip(rows,range(len(rows))):
75
87
  self.matrix[j][i] = value[jj,ii]
@@ -97,11 +109,20 @@ class Matrix:
97
109
  "Squared Frobenius/Euclidean norm."
98
110
  return sum( sum(x*x for x in row) for row in self.matrix )
99
111
 
100
- def norm(self) -> float:
101
- "Frobenius/Euclidean norm."
102
- return sqrt(self.norm2())
112
+ def norm(self, p: int = 2) -> float:
113
+ "p-norm of a matrix regarded as a vector."
114
+ if p == 2:
115
+ return sqrt(self.norm2())
116
+ if p == 1:
117
+ return sum( sum(abs(x) for x in row) for row in self.matrix )
118
+ if p == inf:
119
+ return max( max(abs(x) for x in row) for row in self.matrix )
120
+ tmp = sum( sum(abs(x)**p for x in row) for row in self.matrix )
121
+ return tmp**(1/p)
122
+
103
123
 
104
124
  def dot(self, other) -> int:
125
+ "Dot product of two vectors."
105
126
  if self.rows == 1 and other.rows == 1 and self.cols == other.cols:
106
127
  return sum(x * y for x, y in zip(self.matrix[0], other.matrix[0]))
107
128
  if self.cols == 1 and other.cols == 1 and self.rows == other.rows:
@@ -109,17 +130,21 @@ class Matrix:
109
130
  return NotImplemented
110
131
 
111
132
  def transpose(self) -> "Matrix":
133
+ "Transpose of a matrix."
112
134
  return Matrix([list(i) for i in zip(*self.matrix)])
113
135
 
114
136
  def multiply(self, other) -> "Matrix":
115
- if not isinstance(other, Matrix) or self.cols != other.rows:
137
+ "Matrix multiplication."
138
+ if not isinstance(other, Matrix):
116
139
  return NotImplemented
117
- result = [[0 for j in range(other.cols)] for i in range(self.rows)]
140
+ if self.cols != other.rows:
141
+ raise NotImplementedError("Matrix dimensions do not match!")
142
+ result = self.zeros(self.rows, other.cols)
118
143
  for i in range(self.rows):
119
144
  for j in range(other.cols):
120
145
  for k in range(other.rows):
121
- result[i][j] += self.matrix[i][k] * other.matrix[k][j]
122
- return Matrix(result)
146
+ result.matrix[i][j] += self.matrix[i][k] * other.matrix[k][j]
147
+ return result
123
148
 
124
149
  def __add__(self, other) -> "Matrix":
125
150
  if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
@@ -150,7 +175,7 @@ class Matrix:
150
175
  R = self[:, :]
151
176
  i = 0
152
177
  for j in range(n):
153
- if not R[i, j]: # search for am nonzero entry in the present column
178
+ if not R[i, j]: # search for a nonzero entry in the present column
154
179
  for ii in range(i+1,m):
155
180
  if R[ii, j]:
156
181
  R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows
@@ -173,12 +198,12 @@ class Matrix:
173
198
  "Compute the determinant of a matrix M."
174
199
  if self.rows != self.rows:
175
200
  raise ValueError("Matrix must be square!")
176
- n = self.cols
201
+ n, m = self.cols, self.rows
177
202
  R = self[:, :]
178
203
  D = 1
179
204
  i = 0
180
205
  for j in range(n):
181
- if not R[i, j]: # search for am nonzero entry in the present column
206
+ if not R[i, j]: # search for a nonzero entry in the present column
182
207
  for ii in range(i+1,m):
183
208
  if R[ii, j]:
184
209
  D *= -1
@@ -211,13 +236,42 @@ class Matrix:
211
236
  raise ValueError("Matrix is not invertible!")
212
237
  return MM[:,n:]
213
238
 
239
+ def zeros(self, m: int = None, n: int = None):
240
+ "Returns a zero matrix of the same dimension"
241
+ if not m and not n:
242
+ n, m = self.cols, self.rows
243
+ elif not n:
244
+ n = m
245
+ zero = 0 * self[0]
246
+ return Matrix([[ zero for j in range(n)] for i in range(m) ])
247
+
248
+ def eye(self, m: int = None, n: int = None):
249
+ "Returns an identity matrix of the same dimension"
250
+ def delta(i, j):
251
+ if i == j:
252
+ return 1
253
+ return 0
254
+ if not m and not n:
255
+ n, m = self.cols, self.rows
256
+ elif not n:
257
+ n = m
258
+ zero = 0 * self[0]
259
+ one = 1 + zero
260
+ return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
261
+
214
262
 
215
- def zeros(m:int, n: int) -> "Matrix":
263
+ def zeros(m: int, n: int = None) -> "Matrix":
264
+ "Returns a zero matrix of the given dimension"
265
+ if not n:
266
+ n = m
216
267
  return Matrix([[ 0 for j in range(n)] for i in range(m) ])
217
268
 
218
- def eye(m:int, n: int) -> "Matrix":
269
+ def eye(m:int, n: int = None) -> "Matrix":
270
+ "Returns an identity matrix of the given dimension"
219
271
  def delta(i, j):
220
272
  if i == j:
221
273
  return 1
222
274
  return 0
275
+ if not n:
276
+ n = m
223
277
  return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])
@@ -4,13 +4,8 @@ Lattice tools
4
4
 
5
5
  from math import prod
6
6
  from fractions import Fraction
7
- from .la import Matrix, zeros, eye
8
-
9
- def babai_round_cvp(x: Matrix, U: Matrix) -> Matrix:
10
- "Babai's rounding algorithm for solving the CVP."
11
- s = U.inv() * x
12
- k = s.map(round)
13
- return U * k
7
+ from random import choice, sample
8
+ from .la import Matrix, zeros
14
9
 
15
10
  def hermite_nf(M: Matrix) -> Matrix:
16
11
  "Compute the Hermite normal form of a matrix M."
@@ -57,7 +52,7 @@ def norm2(v: Matrix) -> float:
57
52
 
58
53
  def gram_schmidt(U: Matrix) -> (Matrix, Matrix):
59
54
  "Compute the Gram-Schmidt orthogonalization of the column vectors of a matrix M."
60
- M = eye(U.cols, U.rows)
55
+ M = U.eye()
61
56
  Us = U[:, :]
62
57
  for j in range(1, U.rows):
63
58
  tmp = U[:, j]
@@ -68,13 +63,21 @@ def gram_schmidt(U: Matrix) -> (Matrix, Matrix):
68
63
  return Us, M
69
64
 
70
65
  def gram_det(U: Matrix) -> float:
66
+ "Compute the Gram determinant of a matrix."
71
67
  Us = gram_schmidt(U)[0]
72
68
  return prod([Us[:, i].norm() for i in range(U.rows)])
73
69
 
74
70
  def hadamard_ratio(M: Matrix) -> float:
71
+ "Compute the Hadamard ratio of a matrix."
75
72
  m = M.rows
76
73
  return (gram_det(M) / prod([M[:, i].norm() for i in range(m)])) ** (1 / m)
77
74
 
75
+ def babai_round_cvp(x: Matrix, U: Matrix) -> Matrix:
76
+ "Babai's rounding algorithm for solving the CVP."
77
+ s = U.inv() * x
78
+ k = s.applyfunc(round)
79
+ return U * k
80
+
78
81
  def babai_plane_cvp(x: Matrix, U: Matrix) -> Matrix:
79
82
  "Babai's closest plane algorithm for solving the CVP."
80
83
  Us = gram_schmidt(U)[0]
@@ -83,6 +86,18 @@ def babai_plane_cvp(x: Matrix, U: Matrix) -> Matrix:
83
86
  y = y - round(y.dot(Us[:, k]) / norm2(Us[:, k])) * U[:, k]
84
87
  return (x - y).applyfunc(round)
85
88
 
89
+ def lagrange_lr(V: Matrix) -> Matrix:
90
+ "Lagrange lattice reduction."
91
+ assert (V.rows, V.cols) == (2, 2)
92
+ v1, v2 = V[:, 0], V[:, 1]
93
+ if norm2(v1) > norm2(v2):
94
+ v1, v2 = v2, v1
95
+ v3 = v2 - round(v1.dot(v2) / norm2(v1)) * v1
96
+ while norm2(v3) < norm2(v1):
97
+ v2, v1 = v1, v3
98
+ v3 = v2 - round(v1.dot(v2) / norm2(v1)) * v1
99
+ return Matrix([list(v1), list(v3)]).transpose()
100
+
86
101
  def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
87
102
  "lll algorithm for lattice reduction"
88
103
 
@@ -91,7 +106,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
91
106
  U = V[:, :]
92
107
  Us = U[:, :]
93
108
  Us.map(Fraction)
94
- M = zeros(U.cols, U.rows)
109
+ M = U.zeros()
95
110
  M.map(Fraction)
96
111
  M[0, 0] = norm2(Us[:, 0]) # we store the squared norms on the diagonal
97
112
  for l in range(1, U.rows): # Gram-Schmidt decomposition
@@ -147,11 +162,9 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
147
162
  U[:, j] = tmp[j]
148
163
  return U
149
164
 
150
- from random import choice, sample
151
-
152
- def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) -> Matrix:
165
+ def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = None) -> Matrix:
153
166
  "Create a random unimodular matrix of dimension n."
154
- W = Matrix.zeros(n, n)
167
+ W = zeros(n)
155
168
  for i in range(n):
156
169
  for j in range(i, n):
157
170
  W[i, j] = choice([-1, 1])
@@ -159,10 +172,10 @@ def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) ->
159
172
  for _ in range(iterations):
160
173
  i, j = sample(range(n), 2)
161
174
  tmp = W[i, :] + choice([-1, 1]) * W[j, :]
162
- if max([abs(x) for x in tmp]) <= max_val:
175
+ if not max_val or max([abs(x) for x in tmp]) <= max_val:
163
176
  W[i, :] = tmp
164
177
  i, j = sample(range(n), 2)
165
178
  tmp = W[:, i] + choice([-1, 1]) * W[:, j]
166
- if max([abs(x) for x in tmp]) <= max_val:
179
+ if not max_val or max([abs(x) for x in tmp]) <= max_val:
167
180
  W[:, i] = tmp
168
181
  return W
@@ -10,7 +10,6 @@ Number theory tools:
10
10
  """
11
11
  from math import gcd, prod
12
12
  from fractions import Fraction
13
- from .factor import factorint
14
13
 
15
14
  # Euclid and friends
16
15
 
@@ -115,8 +114,7 @@ def crt(a: list, m: list) -> int:
115
114
  def fraction_repr(self):
116
115
  if self.denominator == 1:
117
116
  return str(self.numerator)
118
- else:
119
- return str(self.numerator) + "/" + str(self.denominator)
117
+ return str(self.numerator) + "/" + str(self.denominator)
120
118
 
121
119
  Fraction.__repr__ = fraction_repr
122
120
 
@@ -209,7 +207,7 @@ def sqrt_mod(a: int, p: int) -> list:
209
207
 
210
208
  # Euler phi and Carmichael function
211
209
 
212
- from math import prod
210
+ from .factor import factorint
213
211
 
214
212
 
215
213
  def euler_phi(n: int) -> int:
@@ -250,22 +248,22 @@ def order(a: int, n: int, factor=False) -> int:
250
248
  factors[p] += k - 1
251
249
  else:
252
250
  factors[p] = k - 1
253
- order = 1 # compute the group order euler_phi(n) as our current guess
251
+ order_a = 1 # compute the group order euler_phi(n) as our current guess
254
252
  for p, k in factors.items():
255
- order *= p**k
253
+ order_a *= p**k
256
254
  if factor: # we compute the factorization of the order along the way
257
255
  factors_order = {} # factorization of the order
258
256
  for p, k in factors.items():
259
257
  i = 0
260
258
  for _ in range(k):
261
- order_try = order // p
259
+ order_try = order_a // p
262
260
  if pow(a, order_try, n) == 1:
263
- order = order_try
261
+ order_a = order_try
264
262
  i += 1
265
263
  else:
266
264
  break
267
265
  if factor and i < k:
268
266
  factors_order[p] = k - i
269
267
  if factor:
270
- return order, factors_order
271
- return order
268
+ return order_a, factors_order
269
+ return order_a
@@ -83,7 +83,12 @@ class Poly:
83
83
 
84
84
  def __add__(self, other: "Poly") -> "Poly":
85
85
  if not isinstance(other, self.__class__):
86
- raise NotImplementedError(f"Cannot add {self} and {other}.")
86
+ try:
87
+ tmp = self.coeff[:]
88
+ tmp[0] += other
89
+ return self.__class__(tmp, modulus=self.modulus)
90
+ except:
91
+ return NotImplemented
87
92
  ls, lo = len(self.coeff), len(other.coeff)
88
93
  if ls < lo:
89
94
  scoeff = self.coeff + (lo - ls) * [0]
@@ -98,12 +103,27 @@ class Poly:
98
103
  modulus = other.modulus
99
104
  return self.__class__([s + o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
100
105
 
106
+ def __radd__(self, other: "Poly") -> "Poly":
107
+ if not isinstance(other, self.__class__):
108
+ try:
109
+ tmp = self.coeff[:]
110
+ tmp[0] += other
111
+ return self.__class__(tmp, modulus=self.modulus)
112
+ except:
113
+ pass
114
+ return NotImplemented
115
+
101
116
  def __neg__(self) -> "Poly":
102
117
  return Poly([-s for s in self.coeff], modulus=self.modulus)
103
118
 
104
119
  def __sub__(self, other: "Poly") -> "Poly":
105
120
  if not isinstance(other, self.__class__):
106
- raise NotImplementedError(f"Cannot subtract {self} and {other}.")
121
+ try:
122
+ tmp = self.coeff[:]
123
+ tmp[0] -= other
124
+ return self.__class__(tmp, modulus=self.modulus)
125
+ except:
126
+ return NotImplemented
107
127
  ls, lo = len(self.coeff), len(other.coeff)
108
128
  if ls < lo:
109
129
  scoeff = self.coeff + (lo - ls) * [0]
@@ -118,11 +138,22 @@ class Poly:
118
138
  modulus = other.modulus
119
139
  return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus=modulus)
120
140
 
141
+ def __rsub__(self, other: "Poly") -> "Poly":
142
+ if not isinstance(other, self.__class__):
143
+ try:
144
+ tmp = self.coeff[:]
145
+ tmp[0] -= other
146
+ return self.__class__(tmp, modulus=self.modulus)
147
+ except:
148
+ pass
149
+ return NotImplemented
150
+
121
151
  def __mul__(self, other: "Poly") -> "Poly":
122
- if isinstance(other, int):
123
- return Poly([other * s for s in self.coeff])
124
152
  if not isinstance(other, self.__class__):
125
- raise NotImplementedError(f"Cannot multiply {self} and {other}.")
153
+ try:
154
+ return Poly([other * s for s in self.coeff])
155
+ except:
156
+ return NotImplemented
126
157
  ls, lo = len(self.coeff), len(other.coeff)
127
158
  coeff = [0] * (ls + lo - 1)
128
159
  for k in range(ls + lo - 1):
@@ -153,6 +184,7 @@ class Poly:
153
184
  return res
154
185
 
155
186
  def divmod(self, other: "Poly") -> ("Poly", "Poly"):
187
+ "Polynom division with remainder"
156
188
  if isinstance(other, list):
157
189
  other = self.__class__(other)
158
190
  elif not isinstance(other, self.__class__):
@@ -183,6 +215,7 @@ class Poly:
183
215
  )
184
216
 
185
217
  def mod(self, other: "Poly") -> None:
218
+ "Remainder of polynom division"
186
219
  if isinstance(other, list):
187
220
  other = self.__class__(other)
188
221
  elif not isinstance(other, self.__class__):
@@ -4,7 +4,8 @@ Tools for prime numbers:
4
4
  isprime(n) test if n is probably prime
5
5
  miller_rabin_test(n, b) Miller-Rabin primality test with base b
6
6
  """
7
- from math import isqrt
7
+ from math import isqrt, gcd
8
+ from .nt import jacobi_symbol
8
9
 
9
10
  # Erathostenes
10
11
 
@@ -86,8 +87,6 @@ def _lucas_sequence(n, D, k):
86
87
 
87
88
  def _is_strong_lucas_prp(n: int) -> bool:
88
89
  """Strong Lucas primality test."""
89
- from math import gcd
90
- from .nt import jacobi_symbol
91
90
 
92
91
  # remove powers of 2 from n+1 (= k * 2**s)
93
92
  k = (n + 1) // 2
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kryptools
3
- Version: 0.1
3
+ Version: 0.3
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/tree/main/doc
8
9
  Classifier: Programming Language :: Python :: 3
9
10
  Classifier: License :: OSI Approved :: MIT License
10
11
  Classifier: Operating System :: OS Independent
@@ -33,3 +34,6 @@ The tools contained are:
33
34
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
34
35
  * Poly: a class for polynomials (division, modulo)
35
36
  * Zmod: a class for the ring of integers modulo an integer
37
+
38
+ Documentation can be found in the jupyter notebook
39
+ (currently incomplete: use the force, read the source).
@@ -10,7 +10,9 @@ kryptools/dlp_qs.py
10
10
  kryptools/dlp_rho.py
11
11
  kryptools/ec.py
12
12
  kryptools/factor.py
13
+ kryptools/factor_dix.py
13
14
  kryptools/factor_ecm.py
15
+ kryptools/factor_fmt.py
14
16
  kryptools/factor_pm1.py
15
17
  kryptools/factor_qs.py
16
18
  kryptools/la.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "0.1"
3
+ version = "0.3"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -15,4 +15,5 @@ classifiers = [
15
15
 
16
16
  [project.urls]
17
17
  Homepage = "https://github.com/teschlg/kryptools"
18
- Issues = "https://github.com/teschlg/kryptools/issues"
18
+ Issues = "https://github.com/teschlg/kryptools/issues"
19
+ Docs = "https://github.com/teschlg/kryptools/tree/main/doc"
File without changes
File without changes
File without changes
File without changes
@@ -5,6 +5,9 @@ Factorization of integers:
5
5
 
6
6
  from math import isqrt, gcd
7
7
  from .primes import sieve_eratosthenes, isprime
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
@@ -27,9 +30,6 @@ def _factor_fermat(n: int, steps: int = 10) -> list:
27
30
  return a - b
28
31
  a += step
29
32
 
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
33
 
34
34
  def factorint(n: int, verbose: int = 0) -> list:
35
35
  "Factor a number."
File without changes
File without changes
File without changes