Commit 27caecd416 for openssl.org
commit 27caecd416627c18f9d038aa076f270afbfa4d60
Author: Marcel Cornu <marcel.d.cornu@intel.com>
Date: Mon Apr 13 16:44:04 2026 +0000
test: add SHAKE x4 internal cross-validation tests
Add a new `sha3_x4_internal_test` target and recipe to validate the
internal SHAKE x4 implementation against scalar SHA3 reference paths.
Cover SHAKE-128 and SHAKE-256 in one-shot and incremental modes, plus
multi-absorb and multi-squeeze cases across varied input and output
sizes. Tests are skipped when AVX512VL extensions are not available.
Signed-off-by: Marcel Cornu <marcel.d.cornu@intel.com>
Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
MergeDate: Mon Jul 13 15:55:55 2026
(Merged from https://github.com/openssl/openssl/pull/31090)
diff --git a/test/build.info b/test/build.info
index a5dcc7f89d..5c730f5482 100644
--- a/test/build.info
+++ b/test/build.info
@@ -941,6 +941,8 @@ IF[{- !$disabled{tests} -}]
PROGRAMS{noinst}=cmactest
ENDIF
+ PROGRAMS{noinst}=sha3_x4_internal_test
+
SOURCE[poly1305_internal_test]=poly1305_internal_test.c
INCLUDE[poly1305_internal_test]=.. ../include ../apps/include
DEPEND[poly1305_internal_test]=../libcrypto.a libtestutil.a
@@ -949,6 +951,10 @@ IF[{- !$disabled{tests} -}]
INCLUDE[chacha_internal_test]=.. ../include ../apps/include
DEPEND[chacha_internal_test]=../libcrypto.a libtestutil.a
+ SOURCE[sha3_x4_internal_test]=sha3_x4_internal_test.c
+ INCLUDE[sha3_x4_internal_test]=.. ../include ../apps/include
+ DEPEND[sha3_x4_internal_test]=../libcrypto.a libtestutil.a
+
SOURCE[asn1_internal_test]=asn1_internal_test.c
INCLUDE[asn1_internal_test]=.. ../include ../apps/include
DEPEND[asn1_internal_test]=../libcrypto.a libtestutil.a
diff --git a/test/recipes/03-test_sha3_x4_internal.t b/test/recipes/03-test_sha3_x4_internal.t
new file mode 100644
index 0000000000..9e5793aaf3
--- /dev/null
+++ b/test/recipes/03-test_sha3_x4_internal.t
@@ -0,0 +1,16 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright (c) 2026 Intel Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License"). You may not use
+# this file except in compliance with the License. You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use strict;
+use OpenSSL::Test;
+use OpenSSL::Test::Simple;
+
+setup("test_sha3_x4_internal");
+
+simple_test("test_sha3_x4_internal", "sha3_x4_internal_test");
diff --git a/test/sha3_x4_internal_test.c b/test/sha3_x4_internal_test.c
new file mode 100644
index 0000000000..aa6a452db6
--- /dev/null
+++ b/test/sha3_x4_internal_test.c
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright (c) 2026 Intel Corporation. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/*
+ * Internal cross-validation tests for the SHAKE x4 multi-buffer API.
+ *
+ * Each test computes SHAKE-128 or SHAKE-256 on four independent inputs
+ * using the x4 (AVX-512VL) path and compares every lane's output to the
+ * equivalent result produced by the scalar ossl_sha3_* API.
+ *
+ * Tests cover:
+ * - Single-call (ossl_sha3_shake{128,256}_x4_avx512vl) for many (inlen, outlen) pairs
+ * - Incremental init/absorb/squeeze for the same (inlen, outlen) pairs
+ * - Multi-absorb: input split at every possible block boundary
+ * - Multi-squeeze: output produced in two successive squeeze calls
+ */
+
+#include <string.h>
+#include "testutil.h"
+
+/*
+ * KECCAK1600_ASM is only added to the library compilation flags by the build
+ * system, not to test binaries. Since the x4 declarations in internal/sha3.h
+ * are guarded by that macro, we define it here before the include so that the
+ * KECCAK1600_X4_AVX512VL_CTX type and function prototypes are visible.
+ * The symbols themselves live in libcrypto and are always present.
+ * We additionally gate all x4 code on x86_64 (GCC/Clang: __x86_64__,
+ * MSVC: _M_AMD64/_M_X64) and !OPENSSL_NO_ASM so that the test still
+ * compiles on other platforms or in no-asm builds.
+ */
+#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64)) \
+ && !defined(OPENSSL_NO_ASM)
+#ifndef KECCAK1600_ASM
+#define KECCAK1600_ASM
+#endif
+#endif
+#include "internal/sha3.h"
+
+/*
+ * A single deterministic 1024-byte message. Each of the four lanes receives
+ * a different slice of this buffer, with lane base pointers spaced 64 bytes
+ * apart, so their inputs are distinct yet entirely self-contained.
+ */
+#define MSG_BUF_SIZE 1024
+#define LANE_STRIDE 64 /* byte offset between lane base pointers */
+#define NUM_LANES 4
+
+static unsigned char msg[MSG_BUF_SIZE];
+
+/* Maximum output length used in this file – must fit chunk1 + chunk2. */
+#define MAX_OUT 640
+
+#if defined(KECCAK1600_ASM) \
+ && (defined(__x86_64) || defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64)) \
+ && !defined(OPENSSL_NO_ASM)
+
+/*
+ * Input lengths exercising: empty, tiny, sub-block, block boundary ±1,
+ * multiple blocks and a longer message for SHAKE-128 (rate=168) and
+ * SHAKE-256 (rate=136).
+ */
+static const size_t input_sizes[] = {
+ 0, 1, 17, 100, 135, 136, 137, 168, 169, 200, 400
+};
+#define NUM_INPUT_SIZES (sizeof(input_sizes) / sizeof(input_sizes[0]))
+
+/* Output lengths chosen to straddle rate boundaries for both variants. */
+static const size_t output_sizes[] = {
+ 16, 32, 64, 136, 168, 256, 512
+};
+#define NUM_OUTPUT_SIZES (sizeof(output_sizes) / sizeof(output_sizes[0]))
+
+/* Helpers functions */
+
+/*
+ * Compute a scalar SHAKE-128 or SHAKE-256 digest.
+ * bitlen: 128 or 256. Returns 1 on success, 0 on failure.
+ */
+static int scalar_shake(const unsigned int bitlen,
+ const unsigned char *in, const size_t inlen,
+ unsigned char *out, const size_t outlen)
+{
+ KECCAK1600_CTX ctx;
+
+ if (!ossl_sha3_init(&ctx, 0x1f, bitlen))
+ return 0;
+ /* ossl_sha3_init does not populate the method vtable; do it here. */
+ ctx.meth.absorb = ossl_sha3_absorb_default;
+ ctx.meth.final = ossl_sha3_final_default;
+ ctx.meth.squeeze = ossl_shake_squeeze_default;
+ return ossl_sha3_absorb(&ctx, in, inlen)
+ && ossl_sha3_squeeze(&ctx, out, outlen);
+}
+
+/*
+ * Encode (inlen_idx, outlen_idx) into a single test index and back.
+ * test index n = inlen_idx * NUM_OUTPUT_SIZES + outlen_idx
+ */
+static void decode_idx(const int n, size_t *inlen, size_t *outlen)
+{
+ *inlen = input_sizes[n / (int)NUM_OUTPUT_SIZES];
+ *outlen = output_sizes[n % (int)NUM_OUTPUT_SIZES];
+}
+
+/* One-shot tests */
+
+static int test_shake_x4_oneshot(const unsigned int bitlen, const int n)
+{
+ size_t inlen, outlen;
+ const unsigned char *in[NUM_LANES];
+ unsigned char x4_out[NUM_LANES][MAX_OUT];
+ unsigned char ref_out[NUM_LANES][MAX_OUT];
+ int i;
+
+ decode_idx(n, &inlen, &outlen);
+
+ for (i = 0; i < NUM_LANES; i++)
+ in[i] = msg + i * LANE_STRIDE;
+
+ /* Ensure the lane inputs fit within the message buffer. */
+ if (!TEST_size_t_le(inlen + (NUM_LANES - 1) * LANE_STRIDE, MSG_BUF_SIZE))
+ return 0;
+ if (!TEST_size_t_le(outlen, MAX_OUT))
+ return 0;
+
+ /* x4 single-call */
+ if (bitlen == 128)
+ ossl_sha3_shake128_x4_avx512vl(x4_out[0], x4_out[1], x4_out[2], x4_out[3],
+ outlen,
+ in[0], in[1], in[2], in[3], inlen);
+ else
+ ossl_sha3_shake256_x4_avx512vl(x4_out[0], x4_out[1], x4_out[2], x4_out[3],
+ outlen,
+ in[0], in[1], in[2], in[3], inlen);
+
+ /* scalar reference */
+ for (i = 0; i < NUM_LANES; i++)
+ if (!TEST_true(scalar_shake(bitlen, in[i], inlen, ref_out[i], outlen)))
+ return 0;
+
+ /* compare */
+ for (i = 0; i < NUM_LANES; i++) {
+ if (!TEST_mem_eq(x4_out[i], outlen, ref_out[i], outlen)) {
+ TEST_info("SHAKE-%u x4 oneshot lane %d: inlen=%zu outlen=%zu",
+ bitlen, i, inlen, outlen);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int test_shake128_x4_oneshot(const int n)
+{
+ return test_shake_x4_oneshot(128, n);
+}
+
+static int test_shake256_x4_oneshot(const int n)
+{
+ return test_shake_x4_oneshot(256, n);
+}
+
+/* Incremental (init / absorb / finalize / squeeze) tests */
+
+static int test_shake_x4_incremental(const unsigned int bitlen, const int n)
+{
+ size_t inlen, outlen;
+ const unsigned char *in[NUM_LANES];
+ unsigned char x4_out[NUM_LANES][MAX_OUT];
+ unsigned char ref_out[NUM_LANES][MAX_OUT];
+ KECCAK1600_X4_AVX512VL_CTX ctx;
+ int i;
+
+ decode_idx(n, &inlen, &outlen);
+
+ for (i = 0; i < NUM_LANES; i++)
+ in[i] = msg + i * LANE_STRIDE;
+
+ if (!TEST_size_t_le(inlen + (NUM_LANES - 1) * LANE_STRIDE, MSG_BUF_SIZE))
+ return 0;
+
+ /* x4 incremental */
+ if (bitlen == 128) {
+ ossl_sha3_shake128_x4_inc_init_avx512vl(&ctx);
+ ossl_sha3_shake128_x4_inc_absorb_avx512vl(&ctx, in[0], in[1], in[2], in[3],
+ inlen);
+ ossl_sha3_shake128_x4_inc_squeeze_avx512vl(x4_out[0], x4_out[1],
+ x4_out[2], x4_out[3], outlen, &ctx);
+ } else {
+ ossl_sha3_shake256_x4_inc_init_avx512vl(&ctx);
+ ossl_sha3_shake256_x4_inc_absorb_avx512vl(&ctx, in[0], in[1], in[2], in[3],
+ inlen);
+ ossl_sha3_shake256_x4_inc_squeeze_avx512vl(x4_out[0], x4_out[1],
+ x4_out[2], x4_out[3], outlen, &ctx);
+ }
+
+ /* scalar reference */
+ for (i = 0; i < NUM_LANES; i++)
+ if (!TEST_true(scalar_shake(bitlen, in[i], inlen, ref_out[i], outlen)))
+ return 0;
+
+ for (i = 0; i < NUM_LANES; i++) {
+ if (!TEST_mem_eq(x4_out[i], outlen, ref_out[i], outlen)) {
+ TEST_info("SHAKE-%u x4 incremental lane %d: inlen=%zu outlen=%zu",
+ bitlen, i, inlen, outlen);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int test_shake128_x4_incremental(const int n)
+{
+ return test_shake_x4_incremental(128, n);
+}
+
+static int test_shake256_x4_incremental(const int n)
+{
+ return test_shake_x4_incremental(256, n);
+}
+
+/* Multi-absorb tests */
+
+/*
+ * Split the input at every tested input size, absorbing the two halves
+ * in separate calls. The split length is chosen as input_sizes[n] so that
+ * we exercise sub-block, at-block and multi-block split points.
+ *
+ * Full message length is fixed at the largest tested input size so that
+ * every split index is meaningful.
+ */
+static int test_shake_x4_multi_absorb(const unsigned int bitlen, const int n)
+{
+ const size_t total = input_sizes[NUM_INPUT_SIZES - 1];
+ const size_t split = input_sizes[n];
+ const size_t outlen = 64; /* fixed output length for this sub-test */
+ const unsigned char *in[NUM_LANES];
+ unsigned char x4_out[NUM_LANES][MAX_OUT];
+ unsigned char ref_out[NUM_LANES][MAX_OUT];
+ KECCAK1600_X4_AVX512VL_CTX ctx;
+ int i;
+
+ if (split > total)
+ return 1; /* nothing to test */
+
+ for (i = 0; i < NUM_LANES; i++)
+ in[i] = msg + i * LANE_STRIDE;
+
+ if (!TEST_size_t_le(total + (NUM_LANES - 1) * LANE_STRIDE, MSG_BUF_SIZE))
+ return 0;
+
+ /* x4 split absorb */
+ if (bitlen == 128) {
+ ossl_sha3_shake128_x4_inc_init_avx512vl(&ctx);
+ ossl_sha3_shake128_x4_inc_absorb_avx512vl(&ctx,
+ in[0], in[1], in[2], in[3], split);
+ ossl_sha3_shake128_x4_inc_absorb_avx512vl(&ctx,
+ in[0] + split, in[1] + split, in[2] + split, in[3] + split,
+ total - split);
+ ossl_sha3_shake128_x4_inc_squeeze_avx512vl(x4_out[0], x4_out[1],
+ x4_out[2], x4_out[3], outlen, &ctx);
+ } else {
+ ossl_sha3_shake256_x4_inc_init_avx512vl(&ctx);
+ ossl_sha3_shake256_x4_inc_absorb_avx512vl(&ctx,
+ in[0], in[1], in[2], in[3], split);
+ ossl_sha3_shake256_x4_inc_absorb_avx512vl(&ctx,
+ in[0] + split, in[1] + split, in[2] + split, in[3] + split,
+ total - split);
+ ossl_sha3_shake256_x4_inc_squeeze_avx512vl(x4_out[0], x4_out[1],
+ x4_out[2], x4_out[3], outlen, &ctx);
+ }
+
+ /* scalar reference (single absorb of full message) */
+ for (i = 0; i < NUM_LANES; i++)
+ if (!TEST_true(scalar_shake(bitlen, in[i], total, ref_out[i], outlen)))
+ return 0;
+
+ for (i = 0; i < NUM_LANES; i++) {
+ if (!TEST_mem_eq(x4_out[i], outlen, ref_out[i], outlen)) {
+ TEST_info("SHAKE-%u x4 multi-absorb lane %d: total=%zu split=%zu",
+ bitlen, i, total, split);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int test_shake128_x4_multi_absorb(const int n)
+{
+ return test_shake_x4_multi_absorb(128, n);
+}
+
+static int test_shake256_x4_multi_absorb(const int n)
+{
+ return test_shake_x4_multi_absorb(256, n);
+}
+
+/* Multi-squeeze tests */
+
+/*
+ * Squeeze in two successive calls and verify that the concatenated output
+ * matches a single scalar squeeze of the same total length.
+ * Parameterized over output_sizes[] for the first chunk; the second chunk
+ * is always 64 bytes so the total length varies.
+ */
+static int test_shake_x4_multi_squeeze(const unsigned int bitlen, const int n)
+{
+ const size_t inlen = 200; /* fixed input length */
+ const size_t chunk1 = output_sizes[n];
+ const size_t chunk2 = 64;
+ const size_t total = chunk1 + chunk2;
+ const unsigned char *in[NUM_LANES];
+ unsigned char x4_a[NUM_LANES][MAX_OUT]; /* first chunk */
+ unsigned char x4_b[NUM_LANES][MAX_OUT]; /* second chunk */
+ unsigned char ref_out[NUM_LANES][MAX_OUT];
+ KECCAK1600_X4_AVX512VL_CTX ctx;
+ int i;
+
+ if (!TEST_size_t_le(total, MAX_OUT))
+ return 0;
+ if (!TEST_size_t_le(inlen + (NUM_LANES - 1) * LANE_STRIDE, MSG_BUF_SIZE))
+ return 0;
+
+ for (i = 0; i < NUM_LANES; i++)
+ in[i] = msg + i * LANE_STRIDE;
+
+ /* x4 two-shot squeeze */
+ if (bitlen == 128) {
+ ossl_sha3_shake128_x4_inc_init_avx512vl(&ctx);
+ ossl_sha3_shake128_x4_inc_absorb_avx512vl(&ctx, in[0], in[1], in[2], in[3],
+ inlen);
+ /* first squeeze */
+ ossl_sha3_shake128_x4_inc_squeeze_avx512vl(x4_a[0], x4_a[1], x4_a[2], x4_a[3],
+ chunk1, &ctx);
+ /* second squeeze – context carries state from previous call */
+ ossl_sha3_shake128_x4_inc_squeeze_avx512vl(x4_b[0], x4_b[1], x4_b[2], x4_b[3],
+ chunk2, &ctx);
+ } else {
+ ossl_sha3_shake256_x4_inc_init_avx512vl(&ctx);
+ ossl_sha3_shake256_x4_inc_absorb_avx512vl(&ctx, in[0], in[1], in[2], in[3],
+ inlen);
+ ossl_sha3_shake256_x4_inc_squeeze_avx512vl(x4_a[0], x4_a[1], x4_a[2], x4_a[3],
+ chunk1, &ctx);
+ ossl_sha3_shake256_x4_inc_squeeze_avx512vl(x4_b[0], x4_b[1], x4_b[2], x4_b[3],
+ chunk2, &ctx);
+ }
+
+ /* scalar reference – squeeze the full total in one call */
+ for (i = 0; i < NUM_LANES; i++)
+ if (!TEST_true(scalar_shake(bitlen, in[i], inlen, ref_out[i], total)))
+ return 0;
+
+ /* check first chunk, then second chunk */
+ for (i = 0; i < NUM_LANES; i++) {
+ if (!TEST_mem_eq(x4_a[i], chunk1, ref_out[i], chunk1)) {
+ TEST_info("SHAKE-%u x4 multi-squeeze lane %d chunk1: "
+ "inlen=%zu chunk1=%zu chunk2=%zu",
+ bitlen, i, inlen, chunk1, chunk2);
+ return 0;
+ }
+ if (!TEST_mem_eq(x4_b[i], chunk2, ref_out[i] + chunk1, chunk2)) {
+ TEST_info("SHAKE-%u x4 multi-squeeze lane %d chunk2: "
+ "inlen=%zu chunk1=%zu chunk2=%zu",
+ bitlen, i, inlen, chunk1, chunk2);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int test_shake128_x4_multi_squeeze(const int n)
+{
+ return test_shake_x4_multi_squeeze(128, n);
+}
+
+static int test_shake256_x4_multi_squeeze(const int n)
+{
+ return test_shake_x4_multi_squeeze(256, n);
+}
+
+#endif /* KECCAK1600_ASM && x86_64 && !OPENSSL_NO_ASM */
+
+/* Test entry point */
+
+int setup_tests(void)
+{
+ size_t i;
+
+ /* Fill the message buffer with a deterministic non-zero pattern. */
+ for (i = 0; i < MSG_BUF_SIZE; i++)
+ msg[i] = (unsigned char)(251 * i + 17);
+
+#ifdef OPENSSL_CPUID_OBJ
+ OPENSSL_cpuid_setup();
+#endif
+
+#if !defined(KECCAK1600_ASM) \
+ || !(defined(__x86_64) || defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64)) \
+ || defined(OPENSSL_NO_ASM)
+ return TEST_skip("SHAKE x4 API not available in this build");
+#else
+ if (!SHA3_avx512vl_capable()) {
+ return TEST_skip("AVX-512VL not available; skipping SHAKE x4 tests");
+ }
+
+ ADD_ALL_TESTS(test_shake128_x4_oneshot,
+ (int)(NUM_INPUT_SIZES * NUM_OUTPUT_SIZES));
+ ADD_ALL_TESTS(test_shake256_x4_oneshot,
+ (int)(NUM_INPUT_SIZES * NUM_OUTPUT_SIZES));
+
+ ADD_ALL_TESTS(test_shake128_x4_incremental,
+ (int)(NUM_INPUT_SIZES * NUM_OUTPUT_SIZES));
+ ADD_ALL_TESTS(test_shake256_x4_incremental,
+ (int)(NUM_INPUT_SIZES * NUM_OUTPUT_SIZES));
+
+ ADD_ALL_TESTS(test_shake128_x4_multi_absorb, (int)NUM_INPUT_SIZES);
+ ADD_ALL_TESTS(test_shake256_x4_multi_absorb, (int)NUM_INPUT_SIZES);
+
+ ADD_ALL_TESTS(test_shake128_x4_multi_squeeze, (int)NUM_OUTPUT_SIZES);
+ ADD_ALL_TESTS(test_shake256_x4_multi_squeeze, (int)NUM_OUTPUT_SIZES);
+#endif
+
+ return 1;
+}