kryptools 1.9.2__tar.gz → 1.9.4__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.2 → kryptools-1.9.4}/PKG-INFO +1 -1
  2. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/__init__.py +3 -3
  3. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/la.py +7 -5
  4. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/lat.py +12 -0
  5. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/lat2.py +8 -0
  6. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/poly.py +96 -38
  7. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools.egg-info/PKG-INFO +1 -1
  8. {kryptools-1.9.2 → kryptools-1.9.4}/pyproject.toml +1 -1
  9. {kryptools-1.9.2 → kryptools-1.9.4}/LICENSE +0 -0
  10. {kryptools-1.9.2 → kryptools-1.9.4}/README.md +0 -0
  11. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/GF2.py +0 -0
  12. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/Zmod.py +0 -0
  13. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/aes.py +0 -0
  14. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/blockcipher.py +0 -0
  15. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/code.py +0 -0
  16. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/conway_polynomials.py +0 -0
  17. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/des.py +0 -0
  18. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/dlp.py +0 -0
  19. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/dlp_bsgs.py +0 -0
  20. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/dlp_ic.py +0 -0
  21. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/dlp_qs.py +0 -0
  22. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/dlp_rho.py +0 -0
  23. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/ec.py +0 -0
  24. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor.py +0 -0
  25. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor_dix.py +0 -0
  26. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor_ecm.py +0 -0
  27. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor_fmt.py +0 -0
  28. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor_pm1.py +0 -0
  29. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor_qs.py +0 -0
  30. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/factor_rho.py +0 -0
  31. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/intfuncs.py +0 -0
  32. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/keccak.py +0 -0
  33. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/nt.py +0 -0
  34. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/primes.py +0 -0
  35. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools/sha1.py +0 -0
  36. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools.egg-info/SOURCES.txt +0 -0
  37. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools.egg-info/dependency_links.txt +0 -0
  38. {kryptools-1.9.2 → kryptools-1.9.4}/kryptools.egg-info/top_level.txt +0 -0
  39. {kryptools-1.9.2 → kryptools-1.9.4}/setup.cfg +0 -0
  40. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_GF2.py +0 -0
  41. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_Zmod.py +0 -0
  42. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_aes.py +0 -0
  43. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_code.py +0 -0
  44. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_des.py +0 -0
  45. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_dlog.py +0 -0
  46. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_ec.py +0 -0
  47. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_factor.py +0 -0
  48. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_factor_fmt.py +0 -0
  49. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_factor_pm1.py +0 -0
  50. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_factor_qs.py +0 -0
  51. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_intfuncs.py +0 -0
  52. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_keccak.py +0 -0
  53. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_la.py +0 -0
  54. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_lat.py +0 -0
  55. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_nt.py +0 -0
  56. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_poly.py +0 -0
  57. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_primes.py +0 -0
  58. {kryptools-1.9.2 → kryptools-1.9.4}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.9.2
3
+ Version: 1.9.4
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
@@ -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.2"
8
+ __version__ = "1.9.4"
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,8 +16,8 @@ from .dlp import dlog
16
16
  from .ec import EC_Weierstrass
17
17
  from .la import Matrix, zeros, eye, circulant, BinaryMatrix
18
18
  from .lat import Lattice, random_unimodular_matrix
19
- from .lat2 import q_ary_lattice, gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_plane_cvp, babai_plane_bnd, lll, svp, cvp, sis, isis, sis_search, isis_search
20
- from .poly import Poly, PolyBinMult, lagrange_interpolation
19
+ from .lat2 import q_ary_lattice, gram_det, hadamard_ratio, hermite_factor, root_hermite_factor, hermite_nf, gram_schmidt, babai_round_cvp, babai_plane_cvp, babai_plane_bnd, lll, svp, cvp, sis, isis, sis_search, isis_search
20
+ from .poly import Poly, PolyBinMult, lagrange_interpolation, sylvester, resultant
21
21
  from .Zmod import Zmod
