Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/interfaces/ecm.py
8814 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
@rename_keyword(deprecation=6094, method="algorithm")
155
def one_curve(self, n, factor_digits=None, B1=2000, algorithm="ECM", **kwds):
156
"""
157
Run one single ECM (or P-1/P+1) curve on input n.
158
159
INPUT:
160
n -- a positive integer
161
factor_digits -- decimal digits estimate of the wanted factor
162
B1 -- stage 1 bound (default 2000)
163
algorithm -- either "ECM" (default), "P-1" or "P+1"
164
OUTPUT:
165
a list [p,q] where p and q are integers and n = p * q.
166
If no factor was found, then p = 1 and q = n.
167
WARNING: neither p nor q is guaranteed to be prime.
168
EXAMPLES:
169
sage: f = ECM()
170
sage: n = 508021860739623467191080372196682785441177798407961
171
sage: f.one_curve(n, B1=10000, sigma=11)
172
[1, 508021860739623467191080372196682785441177798407961]
173
sage: f.one_curve(n, B1=10000, sigma=1022170541)
174
[79792266297612017, 6366805760909027985741435139224233]
175
sage: n = 432132887883903108009802143314445113500016816977037257
176
sage: f.one_curve(n, B1=500000, algorithm="P-1")
177
[67872792749091946529, 6366805760909027985741435139224233]
178
sage: n = 2088352670731726262548647919416588631875815083
179
sage: f.one_curve(n, B1=2000, algorithm="P+1", x0=5)
180
[328006342451, 6366805760909027985741435139224233]
181
"""
182
n = Integer(n)
183
self._validate(n)
184
if not factor_digits is None:
185
B1 = self.recommended_B1(factor_digits)
186
if algorithm == "P-1":
187
kwds['pm1'] = ''
188
elif algorithm == "P+1":
189
kwds['pp1'] = ''
190
else:
191
if not algorithm == "ECM":
192
err = "unexpected algorithm: " + algorithm
193
raise ValueError, err
194
self.__cmd = self._ECM__startup_cmd(B1, None, kwds)
195
child = pexpect.spawn(self.__cmd)
196
cleaner.cleaner(child.pid, self.__cmd)
197
child.timeout = None
198
child.__del__ = nothing # work around stupid exception ignored error
199
child.expect('[ECM]')
200
child.sendline(str(n))
201
child.sendline("bad") # child.sendeof()
202
while True:
203
try:
204
child.expect('(Using B1=(\d+), B2=(\d+), polynomial ([^,]+), sigma=(\d+)\D)|(Factor found in step \d:\s+(\d+)\D)|(Error - invalid number)')
205
info = child.match.groups()
206
# B1 is info[1], B2 is info[2], poly is info[3], sigma is info[4],
207
# step is info[5], factor is info[6], cofactor is info[7]
208
if not info[0] is None:
209
# got Using B1=... line
210
self.last_params = { 'B1' : child.match.groups()[1],
211
'B2' : child.match.groups()[2],
212
'poly' : child.match.groups()[3],
213
'sigma' : child.match.groups()[4] }
214
elif info[7] != None:
215
# got Error - invalid number, which means the curve did
216
# end without finding any factor, and the next input 'bad'
217
# was given to GMP-ECM
218
child.kill(0)
219
return [1, n]
220
else:
221
# got Factor found...
222
p = Integer(info[6])
223
child.kill(0)
224
return [p, n/p]
225
226
except pexpect.EOF:
227
child.kill(0)
228
return [1, n]
229
child.kill(0)
230
231
232
def find_factor(self, n, factor_digits=None, B1=2000, **kwds):
233
"""
234
Splits off a single factor of n.
235
See ECM.factor()
236
237
OUTPUT:
238
list of integers whose product is n
239
240
EXAMPLES:
241
sage: f = ECM()
242
sage: n = 508021860739623467191080372196682785441177798407961
243
sage: f.find_factor(n)
244
[79792266297612017, 6366805760909027985741435139224233]
245
246
Note that the input number can't have more than 4095 digits:
247
sage: f=2^2^14+1
248
sage: ecm.find_factor(f)
249
Traceback (most recent call last):
250
...
251
ValueError: n must have at most 4095 digits
252
"""
253
n = Integer(n)
254
self._validate(n)
255
if not 'c' in kwds: kwds['c'] = 1000000000
256
if not 'I' in kwds: kwds['I'] = 1
257
if not factor_digits is None:
258
B1 = self.recommended_B1(factor_digits)
259
kwds['one'] = ''
260
kwds['cofdec'] = ''
261
self.__cmd = self._ECM__startup_cmd(B1, None, kwds)
262
self.last_params = { 'B1' : B1 }
263
child = pexpect.spawn(self.__cmd)
264
cleaner.cleaner(child.pid, self.__cmd)
265
child.timeout = None
266
child.__del__ = nothing # program around stupid exception ignored error
267
child.expect('[ECM]')
268
child.sendline(str(n))
269
child.sendline("bad") # child.sendeof()
270
while True:
271
272
try:
273
child.expect('(Using B1=(\d+), B2=(\d+), polynomial ([^,]+), sigma=(\d+)\D)|(Factor found in step \d:\s+(\d+)\D)|(Error - invalid number)')
274
info = child.match.groups()
275
if not info[0] is None:
276
self.last_params = { 'B1' : child.match.groups()[1],
277
'B2' : child.match.groups()[2],
278
'poly' : child.match.groups()[3],
279
'sigma' : child.match.groups()[4] }
280
elif info[7] != None:
281
child.kill(0)
282
self.primality = [False]
283
return [n]
284
else:
285
p = Integer(info[6])
286
child.expect('(input number)|(prime factor)|(composite factor)')
287
if not child.match.groups()[0] is None:
288
child.kill(0)
289
return self.find_factor(n, B1=4+floor(float(B1)/2), **kwds)
290
else:
291
# primality testing is cheap compared to factoring, but has already been done
292
# return [p, n/p]
293
self.primality = [not child.match.groups()[1] is None]
294
child.expect('((prime cofactor)|(Composite cofactor)) (\d+)\D')
295
q = Integer(child.match.groups()[3])
296
self.primality += [not child.match.groups()[1] is None]
297
child.kill(0)
298
return [p, q]
299
300
301
except pexpect.EOF:
302
child.kill(0)
303
self.primality = [False]
304
return [n]
305
child.kill(0)
306
307
308
def factor(self, n, factor_digits=None, B1=2000, **kwds):
309
"""
310
Returns a list of integers whose product is n, computed using
311
GMP-ECM, and PARI for small factors.
312
313
** WARNING: There is no guarantee that the factors returned are
314
prime. **
315
316
INPUT:
317
n -- a positive integer
318
factor_digits -- optional guess at how many digits are in the smallest factor.
319
B1 -- initial lower bound, defaults to 2000 (15 digit factors)
320
kwds -- arguments to pass to ecm-gmp. See help for ECM for more details.
321
322
OUTPUT:
323
a list of integers whose product is n
324
325
NOTE:
326
Trial division should typically be performed before using
327
this method. Also, if you suspect that n is the product
328
of two similarly-sized primes, other methods (such as a
329
quadratic sieve -- use the qsieve command) will usually be
330
faster.
331
332
EXAMPLES:
333
sage: ecm.factor(602400691612422154516282778947806249229526581)
334
[45949729863572179, 13109994191499930367061460439]
335
336
sage: ecm.factor((2^197 + 1)/3) # takes a long time
337
[197002597249, 1348959352853811313, 251951573867253012259144010843]
338
"""
339
if B1 < 2000 or len(str(n)) < 15:
340
return sum([[p]*e for p, e in Integer(n).factor()], [])
341
342
factors = self.find_factor(n, factor_digits, B1, **kwds)
343
factors.sort()
344
if len(factors) == 1:
345
return factors
346
assert len(factors) == 2
347
_primality = [self.primality[0], self.primality[1]]
348
try:
349
last_B1 = self.last_params['B1']
350
except AttributeError:
351
self.last_params = {}
352
self.last_params['B1'] = 10
353
last_B1 = 10
354
if not _primality[1]:
355
factors[1:2] = self.factor(factors[1], B1=last_B1, **kwds)
356
_primality[1:2] = self.primality
357
if not _primality[0]:
358
factors[0:1] = self.factor(factors[0], B1=last_B1, **kwds)
359
_primality[0:1] = self.primality
360
self.primality = _primality
361
factors.sort()
362
return factors
363
364
365
def get_last_params(self):
366
"""
367
Returns the parameters (including the curve) of the last ecm run.
368
In the case that the number was factored successfully, this will return the parameters that yielded the factorization.
369
370
INPUT:
371
none
372
373
OUTPUT:
374
The parameters for the most recent factorization.
375
376
EXAMPLES:
377
sage: ecm.factor((2^197 + 1)/3) # long time
378
[197002597249, 1348959352853811313, 251951573867253012259144010843]
379
sage: ecm.get_last_params() # random output
380
{'poly': 'x^1', 'sigma': '1785694449', 'B1': '8885', 'B2': '1002846'}
381
382
"""
383
return self.last_params
384
385
386
387
def time(self, n, factor_digits, verbose=0):
388
"""
389
Gives an approximation for the amount of time it will take to find a factor
390
of size factor_digits in a single process on the current computer.
391
This estimate is provided by GMP-ECM's verbose option on a single run of a curve.
392
393
INPUT:
394
n -- a positive integer
395
factor_digits -- the (estimated) number of digits of the smallest factor
396
397
EXAMPLES::
398
399
sage: n = next_prime(11^23)*next_prime(11^37)
400
401
sage: ecm.time(n, 20) # not tested
402
Expected curves: 77 Expected time: 7.21s
403
sage: ecm.time(n, 25) # not tested
404
Expected curves: 206 Expected time: 1.56m
405
sage: ecm.time(n, 30, verbose=1) # not tested
406
GMP-ECM 6.1.3 [powered by GMP 4.2.1] [ECM]
407
408
Input number is 304481639541418099574459496544854621998616257489887231115912293 (63 digits)
409
Using MODMULN
410
Using B1=250000, B2=128992510, polynomial Dickson(3), sigma=2307628716
411
dF=2048, k=3, d=19110, d2=11, i0=3
412
Expected number of curves to find a factor of n digits:
413
20 25 30 35 40 45 50 55 60 65
414
8 50 430 4914 70293 1214949 2.5e+07 5.9e+08 1.6e+10 2.7e+13
415
Step 1 took 6408ms
416
Using 16 small primes for NTT
417
Estimated memory usage: 3862K
418
Initializing tables of differences for F took 16ms
419
Computing roots of F took 128ms
420
Building F from its roots took 408ms
421
Computing 1/F took 608ms
422
Initializing table of differences for G took 12ms
423
Computing roots of G took 120ms
424
Building G from its roots took 404ms
425
Computing roots of G took 120ms
426
Building G from its roots took 412ms
427
Computing G * H took 328ms
428
Reducing G * H mod F took 348ms
429
Computing roots of G took 120ms
430
Building G from its roots took 408ms
431
Computing G * H took 328ms
432
Reducing G * H mod F took 348ms
433
Computing polyeval(F,G) took 1128ms
434
Step 2 took 5260ms
435
Expected time to find a factor of n digits:
436
20 25 30 35 40 45 50 55 60 65
437
1.58m 9.64m 1.39h 15.93h 9.49d 164.07d 9.16y 218.68y 5825y 1e+07y
438
Expected curves: 4914 Expected time: 1.39h
439
440
"""
441
self._validate(n)
442
B1 = self.recommended_B1(factor_digits)
443
self.__cmd = self._ECM__startup_cmd(B1, None, {'v': ' '})
444
child = pexpect.spawn(self.__cmd)
445
cleaner.cleaner(child.pid, self.__cmd)
446
child.timeout = None
447
child.expect('[ECM]')
448
child.sendline(str(n))
449
try:
450
child.sendeof()
451
except Exception:
452
pass
453
child.expect('20\s+25\s+30\s+35\s+40\s+45\s+50\s+55\s+60\s+65')
454
if verbose:
455
print child.before,
456
print child.after,
457
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)
458
offset = (self.__B1_table_value(factor_digits, 20, 65)-20)/5
459
curve_count = child.match.groups()[int(offset)]
460
if verbose:
461
print child.before,
462
print child.after,
463
child.expect('20\s+25\s+30\s+35\s+40\s+45\s+50\s+55\s+60\s+65', timeout=None)
464
if verbose:
465
print child.before,
466
print child.after,
467
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)
468
if verbose:
469
print child.before,
470
print child.after
471
time = child.match.groups()[int(offset)]
472
child.kill(0)
473
print "Expected curves:", curve_count, "\tExpected time:", time
474
475
def _validate(self, n):
476
"""
477
Verify that n is positive and has at most 4095 digits.
478
479
INPUT:
480
n
481
482
This function raises a ValueError if the two conditions listed above
483
are not both satisfied. It is here because GMP-ECM silently ignores
484
all digits of input after the 4095th!
485
486
EXAMPLES:
487
sage: ecm = ECM()
488
sage: ecm._validate(0)
489
Traceback (most recent call last):
490
...
491
ValueError: n must be positive
492
sage: ecm._validate(10^5000)
493
Traceback (most recent call last):
494
...
495
ValueError: n must have at most 4095 digits
496
"""
497
n = Integer(n)
498
if n <= 0:
499
raise ValueError, "n must be positive"
500
if n.ndigits() > 4095:
501
raise ValueError, "n must have at most 4095 digits"
502
503
504
# unique instance
505
ecm = ECM()
506
507