| Crates.io | rust-bottle |
| lib.rs | rust-bottle |
| version | 0.2.3 |
| created_at | 2026-01-05 16:02:17.213581+00 |
| updated_at | 2026-01-11 14:46:49.277579+00 |
| description | Rust implementation of Bottle protocol - layered message containers with encryption and signatures |
| homepage | |
| repository | https://github.com/thanos/rust-bottle |
| max_upload_size | |
| id | 2024102 |
| size | 535,562 |
Rust implementation of the Bottle protocol - a layered message container system with encryption and signatures. This library provides secure, type-safe cryptographic operations for building privacy-preserving applications.
rust-bottle implements the Bottle protocol, which provides layered message containers with support for multiple encryption layers, multiple signatures, key management through IDCards and Keychains, and cryptographically signed group memberships. The library is designed to match the functionality of the Go implementation gobottle while leveraging Rust's type safety and memory safety guarantees.
Add rust-bottle to your Cargo.toml:
[dependencies]
rust-bottle = "0.2.0"
rand = "0.8"
use rust_bottle::*;
use rand::rngs::OsRng;
// Create a message container
let message = b"Hello, Bottle!";
let mut bottle = Bottle::new(message.to_vec());
// Generate encryption keys
let rng = &mut OsRng;
let key = X25519Key::generate(rng);
// Encrypt the bottle
bottle.encrypt(rng, &key.public_key_bytes()).unwrap();
// Decrypt and open the bottle
let opener = Opener::new();
let decrypted = opener.open(&bottle, Some(&key.private_key_bytes())).unwrap();
assert_eq!(decrypted, message);
use rust_bottle::*;
use rand::rngs::OsRng;
let message = b"Signed message";
let mut bottle = Bottle::new(message.to_vec());
// Generate signing key
let rng = &mut OsRng;
let signing_key = Ed25519Key::generate(rng);
let public_key = signing_key.public_key_bytes();
// Sign the bottle
bottle.sign(rng, &signing_key, &public_key).unwrap();
// Verify signature
let opener = Opener::new();
let info = opener.open_info(&bottle).unwrap();
assert!(info.is_signed_by(&public_key));
Bottles are layered message containers that support multiple encryption and signature layers. Each encryption layer can be for a different recipient, and multiple signers can sign the same bottle.
use rust_bottle::*;
use rand::rngs::OsRng;
// Create a bottle with a message
let message = b"Multi-layer encrypted and signed message";
let mut bottle = Bottle::new(message.to_vec());
// Add metadata
bottle.set_metadata("sender", "alice@example.com");
bottle.set_metadata("timestamp", "2024-01-01T00:00:00Z");
// Encrypt to multiple recipients (layered encryption)
let rng = &mut OsRng;
let bob_key = X25519Key::generate(rng);
let charlie_key = X25519Key::generate(rng);
// First encryption layer (innermost)
bottle.encrypt(rng, &bob_key.public_key_bytes()).unwrap();
// Second encryption layer (outermost)
bottle.encrypt(rng, &charlie_key.public_key_bytes()).unwrap();
// Sign with multiple signers
let alice_signing_key = Ed25519Key::generate(rng);
let alice_pub = alice_signing_key.public_key_bytes();
bottle.sign(rng, &alice_signing_key, &alice_pub).unwrap();
// Serialize for storage or transmission
let serialized = bottle.to_bytes().unwrap();
// Deserialize
let deserialized = Bottle::from_bytes(&serialized).unwrap();
// Open with appropriate key (decrypts all layers)
let opener = Opener::new();
let decrypted = opener.open(&deserialized, Some(&bob_key.private_key_bytes())).unwrap();
assert_eq!(decrypted, message);
IDCards allow entities to declare multiple keys with specific purposes and manage key lifecycles. They can be signed to establish trust.
use rust_bottle::*;
use rand::rngs::OsRng;
use std::time::{Duration, SystemTime};
let rng = &mut OsRng;
// Generate primary signing key
let primary_key = Ed25519Key::generate(rng);
let primary_pub = primary_key.public_key_bytes();
// Create IDCard
let mut idcard = IDCard::new(&primary_pub);
// Add metadata
idcard.set_metadata("name", "Alice");
idcard.set_metadata("email", "alice@example.com");
idcard.set_metadata("organization", "Example Corp");
// Set purposes for the primary key
idcard.set_key_purposes(&primary_pub, &["sign", "decrypt"]);
// Add a dedicated encryption key with expiration
let encryption_key = X25519Key::generate(rng);
let encryption_pub = encryption_key.public_key_bytes();
idcard.set_key_purposes(&encryption_pub, &["decrypt"]);
idcard.set_key_duration(&encryption_pub, Duration::from_secs(365 * 24 * 3600)); // 1 year
// Sign the IDCard with the primary key
let signed_idcard = idcard.sign(rng, &primary_key, &primary_pub).unwrap();
// Verify key purposes
assert!(idcard.test_key_purpose(&primary_pub, "sign").is_ok());
assert!(idcard.test_key_purpose(&encryption_pub, "decrypt").is_ok());
assert!(idcard.test_key_purpose(&encryption_pub, "sign").is_err());
// Get all keys for a specific purpose
let decrypt_keys = idcard.get_keys("decrypt");
assert_eq!(decrypt_keys.len(), 2);
Keychains provide secure storage for private keys, indexed by their public key fingerprints. They enable signing with specific keys without exposing the key selection logic.
use rust_bottle::*;
use rand::rngs::OsRng;
let rng = &mut OsRng;
// Create a keychain
let mut keychain = Keychain::new();
// Add multiple keys of different types
let ed25519_key = Ed25519Key::generate(rng);
let ecdsa_key = EcdsaP256Key::generate(rng);
let x25519_key = X25519Key::generate(rng);
keychain.add_key(ed25519_key);
keychain.add_key(ecdsa_key);
keychain.add_key(x25519_key);
// Retrieve a signer by public key
let ed25519_pub = ed25519_key.public_key_bytes();
let signer = keychain.get_signer(&ed25519_pub).unwrap();
// Sign a message
let message = b"Message to sign";
let signature = keychain.sign(rng, &ed25519_pub, message).unwrap();
// Use keychain with bottles
let mut bottle = Bottle::new(b"Keychain-signed message".to_vec());
bottle.sign(rng, signer, &ed25519_pub).unwrap();
Memberships provide cryptographically signed group affiliations, allowing entities to prove membership in groups with specific roles.
use rust_bottle::*;
use rand::rngs::OsRng;
let rng = &mut OsRng;
// Create member and group IDCards
let member_key = Ed25519Key::generate(rng);
let member_pub = member_key.public_key_bytes();
let member_idcard = IDCard::new(&member_pub);
let group_key = Ed25519Key::generate(rng);
let group_pub = group_key.public_key_bytes();
let group_idcard = IDCard::new(&group_pub);
// Create a membership
let mut membership = Membership::new(&member_idcard, &group_pub);
membership.set_info("role", "admin");
membership.set_info("department", "Engineering");
membership.set_info("joined", "2024-01-01");
// Sign the membership with the group owner's key
membership.sign(rng, &group_key, &group_pub).unwrap();
// Verify the membership
assert!(membership.verify(&group_idcard).is_ok());
// Add membership to member's IDCard
let mut member_idcard = IDCard::new(&member_pub);
member_idcard.update_groups(vec![membership.to_bytes().unwrap()]);
Direct ECDH encryption can be used independently of bottles for encrypting data to public keys.
use rust_bottle::*;
use rand::rngs::OsRng;
let plaintext = b"Secret message";
let rng = &mut OsRng;
// Generate key pairs for Alice and Bob
let alice_key = X25519Key::generate(rng);
let bob_key = X25519Key::generate(rng);
// Alice encrypts to Bob's public key
let ciphertext = ecdh_encrypt(
rng,
plaintext,
&bob_key.public_key_bytes()
).unwrap();
// Bob decrypts with his private key
let decrypted = ecdh_decrypt(
&ciphertext,
&bob_key.private_key_bytes()
).unwrap();
assert_eq!(decrypted, plaintext);
Post-quantum cryptography support is available via feature flags. See PQC_FEATURE_FLAG.md for details on enabling PQC features.
ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism) provides post-quantum encryption. Requires the ml-kem feature:
#[cfg(feature = "ml-kem")]
use rust_bottle::*;
use rand::rngs::OsRng;
let plaintext = b"Post-quantum encrypted message";
let rng = &mut OsRng;
// Generate ML-KEM-768 keys
let alice_key = MlKem768Key::generate(rng);
let bob_key = MlKem768Key::generate(rng);
// Alice encrypts to Bob's public key
let ciphertext = mlkem768_encrypt(
rng,
plaintext,
&bob_key.public_key_bytes()
).unwrap();
// Bob decrypts
let decrypted = mlkem768_decrypt(
&ciphertext,
&bob_key.private_key_bytes()
).unwrap();
assert_eq!(decrypted, plaintext);
ML-DSA (Module-Lattice-Based Digital Signature Algorithm) provides post-quantum signatures. Requires the post-quantum feature:
#[cfg(feature = "post-quantum")]
use rust_bottle::*;
use rand::rngs::OsRng;
let message = b"Post-quantum signed message";
let rng = &mut OsRng;
// Generate ML-DSA-44 signing key (128-bit security)
let signing_key = MlDsa44Key::generate(rng);
let pub_key = signing_key.public_key_bytes();
// Sign the message
let signature = signing_key.sign(rng, message).unwrap();
// Verify signature
assert!(signing_key.verify(message, &signature).is_ok());
SLH-DSA (Stateless Hash-Based Digital Signature Algorithm) provides hash-based post-quantum signatures. Requires the post-quantum feature:
#[cfg(feature = "post-quantum")]
use rust_bottle::*;
use rand::rngs::OsRng;
let message = b"Hash-based signed message";
let rng = &mut OsRng;
// Generate SLH-DSA-128s signing key (128-bit security)
let signing_key = SlhDsa128sKey::generate(rng);
let pub_key = signing_key.public_key_bytes();
// Sign the message
let signature = signing_key.sign(rng, message).unwrap();
// Verify signature
assert!(signing_key.verify(message, &signature).is_ok());
Hybrid encryption provides both post-quantum and classical security. Requires the ml-kem feature:
#[cfg(feature = "ml-kem")]
use rust_bottle::*;
use rand::rngs::OsRng;
let plaintext = b"Hybrid encrypted message";
let rng = &mut OsRng;
// Generate both post-quantum and classical keys
let mlkem_key = MlKem768Key::generate(rng);
let x25519_key = X25519Key::generate(rng);
// Encrypt with both (provides both post-quantum and classical security)
let ciphertext = hybrid_encrypt_mlkem768_x25519(
rng,
plaintext,
&mlkem_key.public_key_bytes(),
&x25519_key.public_key_bytes(),
).unwrap();
// Decrypt (tries ML-KEM first, falls back to X25519)
let mlkem_sec = mlkem_key.private_key_bytes();
let x25519_sec: [u8; 32] = x25519_key.private_key_bytes().try_into().unwrap();
let decrypted = hybrid_decrypt_mlkem768_x25519(
&ciphertext,
&mlkem_sec,
&x25519_sec,
).unwrap();
assert_eq!(decrypted, plaintext);
Post-quantum keys work seamlessly with the Bottle API:
#[cfg(feature = "post-quantum")]
use rust_bottle::*;
use rand::rngs::OsRng;
let mut bottle = Bottle::new(b"Post-quantum secure message".to_vec());
let rng = &mut OsRng;
// Encrypt with ML-KEM (requires ml-kem feature)
#[cfg(feature = "ml-kem")]
{
let mlkem_key = MlKem768Key::generate(rng);
bottle.encrypt(rng, &mlkem_key.public_key_bytes()).unwrap();
}
// Sign with ML-DSA (requires post-quantum feature)
let mldsa_key = MlDsa44Key::generate(rng);
let pub_key = mldsa_key.public_key_bytes();
bottle.sign(rng, &mldsa_key, &pub_key).unwrap();
// Decrypt and verify
let opener = Opener::new();
#[cfg(feature = "ml-kem")]
{
let mlkem_key = MlKem768Key::generate(rng);
let decrypted = opener.open(&bottle, Some(&mlkem_key.private_key_bytes())).unwrap();
}
let info = opener.open_info(&bottle).unwrap();
assert!(info.is_signed_by(&pub_key));
RSA support is available for both encryption and signing operations. RSA is useful for compatibility with legacy systems, though modern applications should prefer ECDSA/Ed25519 for signatures and X25519 for encryption.
use rust_bottle::*;
use rand::rngs::OsRng;
let rng = &mut OsRng;
// Generate RSA key pair (2048-bit or 4096-bit)
let rsa_key = RsaKey::generate(rng, 2048).unwrap();
// RSA Encryption (for small messages)
let plaintext = b"Small message";
let ciphertext = rsa_encrypt(rng, plaintext, rsa_key.public_key()).unwrap();
let decrypted = rsa_decrypt(&ciphertext, &rsa_key).unwrap();
assert_eq!(decrypted, plaintext);
// RSA Signing
let message = b"Message to sign";
let signature = rsa_key.sign(rng, message).unwrap();
assert!(rsa_key.verify(message, &signature).is_ok());
// Use RSA with Bottles
let mut bottle = Bottle::new(b"RSA-encrypted message".to_vec());
// Note: RSA encryption is limited to small messages (key_size - 42 bytes)
// For larger messages, use RSA to encrypt a symmetric key
Note: RSA can only encrypt small messages (typically up to key_size - 42 bytes for OAEP with SHA-256). For larger messages, use RSA to encrypt a symmetric key and then encrypt the message with that key.
The library also supports P-256 ECDH for compatibility with ECDSA keys.
use rust_bottle::*;
use rand::rngs::OsRng;
let plaintext = b"P-256 encrypted message";
let rng = &mut OsRng;
// Generate P-256 key pair
let key = EcdsaP256Key::generate(rng);
let public_key = key.public_key_bytes();
// Encrypt
let ciphertext = ecdh_encrypt(rng, plaintext, &public_key).unwrap();
// Decrypt (requires private key bytes)
let private_key_bytes = key.private_key_bytes();
let decrypted = ecdh_decrypt(&ciphertext, &private_key_bytes).unwrap();
assert_eq!(decrypted, plaintext);
| Algorithm | Purpose | Status | Notes |
|---|---|---|---|
| ECDSA P-256 | Signing | Supported | Full implementation |
| ECDSA P-384 | Signing | Supported | Full implementation |
| ECDSA P-521 | Signing | Supported | Full implementation |
| Ed25519 | Signing | Supported | Full implementation |
| RSA | Signing/Encryption | Supported | RSA-OAEP encryption, PKCS#1 v1.5 signing |
| X25519 | Encryption | Supported | ECDH key exchange |
| P-256 ECDH | Encryption | Supported | ECDH key exchange |
| AES-256-GCM | Encryption | Supported | Used for shared secret encryption |
| SHA-256 | Hashing | Supported | Key fingerprinting and message hashing |
| SHA-3 | Hashing | Supported | Available for custom use |
Comprehensive post-quantum cryptography support is available via feature flags. The implementation includes:
Encryption (requires ml-kem feature):
Signatures (requires post-quantum feature):
Hybrid Encryption (requires ml-kem feature):
# Enable signatures only (ML-DSA and SLH-DSA)
cargo build --features post-quantum
# Enable encryption (ML-KEM) - works on all platforms including macOS/ARM
cargo build --features post-quantum,ml-kem
# Enable everything
cargo build --features post-quantum,ml-kem
Note: ML-KEM uses RustCrypto's pure Rust ml-kem crate, which works on all platforms including macOS/ARM. No platform-specific issues or patches are required.
All post-quantum algorithms are integrated into the existing API and work seamlessly with:
ecdh_encrypt/ecdh_decrypt automatically detect PQC key typesSee POST_QUANTUM.md for detailed documentation.
RSA support is fully implemented with:
rust-bottle provides a trait-based interface for integrating Trusted Platform Modules (TPM) and Hardware Security Modules (HSM) with ECDH operations. This allows you to use hardware-backed keys for enhanced security.
cargo build --features tpm
To use TPM/HSM with rust-bottle, implement the ECDHHandler trait for your TPM library:
use rust_bottle::tpm::ECDHHandler;
use rust_bottle::ecdh::ecdh_decrypt_with_handler;
use rust_bottle::errors::Result;
struct MyTpmHandler {
// Your TPM-specific fields
}
impl ECDHHandler for MyTpmHandler {
fn public_key(&self) -> Result<Vec<u8>> {
// Return the public key from TPM/HSM
// Format: 32 bytes for X25519, 65 bytes for P-256
Ok(vec![])
}
fn ecdh(&self, peer_public_key: &[u8]) -> Result<Vec<u8>> {
// Perform ECDH using TPM/HSM private key
// Return the shared secret
Ok(vec![])
}
}
// Use the handler for decryption
// let handler = MyTpmHandler::new()?;
// let decrypted = ecdh_decrypt_with_handler(&ciphertext, &[], Some(&handler))?;
ECDHHandler trait provides a clean interface for any TPM/HSM backendtss-esapi or platform-specific TPM libraries to implement the traitSee the tpm module documentation for more details.
bottle.rs: Core Bottle and Opener typesecdh.rs: ECDH encryption and decryption implementationskeys.rs: Key type implementations (ECDSA, Ed25519, X25519, RSA)signing.rs: Sign and Verify traitsidcard.rs: IDCard implementationkeychain.rs: Keychain implementationmembership.rs: Membership implementationtpm.rs: TPM/HSM support (trait-based interface)hash.rs: Hashing utilitiesutils.rs: Utility functionserrors.rs: Error typesSign, Verify, SignerKey) for polymorphism and flexibilitythiserror for detailed error informationbincode for efficient binary serializationKeys implement traits based on their capabilities:
Sign: Types that can sign data (ECDSA, Ed25519, RSA)Verify: Types that can verify signatures (ECDSA, Ed25519, RSA)SignerKey: Keys that can be stored in keychains (all key types)This design allows the library to work with different key types polymorphically while maintaining type safety.
The library uses a comprehensive error type system:
pub enum BottleError {
Encryption(String),
Decryption(String),
VerifyFailed,
InvalidFormat,
InvalidKeyType,
KeyNotFound,
KeyUnfit,
NoAppropriateKey,
Serialization(String),
Deserialization(String),
UnsupportedAlgorithm,
}
All operations return Result<T, BottleError> for explicit error handling.
The library includes a comprehensive test suite that matches the gobottle test structure:
# Run all tests
cargo test
# Run tests with specific features
cargo test --features ml-kem
cargo test --features post-quantum
cargo test --features ml-kem,post-quantum
tests/bottle_test.rs: Core bottle functionality (7 tests)
tests/ecdh_test.rs: ECDH encryption/decryption (3 tests)
tests/aliceandbob_test.rs: End-to-end scenarios (4 tests)
tests/pqc_test.rs: Post-quantum cryptography (24 tests)
tests/pkix_test.rs: PKIX/PKCS#8 serialization (9 tests)
tests/rsa_test.rs: RSA operations (11 tests)
tests/short_buffer_test.rs: Short buffer encryption (7 tests)
All tests pass and demonstrate the library's functionality.
Code coverage is tracked using cargo-tarpaulin. To generate coverage reports:
# Install cargo-tarpaulin (if not already installed)
cargo install cargo-tarpaulin
# Generate coverage report
cargo tarpaulin --out Html --output-dir coverage
# Generate coverage with specific features
cargo tarpaulin --features ml-kem --out Html --output-dir coverage
cargo tarpaulin --features post-quantum --out Html --output-dir coverage
cargo tarpaulin --features ml-kem,post-quantum --out Html --output-dir coverage
# View HTML report
open coverage/tarpaulin-report.html
Coverage reports are generated in multiple formats:
coverage/tarpaulin-report.html)The coverage configuration is in tarpaulin.toml. Coverage reports exclude test files, examples, and benchmarks.
Note: Coverage may vary depending on which features are enabled, as optional dependencies are only included when their features are active.
zeroize crateOsRng or equivalent)The library is designed for efficiency:
Performance characteristics:
ml-kem cratetpm feature)from_private_key_bytes() for PQC keys cannot derive public keys (limitation of underlying crates)ECDHHandler trait for their TPM library)rust-bottle aims to match gobottle's functionality while adapting to Rust's type system:
Note: Serialization formats differ (bincode vs custom binary), so bottles created with one library cannot be directly read by the other. The protocol semantics are equivalent.
rust-bottle is a Rust implementation that matches the functionality of the Go implementation gobottle. Below is a summary comparison:
| Aspect | rust-bottle | gobottle | Status |
|---|---|---|---|
| Core Protocol | Complete | Complete | Equivalent |
| Classical Crypto | Complete | Complete | Equivalent |
| Post-Quantum Crypto | Complete (via features) | Complete | Equivalent |
| RSA Support | Complete | Complete | Equivalent |
| PKIX/PKCS#8 | Complete | Complete | Equivalent |
| TPM/HSM Support | Available (trait-based) | Complete | Equivalent |
| Type Safety | Strong (compile-time) | Runtime | Better |
| Memory Safety | Guaranteed | Manual | Better |
Classical Cryptography:
Post-Quantum Cryptography:
Advantages of rust-bottle:
Advantages of gobottle:
rust-bottle:
thiserrorbincode for serializationgobottle:
rust-bottle includes 372 tests covering:
Choose rust-bottle if:
Choose gobottle if:
For a detailed comparison, see COMPARISON.md.
Contributions are welcome. Areas that need work:
MIT License - see LICENSE file for details.