This directory contains tooling for testing go-qrllib's ML-DSA-87 implementation against official NIST ACVP (Automated Cryptographic Validation Protocol) test vectors.
The GitHub Action (.github/workflows/acvp.yml) clones the NIST ACVP-Server
repository at its latest commit and extracts the ML-DSA test vectors at runtime.
Vectors are never vendored — they always come directly from NIST's repository.
- Clone: Sparse checkout of
github.com/usnistgov/ACVP-Server(only the ML-DSA JSON files) - Merge:
merge_vectors.pycombines the ACVPprompt.json(inputs) andexpectedResults.json(expected outputs) into simplified test vector files, filtered to ML-DSA-87 - Test:
acvp_test.goruns the vectors through go-qrllib's internal key generation and signing functions, comparing byte-exact output
| Test | Vectors | Description |
|---|---|---|
TestACVPKeyGen |
25 | Seed -> (pk, sk) matches NIST expected output |
TestACVPSigGen |
15 | sk + message + context -> signature matches NIST expected output |
Only deterministic, external-interface, pure (non-preHash) signature
vectors are tested. Note that go-qrllib's public ML-DSA-87 API is
hedged by default per FIPS 204 §3.4 (see SECURITY.md and
TOB-QRLLIB-6); a public Sign call mixes fresh crypto/rand into the
per-signature RND_BYTES and therefore cannot reproduce a fixed ACVP
vector byte-for-byte. The ACVP test runner here uses the unexported
cryptoSignSignatureWithRnd(..., rnd = zero) entry point — the same
internal function the hedged path calls into, but with an explicit
all-zero rnd so the FIPS 204 §3.5 deterministic-mode signatures the
ACVP vectors were generated against can be reproduced.
# Clone the ACVP-Server repo
git clone --depth 1 https://github.com/usnistgov/ACVP-Server.git /tmp/acvp-server
# Extract and merge ML-DSA-87 vectors
python3 .github/acvp/merge_vectors.py \
--keygen-prompt /tmp/acvp-server/gen-val/json-files/ML-DSA-keyGen-FIPS204/prompt.json \
--keygen-results /tmp/acvp-server/gen-val/json-files/ML-DSA-keyGen-FIPS204/expectedResults.json \
--siggen-prompt /tmp/acvp-server/gen-val/json-files/ML-DSA-sigGen-FIPS204/prompt.json \
--siggen-results /tmp/acvp-server/gen-val/json-files/ML-DSA-sigGen-FIPS204/expectedResults.json \
--parameter-set ML-DSA-87 \
--output-dir /tmp/acvp-vectors
# Run the tests
ACVP_VECTORS_DIR=/tmp/acvp-vectors go test -v -tags acvp -run TestACVP ./crypto/ml_dsa_87/| Algorithm | ACVP Vectors Available? | Compatible? | Reason |
|---|---|---|---|
| ML-DSA-87 | Yes (ML-DSA FIPS 204) | Yes | Direct match |
| SPHINCS+ | No (SLH-DSA FIPS 205 only) | No | go-qrllib implements SPHINCS+ SHAKE-256s-robust (pre-FIPS submission). FIPS 205 (SLH-DSA) dropped the robust variant and only standardized the simple variant. Different thash construction means different outputs. Cross-verified against sphincsplus reference (consistent-basew branch) instead. |
| XMSS | No | N/A | XMSS (RFC 8391) is not an ACVP-validated algorithm. One-directional cross-verification against xmss-reference instead. |
The NIST ACVP-Server stores vectors in two files per algorithm:
prompt.json— Test inputs (seed, message, sk, context)expectedResults.json— Expected outputs (pk, sk, signature)
These are linked by tcId within test groups. merge_vectors.py joins them and
filters to the requested parameter set.