Skip to content

Add external mu (message representative) support for ML-DSA#14979

Draft
reaperhulk wants to merge 5 commits into
mainfrom
claude/compassionate-einstein-0qHpD
Draft

Add external mu (message representative) support for ML-DSA#14979
reaperhulk wants to merge 5 commits into
mainfrom
claude/compassionate-einstein-0qHpD

Conversation

@reaperhulk
Copy link
Copy Markdown
Member

This PR adds support for the "external mu" variant of ML-DSA signing and verification as defined in FIPS 204. This allows applications to precompute the message representative (mu) and sign/verify it directly, which is useful for protocols that need to work with the intermediate mu value.

Key Changes

  • New constant: MLDSA_MU_BYTES (64 bytes) defines the length of an ML-DSA external mu value
  • New signing method: sign_mu(mu) on all three private key classes (MlDsa44, MlDsa65, MlDsa87) to sign a precomputed 64-byte mu
  • New verification method: verify_mu(signature, mu) on all three public key classes to verify a signature over a precomputed mu
  • OpenSSL 3.5.0+ support: Added CRYPTOGRAPHY_OPENSSL_350_OR_GREATER to the conditional compilation flags for the new functions
  • Backend implementations:
    • BoringSSL: Uses low-level ML-DSA API with private key reconstruction from seed
    • AWS-LC: Uses EVP_PKEY_sign/verify with "ExternalMu" format
    • OpenSSL 3.5.0+: Uses EVP with the "mu" signature parameter
  • Input validation: Both methods validate that mu is exactly 64 bytes, raising ValueError if not
  • Comprehensive tests: Added unit tests and Wycheproof test vector validation for the external mu interface

Implementation Details

The external mu interface is implemented with conditional compilation to support three different backends:

  • For BoringSSL, the implementation reconstructs the private key from its 32-byte seed and calls the low-level MLDSA*_sign_message_representative functions
  • For AWS-LC, the EVP interface natively supports external mu through the "ExternalMu" format
  • For OpenSSL 3.5.0+, a helper function set_mu() configures the signature context with the "mu" parameter before signing/verifying

The external mu signatures are fully compatible with ordinary ML-DSA signatures—a signature produced via sign_mu() can be verified with the standard verify() method if the message and context are provided, and vice versa.

https://claude.ai/code/session_01MmjphxZ6ookRpjUhQouKjf

claude added 4 commits June 7, 2026 05:03
Adds sign_mu()/verify_mu() to the ML-DSA private/public key classes for
signing and verifying a precomputed 64-byte external mu, as defined in
FIPS 204. mu already incorporates the public key and any context string,
so these methods take no context.

Per-backend mechanism:
- OpenSSL 3.5+: set the integer "mu" signature parameter
  (OSSL_SIGNATURE_PARAM_MU) and pass mu through EVP_DigestSign.
- AWS-LC: EVP_PKEY_sign/EVP_PKEY_verify, which use the "ExternalMu"
  format for ML-DSA keys.
- BoringSSL: the EVP layer has no external-mu support, so use the
  low-level MLDSA*_{sign,verify}_message_representative functions.
Derive mu from the existing deterministic pure KAT vectors (whose
signatures come from an independent reference implementation) and check
that verify_mu accepts each reference signature for its derived mu and
rejects it for a tampered mu, across ML-DSA-44/65/87.

https://claude.ai/code/session_01MmjphxZ6ookRpjUhQouKjf
The Wycheproof ML-DSA sign vectors carry a precomputed mu ('External Mu')
for every case with a valid signature, including the mu-only 'Internal'
cases NIST provides without an accompanying message or context. The
signing tests skip those, so add external-mu tests that exercise
verify_mu against the vector mu/signature pairs, confirm a perturbed mu
fails, and (when msg/ctx are present) check the derived mu matches the
vector.

https://claude.ai/code/session_01MmjphxZ6ookRpjUhQouKjf
Point openssl and openssl-sys at the rust-openssl master branch via
[patch.crates-io] so the BoringSSL build picks up the merged mldsa.h
addition to the bindgen wrapper (rust-openssl/rust-openssl#2650), which the
external-mu BoringSSL path needs for the MLDSA* low-level functions and
the CBS public-key parser. Temporary until a release including it ships.

https://claude.ai/code/session_01MmjphxZ6ookRpjUhQouKjf
@reaperhulk reaperhulk marked this pull request as draft June 7, 2026 17:35
Replace the allocating OSSL_PARAM_BLD in set_mu with a fixed two-element
OSSL_PARAM array constructed via OSSL_PARAM_construct_uint. The provider
reads the 'mu' flag back with OSSL_PARAM_get_int, which accepts an
unsigned integer param. This removes the builder's allocation-failure
branch, which was unreachable in practice and left uncovered.

https://claude.ai/code/session_01MmjphxZ6ookRpjUhQouKjf
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