kryptools 1.9.4__tar.gz → 1.9.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.
Files changed (58) hide show
  1. {kryptools-1.9.4 → kryptools-1.9.6}/PKG-INFO +1 -1
  2. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/Zmod.py +16 -2
  3. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/__init__.py +1 -1
  4. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/blockcipher.py +65 -30
  5. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/dlp_rho.py +5 -0
  6. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor_rho.py +1 -1
  7. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/poly.py +6 -0
  8. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools.egg-info/PKG-INFO +1 -1
  9. {kryptools-1.9.4 → kryptools-1.9.6}/pyproject.toml +1 -1
  10. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_aes.py +13 -8
  11. {kryptools-1.9.4 → kryptools-1.9.6}/LICENSE +0 -0
  12. {kryptools-1.9.4 → kryptools-1.9.6}/README.md +0 -0
  13. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/GF2.py +0 -0
  14. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/aes.py +0 -0
  15. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/code.py +0 -0
  16. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/conway_polynomials.py +0 -0
  17. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/des.py +0 -0
  18. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/dlp.py +0 -0
  19. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/dlp_bsgs.py +0 -0
  20. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/dlp_ic.py +0 -0
  21. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/dlp_qs.py +0 -0
  22. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/ec.py +0 -0
  23. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor.py +0 -0
  24. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor_dix.py +0 -0
  25. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor_ecm.py +0 -0
  26. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor_fmt.py +0 -0
  27. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor_pm1.py +0 -0
  28. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/factor_qs.py +0 -0
  29. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/intfuncs.py +0 -0
  30. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/keccak.py +0 -0
  31. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/la.py +0 -0
  32. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/lat.py +0 -0
  33. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/lat2.py +0 -0
  34. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/nt.py +0 -0
  35. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/primes.py +0 -0
  36. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools/sha1.py +0 -0
  37. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools.egg-info/SOURCES.txt +0 -0
  38. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools.egg-info/dependency_links.txt +0 -0
  39. {kryptools-1.9.4 → kryptools-1.9.6}/kryptools.egg-info/top_level.txt +0 -0
  40. {kryptools-1.9.4 → kryptools-1.9.6}/setup.cfg +0 -0
  41. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_GF2.py +0 -0
  42. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_Zmod.py +0 -0
  43. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_code.py +0 -0
  44. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_des.py +0 -0
  45. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_dlog.py +0 -0
  46. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_ec.py +0 -0
  47. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_factor.py +0 -0
  48. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_factor_fmt.py +0 -0
  49. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_factor_pm1.py +0 -0
  50. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_factor_qs.py +0 -0
  51. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_intfuncs.py +0 -0
  52. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_keccak.py +0 -0
  53. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_la.py +0 -0
  54. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_lat.py +0 -0
  55. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_nt.py +0 -0
  56. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_poly.py +0 -0
  57. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_primes.py +0 -0
  58. {kryptools-1.9.4 → kryptools-1.9.6}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.9.4
3
+ Version: 1.9.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
@@ -2,8 +2,9 @@
2
2
  Ring of intergers modulo `n`.
