
Mensageiro criptografado peer-to-peer em Rust com Noise IK e UDP NAT hole punching
Chat ponto a ponto criptografado de ponta a ponta via UDP. Sem contas, sem servidor central retransmitindo/armazenando mensagens, sem intermediário. Apenas dois pares, uma conexão direta e criptografia pelo protocolo Noise.
https://github.com/user-attachments/assets/939e96d3-45e3-4484-9a27-28c3a0457b05
Duas pessoas executam punchline connect <peer> em suas máquinas. O Punchline atravessa seus NATs, realiza um handshake criptografado e os coloca em um chat privado – tudo em alguns milissegundos. Os servidores STUN e de sinalização incluídos cuidam da descoberta e depois saem do caminho.

cargo build --release
Inicie os servidores (em uma máquina que ambos os pares possam acessar) ou use os meus públicos hospedados em 64.225.107.28 (STUN: porta 3478, sinalização: porta 8743):
punchline-stund # Servidor STUN - informa aos pares seu IP público
punchline-signald # Servidor de sinalização - combina pares que querem conversar
Em cada máquina dos pares:
# Gere sua identidade (par de chaves X25519)
punchline keygen
# Compartilhe sua chave pública com seu par
punchline pubkey
# Salve a chave dele
punchline peers add alice a1b2c3d4...64_hex_chars
# Conecte-se (ambos os pares executam isso, direcionando um ao outro)
punchline connect alice --stun <servidor>:3478 --signal <servidor>:8743
A TUI é iniciada com uma visualização do progresso da conexão ao vivo:
Descoberta STUN - resolvendo seu endereço externo via punchline-stund
Servidor de sinalização - conectando ao punchline-signald
Aguardando o par - o servidor de sinalização combina ambos os pares
Furação de NAT - estabelecendo o caminho UDP direto
Handshake Noise - troca de chaves criptografada
Quando concluído, você está no chat. Digite e pressione Enter. Pressione Esc para sair.
Todo o sistema consiste em três binários, todos incluídos neste repositório:
| Binário | Função | Quando usado |
|---|
Após a configuração inicial, os servidores STUN e de sinalização não são mais contatados. Tudo flui diretamente entre pares.
punchlineBandeiras globais:
| Bandeira | Descrição |
|---|---|
-v | Aumenta a verbosidade do log (-v = debug, -vv = trace). |
-q, --quiet | Suprime toda a saída de log. |
punchline-stundpunchline-signaldEm vez de passar --stun e --signal toda vez, crie ~/.config/punchline/config.toml:
stun_server = "203.0.113.10:3478"
signal_server = "203.0.113.10:8743"
punchline peers # listar todos
punchline peers add alice a1b2c3d4... # adicionar
punchline peers remove alice # remover
Os apelidos são armazenados em ~/.punchline/known_peers.toml. Você também pode conectar diretamente com uma chave hex bruta de 64 caracteres.
punchline status
Mostra sua identidade, configuração, alcançabilidade do servidor (envia uma sonda STUN real e conexão TCP) e número de pares.
Ambos os servidores suportam -v (debug), -vv (trace), -q (silencioso), --address e --port:
punchline-stund -v --port 3478
punchline-signald -v --port 8743
Personalize a TUI via ~/.config/punchline/style.toml
Estilos usados no vídeo:
[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
Todas as cores são RGB hex. Se o arquivo estiver ausente, as cores padrão do terminal são usadas.
punchline completions bash > ~/.local/share/bash-completion/completions/punchline
punchline completions zsh > ~/.zfunc/_punchline
punchline completions fish > ~/.config/fish/completions/punchline.fish
Nome completo do protocolo: Noise_IK_25519_ChaChaPoly_SHA256
| Componente | Função |
|---|---|
O padrão IK significa que o iniciador conhece a chave pública estática do respondedor antes do handshake começar. Ambos os pares já possuem as chaves um do outro (trocadas fora da banda ou via registro de pares), portanto nenhuma confiança no primeiro uso é necessária.
O Punchline determina deterministicamente o iniciador comparando os primeiros 8 bytes da chave pública de cada par como um u64 big-endian. O par com o menor valor se torna o iniciador. Ambos os lados calculam isso independentemente.
A identidade é uma chave secreta X25519 de 32 bytes em ~/.punchline/id_x25519 com permissões Unix 0600. A chave pública é derivada na carga. A geração de chaves usa x25519-dalek com OsRng.
O primeiro byte de cada pacote UDP identifica seu tipo:
Ambos os pares executam o mesmo algoritmo simultaneamente:
PROBE (0x00) a cada 200ms para o endereço externo do par.PROBE, alterne para enviar ACK (0x01).ACK, envie um ACK final e declare sucesso.Mensagens (0x02) carregam payloads UTF-8 criptografados com Noise. Keepalives (0x03) são payloads vazios criptografados enviados a cada 10 segundos para manter a sincronização do nonce da cifra. 30 segundos sem nenhum pacote aciona desconexão.
JSON sobre WebSocket:
// PairRequest (cliente -> servidor)
{ "external_addr": "203.0.113.5:48291", "public_key": "a1b2...", "target_public_key": "d4e5..." }
// PairResponse (servidor -> cliente)
{ "target_external_addr": "198.51.100.7:51003", "target_public_key": "d4e5..." }
Segue RFC 5389 (simplificado): requisição/resposta de binding com XOR-MAPPED-ADDRESS. Apenas IPv4.
Workspace Cargo com quatro crates:
crates/
├── proto/ # Biblioteca compartilhada: criptografia, tipos STUN e sinalização, trait de transporte
├── client/ # Cliente P2P: CLI, TUI, lógica de conexão, gerenciamento de pares
├── signald/ # Servidor de sinalização: combinação de pares via WebSocket
└── stund/ # Servidor STUN: descoberta de endereço externo
cargo install punchline # Cliente TUI
cargo install punchline-signald # Servidor de sinalização
cargo install punchline-stund # Servidor STUN
Pré-requisitos: Edição Rust 2024 (rustc 1.85+)
git clone https://github.com/michal-pielka/punchline.git
cd punchline
cargo build --release
Os binários são colocados em target/release/:
punchlinepunchline-signaldpunchline-stundcargo test
Os testes cobrem operações criptográficas, codificação/decodificação STUN, serialização do protocolo de sinalização, análise de configuração, gerenciamento de pares, temas de estilo e o handshake Noise IK.
MIT - veja LICENSE.
punchline-stund | Servidor STUN (UDP) - responde com o IP:porta externo do cliente | Apenas durante a configuração |
punchline-signald | Servidor de sinalização (WebSocket) - combina pares e troca endereços | Apenas durante a configuração |
punchline | O próprio mensageiro - CLI, TUI, criptografia, furação de NAT | Sempre |
| Comando | Descrição |
|---|
keygen [--force] [-i caminho] | Gera um novo par de chaves de identidade X25519. Use --force para sobrescrever sem confirmação. Use -i para especificar o caminho de saída. |
pubkey [-i caminho] | Imprime sua chave pública (64 caracteres hexadecimais). Use -i para derivar de um arquivo de chave específico. |
connect <par> [-i caminho] [--stun endereço] [--signal endereço] | Conecta-se a um par por apelido ou chave hex bruta. Use -i para especificar a chave de identidade. Inicia a TUI. |
peers | Lista todos os pares conhecidos. |
peers add <nome> <chave> | Salva a chave pública de um par sob um apelido. |
peers remove <nome> | Remove um par pelo apelido. |
config path | Exibe o caminho do arquivo de configuração. |
config show | Mostra os valores atuais da configuração. |
status | Mostra identidade, configuração, alcançabilidade do servidor e número de pares. |
completions <shell> | Gera completudes para o shell (bash, zsh ou fish). |
| Bandeira | Descrição |
|---|
--address <endereço> | Endereço de escuta (padrão: 0.0.0.0). |
--port <porta> | Porta de escuta (padrão: 3478). |
-v / -vv | Log debug / trace. |
-q | Modo silencioso. |
| Bandeira | Descrição |
|---|
--address <endereço> | Endereço de escuta (padrão: 0.0.0.0). |
--port <porta> | Porta de escuta (padrão: 8743). |
-v / -vv | Log debug / trace. |
-q | Modo silencioso. |
| Noise IK |
| Padrão de handshake - o iniciador conhece a chave pública do respondedor. Completa em 2 mensagens. |
| X25519 | Troca de chaves Diffie-Hellman de curva elíptica (RFC 7748). Segurança de 128 bits, tempo constante. |
| ChaCha20-Poly1305 | Cifra AEAD para criptografia de mensagens (RFC 8439). A mesma cifra usada no TLS 1.3 e WireGuard. |
| SHA-256 | Usado internamente pelo Noise para derivação de chaves e hashing do handshake. |
| Prefixo | Tipo | Fase | Descrição |
|---|
0x00 | PROBE | Furação de NAT | Enviado a cada 200ms para abrir pinhole NAT |
0x01 | ACK | Furação de NAT | Confirma recebimento de um PROBE |
| (nenhum) | Handshake | Handshake | Payload de handshake criptografado em bruto |
0x02 | Mensagem | Transporte | Mensagem de chat criptografada |
0x03 | Keepalive | Transporte | Payload vazio criptografado (batimento cardíaco) |
| Crate | Propósito |
|---|
snow | Framework do protocolo Noise (handshake + criptografia de transporte) |
x25519-dalek | Geração e derivação de chaves X25519 |
ratatui | Framework de interface de usuário no terminal |
crossterm | Manipulação de eventos do terminal |
clap | Análise de argumentos CLI + completudes para shell |
tungstenite | Cliente/servidor WebSocket |
tracing | Log estruturado |