Bruno Haible <br...@clisp.org> writes:

> Thanks for testing. Now I'm convinced of the absence of a fatal typo 
> somewhere.
> [...]
> I see. Indeed, some Gnulib modules make fewer assumptions than others /
> strive for greater portability than others. Well spotted!

Thanks for the review, all. I pushed the attached v2 patches. The only
change outside of some comment/typo fixes is fixing two -Wpointer-arith
warnings I saw when building coreutils. I always fix those anyways, even
though I have personally not used a compiler that does not support the
GNU C extension allowing addition to void pointers [1]. Bruno, I am
curious, do you know of any compilers where that is not the case?

I'll add the test case Pádraig recommended in a seperate commit later.
And clean up my local coreutils patch before sending.

Collin

[1] 
https://gcc.gnu.org/onlinedocs/gcc/Pointer-Arith.html#Arithmetic-on-void--and-Function-Pointers

>From 4cdf7734fbdb040557f253ceb885c8aa1dbf225b Mon Sep 17 00:00:00 2001
Message-ID: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
From: Collin Funk <collin.fu...@gmail.com>
Date: Sun, 31 Aug 2025 11:43:37 -0700
Subject: [PATCH v2 1/4] crypto/sha3-buffer: New module.

* lib/u64.h (u64getlo, u64not): New functions.
* lib/sha3.c: New file, based on lib/sha512.c.
* lib/sha3.h: New file, based on lib/sha512.h.
* modules/crypto/sha3-buffer: New file.
---
 ChangeLog                  |   8 +
 lib/sha3.c                 | 316 +++++++++++++++++++++++++++++++++++++
 lib/sha3.h                 | 101 ++++++++++++
 lib/u64.h                  |  19 +++
 modules/crypto/sha3-buffer |  28 ++++
 5 files changed, 472 insertions(+)
 create mode 100644 lib/sha3.c
 create mode 100644 lib/sha3.h
 create mode 100644 modules/crypto/sha3-buffer

diff --git a/ChangeLog b/ChangeLog
index 70dc193626..24093d2d63 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2025-08-31  Collin Funk  <collin.fu...@gmail.com>
+
+	crypto/sha3-buffer: New module.
+	* lib/u64.h (u64getlo, u64not): New functions.
+	* lib/sha3.c: New file, based on lib/sha512.c.
+	* lib/sha3.h: New file, based on lib/sha512.h.
+	* modules/crypto/sha3-buffer: New file.
+
 2025-08-31  Paul Eggert  <egg...@cs.ucla.edu>
 
 	u64: avoid theoretical problem with >64-bit int
