from idealized import Idealized from collections import namedtuple debugging = True def debug_print(foo): if debugging: print foo checkGroupLaws = True checkTorsion = True checkIsogenies = True def memoize(f): # list cache because my __hash__ hack doesn't seem to work cache = [] def ff(*args, **kwargs): key = (tuple(args),tuple(sorted(kwargs.iteritems()))) for key_,value in cache: if key == key_: return value out = f(*args,**kwargs) cache.append((key,out)) return out try: ff.__name__ = f.__name__ except AttributeError: pass return ff def EcBase(curvename,varnames,ad=()): if isinstance(ad,str) or isinstance(ad[0],str): ad = Idealized.vars(ad) class Inner(namedtuple(curvename,(v for v in varnames))): params = ad torsion_points = {} def __new__(cls,*xy): def apply_invariants(xy,x): for inv in cls.invariants(*(ad+xy)): x = x.assuming(inv) return x xy = tuple(xy) if len(xy) == 0: xy = Idealized.uvars(varnames) xy = [apply_invariants(xy,x) for x in xy] else: for i,inv in enumerate(cls.invariants(*(ad + xy))): if inv != 0: raise Exception("Invariant inv[%d] not satisfied for %s: got \n%s" % (i,curvename,str(inv))) return super(Inner,cls).__new__(cls,*xy) varnames = "xy" @classmethod def invariants(self,*args): return [] @classmethod @memoize def check_group(cls): if checkGroupLaws: debug_print("Checking group law for %s..." % cls.__name__) a,b,c,z = cls(),cls(),cls(),cls.basepoint if a+z != a: raise Exception("Base point is not identity!") if a-a != z: raise Exception("Subtraction doesn't work!") if a+b != b+a: raise Exception("Addition is not commutative!") #if a+(b+c) != (a+b)+c: # raise Exception("Addition is not associative!") for t,n in cls.torsion(): if checkTorsion: debug_print(" Checking %d-torsion..." % n) cls.check_torsion(t,n) #if n not in cls.torsion_points: # cls.torsion_points[n] = set() #cls.torsion_points[n].add(cls(*t(cls.basepoint))) @classmethod def check_torsion(cls,f,n): P = Q = cls() good = False for i in xrange(1,n+1): Q = cls(*f(Q)) if Q == P: if i==n: good = True break raise Exception("Claimed %d-torsion, but is actually %d-torsion" % (n,i)) if not good: raise Exception("Claimed %d-torsion, but isn't" % n) if n*P+n*cls(*f(P)) == cls.basepoint: raise Exception("Torsion operation inverts element") @classmethod def torsion(cls): return [] def __sub__(self,other): return self + (-other) def __mul__(self,other): if other==0: return self.basepoint if other < 0: return -(self*-other) if other==1: return self if is_even(other): return (self+self)*(other//2) return (self+self)*(other//2) + self def __rmul__(self,other): return self*other Inner.__name__ = curvename + "_base" return Inner class Isogeny(object): isograph = DiGraph(weighted=True) isomap = {} @classmethod def generate(cls, fro, to): path = cls.isograph.shortest_path(fro,to,by_weight=True) if len(path): iso = cls.isomap[(path[0], path[1])] for i in xrange(1,len(path)-1): iso = cls.isomap[(path[i],path[i+1])].compose(iso) return iso else: return None def __init__(self,c1,c2,deg,fw,rv,check=True,dual=None,add=True): self.c1 = c1 self.c2 = c2 self.fw = fw self.rv = rv self.deg = deg if add: Isogeny.isomap[(c1,c2)] = self Isogeny.isograph.add_edge(c1,c2,log(deg)/log(2) + 0.1) if dual is not None: self.dual = dual else: self.dual = Isogeny(c2,c1,deg,rv,fw,False,self,add) if not check: return if not checkIsogenies: return debug_print("Checking isogeny %s <-%d-> %s..." % (c1.__name__,deg,c2.__name__)) if c2(*fw(*c1.basepoint)) != c2.basepoint: raise Exception("Isogeny doesn't preserve basepoints") if c1(*fw(*c2.basepoint)) != c1.basepoint: raise Exception("Isogeny dual doesn't preserve basepoints") foo = c1() bar = c2() c2(*fw(*foo)) c1(*rv(*bar)) if c1(*rv(*c2(*fw(*foo)))) != deg*foo: raise Exception("Isogeny degree is wrong") if c2(*fw(*c1(*rv(*bar)))) != deg*bar: raise Exception("Isogeny degree is wrong") if -c2(*fw(*foo)) != c2(*fw(*(-foo))): raise Exception("Isogeny uses wrong negmap") if -c1(*rv(*bar)) != c1(*rv(*(-bar))): raise Exception("Isogeny uses wrong negmap") def __call__(self,ipt,**kwargs): return self.c2(*self.fw(*ipt,**kwargs)) def __repr__(self): return str(self) def __str__(self): out = "Isogeny %s%s <-%d-> %s%s..." %\ (self.c1.__name__,str(self.c1.params),self.deg, self.c2.__name__,self.c2.params) out += "\n fw: %s" % str(self(self.c1())) out += "\n rv: %s" % str(self.dual(self.c2())) return out def compose(self,other): def fw(*args): return self.fw(*other.fw(*args)) def rv(*args): return other.rv(*self.rv(*args)) return Isogeny(other.c1,self.c2,self.deg*other.deg,fw,rv,False,None,False) def ec_family(defs,vars): def inner1(CLS): @memoize def inner2(*args,**kwargs): if len(args)==0 and len(kwargs)==0: args = tuple(defs) chk = True else: chk = False class ret(CLS,EcBase(CLS.__name__,vars,args)): def __new__(cls,*args,**kwargs): return super(ret,cls).__new__(cls,*args,**kwargs) ret.__name__ = CLS.__name__ ret.basepoint = ret(*ret.get_basepoint()) if chk: ret.check_group() return ret inner2.__name__ = CLS.__name__ + "_family" inner2() return inner2 return inner1 #checkGroupLaws = checkTorsion = False @ec_family("ad","xy") class Edwards: @classmethod def invariants(cls,a,d,x,y): return [y^2 + a*x^2 - 1 - d*x^2*y^2] def __neg__(self): return self.__class__(-self.x,self.y) def __add__(self,other): (x,y) = self (X,Y) = other a,d = self.params dd = d*x*X*y*Y return self.__class__((x*Y+X*y)/(1+dd),(y*Y-a*x*X)/(1-dd)) @classmethod def get_basepoint(cls): return (0,1) @classmethod @memoize def torsion(cls): a,d = cls.params sa = a.sqrt() sd = d.sqrt() sad = (a*d).sqrt() def tor2_1((x,y)): return (-x,-y) def tor4_1((x,y)): return (y/sa,-x*sa) def tor4_2((x,y)): return (1/(sd*y),-1/(sd*x)) def tor2_2((x,y)): return (-1/(sad*x),-a/(sad*y)) return [(tor2_1,2),(tor2_2,2),(tor4_1,4),(tor4_2,4)] @ec_family("eA","st") class JacobiQuartic: @classmethod def invariants(cls,e,A,s,t): return [-t^2 + e*s^4 + 2*A*s^2 + 1] def __neg__(self): return self.__class__(-self.s,self.t) def __add__(self,other): (x,y) = self (X,Y) = other e,A = self.params dd = e*(x*X)^2 YY = (1+dd)*(y*Y+2*A*x*X) + 2*e*x*X*(x^2+X^2) return self.__class__((x*Y+X*y)/(1-dd),YY/(1-dd)^2) @classmethod def get_basepoint(cls): return (0,1) @classmethod @memoize def torsion(cls): e,A = cls.params se = e.sqrt() def tor2_1((s,t)): return (-s,-t) def tor2_2((s,t)): return (1/(se*s),-t/(se*s^2)) return [(tor2_1,2),(tor2_2,2)] a,d = Idealized.vars("ad") def phi_iso(a,d): return Isogeny(Edwards(a,d),JacobiQuartic(a^2,a-2*d), 2, lambda x,y: (x/y, (2-y^2-a*x^2)/y^2), lambda s,t: (2*s/(1+a*s^2), (1-a*s^2)/t) ) print phi_iso(a,d) print phi_iso(-a,d-a) print Isogeny.generate(Edwards(a,d),Edwards(-a,d-a))