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.
 
 
 
 
 

292 lines
9.0 KiB

  1. from idealized import Idealized
  2. from collections import namedtuple
  3. debugging = True
  4. def debug_print(foo):
  5. if debugging: print foo
  6. checkGroupLaws = True
  7. checkTorsion = True
  8. checkIsogenies = True
  9. def memoize(f):
  10. # list cache because my __hash__ hack doesn't seem to work
  11. cache = []
  12. def ff(*args, **kwargs):
  13. key = (tuple(args),tuple(sorted(kwargs.iteritems())))
  14. for key_,value in cache:
  15. if key == key_: return value
  16. out = f(*args,**kwargs)
  17. cache.append((key,out))
  18. return out
  19. try:
  20. ff.__name__ = f.__name__
  21. except AttributeError: pass
  22. return ff
  23. def EcBase(curvename,varnames,ad=()):
  24. if isinstance(ad,str) or isinstance(ad[0],str):
  25. ad = Idealized.vars(ad)
  26. class Inner(namedtuple(curvename,(v for v in varnames))):
  27. params = ad
  28. torsion_points = {}
  29. def __new__(cls,*xy):
  30. def apply_invariants(xy,x):
  31. for inv in cls.invariants(*(ad+xy)):
  32. x = x.assuming(inv)
  33. return x
  34. xy = tuple(xy)
  35. if len(xy) == 0:
  36. xy = Idealized.uvars(varnames)
  37. xy = [apply_invariants(xy,x) for x in xy]
  38. else:
  39. for i,inv in enumerate(cls.invariants(*(ad + xy))):
  40. if inv != 0:
  41. raise Exception("Invariant inv[%d] not satisfied for %s: got \n%s" %
  42. (i,curvename,str(inv)))
  43. return super(Inner,cls).__new__(cls,*xy)
  44. varnames = "xy"
  45. @classmethod
  46. def invariants(self,*args): return []
  47. @classmethod
  48. @memoize
  49. def check_group(cls):
  50. if checkGroupLaws:
  51. debug_print("Checking group law for %s..." % cls.__name__)
  52. a,b,c,z = cls(),cls(),cls(),cls.basepoint
  53. if a+z != a:
  54. raise Exception("Base point is not identity!")
  55. if a-a != z:
  56. raise Exception("Subtraction doesn't work!")
  57. if a+b != b+a:
  58. raise Exception("Addition is not commutative!")
  59. #if a+(b+c) != (a+b)+c:
  60. # raise Exception("Addition is not associative!")
  61. for t,n in cls.torsion():
  62. if checkTorsion:
  63. debug_print(" Checking %d-torsion..." % n)
  64. cls.check_torsion(t,n)
  65. #if n not in cls.torsion_points:
  66. # cls.torsion_points[n] = set()
  67. #cls.torsion_points[n].add(cls(*t(cls.basepoint)))
  68. @classmethod
  69. def check_torsion(cls,f,n):
  70. P = Q = cls()
  71. good = False
  72. for i in xrange(1,n+1):
  73. Q = cls(*f(Q))
  74. if Q == P:
  75. if i==n:
  76. good = True
  77. break
  78. raise Exception("Claimed %d-torsion, but is actually %d-torsion" % (n,i))
  79. if not good: raise Exception("Claimed %d-torsion, but isn't" % n)
  80. if n*P+n*cls(*f(P)) == cls.basepoint:
  81. raise Exception("Torsion operation inverts element")
  82. @classmethod
  83. def torsion(cls):
  84. return []
  85. def __sub__(self,other):
  86. return self + (-other)
  87. def __mul__(self,other):
  88. if other==0: return self.basepoint
  89. if other < 0: return -(self*-other)
  90. if other==1: return self
  91. if is_even(other): return (self+self)*(other//2)
  92. return (self+self)*(other//2) + self
  93. def __rmul__(self,other):
  94. return self*other
  95. Inner.__name__ = curvename + "_base"
  96. return Inner
  97. class Isogeny(object):
  98. isograph = DiGraph(weighted=True)
  99. isomap = {}
  100. @classmethod
  101. def generate(cls, fro, to):
  102. path = cls.isograph.shortest_path(fro,to,by_weight=True)
  103. if len(path):
  104. iso = cls.isomap[(path[0], path[1])]
  105. for i in xrange(1,len(path)-1):
  106. iso = cls.isomap[(path[i],path[i+1])].compose(iso)
  107. return iso
  108. else:
  109. return None
  110. def __init__(self,c1,c2,deg,fw,rv,check=True,dual=None,add=True):
  111. self.c1 = c1
  112. self.c2 = c2
  113. self.fw = fw
  114. self.rv = rv
  115. self.deg = deg
  116. if add:
  117. Isogeny.isomap[(c1,c2)] = self
  118. Isogeny.isograph.add_edge(c1,c2,log(deg)/log(2) + 0.1)
  119. if dual is not None:
  120. self.dual = dual
  121. else:
  122. self.dual = Isogeny(c2,c1,deg,rv,fw,False,self,add)
  123. if not check: return
  124. if not checkIsogenies: return
  125. debug_print("Checking isogeny %s <-%d-> %s..." % (c1.__name__,deg,c2.__name__))
  126. if c2(*fw(*c1.basepoint)) != c2.basepoint:
  127. raise Exception("Isogeny doesn't preserve basepoints")
  128. if c1(*fw(*c2.basepoint)) != c1.basepoint:
  129. raise Exception("Isogeny dual doesn't preserve basepoints")
  130. foo = c1()
  131. bar = c2()
  132. c2(*fw(*foo))
  133. c1(*rv(*bar))
  134. if c1(*rv(*c2(*fw(*foo)))) != deg*foo:
  135. raise Exception("Isogeny degree is wrong")
  136. if c2(*fw(*c1(*rv(*bar)))) != deg*bar:
  137. raise Exception("Isogeny degree is wrong")
  138. if -c2(*fw(*foo)) != c2(*fw(*(-foo))):
  139. raise Exception("Isogeny uses wrong negmap")
  140. if -c1(*rv(*bar)) != c1(*rv(*(-bar))):
  141. raise Exception("Isogeny uses wrong negmap")
  142. def __call__(self,ipt,**kwargs):
  143. return self.c2(*self.fw(*ipt,**kwargs))
  144. def __repr__(self): return str(self)
  145. def __str__(self):
  146. out = "Isogeny %s%s <-%d-> %s%s..." %\
  147. (self.c1.__name__,str(self.c1.params),self.deg,
  148. self.c2.__name__,self.c2.params)
  149. out += "\n fw: %s" % str(self(self.c1()))
  150. out += "\n rv: %s" % str(self.dual(self.c2()))
  151. return out
  152. def compose(self,other):
  153. def fw(*args): return self.fw(*other.fw(*args))
  154. def rv(*args): return other.rv(*self.rv(*args))
  155. return Isogeny(other.c1,self.c2,self.deg*other.deg,fw,rv,False,None,False)
  156. def ec_family(defs,vars):
  157. def inner1(CLS):
  158. @memoize
  159. def inner2(*args,**kwargs):
  160. if len(args)==0 and len(kwargs)==0:
  161. args = tuple(defs)
  162. chk = True
  163. else:
  164. chk = False
  165. class ret(CLS,EcBase(CLS.__name__,vars,args)):
  166. def __new__(cls,*args,**kwargs):
  167. return super(ret,cls).__new__(cls,*args,**kwargs)
  168. ret.__name__ = CLS.__name__
  169. ret.basepoint = ret(*ret.get_basepoint())
  170. if chk: ret.check_group()
  171. return ret
  172. inner2.__name__ = CLS.__name__ + "_family"
  173. inner2()
  174. return inner2
  175. return inner1
  176. #checkGroupLaws = checkTorsion = False
  177. @ec_family("ad","xy")
  178. class Edwards:
  179. @classmethod
  180. def invariants(cls,a,d,x,y):
  181. return [y^2 + a*x^2 - 1 - d*x^2*y^2]
  182. def __neg__(self):
  183. return self.__class__(-self.x,self.y)
  184. def __add__(self,other):
  185. (x,y) = self
  186. (X,Y) = other
  187. a,d = self.params
  188. dd = d*x*X*y*Y
  189. return self.__class__((x*Y+X*y)/(1+dd),(y*Y-a*x*X)/(1-dd))
  190. @classmethod
  191. def get_basepoint(cls): return (0,1)
  192. @classmethod
  193. @memoize
  194. def torsion(cls):
  195. a,d = cls.params
  196. sa = a.sqrt()
  197. sd = d.sqrt()
  198. sad = (a*d).sqrt()
  199. def tor2_1((x,y)): return (-x,-y)
  200. def tor4_1((x,y)): return (y/sa,-x*sa)
  201. def tor4_2((x,y)): return (1/(sd*y),-1/(sd*x))
  202. def tor2_2((x,y)): return (-1/(sad*x),-a/(sad*y))
  203. return [(tor2_1,2),(tor2_2,2),(tor4_1,4),(tor4_2,4)]
  204. @ec_family("eA","st")
  205. class JacobiQuartic:
  206. @classmethod
  207. def invariants(cls,e,A,s,t):
  208. return [-t^2 + e*s^4 + 2*A*s^2 + 1]
  209. def __neg__(self):
  210. return self.__class__(-self.s,self.t)
  211. def __add__(self,other):
  212. (x,y) = self
  213. (X,Y) = other
  214. e,A = self.params
  215. dd = e*(x*X)^2
  216. YY = (1+dd)*(y*Y+2*A*x*X) + 2*e*x*X*(x^2+X^2)
  217. return self.__class__((x*Y+X*y)/(1-dd),YY/(1-dd)^2)
  218. @classmethod
  219. def get_basepoint(cls): return (0,1)
  220. @classmethod
  221. @memoize
  222. def torsion(cls):
  223. e,A = cls.params
  224. se = e.sqrt()
  225. def tor2_1((s,t)): return (-s,-t)
  226. def tor2_2((s,t)): return (1/(se*s),-t/(se*s^2))
  227. return [(tor2_1,2),(tor2_2,2)]
  228. a,d = Idealized.vars("ad")
  229. def phi_iso(a,d):
  230. return Isogeny(Edwards(a,d),JacobiQuartic(a^2,a-2*d),
  231. 2,
  232. lambda x,y: (x/y, (2-y^2-a*x^2)/y^2),
  233. lambda s,t: (2*s/(1+a*s^2), (1-a*s^2)/t)
  234. )
  235. print phi_iso(a,d)
  236. print phi_iso(-a,d-a)
  237. print Isogeny.generate(Edwards(a,d),Edwards(-a,d-a))