symm/sha3.[ch]: Add support for SHA3 and related functions based on Keccak.
[catacomb] / symm / sha3.c
diff --git a/symm/sha3.c b/symm/sha3.c
new file mode 100644 (file)
index 0000000..97b41e3
--- /dev/null
@@ -0,0 +1,1190 @@
+/* -*-c-*-
+ *
+ * The SHA3 algorithm family
+ *
+ * (c) 2017 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Catacomb 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "hash.h"
+#include "ghash-def.h"
+#include "sha3.h"
+
+/*----- General utilities -------------------------------------------------*/
+
+static void absorb(sha3_ctx *ctx, const octet *p, unsigned r)
+{
+  kludge64 t[25];
+  unsigned i;
+
+/* memdump("absorb", p, 8*r, 0); */
+  for (i = 0; i < r; i++) { LOAD64_L_(t[i], p); p += 8; }
+  keccak1600_mix(&ctx->s, t, r);
+}
+
+static void step(sha3_ctx *ctx) { keccak1600_p(&ctx->s, &ctx->s, 24); }
+
+static void pad(sha3_ctx *ctx, unsigned lo, unsigned hi)
+{
+  size_t spare = ctx->r - ctx->n;
+
+  if (spare == 1)
+    ctx->buf[ctx->n] = lo | hi;
+  else {
+    ctx->buf[ctx->n] = lo;
+    ctx->buf[ctx->r - 1] = hi;
+    memset(ctx->buf + ctx->n + 1, 0, spare - 2);
+  }
+  absorb(ctx, ctx->buf, ctx->r/8);
+}
+
+static void squeeze(sha3_ctx *ctx, octet *p, unsigned r)
+{
+  kludge64 t[25];
+  unsigned i;
+
+  keccak1600_extract(&ctx->s, t, r);
+  for (i = 0; i < r; i++) { STORE64_L_(p, t[i]); p += 8; }
+/* memdump("squeeze", p - 8*r, 8*r, 0); */
+}
+
+enum {
+  OP_CSHAKE = 0x04,
+  OP_SHA3 = 0x06,
+  OP_SHAKE = 0x1f
+};
+
+enum { ST_ABSORB, ST_SQUEEZE, ST_DEAD };
+
+/*----- The SHA3 algorithms -----------------------------------------------*/
+
+/* --- @sha3_{224,256,384,512}_init@ --- *
+ *
+ * Arguments:  @sha3_ctx *ctx@ = pointer to context block to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a SHA3 hashing context for use.
+ */
+
+static void init_sha3(sha3_ctx *ctx, unsigned w)
+{
+  keccak1600_init(&ctx->s);
+  ctx->w = w/8; ctx->r = (1600 - 2*w)/8; ctx->n = 0;
+}
+
+void sha3_224_init(sha3_ctx *ctx) { init_sha3(ctx, 224); }
+void sha3_256_init(sha3_ctx *ctx) { init_sha3(ctx, 256); }
+void sha3_384_init(sha3_ctx *ctx) { init_sha3(ctx, 384); }
+void sha3_512_init(sha3_ctx *ctx) { init_sha3(ctx, 512); }
+
+/* --- @sha3_hash@ --- *
+ *
+ * Arguments:  @sha3_ctx *ctx@ = pointer to context bock
+ *             @const void *p@ = pointer to data to hash
+ *             @size_t sz@ = size of buffer to hash
+ *
+ * Returns:    ---
+ *
+ * Use:                Hashes a buffer of data.  The buffer may be of any size and
+ *             alignment.
+ */
+
+void sha3_hash(sha3_ctx *ctx, const void *p, size_t sz)
+{
+  const octet *q = p;
+  size_t spare = ctx->r - ctx->n;
+
+  if (sz < spare) {
+    memcpy(ctx->buf + ctx->n, q, sz);
+    ctx->n += sz;
+    return;
+  }
+  if (ctx->n) {
+    memcpy(ctx->buf + ctx->n, q, spare);
+    absorb(ctx, ctx->buf, ctx->r/8);
+    step(ctx);
+    q += spare; sz -= spare;
+  }
+  while (sz >= ctx->r) {
+    absorb(ctx, q, ctx->r/8);
+    step(ctx);
+    q += ctx->r; sz -= ctx->r;
+  }
+  if (sz) memcpy(ctx->buf, q, sz);
+  ctx->n = sz;
+}
+
+/* --- @sha3_done@ --- *
+ *
+ * Arguments:  @sha3_ctx *ctx@ = pointer to context block
+ *             @void *hash@ = pointer to output buffer
+ *
+ * Returns:    ---
+ *
+ * Use:                Returns the hash of the data read so far.
+ */
+
+void sha3_done(sha3_ctx *ctx, void *hash)
+{
+  pad(ctx, OP_SHA3, 0x80);
+  step(ctx);
+
+  if (ctx->w%8 == 0)
+    squeeze(ctx, hash, ctx->w/8);
+  else {
+    squeeze(ctx, ctx->buf, (ctx->w + 7)/8);
+    memcpy(hash, ctx->buf, ctx->w);
+  }
+}
+
+/* --- @sha3_{224,256,384,512}_set@ --- *
+ *
+ * Arguments:  @sha3_ctx *ctx@ = pointer to context block
+ *             @const void *buf@ = pointer to state buffer
+ *             @unsigned long count@ = current count of bytes processed
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a context block from a given state.  This is
+ *             something of a kludge for the benefit of HMAC, but there are
+ *             better ways to use SHA3 as a MAC.
+ *
+ *             Furthermore, the @count@ argument is expected to be zero or
+ *             be the output of @sha3_state@ below.  Doing anything else
+ *             won't work properly.
+ */
+
+static void set(sha3_ctx *ctx, const void *buf,
+               unsigned long count, unsigned w)
+{
+  if (count)
+    memcpy(ctx, buf, sizeof(*ctx));
+  else {
+    static const char *prefix = "Catacomb SHA3 NMAC kludge";
+    init_sha3(ctx, w);
+    sha3_hash(ctx, prefix, sizeof(prefix));
+    sha3_hash(ctx, buf, SHA3_STATESZ);
+  }
+}
+
+void sha3_224_set(sha3_ctx *ctx, const void *buf, unsigned long count)
+  { set(ctx, buf, count, 224); }
+void sha3_256_set(sha3_ctx *ctx, const void *buf, unsigned long count)
+  { set(ctx, buf, count, 256); }
+void sha3_384_set(sha3_ctx *ctx, const void *buf, unsigned long count)
+  { set(ctx, buf, count, 384); }
+void sha3_512_set(sha3_ctx *ctx, const void *buf, unsigned long count)
+  { set(ctx, buf, count, 512); }
+
+/* --- @sha3_state@ --- *
+ *
+ * Arguments:  @sha3_ctx *ctx@ = pointer to context block
+ *             @void *state@ = pointer to buffer for current state
+ *
+ * Returns:    A value which is meaningful to @sha3_..._set@.
+ *
+ * Use:                Returns the current state of the hash function such that it
+ *             can be passed to @sha3_..._set@.
+ */
+
+unsigned long sha3_state(sha3_ctx *ctx, void *state)
+  { memcpy(state, ctx, sizeof(*ctx)); return (1); }
+
+#define HASHES(_)                                                      \
+  _(SHA3_224, sha3_224, "sha3-224")                                    \
+  _(SHA3_256, sha3_256, "sha3-256")                                    \
+  _(SHA3_384, sha3_384, "sha3-384")                                    \
+  _(SHA3_512, sha3_512, "sha3-512")
+HASHES(GHASH_DEFX)
+
+/*----- The cSHAKE XOF algorithm ------------------------------------------*/
+
+static void leftenc_sz(shake_ctx *ctx, size_t n)
+{
+  kludge64 t;
+  octet b[9];
+  unsigned i;
+
+  SET64(t, ((n&~MASK32) >> 16) >> 16, n&MASK32);
+  STORE64_B_(b + 1, t);
+  for (i = 1; i < 8 && !b[i]; i++);
+  i--; b[i] = 8 - i;
+  shake_hash(ctx, b + i, 9 - i);
+}
+
+static void rightenc_sz(shake_ctx *ctx, size_t n)
+{
+  kludge64 t;
+  octet b[9];
+  unsigned i;
+
+  SET64(t, ((n&~MASK32) >> 16) >> 16, n&MASK32);
+  STORE64_B_(b, t);
+  for (i = 0; i < 7 && !b[i]; i++);
+  b[8] = 8 - i;
+  shake_hash(ctx, b + i, 9 - i);
+}
+
+static void stringenc(shake_ctx *ctx, const void *p, size_t sz)
+  { leftenc_sz(ctx, 8*sz); if (sz) shake_hash(ctx, p, sz); }
+
+static void bytepad_before(shake_ctx *ctx)
+  { leftenc_sz(ctx, ctx->h.r); }
+
+static void bytepad_after(shake_ctx *ctx)
+{
+  unsigned pad;
+
+  if (ctx->h.n%8) {
+    pad = 8 - ctx->h.n%8;
+    memset(ctx->h.buf + ctx->h.n, 0, pad);
+    ctx->h.n += pad;
+  }
+  if (ctx->h.n) {
+    absorb(&ctx->h, ctx->h.buf, ctx->h.n/8);
+    step(&ctx->h); ctx->h.n = 0;
+  }
+}
+
+const octet
+  shake128_keysz[] = { KSZ_ANY, SHAKE128_KEYSZ },
+  shake256_keysz[] = { KSZ_ANY, SHAKE256_KEYSZ };
+
+/* --- @cshake{128,256}_init@ --- *
+ *
+ * Arguments:  @shake_ctx *ctx@ = pointer to context to initialize
+ *             @const void *func@ = NIST-allocated function name
+ *             @size_t fsz@ = length of function name
+ *             @const void *perso@ = user personalization string
+ *             @size_t psz@ = length of personalization string
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a cSHAKE context.  The context is initially in
+ *             the `absorbing' state: feed it data with @shake_hash@.
+ */
+
+static void init_shake(shake_ctx *ctx, unsigned c0,
+                      const void *func, size_t fsz,
+                      const void *perso, size_t psz)
+{
+  keccak1600_init(&ctx->h.s); ctx->st = ST_ABSORB;
+  ctx->h.r = (1600 - 2*c0)/8; ctx->h.w = 0; ctx->h.n = 0;
+  if (!fsz && !psz)
+    ctx->op = OP_SHAKE;
+  else {
+    bytepad_before(ctx);
+    stringenc(ctx, func, fsz);
+    stringenc(ctx, perso, psz);
+    bytepad_after(ctx);
+    ctx->op = OP_CSHAKE;
+  }
+}
+
+void cshake128_init(shake_ctx *ctx,
+                   const void *func, size_t fsz,
+                   const void *perso, size_t psz)
+  { init_shake(ctx, 128, func, fsz, perso, psz); }
+
+void cshake256_init(shake_ctx *ctx,
+                   const void *func, size_t fsz,
+                   const void *perso, size_t psz)
+  { init_shake(ctx, 256, func, fsz, perso, psz); }
+
+/* --- @shake{128,256}_init@ --- *
+ *
+ * Arguments:  @sha3_ctx *ctx@ = pointer to context to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a SHAKE context.  The context is initially in
+ *             the `absorbing' state: feed it data with @shake_hash@.
+ */
+
+void shake128_init(shake_ctx *ctx) { init_shake(ctx, 128, 0, 0, 0, 0); }
+void shake256_init(shake_ctx *ctx) { init_shake(ctx, 256, 0, 0, 0, 0); }
+
+/* --- @shake_hash@ --- *
+ *
+ * Arguments:  @shake_ctx *ctx@ = context to update
+ *             @const void *p@ = input buffer
+ *             @size_t sz@ = size of input
+ *
+ * Returns:    ---
+ *
+ * Use:                Feeds input data into a SHAKE context.  The context must be
+ *             in `absorbing' state.
+ */
+
+void shake_hash(shake_ctx *ctx, const void *p, size_t sz)
+  { assert(ctx->st == ST_ABSORB); sha3_hash(&ctx->h, p, sz); }
+
+/* --- @shake_xof@ --- *
+ *
+ * Arguments:  @shake_ctx *ctx@ = context to update
+ *
+ * Returns:    ---
+ *
+ * Use:                Switches the context into `squeezing' state.  Use @shake_get@
+ *             or @shake_mask@ to extract data.
+ */
+
+void shake_xof(shake_ctx *ctx)
+{
+  assert(ctx->st == ST_ABSORB);
+  pad(&ctx->h, ctx->op, 0x80);
+  ctx->st = ST_SQUEEZE;
+  ctx->h.n = ctx->h.r;
+}
+
+/* --- @shake_get@ --- *
+ *
+ * Arguments:  @shake_ctx *ctx@ = context to update
+ *             @void *p@ = output buffer
+ *             @size_t sz@ = size of output
+ *
+ * Returns:    ---
+ *
+ * Use:                Extracts output from a SHAKE context.  The context must be
+ *             in `squeezing' state.
+ */
+
+void shake_get(shake_ctx *ctx, void *p, size_t sz)
+{
+  octet *q = p;
+  size_t left = ctx->h.r - ctx->h.n;
+
+  assert(ctx->st == ST_SQUEEZE);
+  if (left >= sz) {
+    memcpy(q, ctx->h.buf + ctx->h.n, sz);
+    ctx->h.n += sz;
+    return;
+  }
+  if (left) {
+    memcpy(q, ctx->h.buf + ctx->h.n, left);
+    q += left; sz -= left;
+  }
+  while (sz >= ctx->h.r) {
+    step(&ctx->h);
+    squeeze(&ctx->h, q, ctx->h.r/8);
+    q += ctx->h.r; sz -= ctx->h.r;
+  }
+  if (!sz)
+    ctx->h.n = ctx->h.r;
+  else {
+    step(&ctx->h);
+    squeeze(&ctx->h, ctx->h.buf, ctx->h.r/8);
+    memcpy(q, ctx->h.buf, sz);
+    ctx->h.n = sz;
+  }
+}
+
+/* --- @shake_mask@ --- *
+ *
+ * Arguments:  @shake_ctx *ctx@ = context to update
+ *             @const void *src@ = pointer to source data, or null
+ *             @void *dest@ = output buffer
+ *             @size_t sz@ = size of output
+ *
+ * Returns:    ---
+ *
+ * Use:                Mask the @src@ data by XORing it with output from the SHAKE
+ *             context, writing the result to @dest@.  The @src@ and @dest
+ *             buffers may be equal but must not otherwise overlap.  The
+ *             context must be in `squeezing' state.
+ */
+
+void shake_mask(shake_ctx *ctx, const void *src, void *dest, size_t sz)
+{
+  const octet *p = src, *pp, *l;
+  octet *q = dest;
+  size_t left = ctx->h.r - ctx->h.n;
+
+  if (!src) { shake_get(ctx, dest, sz); return; }
+
+  assert(ctx->st == ST_SQUEEZE);
+  if (left >= sz) {
+    pp = ctx->h.buf + ctx->h.n; l = q + sz;
+    while (q < l) *q++ = *p++ ^ *pp++;
+    ctx->h.n += sz;
+    return;
+  }
+  if (left) {
+    pp = ctx->h.buf + ctx->h.n; l = q + left;
+    while (q < l) *q++ = *p++ ^ *pp++;
+    sz -= left;
+  }
+  while (sz >= ctx->h.r) {
+    step(&ctx->h);
+    squeeze(&ctx->h, ctx->h.buf, ctx->h.r/8);
+    pp = ctx->h.buf; l = pp + ctx->h.r;
+    while (pp < l) *q++ = *p++ ^ *pp++;
+    sz -= ctx->h.r;
+  }
+  if (!sz)
+    ctx->h.n = ctx->h.r;
+  else {
+    step(&ctx->h);
+    squeeze(&ctx->h, ctx->h.buf, ctx->h.r/8);
+    pp = ctx->h.buf; l = q + sz;
+    while (q < l) *q++ = *p++ ^ *pp++;
+    ctx->h.n = sz;
+  }
+}
+
+/* --- @shake_done@ --- *
+ *
+ * Arguments:  @shake_ctx *ctx@ = context to update
+ *             @void *h@ = where to write the hash
+ *             @size_t hsz@ = size of the hash to make
+ *
+ * Returns:    ---
+ *
+ * Use:                Switches the context into `squeezing' state.  Use @shake_get@
+ *             or @shake_mask@ to extract data.
+ */
+
+void shake_done(shake_ctx *ctx, void *h, size_t hsz)
+  { shake_xof(ctx); shake_get(ctx, h, hsz); ctx->st = ST_DEAD; }
+
+/* --- Hash interface --- */
+
+typedef struct shake_ghctx {
+  ghash h;
+  shake_ctx c;
+  octet hb[SHAKE256_HASHSZ];
+} shake_ghctx;
+
+static const ghash_ops shake128_ghops, shake256_ghops;
+
+static void shake_ghhash(ghash *h, const void *p, size_t sz)
+  { shake_ghctx *cc = (shake_ghctx *)h; shake_hash(&cc->c, p, sz); }
+
+static octet *shake_ghdone(ghash *h, void *buf)
+{
+  shake_ghctx *cc = (shake_ghctx *)h;
+  if (!buf) buf = cc->hb;
+  shake_done(&cc->c, buf, h->ops->c->hashsz);
+  return (buf);
+}
+
+static void shake_ghdestroy(ghash *h)
+  { shake_ghctx *cc = (shake_ghctx *)h; BURN(cc); S_DESTROY(cc); }
+
+static ghash *shake_ghcopy(ghash *h)
+{
+  shake_ghctx *cc = (shake_ghctx *)h;
+  shake_ghctx *hc = S_CREATE(shake_ghctx);
+  *hc = *cc;
+  return (&hc->h);
+}
+
+static ghash *shake128_ghinit(void)
+{
+  shake_ghctx *cc = S_CREATE(shake_ghctx);
+  cc->h.ops = &shake128_ghops;
+  shake128_init(&cc->c);
+  return (&cc->h);
+}
+
+static ghash *shake256_ghinit(void)
+{
+  shake_ghctx *cc = S_CREATE(shake_ghctx);
+  cc->h.ops = &shake256_ghops;
+  shake256_init(&cc->c);
+  return (&cc->h);
+}
+
+const gchash
+  shake128 = { "shake128", SHAKE128_HASHSZ, shake128_ghinit, 168 },
+  shake256 = { "shake256", SHAKE256_HASHSZ, shake256_ghinit, 136 };
+
+static const ghash_ops
+  shake128_ghops = { &shake128, shake_ghhash, shake_ghdone,
+                    shake_ghdestroy, shake_ghcopy },
+  shake256_ghops = { &shake256, shake_ghhash, shake_ghdone,
+                    shake_ghdestroy, shake_ghcopy };
+
+/* --- Cipher interface --- */
+
+typedef struct shake_gcctx {
+  gcipher gc;
+  shake_ctx c;
+} shake_gcctx;
+
+static const gcipher_ops shake128_gcops, shake256_gcops;
+
+static void shake_gcencdec(gcipher *c, const void *s, void *d, size_t sz)
+  { shake_gcctx *cc = (shake_gcctx *)c; shake_mask(&cc->c, s, d, sz); }
+
+static void shake_gcdestroy(gcipher *c)
+  { shake_gcctx *cc = (shake_gcctx *)c; BURN(*cc); S_DESTROY(cc); }
+
+static const gcipher_ops
+  shake128_gcops = { &shake128_xof, shake_gcencdec, shake_gcencdec,
+                    shake_gcdestroy, 0, 0 },
+  shake256_gcops = { &shake256_xof, shake_gcencdec, shake_gcencdec,
+                    shake_gcdestroy, 0, 0 };
+
+static gcipher *shake128_gcinit(const void *k, size_t sz)
+{
+  shake_gcctx *cc = S_CREATE(shake_gcctx);
+  cc->gc.ops = &shake128_gcops;
+  shake128_init(&cc->c); shake_hash(&cc->c, k, sz); shake_xof(&cc->c);
+  return (&cc->gc);
+}
+
+static gcipher *shake256_gcinit(const void *k, size_t sz)
+{
+  shake_gcctx *cc = S_CREATE(shake_gcctx);
+  cc->gc.ops = &shake128_gcops;
+  shake256_init(&cc->c); shake_hash(&cc->c, k, sz); shake_xof(&cc->c);
+  return (&cc->gc);
+}
+
+const gccipher
+  shake128_xof = { "shake128-xof", shake128_keysz, 0, shake128_gcinit },
+  shake256_xof = { "shake256-xof", shake256_keysz, 0, shake256_gcinit };
+
+/* --- Random generator interface --- */
+
+typedef struct shake_grctx {
+  grand gr;
+  shake_ctx c;
+} shake_grctx;
+
+static int shake_grmisc(grand *r, unsigned op, ...)
+{
+  int rc = 0;
+  switch (op) {
+    case GRAND_CHECK: rc = 0; break;
+    default: GRAND_BADOP; break;
+  }
+  return (rc);
+}
+
+static void shake_grdestroy(grand *r)
+  { shake_grctx *cc = (shake_grctx *)r; BURN(cc); S_DESTROY(cc); }
+
+static octet shake_grbyte(grand *r)
+{
+  shake_grctx *cc = (shake_grctx *)r;
+  octet o;
+  shake_get(&cc->c, &o, 1);
+  return (o);
+}
+
+static uint32 shake_grword(grand *r)
+{
+  shake_grctx *cc = (shake_grctx *)r;
+  octet b[4];
+  shake_get(&cc->c, &b, 4);
+  return (LOAD32(b));
+}
+
+static void shake_grfill(grand *r, void *p, size_t sz)
+  { shake_grctx *cc = (shake_grctx *)r; shake_get(&cc->c, p, sz); }
+
+static const grand_ops
+  shake128_grops = { "shake128", GRAND_CRYPTO, 0,
+                    shake_grmisc, shake_grdestroy, shake_grword,
+                    shake_grbyte, shake_grword, grand_defaultrange,
+                    shake_grfill },
+  shake256_grops = { "shake256", GRAND_CRYPTO, 0,
+                    shake_grmisc, shake_grdestroy, shake_grword,
+                    shake_grbyte, shake_grword, grand_defaultrange,
+                    shake_grfill },
+  cshake128_grops = { "cshake128", GRAND_CRYPTO, 0,
+                     shake_grmisc, shake_grdestroy, shake_grword,
+                     shake_grbyte, shake_grword, grand_defaultrange,
+                     shake_grfill },
+  cshake256_grops = { "cshake256", GRAND_CRYPTO, 0,
+                     shake_grmisc, shake_grdestroy, shake_grword,
+                     shake_grbyte, shake_grword, grand_defaultrange,
+                     shake_grfill };
+
+/* --- @shake{128,256}_rand@ --- *
+ *
+ * Arguments:  @const void *k@ = pointer to seed material
+ *             @size_t sz@ = size of the seed
+ *
+ * Returns:    A pseudorandom generator with the given seed.
+ */
+
+grand *shake128_rand(const void *k, size_t sz)
+{
+  shake_grctx *cc = S_CREATE(shake_grctx);
+  cc->gr.ops = &shake128_grops;
+  shake128_init(&cc->c); shake_hash(&cc->c, k, sz); shake_xof(&cc->c);
+  return (&cc->gr);
+}
+
+grand *shake256_rand(const void *k, size_t sz)
+{
+  shake_grctx *cc = S_CREATE(shake_grctx);
+  cc->gr.ops = &shake256_grops;
+  shake256_init(&cc->c); shake_hash(&cc->c, k, sz); shake_xof(&cc->c);
+  return (&cc->gr);
+}
+
+/* --- @cshake{128,256}_rand@ --- *
+ *
+ * Arguments:  @const void *func@ = function name
+ *             @size_t fsz@ = length of function name
+ *             @const void *perso@ = personalization string
+ *             @size_t psz@ = length of personalization string
+ *             @const void *k@ = pointer to seed material
+ *             @size_t sz@ = size of the seed
+ *
+ * Returns:    A pseudorandom generator with the given seed.
+ */
+
+grand *cshake128_rand(const void *func, size_t fsz,
+                     const void *perso, size_t psz,
+                     const void *k, size_t sz)
+{
+  shake_grctx *cc = S_CREATE(shake_grctx);
+  cc->gr.ops = &cshake128_grops;
+  cshake128_init(&cc->c, func, fsz, perso, psz);
+  shake_hash(&cc->c, k, sz); shake_xof(&cc->c);
+  return (&cc->gr);
+}
+
+grand *cshake256_rand(const void *func, size_t fsz,
+                     const void *perso, size_t psz,
+                     const void *k, size_t sz)
+{
+  shake_grctx *cc = S_CREATE(shake_grctx);
+  cc->gr.ops = &cshake256_grops;
+  cshake256_init(&cc->c, func, fsz, perso, psz);
+  shake_hash(&cc->c, k, sz); shake_xof(&cc->c);
+  return (&cc->gr);
+}
+
+/*----- The KMAC variable-length PRF --------------------------------------*/
+
+typedef shake_ctx kmac_ctx;
+
+/* --- @kmac{128,256}_init@ --- *
+ *
+ * Arguments:  @kmac_ctx *ctx@ = pointer to context to fill in
+ *             @const char *perso@ = personalization string, or null
+ *             @size_t psz@ = length of personalization string
+ *             @const void *k@ = pointer to key material
+ *             @size_t sz@ = size of key material
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets up a KMAC context.  Use @kmac_hash@ to feed in the input
+ *             message.
+ */
+
+static void init_kmac(kmac_ctx *ctx,
+                     unsigned c0, const void *perso, size_t psz,
+                     const void *k, size_t sz)
+{
+  init_shake(ctx, c0, "KMAC", 4, perso, psz);
+  bytepad_before(ctx); stringenc(ctx, k, sz); bytepad_after(ctx);
+}
+
+void kmac128_init(kmac_ctx *ctx, const void *perso, size_t psz,
+                 const void *k, size_t sz)
+  { init_kmac(ctx, 128, perso, psz, k, sz); }
+
+void kmac256_init(kmac_ctx *ctx, const void *perso, size_t psz,
+                 const void *k, size_t sz)
+  { init_kmac(ctx, 256, perso, psz, k, sz); }
+
+/* --- @kmac_xof@ --- *
+ *
+ * Arguments:  @kmac_ctx *ctx@ = pointer to context
+ *
+ * Returns:    ---
+ *
+ * Use:                Marks the end of the message to be processed.  The output can
+ *             be read using @kmac_get@.
+ */
+
+void kmac_xof(kmac_ctx *ctx)
+  { rightenc_sz(ctx, 0); shake_xof(ctx); }
+
+/* --- @kmac_done@ --- *
+ *
+ * Arguments:  @kmac_ctx *ctx@ = pointer to context
+ *             @void *h@ = where to put the tag
+ *             @size_t hsz@ = size of tag to produce
+ *
+ * Returns:    ---
+ *
+ * Use:                Marks the end of the message to be processed and returns a
+ *             tag.  Note that the tag value is dependent on the output
+ *             size.
+ */
+
+void kmac_done(kmac_ctx *ctx, void *h, size_t hsz)
+  { rightenc_sz(ctx, 8*hsz); shake_done(ctx, h, hsz); }
+
+/* --- MAC interface --- */
+
+typedef struct kmac_ghctx {
+  ghash h;
+  kmac_ctx c;
+  octet hb[KMAC256_TAGSZ];
+} kmac_ghctx;
+
+typedef struct kmac_gmctx {
+  gmac m;
+  kmac_ctx c;
+  const ghash_ops *hops;
+} kmac_gmctx;
+
+static const ghash_ops kmac128_ghops, kmac256_ghops;
+static const gmac_ops kmac128_gmops, kmac256_gmops;
+
+static void kmac_ghhash(ghash *h, const void *p, size_t sz)
+  { kmac_ghctx *cc = (kmac_ghctx *)h; kmac_hash(&cc->c, p, sz); }
+
+static octet *kmac_ghdone(ghash *h, void *buf)
+{
+  kmac_ghctx *cc = (kmac_ghctx *)h;
+  if (!buf) buf = cc->hb;
+  kmac_done(&cc->c, buf, cc->h.ops->c->hashsz);
+  return (buf);
+}
+
+static void kmac_ghdestroy(ghash *h)
+  { kmac_ghctx *cc = (kmac_ghctx *)h; BURN(cc); S_DESTROY(cc); }
+
+static ghash *kmac_ghcopy(ghash *h)
+{
+  kmac_ghctx *cc = (kmac_ghctx *)h;
+  kmac_ghctx *hc = S_CREATE(kmac_ghctx);
+  *hc = *cc;
+  return (&hc->h);
+}
+
+static ghash *kmac_ghinit(void)
+  { assert(((void)"Attempt to instantiate an unkeyed MAC", 0)); return (0); }
+
+static ghash *kmac_gminit(gmac *m)
+{
+  kmac_gmctx *cc = (kmac_gmctx *)m;
+  kmac_ghctx *hc = S_CREATE(kmac_ghctx);
+  hc->h.ops = cc->hops;
+  hc->c = cc->c;
+  return (&hc->h);
+}
+
+static void kmac_gmdestroy(gmac *m)
+  { kmac_gmctx *cc = (kmac_gmctx *)m; BURN(cc); S_DESTROY(cc); }
+
+static gmac *kmac128_gmkey(const void *k, size_t sz)
+{
+  kmac_gmctx *cc = S_CREATE(kmac_gmctx);
+  cc->m.ops = &kmac128_gmops; cc->hops = &kmac128_ghops;
+  kmac128_init(&cc->c, 0, 0, k, sz);
+  return (&cc->m);
+}
+
+static gmac *kmac256_gmkey(const void *k, size_t sz)
+{
+  kmac_gmctx *cc = S_CREATE(kmac_gmctx);
+  cc->m.ops = &kmac256_gmops; cc->hops = &kmac256_ghops;
+  kmac256_init(&cc->c, 0, 0, k, sz);
+  return (&cc->m);
+}
+
+static const gchash
+  kmac128_ghcls = { "kmac128", KMAC128_TAGSZ, kmac_ghinit, 168 },
+  kmac256_ghcls = { "kmac256", KMAC256_TAGSZ, kmac_ghinit, 136 };
+
+const gcmac
+  kmac128 = { "kmac128", KMAC128_TAGSZ, kmac128_keysz, kmac128_gmkey },
+  kmac256 = { "kmac256", KMAC256_TAGSZ, kmac256_keysz, kmac256_gmkey };
+
+static const ghash_ops
+  kmac128_ghops = { &kmac128_ghcls, kmac_ghhash, kmac_ghdone,
+                   kmac_ghdestroy, kmac_ghcopy },
+  kmac256_ghops = { &kmac256_ghcls, kmac_ghhash, kmac_ghdone,
+                   kmac_ghdestroy, kmac_ghcopy };
+
+static const gmac_ops
+  kmac128_gmops = { &kmac128, kmac_gminit, kmac_gmdestroy },
+  kmac256_gmops = { &kmac256, kmac_gminit, kmac_gmdestroy };
+
+/* --- Random generator XOF interface --- */
+
+typedef struct kmac_grctx {
+  grand gr;
+  kmac_ctx k, c;
+} kmac_grctx;
+
+static int kmac_grmisc(grand *r, unsigned op, ...)
+{
+  kmac_grctx *cc = (kmac_grctx *)r;
+  int rc = 0;
+  octet buf[64];
+  va_list ap;
+  int i;
+  uint32 u;
+  grand *rr;
+  const void *p;
+  size_t sz;
+
+  va_start(ap, op);
+  switch (op) {
+    case GRAND_CHECK:
+      op = va_arg(ap, unsigned);
+      switch (op) {
+       case GRAND_SEEDINT: case GRAND_SEEDUINT32:
+       case GRAND_SEEDBLOCK: case GRAND_SEEDRAND:
+         rc = 1; break;
+       default:
+         rc = 0; break;
+      }
+      break;
+    case GRAND_SEEDINT:
+      i = va_arg(ap, int); STORE32_L(buf, i); p = buf; sz = 4; goto seed;
+    case GRAND_SEEDUINT32:
+      u = va_arg(ap, uint32); STORE32_L(buf, u); p = buf; sz = 4; goto seed;
+    case GRAND_SEEDRAND:
+      rr = va_arg(ap, grand *);
+      p = buf; sz = (200 - cc->c.h.r)/2; GR_FILL(rr, buf, sz); goto seed;
+    case GRAND_SEEDBLOCK:
+      p = va_arg(ap, const void *); sz = va_arg(ap, size_t);
+    seed:
+      cc->c = cc->k;
+      kmac_hash(&cc->c, p, sz);
+      kmac_xof(&cc->c);
+      break;
+    default: GRAND_BADOP; break;
+  }
+  return (rc);
+}
+
+static void kmac_grdestroy(grand *r)
+  { kmac_grctx *cc = (kmac_grctx *)r; BURN(cc); S_DESTROY(cc); }
+
+static octet kmac_grbyte(grand *r)
+{
+  kmac_grctx *cc = (kmac_grctx *)r;
+  octet o;
+  kmac_get(&cc->c, &o, 1);
+  return (o);
+}
+
+static uint32 kmac_grword(grand *r)
+{
+  kmac_grctx *cc = (kmac_grctx *)r;
+  octet b[4];
+  kmac_get(&cc->c, &b, 4);
+  return (LOAD32(b));
+}
+
+static void kmac_grfill(grand *r, void *p, size_t sz)
+  { kmac_grctx *cc = (kmac_grctx *)r; kmac_get(&cc->c, p, sz); }
+
+static const grand_ops
+  kmac128_grops = { "kmac128", GRAND_CRYPTO, 0,
+                   kmac_grmisc, kmac_grdestroy, kmac_grword,
+                   kmac_grbyte, kmac_grword, grand_defaultrange,
+                   kmac_grfill },
+  kmac256_grops = { "kmac256", GRAND_CRYPTO, 0,
+                   kmac_grmisc, kmac_grdestroy, kmac_grword,
+                   kmac_grbyte, kmac_grword, grand_defaultrange,
+                   kmac_grfill };
+
+/* --- @kmac{128,256}_rand@ --- *
+ *
+ * Arguments:  @const void *perso@ = personalization string, or null
+ *             @size_t psz@ = length of personalization string
+ *             @const void *k@ = pointer to seed material
+ *             @size_t sz@ = size of the seed
+ *
+ * Returns:    A pseudorandom generator with the given key.
+ *
+ * Use:                The generator processes an empty message by default, but this
+ *             can be changed by seeding it.
+ */
+
+grand *kmac128_rand(const void *perso, size_t psz, const void *k, size_t sz)
+{
+  kmac_grctx *cc = S_CREATE(kmac_grctx);
+  cc->gr.ops = &kmac128_grops;
+  kmac128_init(&cc->k, perso, psz, k, sz);
+  cc->c = cc->k; kmac_xof(&cc->c);
+  return (&cc->gr);
+}
+
+grand *kmac256_rand(const void *perso, size_t psz, const void *k, size_t sz)
+{
+  kmac_grctx *cc = S_CREATE(kmac_grctx);
+  cc->gr.ops = &kmac256_grops;
+  kmac256_init(&cc->k, perso, psz, k, sz);
+  cc->c = cc->k; kmac_xof(&cc->c);
+  return (&cc->gr);
+}
+
+/*----- Test rig ----------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#include <stdio.h>
+
+#include <mLib/report.h>
+#include <mLib/testrig.h>
+
+HASHES(HASH_VERIFYX)
+
+static int vrf_sha3_mct(void (*initfn)(sha3_ctx *),
+                       int n, dstr *in, dstr *out)
+{
+  sha3_ctx ctx;
+  dstr d = DSTR_INIT;
+  int ok = 1;
+  int i;
+
+  if (in->len != out->len) die(1, "inconsistent lengths");
+  dstr_ensure(&d, out->len); d.len = out->len;
+  memcpy(d.buf, in->buf, in->len);
+  for (i = 0; i < n; i++) {
+    initfn(&ctx);
+    sha3_hash(&ctx, d.buf, d.len);
+    sha3_done(&ctx, d.buf);
+  }
+
+  if (memcmp(d.buf, out->buf, out->len) != 0) {
+    ok = 0;
+    printf("\nfail\n\tsteps = %d\n\tinput = ", n);
+    type_hex.dump(in, stdout);
+    printf("\n\texpected = ");
+    type_hex.dump(out, stdout);
+    fputs("\n\tcomputed = ", stdout);
+    type_hex.dump(&d, stdout);
+    putchar('\n');
+  }
+
+  dstr_destroy(&d);
+  return (ok);
+}
+
+#define VRF_MCT(PRE, pre, name)                                                \
+  static int vrf_##pre##_mct(dstr *v)                                  \
+    { return (vrf_sha3_mct(pre##_init, *(int *)v[0].buf, &v[1], &v[2])); }
+HASHES(VRF_MCT)
+#undef VRF_MCT
+
+static int vrf_shaky(void (*initfn)(shake_ctx *,
+                                   const void *, size_t,
+                                   const void *, size_t),
+                    dstr *func, dstr *perso,
+                    dstr *m, dstr *want)
+{
+  shake_ctx ctx;
+  dstr d = DSTR_INIT;
+  int ok = 1;
+  int i;
+  const int *ip;
+  size_t sz;
+  octet *p;
+  static const int szs[] = { 1, 7, 192, -1, 0 };
+
+  dstr_ensure(&d, want->len); d.len = want->len;
+  for (ip = szs; *ip; ip++) {
+    initfn(&ctx,
+          func ? func->buf : 0, func ? func->len : 0,
+          perso ? perso->buf : 0, perso ? perso->len : 0);
+    p = (octet *)m->buf; sz = m->len;
+    i = (*ip == -1 || *ip > sz) ? sz : *ip;
+    while (sz) {
+      if (i > sz) i = sz;
+      shake_hash(&ctx, p, i);
+      p += i; sz -= i;
+    }
+    shake_xof(&ctx);
+
+    p = (octet *)d.buf; sz = d.len;
+    i = (*ip == -1 || *ip > sz) ? sz : *ip;
+    while (sz) {
+      if (i > sz) i = sz;
+      shake_get(&ctx, p, i);
+      p += i; sz -= i;
+    }
+
+    if (memcmp(d.buf, want->buf, want->len) != 0) {
+      ok = 0;
+      printf("\nfail (get):\n\tstep = %i\n\tinput = ", *ip);
+      type_hex.dump(m, stdout);
+      if (func) printf("\n\tfunction = `%s'", func->buf);
+      if (perso) printf("\n\tperso = `%s'", perso->buf);
+      printf("\n\texpected = ");
+      type_hex.dump(want, stdout);
+      fputs("\n\tcomputed = ", stdout);
+      type_hex.dump(&d, stdout);
+      putchar('\n');
+    }
+
+    initfn(&ctx,
+          func ? func->buf : 0, func ? func->len : 0,
+          perso ? perso->buf : 0, perso ? perso->len : 0);
+    p = (octet *)m->buf; sz = m->len;
+    i = (*ip == -1 || *ip > sz) ? sz : *ip;
+    while (sz) {
+      if (i > sz) i = sz;
+      shake_hash(&ctx, p, i);
+      p += i; sz -= i;
+    }
+    shake_xof(&ctx);
+
+    memset(d.buf, 0, d.len);
+    p = (octet *)d.buf; sz = d.len;
+    i = (*ip == -1 || *ip > sz) ? sz : *ip;
+    while (sz) {
+      if (i > sz) i = sz;
+      shake_mask(&ctx, p, p, i);
+      p += i; sz -= i;
+    }
+
+    if (memcmp(d.buf, want->buf, want->len) != 0) {
+      ok = 0;
+      printf("\nfail (mask):\n\tstep = %i\n\tinput = ", *ip);
+      type_hex.dump(m, stdout);
+      if (func) printf("\n\tfunction = `%s'", func->buf);
+      if (perso) printf("\n\tperso = `%s'", perso->buf);
+      printf("\n\texpected = ");
+      type_hex.dump(want, stdout);
+      fputs("\n\tcomputed = ", stdout);
+      type_hex.dump(&d, stdout);
+      putchar('\n');
+    }
+  }
+
+  dstr_destroy(&d);
+  return (ok);
+}
+
+static int vrf_cshake128(dstr *v)
+{
+  return (vrf_shaky(cshake128_init, &v[0], &v[1], &v[2], &v[3]));
+}
+
+static int vrf_cshake256(dstr *v)
+{
+  return (vrf_shaky(cshake256_init, &v[0], &v[1], &v[2], &v[3]));
+}
+
+static void shake128_init_adaptor(shake_ctx *ctx,
+                                 const void *func, size_t fsz,
+                                 const void *perso, size_t psz)
+  { assert(!fsz); assert(!psz); shake128_init(ctx);}
+
+static void shake256_init_adaptor(shake_ctx *ctx,
+                                 const void *func, size_t fsz,
+                                 const void *perso, size_t psz)
+  { assert(!fsz); assert(!psz); shake256_init(ctx);}
+
+static int vrf_shake128(dstr *v)
+  { return (vrf_shaky(shake128_init_adaptor, 0, 0, &v[0], &v[1])); }
+
+static int vrf_shake256(dstr *v)
+  { return (vrf_shaky(shake256_init_adaptor, 0, 0, &v[0], &v[1])); }
+
+static int vrf_kmac(void (*initfn)(kmac_ctx *, const void *, size_t,
+                                  const void *, size_t),
+                   dstr *perso, int tsz,
+                   dstr *k, dstr *m, dstr *want)
+{
+  kmac_ctx ctx;
+  dstr d = DSTR_INIT;
+  int ok = 1;
+
+  if (tsz && tsz != want->len) die(1, "inconsistent tag length request");
+  dstr_ensure(&d, want->len); d.len = want->len;
+  initfn(&ctx, perso->buf, perso->len, k->buf, k->len);
+  kmac_hash(&ctx, m->buf, m->len);
+  if (tsz) kmac_done(&ctx, d.buf, tsz);
+  else { kmac_xof(&ctx); kmac_get(&ctx, d.buf, d.len); }
+
+  if (memcmp(d.buf, want->buf, want->len) != 0) {
+    ok = 0;
+    printf("\nfail");
+    printf("\n\tperso = `%s'", perso->buf);
+    printf("\n\ttag size = %d", tsz);
+    printf("\n\tkey = "); type_hex.dump(k, stdout);
+    printf("\n\tinput = "); type_hex.dump(m, stdout);
+    printf("\n\texpected = "); type_hex.dump(want, stdout);
+    fputs("\n\tcomputed = ", stdout); type_hex.dump(&d, stdout);
+    putchar('\n');
+  }
+
+  dstr_destroy(&d);
+  return (ok);
+}
+
+static int vrf_kmac128(dstr *v)
+{
+  return (vrf_kmac(kmac128_init, &v[0], *(int *)v[1].buf,
+                  &v[2], &v[3], &v[4]));
+}
+
+static int vrf_kmac256(dstr *v)
+{
+  return (vrf_kmac(kmac256_init, &v[0], *(int *)v[1].buf,
+                  &v[2], &v[3], &v[4]));
+}
+
+static const test_chunk defs[] = {
+  HASHES(HASH_TESTDEFSX)
+#define VRF_MCTDEF(PRE, pre, name)                                     \
+  { name "-mct", vrf_##pre##_mct,                                      \
+    { &type_int, &type_hex, &type_hex, 0 } },
+  HASHES(VRF_MCTDEF)
+#undef VRF_MCTDEF
+  { "shake128", vrf_shake128, { &type_hex, &type_hex, 0 } },
+  { "shake256", vrf_shake256, { &type_hex, &type_hex, 0 } },
+  { "cshake128", vrf_cshake128,
+    { &type_string, &type_string, &type_hex, &type_hex, 0 } },
+  { "cshake256", vrf_cshake256,
+    { &type_string, &type_string, &type_hex, &type_hex, 0 } },
+  { "kmac128", vrf_kmac128,
+    { &type_string, &type_int, &type_hex, &type_hex, &type_hex, 0 } },
+  { "kmac256", vrf_kmac256,
+    { &type_string, &type_int, &type_hex, &type_hex, &type_hex, 0 } },
+  { 0, 0, { 0 } }
+};
+
+int main(int argc, char *argv[])
+{
+  test_run(argc, argv, defs, SRCDIR"/t/sha3");
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/