kryptools 1.7__tar.gz → 1.8.1__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.7 → kryptools-1.8.1}/PKG-INFO +2 -2
- {kryptools-1.7 → kryptools-1.8.1}/README.md +1 -1
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/Zmod.py +32 -6
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/__init__.py +3 -2
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/la.py +224 -61
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/lat.py +123 -24
- {kryptools-1.7 → kryptools-1.8.1}/kryptools.egg-info/PKG-INFO +2 -2
- {kryptools-1.7 → kryptools-1.8.1}/pyproject.toml +1 -1
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_Zmod.py +5 -2
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_la.py +32 -3
- {kryptools-1.7 → kryptools-1.8.1}/LICENSE +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/GF2.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/aes.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/blockcipher.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/code.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/des.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/dlp.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/ec.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor_dix.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor_qs.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/factor_rho.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/intfuncs.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/keccak.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/nt.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/poly.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/primes.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools/sha1.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/setup.cfg +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_GF2.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_aes.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_code.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_des.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_dlog.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_ec.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_factor.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_factor_qs.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_intfuncs.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_keccak.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_lat.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_nt.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_poly.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_primes.py +0 -0
- {kryptools-1.7 → kryptools-1.8.1}/tests/test_sha1.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
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
|
|
@@ -44,7 +44,7 @@ The tools contained are:
|
|
|
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
46
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
47
|
-
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
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
50
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
|
|
@@ -26,7 +26,7 @@ The tools contained are:
|
|
|
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
28
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
29
|
-
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
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
32
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
|
|
@@ -28,13 +28,15 @@ class Zmod:
|
|
|
28
28
|
0
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
def __init__(self, n: int, short: bool = True):
|
|
31
|
+
def __init__(self, n: int, short: bool = True, sharp: bool = False):
|
|
32
32
|
if not isinstance(n, int) or n < 1:
|
|
33
33
|
raise ValueError(f"{n} is not a positive integer.")
|
|
34
34
|
self.n = n
|
|
35
|
+
self.n2 = n // 2
|
|
35
36
|
self.isfield = None
|
|
36
37
|
self.n_factors = {} # factoring of n
|
|
37
38
|
self.short = short
|
|
39
|
+
self.sharp = sharp
|
|
38
40
|
self.group_order = 0 # order of the multiplicative group
|
|
39
41
|
self.factors = {} # factoring of the multiplicative group order
|
|
40
42
|
|
|
@@ -161,9 +163,13 @@ class ZmodPoint:
|
|
|
161
163
|
self.ring = ring
|
|
162
164
|
|
|
163
165
|
def __repr__(self):
|
|
166
|
+
if self.ring.sharp:
|
|
167
|
+
x = self.sharp()
|
|
168
|
+
else:
|
|
169
|
+
x = self.x
|
|
164
170
|
if self.ring.short:
|
|
165
|
-
return str(
|
|
166
|
-
return f"{
|
|
171
|
+
return str(x)
|
|
172
|
+
return f"{x} (mod {self.ring.n})"
|
|
167
173
|
|
|
168
174
|
def __eq__(self, other):
|
|
169
175
|
if not isinstance(other, self.__class__) or self.ring != other.ring:
|
|
@@ -239,7 +245,9 @@ class ZmodPoint:
|
|
|
239
245
|
return NotImplemented
|
|
240
246
|
|
|
241
247
|
def __abs__(self) -> int:
|
|
242
|
-
|
|
248
|
+
if self.x <= self.ring.n2:
|
|
249
|
+
return self.x
|
|
250
|
+
return self.ring.n - self.x
|
|
243
251
|
|
|
244
252
|
def bits(self) -> list:
|
|
245
253
|
"Convert to a list of bits."
|
|
@@ -247,7 +255,7 @@ class ZmodPoint:
|
|
|
247
255
|
|
|
248
256
|
def sharp(self):
|
|
249
257
|
"Returns a symmetric (w.r.t. 0) representative."
|
|
250
|
-
if self.x <= self.ring.
|
|
258
|
+
if self.x <= self.ring.n2:
|
|
251
259
|
return self.x
|
|
252
260
|
return self.x - self.ring.n
|
|
253
261
|
|
|
@@ -320,6 +328,24 @@ class ZmodPoint:
|
|
|
320
328
|
roots.append((p**(j//2) * x) % pk)
|
|
321
329
|
return self.ring(crt(roots, powers))
|
|
322
330
|
|
|
323
|
-
def
|
|
331
|
+
def solve(self, b, all_solutions: bool = False) -> "ZmodPoint":
|
|
332
|
+
"Find a solution `x` of the linear equation `self * x == b` in Z_n."
|
|
333
|
+
b = int(b)
|
|
334
|
+
if not self:
|
|
335
|
+
if b:
|
|
336
|
+
return None
|
|
337
|
+
if all_solutions:
|
|
338
|
+
return list(self.ring)
|
|
339
|
+
return self.ring(0)
|
|
340
|
+
g = gcd(self.x, self.ring.n)
|
|
341
|
+
if b % g:
|
|
342
|
+
return None
|
|
343
|
+
m = self.ring.n // g
|
|
344
|
+
sol = self.ring(pow(self.x // g, -1, m) * (b // g) % m)
|
|
345
|
+
if all_solutions:
|
|
346
|
+
return [ sol + j * m for j in range(g) ]
|
|
347
|
+
return sol
|
|
348
|
+
|
|
349
|
+
def is_generator(self) -> bool:
|
|
324
350
|
"Test if the point is a generator of the group Z_n^*."
|
|
325
351
|
return self.ring.order() == self.order()
|
|
@@ -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
|
+
__version__ = "1.8.1"
|
|
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
|
|
@@ -15,7 +15,8 @@ from .factor import factorint, divisors
|
|
|
15
15
|
from .dlp import dlog
|
|
16
16
|
from .ec import EC_Weierstrass
|
|
17
17
|
from .la import Matrix, zeros, eye, circulant, BinaryMatrix
|
|
18
|
-
from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_round_bnd, babai_plane_cvp, babai_plane_bnd, lagrange_lr, lll, random_unimodular_matrix
|
|
18
|
+
from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_round_bnd, babai_plane_cvp, babai_plane_bnd, lagrange_lr, lll, random_unimodular_matrix, q_ary_lattice
|
|
19
|
+
from .lat import svp_lll, cvp_lll, sis_lll, isis_lll, svp_search, cvp_search, sis_search, isis_search
|
|
19
20
|
from .poly import Poly, PolyBinMult, lagrange_interpolation
|
|
20
21
|
from .Zmod import Zmod
|
|
21
22
|
from .GF2 import GF2, GF2_aes, GF2_miniaes, GF2_ghash
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Linear algebra
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# pragma pylint: disable=C0302
|
|
6
|
+
from math import gcd, inf, sqrt
|
|
6
7
|
from numbers import Number
|
|
7
8
|
from fractions import Fraction
|
|
8
9
|
from .Zmod import Zmod
|
|
@@ -69,7 +70,7 @@ class Matrix:
|
|
|
69
70
|
res = "\\begin{pmatrix}\n"
|
|
70
71
|
for row in self.matrix:
|
|
71
72
|
res += " & ".join(map(str, row)) + '\\\\\n'
|
|
72
|
-
res
|
|
73
|
+
res = res[:-3] + '\n\\end{pmatrix}'
|
|
73
74
|
return res
|
|
74
75
|
|
|
75
76
|
def __len__(self):
|
|
@@ -177,26 +178,42 @@ class Matrix:
|
|
|
177
178
|
|
|
178
179
|
def append_row(self, row: list|tuple, ring = None) -> None:
|
|
179
180
|
"Append a row."
|
|
180
|
-
if
|
|
181
|
+
if isinstance(row, self.__class__):
|
|
182
|
+
row = row.matrix
|
|
183
|
+
elif not isinstance(row, list|tuple):
|
|
181
184
|
row = list(row)
|
|
182
|
-
if
|
|
185
|
+
if not isinstance(row[0], list|tuple):
|
|
186
|
+
row = [ row ]
|
|
187
|
+
if len(row[0]) != self.cols:
|
|
183
188
|
raise ValueError("Length does not match the number of columns.")
|
|
184
189
|
if ring:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
self.
|
|
190
|
+
for i, r in enumerate(row):
|
|
191
|
+
row[i] =list(map(ring, r))
|
|
192
|
+
self.matrix += row
|
|
193
|
+
self.rows += len(row)
|
|
188
194
|
|
|
189
195
|
def append_column(self, col: list|tuple, ring = None) -> None:
|
|
190
196
|
"Append a column."
|
|
191
|
-
if
|
|
197
|
+
if isinstance(col, self.__class__):
|
|
198
|
+
col = col.matrix
|
|
199
|
+
elif not isinstance(col, list|tuple):
|
|
192
200
|
col = list(col)
|
|
193
201
|
if len(col) != self.rows:
|
|
194
202
|
raise ValueError("Length does not match the number of rows.")
|
|
195
|
-
if
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
if isinstance(col[0], list|tuple):
|
|
204
|
+
for i, c in enumerate(col):
|
|
205
|
+
if ring:
|
|
206
|
+
self.matrix[i] += list(map(ring, c))
|
|
207
|
+
else:
|
|
208
|
+
self.matrix[i] += c
|
|
209
|
+
self.cols += len(col[0])
|
|
210
|
+
else:
|
|
211
|
+
for i, c in enumerate(col):
|
|
212
|
+
if ring:
|
|
213
|
+
self.matrix[i].append(ring(c))
|
|
214
|
+
else:
|
|
215
|
+
self.matrix[i].append(c)
|
|
216
|
+
self.cols += 1
|
|
200
217
|
|
|
201
218
|
def permute_columns(self, permutation) -> None:
|
|
202
219
|
"Permute columns according to a list of new positions."
|
|
@@ -240,7 +257,7 @@ class Matrix:
|
|
|
240
257
|
|
|
241
258
|
def norm2(self) -> float:
|
|
242
259
|
"Squared Frobenius/Euclidean norm."
|
|
243
|
-
return sum(sum(x
|
|
260
|
+
return sum(sum(abs(x)**2 for x in row) for row in self.matrix)
|
|
244
261
|
|
|
245
262
|
def norm(self, p: int = 2) -> float:
|
|
246
263
|
"p-norm of the matrix regarded as a vector."
|
|
@@ -312,37 +329,45 @@ class Matrix:
|
|
|
312
329
|
return self.__class__([[item * other for item in row] for row in self.matrix])
|
|
313
330
|
return NotImplemented
|
|
314
331
|
|
|
315
|
-
def rref(self, start = 0, drop_zero_rows = False) -> "Matrix":
|
|
332
|
+
def rref(self, start: int = 0, drop_zero_rows: bool = False) -> "Matrix":
|
|
316
333
|
"Compute the reduced echelon form."
|
|
334
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
335
|
+
# the matrix is over a ring (not a field)
|
|
336
|
+
return self._rref_ring(start = start, drop_zero_rows = drop_zero_rows)
|
|
317
337
|
one = self._guess_zero()[1]
|
|
318
338
|
n, m = self.cols, self.rows
|
|
319
|
-
R = self[
|
|
320
|
-
|
|
321
|
-
|
|
339
|
+
R = [ self.matrix[i][:] for i in range(m) ]
|
|
340
|
+
pivotcols = []
|
|
341
|
+
nonpivotcols = []
|
|
322
342
|
i = 0
|
|
323
343
|
if start >= n:
|
|
324
344
|
raise ValueError("Start value cannot be beyond the last column.")
|
|
325
345
|
for j in range(start, n):
|
|
326
|
-
if not R[i
|
|
327
|
-
for ii in range(i+1, m):
|
|
328
|
-
if R[ii
|
|
329
|
-
R[i
|
|
346
|
+
if not R[i][j]: # search for a nonzero entry in the present column
|
|
347
|
+
for ii in range(i + 1, m):
|
|
348
|
+
if R[ii][j]:
|
|
349
|
+
R[i], R[ii] = R[ii], R[i] # swap rows
|
|
330
350
|
break
|
|
331
351
|
else:
|
|
332
|
-
|
|
352
|
+
nonpivotcols.append(j)
|
|
333
353
|
continue # all entries are zero
|
|
334
|
-
|
|
335
|
-
if R[i
|
|
336
|
-
|
|
354
|
+
pivotcols.append(j)
|
|
355
|
+
if R[i][j] != one:
|
|
356
|
+
tmp = R[i][j]**-1
|
|
357
|
+
for k in range(n):
|
|
358
|
+
R[i][k] *= tmp # make the pivot one
|
|
337
359
|
for ii in range(m): # remove the column entries above/below the pivot
|
|
338
360
|
if i == ii:
|
|
339
361
|
continue
|
|
340
|
-
tmp = R[ii
|
|
341
|
-
|
|
362
|
+
tmp = R[ii][j]
|
|
363
|
+
for k in range(n):
|
|
364
|
+
R[ii][k] -= tmp * R[i][k]
|
|
342
365
|
i += 1
|
|
343
366
|
if i == m:
|
|
344
367
|
break
|
|
345
|
-
R
|
|
368
|
+
R = Matrix(R)
|
|
369
|
+
R.pivotcols = pivotcols
|
|
370
|
+
R.nonpivotcols = nonpivotcols + list(range(j+1, R.cols))
|
|
346
371
|
# purge zero rows
|
|
347
372
|
if drop_zero_rows:
|
|
348
373
|
l = len(R.pivotcols)
|
|
@@ -352,6 +377,91 @@ class Matrix:
|
|
|
352
377
|
R.rows = l
|
|
353
378
|
return R
|
|
354
379
|
|
|
380
|
+
def _rref_ring(self, start: int = 0, drop_zero_rows: bool = False) -> "Matrix":
|
|
381
|
+
"Compute the reduced echelon form if the base ring is Z_n and no field."
|
|
382
|
+
ring = self.matrix[0][0].ring
|
|
383
|
+
M = self.applyfunc(int)
|
|
384
|
+
done = start
|
|
385
|
+
while done < M.cols:
|
|
386
|
+
M = M.hrnf(start = start, drop_zero_rows = drop_zero_rows)
|
|
387
|
+
done = M.cols
|
|
388
|
+
for i, j in enumerate(M.pivotcols):
|
|
389
|
+
if j < start:
|
|
390
|
+
continue
|
|
391
|
+
pivot = M.matrix[i][j]
|
|
392
|
+
if pivot >= ring.n:
|
|
393
|
+
done = min(j, done)
|
|
394
|
+
pivot %= ring.n
|
|
395
|
+
if pivot: # find the invertible part
|
|
396
|
+
g = gcd(pivot, ring.n)
|
|
397
|
+
while g > 1:
|
|
398
|
+
pivot //= g
|
|
399
|
+
g = gcd(pivot, ring.n)
|
|
400
|
+
if pivot > 1: # we can make the pivot smaller
|
|
401
|
+
done = min(j, done)
|
|
402
|
+
tmp = pow(pivot, -1, ring.n)
|
|
403
|
+
for k in range(j, M.cols):
|
|
404
|
+
M.matrix[i][k] *= tmp
|
|
405
|
+
M.map(lambda x: x % ring.n)
|
|
406
|
+
M.map(ring)
|
|
407
|
+
return M
|
|
408
|
+
|
|
409
|
+
def hrnf(self, start = 0, drop_zero_rows: bool = False) -> "Matrix":
|
|
410
|
+
"Compute the Hermite row normal form."
|
|
411
|
+
n, m = self.cols, self.rows
|
|
412
|
+
if not isinstance(self.matrix[0][0], int):
|
|
413
|
+
raise ValueError("Hermite normal form requires integer entries!")
|
|
414
|
+
H = [ self.matrix[i][:] for i in range(m) ]
|
|
415
|
+
pivotcols = []
|
|
416
|
+
nonpivotcols = []
|
|
417
|
+
i = 0
|
|
418
|
+
if start >= n:
|
|
419
|
+
raise ValueError("Start value cannot be beyond the last column.")
|
|
420
|
+
for j in range(start, n):
|
|
421
|
+
i0 = i
|
|
422
|
+
minimum = abs(H[i][j]) # search for the pivot in the present column
|
|
423
|
+
for ii in range(i + 1, m):
|
|
424
|
+
tmp = abs(H[ii][j])
|
|
425
|
+
if tmp > 0 and (tmp < minimum or minimum == 0):
|
|
426
|
+
minimum = tmp
|
|
427
|
+
i0 = ii
|
|
428
|
+
if minimum == 0:
|
|
429
|
+
nonpivotcols.append(j)
|
|
430
|
+
continue # all entrjes are zero
|
|
431
|
+
pivotcols.append(j)
|
|
432
|
+
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
|
|
446
|
+
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]
|
|
450
|
+
i += 1
|
|
451
|
+
if i >= m:
|
|
452
|
+
break
|
|
453
|
+
H = Matrix(H)
|
|
454
|
+
H.pivotcols = pivotcols
|
|
455
|
+
H.nonpivotcols = nonpivotcols + list(range(j+1, H.cols))
|
|
456
|
+
# purge zero rows
|
|
457
|
+
if drop_zero_rows:
|
|
458
|
+
l = len(H.pivotcols)
|
|
459
|
+
if not l:
|
|
460
|
+
l = 1 # do not delete all rows
|
|
461
|
+
del H.matrix[l:]
|
|
462
|
+
H.rows = l
|
|
463
|
+
return H
|
|
464
|
+
|
|
355
465
|
def left_standard_form(self) -> "Matrix":
|
|
356
466
|
"Compute the left standard form."
|
|
357
467
|
# reduced row echelon form
|
|
@@ -363,6 +473,8 @@ class Matrix:
|
|
|
363
473
|
|
|
364
474
|
def kernel(self) -> "Matrix":
|
|
365
475
|
"Compute a basis for the kernel."
|
|
476
|
+
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.")
|
|
366
478
|
_, one = self._guess_zero()
|
|
367
479
|
M = self.rref(drop_zero_rows = True)
|
|
368
480
|
K = M.zeros(M.cols, max(1,len(M.nonpivotcols)))
|
|
@@ -374,31 +486,43 @@ class Matrix:
|
|
|
374
486
|
|
|
375
487
|
def det(self) -> int:
|
|
376
488
|
"Compute the determinant."
|
|
377
|
-
zero, one = self._guess_zero()
|
|
378
|
-
if self.rows != self.cols:
|
|
379
|
-
raise ValueError("Matrix must be square!")
|
|
380
489
|
n, m = self.cols, self.rows
|
|
381
|
-
|
|
490
|
+
if n != m:
|
|
491
|
+
raise ValueError("Matrix must be square!")
|
|
492
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
493
|
+
# the matrix is over a ring (not a field)
|
|
494
|
+
ring = self.matrix[0][0].ring
|
|
495
|
+
R = [ list(map(lambda x: Fraction(int(x)), self.matrix[i])) for i in range(m) ]
|
|
496
|
+
zero, one = ring(0), 1
|
|
497
|
+
else:
|
|
498
|
+
ring = None
|
|
499
|
+
R = [ self.matrix[i][:] for i in range(m) ]
|
|
500
|
+
zero, one = self._guess_zero()
|
|
382
501
|
D = one
|
|
383
502
|
i = 0
|
|
384
503
|
for j in range(n):
|
|
385
|
-
if not R[i
|
|
504
|
+
if not R[i][j]: # search for a nonzero entry in the present column
|
|
386
505
|
for ii in range(i+1, m):
|
|
387
|
-
if R[ii
|
|
506
|
+
if R[ii][j]:
|
|
388
507
|
D *= -one
|
|
389
|
-
R[i
|
|
508
|
+
R[i], R[ii] = R[ii], R[i] # swap rows
|
|
390
509
|
break
|
|
391
|
-
else:
|
|
392
|
-
return zero
|
|
393
|
-
if R[i
|
|
394
|
-
D *= R[i
|
|
395
|
-
|
|
396
|
-
|
|
510
|
+
else: # all entries are zero
|
|
511
|
+
return zero
|
|
512
|
+
if R[i][j] != one:
|
|
513
|
+
D *= R[i][j]
|
|
514
|
+
tmp = R[i][j]**-1
|
|
515
|
+
for k in range(n):
|
|
516
|
+
R[i][k] *= tmp # make the pivot one
|
|
517
|
+
for ii in range(i + 1, n): # remove the column entries below the pivot
|
|
397
518
|
if i == ii:
|
|
398
519
|
continue
|
|
399
|
-
tmp = R[ii
|
|
400
|
-
|
|
520
|
+
tmp = R[ii][j]
|
|
521
|
+
for k in range(n):
|
|
522
|
+
R[ii][k] -= tmp * R[i][k]
|
|
401
523
|
i += 1
|
|
524
|
+
if ring:
|
|
525
|
+
return ring(D)
|
|
402
526
|
return D
|
|
403
527
|
|
|
404
528
|
def rank(self) -> int:
|
|
@@ -425,28 +549,68 @@ class Matrix:
|
|
|
425
549
|
M = self.__class__(M).rref()
|
|
426
550
|
if not left and M.pivotcols[self.cols - 1] != self.cols -1:
|
|
427
551
|
raise ValueError("Matrix is not invertible!")
|
|
552
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
553
|
+
# the matrix is over a ring (not a field)
|
|
554
|
+
for i, j in enumerate(M.pivotcols):
|
|
555
|
+
if M.matrix[i][j] != one:
|
|
556
|
+
raise ValueError("Matrix is not invertible!")
|
|
428
557
|
return M[:, n:]
|
|
429
558
|
|
|
430
|
-
def solve(self, b: list|tuple
|
|
559
|
+
def solve(self, b: list|tuple) -> "Matrix":
|
|
431
560
|
"Solve the linear system with given inhomogenous vector."
|
|
432
561
|
if isinstance(b, list|tuple):
|
|
433
|
-
b =
|
|
562
|
+
b = self.__class__(b)
|
|
434
563
|
if self.rows != b.rows or b.cols != 1:
|
|
435
564
|
raise ValueError("Matrix dimensions do not match.")
|
|
436
|
-
A = self
|
|
437
|
-
A
|
|
438
|
-
A[:, self.cols] = b
|
|
565
|
+
A = self[:,:]
|
|
566
|
+
A.append_column(b)
|
|
439
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
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
571
|
+
# the matrix is over a ring (not a field)
|
|
572
|
+
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
|
|
605
|
+
return None
|
|
606
|
+
if solution is not None:
|
|
607
|
+
return R * Matrix(solution)
|
|
608
|
+
next_solution = True
|
|
609
|
+
return None
|
|
610
|
+
# the matrix is over a field
|
|
440
611
|
solution = self.zeros(self.cols, 1)
|
|
441
|
-
for i in
|
|
442
|
-
|
|
443
|
-
if A.matrix[i][-1]:
|
|
444
|
-
return None # Not solvable
|
|
445
|
-
else:
|
|
446
|
-
for j in range(A.cols - 1):
|
|
447
|
-
if A.matrix[i][j]:
|
|
448
|
-
break # leading nonzero coefficient
|
|
449
|
-
solution[j]= A.matrix[i][-1]
|
|
612
|
+
for i, j in enumerate(A.pivotcols):
|
|
613
|
+
solution[j] = A.matrix[i][-1]
|
|
450
614
|
return solution
|
|
451
615
|
|
|
452
616
|
def is_unimodular(self) -> bool:
|
|
@@ -520,7 +684,6 @@ def circulant(vector: list|tuple, m: int = None, ring=None) -> "Matrix":
|
|
|
520
684
|
vector = list(reversed(vector))
|
|
521
685
|
return Matrix([rotate(vector, -n-1) for n in range(m)], ring=ring)
|
|
522
686
|
|
|
523
|
-
|
|
524
687
|
class BinaryMatrix:
|
|
525
688
|
"""
|
|
526
689
|
Binary Matrix class.
|
|
@@ -528,7 +691,7 @@ class BinaryMatrix:
|
|
|
528
691
|
Example:
|
|
529
692
|
|
|
530
693
|
To define a binary matrix use
|
|
531
|
-
>>>
|
|
694
|
+
>>> BinaryMatrix([[0, 1], [1, 0]])
|
|
532
695
|
[01]
|
|
533
696
|
[10]
|
|
534
697
|
"""
|
|
@@ -793,7 +956,7 @@ class BinaryMatrix:
|
|
|
793
956
|
return res
|
|
794
957
|
return self.from_bits(res)
|
|
795
958
|
|
|
796
|
-
def rref(self, start = 0, reduce = True, drop_zero_rows = False) -> "Matrix":
|
|
959
|
+
def rref(self, start: int = 0, reduce: bool = True, drop_zero_rows: bool = False) -> "Matrix":
|
|
797
960
|
"Row reduced echelon form."
|
|
798
961
|
rref = [ None ] * self.cols # store rows accoring to leading bit
|
|
799
962
|
rows = self.matrix[:] # copy
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
Lattice tools
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from math import prod, floor
|
|
5
|
+
from math import prod, floor, inf
|
|
6
|
+
from itertools import product
|
|
6
7
|
from fractions import Fraction
|
|
7
8
|
from random import choice, sample
|
|
8
|
-
from .la import Matrix, zeros
|
|
9
|
+
from .la import Matrix, eye, zeros
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def hermite_nf(M: Matrix) -> Matrix:
|
|
@@ -13,7 +14,7 @@ def hermite_nf(M: Matrix) -> Matrix:
|
|
|
13
14
|
n, m = M.cols, M.rows
|
|
14
15
|
H = M[:, :]
|
|
15
16
|
j = n - 1
|
|
16
|
-
for i in
|
|
17
|
+
for i in range(m-1,-1,-1):
|
|
17
18
|
j0 = j
|
|
18
19
|
minimum = abs(H[i, j]) # search for the pivot in the present row
|
|
19
20
|
for jj in range(j):
|
|
@@ -24,10 +25,7 @@ def hermite_nf(M: Matrix) -> Matrix:
|
|
|
24
25
|
if minimum == 0:
|
|
25
26
|
continue # all entries are zero
|
|
26
27
|
if j0 < j:
|
|
27
|
-
H[:, j], H[:, j0] =
|
|
28
|
-
H[:, j0],
|
|
29
|
-
H[:, j],
|
|
30
|
-
) # swap columns, to move the pivot in place
|
|
28
|
+
H[:, j], H[:, j0] = H[:, j0], H[:, j] # swap columns, to move the pivot in place
|
|
31
29
|
if H[i, j] < 0:
|
|
32
30
|
H[:, j] *= -1 # make the pivot positive
|
|
33
31
|
jj = j - 1
|
|
@@ -41,7 +39,6 @@ def hermite_nf(M: Matrix) -> Matrix:
|
|
|
41
39
|
for jj in range(j + 1, n): # reduce the row entries right to the pivot
|
|
42
40
|
tmp = H[i, jj] // H[i, j]
|
|
43
41
|
H[:, jj] -= tmp * H[:, j]
|
|
44
|
-
#print(H)
|
|
45
42
|
j -= 1
|
|
46
43
|
if j < 0:
|
|
47
44
|
break
|
|
@@ -55,22 +52,32 @@ def norm2(v: Matrix) -> float:
|
|
|
55
52
|
return sum(map(lambda x: x * x, v))
|
|
56
53
|
|
|
57
54
|
|
|
58
|
-
def gram_schmidt(U: Matrix) -> (Matrix, Matrix):
|
|
55
|
+
def gram_schmidt(U: Matrix, drop_dependent: bool = True) -> (Matrix, Matrix):
|
|
59
56
|
"Compute the Gram-Schmidt orthogonalization of the column vectors of a matrix M."
|
|
60
|
-
M = U.eye()
|
|
61
|
-
Us = U
|
|
62
|
-
|
|
57
|
+
M = U.eye(U.cols)
|
|
58
|
+
Us = U.zeros()
|
|
59
|
+
Us[:, 0] = U[:, 0]
|
|
60
|
+
jj = 0 # offset taking removed vectors into account
|
|
61
|
+
for j in range(1, U.cols):
|
|
63
62
|
tmp = U[:, j]
|
|
64
|
-
for i in range(j):
|
|
65
|
-
M[i, j] = U[:, j].dot(Us[:, i]) /
|
|
66
|
-
tmp -= M[i, j] * Us[:, i]
|
|
67
|
-
|
|
63
|
+
for i in range(j - jj):
|
|
64
|
+
M[i, j - jj] = U[:, j].dot(Us[:, i]) / Us[:, i].norm2()
|
|
65
|
+
tmp -= M[i, j - jj] * Us[:, i]
|
|
66
|
+
if not tmp:
|
|
67
|
+
if not drop_dependent:
|
|
68
|
+
raise ValueError("Vectors are linearly dependent.")
|
|
69
|
+
jj += 1
|
|
70
|
+
else:
|
|
71
|
+
Us[:, j - jj] = tmp
|
|
72
|
+
if jj:
|
|
73
|
+
Us = Us[:,:-jj]
|
|
74
|
+
M = M[:-jj,:-jj]
|
|
68
75
|
return Us, M
|
|
69
76
|
|
|
70
77
|
|
|
71
78
|
def gram_det(U: Matrix) -> float:
|
|
72
79
|
"Compute the Gram determinant of a matrix."
|
|
73
|
-
Us = gram_schmidt(U)[0]
|
|
80
|
+
Us = gram_schmidt(U, drop_dependent = False)[0]
|
|
74
81
|
return prod([Us[:, i].norm() for i in range(U.rows)])
|
|
75
82
|
|
|
76
83
|
|
|
@@ -109,7 +116,7 @@ def babai_plane_bnd(U: Matrix, p=2) -> float:
|
|
|
109
116
|
|
|
110
117
|
def lagrange_lr(V: Matrix) -> Matrix:
|
|
111
118
|
"Lagrange lattice reduction."
|
|
112
|
-
if
|
|
119
|
+
if V.cols != 2:
|
|
113
120
|
raise ValueError("Lagrange lattice reduction requires dimension two.")
|
|
114
121
|
v1, v2 = V[:, 0], V[:, 1]
|
|
115
122
|
if norm2(v1) > norm2(v2):
|
|
@@ -130,10 +137,10 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
130
137
|
U.map(int)
|
|
131
138
|
Us = U[:, :]
|
|
132
139
|
Us.map(Fraction)
|
|
133
|
-
M = U.zeros()
|
|
140
|
+
M = U.zeros(U.cols)
|
|
134
141
|
M.map(Fraction)
|
|
135
142
|
M[0, 0] = norm2(Us[:, 0]) # we store the squared norms on the diagonal
|
|
136
|
-
for l in range(1, U.
|
|
143
|
+
for l in range(1, U.cols): # Gram-Schmidt decomposition
|
|
137
144
|
tmp = U[:, l]
|
|
138
145
|
for i in range(l):
|
|
139
146
|
M[i, l] = U[:, l].dot(Us[:, i]) / M[i, i]
|
|
@@ -141,7 +148,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
141
148
|
Us[:, l] = tmp
|
|
142
149
|
M[l, l] = norm2(Us[:, l])
|
|
143
150
|
|
|
144
|
-
while j < U.
|
|
151
|
+
while j < U.cols:
|
|
145
152
|
for i in range(j - 1, -1, -1): # reduce the weights of the basis vectors
|
|
146
153
|
r = round(M[i, j])
|
|
147
154
|
if r:
|
|
@@ -172,7 +179,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
172
179
|
M[l, j], M[l, j - 1] = M[l, j - 1], M[l, j]
|
|
173
180
|
tmp1 = oldM00 / M[j - 1, j - 1]
|
|
174
181
|
tmp2 = oldM10 * oldM11 / M[j - 1, j - 1]
|
|
175
|
-
for l in range(j + 1, U.
|
|
182
|
+
for l in range(j + 1, U.cols):
|
|
176
183
|
M[j - 1, l], M[j, l] = (
|
|
177
184
|
tmp1 * M[j, l] + tmp2 * M[j - 1, l],
|
|
178
185
|
M[j - 1, l] - oldM10 * M[j, l],
|
|
@@ -180,9 +187,9 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix:
|
|
|
180
187
|
j = max(j - 1, 1) # redo the last step
|
|
181
188
|
|
|
182
189
|
if sort: # sort the vectors according to their norm
|
|
183
|
-
tmp = [U[:, j] for j in range(U.
|
|
190
|
+
tmp = [U[:, j] for j in range(U.cols)]
|
|
184
191
|
tmp.sort(key=norm2)
|
|
185
|
-
for j in range(U.
|
|
192
|
+
for j in range(U.cols):
|
|
186
193
|
U[:, j] = tmp[j]
|
|
187
194
|
return U
|
|
188
195
|
|
|
@@ -204,3 +211,95 @@ def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = None)
|
|
|
204
211
|
if not max_val or max(abs(x) for x in tmp) <= max_val:
|
|
205
212
|
W[:, i] = tmp
|
|
206
213
|
return W
|
|
214
|
+
|
|
215
|
+
def q_ary_lattice(U: Matrix, lll: bool = False) -> Matrix: # pylint: disable=W0621
|
|
216
|
+
"Create a q-ary lattice and (optinally) LLL reduce the basis."
|
|
217
|
+
if isinstance(U, Matrix) and hasattr(U.matrix[0][0], "ring"):
|
|
218
|
+
q = U.matrix[0][0].ring.n
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError("The matrix does not seem to be over Zmod.")
|
|
221
|
+
V = eye(U.rows, one = q)
|
|
222
|
+
V.append_column(U, ring = int)
|
|
223
|
+
V = hermite_nf(V)
|
|
224
|
+
if lll:
|
|
225
|
+
return globals()['lll'](V)
|
|
226
|
+
return V
|
|
227
|
+
|
|
228
|
+
def svp_lll(U: Matrix) -> Matrix:
|
|
229
|
+
"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)
|
|
232
|
+
x = V[:, 0]
|
|
233
|
+
x.map(gf)
|
|
234
|
+
return x
|
|
235
|
+
|
|
236
|
+
def cvp_lll(U: Matrix, x: Matrix) -> Matrix:
|
|
237
|
+
"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)
|
|
240
|
+
x.map(int)
|
|
241
|
+
y = babai_plane_cvp(x, V)
|
|
242
|
+
x.map(gf)
|
|
243
|
+
return y
|
|
244
|
+
|
|
245
|
+
def sis_lll(A: Matrix) -> Matrix:
|
|
246
|
+
"Solve the short integer problem over a finite ring Z_q (SIS)."
|
|
247
|
+
return svp_lll(A.kernel())
|
|
248
|
+
|
|
249
|
+
def isis_lll(A: Matrix, b: Matrix) -> Matrix:
|
|
250
|
+
"Solve the inhomogenous short integer problem over a finite ring Z_q (ISIS)."
|
|
251
|
+
y = A.solve(b)
|
|
252
|
+
return y - cvp_lll(A.kernel(), y)
|
|
253
|
+
|
|
254
|
+
def svp_search(U: Matrix, m: int = 0, p: int = 2) -> Matrix:
|
|
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
|
|
257
|
+
if m:
|
|
258
|
+
ml, mu = -m, m + 1
|
|
259
|
+
else:
|
|
260
|
+
ml, mu = -((gf.n-1)//2), gf.n//2 + 1
|
|
261
|
+
U.map(int)
|
|
262
|
+
norm_min = inf
|
|
263
|
+
c_min = None
|
|
264
|
+
for j in range(U.cols):
|
|
265
|
+
for c in product(range(ml, mu), repeat = U.cols-j-1):
|
|
266
|
+
for cc in range(1, mu):
|
|
267
|
+
cc = U * Matrix([ 0 ] * j + [ cc ] + list(c))
|
|
268
|
+
cc.map(gf)
|
|
269
|
+
norm = cc.norm(p)
|
|
270
|
+
if norm < norm_min:
|
|
271
|
+
norm_min = norm
|
|
272
|
+
c_min = cc
|
|
273
|
+
U.map(gf)
|
|
274
|
+
return c_min
|
|
275
|
+
|
|
276
|
+
def cvp_search(U: Matrix, b: Matrix, p: int = 2) -> Matrix:
|
|
277
|
+
"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)
|
|
281
|
+
norm_min = inf
|
|
282
|
+
c_min = None
|
|
283
|
+
for c in product(range(-((gf.n-1)//2), gf.n//2 + 1), repeat = U.cols):
|
|
284
|
+
c = U * Matrix(c)
|
|
285
|
+
d = c - b
|
|
286
|
+
d.map(gf)
|
|
287
|
+
norm = d.norm(p)
|
|
288
|
+
if norm < norm_min:
|
|
289
|
+
norm_min = norm
|
|
290
|
+
c.map(gf)
|
|
291
|
+
c_min = c
|
|
292
|
+
U.map(gf)
|
|
293
|
+
b.map(gf)
|
|
294
|
+
return c_min
|
|
295
|
+
|
|
296
|
+
def sis_search(A: Matrix, p: int = 2) -> Matrix:
|
|
297
|
+
"Solve the short integer problem over a finite ring Z_q (SIS)."
|
|
298
|
+
return svp_search(A.kernel(), p = p)
|
|
299
|
+
|
|
300
|
+
def isis_search(A: Matrix, b: Matrix, p: int = 2) -> Matrix:
|
|
301
|
+
"Solve the inhomogenous short integer problem over a finite ring Z_q (ISIS)."
|
|
302
|
+
y = A.solve(b)
|
|
303
|
+
if y is None:
|
|
304
|
+
return None
|
|
305
|
+
return y - cvp_search(A.kernel(), y, p = p)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
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
|
|
@@ -44,7 +44,7 @@ The tools contained are:
|
|
|
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
46
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
47
|
-
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
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
50
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
|
|
@@ -45,10 +45,11 @@ def test_Zmod_methods():
|
|
|
45
45
|
assert [abs(i) for i in Z_5] == [0, 1, 2, 2, 1]
|
|
46
46
|
assert not Z_5(0)
|
|
47
47
|
assert Z_5(1)
|
|
48
|
-
assert Z_5(2) == Z_5(
|
|
48
|
+
assert Z_5(2) == Z_5(7)
|
|
49
49
|
assert Z_5(2) != Z_5(3)
|
|
50
50
|
assert Z_5(2) != 2
|
|
51
51
|
assert str(Z_5(3)) == "3"
|
|
52
|
+
assert Z_5(2).solve(3) == 3 * Z_5(2)**-1
|
|
52
53
|
Z_5.short = False
|
|
53
54
|
assert str(Z_5(6)) == "1 (mod 5)"
|
|
54
55
|
|
|
@@ -67,10 +68,12 @@ def test_Zmod_methods():
|
|
|
67
68
|
assert [abs(i) for i in Z_6] == [0, 1, 2, 3, 2, 1]
|
|
68
69
|
assert not Z_6(0)
|
|
69
70
|
assert Z_6(1)
|
|
70
|
-
assert Z_6(2) == Z_6(
|
|
71
|
+
assert Z_6(2) == Z_6(8)
|
|
71
72
|
assert Z_6(2) != Z_6(3)
|
|
72
73
|
assert Z_5(2) != Z_6(2)
|
|
73
74
|
assert str(Z_6(3)) == "3"
|
|
75
|
+
assert Z_6(2).solve(4) == Z_6(2)
|
|
76
|
+
assert Z_6(2).solve(3) is None
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
def test_Zmod_order():
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# pragma pylint: disable=C0114,C0116
|
|
2
2
|
import pytest # pylint: disable=W0611
|
|
3
|
-
from
|
|
3
|
+
from math import gcd # pylint: disable=C0411
|
|
4
4
|
from random import randint, seed # pylint: disable=C0411
|
|
5
|
+
from fractions import Fraction # pylint: disable=C0411
|
|
5
6
|
from kryptools import Matrix, Zmod, GF2, eye, circulant, BinaryMatrix
|
|
6
7
|
|
|
7
8
|
|
|
@@ -36,11 +37,39 @@ def test_Matrix():
|
|
|
36
37
|
Mi = M.inv()
|
|
37
38
|
assert M * Mi == M.eye()
|
|
38
39
|
|
|
40
|
+
def test_Matrix2():
|
|
41
|
+
num_tests = 100
|
|
42
|
+
seed(0)
|
|
43
|
+
for ring in Zmod(7), Zmod(8), Zmod(10):
|
|
44
|
+
for m,n in ((3, 5), (5, 3), (3, 3)):
|
|
45
|
+
for _ in range(num_tests):
|
|
46
|
+
A = Matrix([[ring.random() for _ in range(m)] for _ in range(n)])
|
|
47
|
+
b = Matrix([ring.random() for _ in range(n)])
|
|
48
|
+
AA = A.applyfunc(lambda x: Fraction(int(x)))
|
|
49
|
+
if n == m:
|
|
50
|
+
d = A.det()
|
|
51
|
+
assert d == ring(AA.det())
|
|
52
|
+
if gcd(d.x, ring.n) == 1:
|
|
53
|
+
assert A.inv() * A == A.eye()
|
|
54
|
+
else:
|
|
55
|
+
with pytest.raises(ValueError):
|
|
56
|
+
A.inv()
|
|
57
|
+
x = A.solve(b)
|
|
58
|
+
if x is not None:
|
|
59
|
+
assert A * x == b
|
|
60
|
+
else:
|
|
61
|
+
if n == m:
|
|
62
|
+
assert gcd(d.x, ring.n) != 1
|
|
63
|
+
xx = Matrix([ring.random() for _ in range(m)])
|
|
64
|
+
b = A * xx
|
|
65
|
+
x = A.solve(b)
|
|
66
|
+
assert A * x == b
|
|
67
|
+
|
|
39
68
|
def test_kernel():
|
|
40
69
|
for gf in Zmod(2), Zmod(7), GF2(1), GF2(8):
|
|
41
70
|
for m,n in ((3, 5), (5, 3), (3, 3)):
|
|
42
71
|
for _ in range(25):
|
|
43
|
-
A = Matrix([[gf.random() for _ in range(m)] for _ in range(n)]
|
|
72
|
+
A = Matrix([[gf.random() for _ in range(m)] for _ in range(n)])
|
|
44
73
|
K = A.kernel()
|
|
45
74
|
assert not A * K
|
|
46
75
|
assert K.rows == A.cols
|
|
@@ -84,7 +113,7 @@ def test_BinaryMatrix():
|
|
|
84
113
|
assert BinaryMatrix(Mat.inv()) == mat.inv()
|
|
85
114
|
assert Mat.rank() == mat.rank()
|
|
86
115
|
sol = mat.solve(b)
|
|
87
|
-
Sol = Mat.solve(b
|
|
116
|
+
Sol = Mat.solve(Z_2(b))
|
|
88
117
|
assert (sol is None and Sol is None) or list(Sol.applyfunc(int)) == sol
|
|
89
118
|
if mat.rows == 1:
|
|
90
119
|
assert int(Mat * X) == mat.apply(x)[0]
|
|
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
|
|
File without changes
|
|
File without changes
|