22
22
  from .GF2 import GF2, GF2_aes, GF2_miniaes, GF2_ghash
23
23
  from .code import gen2pchk, hamming_dist, BinaryCode, GolayCode, HammingCode, ReedMullerCode, GoppaCode, CyclicCode, ReedSolomonCode, BCHCode
@@ -709,7 +709,7 @@ class Matrix:
709
709
  R = [ list(map(lambda x: Fraction(int(x)), row)) for row in self.matrix ]
710
710
  zero, one = ring(0), 1
711
711
  elif self.is_integer():
712
- ring = None
712
+ ring = int
713
713
  R = [ list(map(Fraction, row)) for row in self.matrix ]
714
714
  zero, one = 0, 1
715
715
  else:
@@ -868,17 +868,19 @@ def eye(m: int, n: int = None, zero=0, one=1, ring=None) -> "Matrix":
868
868
  raise ValueError(f"Matrix dimensions {m}x{n} must be positive!")
869
869
  return Matrix([[delta(i, j) for j in range(n)] for i in range(m)], ring=ring)
870
870
 
871
- def rotate(l: list, n: int) -> list:
871
+ def rotate(l: list, n: int, skew=False) -> list:
872
872
  "Rotate a list cyclically n places to the left."
873
873
  n %= len(l)
874
+ if skew:
875
+ return l[n:] + [ -a for a in l[:n] ]
874
876
  return l[n:] + l[:n]
875
877
 
876
- def circulant(vector: list|tuple, m: int = None, ring=None) -> "Matrix":
877
- "Returns a circulant matrix from the given vector."
878
+ def circulant(vector: list|tuple, m: int = None, ring=None, skew=False) -> "Matrix":
879
+ "Returns a (skew) circulant matrix from the given vector."
878
880
  if m is None:
879
881
  m = len(vector)
880
882
  vector = list(reversed(vector))
881
- return Matrix([rotate(vector, -n-1) for n in range(m)], ring=ring)
883
+ return Matrix([rotate(vector, -n-1, skew=skew) for n in range(m)], ring=ring)
882
884
 
883
885
  class BinaryMatrix:
884
886
  """
@@ -473,6 +473,18 @@ class Lattice():
473
473
  "Compute the Hadamard ratio of the current basis."
474
474
  return (self.gram_det() / prod(map(self.norm2, self.U)) ) ** (1 / self.len)
475
475
 
476
+ def hermite_factor(self) -> float:
477
+ "Compute the Hermit factor of the current basis."
478
+ if self.Nu is not None:
479
+ norm = self.Nu[0] ** (1/2)
480
+ else:
481
+ norm = self.norm2(self.U[0]) ** (1/2)
482
+ return norm / (self.gram_det() ** (1 / self.len))
483
+
484
+ def root_hermite_factor(self) -> float:
485
+ "Compute the root Hermit factor of the current basis."
486
+ return self.hermite_factor() ** (1 / (self.len - 1))
487
+
476
488
  def svp(self, method: str = 'all', delta: float = 0.75) -> Matrix:
477
489
  "Solve the SVP (approximately) using a given method."
478
490
  method = method.lower()
@@ -45,6 +45,14 @@ def hadamard_ratio(M: Matrix) -> float:
45
45
  m = M.rows
46
46
  return (gram_det(M) / prod([M[:, i].norm() for i in range(m)])) ** (1 / m)
47
47
 
48
+ def hermite_factor(M: Matrix) -> float:
49
+ "Compute the Hermite factor of the current basis."
50
+ return M[:, 0].norm() / (gram_det(M) ** (1 / M.rows))
51
+
52
+ def root_hermite_factor(M: Matrix) -> float:
53
+ "Compute the Hermite root factor of the current basis."
54
+ return hermite_factor(M) ** (1 / (M.rows - 1))
55
+
48
56
  def babai_round_cvp(x: Matrix, U: Matrix) -> Matrix:
49
57
  "Babai's rounding algorithm for approximately solving the CVP."
50
58
  s = U.inv() * x
@@ -2,13 +2,15 @@
2
2
  Polynomials
3
3
  """
