mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:25:21 +00:00
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so platform-specific code is never touched), with test targets indexed and the share extension built. 277 dead declarations removed or demoted: dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed- feature remnants (autocomplete command suggestions, back-swipe tuning, MediaSendError, GeohashParticipantTracker), unused Tor dormancy bindings, assign-only properties, unused parameters (renamed to _), and redundant public accessibility. 13 orphaned localization keys deleted across all 29 locales (old pre-#1392 location-notes UI, app_info warnings). Two real tests were flagged as unused because they never ran: Swift Testing methods missing @Test (NostrProtocolTests. testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests. testAssemblesCompressedLargeFrame). Re-armed both; they pass. Deliberately kept, now recorded in .periphery.baseline.json: iOS-only code invisible to the CI macOS scan, C FFI signatures, keep-alive NWPathMonitor reference, InboundEventKey.eventID (dedup semantics), wifiBulk capability bit (reserved for Wi-Fi bulk work, used by BitFoundation package tests), and the String secureClear cluster (exercised by package tests). New: .periphery.yml config and an advisory Dead Code CI job (mirrors the SwiftLint precedent from #1361) that fails on findings not in the committed baseline. Verified: full macOS app suite, BitFoundation (119) and BitLogger (13) package tests green; periphery scan --strict exits clean. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
198 lines
7.3 KiB
Swift
198 lines
7.3 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
|
|
import BitFoundation
|
|
|
|
// 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 {
|
|
var handshakeState: HandshakeState
|
|
}
|
|
|
|
enum HandshakeState {
|
|
case none
|
|
case initiated
|
|
case inProgress
|
|
case completed(fingerprint: 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
|
|
}
|
|
|
|
/// 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?
|
|
}
|
|
|
|
/// Trust ladder: unknown → casual → vouched → trusted → verified.
|
|
///
|
|
/// Persistence compatibility: `TrustLevel` is stored by its *String* raw
|
|
/// value ("unknown", "casual", …), not by ordinal position, so inserting
|
|
/// `vouched` mid-ladder cannot corrupt previously persisted values — every
|
|
/// pre-existing case keeps the exact raw value it was written with. The
|
|
/// `vouched` tier is additionally never persisted into `SocialIdentity`
|
|
/// (it's recomputed on read from stored vouches), so downgraded builds never
|
|
/// encounter the unfamiliar raw value.
|
|
enum TrustLevel: String, Codable {
|
|
case unknown
|
|
case casual
|
|
/// Transitively trusted: vouched for by at least one peer *I* verified.
|
|
/// Derived at read time — never written to persistent storage.
|
|
case vouched
|
|
case trusted
|
|
case verified
|
|
}
|
|
|
|
// MARK: - Vouching (transitive verification)
|
|
|
|
/// One accepted vouch: a peer I verified (the voucher) attested that they
|
|
/// verified the vouchee. Validity is recomputed on read — a record only
|
|
/// counts while its voucher remains in `verifiedFingerprints` and its
|
|
/// timestamp is within `VouchAttestation.maxAge` — so unverifying a voucher
|
|
/// silently invalidates the vouches they gave without a cascade delete.
|
|
struct VouchRecord: Codable, Equatable {
|
|
let voucherFingerprint: String
|
|
let timestamp: Date
|
|
}
|
|
|
|
// 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> = []
|
|
|
|
// Vouching (transitive verification). All three fields are Optional so
|
|
// caches persisted before this feature decode cleanly — the synthesized
|
|
// decoder uses decodeIfPresent for optionals, and a missing key must not
|
|
// trip the "unreadable cache" recovery path that discards everything.
|
|
|
|
// Vouchee fingerprint -> accepted vouches (capped per vouchee)
|
|
var vouchesByVouchee: [String: [VouchRecord]]? = nil
|
|
|
|
// Peer fingerprint -> when we last sent them a vouch batch (rate limit)
|
|
var vouchBatchSentAt: [String: Date]? = nil
|
|
|
|
// Fingerprint -> when we verified it (orders outgoing vouch batches;
|
|
// entries verified before this field exists sort as oldest)
|
|
var verifiedAt: [String: Date]? = nil
|
|
}
|
|
|
|
//
|
|
|
|
// MARK: - Migration Support
|
|
//
|