kryptools 1.5.2__tar.gz → 1.6__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.5.2 → kryptools-1.6}/PKG-INFO +3 -2
- {kryptools-1.5.2 → kryptools-1.6}/README.md +2 -1
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/GF2.py +52 -38
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/Zmod.py +8 -5
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/__init__.py +2 -2
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/code.py +90 -58
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_qs.py +2 -2
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/la.py +54 -2
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/poly.py +26 -4
- {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/PKG-INFO +3 -2
- {kryptools-1.5.2 → kryptools-1.6}/pyproject.toml +1 -1
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_GF2.py +12 -1
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_Zmod.py +5 -2
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_code.py +33 -1
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_la.py +14 -1
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_poly.py +9 -0
- {kryptools-1.5.2 → kryptools-1.6}/LICENSE +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/aes.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/blockcipher.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/des.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/ec.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_dix.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_rho.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/intfuncs.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/keccak.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/lat.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/nt.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/primes.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools/sha1.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/setup.cfg +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_aes.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_des.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_dlog.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_ec.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor_qs.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_intfuncs.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_keccak.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_lat.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_nt.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/tests/test_primes.py +0 -0
- {kryptools-1.5.2 → kryptools-1.6}/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.6
|
|
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
|
|
@@ -47,13 +47,14 @@ The tools contained are:
|
|
|
47
47
|
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
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, etc.)
|
|
50
|
+
* Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
|
|
51
51
|
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
52
52
|
* Zmod: a class for the ring of integers modulo an integer
|
|
53
53
|
* GF2: a class for Galois fields GF(2^n)
|
|
54
54
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
55
55
|
* CyclicCode: a class for encoding and decoding cyclic codes
|
|
56
56
|
* ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
|
|
57
|
+
* BCHCode: a class for encoding and decoding primitive narrow sense BCH codes
|
|
57
58
|
|
|
58
59
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
59
60
|
* AESCipher: individual AES operations
|
|
@@ -29,13 +29,14 @@ The tools contained are:
|
|
|
29
29
|
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
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, etc.)
|
|
32
|
+
* Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
|
|
33
33
|
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
34
34
|
* Zmod: a class for the ring of integers modulo an integer
|
|
35
35
|
* GF2: a class for Galois fields GF(2^n)
|
|
36
36
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
37
37
|
* CyclicCode: a class for encoding and decoding cyclic codes
|
|
38
38
|
* ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
|
|
39
|
+
* BCHCode: a class for encoding and decoding primitive narrow sense BCH codes
|
|
39
40
|
|
|
40
41
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
41
42
|
* AESCipher: individual AES operations
|
|
@@ -61,8 +61,12 @@ class GF2:
|
|
|
61
61
|
|
|
62
62
|
def __call__(self, x: int | list | tuple | range | map):
|
|
63
63
|
if isinstance(x, list|tuple|range|map):
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if isinstance(x, bytes | bytearray):
|
|
65
|
+
return [ GF2nPoint(int.from_bytes(x, byteorder=self.byteorder), self) for xx in x]
|
|
66
|
+
return [GF2nPoint(int(xx) % self.order, self) for xx in x]
|
|
67
|
+
if isinstance(x, bytes | bytearray):
|
|
68
|
+
return GF2nPoint(int.from_bytes(x, byteorder=self.byteorder), self)
|
|
69
|
+
return GF2nPoint(int(x) % self.order, self)
|
|
66
70
|
|
|
67
71
|
def __len__(self):
|
|
68
72
|
return self.order
|
|
@@ -80,26 +84,26 @@ class GF2:
|
|
|
80
84
|
return isinstance(other, GF2nPoint) and self == other.field
|
|
81
85
|
|
|
82
86
|
def _factors(self) -> dict:
|
|
83
|
-
"Return the factroization
|
|
87
|
+
"Return the factroization of the group order of GF(2^n)^*."
|
|
84
88
|
if not self.factors:
|
|
85
89
|
self.factors = factorint(self.mult_order)
|
|
86
90
|
return self.factors
|
|
87
91
|
|
|
88
92
|
def zero(self) -> bool:
|
|
89
93
|
"Return zero."
|
|
90
|
-
return
|
|
94
|
+
return GF2nPoint(0, self)
|
|
91
95
|
|
|
92
96
|
def one(self) -> bool:
|
|
93
97
|
"Return one."
|
|
94
98
|
if self.bitreversed:
|
|
95
|
-
return
|
|
96
|
-
return
|
|
99
|
+
return GF2nPoint(self.order >> 1, self)
|
|
100
|
+
return GF2nPoint(1, self)
|
|
97
101
|
|
|
98
102
|
def random(self, num: int = 0) -> "GF2nPoint":
|
|
99
103
|
"Return a single random point or a list of random points."
|
|
100
104
|
if num:
|
|
101
|
-
return([
|
|
102
|
-
return
|
|
105
|
+
return([GF2nPoint(randint(0,self.order-1), self) for _ in range(num)])
|
|
106
|
+
return GF2nPoint(randint(0,self.order-1), self)
|
|
103
107
|
|
|
104
108
|
def is_cyclic(self) -> bool:
|
|
105
109
|
"GF(2^n)^* is cyclic."
|
|
@@ -112,9 +116,9 @@ class GF2:
|
|
|
112
116
|
def generator(self) -> int | None:
|
|
113
117
|
"Return a generator of the group GF(2^n)^*."
|
|
114
118
|
if self.degree == 1:
|
|
115
|
-
return self(
|
|
119
|
+
return self.one()
|
|
116
120
|
for a in range(2, self.order):
|
|
117
|
-
a =
|
|
121
|
+
a = GF2nPoint(a, self)
|
|
118
122
|
if a.is_generator():
|
|
119
123
|
return a
|
|
120
124
|
|
|
@@ -129,16 +133,21 @@ class GF2:
|
|
|
129
133
|
def star(self) -> list:
|
|
130
134
|
"Return a generator for all elements of the group GF(2^n)n^*."
|
|
131
135
|
for x in range(1, self.order):
|
|
132
|
-
yield
|
|
136
|
+
yield GF2nPoint(x, self)
|
|
133
137
|
|
|
138
|
+
def poly(self) -> Poly:
|
|
139
|
+
"Return the associated irreducible polynomial."
|
|
140
|
+
coeff = [int(d) for d in str(
|
|
141
|
+
format(self.modulus, "b"))]
|
|
142
|
+
if self.bitreversed:
|
|
143
|
+
return Poly(coeff + [1])
|
|
144
|
+
return Poly(list(reversed(coeff)))
|
|
134
145
|
|
|
135
146
|
class GF2nPoint:
|
|
136
147
|
"Represents a point in the Galois field GF(2^n)."
|
|
137
148
|
|
|
138
149
|
def __init__(self, x: int, field: "GF2"):
|
|
139
|
-
|
|
140
|
-
x = int.from_bytes(x, byteorder=field.byteorder)
|
|
141
|
-
self.x = int(x) % field.order
|
|
150
|
+
self.x = x
|
|
142
151
|
self.field = field
|
|
143
152
|
|
|
144
153
|
def __repr__(self):
|
|
@@ -162,13 +171,11 @@ class GF2nPoint:
|
|
|
162
171
|
|
|
163
172
|
def bits(self) -> list:
|
|
164
173
|
"Convert to a list of bits."
|
|
174
|
+
coeff = [int(d) for d in str(
|
|
175
|
+
format(self.x, "0" + str(self.field.degree) + "b"))]
|
|
165
176
|
if self.field.bitreversed:
|
|
166
|
-
coeff
|
|
167
|
-
|
|
168
|
-
else:
|
|
169
|
-
coeff = reversed([int(d) for d in str(
|
|
170
|
-
format(self.x, "0" + str(self.field.degree) + "b"))])
|
|
171
|
-
return list(coeff)
|
|
177
|
+
return coeff
|
|
178
|
+
return list(reversed(coeff))
|
|
172
179
|
|
|
173
180
|
def poly(self) -> "Poly":
|
|
174
181
|
"Convert to a polynomial."
|
|
@@ -193,26 +200,20 @@ class GF2nPoint:
|
|
|
193
200
|
return NotImplemented
|
|
194
201
|
if self.field != other.field:
|
|
195
202
|
raise NotImplementedError("Cannot add elements from different fields.")
|
|
196
|
-
return self.
|
|
203
|
+
return self.__class__(self.x ^ other.x, self.field)
|
|
197
204
|
|
|
198
|
-
|
|
199
|
-
return self
|
|
205
|
+
__sub__ = __add__
|
|
200
206
|
|
|
201
207
|
def __pos__(self) -> "GF2nPoint":
|
|
202
208
|
return self
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
if not isinstance(other, self.__class__):
|
|
206
|
-
return NotImplemented
|
|
207
|
-
if self.field != other.field:
|
|
208
|
-
raise NotImplementedError("Cannot add elements from different fields.")
|
|
209
|
-
return self.field(self.x ^ other.x)
|
|
210
|
+
__neg__ = __pos__
|
|
210
211
|
|
|
211
212
|
def __mul__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
212
213
|
if isinstance(other, int):
|
|
213
214
|
if other % 2:
|
|
214
215
|
return self
|
|
215
|
-
return self.
|
|
216
|
+
return self.__class__(0, self.field)
|
|
216
217
|
if not isinstance(other, self.__class__):
|
|
217
218
|
return NotImplemented
|
|
218
219
|
if self.field != other.field:
|
|
@@ -238,13 +239,13 @@ class GF2nPoint:
|
|
|
238
239
|
y <<= 1
|
|
239
240
|
if y & bitmap1:
|
|
240
241
|
y ^= self.field.modulus
|
|
241
|
-
return self.
|
|
242
|
+
return self.__class__(z, self.field)
|
|
242
243
|
|
|
243
244
|
def __rmul__(self, scalar: int) -> "GF2nPoint":
|
|
244
245
|
if isinstance(scalar, int):
|
|
245
246
|
if scalar % 2:
|
|
246
247
|
return self
|
|
247
|
-
return self.
|
|
248
|
+
return self.__class__(0, self.field)
|
|
248
249
|
return NotImplemented
|
|
249
250
|
|
|
250
251
|
def __truediv__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
@@ -261,14 +262,16 @@ class GF2nPoint:
|
|
|
261
262
|
return NotImplemented
|
|
262
263
|
if not (self.x) and j < 0:
|
|
263
264
|
raise ValueError("Division by zero.")
|
|
265
|
+
j %= self.field.order - 1
|
|
264
266
|
if self.field.order == 2:
|
|
267
|
+
if j == 0:
|
|
268
|
+
return self.field.one()
|
|
265
269
|
return self
|
|
266
270
|
if self.field.bitreversed:
|
|
267
|
-
res = self.
|
|
271
|
+
res = self.__class__(self.field.order >> 1, self.field)
|
|
268
272
|
else:
|
|
269
|
-
res = self.
|
|
270
|
-
x = self.
|
|
271
|
-
j %= self.field.order - 1
|
|
273
|
+
res = self.__class__(1, self.field)
|
|
274
|
+
x = self.__class__(self.x, self.field)
|
|
272
275
|
while j > 0:
|
|
273
276
|
# If j is odd, multiply with x
|
|
274
277
|
if j & 1:
|
|
@@ -282,13 +285,13 @@ class GF2nPoint:
|
|
|
282
285
|
"Cyclic rotation to the left."
|
|
283
286
|
x = self.x << j
|
|
284
287
|
x = (x % self.field.order) + (x // self.field.order)
|
|
285
|
-
return self.
|
|
288
|
+
return self.__class__(self.x, self.field)
|
|
286
289
|
|
|
287
290
|
def __rshift__(self, j: int) -> "GF2nPoint":
|
|
288
291
|
"Cyclic rotation to the right."
|
|
289
292
|
x = self.x >> j
|
|
290
293
|
x += (self.field.order >> j) * (self.x % 2**j)
|
|
291
|
-
return self.
|
|
294
|
+
return self.__class__(self.x, self.field)
|
|
292
295
|
|
|
293
296
|
def sqrt(self) -> "GF2nPoint":
|
|
294
297
|
"Compute the square root."
|
|
@@ -314,6 +317,17 @@ class GF2nPoint:
|
|
|
314
317
|
"Test if the point is a generator of the group GF(2^n)^*."
|
|
315
318
|
return self.field.mult_order == self.order()
|
|
316
319
|
|
|
320
|
+
def minpoly(self) -> Poly:
|
|
321
|
+
"Return the minimal polynomial of an element."
|
|
322
|
+
aj = self
|
|
323
|
+
mpoly = Poly([aj, self.field.one()])
|
|
324
|
+
for _ in range(1, self.field.degree):
|
|
325
|
+
aj = aj**2 # Frobenius
|
|
326
|
+
if aj == self:
|
|
327
|
+
break
|
|
328
|
+
mpoly *= Poly([aj, self.field.one()])
|
|
329
|
+
return mpoly
|
|
330
|
+
|
|
317
331
|
def sbox(self, inv: bool = False) -> "GF2nPoint":
|
|
318
332
|
"Apply the AES sbox."
|
|
319
333
|
if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
|
|
@@ -43,8 +43,8 @@ class Zmod:
|
|
|
43
43
|
|
|
44
44
|
def __call__(self, x: int | list | tuple | range | map):
|
|
45
45
|
if isinstance(x, list|tuple|range|map):
|
|
46
|
-
return [ZmodPoint(xx, self) for xx in x]
|
|
47
|
-
return ZmodPoint(x, self)
|
|
46
|
+
return [ZmodPoint(int(xx), self) for xx in x]
|
|
47
|
+
return ZmodPoint(int(x), self)
|
|
48
48
|
|
|
49
49
|
def __iter__(self):
|
|
50
50
|
for x in range(self.n):
|
|
@@ -157,7 +157,7 @@ class ZmodPoint:
|
|
|
157
157
|
"Represents a point in the ring Zmod."
|
|
158
158
|
|
|
159
159
|
def __init__(self, x: int, ring: "Zmod"):
|
|
160
|
-
self.x =
|
|
160
|
+
self.x = x % ring.n
|
|
161
161
|
self.ring = ring
|
|
162
162
|
|
|
163
163
|
def __repr__(self):
|
|
@@ -247,8 +247,11 @@ class ZmodPoint:
|
|
|
247
247
|
|
|
248
248
|
def sharp(self):
|
|
249
249
|
"Returns a symmetric (w.r.t. 0) representative."
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
if self.x <= self.ring.n // 2:
|
|
251
|
+
return self.x
|
|
252
|
+
return self.x - self.ring.n
|
|
253
|
+
|
|
254
|
+
mods = sharp
|
|
252
255
|
|
|
253
256
|
def order(self) -> int:
|
|
254
257
|
"Compute the order of the point in the group Z_n^*."
|
|
@@ -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.6"
|
|
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
|
|
@@ -19,7 +19,7 @@ from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round
|
|
|
19
19
|
from .poly import Poly, lagrange_interpolation
|
|
20
20
|
from .Zmod import Zmod
|
|
21
21
|
from .GF2 import GF2, GF2_aes, GF2_miniaes, GF2_ghash
|
|
22
|
-
from .code import
|
|
22
|
+
from .code import gen2pchk, hamming_dist, Goppa, CyclicCode, ReedSolomonCode, BCHCode
|
|
23
23
|
from .blockcipher import BlockCipher, bytexor
|
|
24
24
|
from .aes import AESKeySchedule, AESCipher, AESBlockCipher
|
|
25
25
|
from .des import list2int, int2list, permute, invert_permutation, rotate_left, SDESKeySchedule, SDESCipher, DESKeySchedule, DESCipher, DESBlockCipher
|
|
@@ -5,8 +5,8 @@ Linear codes
|
|
|
5
5
|
from math import prod
|
|
6
6
|
from .Zmod import Zmod
|
|
7
7
|
from .GF2 import GF2
|
|
8
|
-
from .poly import Poly
|
|
9
|
-
from .la import Matrix
|
|
8
|
+
from .poly import Poly
|
|
9
|
+
from .la import Matrix
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def poly2bits(x: Poly) -> list:
|
|
@@ -16,45 +16,13 @@ def poly2bits(x: Poly) -> list:
|
|
|
16
16
|
out += c.bits()
|
|
17
17
|
return out
|
|
18
18
|
|
|
19
|
-
def left_standard_form(
|
|
19
|
+
def left_standard_form(G: Matrix) -> (Matrix, Matrix):
|
|
20
20
|
"Compute the left standard form of a generator matrix. Return the standard form and the pertmutation matrix."
|
|
21
|
-
|
|
22
|
-
M = M.rref()
|
|
23
|
-
# purge zero rows
|
|
24
|
-
for i in range(M.rows-1,-1,-1):
|
|
25
|
-
if not M[i,:]:
|
|
26
|
-
M.delete_rows(i)
|
|
27
|
-
# permute columns to get the identity on the left
|
|
28
|
-
last = min(M.rows, M.cols)
|
|
29
|
-
P = eye(M.cols)
|
|
30
|
-
for i in range(last):
|
|
31
|
-
if not M[i,i]: # the diagonal entry vanishes
|
|
32
|
-
# find the index of the pivot and permute
|
|
33
|
-
j = i + 1
|
|
34
|
-
while j <= M.cols and not M[i, j]:
|
|
35
|
-
j += 1
|
|
36
|
-
P[:,i], P[:,j] = P[:,j], P[:,i]
|
|
37
|
-
M[:,i], M[:,j] = M[:,j], M[:,i]
|
|
38
|
-
return M, P
|
|
21
|
+
return G.left_standard_form()
|
|
39
22
|
|
|
40
23
|
def gen2pchk(G: Matrix) -> Matrix:
|
|
41
24
|
"Compute the parity check matrix from a given generator matrix (and vice versa)."
|
|
42
|
-
G
|
|
43
|
-
d = G.cols - G.rows
|
|
44
|
-
if d <= 0:
|
|
45
|
-
raise ValueError("A generator matrix must have more columns than rows!")
|
|
46
|
-
zero = 0 * G[0]
|
|
47
|
-
one = zero**0
|
|
48
|
-
# we start with a zero matrix
|
|
49
|
-
H = zeros(d, G.cols, zero = zero)
|
|
50
|
-
# create the identity on the right
|
|
51
|
-
for i in range(d):
|
|
52
|
-
H[i,G.rows+i] = one
|
|
53
|
-
# add minus the transpose of the right part of G as the left part
|
|
54
|
-
tmp = - G[:,-d:].transpose()
|
|
55
|
-
for i in range(G.cols - d):
|
|
56
|
-
H[:, i] = tmp[:, i]
|
|
57
|
-
return H * P.transpose()
|
|
25
|
+
return G.kernel().transpose()
|
|
58
26
|
|
|
59
27
|
def hamming_dist(a: iter, b: iter):
|
|
60
28
|
"Hamming distance of two iterables."
|
|
@@ -102,7 +70,7 @@ class Goppa():
|
|
|
102
70
|
self.G = gen2pchk(self.H)
|
|
103
71
|
|
|
104
72
|
def __repr__(self):
|
|
105
|
-
return f"Binary irreducible Goppa code over GF(2^{self.gf.
|
|
73
|
+
return f"Binary irreducible Goppa [{self.G.cols}, {self.G.rows}] code over GF(2^{self.gf.degree}) with polynomial g(x) = {self.g}."
|
|
106
74
|
|
|
107
75
|
def encode(self, x: list[int]):
|
|
108
76
|
"Encode a given list of bits."
|
|
@@ -217,13 +185,18 @@ class CyclicCode():
|
|
|
217
185
|
raise ValueError(f"Degree can be at most {self.n-1}.")
|
|
218
186
|
x, r = y.divmod(self.g)
|
|
219
187
|
if r:
|
|
220
|
-
|
|
188
|
+
y = self.correct(y)
|
|
189
|
+
x, r = y.divmod(self.g)
|
|
221
190
|
if systematic_form:
|
|
222
191
|
x.coeff = y.coeff[self.g.degree():]
|
|
223
192
|
if as_list:
|
|
224
193
|
return x.coeff + [zero] * (self.k - len(x.coeff))
|
|
225
194
|
return x
|
|
226
195
|
|
|
196
|
+
def correct(self, y: Poly):
|
|
197
|
+
"Correct a given polynomial."
|
|
198
|
+
raise NotImplementedError("Error correction not implemented.")
|
|
199
|
+
|
|
227
200
|
def generator_matrix(self):
|
|
228
201
|
"Return the generator matrix of the code."
|
|
229
202
|
zero = 0 * self.g[0]
|
|
@@ -246,12 +219,12 @@ class ReedSolomonCode(CyclicCode):
|
|
|
246
219
|
>>> rsc = ReedSolomonCode(k, gf)
|
|
247
220
|
|
|
248
221
|
To encode a list of letters (the length must be equal to the dimension k of the code; conversion to the Galois field is done automatically):
|
|
249
|
-
>>>
|
|
250
|
-
[
|
|
222
|
+
>>> rsc.encode([1, 2, 3])
|
|
223
|
+
[6, 4, 5, 1, 8, 4, 2, 9, 2, 8, 9, 6]
|
|
251
224
|
|
|
252
|
-
|
|
253
|
-
>>>
|
|
254
|
-
[
|
|
225
|
+
To decode use:
|
|
226
|
+
>>> rsc.decode([1, 4, 4, 0, 8, 4, 2, 9, 2, 0, 9, 6])
|
|
227
|
+
[1, 2, 3]
|
|
255
228
|
|
|
256
229
|
"""
|
|
257
230
|
def __init__(self, k:int, gf, alpha = None): # pylint: disable=W0231
|
|
@@ -265,7 +238,9 @@ class ReedSolomonCode(CyclicCode):
|
|
|
265
238
|
self.order = n + 1 # order of the base field
|
|
266
239
|
self.n = n # lenght
|
|
267
240
|
self.k = k # dimension
|
|
268
|
-
|
|
241
|
+
if k >= n:
|
|
242
|
+
raise ValueError(f"Code dimension k = {k} must be smaller than the code lenght n = {self.n}.")
|
|
243
|
+
self.d = n - k + 1 # minimal distance
|
|
269
244
|
self.modulus = Poly([-one] + [zero] * (n-1) + [one])
|
|
270
245
|
if alpha is None:
|
|
271
246
|
if hasattr(gf, "primitive_element"):
|
|
@@ -279,9 +254,9 @@ class ReedSolomonCode(CyclicCode):
|
|
|
279
254
|
assert self.g * self.h == self.modulus
|
|
280
255
|
|
|
281
256
|
def __repr__(self):
|
|
282
|
-
return f"Reed-Solomon
|
|
257
|
+
return f"Reed-Solomon [{self.n}, {self.k}, {self.d}] code over GF({self.n+1})."
|
|
283
258
|
|
|
284
|
-
def encode(self, x: list|Poly): # pylint: disable=W0221
|
|
259
|
+
def encode(self, x: list|Poly) -> list|Poly: # pylint: disable=W0221
|
|
285
260
|
"Encode a given list or polynomial."
|
|
286
261
|
as_list = False
|
|
287
262
|
zero = 0 * self.g[0]
|
|
@@ -295,7 +270,7 @@ class ReedSolomonCode(CyclicCode):
|
|
|
295
270
|
return y + [zero] * (self.n - len(y))
|
|
296
271
|
return Poly(y)
|
|
297
272
|
|
|
298
|
-
def decode(self, y: list|Poly): # pylint: disable=W0221
|
|
273
|
+
def decode(self, y: list|Poly) -> list|Poly: # pylint: disable=W0221
|
|
299
274
|
"Decode a given list or polynomial."
|
|
300
275
|
as_list = False
|
|
301
276
|
zero = 0 * self.g[0]
|
|
@@ -309,19 +284,76 @@ class ReedSolomonCode(CyclicCode):
|
|
|
309
284
|
x = Poly(xx)
|
|
310
285
|
if x.degree() >= self.k: # No codewort
|
|
311
286
|
t = (self.n - self.k) // 2
|
|
287
|
+
if not t:
|
|
288
|
+
raise ValueError("This code cannot correct any errors.")
|
|
312
289
|
A = Matrix([[ xx[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
|
|
313
290
|
b = [ -xx[self.k + t + l ] for l in range(t) ]
|
|
314
291
|
lam = Poly([one] + list(A.solve(b)))
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
292
|
+
lamp = lam.derivative()
|
|
293
|
+
s = Poly([x[self.k + j] for j in range(self.n - self.k)])
|
|
294
|
+
omega = s * lam
|
|
295
|
+
omega = Poly(omega.coeff[:self.n - self.k]) # mod x^(n-k)
|
|
296
|
+
# Chien search
|
|
297
|
+
lam = lam.coeff
|
|
298
|
+
alphai = [ self.alpha**i for i in range(len(lam)) ]
|
|
299
|
+
for i in range(self.n):
|
|
300
|
+
if not sum(lam, start = zero):
|
|
301
|
+
# Fornay
|
|
302
|
+
ali = self.alpha**i
|
|
303
|
+
y[i] -= ali**(self.k-1) * omega(ali) / lamp(ali)
|
|
304
|
+
for j in range(len(lam)): # pylint: disable=C0200
|
|
305
|
+
lam[j] *= alphai[j]
|
|
306
|
+
x = Poly([ -y(self.alpha**(-j)) for j in range(self.n) ])
|
|
325
307
|
if as_list:
|
|
326
308
|
return x.coeff + [zero] * (self.k - len(x.coeff))
|
|
327
309
|
return x
|
|
310
|
+
|
|
311
|
+
def correct(self, y: Poly):
|
|
312
|
+
"Correct a given polynomial."
|
|
313
|
+
x = self.decode(y)
|
|
314
|
+
return self.encode(x)
|
|
315
|
+
|
|
316
|
+
class BCHCode(CyclicCode):
|
|
317
|
+
"""
|
|
318
|
+
Binary narrow sense primitive BCH code.
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
|
|
322
|
+
>>> n = 7 # lenght of the code
|
|
323
|
+
>>> D = 2 # desired distance
|
|
324
|
+
>>> bch = BCHCode(k, D)
|
|
325
|
+
|
|
326
|
+
To encode a list of letters (the length must be equal to the dimension k of the code; conversion to the Galois field is done automatically):
|
|
327
|
+
>>> bch.encode([0, 1, 0, 1])
|
|
328
|
+
[0, 1, 1, 1, 0, 0, 1]
|
|
329
|
+
|
|
330
|
+
To decode use:
|
|
331
|
+
>>> bch.decode([0, 1, 1, 1, 0, 0, 1])
|
|
332
|
+
[0, 1, 0, 1]
|
|
333
|
+
|
|
334
|
+
"""
|
|
335
|
+
def __init__(self, n:int, D: int):
|
|
336
|
+
m = n.bit_length()
|
|
337
|
+
if 2**m - 1 != n:
|
|
338
|
+
raise ValueError(f"The codelenght n={n} plus one must be a power of 2.")
|
|
339
|
+
self.rsc = ReedSolomonCode(n - D + 1, GF2(m))
|
|
340
|
+
# Compute the generator polynomial
|
|
341
|
+
aj = self.rsc.alpha
|
|
342
|
+
g = aj.minpoly()
|
|
343
|
+
for _ in range(2,D):
|
|
344
|
+
aj *= self.rsc.alpha
|
|
345
|
+
mj = aj.minpoly()
|
|
346
|
+
g = g.lcm(mj)
|
|
347
|
+
g.map(GF2(1))
|
|
348
|
+
super().__init__(n, g)
|
|
349
|
+
|
|
350
|
+
def __repr__(self):
|
|
351
|
+
return f"BCH [{self.n}, {self.k}] code over GF({self.order}) with generator polynomial g(x) = {self.g}."
|
|
352
|
+
|
|
353
|
+
def correct(self, y: Poly):
|
|
354
|
+
"Correct a given polynomial."
|
|
355
|
+
y.map(self.rsc.gf)
|
|
356
|
+
x = self.rsc.decode(y)
|
|
357
|
+
y = self.rsc.encode(x)
|
|
358
|
+
y.map(GF2(1))
|
|
359
|
+
return y
|
|
@@ -13,13 +13,13 @@ def bytexor(a: bytearray, b: bytes) -> bytes:
|
|
|
13
13
|
a[i] ^= b[i]
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def byteset(a: bytearray, i: int) ->
|
|
16
|
+
def byteset(a: bytearray, i: int) -> None:
|
|
17
17
|
"""Set the i'th bit in the bytearray `a` to 1."""
|
|
18
18
|
i1, i0 = divmod(i, 8)
|
|
19
19
|
a[i1] |= 2**i0
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def bytetest(a: bytes, i: int) ->
|
|
22
|
+
def bytetest(a: bytes, i: int) -> bool:
|
|
23
23
|
"""Test if the i'th bit in the bytestring `a` is 1."""
|
|
24
24
|
i1, i0 = divmod(i, 8)
|
|
25
25
|
return (a[i1] & 2**i0) != 0
|
|
@@ -169,7 +169,9 @@ class Matrix:
|
|
|
169
169
|
|
|
170
170
|
def append_row(self, row: list|tuple, ring = None) -> None:
|
|
171
171
|
"Append a row."
|
|
172
|
-
if not isinstance(row, list|tuple)
|
|
172
|
+
if not isinstance(row, list|tuple):
|
|
173
|
+
row = list(row)
|
|
174
|
+
if len(row) != self.cols:
|
|
173
175
|
raise ValueError("Length does not match the number of columns.")
|
|
174
176
|
if ring:
|
|
175
177
|
row =list(map(ring, row))
|
|
@@ -178,7 +180,9 @@ class Matrix:
|
|
|
178
180
|
|
|
179
181
|
def append_column(self, col: list|tuple, ring = None) -> None:
|
|
180
182
|
"Append a column."
|
|
181
|
-
if not isinstance(col, list|tuple)
|
|
183
|
+
if not isinstance(col, list|tuple):
|
|
184
|
+
col = list(col)
|
|
185
|
+
if len(col) != self.rows:
|
|
182
186
|
raise ValueError("Length does not match the number of rows.")
|
|
183
187
|
if ring:
|
|
184
188
|
col =list(map(ring, col))
|
|
@@ -312,6 +316,50 @@ class Matrix:
|
|
|
312
316
|
break
|
|
313
317
|
return R
|
|
314
318
|
|
|
319
|
+
def left_standard_form(self) -> ("Matrix", "Matrix"):
|
|
320
|
+
"Compute the left standard form of a matrix. Return the standard form and the pertmutation matrix."
|
|
321
|
+
# reduced row echelon form
|
|
322
|
+
M = self.rref()
|
|
323
|
+
# purge zero rows
|
|
324
|
+
for i in range(M.rows-1,-1,-1):
|
|
325
|
+
if not M[i,:]:
|
|
326
|
+
M.delete_rows(i)
|
|
327
|
+
# permute columns to get the identity on the left
|
|
328
|
+
last = min(M.rows, M.cols)
|
|
329
|
+
P = M.eye(M.cols)
|
|
330
|
+
for i in range(last):
|
|
331
|
+
if not M[i,i]: # the diagonal entry vanishes
|
|
332
|
+
# find the index of the pivot and permute
|
|
333
|
+
j = i + 1
|
|
334
|
+
while j <= M.cols and not M[i, j]:
|
|
335
|
+
j += 1
|
|
336
|
+
P[:,i], P[:,j] = P[:,j], P[:,i]
|
|
337
|
+
M[:,i], M[:,j] = M[:,j], M[:,i]
|
|
338
|
+
return M, P
|
|
339
|
+
|
|
340
|
+
def kernel(self) -> "Matrix":
|
|
341
|
+
"Compute a basis for the kernel of a matrix."
|
|
342
|
+
_, one = self._guess_zero()
|
|
343
|
+
M = self.rref()
|
|
344
|
+
pivotcols = []
|
|
345
|
+
nonpivotcols = []
|
|
346
|
+
j = 0
|
|
347
|
+
for i in range(0, M.rows):
|
|
348
|
+
while j < M.cols and not M[i,j]:
|
|
349
|
+
nonpivotcols.append(j)
|
|
350
|
+
j += 1
|
|
351
|
+
if j == M.cols:
|
|
352
|
+
break
|
|
353
|
+
pivotcols.append(j)
|
|
354
|
+
j += 1
|
|
355
|
+
nonpivotcols += list(range(j, M.cols))
|
|
356
|
+
K = M.zeros(M.cols, max(1,len(nonpivotcols)))
|
|
357
|
+
for k, j in enumerate(nonpivotcols):
|
|
358
|
+
K[j, k] = one
|
|
359
|
+
for l, i in enumerate(pivotcols):
|
|
360
|
+
K[i, k] = - M[l, j]
|
|
361
|
+
return K
|
|
362
|
+
|
|
315
363
|
def det(self) -> int:
|
|
316
364
|
"Compute the determinant of a matrix M."
|
|
317
365
|
zero, one = self._guess_zero()
|
|
@@ -349,6 +397,10 @@ class Matrix:
|
|
|
349
397
|
return i+1
|
|
350
398
|
return 0
|
|
351
399
|
|
|
400
|
+
def nullity(self) -> int:
|
|
401
|
+
"Compute the nillity of a matrix M."
|
|
402
|
+
return self.cols - self.rank()
|
|
403
|
+
|
|
352
404
|
def inv(self) -> "Matrix":
|
|
353
405
|
"Compute the inverse of a square matrix M."
|
|
354
406
|
if self.rows != self.cols:
|
|
@@ -18,6 +18,7 @@ class Poly:
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
print_reversed = True # print the terms in reversed order
|
|
21
|
+
print_latex = False # print in latex format
|
|
21
22
|
print_x = "x" # variable for printing
|
|
22
23
|
print_pow = "^" # you can change this to "**" if you want it python style
|
|
23
24
|
|
|
@@ -63,16 +64,20 @@ class Poly:
|
|
|
63
64
|
def __hash__(self):
|
|
64
65
|
return hash(tuple(self.coeff))
|
|
65
66
|
|
|
66
|
-
def __repr__(self, latex=
|
|
67
|
+
def __repr__(self, latex=None):
|
|
67
68
|
def prx(i: int) -> str:
|
|
68
69
|
if i == 0:
|
|
69
70
|
return ""
|
|
70
71
|
if i == 1:
|
|
71
72
|
return self.__class__.print_x
|
|
72
73
|
if latex:
|
|
74
|
+
if i < 10:
|
|
75
|
+
return self.__class__.print_x + "^" + str(i)
|
|
73
76
|
return self.__class__.print_x + "^{" + str(i) + "}"
|
|
74
77
|
return self.__class__.print_x + self.__class__.print_pow + str(i)
|
|
75
78
|
|
|
79
|
+
if latex is None:
|
|
80
|
+
latex = self.__class__.print_latex
|
|
76
81
|
if not self:
|
|
77
82
|
return str(self.coeff[0])
|
|
78
83
|
one = self.coeff[0]**0
|
|
@@ -130,6 +135,10 @@ class Poly:
|
|
|
130
135
|
"Return the degree."
|
|
131
136
|
return len(self.coeff) - 1
|
|
132
137
|
|
|
138
|
+
def weight(self) -> int:
|
|
139
|
+
"Return the number of nonzero coefficients."
|
|
140
|
+
return sum(map(bool,self.coeff))
|
|
141
|
+
|
|
133
142
|
def map(self, func):
|
|
134
143
|
"Apply a given function to all coefficients in place."
|
|
135
144
|
self.coeff = list(map(func, self.coeff))
|
|
@@ -294,7 +303,7 @@ class Poly:
|
|
|
294
303
|
return self.__class__([zero]), self
|
|
295
304
|
div = [zero] * (sd - od + 1)
|
|
296
305
|
lco = other.coeff[-1]
|
|
297
|
-
if
|
|
306
|
+
if lco != one:
|
|
298
307
|
tmp = one / lco
|
|
299
308
|
oth = [c * tmp for c in other.coeff]
|
|
300
309
|
rem = [c * tmp for c in self.coeff]
|
|
@@ -306,7 +315,7 @@ class Poly:
|
|
|
306
315
|
div[sd - od - i] = tmp
|
|
307
316
|
for j in range(od + 1):
|
|
308
317
|
rem[sd - i - j] -= tmp * oth[od - j]
|
|
309
|
-
if
|
|
318
|
+
if lco != one:
|
|
310
319
|
rem = [c * lco for c in rem]
|
|
311
320
|
return self.__class__(div, modulus=self.modulus), self.__class__(rem, modulus=self.modulus)
|
|
312
321
|
|
|
@@ -323,7 +332,7 @@ class Poly:
|
|
|
323
332
|
if sd < od:
|
|
324
333
|
return self
|
|
325
334
|
lco = other.coeff[-1]
|
|
326
|
-
if
|
|
335
|
+
if lco != one:
|
|
327
336
|
tmp = one / lco
|
|
328
337
|
oth = [c * tmp for c in other.coeff]
|
|
329
338
|
else:
|
|
@@ -359,12 +368,15 @@ class Poly:
|
|
|
359
368
|
if r0.degree() != 0:
|
|
360
369
|
raise ValueError(f"{self} is not invertible mod {other}.")
|
|
361
370
|
y0.modulus = self.modulus
|
|
371
|
+
if r0.coeff[0] == one:
|
|
372
|
+
return y0
|
|
362
373
|
return y0 / r0.coeff[0]
|
|
363
374
|
|
|
364
375
|
def gcd(self, other: "Poly") -> "Poly":
|
|
365
376
|
"Greates common divisor with a given polynomial."
|
|
366
377
|
if not isinstance(other, self.__class__):
|
|
367
378
|
raise NotImplementedError(f"Cannot compute gcd: {other} must be a polynomial.")
|
|
379
|
+
_, one, _ = self._guess_ring()
|
|
368
380
|
if not self:
|
|
369
381
|
if not other:
|
|
370
382
|
return Poly([self.coeff[0]])
|
|
@@ -375,8 +387,14 @@ class Poly:
|
|
|
375
387
|
while r1:
|
|
376
388
|
r0, r1 = r1, r0.divmod(r1)[1]
|
|
377
389
|
r0.modulus = None
|
|
390
|
+
if r0.coeff[-1] == one:
|
|
391
|
+
return r0
|
|
378
392
|
return r0 / r0.coeff[-1]
|
|
379
393
|
|
|
394
|
+
def lcm(self, other: "Poly") -> "Poly":
|
|
395
|
+
"Least common multiple with a given polynomial."
|
|
396
|
+
return self * other // self.gcd(other)
|
|
397
|
+
|
|
380
398
|
def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
|
|
381
399
|
"""Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
|
|
382
400
|
zero, one, _ = self._guess_ring()
|
|
@@ -385,6 +403,8 @@ class Poly:
|
|
|
385
403
|
if not self:
|
|
386
404
|
if not other:
|
|
387
405
|
return Poly([zero]), Poly([zero]), Poly([zero])
|
|
406
|
+
if other.coeff[-1] == one:
|
|
407
|
+
return other, Poly([zero]), one
|
|
388
408
|
return other / other.coeff[-1], Poly([zero]), one / other.coeff[-1]
|
|
389
409
|
r0, r1 = other, self
|
|
390
410
|
x0, x1 = self.__class__([one]), self.__class__([zero])
|
|
@@ -395,6 +415,8 @@ class Poly:
|
|
|
395
415
|
x0, x1 = x1, x0 - x1 * q
|
|
396
416
|
y0, y1 = y1, y0 - y1 * q
|
|
397
417
|
lcr0 = r0.coeff[-1]
|
|
418
|
+
if lcr0 == one:
|
|
419
|
+
return r0, x0, y0
|
|
398
420
|
return r0 / lcr0, x0 / lcr0, y0 / lcr0
|
|
399
421
|
|
|
400
422
|
def derivative(self) -> "Poly":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6
|
|
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
|
|
@@ -47,13 +47,14 @@ The tools contained are:
|
|
|
47
47
|
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
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, etc.)
|
|
50
|
+
* Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
|
|
51
51
|
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
52
52
|
* Zmod: a class for the ring of integers modulo an integer
|
|
53
53
|
* GF2: a class for Galois fields GF(2^n)
|
|
54
54
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
55
55
|
* CyclicCode: a class for encoding and decoding cyclic codes
|
|
56
56
|
* ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
|
|
57
|
+
* BCHCode: a class for encoding and decoding primitive narrow sense BCH codes
|
|
57
58
|
|
|
58
59
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
59
60
|
* AESCipher: individual AES operations
|
|
@@ -4,7 +4,7 @@ from random import randint, seed # pylint: disable=C0411
|
|
|
4
4
|
from kryptools import GF2, euler_phi
|
|
5
5
|
seed(0)
|
|
6
6
|
|
|
7
|
-
num_tests =
|
|
7
|
+
num_tests = 25
|
|
8
8
|
|
|
9
9
|
def test_GF2_ops():
|
|
10
10
|
for n, modulus in [(4, None), (8, None), (8, 0b100011011), (12, None), (128, 0b11100001 << 120)]:
|
|
@@ -14,6 +14,10 @@ def test_GF2_ops():
|
|
|
14
14
|
assert gf(1) == gf(1)
|
|
15
15
|
assert gf(1) != gf(0)
|
|
16
16
|
assert gf(0) != 0
|
|
17
|
+
assert 0 * gf.one() == gf.zero()
|
|
18
|
+
assert 1 * gf.one() == gf.one()
|
|
19
|
+
assert 2 * gf.one() == gf.zero()
|
|
20
|
+
assert gf.zero()**0 == gf.one()
|
|
17
21
|
if gf.degree < 17:
|
|
18
22
|
assert len(list(gf)) == gf.order
|
|
19
23
|
assert len(list(gf.star())) == gf.order - 1
|
|
@@ -21,9 +25,16 @@ def test_GF2_ops():
|
|
|
21
25
|
for _ in range(num_tests):
|
|
22
26
|
a = gf(randint(0, gf.order-1))
|
|
23
27
|
b = gf(randint(0, gf.order-1))
|
|
28
|
+
assert a * gf.one() == a
|
|
29
|
+
assert gf.one() * a == a
|
|
30
|
+
assert a**0 == gf.one()
|
|
31
|
+
assert a**1 == a
|
|
32
|
+
assert a**2 == a * a
|
|
33
|
+
assert a * b == b * a
|
|
24
34
|
assert (a + b).poly() == a.poly() + b.poly()
|
|
25
35
|
assert (a - b).poly() == a.poly() - b.poly()
|
|
26
36
|
assert (a * b).poly() == a.poly() * b.poly()
|
|
37
|
+
assert (a**2).poly() == a.poly()**2
|
|
27
38
|
if a:
|
|
28
39
|
assert (a**-1).poly() == a.poly().inv()
|
|
29
40
|
if b:
|
|
@@ -41,8 +41,8 @@ def test_Zmod_methods():
|
|
|
41
41
|
assert Z_5(3).is_generator() is True
|
|
42
42
|
assert list(Z_5.generators()) == Z_5([2, 3])
|
|
43
43
|
assert list(Z_5.star()) == Z_5([1, 2, 3, 4])
|
|
44
|
-
assert [
|
|
45
|
-
assert [abs(
|
|
44
|
+
assert [i.sharp() for i in Z_5] == [0, 1, 2, -2, -1]
|
|
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
48
|
assert Z_5(2) == Z_5(2)
|
|
@@ -63,6 +63,9 @@ def test_Zmod_methods():
|
|
|
63
63
|
assert Z_6(5).is_generator() is True
|
|
64
64
|
assert list(Z_6.generators()) == Z_6([5])
|
|
65
65
|
assert list(Z_6.star()) == Z_6([1, 5])
|
|
66
|
+
assert [i.sharp() for i in Z_6] == [0, 1, 2, 3, -2, -1]
|
|
67
|
+
assert [abs(i) for i in Z_6] == [0, 1, 2, 3, 2, 1]
|
|
68
|
+
assert not Z_6(0)
|
|
66
69
|
assert Z_6(1)
|
|
67
70
|
assert Z_6(2) == Z_6(2)
|
|
68
71
|
assert Z_6(2) != Z_6(3)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import pytest # pylint: disable=W0611
|
|
3
3
|
from random import randint, seed # pylint: disable=C0411
|
|
4
4
|
from kryptools import Zmod, GF2, Poly, Matrix
|
|
5
|
-
from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode
|
|
5
|
+
from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode, BCHCode
|
|
6
6
|
|
|
7
7
|
seed(0)
|
|
8
8
|
Z_2 = Zmod(2)
|
|
@@ -33,6 +33,7 @@ def test_Goppa():
|
|
|
33
33
|
alpha = list(gf)
|
|
34
34
|
|
|
35
35
|
goppa = Goppa(gf, g, alpha)
|
|
36
|
+
assert not goppa.H * goppa.G.transpose()
|
|
36
37
|
for _ in range(num_tests):
|
|
37
38
|
x = [ randint(0,1) for _ in range(goppa.G.rows) ]
|
|
38
39
|
y = goppa.encode(x)
|
|
@@ -45,6 +46,12 @@ def test_Cyclic():
|
|
|
45
46
|
for gf, n, g in [ [Zmod(2), 4, [1, 1]], [Zmod(3), 4, [-1, 1]], [Zmod(11), 5, [4, 6, 1]] ]:
|
|
46
47
|
g = Poly(g, ring = gf)
|
|
47
48
|
cc = CyclicCode(n, g)
|
|
49
|
+
G = cc.generator_matrix()
|
|
50
|
+
H = cc.check_matrix()
|
|
51
|
+
assert not H * G.transpose()
|
|
52
|
+
H2 = gen2pchk(G)
|
|
53
|
+
assert H2.rows == H.rows
|
|
54
|
+
assert not H2 * G.transpose()
|
|
48
55
|
for _ in range(num_tests):
|
|
49
56
|
x = [ gf(randint(0, cc.order-1)) for _ in range(cc.k) ]
|
|
50
57
|
y = cc.encode(x)
|
|
@@ -53,6 +60,12 @@ def test_Cyclic():
|
|
|
53
60
|
def test_ReedSolomon():
|
|
54
61
|
for gf, k in [ [Zmod(13), 3], [GF2(6), 4]]:
|
|
55
62
|
rsc = ReedSolomonCode(k, gf)
|
|
63
|
+
G = rsc.generator_matrix()
|
|
64
|
+
H = rsc.check_matrix()
|
|
65
|
+
assert not H * G.transpose()
|
|
66
|
+
H2 = gen2pchk(G)
|
|
67
|
+
assert H2.rows == H.rows
|
|
68
|
+
assert not H2 * G.transpose()
|
|
56
69
|
t = (rsc.n - rsc.k) // 2
|
|
57
70
|
for _ in range(num_tests):
|
|
58
71
|
x = [ gf(randint(0, rsc.n)) for _ in range(rsc.k) ]
|
|
@@ -61,3 +74,22 @@ def test_ReedSolomon():
|
|
|
61
74
|
i = randint(0, rsc.n-1)
|
|
62
75
|
y[i] += gf(1)
|
|
63
76
|
assert rsc.decode(y) == x
|
|
77
|
+
|
|
78
|
+
def test_BCH():
|
|
79
|
+
gf = GF2(1)
|
|
80
|
+
for n, D in [ [7, 3], [15, 5]]:
|
|
81
|
+
bch = BCHCode(n, D)
|
|
82
|
+
G = bch.generator_matrix()
|
|
83
|
+
H = bch.check_matrix()
|
|
84
|
+
assert not H * G.transpose()
|
|
85
|
+
H2 = gen2pchk(G)
|
|
86
|
+
assert H2.rows == H.rows
|
|
87
|
+
assert not H2 * G.transpose()
|
|
88
|
+
t = (D - 1) // 2
|
|
89
|
+
for _ in range(num_tests):
|
|
90
|
+
x = [ gf(randint(0, 1)) for _ in range(bch.k) ]
|
|
91
|
+
y = bch.encode(x)
|
|
92
|
+
for _ in range(t):
|
|
93
|
+
i = randint(0, bch.n-1)
|
|
94
|
+
y[i] += gf(1)
|
|
95
|
+
assert bch.decode(y) == x
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# pragma pylint: disable=C0114,C0116
|
|
2
2
|
import pytest # pylint: disable=W0611
|
|
3
3
|
from fractions import Fraction # pylint: disable=C0411
|
|
4
|
-
from kryptools import Matrix, Zmod
|
|
4
|
+
from kryptools import Matrix, Zmod, GF2
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_Matrix():
|
|
@@ -26,3 +26,16 @@ def test_Matrix():
|
|
|
26
26
|
M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 12]], ring=Z_11)
|
|
27
27
|
Mi = M.inv()
|
|
28
28
|
assert M * Mi == M.eye()
|
|
29
|
+
|
|
30
|
+
def test_kernel():
|
|
31
|
+
for gf in Zmod(2), Zmod(7), GF2(1), GF2(8):
|
|
32
|
+
for m,n in ((3, 5), (5, 3), (3, 3)):
|
|
33
|
+
for _ in range(25):
|
|
34
|
+
A = Matrix([[gf.random() for _ in range(m)] for _ in range(n)], ring = gf)
|
|
35
|
+
K = A.kernel()
|
|
36
|
+
assert not A * K
|
|
37
|
+
assert K.rows == A.cols
|
|
38
|
+
if K:
|
|
39
|
+
assert K.cols == A.nullity()
|
|
40
|
+
else:
|
|
41
|
+
assert K.cols == 1
|
|
@@ -8,9 +8,17 @@ seed(0)
|
|
|
8
8
|
num_tests = 10
|
|
9
9
|
|
|
10
10
|
def test_Poly():
|
|
11
|
+
assert Poly([0]).weight() == 0
|
|
12
|
+
assert Poly([1]).weight() == 1
|
|
13
|
+
assert Poly([1]).degree() == 0
|
|
11
14
|
p = Poly([2, 0, 2])
|
|
12
15
|
q = Poly([1, 2, 3, 4])
|
|
16
|
+
assert p.degree() == 2
|
|
17
|
+
assert p.weight() == 2
|
|
18
|
+
assert q.degree() == 3
|
|
19
|
+
assert q.weight() == 4
|
|
13
20
|
d, m = q.divmod(p)
|
|
21
|
+
assert q * p == p.gcd(q) * p.lcm(q)
|
|
14
22
|
assert d * p + m == q
|
|
15
23
|
for _ in range(num_tests):
|
|
16
24
|
p = Poly([randint(0,100) for _ in range(10)])
|
|
@@ -26,6 +34,7 @@ def test_Poly():
|
|
|
26
34
|
q = Poly([1, 2, 3, 4], ring = gf)
|
|
27
35
|
d, m = q.divmod(p)
|
|
28
36
|
assert d * p + m == q
|
|
37
|
+
assert q * p == p.gcd(q) * p.lcm(q)
|
|
29
38
|
for _ in range(num_tests):
|
|
30
39
|
p = Poly(gf.random(10))
|
|
31
40
|
q = Poly(gf.random(12))
|
|
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
|