1 /* Copyright (C) 2014 The PARI group.
2 
3 This file is part of the PARI/GP package.
4 
5 PARI/GP is free software; you can redistribute it and/or modify it under the
6 terms of the GNU General Public License as published by the Free Software
7 Foundation; either version 2 of the License, or (at your option) any later
8 version. It is distributed in the hope that it will be useful, but WITHOUT
9 ANY WARRANTY WHATSOEVER.
10 
11 Check the License for details. You should have received a copy of it, along
12 with the package; see the file 'COPYING'. If not, write to the Free Software
13 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
14 
15 #include "pari.h"
16 #include "paripriv.h"
17 
18 #define dbg_mode()  if (DEBUGLEVEL >= 2)
19 #define dbg_mode1() if (DEBUGLEVEL >= 3)
20 
21 #define ANSI_RED     "\x1b[31m"
22 #define ANSI_GREEN   "\x1b[32m"
23 #define ANSI_YELLOW  "\x1b[33m"
24 #define ANSI_BLUE    "\x1b[34m"
25 #define ANSI_MAGENTA "\x1b[35m"
26 #define ANSI_CYAN    "\x1b[36m"
27 #define ANSI_WHITE   "\x1b[37m"
28 
29 #define ANSI_BRIGHT_RED     "\x1b[31;1m"
30 #define ANSI_BRIGHT_GREEN   "\x1b[32;1m"
31 #define ANSI_BRIGHT_YELLOW  "\x1b[33;1m"
32 #define ANSI_BRIGHT_BLUE    "\x1b[34;1m"
33 #define ANSI_BRIGHT_MAGENTA "\x1b[35;1m"
34 #define ANSI_BRIGHT_CYAN    "\x1b[36;1m"
35 #define ANSI_BRIGHT_WHITE   "\x1b[37;1m"
36 
37 #define ANSI_RESET   "\x1b[0m"
38 
39 /* THINGS THAT DON'T BELONG */
40 
41 /* Assume that x is a vector such that
42    f(x) = 1 for x <= k
43    f(x) = 0 otherwise.
44    Return k.
45 */
46 static long
zv_binsearch0(void * E,long (* f)(void * E,GEN x),GEN x)47 zv_binsearch0(void *E, long (*f)(void* E, GEN x), GEN x)
48 {
49   long lo = 1;
50   long hi = lg(x)-1;
51   while (lo < hi) {
52     long mi = lo + (hi - lo)/2 + 1;
53     if (f(E,gel(x,mi))) lo = mi;
54     else hi = mi - 1;
55   }
56   if (f(E,gel(x,lo))) return lo;
57   return 0;
58 }
59 
60 INLINE long
time_record(GEN * X0,const char * Xx,long t)61 time_record(GEN* X0, const char* Xx, long t)
62 {
63   long i = Xx[0]-'A'+1, j = Xx[1]-'1'+1;
64   umael3(*X0, 1, i, j) += t;
65   umael3(*X0, 2, i, j) ++; return t;
66 }
67 
68 INLINE long
timer_record(GEN * X0,const char * Xx,pari_timer * ti)69 timer_record(GEN* X0, const char* Xx, pari_timer* ti)
70 { return time_record(X0, Xx, timer_delay(ti)); }
71 
72 INLINE long
FpJ_is_inf(GEN P)73 FpJ_is_inf(GEN P) { return signe(gel(P, 3)) == 0; }
74 
75 /*****************************************************************/
76 
77 /* D < 0 fundamental: return the number of units in Q(sqrt(D)) */
78 INLINE long
D_get_wD(long D)79 D_get_wD(long D)
80 {
81   if (D == -4) return 4;
82   if (D == -3) return 6;
83   return 2;
84 }
85 
86 /* Dinfo contains information related to the discirminant
87  *    D: the discriminant (D < 0)
88  *    h: the class number associated to D
89  *   bi: the "best invariant" for computing polclass(D, bi)
90  *   pd: the degree of polclass; equal to h except when bi is a double
91  *       eta-quotient w_p,q with p|D and q|D, where pd = h/2.
92  * Dfac: the prime factorization of D; we have D = q0 q1* q2* ... qn*
93  *       where q0 = 1, -4, 8, -8, qi* = 1 mod 4 and |qi| is a prime.
94  *       The factorization is a vecsmall listing the indices of the qi* as
95  *       they appear in the primelist (q0 = 1 is omitted) */
96 INLINE long
Dinfo_get_D(GEN Dinfo)97 Dinfo_get_D(GEN Dinfo) { return gel(Dinfo, 1)[1]; }
98 INLINE long
Dinfo_get_h(GEN Dinfo)99 Dinfo_get_h(GEN Dinfo) { return gel(Dinfo, 1)[2]; }
100 INLINE long
Dinfo_get_bi(GEN Dinfo)101 Dinfo_get_bi(GEN Dinfo) { return gel(Dinfo, 1)[3]; }
102 INLINE ulong
Dinfo_get_pd(GEN Dinfo)103 Dinfo_get_pd(GEN Dinfo) { return umael(Dinfo, 1, 4); }
104 INLINE GEN
Dinfo_get_Dfac(GEN Dinfo)105 Dinfo_get_Dfac(GEN Dinfo) { return gel(Dinfo, 2); }
106 
107 /* primelist and indexlist
108  *
109  * primelist begins with 8, -4, -8; subsequent elements are the p^* for
110  * successive odd primes <= maxsqrt, by increasing absolute value
111  *
112  *        i |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 |  10 |  11 |
113  * ---------+----+----+----+----+----+----+----+----+----+-----+-----+
114  *        i |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 |  10 |  11 |
115  * Plist[i] |  8 | -4 | -8 | -3 |  5 | -7 |-11 | 13 | 17 | -19 | -23 |
116  *        p |  3 |  5 |  7 |  9 | 11 | 13 | 15 | 17 | 19 |  21 |  23 |
117  * ---------+----+----+----+----+----+----+----+----+----+-----+-----+*/
118 
119 /* primelist containing primes whose absolute value is at most maxsqrt */
120 static GEN
ecpp_primelist_init(long maxsqrt)121 ecpp_primelist_init(long maxsqrt)
122 {
123   GEN P = cgetg(maxsqrt, t_VECSMALL);
124   forprime_t T;
125   long p, j = 4;
126   u_forprime_init(&T, 3, maxsqrt);
127   P[1] =  8; P[2] = -4; P[3] = -8;
128   while((p = u_forprime_next(&T))) P[j++] = ((p & 3UL) == 1)? p: -p;
129   setlg(P,j); return P;
130 }
131 
132 static GEN
Dfac_to_p(GEN x,GEN P)133 Dfac_to_p(GEN x, GEN P) { pari_APPLY_long(uel(P,x[i])); }
134 static GEN
Dfac_to_roots(GEN x,GEN P)135 Dfac_to_roots(GEN x, GEN P) { pari_APPLY_type(t_VEC, gel(P,x[i])); }
136 
137 #if 0
138 INLINE ulong
139 ecpp_param_get_maxsqrt(GEN param) { return umael3(param, 1, 1, 1); }
140 INLINE ulong
141 ecpp_param_get_maxdisc(GEN param) { return umael3(param, 1, 1, 2); }
142 #endif
143 INLINE ulong
ecpp_param_get_maxpcdg(GEN param)144 ecpp_param_get_maxpcdg(GEN param) { return umael3(param, 1, 1, 3); }
145 INLINE GEN
ecpp_param_get_primelist(GEN param)146 ecpp_param_get_primelist(GEN param) { return gmael(param, 1, 2); }
147 INLINE GEN
ecpp_param_get_disclist(GEN param)148 ecpp_param_get_disclist(GEN param) { return gmael(param, 1, 3); }
149 INLINE GEN
ecpp_param_get_primeord(GEN param)150 ecpp_param_get_primeord(GEN param) { return gmael(param, 1, 4); }
151 INLINE GEN
ecpp_param_get_primorial_vec(GEN param)152 ecpp_param_get_primorial_vec(GEN param) { return gel(param, 2); }
153 INLINE GEN
ecpp_param_get_tune(GEN param)154 ecpp_param_get_tune(GEN param) { return gel(param, 3); }
155 
156 /*  Input: x, 20 <= x <= 35
157  * Output: a vector whose ith entry is the product of all primes below 2^x */
158 static GEN
primorial_vec(ulong x)159 primorial_vec(ulong x)
160 {
161   pari_sp av = avma;
162   long i, y = x-19;
163   GEN v = primes_upto_zv(1UL << x), w = cgetg(y+1, t_VEC);
164   /* ind[i]th prime number is the largest prime <= 2^(20+i) */
165   long ind[] = { 0, 82025L, 155611L, 295947L, 564163L, 1077871L, 2063689L,
166                  3957809L, 7603553L, 14630843L, 28192750L, 54400028L,
167                  105097565L, 203280221L, 393615806L, 762939111L, 1480206279L};
168   gel(w,1) = zv_prod_Z(vecslice(v,1,ind[1]));
169   for (i = 2; i <= y; i++)
170   {
171     pari_sp av2 = avma;
172     GEN q = mulii(gel(w,i-1), zv_prod_Z(vecslice(v,ind[i-1]+1,ind[i])));
173     gel(w,i) = gerepileuptoint(av2, q);
174   }
175   return gerepileupto(av, w);
176 }
177 
178 /* NDmqg contains
179    N, as in the theorem in ??ecpp
180    Dinfo, as discussed in the comment about Dinfo
181    m, as in the theorem in ??ecpp
182    q, as in the theorem in ??ecpp
183    g, a quadratic nonresidue modulo N
184    sqrt, a list of squareroots
185 */
186 INLINE GEN
NDmqg_get_N(GEN x)187 NDmqg_get_N(GEN x) { return gel(x,1); }
188 INLINE GEN
NDmqg_get_Dinfo(GEN x)189 NDmqg_get_Dinfo(GEN x) { return gel(x,2); }
190 INLINE GEN
NDmqg_get_m(GEN x)191 NDmqg_get_m(GEN x) { return gel(x,3); }
192 INLINE GEN
NDmqg_get_q(GEN x)193 NDmqg_get_q(GEN x) { return gel(x,4); }
194 INLINE GEN
NDmqg_get_g(GEN x)195 NDmqg_get_g(GEN x) { return gel(x,5); }
196 INLINE GEN
NDmqg_get_sqrt(GEN x)197 NDmqg_get_sqrt(GEN x) { return gel(x,6); }
198 
199 /* COMPARATOR FUNCTIONS */
200 
201 static int
sort_disclist(void * data,GEN x,GEN y)202 sort_disclist(void *data, GEN x, GEN y)
203 {
204   long d1, h1, g1, o1, pd1, hf1, wD1, d2, h2, g2, o2, pd2, hf2, wD2;
205   (void)data;
206   d1 = Dinfo_get_D(x); wD1 = D_get_wD(d1);
207   d2 = Dinfo_get_D(y); wD2 = D_get_wD(d2);
208   /* higher number of units means more elliptic curves to try */
209   if (wD1 != wD2) return wD2 > wD1 ? 1 : -1;
210   /* lower polclass degree is better: faster computation of roots modulo N */
211   pd1 = Dinfo_get_pd(x); /* degree of polclass */
212   pd2 = Dinfo_get_pd(y);
213   if (pd1 != pd2) return pd1 > pd2 ? 1 : -1;
214   g1 = lg(Dinfo_get_Dfac(x))-1; /* genus number */
215   h1 = Dinfo_get_h(x); /* class number */
216   o1 = h1 >> (g1-1); /* odd class number */
217   g2 = lg(Dinfo_get_Dfac(y))-1;
218   h2 = Dinfo_get_h(y);
219   o2 = h2 >> (g2-1);
220   if (o1 != o2) return g1 > g2 ? 1 : -1;
221   /* lower class number is better: higher probability of succesful cornacchia */
222   if (h1 != h2) return h1 > h2 ? 1 : -1;
223   /* higher height factor is better: polclass would have lower coefficients */
224   hf1 = modinv_height_factor(Dinfo_get_bi(x)); /* height factor for best inv. */
225   hf2 = modinv_height_factor(Dinfo_get_bi(y));
226   if (hf1 != hf2) return hf2 > hf1 ? 1 : -1;
227   /* "higher" discriminant is better since its absolute value is lower */
228   if (d1 != d2) return d2 > d1 ? 1 : -1;
229   return 0;
230 }
231 
232 INLINE long
NDmqg_get_D(GEN x)233 NDmqg_get_D(GEN x) { return Dinfo_get_D(NDmqg_get_Dinfo(x)); }
234 static int
sort_NDmq_by_D(void * data,GEN x,GEN y)235 sort_NDmq_by_D(void *data, GEN x, GEN y)
236 {
237   long d1 = NDmqg_get_D(x);
238   long d2 = NDmqg_get_D(y);
239   (void)data; return d2 > d1 ? 1 : -1;
240 }
241 
242 static int
sort_Dmq_by_q(void * data,GEN x,GEN y)243 sort_Dmq_by_q(void *data, GEN x, GEN y)
244 { (void)data; return cmpii(gel(x,3), gel(y,3)); }
245 
246 INLINE long
Dmq_get_D(GEN Dmq)247 Dmq_get_D(GEN Dmq) { return Dinfo_get_D(gel(Dmq,1)); }
248 INLINE long
Dmq_get_h(GEN Dmq)249 Dmq_get_h(GEN Dmq) { return Dinfo_get_h(gel(Dmq,1)); }
250 static int
sort_Dmq_by_cnum(void * data,GEN x,GEN y)251 sort_Dmq_by_cnum(void *data, GEN x, GEN y)
252 {
253   ulong h1 = Dmq_get_h(x);
254   ulong h2 = Dmq_get_h(y);
255   if (h1 != h2) return h1 > h2 ? 1 : -1;
256   return sort_Dmq_by_q(data, x, y);
257 }
258 
259 /* return H s.t if -maxD <= D < 0 is fundamental then H[(-D)>>1] is the
260  * ordinary class number of Q(sqrt(D)); junk at other entries. */
261 static GEN
allh(ulong maxD)262 allh(ulong maxD)
263 {
264   ulong a, A = usqrt(maxD/3), maxD2 = maxD >> 1;
265   GEN H = zero_zv(maxD2);
266   for (a = 1; a <= A; a++)
267   {
268     ulong a2 = a << 1, aa = a*a, aa4 = aa << 2, b, c;
269     { /* b = 0 */
270       ulong D = aa << 1;
271       for (c = a; D <= maxD2; c++) { H[D]++; D += a2; }
272     }
273     for (b = 1; b < a; b++)
274     {
275       ulong B = b*b, D = (aa4 - B) >> 1;
276       if (D > maxD2) continue;
277       H[D]++; D += a2; /* c = a */
278       for (c = a+1; D <= maxD2; c++) { H[D] += 2; D += a2; }
279     }
280     { /* b = a */
281       ulong D = (aa4 - aa) >> 1;
282       for (c = a; D <= maxD2; c++) { H[D]++; D += a2; }
283     }
284   }
285   return H;
286 }
287 
288 static GEN
mkDinfo(GEN c,long D,long h)289 mkDinfo(GEN c, long D, long h)
290 {
291   long bi, pd, p1, p2;
292   bi = disc_best_modinv(D);
293   pd = (modinv_degree(&p1,&p2,bi) && (-D)%p1==0 && (-D)%p2==0)? h/2: h;
294   gel(c,1) = mkvecsmall4(D, h, bi, pd); return c;
295 }
296 
297 static GEN
ecpp_disclist_init(ulong maxdisc,GEN primelist)298 ecpp_disclist_init(ulong maxdisc, GEN primelist)
299 {
300   pari_sp av = avma;
301   long t, ip, u, lp, lmerge;
302   GEN merge, ev, od, Harr = allh(maxdisc); /* table of class numbers*/
303   long lenv = maxdisc/4; /* max length of od/ev */
304   long N; /* maximum number of positive prime factors */
305 
306   /* tuning paramaters blatantly copied from vecfactoru */
307   if (maxdisc < 510510UL) N = 7;
308   else if (maxdisc < 9699690UL) N = 8;
309     else if (maxdisc < 223092870UL) N = 9;
310   #ifdef LONG_IS_64BIT
311     else if (maxdisc < 6469693230UL) N = 10;
312     else if (maxdisc < 200560490130UL) N = 11;
313     else if (maxdisc < 7420738134810UL) N = 12;
314     else if (maxdisc < 304250263527210UL) N = 13;
315     else N = 16; /* don't bother */
316   #else
317     else N = 10;
318   #endif
319 
320   /* od[t] attached to discriminant 1-4*t, ev[t] attached to -4*t */
321   od = cgetg(lenv + 1, t_VEC); /* contains 'long' at first: save memory */
322   ev = cgetg(lenv + 1, t_VEC);
323   /* first pass: squarefree sieve and restrict to maxsqrt-smooth disc */
324   for (t = 1; t <= lenv; t++)
325   {
326     od[t] = 1;
327     switch(t&7)
328     {
329       case 0: case 4: ev[t] = 0; break;
330       case 2: ev[t] =-8; break;
331       case 6: ev[t] = 8; break;
332       default:ev[t] =-4; break;
333     }
334   }
335   lp = lg(primelist);
336   for (ip = 4; ip < lp; ip++) /* skip 8,-4,-8 */
337   { /* sieve by squares of primes */
338     long s, q = primelist[ip], p = labs(q);
339     s = (q == p)? 3: 1;
340     for (t = (s*p+1)>>2; t <= lenv; t += p, s += 4)
341     {
342       long c = od[t];
343       if (c) { if (s%p == 0) od[t] = 0; else  od[t] = c*q; }
344     }
345     s = 1;
346     for (t = p; t <= lenv; t += p, s++)
347     {
348       long c = ev[t];
349       if (c) { if (s%p == 0) ev[t] = 0; else  ev[t] = c*q; }
350     }
351   }
352   for (u = 0, t = 1; t <= lenv; t++)
353   { /* restrict to maxsqrt-smooth disc */
354     if (od[t] != -4*t+1) od[t] = 0; else u++;
355     if (ev[t] != -4*t)   ev[t] = 0; else u++;
356   }
357 
358   /* second pass: sieve by primes and record factorization */
359   for (t = 1; t <= lenv; t++)
360   {
361     if (od[t]) gel(od,t) = mkvec2(NULL, vecsmalltrunc_init(N));
362     if (ev[t])
363     {
364       GEN F = vecsmalltrunc_init(N);
365       long id;
366       switch(t&7)
367       {
368         case 2: id = 3; break;
369         case 6: id = 1; break;
370         default:id = 2; break;
371       }
372       vecsmalltrunc_append(F, id);
373       gel(ev,t) = mkvec2(NULL, F);
374     }
375   }
376   lp = lg(primelist);
377   for (ip = 4; ip < lp; ip++) /* skip 8,-4,-8 */
378   {
379     long s, q = primelist[ip], p = labs(q);
380     s = (q == p)? 3: 1;
381     for (t = (s*p+1)>>2; t <= lenv; t += p, s += 4)
382     {
383       GEN c = gel(od,t);
384       if (c) vecsmalltrunc_append(gel(c,2), ip);
385     }
386     s = 1;
387     for (t = p; t <= lenv; t += p, s++)
388     {
389       GEN c = gel(ev,t);
390       if (c) vecsmalltrunc_append(gel(c,2), ip);
391     }
392   }
393   /* merging the two arrays */
394   merge = cgetg(u+1, t_VEC); lmerge = 0;
395   for (t = 1; t <= lenv; t++)
396   {
397     GEN c;
398     c = gel(od,t); if (c) gel(merge, ++lmerge) = mkDinfo(c,1-4*t, Harr[2*t-1]);
399     c = gel(ev,t); if (c) gel(merge, ++lmerge) = mkDinfo(c, -4*t, Harr[2*t]);
400   }
401   setlg(merge, lmerge);
402   gen_sort_inplace(merge, NULL, &sort_disclist, NULL);
403   return gerepilecopy(av, merge);
404 }
405 
406 static GEN
ecpp_primeord_init(GEN primelist,GEN disclist)407 ecpp_primeord_init(GEN primelist, GEN disclist)
408 {
409   long i, k=1, lgdisclist = lg(disclist), lprimelist = lg(primelist);
410   GEN primeord = zero_Flv(lprimelist-1);
411   for (i=1;i < lgdisclist; i++)
412   {
413     GEN Dinfo = gel(disclist, i), Dfac = Dinfo_get_Dfac(Dinfo);
414     long j, l = lg(Dfac);
415     for (j = 1; j < l; j++)
416     {
417       long ip = Dfac[j];
418       if (primeord[ip]==0)
419         primeord[ip]=k++;
420     }
421   }
422   return perm_inv(primeord);
423 }
424 
425 /*  Input: a vector tune whose components are [maxsqrt,maxpcdg,tdivexp,expiN]
426  * Output: vector param of precomputations
427  *   let x =  be a component of tune then
428  *   param[1][1] = [maxsqrt, maxdisc, maxpcdg]
429  *   param[1][2] = primelist = Vecsmall of primes
430  *   param[1][3] = disclist  = vector of Dinfos, sorted by quality
431  *   param[2]    = primorial_vec
432  *   param[3]    = tune */
433 static GEN
ecpp_param_set(GEN tune,GEN x)434 ecpp_param_set(GEN tune, GEN x)
435 {
436   pari_sp av = avma;
437   ulong maxsqrt = uel(x,1), maxpcdg = uel(x,2), tdivexp = uel(x,3);
438   ulong maxdisc = maxsqrt * maxsqrt;
439   GEN T = mkvecsmall3(maxsqrt, maxdisc, maxpcdg);
440   GEN Plist = ecpp_primelist_init(maxsqrt);
441   GEN Dlist = ecpp_disclist_init(maxdisc, Plist);
442   GEN Olist = ecpp_primeord_init(Plist, Dlist);
443   GEN primorial = primorial_vec(tdivexp);
444   return gerepilecopy(av, mkvec3(mkvec4(T,Plist,Dlist,Olist), primorial, tune));
445 }
446 
447 /* cert contains [N, t, s, a4, [x, y]] as documented in ??ecpp; the following
448  * information can be obtained:
449  * D = squarefreepart(t^2-4N)
450  * m = (N+1-t), q = m/s, a6 = y^3 - x^2 - a4*x, J */
451 INLINE GEN
cert_get_N(GEN x)452 cert_get_N(GEN x) { return gel(x,1); }
453 INLINE GEN
cert_get_t(GEN x)454 cert_get_t(GEN x) { return gel(x,2); }
455 INLINE GEN
cert_get_D(GEN x)456 cert_get_D(GEN x)
457 {
458   GEN N = cert_get_N(x), t = cert_get_t(x);
459   return coredisc(subii(sqri(t), shifti(N, 2)));
460 }
461 INLINE GEN
cert_get_m(GEN x)462 cert_get_m(GEN x)
463 {
464   GEN N = cert_get_N(x), t = cert_get_t(x);
465   return subii(addiu(N, 1), t);
466 }
467 INLINE GEN
cert_get_s(GEN x)468 cert_get_s(GEN x) { return gel(x,3); }
469 INLINE GEN
cert_get_q(GEN x)470 cert_get_q(GEN x) { return diviiexact(cert_get_m(x), cert_get_s(x)); }
471 INLINE GEN
cert_get_a4(GEN x)472 cert_get_a4(GEN x) { return gel(x,4); }
473 INLINE GEN
cert_get_P(GEN x)474 cert_get_P(GEN x) { return gel(x,5); }
475 INLINE GEN
cert_get_x(GEN x)476 cert_get_x(GEN x) { return gel(cert_get_P(x), 1); }
477 INLINE GEN
cert_get_a6(GEN z)478 cert_get_a6(GEN z)
479 {
480   GEN N = cert_get_N(z), a = cert_get_a4(z), P = cert_get_P(z);
481   GEN x = gel(P,1), xx = Fp_sqr(x, N);
482   GEN y = gel(P,2), yy = Fp_sqr(y, N);
483   return Fp_sub(yy, Fp_mul(x, Fp_add(xx,a,N), N), N);
484 }
485 INLINE GEN
cert_get_E(GEN x)486 cert_get_E(GEN x) { return mkvec2(cert_get_a4(x), cert_get_a6(x)); }
487 INLINE GEN
cert_get_J(GEN x)488 cert_get_J(GEN x)
489 {
490   GEN a = cert_get_a4(x), b = cert_get_a6(x), N = cert_get_N(x);
491   return Fp_ellj(a, b, N);
492 }
493 
494 /* Given J, N, set A = 3*J*(1728-J) mod N, B = 2*J*(1728-J)^2 mod N */
495 static void
Fp_ellfromj(GEN j,GEN N,GEN * A,GEN * B)496 Fp_ellfromj(GEN j, GEN N, GEN *A, GEN *B)
497 {
498   GEN k, jk;
499   j = Fp_red(j, N);
500   if (isintzero(j)) { *A = gen_0; *B = gen_1; return; }
501   if (absequalui(umodui(1728,N), j)) { *A = gen_1; *B = gen_0; return; }
502   k = Fp_sub(utoi(1728), j, N);
503   jk = Fp_mul(j, k, N);
504   *A = Fp_mulu(jk, 3, N);
505   *B = Fp_mulu(Fp_mul(j, Fp_sqr(k,N), N), 2, N);
506 }
507 
508 /* "Twist factor". Does not cover J = 0, 1728 */
509 static GEN
cert_get_lambda(GEN z)510 cert_get_lambda(GEN z)
511 {
512   GEN N, J, a, b, A, B;
513   J = cert_get_J(z);
514   N = cert_get_N(z);
515   a = cert_get_a4(z);
516   b = cert_get_a6(z);
517   Fp_ellfromj(J, N, &A, &B);
518   return Fp_div(Fp_mul(a,B,N), Fp_mul(b,A,N), N);
519 }
520 
521 /* Solves for T such that if
522    [A, B] = [3*J*(1728-J), 2*J*(1728-J)^2]
523    and you let
524    L = T^3 + A*T + B, A = A*L^2, B = B*L^3
525    then
526    x == TL and y == L^2
527 */
528 static GEN
cert_get_T(GEN z)529 cert_get_T(GEN z)
530 {
531   GEN N = cert_get_N(z), x = cert_get_x(z);
532   GEN l = cert_get_lambda(z); /* l = 1/L */
533   return Fp_mul(x, l, N);
534 }
535 
536 /* database for all invariants, stolen from Hamish's code */
537 static GEN
polmodular_db_init_allinv(void)538 polmodular_db_init_allinv(void)
539 {
540   const long DEFAULT_MODPOL_DB_LEN = 32;
541   GEN a, b = cgetg(39+1, t_VEC);
542   long i;
543   for (i = 1; i < 40; i++) gel(b,i) = zerovec_block(DEFAULT_MODPOL_DB_LEN);
544   a = zerovec_block(DEFAULT_MODPOL_DB_LEN);
545   return mkvec2(a, b);
546 }
547 
548 /* Given D and a database of modular polynomials, return polclass(D, inv) */
549 static GEN
D_polclass(long D,long inv,GEN * db)550 D_polclass(long D, long inv, GEN *db)
551 {
552   GEN HD, t = mkvec2(gel(*db, 1), inv == 0? gen_0: gmael(*db, 2, inv));
553   HD = polclass0(D, inv, 0, &t);
554   gel(*db, 1) = gel(t,1);
555   if (inv != 0) gmael(*db, 2, inv) = gel(t,2);
556   return HD;
557 }
558 
559 static GEN
NqE_check(GEN N,GEN q,GEN a,GEN b,GEN s)560 NqE_check(GEN N, GEN q, GEN a, GEN b, GEN s)
561 {
562   GEN mP, sP, P = random_FpE(a, b, N);
563   GEN PJ = mkvec3(gel(P,1), gel(P,2), gen_1);
564   sP = FpJ_mul(PJ, s, a, N); if (FpJ_is_inf(sP)) return NULL;
565   mP = FpJ_mul(sP, q, a, N); return FpJ_is_inf(mP)? mkvec2(a, P): NULL;
566 }
567 
568 /* Find an elliptic curve E modulo N and a point P on E which corresponds to
569  * the ith element of the certificate; uses N, D, m, q, J from previous steps.
570  * All computations are modulo N unless stated otherwise */
571 
572 /* g is a quadratic and cubic nonresidue modulo N */
573 static GEN
j0_find_g(GEN N)574 j0_find_g(GEN N)
575 {
576   GEN n = diviuexact(subiu(N, 1), 3);
577   for(;;)
578   {
579     GEN g = randomi(N);
580     if (kronecker(g, N) == -1 && !equali1(Fp_pow(g, n, N))) return g;
581   }
582 }
583 
584 static GEN
find_EP(GEN N,long D,GEN q,GEN g,GEN J,GEN s)585 find_EP(GEN N, long D, GEN q, GEN g, GEN J, GEN s)
586 {
587   GEN A0, B0; Fp_ellfromj(J, N, &A0, &B0);
588   for(;;)
589   { /* expect one iteration: not worth saving the A's and B's */
590     GEN gg, v, A = A0, B = B0;
591     long i;
592     if ((v = NqE_check(N, q, A, B, s))) return v;
593     switch (D_get_wD(D))
594     {
595       case 2:
596         gg = Fp_sqr(g, N);
597         A = Fp_mul(A, gg, N);
598         B = Fp_mul(Fp_mul(B, gg, N), g, N);
599         if ((v = NqE_check(N, q, A, B, s))) return v;
600         break;
601       case 4:
602         for (i = 1; i < 4; i++)
603         {
604           A = Fp_mul(A, g, N);
605           if ((v = NqE_check(N, q, A, B, s))) return v;
606         }
607         break;
608       default: /* 6 */
609         B = Fp_mul(B, g, N);
610         if ((v = NqE_check(N, q, A, B, s))) return v;
611         g = j0_find_g(N);
612         for (i = 1; i < 6; i++)
613         {
614           B = Fp_mul(B, g, N);
615           if (i == 3) continue;
616           if ((v = NqE_check(N, q, A, B, s))) return v;
617         }
618         break;
619     }
620   }
621 }
622 
623 /* Convert the disc. factorisation of a genus field to the
624  * disc. factorisation of its real part. */
625 static GEN
realgenusfield(GEN Dfac,GEN sq,GEN p)626 realgenusfield(GEN Dfac, GEN sq, GEN p)
627 {
628   long i, j, l = lg(Dfac), dn, n = 0;
629   GEN sn, s = gen_0, R = cgetg(l-1, t_VECSMALL);
630   for (i = 1; i < l; i++)
631     if (Dfac[i] < 0) { n = i; break; }
632   if (n == 0) pari_err_BUG("realgenusfield");
633   dn = Dfac[n]; sn = gel(sq, n);
634   for (j = i = 1; i < l; i++)
635     if (i != n)
636     {
637       long d = Dfac[i];
638       GEN si = gel(sq,i);
639       R[j++] = d > 0 ? d : d * dn;
640       s = Fp_add(s, d > 0? si: Fp_mul(si, sn, p), p);
641     }
642   return mkvec2(R, s);
643 }
644 
645 static GEN
FpX_classtower_oneroot(GEN P,GEN Dfac,GEN sq,GEN p)646 FpX_classtower_oneroot(GEN P, GEN Dfac, GEN sq, GEN p)
647 {
648   pari_sp av = avma;
649   GEN C;
650   if (degpol(P) > 1)
651   {
652     GEN N = NULL, V = realgenusfield(Dfac, sq, p), v = gel(V,1), R = gel(V,2);
653     long i, l = lg(v);
654     for (i = 1; i < l; i++)
655     {
656       GEN Q = deg2pol_shallow(gen_1, gen_0, stoi(-v[i]), 1);
657       if (!N) N = Q;
658       else
659       {
660         GEN cm = polcompositum0(N,Q,3);
661         N = gel(cm,1); P = gsubst(P, 1, gel(cm,2));
662       }
663       P = liftpol_shallow(gmael(nffactor(N,P),1,1));
664     }
665     if (N)
666       P = FpXY_evalx(Q_primpart(P), R, p);
667   }
668   C = FpX_oneroot_split(P, p);
669   return gerepileupto(av, C);
670 }
671 
672 GEN
ecpp_step2_worker(GEN S,GEN HD,GEN primelist)673 ecpp_step2_worker(GEN S, GEN HD, GEN primelist)
674 {
675   pari_sp av = avma;
676   pari_timer ti;
677   GEN J, t, s, EP, rt, res;
678   GEN N = NDmqg_get_N(S), Dinfo = NDmqg_get_Dinfo(S);
679   GEN m = NDmqg_get_m(S), q = NDmqg_get_q(S);
680   GEN g = NDmqg_get_g(S), sq = NDmqg_get_sqrt(S);
681   long D = Dinfo_get_D(Dinfo), inv = Dinfo_get_bi(Dinfo);
682   GEN Dfacp = Dfac_to_p(Dinfo_get_Dfac(Dinfo), primelist);
683   long C2 = 0, C3 = 0, D1 = 0;
684   GEN c = getrand();
685   setrand(gen_1); /* for reproducibility */
686   /* C2: Find a root modulo N of polclass(D,inv) */
687   dbg_mode() timer_start(&ti);
688   rt = FpX_classtower_oneroot(HD, Dfacp, sq, N);
689   dbg_mode() C2 = timer_delay(&ti);
690   /* C3: Convert root from previous step into the appropriate j-invariant */
691   J = Fp_modinv_to_j(rt, inv, N); /* root of polclass(D) */
692   dbg_mode() C3 = timer_delay(&ti);
693   /* D1: Find an elliptic curve E with a point P satisfying the theorem */
694   s = diviiexact(m, q);
695   EP = find_EP(N, D, q, g, J, s);
696   dbg_mode() D1 = timer_delay(&ti);
697 
698   /* D2: Compute for t and s */
699   t = subii(addiu(N, 1), m); /* t = N+1-m */
700   res = mkvec2(mkvec5(N, t, s, gel(EP,1), gel(EP,2)),mkvecsmall3(C2,C3,D1));
701   setrand(c);
702   return gerepilecopy(av, res);
703 }
704 
705 /* This uses [N, D, m, q] from step 1 to find the appropriate j-invariants
706  * to be used in step 3. Step 2 is divided into substeps 2a, 2b, 2c */
707 static GEN
ecpp_step2(GEN step1,GEN * X0,GEN primelist)708 ecpp_step2(GEN step1, GEN *X0, GEN primelist)
709 {
710   long j, Dprev = 0;
711   pari_timer ti;
712   GEN perm = gen_indexsort(step1, NULL, &sort_NDmq_by_D);
713   GEN step2 = cgetg(lg(step1), t_VEC);
714   GEN HD = NULL, db = polmodular_db_init_allinv();
715   long ls = lg(step2), pending = 0;
716   GEN worker;
717   struct pari_mt pt;
718   GEN Hi = cgetg(ls, t_VEC);
719   for (j = 1; j < ls; j++)
720   {
721     long i = uel(perm, j);
722     GEN S = gel(step1, i);
723     GEN Dinfo = NDmqg_get_Dinfo(S);
724     long D = Dinfo_get_D(Dinfo), inv = Dinfo_get_bi(Dinfo);
725     /* C1: Find the appropriate class polynomial modulo N */
726     dbg_mode() timer_start(&ti);
727     if (D != Dprev) HD = D_polclass(D, inv, &db);
728     dbg_mode() {
729       GEN N = NDmqg_get_N(S);
730       long tt = timer_record(X0, "C1", &ti);
731       err_printf(ANSI_BRIGHT_GREEN "\n[ %3d | %4ld bits]" ANSI_RESET, i, expi(N));
732       err_printf(ANSI_GREEN " D = %8ld poldeg = %4ld" ANSI_RESET, D, degpol(HD));
733       if (D == Dprev) err_printf(" %6ld", tt);
734       else err_printf(ANSI_BRIGHT_WHITE " %6ld" ANSI_RESET, tt);
735     }
736     gel(Hi, i) = HD;
737   }
738   gunclone_deep(db);
739   worker = snm_closure(is_entry("_ecpp_step2_worker"),mkvec(primelist));
740   mt_queue_start_lim(&pt, worker, ls-1);
741   for (j = 1; j < ls || pending; j++)
742   {
743     long jj;
744     GEN S = gel(step1, j), HD = gel(Hi, j), done;
745     mt_queue_submit(&pt, j, j < ls ? mkvec2(S, HD) : NULL);
746     done = mt_queue_get(&pt, &jj, &pending);
747     if (!done) continue;
748     dbg_mode() {
749       GEN T = gel(done,2);
750       GEN N = NDmqg_get_N(gel(step1, jj));
751       err_printf(ANSI_BRIGHT_GREEN "\n[ %3d | %4ld bits]" ANSI_RESET, jj, expi(N));
752       err_printf(" %6ld", time_record(X0, "C2", T[1]));
753       err_printf(" %6ld", time_record(X0, "C3", T[2]));
754       err_printf(" %6ld", time_record(X0, "D1", T[3]));
755     }
756     gel(step2, jj) = gel(done,1);
757   }
758   mt_queue_end(&pt);
759   return step2;
760 }
761 /* end of functions for step 2 */
762 
763 GEN
ecpp_sqrt_worker(GEN p,GEN g,GEN N)764 ecpp_sqrt_worker(GEN p, GEN g, GEN N)
765 {
766   GEN r = Fp_sqrt_i(p, g, N);
767   return r ? r: gen_0;
768 }
769 
770 static void
ecpp_parsqrt(GEN N,GEN param,GEN kroP,GEN sqrtlist,GEN g,long nb,long * nbsqrt)771 ecpp_parsqrt(GEN N, GEN param, GEN kroP, GEN sqrtlist, GEN g, long nb, long *nbsqrt)
772 {
773   GEN primelist = ecpp_param_get_primelist(param);
774   GEN ordinv = ecpp_param_get_primeord(param);
775   long i, k, l = lg(ordinv), n = *nbsqrt+1;
776   GEN worker = snm_closure(is_entry("_ecpp_sqrt_worker"), mkvec2(g, N));
777   GEN W = cgetg(nb+1,t_VEC), V;
778   for (i=n, k=1; k<=nb && i<l; i++)
779   {
780     long pi = ordinv[i];
781     long s = kroP[pi];
782     if (s > 1) kroP[pi] = s = krosi(primelist[pi], N); /* update cache */
783     if (kroP[pi] > 0)
784       gel(W,k++) = stoi(primelist[pi]);
785   }
786   *nbsqrt=i-1;
787   setlg(W,k);
788   V = gen_parapply(worker, W);
789   for (i=n, k=1; k<=nb && i<l; i++)
790   {
791     long pi = ordinv[i];
792     if (kroP[pi] > 0)
793     {
794       dbg_mode() err_printf(ANSI_MAGENTA "S" ANSI_RESET);
795       if (!signe(gel(V,k)))
796         pari_err_BUG("D_find_discsqrt"); /* possible if N composite */
797       gel(sqrtlist,pi) = gel(V,k++);
798     }
799   }
800 }
801 
802 /* start of functions for step 1 */
803 
804 /* This finds the square root of D modulo N [given by Dfac]: find the square
805  * root modulo N of each prime p dividing D and multiply them out */
806 static GEN
D_find_discsqrt(GEN N,GEN param,GEN Dfac,GEN kroP,GEN sqrtlist,GEN g,long * nbsqrt)807 D_find_discsqrt(GEN N, GEN param, GEN Dfac, GEN kroP, GEN sqrtlist, GEN g, long *nbsqrt)
808 {
809   GEN s = NULL, sj;
810   long i, l = lg(Dfac);
811   for (i = 1; i < l; i++)
812   {
813     long j = Dfac[i];
814     while(!signe(gel(sqrtlist,j)))
815       ecpp_parsqrt(N, param, kroP, sqrtlist, g, mt_nbthreads(), nbsqrt);
816     sj = gel(sqrtlist,j);
817     s = s? Fp_mul(s, sj, N): sj;
818   }
819   return s;/* != NULL */
820 }
821 
822 /* Given a solution U, V to U^2 + |D|V^2 = 4N, this find all the possible
823      cardinalities of the elliptic curves over the finite field F_N
824      whose endomorphism ring is isomorphic to the maximal order
825      in the imaginary quadratic number field K = Q(sqrt(D)). ???
826 */
827 static GEN
NUV_find_m(GEN Dinfo,GEN N,GEN U,GEN V,long wD)828 NUV_find_m(GEN Dinfo, GEN N, GEN U, GEN V, long wD)
829 {
830   GEN m, Nplus1 = addiu(N, 1), u = U, mvec = cgetg(wD+1, t_VEC);
831   long i;
832   for (i = 0; i < wD; i++)
833   {
834     if (odd(i)) m = subii(Nplus1, u);
835     else
836     {
837       if (wD == 4 && i==2) u = shifti(V, 1);
838       else if (wD == 6 && i==2) u = shifti(submuliu(U, V, 3), -1);
839       else if (wD == 6 && i==4) u = shifti(addmuliu(U, V, 3), -1);
840       m = addii(Nplus1, u);
841     }
842     gel(mvec, i+1) = mkvec2(Dinfo, m);
843   }
844   return mvec;
845 }
846 
847 /* Populates Dmbatch with Dmvec's -- whose components are of the form [D,m],
848    where m is a cardinalities of an elliptic curve with endomorphism ring
849    isomorphic to the maximal order of imaginary quadratic K = Q(sqrt(D)).
850    It returns 0 if:
851      * Any of the p* dividing D is not a quadratic residue mod N
852      * Cornacchia cannot find a solution U^2 + |D|V^2 = 4N.
853    Otherwise, it returns the number of cardinalities added to Dmbatch.
854    Finally, sqrtlist and g help compute the square root modulo N of D.
855 */
856 static long
D_collectcards(GEN N,GEN param,GEN * X0,GEN Dinfo,GEN sqrtlist,GEN g,GEN Dmbatch,GEN kroP,long * nbsqrt)857 D_collectcards(GEN N, GEN param, GEN* X0, GEN Dinfo, GEN sqrtlist, GEN g, GEN Dmbatch, GEN kroP, long *nbsqrt)
858 {
859   long i, l, corn_succ, wD, D = Dinfo_get_D(Dinfo);
860   GEN U, V, sqrtofDmodN, Dfac = Dinfo_get_Dfac(Dinfo);
861   GEN primelist = ecpp_param_get_primelist(param);
862   pari_timer ti;
863 
864   /* A1: Check (p*|N) = 1 for all primes dividing D */
865   l = lg(Dfac);
866   for (i = 1; i < l; i++)
867   {
868     long j = Dfac[i], s = kroP[j];
869     if (s > 1) kroP[j] = s = krosi(primelist[j], N); /* update cache */
870     if (s == 0) return -1; /* N is composite */
871     if (s < 0) return 0;
872   }
873   /* A3: Get square root of D mod N */
874   dbg_mode() timer_start(&ti);
875   sqrtofDmodN = D_find_discsqrt(N, param, Dfac, kroP, sqrtlist, g, nbsqrt);
876   dbg_mode() timer_record(X0, "A3", &ti);
877   /* A5: Use square root with Cornacchia to solve U^2 + |D|V^2 = 4N */
878   dbg_mode() timer_start(&ti);
879   corn_succ = cornacchia2_sqrt( utoi(labs(D)), N, sqrtofDmodN, &U, &V);
880   dbg_mode() timer_record(X0, "A5", &ti);
881   if (!corn_succ) {
882     dbg_mode1() err_printf(ANSI_YELLOW "c" ANSI_RESET);
883     return 0;
884   }
885   /* A6: Collect the w(D) cardinalities of E/(F_N) with CM by D */
886   dbg_mode() err_printf(ANSI_BRIGHT_YELLOW "D" ANSI_RESET);
887   wD = D_get_wD(D);
888   vectrunc_append_batch(Dmbatch, NUV_find_m(Dinfo,N,U,V,wD));
889   return wD;
890 }
891 
892 /* Compute (S(16N, 2) + S(4096N, 4) + 4)\4 + 1,  where S is the nth root
893  * rounded down. This is at most one more than (N^1/4 + 1)^2. */
894 static GEN
ecpp_qlo(GEN N)895 ecpp_qlo(GEN N)
896 {
897   GEN a = sqrtnint(shifti(N, 4), 2);
898   GEN b = sqrtnint(shifti(N, 12), 4);
899   return addiu(shifti(addii(a, b), -2), 2);
900 }
901 
902 static long
lessthan_qlo(void * E,GEN Dmq)903 lessthan_qlo(void* E, GEN Dmq) { return (cmpii(gel(Dmq,3), (GEN)E) < 0); }
904 static long
gained_bits(void * E,GEN Dmq)905 gained_bits(void* E, GEN Dmq) { return (expi(gel(Dmq,3)) <= (long)E); }
906 
907 /*  Input: Dmqvec
908  * Output: Dmqvec such that q satisfies (N^1/4 + 1)^2 < q < N/2 */
909 static GEN
Dmqvec_slice(GEN N,GEN Dmqvec)910 Dmqvec_slice(GEN N, GEN Dmqvec)
911 {
912   long lo, hi;
913 
914   gen_sort_inplace(Dmqvec, NULL, &sort_Dmq_by_q, NULL); /* sort wrt q */
915   lo = zv_binsearch0((void*)ecpp_qlo(N), &lessthan_qlo, Dmqvec); lo++;
916   if (lo >= lg(Dmqvec)) return NULL;
917 
918   hi = zv_binsearch0((void*)(expi(N)-1), &gained_bits, Dmqvec);
919   if (hi == 0) return NULL;
920 
921   return vecslice(Dmqvec, lo, hi);
922 }
923 
924 /* Given a Dmvec of [D,m]'s, simultaneously remove all prime factors of each
925  * m less then BOUND_PRIMORIAL. This implements Franke 2004: Proving the
926  * Primality of Very Large Numbers with fastECPP */
927 static GEN
Dmvec_batchfactor(GEN Dmvec,GEN primorial)928 Dmvec_batchfactor(GEN Dmvec, GEN primorial)
929 {
930   long i, l = lg(Dmvec);
931   GEN leaf, v = cgetg(l, t_VEC);
932   for (i = 1; i < l; i++)
933   { /* cheaply remove powers of 2 */
934     GEN m = gmael(Dmvec, i, 2);
935     long v2 = vali(m);
936     gel(v,i) = v2? shifti(m,-v2): m;
937   }
938   leaf = Z_ZV_mod_tree(primorial, v, ZV_producttree(v));
939   /* Go through each leaf and remove small factors. */
940   for (i = 1; i < l; i++)
941   {
942     GEN q = gel(v,i), Dm = gel(Dmvec,i), L = gel(leaf,i);
943     while (!equali1(L)) { L = gcdii(q, L); q = diviiexact(q, L); }
944     gel(v,i) = mkvec3(gel(Dm,1), gel(Dm,2), q);
945   }
946   return v;
947 }
948 
949 /* return good parameters [maxsqrt, maxpcdg, tdivexp] for ecpp(N) */
950 static GEN
tunevec(long expiN,GEN param)951 tunevec(long expiN, GEN param)
952 {
953   GEN T = ecpp_param_get_tune(param);
954   long i, n = lg(T)-1;
955   for (i = 1; i < n; i++) { GEN x = gel(T,i); if (expiN <= x[4]) return x; }
956   return gel(T,n);
957 }
958 static long
tunevec_tdivbd(long expiN,GEN param)959 tunevec_tdivbd(long expiN, GEN param) { return uel(tunevec(expiN, param), 3); }
960 static long
tunevec_batchsize(long expiN,GEN param)961 tunevec_batchsize(long expiN, GEN param)
962 {
963   long t, b = 28 - tunevec_tdivbd(expiN, param);
964   if (b < 0) return expiN;
965   t = expiN >> b; return t < 1? 1: t;
966 }
967 
968 static GEN
Dmbatch_factor_Dmqvec(GEN N,long expiN,GEN * X0,GEN Dmbatch,GEN param)969 Dmbatch_factor_Dmqvec(GEN N, long expiN, GEN* X0, GEN Dmbatch, GEN param)
970 {
971   pari_sp av = avma;
972   pari_timer ti;
973   GEN Dmqvec, primorial_vec = ecpp_param_get_primorial_vec(param);
974   GEN primorial = gel(primorial_vec, tunevec_tdivbd(expiN, param)-19);
975 
976   /* B1: Factor by batch */
977   dbg_mode() timer_start(&ti);
978   Dmqvec = Dmvec_batchfactor(Dmbatch, primorial);
979   dbg_mode() timer_record(X0, "B1", &ti);
980 
981   /* B2: For each batch, remove cardinalities lower than (N^(1/4)+1)^2
982    *     and cardinalities in which we didn't win enough bits */
983   Dmqvec = Dmqvec_slice(N, Dmqvec);
984   if (!Dmqvec) return gc_NULL(av); /* nothing is left */
985   return gerepilecopy(av, Dmqvec);
986 }
987 
988 GEN
ecpp_ispsp_worker(GEN N)989 ecpp_ispsp_worker(GEN N)
990 { return mkvecsmall(BPSW_psp_nosmalldiv(N)); }
991 
992 static GEN
mkNDmqg(GEN z,GEN N,GEN Dmq,GEN g,GEN sqrtlist)993 mkNDmqg(GEN z, GEN N, GEN Dmq, GEN g, GEN sqrtlist)
994 {
995   GEN Dinfo = gel(Dmq,1), sq =  Dfac_to_roots(Dinfo_get_Dfac(Dinfo),sqrtlist);
996   GEN NDmqg = mkcol6(N, Dinfo, gel(Dmq,2), gel(Dmq,3), g, sq);
997   return mkvec2(NDmqg, z);
998 }
999 /* expi(N) > 64. Return [ NDmqg, N_downrun(q) ]; NDmqg is a vector of the form
1000  * [N,D,m,q,g,sqrt]. For successive D, find a square root of D mod N (g is a
1001  * quadratic nonresidue), solve U^2 + |D|V^2 = 4N, then find candidates for m.
1002  * When enough m's, batch-factor them to produce the q's. If one of the q's is
1003  * pseudoprime, recursive call with N = q. May return gen_0 at toplevel
1004  * => N has a small prime divisor */
1005 static GEN
N_downrun(GEN N,GEN param,GEN worker,GEN * X0,long * depth,long persevere)1006 N_downrun(GEN N, GEN param, GEN worker, GEN *X0, long *depth, long persevere)
1007 {
1008   pari_timer T, ti;
1009   long lgdisclist, lprimelist, nbsqrt = 0, t, i, j, expiN = expi(N);
1010   long persevere_next = 0, FAIL = 0;
1011   ulong maxpcdg;
1012   GEN primelist, disclist, sqrtlist, g, Dmbatch, kroP;
1013 
1014   dbg_mode() timer_start(&T);
1015   (*depth)++; /* we're going down the tree. */
1016   maxpcdg = ecpp_param_get_maxpcdg(param);
1017   primelist = ecpp_param_get_primelist(param);
1018   disclist = ecpp_param_get_disclist(param);
1019   lgdisclist = lg(disclist);
1020   t = tunevec_batchsize(expiN, param);
1021 
1022   /* Precomputation for taking square roots, g needed for Fp_sqrt_i */
1023   g = Fp_2gener(N); if (!g) return gen_0; /* Composite if this happens. */
1024 
1025   /* Initialize sqrtlist for this N. */
1026   lprimelist = lg(primelist);
1027   sqrtlist = zerovec(lprimelist-1);
1028 
1029   /* A2: Check (p*|N) = 1 for all p */
1030   dbg_mode() timer_start(&ti);
1031   /* kroP[i] will contain (primelist[i] | N) */
1032   kroP = const_vecsmall(lprimelist-1, 2/*sentinel*/);
1033   switch(mod8(N))
1034   { /* primelist[1,2,3] = [8,-4,-8]; N is odd */
1035     case 1: kroP[1] = kroP[2] = kroP[3] = 1; break;
1036     case 3: kroP[1] = -1; kroP[2] =-1; kroP[3] = 1; break;
1037     case 5: kroP[1] = -1; kroP[2] = 1; kroP[3] =-1; break;
1038     case 7: kroP[1] =  1; kroP[2] =-1; kroP[3] =-1; break;
1039   }
1040   for(i=4; i<lprimelist; i++)
1041     kroP[i]= krosi(primelist[i],N);
1042   dbg_mode() timer_record(X0, "A2", &ti);
1043 
1044   /* Print the start of this iteration. */
1045   dbg_mode() {
1046     char o = persevere? '<': '[';
1047     char c = persevere? '>': ']';
1048     err_printf(ANSI_BRIGHT_CYAN "\n%c %3d | %4ld bits%c "
1049                ANSI_RESET, o, *depth, expiN, c);
1050   }
1051   /* Initialize Dmbatch, populated with candidate cardinalities in Phase I
1052    * (until #Dmbatch >= t); its elements will be factored on Phase II */
1053   Dmbatch = vectrunc_init(t+7);
1054 
1055   /* Number of cardinalities so far; should always be equal to lg(Dmbatch)-1. */
1056   /* i determines which discriminant we are considering. */
1057   i = 1;
1058   while (!FAIL)
1059   {
1060     pari_timer F;
1061     long lgDmqlist, last_i = i, numcard = 0; /* #Dmbatch */
1062     GEN Dmqlist;
1063 
1064     /* Dmbatch begins "empty", but keep the allocated memory. */
1065     setlg(Dmbatch, 1);
1066 
1067     /* PHASE I: Go through the D's and search for candidate m's.
1068      * Fill up Dmbatch until there are at least t elements. */
1069     while (i < lgdisclist )
1070     {
1071       GEN Dinfo = gel(disclist, i);
1072       long n;
1073       if (!persevere && Dinfo_get_pd(Dinfo) > maxpcdg) { FAIL = 1; break; }
1074       n = D_collectcards(N,param, X0, Dinfo, sqrtlist, g, Dmbatch, kroP, &nbsqrt);
1075       if (n < 0) return gen_0;
1076       last_i = i++;
1077       numcard += n; if (numcard >= t) break;
1078     }
1079 
1080     /* We have exhausted disclist and there are no card. to be factored */
1081     if (numcard <= 0 && (FAIL || i >= lgdisclist)) break;
1082 
1083     /* PHASE II: Find the corresponding q's for the m's found. Use Dmbatch */
1084     /* Find coresponding q of the candidate m's. */
1085     dbg_mode() timer_start(&F);
1086     Dmqlist = Dmbatch_factor_Dmqvec(N, expiN, X0, Dmbatch, param);
1087     if (Dmqlist == NULL) continue; /* none left => next discriminant. */
1088 
1089     /* If we are cheating by adding class numbers, sort by class number */
1090     if (Dinfo_get_pd(gel(disclist, last_i)) > maxpcdg)
1091       gen_sort_inplace(Dmqlist, NULL, &sort_Dmq_by_cnum, NULL);
1092 
1093     /* Check pseudoprimality before going down. */
1094     lgDmqlist = lg(Dmqlist);
1095     for (j = 1; j < lgDmqlist; j++)
1096     {
1097       GEN z, Dmq, q;
1098       struct pari_mt pt;
1099       pari_timer ti2;
1100       long running, stop = 0, pending = 0;
1101       mt_queue_start_lim(&pt, worker, lgDmqlist-j);
1102       dbg_mode() timer_start(&ti2);
1103       while ((running = (!stop && j < lgDmqlist)) || pending)
1104       {
1105         long jj;
1106         GEN done;
1107         mt_queue_submit(&pt, j, running ? mkvec(gmael(Dmqlist,j,3)) : NULL);
1108         done = mt_queue_get(&pt, &jj, &pending);
1109         if (done)
1110         {
1111           if (done[1] == 0)
1112           { dbg_mode() err_printf(ANSI_WHITE "." ANSI_RESET); }
1113           else if (!stop || jj < stop)
1114             stop = jj;
1115         }
1116         j++;
1117       }
1118       mt_queue_end(&pt);
1119       dbg_mode() timer_record(X0, "B3", &ti2);
1120       if (!stop && j >= lgDmqlist) break;
1121       j = stop; Dmq = gel(Dmqlist,j); q = gel(Dmq,3);
1122 
1123       dbg_mode() {
1124         err_printf(ANSI_BRIGHT_RED "\n       %5ld bits " ANSI_RESET,
1125                    expi(q)-expiN);
1126         err_printf("  D = %ld", Dmq_get_D(Dmq));
1127         err_printf(ANSI_BRIGHT_BLUE "  h = %ld" ANSI_RESET, Dmq_get_h(Dmq));
1128       }
1129       /* q is pseudoprime */
1130       if (expi(q) < 64) z = gen_1; /* q is prime; sentinel */
1131       else
1132       {
1133         z = N_downrun(q, param, worker, X0, depth, persevere_next);
1134         if (!z) {
1135           dbg_mode() { char o = persevere? '<': '[', c = persevere? '>': ']';
1136                        err_printf(ANSI_CYAN "\n%c %3d | %4ld bits%c "
1137                                   ANSI_RESET, o, *depth, expiN, c); }
1138           continue; /* downrun failed */
1139         }
1140       }
1141       return mkNDmqg(z, N, Dmq, g, sqrtlist); /* SUCCESS */
1142     }
1143     if (i >= lgdisclist) break; /* discriminants exhausted: FAIL */
1144     if (Dinfo_get_pd(gel(disclist, last_i)) > maxpcdg)
1145     {
1146       dbg_mode() err_printf(ANSI_BRIGHT_RED "  !" ANSI_RESET);
1147       persevere_next = 1;
1148     }
1149   }
1150   /* FAILED: Out of discriminants. */
1151   umael(*X0, 3, 1)++; /* FAILS++ */
1152   dbg_mode() err_printf(ANSI_BRIGHT_RED "  X" ANSI_RESET);
1153   (*depth)--; return NULL;
1154 }
1155 
1156 /* x: the output of the (recursive) downrun function; return a vector
1157  * whose components are [N, D, m, q, g] */
1158 static GEN
ecpp_flattencert(GEN x,long depth)1159 ecpp_flattencert(GEN x, long depth)
1160 {
1161   long i, l = depth+1;
1162   GEN ret = cgetg(l, t_VEC);
1163   if (typ(x) != t_VEC) return gen_0;
1164   for (i = 1; i < l; i++) { gel(ret, i) = gel(x,1); x = gel(x,2); }
1165   return ret;
1166 }
1167 
1168 /* Call the first downrun then unravel the skeleton of the certificate.
1169  * Assume expi(N) < 64. This returns one of the following:
1170  * - a vector of the form [N, D, m, q, y]
1171  * - gen_0 (if N is composite)
1172  * - NULL (if FAIL) */
1173 static GEN
ecpp_step1(GEN N,GEN param,GEN * X0)1174 ecpp_step1(GEN N, GEN param, GEN* X0)
1175 {
1176   pari_sp av = avma;
1177   long depth = 0;
1178   GEN worker = strtofunction("_ecpp_ispsp_worker");
1179   GEN downrun = N_downrun(N, param, worker, X0, &depth, 1);
1180   if (downrun == NULL) return gc_NULL(av);
1181   return gerepilecopy(av, ecpp_flattencert(downrun, depth));
1182 }
1183 
1184 /* The input is an integer N.
1185    It uses the precomputation step0 done in ecpp_step0. */
1186 static GEN
ecpp0(GEN N,GEN param)1187 ecpp0(GEN N, GEN param)
1188 {
1189 
1190   GEN step1, answer, Tv, Cv, X0;
1191   pari_sp av = avma;
1192   long i, j;
1193 
1194   /* Check if N is pseudoprime to begin with. */
1195   if (!BPSW_psp(N)) return gen_0;
1196 
1197   /* Check if we should even prove it. */
1198   if (expi(N) < 64) return N;
1199 
1200   /* Timers and Counters */
1201   Tv = mkvec4(zero_zv(5), zero_zv(3), zero_zv(3), zero_zv(1));
1202   Cv = mkvec4(zero_zv(5), zero_zv(3), zero_zv(3), zero_zv(1));
1203   X0 = mkvec3(Tv, Cv, zero_zv(1));
1204 
1205   step1 = ecpp_step1(N, param, &X0);
1206   if (step1 == NULL) return gc_NULL(av);
1207   if (typ(step1) != t_VEC) { set_avma(av); return gen_0; }
1208 
1209   answer = ecpp_step2(step1, &X0, ecpp_param_get_primelist(param));
1210 
1211   dbg_mode() {
1212   for (i = 1; i < lg(Tv); i++)
1213   {
1214     GEN v = gel(Tv, i);
1215     long l = lg(v);
1216     for (j = 1; j < l; j++)
1217     {
1218       long t = umael3(X0,1, i,j), n = umael3(X0,2, i,j);
1219       if (!t) continue;
1220       err_printf("\n  %c%ld: %16ld %16ld %16.3f", 'A'+i-1,j, t,n, (double)t/n);
1221     }
1222   }
1223     err_printf("\n" ANSI_BRIGHT_RED "\nFAILS: %16ld" ANSI_RESET "\n", umael(X0, 3, 1));
1224   }
1225   return gerepilecopy(av, answer);
1226 }
1227 
1228 static const long ecpp_tune[][4]=
1229 {
1230   {  100,  2,  20,   500 },
1231   {  350, 14,  21,  1000 },
1232   {  450, 18,  21,  1500 },
1233   {  750, 22,  22,  2000 },
1234   {  900, 26,  23,  2500 },
1235   { 1000, 32,  23,  3000 },
1236   { 1200, 38,  24,  3500 },
1237   { 1400, 44,  24,  4000 },
1238   { 1600, 50,  24,  4500 },
1239   { 1800, 56,  25,  5000 },
1240   { 2000, 62,  25,  5500 },
1241   { 2200, 68,  26,  6000 },
1242   { 2400, 74,  26,  6500 },
1243   { 2600, 80,  26,  7000 },
1244   { 2800, 86,  26,  7500 },
1245   { 3000, 92,  27,  8000 },
1246   { 3200, 98,  27,  8500 },
1247   { 3400, 104, 28,  9000 },
1248   { 3600, 110, 28,  9500 },
1249   { 3800, 116, 29, 10000 },
1250   { 4000, 122, 29,     0 }
1251 };
1252 
1253 /* assume N BPSW-pseudoprime */
1254 GEN
ecpp(GEN N)1255 ecpp(GEN N)
1256 {
1257   long expiN, i, tunelen;
1258   GEN tune;
1259 
1260   /* Check if we should even prove it. */
1261   expiN = expi(N);
1262   if (expiN < 64) return N;
1263 
1264   tunelen = (expiN+499)/500;
1265   tune = cgetg(tunelen+1, t_VEC);
1266   for (i = 1; i <= tunelen && ecpp_tune[i-1][3]; i++)
1267     gel(tune,i) = mkvecsmall4(ecpp_tune[i-1][0], ecpp_tune[i-1][1],
1268                               ecpp_tune[i-1][2], ecpp_tune[i-1][3]);
1269   for (; i <= tunelen; i++) gel(tune,i) = mkvecsmall4(200*(i-1),6*i-4,30,500*i);
1270   for(;;)
1271   {
1272     GEN C, param, x = gel(tune, tunelen);
1273     pari_timer T;
1274     dbg_mode() timer_start(&T);
1275     param = ecpp_param_set(tune, x);
1276     dbg_mode() {
1277       err_printf(ANSI_BRIGHT_WHITE "\n%Ps" ANSI_RESET, x);
1278       err_printf(ANSI_WHITE "  %8ld" ANSI_RESET, timer_delay(&T));
1279     }
1280     if ((C = ecpp0(N, param))) return C;
1281     x[1] *= 2;
1282     x[2] *= 2;
1283     x[3] = minss(x[3]+1, 30);
1284   }
1285 }
1286 long
isprimeECPP(GEN N)1287 isprimeECPP(GEN N)
1288 {
1289   pari_sp av = avma;
1290   if (!BPSW_psp(N)) return 0;
1291   return gc_long(av, !isintzero(ecpp(N)));
1292 }
1293 
1294 /* PARI ECPP Certificate -> Human-readable format */
1295 static GEN
cert_out(GEN x)1296 cert_out(GEN x)
1297 {
1298   long i, l = lg(x);
1299   pari_str str;
1300   str_init(&str, 1);
1301   if (typ(x) == t_INT)
1302   {
1303     str_printf(&str, "%Ps is prime.\nIndeed, ispseudoprime(%Ps) = 1 and %Ps < 2^64.\n", x, x, x);
1304     return strtoGENstr(str.string);
1305   }
1306   for (i = 1; i < l; i++)
1307   {
1308     GEN xi = gel(x, i);
1309     str_printf(&str, "[%ld]\n", i);
1310     str_printf(&str, " N = %Ps\n", cert_get_N(xi));
1311     str_printf(&str, " t = %Ps\n", cert_get_t(xi));
1312     str_printf(&str, " s = %Ps\n", cert_get_s(xi));
1313     str_printf(&str, "a = %Ps\n", cert_get_a4(xi));
1314     str_printf(&str, "D = %Ps\n", cert_get_D(xi));
1315     str_printf(&str, "m = %Ps\n", cert_get_m(xi));
1316     str_printf(&str, "q = %Ps\n", cert_get_q(xi));
1317     str_printf(&str, "E = %Ps\n", cert_get_E(xi));
1318     str_printf(&str, "P = %Ps\n", cert_get_P(xi));
1319   }
1320   return strtoGENstr(str.string);
1321 }
1322 
1323 /* PARI ECPP Certificate -> Magma Certificate
1324  * [* [*
1325  *     N, |D|, -1, m,
1326  *     [* a, b *],
1327  *     [* x, y, 1 *],
1328  *     [* [* q, 1 *] *]
1329  *   *], ... *] */
1330 static GEN
magma_out(GEN x)1331 magma_out(GEN x)
1332 {
1333   long i, n = lg(x)-1;
1334   pari_str ret;
1335   str_init(&ret, 1);
1336   if (typ(x) == t_INT)
1337   {
1338     str_printf(&ret, "Operation not supported.");
1339     return strtoGENstr(ret.string);
1340   }
1341   str_printf(&ret, "[* ");
1342   for (i = 1; i<=n; i++)
1343   {
1344     GEN xi = gel(x,i), E = cert_get_E(xi), P = cert_get_P(xi);
1345     str_printf(&ret, "[* %Ps, %Ps, -1, %Ps, ",
1346               cert_get_N(xi), negi(cert_get_D(xi)), cert_get_m(xi));
1347     str_printf(&ret, "[* %Ps, %Ps *], ", gel(E,1), gel(E,2));
1348     str_printf(&ret, "[* %Ps, %Ps, 1 *], ", gel(P,1), gel(P,2));
1349     str_printf(&ret, "[* [* %Ps, 1 *] *] *]", cert_get_q(xi));
1350     if (i != n) str_printf(&ret, ", ");
1351   }
1352   str_printf(&ret, " *]");
1353   return strtoGENstr(ret.string);
1354 }
1355 
1356 /* Prints: label=(sign)hexvalue\n
1357  *   where sign is + or -
1358  *   hexvalue is value in hex, of the form: 0xABC123 */
1359 static void
primo_printval(pari_str * ret,const char * label,GEN value)1360 primo_printval(pari_str *ret, const char* label, GEN value)
1361 {
1362   str_printf(ret, label);
1363   if (signe(value) >= 0) str_printf(ret, "=0x%P0X\n", value);
1364   else str_printf(ret, "=-0x%P0X\n", negi(value));
1365 }
1366 
1367 /* Input: PARI ECPP Certificate / Output: PRIMO Certificate
1368  *
1369  * Let Y^2 = X^3 + aX + b be the elliptic curve over N with the point (x,y)
1370  * as described in the PARI certificate.
1371  *
1372  * If J != 0, 1728, PRIMO asks for [J,T], where T = a/A * B/b * x,
1373  * A = 3J(1728-J) and B = 2J(1728-J)^2.
1374  *
1375  * If J=0 or J=1728, PRIMO asks for [A,B,T]; we let A=a and B=b => T = x */
1376 static GEN
primo_out(GEN x)1377 primo_out(GEN x)
1378 {
1379   long i, l = (typ(x) == t_INT) ? 1 : lg(x);
1380   pari_str ret;
1381   str_init(&ret, 1);
1382   str_printf(&ret, "[PRIMO - Primality Certificate]\nFormat=4\n");
1383   str_printf(&ret, "TestCount=%ld\n\n[Comments]\n", l-1);
1384   str_printf(&ret, "Generated by %s\n",paricfg_version);
1385   str_printf(&ret, "https://pari.math.u-bordeaux.fr/\n\n[Candidate]\n");
1386   if (typ(x) == t_INT)
1387   {
1388     str_printf(&ret, "N=0x%P0X\n", x);
1389     return strtoGENstr(ret.string);
1390   }
1391   str_printf(&ret, "N=0x%P0X\n\n", cert_get_N(gel(x,1)));
1392   for (i = 1; i < l; i++)
1393   {
1394     GEN a4, a6, N, Nover2, xi = gel(x,i);
1395     long Ais0, Bis0;
1396     str_printf(&ret, "[%ld]\n", i);
1397     N = cert_get_N(xi);
1398     Nover2 = shifti(N, -1);
1399     primo_printval(&ret, "S", cert_get_s(xi));
1400     primo_printval(&ret, "W", cert_get_t(xi));
1401     a4 = cert_get_a4(xi); Ais0 = isintzero(a4);
1402     a6 = cert_get_a6(xi); Bis0 = isintzero(a6);
1403     if (!Ais0 && !Bis0) { /* J != 0, 1728 */
1404       primo_printval(&ret, "J", Fp_center_i(cert_get_J(xi), N, Nover2));
1405       primo_printval(&ret, "T", cert_get_T(xi));
1406     } else {
1407       if (!Ais0) a4 = Fp_center_i(a4, N, Nover2);
1408       if (!Bis0) a6 = Fp_center_i(a6, N, Nover2);
1409       primo_printval(&ret, "A", a4);
1410       primo_printval(&ret, "B", a6);
1411       primo_printval(&ret, "T", cert_get_x(xi));
1412     }
1413     str_printf(&ret, "\n");
1414   }
1415   return strtoGENstr(ret.string);
1416 }
1417 
1418 /* return 1 if q > (N^1/4 + 1)^2, 0 otherwise */
1419 static long
Nq_isvalid(GEN N,GEN q)1420 Nq_isvalid(GEN N, GEN q)
1421 {
1422   GEN c = subii(sqri(subiu(q,1)), N);
1423   if (signe(c) <= 0) return 0;
1424   /* (q-1)^2 > N; check that (N - (q-1)^2)^2 > 16Nq */
1425   return (cmpii(sqri(c), shifti(mulii(N,q), 4)) > 0);
1426 }
1427 
1428 GEN
primecertisvalid_ecpp_worker(GEN certi)1429 primecertisvalid_ecpp_worker(GEN certi)
1430 {
1431   GEN N, W, mP, sP, r, m, s, a, P, q;
1432   if (lg(certi)-1 != 5) return gen_0;
1433 
1434   N = cert_get_N(certi);
1435   if (typ(N) != t_INT || signe(N) <= 0) return gen_0;
1436   switch(umodiu(N,6))
1437   {
1438     case 1: case 5: break; /* Check if N is not divisible by 2 or 3 */
1439     default: return gen_0;
1440   }
1441 
1442   W = cert_get_t(certi);
1443   if (typ(W) != t_INT) return gen_0;
1444   /* Check if W^2 < 4N (Hasse interval) */
1445   if (!(cmpii(sqri(W), shifti(N,2)) < 0)) return gen_0;
1446 
1447   s = cert_get_s(certi);
1448   if (typ(s) != t_INT || signe(s) < 0) return gen_0;
1449 
1450   m = cert_get_m(certi);
1451   q = dvmdii(m, s, &r);
1452   /* Check m%s == 0 */
1453   if (!isintzero(r)) return gen_0;
1454   /* Check q > (N^(1/4) + 1)^2 */
1455   if (!Nq_isvalid(N, q)) return gen_0;
1456 
1457   a = cert_get_a4(certi);
1458   if (typ(a) != t_INT) return gen_0;
1459 
1460   P = cert_get_P(certi);
1461   if (typ(P) != t_VEC || lg(P) != 3) return gen_0;
1462   P = FpE_to_FpJ(P);
1463 
1464   /* Check mP == 0 */
1465   mP = FpJ_mul(P, m, a, N);
1466   if (!FpJ_is_inf(mP)) return gen_0;
1467 
1468   /* Check sP != 0 and third component is coprime to N */
1469   sP = FpJ_mul(P, s, a, N);
1470   if (!isint1(gcdii(gel(sP, 3), N))) return gen_0;
1471   return q;
1472 }
1473 
1474 /* return 1 if 'cert' is a valid PARI ECPP certificate */
1475 static long
ecppisvalid_i(GEN cert)1476 ecppisvalid_i(GEN cert)
1477 {
1478   const long trustbits = 64;/* a pseudoprime < 2^trustbits is prime */
1479   long i, lgcert = lg(cert);
1480   GEN ql, q = gen_0, worker, check;
1481 
1482   if (typ(cert) == t_INT)
1483   {
1484     if (expi(cert) >= trustbits) return 0; /* >= 2^trustbits */
1485     return BPSW_psp(cert);
1486   }
1487   if (typ(cert) != t_VEC || lgcert <= 1) return 0;
1488   ql = gel(cert, lgcert-1); if (lg(ql)-1 != 5) return 0;
1489   ql = cert_get_q(ql);
1490   if (expi(ql) >= trustbits || !BPSW_psp(ql)) return 0;
1491   worker = strtofunction("_primecertisvalid_ecpp_worker");
1492   check = gen_parapply(worker, cert);
1493   for (i = 1; i < lgcert; i++)
1494   {
1495     GEN certi = gel(cert, i);
1496     GEN qq = gel(check,i), N = cert_get_N(certi);
1497     if (isintzero(qq)) return 0;
1498     if (i > 1 && !equalii(N, q)) return 0;
1499     q = qq;
1500   }
1501   return 1;
1502 }
1503 long
ecppisvalid(GEN cert)1504 ecppisvalid(GEN cert)
1505 { pari_sp av = avma; return gc_long(av, ecppisvalid_i(cert)); }
1506 
1507 GEN
ecppexport(GEN cert,long flag)1508 ecppexport(GEN cert, long flag)
1509 {
1510   switch(flag)
1511   {
1512     case 0: return cert_out(cert);
1513     case 1: return primo_out(cert);
1514     case 2: return magma_out(cert);
1515   }
1516   pari_err_FLAG("primecertexport");
1517   return NULL;/*LCOV_EXCL_LINE*/
1518 }
1519