NIST FIPS 204 (ML-DSA) standard compliant, C++20, fully constexpr, header-only library implementation.
FIPS 204 compliance is assured by testing this implementation against NIST ACVP Known Answer Tests and tons of property based tests. We also fuzz the library and its internal components with LLVM libFuzzer to get an extra level of assurance. Fuzzing is still work in progress.
Note
constexpr? Yes, you can compile-time execute keygen, sign and verify.
Caution
This ML-DSA implementation is conformant with ML-DSA standard https://doi.org/10.6028/NIST.FIPS.204 and I also try to make it timing leakage free, but be informed that this implementation is not yet audited. If you consider using it in production, please be careful !
Add ml-dsa to your CMake project via FetchContent:
include(FetchContent)
FetchContent_Declare(
ml-dsa
GIT_REPOSITORY https://github.com/itzmeanjan/ml-dsa.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(ml-dsa)
target_link_libraries(my_app PRIVATE ml-dsa)Generate a keypair, sign a message, and verify the signature:
#include "ml_dsa/ml_dsa_65.hpp"
#include "randomshake/randomshake.hpp"
// Generate a keypair
std::array<uint8_t, ml_dsa_65::KeygenSeedByteLen> seed{};
std::array<uint8_t, ml_dsa_65::PubKeyByteLen> pubkey{};
std::array<uint8_t, ml_dsa_65::SecKeyByteLen> seckey{};
randomshake::randomshake_t csprng;
csprng.generate(seed);
ml_dsa_65::keygen(seed, pubkey, seckey);
// Sign a message (hedged mode)
std::array<uint8_t, ml_dsa_65::SigningSeedByteLen> rnd{};
std::array<uint8_t, ml_dsa_65::SigByteLen> sig{};
csprng.generate(rnd);
std::vector<uint8_t> msg = { /* your message bytes */ };
const bool signed_ok = ml_dsa_65::sign(rnd, seckey, msg, {}, sig);
// Verify
const bool valid = ml_dsa_65::verify(pubkey, msg, {}, sig);See examples/ for a complete standalone CMake project.
ML-DSA has been standardized by NIST as post-quantum secure digital signature algorithm (DSA), which can be used for verifying the authenticity of digital messages, giving the recipient confidence that the message indeed came from the known sender.
ML-DSA's security is based on the hardness of finding short vectors in module (i.e., structured) lattices. It is built on the "Fiat-Shamir with Aborts" paradigm, where the signing procedure might abort and restart multiple times based on the message, context string and randomness.
| Algorithm | Input | Output | What does it do ? |
|---|---|---|---|
| KeyGen | 32 -bytes seed | Public and Secret Key | 32 -bytes seed is used for deterministically deriving a ML-DSA keypair. |
| Sign | 32 -bytes seed, secret key, message, context | Signature | Signs a message with an optional context string (max 255 -bytes). Hedged (randomized) signing is default and recommended; for deterministic signing, fill seed with zero bytes. |
| Verify | Public key, message, context, signature | Boolean | Verifies a signature against a message and optional context. |
| HashSign | 32 -bytes seed, secret key, message, context | Signature | Pre-hash mode signing (FIPS 204 §5.4). Pre-hashes the message with a SHA3 hash function before signing. Same hedged/deterministic modes as Sign. |
| HashVerify | Public key, message, context, signature | Boolean | Pre-hash mode verification (FIPS 204 §5.5). Verifies a signature against the pre-hashed representation of a message. |
| Sign_internal | 32 -bytes seed, secret key, M' | Signature | FIPS 204 Algorithm 7. Signs using the encoded message M' directly (bypassing context encoding). |
| Verify_internal | Public key, M', signature | Boolean | FIPS 204 Algorithm 8. Verifies using the encoded message M' directly. |
| Sign_core | 32 -bytes seed, secret key, 64 -bytes mu | Signature | Core signing operation. Accepts the 64 -bytes message representative mu directly. Caller is responsible for computing mu. |
| Verify_core | Public key, 64 -bytes mu, signature | Boolean | Core verification operation. Accepts the 64 -bytes message representative mu directly. |
Tip
API tiers: Most users should use the standard API -- keygen, sign, verify, hash_sign, hash_verify. The advanced API (sign_internal, verify_internal, sign_core, verify_core) is for implementors who has direct access to the encoded message M' or the 64-byte message representative mu.
Note
Return values: sign() and hash_sign() return false only if the context string exceeds 255 bytes. The internal rejection-sampling loop runs until a valid signature is produced -- it does not abort to the caller. In normal usage with valid inputs, signing always succeeds. verify() and hash_verify() return false if the context exceeds 255 bytes, or if the signature is invalid (malformed hint bits, norm check failure, or commitment mismatch).
Note
Context strings (ctx): The context parameter is a domain-separation mechanism (FIPS 204 §5.2) that allows the same key to be safely used across different application protocols without cross-protocol attacks. The context used at signing time must match at verification time. Pass an empty span ({}) if you don't need domain separation. Maximum length: 255 bytes.
Here I'm maintaining ml-dsa - a C++20 header-only fully constexpr library, implementing ML-DSA, supporting ML-DSA-{44, 65, 87} parameter sets, as defined in table 1 of ML-DSA standard. It's easy to use, see usage.
| ML-DSA-65 Algorithm | Time taken on "12th Gen Intel(R) Core(TM) i7-1260P" (x86_64) |
Time taken on "AWS EC2 Instance c8g.large, featuring ARM Neoverse-V2" (aarch64) |
|---|---|---|
| keygen | 82.1us | 126.2us |
| sign | 587us | 879us |
| verify | 86.3us | 134.4us |
Note
All numbers in the table above represent the median time required to execute a specific algorithm, except for signing. In the case of signing, the number represents the minimum time required to sign a 32 -bytes message. Find more details in benchmarking.
Note
Find ML-DSA standard @ https://doi.org/10.6028/NIST.FIPS.204 - this is the document that I followed when implementing ML-DSA. I suggest you go through the specification to get an in-depth understanding of the scheme.
This implementation is built with a "Security-First" approach, incorporating programming language features and multiple tools for enforcement:
- No Raw Pointers: Completely avoids dealing with raw pointers. Everything is wrapped in statically-sized
std::span, which provides much better type safety and compile-time error reporting as the exact byte lengths for all buffers (seeds, keys, signatures) are known for each parameter set. - Strict Build Guards: Compiled with
-Werrorand comprehensive warning flags (-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wformat=2 -Wcast-qual -Wold-style-cast -Wundef). Any warnings triggered during compilation are treated as fatal errors. - Memory Safety: Verified using AddressSanitizer (ASan) in both release and debug build configuration.
- Undefined Behavior: Hardened with UndefinedBehaviorSanitizer (UBSan), configured to treat any undefined behavior as a fatal, non-recoverable error.
- Continuous Fuzzing: Includes a suite of 13 specialized fuzzer binaries (9 signature variants + 4 internal component units).
- Static Analysis: Integrated with
clang-tidyusing an extensive check suite (bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,hicpp-*,misc-*,modernize-*,performance-*,portability-*,readability-*) with all warnings treated as errors. - CI-Verified: Automatically tested on every push across a matrix of operating systems (Linux, macOS) and compilers (
clang++,g++).
- A C++ compiler such as
clang++/g++, with support for compiling C++20 programs. - CMake 3.28 or later.
- For testing,
google-testis required. It can be installed globally or fetched automatically by setting-DML_DSA_FETCH_DEPS=ON. - For benchmarking,
google-benchmarkis required. It can be installed globally or fetched automatically by setting-DML_DSA_FETCH_DEPS=ON. - For static analysis, you'll need
clang-tidy. - For code formatting, you'll need
clang-format.
The following libraries are automatically fetched by CMake at configure time -- no manual installation needed:
sha3: SHA3 suite of hash functions, from FIPS 202, used internally by ML-DSA.randomshake: A CSPRNG built on (Turbo)SHAKE256 XOF, seeded from OS entropy. Used in tests, benchmarks and examples for generating keygen seeds and signing randomness. You may use any cryptographically secure pseudo-random source to fill the seed/rnd arrays; the library does not mandate a specific RNG.
Note
If you are on a machine running GNU/Linux kernel and you want to obtain CPU cycle count for ML-DSA routines, you should consider building google-benchmark library with libPFM support, following https://gist.github.com/itzmeanjan/05dc3e946f635d00c5e0b21aae6203a7, a step-by-step guide. Find more about libPFM @ https://perfmon2.sourceforge.net. When libpfm is installed, CMake will automatically detect and link it.
For testing functional correctness of this implementation and conformance with ML-DSA standard, you have to run following command(s).
Note
All Known Answer Test (KAT) files live inside kats directory. KAT files from official reference implementation, are generated by following (reproducible) steps, described in https://gist.github.com/itzmeanjan/d14afc3866b82119221682f0f3c9822d. ACVP KATs can be synced by building sync_acvp_kats target.
| Option | Description | Default |
|---|---|---|
ML_DSA_BUILD_TESTS |
Build test suite | OFF |
ML_DSA_BUILD_BENCHMARKS |
Build benchmarks | OFF |
ML_DSA_BUILD_EXAMPLES |
Build examples | OFF |
ML_DSA_BUILD_FUZZERS |
Build fuzzers (requires Clang) | OFF |
ML_DSA_FETCH_DEPS |
Fetch missing dependencies (Google Test, Google Benchmark) | OFF |
ML_DSA_ASAN |
Enable AddressSanitizer | OFF |
ML_DSA_UBSAN |
Enable UndefinedBehaviorSanitizer | OFF |
ML_DSA_NATIVE_OPT |
Enable -march=native (not safe for cross-compilation) |
OFF |
ML_DSA_ENABLE_LTO |
Enable Interprocedural Optimization (LTO) | ON |
Tip
If you are building for the same machine that will run the code (i.e., cross-compilation is not the goal), you should enable -DML_DSA_NATIVE_OPT=ON to allow the compiler to unroll loops and auto-vectorize, using processor-specific optimizations (like AVX2, NEON, etc.) for maximum performance.
To build and run the tests, use the following CMake commands:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_TESTS=ON -DML_DSA_FETCH_DEPS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure -jTo run tests with sanitizers, reconfigure the build with the desired sanitizer option:
# With AddressSanitizer, in Release mode
cmake -B build -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_TESTS=ON -DML_DSA_FETCH_DEPS=ON -DML_DSA_ASAN=ON
cmake --build build -j && ctest --test-dir build --output-on-failure -j
# With UndefinedBehaviorSanitizer, in Release mode
cmake -B build -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_TESTS=ON -DML_DSA_FETCH_DEPS=ON -DML_DSA_UBSAN=ON
cmake --build build -j && ctest --test-dir build --output-on-failure -jTo run the benchmarks (using Google Benchmark):
cmake -B build -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_BENCHMARKS=ON -DML_DSA_FETCH_DEPS=ON -DML_DSA_NATIVE_OPT=ON
cmake --build build -jCaution
When benchmarking, ensure that you've disabled CPU frequency scaling, by following guide @ https://github.com/google/benchmark/blob/main/docs/reducing_variance.md.
Warning
Relying only on average timing measurement for understanding performance characteristics of ML-DSA sign algorithm may not be a good idea, given that it's a post-quantum digital signature scheme of "Fiat-Shamir with Aborts" paradigm - simply put, during signing procedure it may need to abort and restart again, multiple times, based on what message is being signed or what random seed is being used for default hedged signing. So it's a better idea to also compute other statistics such as minimum, maximum and median when timing execution of sign procedure. In following benchmark results, you'll see such statistics demonstrating broader performance characteristics of ML-DSA sign procedure for various parameter sets. Also to easily compare performance benchmarking result of sign function across different configurations, we use a fixed seed to initialize RandomShake CSPRNG. This gives us deterministic result.
# In case it linked with libPFM, you can get CPU cycle count
./build/ml_dsa_benchmarks --benchmark_time_unit=us --benchmark_min_warmup_time=.5 --benchmark_enable_random_interleaving=true --benchmark_repetitions=10 --benchmark_min_time=0.1s --benchmark_display_aggregates_only=true --benchmark_report_aggregates_only=true --benchmark_counters_tabular=true --benchmark_perf_counters=CYCLES
# Otherwise, you can get time taken in micro-seconds
./build/ml_dsa_benchmarks --benchmark_time_unit=us --benchmark_min_warmup_time=.5 --benchmark_enable_random_interleaving=true --benchmark_repetitions=10 --benchmark_min_time=0.1s --benchmark_display_aggregates_only=true --benchmark_report_aggregates_only=true --benchmark_counters_tabular=trueThis project includes 13 specialized fuzzer binaries built with LLVM libFuzzer. Each fuzzer has its own isolated corpus directory and tuned input sizes for maximum coverage.
The easiest way to fuzz is with the full-lifecycle script. It configures, builds, generates per-fuzzer seed corpus with correctly-sized seed files, runs all 13 fuzzers in parallel, and produces a summary report:
# Default: 1 hour per fuzzer, using all CPU cores
./scripts/fuzz_all.sh
# Quick smoke test (2 minutes)
FUZZ_TIME=120 ./scripts/fuzz_all.sh
# Customize parallelism
FUZZ_JOBS=4 FUZZ_FORK=2 ./scripts/fuzz_all.sh| Variable | Description | Default |
|---|---|---|
FUZZ_TIME |
Seconds to run each fuzzer | 3600 (1 hour) |
FUZZ_JOBS |
Max concurrent fuzzer processes | 4 |
FUZZ_FORK |
Fork workers per fuzzer | (nproc / FUZZ_JOBS) - 1 |
CXX |
C++ compiler (must be Clang) | clang++ |
BUILD_DIR |
Build output directory | build |
CORPUS_DIR |
Persistent corpus directory | fuzz_corpus |
After completion, the script prints a report showing corpus size, total executions, and status per fuzzer. Log files are saved to fuzz_report/.
To run a specific fuzzer manually (requires clang++):
cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_FUZZERS=ON
cmake --build build -j
# Create corpus with correctly-sized seed
mkdir -p fuzz_corpus/ml_dsa_65_sign
head -c 4129 /dev/urandom > fuzz_corpus/ml_dsa_65_sign/seed
./build/ml_dsa_65_sign_fuzzer fuzz_corpus/ml_dsa_65_sign \
-max_total_time=3600 \
-max_len=4129 \
-fork=$(nproc) \
-print_final_stats=1 \
-print_corpus_stats=1Important
Each fuzzer requires a specific minimum input size. Using incorrect sizes wastes mutation cycles. Use -max_len matching the size from the table below.
Each fuzzer has a minimum input size determined by its input requirements. Use these as -max_len values:
| Fuzzer | -max_len |
Composition |
|---|---|---|
ml_dsa_44_keygen |
32 | seed(32) |
ml_dsa_65_keygen |
32 | seed(32) |
ml_dsa_87_keygen |
32 | seed(32) |
ml_dsa_44_sign |
2657 | seckey(2560) + rnd(32) + msg(32) + ctx(1) + seed(32) |
ml_dsa_65_sign |
4129 | seckey(4032) + rnd(32) + msg(32) + ctx(1) + seed(32) |
ml_dsa_87_sign |
4993 | seckey(4896) + rnd(32) + msg(32) + ctx(1) + seed(32) |
ml_dsa_44_verify |
3797 | pubkey(1312) + sig(2420) + msg(32) + ctx(1) + seed(32) |
ml_dsa_65_verify |
5326 | pubkey(1952) + sig(3309) + msg(32) + ctx(1) + seed(32) |
ml_dsa_87_verify |
7348 | pubkey(2592) + sig(4627) + msg(32) + ctx(1) + seed(32) + extra(64) |
field_arithmetic |
8 | 2 × uint32 |
poly_ntt |
1024 | 256 × uint32 |
poly_sampling |
65 | 1B selector + 64B sampling input |
poly_bit_packing |
641 | 1B selector + 640B packing input |
You can easily integrate ml-dsa into your project using CMake.
# Install system-wide (default prefix: /usr/local)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
sudo cmake --install build
# Install to custom directory (e.g., ./dist)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./dist
cmake --build build
cmake --install buildOr using FetchContent in your CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(
ml-dsa
GIT_REPOSITORY https://github.com/itzmeanjan/ml-dsa.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(ml-dsa)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE ml-dsa)# Configure
cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_TESTS=ON -DML_DSA_BUILD_EXAMPLES=ON -DML_DSA_FETCH_DEPS=ON
# Static analysis (requires clang-tidy)
cmake --build build --target tidy
# Format source code (requires clang-format)
cmake --build build --target format
# Sync ACVP Known Answer Test vectors
cmake --build build --target sync_acvp_katsml-dsa is written as a header-only C++20 fully constexpr library, mainly targeting 64 -bit mobile/ desktop/ server grade platforms and it's easy to get started with. All you need to do is following.
- Clone
ml-dsarepository.
git clone https://github.com/itzmeanjan/ml-dsa.git && cd ml-dsa
cmake -B build -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_TESTS=ON -DML_DSA_FETCH_DEPS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure -j- Write your program; include proper header files ( based on which variant of ML-DSA you want to use, see include directory ), which includes declarations ( and definitions ) of all required ML-DSA routines and constants ( such as byte length of public/ private key, signature etc. ).
// main.cpp
#include "ml_dsa/ml_dsa_44.hpp"
#include "randomshake/randomshake.hpp"
#include <cassert>
#include <iostream>
int main() {
std::array<uint8_t, ml_dsa_44::KeygenSeedByteLen> seed{};
std::array<uint8_t, ml_dsa_44::PubKeyByteLen> pubkey{};
std::array<uint8_t, ml_dsa_44::SecKeyByteLen> seckey{};
randomshake::randomshake_t csprng;
csprng.generate(seed);
ml_dsa_44::keygen(seed, pubkey, seckey);
std::array<uint8_t, ml_dsa_44::SigningSeedByteLen> rnd{};
std::array<uint8_t, ml_dsa_44::SigByteLen> sig{};
csprng.generate(rnd);
std::vector<uint8_t> msg(32, 0);
csprng.generate(msg);
const bool has_signed = ml_dsa_44::sign(rnd, seckey, msg, {}, sig);
const bool is_valid = ml_dsa_44::verify(pubkey, msg, {}, sig);
assert(has_signed && is_valid);
std::cout << "Verified: " << std::boolalpha << is_valid << "\n";
return 0;
}- If your project uses CMake, the recommended approach is to use
find_packageorFetchContent(see Integration section above).
Note
Randomness: The examples use randomshake::randomshake_t, a CSPRNG seeded from OS entropy, to generate keygen seeds and signing randomness. You may use any cryptographically secure pseudo-random source to fill the seed and rnd arrays -- the library does not mandate a specific RNG. For deterministic signing, fill rnd with zero bytes. Though that is not the recommended way of using ML-DSA signing.
Tip
Add -march=native to optimize for your specific CPU. Omit it if building for distribution or cross-compilation.
For signing pre-hashed messages (FIPS 204 §5.4-5.5), use hash_sign and hash_verify with a hash_algorithm template parameter:
#include "ml_dsa/ml_dsa_65.hpp"
using ml_dsa_65::hash_algorithm;
// Sign a pre-hashed message using SHA3-256
const bool ok = ml_dsa_65::hash_sign<hash_algorithm::SHA3_256>(rnd, seckey, msg, ctx, sig);
// Verify
const bool valid = ml_dsa_65::hash_verify<hash_algorithm::SHA3_256>(pubkey, msg, ctx, sig);Supported hash_algorithm values: SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE_128, SHAKE_256. We do not support using SHA2 hash functions for pre-hashing.
| Variant | NIST Security Level | Public Key | Secret Key | Signature | Namespace | Header |
|---|---|---|---|---|---|---|
| ML-DSA-44 | 2 (comparable to AES-128) | 1,312 B | 2,560 B | 2,420 B | ml_dsa_44:: |
include/ml_dsa/ml_dsa_44.hpp |
| ML-DSA-65 | 3 (comparable to AES-192) | 1,952 B | 4,032 B | 3,309 B | ml_dsa_65:: |
include/ml_dsa/ml_dsa_65.hpp |
| ML-DSA-87 | 5 (comparable to AES-256) | 2,592 B | 4,896 B | 4,627 B | ml_dsa_87:: |
include/ml_dsa/ml_dsa_87.hpp |
ML-DSA-65 is recommended for general use. Choose ML-DSA-44 when signature/key size is critical, or ML-DSA-87 when you need the highest security margin.
Note
ML-DSA parameter sets are taken from table 1 of ML-DSA standard @ https://doi.org/10.6028/NIST.FIPS.204.
All the functions, in this ML-DSA header-only library, are implemented as constexpr functions. Hence you should be able to evaluate ML-DSA key generation, signing or verification at compile-time itself, given that all inputs are known at compile-time. I present you with the following demonstration program, which generates a ML-DSA-44 keypair, signs a message, and verifies the signature - all at program compile-time. Notice, the static assertion.
/**
* Filename: compile-time-ml-dsa-44.cpp
*
* Compile and run this program with
* Build and run using the CMake example project in examples/ directory, or
* use `cmake --install` to install ml-dsa system-wide and compile with:
* $ g++ -std=c++20 -Wall -Wextra -Wpedantic -fconstexpr-ops-limit=500000000 compile-time-ml-dsa-44.cpp && ./a.out
* $ clang++ -std=c++20 -Wall -Wextra -Wpedantic -fconstexpr-steps=500000000 compile-time-ml-dsa-44.cpp && ./a.out
*/
#include "ml_dsa/ml_dsa_44.hpp"
#include <string_view>
// Compile-time hex character to nibble conversion.
constexpr uint8_t
hex_digit(char c)
{
if (c >= '0' && c <= '9') return static_cast<uint8_t>(c - '0');
if (c >= 'a' && c <= 'f') return static_cast<uint8_t>(c - 'a' + 10);
if (c >= 'A' && c <= 'F') return static_cast<uint8_t>(c - 'A' + 10);
return 0;
}
// Compile-time hex string to byte array conversion.
template<size_t L>
constexpr std::array<uint8_t, L>
from_hex(std::string_view str)
{
std::array<uint8_t, L> res{};
for (size_t i = 0; i < L; i++) {
res[i] = static_cast<uint8_t>((hex_digit(str[2 * i]) << 4) | hex_digit(str[(2 * i) + 1]));
}
return res;
}
// Compile-time evaluation of ML-DSA-44 keygen, signing and verification.
constexpr bool
eval_ml_dsa_44()
{
constexpr auto seed = from_hex<32>("7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d");
constexpr auto msg = from_hex<33>("d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8");
constexpr auto ctx = from_hex<33>("8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8fab");
constexpr auto rnd = from_hex<32>("6255563ba961772146ca0867678d56787cad77ab4fc8fcfe9e02df839c99424d");
std::array<uint8_t, ml_dsa_44::PubKeyByteLen> pubkey{};
std::array<uint8_t, ml_dsa_44::SecKeyByteLen> seckey{};
std::array<uint8_t, ml_dsa_44::SigByteLen> sig{};
ml_dsa_44::keygen(seed, pubkey, seckey);
const auto signed_ok = ml_dsa_44::sign(rnd, seckey, msg, ctx, sig);
const auto verified = ml_dsa_44::verify(pubkey, msg, ctx, sig);
return signed_ok && verified;
}
int
main()
{
// Entire ML-DSA-44 keygen + sign + verify round-trip, evaluated at compile-time.
static_assert(eval_ml_dsa_44(), "ML-DSA-44 keygen/sign/verify must be correct at compile-time");
return 0;
}See examples/README.md for a standalone CMake project and detailed documentation on how to integrate and use ml-dsa in your own project. It's the quickest way to get started:
# Build the example as a standalone project
cd examples
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/ml_dsa_44
# Or build from the repository root
cmake -B build -DCMAKE_BUILD_TYPE=Release -DML_DSA_BUILD_EXAMPLES=ON
cmake --build build -j
./build/ml_dsa_44ML-DSA-44 @ NIST security level 2
Seed : e4196b0fd4bb4f0721860fe6f83184180276d04f5017bb971d09bbbaac70c223
Pubkey : e3f37358b35bb4b7807acf7bea046db0a30fc74c6926100e5230d2e7c562e4eb762370fffac83fc2a72efb24c10a1119c4edd224cc29d7e042b53d6b1b2c08ef71c0e4b3b36df428976ffabd3e077643437666367f3e64b36d65c10315e57de2f12bc21b7ff144bfcd50eb1bf7967b58d859e3cc2f278d3ff4ea94b69ca1eaa804b097cfbd3d8a36860f335f17fce76cce005d608f8f1baac1f27e4b39adb201944c1f0c31026787f393581248585caed04c85977d4cd94ef1ae00dfff33eaf6750d455157146c91b7d64bcbaacb76ae28999e84cd05f15f796dae6c67d682d86bd0b7e09a185f8bccb480a32693abe9af5744913084f459b1887e018c8c267b02864c36e41fd7845f6fa3bb73201aa10ce2cb9b743e6c5c2e90c9c2dc59de89ccece4f66078fc74eb40fe2f86d65faed5abe4ec862fb7f04ba0775f4dd9ecfa18dcab6ad867e067bcd2c101869f81e2967da02d8b832769a558f6d04e0048b02e20dc126f90dd1323649a6b4c25ad0d54e0c99e35945febe89a80ebeb55416c66243d5900557a4c0b981fb297997e8ae7cefb1efdb6f5dee4a931995c7d702ef2849434c3c193a12d8da31e30d8c774f4c0c5a0b1cbc80a9cd282c7cc746e4e0aebfbbf56d25cb4ebade59fc23e1a0dba83608753167adb80cce41d9f14e6c2e2dd9cdd54887058e68fb838903f17571650b2555eb45192c7a952f8cb20de67655c2bfa8181a8dc72bb962192904cd29cc3275f630b1e5826f0117a83262cf275f4a3f484e1d6336d63ffa0e5dc03b3aa508e6e1f688b9fe72dc46c1453a901e4233c237497f97d5cdd0484ccd1cc983b465e69c2e0e40d0183f386b5862ca14487b3423292455748deb5bdcef13c18f0510613468825603d71712393fff83cbe7414c92670379f9eccee1c95f433c73c09cd3da1bf3c67ec31d0f5a67f18d01347cd3684a27f196fe67d49b4a95b5caa2fbdd8c12050771276d5637537b9f7407a7f9b0f9326ca292be99392d0c91657e74fc63f96f968724be3959ae459e8f1bcbf10583655eb0bf2dbbe4eab80701853ee112a5ce42efbe2587ca323585bb023d862e4b5f8f8c7a916bad7a36710d1cd06c0948f83e7abbe7cef2a1de82d9e55e003dd31433214f5ae5937253176a1a95ccb4e12489c290839013e33df62ce5d08e36338ae4e6b707f570ab1829ed3c944a643f4e8d485e93379ed1def902d25c0adcb517e10e11a2f1bfee1754162b82a116e2bd52ec8869ce1c5037bc0a2e3cf6be290189a8c6c73835890a302ed40c4d2206dd06a5db31c87349852e94aa78506c16224f0d0ba8e9cfd3579e12c01664c51799108653b58c9e4814ef4257e446a42632e939577cc6a5cf9e4313053343bde753be7911dd8ce70a66c5cd529d08e021e1cbeb67ead34abd0b250f2901b0710288e4310796444eb98d9aa79012690b5b674791b3ebeb162d5321b63b99bb1e4b504e58cf47452e9ea180d695a94a75817b338daaa032ae51bf5c6a2cb69ea51d075c6d4fa112120b9ee44e369e5bb39edd29f37ce30e800eeaf05d6abfa8225441cfd2224bac608fcab3441a40601714a7418a717ad130f850243ffdde1836650a3c24cd9d74efd0e2f37b7e3c30d0d161f980d5a11c1f46948d59e68f35b0449ad07fd0c395a8af7f7ec5eae43566eb52b3180561475df1272a32295b2b8ef614e47d45ffeaa01e51f4e28d1e58f31ea0e3342a3606af5af5e4f40a346846d3f77237eea3dae47660d74bb9c59590363dd5da7bdc8449686639077c3d5cc5df8f8de33e99551422f3dc33e4fcff8ccc3850142c8f9890ce945e441e50acaf3cf101e77e88620507a9f2f
Seckey : e3f37358b35bb4b7807acf7bea046db0a30fc74c6926100e5230d2e7c562e4eb9c4dfc6bb1127ef2ad37bd1500ebb95cf1ac6b88ae4bcca8ef90b41f57d2847cd5395f248599d8b561914174f228e62c2224c6d99543c8a10a6a064e3e3d2e01d51eba5df15ec75ad1bfff885931074d86aca65dc98297e9e4797f3a0109b8f40b488d133489c0c8411c18521490854118681c3606d2246e49180c9194885226659cc0696088701b32501cb224c128060a926cd382641c12021cb36421b6844242285112021ba0251ca6042114010443448938659418295c3831c42806004768a224511034095a304d22200821a54d02366420b58c54c82543245021369018042a09b50d0ab88801018ca2828d12866519006c52c4499c2266e02601c21821e242691a198a50864dda2800a484259b428419134609302253406c42400053a8485b18655c00705c8064c4a24c913460188160ccb64c8b063292b685c4000ea3b871039610d30401d2c88008116ad38871c1186120b43188842c001451a3022891c6044b800809c130242285199990cc822422a0490b902c91a6094100254b027261804d8b226cda846814004a1a890522448550c62ca0228194048d0495504c121008946ce486256402400c114e1c48029830928a2850a0b6651903285a926052445084184a5c041258441020309219467100a685a286290386054c325213b2005ab260db1690d1b89158164919b58d4408892032880201641918728c026500214611812d03198c52400c08b30903898d4c008c14c030208940d3384e483824e3487084a62122445290c68cdb008e01070a0cc62c84168c19018019000210c248d0268c02940cc424521a092118b0252349815aa660c8066820c7001341129044266112324bb46892385109444d19110913350e0a458e8b32040ab88803a204844224d846910b012000490509498688c6810a012a93201212317224b650e0b029004006c4a62503914ce38008021471440820011741d4344923c12590922d838071208431c39471003092c3b68182408d02c90590b06889a6311b462de1208e043092c2488d614849ccc65120b241ca446a230185e1b891c2322219970d6182641a278a11164c49a66903347080b81083c0811b1850944482dc400694c4718ab801e20671c0902d1a322e5c34804ab0510421100a98611339924214329a30881c8101e336091ab604c4164908477209c741c1068d61382dcc1431a1b269d10628261021cb24df3553583f9f1dbfc8080f6f2047fc35d72b76e012d46fb54a5c4b0939bf2066d606397f6b433b2d1fef9100c23b0643dfe12083a0e9bf8f65e48ace8e0f4e4816a4a84becd9765c1faa25a3b4f825e7982d4fd4dc7bcc441a2a4712dbe73b1d4b8574924bb0c8797f9f62a902f3f5a723dace4fd7fad53b7ec70c9b89cb86402a8690b700693716cca8ebf721b4b6d7180dcd39393021d1670ef8db147f1bed828d645c816ff78531ae3a5c3635072b09380f596be3a2d35ad74aec50c470707c21b5d099e44dcce037d1a02dc2bbd939eb6d09ee9dd3adefe347e241681e5a2bc664a390077dccb7d10a14533837f1b95550dabd511ddd62b8ac2e893667cc0086a712b95bb7dffa694a73f579d1b60ed85905b80e2437fc53551de89fc0277ef4596a3fcabec38c3c7d740541873b0170ad0805f10daf845492d05bab771dd7800dde683c9dc154c2a8a499e48216f8af8a9b168ea5d2fd5efe3db4e1f89267dfa7acddd6ac7b665625e8dd6d265b6003dc02fd49cf4e30338f79efee84a0f69a060a4857226417fb188abffc223fa7f63bdb879dcd6d8871b3ffd153cf5a810403c85e0ef9893bbf4ad97bde7e07b336ffcd754efd013dcb9144be9707c8f327d677af87760fb1053c5b6610430174503348a4cc990d7946d7b6ed6bc9a699fc6b9a66fc5564dfd34731bba78eeb29aa9eaf11607b15dca330213b2f0a14bb2fab689a0ce558a2e12f496d35a723c7a5f898fa9e11f088af45054d0360ed1a95c6f9a1b4dea380da8b4193419a8ad71681794dfa16c074297c65c112f2578828c071b5bbfa7e9268926572a1db75ef72c078ad550a15b9ca61fbf2d1c883b83b3866b6f361f5d09414259bb2192db759c7beae61aefd13bd3ba49ef16151456051b67819494ee1929f1ce0f1fcbe7485fc2b95f340e128e00eb833d333553a2df8f7462125abe6d2c43de9caa99a3c6c4cc8155909babc3676bfb814939d23caaadd2eaf7b6514fd923a6c2ab414591b37820c75c16b5a0c8d5fdfa02aeac272a8ffb64389720a68bb8251251eca57df2aee87477e3c2dc1050efd5e76a04088ddcff1314c636b37d4e8a7e08393eeefffb50e19ccd811de84fb2245f54c714b7d93201d33ee967b39d71173cde7535e4455b19abaa8c5af90c919d9eaf31e5a93712a64f1c0b16e628498beb9502f1558cfec0ce0b382d3889114c1e9120790a03e2868aecc225b49105a7bd7ccb9b61ce7cc40d026e83617863c13ed5771fb8ab574b468503f4490d132b1c6d1cd7aa2de9e0bb7198bd4ee25551e272817f1917ebc3b2e39152de77dee6ac95331691a74419a2dc4d0cf9872323ab43ec50658cc40deabaffed0e4e2df22b4a1d68d606179f9c8f6c12683f8b8e35f2a670cf96cdc31d27568a5cc7b776a750e9ad77a3c5b8d5d9720954ce50272b1efb4ae071b7c5c652f41762bc56fd66fb36a365d69022c1673cc9d60b0daf2d7921de030b4b2857e94ba2d37cd15dd5a8b2b4eef724f46590eaea7db3de6d27c58218cf2615b8128200be932989cf9af1805c4608e4f573b898cca79aa3012ff5309745f7e55b176f66c2d36fd7b30ff1d1ecf5d22288ea7ba2a1e386900bb3e88b155a26ad50277a26d6fa8aea41d047c640caff54095fb6bc2a9c21986100a97b8788803d72888c49683994fa41828d5dd6385e7efa40af360808a04beea14c3dcd9c0eed865a8842d9081db8b6164d27507c30158afa563eeedc3ab18006a6b7f1508791a3f1bdcd723dc1dfa48f1ad9cbab33cc5f0da1802315fa0907fd803396c010ffb82f1d19739189c9a59608186d286f6c39eb3051dcf7c4e1147c853938c6a3a127c2a1540c2983c3de2d459d0d37680cba174696d3002bc424cef5439ae8dc968d77e4f012d026c81fb919ea7fd8bf97801b28c64fa8391761d8fb7c320472eece33af1b5909b064d08715995164faae81743d0fd6dabc9e0a79695cb8ef13e559b9a8ec7789aa98796cfb9a1a48b0ab5a02007be1a7218af8941fb3db521cde22c6f207dc40aeae6863bd14c2c4bf08a206b77a9018dbd00b94b5276d049221d1a2f565eaa4eb32ffa23823bacae138757f8e77d27b22891cb9bb459e070e2419c4f1252926e27a59c39e4850c7688096ca283f8bcccb6b23a8a64dc9ff2020e2b62bc33714ed9c9a09207f730231177129fb253cd2d63ebd1b209eab62ab1fea0db284049205e07cde7c3c08ca5cdf830bf0618f162fe34fc59f5181bcaa20457bd2401e3b97aea8576032bcd1b6cc641f1508ec5116df4493f6bf85cb1cbd5af36273c714dbe828cb1d68ac47b011a916a54a414d3
Message : 48ae5de0058d4a160a5e01b674386711e0e97dd007fe57d5049e5eacc95dd957
Context : b0f9f06e737d9f95
Signature : a0fb039e48b35e7d33c101cf955174a51bc5afcbad43e4cc8fc2d58c72308e30f3e5883d93b8c21dbe303adb9d9f8e54d2b6f0df6449a2bc7070f414aa2eac94a69cbc1fd40efcfbc93739cf97e487aad68ea26e43f1b0515e7b895705f01b05d1f4896574e44afd066622d246ffb57cf97742b0951041c88935d960420387f5191cc53c7209d813a160c83777f6e419e870f4a14016eaee142bd027fda7d6ae27070f8b81ce4e1f4a077a6e6d1163e97b2969ea845e206a738e58deda76d328bcad0755a219f0641738e6d27f073aeac8d7dcea94e2e161f69f8fd843965b5edcfa1be7fb74c2f9b4ae950a9a580baa18bd6799bb0cd7ad804b20144c26d707751b6a9c5d0f8bb4abebb2f05264bd79595ebdd58ed87fbdd525d6c11dc6a08f1db76bc4a7fcfc065a9959a3b3f50e00635885f54f9ca23acbc6db1db6c98e77bf6fc9a5381011d999af8770960f670096ca145d96e7e9e125702752d55e9002adf2c1fb45be717bc2565b60facf9813d9acc09accd46b06aaa69d198406ac6368bcb4741490a01b7da979def2f0f600a3ec560cce2ed8c0701c6aa11d254aaf4641569555328669296f47d5624fa14029dd713b24368ede308fb3053fcc62cd2b7471e1adefb2279df71693c0eb3ae5b18a86ae238d12519a3c3e4cd361d4f61ba8aab151cb23e275b8bd2009c1f293339db47d460e739d37da882cc640eef49810df97680076e7ffa9271074682f028d4b81e54796d9b32a49b26c6a8e06b372bbf1ac140212808844663244a5f1d8b55d35f5c96b4db17310b77995e560ff27e0b99b0766602757ac6e6a74d234a57ed479a808fa343970a76524eec22e81f6da0723d8770f37002dc0d1ecc3a6400979ab23f2eba7305514bead1efa86fd43dee2825645839801c0898bc560d08a391c37da5e7dced66679dcbe2bd444f46f5f730b98559324dbf4c9f2559fe506e01225c4d47438f57e5fa45a41aa474adce59eafc01cb85cdf6ad0d0236c217654628a02f05f29a32bb3ce929824fd494e2df8fa5c0ef3e33d8522b847408fd677c441d9d06a09ecb3d79c827486502d15ab00768557ad9d932507ea5d228032579ce3ee6a42b964704589adc948687341a24c5a0e604f8ac67495c7e2aa762eac65bfd06f1572dd22f51ab9bc774ff0de3ee1a5fbd591101250c70b629eef35e6b978a0c3431ffbc8a189cb86d2de3c16fe3b7d9f2473c7377b33e639bd089882208c831d37e10868e78223365c0249179d17ef74db2e914b32434af633ebfbbcc3ae73d4626d039e2400d04f6a1e48cfa1ce6c778f22e1caaeb1e48b80258043f9ed74a49b45cb9f7d2ffd62c02223f1fe4ec9ddef6303b5f0eaef97d15dfe01184b6fea7a1999d2d1dc28269370670ead04f64c719c941e681cd14408383c81a98f2fd3379971663801970e9c28e39a3550e1201b356a9ca8d797b51030231bccebfe241926b82b7771e60e70ee0bd5963cd7d2ae974747d4d3cc8d2e004e61d2e2b1cd6c05465710c1f3a5ff2f554b7608cbf3aac9e29e55e4f7d6281c5e2b8e43c5a5e117164f389b028ca6cfa3b407d75564d1d1ac7ec4603f35f6299587e69ca45d355ddd3c66e43f6853d44a6f4edd78cf15b815f784d1d95e8aa44120d78ceb4eb91beb208f866eb9128ae4e13ba8e88a1ec1ab1986c9de288dcf2f150e6bc6df54a117c45b14b57ae1b97210cdd24f83c2df6eb351d9998900753124971541581f166ff74115ea47dfd98aa68d0280df6f2907031c75a52259befa1dd387d162e1919e7f57a7f4b12f0fb85f4476bc9645d598f79930f98e653c0dc4fb2f1a5b249d4e455c55b47c865b054058173abe05c942f93f80eb087ef0ef286171682a1e967f2a8ae849288727f84c30d65851a2a87e0c87df9f059b2835b60d024ce7104c36d8e4447b614dc209876c04ec608374f95c5c59288ded2e35a6db1f6461f116661e2f9a363480e62979a272b4a713133846eaf5febfeb225ec1a457933217b90ff3e258362febcc88630bdbc293ada08c63bd119bc7985cc1681967e310b9cb844e55397d27d2daea76fee0eed190c124a750cdfd6722ad340d7e8af33937aac217b08f477bdc627baca4b371663db9898b6658728ab71fcc13f6d2d0cf336b548812ffeba4b08502250bdfa999a64682c4475ecfc48c73ab1b8bcd899ac34b1a6f3026343d7edcae666cd04760e373fe747585379fe3885a02b69942d28b54ceda933b41776b9665688d5937b1c59ad98abdc0118a8632f62848a52f57a2ed87f153a965904f442a525624f50de9cd53a6b0f0f6c5c41a64074e68c8d4b114c3d7e15bbfb8c6aeaa22b54b476e23415bc6a622d6ca759bee7fcfe016a2933aad416bf1fedc367ff2560e72c25e2a4656987b6a28def5ddf30ef170ff93c29f08a570fc757e36c98cce548861d4837416268d2345a45621c10f6653feb81baf746bc6cf12a4c1b35ac263139a0dcf82710c861c50b72c3234ca0553bd5c5940c8fc7e5fe737c876a2982252a0420925ca72932f8c5b6f7f0bbdda00c14545c163a380cc22db0c26446e07e5f9a55f57e71665b5bf1a30c3723bd1a4708d26065bd01ab543f53efa03b2182f35f4cf4f28a6d312980ee1fdbb565565f92e63d33f3b9a9e82bdb5b93333dfd929aebd7b081120577377fb42e5329248b0256f5c42c231a142311ca95209637c226a348605ded83c0a55f1c7b4af13872c97bab2bef84bbfa249d741849420bf750798701d7bbf912cb45826e711d5e5261143baaa7811393a5874d4482906a09785e11dd6f96b0c5b2b409de08d87e785c1dd226125fecc81e0160d8d705b56d82215ea98b982f737a11fd5376ff99dc57f2a21b53fd5852b03f2b761e1921806e792c8529ce9c66c9f873ab31e4b59eba9e5bff0dc7d336d01b061879d031b3bd03958826028a984ce6c4b8290606356a3db40e1f8a7c1ae33927d634104c168fab585e88e332716c8d37b2c4a6904dc6c75cc1fc305419bc60e2603ebb71b4dea0d4ddfafeb5e486a52da02c163a9f2434ea6ef6756dad105028fea8ca1563a3a58d57a6f7e0b8575213e734c97c8058cadd13dcb956d99c60e5e7d0b8cd9eb1da854def9367c2254d56c37daa29f28b5f790ef9bac20dda46c2e55e4e737708aab12e41624c70e1766de2e6c85334d8db0c6579eea0031da585d7e7228a6c51c870f2caaf607eec1a07c0632d71cd65102a8292cd31df53a93a8881ef11208cc6162a2f86d9084751d70997c8879b99aaa79b33c3a8bd08c5f94c22c2d5d61636798c4ccdce1e5ecf91015232450545b646e7a879aa0a9bbbdcfdaf6fa000e1427385877787ca9b1b5dbe3eef2fa0b1a3042496075797c8e90949699a2a5acb6b9d6dadcdef8fb000000000e22334c
Verified : true