[RFC PATCH v1 13/13] tests: Add DecoyAuth interpolation cache coverage
Jeff Hansen
x at jeffhansen.com
Fri Sep 11 12:20:04 PDT 2026
Verify cold and warm cache results against direct interpolation. Report
the measured cache preparation and reuse cost for configurable password
counts.
Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
tests/Makefile | 11 ++-
tests/test-decoyauth-cache.c | 179 +++++++++++++++++++++++++++++++++++
2 files changed, 189 insertions(+), 1 deletion(-)
create mode 100644 tests/test-decoyauth-cache.c
diff --git a/tests/Makefile b/tests/Makefile
index ea2fa843f..dcd1b4051 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -3,7 +3,7 @@ ALL=test-base64 test-md4 test-milenage \
test-sha1 \
test-https test-https_server \
test-sha256 test-aes test-x509v3 test-list test-rc4 \
- test-bss
+ test-bss test-decoyauth-cache
include ../src/build.rules
@@ -24,6 +24,10 @@ endif
CFLAGS += -DCONFIG_IEEE80211R_AP
CFLAGS += -DCONFIG_IEEE80211R
CFLAGS += -DCONFIG_TDLS
+CFLAGS += -DCONFIG_ECC
+CFLAGS += -DCONFIG_SAE_DECOYAUTH
+CFLAGS += -DCONFIG_SAE_DECOYAUTH_CACHE
+CFLAGS += -DCONFIG_SAE_DECOYAUTH_THREADS
CFLAGS += -I../src
CFLAGS += -I../src/utils
@@ -45,6 +49,7 @@ LLIBS = -Wl,--start-group $(DLIBS) -Wl,--end-group $(SLIBS)
# glibc < 2.17 needs -lrt for clock_gettime()
LLIBS += -lrt
+LLIBS += -pthread
TEST=test-aes
include test.mk
@@ -52,6 +57,9 @@ include test.mk
TEST=test-base64
include test.mk
+TEST=test-decoyauth-cache
+include test.mk
+
TEST=test-https
include test.mk
@@ -151,6 +159,7 @@ run-tests: $(ALL)
./test-sha1
./test-sha256
./test-bss
+ ./test-decoyauth-cache
@echo
@echo All tests completed successfully.
diff --git a/tests/test-decoyauth-cache.c b/tests/test-decoyauth-cache.c
new file mode 100644
index 000000000..42ecfd9d1
--- /dev/null
+++ b/tests/test-decoyauth-cache.c
@@ -0,0 +1,179 @@
+/*
+ * DecoyAuth interpolation cache correctness and performance test
+ * Copyright (c) 2026, Jeff Hansen <x at jeffhansen.com>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+#include <dirent.h>
+
+#include "common.h"
+#include "crypto/crypto.h"
+#include "crypto/sha256.h"
+
+
+static long elapsed_ms(const struct os_reltime *start,
+ const struct os_reltime *end)
+{
+ struct os_reltime elapsed;
+
+ os_reltime_sub(end, start, &elapsed);
+ return elapsed.sec * 1000L + elapsed.usec / 1000;
+}
+
+
+static void free_values(struct crypto_bignum **values, int count)
+{
+ if (!values)
+ return;
+ for (int i = 0; i < count; i++)
+ crypto_bignum_deinit(values[i], 1);
+ os_free(values);
+}
+
+
+static struct crypto_bignum ** make_values(int count, const char *label)
+{
+ struct crypto_bignum **values;
+ u8 digest[SHA256_MAC_LEN];
+ char input[80];
+
+ values = os_calloc(count, sizeof(*values));
+ if (!values)
+ return NULL;
+ for (int i = 0; i < count; i++) {
+ const u8 *addr[1];
+ size_t len[1];
+
+ os_snprintf(input, sizeof(input), "%s-%d", label, i);
+ addr[0] = (const u8 *) input;
+ len[0] = os_strlen(input);
+ if (sha256_vector(1, addr, len, digest) < 0)
+ goto fail;
+ values[i] = crypto_bignum_init_set(digest, sizeof(digest));
+ if (!values[i])
+ goto fail;
+ }
+ forced_memzero(digest, sizeof(digest));
+ return values;
+fail:
+ forced_memzero(digest, sizeof(digest));
+ free_values(values, count);
+ return NULL;
+}
+
+
+static int coefficients_equal(struct crypto_bignum **a,
+ struct crypto_bignum **b, int count)
+{
+ for (int i = 0; i < count; i++) {
+ if (!a[i] || !b[i] || crypto_bignum_cmp(a[i], b[i]) != 0)
+ return 0;
+ }
+ return 1;
+}
+
+
+static void remove_cache_dir(const char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *entry;
+ char file[512];
+
+ if (!dir)
+ return;
+ while ((entry = readdir(dir))) {
+ if (os_strcmp(entry->d_name, ".") == 0 ||
+ os_strcmp(entry->d_name, "..") == 0)
+ continue;
+ os_snprintf(file, sizeof(file), "%s/%s", path, entry->d_name);
+ unlink(file);
+ }
+ closedir(dir);
+ rmdir(path);
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct crypto_ec *ec = NULL;
+ struct crypto_bignum **x = NULL, **first = NULL, **second = NULL;
+ struct crypto_bignum **direct_first = NULL, **direct_second = NULL;
+ struct crypto_bignum **cold_first = NULL, **cold_second = NULL;
+ struct crypto_bignum **warm_first = NULL, **warm_second = NULL;
+ struct crypto_interpolation_cache_stats cold = { 0 }, warm = { 0 };
+ struct os_reltime start, end;
+ char directory[] = "/tmp/decoyauth-cache-test-XXXXXX";
+ const char *configured = getenv("DECOYAUTH_CACHE_PASSWORDS");
+ long direct_ms = -1, cold_ms = -1, warm_ms = -1;
+ int count = configured ? atoi(configured) : 512;
+ int ret = 1;
+
+ if (count < 2 || count > 512) {
+ printf("DECOYAUTH_CACHE_PASSWORDS must be between 2 and 512\n");
+ return 1;
+ }
+ ec = crypto_ec_init(19);
+ x = make_values(count, "password");
+ first = make_values(count, "first");
+ second = make_values(count, "second");
+ if (!ec || !x || !first || !second || !mkdtemp(directory))
+ goto out;
+
+ os_get_reltime(&start);
+ if (crypto_interpolate_pair(x, first, second, count, ec,
+ &direct_first, &direct_second) < 0)
+ goto out;
+ os_get_reltime(&end);
+ direct_ms = elapsed_ms(&start, &end);
+
+ os_get_reltime(&start);
+ if (crypto_interpolate_pair_cached(
+ x, first, second, count, ec, directory, 10,
+ &cold_first, &cold_second, &cold) < 0)
+ goto out;
+ os_get_reltime(&end);
+ cold_ms = elapsed_ms(&start, &end);
+
+ os_get_reltime(&start);
+ if (crypto_interpolate_pair_cached(
+ x, first, second, count, ec, directory, 10,
+ &warm_first, &warm_second, &warm) < 0)
+ goto out;
+ os_get_reltime(&end);
+ warm_ms = elapsed_ms(&start, &end);
+
+ if (cold.hit || !warm.hit ||
+ cold.bytes != (size_t) count * count * 32 ||
+ warm.bytes != cold.bytes ||
+ !coefficients_equal(direct_first, cold_first, count) ||
+ !coefficients_equal(direct_second, cold_second, count) ||
+ !coefficients_equal(direct_first, warm_first, count) ||
+ !coefficients_equal(direct_second, warm_second, count)) {
+ printf("DecoyAuth cache result mismatch\n");
+ goto out;
+ }
+
+ printf("DecoyAuth cache benchmark: passwords=%d matrix_bytes=%zu "
+ "direct_ms=%ld cold_ms=%ld warm_ms=%ld saved_ms=%ld "
+ "cold_prepare_ms=%ld cold_weave_ms=%ld warm_weave_ms=%ld\n",
+ count, warm.bytes, direct_ms, cold_ms, warm_ms,
+ direct_ms - warm_ms, cold.prepare_ms, cold.weave_ms,
+ warm.weave_ms);
+ ret = 0;
+out:
+ free_values(x, count);
+ free_values(first, count);
+ free_values(second, count);
+ free_values(direct_first, count);
+ free_values(direct_second, count);
+ free_values(cold_first, count);
+ free_values(cold_second, count);
+ free_values(warm_first, count);
+ free_values(warm_second, count);
+ crypto_ec_deinit(ec);
+ remove_cache_dir(directory);
+ return ret;
+}
--
2.53.0
More information about the Hostap
mailing list