Commit 5fd4370326 for openssl.org
commit 5fd4370326b5586c5cf1ee6c43e5dce10f311f3b
Author: Timo Keller <tkeller@linux.ibm.com>
Date: Tue Aug 25 09:37:11 2026 +0200
Add scalar/vector NTT comparison test for ML-DSA
Add a test that compares the results of the ML-DSA scalar and the s390x
vectorized NTT for 2^16 random input values. Compile it if the
vectorized s390x function is compiled and run it if VX is enabled.
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Assisted-by: IBM Bob:2.0.3
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Sep 29 16:47:14 2026
Merged-from: https://github.com/openssl/openssl/pull/31929
diff --git a/crypto/ml_dsa/ml_dsa_local.h b/crypto/ml_dsa/ml_dsa_local.h
index aa34e1dccd..491db1e2e2 100644
--- a/crypto/ml_dsa/ml_dsa_local.h
+++ b/crypto/ml_dsa/ml_dsa_local.h
@@ -99,6 +99,7 @@ void ossl_ml_dsa_poly_ntt_mult(const POLY *lhs, const POLY *rhs, POLY *out);
*/
#if defined(OPENSSL_ML_DSA_S390X)
#include "arch/s390x_arch.h"
+void ossl_ml_dsa_poly_ntt_scalar(POLY *p);
void ossl_ml_dsa_poly_ntt_vec128(POLY *p);
void ossl_ml_dsa_poly_ntt_inverse_vec128(POLY *p);
void ossl_poly_ntt_mult_scalar_vec128(const POLY *lhs, const POLY *rhs, POLY *out);
diff --git a/crypto/ml_dsa/ml_dsa_ntt.c b/crypto/ml_dsa/ml_dsa_ntt.c
index b204ea6896..9181f7bab6 100644
--- a/crypto/ml_dsa/ml_dsa_ntt.c
+++ b/crypto/ml_dsa/ml_dsa_ntt.c
@@ -30,14 +30,14 @@ typedef void (*ml_dsa_poly_ntt_mult_fn)(const POLY *lhs, const POLY *rhs,
POLY *out);
/* Forward declarations of scalar NTT functions */
-static void poly_ntt_scalar(POLY *p);
+void ossl_ml_dsa_poly_ntt_scalar(POLY *p);
static void poly_ntt_inverse_scalar(POLY *p);
static void poly_ntt_mult_scalar(const POLY *lhs, const POLY *rhs, POLY *out);
/*
* NTT function pointers - initialized to scalar implementations by default.
*/
-static ml_dsa_poly_ntt_fn poly_ntt_impl = poly_ntt_scalar;
+static ml_dsa_poly_ntt_fn poly_ntt_impl = ossl_ml_dsa_poly_ntt_scalar;
static ml_dsa_poly_ntt_inverse_fn poly_ntt_inverse_impl = poly_ntt_inverse_scalar;
static ml_dsa_poly_ntt_mult_fn poly_ntt_mult_impl = poly_ntt_mult_scalar;
@@ -145,7 +145,7 @@ static void poly_ntt_mult_scalar(const POLY *lhs, const POLY *rhs, POLY *out)
* (uint64_t)rhs->coeff[i]);
}
-static void poly_ntt_scalar(POLY *p)
+void ossl_ml_dsa_poly_ntt_scalar(POLY *p)
{
int i, j, k;
int step;
diff --git a/test/build.info b/test/build.info
index 4846ece61a..74b63b558d 100644
--- a/test/build.info
+++ b/test/build.info
@@ -363,6 +363,11 @@ IF[{- !$disabled{tests} -}]
SOURCE[ml_dsa_internal_test]=ml_dsa_internal_test.c
INCLUDE[ml_dsa_internal_test]=../include ../apps/include
DEPEND[ml_dsa_internal_test]=../libcrypto.a libtestutil.a
+
+ IF[{- !$disabled{asm} && ($target{perlasm_scheme} // '') ne '31' && defined($config{s390x_vector_cflags}) -}]
+ DEFINE[ml_dsa_internal_test]=OPENSSL_ML_DSA_S390X
+ INCLUDE[ml_dsa_internal_test]=../crypto/ml_dsa
+ ENDIF
ENDIF
PROGRAMS{noinst}=aeswrap_test
diff --git a/test/ml_dsa_internal_test.c b/test/ml_dsa_internal_test.c
index b992a2301b..48e2862fb4 100644
--- a/test/ml_dsa_internal_test.c
+++ b/test/ml_dsa_internal_test.c
@@ -24,6 +24,68 @@
#include "crypto/ml_dsa.h"
#include "testutil.h"
+#if defined(OPENSSL_ML_DSA_S390X)
+/*
+ * ml_dsa_local.h and ml_dsa_poly.h are internal headers from crypto/ml_dsa/.
+ * They are reachable because test/build.info adds ../crypto/ml_dsa to the
+ * include path when OPENSSL_ML_DSA_S390X is defined.
+ */
+#include "ml_dsa_local.h"
+#include "ml_dsa_poly.h"
+
+/*
+ * Minimal xorshift32 PRNG.
+ * Period: 2^32 - 1. Seed must be non-zero.
+ */
+static uint32_t xorshift32(uint32_t *state)
+{
+ uint32_t x = *state;
+
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ return *state = x;
+}
+
+/*
+ * Verify that ossl_ml_dsa_poly_ntt_vec128 produces the same output as the
+ * scalar forward NTT on 2^16 random polynomials with coefficients in [0, q).
+ *
+ * The test is compiled only when OPENSSL_ML_DSA_S390X is defined (i.e., when
+ * ml_dsa_ntt_vec128.c is compiled in). It skips at runtime when the CPU
+ * does not support the VX facility so the binary can run on any s390x host.
+ */
+static int test_poly_ntt_vec128(void)
+{
+ /* xorshift32 seed */
+ uint32_t rng = 31929;
+ int i, j;
+ POLY scalar_copy, vec128_copy;
+
+ if (!S390X_VX_CAPABLE)
+ return TEST_skip("S390X VX not available at runtime");
+
+ for (i = 0; i < (1 << 16); i++) {
+ /* Fill both copies with the same random polynomial in [0, q). */
+ for (j = 0; j < ML_DSA_NUM_POLY_COEFFICIENTS; j++) {
+ uint32_t c = xorshift32(&rng) % ML_DSA_Q;
+
+ scalar_copy.coeff[j] = c;
+ vec128_copy.coeff[j] = c;
+ }
+
+ ossl_ml_dsa_poly_ntt_scalar(&scalar_copy);
+ ossl_ml_dsa_poly_ntt_vec128(&vec128_copy);
+
+ for (j = 0; j < ML_DSA_NUM_POLY_COEFFICIENTS; j++) {
+ if (!TEST_uint_eq(vec128_copy.coeff[j], scalar_copy.coeff[j]))
+ return 0;
+ }
+ }
+ return 1;
+}
+#endif /* OPENSSL_ML_DSA_S390X */
+
/* Fixed 32-byte seed used for all three parameter-set tests. */
static const uint8_t test_seed[ML_DSA_SEED_BYTES] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
@@ -110,5 +172,8 @@ int setup_tests(void)
ADD_TEST(test_ml_dsa_44);
ADD_TEST(test_ml_dsa_65);
ADD_TEST(test_ml_dsa_87);
+#if defined(OPENSSL_ML_DSA_S390X)
+ ADD_TEST(test_poly_ntt_vec128);
+#endif
return 1;
}