mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 16:45:19 +00:00
* Sign public broadcasts; verify relayed messages via persisted signing keys; keep scheduled relays in sparse graphs and speed their jitter; persist announce signing key for offline auth; add short backoff after disconnect errors to reduce reconnect thrash * Add connected vs reachable model: retain peers after link drop, expire after reachability window; expose all peers in snapshots; compute isReachable in UI; add meshReachable state and sorting; avoid removing peers on link events; notify UI on stale removals * ContentView: handle new .meshReachable connection state in header icon switch (exhaustive switch fix) * Logs: tag relayed announces as 'Reachable via mesh' and annotate public message logs with (direct|mesh) path for easier field analysis * Fix syntax error: remove stray else/log inserted into writeOrEnqueue; keep logs clean * UI: use 'point.3.connected.trianglepath.dotted' for mesh-reachable; change people count to include connected+reachable (exclude Nostr-only) * UI: switch to 'point.3.filled.connected.trianglepath.dotted' for mesh-reachable icons in list and header * Reachability: reduce retention to 21s for all peers (verified and unverified) to minimize stale presence * mesh DMs/acks: route to reachable peers; queue READ/DELIVERED until handshake; add Transport.isPeerReachable; UI: hide offline non-mutuals; DM header: better name fallback + show transport + encryption icons; fix NostrTransport conformance * Verification sheet: compute encryption status and fingerprint using short mesh ID mapping (fix 'not encrypted/handshake' for DMs with stable key) * Announce cadence: faster discovery (4s), sparse 15±4s, dense 30±8s; initial 0.6s; post-subscribe 50ms; min-force 150ms; maintenance 5s; proactive announces on handshake + recent-traffic nudge * Relay: increase broadcast TTL cap in sparse graphs to 6; tighten jitter for handshake (10–35ms) and directed (20–60ms) relays * Range/robustness: store-and-forward for directed packets (15s) with flush on new links + periodic; announces: no subset + afterglow re-announce on first-seen; adaptive scanning: force ON when <=2 neighbors or recent traffic * Fix warnings: remove unused msgID and unused mutable var in directed spool flush * Announces: TTL 7 (sparse only) via RelayController; no fanout subset for announces; neighbor-change rebroadcast of last 2–3 announces. Fragments: faster pacing (5ms global, 4ms directed). * Peer list: real-time icon updates by publishing snapshots on connectivity checks; add unread message indicator (envelope) next to peers with unread DMs * UI: unread envelope uses orange; hasUnreadMessages checks Nostr conv key for peers with known Nostr pubkeys (geohash DM consistency) * Logs/robustness: debounce disconnect notifications (1.5s), debounce 'reconnected' logs (2s), add weak-link cooldown after timeouts on very weak RSSI (<= -90) * Peer icons: faster, accurate reachability\n\n- Run connectivity checks every maintenance tick (5s)\n- Publish peer snapshots on central unsubscribe for instant UI refresh\n- Lower inactivity timeout to 8s and disconnect debounce to 0.9s\n- Gate reachability on mesh-attached (>=1 direct link); no links => no reachable peers\n- Keep 21s retention for verified/unverified, but only when attached to mesh\n\nImproves list responsiveness when walking out of range and prevents stale 'reachable' states when isolated. --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
182 lines
5.9 KiB
Swift
182 lines
5.9 KiB
Swift
//
|
|
// IdentityModels.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
///
|
|
/// # IdentityModels
|
|
///
|
|
/// Defines BitChat's innovative three-layer identity model that balances
|
|
/// privacy, security, and usability in a decentralized mesh network.
|
|
///
|
|
/// ## Overview
|
|
/// BitChat's identity system separates concerns across three distinct layers:
|
|
/// 1. **Ephemeral Identity**: Short-lived, rotatable peer IDs for privacy
|
|
/// 2. **Cryptographic Identity**: Long-term Noise static keys for security
|
|
/// 3. **Social Identity**: User-assigned names and trust relationships
|
|
///
|
|
/// This separation allows users to maintain stable cryptographic identities
|
|
/// while frequently rotating their network identifiers for privacy.
|
|
///
|
|
/// ## Three-Layer Architecture
|
|
///
|
|
/// ### Layer 1: Ephemeral Identity
|
|
/// - Random 8-byte peer IDs that rotate periodically
|
|
/// - Provides network-level privacy and prevents tracking
|
|
/// - Changes don't affect cryptographic relationships
|
|
/// - Includes handshake state tracking
|
|
///
|
|
/// ### Layer 2: Cryptographic Identity
|
|
/// - Based on Noise Protocol static key pairs
|
|
/// - Fingerprint derived from SHA256 of public key
|
|
/// - Enables end-to-end encryption and authentication
|
|
/// - Persists across peer ID rotations
|
|
///
|
|
/// ### Layer 3: Social Identity
|
|
/// - User-assigned names (petnames) for contacts
|
|
/// - Trust levels from unknown to verified
|
|
/// - Favorite/blocked status
|
|
/// - Personal notes and metadata
|
|
///
|
|
/// ## Privacy Design
|
|
/// The model is designed with privacy-first principles:
|
|
/// - No mandatory persistent storage
|
|
/// - Optional identity caching with user consent
|
|
/// - Ephemeral IDs prevent long-term tracking
|
|
/// - Social mappings stored locally only
|
|
///
|
|
/// ## Trust Model
|
|
/// Four levels of trust:
|
|
/// 1. **Unknown**: New or unverified peers
|
|
/// 2. **Casual**: Basic interaction history
|
|
/// 3. **Trusted**: User has explicitly trusted
|
|
/// 4. **Verified**: Cryptographic verification completed
|
|
///
|
|
/// ## Identity Resolution
|
|
/// When a peer rotates their ephemeral ID:
|
|
/// 1. Cryptographic handshake reveals their fingerprint
|
|
/// 2. System looks up social identity by fingerprint
|
|
/// 3. UI seamlessly maintains user relationships
|
|
/// 4. Historical messages remain properly attributed
|
|
///
|
|
/// ## Conflict Resolution
|
|
/// Handles edge cases like:
|
|
/// - Multiple peers claiming same nickname
|
|
/// - Nickname changes and conflicts
|
|
/// - Identity rotation during active chats
|
|
/// - Network partitions and rejoins
|
|
///
|
|
/// ## Usage Example
|
|
/// ```swift
|
|
/// // When peer connects with new ID
|
|
/// let ephemeral = EphemeralIdentity(peerID: "abc123", ...)
|
|
/// // After handshake
|
|
/// let crypto = CryptographicIdentity(fingerprint: "sha256...", ...)
|
|
/// // User assigns name
|
|
/// let social = SocialIdentity(localPetname: "Alice", ...)
|
|
/// ```
|
|
///
|
|
|
|
import Foundation
|
|
|
|
// MARK: - Three-Layer Identity Model
|
|
|
|
/// Represents the ephemeral layer of identity - short-lived peer IDs that provide network privacy.
|
|
/// These IDs rotate periodically to prevent tracking while maintaining cryptographic relationships.
|
|
struct EphemeralIdentity {
|
|
let peerID: String // 8 random bytes
|
|
let sessionStart: Date
|
|
var handshakeState: HandshakeState
|
|
}
|
|
|
|
enum HandshakeState {
|
|
case none
|
|
case initiated
|
|
case inProgress
|
|
case completed(fingerprint: String)
|
|
case failed(reason: String)
|
|
}
|
|
|
|
/// Represents the cryptographic layer of identity - the stable Noise Protocol static key pair.
|
|
/// This identity persists across ephemeral ID rotations and enables secure communication.
|
|
/// The fingerprint serves as the permanent identifier for a peer's cryptographic identity.
|
|
struct CryptographicIdentity: Codable {
|
|
let fingerprint: String // SHA256 of public key
|
|
let publicKey: Data // Noise static public key
|
|
// Optional Ed25519 signing public key (used to authenticate public messages)
|
|
var signingPublicKey: Data? = nil
|
|
let firstSeen: Date
|
|
let lastHandshake: Date?
|
|
}
|
|
|
|
/// Represents the social layer of identity - user-assigned names and trust relationships.
|
|
/// This layer provides human-friendly identification and relationship management.
|
|
/// All data in this layer is local-only and never transmitted over the network.
|
|
struct SocialIdentity: Codable {
|
|
let fingerprint: String
|
|
var localPetname: String? // User's name for this peer
|
|
var claimedNickname: String // What peer calls themselves
|
|
var trustLevel: TrustLevel
|
|
var isFavorite: Bool
|
|
var isBlocked: Bool
|
|
var notes: String?
|
|
}
|
|
|
|
enum TrustLevel: String, Codable {
|
|
case unknown = "unknown"
|
|
case casual = "casual"
|
|
case trusted = "trusted"
|
|
case verified = "verified"
|
|
}
|
|
|
|
// MARK: - Identity Cache
|
|
|
|
/// Persistent storage for identity mappings and relationships.
|
|
/// Provides efficient lookup between fingerprints, nicknames, and social identities.
|
|
/// Storage is optional and controlled by user privacy settings.
|
|
struct IdentityCache: Codable {
|
|
// Fingerprint -> Social mapping
|
|
var socialIdentities: [String: SocialIdentity] = [:]
|
|
|
|
// Nickname -> [Fingerprints] reverse index
|
|
// Multiple fingerprints can claim same nickname
|
|
var nicknameIndex: [String: Set<String>] = [:]
|
|
|
|
// Verified fingerprints (cryptographic proof)
|
|
var verifiedFingerprints: Set<String> = []
|
|
|
|
// Last interaction timestamps (privacy: optional)
|
|
var lastInteractions: [String: Date] = [:]
|
|
|
|
// Blocked Nostr pubkeys (lowercased hex) for geohash chats
|
|
var blockedNostrPubkeys: Set<String> = []
|
|
|
|
// Schema version for future migrations
|
|
var version: Int = 1
|
|
}
|
|
|
|
// MARK: - Identity Resolution
|
|
|
|
enum IdentityHint {
|
|
case unknown
|
|
case likelyKnown(fingerprint: String)
|
|
case ambiguous(candidates: Set<String>)
|
|
case verified(fingerprint: String)
|
|
}
|
|
|
|
// MARK: - Pending Actions
|
|
|
|
struct PendingActions {
|
|
var toggleFavorite: Bool?
|
|
var setTrustLevel: TrustLevel?
|
|
var setPetname: String?
|
|
}
|
|
|
|
//
|
|
|
|
// MARK: - Migration Support
|
|
//
|