3
3
  """
4
4
 
5
- from math import gcd
5
+ from math import gcd, floor
6
6
  from random import randint
7
+ from fractions import Fraction
7
8
  from .factor import factorint
8
9
  from .primes import is_prime
9
10
  from .nt import sqrt_mod, crt
@@ -60,6 +61,13 @@ class Zmod:
60
61
  def __contains__(self, other: "ZmodPoint") -> bool:
61
62
  return isinstance(other, ZmodPoint) and self.n == other.ring.n
62
63
 
64
+ def decompress(self, x: int, d: int) -> "ZmodPoint":
65
+ "Decompress an integer `x` with at most `d` bits to Z_n."
66
+ if not isinstance(d, int) or d <= 0 or d >= self.n.bit_length():
67
+ raise ValueError("The number of bits must be between 1 and the bit length of the modulus.")
68
+ x %= 2**d
69
+ return ZmodPoint( floor(Fraction(x * self.n, 2**d) + Fraction(1, 2) ), self)
70
+
63
71
  def random(self, num: int = 0) -> "ZmodPoint":
64
72
  "Return a single random point or a list of random points."
65
73
  if num:
@@ -255,8 +263,14 @@ class ZmodPoint:
255
263
  return self.x
256
264
  return self.ring.n - self.x
257
265
 
266
+ def compress(self, d: int) -> list:
267
+ "Returns the value compressed to `d` bits."
268
+ if not isinstance(d, int) or d <= 0 or d > self.ring.n.bit_length():
269
+ raise ValueError("The number of bits must be between 1 and the bit length of the modulus.")
270
+ return floor(Fraction(self.x * 2**d, self.ring.n) + Fraction(1, 2) ) % 2**d
271
+
258
272
  def bits(self) -> list:
259
- "Convert to a list of bits."
273
+ "Returns a list of bits."
260
274
  return [int(d) for d in str(format(self.x, "0" + str(int.bit_length(self.ring.n - 1)) + "b"))]
261
275
 
262
276
  def sharp(self):
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
5
5
  __author__ = "Gerald Teschl"
6
6
  __copyright__ = "Copyright 2024-2026, Gerald Teschl"
7
7
  __license__ = "MIT License"
8
- __version__ = "1.9.4"
8
+ __version__ = "1.9.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
@@ -13,10 +13,11 @@ class BlockCipher:
13
13
 
14
14
  blocksize: int = 1 # blocksize in bytes
15
15
 
16
- def __init__(self, key: bytes|int = None, mode: str|None = None):
16
+ def __init__(self, key: bytes|int = None, mode: str|None = None, ctrlen: int | None = None):
17
17
  self.key = None # here we store the key (if any)
18
18
  self.cipher = None # here we can store an instance of the cipher initialized with the key
19
19
  self.mode = mode # here we store the default en/decryption mode
20
+ self.ctrlen = ctrlen # default counter length for CTR mode
20
21
  if key:
21
22
  self.set_key(key)
22
23
 
@@ -142,6 +143,7 @@ class BlockCipher:
142
143
  if iv is None:
143
144
  z = randbytes(self.__class__.blocksize) # iv
144
145
  else:
146
+ assert len(iv) == self.__class__.blocksize, "Length of IV must be equal to the block length."
145
147
  z = iv
146
148
  ctext.extend(z) # add iv as first block of the cipher text
147
149
  for b in self.blocksplit(text, False):
@@ -159,40 +161,73 @@ class BlockCipher:
159
161
  text.extend(bytexor(b, z))
160
162
  return bytes(text)
161
163
 
162
- def adv_ctr(self, z: bytes, s: int) -> bytes:
164
+ def adv_ctr(self, z: bytearray, ctrlen: int) -> bytes:
163
165
  "Advance the counter."
164
- zz = bytearray(z)
165
- for i in range(self.__class__.blocksize-1, self.__class__.blocksize-s-1, -1):
166
+ for i in range(self.__class__.blocksize-1, self.__class__.blocksize-ctrlen-1, -1):
166
167
  c = z[i]+1
167
168
  if c < 256:
168
- zz[i] = c
169
+ z[i] = c
169
170
  break
170
- zz[i] = 0
171
- return zz
171
+ z[i] = 0
172
+ return z
172
173
 
173
- def encrypt_ctr(self, text: bytes, iv: bytes | None = None) -> bytes:
174
+ def encrypt_ctr(self, text: bytes, ctrlen: int | None = None, nonce: bytes = b'', iv: bytes | None = None) -> bytes:
174
175
  "Encrypt using CTR mode."
175
- ctext = bytearray(b'')
176
+ if ctrlen is None:
177
+ ctrlen = self.ctrlen
178
+ if ctrlen is None:
179
+ if self.blocksize >= 8:
180
+ ctrlen = 4
181
+ else:
182
+ ctrlen = 0 # use the entire iv as counter
183
+ assert 0 <= ctrlen <= self.__class__.blocksize, "Counter too long."
184
+ assert len(nonce) + ctrlen <= self.__class__.blocksize, "Nonce too long."
176
185
  if iv is None:
177
- ctr = randbytes(self.__class__.blocksize) # iv
178
- else:
179
- ctr = iv
180
- ctext.extend(ctr) # add iv as first block of the cipher text
186
+ iv = randbytes(self.__class__.blocksize - ctrlen - len(nonce))# iv
187
+ iv = nonce + iv
188
+ assert len(iv) + ctrlen <= self.__class__.blocksize, "IV too long."
189
+ if ctrlen == 0 and len(iv) < self.__class__.blocksize:
190
+ ctrlen = self.__class__.blocksize - len(iv)
191
+ assert len(iv) + ctrlen == self.__class__.blocksize, "Length of nonce + iv + ctr do not match the blocksize."
192
+ ctr = bytearray(iv + b"\x00" * ctrlen)
193
+ if ctrlen == 0:
194
+ ctrlen = self.__class__.blocksize
195
+
196
+ ctext = bytearray(iv) # add nonce+iv as first block of the cipher text
181
197
  for b in self.blocksplit(text, False):
198
+ ctr = self.adv_ctr(ctr, ctrlen)
182
199
  z = self.encrypt_block(ctr)
183
200
  ctext.extend(bytexor(b, z))
184
- ctr = self.adv_ctr(ctr, self.__class__.blocksize)
185
201
  return bytes(ctext)
186
202
 
187
- def decrypt_ctr(self, ctext: bytes) -> bytes:
203
+ def decrypt_ctr(self, ctext: bytes, ctrlen: int | None = None, nonce: bytes = b'', iv: bytes | None = None) -> bytes:
188
204
  "Decrypt using CTR mode."
205
+ if ctrlen is None:
206
+ ctrlen = self.ctrlen
207
+ if ctrlen is None:
208
+ if self.blocksize >= 8:
209
+ ctrlen = 4
210
+ else:
211
+ ctrlen = 0 # use the entire iv as counter
212
+ assert 0 <= ctrlen <= self.__class__.blocksize, "Counter too long."
213
+ assert len(nonce) + ctrlen <= self.__class__.blocksize, "Nonce too long."
214
+ if iv is None:
215
+ iv = ctext[:self.__class__.blocksize - ctrlen - len(nonce)]
216
+ ctext = ctext[self.__class__.blocksize - ctrlen - len(nonce):] # remove iv
217
+ iv = nonce + iv
218
+ assert len(iv) +ctrlen <= self.__class__.blocksize, "IV too long."
219
+ if ctrlen == 0 and len(iv) < self.__class__.blocksize:
220
+ ctrlen = self.__class__.blocksize - len(iv)
221
+ assert len(iv) + ctrlen == self.__class__.blocksize, "Length of nonce + iv + ctr do not match the blocksize."
222
+ ctr = bytearray(iv + b"\x00" * ctrlen)
223
+ if ctrlen == 0:
224
+ ctrlen = self.__class__.blocksize
225
+
189
226
  text = bytearray(b'')
190
- ctr = ctext[:self.__class__.blocksize] # iv
191
- ctext = ctext[self.__class__.blocksize:] # remove iv
192
227
  for b in self.blocksplit(ctext, False):
228
+ ctr = self.adv_ctr(ctr, ctrlen)
193
229
  z = self.encrypt_block(ctr)
194
230
  text.extend(bytexor(b, z))
195
- ctr = self.adv_ctr(ctr, self.__class__.blocksize)
196
231
  return bytes(text)
197
232
 
198
233
  def mac_cbc(self, text: bytes, padding: bool = True) -> bytes:
@@ -286,21 +321,21 @@ class BlockCipher:
286
321
  "Encrypt using Galois CTR mode."
287
322
  assert self.__class__.blocksize == 16, "GHASH requires a blocksize of 16 bytes."
288
323
  ctext = bytearray(b'')
289
- len_ctr = self.__class__.blocksize // 4
324
+ ctrlen = self.__class__.blocksize // 4
290
325
  if iv is None:
291
- iv = randbytes(self.__class__.blocksize - len_ctr)
326
+ iv = randbytes(self.__class__.blocksize - ctrlen)
292
327
  else:
293
328
  assert len(iv) + \
294
- len_ctr == self.__class__.blocksize, "IV has inappropriate length!"
295
- ctr = iv + (len_ctr - 1) * b'\x00' + b'\x01'
296
- ctr0 = ctr
329
+ ctrlen == self.__class__.blocksize, "IV has inappropriate length!"
330
+ ctr = bytearray(iv + (ctrlen - 1) * b'\x00' + b'\x01')
331
+ ctr0 = bytes(ctr)
297
332
  ctext.extend(iv)
298
333
  h, len_block, tag = self._aad_helper(len(text), aad)
299
334
  for b in self.blocksplit(text, 0):
300
- ctr = self.adv_ctr(ctr, len_ctr)
335
+ ctr = self.adv_ctr(ctr, ctrlen)
301
336
  z = self.encrypt_block(ctr)
302
337
  ctext.extend(bytexor(b, z))
303
- tag = self.ghash(ctext[self.__class__.blocksize - len_ctr:], h, init=tag)
338
+ tag = self.ghash(ctext[self.__class__.blocksize - ctrlen:], h, init=tag)
304
339
  tag = self.ghash(len_block, h, init=tag).to_bytes(
305
340
  self.__class__.blocksize, byteorder="big")
306
341
  z = self.encrypt_block(ctr0)
@@ -311,11 +346,11 @@ class BlockCipher:
311
346
  "Decrypt using Galois CTR mode."
312
347
  assert self.__class__.blocksize == 16, "GHASH requires a blocksize of 16 bytes."
313
348
  text = bytearray(b'')
314
- len_ctr = self.__class__.blocksize // 4
315
- iv = ctext[:self.__class__.blocksize - len_ctr] # iv
316
- ctr = iv + (len_ctr - 1) * b'\x00' + b'\x01'
349
+ ctrlen = self.__class__.blocksize // 4
350
+ iv = ctext[:self.__class__.blocksize - ctrlen] # iv
351
+ ctr = bytearray(iv + (ctrlen - 1) * b'\x00' + b'\x01')
317
352
  mac = ctext[-self.__class__.blocksize:] # mac
318
- ctext = ctext[self.__class__.blocksize - len_ctr:-self.__class__.blocksize]
353
+ ctext = ctext[self.__class__.blocksize - ctrlen:-self.__class__.blocksize]
319
354
  h, len_block, tag = self._aad_helper(len(ctext), aad)
320
355
  tag = self.ghash(ctext, h, init=tag)
321
356
  tag = self.ghash(len_block, h, init=tag).to_bytes(
@@ -324,7 +359,7 @@ class BlockCipher:
324
359
  tag = bytexor(tag, z)
325
360
  assert tag == mac, "Incorrect MAC!"
326
361
  for b in self.blocksplit(ctext, 0):
327
- ctr = self.adv_ctr(ctr, len_ctr)
362
+ ctr = self.adv_ctr(ctr, ctrlen)
328
363
  z = self.encrypt_block(ctr)
329
364
  text.extend(bytexor(b, z))
330
365
  return bytes(text)
@@ -12,6 +12,11 @@ def dlog_rho(a: int, b: int, n: int, m: int = None, brent=0) -> int:
12
12
  b %= n
13
13
  if not m:
14
14
  m = n - 1
15
+ else:
16
+ if pow(a, m, n) != 1:
17
+ raise ValueError("Incorrect order.")
18
+ if pow(b, m, n) != 1:
19
+ raise ValueError("DLP not solvable.")
15
20
  trys = 10
16
21
  max_iter = 10 * isqrt(m) # stop iterating and try a different start value
17
22
 
@@ -9,7 +9,7 @@ from random import randint
9
9
  def factor_rho(n: int, x: int = 2, maxinit: int = 10, maxiter: int = 1000, brent: bool = True, verbose: int = 0) -> int | None:
10
10
  """Factor `n` using the Pollard's rho method with start value `x`."""