4
4
 
5
- from math import prod, comb
5
+ from math import prod, gcd, lcm, comb, sqrt
6
+ from fractions import Fraction
6
7
  from numbers import Number
7
8
  from random import randint
8
9
  from itertools import combinations
9
10
  from copy import copy
10
11
  from .Zmod import Zmod
11
12
  from .nt import crt
13
+ from .la import Matrix, zeros
12
14
 
13
15
  class Poly:
14
16
  """
@@ -102,7 +104,7 @@ class Poly:
102
104
  def __hash__(self):
103
105
  return hash(tuple(self.coeff))
104
106
 
105
- def __repr__(self, latex=None):
107
+ def __repr__(self, latex: bool|None = None):
106
108
  def prx(i: int) -> str:
107
109
  if i == 0:
108
110
  return ""
@@ -116,7 +118,7 @@ class Poly:
116
118
 
117
119
  if latex is None:
118
120
  latex = self.__class__.print_latex
119
- if not self:
121
+ if len(self.coeff) == 1:
120
122
  return str(self.coeff[0])
121
123
  one = self.coeff[0]**0
122
124
  plus = ""
@@ -132,25 +134,17 @@ class Poly:
132
134
  tmp += plus + prx(i)
133
135
  plus = " + "
134
136
  continue
135
- try:
136
- if not s + one and i != 0:
137
- if plus:
138
- plus = " "
139
- tmp += plus + "- " + prx(i)
140
- plus = " + "
141
- continue
142
- except: # pylint: disable=W0702
143
- pass
144
- try:
145
- if hasattr(s, "ring") and hasattr(s.ring, "sharp") and s.ring.sharp:
146
- s = s.sharp()
147
- if plus and s < 0:
148
- tmp += " - " + str(-s)
149
- if i != 0:
150
- tmp += " " + prx(i)
151
- continue
152
- except: # pylint: disable=W0702
153
- pass
137
+ if not s + one and i != 0 and str(s)[0] == '-':
138
+ if plus:
139
+ plus = " "
140
+ tmp += plus + "- " + prx(i)
141
+ plus = " + "
142
+ continue
143
+ if plus and str(s)[0] == '-':
144
+ tmp += " - " + str(-s)
145
+ if i != 0:
146
+ tmp += " " + prx(i)
147
+ continue
154
148
  tmp += plus + str(s)
155
149
  if i != 0:
156
150
  tmp += " " + prx(i)
@@ -181,7 +175,7 @@ class Poly:
181
175
  def __eq__(self, other):
182
176
  if not isinstance(other, self.__class__):
183
177
  return False
184
- return not bool(self - other)
178
+ return self.coeff == other.coeff
185
179
 
186
180
  def __bool__(self):
187
181
  return bool(self.degree()) or bool(self.coeff[0])
@@ -306,7 +300,16 @@ class Poly:
306
300
  return self.__class__([other * s for s in self.coeff], modulus = self.modulus, cyclic = self.cyclic, check = False)
307
301
  return NotImplemented
308
302
  ls, lo = len(self.coeff), len(other.coeff)
309
- if self.cyclic or other.cyclic:
303
+ modulus = self.modulus
304
+ if not modulus and other.modulus:
305
+ modulus = other.modulus
306
+ cyclic = self.cyclic
307
+ if not self.cyclic and other.cyclic:
308
+ cyclic = other.cyclic
309
+ modulus = other.modulus
310
+ if (ls == 1 and not self.coeff[0]) or (lo == 1 and not other.coeff[0]):
311
+ return self.__class__([zero], modulus = modulus, cyclic = cyclic, check = False)
312
+ if cyclic:
310
313
  if self.cyclic:
311
314
  cyclic = self.cyclic
312
315
  modulus = self.modulus
@@ -397,6 +400,22 @@ class Poly:
397
400
  "Sum of the absolute value of all coefficients."
398
401
  return sum(abs(c) for c in self.coeff)
399
402
 
403
+ def norm(self) -> int:
404
+ "Euclidean norm of the coefficients."
405
+ return sqrt(sum(abs(c)**2 for c in self.coeff))
406
+
407
+ def content(self) -> int:
408
+ "Return the content, the gcd of all coefficients, of a polynomial."
409
+ if not all(map(lambda _: isinstance(_, int), self.coeff)):
410
+ raise ValueError("Content requires integer coefficients.")
411
+ return gcd(*self.coeff)
412
+
413
+ def primitive_part(self) -> int:
414
+ "Return the primitive part of a polynomial."
415
+ if not all(map(lambda _: isinstance(_, int), self.coeff)):
416
+ raise ValueError("Primitive part requires integer coefficients.")
417
+ return self // gcd(*self.coeff)
418
+
400
419
  def __floordiv__(self, other: "Poly") -> "Poly":
401
420
  return self.divmod(other)[0]
402
421
 
@@ -554,18 +573,24 @@ class Poly:
554
573
  "Least common multiple with a given polynomial."
555
574
  return self * other // self.gcd(other)
556
575
 
557
- def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
558
- """Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
576
+ def egcd(self, other: "Poly", integer:bool = False) -> ("Poly", "Poly", "Poly"):
577
+ """Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that self * x + other * y = gcd."""
559
578
  zero, one, _ = self._guess_ring()
