mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-29 07:27:16 +00:00
* Stop claiming encryption and privacy properties that aren't live
Two of the app's most safety-critical signals could show more security
than actually existed:
1. getEncryptionStatus was sticky-optimistic: any peer whose fingerprint
was ever persisted resolved to noiseSecured/noiseVerified even when
the live Noise session was .none, .handshaking, or .failed. After a
cold launch or a handshake failure the DM header showed a solid lock
and the composer caption claimed "end-to-end encrypted" with no
secure session in existence. The status now reflects only the live
session; a remembered fingerprint decides what an established
session upgrades to (verified vs secured), nothing more. Peers
reachable only over Nostr keep the encrypted caption — gift-wrapped
delivery is end-to-end without a Noise session.
2. Privacy copy told three lies (all 30 locales fixed):
- "ephemeral identity — new peer ID generated regularly": the peer
ID is stable until a panic wipe (rotation is spec-only). Now
"local-only identity — your identity is a keypair created on this
device; panic wipe replaces it instantly."
- "no servers, accounts, or data collection": location channels and
internet delivery go through Nostr relays. Now "no accounts, no
phone numbers, no data collection."
- location channels: "your IP address is hidden by routing all
traffic over tor" — tor is a toggle. The description now says tor
is the default, and the sheet shows the existing red tor-off
warning at the point of joining whenever the toggle is off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Review fixes: gate nostr availability on a stored recipient key
- Codex P2 / review: a mutual favorite with a nil peerNostrPublicKey
still reported .nostrAvailable, so the DM caption could claim
"end-to-end encrypted" with neither a Noise session nor a usable
Nostr key. BitchatPeer.connectionState (and the header fallback in
PrivateConversationModel) now require a stored recipient key —
mirroring NostrTransport's reachability rule. Regression test:
mutual + nil key → .offline; key present → .nostrAvailable.
- Copy: "local-only identity" → "device-local identity" (all 30
locales) — once a fingerprint or QR is shared it isn't "only" local;
device-local says where the keys live without over-claiming.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
109 lines
4.6 KiB
Swift
109 lines
4.6 KiB
Swift
import Foundation
|
|
import Testing
|
|
import BitFoundation
|
|
@testable import bitchat
|
|
|
|
@Suite("BitchatPeer Tests")
|
|
struct BitchatPeerTests {
|
|
typealias FavoriteRelationship = FavoritesPersistenceService.FavoriteRelationship
|
|
|
|
@Test("Connection state prioritizes bluetooth, mesh, nostr, then offline")
|
|
func connectionStatePriorityIsCorrect() {
|
|
let peerID = PeerID(str: "0123456789abcdef")
|
|
let noiseKey = Data((0..<32).map(UInt8.init))
|
|
let mutual = makeRelationship(isFavorite: true, theyFavoritedUs: true)
|
|
|
|
let bluetooth = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "A", isConnected: true, isReachable: true)
|
|
let mesh = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "A", isConnected: false, isReachable: true)
|
|
var nostr = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "A", isConnected: false, isReachable: false)
|
|
nostr.favoriteStatus = mutual
|
|
let offline = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "A", isConnected: false, isReachable: false)
|
|
|
|
#expect(bluetooth.connectionState == .bluetoothConnected)
|
|
#expect(mesh.connectionState == .meshReachable)
|
|
#expect(nostr.connectionState == .nostrAvailable)
|
|
#expect(offline.connectionState == .offline)
|
|
}
|
|
|
|
@Test("Display name falls back to peer prefix and offline icon reflects inbound favorite")
|
|
func displayNameAndOfflineIconUseDerivedState() {
|
|
let peerID = PeerID(str: "fedcba9876543210")
|
|
let noiseKey = Data((32..<64).map(UInt8.init))
|
|
var peer = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "", isConnected: false, isReachable: false)
|
|
peer.favoriteStatus = makeRelationship(isFavorite: false, theyFavoritedUs: true)
|
|
|
|
#expect(peer.displayName == String(peerID.id.prefix(8)))
|
|
#expect(peer.statusIcon == "🌙")
|
|
}
|
|
|
|
@Test("Mutual favorite without a stored Nostr key is offline, not nostr-available")
|
|
func mutualFavoriteWithoutNostrKeyIsOffline() {
|
|
let peerID = PeerID(str: "0123456789abcdef")
|
|
let noiseKey = Data((0..<32).map(UInt8.init))
|
|
var peer = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "A", isConnected: false, isReachable: false)
|
|
peer.favoriteStatus = FavoriteRelationship(
|
|
peerNoisePublicKey: noiseKey,
|
|
peerNostrPublicKey: nil,
|
|
peerNickname: "A",
|
|
isFavorite: true,
|
|
theyFavoritedUs: true,
|
|
favoritedAt: Date(timeIntervalSince1970: 1),
|
|
lastUpdated: Date(timeIntervalSince1970: 2)
|
|
)
|
|
|
|
// Nothing to seal a Nostr envelope to: claiming availability here
|
|
// would relight the DM header's "end-to-end encrypted" caption with
|
|
// neither a Noise session nor a usable recipient key.
|
|
#expect(peer.connectionState == .offline)
|
|
|
|
peer.nostrPublicKey = "abcdef"
|
|
#expect(peer.connectionState == .nostrAvailable)
|
|
}
|
|
|
|
@Test("Mutual offline peers show Nostr icon")
|
|
func mutualFavoriteOfflinePeerShowsNostrIcon() {
|
|
let peerID = PeerID(str: "0011223344556677")
|
|
let noiseKey = Data((64..<96).map(UInt8.init))
|
|
var peer = BitchatPeer(peerID: peerID, noisePublicKey: noiseKey, nickname: "Peer", isConnected: false, isReachable: false)
|
|
peer.favoriteStatus = makeRelationship(isFavorite: true, theyFavoritedUs: true)
|
|
|
|
#expect(peer.statusIcon == "🌐")
|
|
#expect(peer.isFavorite)
|
|
#expect(peer.isMutualFavorite)
|
|
#expect(peer.theyFavoritedUs)
|
|
}
|
|
|
|
@Test("Equality is based only on peer ID")
|
|
func equalityUsesPeerIDOnly() {
|
|
let peerID = PeerID(str: "8899aabbccddeeff")
|
|
let first = BitchatPeer(
|
|
peerID: peerID,
|
|
noisePublicKey: Data(repeating: 1, count: 32),
|
|
nickname: "First",
|
|
isConnected: false,
|
|
isReachable: false
|
|
)
|
|
let second = BitchatPeer(
|
|
peerID: peerID,
|
|
noisePublicKey: Data(repeating: 2, count: 32),
|
|
nickname: "Second",
|
|
isConnected: true,
|
|
isReachable: true
|
|
)
|
|
|
|
#expect(first == second)
|
|
}
|
|
|
|
private func makeRelationship(isFavorite: Bool, theyFavoritedUs: Bool) -> FavoriteRelationship {
|
|
FavoriteRelationship(
|
|
peerNoisePublicKey: Data(repeating: 7, count: 32),
|
|
peerNostrPublicKey: "npub1example",
|
|
peerNickname: "Peer",
|
|
isFavorite: isFavorite,
|
|
theyFavoritedUs: theyFavoritedUs,
|
|
favoritedAt: Date(timeIntervalSince1970: 1),
|
|
lastUpdated: Date(timeIntervalSince1970: 2)
|
|
)
|
|
}
|
|
}
|