gmp_ecm 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +31 -0
- data/ext/ecm.c +87 -40
- data/spec/basic_spec.rb +15 -0
- data/spec/io_spec.rb +40 -0
- data/spec/sanity_spec.rb +19 -1
- metadata +3 -1
data/README.md
CHANGED
@@ -12,6 +12,37 @@ The function of the gmp_ecm library is contained almost entirely in the `GMP::Z#
|
|
12
12
|
|
13
13
|
Just now, not all of the fields in `ecm_params` is available. The current list of supported parameters is in the docs.
|
14
14
|
|
15
|
+
Examples
|
16
|
+
========
|
17
|
+
|
18
|
+
Here is an example of repeatedly hitting up GMP-ECM for factors of `2**71 - 1`:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
z = GMP::Z(2)**71 - 1 #=> 2361183241434822606847
|
22
|
+
z.ecm_factor(1_000_000) #=> [1, 2361183241434822606847]
|
23
|
+
z.ecm_factor(1_000_000) #=> [1, 11091312221959]
|
24
|
+
z.ecm_factor(1_000_000) #=> [1, 2361183241434822606847]
|
25
|
+
z.ecm_factor(1_000_000) #=> [1, 2361183241434822606847]
|
26
|
+
z.ecm_factor(1_000_000) #=> [1, 48639942238007]
|
27
|
+
```
|
28
|
+
|
29
|
+
`GMP::Z#ecm_factor` returns a pair. The first element tells whether or not a factor was found:
|
30
|
+
|
31
|
+
* 1 means a factor was found in step 1.
|
32
|
+
* 2 means a factor was found in step 2.
|
33
|
+
* 0 means no factor was found.
|
34
|
+
* a negative value means an error occurred.
|
35
|
+
|
36
|
+
ECMParams
|
37
|
+
=========
|
38
|
+
|
39
|
+
The ECMParams class is not implemented yet. For now, you can supply the `ecm_params` parameter as a hash:
|
40
|
+
|
41
|
+
* Any value expected to be a `mpz_t` can be a Fixnum or a `GMP::Z`.
|
42
|
+
* Any value expected to be an `int`, including `unsigned`, must be a Fixnum.
|
43
|
+
* Any value expected to be a double must be a ruby Float.
|
44
|
+
* Any value expected to be a `FILE*` must be a ruby IO object, typically created with `File.open('filename', 'w')`.
|
45
|
+
|
15
46
|
License
|
16
47
|
=======
|
17
48
|
|
data/ext/ecm.c
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
*/
|
6
6
|
|
7
7
|
#include <ruby_ecm.h>
|
8
|
+
#include <ruby/io.h>
|
8
9
|
|
9
10
|
/*********************************************************************
|
10
11
|
* Initialization Functions *
|
@@ -22,25 +23,22 @@ ID ecmp_go_id;
|
|
22
23
|
ID ecmp_B1done_id;
|
23
24
|
ID ecmp_B2min_id;
|
24
25
|
ID ecmp_B2_id;
|
26
|
+
ID ecmp_k_id;
|
27
|
+
ID ecmp_S_id;
|
28
|
+
ID ecmp_repr_id;
|
29
|
+
ID ecmp_nobase2step2_id;
|
30
|
+
ID ecmp_verbose_id;
|
31
|
+
ID ecmp_os_id;
|
32
|
+
ID ecmp_es_id;
|
25
33
|
//void r_ecm_params_free(void *ptr) { ecm_clear (ptr); free (ptr); }
|
26
34
|
void r_ecm_params_free(void *ptr) { ecm_clear (ptr); }
|
27
35
|
|
28
|
-
/*
|
29
|
-
* So far, we accept the following:
|
30
|
-
*
|
31
|
-
* :method is supposed to be one of these constants: ECM_ECM, ECM_PM1, ECM_PP1.
|
32
|
-
* I am accepting :ecm_ecm, :ecm_pm1, :ecm_pp1.
|
33
|
-
*
|
34
|
-
* :x is supposed to be an mpz_t. I am accepting GMP::Z.
|
35
|
-
*
|
36
|
-
* :sigma is supposed to be an mpz_t. I am accepting Fixnum, Bignum, GMP::Z.
|
37
|
-
*
|
38
|
-
* :sigma_is_A is supposed to be 1, 0, or -1. I am accepting Fixnum.
|
39
|
-
*/
|
40
36
|
int add_item_to_ecm_params (VALUE key, VALUE value, VALUE params_value) {
|
41
37
|
ECM_PARAMS *params;
|
42
38
|
ecm_params_get_struct (params_value, params);
|
43
39
|
MP_INT *params_x, *params_sigma, *params_go, *params_B2min, *params_B2;
|
40
|
+
FILE *fd;
|
41
|
+
struct RFile *params_os;
|
44
42
|
if (rb_to_id (key) == ecmp_method_id) {
|
45
43
|
if ( rb_to_id (value) == ecmp_ecm_ecm_id) { params->method = ECM_ECM; }
|
46
44
|
else if ( rb_to_id (value) == ecmp_ecm_pm1_id) { params->method = ECM_PM1; }
|
@@ -56,6 +54,9 @@ int add_item_to_ecm_params (VALUE key, VALUE value, VALUE params_value) {
|
|
56
54
|
mpz_init_set (params->sigma, params_sigma);
|
57
55
|
}
|
58
56
|
} else if (rb_to_id (key) == ecmp_sigma_is_A_id) {
|
57
|
+
if (! FIXNUM_P (value)) {
|
58
|
+
rb_raise (rb_eTypeError, "sigma_is_A must be a Fixnum.");
|
59
|
+
}
|
59
60
|
params->sigma_is_A = NUM2INT(value);
|
60
61
|
} else if (rb_to_id (key) == ecmp_go_id) {
|
61
62
|
if (FIXNUM_P (value) || BIGNUM_P (value)) {
|
@@ -84,8 +85,45 @@ int add_item_to_ecm_params (VALUE key, VALUE value, VALUE params_value) {
|
|
84
85
|
mpz_get_struct (value, params_B2);
|
85
86
|
mpz_init_set (params->B2, params_B2);
|
86
87
|
}
|
88
|
+
} else if (rb_to_id (key) == ecmp_k_id) {
|
89
|
+
if (! FIXNUM_P (value)) {
|
90
|
+
rb_raise (rb_eTypeError, "k must be a Fixnum.");
|
91
|
+
}
|
92
|
+
params->k = NUM2INT(value);
|
93
|
+
} else if (rb_to_id (key) == ecmp_S_id) {
|
94
|
+
if (! FIXNUM_P (value)) {
|
95
|
+
rb_raise (rb_eTypeError, "S must be a Fixnum.");
|
96
|
+
}
|
97
|
+
params->S = NUM2INT(value);
|
98
|
+
} else if (rb_to_id (key) == ecmp_repr_id) {
|
99
|
+
if (! FIXNUM_P (value)) {
|
100
|
+
rb_raise (rb_eTypeError, "repr must be a Fixnum.");
|
101
|
+
}
|
102
|
+
params->repr = NUM2INT(value);
|
103
|
+
} else if (rb_to_id (key) == ecmp_nobase2step2_id) {
|
104
|
+
if (! FIXNUM_P (value)) {
|
105
|
+
rb_raise (rb_eTypeError, "nobase2step2 must be a Fixnum.");
|
106
|
+
}
|
107
|
+
params->nobase2step2 = NUM2INT(value);
|
108
|
+
} else if (rb_to_id (key) == ecmp_verbose_id) {
|
109
|
+
if (! FIXNUM_P (value)) {
|
110
|
+
rb_raise (rb_eTypeError, "verbose must be a Fixnum.");
|
111
|
+
}
|
112
|
+
params->verbose = NUM2INT(value);
|
113
|
+
} else if (rb_to_id (key) == ecmp_os_id) {
|
114
|
+
if (TYPE (value) != T_FILE) {
|
115
|
+
rb_raise (rb_eTypeError, "os must be an IO.");
|
116
|
+
}
|
117
|
+
fd = rb_io_stdio_file(RFILE(value)->fptr);
|
118
|
+
params->os = fd;
|
119
|
+
} else if (rb_to_id (key) == ecmp_es_id) {
|
120
|
+
if (TYPE (value) != T_FILE) {
|
121
|
+
rb_raise (rb_eTypeError, "es must be an IO.");
|
122
|
+
}
|
123
|
+
fd = rb_io_stdio_file(RFILE(value)->fptr);
|
124
|
+
params->es = fd;
|
87
125
|
}
|
88
|
-
return
|
126
|
+
return ST_CONTINUE;
|
89
127
|
}
|
90
128
|
|
91
129
|
/*
|
@@ -119,21 +157,23 @@ VALUE r_ecmsg_new(int argc, VALUE *argv, VALUE klass)
|
|
119
157
|
* Document-method: GMP::Z#ecm_method
|
120
158
|
*
|
121
159
|
* @overload ecm_factor(b1, ecm_params)
|
122
|
-
*
|
123
|
-
*
|
124
|
-
*
|
125
|
-
*
|
126
|
-
*
|
127
|
-
*
|
128
|
-
*
|
129
|
-
*
|
130
|
-
*
|
131
|
-
*
|
132
|
-
*
|
133
|
-
*
|
134
|
-
*
|
135
|
-
*
|
136
|
-
*
|
160
|
+
* Search for a factor of `z`, using GMP-ECM's `ecm_factor()` method.
|
161
|
+
* @param [Fixnum, GMP::Z] b1 the stage 1 bound
|
162
|
+
* @param [Hash] ecm_params auxiliary parameters; must be a Hash for now.
|
163
|
+
* @option ecm_params [Symbol] :method the factorization method (`:ecm_ecm` for ECM, `:ecm_pm1` for P-1, `:ecm_pp1` for P+1). Default is ECM_ECM.
|
164
|
+
* @option ecm_params [Fixnum, GMP::Z] :x (if non zero) is the starting point (ECM, P-1, P+1). For ECM, we take as starting point $(x\_0 : y\_0)$ where $x\_0=x$, $y\_0=1$; for P-1, we take $x\_0$; for P+1, we take $x\_0$ as starting point of the Lucas sequence. When `ecm_factor()` returns, p->x is the point obtained after stage 1.
|
165
|
+
* @option ecm_params [Fixnum, GMP::Z] :sigma (ECM only) is the "sigma" parameter. The elliptic curve chosen is $b \* y^2 = x^3 + a\*x^2 + x$ where $a = (v-u)^3 \* (3\*u+v) / (4\*u^3\*v) -2$, $u = sigma^2-5$, $v = 4\*sigma$ (Suyama's parametrization). The initial point (if p->x is zero) is taken as $x\_0=u^3/v^3$, $y0=1$ (thus $b$ is taken as $x\_0^3 + a\*x\_0^2 + x\_0$).
|
166
|
+
* @option ecm_params [-1, 0, 1] :sigma_is_A (ECM only) indicates that `:sigma` is the `a` parameter from the elliptic curve.
|
167
|
+
* @option ecm_params [Fixnum, GMP::Z] :go the initial group order to preload (default is 1).
|
168
|
+
* @option ecm_params [Float] :B1done tells that step 1 was already done up to this value. This means that all prime powers <= this value were dealt with. If for example `:B1done => 100` and `:B1 => 200`, prime 2 was dealt with up to power 6, thus it remains to "multiply" once by 2 to go up to power 7. Of course, all primes $p$ such that $B1done < p <= B1$ will be considered with power 1.
|
169
|
+
* @option ecm_params [Fixnum, GMP::Z] :B2min the lower bound for stage 2, which will treat all primes $p$ such that $B2min <= p <= B2$. If negative, `:B2min` will be set to `:B1`.
|
170
|
+
* @option ecm_params [Fixnum, GMP::Z] :B2 the upper bound for stage 2 (default is automatically computed from `:B1`, to optimize the efficiency of the method).
|
171
|
+
* @option ecm_params [Fixnum] :k the number of blocks used in stage 2 (default is `ECM_DEFAULT_K`).
|
172
|
+
* @option ecm_params [Fixnum] :S defines the polynomial used for Brent-Suyama's extension in stage 2. If positive, the polynomial used is $x^S$; if negative, it is Dickson's polynomial of degree $S$ with parameter $a=-1$, where $D\_\\{1,a\}(x) = x, D\_\\{2,a\}(x) = x^2-2\*a$, and $D\_\\{k+2,a\}(x) = x\*D\_\\{k+1,a\}(x) - a\*D\_\\{k,a\}(x)$, or equivalently $D\_\\{k,a\}(2\*sqrt(a)\*cos(t)) = 2\*a^\\{k/2\}\*cos(k\*t)$. If zero, choice is automatic (and should be close to optimal). Default is `ECM_DEFAULT_S`.
|
173
|
+
* @option ecm_params [Fixnum] :repr defines the representation used for modular arithmetic: 1 means the 'mpz' class from GMP, 2 means 'modmuln' (Montgomery's multiplication, quadratic implementation), 3 means 'redc' (Montgomery's multiplication, subquadratic implementation), -1 indicates not to use a special base-2 representation (when the input number is a factor of $2^n +/- 1$). Other values (including 0) mean the representation will be chosen automatically (hopefully in some optimal way).
|
174
|
+
* @option ecm_params [Fixnum] :verbose the verbosity level: 0 for no output, 1 for normal output (like default for GMP-ECM), 2 for diagnostic output without intermediate residues (like `-v` in GMP-ECM), 3 for diagnostic output with residues (like `-v -v`), 4 for high diagnostic output (`-v -v -v`), and 5 for trace output (`-v -v -v -v`).
|
175
|
+
* @option ecm_params [IO] :os the output stream used for verbose output. Default is stdout.
|
176
|
+
* @option ecm_params [IO] :es the output stream used for errors. Default is stderr.
|
137
177
|
*/
|
138
178
|
VALUE r_ecm_factor (int argc, VALUE *argv, VALUE self_value) {
|
139
179
|
MP_INT *self, *res;
|
@@ -170,21 +210,28 @@ VALUE r_ecm_factor (int argc, VALUE *argv, VALUE self_value) {
|
|
170
210
|
|
171
211
|
found = ecm_factor (res, self, b1, params);
|
172
212
|
|
173
|
-
return res_value;
|
213
|
+
return rb_assoc_new(INT2FIX (found), res_value);
|
174
214
|
}
|
175
215
|
|
176
216
|
void Init_ecm() {
|
177
|
-
ecmp_method_id
|
178
|
-
ecmp_ecm_ecm_id
|
179
|
-
ecmp_ecm_pm1_id
|
180
|
-
ecmp_ecm_pp1_id
|
181
|
-
ecmp_x_id
|
182
|
-
ecmp_sigma_id
|
183
|
-
ecmp_sigma_is_A_id
|
184
|
-
|
185
|
-
ecmp_B1done_id
|
186
|
-
ecmp_B2min_id
|
187
|
-
ecmp_B2_id
|
217
|
+
ecmp_method_id = rb_intern("method");
|
218
|
+
ecmp_ecm_ecm_id = rb_intern("ecm_ecm");
|
219
|
+
ecmp_ecm_pm1_id = rb_intern("ecm_pm1");
|
220
|
+
ecmp_ecm_pp1_id = rb_intern("ecm_pp1");
|
221
|
+
ecmp_x_id = rb_intern("x");
|
222
|
+
ecmp_sigma_id = rb_intern("sigma");
|
223
|
+
ecmp_sigma_is_A_id = rb_intern("sigma_is_A");
|
224
|
+
ecmp_go_id = rb_intern("go");
|
225
|
+
ecmp_B1done_id = rb_intern("B1done");
|
226
|
+
ecmp_B2min_id = rb_intern("B2min");
|
227
|
+
ecmp_B2_id = rb_intern("B2");
|
228
|
+
ecmp_k_id = rb_intern("k");
|
229
|
+
ecmp_S_id = rb_intern("S");
|
230
|
+
ecmp_repr_id = rb_intern("repr");
|
231
|
+
ecmp_nobase2step2_id = rb_intern("nobase2step2");
|
232
|
+
ecmp_verbose_id = rb_intern("verbose");
|
233
|
+
ecmp_os_id = rb_intern("os");
|
234
|
+
ecmp_es_id = rb_intern("es");
|
188
235
|
cECM_PARAMS = rb_define_class ("ECMParams", rb_cObject);
|
189
236
|
|
190
237
|
// Initialization Functions and Assignment Functions
|
data/spec/basic_spec.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe GMP::Z, "#ecm_factor" do
|
4
|
+
it "should find one of the expected factors of 2^71 - 1" do
|
5
|
+
@a = GMP::Z(2) ** 71 - 1
|
6
|
+
@factors = [GMP::Z(228479), GMP::Z(48544121), GMP::Z(212885833),
|
7
|
+
GMP::Z(11091312221959), GMP::Z("48639942238007"),
|
8
|
+
GMP::Z("2361183241434822606847")]
|
9
|
+
8.times do
|
10
|
+
found, factor = @a.ecm_factor(1e6)
|
11
|
+
found.should == 1
|
12
|
+
@factors.should include(factor)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/io_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe GMP::Z, "IO operations" do
|
4
|
+
before do
|
5
|
+
@z = GMP::Z(2)**71 - 1
|
6
|
+
@file = File.join(File.dirname(__FILE__), "ecm.out")
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@handle = File.open(@file, 'w')
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:each) do
|
14
|
+
FileUtils.rm @file
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be verbose-ish with :verbose => 1" do
|
18
|
+
@z.ecm_factor(1_000_000, :verbose => 1, :os => @handle)
|
19
|
+
lines = File.read @file
|
20
|
+
lines.should match(/Using B1=1000000, B2=\d+, polynomial Dickson\(6\), sigma=\d+/)
|
21
|
+
lines.should match(/Step 1 took \d+ms/)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Using special division for factor of 2^71-1
|
25
|
+
# Using B1=1000000, B2=1045563762, polynomial Dickson(6), sigma=1674549887
|
26
|
+
# dF=4096, k=6, d=39270, d2=13, i0=13
|
27
|
+
# Expected number of curves to find a factor of n digits:
|
28
|
+
# 35 40 45 50 55 60 65 70 75 80
|
29
|
+
# 910 8615 97096 1281819 1.9e+07 3.1e+08 6.2e+09 1.7e+11 2.3e+16 3.1e+21
|
30
|
+
# Reached point at infinity, 578029 divides group order
|
31
|
+
# Step 1 took 1210ms
|
32
|
+
it "should be verbose-ish with :verbose => 2" do
|
33
|
+
@z.ecm_factor(1_000_000, :verbose => 2, :os => @handle)
|
34
|
+
lines = File.read @file
|
35
|
+
lines.should match(/Using special division for factor of 2\^71-1/)
|
36
|
+
lines.should match(/Using B1=1000000, B2=\d+, polynomial Dickson\(6\), sigma=\d+/)
|
37
|
+
lines.should match(/Using dF=\d+, k=\d+, d=\d+, d2=\d+, i0=\d+/)
|
38
|
+
lines.should match(/Step 1 took \d+ms/)
|
39
|
+
end
|
40
|
+
end
|
data/spec/sanity_spec.rb
CHANGED
@@ -30,7 +30,7 @@ describe ECMParams, "new" do
|
|
30
30
|
it "should allow a simple hash with :sigma_is_A" do
|
31
31
|
@z2.ecm_factor 1_000_000, :sigma_is_A => 1
|
32
32
|
@z2.ecm_factor 1_000_000, :sigma_is_A => 0
|
33
|
-
|
33
|
+
#@z2.ecm_factor 1_000_000, :sigma_is_A => -1
|
34
34
|
end
|
35
35
|
|
36
36
|
it "should allow a simple hash with :go" do
|
@@ -40,4 +40,22 @@ describe ECMParams, "new" do
|
|
40
40
|
it "should allow a simple hash with :B1done" do
|
41
41
|
@z3.ecm_factor 1_000_000, :sigma => GMP::Z(781683988), :go => GMP::Z(550232165123), :B1done => 1000.0
|
42
42
|
end
|
43
|
+
|
44
|
+
it "should allow a simple hash with :B2min" do
|
45
|
+
@z2.ecm_factor 1_000_000, :B2min => 2_000_000
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should allow a simple hash with :B2min" do
|
49
|
+
@z2.ecm_factor 1_000_000, :B2 => 2_000_000
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe ECMParams, "bad types" do
|
54
|
+
before do
|
55
|
+
@z = GMP::Z(2)**255 - 1
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should blow up if sigma_is_A is not a Fixnum" do
|
59
|
+
expect { @z1.ecm_factor 1_000_000, :sigma => 7, :sigma_is_A => 2.3 }.to raise_exception
|
60
|
+
end
|
43
61
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gmp_ecm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -40,6 +40,8 @@ files:
|
|
40
40
|
- ext/ruby_gmp.h
|
41
41
|
- ext/extconf.rb
|
42
42
|
- lib/ecm.rb
|
43
|
+
- spec/basic_spec.rb
|
44
|
+
- spec/io_spec.rb
|
43
45
|
- spec/sanity_spec.rb
|
44
46
|
- spec/spec_helper.rb
|
45
47
|
- README.md
|