From 7c0e3268da91149a0a40fad1b188c8dfe8528241 Mon Sep 17 00:00:00 2001 From: M-AlNoaimi Date: Sun, 12 Oct 2025 20:59:25 +0100 Subject: [PATCH 1/4] Add build option for randomized ML-DSA signing Signed-off-by: M-AlNoaimi --- .CMake/alg_support.cmake | 1 + CONFIGURE.md | 8 +- .../add_enable_by_alg.fragment | 3 + tests/test_sig.c | 105 ++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/.CMake/alg_support.cmake b/.CMake/alg_support.cmake index 9c7fc77d4b..bbf95de8b1 100644 --- a/.CMake/alg_support.cmake +++ b/.CMake/alg_support.cmake @@ -174,6 +174,7 @@ cmake_dependent_option(OQS_ENABLE_KEM_ml_kem_768 "" ON "OQS_ENABLE_KEM_ML_KEM" O cmake_dependent_option(OQS_ENABLE_KEM_ml_kem_1024 "" ON "OQS_ENABLE_KEM_ML_KEM" OFF) option(OQS_ENABLE_SIG_ML_DSA "Enable ml_dsa algorithm family" ON) +cmake_dependent_option(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING "Enable randomized signing for ML-DSA" OFF "OQS_ENABLE_SIG_ML_DSA" OFF) cmake_dependent_option(OQS_ENABLE_SIG_ml_dsa_44 "" ON "OQS_ENABLE_SIG_ML_DSA" OFF) cmake_dependent_option(OQS_ENABLE_SIG_ml_dsa_65 "" ON "OQS_ENABLE_SIG_ML_DSA" OFF) cmake_dependent_option(OQS_ENABLE_SIG_ml_dsa_87 "" ON "OQS_ENABLE_SIG_ML_DSA" OFF) diff --git a/CONFIGURE.md b/CONFIGURE.md index c73eb84889..dcd1456d1c 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -62,7 +62,13 @@ To enable `XMSS` stateful signature, set `OQS_ENABLE_SIG_STFL_XMSS` to `ON`, the For a full list of such options and their default values, consult [.CMake/alg_support.cmake](https://github.com/open-quantum-safe/liboqs/blob/master/.CMake/alg_support.cmake). -**Default**: Unset. +### OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING + +Can be set to `ON` or `OFF`. When `ON`, ML-DSA signature algorithms are built with randomized signing enabled, resulting in non-deterministic signatures (randomized seed/nonce per signature). + +This option is only available if `OQS_ENABLE_SIG_ML_DSA` is `ON`. + +**Default**: `OFF`. ## OQS_ALGS_ENABLED diff --git a/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment b/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment index 62135d9d43..62d65e3413 100644 --- a/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment +++ b/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment @@ -17,6 +17,9 @@ cmake_dependent_option(OQS_ENABLE_KEM_{{ family['name'] }}_{{ scheme['alias_sche option(OQS_ENABLE_SIG_{{ family['name']|upper }} "Enable {{ family['name'] }} algorithm family" OFF) {%- else %} option(OQS_ENABLE_SIG_{{ family['name']|upper }} "Enable {{ family['name'] }} algorithm family" ON) +{%- endif %} +{%- if family['name'] == "ml_dsa" %} +cmake_dependent_option(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING "Enable randomized signing for ML-DSA" OFF "OQS_ENABLE_SIG_ML_DSA" OFF) {%- endif %} {%- for scheme in family['schemes'] %} cmake_dependent_option(OQS_ENABLE_SIG_{{ family['name'] }}_{{ scheme['scheme'] }} "" ON "OQS_ENABLE_SIG_{{ family['name']|upper }}" OFF) diff --git a/tests/test_sig.c b/tests/test_sig.c index 40a60f654c..03f0d1c686 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -244,6 +244,101 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al return ret; } +#if defined(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) +// Test that two signatures of the same message are different. +static OQS_STATUS sig_test_randomized_signing(const char *method_name) { + OQS_SIG *sig = NULL; + uint8_t *public_key = NULL; + uint8_t *secret_key = NULL; + uint8_t *message = NULL; + size_t message_len = 100; + uint8_t *signature1 = NULL; + size_t signature1_len; + uint8_t *signature2 = NULL; + size_t signature2_len; + OQS_STATUS rc, ret = OQS_ERROR; + + sig = OQS_SIG_new(method_name); + if (sig == NULL) { + // ML-DSA is not enabled, so we can't test it. + return OQS_SUCCESS; + } + + printf("Testing randomized signing for %s\n", sig->method_name); + + public_key = OQS_MEM_malloc(sig->length_public_key); + secret_key = OQS_MEM_malloc(sig->length_secret_key); + message = OQS_MEM_malloc(message_len); + signature1 = OQS_MEM_malloc(sig->length_signature); + signature2 = OQS_MEM_malloc(sig->length_signature); + + if ((public_key == NULL) || (secret_key == NULL) || (message == NULL) || (signature1 == NULL) || (signature2 == NULL)) { + fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); + goto err; + } + + OQS_randombytes(message, message_len); + + rc = OQS_SIG_keypair(sig, public_key, secret_key); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_keypair failed\n"); + goto err; + } + + rc = OQS_SIG_sign(sig, signature1, &signature1_len, message, message_len, secret_key); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_sign failed\n"); + goto err; + } + + rc = OQS_SIG_sign(sig, signature2, &signature2_len, message, message_len, secret_key); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_sign failed\n"); + goto err; + } + + if (signature1_len != signature2_len) { + printf("Signatures have different lengths, so they are not identical.\n"); + } else if (memcmp(signature1, signature2, signature1_len) == 0) { + fprintf(stderr, "ERROR: Two signatures of the same message are identical.\n"); + goto err; + } else { + printf("Two signatures of the same message are not identical, as expected.\n"); + } + + rc = OQS_SIG_verify(sig, message, message_len, signature1, signature1_len, public_key); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_verify failed for signature 1\n"); + goto err; + } + + rc = OQS_SIG_verify(sig, message, message_len, signature2, signature2_len, public_key); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_verify failed for signature 2\n"); + goto err; + } + + printf("verification passes as expected\n"); + ret = OQS_SUCCESS; + goto cleanup; + +err: + ret = OQS_ERROR; + +cleanup: + if (secret_key) { + OQS_MEM_secure_free(secret_key, sig->length_secret_key); + } + OQS_MEM_insecure_free(public_key); + OQS_MEM_insecure_free(message); + OQS_MEM_insecure_free(signature1); + OQS_MEM_insecure_free(signature2); + OQS_SIG_free(sig); + + return ret; +} +#endif + #ifdef OQS_ENABLE_TEST_CONSTANT_TIME static void TEST_SIG_randombytes(uint8_t *random_array, size_t bytes_to_read) { // We can't make direct calls to the system randombytes on some platforms, @@ -349,6 +444,16 @@ int main(int argc, char **argv) { } #endif +#if defined(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + if (strncmp(alg_name, "ML-DSA", 6) == 0) { + rc = sig_test_randomized_signing(alg_name); + if (rc != OQS_SUCCESS) { + OQS_destroy(); + return EXIT_FAILURE; + } + } +#endif + #if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) #define MAX_LEN_SIG_NAME_ 64 // don't run algorithms with large stack usage in threads From ec7289ec08c7b50d8a1b8bd4a7af1dd1db93c372 Mon Sep 17 00:00:00 2001 From: M-AlNoaimi Date: Sun, 19 Oct 2025 23:27:54 +0100 Subject: [PATCH 2/4] Updated logic for ML-DSA randomized signing, updated docs and tests (Python & C) Signed-off-by: M-AlNoaimi --- .CMake/alg_support.cmake | 2 +- CONFIGURE.md | 4 ++-- .../add_enable_by_alg.fragment | 2 +- .../patches/pqcrystals-ml_dsa.patch | 24 +++++++++++++++---- .../src/sig/family/CMakeLists.txt | 5 ++++ src/sig/ml_dsa/CMakeLists.txt | 18 ++++++++++++++ .../config.h | 1 - .../config.h | 1 - .../config.h | 1 - .../config.h | 1 - .../config.h | 1 - .../config.h | 1 - tests/CMakeLists.txt | 3 +++ tests/helpers.py | 11 +++++++++ tests/test_acvp_vectors.py | 3 +++ tests/test_kat.py | 3 +++ tests/test_kat_all.py | 3 +++ tests/test_sig.c | 4 ++-- 18 files changed, 72 insertions(+), 16 deletions(-) diff --git a/.CMake/alg_support.cmake b/.CMake/alg_support.cmake index bbf95de8b1..0c17408e29 100644 --- a/.CMake/alg_support.cmake +++ b/.CMake/alg_support.cmake @@ -174,7 +174,7 @@ cmake_dependent_option(OQS_ENABLE_KEM_ml_kem_768 "" ON "OQS_ENABLE_KEM_ML_KEM" O cmake_dependent_option(OQS_ENABLE_KEM_ml_kem_1024 "" ON "OQS_ENABLE_KEM_ML_KEM" OFF) option(OQS_ENABLE_SIG_ML_DSA "Enable ml_dsa algorithm family" ON) -cmake_dependent_option(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING "Enable randomized signing for ML-DSA" OFF "OQS_ENABLE_SIG_ML_DSA" OFF) +cmake_dependent_option(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING "Enable randomized signing for ML-DSA" ON "OQS_ENABLE_SIG_ML_DSA" OFF) cmake_dependent_option(OQS_ENABLE_SIG_ml_dsa_44 "" ON "OQS_ENABLE_SIG_ML_DSA" OFF) cmake_dependent_option(OQS_ENABLE_SIG_ml_dsa_65 "" ON "OQS_ENABLE_SIG_ML_DSA" OFF) cmake_dependent_option(OQS_ENABLE_SIG_ml_dsa_87 "" ON "OQS_ENABLE_SIG_ML_DSA" OFF) diff --git a/CONFIGURE.md b/CONFIGURE.md index dcd1456d1c..410075ac0a 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -64,11 +64,11 @@ For a full list of such options and their default values, consult [.CMake/alg_su ### OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING -Can be set to `ON` or `OFF`. When `ON`, ML-DSA signature algorithms are built with randomized signing enabled, resulting in non-deterministic signatures (randomized seed/nonce per signature). +Can be set to `ON` or `OFF`. When `ON`, ML-DSA signature algorithms are built with randomized signing enabled, resulting in non-deterministic signatures. When `OFF`, ML-DSA signature algorithms use deterministic signing. This option is only available if `OQS_ENABLE_SIG_ML_DSA` is `ON`. -**Default**: `OFF`. +**Default**: `ON`. ## OQS_ALGS_ENABLED diff --git a/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment b/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment index 62d65e3413..5b70f269f0 100644 --- a/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment +++ b/scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg.fragment @@ -19,7 +19,7 @@ option(OQS_ENABLE_SIG_{{ family['name']|upper }} "Enable {{ family['name'] }} al option(OQS_ENABLE_SIG_{{ family['name']|upper }} "Enable {{ family['name'] }} algorithm family" ON) {%- endif %} {%- if family['name'] == "ml_dsa" %} -cmake_dependent_option(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING "Enable randomized signing for ML-DSA" OFF "OQS_ENABLE_SIG_ML_DSA" OFF) +cmake_dependent_option(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING "Enable randomized signing for ML-DSA" ON "OQS_ENABLE_SIG_ML_DSA" OFF) {%- endif %} {%- for scheme in family['schemes'] %} cmake_dependent_option(OQS_ENABLE_SIG_{{ family['name'] }}_{{ scheme['scheme'] }} "" ON "OQS_ENABLE_SIG_{{ family['name']|upper }}" OFF) diff --git a/scripts/copy_from_upstream/patches/pqcrystals-ml_dsa.patch b/scripts/copy_from_upstream/patches/pqcrystals-ml_dsa.patch index e82d5c1edb..344e8e1130 100644 --- a/scripts/copy_from_upstream/patches/pqcrystals-ml_dsa.patch +++ b/scripts/copy_from_upstream/patches/pqcrystals-ml_dsa.patch @@ -140,10 +140,18 @@ index 5163526..e9bff1e 100644 - architecture: x86_64 operating_systems: diff --git a/avx2/config.h b/avx2/config.h -index a9facc0..3944cb4 100644 +index a9facc0..da97994 100644 --- a/avx2/config.h +++ b/avx2/config.h -@@ -11,17 +11,17 @@ +@@ -2,7 +2,6 @@ + #define CONFIG_H + + //#define DILITHIUM_MODE 2 +-#define DILITHIUM_RANDOMIZED_SIGNING + //#define USE_RDPMC + //#define DBENCH + +@@ -11,17 +10,17 @@ #endif #if DILITHIUM_MODE == 2 @@ -520,10 +528,18 @@ index 8f3c3c5..fa49963 100644 #endif diff --git a/ref/config.h b/ref/config.h -index 98b8ccb..8008e11 100644 +index 98b8ccb..7d52ebc 100644 --- a/ref/config.h +++ b/ref/config.h -@@ -11,17 +11,17 @@ +@@ -2,7 +2,6 @@ + #define CONFIG_H + + //#define DILITHIUM_MODE 2 +-#define DILITHIUM_RANDOMIZED_SIGNING + //#define USE_RDPMC + //#define DBENCH + +@@ -11,17 +10,17 @@ #endif #if DILITHIUM_MODE == 2 diff --git a/scripts/copy_from_upstream/src/sig/family/CMakeLists.txt b/scripts/copy_from_upstream/src/sig/family/CMakeLists.txt index b9103baa19..10b31e78ee 100644 --- a/scripts/copy_from_upstream/src/sig/family/CMakeLists.txt +++ b/scripts/copy_from_upstream/src/sig/family/CMakeLists.txt @@ -46,6 +46,11 @@ if(OQS_ENABLE_SIG_{{ family }}_{{ scheme['scheme_c'] }}_{{ impl['name'] }}{%- if {%- if impl['compile_opts'] %} target_compile_options({{ family }}_{{ scheme['scheme'] }}_{{ impl['name'] }} PUBLIC {{ impl['compile_opts'] }}) {%- endif %} + {%- if family == 'ml_dsa' %} + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options({{ family }}_{{ scheme['scheme'] }}_{{ impl['name'] }} PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() + {%- endif %} set(_{{ family|upper }}_OBJS ${_{{ family|upper }}_OBJS} $) endif() {%- endfor -%} diff --git a/src/sig/ml_dsa/CMakeLists.txt b/src/sig/ml_dsa/CMakeLists.txt index 37c1b373d7..731447f2db 100644 --- a/src/sig/ml_dsa/CMakeLists.txt +++ b/src/sig/ml_dsa/CMakeLists.txt @@ -11,6 +11,9 @@ if(OQS_ENABLE_SIG_ml_dsa_44) target_include_directories(ml_dsa_44_ref PRIVATE ${CMAKE_CURRENT_LIST_DIR}/pqcrystals-dilithium-standard_ml-dsa-44_ref) target_include_directories(ml_dsa_44_ref PRIVATE ${PROJECT_SOURCE_DIR}/src/common/pqclean_shims) target_compile_options(ml_dsa_44_ref PUBLIC -DDILITHIUM_MODE=2) + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options(ml_dsa_44_ref PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() set(_ML_DSA_OBJS ${_ML_DSA_OBJS} $) endif() @@ -20,6 +23,9 @@ if(OQS_ENABLE_SIG_ml_dsa_44_avx2) target_include_directories(ml_dsa_44_avx2 PRIVATE ${PROJECT_SOURCE_DIR}/src/common/pqclean_shims) target_compile_options(ml_dsa_44_avx2 PRIVATE -mavx2 -mpopcnt) target_compile_options(ml_dsa_44_avx2 PUBLIC -DDILITHIUM_MODE=2) + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options(ml_dsa_44_avx2 PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() set(_ML_DSA_OBJS ${_ML_DSA_OBJS} $) endif() @@ -29,6 +35,9 @@ if(OQS_ENABLE_SIG_ml_dsa_65) target_include_directories(ml_dsa_65_ref PRIVATE ${CMAKE_CURRENT_LIST_DIR}/pqcrystals-dilithium-standard_ml-dsa-65_ref) target_include_directories(ml_dsa_65_ref PRIVATE ${PROJECT_SOURCE_DIR}/src/common/pqclean_shims) target_compile_options(ml_dsa_65_ref PUBLIC -DDILITHIUM_MODE=3) + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options(ml_dsa_65_ref PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() set(_ML_DSA_OBJS ${_ML_DSA_OBJS} $) endif() @@ -38,6 +47,9 @@ if(OQS_ENABLE_SIG_ml_dsa_65_avx2) target_include_directories(ml_dsa_65_avx2 PRIVATE ${PROJECT_SOURCE_DIR}/src/common/pqclean_shims) target_compile_options(ml_dsa_65_avx2 PRIVATE -mavx2 -mpopcnt) target_compile_options(ml_dsa_65_avx2 PUBLIC -DDILITHIUM_MODE=3) + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options(ml_dsa_65_avx2 PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() set(_ML_DSA_OBJS ${_ML_DSA_OBJS} $) endif() @@ -47,6 +59,9 @@ if(OQS_ENABLE_SIG_ml_dsa_87) target_include_directories(ml_dsa_87_ref PRIVATE ${CMAKE_CURRENT_LIST_DIR}/pqcrystals-dilithium-standard_ml-dsa-87_ref) target_include_directories(ml_dsa_87_ref PRIVATE ${PROJECT_SOURCE_DIR}/src/common/pqclean_shims) target_compile_options(ml_dsa_87_ref PUBLIC -DDILITHIUM_MODE=5) + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options(ml_dsa_87_ref PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() set(_ML_DSA_OBJS ${_ML_DSA_OBJS} $) endif() @@ -56,6 +71,9 @@ if(OQS_ENABLE_SIG_ml_dsa_87_avx2) target_include_directories(ml_dsa_87_avx2 PRIVATE ${PROJECT_SOURCE_DIR}/src/common/pqclean_shims) target_compile_options(ml_dsa_87_avx2 PRIVATE -mavx2 -mpopcnt) target_compile_options(ml_dsa_87_avx2 PUBLIC -DDILITHIUM_MODE=5) + if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_options(ml_dsa_87_avx2 PUBLIC -DDILITHIUM_RANDOMIZED_SIGNING) + endif() set(_ML_DSA_OBJS ${_ML_DSA_OBJS} $) endif() diff --git a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_avx2/config.h b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_avx2/config.h index 3944cb4412..e95287e5f4 100644 --- a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_avx2/config.h +++ b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_avx2/config.h @@ -2,7 +2,6 @@ #define CONFIG_H //#define DILITHIUM_MODE 2 -#define DILITHIUM_RANDOMIZED_SIGNING //#define USE_RDPMC //#define DBENCH diff --git a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_ref/config.h b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_ref/config.h index 8008e11a92..73623fef56 100644 --- a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_ref/config.h +++ b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-44_ref/config.h @@ -2,7 +2,6 @@ #define CONFIG_H //#define DILITHIUM_MODE 2 -#define DILITHIUM_RANDOMIZED_SIGNING //#define USE_RDPMC //#define DBENCH diff --git a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_avx2/config.h b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_avx2/config.h index 3944cb4412..e95287e5f4 100644 --- a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_avx2/config.h +++ b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_avx2/config.h @@ -2,7 +2,6 @@ #define CONFIG_H //#define DILITHIUM_MODE 2 -#define DILITHIUM_RANDOMIZED_SIGNING //#define USE_RDPMC //#define DBENCH diff --git a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_ref/config.h b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_ref/config.h index 8008e11a92..73623fef56 100644 --- a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_ref/config.h +++ b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-65_ref/config.h @@ -2,7 +2,6 @@ #define CONFIG_H //#define DILITHIUM_MODE 2 -#define DILITHIUM_RANDOMIZED_SIGNING //#define USE_RDPMC //#define DBENCH diff --git a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_avx2/config.h b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_avx2/config.h index 3944cb4412..e95287e5f4 100644 --- a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_avx2/config.h +++ b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_avx2/config.h @@ -2,7 +2,6 @@ #define CONFIG_H //#define DILITHIUM_MODE 2 -#define DILITHIUM_RANDOMIZED_SIGNING //#define USE_RDPMC //#define DBENCH diff --git a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_ref/config.h b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_ref/config.h index 8008e11a92..73623fef56 100644 --- a/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_ref/config.h +++ b/src/sig/ml_dsa/pqcrystals-dilithium-standard_ml-dsa-87_ref/config.h @@ -2,7 +2,6 @@ #define CONFIG_H //#define DILITHIUM_MODE 2 -#define DILITHIUM_RANDOMIZED_SIGNING //#define USE_RDPMC //#define DBENCH diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 76b47492f8..436984e4d7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -154,6 +154,9 @@ endif() add_executable(test_sig test_sig.c test_helpers.c) target_link_libraries(test_sig PRIVATE ${TEST_DEPS}) +if(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + target_compile_definitions(test_sig PRIVATE OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) +endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) # workaround for Windows .dll if(CMAKE_CROSSCOMPILING) diff --git a/tests/helpers.py b/tests/helpers.py index 077b4d428f..8fb115ed64 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -193,6 +193,17 @@ def available_use_options_by_name(): def is_use_option_enabled_by_name(name): return name in available_use_options_by_name() +def is_ml_dsa_randomized_signing_enabled(): + header = os.path.join(get_current_build_dir_name(), 'include', 'oqs', 'oqsconfig.h') + try: + with open(header) as fh: + for line in fh: + if line.strip() == "#define OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING": + return True + except Exception: + pass + return False + def get_kats(t): if kats[t] is None: with open(os.path.join('tests', 'KATs', t, 'kats.json'), 'r') as fp: diff --git a/tests/test_acvp_vectors.py b/tests/test_acvp_vectors.py index 7cc4240490..26f0893382 100644 --- a/tests/test_acvp_vectors.py +++ b/tests/test_acvp_vectors.py @@ -159,6 +159,9 @@ def test_acvp_vec_ml_dsa_sig_gen(sig_name): if variant["parameterSet"] == sig_name: variantFound = True for testCase in variant["tests"]: + # Skip randomized signature vectors if built in deterministic mode + if (not helpers.is_ml_dsa_randomized_signing_enabled()) and (not variant["deterministic"]): + pytest.skip("Skipping randomized signature vector test: implementation built in deterministic mode") sk = testCase["sk"] message = testCase["message"] signature = testCase["signature"] diff --git a/tests/test_kat.py b/tests/test_kat.py index dde7b02a78..3a10594633 100644 --- a/tests/test_kat.py +++ b/tests/test_kat.py @@ -27,6 +27,9 @@ def test_sig(sig_name): kats = helpers.get_kats("sig") # slh dsa will run ACVP vectors instead if ("SLH_DSA" in sig_name): pytest.skip('slhdsa not enabled for KATs') + # Skip ML-DSA KATs if randomized signing is not enabled + if sig_name in ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"] and not helpers.is_ml_dsa_randomized_signing_enabled(): + pytest.skip("Skipping ML-DSA KAT signature test: implementation built in deterministic mode") if not(helpers.is_sig_enabled_by_name(sig_name)): pytest.skip('Not enabled') output = helpers.run_subprocess( [helpers.path_to_executable('kat_sig'), sig_name], diff --git a/tests/test_kat_all.py b/tests/test_kat_all.py index 67354876f2..236f96e3fd 100644 --- a/tests/test_kat_all.py +++ b/tests/test_kat_all.py @@ -27,6 +27,9 @@ def test_sig(sig_name): kats = helpers.get_kats("sig") # slh dsa will run ACVP vectors instead if ("SLH_DSA" in sig_name): pytest.skip('slhdsa not enabled for KATs') + # Skip ML-DSA KATs if randomized signing is not enabled + if sig_name in ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"] and not helpers.is_ml_dsa_randomized_signing_enabled(): + pytest.skip("Skipping ML-DSA KAT signature test: implementation built in deterministic mode") if not(helpers.is_sig_enabled_by_name(sig_name)): pytest.skip('Not enabled') output = helpers.run_subprocess( [helpers.path_to_executable('kat_sig'), sig_name, '--all'], diff --git a/tests/test_sig.c b/tests/test_sig.c index 03f0d1c686..79e6136f68 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -260,8 +260,8 @@ static OQS_STATUS sig_test_randomized_signing(const char *method_name) { sig = OQS_SIG_new(method_name); if (sig == NULL) { - // ML-DSA is not enabled, so we can't test it. - return OQS_SUCCESS; + fprintf(stderr, "ERROR: OQS_SIG_new failed for %s\n", method_name); + return OQS_ERROR; } printf("Testing randomized signing for %s\n", sig->method_name); From f54e3f49d0b19597d4ea6dec52748d37f530d33b Mon Sep 17 00:00:00 2001 From: M-AlNoaimi Date: Mon, 3 Nov 2025 16:37:14 +0000 Subject: [PATCH 3/4] improved ML-DSA signature tests & reverted the unset flag text Signed-off-by: M-AlNoaimi --- CONFIGURE.md | 2 ++ tests/test_sig.c | 37 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) mode change 100644 => 100755 tests/test_sig.c diff --git a/CONFIGURE.md b/CONFIGURE.md index 410075ac0a..20783b629c 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -62,6 +62,8 @@ To enable `XMSS` stateful signature, set `OQS_ENABLE_SIG_STFL_XMSS` to `ON`, the For a full list of such options and their default values, consult [.CMake/alg_support.cmake](https://github.com/open-quantum-safe/liboqs/blob/master/.CMake/alg_support.cmake). +**Default**: Unset. + ### OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING Can be set to `ON` or `OFF`. When `ON`, ML-DSA signature algorithms are built with randomized signing enabled, resulting in non-deterministic signatures. When `OFF`, ML-DSA signature algorithms use deterministic signing. diff --git a/tests/test_sig.c b/tests/test_sig.c old mode 100644 new mode 100755 index 79e6136f68..c548733b9d --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -244,9 +244,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al return ret; } -#if defined(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) -// Test that two signatures of the same message are different. -static OQS_STATUS sig_test_randomized_signing(const char *method_name) { +static OQS_STATUS sig_test_repeat_signing(const char *method_name, bool is_randomized) { OQS_SIG *sig = NULL; uint8_t *public_key = NULL; uint8_t *secret_key = NULL; @@ -264,7 +262,7 @@ static OQS_STATUS sig_test_randomized_signing(const char *method_name) { return OQS_ERROR; } - printf("Testing randomized signing for %s\n", sig->method_name); + printf("Testing %s signing for %s\n", is_randomized ? "randomized" : "deterministic", sig->method_name); public_key = OQS_MEM_malloc(sig->length_public_key); secret_key = OQS_MEM_malloc(sig->length_secret_key); @@ -298,12 +296,24 @@ static OQS_STATUS sig_test_randomized_signing(const char *method_name) { } if (signature1_len != signature2_len) { - printf("Signatures have different lengths, so they are not identical.\n"); - } else if (memcmp(signature1, signature2, signature1_len) == 0) { - fprintf(stderr, "ERROR: Two signatures of the same message are identical.\n"); + fprintf(stderr, "ERROR: Signatures have different lengths.\n"); goto err; + } + + if (is_randomized) { + if (memcmp(signature1, signature2, signature1_len) == 0) { + fprintf(stderr, "ERROR: Two signatures of the same message are identical in randomized mode.\n"); + goto err; + } else { + printf("Two signatures of the same message are not identical, as expected for randomized mode.\n"); + } } else { - printf("Two signatures of the same message are not identical, as expected.\n"); + if (memcmp(signature1, signature2, signature1_len) != 0) { + fprintf(stderr, "ERROR: Two signatures of the same message are NOT identical in deterministic mode.\n"); + goto err; + } else { + printf("Two signatures of the same message are identical, as expected for deterministic mode.\n"); + } } rc = OQS_SIG_verify(sig, message, message_len, signature1, signature1_len, public_key); @@ -337,8 +347,6 @@ static OQS_STATUS sig_test_randomized_signing(const char *method_name) { return ret; } -#endif - #ifdef OQS_ENABLE_TEST_CONSTANT_TIME static void TEST_SIG_randombytes(uint8_t *random_array, size_t bytes_to_read) { // We can't make direct calls to the system randombytes on some platforms, @@ -444,16 +452,17 @@ int main(int argc, char **argv) { } #endif -#if defined(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) if (strncmp(alg_name, "ML-DSA", 6) == 0) { - rc = sig_test_randomized_signing(alg_name); +#if defined(OQS_ENABLE_SIG_ML_DSA_RANDOMIZED_SIGNING) + rc = sig_test_repeat_signing(alg_name, true); +#else + rc = sig_test_repeat_signing(alg_name, false); +#endif if (rc != OQS_SUCCESS) { OQS_destroy(); return EXIT_FAILURE; } } -#endif - #if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) #define MAX_LEN_SIG_NAME_ 64 // don't run algorithms with large stack usage in threads From 7193b5f981ee6a0326f0910d858a62f559057c3f Mon Sep 17 00:00:00 2001 From: M-AlNoaimi Date: Mon, 3 Nov 2025 18:00:32 +0000 Subject: [PATCH 4/4] Improve error message for signature length mismatch Signed-off-by: M-AlNoaimi --- tests/test_sig.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_sig.c b/tests/test_sig.c index c548733b9d..13fee43110 100755 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -296,7 +296,8 @@ static OQS_STATUS sig_test_repeat_signing(const char *method_name, bool is_rando } if (signature1_len != signature2_len) { - fprintf(stderr, "ERROR: Signatures have different lengths.\n"); + fprintf(stderr, "ERROR: %s signing for %s produced signatures of different lengths (%zu vs %zu). This should not happen.\n", + is_randomized ? "Randomized" : "Deterministic", sig->method_name, signature1_len, signature2_len); goto err; }