Skip to content

Latest commit

 

History

History
636 lines (481 loc) · 19.5 KB

File metadata and controls

636 lines (481 loc) · 19.5 KB

LMP Protocol Implementation Guide

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.


Table of Contents

  1. Quick Start
  2. Installation
  3. Core Concepts
  4. Basic Usage
  5. Full Messaging Integration
  6. Network Transport
  7. Key Management
  8. Error Handling
  9. Security Best Practices
  10. API Reference
  11. Architecture Overview

Quick Start

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);

Installation

Add to Cargo.toml

[dependencies]
lmp-core = { git = "https://github.com/AaryanBansal-Dev/LMP_Lattice-Mesh-Protocol" }

Features

[dependencies]
lmp-core = { 
    git = "https://github.com/AaryanBansal-Dev/LMP_Lattice-Mesh-Protocol",
    features = ["paranoid"]  # Extra entropy mixing
}

System Requirements

  • Rust 1.70+
  • OpenSSL development headers (for some platforms)
  • ~50MB RAM minimum for cryptographic operations

Core Concepts

Identity

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

Sessions

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

Introduction tokens are shareable credentials containing public keys. Share these via:

  • QR codes
  • Deep links
  • Any out-of-band channel

Basic Usage

Creating a Client

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"

Creating Introduction Tokens

// 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"

Importing a Peer's Token

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");
}

Full Messaging Integration

Step 1: Initialize Both Clients

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()?;

Step 2: Exchange Introduction Tokens

// 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")?;

Step 3: Perform Handshake

// 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!

Step 4: Send Encrypted Messages

// 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));

Step 5: Bidirectional Messaging

// 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)?;

Network Transport

LMP is transport-agnostic. Here's how to integrate with common transports:

WebSocket Example

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"))
    }
}

TCP Example

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)
}

HTTP API Example

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))
}

Key Management

Persisting Identity

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")?;

Key Rotation

// 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")?;

Secure Deletion

Keys are automatically zeroized when dropped. For explicit deletion:

// Delete specific key from storage
store.delete_key("old_key_name")?;  // Securely overwrites before deletion

Error Handling

LMP 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);
    }
}

Security Best Practices

1. Verify Fingerprints Out-of-Band

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);

2. Rotate Keys Regularly

// 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()?;
}

3. Clean Up Old Data

// Run periodically (e.g., daily)
client.cleanup();

4. Handle Token Expiration

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");
}

5. Secure Password Input

use rpassword::read_password;

print!("Enter keystore password: ");
let password = read_password()?;

// Password is zeroized when dropped
let store = FileKeyStore::new(path, &password)?;

API Reference

LmpClient

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

IntroductionToken

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

ClientHello / ServerHello

Method Description
to_bytes() Serialize for transport
from_bytes(bytes) Parse from bytes

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                        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     │  │
│   └─────────────┘  └──────────────┘  └───────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Module Hierarchy

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

Example: Complete Messaging App

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));
    }
}

Getting Help

  • Issues: GitHub Issues
  • Security: Report vulnerabilities privately to the maintainers
  • Examples: See /lmp-core/tests/ for more usage examples

License

MIT OR Apache-2.0