Skip to content

Commit b110c10

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. Also introduce a Sage script to generate the parameters.
1 parent cec7b18 commit b110c10

File tree

5 files changed

+166
-48
lines changed

5 files changed

+166
-48
lines changed

sage/gen_exhaustive_groups.sage

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Define field size and field
2+
P = 2^256 - 2^32 - 977
3+
F = GF(P)
4+
BETA = F(0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee)
5+
6+
assert(BETA != F(1) and BETA^3 == F(1))
7+
8+
orders_done = set()
9+
results = {}
10+
first = True
11+
for b in range(1, P):
12+
# There are only 6 curves (up to isomorphism) of the form y^2=x^3+B. Stop once we have tried all.
13+
if len(orders_done) == 6:
14+
break
15+
16+
E = EllipticCurve(F, [0, b])
17+
print("Analyzing curve y^2 = x^3 + %i" % b)
18+
n = E.order()
19+
# Skip curves with an order we've already tried
20+
if n in orders_done:
21+
print("- Isomorphic to earlier curve")
22+
continue
23+
orders_done.add(n)
24+
# Skip curves isomorphic to the real secp256k1
25+
if n.is_pseudoprime():
26+
print(" - Isomorphic to secp256k1")
27+
continue
28+
29+
print("- Finding subgroups")
30+
31+
# Find what prime subgroups exist
32+
for f, _ in n.factor():
33+
print("- Analyzing subgroup of order %i" % f)
34+
# Skip subgroups of order >1000
35+
if f < 4 or f > 1000:
36+
print(" - Bad size")
37+
continue
38+
39+
# Iterate over X coordinates until we find one that is on the curve, has order f,
40+
# and for which curve isomorphism exists that maps it to X coordinate 1.
41+
for x in range(1, P):
42+
# Skip X coordinates not on the curve, and construct the full point otherwise.
43+
if not E.is_x_coord(x):
44+
continue
45+
G = E.lift_x(F(x))
46+
47+
print(" - Analyzing (multiples of) point with X=%i" % x)
48+
49+
# Skip points whose order is not a multiple of f. Project the point to have
50+
# order f otherwise.
51+
if (G.order() % f):
52+
print(" - Bad order")
53+
continue
54+
G = G * (G.order() // f)
55+
56+
# Find lambda for endomorphism. Skip if none can be found.
57+
lam = None
58+
for l in Integers(f)(1).nth_root(3, all=True):
59+
if int(l)*G == E(BETA*G[0], G[1]):
60+
lam = int(l)
61+
break
62+
if lam is None:
63+
print(" - No endomorphism for this subgroup")
64+
break
65+
66+
# Now look for an isomorphism of the curve that gives this point an X
67+
# coordinate equal to 1.
68+
# 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.
69+
# So look for m=a^2=1/x.
70+
m = F(1)/G[0]
71+
if not m.is_square():
72+
print(" - No curve isomorphism maps it to a point with X=1")
73+
continue
74+
a = m.sqrt()
75+
rb = a^6*b
76+
RE = EllipticCurve(F, [0, rb])
77+
78+
# Use as generator twice the image of G under the above isormorphism.
79+
# This means that generator*(1/2 mod f) will have X coordinate 1.
80+
RG = RE(1, a^3*G[1]) * 2
81+
# And even Y coordinate.
82+
if int(RG[1]) % 2:
83+
RG = -RG
84+
assert(RG.order() == f)
85+
assert(lam*RG == RE(BETA*RG[0], RG[1]))
86+
87+
# We have found curve RE:y^2=x^3+rb with generator RG of order f. Remember it
88+
results[f] = {"b": rb, "G": RG, "lambda": lam}
89+
print(" - Found solution")
90+
break
91+
92+
print("")
93+
94+
print("")
95+
print("")
96+
print("/* To be put in src/group_impl.h: */")
97+
first = True
98+
for f in sorted(results.keys()):
99+
b = results[f]["b"]
100+
G = results[f]["G"]
101+
print("# %s EXHAUSTIVE_TEST_ORDER == %i" % ("if" if first else "elif", f))
102+
first = False
103+
print("static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(")
104+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(G[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
105+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(G[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
106+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(G[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
107+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x" % tuple((int(G[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
108+
print(");")
109+
print("static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(")
110+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(b) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
111+
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x" % tuple((int(b) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
112+
print(");")
113+
print("# else")
114+
print("# error No known generator for the specified exhaustive test group order.")
115+
print("# endif")
116+
117+
print("")
118+
print("")
119+
print("/* To be put in src/scalar_impl.h: */")
120+
first = True
121+
for f in sorted(results.keys()):
122+
lam = results[f]["lambda"]
123+
print("# %s EXHAUSTIVE_TEST_ORDER == %i" % ("if" if first else "elif", f))
124+
first = False
125+
print("# define EXHAUSTIVE_TEST_LAMBDA %i" % lam)
126+
print("# else")
127+
print("# error No known lambda for the specified exhaustive test group order.")
128+
print("# endif")
129+
print("")

src/group_impl.h

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,52 +11,38 @@
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 admits an endomorphism of the form lambda*(x,y) == (beta*x,y).
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+
* These parameters are generated using 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
5645
);
57-
58-
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 2);
59-
6046
# else
6147
# error No known generator for the specified exhaustive test group order.
6248
# 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/scalar_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ static void secp256k1_scalar_inverse_var(secp256k1_scalar *r, const secp256k1_sc
253253
}
254254

255255
#ifdef USE_ENDOMORPHISM
256+
/* These parameters are generated using sage/gen_exhaustive_groups.sage. */
256257
#if defined(EXHAUSTIVE_TEST_ORDER)
257258
# if EXHAUSTIVE_TEST_ORDER == 13
258259
# define EXHAUSTIVE_TEST_LAMBDA 9

src/tests_exhaustive.c

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

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

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

0 commit comments

Comments
 (0)