@@@ crypto-test
authorMark Wooding <mdw@distorted.org.uk>
Wed, 25 Sep 2019 19:38:06 +0000 (20:38 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 25 Sep 2019 22:08:31 +0000 (23:08 +0100)
Makefile.in
crypto-test.c [new file with mode: 0644]
crypto-test.h [new file with mode: 0644]
ec-field-test.c [new file with mode: 0644]
xdh-test.c

index a130c20..7b58f75 100644 (file)
@@ -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 (file)
index 0000000..137f7a3
--- /dev/null
@@ -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 <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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(&reg->v); def->ty->init(&reg->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&REGF_LIVE) any = 1;
+       else if (!miss && !(def->f&REGF_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(&reg->v); def->ty->init(&reg->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(&REG(in, def->i)->v);
+                   if (def->i < state->nrout)
+                       def->ty->release(&REG(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(&reg->v);
+               if (def->i < state->nrout) {
+                   reg = REG(out, def->i);
+                   reg->f = 0; def->ty->init(&reg->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&REGF_LIVE) bail("register `%s' already set", def->name);
+       def->ty->parse(&reg->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 (file)
index 0000000..e7ca777
--- /dev/null
@@ -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 (file)
index 0000000..5c29704
--- /dev/null
@@ -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 <stdio.h>
+
+#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&REGF_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, &regty_fe, 0 }
+#define REG_V { "v", RV, &regty_fe, 0 }
+#define REG_W { "w", RW, &regty_fe, 0 }
+#define REG_X { "x", RX, &regty_fe, 0 }
+#define REG_Y { "y", RY, &regty_fe, 0 }
+#define REG_A { "a", RA, &regty_int, 0 }
+#define REG_M { "m", RM, &regty_uint, 0 }
+#define REG_I { "i", RI, &regty_uint, 0 }
+#define REG_XX { "xx", RXX, &regty_fe, 0 }
+#define REG_YY { "yy", RYY, &regty_fe, 0 }
+#define REG_Z { "z", RZ, &regty_fe, 0 }
+#define REG_Z0 { "z0", RZ0, &regty_fe, 0 }
+#define REG_Z1 { "z1", RZ1, &regty_fe, 0 }
+#define REG_BIGY { "Y", RY, &regty_fe, 0 }
+#define REG_BIGZ { "Z", RZ, &regty_fe, 0 }
+#define REG_N { "n", RN, &regty_uint, 0 }
+#define REG_Vi(i) { "v[" # i "]", RV0 + i, &regty_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); }
index 016c8d2..08efabe 100644 (file)
@@ -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
  * https://www.gnu.org/licenses/gpl.html.
  */
 
-#include <assert.h>
-#include <errno.h>
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdint.h>
 #include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 
 #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", &regty_fe, RX }
-#define REG_Y { "y", &regty_fe, RY }
-#define REG_A { "a", &regty_int, RA }
-#define REG_M { "m", &regty_uint, RM }
-#define REG_XX { "xx", &regty_fe, RXX }
-#define REG_YY { "yy", &regty_fe, RYY }
-#define REG_Z { "z", &regty_fe, RZ }
-#define REG_BIGX { "X", &regty_fe, RX }
-#define REG_BIGZ { "Z", &regty_fe, RZ }
-#define REG_K { "k", &regty_fe, RK }
-#define REG_N { "n", &regty_uint, RN }
-#define REGLIST_END { 0 }
+#define REG_X { "x", RX, &regty_key, 0 }
+#define REG_Y { "Y", RY, &regty_key, 0 }
+#define REG_Z { "Z", RZ, &regty_key, 0 }
+#define REG_N { "n", RN, &regty_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); }