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.
- {kryptools-1.8.1 → kryptools-1.8.2}/PKG-INFO +3 -3
- {kryptools-1.8.1 → kryptools-1.8.2}/README.md +2 -2
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/Zmod.py +1 -1
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/__init__.py +1 -1
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/la.py +255 -73
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/lat.py +56 -59
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/nt.py +18 -1
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/PKG-INFO +3 -3
- {kryptools-1.8.1 → kryptools-1.8.2}/pyproject.toml +1 -1
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_la.py +73 -1
- kryptools-1.8.2/tests/test_lat.py +49 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_nt.py +9 -0
- kryptools-1.8.1/tests/test_lat.py +0 -17
- {kryptools-1.8.1 → kryptools-1.8.2}/LICENSE +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/GF2.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/aes.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/blockcipher.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/code.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/des.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/ec.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_dix.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_qs.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/factor_rho.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/intfuncs.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/keccak.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/poly.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/primes.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools/sha1.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/setup.cfg +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_GF2.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_Zmod.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_aes.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_code.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_des.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_dlog.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_ec.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_factor_qs.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_intfuncs.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_keccak.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_poly.py +0 -0
- {kryptools-1.8.1 → kryptools-1.8.2}/tests/test_primes.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
434
|
-
if H[i][j] < 0:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
H
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
A.
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
242
|
-
|
|
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
|
-
|
|
256
|
+
ring = U[0].ring
|
|
257
257
|
if m:
|
|
258
258
|
ml, mu = -m, m + 1
|
|
259
259
|
else:
|
|
260
|
-
ml, mu = -((
|
|
261
|
-
U.
|
|
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(
|
|
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
|
-
|
|
279
|
-
U.
|
|
280
|
-
b.
|
|
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(-((
|
|
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(
|
|
285
|
+
d.map(ring)
|
|
287
286
|
norm = d.norm(p)
|
|
288
287
|
if norm < norm_min:
|
|
289
288
|
norm_min = norm
|
|
290
|
-
c.map(
|
|
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.
|
|
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
|
|
@@ -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(
|
|
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
|
|
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
|