Qight is a lightweight, secure messaging relay built on QUIC (HTTP/3) for low-latency, authenticated communication. It enables clients to send and receive ephemeral messages through a central relay server, with end-to-end signing for authenticity. Perfect for IoT, decentralized apps, or secure event-driven systems.
- QUIC Transport: Fast, encrypted connections over UDP using TLS 1.3.
- Message Signing: Ed25519-based signatures ensure message authenticity and prevent tampering.
- SQLite Storage: Persistent message storage with TTL-based expiration.
- Service Discovery: Automatic relay discovery via mDNS (Bonjour/Avahi).
- Offline Queuing: Clients queue messages locally when disconnected.
- Async Architecture: Built with Tokio for high concurrency.
- Cross-Platform: Runs on Linux, macOS, Windows.
- Rust 1.70+
- SQLite (bundled via
rusqlite)
[dependencies]
qight = { git = "https://github.com/idorocodes/qight.git"}git clone https://github.com/idorocodes/qight.git
cd qight
cargo build --releasecargo run --bin relayThe server listens on 127.0.0.1:4433 and advertises via mDNS.
cargo run --bin qight_demoThis connects, sends a signed message, and fetches it back.
use qight::{RelayClient, MessageEnvelope};
use qight::gen_keypair;
use mdns_sd::{ServiceDaemon, ServiceEvent};
use std::net::IpAddr;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Discover relay via mDNS
let mdns = ServiceDaemon::new()?;
let service_type = "_qight._udp.local.";
let receiver = mdns.browse(service_type)?;
let addr = loop {
if let Ok(ServiceEvent::ServiceResolved(info)) = receiver.recv_async().await {
if let Some(addr) = info.get_addresses_v4().first() {
break SocketAddr::new(IpAddr::V4(*addr), info.get_port());
}
}
};
// Connect to relay
let client = RelayClient::connect(addr).await?;
// Say hello
client.hello("my-client").await?;
// Generate keys
let (recipient_key, _) = gen_keypair();
let (sender_pub, sender_priv) = gen_keypair();
// Create and sign a message
let mut envelope = MessageEnvelope::new(
"alice".to_string(),
recipient_key,
sender_pub,
b"Hello, world!".to_vec(),
3600, // 1 hour TTL
);
envelope.sign(&sender_priv);
// Send it
client.send(&envelope).await?;
// Fetch messages for recipient
let messages = client.fetch(&hex::encode(recipient_key)).await?;
for msg in messages {
println!("From {}: {}", msg.sender, String::from_utf8_lossy(&msg.payload));
}
client.close(Some("done")).await;
Ok(())
}cargo run --bin relayThe server listens on 127.0.0.1:4433 (change to 0.0.0.0:4433 for network access) and handles connections.
- Relay Server (
relaybinary): Central hub handling connections, storage, and message routing. - Client Library (
qightcrate): API for connecting, sending, and fetching messages. - Message Envelope: Structured message format with signing.
- Key Management: Ed25519 utilities for signing/verification.
sequenceDiagram
participant Client
participant Relay
participant DB
Client->>Relay: Connect via QUIC (mDNS discovery)
Client->>Relay: HELLO <client_id>
Relay-->>Client: Welcome response
Client->>Client: Generate keys & sign message
Client->>Relay: SEND <envelope>
Relay->>Relay: Verify signature
alt Signature valid
Relay->>DB: Store message
Relay-->>Client: OK
else Signature invalid
Relay-->>Client: ERROR: Invalid signature
end
Client->>Relay: FETCH <recipient_hex>
Relay->>DB: Query messages for recipient
DB-->>Relay: Return messages
Relay->>Relay: Delete fetched messages
Relay-->>Client: Messages (length-prefixed stream)
graph TB
subgraph "Client Side"
A[Client App] --> B[mDNS Discovery]
A --> C[Key Generation<br/>Ed25519]
A --> D[Message Signing]
A --> E[QUIC Client<br/>Connection]
end
subgraph "Network"
F[QUIC/TLS 1.3<br/>Encrypted Channel]
end
subgraph "Relay Server"
G[QUIC Server<br/>Endpoint] --> H[Signature<br/>Verification]
H --> I[SQLite Storage<br/>with TTL]
G --> J[Command Parser<br/>HELLO/SEND/FETCH]
J --> K[Message Routing]
end
B --> F
E --> F
F --> G
H --> I
K --> I
style A fill:#e1f5fe
style G fill:#fff3e0
style I fill:#f3e5f5
- SQLite Database:
quic.dbfor messages,qight_outbox.dbfor client queues. - Expiration: Messages auto-delete after TTL.
connect(addr: SocketAddr): Connect to relay at address.hello(client_id: &str): Handshake.send(envelope: &MessageEnvelope): Send signed message.fetch(recipient_hex: &str): Fetch messages for recipient (hex-encoded key).close(reason: Option<&str>): Disconnect.
new(sender, recipient, sender_key, payload, ttl): Create envelope.sign(&mut self, private_key): Sign payload.verify(&self): Verify signature.to_bytes()/from_bytes(bytes): Serialize/deserialize.
gen_key(): Random 32-byte key.gen_keypair(): (public, private) Ed25519 keys.sign_message(priv, msg): Sign bytes.verify_message(pub, msg, sig): Verify signature.
- Transport Security: QUIC with TLS 1.3 (self-signed certs for testing).
- Message Authenticity: Ed25519 signatures prevent tampering.
- Key Management: Clients handle keys; relay doesn't store them.
- Denial of Service: Basic rate limiting recommended for production.
Warning: Use strong keys and avoid self-signed certs in production. Implement authentication for real deployments.
Run tests:
cargo testIncludes unit tests for signing, serialization, DB ops, and integration tests.
- Fork the repo.
- Create a feature branch:
git checkout -b feature-name. - Make changes, add tests.
- Run
cargo fmtandcargo clippy. - Submit a PR.
MIT License. See LICENSE for details.
Built with ❤️ in Rust. Questions? Open an issue!