Skip to content

Commit aa48ca9

Browse files
committed
Change exhaustive test groups so they have a point with X=1
This enables testing overflow is correctly encoded in the recid, and likely triggers more edge cases.
1 parent 94cd878 commit aa48ca9

File tree

4 files changed

+130
-48
lines changed

4 files changed

+130
-48
lines changed

sage/gen_exhaustive_groups.sage

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Define field size and field
2+
P = 2^256 - 2^32 - 977
3+
F = GF(P)
4+
5+
orders_done = set()
6+
first = True
7+
for b in range(1, P):
8+
E = EllipticCurve(F, [0, b])
9+
n = E.order()
10+
# Skip curves isomorphic to the real secp256k1
11+
if n.is_pseudoprime():
12+
continue
13+
# Skip curves with an order we've already tried
14+
if n in orders_done:
15+
continue
16+
orders_done.add(n)
17+
18+
# Find what prime subgroups exist
19+
for f, _ in n.factor():
20+
# Skip subgroups of order >1000
21+
if f > 1000:
22+
continue
23+
# Skip subgroups that are not 3n+1 (needed for endomorphism)
24+
if f % 3 != 1:
25+
continue
26+
27+
if first:
28+
print("# if EXHAUSTIVE_TEST_ORDER == %i" % f)
29+
first = False
30+
else:
31+
print("# elif EXHAUSTIVE_TEST_ORDER == %i" % f)
32+
33+
# Iterate over X coordinates until we find one that is on the curve, has order f,
34+
# and for which curve isomorphism exists that maps it to X coordinate 1.
35+
for x in range(1, P):
36+
# Skip X coordinates not on the curve, and construct the full point otherwise.
37+
if not E.is_x_coord(x):
38+
continue
39+
G = E.lift_x(F(x))
40+
41+
# Skip points whose order is not a multiple of f. Project the point to have
42+
# order f otherwise.
43+
if (G.order() % f):
44+
continue
45+
G = G * (G.order() // f)
46+
assert G.order() == f
47+
48+
# Now look for an isomorphism of the curve that gives this generator X
49+
# coordinate equal to 1.
50+
# If (x,y) is on y^2 = x^3 + b, then (a^2*x, a^3*y) is on y^2 = x^3 + a^6*b.
51+
# So look for m=a^2=1/x.
52+
m = F(1)/G[0]
53+
if not m.is_square():
54+
continue
55+
rb = b*m^3
56+
RE = EllipticCurve(F, [0, rb])
57+
58+
# Use as generator twice the point with this low X coordinate (like secp256k1!)
59+
RG = RE.lift_x(1) * 2
60+
# And even Y coordinate.
61+
if int(RG[1]) % 2:
62+
RG = -RG
63+
64+
# We have found curve RE:y^2=x^3+rb with generator RG of order f. Print it out.
65+
print("static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(")
66+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(RG[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
67+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(RG[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
68+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(RG[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
69+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x" % tuple((int(RG[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
70+
print(");")
71+
print("static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(")
72+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(rb) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
73+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x" % tuple((int(rb) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
74+
print(");")
75+
break
76+
77+
# There are only 6 curves (up to isomorphism) of the form y^2=x^3+B. Stop once we have all.
78+
if len(orders_done) == 5:
79+
break
80+
81+
print("# else")
82+
print("# error No known generator for the specified exhaustive test group order.")
83+
print("# endif")

src/group_impl.h

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,52 +11,49 @@
1111
#include "field.h"
1212
#include "group.h"
1313

14-
/* These points can be generated in sage as follows:
14+
/* These exhaustive group test orders and generators are chosen such that:
15+
* - The field size is equal to that of secp256k1, so field code is the same.
16+
* - The curve equation is of the form y^2=x^3+B for some constant B.
17+
* - The subgroup has a generator 2*P, where P.x=1.
18+
* - The subgroup has size less than 1000 to permit exhaustive testing.
19+
* - The subgroup has a size that is multiple of 3 plus 1, enabling the endomorphism optimization.
1520
*
16-
* 0. Setup a worksheet with the following parameters.
17-
* b = 4 # whatever secp256k1_fe_const_b will be set to
18-
* F = FiniteField (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F)
19-
* C = EllipticCurve ([F (0), F (b)])
20-
*
21-
* 1. Determine all the small orders available to you. (If there are
22-
* no satisfactory ones, go back and change b.)
23-
* print C.order().factor(limit=1000)
24-
*
25-
* 2. Choose an order as one of the prime factors listed in the above step.
26-
* (You can also multiply some to get a composite order, though the
27-
* tests will crash trying to invert scalars during signing.) We take a
28-
* random point and scale it to drop its order to the desired value.
29-
* There is some probability this won't work; just try again.
30-
* order = 199
31-
* P = C.random_point()
32-
* P = (int(P.order()) / int(order)) * P
33-
* assert(P.order() == order)
34-
*
35-
* 3. Print the values. You'll need to use a vim macro or something to
36-
* split the hex output into 4-byte chunks.
37-
* print "%x %x" % P.xy()
21+
* They can generated using the Sage code in sage/gen_exhaustive_groups.sage.
3822
*/
3923
#if defined(EXHAUSTIVE_TEST_ORDER)
40-
# if EXHAUSTIVE_TEST_ORDER == 199
24+
# if EXHAUSTIVE_TEST_ORDER == 13
4125
static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
42-
0xFA7CC9A7, 0x0737F2DB, 0xA749DD39, 0x2B4FB069,
43-
0x3B017A7D, 0xA808C2F1, 0xFB12940C, 0x9EA66C18,
44-
0x78AC123A, 0x5ED8AEF3, 0x8732BC91, 0x1F3A2868,
45-
0x48DF246C, 0x808DAE72, 0xCFE52572, 0x7F0501ED
26+
0xc3459c3d, 0x35326167, 0xcd86cce8, 0x07a2417f,
27+
0x5b8bd567, 0xde8538ee, 0x0d507b0c, 0xd128f5bb,
28+
0x8e467fec, 0xcd30000a, 0x6cc1184e, 0x25d382c2,
29+
0xa2f4494e, 0x2fbe9abc, 0x8b64abac, 0xd005fb24
4630
);
47-
48-
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 4);
49-
50-
# elif EXHAUSTIVE_TEST_ORDER == 13
31+
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(
32+
0x3d3486b2, 0x159a9ca5, 0xc75638be, 0xb23a69bc,
33+
0x946a45ab, 0x24801247, 0xb4ed2b8e, 0x26b6a417
34+
);
35+
# elif EXHAUSTIVE_TEST_ORDER == 199
5136
static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
52-
0xedc60018, 0xa51a786b, 0x2ea91f4d, 0x4c9416c0,
53-
0x9de54c3b, 0xa1316554, 0x6cf4345c, 0x7277ef15,
54-
0x54cb1b6b, 0xdc8c1273, 0x087844ea, 0x43f4603e,
55-
0x0eaf9a43, 0xf6effe55, 0x939f806d, 0x37adf8ac
37+
0x226e653f, 0xc8df7744, 0x9bacbf12, 0x7d1dcbf9,
38+
0x87f05b2a, 0xe7edbd28, 0x1f564575, 0xc48dcf18,
39+
0xa13872c2, 0xe933bb17, 0x5d9ffd5b, 0xb5b6e10c,
40+
0x57fe3c00, 0xbaaaa15a, 0xe003ec3e, 0x9c269bae
41+
);
42+
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(
43+
0x2cca28fa, 0xfc614b80, 0x2a3db42b, 0x00ba00b1,
44+
0xbea8d943, 0xdace9ab2, 0x9536daea, 0x0074defb
45+
);
46+
# elif EXHAUSTIVE_TEST_ORDER == 7
47+
static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
48+
0x3629054c, 0x34e70740, 0x5d009072, 0x3172630c,
49+
0x3b93b9fe, 0x53d21c61, 0x3fd29d22, 0xed48a4ca,
50+
0x165c881d, 0x0a95ff40, 0xc81b3e2d, 0xa9be43e0,
51+
0xca5a8953, 0xa2fd0196, 0xf718e5cb, 0x21d245d8
52+
);
53+
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(
54+
0x168b921c, 0xe98a84e1, 0x2e977057, 0x29f0a9c1,
55+
0x5bce37e9, 0x94c30aed, 0xde3460ff, 0x71ac095f
5656
);
57-
58-
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 2);
59-
6057
# else
6158
# error No known generator for the specified exhaustive test group order.
6259
# endif

src/modules/recovery/tests_exhaustive_impl.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1
2525
unsigned char sk32[32], msg32[32];
2626
int expected_recid;
2727
int recid;
28+
int overflow;
2829
secp256k1_scalar_set_int(&msg, i);
2930
secp256k1_scalar_set_int(&sk, j);
3031
secp256k1_scalar_get_b32(sk32, &sk);
@@ -34,17 +35,18 @@ void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1
3435

3536
/* Check directly */
3637
secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, &recid, &rsig);
37-
r_from_k(&expected_r, group, k);
38+
r_from_k(&expected_r, group, k, &overflow);
3839
CHECK(r == expected_r);
3940
CHECK((k * s) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER ||
4041
(k * (EXHAUSTIVE_TEST_ORDER - s)) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER);
4142
/* The recid's second bit is for conveying overflow (R.x value >= group order).
4243
* In the actual secp256k1 this is an astronomically unlikely event, but in the
43-
* small group used here, it will always be the case.
44+
* small group used here, it will be the case for all points except the ones where
45+
* R.x=1 (which the group is specifically selected to have).
4446
* Note that this isn't actually useful; full recovery would need to convey
4547
* floor(R.x / group_order), but only one bit is used as that is sufficient
4648
* in the real group. */
47-
expected_recid = 2;
49+
expected_recid = overflow ? 2 : 0;
4850
r_dot_y_normalized = group[k].y;
4951
secp256k1_fe_normalize(&r_dot_y_normalized);
5052
/* Also the recovery id is flipped depending if we hit the low-s branch */
@@ -61,7 +63,7 @@ void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1
6163
/* Note that we compute expected_r *after* signing -- this is important
6264
* because our nonce-computing function function might change k during
6365
* signing. */
64-
r_from_k(&expected_r, group, k);
66+
r_from_k(&expected_r, group, k, NULL);
6567
CHECK(r == expected_r);
6668
CHECK((k * s) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER ||
6769
(k * (EXHAUSTIVE_TEST_ORDER - s)) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER);
@@ -104,7 +106,7 @@ void test_exhaustive_recovery_verify(const secp256k1_context *ctx, const secp256
104106
should_verify = 0;
105107
for (k = 0; k < EXHAUSTIVE_TEST_ORDER; k++) {
106108
secp256k1_scalar check_x_s;
107-
r_from_k(&check_x_s, group, k);
109+
r_from_k(&check_x_s, group, k, NULL);
108110
if (r_s == check_x_s) {
109111
secp256k1_scalar_set_int(&s_times_k_s, k);
110112
secp256k1_scalar_mul(&s_times_k_s, &s_times_k_s, &s_s);

src/tests_exhaustive.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,14 @@ void test_exhaustive_ecmult_multi(const secp256k1_context *ctx, const secp256k1_
216216
secp256k1_scratch_destroy(&ctx->error_callback, scratch);
217217
}
218218

219-
void r_from_k(secp256k1_scalar *r, const secp256k1_ge *group, int k) {
219+
void r_from_k(secp256k1_scalar *r, const secp256k1_ge *group, int k, int* overflow) {
220220
secp256k1_fe x;
221221
unsigned char x_bin[32];
222222
k %= EXHAUSTIVE_TEST_ORDER;
223223
x = group[k].x;
224224
secp256k1_fe_normalize(&x);
225225
secp256k1_fe_get_b32(x_bin, &x);
226-
secp256k1_scalar_set_b32(r, x_bin, NULL);
226+
secp256k1_scalar_set_b32(r, x_bin, overflow);
227227
}
228228

229229
void test_exhaustive_verify(const secp256k1_context *ctx, const secp256k1_ge *group) {
@@ -251,7 +251,7 @@ void test_exhaustive_verify(const secp256k1_context *ctx, const secp256k1_ge *gr
251251
should_verify = 0;
252252
for (k = 0; k < EXHAUSTIVE_TEST_ORDER; k++) {
253253
secp256k1_scalar check_x_s;
254-
r_from_k(&check_x_s, group, k);
254+
r_from_k(&check_x_s, group, k, NULL);
255255
if (r_s == check_x_s) {
256256
secp256k1_scalar_set_int(&s_times_k_s, k);
257257
secp256k1_scalar_mul(&s_times_k_s, &s_times_k_s, &s_s);
@@ -298,7 +298,7 @@ void test_exhaustive_sign(const secp256k1_context *ctx, const secp256k1_ge *grou
298298
/* Note that we compute expected_r *after* signing -- this is important
299299
* because our nonce-computing function function might change k during
300300
* signing. */
301-
r_from_k(&expected_r, group, k);
301+
r_from_k(&expected_r, group, k, NULL);
302302
CHECK(r == expected_r);
303303
CHECK((k * s) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER ||
304304
(k * (EXHAUSTIVE_TEST_ORDER - s)) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER);

0 commit comments

Comments
 (0)