Skip to content

Commit 5006895

Browse files
committed
Merge #808: Exhaustive test improvements + exhaustive schnorrsig tests
8b7dcdd Add exhaustive test for extrakeys and schnorrsig (Pieter Wuille) 08d7d89 Make pubkey parsing test whether points are in the correct subgroup (Pieter Wuille) 87af00b Abstract out challenge computation in schnorrsig (Pieter Wuille) 63e1b2a Disable output buffering in tests_exhaustive.c (Pieter Wuille) 39f67dd Support splitting exhaustive tests across cores (Pieter Wuille) e99b26f Give exhaustive_tests count and seed cmdline inputs (Pieter Wuille) 49e6630 refactor: move RNG seeding to testrand (Pieter Wuille) b110c10 Change exhaustive test groups so they have a point with X=1 (Pieter Wuille) cec7b18 Select exhaustive lambda in function of order (Pieter Wuille) 78f6cdf Make the curve B constant a secp256k1_fe (Pieter Wuille) d7f39ae Delete gej_is_valid_var: unused outside tests (Pieter Wuille) 8bcd78c Make secp256k1_scalar_b32 detect overflow in scalar_low (Pieter Wuille) c498366 Move exhaustive tests for recovery to module (Pieter Wuille) be31791 Make group order purely compile-time in exhaustive tests (Pieter Wuille) Pull request description: A few miscellaneous improvements: * Just use EXHAUSTIVE_TEST_ORDER as order everywhere, rather than a variable * Move exhaustive tests for recovery module to the recovery module directory * Make `secp256k1_scalar_set_b32` detect overflow correctly for scalar_low (a comment in the recovery exhaustive test indicated why this was the case, but this looks incorrect). * Change the small test groups so that they include a point with X coordinate 1. * Initialize the RNG seed, allowing configurating from the cmdline, and report it. * Permit changing the number of iterations (re-randomizing for each). * Support splitting the work across cores from the cmdline. And a big one: * Add exhaustive tests for schnorrsig module (and limited ones for extrakeys). ACKs for top commit: real-or-random: ACK 8b7dcdd jonasnick: ACK 8b7dcdd Tree-SHA512: 18d7f362402085238faaced164c0ca34079717a477001fc0b13448b3529ea2ad705793a13b7a36f34bf12e9231fee11070f88cc51bfc2a83ca82aa13f7aaae71
2 parents d7838ba + 8b7dcdd commit 5006895

18 files changed

+874
-341
lines changed

sage/gen_exhaustive_groups.sage

+129
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.h

+11
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,15 @@ static void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_g
139139
/** Rescale a jacobian point by b which must be non-zero. Constant-time. */
140140
static void secp256k1_gej_rescale(secp256k1_gej *r, const secp256k1_fe *b);
141141

142+
/** Determine if a point (which is assumed to be on the curve) is in the correct (sub)group of the curve.
143+
*
144+
* In normal mode, the used group is secp256k1, which has cofactor=1 meaning that every point on the curve is in the
145+
* group, and this function returns always true.
146+
*
147+
* When compiling in exhaustive test mode, a slightly different curve equation is used, leading to a group with a
148+
* (very) small subgroup, and that subgroup is what is used for all cryptographic operations. In that mode, this
149+
* function checks whether a point that is on the curve is in fact also in that subgroup.
150+
*/
151+
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge);
152+
142153
#endif /* SECP256K1_GROUP_H */

src/group_impl.h

+52-64
Original file line numberDiff line numberDiff line change
@@ -11,49 +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 CURVE_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 int CURVE_B = 4;
49-
# 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
5036
static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
51-
0xedc60018, 0xa51a786b, 0x2ea91f4d, 0x4c9416c0,
52-
0x9de54c3b, 0xa1316554, 0x6cf4345c, 0x7277ef15,
53-
0x54cb1b6b, 0xdc8c1273, 0x087844ea, 0x43f4603e,
54-
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
5545
);
56-
static const int CURVE_B = 2;
5746
# else
5847
# error No known generator for the specified exhaustive test group order.
5948
# endif
@@ -68,7 +57,7 @@ static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
6857
0xFD17B448UL, 0xA6855419UL, 0x9C47D08FUL, 0xFB10D4B8UL
6958
);
7059

71-
static const int CURVE_B = 7;
60+
static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 7);
7261
#endif
7362