diff --git a/lib/sha3.c b/lib/sha3.c
new file mode 100644
index 0000000000..f4e56ab3a1
--- /dev/null
+++ b/lib/sha3.c
@@ -0,0 +1,316 @@
+/* sha3.c - Functions to calculate SHA-3 hashes as specified by FIPS-202.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Collin Funk <collin.fu...@gmail.com>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sha3.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <byteswap.h>
+#ifdef WORDS_BIGENDIAN
+# define SWAP(n) u64bswap (n)
+#else
+# define SWAP(n) (n)
+#endif
+
+static const u64 rc[] = {
+  u64init (0x00000000, 0x00000001), u64init (0x00000000, 0x00008082),
+  u64init (0x80000000, 0x0000808A), u64init (0x80000000, 0x80008000),
+  u64init (0x00000000, 0x0000808B), u64init (0x00000000, 0x80000001),
+  u64init (0x80000000, 0x80008081), u64init (0x80000000, 0x00008009),
+  u64init (0x00000000, 0x0000008A), u64init (0x00000000, 0x00000088),
+  u64init (0x00000000, 0x80008009), u64init (0x00000000, 0x8000000A),
+  u64init (0x00000000, 0x8000808B), u64init (0x80000000, 0x0000008B),
+  u64init (0x80000000, 0x00008089), u64init (0x80000000, 0x00008003),
+  u64init (0x80000000, 0x00008002), u64init (0x80000000, 0x00000080),
+  u64init (0x00000000, 0x0000800A), u64init (0x80000000, 0x8000000A),
+  u64init (0x80000000, 0x80008081), u64init (0x80000000, 0x00008080),
+  u64init (0x00000000, 0x80000001), u64init (0x80000000, 0x80008008)
+};
+
+#define DEFINE_SHA3_INIT_CTX(SIZE)                                      \
+  void                                                                  \
+  sha3_##SIZE##_init_ctx (struct sha3_ctx *ctx)                         \
+  {                                                                     \
+    memset (&ctx->state, '\0', sizeof ctx->state);                      \
+    ctx->buflen = 0;                                                    \
+    ctx->digestlen = SHA3_##SIZE##_DIGEST_SIZE;                         \
+    ctx->blocklen = SHA3_##SIZE##_BLOCK_SIZE;                           \
+  }
+
+DEFINE_SHA3_INIT_CTX (224)
+DEFINE_SHA3_INIT_CTX (256)
+DEFINE_SHA3_INIT_CTX (384)
+DEFINE_SHA3_INIT_CTX (512)
+
+/* Copy the value from V into the memory location pointed to by *CP,
+   If your architecture allows unaligned access, this is equivalent to
+   * (__typeof__ (v) *) cp = v  */
+static void
+set_uint64 (char *cp, u64 v)
+{
+  memcpy (cp, &v, sizeof v);
+}
+
+void *
+sha3_read_ctx (const struct sha3_ctx *ctx, void *resbuf)
+{
+  char *r = resbuf;
+  int i;
+  size_t words = ctx->digestlen / sizeof *ctx->state;
+  size_t bytes = ctx->digestlen % sizeof *ctx->state;
+
+  for (i = 0; i < words; ++i, r += sizeof *ctx->state)
+    set_uint64 (r, SWAP (ctx->state[i]));
+  if (bytes)
+    {
+      u64 word = ctx->state[i];
+      do
+        {
+          *r++ = u64getlo (word) & 0xFF;
+          word = u64shr (word, 8);
+        }
+      while (--bytes);
+    }
+  return resbuf;
+}
+
+static void
+sha3_conclude_ctx (struct sha3_ctx *ctx)
+{
+  ctx->buffer[ctx->buflen++] = 0x06;
+  memset (ctx->buffer + ctx->buflen, '\0', ctx->blocklen - ctx->buflen);
+  ctx->buffer[ctx->blocklen - 1] |= 0x80;
+  sha3_process_block (ctx->buffer, ctx->blocklen, ctx);
+}
+
+void *
+sha3_finish_ctx (struct sha3_ctx *ctx, void *resbuf)
+{
+  sha3_conclude_ctx (ctx);
+  return sha3_read_ctx (ctx, resbuf);
+}
+
+#define DEFINE_SHA3_BUFFER(SIZE)                                        \
+  void *                                                                \
+  sha3_##SIZE##_buffer (const char *buffer, size_t len, void *resblock) \
+  {                                                                     \
+    struct sha3_ctx ctx;                                                \
+    sha3_##SIZE##_init_ctx (&ctx);                                      \
+    sha3_process_bytes (buffer, len, &ctx);                             \
+    return sha3_finish_ctx (&ctx, resblock);                            \
+  }
+
+DEFINE_SHA3_BUFFER (224)
+DEFINE_SHA3_BUFFER (256)
+DEFINE_SHA3_BUFFER (384)
+DEFINE_SHA3_BUFFER (512)
+
+void
+sha3_process_bytes (const void *buffer, size_t len, struct sha3_ctx *ctx)
+{
+  if (0 < ctx->buflen)
+    {
+      size_t left = ctx->blocklen - ctx->buflen;
+      if (len < left)
+        {
+          /* Not enough to fill a full block.  */
+          memcpy (ctx->buffer + ctx->buflen, buffer, len);
+          ctx->buflen += len;
+          return;
+        }
+      /* Process the block that already had bytes buffered.  */
+      memcpy (ctx->buffer + ctx->buflen, buffer, left);
+      buffer = (char *) buffer + left;
+      len -= left;
+      sha3_process_block (ctx->buffer, ctx->blocklen, ctx);
+    }
+  /* Process as many complete blocks as possible.  */
+  if (0 < len)
+    {
+      size_t full_blocks = (len / ctx->blocklen) * ctx->blocklen;
+      sha3_process_block (buffer, full_blocks, ctx);
+      buffer = (char *) buffer + full_blocks;
+      len -= full_blocks;
+      memcpy (ctx->buffer, buffer, len);
+      ctx->buflen = len;
+    }
+}
+
+void
+sha3_process_block (const void *buffer, size_t len, struct sha3_ctx *ctx)
+{
+  u64 *a = ctx->state;
+  const u64 *words = buffer;
+  size_t nwords = len / sizeof *words;
+  const u64 *endp = words + nwords;
+  u64 c[5];
+  u64 d[5];
+  u64 t1;
+  u64 t2;
+
+  while (words < endp)
+    {
+      for (size_t i = 0; i < ctx->blocklen / sizeof *ctx->state; ++i, ++words)
+        ctx->state[i] = u64xor (ctx->state[i], SWAP (*words));
+      for (int i = 0; i < 24; ++i)
+        {
+          /* Theta step 1.  */
+          c[0] = u64xor (u64xor (u64xor (u64xor (a[0], a[5]), a[10]),
+                                 a[15]), a[20]);
+          c[1] = u64xor (u64xor (u64xor (u64xor (a[1], a[6]), a[11]),
+                                 a[16]), a[21]);
+          c[2] = u64xor (u64xor (u64xor (u64xor (a[2], a[7]), a[12]),
+                                 a[17]), a[22]);
+          c[3] = u64xor (u64xor (u64xor (u64xor (a[3], a[8]), a[13]),
+                                 a[18]), a[23]);
+          c[4] = u64xor (u64xor (u64xor (u64xor (a[4], a[9]), a[14]),
+                                 a[19]), a[24]);
+
+          /* Theta step 2.  */
+          d[0] = u64xor (c[4], u64rol (c[1], 1));
+          d[1] = u64xor (c[0], u64rol (c[2], 1));
+          d[2] = u64xor (c[1], u64rol (c[3], 1));
+          d[3] = u64xor (c[2], u64rol (c[4], 1));
+          d[4] = u64xor (c[3], u64rol (c[0], 1));
+
+          /* Theta step 3.  */
+          a[0] = u64xor (a[0], d[0]);
+          a[5] = u64xor (a[5], d[0]);
+          a[10] = u64xor (a[10], d[0]);
+          a[15] = u64xor (a[15], d[0]);
+          a[20] = u64xor (a[20], d[0]);
+          a[1] = u64xor (a[1], d[1]);
+          a[6] = u64xor (a[6], d[1]);
+          a[11] = u64xor (a[11], d[1]);
+          a[16] = u64xor (a[16], d[1]);
+          a[21] = u64xor (a[21], d[1]);
+          a[2] = u64xor (a[2], d[2]);
+          a[7] = u64xor (a[7], d[2]);
+          a[12] = u64xor (a[12], d[2]);
+          a[17] = u64xor (a[17], d[2]);
+          a[22] = u64xor (a[22], d[2]);
+          a[3] = u64xor (a[3], d[3]);
+          a[8] = u64xor (a[8], d[3]);
+          a[13] = u64xor (a[13], d[3]);
+          a[18] = u64xor (a[18], d[3]);
+          a[23] = u64xor (a[23], d[3]);
+          a[4] = u64xor (a[4], d[4]);
+          a[9] = u64xor (a[9], d[4]);
+          a[14] = u64xor (a[14], d[4]);
+          a[19] = u64xor (a[19], d[4]);
+          a[24] = u64xor (a[24], d[4]);
+
+          /* Rho and Pi.  */
+          t1 = a[1];
+          t2 = u64rol (t1, 1);
+          t1 = a[10];
+          a[10] = t2;
+          t2 = u64rol (t1, 3);
+          t1 = a[7];
+          a[7] = t2;
+          t2 = u64rol (t1, 6);
+          t1 = a[11];
+          a[11] = t2;
+          t2 = u64rol (t1, 10);
+          t1 = a[17];
+          a[17] = t2;
+          t2 = u64rol (t1, 15);
+          t1 = a[18];
+          a[18] = t2;
+          t2 = u64rol (t1, 21);
+          t1 = a[3];
+          a[3] = t2;
+          t2 = u64rol (t1, 28);
+          t1 = a[5];
+          a[5] = t2;
+          t2 = u64rol (t1, 36);
+          t1 = a[16];
+          a[16] = t2;
+          t2 = u64rol (t1, 45);
+          t1 = a[8];
+          a[8] = t2;
+          t2 = u64rol (t1, 55);
+          t1 = a[21];
+          a[21] = t2;
+          t2 = u64rol (t1, 2);
+          t1 = a[24];
+          a[24] = t2;
+          t2 = u64rol (t1, 14);
+          t1 = a[4];
+          a[4] = t2;
+          t2 = u64rol (t1, 27);
+          t1 = a[15];
+          a[15] = t2;
+          t2 = u64rol (t1, 41);
+          t1 = a[23];
+          a[23] = t2;
+          t2 = u64rol (t1, 56);
+          t1 = a[19];
+          a[19] = t2;
+          t2 = u64rol (t1, 8);
+          t1 = a[13];
+          a[13] = t2;
+          t2 = u64rol (t1, 25);
+          t1 = a[12];
+          a[12] = t2;
+          t2 = u64rol (t1, 43);
+          t1 = a[2];
+          a[2] = t2;
+          t2 = u64rol (t1, 62);
+          t1 = a[20];
+          a[20] = t2;
+          t2 = u64rol (t1, 18);
+          t1 = a[14];
+          a[14] = t2;
+          t2 = u64rol (t1, 39);
+          t1 = a[22];
+          a[22] = t2;
+          t2 = u64rol (t1, 61);
+          t1 = a[9];
+          a[9] = t2;
+          t2 = u64rol (t1, 20);
+          t1 = a[6];
+          a[6] = t2;
+          t2 = u64rol (t1, 44);
+          t1 = a[1];
+          a[1] = t2;
+
+          /* Chi.  */
+          for (int j = 0; j < 25; j += 5)
+            {
+              t1 = a[j];
+              t2 = a[j + 1];
+              a[j] = u64xor (a[j], u64and (u64not (a[j + 1]), a[j + 2]));
+              a[j + 1] = u64xor (a[j + 1], u64and (u64not (a[j + 2]),
+                                                   a[j + 3]));
+              a[j + 2] = u64xor (a[j + 2], u64and (u64not (a[j + 3]),
+                                                   a[j + 4]));
+              a[j + 3] = u64xor (a[j + 3], u64and (u64not (a[j + 4]), t1));
+              a[j + 4] = u64xor (a[j + 4], u64and (u64not (t1), t2));
+            }
+
+          /* Iota.  */
+          a[0] = u64xor (a[0], rc[i]);
+        }
+    }
+}
diff --git a/lib/sha3.h b/lib/sha3.h
new file mode 100644
index 0000000000..c299c6fab7
--- /dev/null
+++ b/lib/sha3.h
@@ -0,0 +1,101 @@
+/* sha3.h - Functions to calculate SHA-3 hashes as specified by FIPS-202.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Collin Funk <collin.fu...@gmail.com>, 2025.  */
+
+#ifndef SHA3_H
+# define SHA3_H 1
+
+# include <stdio.h>
+# include <stdint.h>
+
+# include "u64.h"
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/* Digest sizes in bytes.  */
+enum { SHA3_224_DIGEST_SIZE = 224 / 8 };
+enum { SHA3_256_DIGEST_SIZE = 256 / 8 };
+enum { SHA3_384_DIGEST_SIZE = 384 / 8 };
+enum { SHA3_512_DIGEST_SIZE = 512 / 8 };
+
+/* Block sizes in bytes.  */
+enum { SHA3_224_BLOCK_SIZE = 1152 / 8 };
+enum { SHA3_256_BLOCK_SIZE = 1088 / 8 };
+enum { SHA3_384_BLOCK_SIZE = 832 / 8 };
+enum { SHA3_512_BLOCK_SIZE = 576 / 8 };
+
+/* Structure to save state of computation between the single steps.  */
+struct sha3_ctx
+{
+  u64 state[25];
+  uint8_t buffer[144]; /* Up to BLOCKLEN in use.  */
+  size_t buflen;       /* ≥ 0, ≤ BLOCKLEN  */
+  size_t digestlen;    /* One of SHA3_{224,256,384,512}_DIGEST_SIZE.  */
+  size_t blocklen;     /* One of SHA3_{224,256,384,512}_BLOCK_SIZE.  */
+};
+
+/* Initialize structure containing state of computation.  */
+extern void sha3_224_init_ctx (struct sha3_ctx *ctx);
+extern void sha3_256_init_ctx (struct sha3_ctx *ctx);
+extern void sha3_384_init_ctx (struct sha3_ctx *ctx);
+extern void sha3_512_init_ctx (struct sha3_ctx *ctx);
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is necessary that LEN is a multiple of the BLOCKLEN member of CTX!!!  */
+extern void sha3_process_block (const void *buffer, size_t len,
+                                struct sha3_ctx *ctx);
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+   It is NOT required that LEN is a multiple of the BLOCKLEN member of CTX.  */
+extern void sha3_process_bytes (const void *buffer, size_t len,
+                                struct sha3_ctx *ctx);
+
+/* Process the remaining bytes in the buffer and put result from CTX in RESBUF.
+   The result is always in little endian byte order, so that a byte-wise output
+   yields to the wanted ASCII representation of the message digest.  */
+extern void *sha3_finish_ctx (struct sha3_ctx *ctx, void *restrict resbuf);
+
+/* Put result from CTX in RESBUF.  The result is always in little endian byte
+   order, so that a byte-wise output yields to the wanted ASCII representation
+   of the message digest.  */
+extern void *sha3_read_ctx (const struct sha3_ctx *ctx,
+                            void *restrict resbuf);
+
+/* Compute a SHA-3 message digest for LEN bytes beginning at BUFFER.
+   The result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.  */
+extern void *sha3_224_buffer (const char *buffer, size_t len,
+                              void *restrict resblock);
+extern void *sha3_256_buffer (const char *buffer, size_t len,
+                              void *restrict resblock);
+extern void *sha3_384_buffer (const char *buffer, size_t len,
+                              void *restrict resblock);
+extern void *sha3_512_buffer (const char *buffer, size_t len,
+                              void *restrict resblock);
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif
diff --git a/lib/u64.h b/lib/u64.h
index 01135057c2..a344bc7bd5 100644
--- a/lib/u64.h
+++ b/lib/u64.h
@@ -46,7 +46,9 @@ typedef uint64_t u64;
 # define u64hilo(hi, lo) ((u64) (((u64) (hi) << 32) + (lo)))
 # define u64init(hi, lo) u64hilo (hi, lo)
 # define u64lo(x) ((u64) (x))
+# define u64getlo(x) ((uint32_t) ((x) & UINT32_MAX))
 # define u64size(x) u64lo (x)
+# define u64not(x) (~(x))
 # define u64lt(x, y) ((x) < (y))
 # define u64and(x, y) ((x) & (y))
 # define u64or(x, y) ((x) | (y))
@@ -95,6 +97,13 @@ u64lo (unsigned int lo)
   return r;
 }
 
+/* Return the low 32 bits of the u64 value X.  */
+_GL_U64_INLINE unsigned int
+u64getlo (u64 x)
+{
+  return x.lo & _GL_U64_MASK32;
+}
+
 /* Return a u64 value representing SIZE, where 0 <= SIZE < 2**64.  */
 _GL_U64_INLINE u64
 u64size (size_t size)
@@ -105,6 +114,16 @@ u64size (size_t size)
   return r;
 }
 
+/* Return the bitwise NOT of X.  */
+_GL_U64_INLINE u64
+u64not (u64 x)
+{
+  u64 r;
+  r.hi = ~x.hi;
+  r.lo = ~x.lo;
+  return r;
+}
+
 /* Return X < Y.  */
 _GL_U64_INLINE bool
 u64lt (u64 x, u64 y)
diff --git a/modules/crypto/sha3-buffer b/modules/crypto/sha3-buffer
new file mode 100644
index 0000000000..375e7ba6a2
--- /dev/null
+++ b/modules/crypto/sha3-buffer
@@ -0,0 +1,28 @@
+Description:
+Compute SHA-3 checksums.
+
+Files:
+lib/sha3.h
+lib/sha3.c
+
+Depends-on:
+byteswap
+c99
+stdint-h
+u64
+
+configure.ac:
+AC_REQUIRE([AC_C_RESTRICT])
+AC_REQUIRE([gl_BIGENDIAN])
+
+Makefile.am:
+lib_SOURCES += sha3.c
+
+Include:
+"sha3.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.51.0

>From ec500f501da4eab0eecb862ad734d9276c600654 Mon Sep 17 00:00:00 2001
Message-ID: <ec500f501da4eab0eecb862ad734d9276c600654.1756677369.git.collin.fu...@gmail.com>
In-Reply-To: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
References: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
From: Collin Funk <collin.fu...@gmail.com>
Date: Sun, 31 Aug 2025 12:51:14 -0700
Subject: [PATCH v2 2/4] crypto/sha3-buffer: Add tests.

* modules/crypto/sha3-buffer-tests: New file.
* tests/test-sha3-224-buffer.c: Likewise.
* tests/test-sha3-256-buffer.c: Likewise.
* tests/test-sha3-384-buffer.c: Likewise.
* tests/test-sha3-512-buffer.c: Likewise.
* tests/bench-sha3-224.c: Likewise.
* tests/bench-sha3-256.c: Likewise.
* tests/bench-sha3-384.c: Likewise.
* tests/bench-sha3-512.c: Likewise.
---
 ChangeLog                        |  11 ++++
 modules/crypto/sha3-buffer-tests |  30 +++++++++
 tests/bench-sha3-224.c           |  24 +++++++
 tests/bench-sha3-256.c           |  24 +++++++
 tests/bench-sha3-384.c           |  24 +++++++
 tests/bench-sha3-512.c           |  24 +++++++
 tests/test-sha3-224-buffer.c     |  93 +++++++++++++++++++++++++++
 tests/test-sha3-256-buffer.c     |  94 +++++++++++++++++++++++++++
 tests/test-sha3-384-buffer.c     |  99 +++++++++++++++++++++++++++++
 tests/test-sha3-512-buffer.c     | 105 +++++++++++++++++++++++++++++++
 10 files changed, 528 insertions(+)
 create mode 100644 modules/crypto/sha3-buffer-tests
 create mode 100644 tests/bench-sha3-224.c
 create mode 100644 tests/bench-sha3-256.c
 create mode 100644 tests/bench-sha3-384.c
 create mode 100644 tests/bench-sha3-512.c
 create mode 100644 tests/test-sha3-224-buffer.c
 create mode 100644 tests/test-sha3-256-buffer.c
 create mode 100644 tests/test-sha3-384-buffer.c
 create mode 100644 tests/test-sha3-512-buffer.c

diff --git a/ChangeLog b/ChangeLog
index 24093d2d63..924d67e135 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2025-08-31  Collin Funk  <collin.fu...@gmail.com>
 
+	crypto/sha3-buffer: Add tests.
+	* modules/crypto/sha3-buffer-tests: New file.
+	* tests/test-sha3-224-buffer.c: Likewise.
+	* tests/test-sha3-256-buffer.c: Likewise.
+	* tests/test-sha3-384-buffer.c: Likewise.
+	* tests/test-sha3-512-buffer.c: Likewise.
+	* tests/bench-sha3-224.c: Likewise.
+	* tests/bench-sha3-256.c: Likewise.
+	* tests/bench-sha3-384.c: Likewise.
+	* tests/bench-sha3-512.c: Likewise.
+
 	crypto/sha3-buffer: New module.
 	* lib/u64.h (u64getlo, u64not): New functions.
 	* lib/sha3.c: New file, based on lib/sha512.c.
diff --git a/modules/crypto/sha3-buffer-tests b/modules/crypto/sha3-buffer-tests
new file mode 100644
index 0000000000..0f9e57318a
--- /dev/null
+++ b/modules/crypto/sha3-buffer-tests
@@ -0,0 +1,30 @@
+Files:
+tests/test-sha3-224-buffer.c
+tests/test-sha3-256-buffer.c
+tests/test-sha3-384-buffer.c
+tests/test-sha3-512-buffer.c
+tests/bench-sha3-224.c
+tests/bench-sha3-256.c
+tests/bench-sha3-384.c
+tests/bench-sha3-512.c
+tests/bench-digest.h
+tests/bench.h
+
+Depends-on:
+c99
+getrusage
+gettimeofday
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-sha3-224-buffer test-sha3-256-buffer
+TESTS += test-sha3-384-buffer test-sha3-512-buffer
+check_PROGRAMS += test-sha3-224-buffer test-sha3-256-buffer
+check_PROGRAMS += test-sha3-384-buffer test-sha3-512-buffer
+noinst_PROGRAMS += bench-sha3-224 bench-sha3-256
+noinst_PROGRAMS += bench-sha3-384 bench-sha3-512
+bench_sha3_224_CPPFLAGS = $(AM_CPPFLAGS) -DNDEBUG
+bench_sha3_256_CPPFLAGS = $(AM_CPPFLAGS) -DNDEBUG
+bench_sha3_384_CPPFLAGS = $(AM_CPPFLAGS) -DNDEBUG
+bench_sha3_512_CPPFLAGS = $(AM_CPPFLAGS) -DNDEBUG
diff --git a/tests/bench-sha3-224.c b/tests/bench-sha3-224.c
new file mode 100644
index 0000000000..db90e654fe
--- /dev/null
+++ b/tests/bench-sha3-224.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 Free Software Foundation, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Benchmark program for the sha3_224_buffer function.  */
+
+#include <config.h>
+
+#include "sha3.h"
+
+#define FUNC sha3_224_buffer
+#include "bench-digest.h"
diff --git a/tests/bench-sha3-256.c b/tests/bench-sha3-256.c
new file mode 100644
index 0000000000..24583c02b1
--- /dev/null
+++ b/tests/bench-sha3-256.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 Free Software Foundation, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Benchmark program for the sha3_256_buffer function.  */
+
+#include <config.h>
+
+#include "sha3.h"
+
+#define FUNC sha3_256_buffer
+#include "bench-digest.h"
diff --git a/tests/bench-sha3-384.c b/tests/bench-sha3-384.c
new file mode 100644
index 0000000000..09834adeaa
--- /dev/null
+++ b/tests/bench-sha3-384.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 Free Software Foundation, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Benchmark program for the sha3_384_buffer function.  */
+
+#include <config.h>
+
+#include "sha3.h"
+
+#define FUNC sha3_384_buffer
+#include "bench-digest.h"
diff --git a/tests/bench-sha3-512.c b/tests/bench-sha3-512.c
new file mode 100644
index 0000000000..8479ffc5ad
--- /dev/null
+++ b/tests/bench-sha3-512.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 Free Software Foundation, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Benchmark program for the sha3_512_buffer function.  */
+
+#include <config.h>
+
+#include "sha3.h"
+
+#define FUNC sha3_512_buffer
+#include "bench-digest.h"
diff --git a/tests/test-sha3-224-buffer.c b/tests/test-sha3-224-buffer.c
new file mode 100644
index 0000000000..0fe5f329d8
--- /dev/null
+++ b/tests/test-sha3-224-buffer.c
@@ -0,0 +1,93 @@
+/* Test of the sha3_224_buffer() function.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Collin Funk <collin.fu...@gmail.com>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sha3.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct test_case {
+  char const *message;
+  char const *digest;
+};
+
+static const struct test_case test_cases[] = {
+  { "", "\x6b\x4e\x03\x42\x36\x67\xdb\xb7\x3b\x6e\x15\x45\x4f\x0e\xb1\xab\xd4"
+        "\x59\x7f\x9a\x1b\x07\x8e\x3f\x5b\x5a\x6b\xc7" },
+  { "abc", "\xe6\x42\x82\x4c\x3f\x8c\xf2\x4a\xd0\x92\x34\xee\x7d\x3c\x76\x6f"
+           "\xc9\xa3\xa5\x16\x8d\x0c\x94\xad\x73\xb4\x6f\xdf" },
+  { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    "\x8a\x24\x10\x8b\x15\x4a\xda\x21\xc9\xfd\x55\x74\x49\x44\x79\xba\x5c\x7e"
+    "\x7a\xb7\x6e\xf2\x64\xea\xd0\xfc\xce\x33" },
+  { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopj"
+    "klmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    "\x54\x3e\x68\x68\xe1\x66\x6c\x1a\x64\x36\x30\xdf\x77\x36\x7a\xe5\xa6\x2a"
+    "\x85\x07\x0a\x51\xc1\x4c\xbf\x66\x5c\xbc" },
+};
+
+static int
+check (char const *message, size_t len, char const *expect)
+{
+  char buf[SHA3_224_DIGEST_SIZE];
+  if (memcmp (sha3_224_buffer (message, len, buf),
+              expect, SHA3_224_DIGEST_SIZE) != 0)
+    {
+      size_t i;
+      printf ("expected:\n");
+      for (i = 0; i < SHA3_224_DIGEST_SIZE; i++)
+        printf ("%02x ", expect[i] & 0xFFu);
+      printf ("\ncomputed:\n");
+      for (i = 0; i < SHA3_224_DIGEST_SIZE; i++)
+        printf ("%02x ", buf[i] & 0xFFu);
+      printf ("\n");
+      return 1;
+    }
+  return 0;
+}
+
+int
+main (void)
+{
+  /* Check the test vectors.  */
+  for (size_t i = 0; i < sizeof test_cases / sizeof *test_cases; ++i)
+    {
+      char const *message = test_cases[i].message;
+      char const *digest = test_cases[i].digest;
+      if (check (message, strlen (message), digest) != 0)
+        return 1;
+    }
+
+  /* Check that a large buffer works.  */
+  char *large = malloc (1000000);
+  if (large != NULL)
+    {
+      memset (large, 'a', 1000000);
+      char const *expect = ("\xd6\x93\x35\xb9\x33\x25\x19\x2e\x51\x6a\x91\x2e"
+                            "\x6d\x19\xa1\x5c\xb5\x1c\x6e\xd5\xc1\x52\x43\xe7"
+                            "\xa7\xfd\x65\x3c");
+      if (check (large, 1000000, expect) != 0)
+        return 1;
+      free (large);
+    }
+
+  return 0;
+}
diff --git a/tests/test-sha3-256-buffer.c b/tests/test-sha3-256-buffer.c
new file mode 100644
index 0000000000..8d47874adc
--- /dev/null
+++ b/tests/test-sha3-256-buffer.c
@@ -0,0 +1,94 @@
+/* Test of the sha3_256_buffer() function.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Collin Funk <collin.fu...@gmail.com>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sha3.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct test_case {
+  char const *message;
+  char const *digest;
+};
+
+static const struct test_case test_cases[] = {
+  { "", "\xa7\xff\xc6\xf8\xbf\x1e\xd7\x66\x51\xc1\x47\x56\xa0\x61\xd6\x62\xf5"
+        "\x80\xff\x4d\xe4\x3b\x49\xfa\x82\xd8\x0a\x4b\x80\xf8\x43\x4a" },
+  { "abc",
+    "\x3a\x98\x5d\xa7\x4f\xe2\x25\xb2\x04\x5c\x17\x2d\x6b\xd3\x90\xbd\x85\x5f"
+    "\x08\x6e\x3e\x9d\x52\x5b\x46\xbf\xe2\x45\x11\x43\x15\x32" },
+  { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    "\x41\xc0\xdb\xa2\xa9\xd6\x24\x08\x49\x10\x03\x76\xa8\x23\x5e\x2c\x82\xe1"
+    "\xb9\x99\x8a\x99\x9e\x21\xdb\x32\xdd\x97\x49\x6d\x33\x76" },
+  { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopj"
+    "klmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    "\x91\x6f\x60\x61\xfe\x87\x97\x41\xca\x64\x69\xb4\x39\x71\xdf\xdb\x28\xb1"
+    "\xa3\x2d\xc3\x6c\xb3\x25\x4e\x81\x2b\xe2\x7a\xad\x1d\x18" },
+};
+
+static int
+check (char const *message, size_t len, char const *expect)
+{
+  char buf[SHA3_256_DIGEST_SIZE];
+  if (memcmp (sha3_256_buffer (message, len, buf),
+              expect, SHA3_256_DIGEST_SIZE) != 0)
+    {
+      size_t i;
+      printf ("expected:\n");
+      for (i = 0; i < SHA3_256_DIGEST_SIZE; i++)
+        printf ("%02x ", expect[i] & 0xFFu);
+      printf ("\ncomputed:\n");
+      for (i = 0; i < SHA3_256_DIGEST_SIZE; i++)
+        printf ("%02x ", buf[i] & 0xFFu);
+      printf ("\n");
+      return 1;
+    }
+  return 0;
+}
+
+int
+main (void)
+{
+  /* Check the test vectors.  */
+  for (size_t i = 0; i < sizeof test_cases / sizeof *test_cases; ++i)
+    {
+      char const *message = test_cases[i].message;
+      char const *digest = test_cases[i].digest;
+      if (check (message, strlen (message), digest) != 0)
+        return 1;
+    }
+
+  /* Check that a large buffer works.  */
+  char *large = malloc (1000000);
+  if (large != NULL)
+    {
+      memset (large, 'a', 1000000);
+      char const *expect = ("\x5c\x88\x75\xae\x47\x4a\x36\x34\xba\x4f\xd5\x5e"
+                            "\xc8\x5b\xff\xd6\x61\xf3\x2a\xca\x75\xc6\xd6\x99"
+                            "\xd0\xcd\xcb\x6c\x11\x58\x91\xc1");
+      if (check (large, 1000000, expect) != 0)
+        return 1;
+      free (large);
+    }
+
+  return 0;
+}
diff --git a/tests/test-sha3-384-buffer.c b/tests/test-sha3-384-buffer.c
new file mode 100644
index 0000000000..c85408ed4a
--- /dev/null
+++ b/tests/test-sha3-384-buffer.c
@@ -0,0 +1,99 @@
+/* Test of the sha3_384_buffer() function.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Collin Funk <collin.fu...@gmail.com>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sha3.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct test_case {
+  char const *message;
+  char const *digest;
+};
+
+static const struct test_case test_cases[] = {
+  { "", "\x0c\x63\xa7\x5b\x84\x5e\x4f\x7d\x01\x10\x7d\x85\x2e\x4c\x24\x85\xc5"
+        "\x1a\x50\xaa\xaa\x94\xfc\x61\x99\x5e\x71\xbb\xee\x98\x3a\x2a\xc3\x71"
+        "\x38\x31\x26\x4a\xdb\x47\xfb\x6b\xd1\xe0\x58\xd5\xf0\x04" },
+  { "abc",
+    "\xec\x01\x49\x82\x88\x51\x6f\xc9\x26\x45\x9f\x58\xe2\xc6\xad\x8d\xf9\xb4"
+    "\x73\xcb\x0f\xc0\x8c\x25\x96\xda\x7c\xf0\xe4\x9b\xe4\xb2\x98\xd8\x8c\xea"
+    "\x92\x7a\xc7\xf5\x39\xf1\xed\xf2\x28\x37\x6d\x25" },
+  { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    "\x99\x1c\x66\x57\x55\xeb\x3a\x4b\x6b\xbd\xfb\x75\xc7\x8a\x49\x2e\x8c\x56"
+    "\xa2\x2c\x5c\x4d\x7e\x42\x9b\xfd\xbc\x32\xb9\xd4\xad\x5a\xa0\x4a\x1f\x07"
+    "\x6e\x62\xfe\xa1\x9e\xef\x51\xac\xd0\x65\x7c\x22" },
+  { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopj"
+    "klmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    "\x79\x40\x7d\x3b\x59\x16\xb5\x9c\x3e\x30\xb0\x98\x22\x97\x47\x91\xc3\x13"
+    "\xfb\x9e\xcc\x84\x9e\x40\x6f\x23\x59\x2d\x04\xf6\x25\xdc\x8c\x70\x9b\x98"
+    "\xb4\x3b\x38\x52\xb3\x37\x21\x61\x79\xaa\x7f\xc7" }
+};
+
+static int
+check (char const *message, size_t len, char const *expect)
+{
+  char buf[SHA3_384_DIGEST_SIZE];
+  if (memcmp (sha3_384_buffer (message, len, buf),
+              expect, SHA3_384_DIGEST_SIZE) != 0)
+    {
+      size_t i;
+      printf ("expected:\n");
+      for (i = 0; i < SHA3_384_DIGEST_SIZE; i++)
+        printf ("%02x ", expect[i] & 0xFFu);
+      printf ("\ncomputed:\n");
+      for (i = 0; i < SHA3_384_DIGEST_SIZE; i++)
+        printf ("%02x ", buf[i] & 0xFFu);
+      printf ("\n");
+      return 1;
+    }
+  return 0;
+}
+
+int
+main (void)
+{
+  /* Check the test vectors.  */
+  for (size_t i = 0; i < sizeof test_cases / sizeof *test_cases; ++i)
+    {
+      char const *message = test_cases[i].message;
+      char const *digest = test_cases[i].digest;
+      if (check (message, strlen (message), digest) != 0)
+        return 1;
+    }
+
+  /* Check that a large buffer works.  */
+  char *large = malloc (1000000);
+  if (large != NULL)
+    {
+      memset (large, 'a', 1000000);
+      char const *expect = ("\xee\xe9\xe2\x4d\x78\xc1\x85\x53\x37\x98\x34\x51"
+                            "\xdf\x97\xc8\xad\x9e\xed\xf2\x56\xc6\x33\x4f\x8e"
+                            "\x94\x8d\x25\x2d\x5e\x0e\x76\x84\x7a\xa0\x77\x4d"
+                            "\xdb\x90\xa8\x42\x19\x0d\x2c\x55\x8b\x4b\x83\x40");
+      if (check (large, 1000000, expect) != 0)
+        return 1;
+      free (large);
+    }
+
+  return 0;
+}
diff --git a/tests/test-sha3-512-buffer.c b/tests/test-sha3-512-buffer.c
new file mode 100644
index 0000000000..535dfde168
--- /dev/null
+++ b/tests/test-sha3-512-buffer.c
@@ -0,0 +1,105 @@
+/* Test of the sha3_512_buffer() function.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Collin Funk <collin.fu...@gmail.com>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sha3.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct test_case {
+  char const *message;
+  char const *digest;
+};
+
+static const struct test_case test_cases[] = {
+  { "", "\xa6\x9f\x73\xcc\xa2\x3a\x9a\xc5\xc8\xb5\x67\xdc\x18\x5a\x75\x6e\x97"
+        "\xc9\x82\x16\x4f\xe2\x58\x59\xe0\xd1\xdc\xc1\x47\x5c\x80\xa6\x15\xb2"
+        "\x12\x3a\xf1\xf5\xf9\x4c\x11\xe3\xe9\x40\x2c\x3a\xc5\x58\xf5\x00\x19"
+        "\x9d\x95\xb6\xd3\xe3\x01\x75\x85\x86\x28\x1d\xcd\x26" },
+  { "abc",
+    "\xb7\x51\x85\x0b\x1a\x57\x16\x8a\x56\x93\xcd\x92\x4b\x6b\x09\x6e\x08\xf6"
+    "\x21\x82\x74\x44\xf7\x0d\x88\x4f\x5d\x02\x40\xd2\x71\x2e\x10\xe1\x16\xe9"
+    "\x19\x2a\xf3\xc9\x1a\x7e\xc5\x76\x47\xe3\x93\x40\x57\x34\x0b\x4c\xf4\x08"
+    "\xd5\xa5\x65\x92\xf8\x27\x4e\xec\x53\xf0" },
+  { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    "\x04\xa3\x71\xe8\x4e\xcf\xb5\xb8\xb7\x7c\xb4\x86\x10\xfc\xa8\x18\x2d\xd4"
+    "\x57\xce\x6f\x32\x6a\x0f\xd3\xd7\xec\x2f\x1e\x91\x63\x6d\xee\x69\x1f\xbe"
+    "\x0c\x98\x53\x02\xba\x1b\x0d\x8d\xc7\x8c\x08\x63\x46\xb5\x33\xb4\x9c\x03"
+    "\x0d\x99\xa2\x7d\xaf\x11\x39\xd6\xe7\x5e" },
+  { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopj"
+    "klmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    "\xaf\xeb\xb2\xef\x54\x2e\x65\x79\xc5\x0c\xad\x06\xd2\xe5\x78\xf9\xf8\xdd"
+    "\x68\x81\xd7\xdc\x82\x4d\x26\x36\x0f\xee\xbf\x18\xa4\xfa\x73\xe3\x26\x11"
+    "\x22\x94\x8e\xfc\xfd\x49\x2e\x74\xe8\x2e\x21\x89\xed\x0f\xb4\x40\xd1\x87"
+    "\xf3\x82\x27\x0c\xb4\x55\xf2\x1d\xd1\x85" }
+};
+
+static int
+check (char const *message, size_t len, char const *expect)
+{
+  char buf[SHA3_512_DIGEST_SIZE];
+  if (memcmp (sha3_512_buffer (message, len, buf),
+              expect, SHA3_512_DIGEST_SIZE) != 0)
+    {
+      size_t i;
+      printf ("expected:\n");
+      for (i = 0; i < SHA3_512_DIGEST_SIZE; i++)
+        printf ("%02x ", expect[i] & 0xFFu);
+      printf ("\ncomputed:\n");
+      for (i = 0; i < SHA3_512_DIGEST_SIZE; i++)
+        printf ("%02x ", buf[i] & 0xFFu);
+      printf ("\n");
+      return 1;
+    }
+  return 0;
+}
+
+int
+main (void)
+{
+  /* Check the test vectors.  */
+  for (size_t i = 0; i < sizeof test_cases / sizeof *test_cases; ++i)
+    {
+      char const *message = test_cases[i].message;
+      char const *digest = test_cases[i].digest;
+      if (check (message, strlen (message), digest) != 0)
+        return 1;
+    }
+
+  /* Check that a large buffer works.  */
+  char *large = malloc (1000000);
+  if (large != NULL)
+    {
+      memset (large, 'a', 1000000);
+      char const *expect = ("\x3c\x3a\x87\x6d\xa1\x40\x34\xab\x60\x62\x7c\x07"
+                            "\x7b\xb9\x8f\x7e\x12\x0a\x2a\x53\x70\x21\x2d\xff"
+                            "\xb3\x38\x5a\x18\xd4\xf3\x88\x59\xed\x31\x1d\x0a"
+                            "\x9d\x51\x41\xce\x9c\xc5\xc6\x6e\xe6\x89\xb2\x66"
+                            "\xa8\xaa\x18\xac\xe8\x28\x2a\x0e\x0d\xb5\x96\xc9"
+                            "\x0b\x0a\x7b\x87");
+      if (check (large, 1000000, expect) != 0)
+        return 1;
+      free (large);
+    }
+
+  return 0;
+}
-- 
2.51.0

>From 3a3a63530b1b6931be7dba66371aed3ffd9cb1db Mon Sep 17 00:00:00 2001
Message-ID: <3a3a63530b1b6931be7dba66371aed3ffd9cb1db.1756677369.git.collin.fu...@gmail.com>
In-Reply-To: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
References: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
From: Collin Funk <collin.fu...@gmail.com>
Date: Sun, 31 Aug 2025 14:21:02 -0700
Subject: [PATCH v2 3/4] crypto/sha3: New module.

* modules/crypto/sha3: New file.
* lib/sha3-stream.c: New file, based on sha512-stream.c.
* lib/sha3.h (sha3_224_stream, sha3_256_stream, sha3_384_stream)
(sha3_512_stream): New declarations.
---
 ChangeLog           |   6 ++
 lib/sha3-stream.c   | 145 ++++++++++++++++++++++++++++++++++++++++++++
 lib/sha3.h          |  10 +++
 modules/crypto/sha3 |  23 +++++++
 4 files changed, 184 insertions(+)
 create mode 100644 lib/sha3-stream.c
 create mode 100644 modules/crypto/sha3

diff --git a/ChangeLog b/ChangeLog
index 924d67e135..d202892dd1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2025-08-31  Collin Funk  <collin.fu...@gmail.com>
 
+	crypto/sha3: New module.
+	* modules/crypto/sha3: New file.
+	* lib/sha3-stream.c: New file, based on sha512-stream.c.
+	* lib/sha3.h (sha3_224_stream, sha3_256_stream, sha3_384_stream)
+	(sha3_512_stream): New declarations.
+
 	crypto/sha3-buffer: Add tests.
 	* modules/crypto/sha3-buffer-tests: New file.
 	* tests/test-sha3-224-buffer.c: Likewise.
diff --git a/lib/sha3-stream.c b/lib/sha3-stream.c
new file mode 100644
index 0000000000..708dafa7dc
--- /dev/null
+++ b/lib/sha3-stream.c
@@ -0,0 +1,145 @@
+/* sha3-stream.c - Functions to compute the SHA-3 message digest of files as
+   specified by FIPS-202.
+
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sha3.h"
+
+#include <stdlib.h>
+
+#if USE_UNLOCKED_IO
+# include "unlocked-io.h"
+#endif
+
+#include "af_alg.h"
+
+#define BLOCKSIZE 31824
+#if (BLOCKSIZE % 144 != 0 && BLOCKSIZE % 136 != 0 && BLOCKSIZE % 104 != 0 \
+     && BLOCKSIZE % 72 != 0)
+# error "invalid BLOCKSIZE"
+#endif
+
+/* Compute message digest for bytes read from STREAM using algorithm ALG.
+   Write the message digest into RESBLOCK, which contains HASHLEN bytes.
+   The initial operation is INIT_CTX.  Return zero if and only if
+   successful.  */
+static int
+sha3_xxx_stream (FILE *stream, char const *alg, void *resblock,
+                 ssize_t hashlen, void (*init_ctx) (struct sha3_ctx *))
+{
+  switch (afalg_stream (stream, alg, resblock, hashlen))
+    {
+    case 0: return 0;
+    case -EIO: return 1;
+    }
+
+  char *buffer = malloc (BLOCKSIZE + 72);
+  if (!buffer)
+    return 1;
+
+  struct sha3_ctx ctx;
+  init_ctx (&ctx);
+  size_t sum;
+
+  /* Iterate over full file contents.  */
+  while (1)
+    {
+      /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
+         computation function processes the whole buffer so that with the
+         next round of the loop another block can be read.  */
+      size_t n;
+      sum = 0;
+
+      /* Read block.  Take care for partial reads.  */
+      while (1)
+        {
+          /* Either process a partial fread() from this loop,
+             or the fread() in afalg_stream may have gotten EOF.
+             We need to avoid a subsequent fread() as EOF may
+             not be sticky.  For details of such systems, see:
+             https://sourceware.org/PR1190  */
+          if (feof (stream))
+            goto process_partial_block;
+
+          n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
+
+          sum += n;
+
+          if (sum == BLOCKSIZE)
+            break;
+
+          if (n == 0)
+            {
+              /* Check for the error flag IFF N == 0, so that we don't
+                 exit the loop after a partial read due to e.g., EAGAIN
+                 or EWOULDBLOCK.  */
+              if (ferror (stream))
+                {
+                  free (buffer);
+                  return 1;
+                }
+              goto process_partial_block;
+            }
+        }
+
+      /* Process buffer with BLOCKSIZE bytes.  Note that
+                        BLOCKSIZE % ctx.blocklen == 0
+       */
+      sha3_process_block (buffer, BLOCKSIZE, &ctx);
+    }
+
+ process_partial_block:;
+
+  /* Process any remaining bytes.  */
+  if (sum > 0)
+    sha3_process_bytes (buffer, sum, &ctx);
+
+  /* Construct result in desired memory.  */
+  sha3_finish_ctx (&ctx, resblock);
+  free (buffer);
+  return 0;
+}
+
+int
+sha3_224_stream (FILE *stream, void *resblock)
+{
+  return sha3_xxx_stream (stream, "sha3-224", resblock, SHA3_224_DIGEST_SIZE,
+                          sha3_224_init_ctx);
+}
+
+int
+sha3_256_stream (FILE *stream, void *resblock)
+{
+  return sha3_xxx_stream (stream, "sha3-256", resblock, SHA3_256_DIGEST_SIZE,
+                          sha3_256_init_ctx);
+}
+
+int
+sha3_384_stream (FILE *stream, void *resblock)
+{
+  return sha3_xxx_stream (stream, "sha3-384", resblock, SHA3_384_DIGEST_SIZE,
+                          sha3_384_init_ctx);
+}
+
+int
+sha3_512_stream (FILE *stream, void *resblock)
+{
+  return sha3_xxx_stream (stream, "sha3-512", resblock, SHA3_512_DIGEST_SIZE,
+                          sha3_512_init_ctx);
+}
diff --git a/lib/sha3.h b/lib/sha3.h
index c299c6fab7..8e0a7a7fdf 100644
--- a/lib/sha3.h
+++ b/lib/sha3.h
@@ -94,6 +94,16 @@ extern void *sha3_384_buffer (const char *buffer, size_t len,
 extern void *sha3_512_buffer (const char *buffer, size_t len,
                               void *restrict resblock);
 
+/* Compute SHA-3 message digest for bytes read from STREAM.  STREAM is an open
+   file stream.  Regular files are handled more efficiently.  The contents of
+   STREAM from its current position to its end will be read.  The case that the
+   last operation on STREAM was an 'ungetc' is not supported.  The resulting
+   message digest number will be written into RESBLOCK.  */
+extern int sha3_224_stream (FILE *stream, void *resblock);
+extern int sha3_256_stream (FILE *stream, void *resblock);
+extern int sha3_384_stream (FILE *stream, void *resblock);
+extern int sha3_512_stream (FILE *stream, void *resblock);
+
 # ifdef __cplusplus
 }
 # endif
