div2.sage 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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. # i = 0
  199. L0 = div_line(P0, -P0)
  200. L1 = div_line(P1, -P1)
  201. L2 = div_line(P2, -P2)
  202. L3 = div_line(Q, -Q)
  203. # P₀ + 2P₁ + 3P₂ + 5Q
  204. # i = 1
  205. D1 = div_line(P1, P1)
  206. R1 = P1 + P1
  207. D2 = div_line(P2, P2)
  208. R2 = P2 + P2
  209. D3 = div_line(P2, Q)
  210. R3 = P2 + Q
  211. D4 = div_line(Q, Q)
  212. R4 = Q + Q
  213. D5 = D4
  214. R5 = R4
  215. D6 = L0
  216. R6 = P0
  217. # i = 2
  218. D1 = D1 + D2 + div_line(R1, R2) - (div_line(R1, -R1) + div_line(R2, -R2))
  219. R1 = R1 + R2
  220. D2 = D3 + D4 + div_line(R3, R4) - (div_line(R3, -R3) + div_line(R4, -R4))
  221. R2 = R3 + R4
  222. D3 = D5 + D6 + div_line(R5, R6) - (div_line(R5, -R5) + div_line(R6, -R6))
  223. R3 = R5 + R6
  224. # i = 3
  225. Dx = D1
  226. Rx = R1
  227. D1 = D2 + D3 + div_line(R2, R3) - (div_line(R2, -R2) + div_line(R3, -R3))
  228. R1 = R2 + R3
  229. D2, R2 = Dx, Rx
  230. # i = 4
  231. D = D1 + D2 + div_line(R1, R2) - (div_line(R1, -R1) + div_line(R2, -R2))
  232. assert D.is_equiv({
  233. "P₀": 1,
  234. "P₁": 2,
  235. "P₂": 3,
  236. "Q": 5,
  237. "∞": -11
  238. })
  239. assert X1.eval(D) == (-1)^D.effective_degree() * D.eval(X1)
  240. f_numer = D.func.numerator().mod(eqn)
  241. f_denom = D.func.denominator().mod(eqn)
  242. f = f_numer / f_denom
  243. assert f.denominator() == 1
  244. print(f.numerator())