端到端加密的点对点聊天,基于UDP。无需账户、无需中央服务器中转/存储消息、无需中间人。只有两个对等点、一条直连链路,以及Noise协议加密。
https://github.com/user-attachments/assets/939e96d3-45e3-4484-9a27-28c3a0457b05
两个人分别在各自的机器上运行 punchline connect <peer>。Punchline 会穿透 NAT、执行加密握手,然后进入私密聊天——整个过程仅需几毫秒。内置的 STUN 和信令服务器负责发现,然后功成身退。

cargo build --release
启动服务器(在两台对等点都能访问的机器上),或者使用我在 64.225.107.28 运行的公共服务器(STUN: 端口 3478,信令: 端口 8743):
punchline-stund # STUN服务器——告知对等点其公网IP
punchline-signald # 信令服务器——匹配想要通信的对等点
在每个对等点的机器上:
# 生成身份(X25519密钥对)
punchline keygen
# 与对等点分享你的公钥
punchline pubkey
# 保存对方的密钥
punchline peers add alice a1b2c3d4...64_hex_chars
# 连接(双方都运行该命令,目标设为对方)
punchline connect alice --stun <server>:3478 --signal <server>:8743
TUI 启动后会显示实时连接进度视图:
STUN 探测——通过 punchline-stund 获取你的外网地址
信令服务器——连接到 punchline-signald
等待对等点——信令服务器匹配双方
打洞——建立直接 UDP 路径
Noise 握手——加密密钥交换
完成后即可进入聊天。输入文字并按回车键发送。按 Esc 键退出。
整个系统由三个二进制文件组成,全部包含在此仓库中:
| 二进制文件 | 角色 | 使用时机 |
|---|---|---|
punchline-stund | STUN服务器(UDP)——返回客户端的公网IP:端口 | 仅在建立连接时使用 |
punchline-signald | 信令服务器(WebSocket)——匹配对等点并交换地址 | 仅在建立连接时使用 |
punchline | 信使本身——CLI、TUI、加密、打洞 | 始终运行 |
建立连接后,不再联系 STUN 和信令服务器。所有通信直接点对点进行。
punchline| 命令 | 描述 |
|---|---|
keygen [--force] [-i path] | 生成新的 X25519 身份密钥对。使用 --force 可跳过提示直接覆盖。使用 -i 指定输出路径。 |
pubkey [-i path] | 打印你的公钥(64 个十六进制字符)。使用 -i 从特定密钥文件派生。 |
connect <peer> [-i path] [--stun addr] [--signal addr] | 通过别名或原始十六进制密钥连接到对等点。使用 -i 指定身份密钥。启动 TUI。 |
peers | 列出所有已知对等点。 |
peers add <name> <key> | 保存对等点的公钥并赋予昵称。 |
peers remove <name> | 按昵称删除对等点。 |
config path | 打印配置文件路径。 |
config show | 显示当前配置值。 |
status | 显示身份、配置、服务器可达性和对等点数量。 |
completions <shell> | 生成 Shell 补全(bash、zsh 或 fish)。 |
全局标志:
| 标志 | 描述 |
|---|---|
-v | 增加日志详细程度(-v = debug,-vv = trace)。 |
-q, --quiet | 禁止所有日志输出。 |
punchline-stund| 标志 | 描述 |
|---|---|
--address <addr> | 绑定地址(默认:0.0.0.0)。 |
--port <port> | 绑定端口(默认:3478)。 |
-v / -vv | 调试/跟踪日志。 |
-q | 静默模式。 |
punchline-signald| 标志 | 描述 |
|---|---|
--address <addr> | 绑定地址(默认:0.0.0.0)。 |
--port <port> | 绑定端口(默认:8743)。 |
-v / -vv | 调试/跟踪日志。 |
-q | 静默模式。 |
如果不想每次都传递 --stun 和 --signal,可以创建 ~/.config/punchline/config.toml:
stun_server = "203.0.113.10:3478"
signal_server = "203.0.113.10:8743"
punchline peers # 列出所有
punchline peers add alice a1b2c3d4... # 添加
punchline peers remove alice # 删除
别名存储在 ~/.punchline/known_peers.toml 中。你也可以直接使用 64 字符的十六进制密钥进行连接。
punchline status
显示你的身份、配置、服务器可达性(发送真实的 STUN 探测和 TCP 连接)以及对等点数量。
两个服务器都支持 -v(debug)、-vv(trace)、-q(quiet)、--address 和 --port:
punchline-stund -v --port 3478
punchline-signald -v --port 8743
通过 ~/.config/punchline/style.toml 自定义 TUI
视频中使用的样式:
[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
所有颜色均为十六进制 RGB。若文件不存在,则使用终端的默认颜色。
punchline completions bash > ~/.local/share/bash-completion/completions/punchline
punchline completions zsh > ~/.zfunc/_punchline
punchline completions fish > ~/.config/fish/completions/punchline.fish
完整协议名称:Noise_IK_25519_ChaChaPoly_SHA256
| 组件 | 作用 |
|---|---|
| Noise IK | 握手模式——发起者已知响应者的公钥。2 条消息完成握手。 |
| X25519 | 椭圆曲线 Diffie-Hellman 密钥交换(RFC 7748)。128 位安全性,常数时间。 |
| ChaCha20-Poly1305 | 消息加密的 AEAD 密码(RFC 8439)。与 TLS 1.3 和 WireGuard 使用相同的密码。 |
| SHA-256 | Noise 内部用于密钥派生和握手哈希。 |
IK 模式意味着发起者在握手开始之前就知道响应者的静态公钥。双方之间已经拥有对方的密钥(通过带外方式或对等点注册表交换),因此不需要“首次信任”。
Punchline 通过比较每个对等点公钥的前 8 个字节(作为大端 u64)来确定发起者。值较小的对等点成为发起者。双方独立计算此值。
身份是一个 32 字节的 X25519 私钥,保存在 ~/.punchline/id_x25519,Unix 权限为 0600。公钥在加载时派生。密钥生成使用 x25519-dalek 配合 OsRng。
每个 UDP 数据包的第一个字节标识其类型:
| 前缀 | 类型 | 阶段 | 描述 |
|---|---|---|---|
0x00 | PROBE | 打洞 | 每 200ms 发送一次以打开 NAT 针孔 |
0x01 | ACK | 打洞 | 确认收到 PROBE |
| (无) | Handshake | 握手 | 原始 Noise 加密的握手载荷 |
0x02 | Message | 传输 | 加密的聊天消息 |
0x03 | Keepalive | 传输 | 加密的空载荷(心跳) |
双方同时执行相同的算法:
PROBE(0x00)。PROBE 后,切换为发送 ACK(0x01)。ACK 后,发送最后一个 ACK 并宣告成功。消息(0x02)携带 Noise 加密的 UTF-8 载荷。心跳(0x03)是加密的空载荷,每 10 秒发送一次以维持密码随机数同步。30 秒没有任何数据包则触发断开连接。
基于 WebSocket 的 JSON:
// PairRequest (客户端 -> 服务器)
{ "external_addr": "203.0.113.5:48291", "public_key": "a1b2...", "target_public_key": "d4e5..." }
// PairResponse (服务器 -> 客户端)
{ "target_external_addr": "198.51.100.7:51003", "target_public_key": "d4e5..." }
遵循 RFC 5389(简化版):绑定请求/响应,包含 XOR-MAPPED-ADDRESS。仅 IPv4。
Cargo 工作区,包含四个 crate:
crates/
├── proto/ # 共享库:加密、STUN、信令类型、传输 trait
├── client/ # P2P 客户端:CLI、TUI、连接逻辑、对等点管理
├── signald/ # 信令服务器:WebSocket 对等点匹配
└── stund/ # STUN 服务器:外网地址发现
cargo install punchline # TUI 客户端
cargo install punchline-signald # 信令服务器
cargo install punchline-stund # STUN 服务器
前置要求: Rust 2024 edition(rustc 1.85+)
git clone https://github.com/michal-pielka/punchline.git
cd punchline
cargo build --release
二进制文件位于 target/release/:
punchlinepunchline-signaldpunchline-stundcargo test
测试覆盖加解密操作、STUN 编解码、信令协议序列化、配置解析、对等点管理、样式主题以及 Noise IK 握手。
| Crate | 用途 |
|---|---|
snow | Noise 协议框架(握手 + 传输加密) |
x25519-dalek | X25519 密钥生成和派生 |
ratatui | 终端 UI 框架 |
crossterm | 终端事件处理 |
clap | CLI 参数解析 + Shell 补全 |
tungstenite | WebSocket 客户端/服务器 |
tracing | 结构化日志 |
MIT - 参见 LICENSE。