| Crates.io | tholos-pq |
| lib.rs | tholos-pq |
| version | 0.1.1 |
| created_at | 2025-11-15 17:18:20.380285+00 |
| updated_at | 2025-11-15 17:18:20.380285+00 |
| description | Pure post-quantum multi-recipient encryption: Kyber-1024 + Dilithium-3 + XChaCha20-Poly1305 with a stable, versioned wire format |
| homepage | |
| repository | https://github.com/thanos/tholos-pq |
| max_upload_size | |
| id | 1934593 |
| size | 119,218 |
A pure Rust implementation of post-quantum multi-recipient encryption with a stable, versioned wire format.
tholos-pq provides a complete solution for encrypting messages to multiple recipients using post-quantum cryptographic algorithms. The library uses ML-KEM-1024 (Kyber-1024) for key encapsulation, XChaCha20-Poly1305 for symmetric encryption, and Dilithium-3 for sender authentication.
suite = Kyber1024+XChaCha20P1305+Dilithium3)Add this to your Cargo.toml:
[dependencies]
tholos-pq = "0.1.0"
use tholos_pq::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate recipient keypairs
let (pub_a, priv_a) = gen_recipient_keypair("alice");
let (pub_b, priv_b) = gen_recipient_keypair("bob");
// Generate sender keypair
let sender = gen_sender_keypair("server1");
// Build allowed sender list
let allowed = vec![(sender.sid.clone(), sender_pub(&sender).pk_dilithium)];
// Encrypt message for multiple recipients
let message = b"Hello, post-quantum world!";
let wire = encrypt(message, &sender, &[pub_a.clone(), pub_b.clone()])?;
// Each recipient can decrypt
let decrypted_a = decrypt(&wire, "alice", &priv_a.sk_kyber, &allowed)?;
let decrypted_b = decrypt(&wire, "bob", &priv_b.sk_kyber, &allowed)?;
assert_eq!(decrypted_a, message);
assert_eq!(decrypted_b, message);
Ok(())
}
use tholos_pq::*;
let sender = gen_sender_keypair("server1");
let (pub_a, priv_a) = gen_recipient_keypair("alice");
let (pub_b, priv_b) = gen_recipient_keypair("bob");
let (pub_c, priv_c) = gen_recipient_keypair("charlie");
let allowed = vec![(sender.sid.clone(), sender_pub(&sender).pk_dilithium)];
// Encrypt once for all three recipients
let wire = encrypt(
b"Message for A, B, and C",
&sender,
&[pub_a.clone(), pub_b.clone(), pub_c.clone()]
)?;
// Each recipient can decrypt independently
let pt_a = decrypt(&wire, "alice", &priv_a.sk_kyber, &allowed)?;
let pt_b = decrypt(&wire, "bob", &priv_b.sk_kyber, &allowed)?;
let pt_c = decrypt(&wire, "charlie", &priv_c.sk_kyber, &allowed)?;
use tholos_pq::*;
let sender1 = gen_sender_keypair("server1");
let sender2 = gen_sender_keypair("server2");
let (pub_key, priv_key) = gen_recipient_keypair("recipient");
// Only allow sender1
let allowed = vec![(sender1.sid.clone(), sender_pub(&sender1).pk_dilithium)];
// Message from sender1 succeeds
let wire1 = encrypt(b"Hello", &sender1, &[pub_key.clone()])?;
let pt1 = decrypt(&wire1, "recipient", &priv_key.sk_kyber, &allowed)?;
// Message from sender2 is rejected
let wire2 = encrypt(b"Hello", &sender2, &[pub_key.clone()])?;
let result = decrypt(&wire2, "recipient", &priv_key.sk_kyber, &allowed);
assert!(matches!(result, Err(TholosError::BadSignature)));
gen_recipient_keypair(kid: &str) -> (RecipientPub, RecipientPriv): Generate a new ML-KEM-1024 keypair for a recipientgen_sender_keypair(sid: &str) -> SenderKeypair: Generate a new Dilithium-3 keypair for a sendersender_pub(sender: &SenderKeypair) -> SenderPub: Extract public key information from a sender keypairencrypt(plaintext: &[u8], sender: &SenderKeypair, recipients: &[RecipientPub]) -> Result<Vec<u8>, TholosError>: Encrypt a message for multiple recipientsdecrypt(wire_cbor: &[u8], my_kid: &str, my_sk: &<MlKem1024 as KemCore>::DecapsulationKey, allowed_senders: &[(String, Vec<u8>)]) -> Result<Vec<u8>, TholosError>: Decrypt a message as a recipientThe TholosError enum includes:
BadSignature: Signature verification failed or sender not allowedMissingEnvelope: No recipient envelope found for the specified recipient IDMalformed: A field in the wire format is malformedAead: AEAD encryption or decryption operation failedSer: CBOR serialization or deserialization errorOsRngThe library includes comprehensive test coverage:
proptest for correctness validationRun tests with:
cargo test
Run property tests with:
cargo test --test property
The wire format is a versioned CBOR structure (BundleSigned) containing:
The format is designed for interoperability and includes versioning to support future algorithm updates.
ml-kem: Pure Rust ML-KEM-1024 implementationpqcrypto-dilithium: Dilithium-3 signature implementationchacha20poly1305: XChaCha20-Poly1305 AEAD encryptionserde_cbor: CBOR serializationhkdf: Key derivationLicensed under the Apache License, Version 2.0.
Contributions are welcome. Please ensure all tests pass and code follows Rust conventions.