560
579
  if not isinstance(other, self.__class__):
561
580
  raise NotImplementedError(f"Cannot perform egcd: {other} must be a polynomial.")
581
+ if integer:
582
+ if not( all(map(lambda _: isinstance(_, int), self.coeff)) and all(map(lambda _: isinstance(_, int), other.coeff)) ):
583
+ raise ValueError("The option 'integer' requires integer coefficients.")
562
584
  if not self:
563
585
  if not other:
564
586
  return self.__class__([zero], check = False), self.__class__([zero], check = False), self.__class__([zero], check = False)
565
- if other.coeff[-1] == one:
587
+ if other.coeff[-1] == one or integer:
566
588
  return other, self.__class__([zero], check = False), self.__class__([one], check = False)
567
589
  return other / other.coeff[-1], __class__([zero], check = False), self.__class__([one / other.coeff[-1]], check = False)
568
- r0, r1 = other, self
590
+ if integer:
591
+ r0, r1 = self.applyfunc(Fraction), other.applyfunc(Fraction)
592
+ else:
593
+ r0, r1 = self, other
569
594
  x0, x1 = self.__class__([one], check = False), self.__class__([zero], check = False)
570
595
  y0, y1 = x1, x0
571
596
  while r1:
@@ -574,9 +599,17 @@ class Poly:
574
599
  x0, x1 = x1, x0 - x1 * q
575
600
  y0, y1 = y1, y0 - y1 * q
576
601
  lcr0 = r0.coeff[-1]
577
- if lcr0 == one:
578
- return r0, x0, y0
579
- return r0 / lcr0, x0 / lcr0, y0 / lcr0
602
+ if lcr0 != one:
603
+ r0, x0, y0 = r0 / lcr0, x0 / lcr0, y0 / lcr0
604
+ if integer:
605
+ lcmx = lcm(*map(lambda _: _.denominator, x0.coeff))
606
+ lcmy = lcm(*map(lambda _: _.denominator, y0.coeff))
607
+ lcmxy = lcm(lcmx,lcmy)
608
+ r0, x0, y0 = lcmxy * r0, lcmxy * x0, lcmxy * y0
609
+ r0.map(int)
610
+ x0.map(int)
611
+ y0.map(int)
612
+ return r0, x0, y0
580
613
 
581
614
  def derivative(self) -> "Poly":
582
615
  "Returns the formal derivative."
@@ -767,7 +800,7 @@ class PolyBinMult():
767
800
  self.monomials.append(combination)
768
801
  i += 1
769
802
 
