tholos-pq
Post-quantum multi-recipient encryption with a versioned CBOR wire format.
Overview
tholos-pq encrypts messages to multiple recipients using ML-KEM-1024 (Kyber-1024) for key encapsulation, XChaCha20-Poly1305 for symmetric encryption, and Dilithium-3 for sender authentication.
Features
- Multi-recipient encryption: encrypt once for N recipients
- Post-quantum algorithms: ML-KEM-1024 and Dilithium-3
- Sender authentication via Dilithium-3 signatures over the signed inner payload
- Versioned CBOR wire format for interoperability
- ML-KEM-1024 and ML-DSA-65 via pure-Rust crates (
ml-kem,dilithium-rs); XChaCha20-Poly1305 viachacha20poly1305
Algorithm Suite
- Key Encapsulation: ML-KEM-1024 (Kyber-1024) for per-recipient key wrapping
- Symmetric Encryption: XChaCha20-Poly1305 for payload and CEK encryption
- Digital Signatures: ML-DSA-65 (Dilithium3) for sender authentication
- Wire Format: Versioned CBOR (
suite = Kyber1024+XChaCha20P1305+MlDsa65)
Installation
[]
= "0.3"
Usage
Basic Example
use *;
Multi-Recipient Encryption
use *;
Sender Authentication
use *;
Inspecting Headers (Replay Protection)
use *;
API Reference
Key Generation
gen_recipient_keypair(kid) -> (RecipientPub, RecipientPriv)gen_sender_keypair(sid) -> SenderKeypairsender_pub(sender) -> SenderPub
Encryption and Decryption
encrypt(plaintext, sender, recipients) -> Result<Vec<u8>, TholosError>decrypt(wire, my_kid, my_sk, allowed_senders) -> Result<Vec<u8>, TholosError>verify_header(wire, allowed_senders) -> Result<Header, TholosError>
Error Types
BadSignature: signature invalid or sender not allowedMissingEnvelope: no envelope for the recipientMalformed: invalid wire fieldAead: AEAD failureSer: CBOR serialization/deserialization failureNoRecipients: encrypt called with an empty recipient listUnsupportedSuite: unsupported version or algorithm suite
Known Limitations
- No forward secrecy: recipient ML-KEM keys are long-lived; compromise exposes past messages to that recipient
- No replay protection:
msg_idandtimestamp_unixare authenticated but not checked; useverify_headerand trackmsg_idin your application if needed - CBOR encoding uses maintained
ciborium; signatures cover encodedinnerbytes verbatim
Security Considerations
- Cryptographic operations use
OsRngfor randomness - Private keys are the caller's responsibility to protect; CEK/KEK material, ML-KEM decapsulation keys, and ML-DSA secret keys are zeroized on drop
- The allowed sender list must be managed carefully
- No forward secrecy: recipient ML-KEM keys are long-lived; compromise exposes past messages to that recipient
- No replay protection:
msg_idandtimestamp_unixare authenticated but not checked; useverify_headerand trackmsg_idin your application if needed
Testing
The crate includes integration tests, property-based tests (proptest), security regression tests, and doctests (this README is included in crate documentation).
Run the demo:
Wire Format
The wire format is a versioned CBOR BundleSigned structure:
inner: opaque signed CBOR bytes encoding the unsigned bundlesig_dilithium: ML-DSA-65 signature overinnerverbatim
The unsigned bundle contains:
- Header: version, suite, sender, recipient IDs, message ID, timestamp
- Payload: XChaCha20-Poly1305 ciphertext
- Recipient Envelopes: per-recipient ML-KEM ciphertexts and wrapped CEKs
Dependencies
ml-kem: pure-Rust ML-KEM-1024dilithium-rs: ML-DSA-65 (Dilithium3)ciborium: CBOR serializationchacha20poly1305: XChaCha20-Poly1305hkdf: key derivation
License
Licensed under the Apache License, Version 2.0.
Contributing
Contributions are welcome. Please run make check before submitting a pull request.