diff --git a/modules/crypto/sha3 b/modules/crypto/sha3
new file mode 100644
index 0000000000..f2b878794a
--- /dev/null
+++ b/modules/crypto/sha3
@@ -0,0 +1,23 @@
+Description:
+Compute SHA-3 checksums.
+
+Files:
+lib/sha3-stream.c
+
+Depends-on:
+crypto/af_alg
+crypto/sha3-buffer
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += sha3-stream.c
+
+Include:
+"sha3.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.51.0

>From 6c5936dddf2b9685675624313065f2379305598a Mon Sep 17 00:00:00 2001
Message-ID: <6c5936dddf2b9685675624313065f2379305598a.1756677369.git.collin.fu...@gmail.com>
In-Reply-To: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
References: <4cdf7734fbdb040557f253ceb885c8aa1dbf225b.1756677369.git.collin.fu...@gmail.com>
From: Collin Funk <collin.fu...@gmail.com>
Date: Sun, 31 Aug 2025 14:54:56 -0700
Subject: [PATCH v2 4/4] u64: Allow the header to be included twice.

* lib/u64.h: Add include guard.
---
 ChangeLog | 3 +++
 lib/u64.h | 5 +++++
 2 files changed, 8 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index d202892dd1..46e8238da1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2025-08-31  Collin Funk  <collin.fu...@gmail.com>
 
+	u64: Allow the header to be included twice.
+	* lib/u64.h: Add include guard.
+
 	crypto/sha3: New module.
 	* modules/crypto/sha3: New file.
 	* lib/sha3-stream.c: New file, based on sha512-stream.c.
diff --git a/lib/u64.h b/lib/u64.h
index a344bc7bd5..b47d65e503 100644
--- a/lib/u64.h
+++ b/lib/u64.h
@@ -17,6 +17,9 @@
 
 /* Written by Paul Eggert.  */
 
+#ifndef U64_H
+#define U64_H 1
+
 /* This file uses _GL_INLINE_HEADER_BEGIN, _GL_INLINE.  */
 #if !_GL_CONFIG_H_INCLUDED
  #error "Please include config.h first."
@@ -229,3 +232,5 @@ u64rol (u64 x, int n)
 #endif
 
 _GL_INLINE_HEADER_END
+
+#endif
-- 
2.51.0

Reply via email to