kryptools 1.7__tar.gz → 1.8__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}/PKG-INFO +2 -2
- {kryptools-1.7 → kryptools-1.8}/README.md +1 -1
- {kryptools-1.7 → kryptools-1.8}/kryptools/Zmod.py +25 -5
- {kryptools-1.7 → kryptools-1.8}/kryptools/__init__.py +3 -2
- {kryptools-1.7 → kryptools-1.8}/kryptools/la.py +188 -57
- {kryptools-1.7 → kryptools-1.8}/kryptools/lat.py +123 -24
- {kryptools-1.7 → kryptools-1.8}/kryptools.egg-info/PKG-INFO +2 -2
- {kryptools-1.7 → kryptools-1.8}/pyproject.toml +1 -1
- {kryptools-1.7 → kryptools-1.8}/tests/test_Zmod.py +5 -2
- {kryptools-1.7 → kryptools-1.8}/tests/test_la.py +2 -2
- {kryptools-1.7 → kryptools-1.8}/LICENSE +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/GF2.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/aes.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/blockcipher.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/code.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/des.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/dlp.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/ec.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor_dix.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor_qs.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/factor_rho.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/intfuncs.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/keccak.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/nt.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/poly.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/primes.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools/sha1.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.7 → kryptools-1.8}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.7 → kryptools-1.8}/setup.cfg +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_GF2.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_aes.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_code.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_des.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_dlog.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_ec.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_factor.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_factor_qs.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_intfuncs.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_keccak.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_lat.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_nt.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_poly.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/tests/test_primes.py +0 -0
- {kryptools-1.7 → kryptools-1.8}/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
|
|
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,18 @@ class ZmodPoint:
|
|
|
320
328
|
roots.append((p**(j//2) * x) % pk)
|
|
321
329
|
return self.ring(crt(roots, powers))
|
|
322
330
|
|
|
331
|
+
def solve(self, b):
|
|
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
|
+
return self.ring(0)
|
|
338
|
+
g = gcd(self.x, self.ring.n)
|
|
339
|
+
if b % g:
|
|
340
|
+
return None
|
|
341
|
+
return self.ring(pow(self.x // g, -1, self.ring.n) * (b // g))
|
|
342
|
+
|
|
323
343
|
def is_generator(self):
|
|
324
344
|
"Test if the point is a generator of the group Z_n^*."
|
|
325
345
|
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"
|
|
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,85 @@ 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 = False
|
|
385
|
+
while not done:
|
|
386
|
+
M = M.hermite_rnf(start = start, drop_zero_rows = drop_zero_rows)
|
|
387
|
+
done = True
|
|
388
|
+
for i, j in enumerate(M.pivotcols):
|
|
389
|
+
pivot = M.matrix[i][j]
|
|
390
|
+
if pivot >= ring.n:
|
|
391
|
+
done = False
|
|
392
|
+
pivot %= ring.n
|
|
393
|
+
if pivot!=1 and gcd(pivot, ring.n) == 1: # we can make the pivot one
|
|
394
|
+
done = False
|
|
395
|
+
M.matrix[i][j] = 1
|
|
396
|
+
tmp = pow(pivot, -1, ring.n)
|
|
397
|
+
for k in range(j + 1, M.cols):
|
|
398
|
+
M.matrix[i][k] *= tmp
|
|
399
|
+
M.map(lambda x: x % ring.n)
|
|
400
|
+
M.map(ring)
|
|
401
|
+
return M
|
|
402
|
+
|
|
403
|
+
def hermite_rnf(self, start = 0, drop_zero_rows: bool = False) -> "Matrix":
|
|
404
|
+
"Compute the Hermite row normal form."
|
|
405
|
+
n, m = self.cols, self.rows
|
|
406
|
+
if not isinstance(self.matrix[0][0], int):
|
|
407
|
+
raise ValueError("Hermite normal form requires integer entries!")
|
|
408
|
+
H = [ self.matrix[i][:] for i in range(m) ]
|
|
409
|
+
pivotcols = []
|
|
410
|
+
nonpivotcols = []
|
|
411
|
+
i = 0
|
|
412
|
+
if start >= n:
|
|
413
|
+
raise ValueError("Start value cannot be beyond the last column.")
|
|
414
|
+
for j in range(start, n):
|
|
415
|
+
i0 = i
|
|
416
|
+
minimum = abs(H[i][j]) # search for the pivot in the present column
|
|
417
|
+
for ii in range(i + 1, m):
|
|
418
|
+
tmp = abs(H[ii][j])
|
|
419
|
+
if tmp > 0 and (tmp < minimum or minimum == 0):
|
|
420
|
+
minimum = tmp
|
|
421
|
+
i0 = ii
|
|
422
|
+
if minimum == 0:
|
|
423
|
+
nonpivotcols.append(j)
|
|
424
|
+
continue # all entrjes are zero
|
|
425
|
+
pivotcols.append(j)
|
|
426
|
+
if i0 > i:
|
|
427
|
+
H[i], H[i0] = H[i0], H[i] # swap rows, to move the pivot jn place
|
|
428
|
+
if H[i][j] < 0:
|
|
429
|
+
for k in range(n):
|
|
430
|
+
H[i][k] *= -1 # make the pivot positive
|
|
431
|
+
ii = i + 1
|
|
432
|
+
while ii < m: # make the column entries below to the pivot zero
|
|
433
|
+
tmp = H[ii][j] // H[i][j]
|
|
434
|
+
for k in range(n):
|
|
435
|
+
H[ii][k] -= tmp * H[i][k]
|
|
436
|
+
if H[ii][j]:
|
|
437
|
+
H[i], H[ii] = H[ii], H[i] # swap rows
|
|
438
|
+
else:
|
|
439
|
+
ii += 1
|
|
440
|
+
for ii in range(i): # reduce the column entries above to the pivot
|
|
441
|
+
tmp = H[ii][j] // H[i][j]
|
|
442
|
+
for k in range(n):
|
|
443
|
+
H[ii][k] -= tmp * H[i][k]
|
|
444
|
+
i += 1
|
|
445
|
+
if i >= m:
|
|
446
|
+
break
|
|
447
|
+
H = Matrix(H)
|
|
448
|
+
H.pivotcols = pivotcols
|
|
449
|
+
H.nonpivotcols = nonpivotcols + list(range(j+1, H.cols))
|
|
450
|
+
# purge zero rows
|
|
451
|
+
if drop_zero_rows:
|
|
452
|
+
l = len(H.pivotcols)
|
|
453
|
+
if not l:
|
|
454
|
+
l = 1 # do not delete all rows
|
|
455
|
+
del H.matrix[l:]
|
|
456
|
+
H.rows = l
|
|
457
|
+
return H
|
|
458
|
+
|
|
355
459
|
def left_standard_form(self) -> "Matrix":
|
|
356
460
|
"Compute the left standard form."
|
|
357
461
|
# reduced row echelon form
|
|
@@ -363,6 +467,8 @@ class Matrix:
|
|
|
363
467
|
|
|
364
468
|
def kernel(self) -> "Matrix":
|
|
365
469
|
"Compute a basis for the kernel."
|
|
470
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
471
|
+
raise NotImplementedError("The matrix must be over a field, not a ring.")
|
|
366
472
|
_, one = self._guess_zero()
|
|
367
473
|
M = self.rref(drop_zero_rows = True)
|
|
368
474
|
K = M.zeros(M.cols, max(1,len(M.nonpivotcols)))
|
|
@@ -374,31 +480,43 @@ class Matrix:
|
|
|
374
480
|
|
|
375
481
|
def det(self) -> int:
|
|
376
482
|
"Compute the determinant."
|
|
377
|
-
zero, one = self._guess_zero()
|
|
378
|
-
if self.rows != self.cols:
|
|
379
|
-
raise ValueError("Matrix must be square!")
|
|
380
483
|
n, m = self.cols, self.rows
|
|
381
|
-
|
|
484
|
+
if n != m:
|
|
485
|
+
raise ValueError("Matrix must be square!")
|
|
486
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
487
|
+
# the matrix is over a ring (not a field)
|
|
488
|
+
ring = self.matrix[0][0].ring
|
|
489
|
+
R = [ list(map(lambda x: Fraction(int(x)), self.matrix[i])) for i in range(m) ]
|
|
490
|
+
zero, one = ring(0), 1
|
|
491
|
+
else:
|
|
492
|
+
ring = None
|
|
493
|
+
R = [ self.matrix[i][:] for i in range(m) ]
|
|
494
|
+
zero, one = self._guess_zero()
|
|
382
495
|
D = one
|
|
383
496
|
i = 0
|
|
384
497
|
for j in range(n):
|
|
385
|
-
if not R[i
|
|
498
|
+
if not R[i][j]: # search for a nonzero entry in the present column
|
|
386
499
|
for ii in range(i+1, m):
|
|
387
|
-
if R[ii
|
|
500
|
+
if R[ii][j]:
|
|
388
501
|
D *= -one
|
|
389
|
-
R[i
|
|
502
|
+
R[i], R[ii] = R[ii], R[i] # swap rows
|
|
390
503
|
break
|
|
391
|
-
else:
|
|
392
|
-
return zero
|
|
393
|
-
if R[i
|
|
394
|
-
D *= R[i
|
|
395
|
-
|
|
396
|
-
|
|
504
|
+
else: # all entries are zero
|
|
505
|
+
return zero
|
|
506
|
+
if R[i][j] != one:
|
|
507
|
+
D *= R[i][j]
|
|
508
|
+
tmp = R[i][j]**-1
|
|
509
|
+
for k in range(n):
|
|
510
|
+
R[i][k] *= tmp # make the pivot one
|
|
511
|
+
for ii in range(i + 1, n): # remove the column entries below the pivot
|
|
397
512
|
if i == ii:
|
|
398
513
|
continue
|
|
399
|
-
tmp = R[ii
|
|
400
|
-
|
|
514
|
+
tmp = R[ii][j]
|
|
515
|
+
for k in range(n):
|
|
516
|
+
R[ii][k] -= tmp * R[i][k]
|
|
401
517
|
i += 1
|
|
518
|
+
if ring:
|
|
519
|
+
return ring(D)
|
|
402
520
|
return D
|
|
403
521
|
|
|
404
522
|
def rank(self) -> int:
|
|
@@ -425,12 +543,17 @@ class Matrix:
|
|
|
425
543
|
M = self.__class__(M).rref()
|
|
426
544
|
if not left and M.pivotcols[self.cols - 1] != self.cols -1:
|
|
427
545
|
raise ValueError("Matrix is not invertible!")
|
|
546
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
547
|
+
# the matrix is over a ring (not a field)
|
|
548
|
+
for i, j in enumerate(M.pivotcols):
|
|
549
|
+
if M.matrix[i][j] != one:
|
|
550
|
+
raise ValueError("Matrix is not invertible!")
|
|
428
551
|
return M[:, n:]
|
|
429
552
|
|
|
430
|
-
def solve(self, b: list|tuple
|
|
553
|
+
def solve(self, b: list|tuple) -> "Matrix":
|
|
431
554
|
"Solve the linear system with given inhomogenous vector."
|
|
432
555
|
if isinstance(b, list|tuple):
|
|
433
|
-
b = Matrix(b
|
|
556
|
+
b = Matrix(b)
|
|
434
557
|
if self.rows != b.rows or b.cols != 1:
|
|
435
558
|
raise ValueError("Matrix dimensions do not match.")
|
|
436
559
|
A = self.zeros(self.rows, self.cols + 1) # extended coefficient matrix
|
|
@@ -438,15 +561,24 @@ class Matrix:
|
|
|
438
561
|
A[:, self.cols] = b
|
|
439
562
|
A = A.rref(drop_zero_rows = True)
|
|
440
563
|
solution = self.zeros(self.cols, 1)
|
|
564
|
+
if not any(A.matrix[-1][:-1]):
|
|
565
|
+
if A.matrix[-1][-1]:
|
|
566
|
+
return None # Not solvable
|
|
567
|
+
if hasattr(self.matrix[0][0], "ring") and not self.matrix[0][0].ring.is_field():
|
|
568
|
+
# the matrix is over a ring (not a field)
|
|
569
|
+
ring = self.matrix[0][0].ring
|
|
570
|
+
else:
|
|
571
|
+
ring = None
|
|
441
572
|
for i in range(A.rows-1, -1, -1):
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
573
|
+
j = A.pivotcols[i]
|
|
574
|
+
b = A.matrix[i][-1]
|
|
575
|
+
if ring:
|
|
576
|
+
for k in range(j + 1, self.cols):
|
|
577
|
+
b -= A.matrix[i][k] * solution[k]
|
|
578
|
+
b = A.matrix[i][j].solve(b)
|
|
579
|
+
if b is None:
|
|
580
|
+
return None
|
|
581
|
+
solution[j] = b
|
|
450
582
|
return solution
|
|
451
583
|
|
|
452
584
|
def is_unimodular(self) -> bool:
|
|
@@ -520,7 +652,6 @@ def circulant(vector: list|tuple, m: int = None, ring=None) -> "Matrix":
|
|
|
520
652
|
vector = list(reversed(vector))
|
|
521
653
|
return Matrix([rotate(vector, -n-1) for n in range(m)], ring=ring)
|
|
522
654
|
|
|
523
|
-
|
|
524
655
|
class BinaryMatrix:
|
|
525
656
|
"""
|
|
526
657
|
Binary Matrix class.
|
|
@@ -528,7 +659,7 @@ class BinaryMatrix:
|
|
|
528
659
|
Example:
|
|
529
660
|
|
|
530
661
|
To define a binary matrix use
|
|
531
|
-
>>>
|
|
662
|
+
>>> BinaryMatrix([[0, 1], [1, 0]])
|
|
532
663
|
[01]
|
|
533
664
|
[10]
|
|
534
665
|
"""
|
|
@@ -793,7 +924,7 @@ class BinaryMatrix:
|
|
|
793
924
|
return res
|
|
794
925
|
return self.from_bits(res)
|
|
795
926
|
|
|
796
|
-
def rref(self, start = 0, reduce = True, drop_zero_rows = False) -> "Matrix":
|
|
927
|
+
def rref(self, start: int = 0, reduce: bool = True, drop_zero_rows: bool = False) -> "Matrix":
|
|
797
928
|
"Row reduced echelon form."
|
|
798
929
|
rref = [ None ] * self.cols # store rows accoring to leading bit
|
|
799
930
|
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
|
|
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,7 @@
|
|
|
1
1
|
# pragma pylint: disable=C0114,C0116
|
|
2
2
|
import pytest # pylint: disable=W0611
|
|
3
|
-
from fractions import Fraction # pylint: disable=C0411
|
|
4
3
|
from random import randint, seed # pylint: disable=C0411
|
|
4
|
+
from fractions import Fraction # pylint: disable=C0411
|
|
5
5
|
from kryptools import Matrix, Zmod, GF2, eye, circulant, BinaryMatrix
|
|
6
6
|
|
|
7
7
|
|
|
@@ -84,7 +84,7 @@ def test_BinaryMatrix():
|
|
|
84
84
|
assert BinaryMatrix(Mat.inv()) == mat.inv()
|
|
85
85
|
assert Mat.rank() == mat.rank()
|
|
86
86
|
sol = mat.solve(b)
|
|
87
|
-
Sol = Mat.solve(b
|
|
87
|
+
Sol = Mat.solve(Z_2(b))
|
|
88
88
|
assert (sol is None and Sol is None) or list(Sol.applyfunc(int)) == sol
|
|
89
89
|
if mat.rows == 1:
|
|
90
90
|
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
|