// Schoof-Elkies-Atkin Algorithm! // Mike Scott August 1999 mike@compapp.dcu.ie // Counts points on GF(p) Elliptic Curve, y^2=x^3+Ax+B a prerequisite // for implemention of Elliptic Curve Cryptography // This program is intended as an aid to Cryptographers in generating // suitable curves (ideally with a prime number of points) with respect // to prime moduli from 160 to 512 bits in length. This can be done with // relatively modest computing facilities - the average home computer and // a little patience will suffice. // // An "ideal" curve is defined as one with with prime number of points. // // First the credits // // Basic algorithm is due to Schoof // 1. "Elliptic Curves Over Finite Fields and the Computation of Square Roots // mod p", Rene Schoof, Math. Comp., Vol. 44 pp 483-494 // // Elkies-Atkin ideas are described in // // 2. "Counting points on Elliptic Curves over Finite Fields", Rene Schoof, // Jl. de Theorie des Nombres de Bordeaux 7 (1995) pp 219-254 // // The particular variation implemented here is due to Mueller. See his thesis // // 3. "Ein Algorithmus zur Bestimmung der Punktanzahl elliptischer Kurven // uber endlichen Korpern der Charakteristik grosser drei", // available from Volker Mueller's home page // www.informatik.th-darmstadt.de/TI/Mitarbeiter/vmueller.html // // Other useful English-language publications are available from this site. // Strongly recommended is the recent book // // 4. "Elliptic Curves in Cryptography" // by Blake, Seroussi and Smart, London Mathematical Society Lecture Note // Series 265, Cambridge University Press. ISBN 0 521 65374 6 // // Another useful reference is // 5. Elliptic Curve Public Key Cryptosystems", Menezes, // Kluwer Academic Publishers, Chapter 7 // // The Kangaroo continuation is due to Pollard:- // // 6. "Monte Carlo Methods for Index Computation" // by J.M. Pollard in Math. Comp. Vol. 32 1978 pp 918-924 // // Fast FFT methods are largely as described by Shoup // // 7. "A New Polynomial Factorisation Algorithm and its implementation", // Victor Shoup, Jl. Symbolic Computation, 1996 // // A potentially more effective way of using Atkin primes is described in // // 8. "Remarks on the Schoof-Elkies-Atkin Algorithm", L. Dewaghe, Math. Comp. // Vol. 67, 223, July 1998, pp 1247-1252 // // Thanks are due to Richard Crandall for his encouragment, and the idea of // using projective co-ordinates. // // NOTE: Only for use on curves over large prime modulus P. // For smaller P use schoof.exe utility available from the same source. // // This first version does not process so-called Atkin primes // Schoof's original algorithm is used for primes 3, 5 and 7 (this facilitates // "early abort" using the -s option). // // After that only Elkies primes are used. It is therefore not as fast as it // could be, particularly for smaller values of the prime modulus P. // However when the asymptotics kick-in, it starts to get competitive (when // you need it to). Since the average Cryptographer will only wish to // generate a few curves for practical use, this is deemed to be adequate. // The final continuation uses Pollard's Lambda ("kangaroo") algorithm. // // It is envisaged that from time-to-time this program will be modified // and hopefully improved - that is speeded up. // In particular it is planned to exploit Atkin Primes to allow two herds of // kangaroos complete the job more efficiently // // Asyptotically the algorithm should take time O(log(p)^5) // However the Kangaroo continuation favours "smaller" curves, while // the asymptotic behaviour is more accurate for "bigger" curves // // Timings in minutes:- random curves 180MHz Pentium Pro // (ignoring time to generate Modular Polynomials) // // C1 C2 C3 Ave Asymptotic multiplier wrt 160 bits // 160-bit 2.5 3.0 2.0 2.5 1 // 192-bit 5.5 5.5 3.5 4.8 2.5 // 224-bit 9 7.5 10 8.8 5.4 // 256-bit 13.5 21.5 23 19.3 10.5 // 384-bit 86 108 120 105 60 // 512-bit 600 357 398 452 336 // // As can be seen the asymptotic behaviour of the program would appear to // be about right. The wide variations in timing for the same size of curve // is typical - it depends on how "lucky" you are finding Elkies Primes // // **************** // Download Instructions // // To access the Windows 'NT/95/98 executables directly, point your // browser at ftp://ftp.compapp.dcu.ie/pub/crypto, and download // // mueller.exe // modpol.exe // process.exe // sea.exe // // The main program source file for these programs may be found in the // same place, with .cpp extensions. // // To obtain the full source code first look at // the README file on the ftp site, and then download // // ftp://ftp.compapp.dcu.ie/pub/crypto/miracl.zip // To recompile, see the file sea.txt // // For more information:- // // http://indigo.ie/~mscott // // **************** // Instructions for use // // First run the utility "mueller" to build up a collection of Modular // Polynomials. Each modular polynomial is associated with a small odd prime. // This needs to be done once only - ever, but you can from time // to time augment your collection of Polynomials by running it again. // Its quite time consuming, but in less than an hour you should have enough // to get started. The more you have, the bigger the prime modulus that you // can use. // // Then run the utility "process" to process the raw polynomial file with // respect to your chosen prime modulus P. This need to be done just once for // every prime modulus of interest to you. This takes only a few minutes at // most. // // An alternative is to use instead the "modpol" application, which is a // composite of "mueller" and "process". It directly generates the Modular // Polynomials reduced wrt a pre-specified prime modulus, suitable for // immediate use by "sea". If working with limited computing resources such // that sufficient generic Modular Polynomials cannot be generated by // "mueller", this may be your only alternative. // // Finally run this program "sea" specifying the A and B parameters of the // particular curve. This program can also search through many curves for // a curve ideal for cryptographic use (with a prime number of points). // // For example try:- // // mueller 0 120 -o mueller.raw // process -f 65112*2#144-1 -i mueller.raw -o test160.pol // sea -3 49 -i test160.pol // // Here the "mueller" program generates modular polynomials for all odd primes // in the range 0 - 120 into the file mueller.raw. The "process" application // reduces these 'raw' polynomials wrt the 160 bit prime modulus // P = 65112*2^144-1 to a file test160.pol. The "sea" application uses this // file to count the points on the curve Y^2 = X^3 - 3X + 49 mod P // // Alternatively:- // // modpol -f 65112*2#144-1 0 120 -o test160.pol // sea -3 49 -i test160.pol // // The number of Modular Polynomials required depends on the size of the // prime modulus P. It is also random in the sense that it depends on the // probability of each small prime being an "Elkies" prime wrt the given curve. // In the vast majority of cases the range suggested to "mueller" or "modpol" // should be 0 to bits(P), where bits(P) is the number of bits in P. However // you might get away with a much smaller value if you are lucky with your // "Elkies" primes. If modular polynomials could not be generated for all // primes in the range, due to the use of the -s2, -s3 or -s6 flag in // "mueller" or "modpol" (see comments in mueller.cpp), then a somewhat // larger range might be needed. // // When using the "sea" program, the -s option is particularly useful // and allows automatic search for an "ideal" curve. If a curve order is // exactly divisible by a small prime, that curve is immediately abandoned, // and the program moves on to the next, incrementing the B parameter of // the curve. This is a fairly arbitrary but simple way of moving on to // the "next" curve. // // Note that if a prime q is an Atkin prime, then we know at least that q // is not a factor of NP, in other words that NP mod q != 0. // This can be easily proved. // // NOTE: The output file can be used directly with for example the ECDSA // programs IF AND ONLY IF an ideal curve is found. If you wish to use // a less-than-ideal curve, you will first have to factor NP completely, and // find a random point of large prime order. // // **************** // // Rev. 1 September 1999 - Faster and more Kangaroos! // Rev. 2 October 1999 - Poly class revamped. Faster search for tau mod lp // Rev. 3 October 1999 - Eliminated calculation of X^P // Rev. 4 December 1999 - Various optimizations // // This implementation is free. No terms, no conditions. It requires // version 4.24 or greater of the MIRACL library (a Shareware, Commercial // product, but free for non-profit making use), // available from ftp://ftp.compapp.dcu.ie/pub/crypto/miracl.zip // // However this program may be freely used (unmodified!) to generate curves // for commercial use. It may be recompiled for this purpose on any hardware. // // 32-bit build only // // Note that is a little faster to use an integer-only build of MIRACL. // See mirdef.hio // // #include #include #include #include #include "ecn.h" // Elliptic Curve Class #include "crt.h" // Chinese Remainder Theorem Class // // poly.h implements polynomial arithmetic. FFT methods are used for maximum // speed, as the polynomials can get very big. // But all that gruesome detail is hidden away. // // polymod.h implements polynomial arithmetic wrt to a preset poynomial // modulus. This looks a bit neater. Function setmod() sets the modulus // to be used. Again fast FFT methods are used. // // polyxy.h implements a bivariate polynomial class // #include "poly.h" #include "polymod.h" #include "polyxy.h" using namespace std; #ifndef MR_NOFULLWIDTH Miracl precision=18; // max. 18x32 bits per big number #else Miracl precision(18,MAXBASE); #endif PolyMod MY2,MY4; ZZn A,B; // Here ZZn are integers mod the prime p // Montgomery representation is used internally BOOL Edwards=FALSE; // Elliptic curve Point duplication formula void elliptic_dup(PolyMod& X,PolyMod& Y,PolyMod& Z) { // (X,Y,Z)=2.(X,Y,Z) PolyMod W1,W2,W3,W4; W2=Z*Z; // 1 W3=A*(W2*W2); // 2 W1=X*X; // 3 W4=3*W1+W3; Z*=(2*Y); // 4 Z has an implied y W2=MY2*(Y*Y); // 5 W3=4*X*W2; // 6 W1=W4*W4; // 7 X=W1-2*W3; W2*=W2; W2*=8; // 8 W3-=X; W3*=W4; // 9 polynomial multiplications Y=W3-W2; X*=MY2; // fix up - move implied y from Z to Y Y*=MY2; Z*=MY2; } // // This is addition formula for two distinct points on an elliptic curve // Works with projective coordinates which are automatically reduced wrt a // polynomial modulus // Remember that the expression for the Y coordinate of each point // (a function of X) is implicitly multiplied by Y. // We know Y^2=X^3+AX+B, but we don't have an expression for Y // So if Y^2 ever crops up - substitute for it // void elliptic_add(PolyMod& XT,PolyMod& YT,PolyMod& ZT,PolyMod& X,PolyMod& Y) { // add (X,Y,1) to (XT,YT,ZT) // on an elliptic curve PolyMod W1,W2,W4,W5,W6; W1=XT; W6=ZT*ZT; // 1 W4=X*W6; // 2 * W1-=W4; W2=YT; // W2 has an implied y W6*=ZT; // 3 W5=Y*W6; // 4 * W5 has an implied y W2-=W5; if (iszero(W1)) { if (iszero(W2)) { // should have doubled elliptic_dup(XT,YT,ZT); return; } else { // point at infinity ZT.clear(); return; } } W4=W1+2*W4; // W4=2*W4+W1 W5=W2+2*W5; // W5=2*W5+W2 ZT*=W1; // 5 W6=W1*W1; // 6 W1*=W6; // 7 W6*=W4; // 8 W4=MY2*(W2*W2); // 9 Substitute for Y^2 XT=W4-W6; W6=W6-2*XT; W2*=W6; // 10 W1*=W5; // 11 polynomial multiplications W5=W2-W1; YT=W5/(ZZn)2; return; } // // Program to compute the order of a point on an elliptic curve // using Pollard's lambda method for catching kangaroos. // // As a way of counting points on an elliptic curve, this // has complexity O(p^(1/4)) // // However Schoof puts a spring in the step of the kangaroos // allowing them to make bigger jumps, and lowering overall complexity // to O(p^(1/4)/sqrt(L)) where L is the product of the Schoof primes // // See "Monte Carlo Methods for Index Computation" // by J.M. Pollard in Math. Comp. Vol. 32 1978 pp 918-924 // // This code has been considerably speeded up using ideas from // "Parallel Collision Search with Cryptographic Applications", van Oorchot // and Wiener, J. Crypto., Vol. 12, 1-28, 1999 // #define STORE 80 #define HERD 5 ECn wild[STORE],tame[STORE]; Big wdist[STORE],tdist[STORE]; int wname[STORE],tname[STORE]; Big kangaroo(Big p,Big order,Big ordermod) { ECn ZERO,K[2*HERD],TE[2*HERD],X,P,G,table[128],trap; Big start[2*HERD],txc,wxc,mean,leaps,upper,lower,middle,a,b,x,y,n,w,t,nrp; int i,jj,j,m,sp,nw,nt,cw,ct,k,distinguished,nbits; Big D[2*HERD],s,distance[128],real_order; BOOL bad,collision,abort; forever { // find a random point on the curve do { x=rand(p); } while (!P.set(x,x)); lower=p+1-2*sqrt(p)-3; // lower limit of search upper=p+1+2*sqrt(p)+3; // upper limit of search w=1+(upper-lower)/ordermod; leaps=sqrt(w); mean=HERD*leaps/2; // ideal mean for set S=1/2*w^(0.5) nbits=bits(leaps/16); if (nbits>30) nbits=30; distinguished=1<mean) break; } table[0]=ordermod*P; for (i=1;i1) middle+=(ordermod+order-middle%ordermod); // middle must be // order mod ordermod for (i=0;i=STORE) { abort=TRUE; break; } cout << "." << flush; tame[nt]=K[jj]; tdist[nt]=D[jj]; tname[nt]=jj; for (k=0;k=STORE) { abort=TRUE; break; } cout << "." << flush; wild[nw]=K[jj]; wdist[nw]=D[jj]; wname[nw]=jj; for (k=0;kPRIMES[i]; if (sp==0) break; if (real_order%sp==0) { G=P; G*=(real_order/sp); if (G==ZERO) { real_order/=sp; continue; } } i++; } if (real_order <= 4*sqrt(p)) { cout << "Low Order point used - trying again" << endl; continue; } real_order=nrp; for (i=0;(sp=get_mip()->PRIMES[i])!=0;i++) while (real_order%sp==0) real_order/=sp; if (real_order==1) { // all factors of nrp were considered cout << "NP= " << nrp << endl; break; } if (prime(real_order)) { // all factors of NP except for one last one.... G=P; G*=(nrp/real_order); if (G==ZERO) { cout << "Failed - trying again" << endl; continue; } else { cout << "NP= " << nrp << endl; break; } } // Couldn't be bothered factoring nrp completely // Probably not an interesting curve for Cryptographic purposes anyway..... // But if 10 random points are all "killed" by nrp, its almost // certain to be the true NP, and not a multiple of a small order. bad=FALSE; for (i=0;i<10;i++) { do { x=rand(p); } while (!P.set(x,x)); G=P; G*=nrp; if (G!=ZERO) { bad=TRUE; break; } } if (bad) { cout << "Failed - trying again" << endl; continue; } cout << "NP is composite and not ideal for Cryptographic use" << endl; cout << "NP= " << nrp << " (probably)" << endl; break; } return nrp; } // // Coefficients Ck from Mueller's lemma 6.2 // 3. page 90 // 4. page 127-128 // void get_ck(int terms,ZZn a,ZZn b,ZZn *c) { int k,h; if (terms==0) return; c[1]=-a/5; if (terms==1) return; c[2]=-b/7; for (k=3;k<=terms;k++) { c[k]=0; for (h=1;h<=k-2;h++) c[k]+=c[h]*c[k-1-h]; c[k]*=((ZZn)3/(ZZn)((k-2)*(2*k+3))); } } // // quadratic field arithmetic // needed for Atkin Primes // void mulquad(int p,int qnr,int x,int y,int& a,int& b) { int olda=a; a=(a*x+b*y*qnr)%p; b=(olda*y+b*x)%p; } void powquad(int p,int qnr,int x,int y,int e,int& a,int& b) { int k=e; a=1; b=0; if (k==0) return; for(;;) { if (k%2!=0) mulquad(p,qnr,x,y,a,b); k/=2; if (k==0) return; mulquad(p,qnr,x,y,x,y); } } // // Euler Totient function // int phi(int n) { int i,r=1; for (i=2;i <-i file>" << endl; cout << "where the input file contains pre-processed modular polynomials" << endl; cout << "for a particular prime modulus P" << endl; cout << "e.g. sea -3 35317045537 -i mueller.pol" << endl; cout << "To input A and B in Hex, precede with -h" << endl; cout << "To output to a file, use flag -o " << endl; cout << "To observe Atkin prime processing, use flag -a" << endl; cout << "NOTE: Atkin prime information is not currently used" << endl; cout << "To search for NP prime, incrementing B, use flag -s" << endl; cout << "(For Edwards curve the search is for NP=4*prime)" << endl; cout << "\nFreeware from Certivox, Dublin, Ireland" << endl; cout << "Full C++ source code and MIRACL multiprecision library available" << endl; cout << "email mscott@indigo.ie" << endl; return 0; } ip=0; gprime(10000); // generate small primes < 1000 search=fout=gotI=gotA=gotB=atkin=FALSE; a=0; b=0; // Interpret command line Base=10; while (ipIOBASE=Base; a=argv[ip++]; mip->IOBASE=10; gotA=TRUE; continue; } if (!gotB) { mip->IOBASE=Base; b=argv[ip++]; mip->IOBASE=10; gotB=TRUE; continue; } cout << "Error in command line" << endl; return 0; } if (!gotI || !gotA || !gotB) { cout << "Error in command line" << endl; return 0; } // get prime modulus from .pol file mueller >> p; pbits=bits(p); if (pbits<64) { cout << "Prime modulus too small for this program!" << endl; cout << "Use SCHOOF program (ftp://ftp.compapp.dcu.ie/pub/crypto/schoof.exe)" << endl; exit(0); } cout << "P= " << p << endl; cout << "P mod 24 = " << p%24 << endl; cout << "P is " << pbits << " bits long" << endl; cout << "Reading in pre-processed Modular Polynomials... " << endl; // // read in all pre-processed bivariate modular polynomials // modulo(p); // set modulus l[0]=2; max=0; for (i=1;;i++) { mueller >> lp; if (mueller.eof()) break; max=i; l[i]=lp; cout << setw(3) << lp << flush; posXY=NULL; Gl[i].clear(); forever { mueller >> c >> nx >> ny; posXY=Gl[i].addterm((ZZn)c,nx,ny,posXY); if (nx==0 && ny==0) break; } cout << "\b\b\b" << flush; } mueller.close(); // loop for "-s" search option forever { fft_reset(); // reset FFT tables if (Edwards) { modulo(p); EB=b; EA=a; AZ=(ZZn)1/(EA-EB); A2=2*(EA+EB)/(EA-EB); A4=1; AW=1; AW*=AZ; A2*=AZ; A4*=AZ; A4*=AW; T=4*A2; T1=3*T; T3=18*36*(2*A4); A=T3-3*T1*T1; B=-T1*T3+2*T1*T1*T1; ecurve((Big)A,(Big)B,p,MR_AFFINE); // initialise Elliptic Curve } else { ecurve(a,b,p,MR_AFFINE); // initialise Elliptic Curve A=a; B=b; } // The elliptic curve as a Polynomial Y2=0; Y2.addterm(B,0); Y2.addterm(A,1); Y2.addterm((ZZn)1,3); Y4=Y2*Y2; cout << "Counting the number of points (NP) on the curve" << endl; if (Edwards) { cout << "X^2+" << EA << "*Y^2=X^2*Y^2+" << EB << endl; cout << "Equivalent to Weierstrass form" << endl; } cout << "y^2= " << Y2 << " mod " << p << endl; delta=-16*(4*A*A*A+27*B*B); if (delta==0) { cout << "Not Allowed! 4A^3+27B^2 = 0" << endl; if (search) {b+=1; continue; } else return 0; } // Calculate j-invariant j=(-1728*64*A*A*A)/delta; if (j==0 || j==1728) { cout << "Not Allowed! j-invariant = 0 or 1728" << endl; if (search) {b+=1; continue; } else return 0; } // Finding the order modulo 2 // If GCD(X^P-X,X^3+AX+B) == 1 , trace=1 mod 2, else trace=0 mod 2 XX=0; XX.addterm((ZZn)1,1); YY=0; YY.addterm((ZZn)-1,0); // Point (X,-Y) setmod(Y2); XP=pow(XX,p); G=gcd(XP-XX); parity=0; if (isone(G)) parity=1; cout << "NP mod 2 = " << (p+1-parity)%2; if ((p+1-parity)%2==0) { cout << " ***" << endl; if (search && !Edwards) {b+=1; continue; } } else cout << endl; nl=0; accum=1; // accumulated product of good primes Poly zero; PolyMod one,XT,YT,ZT,XL,YL,ZL,ZL2,ZT2; one=1; // polynomial = 1 zero=0; // polynomial = 0 PolyXY dGx,dGy,dGxx,dGxy,dGyy; ZZn E0b,E0bd,E2bs,E4b,E6b,Dg,Dj,Dgd,Djd,Dgs,Djs,jl,jld,p1,jd; ZZn E4bl,E6bl,deltal,atilde,btilde,gd,f,fd,s,Eg,Ej,Exy; int r,v,ld,ld1,discrim; ZZn M,cf[500],cft[500],ad,K,RF; Poly Fl,T,WP[500],H,X,Y,R; term *pos; E4b=-(A/3); E6b=-(B/2); delta=(E4b*E4b*E4b-E6b*E6b)/1728; // // find out how many bits we are going to need // before Kangaroos can take over // first=5; sl[0]=3; sl[1]=5; sl[2]=7; sl[3]=8; sl[4]=9; sl[5]=0; // do low prime powers SCHP=9; if (pbits<=256) d=pow((Big)2,64); else d=pow((Big)2,72); // kangaroos do more work d=sqrt(p/d); escape=FALSE; // Calculate Divisor Polynomials for small primes - Schoof 1985 p.485 // Set the first few by hand.... P[1]=1; P[2]=2; P[3]=0; P[4]=0; P2[1]=1; P3[1]=1; P2[2]=P[2]*P[2]; P3[2]=P2[2]*P[2]; P[3].addterm(-(A*A),0); P[3].addterm(12*B,1); P[3].addterm(6*A,2) ; P[3].addterm((ZZn)3,4); P2[3]=P[3]*P[3]; P3[3]=P2[3]*P[3]; P[4].addterm((ZZn)(-4)*(8*B*B+A*A*A),0); P[4].addterm((ZZn)(-16)*(A*B),1); P[4].addterm((ZZn)(-20)*(A*A),2); P[4].addterm((ZZn)80*B,3); P[4].addterm((ZZn)20*A,4); P[4].addterm((ZZn)4,6); P2[4]=P[4]*P[4]; P3[4]=P2[4]*P[4]; // generation of Divisor polynomials // See Schoof p. 485 for (jj=5;jj<=SCHP+1;jj++) { // different for even and odd if (jj%2==1) { n=(jj-1)/2; if (n%2==0) P[jj]=P[n+2]*P3[n]*Y4-P3[n+1]*P[n-1]; else P[jj]=P[n+2]*P3[n]-Y4*P3[n+1]*P[n-1]; } else { n=jj/2; P[jj]=P[n]*(P[n+2]*P2[n-1]-P[n-2]*P2[n+1])/(ZZn)2; } if (jj <= 1+(SCHP+1)/2) { // precalculate for later P2[jj]=P[jj]*P[jj]; P3[jj]=P2[jj]*P[jj]; } } // // Schoof's original method for small primes // for (i=0;;i++) { lp=sl[i]; if (lp==0) break; if (lp>=first) { good[nl]=lp; accum*=lp; } k=p%lp; setmod(P[lp]); MY2=Y2; // These next are time-consuming calculations of X^P, X^(P*P), Y^P and Y^(P*P) cout << "X^P " << flush; XP=pow(XX,p); cout << "\b\b\b\bY^P " << flush; YP=pow(MY2,(p-1)/2); cout << "\b\b\b\bX^PP" << flush; XPP=compose(XP,XP); // this is faster cout << "\b\b\b\bY^PP" << flush; YPP=YP*compose(YP,XP); cout << "\b\b\b\b"; PolyMod Pk,P2k,PkP1,PkM1,PkP2; Pk=P[k]; PkP1=P[k+1]; PkM1=P[k-1]; PkP2=P[k+2]; P2k=(Pk*Pk); // // This is Schoof's algorithm, stripped to its bare essentials // // Now looking for the value of tau which satisfies // (X^(P*P),Y^(P*P)) + k.(X,Y) = tau.(X^P,Y^P) // // Note that (X,Y) are rational polynomial expressions for points on // an elliptic curve, so "+" means elliptic curve point addition // // k.(X,Y) can be found directly from Divisor polynomials // Schoof Prop (2.2) // // Points are converted to projective (X,Y,Z) form // This is faster (x2). Observe that (X/Z^2,Y/Z^3,1) is the same // point in projective co-ordinates as (X,Y,Z) // if (k%2==0) { XT=XX*MY2*P2k-PkM1*PkP1; YT=(PkP2*PkM1*PkM1-P[k-2]*PkP1*PkP1)/4; XT*=MY2; // fix up, so that Y has implicit y multiplier YT*=MY2; // rather than Z ZT=MY2*Pk; } else { XT=(XX*P2k-MY2*PkM1*PkP1); if (k==1) YT=(PkP2*PkM1*PkM1+PkP1*PkP1)/4; else YT=(PkP2*PkM1*PkM1-P[k-2]*PkP1*PkP1)/4; ZT=Pk; } elliptic_add(XT,YT,ZT,XPP,YPP); // // Test for Schoof's case 1 - LHS (XT,YT,ZT) is point at infinity // cout << "NP mod " << lp << " = " << flush; if (iszero(ZT)) { // Is it zero point? (XPP,YPP) = - K(X,Y) if (lp>=first) t[nl++]=0; cout << setw(3) << (p+1)%lp; if ((p+1)%lp==0) { cout << " ***" << endl; if (search && (!Edwards || lp!=4)) {escape=TRUE; break;} } else cout << endl; continue; } // // Now keep finding tau.(X^P,Y^P), until equality is detected // This is very simple! // XL=XP; YL=YP; ZL=1; ZT2=ZT*ZT; for (tau=1;tau<=(lp/2);tau++) { cout << setw(3) << (p+1-tau)%lp << flush; ZL2=ZL*ZL; if (iszero(XT*ZL2-ZT2*XL)) // LHS == RHS?? { // LHS = RHS if (!iszero(YT*ZL2*ZL-YL*ZT*ZT2)) { // point doubled - change sign tau=lp-tau; cout << "\b\b\b"; cout << setw(3) << (p+1-tau)%lp << flush; } if (lp>=first) t[nl++]=tau; if ((p+1-tau)%lp==0) { cout << " ***" << endl; if (search && (!Edwards || lp!=4)) escape=TRUE; } else cout << endl; break; } elliptic_add(XL,YL,ZL,XP,YP); cout << "\b\b\b"; } if (escape) break; } if (!escape) for (i=1;accum<=d;i++) { if (i>max) { cout << "WARNING: Ran out of Modular Polynomials!" << endl; break; } lp=l[i]; if (lp<=SCHP) continue; k=p%lp; for (is=1;;is++) if (is*(lp-1)%12==0) break; el=lp; s=is; // Get next Modular Polynomial MP=Gl[i]; // Evaluate bivariate polynomial at Y=j-invariant // and use as polynomial modulus F=MP.F(j); setmod(F); cout << setw(3) << lp << flush; XP=pow(XX,p); // // Determine "Splitting type" // cout << "\b\b\bGCD" << flush; G=gcd(XP-XX); if (degree(G)==lp+1) { cout << "\b\b\b" << flush; continue; // pathological case } if (degree(G)==0) // Atkin Prime { if (!atkin && lp>100) // Don't process large Atkin Primes { cout << "\b\b\b" << flush; continue; } BOOL useful=FALSE; cout << "\b\b\b" << flush; cout << "ATK" << flush; PolyMod u[20]; int max_r,lim=1; u[0]=XP; u[1]=compose(u[0],u[0]); // // The code for processing Atkin Primes is in here, but currently largely // unused. However the simplest case is used, as it suggests only one // value for NP mod lp, and so can be used just like an Elkies prime // // if (atkin) max_r=lp+1; else max_r=2; for (r=2;r<=max_r;r++) { PolyMod C; int kk,m; BOOL first; if ((lp+1)%r!=0) continue; // only keep good ones! v=jac(k,lp); // check Schoof Prop. 6.3 jj=(lp+1)/r; if (jj%2==0 && v==(-1)) continue; if (jj%2==1 && v==1) continue; // if (phi(r)>8) continue; // > 8 candidates // if (lp<30 && phi(r)>2) continue; // if (lp<60 && phi(r)>4) continue; kk=r; m=0; first=TRUE; // // Right-to-Left Power Composition - find X^(P^r) // forever { if (kk%2!=0) { if (first) C=u[m]; else C=compose(u[m],C); first=FALSE; } kk/=2; if (kk==0) break; m++; if (m>lim) { // remember them for next time u[m]=compose(u[m-1],u[m-1]); lim=m; } } if (iszero(C-XX)) { // found splitting type useful=TRUE; break; } } cout << "\b\b\b" << flush; if (!useful) continue; cout << "NP mod " << lp << " = " << flush; int a,b,candidates,gx,gy,ord,qnr=2; BOOL gen; while (jac(qnr,lp)!=(-1)) qnr++; // // [4] Algorithm VII.4 - find a generator of F(lp^2) // ord=lp*lp-1; gy=1; for (gx=1;gx1 && igcd(jj,r)!=1) continue; powquad(lp,qnr,gx,gy,jj*ord/r,a,b); tau=((a+1)*k*(int)invers(2,lp))%lp; if (tau==0) { // r must be 2 - I can make use of this! // Its an Atkin prime, but only one possibility candidates++; cout << (p+1)%lp << flush; if ((p+1)%lp==0) { cout << " ***" << endl; if (search && (!Edwards || lp!=4)) escape=TRUE; } else cout << endl; good[nl]=lp; t[nl]=tau; nl++; accum*=lp; break; } else if (jac(tau,lp)==1) { candidates+=2; tau=sqrmp(tau,lp); tau=(2*tau)%lp; if (candidates==phi(r)) { cout << (p+1-tau)%lp << " or " << (p+1+tau)%lp << endl; break; } else cout << (p+1-tau)%lp << "," << (p+1+tau)%lp << "," << flush; } } if (escape) break; continue; } // // Good Elkies prime - so use it! // // First solve quadratic for a root // if (degree(G)==1) { discrim=0; g=-G.coeff(0); // Elkies Prime, one root, (2 possibilites) } else // degree(G)==2 { // Elkies Prime, two roots discrim=1; qb=G.coeff(1); qc=G.coeff(0); g=sqrt(qb*qb-4*qc); g=(-qb-g)/2; // pick either one } cout << "\b\b\bELK" << flush; // // Mueller's procedure for finding the atilde, btilde and p1 // parameters of the isogenous curve // 3. page 111 // 4. page 131-133 // First we need partial differentials of bivariate Modular Polynomial // dGx=diff_dx(MP); dGy=diff_dy(MP); dGxx=diff_dx(dGx); dGxy=diff_dx(dGy); dGyy=diff_dy(dGy); Eg=dGx.F(g,j); // Evaluated at (g,j) Ej=dGy.F(g,j); Exy=dGxy.F(g,j); Dg=g*Eg; Dj=j*Ej; deltal=delta*pow(g,12/is)/pow(el,12); if (Dj==0) { E4bl=E4b/(el*el); atilde=-3*pow(el,4)*E4bl; jl=pow(E4bl,3)/deltal; btilde=2*pow(el,6)*sqrt((jl-1728)*deltal); p1=0; } else { E2bs=(-12*E6b*Dj)/(s*E4b*Dg); gd=-(s/12)*E2bs*g; jd=-E4b*E4b*E6b/delta; E0b=E6b/(E4b*E2bs); Dgd=gd*Eg+g*(gd*dGxx.F(g,j)+jd*Exy); Djd=jd*Ej+j*(jd*dGyy.F(g,j)+gd*Exy); E0bd=((-s*Dgd)/12-E0b*Djd)/Dj; E4bl=(E4b-E2bs*(12*E0bd/E0b+6*E4b*E4b/E6b-4*E6b/E4b)+E2bs*E2bs)/(el*el); jl=pow(E4bl,3)/deltal; f=pow(el,is)/g; fd=s*E2bs*f/12; Dgs=dGx.F(f,jl); Djs=dGy.F(f,jl); jld=-fd*Dgs/(el*Djs); E6bl=-E4bl*jld/jl; atilde=-3*pow(el,4)*E4bl; btilde=-2*pow(el,6)*E6bl; p1=-el*E2bs/2; } // // Find factor of Division Polynomial from atilde, btilde and p1 // Here we follow 3. p 116 // Polynomials have been modified s.t x=z^2 // // Note that all Polynomials can be reduced mod x^(d+1), // where d=(lp-1)/2, using modxn() function // cout << "\b\b\bFAC" << flush; ld=(lp-1)/2; ld1=(lp-3)/2; get_ck(ld1,A,B,cf); WP[1]=1; pos=NULL; for (k=ld1;k>0;k--) pos=WP[1].addterm(cf[k],k+1,pos); for (v=2;v<=ld;v++) WP[v]=modxn(WP[v-1]*WP[1],ld+1); // // WPv have understood multiplier x^-v // get_ck(ld1,atilde,btilde,cft); Y=0; pos=NULL; for (k=ld1;k>0;k--) pos=Y.addterm((lp*cf[k]-cft[k])/(ZZn)((2*k+1)*(2*k+2)),k+1,pos); Y.addterm(-p1,1,pos); RF=1; H=1; X=1; for (r=1;r<=ld;r++) { X=modxn(X*Y,ld+1); RF*=r; H+=(X/RF); } // // H has understood multiplier x^-d // ad=1; Fl=0; pos=Fl.addterm(ad,ld); for (v=ld-1;v>=0;v--) { H-=ad*WP[v+1]; H=divxn(H,1); ad=H.min(); pos=Fl.addterm(ad,v,pos); } setmod(Fl); MY2=Y2; MY4=Y4; // // Only the Y-coordinate is calculated. No need for X^P ! // cout << "\b\b\bY^P" << flush; YP=pow(MY2,(p-1)/2); cout << "\b\b\b"; // Calculate Divisor Polynomials for small primes - Schoof 1985 p.485 // This time mod the new (small) modulus Fl // Set the first few by hand.... PolyMod Pf[300],P2f[300],P3f[300]; Pf[0]=0; Pf[1]=1; Pf[2]=2; Pf[3]=0; Pf[4]=0; P2f[1]=1; P3f[1]=1; P2f[2]=Pf[2]*Pf[2]; P3f[2]=P2f[2]*Pf[2]; Pf[3].addterm(-(A*A),0); Pf[3].addterm(12*B,1); Pf[3].addterm(6*A,2) ; Pf[3].addterm((ZZn)3,4); P2f[3]=Pf[3]*Pf[3]; P3f[3]=P2f[3]*Pf[3]; Pf[4].addterm((ZZn)(-4)*(8*B*B+A*A*A),0); Pf[4].addterm((ZZn)(-16)*(A*B),1); Pf[4].addterm((ZZn)(-20)*(A*A),2); Pf[4].addterm((ZZn)80*B,3); Pf[4].addterm((ZZn)20*A,4); Pf[4].addterm((ZZn)4,6); P2f[4]=Pf[4]*Pf[4]; P3f[4]=P2f[4]*Pf[4]; lower=5; // // Now looking for value of lambda which satisfies // (X^P,Y^P) = lambda.(XX,YY). // 3. Page 118, Algorithm 7.9 // // Note that it appears to be sufficient to only compare the Y coordinates (!?) // For a justification see page 120 of 3. // Thank you SYSTRAN translation service! (www.altavista.com) // good[nl]=lp; cout << "NP mod " << lp << " = " << flush; for (lambda=1;lambda<=(lp-1)/2;lambda++) { int res=0; PolyMod Ry,Ty; tau=(lambda+invers(lambda,lp)*p)%lp; k=(lp+tau*tau-(4*p)%lp)%lp; if (jac(k,lp)!=discrim) continue; // // Possible values of tau could be eliminated here by an application of // Atkin's algorithm.... // cout << setw(3) << (p+1-tau)%lp << flush; // This "loop" is usually executed just once for (jj=lower;jj<=lambda+2;jj++) { // different for even and odd if (jj%2==1) // 2 mod-muls { n=(jj-1)/2; if (n%2==0) Pf[jj]=Pf[n+2]*P3f[n]*MY4-P3f[n+1]*Pf[n-1]; else Pf[jj]=Pf[n+2]*P3f[n]-MY4*P3f[n+1]*Pf[n-1]; } else // 3 mod-muls { n=jj/2; Pf[jj]=Pf[n]*(Pf[n+2]*P2f[n-1]-Pf[n-2]*P2f[n+1])/(ZZn)2; } P2f[jj]=Pf[jj]*Pf[jj]; // square P3f[jj]=P2f[jj]*Pf[jj]; // cube } if (lambda+3>lower) lower=lambda+3; // compare Y-coordinates - 3 polynomial mod-muls required if (lambda%2==0) { Ry=(Pf[lambda+2]*P2f[lambda-1]-Pf[lambda-2]*P2f[lambda+1])/4; Ty=MY4*YP*P3f[lambda]; } else { if (lambda==1) Ry=(Pf[lambda+2]*P2f[lambda-1]+P2f[lambda+1])/4; else Ry=(Pf[lambda+2]*P2f[lambda-1]-Pf[lambda-2]*P2f[lambda+1])/4; Ty=YP*P3f[lambda]; } if (iszero(Ty-Ry)) res=1; if (iszero(Ty+Ry)) res=2; if (res!=0) { // has it doubled, or become point at infinity? if (res==2) { // it doubled - wrong sign tau=(lp-tau)%lp; cout << "\b\b\b"; cout << setw(3) << (p+1-tau)%lp << flush; } t[nl]=tau; if ((p+1-tau)%lp==0) { cout << " ***" << endl; if (search && (!Edwards || lp!=4)) escape=TRUE; } else cout << endl; break; } cout << "\b\b\b"; } nl++; accum*=lp; if (escape) break; } Modulus.clear(); if (escape) {b+=1; continue;} Crt CRT(nl,good); Big order,ordermod; ordermod=accum; order=(p+1-CRT.eval(t))%ordermod; // get order mod product of primes nrp=kangaroo(p,order,ordermod); if (Edwards) { if (!prime(nrp/4) && search) {b+=1; continue; } else break; } else { if (!prime(nrp) && search) {b+=1; continue; } else break; } } if (fout) { ECn P; ofile << bits(p) << endl; mip->IOBASE=16; ofile << p << endl; ofile << a << endl; ofile << b << endl; // generate a random point on the curve // point will be of prime order for "ideal" curve, otherwise any point if (!Edwards) { do { x=rand(p); } while (!P.set(x,x)); P.get(x,y); ofile << nrp << endl; } else { ZZn X,Y,Z,R,TA,TB,TC,TD,TE,TU; forever { X=randn(); R=(X*X-EB)/(X*X-EA); if (!qr(R))continue; Y=sqrt(R); break; } Z=1; // double point twice (4*P) for (i=0;i<2;i++) { TA = X*X; TB = Y*Y; TU = EA*TB; TC = TA+TU; TD = TA-TU; TE = (X+Y)*(X+Y)-TA-TB; X = TC*TD; Y = TE*(TC-2*EB*Z*Z); Z = TD*TE; } X/=Z; Y/=Z; x=X; y=Y; ofile << nrp/4 << endl; } ofile << x << endl; ofile << y << endl; mip->IOBASE=10; } if (p==nrp) { cout << "WARNING: Curve is anomalous" << endl; return 0; } // check MOV condition for curves of Cryptographic interest d=1; for (i=0;i<50;i++) { d=modmult(d,p,nrp); if (d==1) { cout << "WARNING: Curve fails MOV condition" << endl; return 0; } } return 0; }