Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/interfaces/ecm.py
4045 views
1
r"""
2
The Elliptic Curve Factorization Method
3
4
\sage includes GMP-ECM, which is a highly optimized implementation
5
of Lenstra's elliptic curve factorization method. See
6
\url{http://ecm.gforge.inria.fr/}
7
for more about GMP-ECM.
8
"""
9
10
import os, pexpect
11
from math import ceil, floor
12
13
from sage.rings.integer import Integer
14
from sage.misc.misc import verbose, tmp_filename
15
from sage.misc.decorators import rename_keyword
16
17
import cleaner
18
19
import sage.misc.package
20
21
def nothing():
22
pass
23
24
class ECM:
25
def __init__(self, B1=10, B2=None, **kwds):
26
r"""
27
Create an interface to the GMP-ECM elliptic curve method
28
factorization program.
29
30
See \url{http://ecm.gforge.inria.fr/}.
31
32
AUTHORS:
33
These people wrote GMP-ECM:
34
Pierrick Gaudry, Jim Fougeron,
35
Laurent Fousse, Alexander Kruppa,
36
Dave Newman, Paul Zimmermann
37
38
William Stein and Robert Bradshaw -- wrote the Sage interface to GMP-ECM
39
40
INPUT:
41
B1 -- stage 1 bound
42
B2 -- stage 2 bound (or interval B2min-B2max)
43
44
x0 -- x use x as initial point
45
sigma -- s use s as curve generator [ecm]
46
A -- a use a as curve parameter [ecm]
47
k -- n perform >= n steps in stage 2
48
power -- n use x^n for Brent-Suyama's extension
49
dickson -- n use n-th Dickson's polynomial for Brent-Suyama's extension
50
c -- n perform n runs for each input
51
pm1 -- perform P-1 instead of ECM
52
pp1 -- perform P+1 instead of ECM
53
q -- quiet mode
54
v -- verbose mode
55
timestamp -- print a time stamp with each number
56
mpzmod -- use GMP's mpz_mod for mod reduction
57
modmuln -- use Montgomery's MODMULN for mod reduction
58
redc -- use Montgomery's REDC for mod reduction
59
nobase2 -- disable special base-2 code
60
base2 -- n force base 2 mode with 2^n+1 (n>0) or 2^n-1 (n<0)
61
save -- file save residues at end of stage 1 to file
62
savea -- file like -save, appends to existing files
63
resume -- file resume residues from file, reads from
64
stdin if file is "-"
65
primetest -- perform a primality test on input
66
treefile -- f store product tree of F in files f.0 f.1 ...
67
i -- n increment B1 by this constant on each run
68
I -- f auto-calculated increment for B1 multiplied by 'f' scale factor
69
inp -- file Use file as input (instead of redirecting stdin)
70
b -- Use breadth-first mode of file processing
71
d -- Use depth-first mode of file processing (default)
72
one -- Stop processing a candidate if a factor is found (looping mode)
73
n -- run ecm in 'nice' mode (below normal priority)
74
nn -- run ecm in 'very nice' mode (idle priority)
75
t -- n Trial divide candidates before P-1, P+1 or ECM up to n
76
ve -- n Verbosely show short (< n character) expressions on each loop
77
cofdec -- Force cofactor output in decimal (even if expressions are used)
78
B2scale -- f Multiplies the default B2 value by f
79
go -- val Preload with group order val, which can be a simple expression,
80
or can use N as a placeholder for the number being factored.
81
prp -- cmd use shell command cmd to do large primality tests
82
prplen -- n only candidates longer than this number of digits are 'large'
83
prpval -- n value>=0 which indicates the prp command foundnumber to be PRP.
84
prptmp -- file outputs n value to temp file prior to running (NB. gets deleted)
85
prplog -- file otherwise get PRP results from this file (NB. gets deleted)
86
prpyes -- str literal string found in prplog file when number is PRP
87
prpno -- str literal string found in prplog file when number is composite
88
"""
89
self.__cmd = self.__startup_cmd(B1, B2, kwds)
90
91
def __startup_cmd(self, B1, B2, kwds):
92
options = ' '.join(['-%s %s'%(x,v) for x, v in kwds.iteritems()])
93
s = 'ecm %s %s '%(options, B1)
94
if not B2 is None:
95
s += str(B2)
96
return s
97
98
def __call__(self, n, watch=False):
99
n = Integer(n)
100
self._validate(n)
101
cmd = 'echo "%s" | %s'%(n, self.__cmd)
102
if watch:
103
t = tmp_filename()
104
os.system('%s | tee %s'%(cmd, t))
105
ou = open(t).read()
106
os.unlink(t)
107
else:
108
from subprocess import Popen, PIPE
109
x = Popen(cmd, shell=True,
110
stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
111
x.stdin.close()
112
ou = x.stderr.read() + '\n' + x.stdout.read()
113
if 'command not found' in ou:
114
err = ou + '\n' + 'You must install GMP-ECM.\n'
115
err += sage.misc.package.package_mesg('ecm-6.1.3')
116
raise RuntimeError, err
117
return ou
118
119
def interact(self):
120
"""
121
Interactively interact with the ECM program.
122
"""
123
print "Enter numbers to run ECM on them."
124
print "Press control-C to exit."
125
os.system(self.__cmd)
126
127
128
_recommended_B1_list = {15: 2000,
129
20: 11000,
130
25: 50000,
131
30: 250000,
132
35: 1000000,
133
40: 3000000,
134
45: 11000000,
135
50: 44000000,
136
55: 110000000,
137
60: 260000000,
138
65: 850000000,
139
70: 2900000000 }
140
"""Recommended settings from http://www.mersennewiki.org/index.php/Elliptic_Curve_Method."""
141
142
def __B1_table_value(self, factor_digits, min=15, max=70):
143
"""Coerces to a key in _recommended_B1_list."""
144
if factor_digits < min: factor_digits = min
145
if factor_digits > max: raise ValueError('Too many digits to be factored via the elliptic curve method.')
146
return 5*ceil(factor_digits/5)
147
148
def recommended_B1(self, factor_digits):
149
r"""
150
Recommended settings from \url{http://www.mersennewiki.org/index.php/Elliptic_Curve_Method}.
151
"""
152
return self._recommended_B1_list[self.__B1_table_value(factor_digits)]
153
154
155
@rename_keyword(deprecated='Sage version 4.6', method="algorithm")
156
def one_curve(self, n, factor_digits=None, B1=2000, algorithm="ECM", **kwds):
157
"""
158
Run one single ECM (or P-1/P+1) curve on input n.
159
160
INPUT:
161
n -- a positive integer
162
factor_digits -- decimal digits estimate of the wanted factor
163
B1 -- stage 1 bound (default 2000)
164
algorithm -- either "ECM" (default), "P-1" or "P+1"
165
OUTPUT:
166
a list [p,q] where p and q are integers and n = p * q.
167
If no factor was found, then p = 1 and q = n.
168
WARNING: neither p nor q is guaranteed to be prime.
169
EXAMPLES:
170
sage: f = ECM()
171
sage: n = 508021860739623467191080372196682785441177798407961
172
sage: f.one_curve(n, B1=10000, sigma=11)
173
[1, 508021860739623467191080372196682785441177798407961]
174
sage: f.one_curve(n, B1=10000, sigma=1022170541)
175
[79792266297612017, 6366805760909027985741435139224233]
176
sage: n = 432132887883903108009802143314445113500016816977037257
177
sage: f.one_curve(n, B1=500000, algorithm="P-1")
178
[67872792749091946529, 6366805760909027985741435139224233]
179
sage: n = 2088352670731726262548647919416588631875815083
180
sage: f.one_curve(n, B1=2000, algorithm="P+1", x0=5)
181
[328006342451, 6366805760909027985741435139224233]
182
"""
183
n = Integer(n)
184
self._validate(n)
185
if not factor_digits is None:
186
B1 = self.recommended_B1(factor_digits)
187
if algorithm == "P-1":
188
kwds['pm1'] = ''
189
elif algorithm == "P+1":
190
kwds['pp1'] = ''
191
else:
192
if not algorithm == "ECM":
193
err = "unexpected algorithm: " + algorithm
194
raise ValueError, err
195
self.__cmd = self._ECM__startup_cmd(B1, None, kwds)
196
child = pexpect.spawn(self.__cmd)
197
cleaner.cleaner(child.pid, self.__cmd)
198
child.timeout = None
199
child.__del__ = nothing # work around stupid exception ignored error
200
child.expect('[ECM]')
201
child.sendline(str(n))
202
child.sendline("bad") # child.sendeof()
203
while True:
204
try:
205
child.expect('(Using B1=(\d+), B2=(\d+), polynomial ([^,]+), sigma=(\d+)\D)|(Factor found in step \d:\s+(\d+)\D)|(Error - invalid number)')
206
info = child.match.groups()
207
# B1 is info[1], B2 is info[2], poly is info[3], sigma is info[4],
208
# step is info[5], factor is info[6], cofactor is info[7]
209
if not info[0] is None:
210
# got Using B1=... line
211
self.last_params = { 'B1' : child.match.groups()[1],
212
'B2' : child.match.groups()[2],
213
'poly' : child.match.groups()[3],
214
'sigma' : child.match.groups()[4] }
215
elif info[7] != None:
216
# got Error - invalid number, which means the curve did
217
# end without finding any factor, and the next input 'bad'
218
# was given to GMP-ECM
219
child.kill(0)
220
return [1, n]
221
else:
222
# got Factor found...
223
p = Integer(info[6])
224
child.kill(0)
225
return [p, n/p]
226
227
except pexpect.EOF:
228
child.kill(0)
229
return [1, n]
230
child.kill(0)
231
232
233
def find_factor(self, n, factor_digits=None, B1=2000, **kwds):
234
"""
235
Splits off a single factor of n.
236
See ECM.factor()
237
238
OUTPUT:
239
list of integers whose product is n
240
241
EXAMPLES:
242
sage: f = ECM()
243
sage: n = 508021860739623467191080372196682785441177798407961
244
sage: f.find_factor(n)
245
[79792266297612017, 6366805760909027985741435139224233]
246
247
Note that the input number can't have more than 4095 digits:
248
sage: f=2^2^14+1
249
sage: ecm.find_factor(f)
250
Traceback (most recent call last):
251
...
252
ValueError: n must have at most 4095 digits
253
"""
254
n = Integer(n)
255
self._validate(n)
256
if not 'c' in kwds: kwds['c'] = 1000000000
257
if not 'I' in kwds: kwds['I'] = 1
258
if not factor_digits is None:
259
B1 = self.recommended_B1(factor_digits)
260
kwds['one'] = ''
261
kwds['cofdec'] = ''
262
self.__cmd = self._ECM__startup_cmd(B1, None, kwds)
263
self.last_params = { 'B1' : B1 }
264
child = pexpect.spawn(self.__cmd)
265
cleaner.cleaner(child.pid, self.__cmd)
266
child.timeout = None
267
child.__del__ = nothing # program around stupid exception ignored error
268
child.expect('[ECM]')
269
child.sendline(str(n))
270
child.sendline("bad") # child.sendeof()
271
while True:
272
273
try:
274
child.expect('(Using B1=(\d+), B2=(\d+), polynomial ([^,]+), sigma=(\d+)\D)|(Factor found in step \d:\s+(\d+)\D)|(Error - invalid number)')
275
info = child.match.groups()
276
if not info[0] is None:
277
self.last_params = { 'B1' : child.match.groups()[1],
278
'B2' : child.match.groups()[2],
279
'poly' : child.match.groups()[3],
280
'sigma' : child.match.groups()[4] }
281
elif info[7] != None:
282
child.kill(0)
283
self.primality = [False]
284
return [n]
285
else:
286
p = Integer(info[6])
287
child.expect('(input number)|(prime factor)|(composite factor)')
288
if not child.match.groups()[0] is None:
289
child.kill(0)
290
return self.find_factor(n, B1=4+floor(float(B1)/2), **kwds)
291
else:
292
# primality testing is cheap compared to factoring, but has already been done
293
# return [p, n/p]
294
self.primality = [not child.match.groups()[1] is None]
295
child.expect('((prime cofactor)|(Composite cofactor)) (\d+)\D')
296
q = Integer(child.match.groups()[3])
297
self.primality += [not child.match.groups()[1] is None]
298
child.kill(0)
299
return [p, q]
300
301
302
except pexpect.EOF:
303
child.kill(0)
304
self.primality = [False]
305
return [n]
306
child.kill(0)
307
308
309
def factor(self, n, factor_digits=None, B1=2000, **kwds):
310
"""
311
Returns a list of integers whose product is n, computed using
312
GMP-ECM, and PARI for small factors.
313
314
** WARNING: There is no guarantee that the factors returned are
315
prime. **
316
317
INPUT:
318
n -- a positive integer
319
factor_digits -- optional guess at how many digits are in the smallest factor.
320
B1 -- initial lower bound, defaults to 2000 (15 digit factors)
321
kwds -- arguments to pass to ecm-gmp. See help for ECM for more details.
322
323
OUTPUT:
324
a list of integers whose product is n
325
326
NOTE:
327
Trial division should typically be performed before using
328
this method. Also, if you suspect that n is the product
329
of two similarly-sized primes, other methods (such as a
330
quadratic sieve -- use the qsieve command) will usually be
331
faster.
332
333
EXAMPLES:
334
sage: ecm.factor(602400691612422154516282778947806249229526581)
335
[45949729863572179, 13109994191499930367061460439]
336
337
sage: ecm.factor((2^197 + 1)/3) # takes a long time
338
[197002597249, 1348959352853811313, 251951573867253012259144010843]
339
"""
340
if B1 < 2000 or len(str(n)) < 15:
341
return sum([[p]*e for p, e in Integer(n).factor()], [])
342
343
factors = self.find_factor(n, factor_digits, B1, **kwds)
344
factors.sort()
345
if len(factors) == 1:
346
return factors
347
assert len(factors) == 2
348
_primality = [self.primality[0], self.primality[1]]
349
try:
350
last_B1 = self.last_params['B1']
351
except AttributeError:
352
self.last_params = {}
353
self.last_params['B1'] = 10
354
last_B1 = 10
355
if not _primality[1]:
356
factors[1:2] = self.factor(factors[1], B1=last_B1, **kwds)
357
_primality[1:2] = self.primality
358
if not _primality[0]:
359
factors[0:1] = self.factor(factors[0], B1=last_B1, **kwds)
360
_primality[0:1] = self.primality
361
self.primality = _primality
362
factors.sort()
363
return factors
364
365
366
def get_last_params(self):
367
"""
368
Returns the parameters (including the curve) of the last ecm run.
369
In the case that the number was factored successfully, this will return the parameters that yielded the factorization.
370
371
INPUT:
372
none
373
374
OUTPUT:
375
The parameters for the most recent factorization.
376
377
EXAMPLES:
378
sage: ecm.factor((2^197 + 1)/3) # long time
379
[197002597249, 1348959352853811313, 251951573867253012259144010843]
380
sage: ecm.get_last_params() # random output
381
{'poly': 'x^1', 'sigma': '1785694449', 'B1': '8885', 'B2': '1002846'}
382
383
"""
384
return self.last_params
385
386
387
388
def time(self, n, factor_digits, verbose=0):
389
"""
390
Gives an approximation for the amount of time it will take to find a factor
391
of size factor_digits in a single process on the current computer.
392
This estimate is provided by GMP-ECM's verbose option on a single run of a curve.
393
394
INPUT:
395
n -- a positive integer
396
factor_digits -- the (estimated) number of digits of the smallest factor
397
398
EXAMPLES:
399
400
sage: n = next_prime(11^23)*next_prime(11^37)
401
402
sage: ecm.time(n, 20) # not tested
403
Expected curves: 77 Expected time: 7.21s
404
sage: ecm.time(n, 25) # not tested
405
Expected curves: 206 Expected time: 1.56m
406
sage: ecm.time(n, 30, verbose=1) # not tested
407
GMP-ECM 6.1.3 [powered by GMP 4.2.1] [ECM]
408
409
Input number is 304481639541418099574459496544854621998616257489887231115912293 (63 digits)
410
Using MODMULN
411
Using B1=250000, B2=128992510, polynomial Dickson(3), sigma=2307628716
412
dF=2048, k=3, d=19110, d2=11, i0=3
413
Expected number of curves to find a factor of n digits:
414
20 25 30 35 40 45 50 55 60 65
415
8 50 430 4914 70293 1214949 2.5e+07 5.9e+08 1.6e+10 2.7e+13
416
Step 1 took 6408ms
417
Using 16 small primes for NTT
418
Estimated memory usage: 3862K
419
Initializing tables of differences for F took 16ms
420
Computing roots of F took 128ms
421
Building F from its roots took 408ms
422
Computing 1/F took 608ms
423
Initializing table of differences for G took 12ms
424
Computing roots of G took 120ms
425
Building G from its roots took 404ms
426
Computing roots of G took 120ms
427
Building G from its roots took 412ms
428
Computing G * H took 328ms
429
Reducing G * H mod F took 348ms
430
Computing roots of G took 120ms
431
Building G from its roots took 408ms
432
Computing G * H took 328ms
433
Reducing G * H mod F took 348ms
434
Computing polyeval(F,G) took 1128ms
435
Step 2 took 5260ms
436
Expected time to find a factor of n digits:
437
20 25 30 35 40 45 50 55 60 65
438
1.58m 9.64m 1.39h 15.93h 9.49d 164.07d 9.16y 218.68y 5825y 1e+07y
439
Expected curves: 4914 Expected time: 1.39h
440
441
"""
442
self._validate(n)
443
B1 = self.recommended_B1(factor_digits)
444
self.__cmd = self._ECM__startup_cmd(B1, None, {'v': ' '})
445
child = pexpect.spawn(self.__cmd)
446
cleaner.cleaner(child.pid, self.__cmd)
447
child.timeout = None
448
child.expect('[ECM]')
449
child.sendline(str(n))
450
try:
451
child.sendeof()
452
except:
453
pass
454
child.expect('20\s+25\s+30\s+35\s+40\s+45\s+50\s+55\s+60\s+65')
455
if verbose:
456
print child.before,
457
print child.after,
458
child.expect('(\d\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s', timeout=None)
459
offset = (self.__B1_table_value(factor_digits, 20, 65)-20)/5
460
curve_count = child.match.groups()[int(offset)]
461
if verbose:
462
print child.before,
463
print child.after,
464
child.expect('20\s+25\s+30\s+35\s+40\s+45\s+50\s+55\s+60\s+65', timeout=None)
465
if verbose:
466
print child.before,
467
print child.after,
468
child.expect('(\d\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s', timeout=None)
469
if verbose:
470
print child.before,
471
print child.after
472
time = child.match.groups()[int(offset)]
473
child.kill(0)
474
print "Expected curves:", curve_count, "\tExpected time:", time
475
476
def _validate(self, n):
477
"""
478
Verify that n is positive and has at most 4095 digits.
479
480
INPUT:
481
n
482
483
This function raises a ValueError if the two conditions listed above
484
are not both satisfied. It is here because GMP-ECM silently ignores
485
all digits of input after the 4095th!
486
487
EXAMPLES:
488
sage: ecm = ECM()
489
sage: ecm._validate(0)
490
Traceback (most recent call last):
491
...
492
ValueError: n must be positive
493
sage: ecm._validate(10^5000)
494
Traceback (most recent call last):
495
...
496
ValueError: n must have at most 4095 digits
497
"""
498
n = Integer(n)
499
if n <= 0:
500
raise ValueError, "n must be positive"
501
if n.ndigits() > 4095:
502
raise ValueError, "n must have at most 4095 digits"
503
504
505
# unique instance
506
ecm = ECM()
507
508