mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 16:05:19 +00:00
When a Noise session establishes with a peer I verified and that peer advertises the .vouch capability, send signed attestations (up to 16, most recently verified first, at most once per peer per 24h) for the OTHER fingerprints I verified. Receivers accept vouches only from senders they verified themselves, verify the Ed25519 signature against the sender's announce-bound signing key, and surface the result as a new derived trust tier: vouched (unfilled seal) between casual and trusted. Protocol: - NoisePayloadType.vouch = 0x12 carries a batch of TLV attestations: voucheeFingerprint (32B), voucheeSigningKey (32B), timestamp (uint64 ms BE), Ed25519 signature over "bitchat-vouch-v1" | fingerprint | signingKey | timestamp. The voucher is implicit in the authenticated session. - PeerCapabilities.localSupported now advertises .vouch. Storage (SecureIdentityStateManager / IdentityCache): - vouches keyed by vouchee, capped at 8 vouchers each; validity is recomputed on read (voucher still verified-by-me, < 30 days old), so unverifying a voucher retires their vouches without cascade deletes. - New IdentityCache fields are Optional so pre-existing encrypted caches decode cleanly; TrustLevel.vouched is inserted mid-ladder but raw values are strings, so persisted values are unaffected (and vouched itself is never persisted). - Panic wipe clears vouch state with the rest of the identity cache. UI: unfilled checkmark.seal badge in the mesh peer list (filled seal stays exclusive to verified) and a "vouched for by N people you verified" section with voucher names in FingerprintView; VoiceOver labels and xcstrings entries included. Tests: attestation encode/decode + signature (forged/tampered/expired), accept-policy gates, batch cap, trust-level derivation incl. voucher invalidation, persistence compat, and coordinator exchange/accept policies. Full macOS suite: 1088 tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
162 lines
4.9 KiB
Swift
162 lines
4.9 KiB
Swift
//
|
|
// MockIdentityManager.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import Foundation
|
|
import BitFoundation
|
|
@testable import bitchat
|
|
|
|
final class MockIdentityManager: SecureIdentityStateManagerProtocol {
|
|
private let keychain: KeychainManagerProtocol
|
|
private var blockedFingerprints: Set<String> = []
|
|
private var blockedNostrPubkeys: Set<String> = []
|
|
private var socialIdentities: [String: SocialIdentity] = [:]
|
|
|
|
init(_ keychain: KeychainManagerProtocol) {
|
|
self.keychain = keychain
|
|
}
|
|
|
|
func loadIdentityCache() {}
|
|
|
|
func saveIdentityCache() {}
|
|
|
|
func forceSave() {}
|
|
|
|
func getSocialIdentity(for fingerprint: String) -> SocialIdentity? {
|
|
socialIdentities[fingerprint]
|
|
}
|
|
|
|
func upsertCryptographicIdentity(fingerprint: String, noisePublicKey: Data, signingPublicKey: Data?, claimedNickname: String?) {}
|
|
|
|
func getCryptoIdentitiesByPeerIDPrefix(_ peerID: PeerID) -> [CryptographicIdentity] {
|
|
[]
|
|
}
|
|
|
|
func updateSocialIdentity(_ identity: SocialIdentity) {
|
|
socialIdentities[identity.fingerprint] = identity
|
|
if identity.isBlocked {
|
|
blockedFingerprints.insert(identity.fingerprint)
|
|
} else {
|
|
blockedFingerprints.remove(identity.fingerprint)
|
|
}
|
|
}
|
|
|
|
func getFavorites() -> Set<String> {
|
|
Set()
|
|
}
|
|
|
|
func setFavorite(_ fingerprint: String, isFavorite: Bool) {}
|
|
|
|
func isFavorite(fingerprint: String) -> Bool {
|
|
false
|
|
}
|
|
|
|
func isBlocked(fingerprint: String) -> Bool {
|
|
blockedFingerprints.contains(fingerprint) || socialIdentities[fingerprint]?.isBlocked == true
|
|
}
|
|
|
|
func setBlocked(_ fingerprint: String, isBlocked: Bool) {
|
|
if var identity = socialIdentities[fingerprint] {
|
|
identity.isBlocked = isBlocked
|
|
socialIdentities[fingerprint] = identity
|
|
} else {
|
|
let identity = SocialIdentity(
|
|
fingerprint: fingerprint,
|
|
localPetname: nil,
|
|
claimedNickname: "",
|
|
trustLevel: .unknown,
|
|
isFavorite: false,
|
|
isBlocked: isBlocked,
|
|
notes: nil
|
|
)
|
|
socialIdentities[fingerprint] = identity
|
|
}
|
|
if isBlocked {
|
|
blockedFingerprints.insert(fingerprint)
|
|
} else {
|
|
blockedFingerprints.remove(fingerprint)
|
|
}
|
|
}
|
|
|
|
func isNostrBlocked(pubkeyHexLowercased: String) -> Bool {
|
|
blockedNostrPubkeys.contains(pubkeyHexLowercased)
|
|
}
|
|
|
|
func setNostrBlocked(_ pubkeyHexLowercased: String, isBlocked: Bool) {
|
|
if isBlocked {
|
|
blockedNostrPubkeys.insert(pubkeyHexLowercased)
|
|
} else {
|
|
blockedNostrPubkeys.remove(pubkeyHexLowercased)
|
|
}
|
|
}
|
|
|
|
func getBlockedNostrPubkeys() -> Set<String> {
|
|
blockedNostrPubkeys
|
|
}
|
|
|
|
func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState) {}
|
|
|
|
func updateHandshakeState(peerID: PeerID, state: HandshakeState) {}
|
|
|
|
func clearAllIdentityData() {}
|
|
|
|
func removeEphemeralSession(peerID: PeerID) {}
|
|
|
|
func setVerified(fingerprint: String, verified: Bool) {}
|
|
|
|
func isVerified(fingerprint: String) -> Bool {
|
|
true
|
|
}
|
|
|
|
func getVerifiedFingerprints() -> Set<String> {
|
|
Set()
|
|
}
|
|
|
|
// MARK: Vouching (transitive verification)
|
|
|
|
private var vouchesByVouchee: [String: [VouchRecord]] = [:]
|
|
private var vouchBatchSentAt: [String: Date] = [:]
|
|
|
|
@discardableResult
|
|
func recordVouch(voucheeFingerprint: String, voucherFingerprint: String, timestamp: Date) -> Bool {
|
|
guard voucheeFingerprint != voucherFingerprint else { return false }
|
|
var records = vouchesByVouchee[voucheeFingerprint] ?? []
|
|
records.removeAll { $0.voucherFingerprint == voucherFingerprint }
|
|
records.append(VouchRecord(voucherFingerprint: voucherFingerprint, timestamp: timestamp))
|
|
vouchesByVouchee[voucheeFingerprint] = records
|
|
return true
|
|
}
|
|
|
|
func validVouchers(for fingerprint: String) -> [VouchRecord] {
|
|
vouchesByVouchee[fingerprint] ?? []
|
|
}
|
|
|
|
func isVouched(fingerprint: String) -> Bool {
|
|
!(vouchesByVouchee[fingerprint] ?? []).isEmpty
|
|
}
|
|
|
|
func effectiveTrustLevel(for fingerprint: String) -> TrustLevel {
|
|
socialIdentities[fingerprint]?.trustLevel ?? .unknown
|
|
}
|
|
|
|
func lastVouchBatchSent(to fingerprint: String) -> Date? {
|
|
vouchBatchSentAt[fingerprint]
|
|
}
|
|
|
|
func markVouchBatchSent(to fingerprint: String, at date: Date) {
|
|
vouchBatchSentAt[fingerprint] = date
|
|
}
|
|
|
|
func signingPublicKey(forFingerprint fingerprint: String) -> Data? {
|
|
nil
|
|
}
|
|
|
|
func mostRecentlyVerifiedFingerprints(limit: Int, excluding fingerprint: String) -> [String] {
|
|
[]
|
|
}
|
|
}
|