Commit | Line | Data |
---|---|---|
0bcb8184 MW |
1 | /* |
2 | * xdh-test.c: test harness elliptic-curve Diffie--Hellman | |
3 | * | |
4 | * (The implementations originally came with different test arrangements, | |
5 | * with complicated external dependencies. This file replicates the original | |
6 | * tests, but without the dependencies.) | |
7 | */ | |
8 | /* | |
9 | * This file is Free Software. It was originally written for secnet. | |
10 | * | |
11 | * Copyright 2017 Mark Wooding | |
12 | * | |
13 | * You may redistribute secnet as a whole and/or modify it under the | |
14 | * terms of the GNU General Public License as published by the Free | |
15 | * Software Foundation; either version 3, or (at your option) any | |
16 | * later version. | |
17 | * | |
18 | * You may redistribute this file and/or modify it under the terms of | |
19 | * the GNU General Public License as published by the Free Software | |
20 | * Foundation; either version 2, or (at your option) any later | |
21 | * version. | |
22 | * | |
23 | * This software is distributed in the hope that it will be useful, | |
24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
26 | * GNU General Public License for more details. | |
27 | * | |
28 | * You should have received a copy of the GNU General Public License | |
29 | * along with this software; if not, see | |
30 | * https://www.gnu.org/licenses/gpl.html. | |
31 | */ | |
32 | ||
33 | #include <assert.h> | |
34 | #include <errno.h> | |
35 | #include <ctype.h> | |
36 | #include <stdarg.h> | |
37 | #include <stdint.h> | |
38 | #include <stdio.h> | |
39 | #include <stdlib.h> | |
40 | #include <string.h> | |
41 | ||
42 | #include "secnet.h" | |
43 | ||
44 | #include "f25519.h" | |
45 | #include "fgoldi.h" | |
46 | ||
47 | #include "x25519.h" | |
48 | #include "x448.h" | |
49 | ||
50 | #define GLUE(x, y) GLUE_(x, y) | |
51 | #define GLUE_(x, y) x##y | |
52 | #define FIELDOP(op) GLUE(FIELD, _##op) | |
53 | ||
54 | #define f25519_FESZ 32u | |
55 | #define fgoldi_FESZ 56u | |
56 | ||
57 | /* Define some global variables we shouldn't need. | |
58 | * | |
59 | * Annoyingly, `secnet.h' declares static pointers and initializes them to | |
60 | * point to some external variables. At `-O0', GCC doesn't optimize these | |
61 | * away, so there's a link-time dependency on these variables. Define them | |
62 | * here, so that `f25519.c' and `f448.c' can find them. | |
63 | * | |
64 | * (Later GCC has `-Og', which optimizes without making debugging a | |
65 | * nightmare, but I'm not running that version here. Note that `serpent.c' | |
66 | * doesn't have this problem because it defines its own word load and store | |
67 | * operations to cope with its endian weirdness, whereas the field arithmetic | |
68 | * uses `unaligned.h' which manages to include `secnet.h'.) | |
69 | */ | |
70 | uint64_t now_global; | |
71 | struct timeval tv_now_global; | |
72 | ||
73 | union reg { | |
74 | long i; | |
75 | unsigned long u; | |
76 | uint8_t fe[FIELDOP(FESZ)]; | |
77 | }; | |
78 | ||
79 | struct regty { | |
80 | void (*parse)(union reg *r, char *p); | |
81 | void (*dump)(FILE *fp, const union reg *r); | |
82 | int (*eq)(const union reg *r, const union reg *rr); | |
83 | }; | |
84 | ||
85 | struct regdef { | |
86 | const char *name; | |
87 | const struct regty *ty; | |
88 | int r; | |
89 | }; | |
90 | ||
91 | struct test { | |
92 | const char *name; | |
93 | void (*fn)(union reg *out, const union reg *in); | |
94 | const struct regdef *regs; | |
95 | }; | |
96 | ||
97 | static int lno; | |
98 | ||
99 | static void bail(const char *msg, ...) | |
100 | { | |
101 | va_list ap; | |
102 | va_start(ap, msg); | |
103 | fprintf(stderr, "unexpected error (line %d): ", lno); | |
104 | vfprintf(stderr, msg, ap); | |
105 | va_end(ap); | |
106 | fputc('\n', stderr); | |
107 | exit(3); | |
108 | } | |
109 | ||
110 | static void parse_hex(uint8_t *b, size_t sz, char *p) | |
111 | { | |
112 | size_t n = strlen(p); | |
113 | unsigned i; | |
114 | char bb[3]; | |
115 | ||
116 | if (n%2) bail("bad hex (odd number of nibbles)"); | |
117 | else if (n/2 != sz) bail("bad hex (want %zu bytes, found %zu", sz, n/2); | |
118 | while (sz) { | |
119 | for (i = 0; i < 2; i++) { | |
120 | if (!isxdigit((unsigned char)p[i])) | |
121 | bail("bad hex digit `%c'", p[i]); | |
122 | bb[i] = p[i]; | |
123 | } | |
124 | bb[2] = 0; | |
125 | p += 2; | |
126 | *b++ = strtoul(bb, 0, 16); sz--; | |
127 | } | |
128 | } | |
129 | ||
130 | static void dump_hex(FILE *fp, const uint8_t *b, size_t sz) | |
131 | { while (sz--) fprintf(fp, "%02x", *b++); fputc('\n', fp); } | |
132 | ||
133 | static void parse_int(union reg *r, char *p) | |
134 | { | |
135 | char *q; | |
136 | ||
137 | errno = 0; | |
138 | r->i = strtol(p, &q, 0); | |
139 | if (*q || errno) bail("bad integer `%s'", p); | |
140 | } | |
141 | ||
142 | static void dump_int(FILE *fp, const union reg *r) | |
143 | { fprintf(fp, "%ld\n", r->i); } | |
144 | ||
145 | static int eq_int(const union reg *r, const union reg *rr) | |
146 | { return (r->i == rr->i); } | |
147 | ||
148 | static const struct regty regty_int = { parse_int, dump_int, eq_int }; | |
149 | ||
150 | static void parse_uint(union reg *r, char *p) | |
151 | { | |
152 | char *q; | |
153 | ||
154 | errno = 0; | |
155 | r->u = strtoul(p, &q, 0); | |
156 | if (*q || errno) bail("bad integer `%s'", p); | |
157 | } | |
158 | ||
159 | static void dump_uint(FILE *fp, const union reg *r) | |
160 | { fprintf(fp, "%lu\n", r->u); } | |
161 | ||
162 | static int eq_uint(const union reg *r, const union reg *rr) | |
163 | { return (r->u == rr->u); } | |
164 | ||
165 | static const struct regty regty_uint = { parse_uint, dump_uint, eq_uint }; | |
166 | ||
167 | static void parse_fe(union reg *r, char *p) | |
168 | { parse_hex(r->fe, sizeof(r->fe), p); } | |
169 | ||
170 | static void dump_fe(FILE *fp, const union reg *r) | |
171 | { dump_hex(fp, r->fe, sizeof(r->fe)); } | |
172 | ||
173 | static int eq_fe(const union reg *r, const union reg *rr) | |
174 | { return (memcmp(r->fe, rr->fe, sizeof(r->fe)) == 0); } | |
175 | ||
176 | static const struct regty regty_fe = { parse_fe, dump_fe, eq_fe }; | |
177 | ||
178 | enum { | |
179 | RZ, RXX = RZ, RYY, NROUT, | |
180 | RX = NROUT, RY, RA = RY, RK = RY, RM, RN = RM, NREG | |
181 | }; | |
182 | ||
183 | #define BINOP(op) \ | |
184 | static void test_##op(union reg *out, const union reg *in) \ | |
185 | { \ | |
186 | FIELD x, y, z; \ | |
187 | \ | |
188 | FIELDOP(load)(&x, in[RX].fe); \ | |
189 | FIELDOP(load)(&y, in[RY].fe); \ | |
190 | FIELDOP(op)(&z, &x, &y); \ | |
191 | FIELDOP(store)(out[RZ].fe, &z); \ | |
192 | } | |
193 | ||
194 | #define UNOP(op) \ | |
195 | static void test_##op(union reg *out, const union reg *in) \ | |
196 | { \ | |
197 | FIELD x, z; \ | |
198 | \ | |
199 | FIELDOP(load)(&x, in[RX].fe); \ | |
200 | FIELDOP(op)(&z, &x); \ | |
201 | FIELDOP(store)(out[RZ].fe, &z); \ | |
202 | } | |
203 | ||
204 | BINOP(add) | |
205 | BINOP(sub) | |
206 | BINOP(mul) | |
207 | UNOP(sqr) | |
208 | UNOP(inv) | |
209 | ||
210 | static void test_mulconst(union reg *out, const union reg *in) | |
211 | { | |
212 | FIELD x, z; | |
213 | ||
214 | FIELDOP(load)(&x, in[RX].fe); | |
215 | FIELDOP(mulconst)(&z, &x, in[RA].i); | |
216 | FIELDOP(store)(out[RZ].fe, &z); | |
217 | } | |
218 | ||
219 | static void test_condswap(union reg *out, const union reg *in) | |
220 | { | |
221 | FIELD x, y; | |
222 | ||
223 | FIELDOP(load)(&x, in[RX].fe); | |
224 | FIELDOP(load)(&y, in[RY].fe); | |
225 | FIELDOP(condswap)(&x, &y, in[RM].u); | |
226 | FIELDOP(store)(out[RXX].fe, &x); | |
227 | FIELDOP(store)(out[RYY].fe, &y); | |
228 | } | |
229 | ||
230 | static void test_xdh(union reg *out, const union reg *in) | |
231 | { XDH(out[RZ].fe, in[RK].fe, in[RX].fe); } | |
232 | ||
233 | static void test_xdhmct(union reg *out, const union reg *in) | |
234 | { | |
235 | uint8_t b0[FIELDOP(FESZ)], b1[FIELDOP(FESZ)], *k = b0, *x = b1, *t; | |
236 | unsigned long i, n; | |
237 | ||
238 | memcpy(b0, in[RK].fe, sizeof(b0)); | |
239 | memcpy(b1, in[RX].fe, sizeof(b1)); | |
240 | n = in[RM].u; | |
241 | for (i = 0; i < n; i++) { | |
242 | XDH(x, k, x); | |
243 | t = x; x = k; k = t; | |
244 | } | |
245 | memcpy(out[RZ].fe, k, sizeof(b0)); | |
246 | } | |
247 | ||
248 | #define REG_X { "x", ®ty_fe, RX } | |
249 | #define REG_Y { "y", ®ty_fe, RY } | |
250 | #define REG_A { "a", ®ty_int, RA } | |
251 | #define REG_M { "m", ®ty_uint, RM } | |
252 | #define REG_XX { "xx", ®ty_fe, RXX } | |
253 | #define REG_YY { "yy", ®ty_fe, RYY } | |
254 | #define REG_Z { "z", ®ty_fe, RZ } | |
255 | #define REG_BIGX { "X", ®ty_fe, RX } | |
256 | #define REG_BIGZ { "Z", ®ty_fe, RZ } | |
257 | #define REG_K { "k", ®ty_fe, RK } | |
258 | #define REG_N { "n", ®ty_uint, RN } | |
259 | #define REGLIST_END { 0 } | |
260 | static const struct regdef | |
261 | unop_regs[] = { REG_X, REG_Z, REGLIST_END }, | |
262 | binop_regs[] = { REG_X, REG_Y, REG_Z, REGLIST_END }, | |
263 | mulconst_regs[] = { REG_X, REG_A, REG_Z, REGLIST_END }, | |
264 | condswap_regs[] = { REG_X, REG_Y, REG_M, REG_XX, REG_YY, REGLIST_END }, | |
265 | xdh_regs[] = { REG_K, REG_BIGX, REG_BIGZ, REGLIST_END }, | |
266 | xdhmct_regs[] = { REG_K, REG_BIGX, REG_N, REG_BIGZ, REGLIST_END }; | |
267 | ||
268 | static const struct test tests[] = { | |
269 | { "add", test_add, binop_regs }, | |
270 | { "sub", test_sub, binop_regs }, | |
271 | { "condswap", test_condswap, condswap_regs }, | |
272 | { "mulconst", test_mulconst, mulconst_regs }, | |
273 | { "mul", test_mul, binop_regs }, | |
274 | { "sqr", test_sqr, unop_regs }, | |
275 | { "inv", test_inv, unop_regs }, | |
276 | { "xdh", test_xdh, xdh_regs }, | |
277 | { "xdh-mct", test_xdhmct, xdhmct_regs }, | |
278 | { 0 } | |
279 | }; | |
280 | ||
281 | static int check(const struct test *test, unsigned iat, | |
282 | union reg *out, const union reg *in) | |
283 | { | |
284 | const struct regdef *rdef; | |
285 | int ok = 1; | |
286 | ||
287 | if (!iat) return (0); | |
288 | assert(test); | |
289 | for (rdef = test->regs; rdef->name; rdef++) | |
290 | if (!(iat & (1 << rdef->r))) | |
291 | bail("register `%s' not set", rdef->name); | |
292 | test->fn(out, in); | |
293 | for (rdef = test->regs; rdef->name; rdef++) { | |
294 | if (rdef->r >= NROUT) continue; | |
295 | if (!rdef->ty->eq(&in[rdef->r], &out[rdef->r])) ok = 0; | |
296 | } | |
297 | if (ok) return (0); | |
298 | ||
299 | fprintf(stderr, "failed `%s'\n", test->name); | |
300 | for (rdef = test->regs; rdef->name; rdef++) { | |
301 | if (rdef->r >= NROUT) { | |
302 | fprintf(stderr, "\tinput `%s' = ", rdef->name); | |
303 | rdef->ty->dump(stderr, &in[rdef->r]); | |
304 | } else { | |
305 | fprintf(stderr, "\texpected `%s' = ", rdef->name); | |
306 | rdef->ty->dump(stderr, &in[rdef->r]); | |
307 | fprintf(stderr, "\tcomputed `%s' = ", rdef->name); | |
308 | rdef->ty->dump(stderr, &out[rdef->r]); | |
309 | } | |
310 | } | |
311 | return (-1); | |
312 | } | |
313 | ||
314 | int main(void) | |
315 | { | |
316 | char linebuf[256]; | |
317 | char *p; | |
318 | const char *q; | |
319 | size_t n; | |
320 | unsigned iat = 0, rbit; | |
321 | const struct test *test = 0; | |
322 | union reg in[NREG], out[NROUT]; | |
323 | const struct regdef *rdef; | |
324 | int rc = 0; | |
325 | ||
326 | lno = 0; | |
327 | while (fgets(linebuf, sizeof(linebuf), stdin)) { | |
328 | lno++; | |
329 | n = strlen(linebuf); assert(n); | |
330 | if (linebuf[n - 1] != '\n') bail("line too long"); | |
331 | linebuf[n - 1] = 0; | |
332 | p = linebuf; | |
333 | while (isspace((unsigned char)*p)) p++; | |
334 | if (*p == '#') continue; | |
335 | if (!*p) { | |
336 | if (check(test, iat, out, in)) rc = 2; | |
337 | iat = 0; | |
338 | continue; | |
339 | } | |
340 | q = p; | |
341 | while (*p && !isspace((unsigned char)*p)) p++; | |
342 | if (!*p) bail("missing argument"); | |
343 | *p++ = 0; | |
344 | while (isspace((unsigned char)*p)) p++; | |
345 | if (strcmp(q, "test") == 0) { | |
346 | if (check(test, iat, out, in)) rc = 2; | |
347 | iat = 0; | |
348 | for (test = tests; test->name; test++) | |
349 | if (strcmp(p, test->name) == 0) goto found_test; | |
350 | bail("unknown test `%s'", p); | |
351 | found_test: | |
352 | continue; | |
353 | } | |
354 | if (!test) bail("no test defined yet"); | |
355 | for (rdef = test->regs; rdef->name; rdef++) | |
356 | if (strcmp(q, rdef->name) == 0) goto found_reg; | |
357 | bail("unknown register `%s'", q); | |
358 | found_reg: | |
359 | rbit = 1 << rdef->r; | |
360 | if (iat & rbit) | |
361 | bail("register `%s' already set", rdef->name); | |
362 | rdef->ty->parse(&in[rdef->r], p); | |
363 | iat |= rbit; | |
364 | } | |
365 | if (check(test, iat, out, in)) rc = 2; | |
366 | return (rc); | |
367 | } |