Skip to content

Commit 20b8877

Browse files
committed
Add exhaustive test for group functions on a low-order subgroup
We observe that when changing the b-value in the elliptic curve formula `y^2 = x^3 + ax + b`, the group law is unchanged. Therefore our functions for secp256k1 will be correct if and only if they are correct when applied to the curve defined by `y^2 = x^3 + 4` defined over the same field. This curve has a point P of order 199. This commit adds a test which computes the subgroup generated by P and exhaustively checks that addition of every pair of points gives the correct result. Unfortunately we cannot test const-time scalar multiplication by the same mechanism. The reason is that these ecmult functions both compute a wNAF representation of the scalar, and this representation is tied to the order of the group. Testing with the incomplete version of gej_add_ge (found in 5de4c5d^) shows that this detects the incompleteness when adding P - 106P, which is exactly what we expected since 106 is a cube root of 1 mod 199.
1 parent 80773a6 commit 20b8877

7 files changed

+229
-4
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ bench_schnorr_verify
66
bench_recover
77
bench_internal
88
tests
9+
exhaustive_tests
910
gen_context
1011
*.exe
1112
*.so

Makefile.am

+12-1
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,23 @@ bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB)
8787
bench_internal_CPPFLAGS = -DSECP256K1_BUILD $(SECP_INCLUDES)
8888
endif
8989

90+
TESTS =
9091
if USE_TESTS
9192
noinst_PROGRAMS += tests
9293
tests_SOURCES = src/tests.c
9394
tests_CPPFLAGS = -DSECP256K1_BUILD -DVERIFY -I$(top_srcdir)/src -I$(top_srcdir)/include $(SECP_INCLUDES) $(SECP_TEST_INCLUDES)
9495
tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
9596
tests_LDFLAGS = -static
96-
TESTS = tests
97+
TESTS += tests
98+
endif
99+
100+
if USE_EXHAUSTIVE_TESTS
101+
noinst_PROGRAMS += exhaustive_tests
102+
exhaustive_tests_SOURCES = src/tests_exhaustive.c
103+
exhaustive_tests_CPPFLAGS = -DSECP256K1_BUILD -DVERIFY -I$(top_srcdir)/src $(SECP_INCLUDES)
104+
exhaustive_tests_LDADD = $(SECP_LIBS)
105+
exhaustive_tests_LDFLAGS = -static
106+
TESTS += exhaustive_tests
97107
endif
98108

99109
JAVAROOT=src/java
@@ -140,6 +150,7 @@ $(gen_context_BIN): $(gen_context_OBJECTS)
140150

141151
$(libsecp256k1_la_OBJECTS): src/ecmult_static_context.h
142152
$(tests_OBJECTS): src/ecmult_static_context.h
153+
$(exhaustive_tests_OBJECTS): src/ecmult_static_context.h
143154
$(bench_internal_OBJECTS): src/ecmult_static_context.h
144155

145156
src/ecmult_static_context.h: $(gen_context_BIN)

configure.ac

+6
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ AC_ARG_ENABLE(experimental,
104104
[use_experimental=$enableval],
105105
[use_experimental=no])
106106

107+
AC_ARG_ENABLE(exhaustive_tests,
108+
AS_HELP_STRING([--enable-exhaustive-tests],[compile exhaustive tests (default is yes)]),
109+
[use_exhaustive_tests=$enableval],
110+
[use_exhaustive_tests=yes])
111+
107112
AC_ARG_ENABLE(endomorphism,
108113
AS_HELP_STRING([--enable-endomorphism],[enable endomorphism (default is no)]),
109114
[use_endomorphism=$enableval],
@@ -456,6 +461,7 @@ AC_SUBST(SECP_LIBS)
456461
AC_SUBST(SECP_TEST_LIBS)
457462
AC_SUBST(SECP_TEST_INCLUDES)
458463
AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"])
464+
AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"])
459465
AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"])
460466
AM_CONDITIONAL([USE_ECMULT_STATIC_PRECOMPUTATION], [test x"$set_precomp" = x"yes"])
461467
AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])

src/ecmult_impl.h

+12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#ifndef _SECP256K1_ECMULT_IMPL_H_
88
#define _SECP256K1_ECMULT_IMPL_H_
99

10+
#include <string.h>
11+
1012
#include "group.h"
1113
#include "scalar.h"
1214
#include "ecmult.h"
@@ -16,6 +18,15 @@
1618
/* optimal for 128-bit and 256-bit exponents. */
1719
#define WINDOW_A 5
1820

21+
#if defined(EXHAUSTIVE_TEST_ORDER)
22+
# if EXHAUSTIVE_TEST_ORDER > 128
23+
# define WINDOW_G 8
24+
# elif EXHAUSTIVE_TEST_ORDER > 8
25+
# define WINDOW_G 4
26+
# else
27+
# define WINDOW_G 2
28+
# endif
29+
#else
1930
/** larger numbers may result in slightly better performance, at the cost of
2031
exponentially larger precomputed tables. */
2132
#ifdef USE_ENDOMORPHISM
@@ -25,6 +36,7 @@
2536
/** One table for window size 16: 1.375 MiB. */
2637
#define WINDOW_G 16
2738
#endif
39+
#endif
2840

2941
/** The number of entries a table with precomputed multiples needs to have. */
3042
#define ECMULT_TABLE_SIZE(w) (1 << ((w)-2))

src/field.h

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#error "Please select field implementation"
3131
#endif
3232

33+
#include "util.h"
34+
3335
/** Normalize a field element. */
3436
static void secp256k1_fe_normalize(secp256k1_fe *r);
3537

@@ -50,6 +52,9 @@ static int secp256k1_fe_normalizes_to_zero_var(secp256k1_fe *r);
5052
/** Set a field element equal to a small integer. Resulting field element is normalized. */
5153
static void secp256k1_fe_set_int(secp256k1_fe *r, int a);
5254

55+
/** Sets a field element equal to zero, initializing all fields. */
56+
static void secp256k1_fe_clear(secp256k1_fe *a);
57+
5358
/** Verify whether a field element is zero. Requires the input to be normalized. */
5459
static int secp256k1_fe_is_zero(const secp256k1_fe *a);
5560

src/group_impl.h

+22-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@
1111
#include "field.h"
1212
#include "group.h"
1313

14+
#if defined(EXHAUSTIVE_TEST_ORDER)
15+
# if EXHAUSTIVE_TEST_ORDER == 199
16+
const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
17+
0xFA7CC9A7, 0x0737F2DB, 0xA749DD39, 0x2B4FB069,
18+
0x3B017A7D, 0xA808C2F1, 0xFB12940C, 0x9EA66C18,
19+
0x78AC123A, 0x5ED8AEF3, 0x8732BC91, 0x1F3A2868,
20+
0x48DF246C, 0x808DAE72, 0xCFE52572, 0x7F0501ED
21+
);
22+
# else
23+
# error No known generator for the specified exhaustive test group order.
24+
# endif
25+
#else
1426
/** Generator for secp256k1, value 'g' defined in
1527
* "Standards for Efficient Cryptography" (SEC2) 2.7.1.
1628
*/
@@ -20,6 +32,7 @@ static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(
2032
0x483ADA77UL, 0x26A3C465UL, 0x5DA4FBFCUL, 0x0E1108A8UL,
2133
0xFD17B448UL, 0xA6855419UL, 0x9C47D08FUL, 0xFB10D4B8UL
2234
);
35+
#endif
2336

2437
static void secp256k1_ge_set_gej_zinv(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zi) {
2538
secp256k1_fe zi2;
@@ -145,9 +158,15 @@ static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp
145158

146159
static void secp256k1_gej_set_infinity(secp256k1_gej *r) {
147160
r->infinity = 1;
148-
secp256k1_fe_set_int(&r->x, 0);
149-
secp256k1_fe_set_int(&r->y, 0);
150-
secp256k1_fe_set_int(&r->z, 0);
161+
secp256k1_fe_clear(&r->x);
162+
secp256k1_fe_clear(&r->y);
163+
secp256k1_fe_clear(&r->z);
164+
}
165+
166+
static void secp256k1_ge_set_infinity(secp256k1_ge *r) {
167+
r->infinity = 1;
168+
secp256k1_fe_clear(&r->x);
169+
secp256k1_fe_clear(&r->y);
151170
}
152171

153172
static void secp256k1_gej_clear(secp256k1_gej *r) {

src/tests_exhaustive.c

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**********************************************************************
2+
* Copyright (c) 2015 Andrew Poelstra *
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+
#if defined HAVE_CONFIG_H
8+
#include "libsecp256k1-config.h"
9+
#endif
10+
11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
14+
#include <time.h>
15+
16+
#ifndef EXHAUSTIVE_TEST_ORDER
17+
#define EXHAUSTIVE_TEST_ORDER 199
18+
#endif
19+
20+
#include "include/secp256k1.h"
21+
#include "group.h"
22+
#include "secp256k1.c"
23+
#include "testrand_impl.h"
24+
25+
/** stolen from tests.c */
26+
void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) {
27+
CHECK(a->infinity == b->infinity);
28+
if (a->infinity) {
29+
return;
30+
}
31+
CHECK(secp256k1_fe_equal_var(&a->x, &b->x));
32+
CHECK(secp256k1_fe_equal_var(&a->y, &b->y));
33+
}
34+
35+
void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) {
36+
secp256k1_fe z2s;
37+
secp256k1_fe u1, u2, s1, s2;
38+
CHECK(a->infinity == b->infinity);
39+
if (a->infinity) {
40+
return;
41+
}
42+
/* Check a.x * b.z^2 == b.x && a.y * b.z^3 == b.y, to avoid inverses. */
43+
secp256k1_fe_sqr(&z2s, &b->z);
44+
secp256k1_fe_mul(&u1, &a->x, &z2s);
45+
u2 = b->x; secp256k1_fe_normalize_weak(&u2);
46+
secp256k1_fe_mul(&s1, &a->y, &z2s); secp256k1_fe_mul(&s1, &s1, &b->z);
47+
s2 = b->y; secp256k1_fe_normalize_weak(&s2);
48+
CHECK(secp256k1_fe_equal_var(&u1, &u2));
49+
CHECK(secp256k1_fe_equal_var(&s1, &s2));
50+
}
51+
52+
void random_fe(secp256k1_fe *x) {
53+
unsigned char bin[32];
54+
do {
55+
secp256k1_rand256(bin);
56+
if (secp256k1_fe_set_b32(x, bin)) {
57+
return;
58+
}
59+
} while(1);
60+
}
61+
/** END stolen from tests.c */
62+
63+
void test_exhaustive_addition(const secp256k1_ge *group, const secp256k1_gej *groupj, int order) {
64+
int i, j;
65+
66+
/* Sanity-check (and check infinity functions) */
67+
CHECK(secp256k1_ge_is_infinity(&group[0]));
68+
CHECK(secp256k1_gej_is_infinity(&groupj[0]));
69+
for (i = 1; i < order; i++) {
70+
CHECK(!secp256k1_ge_is_infinity(&group[i]));
71+
CHECK(!secp256k1_gej_is_infinity(&groupj[i]));
72+
}
73+
74+
/* Check all addition formulae */
75+
for (j = 0; j < order; j++) {
76+
secp256k1_fe fe_inv;
77+
secp256k1_fe_inv(&fe_inv, &groupj[j].z);
78+
for (i = 0; i < order; i++) {
79+
secp256k1_ge zless_gej;
80+
secp256k1_gej tmp;
81+
/* add_var */
82+
secp256k1_gej_add_var(&tmp, &groupj[i], &groupj[j], NULL);
83+
ge_equals_gej(&group[(i + j) % order], &tmp);
84+
/* add_ge */
85+
if (j > 0) {
86+
secp256k1_gej_add_ge(&tmp, &groupj[i], &group[j]);
87+
ge_equals_gej(&group[(i + j) % order], &tmp);
88+
}
89+
/* add_ge_var */
90+
secp256k1_gej_add_ge_var(&tmp, &groupj[i], &group[j], NULL);
91+
ge_equals_gej(&group[(i + j) % order], &tmp);
92+
/* add_zinv_var */
93+
zless_gej.infinity = groupj[j].infinity;
94+
zless_gej.x = groupj[j].x;
95+
zless_gej.y = groupj[j].y;
96+
secp256k1_gej_add_zinv_var(&tmp, &groupj[i], &zless_gej, &fe_inv);
97+
ge_equals_gej(&group[(i + j) % order], &tmp);
98+
}
99+
}
100+
101+
/* Check doubling */
102+
for (i = 0; i < order; i++) {
103+
secp256k1_gej tmp;
104+
if (i > 0) {
105+
secp256k1_gej_double_nonzero(&tmp, &groupj[i], NULL);
106+
ge_equals_gej(&group[(2 * i) % order], &tmp);
107+
}
108+
secp256k1_gej_double_var(&tmp, &groupj[i], NULL);
109+
ge_equals_gej(&group[(2 * i) % order], &tmp);
110+
}
111+
112+
/* Check negation */
113+
for (i = 1; i < order; i++) {
114+
secp256k1_ge tmp;
115+
secp256k1_gej tmpj;
116+
secp256k1_ge_neg(&tmp, &group[i]);
117+
ge_equals_ge(&group[order - i], &tmp);
118+
secp256k1_gej_neg(&tmpj, &groupj[i]);
119+
ge_equals_gej(&group[order - i], &tmpj);
120+
}
121+
}
122+
123+
void test_exhaustive_ecmult(secp256k1_context *ctx, secp256k1_ge *group, secp256k1_gej *groupj, int order) {
124+
int i, j;
125+
const int r_log = secp256k1_rand32() % order; /* TODO be less biased */
126+
for (j = 0; j < order; j++) {
127+
for (i = 0; i < order; i++) {
128+
secp256k1_gej tmp;
129+
secp256k1_scalar na, ng;
130+
secp256k1_scalar_set_int(&na, i);
131+
secp256k1_scalar_set_int(&ng, j);
132+
133+
secp256k1_ecmult(&ctx->ecmult_ctx, &tmp, &groupj[r_log], &na, &ng);
134+
ge_equals_gej(&group[(i * r_log + j) % order], &tmp);
135+
136+
/* TODO we cannot exhaustively test ecmult_const as it does a scalar
137+
* negation for even numbers, and our code is not designed to handle
138+
* such a small scalar modulus. */
139+
}
140+
}
141+
}
142+
143+
int main(void) {
144+
int i;
145+
secp256k1_gej groupj[EXHAUSTIVE_TEST_ORDER];
146+
secp256k1_ge group[EXHAUSTIVE_TEST_ORDER];
147+
148+
/* Build context */
149+
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
150+
151+
/* TODO set z = 1, then do num_tests runs with random z values */
152+
153+
/* Generate the entire group */
154+
secp256k1_ge_set_infinity(&group[0]);
155+
secp256k1_gej_set_infinity(&groupj[0]);
156+
for (i = 1; i < EXHAUSTIVE_TEST_ORDER; i++) {
157+
secp256k1_fe z;
158+
random_fe(&z);
159+
160+
secp256k1_gej_add_ge(&groupj[i], &groupj[i - 1], &secp256k1_ge_const_g);
161+
secp256k1_ge_set_gej(&group[i], &groupj[i]);
162+
secp256k1_gej_rescale(&groupj[i], &z);
163+
}
164+
165+
/* Run the tests */
166+
test_exhaustive_addition(group, groupj, EXHAUSTIVE_TEST_ORDER);
167+
test_exhaustive_ecmult(ctx, group, groupj, EXHAUSTIVE_TEST_ORDER);
168+
169+
return 0;
170+
}
171+

0 commit comments

Comments
 (0)