mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 07:45:21 +00:00
Small encrypted crews (hard cap 16) between public broadcast and 1:1 DMs: Protocol - MessageType.groupMessage = 0x25: broadcast packets with a cleartext 16-byte group ID + epoch, ChaCha20-Poly1305 ciphertext (epoch bound as AEAD AAD), inner Ed25519 sender signature over "bitchat-group-msg-v1"|groupID|messageID|timestamp|content - NoisePayloadType.groupInvite = 0x06 / .groupKeyUpdate = 0x07: creator-signed group state (key, epoch, roster) 1:1 over Noise; signature over "bitchat-group-v1"|groupID|epoch|key-hash|roster-hash and the Noise session peer must BE the creator - SyncTypeFlags bit 10 (groupMessage): variable-length LE bitfield widens 1 -> 2 bytes inside the length-prefixed REQUEST_SYNC TLV; old clients ignore unknown bits and answer with types they know - PeerCapabilities.localSupported now advertises .groups Storage - GroupStore: symmetric keys in the keychain, roster/name/epoch as protected JSON in Application Support; wiped in panicClearAllData() Behavior - Non-members relay 0x25 like any broadcast but cannot read it; group messages join gossip-sync backfill with the public-message window - Receivers drop wrong-epoch envelopes, bad sender signatures, and senders missing from the creator-signed roster - Fire-and-flood delivery (no per-member acks in v1) UI - Groups open as chat windows through the private-chat sheet (virtual "group_" peer IDs); groups section in the people sheet; /group create/invite/remove/leave/list commands; invitees get a system message + notification and the group appears in their people sheet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.4 KiB
Swift
63 lines
2.4 KiB
Swift
//
|
|
// SyncTypeFlagsGroupTests.swift
|
|
// bitchat
|
|
//
|
|
// Wire-compat proof for the groupMessage sync bit (bit 10): the types
|
|
// bitfield widens from 1 to 2 bytes, and clients that don't know the bit
|
|
// simply ignore it.
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import Foundation
|
|
import Testing
|
|
import BitFoundation
|
|
@testable import bitchat
|
|
|
|
struct SyncTypeFlagsGroupTests {
|
|
|
|
@Test func groupMessageOccupiesBitTen() {
|
|
#expect(SyncTypeFlags.groupMessage.rawValue == 1 << 10)
|
|
#expect(SyncTypeFlags.groupMessage.contains(.groupMessage))
|
|
#expect(!SyncTypeFlags.publicMessages.contains(.groupMessage))
|
|
}
|
|
|
|
@Test func extendedBitfieldWidensToTwoBytes() throws {
|
|
// Legacy flags fit one byte…
|
|
#expect(SyncTypeFlags.publicMessages.toData() == Data([0x03]))
|
|
|
|
// …the group bit widens the little-endian encoding to two bytes.
|
|
let combined = SyncTypeFlags.publicMessages.union(.groupMessage)
|
|
let encoded = try #require(combined.toData())
|
|
#expect(encoded == Data([0x03, 0x04]))
|
|
|
|
let decoded = try #require(SyncTypeFlags.decode(encoded))
|
|
#expect(decoded == combined)
|
|
#expect(Set(decoded.toMessageTypes()) == Set([.announce, .message, .groupMessage]))
|
|
}
|
|
|
|
@Test func unknownBitsAreIgnoredNotRejected() throws {
|
|
// An "old client" reading a 2-byte field keeps the raw bits but maps
|
|
// unknown bit indices to no message type — it answers with the types
|
|
// it knows instead of dropping the request.
|
|
let futuristic = try #require(SyncTypeFlags.decode(Data([0x03, 0xFC])))
|
|
#expect(Set(futuristic.toMessageTypes()) == Set([.announce, .message, .groupMessage]))
|
|
#expect(futuristic.contains(.announce))
|
|
#expect(futuristic.contains(.message))
|
|
}
|
|
|
|
@Test func requestSyncPacketRoundTripsGroupFlag() throws {
|
|
let types = SyncTypeFlags.publicMessages.union(.groupMessage)
|
|
let packet = RequestSyncPacket(p: 8, m: 1024, data: Data([0xAB, 0xCD]), types: types)
|
|
let encoded = packet.encode()
|
|
|
|
let decoded = try #require(RequestSyncPacket.decode(from: encoded))
|
|
#expect(decoded.types == types)
|
|
#expect(decoded.types?.contains(.groupMessage) == true)
|
|
#expect(decoded.p == 8)
|
|
#expect(decoded.m == 1024)
|
|
#expect(decoded.data == Data([0xAB, 0xCD]))
|
|
}
|
|
}
|