keccak-fast.c

Minimal SIMD keccak implementation
git clone git://git.finwo.net/lib/keccak-fast.c
Log | Files | Refs | README | LICENSE

commit 812b92a5f8c0598e3ede2652ddfe7bf0ea3e5798
Author: finwo <finwo@pm.me>
Date:   Sat, 10 Oct 2026 15:51:51 +0200

Repo init

Diffstat:
A.dep.export | 1+
A.gitignore | 6++++++
ALICENSE.md | 34++++++++++++++++++++++++++++++++++
AMakefile | 28++++++++++++++++++++++++++++
AREADME.md | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abench/.dep | 1+
Abench/.gitignore | 2++
Abench/Makefile | 46++++++++++++++++++++++++++++++++++++++++++++++
Abench/bench.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexport.mk | 3+++
Asrc/backend/avx2.c | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/scalar.c | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/keccak-fast-internal.h | 40++++++++++++++++++++++++++++++++++++++++
Asrc/keccak-fast.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/keccak-fast.h | 33+++++++++++++++++++++++++++++++++
Atest/batch.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/kat.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; +}