From: Mark Wooding Date: Wed, 25 Sep 2019 19:38:06 +0000 (+0100) Subject: @@@ crypto-test X-Git-Url: https://git.distorted.org.uk/~mdw/secnet/commitdiff_plain/a4c6c9bad5c2965cf970071de0cb2f3179c22b50 @@@ crypto-test --- diff --git a/Makefile.in b/Makefile.in index a130c20..7b58f75 100644 --- a/Makefile.in +++ b/Makefile.in @@ -146,7 +146,9 @@ endif check: eax-aes-test.confirm eax-serpent-test.confirm \ eax-serpentbe-test.confirm check-ipaddrset \ - msgcode-test.confirm x25519-test.confirm x448-test.confirm + msgcode-test.confirm \ + f25519-test.confirm x25519-test.confirm \ + fgoldi-test.confirm x448-test.confirm version.c: Makefile echo "#include \"secnet.h\"" >$@.new @@ -176,8 +178,16 @@ msgcode-test.confirm: msgcode-test XDH_FUNCS = x25519 x448 x25519_FIELD = f25519 x448_FIELD = fgoldi +XDH_FIELDS = $(foreach f,$(XDH_FUNCS),$($f_FIELD)) -$(addsuffix -test, $(XDH_FUNCS)): %-test: %-test.o %.o +$(addsuffix -test, $(XDH_FIELDS)): %-test: %-test.o %.o crypto-test.o + $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $^ + +$(addsuffix -test.o, $(XDH_FIELDS)): %-test.o: ec-field-test.c + $(CC) $(CPPFLAGS) $(ALL_CFLAGS) -c \ + -DFIELD=$* $< -o $@ + +$(addsuffix -test, $(XDH_FUNCS)): %-test: %-test.o %.o crypto-test.o $(CC) $(LDFLAGS) $(ALL_CFLAGS) -o $@ $^ x25519-test: f25519.o x448-test: fgoldi.o @@ -187,7 +197,8 @@ $(addsuffix -test.o, $(XDH_FUNCS)): %-test.o: xdh-test.c -DXDH=$* -DFIELD=$($*_FIELD) \ $< -o $@ -$(addsuffix -test.confirm, $(XDH_FUNCS)): %-test.confirm: %-test %-tests.in +$(addsuffix -test.confirm, $(XDH_FUNCS) $(XDH_FIELDS)): \ + %-test.confirm: %-test %-tests.in ./$*-test <$(srcdir)/$*-tests.in touch $@ diff --git a/crypto-test.c b/crypto-test.c new file mode 100644 index 0000000..137f7a3 --- /dev/null +++ b/crypto-test.c @@ -0,0 +1,420 @@ +/* + * crypto-test.c: common test vector processing + */ +/* + * This file is Free Software. It was originally written for secnet. + * + * Copyright 2017 Mark Wooding + * + * You may redistribute secnet as a whole and/or modify it under the + * terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3, or (at your option) any + * later version. + * + * You may redistribute this file and/or modify it under the terms of + * the GNU General Public License as published by the Free Software + * Foundation; either version 2, or (at your option) any later + * version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, see + * https://www.gnu.org/licenses/gpl.html. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "secnet.h" +#include "util.h" + +#include "crypto-test.h" + +/*----- Utilities ---------------------------------------------------------*/ + +static void *xmalloc(size_t sz) +{ + void *p; + + if (!sz) return 0; + p = malloc(sz); + if (!p) { + fprintf(stderr, "out of memory!\n"); + exit(2); + } + return p; +} + +static void *xrealloc(void *p, size_t sz) +{ + void *q; + + if (!sz) { free(p); return 0; } + else if (!p) return xmalloc(sz); + q = realloc(p, sz); + if (!q) { + fprintf(stderr, "out of memory!\n"); + exit(2); + } + return q; +} + +static int lno; + +void bail(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + fprintf(stderr, "unexpected error (line %d): ", lno); + vfprintf(stderr, msg, ap); + va_end(ap); + fputc('\n', stderr); + exit(2); +} + +struct linebuf { + char *p; + size_t sz; +}; +#define LINEBUF_INIT { 0, 0 }; + +static int read_line(struct linebuf *b, FILE *fp) +{ + size_t n = 0; + int ch; + + ch = getc(fp); if (ch == EOF) return EOF; + for (;;) { + if (n >= b->sz) { + b->sz = b->sz ? 2*b->sz : 64; + b->p = xrealloc(b->p, b->sz); + } + if (ch == EOF || ch == '\n') { b->p[n++] = 0; return 0; } + b->p[n++] = ch; + ch = getc(fp); + } +} + +void parse_hex(uint8_t *b, size_t sz, char *p) +{ + size_t n = strlen(p); + unsigned i; + char bb[3]; + + if (n%2) bail("bad hex (odd number of nibbles)"); + else if (n/2 != sz) bail("bad hex (want %zu bytes, found %zu)", sz, n/2); + while (sz) { + for (i = 0; i < 2; i++) { + if (!isxdigit((unsigned char)p[i])) + bail("bad hex digit `%c'", p[i]); + bb[i] = p[i]; + } + bb[2] = 0; + p += 2; + *b++ = strtoul(bb, 0, 16); sz--; + } +} + +void dump_hex(FILE *fp, const uint8_t *b, size_t sz) + { while (sz--) fprintf(fp, "%02x", *b++); fputc('\n', fp); } + +void trivial_regty_init(union regval *v) { ; } +void trivial_regty_release(union regval *v) { ; } + +/* Define some global variables we shouldn't need. + * + * Annoyingly, `secnet.h' declares static pointers and initializes them to + * point to some external variables. At `-O0', GCC doesn't optimize these + * away, so there's a link-time dependency on these variables. Define them + * here, so that `f25519.c' and `f448.c' can find them. + * + * (Later GCC has `-Og', which optimizes without making debugging a + * nightmare, but I'm not running that version here. Note that `serpent.c' + * doesn't have this problem because it defines its own word load and store + * operations to cope with its endian weirdness, whereas the field arithmetic + * uses `unaligned.h' which manages to include `secnet.h'.) + */ +uint64_t now_global; +struct timeval tv_now_global; + +/* Bletch. util.c is a mess of layers. */ +int consttime_memeq(const void *s1in, const void *s2in, size_t n) +{ + const uint8_t *s1=s1in, *s2=s2in; + register volatile uint8_t accumulator=0; + + while (n-- > 0) { + accumulator |= (*s1++ ^ *s2++); + } + accumulator |= accumulator >> 4; /* constant-time */ + accumulator |= accumulator >> 2; /* boolean canonicalisation */ + accumulator |= accumulator >> 1; + accumulator &= 1; + accumulator ^= 1; + return accumulator; +} + +/*----- Built-in types ----------------------------------------------------*/ + +/* Signed integer. */ + +static void parse_int(union regval *v, char *p) +{ + char *q; + + errno = 0; + v->i = strtol(p, &q, 0); + if (*q || errno) bail("bad integer `%s'", p); +} + +static void dump_int(FILE *fp, const union regval *v) + { fprintf(fp, "%ld\n", v->i); } + +static int eq_int(const union regval *v0, const union regval *v1) + { return (v0->i == v1->i); } + +const struct regty regty_int = { + trivial_regty_init, + parse_int, + dump_int, + eq_int, + trivial_regty_release +}; + +/* Unsigned integer. */ + +static void parse_uint(union regval *v, char *p) +{ + char *q; + + errno = 0; + v->u = strtoul(p, &q, 0); + if (*q || errno) bail("bad integer `%s'", p); +} + +static void dump_uint(FILE *fp, const union regval *v) + { fprintf(fp, "%lu\n", v->u); } + +static int eq_uint(const union regval *v0, const union regval *v1) + { return (v0->u == v1->u); } + +const struct regty regty_uint = { + trivial_regty_init, + parse_uint, + dump_uint, + eq_uint, + trivial_regty_release +}; + +/* Byte string, as hex. */ + +void allocate_bytes(union regval *v, size_t sz) + { v->bytes.p = xmalloc(sz); v->bytes.sz = sz; } + +static void init_bytes(union regval *v) { v->bytes.p = 0; v->bytes.sz = 0; } + +static void parse_bytes(union regval *v, char *p) +{ + size_t n = strlen(p); + + allocate_bytes(v, n/2); + parse_hex(v->bytes.p, v->bytes.sz, p); +} + +static void dump_bytes(FILE *fp, const union regval *v) + { dump_hex(fp, v->bytes.p, v->bytes.sz); } + +static int eq_bytes(const union regval *v0, const union regval *v1) +{ + return v0->bytes.sz == v1->bytes.sz && + !memcmp(v0->bytes.p, v1->bytes.p, v0->bytes.sz); +} + +static void release_bytes(union regval *v) { free(v->bytes.p); } + +const struct regty regty_bytes = { + init_bytes, + parse_bytes, + dump_bytes, + eq_bytes, + release_bytes +}; + +/*----- Core test machinery -----------------------------------------------*/ + +/* Say that a register is `reset' by releasing and then re-initializing it. + * While there is a current test, all of that test's registers are + * initialized. The input registers are reset at the end of `check', ready + * for the next test to load new values. The output registers are reset at + * the end of `check_test_output', so that a test runner can run a test + * multiple times against the same test input, but with different context + * data. + */ + +#define REG(rvec, i) \ + ((struct reg *)((unsigned char *)state->rvec + (i)*state->regsz)) + +void check_test_output(struct test_state *state, const struct test *test) +{ + const struct regdef *def; + struct reg *reg, *in, *out; + int ok = 1; + int match; + + for (def = test->regs; def->name; def++) { + if (def->i >= state->nrout) continue; + in = REG(in, def->i); out = REG(out, def->i); + if (!def->ty->eq(&in->v, &out->v)) ok = 0; + } + if (ok) + state->win++; + else { + printf("failed test `%s'\n", test->name); + for (def = test->regs; def->name; def++) { + in = REG(in, def->i); + if (def->i >= state->nrout) { + printf("\t input `%s' = ", def->name); + def->ty->dump(stdout, &in->v); + } else { + out = REG(out, def->i); + match = def->ty->eq(&in->v, &out->v); + printf("\t%s `%s' = ", + match ? " output" : "expected", def->name); + def->ty->dump(stdout, &in->v); + if (!match) { + printf("\tcomputed `%s' = ", def->name); + def->ty->dump(stdout, &out->v); + } + } + } + state->lose++; + } + + for (def = test->regs; def->name; def++) { + if (def->i >= state->nrout) continue; + reg = REG(out, def->i); + def->ty->release(®->v); def->ty->init(®->v); + } +} + +void run_test(struct test_state *state, const struct test *test) +{ + test->fn(state->out, state->in, 0); + check_test_output(state, test); +} + +static void check(struct test_state *state, const struct test *test) +{ + const struct regdef *def, *miss = 0; + struct reg *reg; + int any = 0; + + if (!test) return; + for (def = test->regs; def->name; def++) { + reg = REG(in, def->i); + if (reg->f®F_LIVE) any = 1; + else if (!miss && !(def->f®F_OPT)) miss = def; + } + if (!any) return; + if (miss) + bail("register `%s' not set in test `%s'", def->name, test->name); + + test->run(state, test); + + for (def = test->regs; def->name; def++) { + reg = REG(in, def->i); + reg->f = 0; def->ty->release(®->v); def->ty->init(®->v); + } +} + +int run_test_suite(unsigned nrout, unsigned nreg, size_t regsz, + const struct test *tests, FILE *fp) +{ + struct linebuf buf = LINEBUF_INIT; + struct test_state state[1]; + const struct test *test; + const struct regdef *def; + struct reg *reg; + char *p; + const char *q; + int total; + size_t n; + + for (test = tests; test->name; test++) + for (def = test->regs; def->name; def++) + assert(def->i < nreg); + + state->in = xmalloc(nreg*regsz); + state->out = xmalloc(nrout*regsz); + state->nrout = nrout; + state->nreg = nreg; + state->regsz = regsz; + state->win = state->lose = 0; + + test = 0; + lno = 0; + while (!read_line(&buf, fp)) { + lno++; + p = buf.p; n = strlen(buf.p); + + while (isspace((unsigned char)*p)) p++; + if (*p == '#') continue; + if (!*p) { check(state, test); continue; } + + q = p; + while (*p && !isspace((unsigned char)*p)) p++; + if (*p) *p++ = 0; + + if (!strcmp(q, "test")) { + if (!*p) bail("missing argument"); + check(state, test); + if (test) { + for (def = test->regs; def->name; def++) { + def->ty->release(®(in, def->i)->v); + if (def->i < state->nrout) + def->ty->release(®(out, def->i)->v); + } + } + for (test = tests; test->name; test++) + if (!strcmp(p, test->name)) goto found_test; + bail("unknown test `%s'", p); + found_test: + for (def = test->regs; def->name; def++) { + reg = REG(in, def->i); + reg->f = 0; def->ty->init(®->v); + if (def->i < state->nrout) { + reg = REG(out, def->i); + reg->f = 0; def->ty->init(®->v); + } + } + continue; + } + + if (!test) bail("no current test"); + for (def = test->regs; def->name; def++) + if (!strcmp(q, def->name)) goto found_reg; + bail("unknown register `%s' in test `%s'", q, test->name); + found_reg: + reg = REG(in, def->i); + if (reg->f®F_LIVE) bail("register `%s' already set", def->name); + def->ty->parse(®->v, p); reg->f |= REGF_LIVE; + } + check(state, test); + + total = state->win + state->lose; + if (!state->lose) + printf("PASSED all %d test%s\n", state->win, total == 1 ? "" : "s"); + else + printf("FAILED %d of %d test%s\n", state->lose, total, + total == 1 ? "" : "s"); + return state->lose ? 1 : 0; +} diff --git a/crypto-test.h b/crypto-test.h new file mode 100644 index 0000000..e7ca777 --- /dev/null +++ b/crypto-test.h @@ -0,0 +1,137 @@ +/* + * crypto-test.h: common test vector processing + */ +/* + * This file is Free Software. It was originally written for secnet. + * + * Copyright 2017 Mark Wooding + * + * You may redistribute secnet as a whole and/or modify it under the + * terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3, or (at your option) any + * later version. + * + * You may redistribute this file and/or modify it under the terms of + * the GNU General Public License as published by the Free Software + * Foundation; either version 2, or (at your option) any later + * version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, see + * https://www.gnu.org/licenses/gpl.html. + */ + +#ifndef crypto_test_h +#define crypto_test_h + +/* Basic model. + * + * There is a collection of `registers', each of which can store a value. + * Some registers are designated as `input': their values are set while + * reading the test vector. Other registers are designated as `output': the + * test function is expected to calculate their values, which are then + * compared against final values supplied by the test vector. Any + * discrepancies are reported. + * + * While a test suite is running, there is a single vector of registers, and + * registers are identified by index, starting from zero. The number of + * registers, `nreg', and a threshold `nrout', are defined by the test suite: + * registers with index less than `nrout' are for output; other registers up + * to, but not including, `nreg' are for input. Finally, the test suite + * defines the size of a register. The common test machinery treats + * registers as entirely opaque, acting on them only through their defined + * types. + * + * Each kind of test defines a register mapping, which assigns types and + * (textual) names to some subset of the registers. A register can be marked + * optional; by default, the test vector parser will report an error if a + * defined register is not assigned a value. + * + * The register type is responsible for handling the register on behalf of + * the common code. (Test functions can have built-in knowledge of which + * registers have which types, and can manipulate registers directly, of + * course.) + * + * Finally, each test defines a test runner, which is responsible for + * invoking the test function and checking that the output registers are + * correct. There is a generic test runner, but some tests might benefit + * from special arrangements. + */ + +union regval { + long i; /* signed integer */ + unsigned long u; /* unsigned integer */ + struct { void *p; size_t sz; } bytes; /* buffer of bytes */ +#ifdef REG_MEMBERS + REG_MEMBERS /* your members here */ +#endif +}; + +struct reg { + unsigned f; /* flags */ +#define REGF_LIVE 1u /* input register has a value */ + union regval v; /* register value */ +}; + +struct regty { + void (*init)(union regval *v); /* set up raw memory */ + void (*parse)(union regval *v, char *p); /* parse text input as value */ + void (*dump)(FILE *fp, const union regval *v); /* dump value as text */ + int (*eq)(const union regval *v0, const union regval *v1); /* equal? */ + void (*release)(union regval *v); /* release any resources */ +}; + +struct regdef { + const char *name; /* register name (for input files) */ + unsigned i; /* register index */ + const struct regty *ty; /* register type descriptor */ + unsigned f; /* flags */ +#define REGF_OPT 1u /* (input) register is optional */ +}; +#define REGLIST_END { 0 } + +struct test_state { + struct reg *in, *out; /* vectors of registers */ + unsigned nrout; /* number of output registers */ + unsigned nreg; /* total number of registers */ + size_t regsz; /* size of an individual register */ + int win, lose; /* number of tests passed/failed */ +}; + +struct test { + const char *name; /* name of the test */ + void (*run)(struct test_state *state, const struct test *test); + /* test runner (`run_test') */ + const struct regdef *regs; /* register definitions */ + void (*fn)(struct reg *out, const struct reg *in, void *ctx); + /* test function */ +}; + +/* Utility functions. */ +extern NORETURN(bail(const char *msg, ...)) + FORMAT(printf, 1, 2); +extern void parse_hex(uint8_t *b, size_t sz, char *p); +extern void dump_hex(FILE *fp, const uint8_t *b, size_t sz); +extern void trivial_regty_init(union regval *v); +extern void trivial_regty_release(union regval *v); +extern void allocate_bytes(union regval *v, size_t sz); + +/* Built-in register types. */ +extern const struct regty + regty_int, + regty_uint, + regty_bytes; + +/* Running tests. */ +extern void check_test_output(struct test_state *state, + const struct test *test); +extern void run_test(struct test_state *state, const struct test *test); +extern int run_test_suite(unsigned nrout, unsigned nreg, size_t regsz, + const struct test *tests, FILE *fp); + +#endif diff --git a/ec-field-test.c b/ec-field-test.c new file mode 100644 index 0000000..5c29704 --- /dev/null +++ b/ec-field-test.c @@ -0,0 +1,267 @@ +/* + * ec-field-test.c: test harness for elliptic-curve field arithmetic + * + * (The implementations originally came with different test arrangements, + * with complicated external dependencies. This file replicates the original + * tests, but without the dependencies.) + */ +/* + * This file is Free Software. It was originally written for secnet. + * + * Copyright 2017 Mark Wooding + * + * You may redistribute secnet as a whole and/or modify it under the + * terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3, or (at your option) any + * later version. + * + * You may redistribute this file and/or modify it under the terms of + * the GNU General Public License as published by the Free Software + * Foundation; either version 2, or (at your option) any later + * version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, see + * https://www.gnu.org/licenses/gpl.html. + */ + +#include + +#include "secnet.h" + +#include "f25519.h" +#include "fgoldi.h" + +#define f25519_FESZ 32u +#define fgoldi_FESZ 56u + +#define GLUE(x, y) GLUE_(x, y) +#define GLUE_(x, y) x##y +#define FIELDOP(op) GLUE(FIELD, _##op) + +#define REG_MEMBERS \ + uint8_t fe[FIELDOP(FESZ)]; +#include "crypto-test.h" + +enum { + RZ, RZ0 = RZ, RZ1, RXX = RZ0, RYY = RZ1, NROUT, + RM = NROUT, RI = RM, RN = RM, + RU, RV, RW, RX, RY, RA, + RV0 = RU, RV31 = RV0 + 31, + NREG +}; + +static void parse_fe(union regval *v, char *p) +{ + size_t n = strlen(p); + size_t sz = sizeof(v->fe); + + if (!*p) + memset(v->fe, 0xff, sizeof(v->fe)); + else { + if (sz > n/2) sz = n/2; + parse_hex(v->fe, sz, p); memset(v->fe + sz, 0, sizeof(v->fe) - sz); + } +} + +static void dump_fe(FILE *fp, const union regval *v) + { dump_hex(fp, v->fe, sizeof(v->fe)); } + +static int eq_fe(const union regval *v0, const union regval *v1) + { return (memcmp(v0->fe, v1->fe, sizeof(v0->fe)) == 0); } + +static const struct regty regty_fe = { + trivial_regty_init, + parse_fe, + dump_fe, + eq_fe, + trivial_regty_release +}; + +#define BINOP(op) \ + static void test_##op(struct reg *out, \ + const struct reg *in, void *ctx) \ + { \ + FIELD x, y, z; \ + \ + FIELDOP(load)(&x, in[RX].v.fe); \ + FIELDOP(load)(&y, in[RY].v.fe); \ + FIELDOP(op)(&z, &x, &y); \ + FIELDOP(store)(out[RZ].v.fe, &z); \ + } + +#define UNOP(op) \ + static void test_##op(struct reg *out, \ + const struct reg *in, void *ctx) \ + { \ + FIELD x, z; \ + \ + FIELDOP(load)(&x, in[RX].v.fe); \ + FIELDOP(op)(&z, &x); \ + FIELDOP(store)(out[RZ].v.fe, &z); \ + } + +BINOP(add) +BINOP(sub) +BINOP(mul) +UNOP(neg) +UNOP(sqr) +UNOP(inv) + +static void test_condneg(struct reg *out, const struct reg *in, void *ctx) +{ + FIELD x, z; + + FIELDOP(load)(&x, in[RX].v.fe); + FIELDOP(condneg)(&z, &x, in[RM].v.u); + FIELDOP(store)(out[RZ].v.fe, &z); +} + +static void test_mulconst(struct reg *out, const struct reg *in, void *ctx) +{ + FIELD x, z; + + FIELDOP(load)(&x, in[RX].v.fe); + FIELDOP(mulconst)(&z, &x, in[RA].v.i); + FIELDOP(store)(out[RZ].v.fe, &z); +} + +static void test_condswap(struct reg *out, const struct reg *in, void *ctx) +{ + FIELD x, y; + + FIELDOP(load)(&x, in[RX].v.fe); + FIELDOP(load)(&y, in[RY].v.fe); + FIELDOP(condswap)(&x, &y, in[RM].v.u); + FIELDOP(store)(out[RXX].v.fe, &x); + FIELDOP(store)(out[RYY].v.fe, &y); +} + +static void test_pick2(struct reg *out, const struct reg *in, void *ctx) +{ + FIELD x, y, z; + + FIELDOP(load)(&x, in[RX].v.fe); + FIELDOP(load)(&y, in[RY].v.fe); + FIELDOP(pick2)(&z, &x, &y, in[RM].v.u); + FIELDOP(store)(out[RZ].v.fe, &z); +} + +static void test_pickn(struct reg *out, const struct reg *in, void *ctx) +{ + FIELD v[32], z; + unsigned i; + + for (i = 0; in[RV0 + i].f®F_LIVE; i++) + FIELDOP(load)(&v[i], in[RV0 + i].v.fe); + FIELDOP(pickn)(&z, v, i, in[RI].v.u); + FIELDOP(store)(out[RZ].v.fe, &z); +} + +static void test_quosqrt(struct reg *out, const struct reg *in, void *ctx) +{ + FIELD x, y, z; + + FIELDOP(load)(&x, in[RX].v.fe); + FIELDOP(load)(&y, in[RY].v.fe); + if (FIELDOP(quosqrt)(&z, &x, &y)) + memset(out[RZ0].v.fe, 0xff, sizeof(out[RZ].v.fe)); + else + FIELDOP(store)(out[RZ].v.fe, &z); +} + +static void run_quosqrt(struct test_state *state, const struct test *test) +{ + test->fn(state->out, state->in, 0); + + /* ..._quosqrt returns an arbitrary square root. The test vector + * contains both. We win if we match either. + */ + if (eq_fe(&state->in[RZ1].v, &state->out[RZ].v)) + state->out[RZ0].v = state->in[RZ0].v; + state->out[RZ1].v = state->in[RZ1].v; + check_test_output(state, test); +} + +static void test_sub_mulc_add_sub_mul(struct reg *out, + const struct reg *in, void *ctx) +{ + FIELD u, v, w, x, y, z; + + FIELDOP(load)(&u, in[RU].v.fe); + FIELDOP(load)(&v, in[RV].v.fe); + FIELDOP(load)(&w, in[RW].v.fe); + FIELDOP(load)(&x, in[RX].v.fe); + FIELDOP(load)(&y, in[RY].v.fe); + + FIELDOP(sub)(&z, &u, &v); + FIELDOP(mulconst)(&z, &z, in[RA].v.i); + FIELDOP(add)(&z, &z, &w); + FIELDOP(sub)(&x, &x, &y); + FIELDOP(mul)(&z, &z, &x); + FIELDOP(store)(out[RZ].v.fe, &z); +} + +#define REG_U { "u", RU, ®ty_fe, 0 } +#define REG_V { "v", RV, ®ty_fe, 0 } +#define REG_W { "w", RW, ®ty_fe, 0 } +#define REG_X { "x", RX, ®ty_fe, 0 } +#define REG_Y { "y", RY, ®ty_fe, 0 } +#define REG_A { "a", RA, ®ty_int, 0 } +#define REG_M { "m", RM, ®ty_uint, 0 } +#define REG_I { "i", RI, ®ty_uint, 0 } +#define REG_XX { "xx", RXX, ®ty_fe, 0 } +#define REG_YY { "yy", RYY, ®ty_fe, 0 } +#define REG_Z { "z", RZ, ®ty_fe, 0 } +#define REG_Z0 { "z0", RZ0, ®ty_fe, 0 } +#define REG_Z1 { "z1", RZ1, ®ty_fe, 0 } +#define REG_BIGY { "Y", RY, ®ty_fe, 0 } +#define REG_BIGZ { "Z", RZ, ®ty_fe, 0 } +#define REG_N { "n", RN, ®ty_uint, 0 } +#define REG_Vi(i) { "v[" # i "]", RV0 + i, ®ty_fe, REGF_OPT } +#define REG_VV \ + REG_Vi( 0), REG_Vi( 1), REG_Vi( 2), REG_Vi( 3), \ + REG_Vi( 4), REG_Vi( 5), REG_Vi( 6), REG_Vi( 7), \ + REG_Vi( 8), REG_Vi( 9), REG_Vi(10), REG_Vi(11), \ + REG_Vi(12), REG_Vi(13), REG_Vi(14), REG_Vi(15), \ + REG_Vi(16), REG_Vi(17), REG_Vi(18), REG_Vi(19), \ + REG_Vi(20), REG_Vi(21), REG_Vi(22), REG_Vi(23), \ + REG_Vi(24), REG_Vi(25), REG_Vi(26), REG_Vi(27), \ + REG_Vi(28), REG_Vi(29), REG_Vi(30), REG_Vi(31) +static const struct regdef + unop_regs[] = { REG_X, REG_Z, REGLIST_END }, + binop_regs[] = { REG_X, REG_Y, REG_Z, REGLIST_END }, + condneg_regs[] = { REG_X, REG_M, REG_Z, REGLIST_END }, + mulconst_regs[] = { REG_X, REG_A, REG_Z, REGLIST_END }, + pick2_regs[] = { REG_X, REG_Y, REG_M, REG_Z, REGLIST_END }, + pickn_regs[] = { REG_VV, REG_I, REG_Z, REGLIST_END }, + condswap_regs[] = { REG_X, REG_Y, REG_M, REG_XX, REG_YY, REGLIST_END }, + quosqrt_regs[] = { REG_X, REG_Y, REG_Z0, REG_Z1, REGLIST_END }, + sub_mulc_add_sub_mul_regs[] = + { REG_U, REG_V, REG_A, REG_W, REG_X, REG_Y, REG_Z, REGLIST_END }; + +static const struct test tests[] = { + { "add", run_test, binop_regs, test_add }, + { "sub", run_test, binop_regs, test_sub }, + { "neg", run_test, unop_regs, test_neg }, + { "condneg", run_test, condneg_regs, test_condneg }, + { "condswap", run_test, condswap_regs, test_condswap }, + { "mulconst", run_test, mulconst_regs, test_mulconst }, + { "mul", run_test, binop_regs, test_mul }, + { "sqr", run_test, unop_regs, test_sqr }, + { "inv", run_test, unop_regs, test_inv }, + { "pick2", run_test, pick2_regs, test_pick2 }, + { "pickn", run_test, pickn_regs, test_pickn }, + { "quosqrt", run_quosqrt, quosqrt_regs, test_quosqrt }, + { "sub-mulc-add-sub-mul", run_test, + sub_mulc_add_sub_mul_regs, test_sub_mulc_add_sub_mul }, + { 0 } +}; + +int main(void) + { return run_test_suite(NROUT, NREG, sizeof(struct reg), tests, stdin); } diff --git a/xdh-test.c b/xdh-test.c index 016c8d2..08efabe 100644 --- a/xdh-test.c +++ b/xdh-test.c @@ -1,5 +1,5 @@ /* - * xdh-test.c: test harness elliptic-curve Diffie--Hellman + * xdh-test.c: test harness for elliptic-curve Diffie--Hellman * * (The implementations originally came with different test arrangements, * with complicated external dependencies. This file replicates the original @@ -30,338 +30,76 @@ * https://www.gnu.org/licenses/gpl.html. */ -#include -#include -#include -#include -#include #include -#include -#include #include "secnet.h" -#include "f25519.h" -#include "fgoldi.h" - #include "x25519.h" #include "x448.h" +#define x25519_KEYSZ X25519_KEYSZ +#define x448_KEYSZ X448_KEYSZ + #define GLUE(x, y) GLUE_(x, y) #define GLUE_(x, y) x##y -#define FIELDOP(op) GLUE(FIELD, _##op) - -#define f25519_FESZ 32u -#define fgoldi_FESZ 56u - -/* Define some global variables we shouldn't need. - * - * Annoyingly, `secnet.h' declares static pointers and initializes them to - * point to some external variables. At `-O0', GCC doesn't optimize these - * away, so there's a link-time dependency on these variables. Define them - * here, so that `f25519.c' and `f448.c' can find them. - * - * (Later GCC has `-Og', which optimizes without making debugging a - * nightmare, but I'm not running that version here. Note that `serpent.c' - * doesn't have this problem because it defines its own word load and store - * operations to cope with its endian weirdness, whereas the field arithmetic - * uses `unaligned.h' which manages to include `secnet.h'.) - */ -uint64_t now_global; -struct timeval tv_now_global; - -union reg { - long i; - unsigned long u; - uint8_t fe[FIELDOP(FESZ)]; -}; - -struct regty { - void (*parse)(union reg *r, char *p); - void (*dump)(FILE *fp, const union reg *r); - int (*eq)(const union reg *r, const union reg *rr); -}; - -struct regdef { - const char *name; - const struct regty *ty; - int r; -}; - -struct test { - const char *name; - void (*fn)(union reg *out, const union reg *in); - const struct regdef *regs; -}; - -static int lno; - -static void bail(const char *msg, ...) -{ - va_list ap; - va_start(ap, msg); - fprintf(stderr, "unexpected error (line %d): ", lno); - vfprintf(stderr, msg, ap); - va_end(ap); - fputc('\n', stderr); - exit(3); -} - -static void parse_hex(uint8_t *b, size_t sz, char *p) -{ - size_t n = strlen(p); - unsigned i; - char bb[3]; - - if (n%2) bail("bad hex (odd number of nibbles)"); - else if (n/2 != sz) bail("bad hex (want %zu bytes, found %zu", sz, n/2); - while (sz) { - for (i = 0; i < 2; i++) { - if (!isxdigit((unsigned char)p[i])) - bail("bad hex digit `%c'", p[i]); - bb[i] = p[i]; - } - bb[2] = 0; - p += 2; - *b++ = strtoul(bb, 0, 16); sz--; - } -} - -static void dump_hex(FILE *fp, const uint8_t *b, size_t sz) - { while (sz--) fprintf(fp, "%02x", *b++); fputc('\n', fp); } - -static void parse_int(union reg *r, char *p) -{ - char *q; - - errno = 0; - r->i = strtol(p, &q, 0); - if (*q || errno) bail("bad integer `%s'", p); -} - -static void dump_int(FILE *fp, const union reg *r) - { fprintf(fp, "%ld\n", r->i); } - -static int eq_int(const union reg *r, const union reg *rr) - { return (r->i == rr->i); } - -static const struct regty regty_int = { parse_int, dump_int, eq_int }; +#define XDHOP(op) GLUE(XDH, _##op) -static void parse_uint(union reg *r, char *p) -{ - char *q; - - errno = 0; - r->u = strtoul(p, &q, 0); - if (*q || errno) bail("bad integer `%s'", p); -} - -static void dump_uint(FILE *fp, const union reg *r) - { fprintf(fp, "%lu\n", r->u); } - -static int eq_uint(const union reg *r, const union reg *rr) - { return (r->u == rr->u); } - -static const struct regty regty_uint = { parse_uint, dump_uint, eq_uint }; - -static void parse_fe(union reg *r, char *p) - { parse_hex(r->fe, sizeof(r->fe), p); } - -static void dump_fe(FILE *fp, const union reg *r) - { dump_hex(fp, r->fe, sizeof(r->fe)); } - -static int eq_fe(const union reg *r, const union reg *rr) - { return (memcmp(r->fe, rr->fe, sizeof(r->fe)) == 0); } - -static const struct regty regty_fe = { parse_fe, dump_fe, eq_fe }; +#define REG_MEMBERS \ + uint8_t k[XDHOP(KEYSZ)]; +#include "crypto-test.h" enum { - RZ, RXX = RZ, RYY, NROUT, - RX = NROUT, RY, RA = RY, RK = RY, RM, RN = RM, NREG + RZ, NROUT, + RN = NROUT, RX, RY, NREG }; -#define BINOP(op) \ - static void test_##op(union reg *out, const union reg *in) \ - { \ - FIELD x, y, z; \ - \ - FIELDOP(load)(&x, in[RX].fe); \ - FIELDOP(load)(&y, in[RY].fe); \ - FIELDOP(op)(&z, &x, &y); \ - FIELDOP(store)(out[RZ].fe, &z); \ - } - -#define UNOP(op) \ - static void test_##op(union reg *out, const union reg *in) \ - { \ - FIELD x, z; \ - \ - FIELDOP(load)(&x, in[RX].fe); \ - FIELDOP(op)(&z, &x); \ - FIELDOP(store)(out[RZ].fe, &z); \ - } - -BINOP(add) -BINOP(sub) -BINOP(mul) -UNOP(sqr) -UNOP(inv) - -static void test_mulconst(union reg *out, const union reg *in) -{ - FIELD x, z; +static void parse_key(union regval *v, char *p) + { parse_hex(v->k, sizeof(v->k), p); } - FIELDOP(load)(&x, in[RX].fe); - FIELDOP(mulconst)(&z, &x, in[RA].i); - FIELDOP(store)(out[RZ].fe, &z); -} +static void dump_key(FILE *fp, const union regval *v) + { dump_hex(fp, v->k, sizeof(v->k)); } -static void test_condswap(union reg *out, const union reg *in) -{ - FIELD x, y; +static int eq_key(const union regval *v0, const union regval *v1) + { return (memcmp(v0->k, v1->k, sizeof(v0->k)) == 0); } - FIELDOP(load)(&x, in[RX].fe); - FIELDOP(load)(&y, in[RY].fe); - FIELDOP(condswap)(&x, &y, in[RM].u); - FIELDOP(store)(out[RXX].fe, &x); - FIELDOP(store)(out[RYY].fe, &y); -} +static const struct regty regty_key = { + trivial_regty_init, + parse_key, + dump_key, + eq_key, + trivial_regty_release +}; -static void test_xdh(union reg *out, const union reg *in) - { XDH(out[RZ].fe, in[RK].fe, in[RX].fe); } +static void test_xdh(struct reg *out, const struct reg *in, void *ctx) + { XDH(out[RZ].v.k, in[RX].v.k, in[RY].v.k); } -static void test_xdhmct(union reg *out, const union reg *in) +static void test_xdhmct(struct reg *out, const struct reg *in, void *ctx) { - uint8_t b0[FIELDOP(FESZ)], b1[FIELDOP(FESZ)], *k = b0, *x = b1, *t; + uint8_t b0[XDHOP(KEYSZ)], b1[XDHOP(KEYSZ)], *x = b0, *y = b1, *t; unsigned long i, n; - memcpy(b0, in[RK].fe, sizeof(b0)); - memcpy(b1, in[RX].fe, sizeof(b1)); - n = in[RM].u; + memcpy(b0, in[RX].v.k, sizeof(b0)); + memcpy(b1, in[RY].v.k, sizeof(b1)); + n = in[RN].v.u; for (i = 0; i < n; i++) { - XDH(x, k, x); - t = x; x = k; k = t; + XDH(y, x, y); + t = y; y = x; x = t; } - memcpy(out[RZ].fe, k, sizeof(b0)); + memcpy(out[RZ].v.k, x, sizeof(b0)); } - -#define REG_X { "x", ®ty_fe, RX } -#define REG_Y { "y", ®ty_fe, RY } -#define REG_A { "a", ®ty_int, RA } -#define REG_M { "m", ®ty_uint, RM } -#define REG_XX { "xx", ®ty_fe, RXX } -#define REG_YY { "yy", ®ty_fe, RYY } -#define REG_Z { "z", ®ty_fe, RZ } -#define REG_BIGX { "X", ®ty_fe, RX } -#define REG_BIGZ { "Z", ®ty_fe, RZ } -#define REG_K { "k", ®ty_fe, RK } -#define REG_N { "n", ®ty_uint, RN } -#define REGLIST_END { 0 } +#define REG_X { "x", RX, ®ty_key, 0 } +#define REG_Y { "Y", RY, ®ty_key, 0 } +#define REG_Z { "Z", RZ, ®ty_key, 0 } +#define REG_N { "n", RN, ®ty_uint, 0 } static const struct regdef - unop_regs[] = { REG_X, REG_Z, REGLIST_END }, - binop_regs[] = { REG_X, REG_Y, REG_Z, REGLIST_END }, - mulconst_regs[] = { REG_X, REG_A, REG_Z, REGLIST_END }, - condswap_regs[] = { REG_X, REG_Y, REG_M, REG_XX, REG_YY, REGLIST_END }, - xdh_regs[] = { REG_K, REG_BIGX, REG_BIGZ, REGLIST_END }, - xdhmct_regs[] = { REG_K, REG_BIGX, REG_N, REG_BIGZ, REGLIST_END }; + xdh_regs[] = { REG_X, REG_Y, REG_Z, REGLIST_END }, + xdhmct_regs[] = { REG_X, REG_Y, REG_N, REG_Z, REGLIST_END }; static const struct test tests[] = { - { "add", test_add, binop_regs }, - { "sub", test_sub, binop_regs }, - { "condswap", test_condswap, condswap_regs }, - { "mulconst", test_mulconst, mulconst_regs }, - { "mul", test_mul, binop_regs }, - { "sqr", test_sqr, unop_regs }, - { "inv", test_inv, unop_regs }, - { "xdh", test_xdh, xdh_regs }, - { "xdh-mct", test_xdhmct, xdhmct_regs }, + { STRING(XDH), run_test, xdh_regs, test_xdh }, + { STRING(XDH) "-mct", run_test, xdhmct_regs, test_xdhmct }, { 0 } }; -static int check(const struct test *test, unsigned iat, - union reg *out, const union reg *in) -{ - const struct regdef *rdef; - int ok = 1; - - if (!iat) return (0); - assert(test); - for (rdef = test->regs; rdef->name; rdef++) - if (!(iat & (1 << rdef->r))) - bail("register `%s' not set", rdef->name); - test->fn(out, in); - for (rdef = test->regs; rdef->name; rdef++) { - if (rdef->r >= NROUT) continue; - if (!rdef->ty->eq(&in[rdef->r], &out[rdef->r])) ok = 0; - } - if (ok) return (0); - - fprintf(stderr, "failed `%s'\n", test->name); - for (rdef = test->regs; rdef->name; rdef++) { - if (rdef->r >= NROUT) { - fprintf(stderr, "\tinput `%s' = ", rdef->name); - rdef->ty->dump(stderr, &in[rdef->r]); - } else { - fprintf(stderr, "\texpected `%s' = ", rdef->name); - rdef->ty->dump(stderr, &in[rdef->r]); - fprintf(stderr, "\tcomputed `%s' = ", rdef->name); - rdef->ty->dump(stderr, &out[rdef->r]); - } - } - return (-1); -} - int main(void) -{ - char linebuf[256]; - char *p; - const char *q; - size_t n; - unsigned iat = 0, rbit; - const struct test *test = 0; - union reg in[NREG], out[NROUT]; - const struct regdef *rdef; - int rc = 0; - - lno = 0; - while (fgets(linebuf, sizeof(linebuf), stdin)) { - lno++; - n = strlen(linebuf); assert(n); - if (linebuf[n - 1] != '\n') bail("line too long"); - linebuf[n - 1] = 0; - p = linebuf; - while (isspace((unsigned char)*p)) p++; - if (*p == '#') continue; - if (!*p) { - if (check(test, iat, out, in)) rc = 2; - iat = 0; - continue; - } - q = p; - while (*p && !isspace((unsigned char)*p)) p++; - if (!*p) bail("missing argument"); - *p++ = 0; - while (isspace((unsigned char)*p)) p++; - if (strcmp(q, "test") == 0) { - if (check(test, iat, out, in)) rc = 2; - iat = 0; - for (test = tests; test->name; test++) - if (strcmp(p, test->name) == 0) goto found_test; - bail("unknown test `%s'", p); - found_test: - continue; - } - if (!test) bail("no test defined yet"); - for (rdef = test->regs; rdef->name; rdef++) - if (strcmp(q, rdef->name) == 0) goto found_reg; - bail("unknown register `%s'", q); - found_reg: - rbit = 1 << rdef->r; - if (iat & rbit) - bail("register `%s' already set", rdef->name); - rdef->ty->parse(&in[rdef->r], p); - iat |= rbit; - } - if (check(test, iat, out, in)) rc = 2; - return (rc); -} + { return run_test_suite(NROUT, NREG, sizeof(struct reg), tests, stdin); }