pqlattice 0.1.2__py3-none-any.whl

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 (47) hide show
  1. pqlattice/__init__.py +6 -0
  2. pqlattice/_backends/__init__.py +0 -0
  3. pqlattice/_backends/_fast.py +75 -0
  4. pqlattice/_backends/_native.py +33 -0
  5. pqlattice/_backends/_protocol.py +15 -0
  6. pqlattice/_utils.py +201 -0
  7. pqlattice/integer/__init__.py +8 -0
  8. pqlattice/integer/_integer.py +55 -0
  9. pqlattice/integer/_modintring.py +246 -0
  10. pqlattice/integer/_modring.py +165 -0
  11. pqlattice/integer/_primality.py +78 -0
  12. pqlattice/integer/_primes.py +57 -0
  13. pqlattice/lattice/__init__.py +44 -0
  14. pqlattice/lattice/_bkz.py +87 -0
  15. pqlattice/lattice/_cvp.py +62 -0
  16. pqlattice/lattice/_embeddings.py +149 -0
  17. pqlattice/lattice/_gso.py +43 -0
  18. pqlattice/lattice/_hkz.py +20 -0
  19. pqlattice/lattice/_lattice.py +137 -0
  20. pqlattice/lattice/_lll.py +93 -0
  21. pqlattice/lattice/_svp.py +89 -0
  22. pqlattice/lattice/embeddings.py +3 -0
  23. pqlattice/linalg/__init__.py +37 -0
  24. pqlattice/linalg/_linalg.py +306 -0
  25. pqlattice/linalg/_modint.py +209 -0
  26. pqlattice/linalg/_utils.py +167 -0
  27. pqlattice/polynomial/__init__.py +5 -0
  28. pqlattice/polynomial/_modpolyqring.py +185 -0
  29. pqlattice/polynomial/_modpolyring.py +267 -0
  30. pqlattice/polynomial/_poly.py +250 -0
  31. pqlattice/polynomial/poly.py +3 -0
  32. pqlattice/py.typed +0 -0
  33. pqlattice/random/__init__.py +7 -0
  34. pqlattice/random/_distribution.py +303 -0
  35. pqlattice/random/_lattice.py +53 -0
  36. pqlattice/random/_lwe.py +109 -0
  37. pqlattice/random/_lwr.py +41 -0
  38. pqlattice/random/_prime.py +53 -0
  39. pqlattice/random/distribution.py +3 -0
  40. pqlattice/settings.py +66 -0
  41. pqlattice/typing/__init__.py +4 -0
  42. pqlattice/typing/_types.py +18 -0
  43. pqlattice/typing/_types_validator.py +57 -0
  44. pqlattice-0.1.2.dist-info/METADATA +33 -0
  45. pqlattice-0.1.2.dist-info/RECORD +47 -0
  46. pqlattice-0.1.2.dist-info/WHEEL +4 -0
  47. pqlattice-0.1.2.dist-info/licenses/LICENSE +7 -0
