kryptools 1.8.1__tar.gz → 1.8.2__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 (58) hide show
  1. {kryptools-1.8.1 → kryptools-1.8.2}/PKG-INFO +3 -3
  2. {kryptools-1.8.1 → kryptools-1.8.2}/README.md +2 -2
  3. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/Zmod.py +1 -1
  4. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/__init__.py +1 -1
  5. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/la.py +255 -73
  6. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/lat.py +56 -59
  7. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/nt.py +18 -1
  8. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/PKG-INFO +3 -3
  9. {kryptools-1.8.1 → kryptools-1.8.2}/pyproject.toml +1 -1
  10. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_la.py +73 -1
  11. kryptools-1.8.2/tests/test_lat.py +49 -0
  12. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_nt.py +9 -0
  13. kryptools-1.8.1/tests/test_lat.py +0 -17
  14. {kryptools-1.8.1 → kryptools-1.8.2}/LICENSE +0 -0
  15. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/GF2.py +0 -0
  16. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/aes.py +0 -0
  17. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/blockcipher.py +0 -0
  18. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/code.py +0 -0
  19. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/conway_polynomials.py +0 -0
  20. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/des.py +0 -0
  21. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp.py +0 -0
  22. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_bsgs.py +0 -0
  23. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_ic.py +0 -0
  24. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_qs.py +0 -0
  25. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_rho.py +0 -0
  26. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/ec.py +0 -0
  27. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor.py +0 -0
  28. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_dix.py +0 -0
  29. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_ecm.py +0 -0
  30. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_fmt.py +0 -0
  31. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_pm1.py +0 -0
  32. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_qs.py +0 -0
  33. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_rho.py +0 -0
  34. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/intfuncs.py +0 -0
  35. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/keccak.py +0 -0
  36. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/poly.py +0 -0
  37. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/primes.py +0 -0
  38. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/sha1.py +0 -0
  39. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/SOURCES.txt +0 -0
  40. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/dependency_links.txt +0 -0
  41. {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/top_level.txt +0 -0
  42. {kryptools-1.8.1 → kryptools-1.8.2}/setup.cfg +0 -0
  43. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_GF2.py +0 -0
  44. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_Zmod.py +0 -0
  45. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_aes.py +0 -0
  46. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_code.py +0 -0
  47. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_des.py +0 -0
  48. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_dlog.py +0 -0
  49. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_ec.py +0 -0
  50. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor.py +0 -0
  51. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor_fmt.py +0 -0
  52. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor_pm1.py +0 -0
  53. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor_qs.py +0 -0
  54. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_intfuncs.py +0 -0
  55. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_keccak.py +0 -0
  56. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_poly.py +0 -0
  57. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_primes.py +0 -0
  58. {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  License: MIT
@@ -43,11 +43,11 @@ The tools contained are:
43
43
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
44
44
  * integer factorization (Fermat, Pollard p-1, Pollard rho, Lenstra's ECM, Dixon, basic quadratic sieve)
45
45
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
46
- * linear algebra: Hermite normal form, Gram-Schmidt
46
+ * linear algebra: Hermite normal form, Smith normal form, Gram-Schmidt
47
47
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz), SIS, ISIS, LWE
48
48
  * linear codes: Hamming distance, left standard form, parity check matrix
49
49
 
50
- * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
50
+ * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, solving linear systems, etc.)
51
51
  * BinaryMatrix: a class for Matrices with binary entries (for much faster row operations)
52
52
  * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
53
53
  * Zmod: a class for the ring of integers modulo an integer
@@ -25,11 +25,11 @@ The tools contained are:
25
25
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
26
26
  * integer factorization (Fermat, Pollard p-1, Pollard rho, Lenstra's ECM, Dixon, basic quadratic sieve)
27
27
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
28
- * linear algebra: Hermite normal form, Gram-Schmidt
28
+ * linear algebra: Hermite normal form, Smith normal form, Gram-Schmidt
29
29
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz), SIS, ISIS, LWE
30
30
  * linear codes: Hamming distance, left standard form, parity check matrix
31
31
 
32
- * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
32
+ * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, solving linear systems, etc.)
33
33
  * BinaryMatrix: a class for Matrices with binary entries (for much faster row operations)
34
34
  * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
35
35
  * Zmod: a class for the ring of integers modulo an integer
@@ -172,7 +172,7 @@ class ZmodPoint:
172
172
  return f"{x} (mod {self.ring.n})"
173
173
 
174
174
  def __eq__(self, other):
175
- if not isinstance(other, self.__class__) or self.ring != other.ring:
175
+ if not isinstance(other, self.__class__) or self.ring.n != other.ring.n:
176
176
  return False
177
177
  return self.x == other.x
178
178
 
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
5
5
  __author__ = "Gerald Teschl"
6
6
  __copyright__ = "Copyright 2024-2025, Gerald Teschl"
7
7
  __license__ = "MIT License"
8
- __version__ = "1.8.1"
8
+ __version__ = "1.8.2"
9
9
  __email__ = "Gerald.Teschl@univie.ac.at"
10
10
 
11
11
  from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, is_carmichael_number, order, crt
@@ -6,6 +6,7 @@ Linear algebra
6
6
  from math import gcd, inf, sqrt
7
7
  from numbers import Number
8
8
  from fractions import Fraction
9
+ from .nt import egcd
9
10
  from .Zmod import Zmod
10
11
 
11
12
  class Matrix:
@@ -215,19 +216,66 @@ class Matrix:
215
216
  self.matrix[i].append(c)
216
217
  self.cols += 1
217
218
 
