Why hybrid
ML-DSA is young. It is a NIST standard, it has been thoroughly analysed, and it is the right long-term bet — but it has not yet absorbed the decades of cryptanalysis that RSA and elliptic curves have. Ed25519, meanwhile, is rock-solid against classical adversaries but falls to a cryptographically-relevant quantum computer.
A hybrid signature requires both halves to verify. It is only forgeable by an attacker who can break both algorithms — the smaller of two unlikely events. Treat it as transitional: run it while the industry gains confidence in ML-DSA alone. Dropping to pure ML-DSA later is a single-field change in the JWT header.
Installation
# Gemfile
gem "jwt-pq"
gem "jwt-eddsa" # required for hybrid mode
require "jwt/pq" # registers EdDSA+ML-DSA-{44,65,87}
Usage
hybrid_key = JWT::PQ::HybridKey.generate(:ml_dsa_65)
token = JWT.encode({ sub: "u-1" }, hybrid_key, "EdDSA+ML-DSA-65")
payload, header = JWT.decode(
token, hybrid_key, true, algorithms: ["EdDSA+ML-DSA-65"]
)
header["alg"] # => "EdDSA+ML-DSA-65"
header["pq_alg"] # => "ML-DSA-65"
Wire format
A hybrid JWT's signature section is the concatenation of the Ed25519
signature (exactly 64 bytes) followed by the ML-DSA signature
for the chosen parameter set, stored in the standard JWT signature
field. Both signatures cover the same signing input —
base64url(header) + "." + base64url(payload) — and both
must verify.
| alg | Ed25519 | ML-DSA | Combined |
|---|---|---|---|
EdDSA+ML-DSA-44 | 64 B | 2,420 B | 2,484 B |
EdDSA+ML-DSA-65 | 64 B | 3,309 B | 3,373 B |
EdDSA+ML-DSA-87 | 64 B | 4,627 B | 4,691 B |
Cost
Token size is dominated by the ML-DSA portion — Ed25519 adds a flat 64 bytes. CPU cost is dominated by ML-DSA too; Ed25519 verification is a fraction of an ML-DSA verify. Expect hybrid EdDSA+ML-DSA-65 to run at roughly 4,700 sign/s and 3,900 verify/s on commodity hardware (vs 5,970 / 9,300 for pure ML-DSA-65).
Header convention
The alg header uses a ClassicAlg+PQAlg
convention. jwt-pq also writes a pq_alg header naming
the ML-DSA parameter set directly, to simplify key selection at the
verifier.
draft-ietf-cose-dilithium
is still evolving. The alg string for hybrid mode may
change as the draft finalizes. See
SPEC.md
for the tracked-specs table and the compatibility policy for draft
transitions.