11
11
  if not maxiter:
12
- maxiter = 10 * isqrt(n) # stop iterating and try a different start value
12
+ maxiter = 3 * isqrt(isqrt(n)) # stop iterating and try a different start value
13
13
 
14
14
  def f(x: int) -> int:
15
15
  return (pow(x, 2, n) + 2) % n
@@ -220,6 +220,12 @@ class Poly:
220
220
  return self.__class__(list(map(func, self.coeff)), modulus = list(map(func, self.modulus)))
221
221
  return self.__class__(list(map(func, self.coeff)), modulus = self.modulus, cyclic = self.cyclic)
222
222
 
223
+ def compress(self, d: int) -> list:
224
+ "Retun a list of compressed coefficients."
225
+ if not hasattr(self.coeff[0], "compress"):
226
+ raise ValueError("The unerlying ring does not seem to support compression.")
227
+ return [ c.compress(d) for c in self.coeff ]
228
+
223
229
  def _check_type(self, other):
224
230
  "Check if self and other can be added/multiplied."
225
231
  return isinstance(other, int) or (isinstance(other, Number) and isinstance(self.coeff[0], Number)) or type(other) == type(self.coeff[0]) # pylint: disable=C0123
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.9.4
3
+ Version: 1.9.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "1.9.4"
3
+ version = "1.9.6"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -46,26 +46,28 @@ def test_AES():
46
46
 
