
Seal v0.2.3
P2P, 종단 간 암호화 채팅. 받은편지함 없음. 복구할 계정 없음. 아무도 듣지 않습니다 — 우리조차도요.
메시지는 libp2p를 통해 피어 간에 직접 전송되며, 기기를 떠나기 전에 Signal 스타일의 Olm/Megolm 프로토콜(vodozemac을 통해)로 암호화됩니다. 관여하는 유일한 서버는 피어가 서로의 현재 주소를 찾도록 도와주는 작은 디렉터리입니다. 이 서버는 메시지 내용을 절대 볼 수 없으며, 한 번의 명령으로 제거할 수 있습니다.
실제로 무엇으로부터 보호되는지와 그 방법은 docs/THREAT_MODEL.md 및 docs/SECURITY.md를 참조하세요.
Contents
스크린샷
첫 실행 — 이름을 정하세요. 설정할 것은 그것뿐입니다.
대화 — 그룹 레일, 연락처 목록, 종단 간 암호화된 채팅 창.
설정 — 마이크 감도, 푸시 투 토크, 로그인 시 실행, 네트워크 연결 가능 여부.
기능
- 항상 종단 간 암호화 — 모든 메시지는 기기를 떠나기 전에 Olm(1:1) 또는 Megolm(그룹)으로 봉인되며, vodozemac의 Double-Ratchet 방식 체계를 사용합니다. 모든 메시지는 고유한 키를 갖습니다.
- 받은 편지함 없음 — 메시지는 직접적인 피어 투 피어 연결(libp2p: QUIC/TCP + Noise, NAT용 릴레이 및 홀 펀칭 포함)을 통해 전송됩니다. 수신자가 오프라인이면 메시지는 로컬에서 대기하고 재시도됩니다. 다른 사람의 인프라에 큐에 쌓이지 않습니다.
- 데이터베이스가 아닌 디렉터리 — 관여하는 유일한 서버(
crates/directory-server)는 사용자 ID를 현재 네트워크 주소에 매핑할 뿐 그 이상은 하지 않습니다. 구조적으로 메시지 내용을 읽을 수 없습니다.Cargo.toml은 그 방법을 아는 크레이트조차 의존하지 않습니다. - 하나의 기기, 여러 계정 — 재시작 없이 전환할 수 있는 완전히 분리된 신원(키, 연락처, 메시지)을 지원합니다.
- 실제 멤버십 변경이 있는 그룹 — 그룹별 텍스트 및 음성 채널. 누군가를 제거하면 그룹의 키가 순환되어 이후 전송되는 어떤 내용도 읽을 수 없습니다.
- 내장 음성 기능 — 시스템 전체 단축키로 푸시 투 토크(Seal뿐 아니라 모든 앱에서 작동), 조절 가능한 마이크 감도, 선택적 음성 변조기.
- 메타데이터 없는 첨부 파일 — EXIF 데이터(GPS 위치, 카메라/기기 정보)는 이미지가 전송되기 전에 제거되며 기본적으로 활성화되어 있습니다.
- 진짜 패닉 버튼 — 설정 → 데이터 및 개인정보 보호에서 이 기기의 모든 키, 연락처, 메시지를 즉시 그리고 되돌릴 수 없게 삭제하며, 대화 상대에게는 전혀 영향을 주지 않습니다.
- 원한다면 로그인 시 실행 — 기본적으로 켜져 있으며 설정에서 한 번의 토글로 끌 수 있습니다.
- 하나의 코드베이스, 세 플랫폼 — Tauri를 통해 macOS, Windows, Linux에서 네이티브 창을 제공합니다.
작동 방식
이 앱에는 두 종류의 신원이 있으며, 의도적으로 분리되어 있습니다:
- 채팅 신원은 앱을 처음 열 때 vodozemac이 로컬에서 생성하는 Ed25519/Curve25519 키 쌍입니다(
identity::Identity). 공개 "사용자 ID"는 그 키의 지문일 뿐입니다(wire_proto::user_id_from_ed25519). 생성에 서버가 관여하지 않기 때문에 어떤 서버도 이를 발급하거나 취소할 수 없습니다. - 네트워크 신원은 전송 계층에서만 사용되는 별도의 libp2p 키 쌍(
PeerId)입니다. 재시작 후에도 변경될 수 있으며 채팅 신원에는 전혀 영향을 주지 않습니다. 둘은 사용자가 직접 서명한 프레즌스 레코드로만 묶여 있습니다.
누군가를 찾는 것과 실제로 대화하는 것은 서로 다른 두 단계입니다:``` ┌────────────────────────┐ │ directory server │ │ (axum + one SQLite │ │ file: users, │ │ presence, group │ │ rosters. Never │ │ message content.) │ └─────────┬───────────────┘ 1. "where is bob │ 2. "here's my current right now?" │ address" (signed, │ expires in minutes) ┌─────────┴───────────────┐ ▼ ▼ ┌───────┐ 3. direct libp2p ┌───────┐ │ alice │◄──── connection ────►│ bob │ └───────┘ (Noise + Olm/ └───────┘ Megolm encrypted)
1. Alice는 디렉터리에서 Bob의 사용자 ID로 그를 찾는다. 그러면 Bob의
공개 키와 마지막으로 알린 네트워크 주소가 반환된다. 디렉터리가
보관하는 것은 그것이 전부다: 공개 키, 표시 이름, 그룹
멤버십 목록, 그리고 수명이 짧은 주소 알림
(`crates/directory-server`).
2. Alice는 libp2p(QUIC 또는 TCP+Noise, NAT 뒤에 있는 피어를 위한 릴레이 +
홀 펀칭 지원; `crates/net` 참조)를 통해 Bob에게 직접 연결한다.
디렉터리는 이 시점부터 완전히 관여하지 않는다.
3. 실제 메시지는 libp2p 연결에 올려지기 전에 1:1 채팅용 **Olm** 또는
그룹용 **Megolm**(`crates/crypto-session`)으로 암호화된다. 이는
모든 메시지가 고유한 키를 갖는 Double-Ratchet 방식의 기법이다.
서버 측 받은편지함은 없다: Bob이 오프라인이면 메시지는 로컬에서
대기하고 재시도되며, 다른 누군가의 인프라에 저장되지 않는다.
위의 모든 것은 Tauri 앱의 Rust 백엔드(`apps/desktop/src-tauri`)가 실제로
호출하는 `crates/core`의 `AppService`에 의해 조율된다. UI는
네트워크와 직접 통신하지 않는다.