kryptools 1.3.5__tar.gz → 1.5__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.3.5 → kryptools-1.5}/PKG-INFO +4 -2
- {kryptools-1.3.5 → kryptools-1.5}/README.md +3 -1
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/GF2.py +31 -19
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/Zmod.py +24 -5
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/__init__.py +3 -3
- kryptools-1.5/kryptools/code.py +328 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_qs.py +1 -1
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/poly.py +208 -64
- {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/PKG-INFO +4 -2
- {kryptools-1.3.5 → kryptools-1.5}/pyproject.toml +1 -1
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_GF2.py +1 -1
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_code.py +22 -1
- kryptools-1.5/tests/test_poly.py +63 -0
- kryptools-1.3.5/kryptools/code.py +0 -140
- kryptools-1.3.5/tests/test_poly.py +0 -36
- {kryptools-1.3.5 → kryptools-1.5}/LICENSE +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/aes.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/blockcipher.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/des.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/ec.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_dix.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_qs.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_rho.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/intfuncs.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/keccak.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/la.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/lat.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/nt.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/primes.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools/sha1.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/setup.cfg +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_Zmod.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_aes.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_des.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_dlog.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_ec.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor_qs.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_intfuncs.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_keccak.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_la.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_lat.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_nt.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/tests/test_primes.py +0 -0
- {kryptools-1.3.5 → kryptools-1.5}/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.5
|
|
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
|
|
@@ -42,10 +42,12 @@ The tools contained are:
|
|
|
42
42
|
* linear codes: Hamming distance, left standard form, parity check matrix
|
|
43
43
|
|
|
44
44
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
45
|
-
* Poly: a class for polynomials (division, modulo)
|
|
45
|
+
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
46
46
|
* Zmod: a class for the ring of integers modulo an integer
|
|
47
47
|
* GF2: a class for Galois fields GF(2^n)
|
|
48
48
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
49
|
+
* CyclicCode: a class for encoding and decoding cyclic codes
|
|
50
|
+
* ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
|
|
49
51
|
|
|
50
52
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
51
53
|
* AESCipher: individual AES operations
|
|
@@ -24,10 +24,12 @@ The tools contained are:
|
|
|
24
24
|
* linear codes: Hamming distance, left standard form, parity check matrix
|
|
25
25
|
|
|
26
26
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
27
|
-
* Poly: a class for polynomials (division, modulo)
|
|
27
|
+
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
28
28
|
* Zmod: a class for the ring of integers modulo an integer
|
|
29
29
|
* GF2: a class for Galois fields GF(2^n)
|
|
30
30
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
31
|
+
* CyclicCode: a class for encoding and decoding cyclic codes
|
|
32
|
+
* ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
|
|
31
33
|
|
|
32
34
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
33
35
|
* AESCipher: individual AES operations
|
|
@@ -3,6 +3,7 @@ Galois field GF(2^n).
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from math import gcd
|
|
6
|
+
from random import randint
|
|
6
7
|
from .poly import Poly
|
|
7
8
|
from .Zmod import Zmod
|
|
8
9
|
from .factor import factorint
|
|
@@ -41,13 +42,14 @@ class GF2:
|
|
|
41
42
|
if n in conway_polynomials2:
|
|
42
43
|
modulus = conway_polynomials2[n]
|
|
43
44
|
else:
|
|
44
|
-
raise ValueError("Unknown
|
|
45
|
+
raise ValueError("Unknown degree. Please specify a modulus.")
|
|
45
46
|
self.modulus = modulus # integer whose biniary digits are the coefficients of the irreducible modulus polynomial
|
|
46
47
|
if modulus & 2**n == 2**n:
|
|
47
48
|
self.bitreversed = False
|
|
48
49
|
else:
|
|
49
50
|
self.bitreversed = True
|
|
50
|
-
self.
|
|
51
|
+
self.degree = n # n
|
|
52
|
+
self.characteristic = 2
|
|
51
53
|
self.order = 2**n # 2**n
|
|
52
54
|
self.print_hex: bool = True
|
|
53
55
|
self.byteorder: str = "big" # big|little
|
|
@@ -55,10 +57,10 @@ class GF2:
|
|
|
55
57
|
self.factors = {} # factoring of the group order
|
|
56
58
|
|
|
57
59
|
def __repr__(self):
|
|
58
|
-
return f"GF(2^{self.
|
|
60
|
+
return f"GF(2^{self.degree})"
|
|
59
61
|
|
|
60
|
-
def __call__(self, x: int | list | tuple):
|
|
61
|
-
if isinstance(x, list|tuple):
|
|
62
|
+
def __call__(self, x: int | list | tuple | range | map):
|
|
63
|
+
if isinstance(x, list|tuple|range|map):
|
|
62
64
|
return [GF2nPoint(xx, self) for xx in x]
|
|
63
65
|
return GF2nPoint(x, self)
|
|
64
66
|
|
|
@@ -74,7 +76,7 @@ class GF2:
|
|
|
74
76
|
return self.order == other.order and self.modulus == other.modulus
|
|
75
77
|
return False
|
|
76
78
|
|
|
77
|
-
def __contains__(self, other: "
|
|
79
|
+
def __contains__(self, other: "GF2nPoint") -> bool:
|
|
78
80
|
return isinstance(other, GF2nPoint) and self == other.field
|
|
79
81
|
|
|
80
82
|
def _factors(self) -> dict:
|
|
@@ -83,12 +85,22 @@ class GF2:
|
|
|
83
85
|
self.factors = factorint(self.mult_order)
|
|
84
86
|
return self.factors
|
|
85
87
|
|
|
88
|
+
def zero(self) -> bool:
|
|
89
|
+
"Return zero."
|
|
90
|
+
return self(0)
|
|
91
|
+
|
|
86
92
|
def one(self) -> bool:
|
|
87
93
|
"Return one."
|
|
88
94
|
if self.bitreversed:
|
|
89
95
|
return self(self.order - 1)
|
|
90
96
|
return self(1)
|
|
91
97
|
|
|
98
|
+
def random(self, num: int = 0) -> "GF2nPoint":
|
|
99
|
+
"Return a single random point or a list of random points."
|
|
100
|
+
if num:
|
|
101
|
+
return([self(randint(0,self.order-1)) for _ in range(num)])
|
|
102
|
+
return self(randint(0,self.order-1))
|
|
103
|
+
|
|
92
104
|
def is_cyclic(self) -> bool:
|
|
93
105
|
"GF(2^n)^* is cyclic."
|
|
94
106
|
return True
|
|
@@ -99,7 +111,7 @@ class GF2:
|
|
|
99
111
|
|
|
100
112
|
def generator(self) -> int | None:
|
|
101
113
|
"Return a generator of the group GF(2^n)^*."
|
|
102
|
-
if self.
|
|
114
|
+
if self.degree == 1:
|
|
103
115
|
return self(1)
|
|
104
116
|
for a in range(2, self.order):
|
|
105
117
|
a = self(a)
|
|
@@ -131,7 +143,7 @@ class GF2nPoint:
|
|
|
131
143
|
|
|
132
144
|
def __repr__(self):
|
|
133
145
|
if self.field.print_hex:
|
|
134
|
-
return format(self.x, "0"+str(self.field.
|
|
146
|
+
return format(self.x, "0"+str(self.field.degree // 4)+"x")
|
|
135
147
|
return self.x
|
|
136
148
|
|
|
137
149
|
def __eq__(self, other):
|
|
@@ -146,16 +158,16 @@ class GF2nPoint:
|
|
|
146
158
|
return self.x
|
|
147
159
|
|
|
148
160
|
def __bytes__(self):
|
|
149
|
-
return self.x.to_bytes(self.field.
|
|
161
|
+
return self.x.to_bytes(self.field.degree // 8, byteorder=self.field.byteorder)
|
|
150
162
|
|
|
151
163
|
def bits(self) -> list:
|
|
152
164
|
"Convert to a list of bits."
|
|
153
165
|
if self.field.bitreversed:
|
|
154
166
|
coeff = [int(d) for d in str(
|
|
155
|
-
format(self.x, "0" + str(self.field.
|
|
167
|
+
format(self.x, "0" + str(self.field.degree) + "b"))]
|
|
156
168
|
else:
|
|
157
169
|
coeff = reversed([int(d) for d in str(
|
|
158
|
-
format(self.x, "0" + str(self.field.
|
|
170
|
+
format(self.x, "0" + str(self.field.degree) + "b"))])
|
|
159
171
|
return list(coeff)
|
|
160
172
|
|
|
161
173
|
def poly(self) -> "Poly":
|
|
@@ -163,14 +175,14 @@ class GF2nPoint:
|
|
|
163
175
|
Z_2 = Zmod(2)
|
|
164
176
|
if self.field.bitreversed:
|
|
165
177
|
coeff = [Z_2(int(d)) for d in str(
|
|
166
|
-
format(self.x, "0" + str(self.field.
|
|
178
|
+
format(self.x, "0" + str(self.field.degree) + "b"))]
|
|
167
179
|
coeff_modulus = [Z_2(int(d)) for d in str(
|
|
168
|
-
format((self.field.modulus << 1) + 1, "0" + str(self.field.
|
|
180
|
+
format((self.field.modulus << 1) + 1, "0" + str(self.field.degree) + "b"))]
|
|
169
181
|
else:
|
|
170
182
|
coeff = reversed([Z_2(int(d)) for d in str(
|
|
171
|
-
format(self.x, "0" + str(self.field.
|
|
183
|
+
format(self.x, "0" + str(self.field.degree) + "b"))])
|
|
172
184
|
coeff_modulus = reversed([Z_2(int(d)) for d in str(
|
|
173
|
-
format(self.field.modulus, "0" + str(self.field.
|
|
185
|
+
format(self.field.modulus, "0" + str(self.field.degree) + "b"))])
|
|
174
186
|
return Poly(list(coeff), modulus=list(coeff_modulus))
|
|
175
187
|
|
|
176
188
|
def __hash__(self):
|
|
@@ -208,7 +220,7 @@ class GF2nPoint:
|
|
|
208
220
|
z = 0
|
|
209
221
|
y = other.x
|
|
210
222
|
if self.field.bitreversed:
|
|
211
|
-
for d in bin(self.x)[2:].zfill(self.field.
|
|
223
|
+
for d in bin(self.x)[2:].zfill(self.field.degree):
|
|
212
224
|
if int(d):
|
|
213
225
|
z ^= y
|
|
214
226
|
if y % 2:
|
|
@@ -219,7 +231,7 @@ class GF2nPoint:
|
|
|
219
231
|
else:
|
|
220
232
|
bitmap1 = self.field.order
|
|
221
233
|
bitmap2 = 1
|
|
222
|
-
for _ in range(self.field.
|
|
234
|
+
for _ in range(self.field.degree):
|
|
223
235
|
if self.x & bitmap2:
|
|
224
236
|
z ^= y
|
|
225
237
|
bitmap2 <<= 1
|
|
@@ -300,11 +312,11 @@ class GF2nPoint:
|
|
|
300
312
|
|
|
301
313
|
def sbox(self, inv: bool = False) -> "GF2nPoint":
|
|
302
314
|
"Apply the AES sbox."
|
|
303
|
-
if self.field.
|
|
315
|
+
if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
|
|
304
316
|
if inv:
|
|
305
317
|
return aes_sbox_inv[self.x]
|
|
306
318
|
return aes_sbox[self.x]
|
|
307
|
-
elif self.field.
|
|
319
|
+
elif self.field.degree == 4 and self.field.modulus == 0b10011: # MiniAES
|
|
308
320
|
if inv:
|
|
309
321
|
return miniaes_sbox_inv[self.x]
|
|
310
322
|
return miniaes_sbox[self.x]
|
|
@@ -3,6 +3,7 @@ Ring of intergers modulo `n`.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from math import gcd
|
|
6
|
+
from random import randint
|
|
6
7
|
from .factor import factorint
|
|
7
8
|
from .primes import is_prime
|
|
8
9
|
from .nt import sqrt_mod, crt
|
|
@@ -31,16 +32,17 @@ class Zmod:
|
|
|
31
32
|
if not isinstance(n, int) or n < 1:
|
|
32
33
|
raise ValueError(f"{n} is not a positive integer.")
|
|
33
34
|
self.n = n
|
|
35
|
+
self.isfield = None
|
|
34
36
|
self.n_factors = {} # factoring of n
|
|
35
37
|
self.short = short
|
|
36
|
-
self.group_order = 0
|
|
38
|
+
self.group_order = 0 # order of the multiplicative group
|
|
37
39
|
self.factors = {} # factoring of the multiplicative group order
|
|
38
40
|
|
|
39
41
|
def __repr__(self):
|
|
40
42
|
return f"Z_{self.n}"
|
|
41
43
|
|
|
42
|
-
def __call__(self, x: int | list | tuple):
|
|
43
|
-
if isinstance(x, list|tuple):
|
|
44
|
+
def __call__(self, x: int | list | tuple | range | map):
|
|
45
|
+
if isinstance(x, list|tuple|range|map):
|
|
44
46
|
return [ZmodPoint(xx, self) for xx in x]
|
|
45
47
|
return ZmodPoint(x, self)
|
|
46
48
|
|
|
@@ -56,6 +58,12 @@ class Zmod:
|
|
|
56
58
|
def __contains__(self, other: "ZmodPoint") -> bool:
|
|
57
59
|
return isinstance(other, ZmodPoint) and self.n == other.ring.n
|
|
58
60
|
|
|
61
|
+
def random(self, num: int = 0) -> "ZmodPoint":
|
|
62
|
+
"Return a single random point or a list of random points."
|
|
63
|
+
if num:
|
|
64
|
+
return([self(randint(0,self.n-1)) for _ in range(num)])
|
|
65
|
+
return self(randint(0,self.n-1))
|
|
66
|
+
|
|
59
67
|
def order(self) -> int:
|
|
60
68
|
"Compute the order of the group Z_n^*."
|
|
61
69
|
if self.group_order:
|
|
@@ -63,7 +71,7 @@ class Zmod:
|
|
|
63
71
|
# We compute euler_phi(n) and its factorization in one pass
|
|
64
72
|
if not self.n_factors:
|
|
65
73
|
self.n_factors = factorint(self.n)
|
|
66
|
-
for p, k in self.n_factors.items(): #
|
|
74
|
+
for p, k in self.n_factors.items(): # factorization of n
|
|
67
75
|
# factor p-1 and add the factors
|
|
68
76
|
for pm, km in factorint(p - 1).items():
|
|
69
77
|
if pm in self.factors:
|
|
@@ -96,7 +104,18 @@ class Zmod:
|
|
|
96
104
|
|
|
97
105
|
def is_field(self) -> bool:
|
|
98
106
|
"Test if Z_n is a field."
|
|
99
|
-
|
|
107
|
+
if self.isfield is None:
|
|
108
|
+
if self.n_factors:
|
|
109
|
+
if len(self.n_factors) == 1:
|
|
110
|
+
self.isfield = True
|
|
111
|
+
else:
|
|
112
|
+
self.isfield = False
|
|
113
|
+
elif is_prime(self.n):
|
|
114
|
+
self.isfield = True
|
|
115
|
+
self.n_factors = { self.n: 1}
|
|
116
|
+
else:
|
|
117
|
+
self.isfield = False
|
|
118
|
+
return self.isfield
|
|
100
119
|
|
|
101
120
|
def generator(self) -> int | None:
|
|
102
121
|
"Return a generator of 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.5"
|
|
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
|
|
@@ -16,10 +16,10 @@ from .dlp import dlog
|
|
|
16
16
|
from .ec import EC_Weierstrass
|
|
17
17
|
from .la import Matrix, zeros, eye
|
|
18
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
|
|
19
|
-
from .poly import Poly
|
|
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 left_standard_form, gen2pchk, hamming_dist, Goppa
|
|
22
|
+
from .code import left_standard_form, gen2pchk, hamming_dist, Goppa, CyclicCode, ReedSolomonCode
|
|
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
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Linear codes
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import prod
|
|
6
|
+
from .Zmod import Zmod
|
|
7
|
+
from .GF2 import GF2
|
|
8
|
+
from .poly import Poly, lagrange_interpolation
|
|
9
|
+
from .la import Matrix, eye, zeros
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def poly2bits(x: Poly) -> list:
|
|
13
|
+
"Convert a polynomial in GF(2^n)[x] to a list of binary digits."
|
|
14
|
+
out = []
|
|
15
|
+
for c in x.coeff:
|
|
16
|
+
out += c.bits()
|
|
17
|
+
return out
|
|
18
|
+
|
|
19
|
+
def left_standard_form(M: Matrix) -> (Matrix, Matrix):
|
|
20
|
+
"Compute the left standard form of a generator matrix. Return the standard form and the pertmutation matrix."
|
|
21
|
+
# reduced row echelon form
|
|
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
|
|
39
|
+
|
|
40
|
+
def gen2pchk(G: Matrix) -> Matrix:
|
|
41
|
+
"Compute the parity check matrix from a given generator matrix (and vice versa)."
|
|
42
|
+
G, P = left_standard_form(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()
|
|
58
|
+
|
|
59
|
+
def hamming_dist(a: iter, b: iter):
|
|
60
|
+
"Hamming distance of two iterables."
|
|
61
|
+
if len(a) != len(b):
|
|
62
|
+
raise ValueError("Both iteralbles must have equal length.")
|
|
63
|
+
return sum(aa != bb for aa, bb in zip(a, b))
|
|
64
|
+
|
|
65
|
+
class Goppa():
|
|
66
|
+
"""
|
|
67
|
+
Binary irreducible Goppa code.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
|
|
71
|
+
>>> gf = GF2(4) # base field GF(2^4)
|
|
72
|
+
>>> g = Poly([2, 0, 0, 1], ring = gf) # irreducible Polynomial for the code
|
|
73
|
+
>>> alpha = list(gf) # list of points from the base field (must not contain zeros of g)
|
|
74
|
+
>>> goppa = Goppa(gf, g, alpha)
|
|
75
|
+
|
|
76
|
+
To encode a list of bits (the length must be equal to the dimension k of the code)
|
|
77
|
+
>>> goppa.encode([1, 0, 1, 1])
|
|
78
|
+
[0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1]
|
|
79
|
+
|
|
80
|
+
Decoding still works if there are at most deg(g) errors:
|
|
81
|
+
>>> goppa.decode([0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0])
|
|
82
|
+
[1, 0, 1, 1]
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, gf: GF2, g: Poly, alpha: list["GF2Point"]):
|
|
87
|
+
if not all(map(lambda x: x in gf, alpha)):
|
|
88
|
+
raise ValueError(f"{x}: alpha must be a list of elements from the Galois field!")
|
|
89
|
+
if not g.rabin_test():
|
|
90
|
+
raise ValueError("The polynomial g must be irreducible!")
|
|
91
|
+
self.gf = gf # underlying Galois field
|
|
92
|
+
self.g = g # polynomial
|
|
93
|
+
self.alpha = alpha # list of elements from gf
|
|
94
|
+
# Compute the control matrix
|
|
95
|
+
self.Z_2 = Zmod(2)
|
|
96
|
+
|
|
97
|
+
tmp =[]
|
|
98
|
+
for x in alpha:
|
|
99
|
+
tmp.append(Poly([-x, 1], ring = gf, modulus = g).inv().bits())
|
|
100
|
+
self.H = Matrix(tmp, ring=self.Z_2).transpose()
|
|
101
|
+
# Compute the generator matrix
|
|
102
|
+
self.G = gen2pchk(self.H)
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
return f"Binary irreducible Goppa code over GF(2^{self.gf.power}) with polynomial g(x) = {self.g}."
|
|
106
|
+
|
|
107
|
+
def encode(self, x: list[int]):
|
|
108
|
+
"Encode a given list of bits."
|
|
109
|
+
x = Matrix(x, ring = self.Z_2)
|
|
110
|
+
y = self.G.transpose() * x
|
|
111
|
+
y.map(int)
|
|
112
|
+
return list(y)
|
|
113
|
+
|
|
114
|
+
def decode(self, y: list[int]):
|
|
115
|
+
"Decode a given list of bits."
|
|
116
|
+
y = Matrix(y, ring = self.Z_2)
|
|
117
|
+
zero = self.gf(0)
|
|
118
|
+
one = zero**0
|
|
119
|
+
if self.H * y: # no code word
|
|
120
|
+
s = zero # syndrom polynomial
|
|
121
|
+
for i, x in enumerate(self.alpha):
|
|
122
|
+
s += int(y[i]) * Poly([-x, one], modulus = self.g).inv()
|
|
123
|
+
v = (s.inv() - Poly([zero, one], modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
|
|
124
|
+
# determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
|
|
125
|
+
# find a and b by running EEA until b has the desired degree
|
|
126
|
+
v.modulus = None
|
|
127
|
+
r0, r1 = self.g, v
|
|
128
|
+
y0, y1 = Poly([zero]), Poly([one])
|
|
129
|
+
while 2 * r1.degree() > self.g.degree():
|
|
130
|
+
q, r = r0.divmod(r1)
|
|
131
|
+
r0, r1 = r1, r
|
|
132
|
+
y0, y1 = y1, y0 - q * y1
|
|
133
|
+
a, b = r1, y1
|
|
134
|
+
sigma = a**2 + b**2 * Poly([zero, one])
|
|
135
|
+
# correct the error
|
|
136
|
+
for x in self.alpha:
|
|
137
|
+
if not sigma(x):
|
|
138
|
+
y[int(x)] += 1
|
|
139
|
+
if self.H * y:
|
|
140
|
+
raise ValueError("Decoding failed!")
|
|
141
|
+
x = self.G.transpose().solve(y)
|
|
142
|
+
x.map(int)
|
|
143
|
+
return list(x)
|
|
144
|
+
|
|
145
|
+
class CyclicCode():
|
|
146
|
+
"""
|
|
147
|
+
Cyclic code.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
|
|
151
|
+
>>> n = 4 # length of the code
|
|
152
|
+
>>> g = Poly([1, 1], ring = Zmod(2)) # generating Polynomial for the code
|
|
153
|
+
>>> cc = CyclicCode(n, g)
|
|
154
|
+
|
|
155
|
+
To encode a list of letters (the length must be equal to the dimension k of the code):
|
|
156
|
+
>>> cc.encode([0,1,0], ring = Zmod(2))
|
|
157
|
+
[0, 1, 1, 0]
|
|
158
|
+
|
|
159
|
+
Decoding is only implemnted for code words:
|
|
160
|
+
>>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
|
|
161
|
+
[0, 1, 0]
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, n:int, g: Poly):
|
|
166
|
+
if n < 2:
|
|
167
|
+
raise ValueError(f"Code lenght {n} must be at least 2!")
|
|
168
|
+
zero = 0 * g[0]
|
|
169
|
+
try:
|
|
170
|
+
order = zero.ring.n # Zmod
|
|
171
|
+
except:
|
|
172
|
+
try:
|
|
173
|
+
order = zero.field.order # GF2
|
|
174
|
+
except:
|
|
175
|
+
try:
|
|
176
|
+
order = type(zero).order # galois
|
|
177
|
+
except:
|
|
178
|
+
raise ValueError("Unknown base filed!")
|
|
179
|
+
self.order = order # order of the base field
|
|
180
|
+
one = zero**0
|
|
181
|
+
modulus = Poly([-one] + [zero] * (n-1) + [one])
|
|
182
|
+
self.modulus = modulus
|
|
183
|
+
if modulus % g:
|
|
184
|
+
raise ValueError(f"The polynomial g must be a factor of x^{n} - 1!")
|
|
185
|
+
self.n = n # lenght
|
|
186
|
+
self.k = n - g.degree() # dimension
|
|
187
|
+
self.g = g # generating polynomial
|
|
188
|
+
self.h = Poly(modulus) // g
|
|
189
|
+
|
|
190
|
+
def __repr__(self):
|
|
191
|
+
return f"Cyclic [{self.n}, {self.k}] code over GF({self.order}) with generator polynomial g(x) = {self.g}."
|
|
192
|
+
|
|
193
|
+
def encode(self, x: list|Poly, gf = None, systematic_form = False):
|
|
194
|
+
"Encode a given list or polynomial."
|
|
195
|
+
as_list = False
|
|
196
|
+
zero = 0 * self.g[0]
|
|
197
|
+
if isinstance(x, list | tuple):
|
|
198
|
+
as_list = True
|
|
199
|
+
x = Poly(x, ring = gf)
|
|
200
|
+
if x.degree() >= self.k:
|
|
201
|
+
raise ValueError(f"Degree can be at most {self.k-1}.")
|
|
202
|
+
if systematic_form:
|
|
203
|
+
x.coeff = [zero] * self.g.degree() + x.coeff
|
|
204
|
+
y = x - (x % self.g)
|
|
205
|
+
else:
|
|
206
|
+
y = self.g * x
|
|
207
|
+
if as_list:
|
|
208
|
+
return y.coeff + [zero] * (self.n - len(y.coeff))
|
|
209
|
+
return y
|
|
210
|
+
|
|
211
|
+
def decode(self, y: list|Poly, gf = None, systematic_form = False):
|
|
212
|
+
"Decode a given list or polynomial."
|
|
213
|
+
as_list = False
|
|
214
|
+
zero = 0 * self.g[0]
|
|
215
|
+
if isinstance(y, list | tuple):
|
|
216
|
+
as_list = True
|
|
217
|
+
y = Poly(y, ring = gf)
|
|
218
|
+
if y.degree() >= self.n:
|
|
219
|
+
raise ValueError(f"Degree can be at most {self.n-1}.")
|
|
220
|
+
x, r = y.divmod(self.g)
|
|
221
|
+
if r:
|
|
222
|
+
raise NotImplementedError(f"Word is no code word! Cannot decode.")
|
|
223
|
+
if systematic_form:
|
|
224
|
+
x.coeff = y.coeff[self.g.degree():]
|
|
225
|
+
if as_list:
|
|
226
|
+
return x.coeff + [zero] * (self.k - len(x.coeff))
|
|
227
|
+
return x
|
|
228
|
+
|
|
229
|
+
def generator_matrix(self):
|
|
230
|
+
"Return the generator matrix of the code."
|
|
231
|
+
zero = 0 * self.g[0]
|
|
232
|
+
return Matrix([[zero] * j + self.g.coeff + [zero] * (self.k - j - 1 ) for j in range(self.k)])
|
|
233
|
+
|
|
234
|
+
def check_matrix(self):
|
|
235
|
+
"Return the parity check matrix of the code."
|
|
236
|
+
zero = 0 * self.g[0]
|
|
237
|
+
tmp = list(reversed(self.h.coeff))
|
|
238
|
+
return Matrix([[zero] * j + tmp + [zero] * (self.n - self.k - j - 1 ) for j in range(self.n-self.k)])
|
|
239
|
+
|
|
240
|
+
class ReedSolomonCode(CyclicCode):
|
|
241
|
+
"""
|
|
242
|
+
Reed-Solomon code.
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
|
|
246
|
+
>>> gf = Zmod(13) # Galois field
|
|
247
|
+
>>> k = 3 # dimension of the code
|
|
248
|
+
>>> rsc = ReedSolomonCode(k, gf)
|
|
249
|
+
|
|
250
|
+
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):
|
|
251
|
+
>>> cc.encode([1, 2, 3])
|
|
252
|
+
[0, 1, 1, 0]
|
|
253
|
+
|
|
254
|
+
Decoding is only implemnted for code words:
|
|
255
|
+
>>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
|
|
256
|
+
[0, 1, 0]
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
def __init__(self, k:int, gf, alpha = None):
|
|
260
|
+
zero = gf(0)
|
|
261
|
+
one = zero**0
|
|
262
|
+
self. gf = gf
|
|
263
|
+
try:
|
|
264
|
+
n = gf.order()
|
|
265
|
+
except:
|
|
266
|
+
n = gf.order - 1
|
|
267
|
+
self.order = n + 1 # order of the base field
|
|
268
|
+
self.n = n # lenght
|
|
269
|
+
self.k = k # dimension
|
|
270
|
+
self.d = n - k - 1 # minimal distance
|
|
271
|
+
self.modulus = Poly([-one] + [zero] * (n-1) + [one])
|
|
272
|
+
if alpha is None:
|
|
273
|
+
try:
|
|
274
|
+
alpha = gf.generator() # Zmod|GF2
|
|
275
|
+
except:
|
|
276
|
+
alpha = gf.primitive_element # galois
|
|
277
|
+
self.alpha = alpha
|
|
278
|
+
self.g = prod([Poly([-alpha**j, one]) for j in range(1,n-k+1)], start = one) # generating polynomial
|
|
279
|
+
self.h = Poly(self.modulus) // self.g # check polynomial
|
|
280
|
+
assert k == n - self.g.degree()
|
|
281
|
+
assert self.g * self.h == self.modulus
|
|
282
|
+
|
|
283
|
+
def __repr__(self):
|
|
284
|
+
return f"Reed-Solomon-Code [{self.n}, {self.k}, {self.d}] over GF({self.n+1})."
|
|
285
|
+
|
|
286
|
+
def encode(self, x: list|Poly):
|
|
287
|
+
"Encode a given list or polynomial."
|
|
288
|
+
as_list = False
|
|
289
|
+
zero = 0 * self.g[0]
|
|
290
|
+
if isinstance(x, list | tuple):
|
|
291
|
+
as_list = True
|
|
292
|
+
x = Poly(x, ring = self.gf)
|
|
293
|
+
if x.degree() >= self.k:
|
|
294
|
+
raise ValueError(f"Degree can be at most {self.k-1}.")
|
|
295
|
+
y = [ x(self.alpha**j) for j in range(self.n) ]
|
|
296
|
+
if as_list:
|
|
297
|
+
return y + [zero] * (self.n - len(y))
|
|
298
|
+
return Poly(y)
|
|
299
|
+
|
|
300
|
+
def decode(self, y: list|Poly):
|
|
301
|
+
"Decode a given list or polynomial."
|
|
302
|
+
as_list = False
|
|
303
|
+
zero = 0 * self.g[0]
|
|
304
|
+
one = zero**0
|
|
305
|
+
if isinstance(y, list | tuple):
|
|
306
|
+
as_list = True
|
|
307
|
+
y = Poly(y, ring = self.gf)
|
|
308
|
+
if y.degree() >= self.n:
|
|
309
|
+
raise ValueError(f"Degree can be at most {self.n-1}.")
|
|
310
|
+
x = Poly([ -y(self.alpha**(-j)) for j in range(self.n) ])
|
|
311
|
+
if x.degree() >= self.k: # No codewort
|
|
312
|
+
t = (self.n - self.k) // 2
|
|
313
|
+
A = Matrix([[ x[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
|
|
314
|
+
b = [ -x[self.k + t + l ] for l in range(t) ]
|
|
315
|
+
lam = Poly([one] + list(A.solve(b)))
|
|
316
|
+
x_coordinates = []
|
|
317
|
+
y_coordinates = []
|
|
318
|
+
for j in range(self.n):
|
|
319
|
+
if len(x_coordinates) == self.k:
|
|
320
|
+
break
|
|
321
|
+
alphaj = self.alpha**j
|
|
322
|
+
if lam(alphaj):
|
|
323
|
+
x_coordinates.append(alphaj)
|
|
324
|
+
y_coordinates.append(y[j])
|
|
325
|
+
x = lagrange_interpolation(x_coordinates, y_coordinates)
|
|
326
|
+
if as_list:
|
|
327
|
+
return x.coeff + [zero] * (self.k - len(x.coeff))
|
|
328
|
+
return x
|
|
@@ -97,7 +97,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
|
|
|
97
97
|
break
|
|
98
98
|
trys += 1
|
|
99
99
|
else:
|
|
100
|
-
raise Exception(f"Sorry, Quadratic Sieve failed
|
|
100
|
+
raise Exception(f"Sorry, Quadratic Sieve failed to find a new relation after {trys} trys.")
|
|
101
101
|
if verbose > 2:
|
|
102
102
|
if include_b:
|
|
103
103
|
print(f"rel found: b*a^{x}=", b * pow(a, x, n) % n, relation)
|