@@ -0,0 +1,165 @@
1
+ from typing import overload
2
+
3
+ from ..typing import Array
4
+ from ._integer import eea
5
+
6
+
7
+ @overload
8
+ def mod(a: int, modulus: int) -> int: ...
9
+
10
+
11
+ @overload
12
+ def mod(a: Array, modulus: int) -> Array: ...
13
+
14
+
15
+ def mod(a: int | Array, modulus: int) -> int | Array:
16
+ """_summary_
17
+
18
+ Parameters
19
+ ----------
20
+ a : int | Array
21
+ _description_
22
+ modulus : int
23
+ _description_
24
+
25
+ Returns
26
+ -------
27
+ int | Array
28
+ _description_
29
+ """
30
+ return a % abs(modulus)
31
+
32
+
33
+ @overload
34
+ def cmodl(a: int, modulus: int) -> int: ...
35
+
36
+
37
+ @overload
38
+ def cmodl(a: Array, modulus: int) -> Array: ...
39
+
40
+
41
+ def cmodl(a: int | Array, modulus: int) -> int | Array:
42
+ """_summary_
43
+
44
+ Parameters
45
+ ----------
46
+ a : int | Array
47
+ _description_
48
+ modulus : int
49
+ _description_
50
+
51
+ Returns
52
+ -------
53
+ int | Array
54
+ _description_
55
+ """
56
+ return mod(a, modulus) - modulus // 2
57
+
58
+
59
+ @overload
60
+ def cmodr(a: int, modulus: int) -> int: ...
61
+
62
+
63
+ @overload
64
+ def cmodr(a: Array, modulus: int) -> Array: ...
65
+
66
+
67
+ def cmodr(a: int | Array, modulus: int) -> int | Array:
68
+ """_summary_
69
+
70
+ Parameters
71
+ ----------
72
+ a : int | Array
73
+ _description_
74
+ modulus : int
75
+ _description_
76
+
77
+ Returns
78
+ -------
79
+ int | Array
80
+ _description_
81
+ """
82
+ return mod(a, modulus) - int(modulus / 2 - 0.1)
83
+
84
+
85
+ @overload
86
+ def modinv(a: int, modulus: int) -> int: ...
87
+
88
+
89
+ @overload
90
+ def modinv(a: Array, modulus: int) -> Array: ...
91
+
92
+
93
+ def modinv(a: int | Array, modulus: int) -> int | Array:
94
+ """_summary_
95
+
96
+ Parameters
97
+ ----------
98
+ a : int | Array
99
+ _description_
100
+ modulus : int
101
+ _description_
102
+
103
+ Returns
104
+ -------
105
+ int | Array
106
+ _description_
107
+
108
+ Raises
109
+ ------
110
+ ValueError
111
+ _description_
112
+ ValueError
113
+ _description_
114
+ """
115
+ if isinstance(a, int):
116
+ if mod(a, modulus) == 0:
117
+ raise ValueError(f"{a} mod {modulus} is zero; Modular inverse does not exist")
118
+ gcd, a_inv, _ = eea(a, modulus)
119
+ if gcd != 1:
120
+ raise ValueError(f"Modular inverse of {a} mod {modulus} does not exist; gcd is equal to {gcd}")
121
+ else:
122
+ if (mod(a, modulus) == 0).all():
123
+ raise ValueError(f"{a} mod {modulus} is zero; Modular inverse does not exist")
124
+ gcd, a_inv, _ = eea(a, modulus)
125
+ if (gcd != 1).any():
126
+ raise ValueError(f"Modular inverse of {a} mod {modulus} does not exist; gcd is equal to {gcd}")
127
+
128
+ return mod(a_inv, modulus)
129
+
130
+
131
+ @overload
132
+ def modpow(a: Array, r: int, modulus: int) -> Array: ...
133
+
134
+
135
+ @overload
136
+ def modpow(a: int, r: int, modulus: int) -> int: ...
137
+
138
+
139
+ def modpow(a: int | Array, r: int, modulus: int) -> int | Array:
140
+ """_summary_
141
+
142
+ Parameters
143
+ ----------
144
+ a : int | Array
145
+ _description_
146
+ r : int
147
+ _description_
148
+ modulus : int
149
+ _description_
150
+
151
+ Returns
152
+ -------
153
+ int | Array
154
+ _description_
155
+ """
156
+ if r < 0:
157
+ return modpow(modinv(a, modulus), -r, modulus)
158
+
159
+ y, z = 1, a
160
+ while r != 0:
161
+ if r % 2 == 1:
162
+ y = mod(y * z, modulus)
163
+ r //= 2
164
+ z = mod(z * z, modulus)
165
+ return y
@@ -0,0 +1,78 @@
1
+ import logging
2
+ import random
3
+ from collections.abc import Callable
4
+
5
+ from . import _modring as mr # type: ignore
6
+ from . import _primes as primes
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def fermat_primality_test(p: int, s: int, int_gen: Callable[[int, int], int] | None = None) -> bool:
12
+ if p <= 1:
13
+ return False
14
+
15
+ if int_gen is None:
16
+ int_gen = lambda a, b: random.randint(a, b - 1)
17
+
18
+ for _ in range(s):
19
+ a = int_gen(2, p - 2)
20
+ if mr.modpow(a, p - 1, p) == 1:
21
+ return False
22
+ return True
23
+
24
+
25
+ def miller_rabin_primality_test(n: int, s: int, int_gen: Callable[[int, int], int] | None = None) -> bool:
26
+ if int_gen is None:
27
+ int_gen = lambda a, b: random.randint(a, b)
28
+
29
+ # n - 1 = r * 2 ** u
30
+ u = 0
31
+ r = n - 1
32
+ while r % 2 == 0:
33
+ u += 1
34
+ r //= 2
35
+
36
+ # assert n - 1 == r * 2 ** u, f"{n - 1=}, {r=}, {u=}, {2**u=}, {r * 2 ** u=}"
37
+
38
+ for _ in range(s):
39
+ a = int_gen(2, n - 2)
40
+ z = mr.modpow(a, r, n)
41
+ for _ in range(u):
42
+ y = mr.mod(z * z, n)
43
+ if y == 1 and z != 1 and z != n - 1:
44
+ # composite
45
+ return False
46
+ z = y
47
+ if z != 1:
48
+ # composite
49
+ return False
50
+
51
+ # likely prime
52
+ return True
53
+
54
+
55
+ def is_prime(p: int) -> bool:
56
+ """_summary_
57
+
58
+ Parameters
59
+ ----------
60
+ p : int
61
+ _description_
62
+
63
+ Returns
64
+ -------
65
+ bool
66
+ _description_
67
+ """
68
+ if p <= 1:
69
+ return False
70
+
71
+ for prime in primes.SMALL_PRIMES:
72
+ if p == prime:
73
+ return True
74
+
75
+ if p % prime == 0:
76
+ return False
77
+
78
+ return miller_rabin_primality_test(p, 20)
@@ -0,0 +1,57 @@
1
+ # All primes less than 256
2
+ SMALL_PRIMES = (
3
+ 2,
4
+ 3,
5
+ 5,
6
+ 7,
7
+ 11,
8
+ 13,
9
+ 17,
10
+ 19,
11
+ 23,
12
+ 29,
13
+ 31,
14
+ 37,
15
+ 41,
16
+ 43,
17
+ 47,
18
+ 53,
19
+ 59,
20
+ 61,
21
+ 67,
22
+ 71,
23
+ 73,
24
+ 79,
25
+ 83,
26
+ 89,
27
+ 97,
28
+ 101,
29
+ 103,
30
+ 107,
31
+ 109,
32
+ 113,
33
+ 127,
34
+ 131,
35
+ 137,
36
+ 139,
37
+ 149,
38
+ 151,
39
+ 157,
40
+ 163,
41
+ 167,
42
+ 173,
43
+ 179,
44
+ 181,
45
+ 191,
46
+ 193,
47
+ 197,
48
+ 199,
49
+ 211,
50
+ 223,
51
+ 227,
52
+ 229,
53
+ 233,
54
+ 239,
55
+ 241,
56
+ 251,
57
+ )
@@ -0,0 +1,44 @@
1
+ from .. import settings
2
+ from ..typing import SquareMatrix, Vector
3
+ from . import embeddings
4
+ from ._cvp import babai_closest_vector, babai_nearest_plane, closest_vector
5
+ from ._gso import gso
6
+ from ._lattice import discriminant, gaussian_heuristic, glr_2dim, hadamard_ratio, rank, volume
7
+ from ._lll import is_lll_reduced, is_size_reduced
8
+
9
+
10
+ def lll(lattice_basis: SquareMatrix, delta: float = 0.99) -> SquareMatrix:
11
+ return settings.get_backend().lll(lattice_basis, delta)
12
+
13
+
14
+ def bkz(lattice_basis: SquareMatrix, block_size: int = 10, delta: float = 0.99) -> SquareMatrix:
15
+ return settings.get_backend().bkz(lattice_basis, block_size, delta)
16
+
17
+
18
+ def hkz(lattice_basis: SquareMatrix, delta: float = 0.99) -> SquareMatrix:
19
+ return settings.get_backend().hkz(lattice_basis, delta)
20
+
21
+
22
+ def shortest_vector(lattice_basis: SquareMatrix) -> Vector:
23
+ return settings.get_backend().shortest_vector(lattice_basis)
24
+
25
+
26
+ __all__ = [
27
+ "volume",
28
+ "rank",
29
+ "hadamard_ratio",
30
+ "discriminant",
31
+ "gaussian_heuristic",
32
+ "glr_2dim",
33
+ "gso",
34
+ "lll",
35
+ "is_lll_reduced",
36
+ "is_size_reduced",
37
+ "bkz",
38
+ "hkz",
39
+ "shortest_vector",
40
+ "closest_vector",
41
+ "babai_closest_vector",
42
+ "babai_nearest_plane",
43
+ "embeddings",
44
+ ]
@@ -0,0 +1,87 @@
1
+ import logging
2
+
3
+ from .._utils import as_integer, as_rational
4
+ from ..typing import Matrix, SquareMatrix, Vector, validate_aliases
5
+ from ._gso import gso
6
+ from ._lll import lll
7
+ from ._svp import schnorr_euchner_svp
8
+
9
+ logger = logging.getLogger("BKZ")
10
+
11
+
12
+ @validate_aliases
13
+ def update_block(lattice_basis: SquareMatrix, new_vector: Vector, start_index: int, block_size: int) -> SquareMatrix:
14
+ B = as_integer(lattice_basis)
15
+ local_basis = as_integer([new_vector] + B[start_index : start_index + block_size].tolist())
16
+ reduced_local = lll(local_basis)
17
+
18
+ zero_vec = reduced_local[0]
19
+ if any(s != 0 for s in zero_vec):
20
+ raise ValueError("block update failed")
21
+
22
+ cleaned_basis = as_integer(reduced_local[1:])
23
+
24
+ for i in range(block_size):
25
+ B[start_index + i] = cleaned_basis[i]
26
+
27
+ return B
28
+
29
+
30
+ @validate_aliases
31
+ def bkz(lattice_basis: SquareMatrix, block_size: int = 10) -> SquareMatrix:
32
+ """_summary_
33
+
34
+ Parameters
35
+ ----------
36
+ lattice_basis : SquareMatrix
37
+ _description_
38
+ block_size : int, optional
39
+ _description_, by default 10
40
+
41
+ Returns
42
+ -------
43
+ SquareMatrix
44
+ _description_
45
+ """
46
+ n, m = lattice_basis.shape
47
+
48
+ B = lll(lattice_basis)
49
+
50
+ is_changed = True
51
+ # iteration = 0
52
+ # logger.info("starting bkz loop")
53
+ while is_changed:
54
+ is_changed = False
55
+ # iteration += 1
56
+ # logger.info(f"iter {iteration}")
57
+ for k in range(n - 1):
58
+ h = min(block_size, n - k)
59
+ local_basis: Matrix = B[k : k + h]
60
+ # logger.info(f"block {local_basis}")
61
+ B_star, U = gso(local_basis)
62
+ B_norms2 = as_rational([sum(x * x for x in v) for v in B_star])
63
+ mu = U.T
64
+ coeffs = schnorr_euchner_svp(mu, B_norms2)
65
+
66
+ is_trivial = True
67
+ if abs(coeffs[0]) == 1:
68
+ is_trivial = all(c == 0 for c in coeffs[1:])
69
+ elif coeffs[0] == 0:
70
+ is_trivial = False
71
+ else:
72
+ is_trivial = False
73
+
74
+ if is_trivial:
75
+ continue
76
+
77
+ # logger.info(f"shortest vector in block norm: {norm(coeffs @ local_basis)}")
78
+ new_vector = as_integer([0] * m)
79
+ for i in range(h):
80
+ if coeffs[i] == 0:
81
+ continue
82
+ for d in range(m):
83
+ new_vector[d] += coeffs[i] * local_basis[i][d]
84
+
85
+ B = update_block(B, new_vector, k, h)
86
+ is_changed = True
87
+ return B
@@ -0,0 +1,62 @@
1
+ import numpy as np
2
+
3
+ from .._utils import as_integer, as_rational
4
+ from ..typing import SquareMatrix, Vector, validate_aliases
5
+ from ._gso import gso, project_coeffs
6
+ from ._lll import lll
7
+
8
+
9
+ @validate_aliases
10
+ def schnorr_euchner_cvp(mu: SquareMatrix, B: Vector, target_coeffs: Vector) -> Vector:
11
+ raise NotImplementedError()
12
+
13
+
14
+ @validate_aliases
15
+ def closest_vector(lattice_basis: SquareMatrix, target_vector: Vector) -> Vector:
16
+ raise NotImplementedError()
17
+
18
+
19
+ @validate_aliases
20
+ def babai_nearest_plane(lattice_basis: SquareMatrix, target_vector: Vector) -> Vector:
21
+ """_summary_
22
+
23
+ Parameters
24
+ ----------
25
+ lattice_basis : SquareMatrix
26
+ _description_
27
+ target_vector : Vector
28
+ _description_
29
+
30
+ Returns
31
+ -------
32
+ Vector
33
+ _description_
34
+ """
35
+ n, _ = lattice_basis.shape
36
+ B = lll(lattice_basis)
37
+ b = as_rational(target_vector)
38
+ for j in range(n - 1, -1, -1):
39
+ B_star, _ = gso(B)
40
+ cj = round(project_coeffs(B_star[j], b))
41
+ b -= cj * B[j]
42
+
43
+ return as_integer(as_rational(target_vector) - b)
44
+
45
+
46
+ @validate_aliases
47
+ def babai_closest_vector(lattice_basis: SquareMatrix, target_vector: Vector) -> Vector:
48
+ """_summary_
49
+
50
+ Parameters
51
+ ----------
52
+ lattice_basis : SquareMatrix
53
+ _description_
54
+ target_vector : Vector
55
+ _description_
56
+
57
+ Returns
58
+ -------
59
+ Vector
60
+ _description_
61
+ """
62
+ return as_integer(np.rint(target_vector.astype(float) @ np.linalg.inv(lattice_basis.astype(float))))
@@ -0,0 +1,149 @@
1
+ import numpy as np
2
+
3
+ from .._utils import as_integer, zeros_mat
4
+ from ..linalg import hnf
5
+ from ..typing import Matrix, SquareMatrix, Vector, validate_aliases
6
+
7
+
8
+ def lwe_basis(A: Matrix, q: int) -> SquareMatrix:
9
+ """
10
+ _summary_
11
+
12
+ Parameters
13
+ ----------
14
+ A : Matrix
15
+ _description_
16
+ q : int
17
+ _description_
18
+
19
+ Returns
20
+ -------
21
+ SquareMatrix
22
+ _description_
23
+ """
24
+ # lattice: L = { x | Ax = 0 mod q }
25
+ m, _ = A.shape
26
+ Im = q * as_integer(np.identity(m))
27
+ G = np.vstack((A.T, Im))
28
+ H, _ = hnf(G)
29
+
30
+ return H[:m]
31
+
32
+
33
+ def sis_basis(A: Matrix, q: int) -> SquareMatrix:
34
+ """
35
+ _summary_
36
+
37
+ Parameters
38
+ ----------
39
+ A : Matrix
40
+ _description_
41
+ q : int
42
+ _description_
43
+
44
+ Returns
45
+ -------
46
+ SquareMatrix
47
+ _description_
48
+ """
49
+ # lattice: L = { y | y = xA mod q }
50
+ B_p = lwe_basis(A, q)
51
+ B_inv = np.linalg.inv(B_p.astype(float))
52
+ B_dual = np.round(q * B_inv.T).astype(int)
53
+ return B_dual
54
+
55
+
56
+ @validate_aliases
57
+ def kannan(A: Matrix, b: Vector, q: int) -> SquareMatrix:
58
+ """
59
+ _summary_
60
+
61
+ Parameters
62
+ ----------
63
+ A : Matrix
64
+ _description_
65
+ b : Vector
66
+ _description_
67
+ q : int
68
+ _description_
69
+
70
+ Returns
71
+ -------
72
+ SquareMatrix
73
+ _description_
74
+ """
75
+ return bai_galbraith(A, b, q, 1)
76
+
77
+
78
+ @validate_aliases
79
+ def bai_galbraith(A: Matrix, b: Vector, q: int, M: int) -> SquareMatrix:
80
+ """
81
+ _summary_
82
+
83
+ Parameters
84
+ ----------
85
+ A : Matrix
86
+ _description_
87
+ b : Vector
88
+ _description_
89
+ q : int
90
+ _description_
91
+ M : int
92
+ _description_
93
+
94
+ Returns
95
+ -------
96
+ SquareMatrix
97
+ _description_
98
+ """
99
+ m, n = A.shape
100
+
101
+ Im = as_integer(np.identity(m))
102
+ In = as_integer(np.identity(n))
103
+
104
+ Zmxn = zeros_mat(m, n)
105
+ Zmx1 = zeros_mat(m, 1)
106
+ Znx1 = zeros_mat(n, 1)
107
+ Z1xn = zeros_mat(1, n)
108
+
109
+ bT = b.reshape(1, -1)
110
+ IM = M * (zeros_mat(1, 1) + 1)
111
+
112
+ return np.block(
113
+ [
114
+ [q * Im, Zmxn, Zmx1],
115
+ [A.T, In, Znx1],
116
+ [-bT, Z1xn, IM],
117
+ ]
118
+ )
119
+
120
+
121
+ @validate_aliases
122
+ def subset_sum(sequence: Vector, S: int) -> SquareMatrix:
123
+ """
124
+ _summary_
125
+
126
+ Parameters
127
+ ----------
128
+ sequence : Vector
129
+ _description_
130
+ S : int
131
+ _description_
132
+
133
+ Returns
134
+ -------
135
+ SquareMatrix
136
+ _description_
137
+ """
138
+ n = len(sequence)
139
+ A = as_integer(np.identity(n + 1) * 2)
140
+ A[-1] = 1
141
+ A[:-1, -1] = sequence
142
+ A[-1, -1] = S
143
+
144
+ return A
145
+
146
+
147
+ @validate_aliases
148
+ def ntru() -> SquareMatrix:
149
+ raise NotImplementedError()
@@ -0,0 +1,43 @@
1
+ from fractions import Fraction
2
+
3
+ import numpy as np
4
+
5
+ from .._utils import as_rational
6
+ from ..typing import Matrix, SquareMatrix, Vector, validate_aliases
7
+
8
+
9
+ @validate_aliases
10
+ def project_coeffs(q: Vector, b: Vector) -> Fraction:
11
+ if np.dot(q, q) == 0:
12
+ return Fraction(0, 1)
13
+
14
+ return np.dot(b, q) / np.dot(q, q)
15
+
16
+
17
+ @validate_aliases
18
+ def gso(B: Matrix) -> tuple[Matrix, SquareMatrix]:
19
+ """_summary_
20
+
21
+ Parameters
22
+ ----------
23
+ B : Matrix
24
+ _description_
25
+
26
+ Returns
27
+ -------
28
+ tuple[Matrix, SquareMatrix]
29
+ _description_
30
+ """
31
+ rows, _ = B.shape
32
+
33
+ B_star: Matrix = as_rational(B)
34
+ U: SquareMatrix = as_rational(np.identity(rows))
35
+
36
+ for j in range(1, rows):
37
+ b: Vector = B_star[j].copy()
38
+ for i in range(j):
39
+ U[i, j] = project_coeffs(B_star[i], b)
40
+ B_star[j] -= U[i][j] * B_star[i]
41
+
42
+ # B = U.T @ B_star
43
+ return B_star, U
@@ -0,0 +1,20 @@
1
+ from ..typing import SquareMatrix, validate_aliases
2
+ from ._bkz import bkz
3
+ from ._lattice import rank
4
+
5
+
6
+ @validate_aliases
7
+ def hkz(lattice_basis: SquareMatrix) -> SquareMatrix:
8
+ """_summary_
9
+
10
+ Parameters
11
+ ----------
12
+ lattice_basis : SquareMatrix
13
+ _description_
14
+
15
+ Returns
16
+ -------
17
+ SquareMatrix
18
+ _description_
19
+ """
20
+ return bkz(lattice_basis, rank(lattice_basis))