Fork of the official github repository of the framework Leaky-LWE-Estimator, a Sage Toolkit to attack and estimate the hardness of LWE with Side Information. https://github.com/lducas/leaky-LWE-Estimator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

geometry.sage 8.8KB

  1. from numpy.linalg import inv as np_inv
  2. # from numpy.linalg import slogdet as np_slogdet
  3. from numpy import array
  4. import numpy as np
  5. def dual_basis(B):
  6. """
  7. Compute the dual basis of B
  8. """
  9. return B.pseudoinverse().transpose()
  10. def projection_matrix(A):
  11. """
  12. Construct the projection matrix orthogonally to Span(V)
  13. """
  14. S = A * A.T
  15. return A.T * S.inverse() * A
  16. def project_against(v, X):
  17. """ Project matrix X orthonally to vector v"""
  18. # Pv = projection_matrix(v)
  19. # return X - X * Pv
  20. Z = (X * v.T) * v / scal(v * v.T)
  21. return X - Z
  22. # def make_primitive(B, v):
  23. # assert False
  24. # # project and Scale v's in V so that each v
  25. # # is in the lattice, and primitive in it.
  26. # # Note: does not make V primitive as as set of vector !
  27. # # (e.g. linear dep. not eliminated)
  28. # PB = projection_matrix(B)
  29. # DT = dual_basis(B).T
  30. # v = vec(v) * PB
  31. # w = v * DT
  32. # den = lcm([x.denominator() for x in w[0]])
  33. # num = gcd([x for x in w[0] * den])
  34. # if num==0:
  35. # return None
  36. # v *= den/num
  37. # return v
  38. def vol(B):
  39. return sqrt(det(B * B.T))
  40. def project_and_eliminate_dep(B, W):
  41. # Project v on Span(B)
  42. PB = projection_matrix(B)
  43. V = W * PB
  44. rank_loss = V.nrows() - V.rank()
  45. if rank_loss > 0:
  46. print("WARNING: their were %d linear dependencies out of %d " %
  47. (rank_loss, V.nrows()))
  48. V = V.LLL()
  49. V = V[rank_loss:]
  50. return V
  51. def is_cannonical_direction(v):
  52. v = vec(v)
  53. return sum([x != 0 for x in v[0]]) == 1
  54. def cannonical_param(v):
  55. v = vec(v)
  56. assert is_cannonical_direction(v)
  57. i = [x != 0 for x in v[0]].index(True)
  58. return i, v[0, i]
  59. def remove_linear_dependencies(B, dim=None):
  60. nrows = B.nrows()
  61. if dim is None or nrows > dim:
  62. # Determine the number of dependencies
  63. K, r = None, None
  64. if dim is None:
  65. K = B.left_kernel().basis_matrix() # I assume that the cost of "left_kernel" is negligeable before "LLL"
  66. r = K.dimensions()[0]
  67. else:
  68. r = nrows-dim
  69. if r == 1 and False:
  70. # Find a linear dependency
  71. if K is None:
  72. K = B.left_kernel().basis_matrix()
  73. assert K.dimensions()[0] == 1
  74. combinaison = K[0]
  75. # Detect the redundant vector
  76. pivot, pivot_value = None, None
  77. for ind, value in enumerate(combinaison):
  78. if abs(value) > 0 and (pivot is None or abs(value)<abs(pivot_value)):
  79. pivot, pivot_value = ind, value
  80. B = B[[i for i in range(B.dimensions()[0]) if i != pivot]]
  81. else:
  82. B = B.LLL()
  83. B = B[r:]
  84. return B
  85. def lattice_orthogonal_section(D, V, maintains_basis=True):
  86. """
  87. Compute the intersection of the lattice L(B)
  88. with the hyperplane orthogonal to Span(V).
  89. (V can be either a vector or a matrix)
  90. INPUT AND OUTPUT DUAL BASIS
  91. Algorithm:
  92. - project V onto Span(B)
  93. - project the dual basis onto orth(V)
  94. - eliminate linear dependencies (LLL)
  95. - go back to the primal.
  96. """
  97. #V = project_and_eliminate_dep(D, V) ## No need because D is full-rank
  98. #r = V.nrows()
  99. # Project the dual basis orthogonally to v
  100. PV = projection_matrix(V)
  101. D = D - D * PV
  102. # Eliminate linear dependencies
  103. if maintains_basis:
  104. D = remove_linear_dependencies(D)
  105. # Go back to the primal
  106. return D
  107. def lattice_project_against(B, V, maintains_basis=True):
  108. """
  109. Compute the projection of the lattice L(B) orthogonally to Span(V). All vectors if V
  110. (or at least their projection on Span(B)) must belong to L(B).
  111. Algorithm:
  112. - project V onto Span(B)
  113. - project the basis onto orth(V)
  114. - eliminate linear dependencies (LLL)
  115. """
  116. # Project v on Span(B)
  117. #V = project_and_eliminate_dep(B, V) ## No need because D is full-rank
  118. #r = V.nrows() # Useless
  119. # Check that V belogs to L(B)
  120. D = dual_basis(B)
  121. M = D * V.T
  122. if not lcm([x.denominator() for x in M.list()]) == 1:
  123. raise ValueError("Not in the lattice")
  124. # Project the basis orthogonally to v
  125. PV = projection_matrix(V)
  126. B = B - B * PV
  127. # Eliminate linear dependencies
  128. if maintains_basis:
  129. B = remove_linear_dependencies(B)
  130. # Go back to the primal
  131. return B
  132. def lattice_modular_intersection(D, V, k, maintains_basis=True):
  133. """
  134. Compute the intersection of the lattice L(B) with
  135. the lattice {x | x*V = 0 mod k}
  136. (V can be either a vector or a matrix)
  137. Algorithm:
  138. - project V onto Span(B)
  139. - append the equations in the dual
  140. - eliminate linear dependencies (LLL)
  141. - go back to the primal.
  142. """
  143. # Project v on Span(B)
  144. #V = project_and_eliminate_dep(D, V) ## No need because D is full-rank
  145. #r = V.nrows() # Useless
  146. # append the equation in the dual
  147. V /= k
  148. D = D.stack(V)
  149. # Eliminate linear dependencies
  150. if maintains_basis:
  151. D = remove_linear_dependencies(D)
  152. # Go back to the primal
  153. return D
  154. def is_diagonal(M):
  155. if M.nrows() != M.ncols():
  156. return False
  157. A = M.numpy()
  158. return np.all(A == np.diag(np.diagonal(A)))
  159. def logdet(M, exact=False):
  160. """
  161. Compute the log of the determinant of a large rational matrix,
  162. tryping to avoid overflows.
  163. """
  164. if not exact:
  165. MM = array(M, dtype=float)
  166. _, l = slogdet(MM)
  167. return l
  168. a = abs(M.det())
  169. l = 0
  170. while a > 2**32:
  171. l += RR(32 * ln(2))
  172. a /= 2**32
  173. l += ln(RR(a))
  174. return l
  175. def degen_inverse(S, B=None):
  176. """ Compute the inverse of a symmetric matrix restricted
  177. to its span
  178. """
  179. # Get an orthogonal basis for the Span of B
  180. if B is None:
  181. # Get an orthogonal basis for the Span of B
  182. V = S.echelon_form()
  183. V = V[:V.rank()]
  184. P = projection_matrix(V)
  185. else:
  186. P = projection_matrix(B)
  187. # make S non-degenerated by adding the complement of span(B)
  188. C = identity_matrix(S.ncols()) - P
  189. Sinv = (S + C).inverse() - C
  190. assert S * Sinv == P, "Consistency failed (probably not your fault)."
  191. assert P * Sinv == Sinv, "Consistency failed (probably not your fault)."
  192. return Sinv
  193. def degen_logdet(S, B=None):
  194. """ Compute the determinant of a symmetric matrix
  195. sigma (m x m) restricted to the span of the full-rank
  196. rectangular (k x m, k <= m) matrix V
  197. """
  198. # Get an orthogonal basis for the Span of B
  199. if B is None:
  200. # Get an orthogonal basis for the Span of B
  201. V = S.echelon_form()
  202. V = V[:V.rank()]
  203. P = projection_matrix(V)
  204. else:
  205. P = projection_matrix(B)
  206. # Check that S is indeed supported by span(B)
  207. #assert S == P.T * S * P
  208. assert (S - P.T * S * P).norm() <= 1e-10
  209. # make S non-degenerated by adding the complement of span(B)
  210. C = identity_matrix(S.ncols()) - P
  211. l3 = logdet(S + C)
  212. return l3
  213. def square_root_inverse_degen(S, B=None):
  214. """ Compute the determinant of a symmetric matrix
  215. sigma (m x m) restricted to the span of the full-rank
  216. rectangular (k x m, k <= m) matrix V
  217. """
  218. if B is None:
  219. # Get an orthogonal basis for the Span of B
  220. V = S.echelon_form()
  221. V = V[:V.rank()]
  222. P = projection_matrix(V)
  223. else:
  224. P = projection_matrix(B)
  225. # make S non-degenerated by adding the complement of span(B)
  226. C = identity_matrix(S.ncols()) - P
  227. S_inv = np_inv(array((S + C), dtype=float))
  228. S_inv = array(S_inv, dtype=float)
  229. L_inv = cholesky(S_inv)
  230. L_inv = round_matrix_to_rational(L_inv)
  231. L = L_inv.inverse()
  232. return L, L_inv
  233. def build_standard_substitution_matrix(V, pivot=None, data=None, output_data=False):
  234. _, pivot = V.nonzero_positions()[0] if (pivot is None) else (None, pivot)
  235. assert V[0,pivot] != 0, 'The value of the pivot must be non-zero.'
  236. dim = V.ncols()
  237. V1 = - V[0,:pivot] / V[0,pivot]
  238. V2 = - V[0,pivot+1:] / V[0,pivot]
  239. Gamma = zero_matrix(QQ, dim,dim-1)
  240. Gamma[:pivot,:pivot] = identity_matrix(pivot)
  241. Gamma[pivot,:pivot] = V1
  242. Gamma[pivot,pivot:] = V2
  243. Gamma[pivot+1:,pivot:] = identity_matrix(dim-pivot-1)
  244. data = data if not output_data else {}
  245. if data is not None:
  246. norm2 = 1 + scal(V1*V1.T) + scal(V2*V2.T)
  247. normalization_matrix = zero_matrix(QQ, dim-1,dim-1)
  248. normalization_matrix[:pivot,:pivot] = identity_matrix(pivot) - V1.T*V1 / norm2
  249. normalization_matrix[pivot:,pivot:] = identity_matrix(dim-pivot-1) - V2.T*V2 / norm2
  250. normalization_matrix[:pivot,pivot:] = - V1.T*V2 / norm2
  251. normalization_matrix[pivot:,:pivot] = - V2.T*V1 / norm2
  252. data['det'] = norm2
  253. data['normalization_matrix'] = normalization_matrix
  254. if output_data:
  255. return Gamma, data
  256. else:
  257. return Gamma