7463
static void secp256k1_ge_set_gej_zinv(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zi) {
@@ -219,14 +208,13 @@ static void secp256k1_ge_clear(secp256k1_ge *r) {
219208
}
220209

221210
static int secp256k1_ge_set_xquad(secp256k1_ge *r, const secp256k1_fe *x) {
222-
secp256k1_fe x2, x3, c;
211+
secp256k1_fe x2, x3;
223212
r->x = *x;
224213
secp256k1_fe_sqr(&x2, x);
225214
secp256k1_fe_mul(&x3, x, &x2);
226215
r->infinity = 0;
227-
secp256k1_fe_set_int(&c, CURVE_B);
228-
secp256k1_fe_add(&c, &x3);
229-
return secp256k1_fe_sqrt(&r->y, &c);
216+
secp256k1_fe_add(&x3, &secp256k1_fe_const_b);
217+
return secp256k1_fe_sqrt(&r->y, &x3);
230218
}
231219

232220
static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int odd) {
@@ -269,36 +257,15 @@ static int secp256k1_gej_is_infinity(const secp256k1_gej *a) {
269257
return a->infinity;
270258
}
271259

272-
static int secp256k1_gej_is_valid_var(const secp256k1_gej *a) {
273-
secp256k1_fe y2, x3, z2, z6;
274-
if (a->infinity) {
275-
return 0;
276-
}
277-
/** y^2 = x^3 + 7
278-
* (Y/Z^3)^2 = (X/Z^2)^3 + 7
279-
* Y^2 / Z^6 = X^3 / Z^6 + 7
280-
* Y^2 = X^3 + 7*Z^6
281-
*/
282-
secp256k1_fe_sqr(&y2, &a->y);
283-
secp256k1_fe_sqr(&x3, &a->x); secp256k1_fe_mul(&x3, &x3, &a->x);
284-
secp256k1_fe_sqr(&z2, &a->z);
285-
secp256k1_fe_sqr(&z6, &z2); secp256k1_fe_mul(&z6, &z6, &z2);
286-
secp256k1_fe_mul_int(&z6, CURVE_B);
287-
secp256k1_fe_add(&x3, &z6);
288-
secp256k1_fe_normalize_weak(&x3);
289-
return secp256k1_fe_equal_var(&y2, &x3);
290-
}
291-
292260
static int secp256k1_ge_is_valid_var(const secp256k1_ge *a) {
293-
secp256k1_fe y2, x3, c;
261+
secp256k1_fe y2, x3;
294262
if (a->infinity) {
295263
return 0;
296264
}
297265
/* y^2 = x^3 + 7 */
298266
secp256k1_fe_sqr(&y2, &a->y);
299267
secp256k1_fe_sqr(&x3, &a->x); secp256k1_fe_mul(&x3, &x3, &a->x);
300-
secp256k1_fe_set_int(&c, CURVE_B);
301-
secp256k1_fe_add(&x3, &c);
268+
secp256k1_fe_add(&x3, &secp256k1_fe_const_b);
302269
secp256k1_fe_normalize_weak(&x3);
303270
return secp256k1_fe_equal_var(&y2, &x3);
304271
}
@@ -704,4 +671,25 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) {
704671
return secp256k1_fe_is_quad_var(&yz);
705672
}
706673

674+
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) {
675+
#ifdef EXHAUSTIVE_TEST_ORDER
676+
secp256k1_gej out;
677+
int i;
678+
679+
/* A very simple EC multiplication ladder that avoids a dependecy on ecmult. */
680+
secp256k1_gej_set_infinity(&out);
681+
for (i = 0; i < 32; ++i) {
682+
secp256k1_gej_double_var(&out, &out, NULL);
683+
if ((((uint32_t)EXHAUSTIVE_TEST_ORDER) >> (31 - i)) & 1) {
684+
secp256k1_gej_add_ge_var(&out, &out, ge, NULL);
685+
}
686+
}
687+
return secp256k1_gej_is_infinity(&out);
688+
#else
689+
(void)ge;
690+
/* The real secp256k1 group has cofactor 1, so the subgroup is the entire curve. */
691+
return 1;
692+
#endif
693+
}
694+
707695
#endif /* SECP256K1_GROUP_IMPL_H */
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
include_HEADERS += include/secp256k1_extrakeys.h
22
noinst_HEADERS += src/modules/extrakeys/tests_impl.h
3+
noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h
34
noinst_HEADERS += src/modules/extrakeys/main_impl.h

src/modules/extrakeys/main_impl.h

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ int secp256k1_xonly_pubkey_parse(const secp256k1_context* ctx, secp256k1_xonly_p
3333
if (!secp256k1_ge_set_xo_var(&pk, &x, 0)) {
3434
return 0;
3535
}
36+
if (!secp256k1_ge_is_in_correct_subgroup(&pk)) {
37+
return 0;
38+
}
3639
secp256k1_xonly_pubkey_save(pubkey, &pk);
3740
return 1;
3841
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**********************************************************************
2+
* Copyright (c) 2020 Pieter Wuille *
3+
* Distributed under the MIT software license, see the accompanying *
4+
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
5+
**********************************************************************/
6+
7+
#ifndef _SECP256K1_MODULE_EXTRAKEYS_TESTS_EXHAUSTIVE_
8+
#define _SECP256K1_MODULE_EXTRAKEYS_TESTS_EXHAUSTIVE_
9+
10+
#include "src/modules/extrakeys/main_impl.h"
11+
#include "include/secp256k1_extrakeys.h"
12+
13+
static void test_exhaustive_extrakeys(const secp256k1_context *ctx, const secp256k1_ge* group) {
14+
secp256k1_keypair keypair[EXHAUSTIVE_TEST_ORDER - 1];
15+
secp256k1_pubkey pubkey[EXHAUSTIVE_TEST_ORDER - 1];
16+
secp256k1_xonly_pubkey xonly_pubkey[EXHAUSTIVE_TEST_ORDER - 1];
17+
int parities[EXHAUSTIVE_TEST_ORDER - 1];
18+
unsigned char xonly_pubkey_bytes[EXHAUSTIVE_TEST_ORDER - 1][32];
19+
int i;
20+
21+
for (i = 1; i < EXHAUSTIVE_TEST_ORDER; i++) {
22+
secp256k1_fe fe;
23+
secp256k1_scalar scalar_i;
24+
unsigned char buf[33];
25+
int parity;
26+
27+
secp256k1_scalar_set_int(&scalar_i, i);
28+
secp256k1_scalar_get_b32(buf, &scalar_i);
29+
30+
/* Construct pubkey and keypair. */
31+
CHECK(secp256k1_keypair_create(ctx, &keypair[i - 1], buf));
32+
CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey[i - 1], buf));
33+
34+
/* Construct serialized xonly_pubkey from keypair. */
35+
CHECK(secp256k1_keypair_xonly_pub(ctx, &xonly_pubkey[i - 1], &parities[i - 1], &keypair[i - 1]));
36+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, xonly_pubkey_bytes[i - 1], &xonly_pubkey[i - 1]));
37+
38+
/* Parse the xonly_pubkey back and verify it matches the previously serialized value. */
39+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey[i - 1], xonly_pubkey_bytes[i - 1]));
40+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf, &xonly_pubkey[i - 1]));
41+
CHECK(memcmp(xonly_pubkey_bytes[i - 1], buf, 32) == 0);
42+
43+
/* Construct the xonly_pubkey from the pubkey, and verify it matches the same. */
44+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pubkey[i - 1], &parity, &pubkey[i - 1]));
45+
CHECK(parity == parities[i - 1]);
46+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf, &xonly_pubkey[i - 1]));
47+
CHECK(memcmp(xonly_pubkey_bytes[i - 1], buf, 32) == 0);
48+
49+
/* Compare the xonly_pubkey bytes against the precomputed group. */
50+
secp256k1_fe_set_b32(&fe, xonly_pubkey_bytes[i - 1]);
51+
CHECK(secp256k1_fe_equal_var(&fe, &group[i].x));
52+
53+
/* Check the parity against the precomputed group. */
54+
fe = group[i].y;
55+
secp256k1_fe_normalize_var(&fe);
56+
CHECK(secp256k1_fe_is_odd(&fe) == parities[i - 1]);
57+
58+
/* Verify that the higher half is identical to the lower half mirrored. */
59+
if (i > EXHAUSTIVE_TEST_ORDER / 2) {
60+
CHECK(memcmp(xonly_pubkey_bytes[i - 1], xonly_pubkey_bytes[EXHAUSTIVE_TEST_ORDER - i - 1], 32) == 0);
61+
CHECK(parities[i - 1] == 1 - parities[EXHAUSTIVE_TEST_ORDER - i - 1]);
62+
}
63+
}
64+
65+
/* TODO: keypair/xonly_pubkey tweak tests */
66+
}
67+
68+
#endif

0 commit comments

Comments
 (0)