ML-DSA at three levels
All FIPS 204 parameter sets: ML-DSA-44,
ML-DSA-65, ML-DSA-87.
NIST categories 2, 3, 5.
jwt-pq adds ML-DSA (FIPS 204) and hybrid
EdDSA + ML-DSA signatures to the
ruby-jwt
ecosystem. Drop-in with JWT.encode
and JWT.decode, JWK import/export,
RFC 7638 thumbprints, and a streaming-safe remote JWKS loader.
Paste an alg: ML-DSA-* or
alg: EdDSA+ML-DSA-* token plus its
public JWK. Rails hands both to JWT::PQ::JWK.import
and JWT.decode, then renders whatever
the gem returns. No JavaScript crypto. No mock.
{
/* header will appear here after verify */
}
{
/* payload claims will appear here */
}
require "jwt/pq" registers the new
algorithms via ruby-jwt's public SigningAlgorithm.register_algorithm
hook — not a monkey-patch. Load order between
jwt and
jwt/pq does not matter, and
registration is idempotent.
require "jwt/pq"
key = JWT::PQ::Key.generate(:ml_dsa_65)
token = JWT.encode({ sub: "u-1" }, key, "ML-DSA-65")
payload, header = JWT.decode(
token, key, true, algorithms: ["ML-DSA-65"]
)
header["alg"] # => "ML-DSA-65"
All FIPS 204 parameter sets: ML-DSA-44,
ML-DSA-65, ML-DSA-87.
NIST categories 2, 3, 5.
EdDSA+ML-DSA-{44,65,87} concatenates
an Ed25519 signature with an ML-DSA one. Both must verify. Break
one algorithm, the token still holds.
JWK export/import (kty: "AKP") with
RFC 7638 thumbprints as kid.
PEM via SPKI (public) and PKCS#8 (private).
JWT::PQ::JWKSet.fetch(url) with a
TTL cache, ETag revalidation, HTTPS-only, 1 MB body cap, and
redirect rejection. Failures raise
JWKSFetchError.
CI runs the full NIST ACVP sigVer known-answer tests against
Key#verify for all three parameter
sets, every push — plus cross-interop against
dilithium-py.
Per-instance mutex serializes sign/verify/destroy.
Key#destroy! zeroes the secret
buffer in Ruby and in FFI memory; a GC finalizer wipes buffers as
a safety net.
An ML-DSA-65 JWT signature is roughly 4.4 KB
after base64url encoding — vs ~86 B for Ed25519 or ~342 B
for RS256. Cookie tokens, header buffer limits, and log pipelines
all need a look before rollout.
| alg | level | public key | signature |
|---|---|---|---|
ML-DSA-44 |
2 | 1,312 B | 2,420 B |
ML-DSA-65 |
3 | 1,952 B | 3,309 B |
ML-DSA-87 |
5 | 2,592 B | 4,627 B |