Skip to content

ML-DSA signing counter wraps around as UINT16 #55

@programsurf

Description

@programsurf

Hi, the rejection sampling counter k in the ML-DSA signing loop is declared as UINT16 (mldsa.c:461). For ML-DSA-87 (nCols=7), after 9362 iterations k wraps from 65534 back to 5:

UINT16 k = 0;                          // line 461
while( TRUE ) {
    SymCryptMlDsaExpandMask(..., k, ...);
    k += (UINT16) pParams->nCols;       // line 475: 65534 + 7 = 5 (wrapped)

After wrap, ExpandMask at line 1082 produces identical SHAKE inputs (SYMCRYPT_STORE_LSBFIRST16(seedSuffix, counter + i)), which means nonce reuse in the mask polynomial generation.

I want to note that the NIST reference implementation has the same issuedilithium/ref/sign.c:97 uses uint16_t nonce with the same arithmetic. So SymCrypt is faithfully following the reference. FIPS 204 describes kappa as an "integer" without specifying width.

The probability of actually hitting 9362 iterations with a valid key is astronomically low (~2^{-23400} for ML-DSA-87), so this is theoretical. But widening to UINT32 would eliminate the possibility entirely:

- UINT16 k = 0;
+ UINT32 k = 0;

This might be worth raising with NIST as well, since the root cause is in the reference code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfix-pending-publicationIssue has been fixed in Azure DevOps, pending publication to GitHub.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions