div2.sage 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. class Divisor:
  2. def __init__(self, func, support):
  3. self.func = func
  4. self.support = support
  5. def __repr__(self):
  6. rep = ""
  7. first = True
  8. for v, P in self.support:
  9. if first:
  10. if v < 0:
  11. rep = "- "
  12. first = False
  13. else:
  14. if v < 0:
  15. rep += " - "
  16. else:
  17. rep += " + "
  18. v = abs(v)
  19. if v == 1:
  20. rep += f"[{P}]"
  21. else:
  22. rep += f"{v}[{P}]"
  23. return rep
  24. def rename(self, symbol, new_point):
  25. for i, (v, P) in enumerate(self.support):
  26. if repr(P) == symbol:
  27. self.support[i][1] = new_point
  28. def copy_support(self):
  29. support = []
  30. for v, P in self.support:
  31. support.append([v, P.copy()])
  32. return support
  33. def __add__(self, other):
  34. support = self.copy_support()
  35. for n, P in other.support:
  36. found = False
  37. for i, (_, Q) in enumerate(support):
  38. if P.P == Q.P:
  39. found = True
  40. support[i][0] += n
  41. if not found:
  42. support.append([n, P])
  43. func = self.func * other.func
  44. return Divisor(func, support)._cleanup()
  45. def _cleanup(self):
  46. # Cleanup
  47. support = []
  48. for n, P in self.support:
  49. if n == 0:
  50. continue
  51. support.append([n, P])
  52. self.support = support
  53. return self
  54. def __neg__(self):
  55. support = []
  56. for n, P in self.support:
  57. support.append([-n, P])
  58. func = 1/self.func
  59. return Divisor(func, support)
  60. def __sub__(self, other):
  61. other = -other
  62. return self + other
  63. def is_equiv(self, support):
  64. support = support.copy()
  65. for n, P in self.support:
  66. P = repr(P)
  67. if P not in support:
  68. return False
  69. if n != support[P]:
  70. return False
  71. del support[P]
  72. return not support
  73. def eval(self, other):
  74. f = self.func
  75. result = 1
  76. for n, P in other.support:
  77. if P.P == E(0, 1, 0):
  78. continue
  79. Px, Py = P.P.xy()
  80. result *= f(x=Px, y=Py)^n
  81. return result
  82. def effective_degree(self):
  83. deg = 0
  84. for n, P in self.support:
  85. if P.P == E(0, 1, 0):
  86. continue
  87. deg += n
  88. return deg
  89. def slope_intercept(P1, P2):
  90. P1x, P1y = P1.xy()
  91. P2x, P2y = P2.xy()
  92. P3x, P3y = (-(P1 + P2)).xy()
  93. λ = (P2y - P1y) / (P2x - P1x)
  94. μ = P2y - λ*P2x
  95. return λ, μ
  96. def line(P1, P2):
  97. if -(P1 + P2) == E(0, 1, 0):
  98. assert P1[0] == P2[0]
  99. return x - P1[0]
  100. if P1 == P2:
  101. # Use P3 instead
  102. P2 = -(P1 + P2)
  103. λ, μ = slope_intercept(P1, P2)
  104. return y - λ*x - μ
  105. class LabelPoint:
  106. def __init__(self, P, labels):
  107. self.P = P
  108. self.labels = labels
  109. def _add_labels(self, other_labels):
  110. labels = self.labels.copy()
  111. for key, value in other_labels.items():
  112. if key in labels:
  113. labels[key] += value
  114. else:
  115. labels[key] = value
  116. return labels
  117. def copy(self):
  118. return LabelPoint(self.P, self.labels.copy())
  119. def __add__(self, Q):
  120. P = self.P + Q.P
  121. labels = self._add_labels(Q.labels)
  122. return LabelPoint(P, labels)
  123. def __mul__(self, n):
  124. labels = self.labels.copy()
  125. for key in labels:
  126. labels[key] *= n
  127. return LabelPoint(n*self.P, labels)
  128. def __neg__(self):
  129. P = -self.P
  130. labels = self.labels.copy()
  131. for key in labels:
  132. labels[key] *= -1
  133. return LabelPoint(P, labels)
  134. def __repr__(self):
  135. rep = ""
  136. first = True
  137. for P, m in self.labels.items():
  138. if first:
  139. if m < 0:
  140. rep = "-"
  141. first = False
  142. else:
  143. if m < 0:
  144. rep += " - "
  145. else:
  146. rep += " + "
  147. m = abs(m)
  148. if m == 1:
  149. rep += f"{P}"
  150. else:
  151. rep += f"{m}{P}"
  152. return rep
  153. def div_line(P1, P2):
  154. inf = LabelPoint(E(0, 1, 0), {"∞": 1})
  155. P3 = -(P1 + P2)
  156. if P1 == P2:
  157. support = [
  158. [ 2, P1],
  159. [ 1, P3],
  160. [-3, inf]
  161. ]
  162. elif P3.P == E(0, 1, 0):
  163. support = [
  164. [ 1, P1],
  165. [ 1, P2],
  166. [-2, inf]
  167. ]
  168. else:
  169. support = [
  170. [ 1, P1],
  171. [ 1, P2],
  172. [ 1, P3],
  173. [-3, inf]
  174. ]
  175. func = line(P1.P, P2.P)
  176. return Divisor(func, support)
  177. # Initialize an elliptic curve
  178. p = 115792089237316195423570985008687907853269984665640564039457584007908834671663
  179. r = 115792089237316195423570985008687907852837564279074904382605163141518161494337
  180. Fp = GF(p) # Base Field
  181. Fr = GF(r) # Scalar Field
  182. A = 0
  183. B = 7
  184. E = EllipticCurve(GF(p), [A, B])
  185. assert(E.cardinality() == r)
  186. K.<x> = PolynomialRing(Fp, implementation="generic")
  187. L.<y> = PolynomialRing(K, implementation="generic")
  188. M.<z> = L[]
  189. eqn = y^2 - x^3 - A * x - B
  190. P0 = LabelPoint(E.random_element(), {"P₀": 1})
  191. P1 = LabelPoint(E.random_element(), {"P₁": 1})
  192. P2 = LabelPoint(E.random_element(), {"P₂": 1})
  193. Q = -int(Fr(5)^-1) * (P0.P + 2*P1.P + 3*P2.P)
  194. Q = LabelPoint(Q, {"Q": 1})
  195. A0 = LabelPoint(E.random_element(), {"A₀": 1})
  196. A1 = LabelPoint(E.random_element(), {"A₁": 1})
  197. X1 = div_line(A0, A1)
  198. D1 = div_line(P2, P2)
  199. assert X1.eval(D1) == (-1)^D1.effective_degree() * D1.eval(X1)
  200. D2 = div_line(P1, P1)
  201. assert X1.eval(D2) == (-1)^D2.effective_degree() * D2.eval(X1)
  202. D3 = div_line(P2, -P2)
  203. assert X1.eval(D3) == (-1)^D3.effective_degree() * D3.eval(X1)
  204. D4 = div_line(P0, -P0)
  205. assert X1.eval(D4) == (-1)^D4.effective_degree() * D4.eval(X1)
  206. D5 = D1 + D2 + D3 + D4
  207. assert D5.is_equiv({
  208. "P₀": 1,
  209. "P₁": 2,
  210. "P₂": 3,
  211. "-P₀": 1,
  212. "-2P₁": 1,
  213. "-2P₂": 1,
  214. "-P₂": 1,
  215. "∞": -10
  216. })
  217. assert X1.eval(D5) == (-1)^D5.effective_degree() * D5.eval(X1)
  218. D6 = div_line(P2*-2, P1*-2)
  219. assert X1.eval(D6) == (-1)^D6.effective_degree() * D6.eval(X1)
  220. D7 = div_line(-P2, -P0)
  221. assert X1.eval(D7) == (-1)^D7.effective_degree() * D7.eval(X1)
  222. D8 = D5 - (D6 + D7)
  223. assert D8.is_equiv({
  224. "P₀": 1,
  225. "P₁": 2,
  226. "P₂": 3,
  227. "2P₂ + 2P₁": -1,
  228. "P₂ + P₀": -1,
  229. "∞": -4
  230. })
  231. assert X1.eval(D8) == (-1)^D8.effective_degree() * D8.eval(X1)
  232. D9 = div_line(P0 + P2, (P1 + P2)*2)
  233. D10 = D8 + D9
  234. assert D10.is_equiv({
  235. "P₀": 1,
  236. "P₁": 2,
  237. "P₂": 3,
  238. "-P₀ - 3P₂ - 2P₁": 1,
  239. "∞": -7
  240. })
  241. D10.rename("-P₀ - 3P₂ - 2P₁", Q*5)
  242. assert X1.eval(D10) == (-1)^D10.effective_degree() * D10.eval(X1)
  243. D11 = div_line(Q, Q)
  244. D12 = div_line(-Q*2, Q*2)
  245. D13 = D11 - D12
  246. assert D13.is_equiv({
  247. "Q": 2,
  248. "2Q": -1,
  249. "∞": -1
  250. })
  251. assert X1.eval(D13) == (-1)^D13.effective_degree() * D13.eval(X1)
  252. D14 = D13 + D13 + D13
  253. assert D14.is_equiv({
  254. "Q": 6,
  255. "2Q": -3,
  256. "∞": -3
  257. })
  258. D15 = div_line(Q*2, Q*2)
  259. D16 = D14 + D15
  260. assert D16.is_equiv({
  261. "Q": 6,
  262. "2Q": -1,
  263. "-4Q": 1,
  264. "∞": -6
  265. })
  266. D17 = div_line(Q*2, -Q*2)
  267. D18 = D16 + D17
  268. assert D18.is_equiv({
  269. "Q": 6,
  270. "-2Q": 1,
  271. "-4Q": 1,
  272. "∞": -8
  273. })
  274. D19 = div_line(-Q*2, -Q*4)
  275. D20 = D18 - D19
  276. assert D20.is_equiv({
  277. "Q": 6,
  278. "6Q": -1,
  279. "∞": -5
  280. })
  281. D21 = div_line(Q*6, -Q*6)
  282. D22 = D20 + D21
  283. D23 = div_line(Q, -Q*6)
  284. D24 = D22 - D23
  285. D = D10 + D24
  286. assert D.is_equiv({
  287. "P₀": 1,
  288. "P₁": 2,
  289. "P₂": 3,
  290. "Q": 5,
  291. "∞": -11
  292. })
  293. assert X1.eval(D) == (-1)^D.effective_degree() * D.eval(X1)