kryptools 1.4__tar.gz → 1.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryptools-1.4 → kryptools-1.5.1}/PKG-INFO +2 -2
- {kryptools-1.4 → kryptools-1.5.1}/README.md +1 -1
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/GF2.py +33 -17
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/Zmod.py +22 -3
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/__init__.py +1 -1
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/code.py +11 -8
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/keccak.py +25 -4
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/poly.py +241 -91
- {kryptools-1.4 → kryptools-1.5.1}/kryptools.egg-info/PKG-INFO +2 -2
- {kryptools-1.4 → kryptools-1.5.1}/pyproject.toml +1 -1
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_GF2.py +12 -13
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_Zmod.py +0 -1
- kryptools-1.5.1/tests/test_poly.py +88 -0
- kryptools-1.4/tests/test_poly.py +0 -49
- {kryptools-1.4 → kryptools-1.5.1}/LICENSE +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/aes.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/blockcipher.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/des.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/dlp.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/ec.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor_dix.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor_qs.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/factor_rho.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/intfuncs.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/la.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/lat.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/nt.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/primes.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools/sha1.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/setup.cfg +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_aes.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_code.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_des.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_dlog.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_ec.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_factor.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_factor_qs.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_intfuncs.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_keccak.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_la.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_lat.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_nt.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_primes.py +0 -0
- {kryptools-1.4 → kryptools-1.5.1}/tests/test_sha1.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: Implemenation of same basic algorithms used in cryptography.
|
|
5
5
|
Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
|
|
6
6
|
License: MIT
|
|
@@ -42,7 +42,7 @@ 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, irreducibility test, Lagrange interpolation)
|
|
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
|
|
@@ -24,7 +24,7 @@ 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, irreducibility test, Lagrange interpolation)
|
|
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
|
|
@@ -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,7 +57,7 @@ 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
62
|
def __call__(self, x: int | list | tuple | range | map):
|
|
61
63
|
if isinstance(x, list|tuple|range|map):
|
|
@@ -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
|
|
@@ -249,6 +261,8 @@ class GF2nPoint:
|
|
|
249
261
|
return NotImplemented
|
|
250
262
|
if not (self.x) and j < 0:
|
|
251
263
|
raise ValueError("Division by zero.")
|
|
264
|
+
if self.field.order == 2:
|
|
265
|
+
return self
|
|
252
266
|
if self.field.bitreversed:
|
|
253
267
|
res = self.field(self.field.order >> 1)
|
|
254
268
|
else:
|
|
@@ -278,6 +292,8 @@ class GF2nPoint:
|
|
|
278
292
|
|
|
279
293
|
def sqrt(self) -> "GF2nPoint":
|
|
280
294
|
"Compute the square root."
|
|
295
|
+
if self.field.order == 2:
|
|
296
|
+
return self
|
|
281
297
|
return self**(self.field.order//2)
|
|
282
298
|
|
|
283
299
|
def order(self) -> int:
|
|
@@ -300,11 +316,11 @@ class GF2nPoint:
|
|
|
300
316
|
|
|
301
317
|
def sbox(self, inv: bool = False) -> "GF2nPoint":
|
|
302
318
|
"Apply the AES sbox."
|
|
303
|
-
if self.field.
|
|
319
|
+
if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
|
|
304
320
|
if inv:
|
|
305
321
|
return aes_sbox_inv[self.x]
|
|
306
322
|
return aes_sbox[self.x]
|
|
307
|
-
elif self.field.
|
|
323
|
+
elif self.field.degree == 4 and self.field.modulus == 0b10011: # MiniAES
|
|
308
324
|
if inv:
|
|
309
325
|
return miniaes_sbox_inv[self.x]
|
|
310
326
|
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,9 +32,10 @@ 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):
|
|
@@ -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.1"
|
|
9
9
|
__email__ = "Gerald.Teschl@univie.ac.at"
|
|
10
10
|
|
|
11
11
|
from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, is_carmichael_number, order, crt
|
|
@@ -114,22 +114,24 @@ class Goppa():
|
|
|
114
114
|
def decode(self, y: list[int]):
|
|
115
115
|
"Decode a given list of bits."
|
|
116
116
|
y = Matrix(y, ring = self.Z_2)
|
|
117
|
+
zero = self.gf(0)
|
|
118
|
+
one = zero**0
|
|
117
119
|
if self.H * y: # no code word
|
|
118
|
-
s =
|
|
120
|
+
s = zero # syndrom polynomial
|
|
119
121
|
for i, x in enumerate(self.alpha):
|
|
120
|
-
s += int(y[i]) * Poly([-x,
|
|
121
|
-
v = (s.inv() - Poly([
|
|
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)
|
|
122
124
|
# determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
|
|
123
125
|
# find a and b by running EEA until b has the desired degree
|
|
124
126
|
v.modulus = None
|
|
125
127
|
r0, r1 = self.g, v
|
|
126
|
-
y0, y1 = Poly([
|
|
128
|
+
y0, y1 = Poly([zero]), Poly([one])
|
|
127
129
|
while 2 * r1.degree() > self.g.degree():
|
|
128
130
|
q, r = r0.divmod(r1)
|
|
129
131
|
r0, r1 = r1, r
|
|
130
132
|
y0, y1 = y1, y0 - q * y1
|
|
131
133
|
a, b = r1, y1
|
|
132
|
-
sigma = a**2 + b**2 * Poly([
|
|
134
|
+
sigma = a**2 + b**2 * Poly([zero, one])
|
|
133
135
|
# correct the error
|
|
134
136
|
for x in self.alpha:
|
|
135
137
|
if not sigma(x):
|
|
@@ -305,11 +307,12 @@ class ReedSolomonCode(CyclicCode):
|
|
|
305
307
|
y = Poly(y, ring = self.gf)
|
|
306
308
|
if y.degree() >= self.n:
|
|
307
309
|
raise ValueError(f"Degree can be at most {self.n-1}.")
|
|
308
|
-
|
|
310
|
+
xx = [ -y(self.alpha**(-j)) for j in range(self.n) ]
|
|
311
|
+
x = Poly(xx)
|
|
309
312
|
if x.degree() >= self.k: # No codewort
|
|
310
313
|
t = (self.n - self.k) // 2
|
|
311
|
-
A = Matrix([[
|
|
312
|
-
b = [ -
|
|
314
|
+
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) ])
|
|
315
|
+
b = [ -xx[self.k + t + l ] for l in range(t) ]
|
|
313
316
|
lam = Poly([one] + list(A.solve(b)))
|
|
314
317
|
x_coordinates = []
|
|
315
318
|
y_coordinates = []
|
|
@@ -161,16 +161,21 @@ class SHA3():
|
|
|
161
161
|
def __init__(self, n: int = 256):
|
|
162
162
|
if n not in (224, 256, 384, 512):
|
|
163
163
|
raise ValueError(f"n must be one of: 224, 256, 384, 512")
|
|
164
|
-
self.keccak = Keccak(rate=1600-2*n)
|
|
164
|
+
self.keccak = Keccak(rate = 1600 - 2*n)
|
|
165
165
|
self.keccak.pad = [b'\x86', b'\x06', b'\x80']
|
|
166
|
-
self.
|
|
166
|
+
self.digest_size = n // 8 # in bytes
|
|
167
|
+
self.block_size = (1600 - 2*n) // 8 # in bytes
|
|
168
|
+
self.name = f"SHA3-{n}"
|
|
167
169
|
|
|
168
170
|
def __call__(self, msg: bytes | str) -> bytes:
|
|
169
171
|
"Compute the hash of the message given or absorbed."
|
|
170
172
|
self.keccak.reset()
|
|
171
173
|
self.keccak.absorb(msg)
|
|
172
|
-
return self.keccak.squeeze(self.
|
|
174
|
+
return self.keccak.squeeze(self.digest_size)
|
|
173
175
|
|
|
176
|
+
def __repr__(self):
|
|
177
|
+
return self.name
|
|
178
|
+
|
|
174
179
|
def update(self, msg: bytes | str) -> None:
|
|
175
180
|
"Absorb the given message into the sponge."
|
|
176
181
|
self.keccak.absorb(msg)
|
|
@@ -179,10 +184,14 @@ class SHA3():
|
|
|
179
184
|
"Compute the hash of the message given or absorbed."
|
|
180
185
|
if msg is not None:
|
|
181
186
|
self.keccak.absorb(msg)
|
|
182
|
-
digest = self.keccak.squeeze(self.
|
|
187
|
+
digest = self.keccak.squeeze(self.digest_size)
|
|
183
188
|
self.keccak.reset()
|
|
184
189
|
return digest
|
|
185
190
|
|
|
191
|
+
def hexdigest(self, msg: bytes | str | None = None) -> bytes:
|
|
192
|
+
"Compute the hash of the message given or absorbed. Return as hex value."
|
|
193
|
+
return self.digest(msg).hex()
|
|
194
|
+
|
|
186
195
|
|
|
187
196
|
class SHAKE():
|
|
188
197
|
"""
|
|
@@ -201,7 +210,19 @@ class SHAKE():
|
|
|
201
210
|
self.keccak = Keccak(rate=1600-2*n)
|
|
202
211
|
self.keccak.pad = [b'\x9f', b'\x1f', b'\x80']
|
|
203
212
|
self.keccak.absorb(msg)
|
|
213
|
+
self.name = f"SHAKE-{n}"
|
|
214
|
+
|
|
215
|
+
def __repr__(self):
|
|
216
|
+
return self.name
|
|
204
217
|
|
|
205
218
|
def __call__(self, r: int) -> bytes:
|
|
206
219
|
"Extract `r` bytes."
|
|
207
220
|
return self.keccak.squeeze(r)
|
|
221
|
+
|
|
222
|
+
def digest(self, r: int) -> bytes:
|
|
223
|
+
"Extract `r` bytes."
|
|
224
|
+
return self(r)
|
|
225
|
+
|
|
226
|
+
def hexdigest(self, r: int) -> bytes:
|
|
227
|
+
"Extract `r` bytes as hex values."
|
|
228
|
+
return self(r).hex()
|
|
@@ -4,8 +4,9 @@ Polynomials
|
|
|
4
4
|
|
|
5
5
|
from math import prod
|
|
6
6
|
from numbers import Number
|
|
7
|
+
from random import randint
|
|
7
8
|
from .primes import is_prime
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
|
|
10
11
|
class Poly:
|
|
11
12
|
"""
|
|
@@ -24,7 +25,7 @@ class Poly:
|
|
|
24
25
|
|
|
25
26
|
def __init__(self, coeff: list, ring=None, modulus: list = None):
|
|
26
27
|
self.coeff = list(coeff)
|
|
27
|
-
for i in range(len(self.coeff) - 1, 0, -1):
|
|
28
|
+
for i in range(len(self.coeff) - 1, 0, -1): # strip leading zeros
|
|
28
29
|
if self.coeff[i]:
|
|
29
30
|
break
|
|
30
31
|
self.coeff.pop(i)
|
|
@@ -35,28 +36,45 @@ class Poly:
|
|
|
35
36
|
self.mod(modulus)
|
|
36
37
|
|
|
37
38
|
def __call__(self, x):
|
|
38
|
-
|
|
39
|
-
if not type(x) == type(
|
|
40
|
-
raise ValueError(
|
|
41
|
-
|
|
39
|
+
result = 0 * self.coeff[0]
|
|
40
|
+
if not type(x) == type(result):
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"Evaluation point must be an element of the ring.")
|
|
43
|
+
# Horner's method
|
|
44
|
+
for c in reversed(self.coeff):
|
|
45
|
+
result = result * x + c
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
return sum((c * x**j for j, c in enumerate(self.coeff)), start=zero)
|
|
42
49
|
|
|
43
50
|
def __getitem__(self, item):
|
|
51
|
+
if item >= len(self.coeff):
|
|
52
|
+
return self.coeff[0] * 0
|
|
44
53
|
return self.coeff[item]
|
|
45
54
|
|
|
46
55
|
def __setitem__(self, item, value):
|
|
56
|
+
if item >= len(self.coeff):
|
|
57
|
+
zero = self.coeff[0] * 0
|
|
58
|
+
self.coeff += [zero] * (item - len(self.coeff) + 1)
|
|
47
59
|
self.coeff[item] = value
|
|
48
60
|
|
|
61
|
+
def __iter__(self):
|
|
62
|
+
yield from self.coeff
|
|
63
|
+
|
|
49
64
|
def __len__(self):
|
|
50
65
|
return len(self.coeff)
|
|
51
66
|
|
|
52
|
-
def
|
|
67
|
+
def __hash__(self):
|
|
68
|
+
return hash(tuple(self.coeff))
|
|
69
|
+
|
|
70
|
+
def __repr__(self, latex=False):
|
|
53
71
|
def prx(i: int) -> str:
|
|
54
72
|
if i == 0:
|
|
55
73
|
return ""
|
|
56
74
|
if i == 1:
|
|
57
75
|
return self.__class__.print_x
|
|
58
76
|
if latex:
|
|
59
|
-
return self.__class__.print_x + "^{" + str(i) +"}"
|
|
77
|
+
return self.__class__.print_x + "^{" + str(i) + "}"
|
|
60
78
|
return self.__class__.print_x + self.__class__.print_pow + str(i)
|
|
61
79
|
|
|
62
80
|
if not self:
|
|
@@ -101,7 +119,7 @@ class Poly:
|
|
|
101
119
|
def _repr_mimebundle_(self, **kwargs):
|
|
102
120
|
return {
|
|
103
121
|
"text/plain": repr(self),
|
|
104
|
-
"text/latex": "$" + self.__repr__(latex
|
|
122
|
+
"text/latex": "$" + self.__repr__(latex=True) + "$"
|
|
105
123
|
}
|
|
106
124
|
|
|
107
125
|
def __eq__(self, other):
|
|
@@ -142,27 +160,36 @@ class Poly:
|
|
|
142
160
|
ring = None
|
|
143
161
|
return zero, one, ring
|
|
144
162
|
|
|
163
|
+
def _guess_field(self):
|
|
164
|
+
zero, one, field = self._guess_ring()
|
|
165
|
+
if not field:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
"The polynomial does not seem to be over a known finite field.")
|
|
168
|
+
if hasattr(field, 'n'): # Zmod
|
|
169
|
+
p = field.n
|
|
170
|
+
q = field.n
|
|
171
|
+
if not field.is_field():
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"The polynomial does not seem to be over a finite field.")
|
|
174
|
+
else: # G2 or galois
|
|
175
|
+
p = field.characteristic
|
|
176
|
+
q = field.order
|
|
177
|
+
return field, zero, one, q, p
|
|
178
|
+
|
|
145
179
|
def __add__(self, other: "Poly") -> "Poly":
|
|
146
|
-
zero = 0 * self.coeff[0]
|
|
147
180
|
if not isinstance(other, self.__class__):
|
|
148
181
|
if self._check_type(other):
|
|
149
182
|
tmp = self.coeff[:]
|
|
150
183
|
tmp[0] += other
|
|
151
184
|
return self.__class__(tmp, modulus=self.modulus)
|
|
152
185
|
return NotImplemented
|
|
153
|
-
ls, lo = len(self.coeff), len(other.coeff)
|
|
154
|
-
if ls < lo:
|
|
155
|
-
scoeff = self.coeff + (lo - ls) * [zero]
|
|
156
|
-
else:
|
|
157
|
-
scoeff = self.coeff
|
|
158
|
-
if ls > lo:
|
|
159
|
-
ocoeff = other.coeff + (ls - lo) * [zero]
|
|
160
|
-
else:
|
|
161
|
-
ocoeff = other.coeff
|
|
162
186
|
modulus = self.modulus
|
|
163
187
|
if not modulus and other.modulus:
|
|
164
188
|
modulus = other.modulus
|
|
165
|
-
|
|
189
|
+
ls, lo = len(self.coeff), len(other.coeff)
|
|
190
|
+
if ls < lo:
|
|
191
|
+
return self.__class__([s + o for s, o in zip(self.coeff, other.coeff)] + other.coeff[ls:], modulus=modulus)
|
|
192
|
+
return self.__class__([s + o for s, o in zip(self.coeff, other.coeff)] + self.coeff[lo:], modulus=modulus)
|
|
166
193
|
|
|
167
194
|
def __radd__(self, other: "Poly") -> "Poly":
|
|
168
195
|
return self + other
|
|
@@ -186,9 +213,8 @@ class Poly:
|
|
|
186
213
|
return NotImplemented
|
|
187
214
|
zero = 0 * self.coeff[0]
|
|
188
215
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
189
|
-
coeff = [
|
|
190
|
-
|
|
191
|
-
coeff[k] = sum((self.coeff[j] * other.coeff[k - j] for j in range(max(0, k - lo + 1), min(ls, k + 1))), start=zero)
|
|
216
|
+
coeff = [sum((self.coeff[j] * other.coeff[k - j] for j in range(max(0, k - lo + 1), min(ls, k + 1))), start=zero)
|
|
217
|
+
for k in range(ls + lo - 1)]
|
|
192
218
|
modulus = self.modulus
|
|
193
219
|
if not modulus and other.modulus:
|
|
194
220
|
modulus = other.modulus
|
|
@@ -202,13 +228,15 @@ class Poly:
|
|
|
202
228
|
return self.__class__([s / other for s in self.coeff], modulus=self.modulus)
|
|
203
229
|
if isinstance(other, self.__class__):
|
|
204
230
|
if not other.modulus:
|
|
205
|
-
raise NotImplementedError(
|
|
231
|
+
raise NotImplementedError(
|
|
232
|
+
"Cannot invert polynomials without modulus.")
|
|
206
233
|
return self * other.inv()
|
|
207
234
|
return NotImplemented
|
|
208
235
|
|
|
209
236
|
def __rtruediv__(self, other) -> "Poly":
|
|
210
237
|
if not self.modulus:
|
|
211
|
-
raise NotImplementedError(
|
|
238
|
+
raise NotImplementedError(
|
|
239
|
+
"Cannot invert polynomials without modulus.")
|
|
212
240
|
return other * self.inv()
|
|
213
241
|
|
|
214
242
|
def __pow__(self, j: int) -> "Poly":
|
|
@@ -218,7 +246,8 @@ class Poly:
|
|
|
218
246
|
res = self.__class__([one], modulus=self.modulus)
|
|
219
247
|
if j < 0:
|
|
220
248
|
if not self.modulus:
|
|
221
|
-
raise NotImplementedError(
|
|
249
|
+
raise NotImplementedError(
|
|
250
|
+
"Cannot divide polynomials without modulus.")
|
|
222
251
|
tmp = self.inv()
|
|
223
252
|
j *= -1
|
|
224
253
|
else:
|
|
@@ -236,7 +265,8 @@ class Poly:
|
|
|
236
265
|
"List of bits of all coefficients."
|
|
237
266
|
ring = self.coeff[0].__class__
|
|
238
267
|
if not (hasattr(ring, 'bits') and callable(ring.bits)):
|
|
239
|
-
raise NotImplementedError(
|
|
268
|
+
raise NotImplementedError(
|
|
269
|
+
"Coefficients cannot be converted to bits.")
|
|
240
270
|
out = []
|
|
241
271
|
for c in self.coeff:
|
|
242
272
|
out += c.bits()
|
|
@@ -275,8 +305,8 @@ class Poly:
|
|
|
275
305
|
oth = [c * tmp for c in other.coeff]
|
|
276
306
|
rem = [c * tmp for c in self.coeff]
|
|
277
307
|
else:
|
|
278
|
-
oth = other.coeff
|
|
279
|
-
rem = [c * one for c in self.coeff]
|
|
308
|
+
oth = other.coeff # "* 1" is here to make sure we get a copy
|
|
309
|
+
rem = [c * one for c in self.coeff]
|
|
280
310
|
for i in range(sd - od + 1):
|
|
281
311
|
tmp = rem[sd - i] * one # "* 1" is here to make sure we get a copy
|
|
282
312
|
div[sd - od - i] = tmp
|
|
@@ -305,10 +335,10 @@ class Poly:
|
|
|
305
335
|
else:
|
|
306
336
|
oth = other.coeff
|
|
307
337
|
for i in range(sd - od + 1):
|
|
308
|
-
tmp = self.coeff[sd - i] * one
|
|
338
|
+
tmp = self.coeff[sd - i] * one # "* 1" is here to make sure we get a copy
|
|
309
339
|
for j in range(od + 1):
|
|
310
340
|
self.coeff[sd - i - j] -= tmp * oth[od - j]
|
|
311
|
-
for i in range(len(self.coeff) - 1, 0, -1):
|
|
341
|
+
for i in range(len(self.coeff) - 1, 0, -1): # strip leading zeros
|
|
312
342
|
if self.coeff[i]:
|
|
313
343
|
break
|
|
314
344
|
self.coeff.pop(i)
|
|
@@ -318,53 +348,63 @@ class Poly:
|
|
|
318
348
|
if not other:
|
|
319
349
|
other = self.modulus
|
|
320
350
|
zero, one, ring = self._guess_ring()
|
|
321
|
-
if isinstance(other,
|
|
322
|
-
other =
|
|
323
|
-
elif not isinstance(other,
|
|
324
|
-
raise NotImplementedError(
|
|
351
|
+
if isinstance(other, self.__class__):
|
|
352
|
+
other = other.coeff
|
|
353
|
+
elif not isinstance(other, list):
|
|
354
|
+
raise NotImplementedError(
|
|
355
|
+
f"{other} must be a list of coefficients or a polynomial.")
|
|
356
|
+
other = Poly(other, ring=ring)
|
|
325
357
|
if not other:
|
|
326
358
|
raise NotImplementedError(f"{other} must be nonzero.")
|
|
327
359
|
r0, r1 = other, self
|
|
328
|
-
y0, y1 = self.__class__([zero]
|
|
360
|
+
y0, y1 = self.__class__([zero]), self.__class__([one])
|
|
329
361
|
while r1:
|
|
330
362
|
q, r = r0.divmod(r1)
|
|
331
363
|
r0, r1 = r1, r
|
|
332
364
|
y0, y1 = y1, y0 - q * y1
|
|
333
365
|
if r0.degree() != 0:
|
|
334
366
|
raise ValueError(f"{self} is not invertible mod {other}.")
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
y0.coeff[i] *= tmp
|
|
338
|
-
return y0
|
|
367
|
+
y0.modulus = self.modulus
|
|
368
|
+
return y0 / r0.coeff[0]
|
|
339
369
|
|
|
340
370
|
def gcd(self, other: "Poly") -> "Poly":
|
|
341
371
|
"Greates common divisor with a given polynomial."
|
|
342
372
|
ring = self._guess_ring()[-1]
|
|
343
|
-
if isinstance(other,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
373
|
+
if not isinstance(other, self.__class__):
|
|
374
|
+
raise NotImplementedError(f"Cannot compute gcd: {
|
|
375
|
+
other} must be a polynomial.")
|
|
376
|
+
if not self:
|
|
377
|
+
if not other:
|
|
378
|
+
return Poly([self.coeff[0]])
|
|
379
|
+
tmp = other / other.coeff[-1]
|
|
380
|
+
tmp.modulus = None
|
|
381
|
+
return tmp
|
|
347
382
|
r0, r1 = other, self
|
|
348
383
|
while r1:
|
|
349
384
|
r0, r1 = r1, r0.divmod(r1)[1]
|
|
350
|
-
|
|
385
|
+
r0.modulus = None
|
|
386
|
+
return r0 / r0.coeff[-1]
|
|
351
387
|
|
|
352
388
|
def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
|
|
353
389
|
"""Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
|
|
354
390
|
zero, one, ring = self._guess_ring()
|
|
355
|
-
if isinstance(other,
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
391
|
+
if not isinstance(other, self.__class__):
|
|
392
|
+
raise NotImplementedError(f"Cannot perform egcd: {
|
|
393
|
+
other} must be a polynomial.")
|
|
394
|
+
if not self:
|
|
395
|
+
if not other:
|
|
396
|
+
return Poly([zero]), Poly([zero]), Poly([zero])
|
|
397
|
+
return other / other.coeff[-1], Poly([zero]), one / other.coeff[-1]
|
|
359
398
|
r0, r1 = other, self
|
|
360
|
-
x0, x1 = self.__class__([one]
|
|
361
|
-
y0, y1 = self.__class__([zero]
|
|
399
|
+
x0, x1 = self.__class__([one]), self.__class__([zero])
|
|
400
|
+
y0, y1 = self.__class__([zero]), self.__class__([one])
|
|
362
401
|
while r1:
|
|
363
402
|
q, r = r0.divmod(r1)
|
|
364
403
|
r0, r1 = r1, r
|
|
365
|
-
x0, x1 = x1, x0 -
|
|
366
|
-
y0, y1 = y1, y0 -
|
|
367
|
-
|
|
404
|
+
x0, x1 = x1, x0 - x1 * q
|
|
405
|
+
y0, y1 = y1, y0 - y1 * q
|
|
406
|
+
lcr0 = r0.coeff[-1]
|
|
407
|
+
return r0 / lcr0, x0 / lcr0, y0 / lcr0
|
|
368
408
|
|
|
369
409
|
def derivative(self) -> "Poly":
|
|
370
410
|
"Returns the formal derivative."
|
|
@@ -372,58 +412,168 @@ class Poly:
|
|
|
372
412
|
if l == 1:
|
|
373
413
|
tmp = 0 * self.coeff[0]
|
|
374
414
|
else:
|
|
375
|
-
tmp = [
|
|
415
|
+
tmp = [j * self.coeff[j] for j in range(1, l)]
|
|
376
416
|
return self.__class__(tmp, modulus=self.modulus)
|
|
377
417
|
|
|
378
|
-
def
|
|
379
|
-
"
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
#
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
418
|
+
def square_free_factors(self) -> dict:
|
|
419
|
+
"Determine the square free factors of a polynomial over a Galois field."
|
|
420
|
+
_, zero, _, q, p = self._guess_field()
|
|
421
|
+
pr = q // p # x^pr will be the p'th root of x
|
|
422
|
+
if self.degree() < 1:
|
|
423
|
+
return {}
|
|
424
|
+
if self.degree() == 1:
|
|
425
|
+
return {self / self.coeff[-1]: 1}
|
|
426
|
+
factors = {}
|
|
427
|
+
w = self / self.coeff[-1]
|
|
428
|
+
c = w.gcd(w.derivative())
|
|
429
|
+
w = w // c
|
|
430
|
+
|
|
431
|
+
# first we find all factors which are not a power of p
|
|
432
|
+
i = 1
|
|
433
|
+
while w.degree() > 0:
|
|
434
|
+
y = w.gcd(c)
|
|
435
|
+
fac = w // y
|
|
436
|
+
if fac.degree() > 0:
|
|
437
|
+
factors[fac] = i
|
|
438
|
+
w = y
|
|
439
|
+
c //= y
|
|
440
|
+
i += 1
|
|
441
|
+
# c is now a power of p
|
|
442
|
+
if c.degree() > 0:
|
|
443
|
+
# compute the p'th root of c
|
|
444
|
+
d = c.degree() // p
|
|
445
|
+
root = [zero] * (d + 1)
|
|
446
|
+
while d >= 0:
|
|
447
|
+
root[d] = c.coeff[d * p]**pr
|
|
448
|
+
d -= 1
|
|
449
|
+
# assert Poly(root)**p == c, root
|
|
450
|
+
for fac, mult in Poly(root).square_free_factors().items():
|
|
451
|
+
factors[fac] = p * mult
|
|
452
|
+
return factors
|
|
453
|
+
|
|
454
|
+
def is_square_free(self) -> bool:
|
|
455
|
+
"Test if the polynomial over a Galois field is square free."
|
|
456
|
+
multiplicities = self.square_free_factors().values()
|
|
457
|
+
return len(multiplicities) == 1 and multiplicities[0] == 1
|
|
458
|
+
|
|
459
|
+
def distinct_degree_factors(self, test: bool = False) -> bool | dict:
|
|
460
|
+
"Factors the polynomial over a Galois field into distinct products of irredicible factors of the same degree."
|
|
461
|
+
_, zero, one, q, _ = self._guess_field()
|
|
462
|
+
if self.degree() < 1:
|
|
463
|
+
raise ValueError("The polynomial must be non-constant.")
|
|
464
|
+
factors = {}
|
|
465
|
+
w = self
|
|
466
|
+
x = Poly([zero, one], modulus=self.coeff) # x
|
|
467
|
+
b = x
|
|
468
|
+
k = 1
|
|
469
|
+
while k <= w.degree()//2:
|
|
470
|
+
b **= q # we compute x^{q^k} mod a recursively
|
|
471
|
+
g = w.gcd(b - x) # gcd with x^{q^k} - x
|
|
407
472
|
if g.degree():
|
|
408
|
-
|
|
409
|
-
|
|
473
|
+
if test:
|
|
474
|
+
return False
|
|
475
|
+
factors[k] = g
|
|
476
|
+
w = w // factors[k]
|
|
477
|
+
k += 1
|
|
478
|
+
if test:
|
|
479
|
+
return True
|
|
480
|
+
if w.degree() > 0:
|
|
481
|
+
factors[w.degree()] = w
|
|
482
|
+
return factors
|
|
410
483
|
|
|
484
|
+
def rabin_test(self) -> bool:
|
|
485
|
+
"Determine if a polynomial is irreducible over a Galois field using a Rabin test."
|
|
486
|
+
return self.distinct_degree_factors(test=True)
|
|
487
|
+
|
|
488
|
+
def is_irreducible(self) -> bool:
|
|
489
|
+
"Test if the polynomial over a Galois field is irreducibel."
|
|
490
|
+
if self.degree() == 0:
|
|
491
|
+
return False
|
|
492
|
+
return self.distinct_degree_factors(test=True)
|
|
411
493
|
|
|
412
|
-
def
|
|
494
|
+
def equal_degree_factors(self, k: int) -> list:
|
|
495
|
+
"Factors a square free product of irreducibel polynomials of degree `k` over a Galois field."
|
|
496
|
+
field, zero, one, q, p = self._guess_field()
|
|
497
|
+
if hasattr(field, "degree"):
|
|
498
|
+
degree = field.degree
|
|
499
|
+
else:
|
|
500
|
+
degree = 1
|
|
501
|
+
d = self.degree()
|
|
502
|
+
if not isinstance(k, int) or k < 1:
|
|
503
|
+
raise ValueError(f"The degree {k} must be a positive integer.")
|
|
504
|
+
if d < k or d % k:
|
|
505
|
+
raise ValueError(
|
|
506
|
+
f"The polynomial is no product of irreducibel polynomials of degree {k}.")
|
|
507
|
+
r = d // k
|
|
508
|
+
qk = (q**k - 1) // 2
|
|
509
|
+
poly = [0] * k + [1]
|
|
510
|
+
|
|
511
|
+
factors = [self]
|
|
512
|
+
while len(factors) < r:
|
|
513
|
+
g = Poly([randint(0, q-1)
|
|
514
|
+
for _ in range(d+1)], ring=field, modulus=self)
|
|
515
|
+
if p == 2: # Gathen-Shoup
|
|
516
|
+
h = g
|
|
517
|
+
for i in range(1, k):
|
|
518
|
+
g += h**q
|
|
519
|
+
if degree > 1:
|
|
520
|
+
h = g
|
|
521
|
+
for i in range(1, degree):
|
|
522
|
+
g += g**2
|
|
523
|
+
else: # Cantor–Zassenhaus
|
|
524
|
+
g = g**qk - one
|
|
525
|
+
factors2 = [] # list for the next round (to avoid the the new factors are tested a second time with the same g)
|
|
526
|
+
for fac in factors:
|
|
527
|
+
if fac.degree() == k: # this one is already irreducible
|
|
528
|
+
factors2.append(fac)
|
|
529
|
+
continue
|
|
530
|
+
gg = fac.gcd(g)
|
|
531
|
+
if gg.degree() == 0 or gg.degree() == fac.degree(): # trivial factor
|
|
532
|
+
factors2.append(fac)
|
|
533
|
+
continue
|
|
534
|
+
gg.modulus = None
|
|
535
|
+
factors2.append(gg)
|
|
536
|
+
factors2.append(fac // gg)
|
|
537
|
+
factors = factors2
|
|
538
|
+
return factors
|
|
539
|
+
|
|
540
|
+
def factor(self) -> dict:
|
|
541
|
+
"Factors a polynomials over a Galois field."
|
|
542
|
+
if self.degree() < 1:
|
|
543
|
+
return {self: 1}
|
|
544
|
+
c = Poly([self.coeff[-1]])
|
|
545
|
+
one = c**0
|
|
546
|
+
if c != one:
|
|
547
|
+
factors = {c: 1}
|
|
548
|
+
else:
|
|
549
|
+
factors = {}
|
|
550
|
+
for fac1, m in self.square_free_factors().items():
|
|
551
|
+
for k, fac2 in fac1.distinct_degree_factors().items():
|
|
552
|
+
if k == fac2.degree(): # irreducible
|
|
553
|
+
factors[fac2] = m
|
|
554
|
+
else:
|
|
555
|
+
for fac3 in fac2.equal_degree_factors(k):
|
|
556
|
+
factors[fac3] = m
|
|
557
|
+
return dict(sorted(factors.items(), key=lambda item: item[0].degree()))
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def lagrange_interpolation(x_coordinates: list, y_coordinates: list) -> "Poly":
|
|
413
561
|
"Compute the Lagrange interpolation polynomial from a given list of x and y values."
|
|
414
562
|
l = len(x_coordinates)
|
|
415
563
|
if l == 0 or l != len(y_coordinates):
|
|
416
|
-
raise ValueError(
|
|
564
|
+
raise ValueError(
|
|
565
|
+
'List of x and y values must be nonempty of equal length.')
|
|
417
566
|
if l != len(set(x_coordinates)):
|
|
418
567
|
raise ValueError('List of x values must not contain duplicate values.')
|
|
419
|
-
#
|
|
568
|
+
# guess zero and one
|
|
420
569
|
zero = 0 * x_coordinates[0]
|
|
421
570
|
one = zero**0
|
|
422
571
|
|
|
423
572
|
poly = Poly([zero])
|
|
424
|
-
L_all = prod([Poly([-x, one]) for x in x_coordinates], start
|
|
573
|
+
L_all = prod([Poly([-x, one]) for x in x_coordinates], start=one)
|
|
425
574
|
|
|
426
575
|
for x, y in zip(x_coordinates, y_coordinates):
|
|
427
576
|
L = L_all // Poly([-x, one])
|
|
428
|
-
poly += (y /
|
|
577
|
+
poly += (y /
|
|
578
|
+
prod([x - xx for xx in x_coordinates if xx != x], start=one)) * L
|
|
429
579
|
return poly
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: Implemenation of same basic algorithms used in cryptography.
|
|
5
5
|
Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
|
|
6
6
|
License: MIT
|
|
@@ -42,7 +42,7 @@ 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, irreducibility test, Lagrange interpolation)
|
|
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
|
|
@@ -13,26 +13,25 @@ def test_GF2_ops():
|
|
|
13
13
|
assert gf(1) == gf(1)
|
|
14
14
|
assert not gf(1) == gf(0)
|
|
15
15
|
assert not gf(0) == 0
|
|
16
|
-
if gf.
|
|
16
|
+
if gf.degree < 17:
|
|
17
17
|
assert len(list(gf)) == gf.order
|
|
18
18
|
assert len(list(gf.star())) == gf.order - 1
|
|
19
19
|
assert len(list(gf.generators())) == euler_phi(gf.order - 1)
|
|
20
20
|
for _ in range(num_tests):
|
|
21
|
-
a = randint(0, gf.order-1)
|
|
22
|
-
b = randint(0, gf.order-1)
|
|
23
|
-
assert (
|
|
24
|
-
assert (
|
|
25
|
-
assert (
|
|
26
|
-
if
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
a = gf(randint(0, gf.order-1))
|
|
22
|
+
b = gf(randint(0, gf.order-1))
|
|
23
|
+
assert (a + b).poly() == a.poly() + b.poly()
|
|
24
|
+
assert (a - b).poly() == a.poly() - b.poly()
|
|
25
|
+
assert (a * b).poly() == a.poly() * b.poly()
|
|
26
|
+
if a:
|
|
27
|
+
assert (a**-1).poly() == a.poly().inv()
|
|
28
|
+
if b:
|
|
29
|
+
assert (a / b).poly() == a.poly() / b.poly()
|
|
30
|
+
assert a.sqrt()**2 == a
|
|
29
31
|
with pytest.raises(ValueError):
|
|
30
32
|
gf(1) / gf(0)
|
|
31
33
|
with pytest.raises(ValueError):
|
|
32
34
|
gf(0)**-1
|
|
33
|
-
for _ in range(num_tests):
|
|
34
|
-
a = randint(1, gf.order-1)
|
|
35
|
-
assert (gf(a)**-1).poly() == gf(a).poly().inv()
|
|
36
35
|
|
|
37
36
|
def test_GF2_order():
|
|
38
37
|
for n in range(1, 8):
|
|
@@ -53,7 +52,7 @@ def test_GF2P_order():
|
|
|
53
52
|
xx *=x
|
|
54
53
|
assert o == x.order()
|
|
55
54
|
|
|
56
|
-
def
|
|
55
|
+
def test_GF2_generator():
|
|
57
56
|
for n in range(1, 9):
|
|
58
57
|
gf = GF2(n)
|
|
59
58
|
a = gf.generator()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from math import prod
|
|
3
|
+
from random import sample, choices, randint, seed
|
|
4
|
+
from kryptools import Poly, Zmod, GF2, lagrange_interpolation
|
|
5
|
+
from kryptools import divisors, moebius_mu
|
|
6
|
+
seed(0)
|
|
7
|
+
num_tests = 10
|
|
8
|
+
|
|
9
|
+
def test_Poly():
|
|
10
|
+
p = Poly([2, 0, 2])
|
|
11
|
+
q = Poly([1, 2, 3, 4])
|
|
12
|
+
d, m = q.divmod(p)
|
|
13
|
+
assert d * p + m == q
|
|
14
|
+
for _ in range(num_tests):
|
|
15
|
+
p = Poly([randint(0,100) for _ in range(10)])
|
|
16
|
+
q = Poly([randint(0,100) for _ in range(12)])
|
|
17
|
+
x = randint(0,100)
|
|
18
|
+
assert p(x) == sum((c * x**j for j, c in enumerate(p.coeff)), start=0)
|
|
19
|
+
assert (p + q)(x) == p(x) + q(x)
|
|
20
|
+
assert (p - q)(x) == p(x) - q(x)
|
|
21
|
+
assert (p*q)(x) == p(x) * q(x)
|
|
22
|
+
|
|
23
|
+
gf = Zmod(5)
|
|
24
|
+
p = Poly([2, 0, 2], ring = gf)
|
|
25
|
+
q = Poly([1, 2, 3, 4], ring = gf)
|
|
26
|
+
d, m = q.divmod(p)
|
|
27
|
+
assert d * p + m == q
|
|
28
|
+
for _ in range(num_tests):
|
|
29
|
+
p = Poly(gf.random(10))
|
|
30
|
+
q = Poly(gf.random(12))
|
|
31
|
+
x = gf.random()
|
|
32
|
+
assert p(x) == sum((c * x**j for j, c in enumerate(p.coeff)), start=gf(0))
|
|
33
|
+
assert (p + q)(x) == p(x) + q(x)
|
|
34
|
+
assert (p - q)(x) == p(x) - q(x)
|
|
35
|
+
assert (p * q)(x) == p(x) * q(x)
|
|
36
|
+
|
|
37
|
+
gf = GF2(8, modulus=0b100011011)
|
|
38
|
+
p = Poly([2, 1, 1, 3], modulus=[1, 0, 0, 0, 1], ring=gf)
|
|
39
|
+
q = p.inv()
|
|
40
|
+
assert p * q == Poly([1], modulus=[1, 0, 0, 0, 1], ring=gf)
|
|
41
|
+
for _ in range(num_tests):
|
|
42
|
+
p = Poly(gf.random(10))
|
|
43
|
+
q = Poly(gf.random(12))
|
|
44
|
+
x = gf.random()
|
|
45
|
+
assert p(x) == sum((c * x**j for j, c in enumerate(p.coeff)), start=gf(0))
|
|
46
|
+
assert (p + q)(x) == p(x) + q(x)
|
|
47
|
+
assert (p - q)(x) == p(x) - q(x)
|
|
48
|
+
assert (p * q)(x) == p(x) * q(x)
|
|
49
|
+
|
|
50
|
+
def test_rabin():
|
|
51
|
+
for gf,t in [ [Zmod(7), 4], [GF2(4), 3]]:
|
|
52
|
+
order = len(list(gf))
|
|
53
|
+
count = 0
|
|
54
|
+
for i in range(order**t):
|
|
55
|
+
c = []
|
|
56
|
+
while i:
|
|
57
|
+
i, m = divmod(i, order)
|
|
58
|
+
c.append(m)
|
|
59
|
+
c += [0] * (t- len(c)) + [1]
|
|
60
|
+
a = Poly(c, ring=gf)
|
|
61
|
+
if a.rabin_test():
|
|
62
|
+
count += 1
|
|
63
|
+
assert count == sum([moebius_mu(d) * order ** (t // d) for d in divisors(t)]) // t
|
|
64
|
+
|
|
65
|
+
def test_factor():
|
|
66
|
+
for gf, t in [ [Zmod(7), 14], [GF2(4), 9]]:
|
|
67
|
+
order = len(list(gf))
|
|
68
|
+
one = Poly([gf(1)])
|
|
69
|
+
for _ in range(20):
|
|
70
|
+
p = one
|
|
71
|
+
while p.degree() < 1:
|
|
72
|
+
p = Poly(gf.random(t), ring =gf)
|
|
73
|
+
factors = p.factor()
|
|
74
|
+
assert prod([f**k for f,k in factors.items()], start = one) == p, p
|
|
75
|
+
for fac in factors.keys():
|
|
76
|
+
assert fac.degree() == 0 or fac.rabin_test(), p
|
|
77
|
+
|
|
78
|
+
def test_lagrange():
|
|
79
|
+
for gf in [ Zmod(2), Zmod(3), Zmod(5), Zmod(7), Zmod(23), GF2(4), GF2(6)]:
|
|
80
|
+
all = list(gf)
|
|
81
|
+
l = len(all)//2
|
|
82
|
+
for _ in range(10):
|
|
83
|
+
x_coordinates = sample(all, l)
|
|
84
|
+
y_coordinates = choices(all, k = l)
|
|
85
|
+
p = lagrange_interpolation(x_coordinates, y_coordinates)
|
|
86
|
+
assert p.degree() <= len(x_coordinates) - 1
|
|
87
|
+
for x, y in zip(x_coordinates, y_coordinates):
|
|
88
|
+
assert p(x) == y
|
kryptools-1.4/tests/test_poly.py
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from random import sample, choices
|
|
3
|
-
from kryptools import Poly, Zmod, GF2, lagrange_interpolation
|
|
4
|
-
from kryptools import divisors, moebius_mu
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_Poly():
|
|
8
|
-
p = Poly([2, 0, 2])
|
|
9
|
-
q = Poly([1, 2, 3, 4])
|
|
10
|
-
d, m = q.divmod(p)
|
|
11
|
-
assert d * p + m == q
|
|
12
|
-
|
|
13
|
-
Z_5 = Zmod(5)
|
|
14
|
-
p.map(Z_5)
|
|
15
|
-
q.map(Z_5)
|
|
16
|
-
d, m = q.divmod(p)
|
|
17
|
-
assert d * p + m == q
|
|
18
|
-
|
|
19
|
-
gf = GF2(8, modulus=0b100011011)
|
|
20
|
-
p = Poly([2, 1, 1, 3], modulus=[1, 0, 0, 0, 1], ring=gf)
|
|
21
|
-
q = p.inv()
|
|
22
|
-
assert p * q == Poly([1], modulus=[1, 0, 0, 0, 1], ring=gf)
|
|
23
|
-
|
|
24
|
-
def test_rabin():
|
|
25
|
-
for gf,t in [ [Zmod(7), 4], [GF2(4), 3]]:
|
|
26
|
-
order = len(list(gf))
|
|
27
|
-
count = 0
|
|
28
|
-
for i in range(order**t):
|
|
29
|
-
c = []
|
|
30
|
-
while i:
|
|
31
|
-
i, m = divmod(i, order)
|
|
32
|
-
c.append(m)
|
|
33
|
-
c += [0] * (t- len(c)) + [1]
|
|
34
|
-
a = Poly(c, ring=gf)
|
|
35
|
-
if a.rabin_test():
|
|
36
|
-
count += 1
|
|
37
|
-
assert count == sum([moebius_mu(d) * order ** (t // d) for d in divisors(t)]) // t
|
|
38
|
-
|
|
39
|
-
def test_lagrange():
|
|
40
|
-
for gf in [ Zmod(2), Zmod(3), Zmod(5), Zmod(7), Zmod(23), GF2(4), GF2(6)]:
|
|
41
|
-
all = list(gf)
|
|
42
|
-
l = len(all)//2
|
|
43
|
-
for _ in range(10):
|
|
44
|
-
x_coordinates = sample(all, l)
|
|
45
|
-
y_coordinates = choices(all, k = l)
|
|
46
|
-
p = lagrange_interpolation(x_coordinates, y_coordinates)
|
|
47
|
-
assert p.degree() <= len(x_coordinates) - 1
|
|
48
|
-
for x, y in zip(x_coordinates, y_coordinates):
|
|
49
|
-
assert p(x) == y
|
|
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
|