Build secure, post-quantum encrypted messaging into your application
This guide shows you how to integrate the Lattice Mesh Protocol (LMP) into your own projects. LMP provides military-grade encryption with forward secrecy and post-quantum protection.
- Quick Start
- Installation
- Core Concepts
- Basic Usage
- Full Messaging Integration
- Network Transport
- Key Management
- Error Handling
- Security Best Practices
- API Reference
- Architecture Overview
The fastest way to get secure messaging working:
use lmp_core::easy::{LmpClient, SecureChannel};
// Alice creates her client
let mut alice = LmpClient::new()?;
// Alice gets a token to share with Bob (via QR code, link, etc.)
let alice_token = alice.get_introduction_token("dht://alice-mailbox.example.com")?;
// Share the token (e.g., as base64 string)
let shareable = alice_token.to_base64()?;
println!("Share this with Bob: {}", shareable);[dependencies]
lmp-core = { git = "https://github.com/AaryanBansal-Dev/LMP_Lattice-Mesh-Protocol" }[dependencies]
lmp-core = {
git = "https://github.com/AaryanBansal-Dev/LMP_Lattice-Mesh-Protocol",
features = ["paranoid"] # Extra entropy mixing
}- Rust 1.70+
- OpenSSL development headers (for some platforms)
- ~50MB RAM minimum for cryptographic operations
Each user has an Identity containing:
- LTIK (Long-Term Identity Key): Ed25519 + Dilithium3 hybrid for signatures
- MTSK (Medium-Term Signing Key): Ed25519 for frequent signing
- Prekey: X25519 + Kyber768 hybrid for key exchange
A Session represents an encrypted conversation between two parties. Each session has:
- Unique conversation ID
- Double Ratchet state for forward secrecy
- Replay protection
Introduction tokens are shareable credentials containing public keys. Share these via:
- QR codes
- Deep links
- Any out-of-band channel
use lmp_core::easy::LmpClient;
// Create with auto-generated identity
let mut client = LmpClient::new()?;
// Get device ID (useful for multi-device support)
let device_id = client.device_id();
println!("Device ID: {:?}", hex::encode(device_id));
// Get fingerprint for verification (like Signal safety numbers)
let fingerprint = client.fingerprint();
println!("Fingerprint: {}", fingerprint);
// Output: "1234-5678-9012-3456"// Create a token to share with others
let token = client.get_introduction_token("dht://your-mailbox.example.com")?;
// Option 1: Share as base64 (for links)
let base64_token = token.to_base64()?;
// e.g., "YXNkZmFzZGZhc2Rm..."
// Option 2: Share as bytes (for QR codes)
let bytes = token.to_bytes()?;
// Option 3: Get compact fingerprint for verification
let token_fp = token.fingerprint();
// "1234-5678-9012"use lmp_core::device::token::IntroductionToken;
// From base64 string
let peer_token = IntroductionToken::from_base64(&received_string)?;
// Verify it's valid
peer_token.verify()?;
// Check it hasn't expired
if peer_token.is_expired() {
return Err("Token has expired");
}use lmp_core::easy::LmpClient;
use lmp_core::prelude::*;
// Alice's device
let mut alice = LmpClient::new()?;
// Bob's device
let mut bob = LmpClient::new()?;// Alice creates token and shares with Bob (out of band)
let alice_token = alice.get_introduction_token("dht://alice.example.com")?;
// Bob creates token and shares with Alice (out of band)
let bob_token = bob.get_introduction_token("dht://bob.example.com")?;// Alice initiates connection to Bob
let (session_id, client_hello) = alice.initiate_connection(&bob_token)?;
// Alice sends client_hello to Bob over network
let hello_bytes = client_hello.to_bytes()?;
send_to_bob(hello_bytes);
// Bob receives and responds
let received_hello = ClientHello::from_bytes(&received_bytes)?;
let (bob_session_id, server_hello) = bob.accept_connection(received_hello)?;
// Bob sends server_hello back to Alice
let response_bytes = server_hello.to_bytes()?;
send_to_alice(response_bytes);
// Alice completes connection
let received_response = ServerHello::from_bytes(&received_bytes)?;
alice.complete_connection(session_id, received_response)?;
// Both sessions are now established!// Alice sends a message
let message = b"Hello Bob! This is encrypted.";
let encrypted_bytes = alice.send_bytes(session_id, message)?;
// Send encrypted_bytes over your transport layer
send_to_bob(encrypted_bytes);
// Bob receives and decrypts
let decrypted = bob.receive_bytes(bob_session_id, &encrypted_bytes)?;
println!("Got: {}", String::from_utf8_lossy(&decrypted));// Bob replies
let reply = b"Hi Alice! Got your message.";
let encrypted_reply = bob.send_bytes(bob_session_id, reply)?;
send_to_alice(encrypted_reply);
// Alice decrypts reply
let decrypted_reply = alice.receive_bytes(session_id, &encrypted_reply)?;LMP is transport-agnostic. Here's how to integrate with common transports:
use tokio_tungstenite::tungstenite::Message;
async fn send_encrypted(
socket: &mut WebSocket,
client: &mut LmpClient,
session_id: SessionId,
plaintext: &[u8],
) -> Result<()> {
let encrypted = client.send_bytes(session_id, plaintext)?;
socket.send(Message::Binary(encrypted)).await?;
Ok(())
}
async fn receive_decrypted(
socket: &mut WebSocket,
client: &mut LmpClient,
session_id: SessionId,
) -> Result<Vec<u8>> {
if let Some(Ok(Message::Binary(data))) = socket.next().await {
client.receive_bytes(session_id, &data)
} else {
Err(Error::ConnectionFailed("No message received"))
}
}use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
async fn send_message(
stream: &mut TcpStream,
client: &mut LmpClient,
session_id: SessionId,
plaintext: &[u8],
) -> Result<()> {
let encrypted = client.send_bytes(session_id, plaintext)?;
// Send length prefix (4 bytes) + data
let len = (encrypted.len() as u32).to_be_bytes();
stream.write_all(&len).await?;
stream.write_all(&encrypted).await?;
Ok(())
}
async fn receive_message(
stream: &mut TcpStream,
client: &mut LmpClient,
session_id: SessionId,
) -> Result<Vec<u8>> {
// Read length prefix
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await?;
let len = u32::from_be_bytes(len_buf) as usize;
// Read message
let mut data = vec![0u8; len];
stream.read_exact(&mut data).await?;
client.receive_bytes(session_id, &data)
}use axum::{Json, extract::State};
#[derive(Deserialize)]
struct EncryptedRequest {
session_id: String, // hex-encoded
ciphertext: String, // base64-encoded
}
async fn handle_message(
State(client): State<Arc<Mutex<LmpClient>>>,
Json(req): Json<EncryptedRequest>,
) -> Result<Json<Vec<u8>>> {
let session_id = hex::decode(&req.session_id)?
.try_into()
.map_err(|_| Error::InvalidInput("Bad session ID"))?;
let ciphertext = base64::decode(&req.ciphertext)?;
let mut client = client.lock().await;
let plaintext = client.receive_bytes(session_id, &ciphertext)?;
Ok(Json(plaintext))
}use lmp_core::storage::keychain::FileKeyStore;
use std::path::Path;
// Create encrypted key store
let store = FileKeyStore::new(Path::new("./keys"), "your-secure-password")?;
// Save identity keys (automatically encrypted at rest)
store.store_key("identity_ed25519", &identity.ed25519_secret)?;
store.store_key("identity_kyber", &identity.kyber_secret)?;
// Load keys on app restart
let ed25519_key = store.load_key("identity_ed25519")?;// Rotate prekeys periodically (recommended: every 7-14 days)
client.rotate_prekey()?;
// Publish new token after rotation
let new_token = client.get_introduction_token("dht://your-mailbox.com")?;Keys are automatically zeroized when dropped. For explicit deletion:
// Delete specific key from storage
store.delete_key("old_key_name")?; // Securely overwrites before deletionLMP uses a comprehensive error type. Here's how to handle common scenarios:
use lmp_core::error::Error;
match client.receive_bytes(session_id, &data) {
Ok(plaintext) => {
// Success!
process_message(plaintext);
}
Err(Error::ReplayDetected) => {
// Someone tried to replay an old message
log::warn!("Replay attack detected!");
}
Err(Error::SessionNotEstablished) => {
// Need to complete handshake first
initiate_handshake();
}
Err(Error::DecryptionFailed(_)) => {
// Message was tampered with
log::error!("Message authentication failed");
}
Err(Error::NonceCounterOverflow) => {
// Need to perform ratchet (send any message to trigger)
force_ratchet();
}
Err(e) => {
// Other error
log::error!("Error: {}", e);
}
}Always verify identity fingerprints through a separate channel:
// Display for user verification
let my_fingerprint = client.fingerprint();
let peer_fingerprint = peer_token.fingerprint();
// Show in UI: "Verify these numbers match in person or via call"
println!("Your code: {}", my_fingerprint);
println!("Their code: {}", peer_fingerprint);// Check if rotation is needed
if client.identity().ltik_needs_rotation() {
// Trigger key rotation flow
}
if client.identity().mtsk_needs_rotation() {
client.identity_mut().rotate_mtsk()?;
}// Run periodically (e.g., daily)
client.cleanup();if peer_token.is_expired() {
return Err("Token expired - request a new one");
}
// Check remaining validity
let remaining = peer_token.remaining_validity();
if remaining < 86400 { // Less than 24 hours
log::warn!("Token expires soon");
}use rpassword::read_password;
print!("Enter keystore password: ");
let password = read_password()?;
// Password is zeroized when dropped
let store = FileKeyStore::new(path, &password)?;| Method | Description |
|---|---|
new() |
Create client with new identity |
from_identity(identity) |
Create from existing identity |
device_id() |
Get unique device ID |
fingerprint() |
Get human-readable verification code |
get_introduction_token(dht_addr) |
Create shareable token |
initiate_connection(token) |
Start handshake as initiator |
accept_connection(hello) |
Accept handshake as responder |
complete_connection(id, response) |
Complete initiated handshake |
send_bytes(id, plaintext) |
Encrypt and serialize message |
receive_bytes(id, ciphertext) |
Decrypt and deserialize message |
is_session_active(id) |
Check if session is established |
close_session(id) |
Securely close a session |
rotate_prekey() |
Rotate exchange keys |
cleanup() |
Clean old session data |
| Method | Description |
|---|---|
to_base64() |
Serialize for sharing |
from_base64(str) |
Parse base64 token |
to_bytes() |
Serialize to bytes |
from_bytes(bytes) |
Parse from bytes |
verify() |
Verify signature |
is_expired() |
Check if expired |
fingerprint() |
Get compact verification code |
remaining_validity() |
Seconds until expiration |
| Method | Description |
|---|---|
to_bytes() |
Serialize for transport |
from_bytes(bytes) |
Parse from bytes |
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────────┤
│ easy API │
│ ┌─────────────┐ ┌───────────────┐ ┌──────────────────────┐ │
│ │ LmpClient │ │ SecureChannel │ │ IntroductionToken │ │
│ └─────────────┘ └───────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ protocol │
│ ┌──────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │
│ │ Session │ │ Ratchet │ │Handshake│ │ ReplayProtection│ │
│ └──────────┘ └─────────┘ └─────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ crypto │
│ ┌─────────┐ ┌────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ X25519 │ │ Kyber │ │ Ed25519 │ │ Dilithium │ │
│ └─────────┘ └────────┘ └──────────┘ └──────────────────┘ │
│ ┌─────────────────────────┐ ┌────────────────────────────┐ │
│ │ ChaCha20-Poly1305 │ │ HKDF / Argon2id │ │
│ └─────────────────────────┘ └────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ storage │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ FileKeyStore│ │ Database │ │ ProtectedMemory │ │
│ └─────────────┘ └──────────────┘ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
lmp-core/
├── easy/ # High-level API (start here!)
│ ├── LmpClient # Main client interface
│ └── SecureChannel # Simple point-to-point
├── crypto/ # Cryptographic primitives
│ ├── x25519 # Classical DH
│ ├── kyber # Post-quantum KEM
│ ├── ed25519 # Classical signatures
│ ├── dilithium # Post-quantum signatures
│ ├── aead # ChaCha20-Poly1305
│ ├── hkdf # Key derivation
│ └── rng # Secure RNG with fork detection
├── protocol/ # Protocol implementation
│ ├── handshake # X3DH-style key agreement
│ ├── ratchet # Double Ratchet
│ ├── session # Session management
│ └── replay # Replay protection
├── device/ # Identity management
│ ├── identity # User identity
│ └── token # Introduction tokens
├── network/ # Network helpers
│ ├── mailbox # Offline storage
│ ├── padding # Traffic analysis resistance
│ └── cell # Fixed-size cells
└── storage/ # Secure storage
├── keychain # Encrypted key storage
├── database # SQLite for state
└── memory # Protected memory
Here's a minimal but complete example:
use lmp_core::prelude::*;
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> Result<()> {
// Initialize client
let mut client = LmpClient::new()?;
println!("Your fingerprint: {}", client.fingerprint());
// Create token for others to connect
let token = client.get_introduction_token("tcp://localhost:8080")?;
println!("Share this token:\n{}", token.to_base64()?);
// Wait for incoming connection or connect to peer...
// Once session is established:
let session_id = /* ... */;
// Message loop
loop {
// Send
let msg = get_user_input();
let encrypted = client.send_bytes(session_id, msg.as_bytes())?;
send_over_network(&encrypted).await?;
// Receive
let incoming = receive_from_network().await?;
let decrypted = client.receive_bytes(session_id, &incoming)?;
println!("Received: {}", String::from_utf8_lossy(&decrypted));
}
}- Issues: GitHub Issues
- Security: Report vulnerabilities privately to the maintainers
- Examples: See
/lmp-core/tests/for more usage examples
MIT OR Apache-2.0