47
47
  # AES-128-CTR
48
48
  k = bytes.fromhex('AE6852F8121067CC4BF7A5765577F39E')
49
- iv = bytes.fromhex('00000030000000000000000000000001')
49
+ nonce = bytes.fromhex("00000030")
50
+ iv = bytes.fromhex('0000000000000000')
50
51
  m = bytes.fromhex('53696E676C6520626C6F636B206D7367')
51
52
  c = bytes.fromhex('E4095D4FB7A7B3792D6175A3261311B8')
52
53
 
53
54
  aes.set_key(k)
54
- res = aes.encrypt_ctr(m, iv=iv)
55
- assert res[16:] == c
56
- assert aes.decrypt_ctr(res) == m
55
+ res = aes.encrypt_ctr(m, nonce=nonce, iv=iv)
56
+ assert res[len(nonce) + len(iv):] == c
57
+ assert aes.decrypt_ctr(res, aes.blocksize - len(nonce) - len(iv)) == m
57
58
 
58
59
  k = bytes.fromhex('7691BE035E5020A8AC6E618529F9A0DC')
59
- iv = bytes.fromhex('00E0017B27777F3F4A1786F000000001')
60
+ nonce = bytes.fromhex("00E0017B")
61
+ iv = bytes.fromhex('27777F3F4A1786F0')
60
62
  m = bytes.fromhex(
61
63
  '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223')
