Conversation
jakemas
force-pushed
the
mldsa-cast-rejection-paths
branch
from
September 17, 2026 22:01
8bacd53 to
412520f
Compare
FIPS 140-3 IG 10.3.A Resolution 15 Note23 recommends that the ML-DSA signature generation CAST exercise every applicable rejection-sampling loop path in FIPS 204 Algorithm 7, and points at Table 1 of Section 6.1.2 of draft-celi-acvp-ml-dsa for suitable values. The existing ML-DSA-44 sigGen CAST used a self-chosen message, which rejects only twice: once on z and once on r0. The ct0 check -- reachable only at ML-DSA-44 -- and the bound on the number of 1s in h were never taken. Instrumenting the four rejection branches in mldsa-native and signing the CAST's own inputs gives z=1 r0=1 ct0=0 h=0. Switch the keyGen/sigGen vectors to ML-DSA-44 row 1 of that Table 1, which rejects exactly once at each of the four checks, so coverage no longer depends on a self-chosen message happening to hit the rare branches. The vector reuses the CAST's existing all-zero rnd, so this costs four loop iterations instead of two and adds no new keygen, sign, or verify operation. The draft publishes SHA2-256 digests of pk||sk and of the signature rather than the values themselves. The expanded constants here were generated from the published seed and cross-checked against both digests. kMLDSAVerifySignature is regenerated for the new key over the unchanged kMLDSAVerifyPlaintext, keeping the sigVer CAST an independent known-answer test. Update the three ML-DSA CAST inputs in break-kat.go to match, so test-break-kat.sh can still locate and corrupt them. Add all 27 published vectors -- Table 1 for the rejection paths and Table 2 for at least 32 rejections -- to the existing ACVP sigGen KAT files for all three parameter sets, so they run under the existing ACVPSigGen test. Those records carry siggen_key_hash and siggen_sig_hash attributes holding the published digests, which ACVPSigGen now checks when present; that keeps the expanded keys and signatures pinned to the values NIST published. aws-lc reorders the ct0 norm check ahead of MakeHint relative to the Algorithm 7 pseudocode, which the draft warns can prevent an implementation from using these vectors; all 27 still reproduce.
jakemas
force-pushed
the
mldsa-cast-rejection-paths
branch
from
September 17, 2026 22:20
412520f to
5ecac50
Compare
Contributor
|
🔒 Security Review — View Report Please review before merging. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3545 +/- ##
==========================================
+ Coverage 78.22% 78.23% +0.01%
==========================================
Files 700 700
Lines 126026 126035 +9
Branches 17428 17427 -1
==========================================
+ Hits 98581 98606 +25
+ Misses 26573 26559 -14
+ Partials 872 870 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context and motivation
FIPS 140-3 IG 10.3.A Note23 recommends the ML-DSA signature generation CAST cover every reachable rejection path in FIPS 204 Algorithm 7:
z,r0, the number of 1s inh, andct0(ML-DSA-44 only). It points at Table 1 in Section 6.1.2 of draft-celi-acvp-ml-dsa for values.Our CAST signs a self-chosen message. With counters on the four rejection branches, it only ever takes two:
So
ct0and the OMEGA bound onhare untested, andz/r0are hit by luck rather than by construction.Description of changes
Use ML-DSA-44 row 1 of that Table 1 for the keyGen/sigGen CAST vectors. It rejects once at each of the four checks:
The row's
rndis all zeros, which the CAST already used, so this is a constant swap: two extra loop iterations, no extra keygen, sign or verify. The keyGen seed changes rather than a second key being derived for signing, becauseml_dsa_44_keypair_internal_no_self_test()runs the PCT and a second keygen would roughly triple the CAST.The draft publishes SHA2-256 of
pk || skand of the signature instead of the values themselves. The expanded constants were generated from the published seed and checked against both digests, which are recorded next to the seed.Two places keep their own copies of these constants and are updated to match.
break-kat.gohardcodes the CAST inputs it corrupts, and without the updatebreak-katcannot find them and exits 3.fips_callback_test.ccasserts the exact CAST failure message, so it needs the new expected public key and signature; the ML-DSA-sign "Calculated" value also changes, since break-kat zeroes the message and the signing key is now different.generated-src/crypto_test_data.cc.tar.bz2is regenerated withutil/generate_build_files.py, as the KAT files below are embedded intocrypto_test_data.cc.Finally, add the 27 published vectors (Table 1 and Table 2) to the existing
MLDSA_{44,65,87}_ACVP_sigGen.txtfiles so they run underACVPSigGen. Those records carrysiggen_key_hashandsiggen_sig_hash, whichACVPSigGennow checks when present, pinning the ~540KB of expanded hex to NIST's digests.Testing
All 27 vectors reproduce, including Table 2's exact rejection counts. Worth noting, since we check the
ct0norm beforeMakeHintrather than after, and the draft warns that such deviations can make these vectors unusable.ACVPSigGennow runs 22/25/25 records for ML-DSA-44/65/87. Confirmed the added records really execute: corrupting onesiggen_sig_hashand rebuilding fails withSHA2-256(sig) mismatch.crypto_test: 3038 passed, 2 skipped (environment).crypto_testwith-DFIPS=1 -DBUILD_SHARED_LIBS=1: 4093 passed, 3 skipped. Exercises the CAST aborting on mismatch, viaSelfTests.KATandFIPSCallback.PowerOnSelfTests.Static FIPS builds are Linux-only, so
tests/ci/run_fips_callback_tests.shwas run on an AL2023 x86_64 host: both build phases, all 39 break-KAT cases and all 5 PWCT cases pass, includingML-DSA-keyGen,ML-DSA-sign,ML-DSA-verifyandMLDSA_PWCT. 45FIPSCallbackassertions pass, 0 fail.Review considerations
No API or ABI change. Touches the FIPS boundary, but only the CAST's constants, not the operations it performs.
Two things left out:
ml_dsa_sign_internalis parameter-set-independent, that is probably worth writing down; otherwise 65/87 need CASTs. The new KATs cover those paths in the test suite, but not as CASTs.modulewrapper.ccregisters"deterministic": [false]. The draft's rejection-path assurance needssignatureInterface = internalanddeterministic = true, so CAVP will not send these cases as registered.processMlDsaSigGenalready passesrndthrough, but the server may omitrndfor deterministic signing and the wrapper would need to supply zeros.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.