From 7e90bf6369ae79d9e0473439e8ab3c56ae6b3442 Mon Sep 17 00:00:00 2001 From: jack Date: Wed, 2 Jul 2025 19:18:35 +0200 Subject: [PATCH] Initial commit: Bluetooth mesh chat app with end-to-end encryption --- .gitignore | 61 ++ Package.swift | 26 + README.md | 85 +++ bitchat.xcodeproj/project.pbxproj | 483 ++++++++++++++++ .../AccentColor.colorset/Contents.json | 20 + .../AppIcon.appiconset/Contents.json | 176 ++++++ .../AppIcon.appiconset/icon_1024x1024.png | Bin 0 -> 5315 bytes .../AppIcon.appiconset/icon_128x128.png | Bin 0 -> 848 bytes .../AppIcon.appiconset/icon_128x128@2x.png | Bin 0 -> 1423 bytes .../AppIcon.appiconset/icon_16x16.png | Bin 0 -> 356 bytes .../AppIcon.appiconset/icon_16x16@2x.png | Bin 0 -> 429 bytes .../AppIcon.appiconset/icon_20x20.png | Bin 0 -> 378 bytes .../AppIcon.appiconset/icon_20x20@2x.png | Bin 0 -> 497 bytes .../AppIcon.appiconset/icon_20x20@3x.png | Bin 0 -> 570 bytes .../AppIcon.appiconset/icon_256x256.png | Bin 0 -> 1423 bytes .../AppIcon.appiconset/icon_256x256@2x.png | Bin 0 -> 2709 bytes .../AppIcon.appiconset/icon_29x29.png | Bin 0 -> 401 bytes .../AppIcon.appiconset/icon_29x29@2x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/icon_29x29@3x.png | Bin 0 -> 668 bytes .../AppIcon.appiconset/icon_32x32.png | Bin 0 -> 429 bytes .../AppIcon.appiconset/icon_32x32@2x.png | Bin 0 -> 597 bytes .../AppIcon.appiconset/icon_40x40.png | Bin 0 -> 497 bytes .../AppIcon.appiconset/icon_40x40@2x.png | Bin 0 -> 641 bytes .../AppIcon.appiconset/icon_40x40@3x.png | Bin 0 -> 765 bytes .../AppIcon.appiconset/icon_512x512.png | Bin 0 -> 2709 bytes .../AppIcon.appiconset/icon_512x512@2x.png | Bin 0 -> 5315 bytes .../AppIcon.appiconset/icon_60x60@2x.png | Bin 0 -> 765 bytes .../AppIcon.appiconset/icon_60x60@3x.png | Bin 0 -> 1034 bytes .../AppIcon.appiconset/icon_76x76.png | Bin 0 -> 628 bytes .../AppIcon.appiconset/icon_76x76@2x.png | Bin 0 -> 930 bytes .../AppIcon.appiconset/icon_83.5x83.5@2x.png | Bin 0 -> 976 bytes bitchat/Assets.xcassets/Contents.json | 6 + bitchat/BitchatApp.swift | 17 + bitchat/Info.plist | 42 ++ bitchat/Protocols/BitchatProtocol.swift | 66 +++ bitchat/Services/BluetoothMeshService.swift | 528 ++++++++++++++++++ bitchat/Services/EncryptionService.swift | 67 +++ bitchat/ViewModels/ChatViewModel.swift | 160 ++++++ bitchat/Views/ContentView.swift | 177 ++++++ project.yml | 46 ++ setup.sh | 40 ++ 41 files changed, 2000 insertions(+) create mode 100644 .gitignore create mode 100644 Package.swift create mode 100644 README.md create mode 100644 bitchat.xcodeproj/project.pbxproj create mode 100644 bitchat/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_128x128.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_16x16.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20@3x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_256x256.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29@3x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_32x32.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_40x40.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_40x40@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_40x40@3x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_512x512.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_60x60@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_60x60@3x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_76x76.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_76x76@2x.png create mode 100644 bitchat/Assets.xcassets/AppIcon.appiconset/icon_83.5x83.5@2x.png create mode 100644 bitchat/Assets.xcassets/Contents.json create mode 100644 bitchat/BitchatApp.swift create mode 100644 bitchat/Info.plist create mode 100644 bitchat/Protocols/BitchatProtocol.swift create mode 100644 bitchat/Services/BluetoothMeshService.swift create mode 100644 bitchat/Services/EncryptionService.swift create mode 100644 bitchat/ViewModels/ChatViewModel.swift create mode 100644 bitchat/Views/ContentView.swift create mode 100644 project.yml create mode 100755 setup.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a506f99b --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +/*.gcno + +## macOS +.DS_Store + +## SPM +.swiftpm +.build/ + +## CocoaPods +Pods/ + +## Carthage +Carthage/Build/ + +## fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +## Code Injection +iOSInjectionProject/ + +## Xcode project +*.xcodeproj/project.xcworkspace/ +*.xcodeproj/xcshareddata/ + +## Python +__pycache__/ +*.py[cod] +*$py.class + +## Temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..646bb341 --- /dev/null +++ b/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "bitchat", + platforms: [ + .iOS(.v16), + .macOS(.v13) + ], + products: [ + .executable( + name: "bitchat", + targets: ["bitchat"] + ), + ], + targets: [ + .executableTarget( + name: "bitchat", + path: "bitchat", + resources: [ + .process("Info.plist") + ] + ), + ] +) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..bc8d3135 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# bitchat + +A secure, end-to-end encrypted Bluetooth mesh chat application with an IRC-style interface. + +## Features + +- End-to-end encryption using Curve25519 and AES-GCM +- Bluetooth mesh networking with automatic peer discovery +- Message relay capability (TTL-based flooding) +- IRC-style terminal interface +- Persistent nickname storage +- Universal app (iOS and macOS) +- No internet connection required + +## Setup + +### Option 1: Using XcodeGen (Recommended) + +1. Install XcodeGen if you haven't already: + ```bash + brew install xcodegen + ``` + +2. Generate the Xcode project: + ```bash + cd bitchat + xcodegen generate + ``` + +3. Open the generated project: + ```bash + open bitchat.xcodeproj + ``` + +### Option 2: Using Swift Package Manager + +1. Open the project in Xcode: + ```bash + cd bitchat + open Package.swift + ``` + +2. Select your target device and run + +### Option 3: Manual Xcode Project + +1. Open Xcode and create a new iOS/macOS App +2. Copy all Swift files from the `bitchat` directory into your project +3. Update Info.plist with Bluetooth permissions +4. Set deployment target to iOS 16.0 / macOS 13.0 + +## Usage + +1. Launch the app on multiple devices +2. Choose or modify your nickname +3. The app will automatically discover nearby peers +4. Start chatting! Messages are relayed through the mesh network + +## Security + +- All messages are end-to-end encrypted +- Public key exchange happens automatically on connection +- Messages are signed to prevent tampering +- TTL prevents infinite message loops + +## Protocol + +The bitchat protocol uses JSON-encoded packets with the following structure: +- Packet versioning for future compatibility +- Message types: handshake, message, ack, relay, announce, keyExchange +- TTL-based flooding for mesh relay +- Signature verification for authenticity + +## Building for Production + +1. Set your development team in project settings +2. Configure code signing +3. Archive and distribute through App Store or TestFlight + +## Android Compatibility + +The protocol is designed to be platform-agnostic. An Android client can be built using: +- Bluetooth LE APIs +- Same packet structure and encryption +- Compatible service/characteristic UUIDs \ No newline at end of file diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj new file mode 100644 index 00000000..1ff73c49 --- /dev/null +++ b/bitchat.xcodeproj/project.pbxproj @@ -0,0 +1,483 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 63; + objects = { + +/* Begin PBXBuildFile section */ + 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; }; + 1D9674FA5F998503831DC281 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.swift */; }; + 6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */; }; + 6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; }; + 739429DFDE5C5829CF70DA7D /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.swift */; }; + 7576A357B278E5733E9D9F33 /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */; }; + 7A50E2F04A3515A7E90EEAE4 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; }; + 7DD72D928FF9DD3CA81B46B0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */; }; + 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */; }; + 92D34E7A07C990C8A815B0CE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.swift */; }; + BCCFEDC1EBE59323C3C470BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */; }; + D450CF41F207BDE1A1AAA56E /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */; }; + D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; }; + DDA1DFAB1FF7AADE52DC0F53 /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitchatProtocol.swift; sourceTree = ""; }; + 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = ""; }; + 7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = ""; }; + E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewModel.swift; sourceTree = ""; }; + EA706D8E5097785414646A8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + EF625BB3AD919322C01A46B2 /* BitchatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitchatApp.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 18198ED912AAF495D8AF7763 = { + isa = PBXGroup; + children = ( + 2F82C5FC8433F4064F079D1F /* bitchat */, + 9F37F9F2C353B58AC809E93B /* Products */, + ); + sourceTree = ""; + }; + 2F82C5FC8433F4064F079D1F /* bitchat */ = { + isa = PBXGroup; + children = ( + 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */, + EF625BB3AD919322C01A46B2 /* BitchatApp.swift */, + EA706D8E5097785414646A8E /* Info.plist */, + ADD53BCDA233C02E53458926 /* Protocols */, + D98A3186D7E4C72E35BDF7FE /* Services */, + 45BB7D87CAE42A8C0447D909 /* ViewModels */, + A55126E93155456CAA8D6656 /* Views */, + ); + path = bitchat; + sourceTree = ""; + }; + 45BB7D87CAE42A8C0447D909 /* ViewModels */ = { + isa = PBXGroup; + children = ( + E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 9F37F9F2C353B58AC809E93B /* Products */ = { + isa = PBXGroup; + children = ( + 997D512074C64904D75DDD40 /* bitchat.app */, + 7EEBDA723E1CFD88758DA4AC /* bitchat.app */, + ); + name = Products; + sourceTree = ""; + }; + A55126E93155456CAA8D6656 /* Views */ = { + isa = PBXGroup; + children = ( + A08E03AA0C63E97C91749AEC /* ContentView.swift */, + ); + path = Views; + sourceTree = ""; + }; + ADD53BCDA233C02E53458926 /* Protocols */ = { + isa = PBXGroup; + children = ( + 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + D98A3186D7E4C72E35BDF7FE /* Services */ = { + isa = PBXGroup; + children = ( + D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */, + 6DC1563390A15C042D059CF9 /* EncryptionService.swift */, + ); + path = Services; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0576A29205865664C0937536 /* bitchat_macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DA5644925338B8189B035657 /* Build configuration list for PBXNativeTarget "bitchat_macOS" */; + buildPhases = ( + 137ABE739BF20ACDDF8CC605 /* Sources */, + 0214973A876129753D39EB47 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = bitchat_macOS; + packageProductDependencies = ( + ); + productName = bitchat_macOS; + productReference = 7EEBDA723E1CFD88758DA4AC /* bitchat.app */; + productType = "com.apple.product-type.application"; + }; + AF077EA0474EDEDE2C72716C /* bitchat_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 53EADEF7546F94DDF82271B9 /* Build configuration list for PBXNativeTarget "bitchat_iOS" */; + buildPhases = ( + 4E49E34F00154C051AE90FED /* Sources */, + CD6E8F32BC38357473954F97 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = bitchat_iOS; + packageProductDependencies = ( + ); + productName = bitchat_iOS; + productReference = 997D512074C64904D75DDD40 /* bitchat.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 475D96681D0EA0AE57A4E06E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 0576A29205865664C0937536 = { + DevelopmentTeam = ""; + ProvisioningStyle = Automatic; + }; + AF077EA0474EDEDE2C72716C = { + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 3EA424CBD51200895D361189 /* Build configuration list for PBXProject "bitchat" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 18198ED912AAF495D8AF7763; + minimizedProjectReferenceProxies = 1; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AF077EA0474EDEDE2C72716C /* bitchat_iOS */, + 0576A29205865664C0937536 /* bitchat_macOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0214973A876129753D39EB47 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7DD72D928FF9DD3CA81B46B0 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CD6E8F32BC38357473954F97 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BCCFEDC1EBE59323C3C470BF /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 137ABE739BF20ACDDF8CC605 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */, + 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */, + 7A50E2F04A3515A7E90EEAE4 /* BluetoothMeshService.swift in Sources */, + D450CF41F207BDE1A1AAA56E /* ChatViewModel.swift in Sources */, + 92D34E7A07C990C8A815B0CE /* ContentView.swift in Sources */, + 739429DFDE5C5829CF70DA7D /* EncryptionService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4E49E34F00154C051AE90FED /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */, + 6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */, + D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */, + 7576A357B278E5733E9D9F33 /* ChatViewModel.swift in Sources */, + 1D9674FA5F998503831DC281 /* ContentView.swift in Sources */, + DDA1DFAB1FF7AADE52DC0F53 /* EncryptionService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 702E7395723CADA4B830F4A9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L3N5LHJD5Y; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = bitchat/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.0; + PRODUCT_BUNDLE_IDENTIFIER = net.bitchat.app; + PRODUCT_NAME = bitchat; + SDKROOT = iphoneos; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B36671AEACCBF92BE10852E9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L3N5LHJD5Y; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = bitchat/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.0; + PRODUCT_BUNDLE_IDENTIFIER = net.bitchat.app; + PRODUCT_NAME = bitchat; + SDKROOT = iphoneos; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + BB044400A0F06B93F22D0D55 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = bitchat/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bitchat.app; + PRODUCT_NAME = bitchat; + SDKROOT = macosx; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + BF0D85727BCB6E346962F419 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + CC79F65842D42034ACEE79B7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = bitchat/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bitchat.app; + PRODUCT_NAME = bitchat; + SDKROOT = macosx; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D8C5BF109BB2630752185FA0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3EA424CBD51200895D361189 /* Build configuration list for PBXProject "bitchat" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D8C5BF109BB2630752185FA0 /* Debug */, + BF0D85727BCB6E346962F419 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 53EADEF7546F94DDF82271B9 /* Build configuration list for PBXNativeTarget "bitchat_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 702E7395723CADA4B830F4A9 /* Debug */, + B36671AEACCBF92BE10852E9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + DA5644925338B8189B035657 /* Build configuration list for PBXNativeTarget "bitchat_macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CC79F65842D42034ACEE79B7 /* Debug */, + BB044400A0F06B93F22D0D55 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 475D96681D0EA0AE57A4E06E /* Project object */; +} diff --git a/bitchat/Assets.xcassets/AccentColor.colorset/Contents.json b/bitchat/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..98d4069c --- /dev/null +++ b/bitchat/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "1.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/Contents.json b/bitchat/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..fdf4027a --- /dev/null +++ b/bitchat/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,176 @@ +{ + "images" : [ + { + "filename" : "icon_20x20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon_20x20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon_29x29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon_29x29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon_40x40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon_40x40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon_60x60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon_60x60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon_20x20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon_20x20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon_29x29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon_29x29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon_40x40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon_40x40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon_76x76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon_76x76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon_83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon_1024x1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "icon_16x16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "icon_16x16@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "icon_32x32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "icon_32x32@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "icon_128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "icon_128x128@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "icon_256x256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "icon_256x256@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "icon_512x512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "icon_512x512@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf9b82ade65e9e3a0a946051b0b3863dabef80b GIT binary patch literal 5315 zcmeHLXH=8f);f$ew`#@R^c{(5mo91Pic9Xx zO|?(TC0!ushkju1$K7YMkb|_RS&ggq41uY@(7}aBT~7TuW;>7y9x^zDKl)p3+o75Z6HDBT$uHwvu1HOX3Z4) z#>LlnjpmvHxkdAS0z&3Wg|(4v0jfEI>QU;J(WSw*w@>--G$p`dR!cycN6NL=wy z(xxEd4K{2Q3vt2vQn|>@MVOB_|+X zoF8E;3$V2aB4~x1&nUlAuTX_>B!oNv^GZRVUa=$H@>+txi7V^<3vb2DY$$m&AgSo@A zqy@pZf*1m0wON%b;@XWN)Mo{HQjt9eP-ML!<2`u9)GPT2eI7O~p|yJ6Yx*6BvQaNA z*5}LX$PwOs&6Qa!vP?4Ycl#_o*Z1($=><~cg_pA4r=GRSW>B=L74D9i{VGw+Y{$lS)zeyF#dGB?LPdTgrPfL7dh=F!uF{QNPl8_77*y|>iA zxssks5u(2K00UPjvmb5b0v10lc&k>`%vrPuaeFY&OH@||S1g<~tKYohN}GAmeUxeY z)twhgB%@~OBa_dbouLNsl90#edYb7j&WrYIdXdI>T`Ccg=eQQv=sS4YuT^S=Sa973 zN85Wkiht)o^XaX1hwxg|V+hRmVV-P)>Qi?z)t#4##9jV4dJ{dMr1#?d9&HU2xhHYV z3#Vz^n|%MTPn@=qOV4J~k)8CDH`+vU~ zl0-rhx(Y`-5yixZ`cdr)%@Gd#lM}-oygc$vWa0iW;WElkYHFRpPC01B7NkESVJih# zdz5J>QYA$C4e-icY;dsmKE6lk=g@KxF`GpCVHUdJgJz|UBELGh%A_I-^P(qQ`I7%Q zA7Tg6_eVeF82{9;1=@Njay*jt2Q(^fjlHJxlYT@q$j|(Ly!yMVVRTHm@k<^!XsJdk z$W1dPmSm6A(Vx_M=r$@A*#u5qQv_bJ!Hw@ks{+=}7s%rIP;sLt&)y@w%){Ik=dK!P$2 zL!yQvJI#1{Jdh||cA+})GWR`1Pe%9u1KCMg5CLK*5f~j_n?3qu`;slyg?GIgpcvYm z7jvAYKj(SRJH$}rH~EhxR5aHUTXF@Ch18*NBPm55(wUUSR$fzwssW7}L8;2?nd7X# zSLJ5pI0l2F<@M=qx|CDEZ9Sl^k4pK>dMS-1Q(Gl`6G>vVEUX-{_2~$MFAb2tExGZsjrR6y8_L| z=dQMt-iQ(@yNVu84qq7WthQl3i(N2r-4GDpd3b`=NaU@NF)SlMBkcZer5rF?PS*}?k@_7$QRzP(>+R)+d%w$Pdi{ATIy+p^b9P)#YAp; zxgfp=F{(Y2Fl0ug?VSSru=%>fCDEBTu>`fNCH0rwU{ODg7%iXeW7e#C8lx5H!X+8w zADG(vP~_f^o$KD`pa2o!@V#r+GuhmO_7+13dpJ`&Gx!PO3arMj{%TG(=09gcTuDdb ze5Plat)4f<5l3*e5h2R8Z=%VD+P;IYxkp(CH+TGlq~jCSXB+D)E{P=MDID!>xLm|k zzT>6Vy2de@wtBQE*>a|{G%~!+BO!aIV4^9)LHF}Yduu3%xzGA#^wV9l__2GhjkPrJ zC`y30GWV+eFe~UB!=M9 zGO+P6GFOYBiAw25B{O$n5q8S7sq5{lM>)K$DHe#Xh6CTllaqiYhF~POkW!7%MnS}h z{jz!iB2rn)y3VkbC|Zs8uJ2*cb6SkR{44%+gp&?`yM$QIhv{;ZMugqVxO!m;dChB0 zUt=W9r^?hme-LnxBw~V>pavaeL z&1v0Px?cQ8-f`WDoOip0xg8h+yGk#Mf7kcfS?W9b1Bi7MvsALHwzUM2mvBObo$Ol;oI(@9P(f(T0V4FjXsU<9*0#6R&Ku z?7DtgdYNkNjeIv}wm{1JgfYTBfa=f_k1V{>w=X_+_ka@q;1skm z-`ey?%bD9<`tF+pvVKNMPnOTvJ6Zx3lv9q z|2i^E%uV}K977PQ-rt5b(Znlz*&y6CCYPETv>;<1GNwBvh1(NI=ze=}kfX=i)sg#&hOD%QYB0r87Y&+(F{|E5IsMFE03lEJOIVA0WQl^_1yC?hsXmac}$Ut z;3x>yClL<8qo2kfFFYX@Y-Z0z`?MLPm**c!_(NW>`p-4YQV^AuL%SF|V6q7AG zE18qDzb@rcIj+JjKktEN6}pU3GOlgAtYpvFVkTe*n&tes1>%Pu`%ItWzRVFqkqM7{0LIxIWI>mL;vMtM^s!UL$2$LxS|IKyxGY5Cf#(xGeIC%k(Ac8$)w zBNL069(s%lsrskm-_l1Oh}Ur;f)}RaL$l z>uU>rAswtWy0|Rfus`|IKuf@yR7az4QAJJ2#PFjNVbMW856-;yDQnsBe3wG@Xsr3!rwo}Xs(bXRvbp(%~S|Yl7-Nu&06cQn=+g|c- zB9T_2Osfh1a!VTCp!6WJ)oc5hQw8DGqA)kTKrKEA4D`9p2wVoQ+rDlEGFe!{fZ@D+ zw@D%i(e--T4J&BTXb*w{-U$!gNCLH^TC|$bX;Y<`&aKeY8-s4G=bq?6j->O3wwM8}Zhfqx8TKU%EP-T`ZPYerqg5{OVr*!{QpH@basRCC1~$!6SH~9 zK0q};OLad_y@+=UxCmcBQ$tfnRYOlzL(}1~E?!F;uX992Lj$j&vApe8%2cJCBNykr8&v0#MJe-e*u;FM4JEr literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..f85ced2c5e541beabda19fe19341006ded6b82d5 GIT binary patch literal 848 zcmeAS@N?(olHy`uVBq!ia0vp^4Is<`Bp9BB+KB@xg=CK)Uj~LMHK2G41H&(n{0jz# zQUeBtR|yOZRx=nF#0%!^3bX-AFeQ1ryDA?d9s}&cbM2@n>w*_gg%}@&3`t7|yuj!MgTsv)K>*hbLsOYn~ zzdNSrcw+|~=y+IM*ZI}#`R1#|9F($=f%YZ zzHM0Npdi3;WowzN{~6|w)~EkkvbR3D+jn!Ck@4;c`=hVs9k;ppnbl(Fz4qj<6}&z1 ztWC%7uoeg_JTrK3HGIM1gRuwxurD~)-hJS6a;22Q8-oYp4vP;rc)T;&!I|-fVMdX- z05{Xj@^eg4=a}yNf2fu4y1A?&dusgtE1wT6Q*bDF`s(c7y9{@H|INKM{jLk6)IT0I zH?0{{wZ7CPt4x}$|FJElk?S5WV_SLcst1aCW(VZHen0zTW8Kg6Lpy!aigLc(tBwy7 z7lea}OCOkf*1g{KN&TWOFiEMFxJHzuB$lLFB^RXvDF!10BU4=ib6o?Y5JNL7V-qV= zOKk%KD+7aH0zI`T8glbfGSez?Yv}ehISACC0k@$fGdH!kBr&%Dx1Ku@qH;hz44$rj JF6*2UngCzIV37a- literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6516c53c5bbd14b44292208b8d0b0252f24ab07a GIT binary patch literal 1423 zcmaJ>Yd8~n9R6?FBb($2k;|D>B#lk0W}{BpT03lE$|bopWHPsH$KCdzBSjZoT#id6 zMoA}P5#vxgI)xfm#wfWomfRhRv%Z|?IUnl%@VmY5`}}^NerZlL@;bOa900J6a+v4> z07O|rz*=PlF`E|Zl*yQJ*o6u}tSJEe6abc#BmNiwTr>c3UjXpg0O*G2)j8WL3Ft`& zG7+q%UVPE0>{K}vDoORDDpb>iFloLB13-mMAs%pz?p5^pgwO*FTc@H7mXl#!8R>j| zPFm>sG?Hmg5a&>YY&HqqMRd*)6Ek>8uT;+J>N6g75;j~Ws*u!gY$vL($}7@0pHx4X z-fDC3WOJ9A+IvH8OU%ImVSh?~=;YffH&Nl;fx3dKeMfpf2&1^(lKx>Z^iauz|(t2M}6YMx8LH(?2q5F#P)4_Wlf#x zqMGE?cg!keHH;?Sc+jfrc6kyR}EK8lDoc!rhD{a z=td65`4mM?NqaG>3ELoO*YR*Ru+Jf|>C!_{r!$7o5eOEo?BOlK7%7KT~B&C-xu)vTe+luDY)~vPx zhrvR}zg>hn*coP+#f2=%0{`WLdT(t*sjg)8HmI7R5_(WhWV*JmcVyg|sL~<|94GU8 zb`_tiBs*wscZ-VZ7-q zrV8oq!Q$Z(c*aKn5_a{-@812{Plj(b+M&2 zd^#?EJ?<`Vxvr!8CI=SZA7RXPF`{_T1jHgF9V3Rno=6OXz@Ir@`cezHfMUx&Q zu<`ZRv-oQd0VB94tng;L&Re~KHOCSrMlzoh6~lAX8H+yZ`TKQ`-ST(Jps^xk1l^?n z)BKtH1Jd|A*>fKk zNV{I{DiCWATB2rFa^m!0kG7=n%EU}~=>vL*t`_YRGI(3)EF2VS{N990fib~ zFff!FFfhDI0I6bN5HFasE6@fg!Ib3f?!xfDz5mR9AdkJo)7O>#B|DRVlJ$GdAFMzj z4^J1z5Q)pl2@BX0(h@Q@`q(Vlt2rb!*h1BzT0 zzp1>%z|g>8iFMrNtsbdMx`h`qFqEtl*qv*1sS9YPYKdz^NlIc#s#S7PDv)9@GB7gL zH89sTFbXj=vobcZGPTq;Ft9Q(_$AO&i=rVnKP5A*61RqKUz3AC4H|G8N-}d(i%Sx7 X3vlbX6Co-G)WhKE>gTe~DWM4fwfbc4 literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..84b3ea2c3222572eb172f0e1e93a05a82dd1f8cb GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW(4IWRD z45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^>;_2(k{*s+ZK*{>O<_}h& z&_quc#}JFtZzmdZ9dZzHnJ;ynf1ZoZ7M`xSgXfBGa80{+;+XJ_C8BR^oe!vh(WKay2s==lL0G+VmGpSJ~|~>C-lY^3yfVo3m1T&i=WkHeVxiO2Eo*A^{br zgt+{pD+1-!cCPBnL|I{uo2l}m4@`SGmABuxThO+7zU>@#U;YOF-SUf{%jX|G zWpZ*e>#7q#$ElXMMwFx^mZVxG7o`Fz1|tI_Q(XgdT?3;KLo+L56Dw0oZ36=<1A|`z zJ+&wra`RI%(<*Um==L=^2-Khfx1l66H?_DVF}DD>o;wkuazH%{p00i_>zopr0GgYN AbpQYW literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..86923c1cdd14744640a497fb7839889fb722ee0b GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^A|T8GBp6maa=Hkl6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>dx@v7EBi}!CIKbu_nJRg zfkJVfE{-7*my;7D5(ElskC(8WwUz(A;oObSnf|OH8yXT~+Qx%nxX sX_dG&bo-hd1ZvQL+fb63n_66wm|K8b&z%TSIiMZ}Pgg&ebxsLQ0MlS>(*OVf literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c2fb54a356ed53f786e4da3402d81e671efdec6 GIT binary patch literal 497 zcmeAS@N?(olHy`uVBq!ia0vp^8X(L8Bp6mseZCP$DI|LY`7$t6sWC7#v@kII0tz*} zU|=XUU|@Kaz`$TNgMmT3V9u^U8=wSJlDE4H!~gdFGy8!&_7YEDSN50eOae;Q?=^q0 z0)?)6x;TbdoK8-VU|q~$edE*r|NGOkO(i8HivIlvfqJ<%*8d9+v^Ii(;HnEsje)B= z{GaPKZ&Eyb>aVfHwg0Ap2?-HpC0fjf7lxluvr9}!*zjBa>;WM7kk04x<2cg}E;W^_ z?nEa>wP+0K~1CDkLQ3C@FX(I!z7K1hayJP=O1G&<6?x_Zm)(l2F zE;mM528P`E9-qJOU49(sLDdr1h?11Vl2ohYqEsNoU}Ruqs%v1bYhV;&Xl7+>Vr6Qn zZD3$!VDL+zrxry+ZhlH;S|x4`-M%IVff_X6Hk4%MrWThZ<`&@Ab0(Af+ueoXe|!I#{XiajiKnkC`%88v0VV7Anm z7#QU|T^vI^j=!C3?R~^S#LYh`Mv0AOFURWFVoj|#mT%oCWG(-qenapEVL=z~gQxDf z6$<~McAU4%t>6w>p56^s$ZQ~Z}KKe< z;`Yf#?`qOuda;M$NMYIhxpnJ#Gbc|himjiZJ^9Ye6YHMywxoaE-dt2v_$;|$$Ht!t zF}K%WJGC?R)1t>AYTFKXvmSo(B`te<`hC;Mjlyord!DMVkUo8Q!Ld&|Tf9ylt$+9R zRdP`(kYX@0Ff!FOFxNFO3NbXZGB&X?wbV8+ure_CCD2ogq9Hdw uB{QuOw}x(ClY>AF8gLs*GILXlOA>PnaO=4fAu0#d!{F)a=d#Wzp$P!T+Sn`r literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..6516c53c5bbd14b44292208b8d0b0252f24ab07a GIT binary patch literal 1423 zcmaJ>Yd8~n9R6?FBb($2k;|D>B#lk0W}{BpT03lE$|bopWHPsH$KCdzBSjZoT#id6 zMoA}P5#vxgI)xfm#wfWomfRhRv%Z|?IUnl%@VmY5`}}^NerZlL@;bOa900J6a+v4> z07O|rz*=PlF`E|Zl*yQJ*o6u}tSJEe6abc#BmNiwTr>c3UjXpg0O*G2)j8WL3Ft`& zG7+q%UVPE0>{K}vDoORDDpb>iFloLB13-mMAs%pz?p5^pgwO*FTc@H7mXl#!8R>j| zPFm>sG?Hmg5a&>YY&HqqMRd*)6Ek>8uT;+J>N6g75;j~Ws*u!gY$vL($}7@0pHx4X z-fDC3WOJ9A+IvH8OU%ImVSh?~=;YffH&Nl;fx3dKeMfpf2&1^(lKx>Z^iauz|(t2M}6YMx8LH(?2q5F#P)4_Wlf#x zqMGE?cg!keHH;?Sc+jfrc6kyR}EK8lDoc!rhD{a z=td65`4mM?NqaG>3ELoO*YR*Ru+Jf|>C!_{r!$7o5eOEo?BOlK7%7KT~B&C-xu)vTe+luDY)~vPx zhrvR}zg>hn*coP+#f2=%0{`WLdT(t*sjg)8HmI7R5_(WhWV*JmcVyg|sL~<|94GU8 zb`_tiBs*wscZ-VZ7-q zrV8oq!Q$Z(c*aKn5_a{-@812{Plj(b+M&2 zd^#?EJ?<`Vxvr!8CI=SZA7RXPF`{_T1jHgF9V3Rno=6OXz@Ir@`cezHfMUx&Q zu<`ZRv-oQd0VB94tng;L&Re~KHOCSrMlzoh6~lAX8H+yZ`TKQ`-ST(Jps^xk1l^?n z)BKtH1Jd|A*>fKk zNV{I{DiCWATB2rFa^m!0kG7=n%EU}~=>vL*t`_YRGr_~9w8 z{VGx)nmRBVr;ygk&A)nj>`pFm3WY%YQ9hyEb8jI2bEtAakmh$ zUFD-Q*Ng{QaUuNSl@0NDj7-R9z2^&FhDI_H`$DR#qw;#@7Y|5u*Y%8yKC9J>U44#b zuT01pd}|VHHDBGOm}2}KLlWQS9gdc|<|0)xnF&L=UkSDr$xU`t0qXIE)T%$HZZNIx zVWy$+SW#Mo;5Ai*4D6@_)MZSlo9Rt1N!lkr!r*7-QCpE=A{kgm1E9WF%iJF7i1}dCzKwAG#2O-J)%1mmCC85cnzfCEmP4hH0)IGrn9X! zk&Hm@-=y^Lb|G8~pz$Hbk54!;u8Iv7+D#VMK1>x&5esc^n+h3Cbj1NaR6zRVsYm4% zeWbECvunHGZ!kvc>b*jn($?wBwPx7q;Fx@`!S7_#n_AzPjtn;rcv!*J4OGmhAH9p? z)s8Xh_BRaHX&s1eSW94@4)zdatP9j{bdsR>fzeBEx0X(YhO1HoMYQJjGB6uY=jeJ5 zRxjTQSD@g@wQmDFX~@QXw_mMV^vX7|kEyT>V)v-cirdR#9-^jhIjFF(S$ovt6@q@g zaVw|CPNfX{ZjV}7{)j3nKL_t|=94br;_~{?=Qd5}Y4q_H7+jik)1e_7?|w28LukG2 z*Jr}?PuBv|zF}mO*q0+BB@I_#ICLqECbNb+cz0q2hHw#Ul$(opBYwgVLcIc3Yk16? zX)zG|)?Ovm|C&8fS+U#CKI)WydI)#ntgmJuiDs;!FHk!F)ntZ4_4-U34(o!}AtL13 zWv`c4ae4&Xupy9bhtuP{e|ARRasPc*r!*Fin_1N+m!<`fXjU4P4rI1vc$R`%m4Ip` za1Hu1kl+R2Fnf~Rtx@^tq7CGe(Ejs z8&8PdX6RlH%Ut-ab7*yLU(pWtYp9OT?-f3`tczLTL@ zS7+Wi#jel7hg{uW0aTi9M9$xLj8>|l+?mYbWLns-tga;8lw-`r9gvuk6LFIZs*K=d zy(K|IzUv1i#!{+MaGZGK_;L?$o?pF?&Qm&;L5ARcysM(tx_LkRo^c-Hu1DgFDkYTr z=OOfSvA^_Jv{tWNQLnwbn8VrCH?8-s_VVlFN0y(YNFy!b4y~J?SxF~^8+cw?EW{Gt z6)X;E4gq^oM*@vU(yvUkH@O6ByJ;bijb;57ssk%-!cq+w+~L02jF`4rfQG@xgl@&f zbfv9y^pkb$n`*fG_QNt-pch7~Z zRGj_XwM{W*<<8&(&WZ=?l;Zw8vO7z^QSf|jnYZis-LW{DtTa8VzNBR=-(tm_@dkI0 z06#MqyZhsH3gX8JbJK5>b$X!j3*Gf0p?*ta4V4y*f=FEg{7Hk{l~?)oYUHOC6J9iK z^c$^bi(=*%jI}WiLx5NR7QI)dR*PKrFyJ&TV%#istW1A~l#~sY`AclH`Sm9*#b~=J z*bs#haB<=nroa66OVIe%eYa)wj^&lA)hMWW%V%|w`6Y?%uYn$x2#>-WlS?B5d}t9d z0n0CV1;3|KArw^2&)mfgUc(OJnu71lh10mYsSB=iv&0=Z!o5yXFj z^tcKw6Th696`DC0qitXa?w)rlqnABJu8&IykVYTPN-LKnw2hlI`1XfOooGXb6Y~E7 zU8(Qlc?HLa{m>K#l~Or3ZO%w)R?jxnBLwVv-T|QteaHC}v>e0CXcWAlrE3Au%f2BC zb_>!?n_xR@utb6?7b0MAQw@FCS#Y6m#O!yC2(BXN{X9uIDsoLIv4kwA$XeL{O_^10 z9J|J2B8Umly^1I_!QZtC_m7ax!iaZ~;H2}e_-D>>TIC7cugb!`LHIX&<1!LvC4EyoG zr9Xs|;1g)BKxb7f4vRIJ7rtU~q<7f*&1&ElSg{nOg;J*(8Dr%TtMK9GOyPL9Pw@_S zdVRU#C%dMfv-b%WxHHVcbj@+7{p*-xm#wB>Fbn0sAGuH4robv+Tus^-EZX65=cjzg z_AV1iXLizNw091kWQRM*Sr~UT5BD=3VaJ(}8`A>ZXm7?3ys*zRs$q_x%&7jNOdF*( zsC|7^OJY&c)KqF=Yl@O!bV(yzFx&Nvz$Q!LnqPZUHYfS-C*SK>rh=WmyZp}$<-Fxv9fQJU;K3$H2<#a9HJv{l5KZPM1RQ38&n$Erjq*cQPdd zZyAerrOgR0{z#68x=m~OuI*BlP@K}C&re?xhcfl$n96#Pyq%;Pb8@1k+e4rOLscio z`%U`L=vvh#+h0m1NVHqkf|{E7Cr&N{u+@>Ko$S#py!BXxE~jPs!O7Z!`3~+i8ENj8 zX#M^vk9z(}f-vDAg1boEu$`M5sS<~FWazTl)IwqK=Ht{Lww=n?KiIz|H0Y*+%5|pQ)BxwKdgs714zzb1l!Xy5fGwLa?`ZaPpn-kXMmcGCK1w@Unxc~qF literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5f31add7fd79c0956e165bb848f3bfee699ae8 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^vLMU>Bp8Z+SX>5D3dtTpz6=aiY77hwEes65fIteo_%p{N3KDSZ~eD++X~UP8Jh|whf8xjnz;4fAI{}_`Ty1T z{5W0U>Ga{`m2ZuF_Y*l8=2a@rvM~4-4|ITPiEBhjN@7W>RdP`(kYX@0Ff!FOFxNFO z3NbXZGB&X?wbV8+ure_CCD2ogq9HdwB{QuOw}x(ClY>AF8gLs*GILXlOA>PnaO=4f SAu0#d!{F)a=d#Wzp$P!s!hH+? literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c301074b3c12380ecde7c72f9e312593fd4f36f7 GIT binary patch literal 564 zcmeAS@N?(olHy`uVBq!ia0vp^Rv^p)Bp8ld5d8wA6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDI0IHqAz#v{QXIG#NP=YDR+ueoXe|!I#{XiajiKnkC`%88v0VV7Anm z7#JlyT^vI^j=!B~=*8?P(zbub6z)JXts`C@GrcZzbT!ExkdBCmzR|(%k(jPveuCA7 zUqNh|Qb$AvM~iD`qh<`J6wj~dl+?|gM^&btsJXl6xcU3De;yQA$kfklQdwDCZNhz^ zRY|L01>?5*mNN_f{^^X@dAG)%VeW)2?i}wg@HTEazPjPw`e5z@m*$>Zd}`H`qkkt& z@#Y9VY#t-O!7%yjA;GCuD}Qoc6_Rh9I!n^(d~TRUopk(K<~!3@hou!gTyQZXvrs#K z`7xJ=PgYfZ`aQMquwk|N&$}PR-xZ%cwIr+mrRUYuTVbyX)}?&EZm}^and?&W=9|AJ zWobURGDG~NL~!3PW&1fZb}`(2u&Ru0!dJ$c4?4%JWcZHPKae(Dll^K*h&(W08B|ML zBT7;dOH!?pi&B9UgOP!esjh*!u7OdAp_!GjiIu6Pwt<0_fx$0~+Qx%nxXX_dG& obo-hd1ZvQL+fb63n_66wm|K8b&z%TSIiMZ}Pgg&ebxsLQ0L4Vol>h($ literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29@3x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_29x29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0e9a9c144b6debb89d87225a393271cd1a57e619 GIT binary patch literal 668 zcmeAS@N?(olHy`uVBq!ia0vp^;ULTbBp5;#w`~AY3dtTpz6=aiY77hwEes65fI{%km9;sY*A$UruSjL6VJX`ci4DGyibbT_Uw}S%i)2!eD`PFteqY) zv-?FF?{4e!4>Z=6^Zt6SK1r?Y_hE*gwT(?5=NO*Nd-nUomK)s4yvxcHja$y9J5Fc* z+x{o}JDb*;7tfk5cdZjo5-WLam2m#cy3N(M51!tXcKt;x%i{$7H7A*uoo7t5W;CB7 z!@MQ^^JRlqX3N>jg}oUnr0$EGGg*{;=l<}kpmf*PZwo59Ac6BoKVQzcI=}Qua2qgs zR7+eVN>UO_QmvAUQh^kMk%5t^u7SC(fl-K|nU%4Lm8qq+fq|8Q!7qWHS`-br`6-!c rmAEx@` z45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^>;_2(k{*s+ZK*{>O<_}h& z&_quc#}JFtZzmdZ9dZzHnJ;ynf1ZoZ7M`xSgXfBGa80{+;+XJ_C8BR^oe!vh(WKay2s==lL0G+VmGpSJ~|~>C-lY^3yfVo3m1T&i=WkHeVxiO2Eo*A^{br zgt+{pD+1-!cCPBnL|I{uo2l}m4@`SGmABuxThO+7zU>@#U;YOF-SUf{%jX|G zWpZ*e>#7q#$ElXMMwFx^mZVxG7o`Fz1|tI_Q(XgdT?3;KLo+L56Dw0oZ36=<1A|`z zJ+&wra`RI%(<*Um==L=^2-Khfx1l66H?_DVF}DD>o;wkuazH%{p00i_>zopr0GgYN AbpQYW literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..58e93947b699302744ccadfac8a89c79d8b7b408 GIT binary patch literal 597 zcmeAS@N?(olHy`uVBq!ia0vp^4j{|{BpCXc^q7E@Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeM~FY)wsWq--eB%oydUh@Yl z0|TR_r;B4q#NoG7HhM8T3bgI-E;nR5GSQ`5gUilo17}3kjc)yfWMz&pr6vh(Y3sKl z@&`{TwYEx#Dsn8E+NYVA-dHbFw(oDbgcuK7^Ff0p6<(TG z|J2=(WPcFRI_J=`S1If7PU(men%uhhdTX7qNUrtF=<-lW1y!Z%ul!t&DQGGwsfG#) zPP|w)`R-DVnjgDP$m*R9JNWs)@%79XG8lfCyuNtE&~sj*n|0Y71^*B0bT=$!Ro}kt zwCCFMirXq~ZaNg?dADTVj-Y-k{}X4He#w6;CKGY(#Ftm+zpdT3P~9B1EI)Yg{p32m=02r>SC9V-ADTyViR>?)FK#IZ0z{phB zz+BhBD8$gr%Gkup)Kc5Pz{aVfHwg0Ap2?-HpC0fjf7lxluvr9}!*zjBa>;WM7kk04x<2cg}E;W^_ z?nEa>wP+0K~1CDkLQ3C@FX(I!z7K1hayJP=O1G&<6?x_Zm)(l2F zE;mM528P`E9-qJOU49(sLDdr1h?11Vl2ohYqEsNoU}Ruqs%v1bYhV;&Xl7+>Vr6Qn zZD3$!VDL+zrxry+ZhlH;S|x4`-M%IVff_X6Hk4%MrWThZ<`&@Ab0YSsZr#p(MxgAiL=EPgcd8kZF4Qk@unhulWpp zdmTYd5i&z#1zp!<0zP#$oVNJfA)|5Ui)$;bS zMMwWFj!gFdG1ESt>+zjjQwyVy-nX39npG0-WPD(>>SgP`kYjR#H*)g9WtJY2JXOiD zd*p9$gwzxfVnZ-WUpLRdhH*fpXOHof1zb-x}e>-%w_WJYY zoqye>nYEWr3G!OmwvXY*obIQy=ihn_j04pY*NBpo#FA92m&Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeM~FY)wsWq--eB%oydUh@Yl z0|VnjPZ!6KjC*gdpZ69?lsW$K`xUP4nVT}pB%54bJUSRG;8^)V!EuMx`?B!t7(OY(u)WE&Pu_P0;M%heEOPj>Xp@*DfiO^?ABk_JAZiJ z^Gdd7Gg$>;piuPIy0h=QgO#T@zq%;5`YPXnO-Cz#Uw@d(UHk80a^|`vZ%=Ms^nIJy zBQ~=-CK10J@%8=jrteGEUO1EU<@|Kx>+@#KjQsyEa{t*k&UgG>(#-027!;g8lXK8= z&NDgtI?gSnGDY(L70;*~_$hNpn_+n|$99G(oB7$z8RJsr<@Pby#DC%ypQJWr9Zw14 z{dd319v|NOnW1&^a)uuz$(E}NoDaV{zV-Uz^*Y_SRWrZt zmz$aFD8S+v>z)3e`@x-@@-XdnRw`9>Gj2v@yJ@d8s+HET{Sq6y%lQ#+!Rha+Dxott z7rad3dsxme*Rn;qVY6jRJ)_ApfpzR3YO9uVw==A^Y}w7QFGW6Y7q`LW${i;6cxwK< zk@Bly&V2J??`1B77q@H!GSarmxdw`?UH4w5r@zeY@qgVgPdk;Q@R&QFGuHL+SgzMA zT+OIu%>Qi8ZqJ8aJNFoUuY0t*Jj?|Ko@Cr$D)ut{^`dw0e_;HpmbgZgq$HN4S|t~y z0x1R~10z#i19M#iqYy(gD`OKYQ%h|F11kfAUjjX~C>nC}Q!>*kack)IH8}{>paHj` eBr`X)xFj*R0Joky5u$QHJq(_%elF{r5}E*Z1ug9W literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..5c73d4e63b78ddcbbbb7ab7a59dae287ceea3a1c GIT binary patch literal 2709 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 literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf9b82ade65e9e3a0a946051b0b3863dabef80b GIT binary patch literal 5315 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 literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_60x60@2x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe8d9352db79512494670aef1ee40e5f1dc20b92 GIT binary patch literal 765 zcmeAS@N?(olHy`uVBq!ia0vp^6(Gz3Bp77)Gx>m&Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeM~FY)wsWq--eB%oydUh@Yl z0|VnjPZ!6KjC*gdpZ69?lsW$K`xUP4nVT}pB%54bJUSRG;8^)V!EuMx`?B!t7(OY(u)WE&Pu_P0;M%heEOPj>Xp@*DfiO^?ABk_JAZiJ z^Gdd7Gg$>;piuPIy0h=QgO#T@zq%;5`YPXnO-Cz#Uw@d(UHk80a^|`vZ%=Ms^nIJy zBQ~=-CK10J@%8=jrteGEUO1EU<@|Kx>+@#KjQsyEa{t*k&UgG>(#-027!;g8lXK8= z&NDgtI?gSnGDY(L70;*~_$hNpn_+n|$99G(oB7$z8RJsr<@Pby#DC%ypQJWr9Zw14 z{dd319v|NOnW1&^a)uuz$(E}NoDaV{zV-Uz^*Y_SRWrZt zmz$aFD8S+v>z)3e`@x-@@-XdnRw`9>Gj2v@yJ@d8s+HET{Sq6y%lQ#+!Rha+Dxott z7rad3dsxme*Rn;qVY6jRJ)_ApfpzR3YO9uVw==A^Y}w7QFGW6Y7q`LW${i;6cxwK< zk@Bly&V2J??`1B77q@H!GSarmxdw`?UH4w5r@zeY@qgVgPdk;Q@R&QFGuHL+SgzMA zT+OIu%>Qi8ZqJ8aJNFoUuY0t*Jj?|Ko@Cr$D)ut{^`dw0e_;HpmbgZgq$HN4S|t~y z0x1R~10z#i19M#iqYy(gD`OKYQ%h|F11kfAUjjX~C>nC}Q!>*kack)IH8}{>paHj` eBr`X)xFj*R0Joky5u$QHJq(_%elF{r5}E*Z1ug9W literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_60x60@3x.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6e66f93f6f8e51682e4a22c905881f12fbc87527 GIT binary patch literal 1034 zcmeAS@N?(olHy`uVBq!ia0vp^TR@ltNH9n&i+l&96p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>dx@v7EBi}!CIKbu_nJRg zf%5M>T^vIyZoRz~>k*tNbNu7~J0(Zrq}I8z?5()!A;ucz;o^E!rXo02v476r>D*TM&E)%=#{18o z)!SHKe$RLL;~Zr{pbId8!$qA|`EUDw|9<@?=hOA+5pTXek755G6aB#2bnm0eEyrVS zBxL_Po^y1s+v?-ajcJQA^8c^75xJ@QtZeuTmXOZBFZS9#&P_V6!+J17DgOS1lf_|< z7hT+y>^v_%yLb6kbnW3+C2Qk<-u-HSZ@QVB>C~v3Yo0bwx@qhEwA#1M_p*uaZ1Xoo zCNXvEU+O)nzQs#_) z^}N{BMJxM{%Pn1`Y-45-()Dkdf4|qo)IXb}cfHWH+45e?^>w}3qi!zO>f4oelP>B= z?0xpoN=fP0-KBmdk54$>dog30Q0$viM_=CD_BrC2^3vVWUYk#5?4FjK{_B^j<=Lb2 zUaq+^Ior_l?4pZ@`PufL@)GN_3y|OQ+bL?xi}vpKPd=M7MqYe&?#rjkXR=Fno2RYw zFzK7OKltQD?_);;?!LP=p|@kjveVZzm+#teC);He_iOFj3G<$ZK9HIy)SmzGnC((A zMp!QfBi%UXE=Cntp&$=%cvOZ+Xz9nlzwW|8s zqgQ5F^H2X^K0$Ep{XG*3ue#Y(8TT&rZM4ooY`@g@9Ih=g=kI)*vqN*?&6JYffBGyTPYbGeyQj9AJG$gY6(88Jcvs2U|Hb)p)cAO(Y*?amvDm!K<*7+` zoY-Zz-_^@iRK?}@TG$)^e0eycBq+)dGns)zLblm+`Cls#Uccrmkf&PW8c~vxSdwa$ zT$Bo=7>o>zOmz*+bq$O{49%>JO{`2UwG9lc3=Dn=^wgqg$jwj5OsmALq1)HwAW(w_ h+=i0O+|=Td#M}bhdhSGs$^rE-c)I$ztaD0e0s!T-z!m@i literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/AppIcon.appiconset/icon_76x76.png b/bitchat/Assets.xcassets/AppIcon.appiconset/icon_76x76.png new file mode 100644 index 0000000000000000000000000000000000000000..fdc2d7a9e0d7007f833c9b38fa108ed7aec50963 GIT binary patch literal 628 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 bwYVfPw*a@EI}xICKs^keu6{1-oD!M literal 0 HcmV?d00001 diff --git a/bitchat/Assets.xcassets/Contents.json b/bitchat/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/bitchat/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/bitchat/BitchatApp.swift b/bitchat/BitchatApp.swift new file mode 100644 index 00000000..084fce76 --- /dev/null +++ b/bitchat/BitchatApp.swift @@ -0,0 +1,17 @@ +import SwiftUI + +@main +struct BitchatApp: App { + @StateObject private var chatViewModel = ChatViewModel() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(chatViewModel) + } + #if os(macOS) + .windowStyle(.hiddenTitleBar) + .windowResizability(.contentSize) + #endif + } +} \ No newline at end of file diff --git a/bitchat/Info.plist b/bitchat/Info.plist new file mode 100644 index 00000000..e2b8900c --- /dev/null +++ b/bitchat/Info.plist @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + bitchat + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSBluetoothAlwaysUsageDescription + bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users. + NSBluetoothPeripheralUsageDescription + bitchat uses Bluetooth to discover and connect with other bitchat users nearby. + UILaunchScreen + + UIColorName + Black + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift new file mode 100644 index 00000000..7489094b --- /dev/null +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -0,0 +1,66 @@ +import Foundation +import CryptoKit + +enum MessageType: UInt8 { + case handshake = 0x01 + case message = 0x02 + case ack = 0x03 + case relay = 0x04 + case announce = 0x05 + case keyExchange = 0x06 +} + +struct BitchatPacket: Codable { + let version: UInt8 + let type: UInt8 + let senderID: Data + let recipientID: Data? + let timestamp: UInt64 + let payload: Data + let signature: Data? + var ttl: UInt8 + + init(type: UInt8, senderID: Data, recipientID: Data?, timestamp: UInt64, payload: Data, signature: Data?, ttl: UInt8) { + self.version = 1 + self.type = type + self.senderID = senderID + self.recipientID = recipientID + self.timestamp = timestamp + self.payload = payload + self.signature = signature + self.ttl = ttl + } + + var data: Data? { + try? JSONEncoder().encode(self) + } + + static func from(_ data: Data) -> BitchatPacket? { + try? JSONDecoder().decode(BitchatPacket.self, from: data) + } +} + +struct BitchatMessage: Codable { + let id: String + let sender: String + let content: String + let timestamp: Date + let isRelay: Bool + let originalSender: String? + + init(sender: String, content: String, timestamp: Date, isRelay: Bool, originalSender: String? = nil) { + self.id = UUID().uuidString + self.sender = sender + self.content = content + self.timestamp = timestamp + self.isRelay = isRelay + self.originalSender = originalSender + } +} + +protocol BitchatDelegate: AnyObject { + func didReceiveMessage(_ message: BitchatMessage) + func didConnectToPeer(_ peerID: String) + func didDisconnectFromPeer(_ peerID: String) + func didUpdatePeerList(_ peers: [String]) +} \ No newline at end of file diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift new file mode 100644 index 00000000..f098a4f4 --- /dev/null +++ b/bitchat/Services/BluetoothMeshService.swift @@ -0,0 +1,528 @@ +import Foundation +import CoreBluetooth +import Combine + +class BluetoothMeshService: NSObject { + static let serviceUUID = CBUUID(string: "F47B5E2D-4A9E-4C5A-9B3F-8E1D2C3A4B5C") + static let characteristicUUID = CBUUID(string: "A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D") + + private var centralManager: CBCentralManager! + private var peripheralManager: CBPeripheralManager! + private var discoveredPeripherals: [CBPeripheral] = [] + private var connectedPeripherals: [String: CBPeripheral] = [:] + private var peripheralCharacteristics: [CBPeripheral: CBCharacteristic] = [:] + private var characteristic: CBMutableCharacteristic! + private var subscribedCentrals: [CBCentral] = [] + private var peerNicknames: [String: String] = [:] + private var activePeers: Set = [] // Track all active peers + + weak var delegate: BitchatDelegate? + private let encryptionService = EncryptionService() + private let messageQueue = DispatchQueue(label: "bitchat.messageQueue", attributes: .concurrent) + private var processedMessages = Set() + private let maxTTL: UInt8 = 5 + + let myPeerID: String + + override init() { + self.myPeerID = UUID().uuidString.prefix(8).lowercased() + super.init() + + centralManager = CBCentralManager(delegate: self, queue: nil) + peripheralManager = CBPeripheralManager(delegate: self, queue: nil) + } + + func startServices() { + // Start both central and peripheral services + if centralManager.state == .poweredOn { + startScanning() + } + if peripheralManager.state == .poweredOn { + setupPeripheral() + startAdvertising() + } + } + + func startAdvertising() { + guard peripheralManager.state == .poweredOn else { + return + } + + let advertisementData: [String: Any] = [ + CBAdvertisementDataServiceUUIDsKey: [BluetoothMeshService.serviceUUID], + CBAdvertisementDataLocalNameKey: "bitchat-\(myPeerID)" + ] + peripheralManager.startAdvertising(advertisementData) + } + + func startScanning() { + guard centralManager.state == .poweredOn else { + return + } + centralManager.scanForPeripherals(withServices: [BluetoothMeshService.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]) + } + + private func setupPeripheral() { + let characteristic = CBMutableCharacteristic( + type: BluetoothMeshService.characteristicUUID, + properties: [.read, .write, .writeWithoutResponse, .notify], + value: nil, + permissions: [.readable, .writeable] + ) + + let service = CBMutableService(type: BluetoothMeshService.serviceUUID, primary: true) + service.characteristics = [characteristic] + + peripheralManager.add(service) + self.characteristic = characteristic + } + + func sendMessage(_ content: String, to recipientID: String? = nil) { + messageQueue.async { [weak self] in + guard let self = self else { return } + + let nickname = self.delegate as? ChatViewModel + let senderNick = nickname?.nickname ?? self.myPeerID + + let message = BitchatMessage( + sender: senderNick, + content: content, + timestamp: Date(), + isRelay: false, + originalSender: nil + ) + + if let messageData = try? JSONEncoder().encode(message) { + let packet = BitchatPacket( + type: MessageType.message.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: recipientID?.data(using: .utf8), + timestamp: UInt64(Date().timeIntervalSince1970), + payload: messageData, + signature: try? self.encryptionService.sign(messageData), + ttl: self.maxTTL + ) + + self.broadcastPacket(packet) + } + } + } + + + func getPeerNicknames() -> [String: String] { + return peerNicknames + } + + private func getAllConnectedPeerIDs() -> [String] { + return Array(activePeers) + } + + private func broadcastPacket(_ packet: BitchatPacket) { + guard let data = packet.data else { return } + + // Send to connected peripherals (as central) + for (_, peripheral) in connectedPeripherals { + if let characteristic = peripheralCharacteristics[peripheral] { + // Use withResponse for larger data for reliability + let writeType: CBCharacteristicWriteType = data.count > 50000 ? .withResponse : .withoutResponse + peripheral.writeValue(data, for: characteristic, type: writeType) + } + } + + // Send to subscribed centrals (as peripheral) + if characteristic != nil && !subscribedCentrals.isEmpty { + peripheralManager.updateValue(data, for: characteristic, onSubscribedCentrals: subscribedCentrals) + } + } + + private func handleReceivedPacket(_ packet: BitchatPacket, from peerID: String) { + guard packet.ttl > 0 else { return } + + let messageID = "\(packet.timestamp)-\(String(data: packet.senderID, encoding: .utf8) ?? "")" + guard !processedMessages.contains(messageID) else { return } + processedMessages.insert(messageID) + + if processedMessages.count > 1000 { + processedMessages.removeAll() + } + + switch MessageType(rawValue: packet.type) { + case .message: + if let message = try? JSONDecoder().decode(BitchatMessage.self, from: packet.payload) { + // Ignore our own messages + if let senderID = String(data: packet.senderID, encoding: .utf8), senderID == myPeerID { + return + } + + // Store nickname mapping + if let senderID = String(data: packet.senderID, encoding: .utf8) { + peerNicknames[senderID] = message.sender + } + + DispatchQueue.main.async { + self.delegate?.didReceiveMessage(message) + } + + var relayPacket = packet + relayPacket.ttl -= 1 + if relayPacket.ttl > 0 { + self.broadcastPacket(relayPacket) + } + } + + case .keyExchange: + if let publicKeyData = try? JSONDecoder().decode(Data.self, from: packet.payload) { + try? encryptionService.addPeerPublicKey(peerID, publicKeyData: publicKeyData) + + // Track this peer temporarily but don't announce until we get their name + if peerID != "unknown" && peerID != myPeerID && !peerNicknames.keys.contains(peerID) { + // Just track them, don't announce yet + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + + // Send announce with our nickname immediately + DispatchQueue.main.async { [weak self] in + if let self = self, let vm = self.delegate as? ChatViewModel { + let announcePacket = BitchatPacket( + type: MessageType.announce.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: peerID.data(using: .utf8), + timestamp: UInt64(Date().timeIntervalSince1970), + payload: vm.nickname.data(using: .utf8)!, + signature: nil, + ttl: 1 + ) + self.broadcastPacket(announcePacket) + } + } + } + + case .announce: + if let nickname = String(data: packet.payload, encoding: .utf8) { + // Store the nickname + peerNicknames[peerID] = nickname + + // Add to active peers if not already there + if peerID != "unknown" && peerID != myPeerID && !activePeers.contains(peerID) { + activePeers.insert(peerID) + DispatchQueue.main.async { + self.delegate?.didConnectToPeer(nickname) + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } else { + // Just update the peer list to refresh nicknames + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + } + + default: + break + } + } +} + +extension BluetoothMeshService: CBCentralManagerDelegate { + func centralManagerDidUpdateState(_ central: CBCentralManager) { + if central.state == .poweredOn { + startScanning() + } + } + + func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { + // Connect to any device we discover - we'll filter by service later + if !discoveredPeripherals.contains(peripheral) { + discoveredPeripherals.append(peripheral) + peripheral.delegate = self + central.connect(peripheral, options: nil) + } + } + + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + peripheral.delegate = self + peripheral.discoverServices([BluetoothMeshService.serviceUUID]) + + // Extract peer ID from advertisement or generate one + var peerID = peripheral.identifier.uuidString.prefix(8).lowercased() + if let name = peripheral.name, name.hasPrefix("bitchat-") { + peerID = String(name.dropFirst(8)) + } + + connectedPeripherals[String(peerID)] = peripheral + + // Update peer list to show we're connecting + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + if let peerID = connectedPeripherals.first(where: { $0.value == peripheral })?.key { + connectedPeripherals.removeValue(forKey: peerID) + peripheralCharacteristics.removeValue(forKey: peripheral) + + // Remove from active peers + activePeers.remove(peerID) + + // Only show disconnect if we have a resolved nickname + if let nickname = peerNicknames[peerID], nickname != peerID { + DispatchQueue.main.async { + self.delegate?.didDisconnectFromPeer(nickname) + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } else { + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + } + + // Remove from discovered list to allow reconnection + discoveredPeripherals.removeAll { $0 == peripheral } + + // Continue scanning for reconnection + if centralManager.state == .poweredOn { + // Always restart scan to ensure we can reconnect + centralManager.stopScan() + centralManager.scanForPeripherals(withServices: [BluetoothMeshService.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]) + } + } +} + +extension BluetoothMeshService: CBPeripheralDelegate { + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + guard let services = peripheral.services else { return } + + for service in services { + peripheral.discoverCharacteristics([BluetoothMeshService.characteristicUUID], for: service) + } + } + + func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + guard let characteristics = service.characteristics else { return } + + for characteristic in characteristics { + if characteristic.uuid == BluetoothMeshService.characteristicUUID { + peripheral.setNotifyValue(true, for: characteristic) + peripheralCharacteristics[peripheral] = characteristic + + // Wait a moment for subscription to complete before sending data + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self = self else { return } + + let publicKeyData = self.encryptionService.publicKey.rawRepresentation + let packet = BitchatPacket( + type: MessageType.keyExchange.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: nil, + timestamp: UInt64(Date().timeIntervalSince1970), + payload: try! JSONEncoder().encode(publicKeyData), + signature: nil, + ttl: 1 + ) + + if let data = packet.data { + peripheral.writeValue(data, for: characteristic, type: .withResponse) + } + + // Also send announce packet immediately + if let vm = self.delegate as? ChatViewModel { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + guard let self = self else { return } + let announcePacket = BitchatPacket( + type: MessageType.announce.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: nil, + timestamp: UInt64(Date().timeIntervalSince1970), + payload: vm.nickname.data(using: .utf8)!, + signature: nil, + ttl: 1 + ) + if let data = announcePacket.data { + peripheral.writeValue(data, for: characteristic, type: .withResponse) + } + } + } + } + } + } + } + + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + guard let data = characteristic.value, + let packet = BitchatPacket.from(data) else { + return + } + + let peerID = connectedPeripherals.first(where: { $0.value == peripheral })?.key ?? "unknown" + handleReceivedPacket(packet, from: peerID) + } + + func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { + // Handle write completion if needed + } + + func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { + peripheral.discoverServices([BluetoothMeshService.serviceUUID]) + } + + func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { + // Handle notification state update if needed + } +} + +extension BluetoothMeshService: CBPeripheralManagerDelegate { + func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { + switch peripheral.state { + case .poweredOn: + setupPeripheral() + startAdvertising() + default: + break + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) { + // Handle service addition if needed + } + + func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { + for request in requests { + if let data = request.value, + let packet = BitchatPacket.from(data) { + // Try to identify peer from packet + let peerID = String(data: packet.senderID, encoding: .utf8) ?? "unknown" + + // Store the central for updates + if !subscribedCentrals.contains(request.central) { + subscribedCentrals.append(request.central) + } + + // Track this peer as connected + if peerID != "unknown" && peerID != myPeerID { + // Send key exchange back if we haven't already + if packet.type == MessageType.keyExchange.rawValue { + let publicKeyData = self.encryptionService.publicKey.rawRepresentation + let responsePacket = BitchatPacket( + type: MessageType.keyExchange.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: peerID.data(using: .utf8), + timestamp: UInt64(Date().timeIntervalSince1970), + payload: try! JSONEncoder().encode(publicKeyData), + signature: nil, + ttl: 1 + ) + if let data = responsePacket.data { + peripheral.updateValue(data, for: self.characteristic, onSubscribedCentrals: [request.central]) + } + + // Also send announce immediately after key exchange + if let vm = self.delegate as? ChatViewModel { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self = self else { return } + let announcePacket = BitchatPacket( + type: MessageType.announce.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: peerID.data(using: .utf8), + timestamp: UInt64(Date().timeIntervalSince1970), + payload: vm.nickname.data(using: .utf8)!, + signature: nil, + ttl: 1 + ) + if let data = announcePacket.data { + peripheral.updateValue(data, for: self.characteristic, onSubscribedCentrals: [request.central]) + } + } + } + } + + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + + handleReceivedPacket(packet, from: peerID) + peripheral.respond(to: request, withResult: .success) + } + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { + if !subscribedCentrals.contains(central) { + subscribedCentrals.append(central) + + // Send our public key to the newly connected central + let publicKeyData = encryptionService.publicKey.rawRepresentation + let keyPacket = BitchatPacket( + type: MessageType.keyExchange.rawValue, + senderID: myPeerID.data(using: .utf8)!, + recipientID: nil, + timestamp: UInt64(Date().timeIntervalSince1970), + payload: try! JSONEncoder().encode(publicKeyData), + signature: nil, + ttl: 1 + ) + + if let data = keyPacket.data { + peripheral.updateValue(data, for: self.characteristic, onSubscribedCentrals: [central]) + + // Also send announce after key exchange + if let vm = delegate as? ChatViewModel { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in + guard let self = self else { return } + let announcePacket = BitchatPacket( + type: MessageType.announce.rawValue, + senderID: self.myPeerID.data(using: .utf8)!, + recipientID: nil, + timestamp: UInt64(Date().timeIntervalSince1970), + payload: vm.nickname.data(using: .utf8)!, + signature: nil, + ttl: 1 + ) + if let data = announcePacket.data { + peripheral.updateValue(data, for: self.characteristic, onSubscribedCentrals: [central]) + } + } + } + } + + // Update peer list to show we're connected (even without peer ID yet) + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + } + + func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { + subscribedCentrals.removeAll { $0 == central } + + // If no more centrals are subscribed, clear all central-connected peers + if subscribedCentrals.isEmpty { + // Find and remove peers that were connected as centrals only + let peersToRemove = activePeers.filter { peerID in + !connectedPeripherals.keys.contains(peerID) + } + + for peerID in peersToRemove { + activePeers.remove(peerID) + if let nickname = peerNicknames[peerID] { + DispatchQueue.main.async { + self.delegate?.didDisconnectFromPeer(nickname) + } + } + } + + DispatchQueue.main.async { + self.delegate?.didUpdatePeerList(self.getAllConnectedPeerIDs()) + } + } + + // Ensure advertising continues for reconnection + if peripheralManager.state == .poweredOn && !peripheralManager.isAdvertising { + startAdvertising() + } + } +} \ No newline at end of file diff --git a/bitchat/Services/EncryptionService.swift b/bitchat/Services/EncryptionService.swift new file mode 100644 index 00000000..c6e680dc --- /dev/null +++ b/bitchat/Services/EncryptionService.swift @@ -0,0 +1,67 @@ +import Foundation +import CryptoKit + +class EncryptionService { + private var privateKey: Curve25519.KeyAgreement.PrivateKey + public let publicKey: Curve25519.KeyAgreement.PublicKey + private var peerPublicKeys: [String: Curve25519.KeyAgreement.PublicKey] = [:] + private var sharedSecrets: [String: SymmetricKey] = [:] + + init() { + self.privateKey = Curve25519.KeyAgreement.PrivateKey() + self.publicKey = privateKey.publicKey + } + + func addPeerPublicKey(_ peerID: String, publicKeyData: Data) throws { + let publicKey = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: publicKeyData) + peerPublicKeys[peerID] = publicKey + + let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: publicKey) + let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey( + using: SHA256.self, + salt: "bitchat-v1".data(using: .utf8)!, + sharedInfo: Data(), + outputByteCount: 32 + ) + sharedSecrets[peerID] = symmetricKey + } + + func encrypt(_ data: Data, for peerID: String) throws -> Data { + guard let symmetricKey = sharedSecrets[peerID] else { + throw EncryptionError.noSharedSecret + } + + let sealedBox = try AES.GCM.seal(data, using: symmetricKey) + return sealedBox.combined ?? Data() + } + + func decrypt(_ data: Data, from peerID: String) throws -> Data { + guard let symmetricKey = sharedSecrets[peerID] else { + throw EncryptionError.noSharedSecret + } + + let sealedBox = try AES.GCM.SealedBox(combined: data) + return try AES.GCM.open(sealedBox, using: symmetricKey) + } + + func sign(_ data: Data) throws -> Data { + let signingKey = try Curve25519.Signing.PrivateKey(rawRepresentation: privateKey.rawRepresentation) + return try signingKey.signature(for: data) + } + + func verify(_ signature: Data, for data: Data, from peerID: String) throws -> Bool { + guard let peerPublicKey = peerPublicKeys[peerID] else { + return false + } + + let verifyingKey = try Curve25519.Signing.PublicKey(rawRepresentation: peerPublicKey.rawRepresentation) + return verifyingKey.isValidSignature(signature, for: data) + } +} + +enum EncryptionError: Error { + case noSharedSecret + case invalidPublicKey + case encryptionFailed + case decryptionFailed +} \ No newline at end of file diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift new file mode 100644 index 00000000..5633eee9 --- /dev/null +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -0,0 +1,160 @@ +import Foundation +import SwiftUI +import Combine +#if os(iOS) +import UIKit +#endif + +class ChatViewModel: ObservableObject { + @Published var messages: [BitchatMessage] = [] + @Published var connectedPeers: [String] = [] + @Published var nickname: String = "" { + didSet { + nicknameSaveTimer?.invalidate() + nicknameSaveTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in + self.saveNickname() + } + } + } + @Published var isConnected = false + + let meshService = BluetoothMeshService() + private let userDefaults = UserDefaults.standard + private let nicknameKey = "bitchat.nickname" + private var nicknameSaveTimer: Timer? + + init() { + loadNickname() + meshService.delegate = self + + // Start mesh service immediately + meshService.startServices() + } + + private func loadNickname() { + if let savedNickname = userDefaults.string(forKey: nicknameKey) { + nickname = savedNickname + } else { + nickname = "user\(Int.random(in: 1000...9999))" + saveNickname() + } + } + + func saveNickname() { + userDefaults.set(nickname, forKey: nicknameKey) + userDefaults.synchronize() // Force immediate save + } + + func sendMessage(_ content: String) { + guard !content.isEmpty else { return } + + // Add message to local display + let message = BitchatMessage( + sender: nickname, + content: content, + timestamp: Date(), + isRelay: false, + originalSender: nil + ) + messages.append(message) + + // Send via mesh + meshService.sendMessage(content) + } + + + func formatTimestamp(_ date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter.string(from: date) + } + + func formatMessage(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString { + var result = AttributedString() + + let isDark = colorScheme == .dark + let primaryColor = isDark ? Color.green : Color(red: 0, green: 0.5, blue: 0) + let secondaryColor = primaryColor.opacity(0.7) + + let timestamp = AttributedString("[\(formatTimestamp(message.timestamp))] ") + var timestampStyle = AttributeContainer() + timestampStyle.foregroundColor = secondaryColor + timestampStyle.font = .system(size: 12, design: .monospaced) + result.append(timestamp.mergingAttributes(timestampStyle)) + + if message.sender == "system" { + let content = AttributedString("* \(message.content) *") + var contentStyle = AttributeContainer() + contentStyle.foregroundColor = secondaryColor + contentStyle.font = .system(size: 12, design: .monospaced).italic() + result.append(content.mergingAttributes(contentStyle)) + } else { + let sender = AttributedString("<\(message.sender)> ") + var senderStyle = AttributeContainer() + senderStyle.foregroundColor = message.sender == nickname ? primaryColor : primaryColor.opacity(0.9) + senderStyle.font = .system(size: 12, weight: .medium, design: .monospaced) + result.append(sender.mergingAttributes(senderStyle)) + + let content = AttributedString(message.content) + var contentStyle = AttributeContainer() + contentStyle.font = .system(size: 14, design: .monospaced) + contentStyle.foregroundColor = isDark ? Color.white : Color.black + result.append(content.mergingAttributes(contentStyle)) + + if message.isRelay, let originalSender = message.originalSender { + let relay = AttributedString(" (via \(originalSender))") + var relayStyle = AttributeContainer() + relayStyle.foregroundColor = secondaryColor + relayStyle.font = .system(size: 11, design: .monospaced) + result.append(relay.mergingAttributes(relayStyle)) + } + } + + return result + } +} + +extension ChatViewModel: BitchatDelegate { + func didReceiveMessage(_ message: BitchatMessage) { + messages.append(message) + + #if os(iOS) + // Haptic feedback for new messages + let impactFeedback = UIImpactFeedbackGenerator(style: .light) + impactFeedback.impactOccurred() + #endif + } + + func didConnectToPeer(_ peerID: String) { + isConnected = true + let systemMessage = BitchatMessage( + sender: "system", + content: "\(peerID) has joined", + timestamp: Date(), + isRelay: false, + originalSender: nil + ) + messages.append(systemMessage) + } + + func didDisconnectFromPeer(_ peerID: String) { + let systemMessage = BitchatMessage( + sender: "system", + content: "\(peerID) has left", + timestamp: Date(), + isRelay: false, + originalSender: nil + ) + messages.append(systemMessage) + } + + func didUpdatePeerList(_ peers: [String]) { + connectedPeers = peers + isConnected = !peers.isEmpty + + // If we just disconnected from all peers, ensure UI updates + if peers.isEmpty && isConnected { + isConnected = false + } + } +} \ No newline at end of file diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift new file mode 100644 index 00000000..9b21a8ae --- /dev/null +++ b/bitchat/Views/ContentView.swift @@ -0,0 +1,177 @@ +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var viewModel: ChatViewModel + @State private var messageText = "" + @FocusState private var isTextFieldFocused: Bool + @Environment(\.colorScheme) var colorScheme + @State private var showPeerList = false + + private var backgroundColor: Color { + colorScheme == .dark ? Color.black : Color.white + } + + private var textColor: Color { + colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0) + } + + private var secondaryTextColor: Color { + colorScheme == .dark ? Color.green.opacity(0.8) : Color(red: 0, green: 0.5, blue: 0).opacity(0.8) + } + + var body: some View { + VStack(spacing: 0) { + headerView + Divider() + messagesView + Divider() + inputView + } + .background(backgroundColor) + .foregroundColor(textColor) + #if os(macOS) + .frame(minWidth: 600, minHeight: 400) + #endif + } + + private var headerView: some View { + HStack { + Text("bitchat") + .font(.system(size: 18, weight: .medium, design: .monospaced)) + .foregroundColor(textColor) + + Spacer() + + Menu { + if viewModel.connectedPeers.isEmpty { + Text("No peers connected") + .font(.system(size: 12, design: .monospaced)) + } else { + let peerNicknames = viewModel.meshService.getPeerNicknames() + ForEach(viewModel.connectedPeers, id: \.self) { peerID in + if let displayName = peerNicknames[peerID], displayName != peerID { + // Only show if we have a real nickname + Label(displayName, systemImage: "person.fill") + .font(.system(size: 12, design: .monospaced)) + } + } + } + } label: { + HStack(spacing: 4) { + Circle() + .fill(viewModel.isConnected ? textColor : Color.red) + .frame(width: 8, height: 8) + + HStack(spacing: 0) { + Text(viewModel.isConnected ? "\(viewModel.connectedPeers.count) \(viewModel.connectedPeers.count == 1 ? "peer" : "peers")" : "Scanning...") + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(secondaryTextColor) + Text(Image(systemName: "chevron.down")) + .font(.system(size: 10)) + .foregroundColor(secondaryTextColor) + .baselineOffset(-1) + } + } + .contentShape(Rectangle()) // Make entire area tappable + } + .buttonStyle(.plain) + .menuStyle(.borderlessButton) + .menuIndicator(.hidden) + + Spacer() + + HStack(spacing: 4) { + Text("nick:") + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(secondaryTextColor) + + TextField("nickname", text: $viewModel.nickname) + .textFieldStyle(.plain) + .font(.system(size: 12, design: .monospaced)) + .frame(maxWidth: 100) + .foregroundColor(textColor) + .onChange(of: viewModel.nickname) { _ in + viewModel.saveNickname() + } + .onSubmit { + viewModel.saveNickname() + } + } + } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(backgroundColor.opacity(0.95)) + } + + private var messagesView: some View { + ScrollViewReader { proxy in + ScrollView { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(Array(viewModel.messages.enumerated()), id: \.offset) { index, message in + VStack(alignment: .leading, spacing: 4) { + Text(viewModel.formatMessage(message, colorScheme: colorScheme)) + .font(.system(size: 14, design: .monospaced)) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + + } + .padding(.horizontal, 12) + .padding(.vertical, 2) + .id(index) + } + } + .padding(.vertical, 8) + } + .background(backgroundColor) + .onChange(of: viewModel.messages.count) { _ in + withAnimation { + proxy.scrollTo(viewModel.messages.count - 1, anchor: .bottom) + } + } + } + } + + private var inputView: some View { + HStack(spacing: 4) { + Text("[\(viewModel.formatTimestamp(Date()))]") + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(secondaryTextColor) + .lineLimit(1) + .fixedSize() + .padding(.leading, 12) + + Text("<\(viewModel.nickname)>") + .font(.system(size: 12, weight: .medium, design: .monospaced)) + .foregroundColor(textColor) + .lineLimit(1) + .fixedSize() + + TextField("", text: $messageText) + .textFieldStyle(.plain) + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(textColor) + .focused($isTextFieldFocused) + .onSubmit { + sendMessage() + } + + Button(action: sendMessage) { + Image(systemName: "arrow.right.circle.fill") + .font(.system(size: 16)) + .foregroundColor(textColor) + } + .buttonStyle(.plain) + .padding(.trailing, 12) + } + .padding(.vertical, 10) + .background(backgroundColor.opacity(0.95)) + .onAppear { + isTextFieldFocused = true + } + } + + private func sendMessage() { + viewModel.sendMessage(messageText) + messageText = "" + } +} \ No newline at end of file diff --git a/project.yml b/project.yml new file mode 100644 index 00000000..672340a0 --- /dev/null +++ b/project.yml @@ -0,0 +1,46 @@ +name: bitchat +options: + bundleIdPrefix: com.bitchat + deploymentTarget: + iOS: 16.0 + macOS: 13.0 + createIntermediateGroups: true + +settings: + MARKETING_VERSION: 1.0.0 + CURRENT_PROJECT_VERSION: 1 + DEVELOPMENT_TEAM: "" + +targets: + bitchat: + type: application + platform: [iOS, macOS] + sources: + - bitchat + info: + path: bitchat/Info.plist + properties: + CFBundleDisplayName: bitchat + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + LSMinimumSystemVersion: $(MACOSX_DEPLOYMENT_TARGET) + NSBluetoothAlwaysUsageDescription: bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users. + NSBluetoothPeripheralUsageDescription: bitchat uses Bluetooth to discover and connect with other bitchat users nearby. + UILaunchScreen: + UIColorName: Black + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + settings: + PRODUCT_BUNDLE_IDENTIFIER: com.bitchat.app + INFOPLIST_FILE: bitchat/Info.plist + ENABLE_PREVIEWS: YES + SWIFT_VERSION: 5.0 + IPHONEOS_DEPLOYMENT_TARGET: 16.0 + MACOSX_DEPLOYMENT_TARGET: 13.0 + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: YES + CODE_SIGN_STYLE: Automatic + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS: YES \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 00000000..72b636a2 --- /dev/null +++ b/setup.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +echo "bitchat Setup Script" +echo "===================" + +# Check if XcodeGen is installed +if command -v xcodegen &> /dev/null; then + echo "✓ XcodeGen found" + echo "Generating Xcode project..." + xcodegen generate + echo "✓ Project generated successfully" + echo "" + echo "To open the project, run:" + echo " open bitchat.xcodeproj" +else + echo "⚠️ XcodeGen not found" + echo "" + echo "You have several options:" + echo "1. Install XcodeGen:" + echo " brew install xcodegen" + echo "" + echo "2. Open with Swift Package Manager:" + echo " open Package.swift" + echo "" + echo "3. Create a new Xcode project manually and add the source files" +fi + +echo "" +echo "Project Structure:" +echo "- bitchat/ Main source files" +echo " - BitchatApp.swift App entry point" +echo " - Views/ SwiftUI views" +echo " - ViewModels/ View models" +echo " - Services/ Bluetooth and encryption" +echo " - Protocols/ Protocol definitions" +echo "" +echo "Remember to:" +echo "1. Enable Bluetooth in device settings" +echo "2. Run on physical devices (Bluetooth doesn't work in simulator)" +echo "3. Test with multiple devices for mesh functionality" \ No newline at end of file