How devices discover each other, establish trust, and exchange messages.
16-word mnemonic phrase (BIP-39 style) encodes the salt. With the same PRF output + PIN + salt, identical keys are derived. The mnemonic does NOT contain the PRF output -- the passkey authenticator is still required.
Generate random AES-256 session key. Encrypt plaintext once with AES-GCM. For each group member: seal the (groupId + sessionKey + ciphertext) individually. Publish N sealed events (one per member). Each member decrypts their event, extracts session key, decrypts message.
AudioContext to a single outputOne person may use multiple devices, each with different keys. Users can manually merge contacts that represent the same person.
| Aspect | Behavior |
|---|---|
| Storage | merged_contacts IndexedDB store: { mergedId, displayName, deviceKeys } |
| Contact list | One entry per merged identity |
| Chat view | Messages from all device keys, merge-sorted by time |
| Sending | Encrypts and delivers to all device keys independently |
| Unread counts | Aggregated across all device keys |
Default relays (hardcoded, used on first connection):
wss://relay.damus.io wss://nos.lol wss://relay.snort.social wss://nostr.wine wss://relay.nostr.band ... (21+ relays for redundancy)
The relay manager connects to all relays in parallel, publishes to all, and subscribes on all. Messages are deduplicated by event ID.
Event created: -> JSON serialized -> ID = SHA-256(serialized [0, pubkey, created_at, kind, tags, content]) -> Signature = BIP-340 Schnorr sign(private_key, ID) -> Published: ["EVENT", signed_event_json] Event received: -> Verify ID matches SHA-256 of serialized fields -> Verify BIP-340 signature -> Route by kind to appropriate handler -> For kind 1059: attempt decryption with known contacts
All internal messages can be encoded as bitpacked binary for BLE transport (20-244 byte MTU). Header byte: [VV][TTTTTT] — 2-bit version, 6-bit type. Backward compatible: first byte 0x7B ({) = legacy JSON.
| Message | JSON | Binary | Savings |
|---|---|---|---|
| Text "hello" | 80 B | 19 B | 76% |
| Reaction | 80 B | 10 B | 88% |
| Chess move | 55 B | 3 B | 95% |
| Location | 100 B | 15 B | 85% |
| Ping | 40 B | 2 B | 95% |
| Sketch (10 pts) | 150 B | 30 B | 80% |
Optional per-message. Marker 0x01 + 1 byte = word index (128 common English words). Marker 0x02 + 1 byte = emoji index (256 emoji). Raw UTF-8 passes through unchanged. Dictionary frozen per protocol version.
Type 0x02: includes 8-byte reference to original message + up to 64 bytes quoted preview. Rendered as a quoted bar above the reply bubble. Tap to scroll to original.
Long-press any message to open the action picker. Alongside reaction emoji, a reply button (↩) enters reply mode. The quoted message preview appears above the input field. Send attaches replyToId and replyToPreview to the message. Rendered as a bordered quote bar above the bubble text.
Protocol version 2. Subject to evolution. All changes will be documented.