218
- def permute_columns(self, permutation) -> None:
219
+ def swap_columns(self, i: int, j: int) -> None:
220
+ "Swap two columns."
221
+ for k in range(self.rows):
222
+ self.matrix[k][i], self.matrix[k][j] = self.matrix[k][j], self.matrix[k][i]
223
+
224
+ def swap_rows(self, i: int, j: int) -> None:
225
+ "Swap two rows."
226
+ self.matrix[i], self.matrix[j] = self.matrix[j], self.matrix[i]
227
+
228
+ def permute_columns(self, permutation: list) -> None:
219
229
  "Permute columns according to a list of new positions."
220
230
  if len(permutation) != self.cols:
221
231
  raise ValueError(f"The argument must be a list of indices of length {self.cols}.")
222
232
  for i in range(self.rows):
223
233
  self.matrix[i] = [ self.matrix[i][j] for j in permutation]
224
234
 
225
- def permute_rows(self, permutation) -> None:
235
+ def permute_rows(self, permutation: list) -> None:
226
236
  "Permute rows according to a list of new positions."
227
237
  if len(permutation) != self.rows:
228
238
  raise ValueError(f"The argument must be a list of indices of length {self.rows}.")
229
239
  self.matrix = [ self.matrix[i] for i in permutation]
230
240
 
241
+ def scale_column(self, i: int, a: Number) -> None:
242
+ "Scale a column."
243
+ for k in range(self.rows):
244
+ self.matrix[k][i] *= a
245
+
246
+ def scale_row(self, i: int, a: Number) -> None:
247
+ "Scale a row."
248
+ for k in range(self.cols):
249
+ self.matrix[i][k] *= a
250
+
251
+ def addto_column(self, i: int, j: int, a: Number|None = None) -> None:
252
+ "Add a multiple of the first column to the second."
253
+ if a is None:
254
+ for k in range(self.rows):
255
+ self.matrix[k][i] += self.matrix[k][j]
256
+ else:
257
+ for k in range(self.rows):
258
+ self.matrix[k][i] += a * self.matrix[k][j]
259
+
260
+ def addto_row(self, i: int, j: int, a: Number|None = None) -> None:
261
+ "Add a multiple of the first row to the second."
262
+ if a is None:
263
+ for k in range(self.cols):
264
+ self.matrix[i][k] += self.matrix[j][k]
265
+ else:
266
+ for k in range(self.cols):
267
+ self.matrix[i][k] += a * self.matrix[j][k]
268
+
269
+ def maop_row(self, i: int, k: int, a: Number, b: Number, c: Number, d: Number) -> None:
270
+ "Replaces the i,j and k,j entry by by using an matrix column operation."
271
+ for l in range(self.cols):
272
+ self.matrix[i][l], self.matrix[k][l] = a * self.matrix[i][l] + b * self.matrix[k][l], c * self.matrix[i][l] + d * self.matrix[k][l]
273
+
274
+ def maop_column(self, j: int, k: int, a: Number, b: Number, c: Number, d: Number) -> None:
275
+ "Replaces the i,j and i,k entry by using an matrix column operation."
276
+ for l in range(self.rows):
277
+ self.matrix[l][j], self.matrix[l][k] = a * self.matrix[l][j] + b * self.matrix[l][k], c * self.matrix[l][j] + d * self.matrix[l][k]
278
+
231
279
  def __eq__(self, other):
232
280
  if not isinstance(other, self.__class__):
233
281
  return False
@@ -406,53 +454,46 @@ class Matrix:
406
454
  M.map(ring)
407
455
  return M
408
456
 
409
- def hrnf(self, start = 0, drop_zero_rows: bool = False) -> "Matrix":
457
+
458
+ def hrnf(self, start = 0, drop_zero_rows: bool = True) -> "Matrix":
410
459
  "Compute the Hermite row normal form."
411
460
  n, m = self.cols, self.rows
412
461
  if not isinstance(self.matrix[0][0], int):
413
462
  raise ValueError("Hermite normal form requires integer entries!")
414
- H = [ self.matrix[i][:] for i in range(m) ]
415
- pivotcols = []
416
- nonpivotcols = []
463
+ H = self[:,:]
464
+ H.pivotcols = []
465
+ H.nonpivotcols = []
417
466
  i = 0
418
467
  if start >= n:
419
468
  raise ValueError("Start value cannot be beyond the last column.")
420
469
  for j in range(start, n):
421
470
  i0 = i
422
- minimum = abs(H[i][j]) # search for the pivot in the present column
471
+ minimum = abs(H.matrix[i][j]) # search for the pivot in the present column
423
472
  for ii in range(i + 1, m):
424
- tmp = abs(H[ii][j])
473
+ tmp = abs(H.matrix[ii][j])
425
474
  if tmp > 0 and (tmp < minimum or minimum == 0):
426
475
  minimum = tmp
427
476
  i0 = ii
428
477
  if minimum == 0:
429
- nonpivotcols.append(j)
478
+ H.nonpivotcols.append(j)
430
479
  continue # all entrjes are zero
431
- pivotcols.append(j)
480
+ H.pivotcols.append(j)
432
481
  if i0 > i:
433
- H[i], H[i0] = H[i0], H[i] # swap rows, to move the pivot jn place
434
- if H[i][j] < 0:
435
- for k in range(n):
436
- H[i][k] *= -1 # make the pivot positive
437
- ii = i + 1
438
- while ii < m: # make the column entries below to the pivot zero
439
- tmp = H[ii][j] // H[i][j]
440
- for k in range(n):
441
- H[ii][k] -= tmp * H[i][k]
442
- if H[ii][j]:
443
- H[i], H[ii] = H[ii], H[i] # swap rows
444
- else:
445
- ii += 1
482
+ H.swap_rows(i, i0) # swap rows, to move the pivot in place
483
+ if H.matrix[i][j] < 0:
484
+ H.scale_row(i, -1) # make the pivot positive
485
+ for ii in range(i + 1, m): # make the column entries below to the pivot zero
486
+ if H.matrix[ii][j]:
487
+ g, x, y = egcd(H.matrix[i][j], H.matrix[ii][j], minimal = True)
488
+ xx = H.matrix[i][j] // g
489
+ yy = - H.matrix[ii][j] // g
490
+ H.maop_row(i, ii, x, y, yy, xx)
446
491
  for ii in range(i): # reduce the column entries above to the pivot
447
- tmp = H[ii][j] // H[i][j]
448
- for k in range(n):
449
- H[ii][k] -= tmp * H[i][k]
492
+ H.addto_row(ii, i, -(H.matrix[ii][j] // H.matrix[i][j]))
450
493
  i += 1
451
494
  if i >= m:
452
495
  break
453
- H = Matrix(H)
454
- H.pivotcols = pivotcols
455
- H.nonpivotcols = nonpivotcols + list(range(j+1, H.cols))
496
+ H.nonpivotcols += list(range(j+1, H.cols))
456
497
  # purge zero rows
457
498
  if drop_zero_rows:
458
499
  l = len(H.pivotcols)
@@ -462,6 +503,141 @@ class Matrix:
462
503
  H.rows = l
463
504
  return H
464
505
 
506
+ def hnf(self, drop_zero_columns: bool = True) -> "Matrix":
507
+ "Compute the Hermite normal form."
508
+ n, m = self.cols, self.rows
509
+ if not isinstance(self.matrix[0][0], int):
510
+ raise ValueError("Hermite normal form requires integer entries!")
511
+ H = self[:,:]
512
+ j = n - 1
513
+ for i in range(m-1,-1,-1):
514
+ j0 = j
515
+ minimum = abs(H.matrix[i][j]) # search for the pivot in the present row
516
+ for jj in range(j):
517
+ tmp = abs(H.matrix[i][jj])
518
+ if tmp > 0 and (tmp < minimum or minimum == 0):
519
+ minimum = tmp
520
+ j0 = jj
521
+ if minimum == 0:
522
+ continue # all entries are zero
523
+ if j0 < j:
524
+ H.swap_columns(j, j0) # swap columns, to move the pivot in place
525
+ if H.matrix[i][j] < 0:
526
+ H.scale_column(j, -1) # make the pivot positive
527
+ for jj in range(j): # make the row left to the pivot zero
528
+ if H.matrix[i][jj]:
529
+ g, x, y = egcd(H.matrix[i][j], H.matrix[i][jj], minimal = True)
530
+ xx = H.matrix[i][j] // g
531
+ yy = - H.matrix[i][jj] // g
532
+ H.maop_column(j, jj, x, y, yy, xx)
533
+ for jj in range(j + 1, n): # reduce the row entries right to the pivot
534
+ H.addto_column(jj, j, -(H.matrix[i][jj] // H.matrix[i][j]))
535
+ j -= 1
536
+ if j < 0:
537
+ break
538
+ if drop_zero_columns:
539
+ while H.cols > 1 and all(not H.matrix[i][0] for i in range(m)): # remove zero columns
540
+ H = H[:, 1:]
541
+ return H
542
+
543
+ def snf(self, drop_zero_rows: bool = False, drop_zero_columns: bool = False, include_S: bool = True, include_T: bool = True) -> "Matrix":
544
+ "Computes the Smith normal form D of a matrix A with integer coefficients together with invertible matrices S and T such that D = S * A * T."
545
+ if not isinstance(self.matrix[0][0], int):
546
+ raise ValueError("Smith normal form requires integer entries!")
547
+ m, n = self.rows, self.cols
548
+ A = self[:,:]
549
+ if include_T:
550
+ T = A.eye(n)
551
+ if include_S:
552
+ S = A.eye(m)
553
+ for i in range(min(n,m)):
554
+ minimum = abs(A.matrix[i][i]) # search for the pivot
555
+ i0, j0 = i, i
556
+ for ii in range(i, m):
557
+ for jj in range(i, n):
558
+ tmp = abs(A.matrix[ii][jj])
559
+ if tmp > 0 and (tmp < minimum or minimum == 0):
560
+ minimum = tmp
561
+ i0, j0 = ii, jj
562
+ if not minimum:
563
+ break
564
+ if j0 != i: # swap columns, to move the pivot in place
565
+ if include_T:
566
+ T.swap_columns(i, j0)
567
+ A.swap_columns(i, j0)
568
+ if i0 != i: # swap rows, to move the pivot in place
569
+ if include_S:
570
+ S.swap_rows(i, i0)
571
+ A.swap_rows(i, i0)
572
+ if A[i, i] < 0: # make the pivot positive
573
+ if include_S:
574
+ S.scale_row(i, -1)
575
+ A.scale_row(i, -1)
576
+ done = False
577
+ while not done:
578
+ for jj in range(i+1, n):
579
+ if A.matrix[i][jj] == 0:
580
+ continue
581
+ g, x, y = egcd(A.matrix[i][i], A.matrix[i][jj], minimal = True)
582
+ xx = A.matrix[i][i] // g
583
+ yy = A.matrix[i][jj] // g
584
+ if include_T:
585
+ T.maop_column(i, jj, x, y, -yy, xx)
586
+ A.maop_column(i, jj, x, y, -yy, xx)
587
+ for ii in range(i+1, m):
588
+ if A.matrix[ii][i] == 0:
589
+ continue
590
+ g, x, y = egcd(A.matrix[i][i], A.matrix[ii][i], minimal = True)
591
+ xx = A.matrix[i][i] // g
592
+ yy = -A.matrix[ii][i] // g
593
+ if include_S:
594
+ S.maop_row(i, ii, x, y, yy, xx)
595
+ A.maop_row(i, ii, x, y, yy, xx)
596
+ done = True
597
+ for jj in range(i+1, n):
598
+ if A[i,jj] != 0:
599
+ done = False
600
+ break
601
+ for l in range(min(n,m)-1, 0, -1):
602
+ for i in range(l):
603
+ if A.matrix[i][i] and A.matrix[i+1][i+1] % A.matrix[i][i]:
604
+ g, x, y = egcd(A.matrix[i][i], A.matrix[i+1][i+1], minimal = True)
605
+ aa= A.matrix[i][i] //g
606
+ bb = -A.matrix[i+1][i+1]//g
607
+ by = bb * y
608
+ if include_T:
609
+ T.addto_column(i, i+1)
610
+ T.addto_column(i+1, i, by)
611
+ A.addto_column(i, i+1)
612
+ A.addto_column(i+1, i, by)
613
+ if include_S:
614
+ S.maop_row(i, i+1, x, y, bb, aa)
615
+ A.maop_row(i, i+1, x, y, bb, aa)
616
+ if drop_zero_rows or drop_zero_columns:
617
+ for i in range(min(n,m)):
618
+ if A[i,i] == 0:
619
+ break
620
+ else:
621
+ i += 1
622
+ i = max(i, 1)
623
+ if drop_zero_rows:
624
+ if i < m:
625
+ del A[i:, :]
626
+ if include_S:
627
+ del S[i:, :]
628
+ if drop_zero_columns:
629
+ if i < n:
630
+ del A[:, i:]
631
+ if include_T:
632
+ del T[:, i:]
633
+ if include_S and include_T:
634
+ return A, S, T
635
+ if include_S:
636
+ return A, S
637
+ if include_T:
638
+ return A, T
639
+ return A
640
+
465
641
  def left_standard_form(self) -> "Matrix":
466
642
  "Compute the left standard form."
467
643
  # reduced row echelon form
@@ -474,7 +650,24 @@ class Matrix:
474
650
  def kernel(self) -> "Matrix":
475
651
  "Compute a basis for the kernel."
476
652
  if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
477
- raise NotImplementedError("The matrix must be over a field, not a ring.")
653
+ ring = self.matrix[0][0].ring
654
+ M = self.applyfunc(int)
655
+ D, T = M.snf(include_S=False, drop_zero_rows=True)
656
+ D.map(ring)
657
+ T.map(ring)
658
+ K = D.eye(D.cols)
659
+ shift = 0
660
+ for i in range(D.rows):
661
+ g = gcd(int(D.matrix[i][i]), ring.n)
662
+ if g == 1:
663
+ if shift == D.cols - 1:
664
+ K.matrix[i][i - shift] = ring(0)
665
+ break
666
+ K.delete_columns(i - shift)
667
+ shift += 1
668
+ elif g < ring.n:
669
+ K.matrix[i][i-shift] = ring(ring.n // g)
670
+ return T * K
478
671
  _, one = self._guess_zero()
479
672
  M = self.rref(drop_zero_rows = True)
480
673
  K = M.zeros(M.cols, max(1,len(M.nonpivotcols)))
@@ -562,52 +755,30 @@ class Matrix:
562
755
  b = self.__class__(b)
563
756
  if self.rows != b.rows or b.cols != 1:
564
757
  raise ValueError("Matrix dimensions do not match.")
565
- A = self[:,:]
566
- A.append_column(b)
567
- A = A.rref(drop_zero_rows = True)
568
- if not any(A.matrix[-1][:-1]) and A.matrix[-1][-1]:
569
- return None # Not solvable
570
758
  if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
571
759
  # the matrix is over a ring (not a field)
572
760
  ring = self.matrix[0][0].ring
573
- # compute column normal form (TODO implement full Smith normal form)
574
- b = A[:,-1]
575
-
576
- A = A[:,:-1].transpose()
577
- A.map(int)
578
- A.append_column(A.eye(A.rows))
579
- A = A.hrnf().transpose()
580
- R = A[b.rows:,:b.rows]
581
- A = A[:b.rows,:b.rows]
582
- A.map(ring)
583
-
584
- solution_nr = [ 0 ] * A.rows
585
- solutions_left = True
586
- next_solution = False # start with the first solution
587
- while solutions_left:
588
- solution = solution = [ None ] * A.rows
589
- solutions_left = False
590
- for i in range(A.rows):
591
- bi = b[i]
592
- for k in range(i):
593
- bi -= A.matrix[i][k] * solution[k]
594
- c = A.matrix[i][i].solve(bi, all_solutions = True)
595
- if c is None:
596
- solution = None
597
- break
598
- if next_solution and solution_nr[i] < len(c) - 1:
599
- solution_nr[i] += 1
600
- next_solution = False
601
- if solution_nr[i] < len(c) -1:
602
- solutions_left = True
603
- solution[i] = c[solution_nr[i]]
604
- if next_solution: # no more solutions left
761
+ A = self.applyfunc(int)
762
+ D, S, T = A.snf()
763
+ D.map(ring)
764
+ S.map(ring)
765
+ T.map(ring)
766
+ b = S * b
767
+ for i in range(min(A.rows, A.cols), A.rows):
768
+ if b[i]:
769
+ return None
770
+ y = [ ring(0) ] * A.cols
771
+ for i in range(min(A.rows, A.cols)):
772
+ y[i] = D[i,i].solve(b[i])
773
+ if y[i] is None:
605
774
  return None
606
- if solution is not None:
607
- return R * Matrix(solution)
608
- next_solution = True
609
- return None
775
+ return T * Matrix(y)
610
776
  # the matrix is over a field
777
+ A = self[:,:]
778
+ A.append_column(b)
779
+ A = A.rref(drop_zero_rows = True)
780
+ if not any(A.matrix[-1][:-1]) and A.matrix[-1][-1]:
781
+ return None # Not solvable
611
782
  solution = self.zeros(self.cols, 1)
612
783
  for i, j in enumerate(A.pivotcols):
613
784
  solution[j] = A.matrix[i][-1]
@@ -911,7 +1082,18 @@ class BinaryMatrix:
911
1082
  self.rows += 1
912
1083
  self.matrix.append(row)
913
1084
 
914
- def permute_columns(self, permutation) -> None:
1085
+ def swap_columns(self, i: int, j: int) -> None:
1086
+ "Swap two columns."
1087
+ for k in range(self.rows):
1088
+ b = self.matrix[k]
1089
+ x = ((b >> i) ^ (b >> j)) & 1
1090
+ self.matrix[k] ^= ((x << i) | (x << j))
1091
+
1092
+ def swap_rows(self, i: int, j: int) -> None:
1093
+ "Swap two rows."
1094
+ self.matrix[i], self.matrix[j] = self.matrix[j], self.matrix[i]
1095
+
1096
+ def permute_columns(self, permutation: list) -> None:
915
1097
  "Permute columns according to a list of new positions."
916
1098
  if len(permutation) != self.cols:
917
1099
  raise ValueError(f"The argument must be a list of indices of length {self.cols}.")
@@ -920,7 +1102,7 @@ class BinaryMatrix:
920
1102
  bits = [ bits[j] for j in permutation]
921
1103
  self.matrix[i] = self.from_bits(bits)
922
1104
 
923
- def permute_rows(self, permutation) -> None:
1105
+ def permute_rows(self, permutation: list) -> None:
924
1106
  "Permute rows according to a list of new positions."
925
1107
  if len(permutation) != self.rows:
926
1108
  raise ValueError(f"The argument must be a list of indices of length {self.rows}.")
@@ -11,40 +11,7 @@ from .la import Matrix, eye, zeros
11
11
 
12
12
  def hermite_nf(M: Matrix) -> Matrix:
13
13
  "Compute the Hermite normal form of a matrix M."
14
- n, m = M.cols, M.rows
15
- H = M[:, :]
16
- j = n - 1
17
- for i in range(m-1,-1,-1):
18
- j0 = j
19
- minimum = abs(H[i, j]) # search for the pivot in the present row
20
- for jj in range(j):
21
- tmp = abs(H[i, jj])
22
- if tmp > 0 and (tmp < minimum or minimum == 0):
23
- minimum = tmp
24
- j0 = jj
25
- if minimum == 0:
26
- continue # all entries are zero
27
- if j0 < j:
28
- H[:, j], H[:, j0] = H[:, j0], H[:, j] # swap columns, to move the pivot in place
29
- if H[i, j] < 0:
30
- H[:, j] *= -1 # make the pivot positive
31
- jj = j - 1
32
- while jj >= 0: # make the row entries left to the pivot zero
33
- tmp = H[i, jj] // H[i, j]
34
- H[:, jj] -= tmp * H[:, j]
35
- if H[i, jj]:
36
- H[:, j], H[:, jj] = H[:, jj], H[:, j] # swap columns
37
- else:
38
- jj -= 1
39
- for jj in range(j + 1, n): # reduce the row entries right to the pivot
40
- tmp = H[i, jj] // H[i, j]
41
- H[:, jj] -= tmp * H[:, j]
42
- j -= 1
43
- if j < 0:
44
- break
45
- while H.cols > 1 and all(not H[i, 0] for i in range(m)): # remove zero columns
46
- H = H[:, 1:]
47
- return H
14
+ return M.hnf()
48
15
 
49
16
 
50
17
  def norm2(v: Matrix) -> float:
@@ -56,9 +23,8 @@ def gram_schmidt(U: Matrix, drop_dependent: bool = True) -> (Matrix, Matrix):
56
23
  "Compute the Gram-Schmidt orthogonalization of the column vectors of a matrix M."
57
24
  M = U.eye(U.cols)
58
25
  Us = U.zeros()
59
- Us[:, 0] = U[:, 0]
60
26
  jj = 0 # offset taking removed vectors into account
61
- for j in range(1, U.cols):
27
+ for j in range(0, U.cols):
62
28
  tmp = U[:, j]
63
29
  for i in range(j - jj):
64
30
  M[i, j - jj] = U[:, j].dot(Us[:, i]) / Us[:, i].norm2()
@@ -69,6 +35,8 @@ def gram_schmidt(U: Matrix, drop_dependent: bool = True) -> (Matrix, Matrix):
69
35
  jj += 1
70
36
  else:
71
37
  Us[:, j - jj] = tmp
38
+ if jj == U.cols:
39
+ raise ValueError("The matrix must be nonzero.")
72
40
  if jj:
73
41
  Us = Us[:,:-jj]
74
42
  M = M[:-jj,:-jj]
@@ -114,6 +82,30 @@ def babai_plane_bnd(U: Matrix, p=2) -> float:
114
82
  return float(0.5 * min(Us[:, i].norm(p) for i in range(Us.rows)))
115
83
 
116
84
 
85
+ def kannan_cvp(x: Matrix, U: Matrix, m: int = 1) -> Matrix:
86
+ "Kannan's embedding algorithm for approximately solving the CVP."
87
+ V = U[:,:]
88
+ V.append_column(x)
89
+ V.append_row(V.zeros(1,V.cols))
90
+ V[-1,-1]= m
91
+ V = lll(V)
92
+ e = None
93
+ for i in range(V.cols):
94
+ mm = V.matrix[-1][i]
95
+ if mm == m:
96
+ e = V[:-1,i]
97
+ break
98
+ if mm == -m:
99
+ e = -V[:-1,i]
100
+ break
101
+ if e is None:
102
+ return None
103
+ V = U.applyfunc(Fraction)
104
+ x = V.solve(x-e)
105
+ x.map(int)
106
+ return U * x
107
+
108
+
117
109
  def lagrange_lr(V: Matrix) -> Matrix:
118
110
  "Lagrange lattice reduction."
119
111
  if V.cols != 2:
@@ -220,26 +212,32 @@ def q_ary_lattice(U: Matrix, lll: bool = False) -> Matrix: # pylint: disable=W0
220
212
  raise ValueError("The matrix does not seem to be over Zmod.")
221
213
  V = eye(U.rows, one = q)
222
214
  V.append_column(U, ring = int)
223
- V = hermite_nf(V)
215
+ V = V.hnf()
224
216
  if lll:
225
217
  return globals()['lll'](V)
226
218
  return V
227
219
 
228
220
  def svp_lll(U: Matrix) -> Matrix:
229
221
  "Solve the shortest vector problem in a q-ary lattice associated with a matrix over a finite ring Z_q."
230
- gf = U[0].ring
231
- V = q_ary_lattice(U, lll = True)
222
+ ring = U[0].ring
223
+ V = lll(q_ary_lattice(U, lll = True))
232
224
  x = V[:, 0]
233
- x.map(gf)
225
+ x.map(ring)
234
226
  return x
235
227
 
236
- def cvp_lll(U: Matrix, x: Matrix) -> Matrix:
228
+ def cvp_lll(U: Matrix, x: Matrix, babai_plane: bool = True, kannan: bool = True) -> Matrix:
237
229
  "Solve the closest vector problem in a q-ary lattice associated with a matrix over a finite ring Z_q (LWE)."
238
- gf = U[0].ring
239
- V = q_ary_lattice(U, lll = True)
230
+ ring = U[0].ring
231
+ V = lll(q_ary_lattice(U, lll = True))
240
232
  x.map(int)
241
- y = babai_plane_cvp(x, V)
242
- x.map(gf)
233
+ y = None
234
+ if babai_plane:
235
+ y = babai_plane_cvp(x, V)
236
+ if kannan:
237
+ y2 = kannan_cvp(x, V)
238
+ if y is not None and y2 is not None and y2.norm2() < y.norm2():
239
+ y = y2
240
+ x.map(ring)
243
241
  return y
244
242
 
245
243
  def sis_lll(A: Matrix) -> Matrix:
@@ -249,48 +247,47 @@ def sis_lll(A: Matrix) -> Matrix:
249
247
  def isis_lll(A: Matrix, b: Matrix) -> Matrix:
250
248
  "Solve the inhomogenous short integer problem over a finite ring Z_q (ISIS)."
251
249
  y = A.solve(b)
250
+ if y is None:
251
+ return None
252
252
  return y - cvp_lll(A.kernel(), y)
253
253
 
254
254
  def svp_search(U: Matrix, m: int = 0, p: int = 2) -> Matrix:
255
255
  "Solve the shortest vector problem in a q-ary lattice associated with a matrix over a finite ring Z_q."
256
- gf = U[0].ring
256
+ ring = U[0].ring
257
257
  if m:
258
258
  ml, mu = -m, m + 1
259
259
  else:
260
- ml, mu = -((gf.n-1)//2), gf.n//2 + 1
261
- U.map(int)
260
+ ml, mu = -((ring.n-1)//2), ring.n//2 + 1
261
+ U = U.applyfunc(int)
262
262
  norm_min = inf
263
263
  c_min = None
264
264
  for j in range(U.cols):
265
265
  for c in product(range(ml, mu), repeat = U.cols-j-1):
266
266
  for cc in range(1, mu):
267
267
  cc = U * Matrix([ 0 ] * j + [ cc ] + list(c))
268
- cc.map(gf)
268
+ cc.map(ring)
269
269
  norm = cc.norm(p)
270
- if norm < norm_min:
270
+ if norm and norm < norm_min:
271
271
  norm_min = norm
272
272
  c_min = cc
273
- U.map(gf)
274
273
  return c_min
275
274
 
276
275
  def cvp_search(U: Matrix, b: Matrix, p: int = 2) -> Matrix:
277
276
  "Solve the closest vector problem in a q-ary lattice associated with a matrix over a finite ring Z_q (LWE)."
278
- gf = U[0].ring
279
- U.map(int)
280
- b.map(int)
277
+ ring = U[0].ring
278
+ U = U.applyfunc(int)
279
+ b = b.applyfunc(int)
281
280
  norm_min = inf
282
281
  c_min = None
283
- for c in product(range(-((gf.n-1)//2), gf.n//2 + 1), repeat = U.cols):
282
+ for c in product(range(-((ring.n-1)//2), ring.n//2 + 1), repeat = U.cols):
284
283
  c = U * Matrix(c)
285
284
  d = c - b
286
- d.map(gf)
285
+ d.map(ring)
287
286
  norm = d.norm(p)
288
287
  if norm < norm_min:
289
288
  norm_min = norm
290
- c.map(gf)
289
+ c.map(ring)
291
290
  c_min = c
292
- U.map(gf)
293
- b.map(gf)
294
291
  return c_min
295
292
 
296
293
  def sis_search(A: Matrix, p: int = 2) -> Matrix:
@@ -19,12 +19,16 @@ from fractions import Fraction
19
19
  # extended Euclid
20
20
 
21
21
 
22
- def egcd(a: int, b: int) -> (int, int, int):
22
+ def egcd(a: int, b: int, minimal = False) -> (int, int, int):
23
23
  """Perform the extended Euclidean agorithm. Returns `gcd`, `x`, `y` such that `a x + b y = gcd`."""
24
24
  if a == 0:
25
25
  if b < 0:
26
26
  return -b, 0, -1
27
27
  return b, 0, 1
28
+ if b == 0:
29
+ if a < 0:
30
+ return -a, -1, 0
31
+ return a, 1, 0
28
32
  if a < 0:
29
33
  r0, r1 = -a, b
30
34
  x0, x1, y0, y1 = -1, 0, 0, 1
@@ -39,6 +43,19 @@ def egcd(a: int, b: int) -> (int, int, int):
39
43
  r0, r1 = r1, r
40
44
  x0, x1 = x1, x0 - q * x1
41
45
  y0, y1 = y1, y0 - q * y1
46
+ if minimal:
47
+ bb = abs(b) // r0
48
+ if a < 0:
49
+ x0 *= -1
50
+ k, x0 = divmod(x0, bb)
51
+ if x0 == 0:
52
+ x0 = bb
53
+ k -= 1
54
+ if a < 0:
55
+ x0 *= -1
56
+ if b < 0:
57
+ k *= -1
58
+ y0 += k * abs(a) // r0
42
59
  return r0, x0, y0
43
60
 
44
61
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  License: MIT
@@ -43,11 +43,11 @@ The tools contained are:
43
43
  * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve)
44
44
  * integer factorization (Fermat, Pollard p-1, Pollard rho, Lenstra's ECM, Dixon, basic quadratic sieve)
45
45
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
46
- * linear algebra: Hermite normal form, Gram-Schmidt
46
+ * linear algebra: Hermite normal form, Smith normal form, Gram-Schmidt
47
47
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz), SIS, ISIS, LWE
48
48
  * linear codes: Hamming distance, left standard form, parity check matrix
49
49
 
50
- * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
50
+ * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, solving linear systems, etc.)
51
51
  * BinaryMatrix: a class for Matrices with binary entries (for much faster row operations)
52
52
  * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
53
53
  * Zmod: a class for the ring of integers modulo an integer
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "1.8.1"
3
+ version = "1.8.2"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -3,6 +3,7 @@ import pytest # pylint: disable=W0611
3
3
  from math import gcd # pylint: disable=C0411
4
4
  from random import randint, seed # pylint: disable=C0411
5
5
  from fractions import Fraction # pylint: disable=C0411
6
+ from itertools import product # pylint: disable=C0411
6
7
  from kryptools import Matrix, Zmod, GF2, eye, circulant, BinaryMatrix
7
8
 
8
9
 
@@ -65,11 +66,53 @@ def test_Matrix2():
65
66
  x = A.solve(b)
66
67
  assert A * x == b
67
68
 
69
+ def test_hnf():
70
+ num_tests = 5
71
+ seed(0)
72
+ for m,n in ((3, 5), (5, 3), (3, 3)):
73
+ for _ in range(num_tests):
74
+ A = Matrix([[randint(-10,10) for _ in range(m)] for _ in range(n)])
75
+ H = A.transpose()
76
+ H.permute_columns(range(H.cols-1,-1,-1))
77
+ H = H.hrnf(drop_zero_rows = True)
78
+ H.permute_columns(range(H.cols-1,-1,-1))
79
+ H = H.transpose()
80
+ H.permute_columns(range(H.cols-1,-1,-1))
81
+ assert H == A.hnf()
82
+
83
+ def test_solve():
84
+ seed(0)
85
+ for gf in Zmod(2), Zmod(7), GF2(1), GF2(8):
86
+ for m,n in ((3, 5), (5, 3), (3, 3)):
87
+ for _ in range(25):
88
+ A = Matrix([[gf.random() for _ in range(n)] for _ in range(m)])
89
+ b = Matrix([gf.random() for _ in range(m)])
90
+ x = A.solve(b)
91
+ if x is not None:
92
+ assert A *x == b
93
+ else:
94
+ AA = A[:, :]
95
+ AA.append_column(b)
96
+ assert AA.rank() > A.rank()
97
+ for ring in Zmod(4), Zmod(6):
98
+ for m,n in ((3, 4), (4, 3), (3, 3)):
99
+ for _ in range(25):
100
+ A = Matrix([[ring.random() for _ in range(n)] for _ in range(m)])
101
+ b = Matrix([ring.random() for _ in range(m)])
102
+ x = A.solve(b)
103
+ if x is not None:
104
+ assert A *x == b
105
+ else:
106
+ for x in product(ring, repeat = A.cols):
107
+ x = Matrix(x)
108
+ assert A * x != b
109
+
68
110
  def test_kernel():
111
+ seed(0)
69
112
  for gf in Zmod(2), Zmod(7), GF2(1), GF2(8):
70
113
  for m,n in ((3, 5), (5, 3), (3, 3)):
71
114
  for _ in range(25):
72
- A = Matrix([[gf.random() for _ in range(m)] for _ in range(n)])
115
+ A = Matrix([[gf.random() for _ in range(n)] for _ in range(m)])
73
116
  K = A.kernel()
74
117
  assert not A * K
75
118
  assert K.rows == A.cols
@@ -77,6 +120,29 @@ def test_kernel():
77
120
  assert K.cols == A.nullity()
78
121
  else:
79
122
  assert K.cols == 1
123
+ for ring in Zmod(4), Zmod(6):
124
+ for m,n in ((3, 4), (4, 3), (3, 3)):
125
+ for _ in range(25):
126
+ A = Matrix([[ring.random() for _ in range(n)] for _ in range(m)])
127
+ K = A.kernel()
128
+ assert not A * K
129
+ kernel = []
130
+ for a in product(ring, repeat = K.cols):
131
+ a = K * Matrix(a)
132
+ a.map(int)
133
+ a = list(a)
134
+ if a not in kernel:
135
+ kernel.append(a)
136
+ kernel.sort()
137
+ kernel2 = []
138
+ for a in product(ring, repeat = A.cols):
139
+ a = Matrix(a)
140
+ if not A * a:
141
+ a.map(int)
142
+ a = list(a)
143
+ kernel2.append(a)
144
+ kernel2.sort()
145
+ assert kernel2 == kernel
80
146
 
81
147
  def test_BinaryMatrix():
82
148
  num_tests = 100
@@ -95,6 +161,12 @@ def test_BinaryMatrix():
95
161
  assert BinaryMatrix(Mat1 + Mat2) == mat1 + mat2
96
162
  assert BinaryMatrix(Mat1 - Mat2) == mat1 + mat2
97
163
  assert BinaryMatrix(Mat1 * Mat3) == mat1 * mat3
164
+ Mat1.swap_columns(0,n-1)
165
+ mat1.swap_columns(0,n-1)
166
+ assert BinaryMatrix(Mat1) == mat1
167
+ Mat1.swap_rows(0,m-1)
168
+ mat1.swap_rows(0,m-1)
169
+ assert BinaryMatrix(Mat1) == mat1
98
170
 
99
171
  for _ in range(num_tests):
100
172
  m = randint(n-1,n+1)
@@ -0,0 +1,49 @@
1
+ # pragma pylint: disable=C0114,C0116
2
+ import pytest # pylint: disable=W0611
3
+ from random import seed, randint # pylint: disable=C0411
4
+ from fractions import Fraction # pylint: disable=C0411
5
+ from kryptools import Matrix, gram_schmidt, random_unimodular_matrix
6
+ from kryptools import Zmod, sis_search, sis_lll, isis_search, isis_lll
7
+
8
+
9
+ def test_gram_schmidt():
10
+ seed(0)
11
+ for n in range(2, 5):
12
+ for _ in range(10):
13
+ found = False
14
+ while not found:
15
+ V = Matrix([[ randint(-10,10) for _ in range(n)] for _ in range(n) ], ring=Fraction)
16
+ found = bool(V.rank() == n)
17
+ Vs, M = gram_schmidt(V)
18
+ assert V == Vs * M
19
+ D = V.eye()
20
+ for i in range(Vs.cols):
21
+ assert Vs[:,i].dot(Vs[:,i]) == Vs[:,i].norm2()
22
+ D[i,i] = Vs[:,i].dot(Vs[:,i])
23
+ assert Vs.transpose() * Vs == D
24
+
25
+ def test_random_unimodular_matrix():
26
+ for i in range(2, 5):
27
+ U = random_unimodular_matrix(i)
28
+ U.map(Fraction)
29
+ assert U.det() in (-1, 1)
30
+
31
+ def test_sis():
32
+ seed(0)
33
+ for ring in Zmod(3), Zmod(4), Zmod(5), Zmod(6):
34
+ for _ in range(25):
35
+ A = Matrix([[ring.random() for _ in range(6)] for _ in range(3)])
36
+ xs = sis_search(A)
37
+ assert not A * xs
38
+ x = sis_lll(A)
39
+ assert not A * x
40
+ assert x.norm() == xs.norm()
41
+ b = Matrix([[ring.random()] for _ in range(3)])
42
+ xs = isis_search(A, b)
43
+ if xs is not None:
44
+ assert A * xs == b
45
+ x = isis_lll(A, b)
46
+ if x is not None:
47
+ assert A * x == b
48
+ if xs is not None:
49
+ assert xs.norm() <= x.norm() <= 1.5 * xs.norm()
@@ -9,12 +9,21 @@ seed(0)
9
9
 
10
10
 
11
11
  def test_egcd():
12
+ def sign(x: int) -> int:
13
+ return (1, -1)[x<0]
12
14
  maxsize = 100
13
15
  for a in range(-maxsize, maxsize):
14
16
  for b in range(-maxsize, maxsize):
15
17
  g, x, y = egcd(a, b)
16
18
  assert g == gcd(a, b)
17
19
  assert x * a + y * b == g
20
+ if a and b:
21
+ g, x, y = egcd(a, b, minimal = True)
22
+ assert g == gcd(a, b)
23
+ assert x * a + y * b == g
24
+ assert -abs(a) // g < y * sign(b) <= 0
25
+ assert 1 <= x * sign(a) <= abs(b)//g
26
+
18
27
 
19
28
 
20
29
  def test_crt():
@@ -1,17 +0,0 @@
1
- # pragma pylint: disable=C0114,C0116
2
- import pytest # pylint: disable=W0611
3
- from fractions import Fraction # pylint: disable=C0411
4
- from kryptools import Matrix, gram_schmidt, random_unimodular_matrix
5
-
6
-
7
- def test_gram_schmidt():
8
- V = Matrix([[5, 8], [0, 1]], ring=Fraction)
9
- Vs, M = gram_schmidt(V)
10
- assert V == Vs * M
11
-
12
-
13
- def test_random_unimodular_matrix():
14
- for i in range(2, 5):
15
- U = random_unimodular_matrix(i)
16
- U.map(Fraction)
17
- assert U.det() in (-1, 1)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes