Censorship-resistant, end-to-end encrypted voice, video, and messaging over Nostr.
100% open source. Zero server-side trust. Every line of code is auditable.
pubkey (NIP-59 gift wrap). Relay operators cannot determine sender or recipient.@noble/curves for pure-JS crypto with the same protocol.| Path | Purpose | Lines |
|---|---|---|
crates/nostrcomm-crypto/src/lib.rs | WASM entry -- 20+ exported functions | ~500 |
crates/nostrcomm-crypto/src/identity.rs | Key derivation (PBKDF2+HKDF), device certificates | ~500 |
crates/nostrcomm-crypto/src/envelope.rs | NIP-59 sealed sender (X25519 + ML-KEM-768 hybrid) | ~300 |
crates/nostrcomm-crypto/src/ratchet.rs | Signal Double Ratchet | ~350 |
crates/nostrcomm-crypto/src/pairing.rs | Emoji pairing codes (45-emoji alphabet) | ~200 |
web/src/main.js | App shell, screen routing, inbox handler, group chat | ~2500 |
web/src/vault.js | IndexedDB encrypted storage (AES-GCM) | ~400 |
web/src/relay.js | Multi-relay Nostr pub/sub manager | ~170 |
web/src/call.js | WebRTC call manager (1:1 voice/video) | ~310 |
web/src/group-call.js | Mesh P2P group voice calls | ~220 |
web/src/group-crypto.js | Group E2E encryption (AES-GCM session keys) | ~95 |
web/src/p2p.js | WebRTC DataChannel for direct P2P messaging | ~420 |
web/src/radio.js | Ultra-low-bandwidth voice (8-12 kbps Opus) | ~300 |
web/src/apps/sdk.js | Apps SDK v2 (sandbox interface for apps) | ~160 |
web/src/apps/chess.js | Chess game (full rules + spectator mode) | ~660 |
web/src/apps/sketch.js | Real-time collaborative whiteboard | ~390 |
web/src/apps/location.js | GPS sharing over OSM tiles | ~550 |
CBOR-encoded, Ed25519-signed certificates with:
device_pk -- Ed25519 verifying key (32 bytes)nostr_pk -- secp256k1 BIP-340 x-only pubkey (32 bytes)encryption_pk -- X25519 public key (32 bytes)kem_pk -- ML-KEM-768 encapsulation key (1184 bytes)tier -- 0=self-signed, 1=direct contact, 2=friend-of-friendcapabilities -- { text, voice, video, file }issued_at, expires_at -- Unix timestampsA person may use multiple devices (phone, laptop, tablet), each with different keys. The "merge" feature links multiple nostrPkHex values to one display name. When you send a message to a merged contact, it's encrypted and delivered to all their devices independently.
45-emoji alphabet, visually distinct and cross-platform consistent:
🌙 ⚡ 🐬 🍀 🦋 🌊 🔥 🌸 ⭐ 🦁 🐉 🎈 🍎 🌈 🦅 🏔 🌺 🐢 🎵 🦊 🍄 🌻 🐋 🎯 🔮 🌴 🐝 🏪 🦩 🌮 🐠 🌿 🎭 🦄 🌝 🍋 🐧 🎸 🍇 🐺 🌾 🎨 🦜 🐙 🧊
6 emoji = 456 = ~8.3 billion combinations. One-time use, 24h expiry.
| Kind | Purpose | Encrypted |
|---|---|---|
| 14 | Direct message (inner event) | Inside NIP-59 |
| 1059 | Gift wrap (NIP-59 outer envelope) | Outer layer |
| 10050 | Pairing offer | Signed, public |
| 14100 | Group message (inner, with session key) | Inside NIP-59 |
| 25000 | WebRTC signal (SDP/ICE) | Inside NIP-59 |
| 25001 | Certificate chain | Inside NIP-59 |
| 25002 | P2P DataChannel signal | Inside NIP-59 |
| 25003 | Radio voice signal | Inside NIP-59 |
| 25100 | Group call signal | Inside NIP-59 |
All data encrypted with AES-256-GCM using the vault key derived from identity.
| IDB Store | Key | Contains |
|---|---|---|
| identity | 'self' / 'all' | Device identity (public keys, salt, label) |
| contacts | nostrPkHex | Encrypted contact (x25519, kem, ed25519 keys, label, tier) |
| sessions | peerNostrPkHex | Double Ratchet state per peer |
| messages | id | Encrypted messages (indexed by peer and time) |
| groups | groupId | Group metadata (name, members, keys) |
| merged_contacts | mergedId | Identity merge records (displayName, deviceKeys) |
| Primitive | Library | Use |
|---|---|---|
| secp256k1 (Schnorr BIP-340) | k256 / libsecp256k1 | Nostr event signing |
| X25519 | x25519-dalek | ECDH key agreement |
| Ed25519 | ed25519-dalek | Device certificate signatures |
| ML-KEM-768 | ml-kem | Post-quantum key encapsulation |
| ChaCha20-Poly1305 | chacha20poly1305 | AEAD encryption (messages, ratchet) |
| AES-256-GCM | Web Crypto API | Local vault encryption, group session keys |
| SHA-256 | sha2 | Hashing, event IDs |
| HKDF-SHA256 | hkdf | Key derivation from shared secrets |
| PBKDF2-SHA256 | pbkdf2 | PIN stretching (600,000 rounds) |
| Argon2 | argon2 | Password hashing (planned) |
stun:stun.l.google.com:19302AudioContext -- each remote stream connected to shared destination| Feature | WASM (index.html) | Lite (lite.html) |
|---|---|---|
| Crypto engine | Rust/WASM | @noble/curves (JS) |
| ML-KEM-768 | Yes | No |
| Interoperable | Yes -- hybrid encryption falls back to classical when KEM key absent | |
| Bundle size | ~550 KB WASM + ~100 KB JS | ~128 KB JS |
| Works offline | Yes -- Service Worker caches all assets | |
| What | Relay sees | Why |
|---|---|---|
| Message plaintext | Nothing | ChaCha20-Poly1305 AEAD |
| Real sender pubkey | Nothing | NIP-59: fresh ephemeral key per message |
| Real recipient pubkey | Nothing | Recipient address inside encrypted envelope |
| Social graph | Nothing | No persistent keys in outer events |
| Call initiation | Nothing | SDP offers are sealed events, identical to messages |
| Message timing | Approximate | Timestamps randomized +/- 5 minutes |
| Number of messages | Yes | Cannot be hidden without mix network |
MIT License. Built for privacy.