Skip to content

ML-DSA on x86-64: the norm check and MakeHint with AVX2 (sign −8% instructions) - #519

Merged
alex merged 6 commits into
claude/fervent-einstein-ukl7t7-ybits2from
claude/fervent-einstein-ukl7t7-ynorm
Oct 2, 2026
Merged

alex merged 6 commits into
claude/fervent-einstein-ukl7t7-ybits2from
claude/fervent-einstein-ukl7t7-ynorm

Conversation

@alex

@alex alex commented Oct 1, 2026

Copy link
Copy Markdown
Member

Stacked on #513 (HighBits and LowBits with AVX2); only the last commit is new.

This PR is an implementation only. It changes nothing in Spec/ or TCB/.

What

vg_mldsa_norm_lt_avx2 and vg_mldsa_make_hint_avx2 work on eight coefficients at a time. In each 128-bit lane they run the VEX.256 form of SSE2 code over the lane's four doublewords, the same way high_bits/low_bits do in #513.

  • norm_lt
    • The public bound is clamped to q first. This branches only on the public bound and doesn't change the result, because every reduced coefficient is below q.
    • After the clamp, a - b and (q - b) - a both fit in 32 bits, and one of them is negative exactly when a coefficient is out of bounds.
    • The ORs of the two differences are ANDed into an accumulator. Its sign bits are then spread across each doubleword (vpsrad 31), and the result is (vpmovmskb + 1) >> 32.
    • There is no branch on data.
  • make_hint

Both are added to the MlDsaArith backend as variants of vg_mldsa_norm_lt and vg_mldsa_make_hint. With AVX2, signing calls both and verification calls norm_lt. check_variants passes.

The sign and verify call lemmas for these two callees (normCall_ok/_tr, hintCall_ok/_tr) now take any callee name, as bitsAt_ok already did.

Proofs

  • Round/YNorm.lean
    • the per-doubleword differences and their sign bits (nlL_msb)
    • the accumulator invariant over the loop
    • vpmovmskb of all-set sign bits giving the result
  • Round/YHint.lean
    • the hint of a doubleword is makeHint (mhL_toNat)
    • the nibble count (nib_bools, decided over the 256 cases, and bsum_sp)
    • the loop invariant (stores and running count)
    • the running count equals hintOnes (csum_onesFrom)

Constant time for both uses taint_decide.

Measurements

Instructions measured with callgrind, AVX2 configuration, compared with #513:

before after
ML-DSA-44, 20 deterministic signatures 58.2M 53.5M −8.0%
ML-DSA-65, 20 deterministic signatures 82.2M 75.5M −8.2%
ML-DSA-87, 20 deterministic signatures 113.8M 106.1M −6.8%
ML-DSA-44, 20 verifications 23.5M 23.0M −2.0%
ML-DSA-65, 20 verifications 35.7M 35.4M −0.8%
ML-DSA-87, 20 verifications 72.0M 70.2M −2.4%

With VG_CPU_FEATURES=none the counts are unchanged.

Checks

Run locally, all passing:

  • lake build
  • Emit.lean
  • check_lean_imports, check_lean_speed, check_vectors, check_arch_gates, check_variants, check_mcdt
  • cargo fmt / clippy
  • cargo test with Wycheproof, both with default features and with VG_CPU_FEATURES=none --features cpu-features-env

The README algorithm table was regenerated with ci/algorithms_table.py.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr


Generated by Claude Code

vg_mldsa_norm_lt_avx2 and vg_mldsa_make_hint_avx2 compute on eight
coefficients at a time, in the VEX.256 form of SSE2 code on the four
doublewords of each 128-bit lane, as HighBits and LowBits do.

norm_lt clamps the public bound to q (which changes no result), so that
a - b and (q - b) - a fit in 32 bits and one of them is negative exactly
when a coefficient is out of bounds; it ANDs their ORs into an
accumulator, spreads its sign bits and returns (vpmovmskb + 1) >> 32.
There is no branch on data.

make_hint computes r₁ of r and of (r + z) mod q with hbX, the hint as
(r₁ ^ r₁' + 63) >> 6, and counts the eight hints of an iteration from
the byte mask of the hints shifted to bit 7 (vpmovmskb), summing its
nibbles with three shifts and additions.

Both join the polynomial arithmetic's backend, as variants of
vg_mldsa_norm_lt and vg_mldsa_make_hint, so signing (both) and
verification (norm_lt) with AVX2 call them. The proofs: YNorm (the
differences, the mask and the result) and YHint (the hint in a
doubleword, the count, the loop, and the count is hintOnes).

Instructions (callgrind, AVX2), 20 deterministic signatures:
ML-DSA-44 58.2M → 53.5M (−8.0%), ML-DSA-65 82.2M → 75.5M (−8.2%),
ML-DSA-87 113.8M → 106.1M (−6.8%); 20 verifications: 23.5M → 23.0M
(−2.0%), 35.7M → 35.4M (−0.8%), 72.0M → 70.2M (−2.4%). The baseline is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
claude added 3 commits October 1, 2026 22:53
…ent-einstein-ukl7t7-ynorm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
…ent-einstein-ukl7t7-ynorm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
…ent-einstein-ukl7t7-ynorm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
claude added 2 commits October 2, 2026 02:06
…ent-einstein-ukl7t7-ynorm

The Backend has highBits, lowBits, normLt, makeHint and rej4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
@alex
alex merged commit 88c4d9a into claude/fervent-einstein-ukl7t7-ybits2 Oct 2, 2026
42 checks passed
@alex
alex deleted the claude/fervent-einstein-ukl7t7-ynorm branch October 2, 2026 02:58
alex pushed a commit that referenced this pull request Oct 2, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
alex pushed a commit that referenced this pull request Oct 2, 2026
Main has #513, #519 and #523, which this branch already has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ddof3szoTi7HB8iCsM2MCr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants