Private messaging sealed with Nostr
Seal is a privacy-focused messaging app built on the Nostr protocol. Messages are end-to-end encrypted using NIP-17 Gift Wraps, ensuring that even metadata like sender identity is protected.
- End-to-End Encryption - All messages encrypted using NIP-17 Gift Wraps with NIP-44 encryption
- Encrypted Media - Images and voice messages encrypted with AES-256-GCM before upload
- Disappearing Messages - Self-destructing messages with NIP-40 expiration
- Account Deletion - Request data removal from relays with NIP-62 Vanish Request
- Local Encryption - Optional password protection with AES-256 for all local data
- No Analytics - Zero tracking, no telemetry, no server-side storage
- Text Messages - Rich text messaging with emoji support
- Voice Messages - Record and send encrypted voice messages (up to 60s)
- Image Sharing - Send encrypted images with automatic compression
- Real-time Delivery - Instant message delivery via Nostr relays
- No Account Required - Your cryptographic keys are your identity
- QR Code Import/Export - Easily transfer your identity between devices
- Key Backup - Secure backup of your private key (nsec)
- Device Sync - Transfer chats between devices via P2P WebRTC connection
- PWA - Install as a Progressive Web App on any device
- Multi-Language - English and German support
- Dark/Light Theme - System-aware theme with manual override
Messages are wrapped in multiple layers of encryption:
┌─────────────────────────────────────┐
│ Gift Wrap (kind 1059) │ ← Signed with random key
│ ┌───────────────────────────────┐ │ Encrypted to recipient
│ │ Seal (kind 13) │ │
│ │ ┌─────────────────────────┐ │ │ ← Signed by sender
│ │ │ Rumor (kind 14) │ │ │ Encrypted content
│ │ │ - sender pubkey │ │ │
│ │ │ - message content │ │ │ ← Actual message
│ │ │ - timestamp │ │ │
│ │ │ - expiration (NIP-40) │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
This ensures:
- Relays cannot read message content
- Relays cannot identify the sender
- Only the intended recipient can decrypt
- Messages can auto-expire
Media files are encrypted client-side before upload:
- Generate random AES-256 key
- Encrypt file with AES-GCM
- Encrypt AES key with NIP-44 (recipient's pubkey)
- Upload encrypted blob to Blossom/NIP-96 server
- Send URL + encrypted key in message
# Install dependencies
npm install
# Start development server
npm run dev
# Run tests
npm run test
# Build for production
npm run build# Sync with Capacitor
npx cap sync android
# Build APK
cd android && ./gradlew assembleRelease- React 19 - UI framework
- TypeScript - Type safety
- Vite - Build tool & dev server
- Mantine 7 - UI component library
- Zustand - State management
- IndexedDB - Encrypted local storage
- nostr-tools - Nostr protocol implementation
- Capacitor - Native mobile builds
- WebRTC - P2P device sync
- Vitest - Unit testing
| NIP | Description | Status |
|---|---|---|
| NIP-01 | Basic protocol | ✅ |
| NIP-17 | Private Direct Messages | ✅ |
| NIP-40 | Expiration Timestamp | ✅ |
| NIP-44 | Encrypted Payloads | ✅ |
| NIP-59 | Gift Wrap | ✅ |
| NIP-62 | Request to Vanish | ✅ |
| NIP-96 | HTTP File Storage | ✅ |
| NIP-98 | HTTP Auth | ✅ |
src/
├── components/ # React components
│ ├── chat/ # Chat UI (MessageBubble, MessageInput)
│ ├── onboarding/ # Account setup flow
│ ├── settings/ # Settings screens
│ └── sync/ # Device sync UI
├── hooks/ # Custom React hooks
│ └── useAudioRecorder # Voice message recording
├── i18n/ # Translations (en, de)
├── pages/ # Main app pages
├── services/ # Core services
│ ├── crypto.ts # NIP-17/44/59/62 implementation
│ ├── db.ts # IndexedDB with encryption
│ ├── fileUpload.ts # Encrypted file upload
│ ├── relay.ts # Nostr relay pool
│ └── webrtc.ts # P2P sync connection
├── stores/ # Zustand state stores
└── utils/ # Helper functions
- Private keys are stored locally and never transmitted
- All network traffic uses encrypted Nostr events
- Media files are encrypted before leaving the device
- Optional password protection encrypts all local data
- No server-side components or data collection
- Open source and auditable
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- Tabler Icons - MIT License