62
64
  c = bytes.fromhex(
63
65
  'C1CF48A89F2FFDD9CF4652E9EFDB72D74540A42BDE6D7836D59A5CEAAEF3105325B2072F')
64
66
 
65
67
  aes.set_key(k)
66
- res = aes.encrypt_ctr(m, iv=iv)
67
- assert res[16:] == c
68
- assert aes.decrypt_ctr(res) == m
68
+ res = aes.encrypt_ctr(m, nonce=nonce, iv=iv)
69
+ assert res[len(nonce) + len(iv):] == c
70
+ assert aes.decrypt_ctr(res,aes.blocksize - len(nonce) - len(iv)) == m
69
71
 
70
72
  # AES-128-GCM
71
73
  k = bytes.fromhex('00000000000000000000000000000000')
@@ -79,6 +81,9 @@ def test_AES():
79
81
  res = aes.encrypt_gcm(m, aad=aad, iv=iv)
80
82
  assert res[12:-16] == c and res[-16:] == mac
81
83
  assert aes.decrypt_gcm(res, aad=aad) == m
84
+ with pytest.raises(AssertionError):
85
+ assert aes.decrypt_gcm(res, aad= b'\x00') == m
86
+
82
87
 
83
88
  k = bytes.fromhex('00000000000000000000000000000000')
84
89
  iv = bytes.fromhex('000000000000000000000000')
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