commit 812b92a5f8c0598e3ede2652ddfe7bf0ea3e5798
Author: finwo <finwo@pm.me>
Date: Sat, 10 Oct 2026 15:51:51 +0200
Repo init
Diffstat:
17 files changed, 1014 insertions(+), 0 deletions(-)
diff --git a/.dep.export b/.dep.export
@@ -0,0 +1 @@
+include/finwo/keccak-fast.h src/keccak-fast.h
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,6 @@
+build/
+*.o
+*.a
+*.so
+*.dylib
+*~
diff --git a/LICENSE.md b/LICENSE.md
@@ -0,0 +1,34 @@
+Copyright (c) 2026 finwo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to use, copy,
+modify, and distribute the Software, subject to the following conditions:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions, and the following disclaimer.
+
+ 2. Redistributions in binary form, or any public offering of the Software
+ (including hosted or managed services), must reproduce the above copyright
+ notice, this list of conditions, and the following disclaimer in the
+ documentation and/or other materials provided.
+
+ 3. Any redistribution or public offering of the Software must clearly attribute
+ the Software to the original copyright holder, reference this License, and
+ include a link to the official project repository or website.
+
+ 4. The Software may not be renamed, rebranded, or marketed in a manner that
+ implies it is an independent or proprietary product. Derivative works must
+ clearly state that they are based on the Software.
+
+ 5. Modifications to copies of the Software must carry prominent notices stating
+ that changes were made, the nature of the modifications, and the date of the
+ modifications.
+
+Any violation of these conditions terminates the permissions granted herein.
+
+THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,28 @@
+CC ?= cc
+OPT ?= -O2
+CFLAGS ?= -Wall -Wextra -Wpedantic $(OPT)
+CFLAGS += -std=c11 -I src
+
+LIB := src/keccak-fast.c $(wildcard src/backend/*.c)
+OUT := build
+BINS := $(OUT)/kat $(OUT)/batch
+
+.PHONY: default test bench clean
+default: test
+
+test: $(BINS)
+ @$(OUT)/kat
+ @$(OUT)/batch
+
+bench:
+ @$(MAKE) --no-print-directory --directory bench run
+
+$(OUT)/%: test/%.c $(LIB) | $(OUT)
+ @$(CC) $(CFLAGS) $< $(LIB) -o $@
+
+$(OUT):
+ @mkdir -p $(OUT)
+
+clean:
+ rm -rf $(OUT)
+ @$(MAKE) --no-print-directory --directory bench clean
diff --git a/README.md b/README.md
@@ -0,0 +1,58 @@
+# keccak-fast
+
+Drop-in-speed replacement for `coruus/keccak-tiny`. Six SHA-3/SHAKE one-shots
+plus batch entry points. Plain C11, libc only, no build flags,
+runtime-dispatched, thread-safe.
+
+## Use
+
+```c
+#include <finwo/keccak-fast.h>
+
+kf_shake256(out, sizeof out, in, inlen); /* 0, or -1 */
+kf_shake256_batch(count, in, inlen, out, outlen); /* equal-length, packed */
+```
+
+The one-shots (`kf_shake128`, `kf_shake256`, `kf_sha3_224`, `kf_sha3_256`,
+`kf_sha3_384`, `kf_sha3_512`) are `kf_hash_fn` table entries: a call is one
+indirect jump. Batch variants are `kf_<algo>_batch`, taking `count`
+equal-length messages packed back to back.
+
+## Build
+
+```
+dep add finwo/keccak-fast
+```
+
+`export.mk` lists the sources; `.dep.export` installs `<finwo/keccak-fast.h>`.
+
+## Backends
+
+| backend | one-shot | batch |
+| ------- | -------- | ----- |
+| scalar | yes | — |
+| AVX2 | no | x4 |
+
+Backends register from constructors; the highest available priority wins
+(scalar 0, avx2 20, avx512 30). `kf_backend_name()` and `kf_batch_name()`
+report the active ones. `KECCAK_FAST_NO_AVX2` drops the AVX2 backend.
+
+The scalar backend is the donor, pinned to the x86-64 baseline
+(`no-avx,no-avx2,no-avx512f`) so a consumer's `-march=native` cannot vectorize
+it (clang runs 1.9x slower when it does).
+
+## Dev
+
+```
+make test # KAT and batch differential (TAP)
+make bench # vendored donor, pinned core, best-of-nine ns/hash
+```
+
+`bench/` fetches the donor via `dep install`. `OPT=-O2|-O3` (arch flags
+rejected), `BENCH_CPU=<n>` selects the core.
+
+## License
+
+See `LICENSE.md`. The scalar backend is derived from `coruus/keccak-tiny`
+(David Leon Gil, CC0).
+
diff --git a/bench/.dep b/bench/.dep
@@ -0,0 +1 @@
+coruus/keccak-tiny https://git.finwo.net/misc/dep-repository/archives/heads/pkg/coruus/keccak-tiny.tar.gz
diff --git a/bench/.gitignore b/bench/.gitignore
@@ -0,0 +1,2 @@
+build/
+lib/
diff --git a/bench/Makefile b/bench/Makefile
@@ -0,0 +1,46 @@
+CC ?= cc
+OPT ?= -O2
+
+ARCHFLAGS := $(filter -march=% -mcpu=% -mavx% -mavx2% -mavx512% -msse% \
+ -mssse3% -msse4% -mfma%,$(OPT))
+ifneq ($(ARCHFLAGS),)
+$(error bench: OPT has arch flags ($(ARCHFLAGS)); the library ships without \
+ them and the baseline is not comparable. Use OPT=-O2 or OPT=-O3)
+endif
+
+CFLAGS ?= -Wall -Wextra -Wpedantic $(OPT)
+CFLAGS += -std=c11 -I../src
+BENCH_DESC := $(CC) -std=c11 $(OPT)
+CFLAGS += -DBENCH_FLAGS='"$(BENCH_DESC)"'
+
+OUT := build
+BIN := $(OUT)/bench
+
+SRC := ../src/keccak-fast.c $(wildcard ../src/backend/*.c)
+
+DONOR := lib/coruus/keccak-tiny/keccak-tiny.c
+DONOR_OBJ := $(OUT)/kct.o
+DONOR_DEFS := -Dshake128=kct_shake128 -Dshake256=kct_shake256 \
+ -Dsha3_224=kct_sha3_224 -Dsha3_256=kct_sha3_256 \
+ -Dsha3_384=kct_sha3_384 -Dsha3_512=kct_sha3_512
+
+.PHONY: all run clean
+all: $(BIN)
+
+$(OUT):
+ @mkdir -p $(OUT)
+
+$(DONOR):
+ @dep install
+
+$(DONOR_OBJ): $(DONOR) | $(OUT)
+ @$(CC) $(CFLAGS) $(DONOR_DEFS) -c $< -o $@
+
+$(BIN): bench.c $(SRC) $(DONOR_OBJ) | $(OUT)
+ @$(CC) $(CFLAGS) bench.c $(SRC) $(DONOR_OBJ) -o $@
+
+run: $(BIN)
+ @$(BIN)
+
+clean:
+ rm -rf $(OUT)
diff --git a/bench/bench.c b/bench/bench.c
@@ -0,0 +1,154 @@
+#define _GNU_SOURCE
+#include <sched.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "keccak-fast.h"
+
+int kct_sha3_256(uint8_t *, size_t, const uint8_t *, size_t);
+int kct_shake256(uint8_t *, size_t, const uint8_t *, size_t);
+
+#ifndef BENCH_FLAGS
+#define BENCH_FLAGS ""
+#endif
+
+typedef int (*kf_fn)(uint8_t *, size_t, const uint8_t *, size_t);
+typedef int (*kf_batch_fn)(size_t, const uint8_t *, size_t, uint8_t *, size_t);
+
+static volatile uint64_t g_sink;
+
+static uint64_t now_ns(void) {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
+}
+
+static int pin_cpu(int cpu) {
+ cpu_set_t set;
+ CPU_ZERO(&set);
+ CPU_SET(cpu, &set);
+ return sched_setaffinity(0, sizeof set, &set);
+}
+
+static double time_once(kf_fn fn, uint8_t *out, size_t outlen,
+ const uint8_t *in, size_t inlen, size_t iters) {
+ uint64_t t0 = now_ns();
+ for (size_t i = 0; i < iters; i++) {
+ fn(out, outlen, in, inlen);
+ g_sink += out[0];
+ }
+ return (double)(now_ns() - t0) / (double)iters;
+}
+
+static size_t pick_iters(kf_fn fn, uint8_t *out, size_t outlen,
+ const uint8_t *in, size_t inlen, uint64_t target_ns) {
+ double per = time_once(fn, out, outlen, in, inlen, 1000);
+ if (per <= 0.0) per = 1.0;
+ size_t iters = (size_t)((double)target_ns / per);
+ return iters < 100 ? 100 : iters;
+}
+
+static void run_pair(const char *name, kf_fn donor, kf_fn fast, size_t inlen,
+ size_t outlen, uint8_t *in, uint8_t *out, int reps,
+ uint64_t target_ns) {
+ size_t iters = pick_iters(donor, out, outlen, in, inlen, target_ns);
+ double bt = -1.0;
+ double bf = -1.0;
+ for (int r = 0; r < reps; r++) {
+ double t = time_once(donor, out, outlen, in, inlen, iters);
+ double f = time_once(fast, out, outlen, in, inlen, iters);
+ if (bt < 0.0 || t < bt) bt = t;
+ if (bf < 0.0 || f < bf) bf = f;
+ }
+ printf("%s\n", name);
+ printf(" %-8s %9.2f ns/hash %9.3f Mh/s\n", "donor", bt, 1000.0 / bt);
+ printf(" %-8s %9.2f ns/hash %9.3f Mh/s\n", kf_backend_name(), bf,
+ 1000.0 / bf);
+ printf(" %-8s %9.3fx\n\n", "fast/do", bf / bt);
+}
+
+static double time_batch(kf_batch_fn fn, size_t count, const uint8_t *in,
+ size_t inlen, uint8_t *out, size_t outlen,
+ size_t iters) {
+ uint64_t t0 = now_ns();
+ for (size_t i = 0; i < iters; i++) {
+ fn(count, in, inlen, out, outlen);
+ g_sink += out[0];
+ }
+ return (double)(now_ns() - t0) / ((double)iters * (double)count);
+}
+
+static void case_batch(const char *name, kf_batch_fn fn, size_t count,
+ size_t inlen, size_t outlen, uint8_t *in, uint8_t *out,
+ int reps, uint64_t target_ns) {
+ double per = time_batch(fn, count, in, inlen, out, outlen, 200);
+ if (per <= 0.0) per = 1.0;
+ size_t iters = (size_t)((double)target_ns / (per * (double)count));
+ if (iters < 10) iters = 10;
+
+ double best = -1.0;
+ for (int r = 0; r < reps; r++) {
+ double v = time_batch(fn, count, in, inlen, out, outlen, iters);
+ if (best < 0.0 || v < best) best = v;
+ }
+ char label[64];
+ snprintf(label, sizeof label, "%s count %zu", name, count);
+ printf("%-24s %9.2f ns/hash %9.3f Mh/s\n", label, best, 1000.0 / best);
+}
+
+int main(void) {
+ const int reps = 9;
+ const uint64_t target_ns = 25ULL * 1000000ULL;
+ uint8_t in[256];
+ static uint8_t out[4096];
+ int cpu = 0;
+ const char *cs = getenv("BENCH_CPU");
+
+ for (size_t i = 0; i < sizeof in; i++) in[i] = (uint8_t)(i * 31 + 7);
+ if (cs) cpu = atoi(cs);
+
+ printf("keccak-fast vs coruus/keccak-tiny\n");
+ printf("build: %s\n", BENCH_FLAGS);
+ if (pin_cpu(cpu) != 0) {
+ printf("# warning: could not pin to cpu %d, results will be noisier\n", cpu);
+ } else {
+ printf("pinned to cpu %d\n", cpu);
+ }
+ printf("best of %d interleaved reps, about %llu ms per rep\n\n", reps,
+ (unsigned long long)(target_ns / 1000000ULL));
+
+ run_pair("SHA3-256 32->32", kct_sha3_256, kf_sha3_256, 32, 32, in, out, reps,
+ target_ns);
+ run_pair("SHA3-256 64->32", kct_sha3_256, kf_sha3_256, 64, 32, in, out, reps,
+ target_ns);
+ run_pair("SHAKE256 32->32", kct_shake256, kf_shake256, 32, 32, in, out, reps,
+ target_ns);
+ run_pair("SHAKE256 32->1088", kct_shake256, kf_shake256, 32, 1088, in, out, reps,
+ target_ns);
+
+ static uint8_t bin[256 * 64];
+ static uint8_t bout[256 * 64];
+ for (size_t i = 0; i < sizeof bin; i++) bin[i] = (uint8_t)(i * 17 + 3);
+
+ printf("batch SHAKE256 32->32, per hash (%s)\n", kf_batch_name());
+ case_batch(kf_batch_name(), kf_shake256_batch, 4, 32, 32, bin, bout, reps,
+ target_ns);
+ case_batch(kf_batch_name(), kf_shake256_batch, 64, 32, 32, bin, bout, reps,
+ target_ns);
+ case_batch(kf_batch_name(), kf_shake256_batch, 256, 32, 32, bin, bout, reps,
+ target_ns);
+
+ printf("batch SHA3-256 32->32, per hash (%s)\n", kf_batch_name());
+ case_batch(kf_batch_name(), kf_sha3_256_batch, 4, 32, 32, bin, bout, reps,
+ target_ns);
+ case_batch(kf_batch_name(), kf_sha3_256_batch, 64, 32, 32, bin, bout, reps,
+ target_ns);
+ case_batch(kf_batch_name(), kf_sha3_256_batch, 256, 32, 32, bin, bout, reps,
+ target_ns);
+
+ printf("sink %llu\n", (unsigned long long)g_sink);
+ return 0;
+}
diff --git a/export.mk b/export.mk
@@ -0,0 +1,3 @@
+SRC+={{module.dirname}}/src/keccak-fast.c
+SRC+={{module.dirname}}/src/backend/scalar.c
+SRC+={{module.dirname}}/src/backend/avx2.c
diff --git a/src/backend/avx2.c b/src/backend/avx2.c
@@ -0,0 +1,125 @@
+#include <immintrin.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "../keccak-fast-internal.h"
+
+#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__)) && \
+ !defined(KECCAK_FAST_NO_AVX2)
+
+static const uint64_t RC[24] = {
+ 1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL,
+ 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL,
+ 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL,
+ 0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL,
+ 0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL,
+ 0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL};
+
+#define KF_ROL64(x, n) \
+ _mm256_or_si256(_mm256_slli_epi64((x), (n)), _mm256_srli_epi64((x), 64 - (n)))
+
+__attribute__((target("avx2")))
+static inline void kf_avx2_round_x4(__m256i v[25], uint64_t rc) {
+ __m256i c[5];
+ __m256i d[5];
+ __m256i t, u;
+
+ for (int x = 0; x < 5; x++) {
+ c[x] = _mm256_xor_si256(
+ _mm256_xor_si256(v[x], v[x + 5]),
+ _mm256_xor_si256(_mm256_xor_si256(v[x + 10], v[x + 15]), v[x + 20]));
+ }
+ for (int x = 0; x < 5; x++) {
+ d[x] = _mm256_xor_si256(c[(x + 4) % 5], KF_ROL64(c[(x + 1) % 5], 1));
+ }
+ for (int y = 0; y < 5; y++) {
+ for (int x = 0; x < 5; x++) {
+ v[x + 5 * y] = _mm256_xor_si256(v[x + 5 * y], d[x]);
+ }
+ }
+
+ t = v[1];
+#define KF_RHOPI4(dest, rot) \
+ do { \
+ u = v[dest]; \
+ v[dest] = KF_ROL64(t, rot); \
+ t = u; \
+ } while (0)
+ KF_RHOPI4(10, 1);
+ KF_RHOPI4(7, 3);
+ KF_RHOPI4(11, 6);
+ KF_RHOPI4(17, 10);
+ KF_RHOPI4(18, 15);
+ KF_RHOPI4(3, 21);
+ KF_RHOPI4(5, 28);
+ KF_RHOPI4(16, 36);
+ KF_RHOPI4(8, 45);
+ KF_RHOPI4(21, 55);
+ KF_RHOPI4(24, 2);
+ KF_RHOPI4(4, 14);
+ KF_RHOPI4(15, 27);
+ KF_RHOPI4(23, 41);
+ KF_RHOPI4(19, 56);
+ KF_RHOPI4(13, 8);
+ KF_RHOPI4(12, 25);
+ KF_RHOPI4(2, 43);
+ KF_RHOPI4(20, 62);
+ KF_RHOPI4(14, 18);
+ KF_RHOPI4(22, 39);
+ KF_RHOPI4(9, 61);
+ KF_RHOPI4(6, 20);
+ KF_RHOPI4(1, 44);
+#undef KF_RHOPI4
+
+ for (int y = 0; y < 5; y++) {
+ __m256i row[5];
+ for (int x = 0; x < 5; x++) row[x] = v[x + 5 * y];
+ for (int x = 0; x < 5; x++) {
+ v[x + 5 * y] = _mm256_xor_si256(
+ row[x], _mm256_andnot_si256(row[(x + 1) % 5], row[(x + 2) % 5]));
+ }
+ }
+
+ v[0] = _mm256_xor_si256(v[0], _mm256_set1_epi64x((long long)rc));
+}
+
+__attribute__((target("avx2")))
+static void kf_permute_x4(uint64_t s[4][25]) {
+ __m256i v[25];
+ uint64_t tmp[4];
+
+ for (int j = 0; j < 25; j++) {
+ for (int i = 0; i < 4; i++) tmp[i] = s[i][j];
+ v[j] = _mm256_loadu_si256((const __m256i *)tmp);
+ }
+ for (int i = 0; i < 24; i++) kf_avx2_round_x4(v, RC[i]);
+ for (int j = 0; j < 25; j++) {
+ _mm256_storeu_si256((__m256i *)tmp, v[j]);
+ for (int i = 0; i < 4; i++) s[i][j] = tmp[i];
+ }
+}
+
+__attribute__((target("avx2")))
+static void kf_avx2_batch(uint64_t states[][25], size_t count) {
+ size_t i = 0;
+ for (; i + 4 <= count; i += 4) {
+ kf_permute_x4(&states[i]);
+ }
+ for (; i < count; i++) {
+ kf_scalar_permute(states[i]);
+ }
+}
+
+__attribute__((constructor)) static void kf_avx2_register(void) {
+ if (!__builtin_cpu_supports("avx2")) {
+ return;
+ }
+ if (kf_batch_priority <= KF_PRIO_AVX2) {
+ kf_batch_perm = kf_avx2_batch;
+ kf_batch_priority = KF_PRIO_AVX2;
+ kf_batch_lanes = 4;
+ kf_batch_label = "avx2";
+ }
+}
+
+#endif
diff --git a/src/backend/scalar.c b/src/backend/scalar.c
@@ -0,0 +1,180 @@
+/* Keccak-f[1600] and the FIPS 202 sponge of coruus/keccak-tiny, renamed
+ * kf_scalar_* for the dispatcher.
+ *
+ * Implementor: David Leon Gil. License: CC0, attribution kindly requested.
+ * Blame taken too, but not liability.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "../keccak-fast-internal.h"
+
+/* The fallback must run on any x86-64; clang vectorizes it under -march=native
+ * and runs 1.9x slower. */
+#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
+#pragma clang attribute push( \
+ __attribute__((target("no-avx,no-avx2,no-avx512f"))), apply_to=function)
+#elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
+#pragma GCC target("no-avx,no-avx2,no-avx512f")
+#endif
+
+#if !defined(__STDC_LIB_EXT1__)
+static inline int memset_s(void *dest, size_t destsz, int ch, size_t count) {
+ (void)destsz;
+ memset(dest, ch, count);
+ return 0;
+}
+#endif
+
+static const uint8_t rho[24] = {
+ 1, 3, 6, 10, 15, 21,
+ 28, 36, 45, 55, 2, 14,
+ 27, 41, 56, 8, 25, 43,
+ 62, 18, 39, 61, 20, 44};
+static const uint8_t pi[24] = {
+ 10, 7, 11, 17, 18, 3,
+ 5, 16, 8, 21, 24, 4,
+ 15, 23, 19, 13, 12, 2,
+ 20, 14, 22, 9, 6, 1};
+static const uint64_t RC[24] = {
+ 1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL,
+ 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL,
+ 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL,
+ 0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL,
+ 0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL,
+ 0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL};
+
+#define rol(x, s) (((x) << s) | ((x) >> (64 - s)))
+#define REPEAT6(e) e e e e e e
+#define REPEAT24(e) REPEAT6(e e e e)
+#define REPEAT5(e) e e e e e
+#define FOR5(v, s, e) \
+ v = 0; \
+ REPEAT5(e; v += s;)
+
+static inline void keccakf(void *state) {
+ uint64_t *a = (uint64_t *)state;
+ uint64_t b[5] = {0};
+ uint64_t t = 0;
+ uint8_t x, y;
+
+ for (int i = 0; i < 24; i++) {
+ /* theta */
+ FOR5(x, 1, b[x] = 0; FOR5(y, 5, b[x] ^= a[x + y];))
+ FOR5(x, 1, FOR5(y, 5, a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1);))
+ /* rho and pi */
+ t = a[1];
+ x = 0;
+ REPEAT24(b[0] = a[pi[x]];
+ a[pi[x]] = rol(t, rho[x]);
+ t = b[0];
+ x++;)
+ /* chi */
+ FOR5(y,
+ 5,
+ FOR5(x, 1, b[x] = a[y + x];)
+ FOR5(x, 1, a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]);))
+ /* iota */
+ a[0] ^= RC[i];
+ }
+}
+
+#define _(S) do { S } while (0)
+#define FOR(i, ST, L, S) \
+ _(for (size_t i = 0; i < L; i += ST) { S; })
+#define mkapply_ds(NAME, S) \
+ static inline void NAME(uint8_t *dst, const uint8_t *src, size_t len) { \
+ FOR(i, 1, len, S); \
+ }
+#define mkapply_sd(NAME, S) \
+ static inline void NAME(const uint8_t *src, uint8_t *dst, size_t len) { \
+ FOR(i, 1, len, S); \
+ }
+
+mkapply_ds(xorin, dst[i] ^= src[i])
+mkapply_sd(setout, dst[i] = src[i])
+
+#define P keccakf
+#define Plen 200
+
+#define foldP(I, L, F) \
+ while (L >= rate) { \
+ F(a, I, rate); \
+ P(a); \
+ I += rate; \
+ L -= rate; \
+ }
+
+static inline int kf_scalar_hash(uint8_t *out, size_t outlen,
+ const uint8_t *in, size_t inlen,
+ size_t rate, uint8_t delim) {
+ if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= Plen)) {
+ return -1;
+ }
+ uint8_t a[Plen] = {0};
+ foldP(in, inlen, xorin);
+ a[inlen] ^= delim;
+ a[rate - 1] ^= 0x80;
+ xorin(a, in, inlen);
+ P(a);
+ foldP(out, outlen, setout);
+ setout(a, out, outlen);
+ memset_s(a, 200, 0, 200);
+ return 0;
+}
+
+#define defshake(bits) \
+ int kf_scalar_shake##bits(uint8_t *out, size_t outlen, const uint8_t *in, \
+ size_t inlen) { \
+ return kf_scalar_hash(out, outlen, in, inlen, 200 - (bits / 4), 0x1f); \
+ }
+#define defsha3(bits) \
+ int kf_scalar_sha3_##bits(uint8_t *out, size_t outlen, const uint8_t *in, \
+ size_t inlen) { \
+ if (outlen > (bits / 8)) { \
+ return -1; \
+ } \
+ return kf_scalar_hash(out, outlen, in, inlen, 200 - (bits / 4), 0x06); \
+ }
+
+defshake(128)
+defshake(256)
+defsha3(224)
+defsha3(256)
+defsha3(384)
+defsha3(512)
+
+void kf_scalar_permute(uint64_t state[25]) {
+ keccakf(state);
+}
+
+void kf_scalar_batch(uint64_t states[][25], size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ keccakf(states[i]);
+ }
+}
+
+__attribute__((constructor)) static void kf_scalar_register(void) {
+ if (kf_backend_priority <= KF_PRIO_SCALAR) {
+ kf_shake128 = kf_scalar_shake128;
+ kf_shake256 = kf_scalar_shake256;
+ kf_sha3_224 = kf_scalar_sha3_224;
+ kf_sha3_256 = kf_scalar_sha3_256;
+ kf_sha3_384 = kf_scalar_sha3_384;
+ kf_sha3_512 = kf_scalar_sha3_512;
+ kf_backend_priority = KF_PRIO_SCALAR;
+ kf_backend_label = "scalar";
+ }
+ if (kf_batch_priority <= KF_PRIO_SCALAR) {
+ kf_batch_perm = kf_scalar_batch;
+ kf_batch_priority = KF_PRIO_SCALAR;
+ kf_batch_lanes = 1;
+ kf_batch_label = "scalar";
+ }
+}
+
+#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
+#pragma clang attribute pop
+#endif
diff --git a/src/keccak-fast-internal.h b/src/keccak-fast-internal.h
@@ -0,0 +1,40 @@
+#ifndef FINWO_KECCAK_FAST_INTERNAL_H
+#define FINWO_KECCAK_FAST_INTERNAL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "keccak-fast.h"
+
+typedef void (*kf_batch_perm_fn)(uint64_t states[][25], size_t count);
+
+#define KF_LANES_MAX 8
+
+#define KF_PRIO_SCALAR 0
+#define KF_PRIO_AVX2 20
+#define KF_PRIO_AVX512 30
+
+extern kf_batch_perm_fn kf_batch_perm;
+extern int kf_batch_priority;
+extern int kf_batch_lanes;
+
+extern int kf_backend_priority;
+extern const char *kf_backend_label;
+extern const char *kf_batch_label;
+
+int kf_scalar_shake128(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+int kf_scalar_shake256(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+int kf_scalar_sha3_224(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+int kf_scalar_sha3_256(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+int kf_scalar_sha3_384(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+int kf_scalar_sha3_512(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+void kf_scalar_permute(uint64_t state[25]);
+void kf_scalar_batch(uint64_t states[][25], size_t count);
+
+#endif
diff --git a/src/keccak-fast.c b/src/keccak-fast.c
@@ -0,0 +1,126 @@
+#include "keccak-fast.h"
+
+#include <string.h>
+
+#include "keccak-fast-internal.h"
+
+#define KF_RATE(bits) (200 - ((bits) / 4))
+#define KF_DOMAIN_SHAKE 0x1f
+#define KF_DOMAIN_SHA3 0x06
+
+kf_hash_fn kf_shake128 = kf_scalar_shake128;
+kf_hash_fn kf_shake256 = kf_scalar_shake256;
+kf_hash_fn kf_sha3_224 = kf_scalar_sha3_224;
+kf_hash_fn kf_sha3_256 = kf_scalar_sha3_256;
+kf_hash_fn kf_sha3_384 = kf_scalar_sha3_384;
+kf_hash_fn kf_sha3_512 = kf_scalar_sha3_512;
+
+int kf_backend_priority = KF_PRIO_SCALAR;
+const char *kf_backend_label = "scalar";
+
+kf_batch_perm_fn kf_batch_perm = kf_scalar_batch;
+int kf_batch_priority = KF_PRIO_SCALAR;
+int kf_batch_lanes = 1;
+const char *kf_batch_label = "scalar";
+
+const char *kf_backend_name(void) {
+ return kf_backend_label;
+}
+
+const char *kf_batch_name(void) {
+ return kf_batch_label;
+}
+
+static void kf_wipe(void *p, size_t n) {
+ static void *(*const volatile fn)(void *, int, size_t) = memset;
+ fn(p, 0, n);
+}
+
+static void kf_sponge_group(const uint8_t *in, size_t inlen, uint8_t *out,
+ size_t outlen, size_t base, size_t count,
+ size_t rate, uint8_t delim) {
+ uint64_t states[KF_LANES_MAX][25] = {0};
+ uint8_t *a = (uint8_t *)states;
+ const uint8_t *p[KF_LANES_MAX];
+ uint8_t *o[KF_LANES_MAX];
+
+ for (size_t i = 0; i < count; i++) {
+ p[i] = in + (base + i) * inlen;
+ o[i] = out + (base + i) * outlen;
+ }
+
+ size_t remaining = inlen;
+ while (remaining >= rate) {
+ for (size_t i = 0; i < count; i++) {
+ for (size_t j = 0; j < rate; j++) a[i * 200 + j] ^= p[i][j];
+ p[i] += rate;
+ }
+ kf_batch_perm(states, count);
+ remaining -= rate;
+ }
+
+ for (size_t i = 0; i < count; i++) {
+ for (size_t j = 0; j < remaining; j++) a[i * 200 + j] ^= p[i][j];
+ a[i * 200 + remaining] ^= delim;
+ a[i * 200 + rate - 1] ^= 0x80;
+ }
+ kf_batch_perm(states, count);
+
+ size_t outrem = outlen;
+ while (outrem >= rate) {
+ for (size_t i = 0; i < count; i++) {
+ memcpy(o[i], a + i * 200, rate);
+ o[i] += rate;
+ }
+ outrem -= rate;
+ kf_batch_perm(states, count);
+ }
+ for (size_t i = 0; i < count; i++) {
+ memcpy(o[i], a + i * 200, outrem);
+ }
+
+ kf_wipe(states, sizeof states);
+}
+
+static int kf_batch_impl(kf_hash_fn one, size_t count, const uint8_t *in,
+ size_t inlen, uint8_t *out, size_t outlen,
+ size_t rate, uint8_t delim) {
+ if (count == 0) return 0;
+ if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= 200)) return -1;
+
+ int lanes = kf_batch_lanes;
+ if (lanes > KF_LANES_MAX) lanes = KF_LANES_MAX;
+
+ size_t base = 0;
+ if (lanes > 1) {
+ size_t l = (size_t)lanes;
+ for (; base + l <= count; base += l) {
+ kf_sponge_group(in, inlen, out, outlen, base, l, rate, delim);
+ }
+ }
+ for (; base < count; base++) {
+ one(out + base * outlen, outlen, in + base * inlen, inlen);
+ }
+ return 0;
+}
+
+#define KF_DEFSHAKE_BATCH(bits) \
+ int kf_shake##bits##_batch(size_t count, const uint8_t *in, size_t inlen, \
+ uint8_t *out, size_t outlen) { \
+ return kf_batch_impl(kf_shake##bits, count, in, inlen, out, outlen, \
+ KF_RATE(bits), KF_DOMAIN_SHAKE); \
+ }
+#define KF_DEFSHA3_BATCH(bits) \
+ int kf_sha3_##bits##_batch(size_t count, const uint8_t *in, size_t inlen, \
+ uint8_t *out, size_t outlen) { \
+ if (outlen > ((bits) / 8)) return -1; \
+ return kf_batch_impl(kf_sha3_##bits, count, in, inlen, out, outlen, \
+ KF_RATE(bits), KF_DOMAIN_SHA3); \
+ }
+
+KF_DEFSHAKE_BATCH(128)
+KF_DEFSHAKE_BATCH(256)
+KF_DEFSHA3_BATCH(224)
+KF_DEFSHA3_BATCH(256)
+KF_DEFSHA3_BATCH(384)
+KF_DEFSHA3_BATCH(512)
diff --git a/src/keccak-fast.h b/src/keccak-fast.h
@@ -0,0 +1,33 @@
+#ifndef FINWO_KECCAK_FAST_H
+#define FINWO_KECCAK_FAST_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef int (*kf_hash_fn)(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen);
+
+extern kf_hash_fn kf_shake128;
+extern kf_hash_fn kf_shake256;
+extern kf_hash_fn kf_sha3_224;
+extern kf_hash_fn kf_sha3_256;
+extern kf_hash_fn kf_sha3_384;
+extern kf_hash_fn kf_sha3_512;
+
+int kf_shake128_batch(size_t count, const uint8_t *in, size_t inlen,
+ uint8_t *out, size_t outlen);
+int kf_shake256_batch(size_t count, const uint8_t *in, size_t inlen,
+ uint8_t *out, size_t outlen);
+int kf_sha3_224_batch(size_t count, const uint8_t *in, size_t inlen,
+ uint8_t *out, size_t outlen);
+int kf_sha3_256_batch(size_t count, const uint8_t *in, size_t inlen,
+ uint8_t *out, size_t outlen);
+int kf_sha3_384_batch(size_t count, const uint8_t *in, size_t inlen,
+ uint8_t *out, size_t outlen);
+int kf_sha3_512_batch(size_t count, const uint8_t *in, size_t inlen,
+ uint8_t *out, size_t outlen);
+
+const char *kf_backend_name(void);
+const char *kf_batch_name(void);
+
+#endif
diff --git a/test/batch.c b/test/batch.c
@@ -0,0 +1,83 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "keccak-fast.h"
+
+typedef int (*kf_fn)(uint8_t *, size_t, const uint8_t *, size_t);
+typedef int (*kf_batch_fn)(size_t, const uint8_t *, size_t, uint8_t *, size_t);
+
+static int fails;
+static int testn;
+
+static void check(const char *name, int ok) {
+ testn++;
+ if (ok) {
+ printf("ok %d - %s\n", testn, name);
+ } else {
+ fails++;
+ printf("not ok %d - %s\n", testn, name);
+ }
+}
+
+struct algo {
+ const char *name;
+ kf_fn one;
+ kf_batch_fn batch;
+ size_t outlen;
+};
+
+int main(void) {
+ const struct algo algos[] = {
+ {"shake128", kf_shake128, kf_shake128_batch, 32},
+ {"shake256", kf_shake256, kf_shake256_batch, 64},
+ {"sha3_224", kf_sha3_224, kf_sha3_224_batch, 28},
+ {"sha3_256", kf_sha3_256, kf_sha3_256_batch, 32},
+ {"sha3_384", kf_sha3_384, kf_sha3_384_batch, 48},
+ {"sha3_512", kf_sha3_512, kf_sha3_512_batch, 64},
+ };
+ static const size_t counts[] = {1, 3, 4, 5, 8, 17};
+ static const size_t inlens[] = {32, 200};
+ static uint8_t in[17 * 200];
+ static uint8_t want[17 * 64];
+ static uint8_t got[17 * 64];
+
+ printf("1..%zu\n", (sizeof algos / sizeof algos[0]) *
+ (sizeof counts / sizeof counts[0]) *
+ (sizeof inlens / sizeof inlens[0]));
+
+ for (size_t a = 0; a < sizeof algos / sizeof algos[0]; a++) {
+ for (size_t ci = 0; ci < sizeof counts / sizeof counts[0]; ci++) {
+ size_t count = counts[ci];
+ for (size_t li = 0; li < sizeof inlens / sizeof inlens[0]; li++) {
+ size_t inlen = inlens[li];
+ size_t outlen = algos[a].outlen;
+ char name[64];
+
+ for (size_t m = 0; m < count; m++) {
+ for (size_t j = 0; j < inlen; j++) {
+ in[m * inlen + j] = (uint8_t)(j * 31 + m * 7 + 1);
+ }
+ }
+ for (size_t m = 0; m < count; m++) {
+ int rc = algos[a].one(want + m * outlen, outlen, in + m * inlen,
+ inlen);
+ if (rc != 0) {
+ printf("# %s one-shot returned %d\n", algos[a].name, rc);
+ }
+ }
+
+ memset(got, 0, count * outlen);
+ int rc = algos[a].batch(count, in, inlen, got, outlen);
+ snprintf(name, sizeof name, "%s count=%zu inlen=%zu", algos[a].name,
+ count, inlen);
+ check(name, rc == 0 && memcmp(want, got, count * outlen) == 0);
+ }
+ }
+ }
+
+ printf("# batch backend: %s\n", kf_batch_name());
+
+ if (fails != 0) printf("# %d failed\n", fails);
+ return fails != 0;
+}
diff --git a/test/kat.c b/test/kat.c
@@ -0,0 +1,94 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "keccak-fast.h"
+
+typedef int (*kf_fn)(uint8_t *, size_t, const uint8_t *, size_t);
+
+static int fails;
+static int testn;
+
+static void hex2bin(const char *hex, uint8_t *out) {
+ for (size_t i = 0; hex[i] != '\0'; i += 2) {
+ unsigned v;
+ sscanf(hex + i, "%2x", &v);
+ out[i / 2] = (uint8_t)v;
+ }
+}
+
+static void check(const char *name, kf_fn fn, const uint8_t *in, size_t inlen,
+ const char *hex) {
+ uint8_t got[64] = {0};
+ uint8_t want[64] = {0};
+ size_t outlen = strlen(hex) / 2;
+ int rc = fn(got, outlen, in, inlen);
+ hex2bin(hex, want);
+ testn++;
+ if (rc == 0 && memcmp(got, want, outlen) == 0) {
+ printf("ok %d - %s\n", testn, name);
+ } else {
+ fails++;
+ printf("not ok %d - %s\n", testn, name);
+ }
+}
+
+int main(void) {
+ static uint8_t qbf[200];
+ for (size_t i = 0; i < sizeof qbf; i++) qbf[i] = (uint8_t)i;
+ const uint8_t *abc = (const uint8_t *)"abc";
+ const uint8_t *nil = NULL;
+
+ printf("1..16\n");
+
+ check("shake128 empty", kf_shake128, nil, 0,
+ "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26");
+ check("shake128 abc", kf_shake128, abc, 3,
+ "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8");
+ check("shake256 empty", kf_shake256, nil, 0,
+ "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f"
+ "d75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be");
+ check("shake256 abc", kf_shake256, abc, 3,
+ "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739"
+ "d5a15bef186a5386c75744c0527e1faa9f8726e462a12a4feb06bd8801e751e4");
+
+ check("sha3_224 empty", kf_sha3_224, nil, 0,
+ "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7");
+ check("sha3_224 abc", kf_sha3_224, abc, 3,
+ "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf");
+ check("sha3_256 empty", kf_sha3_256, nil, 0,
+ "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a");
+ check("sha3_256 abc", kf_sha3_256, abc, 3,
+ "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532");
+ check("sha3_256 200", kf_sha3_256, qbf, 200,
+ "5f728f63bf5ee48c77f453c0490398fa645b8d4c4e56be9a41cfec344d6ca899");
+ check("sha3_384 empty", kf_sha3_384, nil, 0,
+ "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2a"
+ "c3713831264adb47fb6bd1e058d5f004");
+ check("sha3_384 abc", kf_sha3_384, abc, 3,
+ "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b2"
+ "98d88cea927ac7f539f1edf228376d25");
+ check("sha3_512 empty", kf_sha3_512, nil, 0,
+ "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a6"
+ "15b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26");
+ check("sha3_512 abc", kf_sha3_512, abc, 3,
+ "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e"
+ "10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0");
+
+ {
+ uint8_t out[64];
+ int bad1 = kf_sha3_256(out, 33, abc, 3);
+ int bad2 = kf_sha3_256(NULL, 32, abc, 3);
+ int bad3 = kf_shake256(out, 32, NULL, 3);
+ printf("%s %d - sha3_256 rejects long output\n", bad1 == -1 ? "ok" : "not ok",
+ ++testn);
+ printf("%s %d - rejects NULL output\n", bad2 == -1 ? "ok" : "not ok",
+ ++testn);
+ printf("%s %d - rejects NULL input\n", bad3 == -1 ? "ok" : "not ok",
+ ++testn);
+ if (bad1 != -1 || bad2 != -1 || bad3 != -1) fails++;
+ }
+
+ if (fails != 0) printf("# %d failed\n", fails);
+ return fails != 0;
+}