mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 23:25:19 +00:00
NotificationService.shared and FavoritesPersistenceService.shared accesses across nine coordinators/components consolidate into ChatViewModel witnesses behind intent-named context members (notifyPrivateMessage, notifyMention, favoriteRelationship(forNoiseKey:), allFavoriteRelationships, postLocalNotification, ...). Singleton reach from coordinators is now zero; nine new tests cover previously untestable notification/favorites flows. Also root-causes the long-flaky gift-wrap dedup test: parallel tests share LocationChannelManager.shared, so channel-switch tests trigger clearProcessedNostrEvents() on every live ChatViewModel, wiping the dedup record mid-test. The invariant (forged-signature copies never poison dedup) now lives as a deterministic NostrInboundPipeline mock-context test; two Schnorr concurrency probes added along the way stay as regression guards for the P256K shared-context assumptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
3.2 KiB
Swift
83 lines
3.2 KiB
Swift
import Testing
|
|
import Foundation
|
|
@testable import bitchat
|
|
|
|
struct SchnorrConcurrencyRepro {
|
|
@Test func concurrentVerificationOfValidEventAlwaysSucceeds() async throws {
|
|
let identity = try NostrIdentity.generate()
|
|
let event = NostrEvent(
|
|
pubkey: identity.publicKeyHex,
|
|
createdAt: Date(),
|
|
kind: .ephemeralEvent,
|
|
tags: [["g", "sn"]],
|
|
content: "concurrency probe"
|
|
)
|
|
let signed = try event.sign(with: identity.schnorrSigningKey())
|
|
#expect(signed.isValidSignature())
|
|
|
|
let failures = await withTaskGroup(of: Int.self) { group in
|
|
for _ in 0..<8 {
|
|
group.addTask {
|
|
var localFailures = 0
|
|
for _ in 0..<250 {
|
|
if !signed.isValidSignature() { localFailures += 1 }
|
|
}
|
|
return localFailures
|
|
}
|
|
}
|
|
var total = 0
|
|
for await f in group { total += f }
|
|
return total
|
|
}
|
|
#expect(failures == 0, "Schnorr verification returned false \(failures)/2000 times under concurrency")
|
|
}
|
|
|
|
@Test func verificationSurvivesConcurrentSigningAndKeyGeneration() async throws {
|
|
let identity = try NostrIdentity.generate()
|
|
let event = NostrEvent(
|
|
pubkey: identity.publicKeyHex,
|
|
createdAt: Date(),
|
|
kind: .ephemeralEvent,
|
|
tags: [["g", "sn"]],
|
|
content: "concurrency probe"
|
|
)
|
|
let signed = try event.sign(with: identity.schnorrSigningKey())
|
|
#expect(signed.isValidSignature())
|
|
|
|
// Signing and key generation may mutate shared library state
|
|
// (secp256k1 context randomization); verification must stay correct
|
|
// while they run on other tasks.
|
|
let failures = await withTaskGroup(of: Int.self) { group in
|
|
for worker in 0..<8 {
|
|
group.addTask {
|
|
if worker < 4 {
|
|
var localFailures = 0
|
|
for _ in 0..<250 {
|
|
if !signed.isValidSignature() { localFailures += 1 }
|
|
}
|
|
return localFailures
|
|
} else {
|
|
for i in 0..<100 {
|
|
if let id = try? NostrIdentity.generate() {
|
|
let e = NostrEvent(
|
|
pubkey: id.publicKeyHex,
|
|
createdAt: Date(),
|
|
kind: .ephemeralEvent,
|
|
tags: [],
|
|
content: "signer \(worker)-\(i)"
|
|
)
|
|
_ = try? e.sign(with: id.schnorrSigningKey())
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
var total = 0
|
|
for await f in group { total += f }
|
|
return total
|
|
}
|
|
#expect(failures == 0, "Schnorr verification returned false \(failures)/1000 times while signing ran concurrently")
|
|
}
|
|
}
|