770
- def __repr__(self, latex=None):
803
+ def __repr__(self, latex: bool|None = None):
771
804
  def prx(i: int) -> str:
772
805
  if len(self.__class__.print_x) >= self.m:
773
806
  return self.__class__.print_x[i]
@@ -792,7 +825,7 @@ class PolyBinMult():
792
825
  def _repr_mimebundle_(self, **kwargs): # pylint: disable=W0613
793
826
  return {
794
827
  "text/plain": repr(self),
795
- "text/latex": "$" + self.__repr__(latex=True) + "$" # pylint: disable=C2801
828
+ "text/latex": "$" + self.__repr__(latex = True) + "$" # pylint: disable=C2801
796
829
  }
797
830
 
798
831
  def index2monomial(self, idx: int) -> tuple|None:
@@ -842,7 +875,7 @@ class PolyBinMult():
842
875
  val += prod(x[i] for i in monomial)
843
876
  return val % 2
844
877
 
845
- def __eq__(self, other):
878
+ def __eq__(self, other: "PolyBinMult"):
846
879
  if not isinstance(other, self.__class__):
847
880
  return False
848
881
  return not bool(self + other)
@@ -850,7 +883,7 @@ class PolyBinMult():
850
883
  def __bool__(self):
851
884
  return bool(self.monomials)
852
885
 
853
- def __add__(self, other) -> "PolyBinMult":
886
+ def __add__(self, other: "PolyBinMult") -> "PolyBinMult":
854
887
  if not isinstance(other, self.__class__):
855
888
  return NotImplemented
856
889
  if other.m != self.m:
@@ -870,7 +903,7 @@ class PolyBinMult():
870
903
 
871
904
  __neg__ = __pos__
872
905
 
873
- def __mul__(self, other) -> "PolyBinMult":
906
+ def __mul__(self, other: "PolyBinMult") -> "PolyBinMult":
874
907
  if isinstance(other, self.__class__):
875
908
  return self.multiply(other)
876
909
  if isinstance(other, int):
@@ -879,14 +912,14 @@ class PolyBinMult():
879
912
  return self.__class__([], self.m)
880
913
  return NotImplemented
881
914
 
882
- def __rmul__(self, other) -> "PolyBinMult":
915
+ def __rmul__(self, other: "PolyBinMult") -> "PolyBinMult":
883
916
  if isinstance(other, int):
884
917
  if other % 2:
885
918
  return self
886
919
  return self.__class__([], self.m)
887
920
  return NotImplemented
888
921
 
889
- def multiply(self, other) -> "PolyBinMult":
922
+ def multiply(self, other: "PolyBinMult") -> "PolyBinMult":
890
923
  "Polynomial Multiplication."
891
924
  res = []
892
925
  for monomials in self.monomials:
@@ -929,3 +962,28 @@ def lagrange_interpolation(x_coordinates: list, y_coordinates: list) -> "Poly":
929
962
  poly += (y /
930
963
  prod([x - xx for xx in x_coordinates if xx != x], start=one)) * L
931
964
  return poly
965
+
966
+ def sylvester(p: "Poly", q: "Poly") -> "Matrix":
967
+ "Sylvester matrix of the given polynomials."
968
+ n = q.degree()
969
+ m = p.degree()
970
+ S = zeros(m + n)
971
+ k = 0
972
+ for i in range(n):
973
+ j = k
974
+ for c in p.coeff:
975
+ S[j, i] = c
976
+ j += 1
977
+ k = k + 1
978
+ k = 0
979
+ for i in range(n, m + n):
980
+ j = k
981
+ for c in q.coeff:
982
+ S[j, i] = c
983
+ j += 1
984
+ k = k + 1
985
+ return S
986
+
987
+ def resultant(p: "Poly", q: "Poly") -> int:
988
+ "Resultant of the given polynomials."
989
+ return sylvester(p,q).det()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.9.2
3
+ Version: 1.9.4
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.2"
3
+ version = "1.9.4"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
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