Skip to content

mldsa: add ML-DSA-87 verify vectors for omitted NTT reduction#255

Merged
FiloSottile merged 6 commits into
C2SP:mainfrom
programsurf:mldsa-reduction-omission-vectors
Jun 25, 2026
Merged

mldsa: add ML-DSA-87 verify vectors for omitted NTT reduction#255
FiloSottile merged 6 commits into
C2SP:mainfrom
programsurf:mldsa-reduction-omission-vectors

Conversation

@programsurf

Copy link
Copy Markdown
Contributor

Summary

Adds a new MlDsaVerify test vector file, testvectors_v1/mldsa_87_reduction_omission_verify_test.json, that targets verifiers omitting a required modular reduction in the ML-DSA verification NTT path. No schema change — it uses the existing mldsa_verify_schema.json.

What these vectors test

When a verifier omits the reduction in the verification NTT path, the inverse-NTT butterfly overflows a signed 32-bit accumulator and the verdict flips. Each vector's verdict therefore diverges between a conforming verifier and an omitting one:

  • Forgeries (result: invalid, flag MissingReductionForgery) — wrongly accepted by a verifier that omits the reduction.
  • Valid signatures (result: valid, flag MissingReductionFalseReject) — wrongly rejected by a verifier that omits the reduction.

The file has one test group: a single ML-DSA-87 public key, two messages, and for each message a valid/forgery pair (4 tests total). Within a pair, the valid and forged signatures differ by a single byte (a hint flip), isolating the reduction behavior. A conforming verifier accepts all valid cases and rejects all invalid cases; an implementation that omits the reduction gets at least one wrong.

Validation

  • Validates against mldsa_verify_sls/vectorlint (valid: 1, invalid: 0; full suite unaffected).
  • Source element: github.com/programsurf/mldsa-reduction-omission, version 1.

Responsible disclosure

The omission was observed in a real implementation. Responsible disclosure was completed before this submission: the issue was reported to the affected library's maintainers and the fix had been released. In accordance with CONTRIBUTING, these vectors are submitted only after the bug was fixed, and are published to guard against the same omission recurring in current or future verifiers.

Authors

Sunwoo Lee (@programsurf), Hyuk Lim (@lubroai), Seunghyun Yoon (@yoonsh) — Korea Institute of Energy Technology

These MlDsaVerify vectors target a verifier that omits a required
modular reduction in the verification NTT path, where the inverse-NTT
butterfly overflows a 32-bit accumulator. Each vector's verdict diverges
between a conforming verifier and an omitting one: forgeries
(result=invalid) are wrongly accepted, and valid signatures
(result=valid) are wrongly rejected. All vectors share a single
ML-DSA-87 public key.

Responsible disclosure was fully completed prior to submission: the
issue was reported to the affected implementation's maintainers and the
fix has already been released. These vectors are published only to guard
against the same omission recurring in current or future verifiers.

Authors: Sunwoo Lee (@programsurf), Hyuk Lim (@lubroai), Seunghyun Yoon (@yoonsh)
Korea Institute of Energy Technology (KENTECH)
@FiloSottile

Copy link
Copy Markdown
Member

Thank you again for the research and for submitting test vectors!

I tried these and they seem to all fail the c̃ == H(μ, UseHint(h, w')) Fiat-Shamir consistency check, so every verifier rejects them, regardless of the bug.

I was already investigating, so I took the liberty to fix that, using a degenerate t1 = 0 key and then bruteforcing messages to hit the overflow. I submitted a PR to your branch: programsurf#1. I also merged them into the main mldsa_87_verify test file, so implementations that use it will immediately benefit.

I used Claude to help with the investigation and bruteforce, so I might have missed something. In case let me know! If you're curious, I had the agent upload its notes and programs to https://github.com/filippo-claude/wycheproof-mldsa-reduction-omission, but I have not reviewed them.

The new vectors catch the bug in the old WolfSSL with -DWOLFSSL_DILITHIUM_SMALL as expected.

Is it correct that this is the only missing required reduction that can be reached with a KAT, and that it's only possible to generate such a KAT for ML-DSA-87?

@programsurf

Copy link
Copy Markdown
Contributor Author

Thanks — and thanks especially for the replacement vectors and for opening the PR.

We tracked down why ours passed on our side but failed for you, and it turned out to be a message-binding mismatch on our end, not the reduction issue. We had generated and checked the vectors through wolfSSL's no-context verification path, whose message binding differs from FIPS-204 "pure" ML-DSA — the convention the pq-crystals reference and Wycheproof use for an empty context. Once we accounted for that, the discrepancy fully explained itself, and it is orthogonal to the missing-reduction behavior. We confirmed this against the unmodified pq-crystals reference.

On your two questions:

  1. Yes — to our knowledge this is the only required reduction whose omission flips a verify verdict reachable from a KAT.

  2. So far we have a reachable KAT at ML-DSA-87. The -44/-65 cases are still in preparation on our side — we'll follow up once those are complete.

Thanks again for the careful review.

@programsurf

Copy link
Copy Markdown
Contributor Author

Pushed two small commits to get CI green: the fold reused tcId 1–2 (colliding with the existing test group), so I renumbered them to 240–241 and fixed the JSON formatting. All four checks pass now — ready to merge whenever you are.

Co-authored-by: Hyuk Lim <90449417+lubroai@users.noreply.github.com>
Co-authored-by: Seunghyun Yoon <7575205+yoonsh@users.noreply.github.com>

@cpu cpu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FiloSottile Did you want to do another pass?

I think we should squash merge it (vs rebase merge) when ready.

@programsurf

programsurf commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @cpu! When you squash-merge, could you keep all three of us credited as squash commit trailers?

Co-authored-by: Sunwoo Lee <46433251+programsurf@users.noreply.github.com>
Co-authored-by: Hyuk Lim <90449417+lubroai@users.noreply.github.com>
Co-authored-by: Seunghyun Yoon <7575205+yoonsh@users.noreply.github.com>

@cpu

cpu commented Jun 25, 2026

Copy link
Copy Markdown
Member

When you squash-merge, could you keep all three of us credited as squash commit trailers?

Yup! Absolutely. I vote we keep the initial message + the trailers and just fold the intermediate commits together so it lands as one discrete commit.

@FiloSottile

Copy link
Copy Markdown
Member

Pushed two small commits to get CI green: the fold reused tcId 1–2 (colliding with the existing test group), so I renumbered them to 240–241 and fixed the JSON formatting. All four checks pass now — ready to merge whenever you are.

Thanks for catching this!

@FiloSottile FiloSottile merged commit 02872f4 into C2SP:main Jun 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants