
Messenger peer-to-peer crittografato in Rust con Noise IK e UDP NAT hole punching
Chat peer-to-peer crittografata end-to-end su UDP. Niente account, nessun server centrale che inoltra/memorizza messaggi, nessun intermediario. Solo due peer, una connessione diretta e crittografia con il protocollo Noise.
https://github.com/user-attachments/assets/939e96d3-45e3-4484-9a27-28c3a0457b05
Due persone eseguono punchline connect <peer> sulle loro macchine. Punchline buca i loro NAT, esegue un handshake crittografato e li porta in una chat privata – tutto in pochi millisecondi. I server STUN e di segnalazione inclusi gestiscono la scoperta, poi si mettono da parte.

cargo build --release
Avvia i server (su una macchina raggiungibile da entrambi i peer), oppure usa quelli pubblici che ospito su 64.225.107.28 (STUN: porta 3478, segnalazione: porta 8743):
punchline-stund # Server STUN – dice ai peer il loro IP pubblico
punchline-signald # Server di segnalazione – abbina i peer che vogliono parlare
Su ogni macchina peer:
# Genera la tua identità (coppia di chiavi X25519)
punchline keygen
# Condividi la tua chiave pubblica con il peer
punchline pubkey
# Salva la loro chiave
punchline peers add alice a1b2c3d4...64_caratteri_hex
# Connettiti (entrambi i peer eseguono questo, puntandosi l'un l'altro)
punchline connect alice --stun <server>:3478 --signal <server>:8743
La TUI si avvia con una vista dell'avanzamento della connessione in tempo reale:
Scoperta STUN – risoluzione del tuo indirizzo esterno tramite punchline-stund
Server di segnalazione – connessione a punchline-signald
Attesa del peer – il server di segnalazione abbina entrambi i peer
Hole punch – creazione del percorso UDP diretto
Handshake Noise – scambio di chiavi crittografato
Una volta completato, sei nella chat. Digita e premi Invio. Premi Esc per uscire.
L'intero sistema è composto da tre binari, tutti inclusi in questo repository:
| Binario | Ruolo | Quando viene usato |
|---|
Dopo la configurazione iniziale, i server STUN e di segnalazione non vengono più contattati. Tutto fluisce direttamente peer-to-peer.
punchlineFlag globali:
| Flag | Descrizione |
|---|---|
-v | Aumenta la verbosità del log (-v = debug, -vv = trace). |
-q, --quiet | Sopprime tutto l'output dei log. |
punchline-stundpunchline-signaldInvece di passare --stun e --signal ogni volta, crea ~/.config/punchline/config.toml:
stun_server = "203.0.113.10:3478"
signal_server = "203.0.113.10:8743"
punchline peers # elenca tutti
punchline peers add alice a1b2c3d4... # aggiungi
punchline peers remove alice # rimuovi
Gli alias sono memorizzati in ~/.punchline/known_peers.toml. Puoi anche connetterti direttamente con una chiave hex di 64 caratteri.
punchline status
Mostra la tua identità, configurazione, raggiungibilità del server (invia una vera sonda STUN e una connessione TCP) e numero di peer.
Entrambi i server supportano -v (debug), -vv (trace), -q (silenzioso), --address e --port:
punchline-stund -v --port 3478
punchline-signald -v --port 8743
Personalizza la TUI tramite ~/.config/punchline/style.toml
Stili usati nel video:
[colors]
my_text = "#ebdbb2"
peer_text = "#bdae93"
input_text = "#ebdbb2"
border = "#ebdbb2"
sidebar_key = "#ebdbb2"
sidebar_value = "#bdae93"
[padding]
chat_horizontal = 2
chat_vertical = 1
Tutti i colori sono RGB esadecimali. Se il file è assente, vengono usati i colori predefiniti del terminale.
punchline completions bash > ~/.local/share/bash-completion/completions/punchline
punchline completions zsh > ~/.zfunc/_punchline
punchline completions fish > ~/.config/fish/completions/punchline.fish
Nome completo del protocollo: Noise_IK_25519_ChaChaPoly_SHA256
| Componente | Ruolo |
|---|
Il pattern IK significa che l'iniziatore conosce la chiave pubblica statica del risponditore prima dell'inizio dell'handshake. Entrambi i peer hanno già le chiavi l'uno dell'altro (scambiate out-of-band o tramite il registro dei peer), quindi non è necessario alcun trust-on-first-use.
Punchline seleziona deterministicamente l'iniziatore confrontando i primi 8 byte della chiave pubblica di ciascun peer come u64 big-endian. Il peer con il valore minore diventa l'iniziatore. Entrambi i lati calcolano questo indipendentemente.
L'identità è una chiave segreta X25519 di 32 byte in ~/.punchline/id_x25519 con permessi Unix 0600. La chiave pubblica viene derivata al caricamento. La generazione delle chiavi utilizza x25519-dalek con OsRng.
Il primo byte di ogni pacchetto UDP identifica il suo tipo:
Entrambi i peer eseguono lo stesso algoritmo simultaneamente:
PROBE (0x00) ogni 200ms all'indirizzo esterno del peer.PROBE, passa all'invio di ACK (0x01).ACK, invia un ultimo ACK e dichiara successo.I messaggi (0x02) trasportano payload UTF-8 crittografati con Noise. I keepalive (0x03) sono payload vuoti crittografati inviati ogni 10 secondi per mantenere la sincronizzazione del nonce del cifrario. 30 secondi senza alcun pacchetto triggerano la disconnessione.
JSON su WebSocket:
// PairRequest (client -> server)
{ "external_addr": "203.0.113.5:48291", "public_key": "a1b2...", "target_public_key": "d4e5..." }
// PairResponse (server -> client)
{ "target_external_addr": "198.51.100.7:51003", "target_public_key": "d4e5..." }
Segue RFC 5389 (semplificato): richiesta/risposta di binding con XOR-MAPPED-ADDRESS. Solo IPv4.
Workspace Cargo con quattro crate:
crates/
├── proto/ # Libreria condivisa: crittografia, tipi STUN, segnalazione, trait di trasporto
├── client/ # Client P2P: CLI, TUI, logica di connessione, gestione peer
├── signald/ # Server di segnalazione: abbinamento peer via WebSocket
└── stund/ # Server STUN: scoperta indirizzo esterno
cargo install punchline # Client TUI
cargo install punchline-signald # Server di segnalazione
cargo install punchline-stund # Server STUN
Requisiti: Edizione Rust 2024 (rustc 1.85+)
git clone https://github.com/michal-pielka/punchline.git
cd punchline
cargo build --release
I binari sono posizionati in target/release/:
punchlinepunchline-signaldpunchline-stundcargo test
I test coprono operazioni crittografiche, codifica/decodifica STUN, serializzazione del protocollo di segnalazione, parsing della configurazione, gestione peer, temi di stile e handshake Noise IK.
MIT – consulta LICENSE.
punchline-stund | Server STUN (UDP) – risponde con l'IP:porta esterno del client | Solo durante la fase di configurazione |
punchline-signald | Server di segnalazione (WebSocket) – abbina i peer e scambia indirizzi | Solo durante la fase di configurazione |
punchline | Il messenger stesso – CLI, TUI, crittografia, hole punch | Sempre |
| Comando | Descrizione |
|---|
keygen [--force] [-i path] | Genera una nuova coppia di chiavi identità X25519. Usa --force per sovrascrivere senza richiesta. Usa -i per specificare il percorso di output. |
pubkey [-i path] | Stampa la tua chiave pubblica (64 caratteri esadecimali). Usa -i per derivare da un file di chiave specifico. |
connect <peer> [-i path] [--stun addr] [--signal addr] | Connettiti a un peer tramite alias o chiave hex grezza. Usa -i per specificare la chiave identità. Avvia la TUI. |
peers | Elenca tutti i peer conosciuti. |
peers add <name> <key> | Salva la chiave pubblica di un peer sotto un soprannome. |
peers remove <name> | Rimuovi un peer tramite soprannome. |
config path | Stampa il percorso del file di configurazione. |
config show | Mostra i valori correnti di configurazione. |
status | Mostra identità, configurazione, raggiungibilità del server e numero di peer. |
completions <shell> | Genera i completamenti per shell (bash, zsh o fish). |
| Flag | Descrizione |
|---|
--address <addr> | Indirizzo di bind (predefinito: 0.0.0.0). |
--port <port> | Porta di bind (predefinito: 3478). |
-v / -vv | Logging debug / trace. |
-q | Modalità silenziosa. |
| Flag | Descrizione |
|---|
--address <addr> | Indirizzo di bind (predefinito: 0.0.0.0). |
--port <port> | Porta di bind (predefinito: 8743). |
-v / -vv | Logging debug / trace. |
-q | Modalità silenziosa. |
| Noise IK | Pattern di handshake – l'iniziatore conosce la chiave pubblica del risponditore. Completa in 2 messaggi. |
| X25519 | Scambio di chiavi Diffie-Hellman su curva ellittica (RFC 7748). Sicurezza a 128 bit, tempo costante. |
| ChaCha20-Poly1305 | Cifrario AEAD per la crittografia dei messaggi (RFC 8439). Lo stesso cifrario usato in TLS 1.3 e WireGuard. |
| SHA-256 | Usato internamente da Noise per la derivazione delle chiavi e l'hashing dell'handshake. |
| Prefisso | Tipo | Fase | Descrizione |
|---|
0x00 | PROBE | Hole punch | Inviato ogni 200ms per aprire un foro nel NAT |
0x01 | ACK | Hole punch | Conferma la ricezione di un PROBE |
| (nessuno) | Handshake | Handshake | Payload di handshake crittografato grezzo Noise |
0x02 | Messaggio | Trasporto | Messaggio chat crittografato |
0x03 | Keepalive | Trasporto | Payload vuoto crittografato (heartbeat) |
| Crate | Scopo |
|---|
snow | Framework protocollo Noise (handshake + crittografia del trasporto) |
x25519-dalek | Generazione e derivazione chiavi X25519 |
ratatui | Framework per interfaccia terminale |
crossterm | Gestione eventi del terminale |
clap | Parsing argomenti CLI + completamenti per shell |
tungstenite | Client/server WebSocket |
tracing | Logging strutturato |