- User identity is a Nostr keypair generated via
opendating-protocol'sgenerateKeypair() - Uses
@noble/curvessecp256k1 underneath - Keys generated on-device, never transmitted
- The private key is persisted with
expo-secure-storeand loaded into the JavaScriptOpenDatingClientfor signing and encryption. - The advanced recovery screen can copy the raw recovery key to the clipboard after an explicit warning.
- The key must never enter AsyncStorage, SQLite, logs, analytics, navigation params, or network payloads.
- A device-bound native signer and passkey-encrypted recovery are required before production approval; see
RELEASE-STATUS.md.
- NDK core signs NIP-42 authentication through
NDKPrivateKeySigner. - Protocol commands and encryption are performed inside the client facade.
- This keeps protocol operations out of screens, but it is not the final native cryptographic boundary.
- All relay communication over WSS (WebSocket Secure)
- NIP-11 info fetched over HTTPS
- NIP-42 AUTH required by relay
- Challenge-response: relay issues challenge → client signs kind 22242 event
- Challenges expire after 60 seconds, single-use, bound to relay URL
- Reconnection requires re-authentication
- NIP-44: Application-level encryption for private content
- NIP-59: Gift wraps for private request/response routing
- Conversation keys derived via ECDH (secp256k1)
- Each private request uses a fresh random request_id
- Raw GPS coordinates consumed internally, never stored
- Only geohash_prefix (max 5 characters, ~5km precision) sent to backend
- Country code optionally included (not required)
- Location not persisted to any storage
- NIP-17 direct messages encrypted end-to-end
- Message plaintext never sent to relay (only encrypted gift wraps)
- Decrypted messages not persisted to AsyncStorage
- NDK cache stores encrypted Nostr events only
- Profile information sent only through authorized discovery
- Arbitrary profile enumeration prevented by relay
- Current profile photos use hosted URLs. Revocable media references and short-lived authorized URLs are required before GA.
- Applied locally immediately upon user action
- Backend block.create sent asynchronously
- Blocked users: cannot discover, like, or DM the blocker
- Relay enforces blocks server-side
- Blocks are private — blocked user is not notified
- Reports encrypted to moderation service
- Reporter identity protected
- Evidence event IDs optionally included
- Report content never in public Nostr tags
- Removes match and messaging capability
- Unmatched user not explicitly notified why
- Reporting remains available after unmatch
- Anyone receiving information (profile, messages) can screenshot it
- The app cannot prevent this
- This limitation is disclosed in privacy screen
- No server-side key recovery
- Users must back up their private key
- Lost keys = lost account (same as self-custody crypto wallet)
- The relay could theoretically observe encrypted traffic metadata
- Content is encrypted, but timing and volume patterns are visible
- This is inherent to the Nostr architecture
- Never log private keys, nsec, or decrypted content
- Never put sensitive data in React Navigation params
- Never send raw GPS to any external service
- Always validate service responses against expected service identity
- Use fresh request_id for every operation
- Clear sensitive state on logout/account deletion