Hybrid Cryptography Briefing: Classical + Post-Quantum Two-Layer Encryption
Last updated: 2026-10-17
Author: Roberto Bourgonjen
Copyright: © 2026 Roberto Bourgonjen. All rights reserved.
Applies to: all asymmetric cryptography in the Catalog suite. Catalog.ID (including its chat and mail keys), the Catalog Provenance Registry (CPR) and Encrypted Filestorage implement this profile; the Licensing Agent and CARD specifications adopt it by reference.
Related documents: Catalog.ID Whitepaper §3.3, §3.4, §4 and §14; CPR Whitepaper §2; Encrypted Filestorage Whitepaper §12; Catalog.ID Recovery Kit Addendum §11
1. Motivation
Catalog's post-quantum primitives are ML-KEM-1024 for key encapsulation and ML-DSA-65 for digital signatures (both NIST FIPS standards since August 2024). These algorithms are mathematically sound according to current analysis, but they have not been subjected to the same decades of adversarial scrutiny that classical algorithms have endured. ECDSA (secp256k1) and X25519/Ed25519 have been battle-tested in systems where the incentive to break them is measured in billions of dollars: Bitcoin alone represents a multi-hundred-billion-dollar bounty on the security of secp256k1. No comparable financial incentive has stress-tested ML-KEM or ML-DSA.
The concern is not speculative. History provides precedent:
- Dual_EC_DRBG (NIST SP 800-90A): a NIST-standardized random number generator that is widely believed, on the basis of leaked documents and subsequent analysis, to have carried a deliberately planted backdoor. NIST withdrew it in 2014.
- SIKE/SIDH: a post-quantum KEM candidate that survived multiple rounds of NIST evaluation before being catastrophically broken in 2022 by a classical attack.
- Rainbow: a post-quantum signature finalist broken during the NIST standardization process.
A two-layer hybrid provides insurance against both threat models:
- If large-scale quantum computers materialize, the post-quantum layer protects.
- If a post-quantum algorithm contains an undiscovered weakness or backdoor, the classical layer protects.
- An attacker must break both layers simultaneously to compromise the system.
2. Architecture Overview
2.1 Principle
Every asymmetric cryptographic operation in the system is performed twice, once with a classical algorithm and once with a post-quantum algorithm, and the results are combined such that both must be broken to compromise confidentiality or forge a signature.
2.2 Hybrid KEM (Key Encapsulation)
Key encapsulation is a hybrid KEM combining X25519 (classical ECDH) and ML-KEM-1024 (post-quantum).
Background: How a KEM Works
A KEM (Key Encapsulation Mechanism) is not traditional public-key encryption. It is a two-step protocol:
- Encapsulate(public_key) produces a ciphertext and a shared secret. Only the holder of the corresponding private key can recover the shared secret from the ciphertext.
- Decapsulate(private_key, ciphertext) recovers the same shared secret.
The shared secret is then used as a symmetric key, or as key material for a key derivation function, to encrypt the actual payload. In Catalog.ID the payload is the claim's random symmetric key: the KEM-derived key encrypts it with AES-256-GCM, and that symmetric key in turn encrypts the claim.
X25519 ECDH follows the same conceptual pattern with slightly different mechanics: the sender generates an ephemeral key pair, computes a shared secret via Diffie-Hellman with the recipient's static public key, and sends the ephemeral public key alongside the ciphertext. The recipient uses their static private key and the ephemeral public key to derive the same shared secret.
Hybrid KEM Construction
The hybrid KEM produces a single combined shared secret that depends on both the classical and the post-quantum component. An attacker who breaks only one component learns nothing about the combined secret.
Recipient's key material (one key bag entry per key ID, for example attr_encrypt):
| Component | Public key | Private key | Size (bytes) |
|---|---|---|---|
| Classical | X25519 static public key | X25519 static private key | 32 / 32 |
| Post-quantum | ML-KEM-1024 public key | ML-KEM-1024 private key | 1568 / 3168 |
| Combined | classical ‖ post-quantum | classical ‖ post-quantum | 1600 / 3200 |
Both key pairs are independently generated during identity creation. The two private halves are stored concatenated as one key bag entry under the algorithm identifier X25519-MLKem1024, encrypted under the supersecret-derived AES-256-GCM key like every other entry.
Encapsulation (performed by the sender, for example when creating a claim or sharing a claim with a recipient):
Input: recipient_pk = X25519_pk (32 bytes) || ML-KEM-1024_pk (1568 bytes)
payload = the bytes to wrap
(for a claim: its random 32-byte symmetric key)
info = protocol family label (see §7.2)
aad = additional authenticated data of the protocol
(may be empty)
Step 1: Classical ECDH
Generate ephemeral X25519 keypair: (ek, epk)
S_classical = X25519(ek, X25519_pk) -> 32-byte shared secret
Discard ek (ephemeral private key)
Step 2: Post-quantum KEM
(ct_pq, S_pq) = ML-KEM-1024.Encaps(ML-KEM-1024_pk)
-> 1568-byte ciphertext,
32-byte shared secret
Step 3: Combine via KDF
combined_secret = HKDF-SHA-512(
ikm = S_classical || S_pq, -> 64 bytes of input key material
salt = fresh_random_32_bytes,
info = info
) -> 32-byte combined secret
Step 4: Encrypt payload
aes_ct = AES-256-GCM(key = combined_secret,
nonce = fresh_random_12_bytes,
aad, payload)
Output: version (1) || epk (32) || ct_pq (1568) || salt (32) || nonce (12)
|| aes_ct (payload + 16-byte tag)
The header before the AES ciphertext is 1645 bytes, so a wrapped 32-byte claim key is 1693 bytes on the wire. The classical layer and the version byte account for 65 of those bytes (the ephemeral public key, the salt and the version).
Decapsulation (performed by the recipient, for example when reading own claims or receiving a shared claim):
Input: recipient_sk = X25519_sk (32 bytes) || ML-KEM-1024_sk (3168 bytes),
from the key bag
wire blob from encapsulation, info, aad
Step 0: Check the version byte; refuse an unknown version.
Step 1: Classical ECDH
S_classical = X25519(X25519_sk, epk) -> 32-byte shared secret
Step 2: Post-quantum KEM
S_pq = ML-KEM-1024.Decaps(ML-KEM-1024_sk, ct_pq)
-> 32-byte shared secret
Step 3: Combine via KDF
combined_secret = HKDF-SHA-512(ikm = S_classical || S_pq, salt, info)
Step 4: Decrypt payload
payload = AES-256-GCM-Decrypt(key = combined_secret,
nonce, aad, aes_ct)
info and aad must match the encapsulation byte for byte. A wrap made for one protocol family cannot be opened by a handler scoped to another.
Why This Is Secure
- HKDF is a randomness extractor. Even if one shared secret (S_classical or S_pq) is fully compromised and reduced to a known constant, the other still provides full entropy into HKDF. The combined secret remains as strong as the stronger input.
- Independent mathematical problems. Breaking X25519 requires solving the elliptic-curve discrete logarithm problem on Curve25519. Breaking ML-KEM-1024 requires solving the Module Learning With Errors (MLWE) problem on structured lattices. These are unrelated mathematical problems: a breakthrough in one provides no leverage against the other.
- No shared key material. The classical and post-quantum key pairs are generated independently with separate randomness. Compromise of one private key reveals nothing about the other.
Claim Lifecycle with the Hybrid KEM
Claim creation (Catalog.ID Whitepaper §4.1):
- The browser generates a random 256-bit symmetric key.
- It encrypts the claim with AES-256-GCM using this symmetric key.
- It destroys the plaintext.
- Hybrid encapsulation with the member's own
attr_encrypthybrid public key wraps the symmetric key into the wire blob above. - It uploads the encrypted claim and the wrapped symmetric key to the server.
Claim sharing (Catalog.ID Whitepaper §4.2):
- Download the own wrapped symmetric key for the claim.
- Hybrid decapsulation with the own
attr_encryptprivate key recovers the claim's symmetric key. - Hybrid encapsulation with the recipient's
attr_encrypthybrid public key produces a new wire blob. - Upload the re-wrapped key for the recipient.
Each sharing operation produces a fresh encapsulation with new ephemeral randomness; the recipient's copy is cryptographically independent from the owner's copy.
Key rotation after compromise (Catalog.ID Whitepaper §3.3):
When a key is compromised and a new hybrid key pair is generated:
- The server wraps every existing wrapped symmetric key in an additional envelope using the new hybrid public key, performing hybrid encapsulation server-side. The server does not decrypt the inner layer.
- Decryption after rotation requires chain walking: open the outer envelope with the new hybrid private key, then the inner envelope with the old (compromised) hybrid private key.
- The compromised key alone is no longer sufficient: the outer hybrid envelope requires the new private key.
The server can perform hybrid encapsulation without any private key material, because encapsulation uses only the recipient's public key. This preserves the existing property that the server never accesses plaintext key material.
2.3 Hybrid Signatures
Every signature is a hybrid signature combining Ed25519 (classical) and ML-DSA-65 (post-quantum).
Key pair:
public key = Ed25519_pk (32) || ML-DSA-65_pk (1952) = 1984 bytes
secret seed = Ed25519_seed (32) || ML-DSA-65_seed (32) = 64 bytes
Signing:
1. sig_classical = Ed25519.Sign(message, Ed25519_seed) -> 64 bytes
2. sig_pq = ML-DSA-65.Sign(message,
expand(ML-DSA-65_seed)) -> 3309 bytes
3. Hybrid signature = (sig_classical, sig_pq)
Verification:
1. Verify sig_classical with Ed25519_pk
2. Verify sig_pq with ML-DSA-65_pk
3. Accept only if BOTH verify
Both halves are generated independently with separate randomness. Only the two 32-byte seeds are stored, as one key bag entry under the algorithm identifier Ed25519-MLDsa65; the ML-DSA-65 expanded private key is a deterministic function of its seed and is re-derived at signing time. A forger must produce valid signatures under both schemes over the same message.
2.4 Symmetric Layer: Single Layer by Design
The supersecret-derived AES-256-GCM encryption of key bag entries is hybrid-resilient by nature:
- AES-256 provides 128-bit security against Grover's quantum algorithm.
- Argon2id key derivation is not affected by quantum attacks.
- No asymmetric cryptography is involved in this layer.
The supersecret → Argon2id → AES-256-GCM pipeline is therefore a single layer by design.
3. Key Bag Impact
3.1 The Same Supersecret
Using the same supersecret for both layers does not compromise security.
The supersecret's role is to derive a symmetric AES-256-GCM key (via Argon2id) that encrypts each key bag entry. Holding classical key material in the bag simply means more bytes encrypted under the same derived key. This is safe because:
- The supersecret-derived key is symmetric (AES-256), quantum-resistant by itself.
- Each key bag entry uses its own unique AES-256-GCM nonce.
- The classical and post-quantum private keys are independently generated; knowing one reveals nothing about the other.
- The supersecret never touches the asymmetric key generation process.
Two supersecrets are not necessary and would harm usability. Members already have to safeguard one backup credential; the Recovery Kit exists for that one.
3.2 Key Bag Contents
Each entry in the bag holds one logical key. For a hybrid key the entry holds both private halves concatenated under one algorithm identifier; there are no separate classical and post-quantum entries.
| Key ID | Classical | Post-quantum | Purpose |
|---|---|---|---|
attr_encrypt |
X25519 | ML-KEM-1024 | Claim encryption and claim sharing |
msg_encrypt |
X25519 | ML-KEM-1024 | External message encryption |
efs_encrypt |
X25519 | ML-KEM-1024 | Encrypted Filestorage package keys |
chat_encrypt |
X25519 | ML-KEM-1024 | Chat epoch keys |
mail_encrypt |
X25519 | ML-KEM-1024 | Mail encryption |
main_signing_key |
Ed25519 | ML-DSA-65 | The account's primary signing key (step-up authentication, signed API requests) |
chat_sign, mail_sign |
Ed25519 | ML-DSA-65 | Chat and mail account roots |
agent:<id>:agent_signing_key |
Ed25519 | ML-DSA-65 | Signing keys of agent identities, held by their principal |
xrpl_master |
Ed25519 | none | XRPL transaction signing (XRPL-native key type, not hybridised; see §7.5) |
The server requires the hybrid algorithm identifier for every asymmetric entry and refuses a single-algorithm key in any of these slots.
Entries without a public half (derive_root, the account's HKDF root; recovery_kit, the Recovery Kit's Shamir coefficients; and an organization's bitcash_wallet key) are symmetric material and outside the scope of this document.
3.3 CPR Signing Keys (Per-Machine)
| Key | Classical | Post-quantum | Purpose |
|---|---|---|---|
| CPR signing key | Ed25519 | ML-DSA-65 | Claim, block, receipt and attribution signing |
Per-machine CPR signing keys are hybrid Ed25519 + ML-DSA-65 pairs, generated locally on the machine (Catalog.ID Whitepaper §3.4.1a). A CPR node signs every block, claim, receipt, allocation and authentication header under the Hybrid-Ed25519-MLDsa65 profile, has no non-hybrid signer, and rejects any other profile on an inbound claim. Its verifier registry additionally knows the single-algorithm profiles ML-DSA-44, ML-DSA-65, ML-DSA-87 and SLH-DSA-SHA2-128s (the conservative archival profile of the CPR Whitepaper §2.2). A registered verification profile is never removed, so every signature the registry holds stays verifiable for as long as the registry exists, as an immutable provenance registry requires.
4. Cost per Operation
4.1 Claim Creation
- The claim's symmetric key is wrapped with the hybrid KEM using the member's
attr_encryptpublic key. - The classical layer adds 65 bytes per wrapped key (ephemeral X25519 public key, HKDF salt and version byte).
4.2 Claim Sharing
- Re-wrapping uses the recipient's hybrid public key.
- Same process, same 65-byte overhead per copy.
4.3 CPR Claim Signing
- Each signature carries two components.
- The classical layer adds 64 bytes (the Ed25519 signature) per signature: 3373 bytes in total.
- Verification requires both components to pass.
4.4 Key Rotation
- Server-side re-wrapping adds an outer envelope using the new hybrid public key.
- The chain grows by one link per rotation.
4.5 Registration Flow
- Both halves of every key are generated at registration and stored as single entries in the key bag.
- The two layers add no user-facing step.
5. Why Two Layers
-
Defense in depth. An attacker must break two fundamentally different mathematical problems (elliptic-curve discrete log AND lattice-based assumptions) to compromise confidentiality or forge signatures. These problems are unrelated: a breakthrough against one does not help with the other.
-
Insurance against post-quantum algorithm weaknesses. If ML-KEM or ML-DSA contains an undiscovered structural weakness, side-channel vulnerability or intentional backdoor, the classical layer remains intact. The system degrades gracefully to classical-only security rather than failing completely.
-
Insurance against quantum computers. If large-scale quantum computers are built, the post-quantum layer protects against Shor's algorithm attacks on the classical layer. The system degrades gracefully to post-quantum-only security.
-
No supersecret change. The same single supersecret protects both layers. No additional backup credential is needed. The user experience is unchanged.
-
Battle-tested classical foundation. X25519 and Ed25519 have been deployed at massive scale (Signal, TLS 1.3, SSH, age). secp256k1 secures the entire Bitcoin network. These algorithms have survived decades of intense cryptanalysis.
-
Alignment with industry direction. Google Chrome, Signal, iMessage and OpenSSH have all adopted hybrid classical + post-quantum key exchange. NIST's recommendations for key-encapsulation mechanisms (SP 800-227) cover combining a classical and a post-quantum KEM into one.
-
Crypto agility. If either algorithm family is compromised, the broken layer can be dropped and a replacement substituted without catastrophic failure.
6. Costs
-
Increased ciphertext and signature sizes. The hybrid KEM adds 65 bytes per wrapped key. Hybrid signatures add 64 bytes (Ed25519) per signature. Against ML-KEM's 1568-byte ciphertexts and ML-DSA's 3309-byte signatures, the percentage overhead is small (about 4% for KEM, 2% for signatures).
-
Implementation complexity. Two key generation paths, two encapsulation/decapsulation paths, two signing/verification paths. More code means more potential for implementation bugs. Mitigated by using established libraries: the browser client uses the noble cryptography libraries for X25519, Ed25519, ML-KEM and ML-DSA with WebCrypto for AES-GCM and the server side uses the .NET platform's post-quantum cryptography with NSec for the classical primitives.
-
Key bag size increase. Each hybrid entry holds extra classical material (32 bytes per X25519 private key, 32 bytes per Ed25519 seed). Negligible in absolute terms.
-
Performance overhead. Two asymmetric operations per encrypt/decrypt/sign/verify instead of one. In practice X25519 and Ed25519 are extremely fast (tens of microseconds per operation on modern hardware), so the overhead is negligible compared to ML-KEM and ML-DSA operations, which are in the millisecond range.
-
Verification complexity for third parties. External verifiers of CPR claims must verify two signatures instead of one, which requires both classical and post-quantum verification libraries. Mitigated by the fact that Ed25519 libraries are ubiquitous.
-
Risk of false confidence. A hybrid approach might reduce the urgency of monitoring post-quantum algorithm health ("we have the classical fallback"). This is a human and organizational risk, not a technical one.
7. Implementation
7.1 Algorithm Selection
| Purpose | Classical | Post-Quantum | Rationale |
|---|---|---|---|
| Key encapsulation | X25519 | ML-KEM-1024 | X25519 is the most widely deployed ECDH. Curve25519 is rigid (no unexplained parameters). |
| Digital signatures | Ed25519 | ML-DSA-65 | Ed25519 is deterministic (no random nonce needed), widely audited and fast. |
| Archival verification (CPR) | none | SLH-DSA-SHA2-128s | Conservative hash-based profile, verification only, for archival attestations (§3.3). |
Why X25519/Ed25519 over secp256k1/ECDSA?
- secp256k1 has unexplained parameter choices (Koblitz curve). Curve25519 is fully rigid.
- Ed25519 is deterministic: no nonce reuse vulnerability (cf. the PlayStation 3 ECDSA break).
- X25519/Ed25519 are faster and more widely deployed in non-blockchain contexts.
- XRPL uses Ed25519, its native key type (§7.5).
Note: while secp256k1's battle-tested status in Bitcoin is a strong argument for its security, the specific advantages of Ed25519 (deterministic signing, rigid curve construction) make it the better choice for the classical layer of a hybrid scheme, where the classical algorithm primarily serves as a safety net.
7.2 Key Derivation and Protocol Families
combined_secret = HKDF-SHA-512(
ikm = X25519_shared_secret || ML-KEM_shared_secret,
salt = fresh_random_32_bytes,
info = <protocol family label>
)
Using HKDF with both shared secrets concatenated as input ensures the combined secret depends on both. Even if one shared secret is fully known to the attacker, the other provides full entropy.
The info label names the protocol family the wrap belongs to. Every caller supplies it explicitly; there is no default. A handler scoped to one family cannot accept a wrap made for another, so a future protocol cannot be silently accepted by an existing one.
info |
Used for |
|---|---|
catalog-hybrid-kem-v1 |
Catalog.ID claim keys and key bag wraps |
catalog-efs-wrap-v1 |
Encrypted Filestorage package keys and key-encryption keys |
catalog-chat-epoch-wrap-v1 |
Chat conversation epoch keys and read-state sync keys |
catalog-mail-encrypt-export-v1 |
Export of the account's mail key to a mail module |
catalog-bitcash-wallets-release-v1, catalog-bitcash-wallets-save-v1 |
Bitcash wallet document release and save ceremonies |
7.3 Signature Formats
The hybrid signature has two encodings, both carrying the same pair of signatures over the same canonical message.
JSON wrapper, used in Catalog.ID API bodies, serialized as canonical JSON (RFC 8785):
{
"algorithm": "Hybrid-Ed25519-MLDsa65",
"ed25519": "<base64 Ed25519 signature, 64 bytes>",
"mldsa65": "<base64 ML-DSA-65 signature, 3309 bytes>"
}
Concatenated form, used for CPR claims, blocks, receipts and federation records and by every server-side verifier: sig_classical (64) || sig_pq (3309), 3373 bytes, under the signing profile Hybrid-Ed25519-MLDsa65.
Public keys are not embedded in the signature. They are bound to the account or, for CPR signing keys, published through the machine's registry_signing_key claim, and resolved by the verifier. Verification requires both signatures to pass.
7.4 Crypto Agility
Every account carries hybrid key material from registration, the server requires the hybrid algorithm identifier for every asymmetric key bag entry, and CPR's signer is hybrid-only. Every entry, wrap and signature carries an explicit algorithm identifier. Should either algorithm family need replacing, a new profile can be introduced alongside the existing one, and material wrapped under a superseded key remains readable through the key chain described in Catalog.ID Whitepaper §3.3.
7.5 XRPL Anchoring
The XRPL integration uses plain Ed25519, the ledger's native key type (xrpl_master), and is the one non-hybrid key pair in the key bag. This is explicitly external to the post-quantum trust model (CPR Whitepaper §2.3): XRPL anchoring provides timestamping, not the cryptographic trust root.
8. Comparison with Alternative Approaches
| Approach | Pros | Cons |
|---|---|---|
| Post-quantum only | Simpler, smaller signatures | Single point of failure if the post-quantum algorithms are broken |
| Classical only | Battle-tested, well understood | Vulnerable if quantum computers are built |
| Hybrid (Catalog) | Resilient against both threats | Slightly larger signatures and ciphertexts, more implementation complexity |
| Two supersecrets | Maximum separation | Poor usability, no meaningful security gain (the supersecret layer is symmetric AES) |
9. Conclusion
The hybrid approach is the conservative, belt-and-suspenders choice. It treats post-quantum cryptography as an additive layer rather than a replacement, preserving the decades of adversarial testing that classical cryptography has earned while preparing for a potential quantum future. The overhead is minimal, the user experience is unchanged (same single supersecret), and the approach aligns with what the most security-conscious organizations in the industry have deployed.
The key insight is that this is not a choice between classical and post-quantum cryptography. It is a requirement that an attacker defeat both.