Skip to content

Hybrid post-quantum crypto

Every Ratify signature is a pair: one Ed25519, one ML-DSA-65. Both must verify. This page explains why, what each algorithm is doing, and what threats this defends against.

HybridSignature
───────────────
┌────────┼────────┐
▼ ▼
┌─────────────┐ ┌──────────────────┐
│ Ed25519 │ │ ML-DSA-65 │
│ signature │ │ signature │
│ │ │ │
│ RFC 8032 │ │ NIST FIPS 204 │
│ Elliptic │ │ Lattice-based │
│ curve │ │ Module-LWE │
│ │ │ │
│ 64 bytes │ │ ~3309 bytes │
│ fast │ │ ~10x slower │
│ │ │ ~50x larger │
└─────────────┘ └──────────────────┘
│ │
└─────── BOTH ────┘
must verify
sign_bytes = canonical_json(object, omit: ["signature"])
signature.ed25519 = Ed25519.Sign(sign_bytes, private_key.ed25519)
signature.ml_dsa_65 = ML-DSA-65.Sign(sign_bytes, private_key.ml_dsa_65)

Both signatures are over the same canonical bytes. The two component keys are unrelated — they share no math, no curve, no seed. The hybrid keypair is the cartesian product of two independent keypairs.

Valid(σ, msg, pub) := Ed25519.Verify(msg, σ.ed25519, pub.ed25519)
∧ ML-DSA-65.Verify(msg, σ.ml_dsa_65, pub.ml_dsa_65)

Logical AND. Failure in either means the whole signature is rejected. This is the structural property — the hybrid defends against algorithm failure in either component because failure in one cannot mask the other.

Today Post-quantum future
───── ───────────────────
Forge a sig with quantum computer
no key access? ✗ Practically ✗ ML-DSA-65 has no
infeasible known quantum attack;
shorter Ed25519 is
broken but doesn't
help — the ML-DSA-65
sig still fails
Recover priv key quantum computer
from sig alone? ✗ Practically ✗ Same — ML-DSA-65 is
infeasible conjectured Q-secure
Forge old archived quantum computer
sigs (HND attack)? — N/A ✗ ML-DSA-65 holds.
Ed25519 alone would
be retroactively
forgeable; hybrid
mode prevents this.
Algorithm flaw Both algorithms used ↓
discovered in in production with Even if a flaw appears in
one of them? decade+ of crypt- ML-DSA-65 (newer, less
analysis (Ed25519) battle-tested), Ed25519
and FIPS standard- still verifies.
ization (ML-DSA-65,
FIPS 204 final
August 2024).

The structural argument: as long as at least one of (Ed25519, ML-DSA-65) is secure, the hybrid signature is secure. You need both to be broken to forge a bundle. That’s the defense in depth.

The specific attack the hybrid mode is designed to prevent:

  1. An adversary records every Ratify proof bundle that crosses the public internet today, in 2026. They store these archives at low cost.
  2. Years later, a cryptographically-relevant quantum computer (CRQC) becomes available to the adversary.
  3. The CRQC can attack Ed25519 (via Shor’s algorithm on the elliptic curve discrete log problem). The adversary can now compute the Ed25519 private key from any 2026 public key they recorded.
  4. With a forged Ed25519 private key, they can produce Ed25519 signatures over arbitrary 2026-dated payloads. In a non-hybrid system, this means they could retroactively claim any agent was authorized to do anything in 2026.

The hybrid defense: even with the Ed25519 private key, the adversary cannot produce a valid hybrid signature because they don’t have the ML-DSA-65 private key. ML-DSA-65 is lattice-based (Module-LWE) and has no known quantum attack. The forged Ed25519 component verifies in isolation, but the verifier requires both — so the forgery is rejected.

ChoiceWhy
Ed25519 (not RSA, not ECDSA)Faster, smaller signatures, no nonce-reuse pitfall, widely deployed, mature libraries everywhere, RFC 8032 standardized
ML-DSA-65 (not Falcon, not Dilithium)ML-DSA-65 is the FIPS 204 standardized form of Dilithium-3, finalized Aug 2024. Wider tooling support than Falcon. Conservative security level (≈ AES-192).
Hybrid pair (not pure PQC)PQC algorithms are new. ML-DSA-65 has years of cryptanalysis but less than Ed25519. Hybrid mode means a flaw in ML-DSA-65 doesn’t sink us.

This combination is what CNSA 2.0 (US NSA’s commercial cryptography guidance) and BSI (German federal cyber agency) both recommend for the post-quantum transition period.

Hybrid signatures are larger:

Ed25519 only: 64 bytes per signature
ML-DSA-65 only: 3309 bytes per signature
Hybrid: 3373 bytes per signature (~50× larger than Ed25519 alone)

For a ProofBundle with one delegation, the wire size is roughly:

Cert metadata + scopes + IDs: ~400 bytes (varies by scope count)
Issuer public key (hybrid): ~2000 bytes
Subject public key (hybrid): ~2000 bytes
Delegation signature (hybrid): ~3400 bytes
Challenge + challenge_sig: ~3500 bytes
Total wire size: ~11–12 KB per bundle

For a sub-delegated chain at depth 3: ~25–30 KB. The bundles are bigger than OAuth bearer tokens (sub-1KB) but fit easily inside any HTTP request and are negligible compared to a video frame, an LLM completion, or a meeting handshake.

Operation Ed25519 ML-DSA-65 Hybrid
───────────────────── ─────── ───────── ───────
Keygen ~50 μs ~120 μs ~170 μs
Sign ~50 μs ~400 μs ~450 μs
Verify ~150 μs ~250 μs ~400 μs

(Measured on a modern x86 CPU. Embedded targets are slower; ML-DSA-65 in particular benefits a lot from vectorization.)

Verification is the hot path in real workloads. Roughly 400 μs per bundle, which means a single CPU core can process ~2500 bundles per second. For Verify’s edge verifier (Cloud Run / edge worker) this is well below saturation.

The exact algorithms, key encodings, and signature encodings are normative: SPEC.md §5. Every SDK uses an audited implementation of each primitive:

  • Go: crypto/ed25519 (standard library) + pqcrypto/mldsa (Cloudflare CIRCL)
  • TypeScript: @noble/ed25519 + @noble/post-quantum
  • Python: cryptography (PyCA) + pqcrypto
  • Rust: ed25519-dalek + pqcrypto-mldsa