Merge branch 'main' into feat/courier

This commit is contained in:
jack
2026-06-17 10:03:07 +02:00
committed by GitHub
23 changed files with 1040 additions and 534 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
MARKETING_VERSION = 1.5.2 MARKETING_VERSION = 1.5.3
CURRENT_PROJECT_VERSION = 1 CURRENT_PROJECT_VERSION = 1
IPHONEOS_DEPLOYMENT_TARGET = 16.0 IPHONEOS_DEPLOYMENT_TARGET = 16.0
+4 -4
View File
@@ -561,7 +561,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -620,7 +620,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -655,7 +655,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)"; MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -749,7 +749,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)"; MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
MARKETING_VERSION = 1.5.2; MARKETING_VERSION = 1.5.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = bitchat; PRODUCT_NAME = bitchat;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -151,38 +151,68 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
// Thread safety // Thread safety
private let queue = DispatchQueue(label: "bitchat.identity.state", attributes: .concurrent) private let queue = DispatchQueue(label: "bitchat.identity.state", attributes: .concurrent)
// Debouncing for keychain saves // Pending-save coalescing flag. Reads/writes are serialized on `queue`.
private var saveTimer: Timer? // Persistence is done with a fire-and-forget `queue.async(.barrier)` rather
private let saveDebounceInterval: TimeInterval = 2.0 // Save at most once every 2 seconds // than a retained DispatchSourceTimer: a lingering, never-cancelled timer
// keeps the dispatch machinery alive and prevents the unit-test process from
// exiting. (The original code used Timer.scheduledTimer on a GCD queue with
// no run loop, so saves never actually fired.)
private var pendingSave = false private var pendingSave = false
// Encryption key // Encryption key
private let encryptionKey: SymmetricKey private let encryptionKey: SymmetricKey
/// True when `encryptionKey` is a throwaway generated this session because the
/// persisted key could not be read (device locked / access denied). In that
/// state we must NOT persist (it would overwrite the real cache with data the
/// next launch can't decrypt) and must NOT delete the existing cache.
private let encryptionKeyIsEphemeral: Bool
init(_ keychain: KeychainManagerProtocol) { init(_ keychain: KeychainManagerProtocol) {
self.keychain = keychain self.keychain = keychain
// Generate or retrieve encryption key from keychain // Retrieve (or, only on genuine first run, generate) the cache
// encryption key. We MUST distinguish "key doesn't exist yet" from a
// transient failure (device locked / access denied): the legacy
// getIdentityKey(forKey:) collapses both to nil, and generating+saving a
// new key deletes the existing one first permanently orphaning the
// encrypted cache on a launch that merely couldn't read the key.
let loadedKey: SymmetricKey let loadedKey: SymmetricKey
let keyIsEphemeral: Bool
// Try to load from keychain switch keychain.getIdentityKeyWithResult(forKey: encryptionKeyName) {
if let keyData = keychain.getIdentityKey(forKey: encryptionKeyName) { case .success(let keyData):
loadedKey = SymmetricKey(data: keyData) loadedKey = SymmetricKey(data: keyData)
keyIsEphemeral = false
SecureLogger.logKeyOperation(.load, keyType: "identity cache encryption key", success: true) SecureLogger.logKeyOperation(.load, keyType: "identity cache encryption key", success: true)
}
// Generate new key if needed case .itemNotFound:
else { // Genuine first run: generate and persist a new key.
loadedKey = SymmetricKey(size: .bits256) let newKey = SymmetricKey(size: .bits256)
let keyData = loadedKey.withUnsafeBytes { Data($0) } let keyData = newKey.withUnsafeBytes { Data($0) }
// Save to keychain
let saved = keychain.saveIdentityKey(keyData, forKey: encryptionKeyName) let saved = keychain.saveIdentityKey(keyData, forKey: encryptionKeyName)
loadedKey = newKey
// If even the save failed, treat the key as ephemeral so we don't
// later try to persist a cache the next launch can't read.
keyIsEphemeral = !saved
SecureLogger.logKeyOperation(.generate, keyType: "identity cache encryption key", success: saved) SecureLogger.logKeyOperation(.generate, keyType: "identity cache encryption key", success: saved)
case .deviceLocked, .authenticationFailed, .accessDenied, .otherError:
// Transient/critical read failure. Do NOT overwrite the persisted
// key. Use a session-only ephemeral key; the real key and cache are
// left intact for a healthy launch.
SecureLogger.warning("Identity cache key unavailable; using ephemeral key for this session (not persisting)", category: .security)
loadedKey = SymmetricKey(size: .bits256)
keyIsEphemeral = true
} }
self.encryptionKey = loadedKey self.encryptionKey = loadedKey
self.encryptionKeyIsEphemeral = keyIsEphemeral
// Load identity cache on init // Only read the persisted cache when we hold the real key; with an
loadIdentityCache() // ephemeral key the decrypt would fail and discard the real cache.
if !keyIsEphemeral {
loadIdentityCache()
}
} }
deinit { deinit {
@@ -211,23 +241,28 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
} }
} }
/// Persists the cache. Always invoked on `queue` under a barrier (its callers
/// run inside `queue.async(.barrier)`), so it simply marks the cache dirty
/// and persists it on the same serialized context no timer, nothing left
/// scheduled to keep the process alive.
private func saveIdentityCache() { private func saveIdentityCache() {
// Mark that we need to save
pendingSave = true pendingSave = true
performSave()
// Cancel any existing timer
saveTimer?.invalidate()
// Schedule a new save after the debounce interval
saveTimer = Timer.scheduledTimer(withTimeInterval: saveDebounceInterval, repeats: false) { [weak self] _ in
self?.performSave()
}
} }
/// Writes the cache to the keychain. Must run on `queue` with exclusive
/// (barrier) access.
private func performSave() { private func performSave() {
guard pendingSave else { return } guard pendingSave else { return }
pendingSave = false pendingSave = false
// Never persist under an ephemeral key it would overwrite the real
// cache with data the next launch cannot decrypt.
guard !encryptionKeyIsEphemeral else {
SecureLogger.debug("Skipping identity cache save (ephemeral key this session)", category: .security)
return
}
do { do {
let data = try JSONEncoder().encode(cache) let data = try JSONEncoder().encode(cache)
let sealedBox = try AES.GCM.seal(data, using: encryptionKey) let sealedBox = try AES.GCM.seal(data, using: encryptionKey)
@@ -240,9 +275,13 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
} }
} }
// Force immediate save (for app termination) // Force immediate save (for app termination / lifecycle events). Mutations
// already persist synchronously via saveIdentityCache, so this is normally a
// no-op (performSave early-returns when nothing is pending). Runs directly on
// the caller's thread deliberately NOT a `queue.sync(barrier)`, which is
// reachable from `deinit` and from async tests on the swift-concurrency
// cooperative pool where a blocking barrier-sync can starve/deadlock it.
func forceSave() { func forceSave() {
saveTimer?.invalidate()
performSave() performSave()
} }
+7
View File
@@ -323,6 +323,13 @@ final class NoiseCipherState {
throw NoiseError.replayDetected throw NoiseError.replayDetected
} }
// The 4-byte nonce prefix has been stripped, so the remaining bytes
// must still hold at least the 16-byte Poly1305 tag. The up-front
// `ciphertext.count >= 16` guard is not sufficient here (it counts
// the nonce), and `prefix(count - 16)` would trap on a short payload.
guard actualCiphertext.count >= 16 else {
throw NoiseError.invalidCiphertext
}
// Split ciphertext and tag // Split ciphertext and tag
encryptedData = actualCiphertext.prefix(actualCiphertext.count - 16) encryptedData = actualCiphertext.prefix(actualCiphertext.count - 16)
tag = actualCiphertext.suffix(16) tag = actualCiphertext.suffix(16)
+7
View File
@@ -82,6 +82,13 @@ final class NostrIdentityBridge {
} }
deviceSeedCache = nil deviceSeedCache = nil
// Also drop the in-memory derived per-geohash identities. These hold the
// actual secp256k1 private keys; if left cached, post-panic geohash
// messages would still be signed with pre-panic keys (linkable across the
// wipe) until the app is force-quit.
cacheLock.lock()
derivedIdentityCache.removeAll()
cacheLock.unlock()
} }
// MARK: - Per-Geohash Identities (Location Channels) // MARK: - Per-Geohash Identities (Location Channels)
+74 -13
View File
@@ -39,22 +39,23 @@ struct NostrProtocol {
content: content content: content
) )
// 2. Create ephemeral key for this message // 2. Seal the rumor (encrypt to recipient) and sign it with the SENDER'S
let ephemeralKey = try P256K.Schnorr.PrivateKey() // real identity key. NIP-17 requires the seal be signed by the sender
// Created ephemeral key for seal // so the recipient can authenticate who sent the message; signing with
// a throwaway key leaves DMs forgeable/impersonatable.
// 3. Seal the rumor (encrypt to recipient) let senderKey = try senderIdentity.schnorrSigningKey()
let sealedEvent = try createSeal( let sealedEvent = try createSeal(
rumor: rumor, rumor: rumor,
recipientPubkey: recipientPubkey, recipientPubkey: recipientPubkey,
senderKey: ephemeralKey senderKey: senderKey
) )
// 4. Gift wrap the sealed event (encrypt to recipient again) // 3. Gift wrap the sealed event with a throwaway ephemeral key (the wrap
// layer hides the sender's identity from relays; createGiftWrap mints
// its own ephemeral key internally).
let giftWrap = try createGiftWrap( let giftWrap = try createGiftWrap(
seal: sealedEvent, seal: sealedEvent,
recipientPubkey: recipientPubkey, recipientPubkey: recipientPubkey
senderKey: ephemeralKey
) )
// Created gift wrap // Created gift wrap
@@ -84,7 +85,15 @@ struct NostrProtocol {
throw error throw error
} }
// 2. Open the seal // 2. Authenticate the seal. The seal MUST be signed by the sender's real
// identity key (NIP-17); without this check a DM is forgeable by anyone
// who knows the recipient's npub. Verify the seal's own signature.
guard seal.isValidSignature() else {
SecureLogger.error("❌ Rejecting DM: seal signature is missing or invalid", category: .session)
throw NostrError.invalidEvent
}
// 3. Open the seal
let rumor: NostrEvent let rumor: NostrEvent
do { do {
rumor = try openSeal( rumor = try openSeal(
@@ -97,9 +106,62 @@ struct NostrProtocol {
throw error throw error
} }
return (content: rumor.content, senderPubkey: rumor.pubkey, timestamp: rumor.created_at) // 4. The sender claimed inside the rumor must match the key that actually
// signed the seal, otherwise the sender field is unauthenticated and
// spoofable.
guard seal.pubkey == rumor.pubkey else {
SecureLogger.error("❌ Rejecting DM: rumor pubkey does not match seal signer", category: .session)
throw NostrError.invalidEvent
}
// Return the seal signer's pubkey as the authenticated sender.
return (content: rumor.content, senderPubkey: seal.pubkey, timestamp: rumor.created_at)
} }
#if DEBUG
static func createPrivateMessageWithInvalidSealSignatureForTesting(
content: String,
recipientPubkey: String,
senderIdentity: NostrIdentity
) throws -> NostrEvent {
let rumor = NostrEvent(
pubkey: senderIdentity.publicKeyHex,
createdAt: Date(),
kind: .dm,
tags: [],
content: content
)
var seal = try createSeal(
rumor: rumor,
recipientPubkey: recipientPubkey,
senderKey: senderIdentity.schnorrSigningKey()
)
seal.sig = String(repeating: "0", count: 128)
return try createGiftWrap(seal: seal, recipientPubkey: recipientPubkey)
}
static func createPrivateMessageWithMismatchedSealRumorPubkeyForTesting(
content: String,
recipientPubkey: String,
rumorIdentity: NostrIdentity,
sealSignerIdentity: NostrIdentity
) throws -> NostrEvent {
let rumor = NostrEvent(
pubkey: rumorIdentity.publicKeyHex,
createdAt: Date(),
kind: .dm,
tags: [],
content: content
)
let seal = try createSeal(
rumor: rumor,
recipientPubkey: recipientPubkey,
senderKey: sealSignerIdentity.schnorrSigningKey()
)
return try createGiftWrap(seal: seal, recipientPubkey: recipientPubkey)
}
#endif
/// Create a geohash-scoped ephemeral public message (kind 20000) /// Create a geohash-scoped ephemeral public message (kind 20000)
static func createEphemeralGeohashEvent( static func createEphemeralGeohashEvent(
content: String, content: String,
@@ -195,8 +257,7 @@ struct NostrProtocol {
private static func createGiftWrap( private static func createGiftWrap(
seal: NostrEvent, seal: NostrEvent,
recipientPubkey: String, recipientPubkey: String
senderKey: P256K.Schnorr.PrivateKey // This is the ephemeral key used for the seal
) throws -> NostrEvent { ) throws -> NostrEvent {
let sealJSON = try seal.jsonString() let sealJSON = try seal.jsonString()
+67
View File
@@ -281,6 +281,7 @@ final class NostrRelayManager: ObservableObject {
task.cancel(with: .goingAway, reason: nil) task.cancel(with: .goingAway, reason: nil)
} }
connections.removeAll() connections.removeAll()
markRelaySocketsClosed(resetState: false)
// Sockets are gone, so per-relay subscription state is cleared but // Sockets are gone, so per-relay subscription state is cleared but
// durable intent (subscriptionRequestState, messageHandlers, parked // durable intent (subscriptionRequestState, messageHandlers, parked
// EOSE callbacks) is kept so REQs replay when relays reconnect // EOSE callbacks) is kept so REQs replay when relays reconnect
@@ -299,6 +300,60 @@ final class NostrRelayManager: ObservableObject {
updateConnectionStatus() updateConnectionStatus()
} }
/// Panic wipe reset: close sockets and drop every user/session-specific
/// relay intent without invoking old callbacks. Unlike `disconnect()`, this
/// must not preserve subscription replay state because geohash DM handlers
/// can capture pre-wipe Nostr private keys.
func resetForPanicWipe() {
connectionGeneration &+= 1
for (_, task) in connections {
task.cancel(with: .goingAway, reason: nil)
}
connections.removeAll()
markRelaySocketsClosed(resetState: true)
subscriptions.removeAll()
pendingSubscriptions.removeAll()
messageHandlers.removeAll()
subscriptionRequestState.removeAll()
subscribeCoalesce.removeAll()
eoseTrackers.removeAll()
pendingEOSECallbacks.removeAll()
pendingTorConnectionURLs.removeAll()
awaitingTorForConnections = false
torReadyWaitAttempts = 0
recentInboundEventKeys.removeAll()
recentInboundEventKeyOrder.removeAll()
duplicateInboundEventDropCount = 0
duplicateInboundEventDropCountBySubscription.removeAll()
inboundEventLogCount = 0
Self.pendingGiftWrapIDs.removeAll()
messageQueueLock.lock()
messageQueue.removeAll()
pendingSendDropCount = 0
messageQueueLock.unlock()
updateConnectionStatus()
}
private func markRelaySocketsClosed(resetState: Bool) {
let now = dependencies.now()
for index in relays.indices {
relays[index].isConnected = false
relays[index].nextReconnectTime = nil
if resetState {
relays[index].lastError = nil
relays[index].lastConnectedAt = nil
relays[index].lastDisconnectedAt = nil
relays[index].messagesSent = 0
relays[index].messagesReceived = 0
relays[index].reconnectAttempts = 0
} else {
relays[index].lastDisconnectedAt = now
}
}
}
/// Ensure connections exist to the given relay URLs (idempotent). /// Ensure connections exist to the given relay URLs (idempotent).
func ensureConnections(to relayUrls: [String]) { func ensureConnections(to relayUrls: [String]) {
// Global network policy gate // Global network policy gate
@@ -1170,6 +1225,18 @@ final class NostrRelayManager: ObservableObject {
return Set(map.keys) return Set(map.keys)
} }
var debugMessageHandlerCount: Int {
messageHandlers.count
}
var debugSubscriptionRequestCount: Int {
subscriptionRequestState.count
}
var debugPendingEOSECallbackCount: Int {
pendingEOSECallbacks.count
}
var debugDuplicateInboundEventDropCount: Int { var debugDuplicateInboundEventDropCount: Int {
duplicateInboundEventDropCount duplicateInboundEventDropCount
} }
@@ -168,8 +168,13 @@ final class BLEAnnounceHandler {
env.updateTopology(peerID, neighbors) env.updateTopology(peerID, neighbors)
} }
// Persist cryptographic identity and signing key for robust offline verification // Persist cryptographic identity and signing key for robust offline
env.persistIdentity(announcement) // verification only for verified announces. Persisting unverified
// announces would let an attacker who replays a victim's noisePublicKey
// overwrite the victim's stored signing key/nickname (identity poisoning).
if verifiedAnnounce {
env.persistIdentity(announcement)
}
let announceBackID = "announce-back-\(peerID)" let announceBackID = "announce-back-\(peerID)"
let shouldSendBack = !env.dedupContains(announceBackID) let shouldSendBack = !env.dedupContains(announceBackID)
@@ -16,6 +16,8 @@ struct BLEPublicMessageHandlerEnvironment {
let now: () -> Date let now: () -> Date
/// Snapshot of known peers keyed by ID (registry read). /// Snapshot of known peers keyed by ID (registry read).
let peersSnapshot: () -> [PeerID: BLEPeerInfo] let peersSnapshot: () -> [PeerID: BLEPeerInfo]
/// Verifies a packet's signature against a known signing public key.
let verifyPacketSignature: (_ packet: BitchatPacket, _ signingPublicKey: Data) -> Bool
/// Resolves a display name from a verified packet signature for peers missing from the registry. /// Resolves a display name from a verified packet signature for peers missing from the registry.
let signedSenderDisplayName: (_ packet: BitchatPacket, _ peerID: PeerID) -> String? let signedSenderDisplayName: (_ packet: BitchatPacket, _ peerID: PeerID) -> String?
/// Tracks the broadcast message packet for gossip sync. /// Tracks the broadcast message packet for gossip sync.
@@ -68,14 +70,39 @@ final class BLEPublicMessageHandler {
// Snapshot peers to avoid concurrent mutation while iterating during nickname collision checks. // Snapshot peers to avoid concurrent mutation while iterating during nickname collision checks.
let peersSnapshot = env.peersSnapshot() let peersSnapshot = env.peersSnapshot()
// Public messages are always signed by their sender. `senderID` is
// attacker-controlled, so registry membership alone is NOT proof of
// identity a peer in the registry as "verified" could be impersonated
// by anyone spoofing their senderID. Require a valid packet signature
// from the claimed sender (our own echoes are exempt; they are matched
// by self-broadcast tracking below).
//
// Verify against the signing key already in the (synchronously-updated)
// peer registry first: identity-cache persistence is asynchronous, so a
// message arriving right after a verified announce would otherwise be
// dropped because `signedSenderDisplayName` only searches the persisted
// cache. Fall back to that persisted-identity lookup for peers not (yet)
// in the registry.
let isSelf = peerID == env.localPeerID()
let registrySigningKey = peersSnapshot[peerID]?.signingPublicKey
let verifiedViaRegistry = !isSelf
&& (registrySigningKey.map { env.verifyPacketSignature(packet, $0) } ?? false)
let signedDisplayName = (isSelf || verifiedViaRegistry) ? nil : env.signedSenderDisplayName(packet, peerID)
guard isSelf || verifiedViaRegistry || signedDisplayName != nil else {
SecureLogger.warning("🚫 Dropping public message with missing/invalid signature for claimed sender \(peerID.id.prefix(8))", category: .security)
return
}
// Authenticity is established; prefer the registry's collision-resolved
// display name, then the signature-derived name.
guard let senderNickname = BLEPeerSenderDisplayName.resolveKnownPeer( guard let senderNickname = BLEPeerSenderDisplayName.resolveKnownPeer(
peerID: peerID, peerID: peerID,
localPeerID: env.localPeerID(), localPeerID: env.localPeerID(),
localNickname: env.localNickname(), localNickname: env.localNickname(),
peers: peersSnapshot, peers: peersSnapshot,
allowConnectedUnverified: false allowConnectedUnverified: false
) ?? env.signedSenderDisplayName(packet, peerID) else { ) ?? signedDisplayName else {
SecureLogger.warning("🚫 Dropping public message from unverified or unknown peer \(peerID.id.prefix(8))", category: .security) SecureLogger.warning("🚫 Dropping public message from unknown peer \(peerID.id.prefix(8))", category: .security)
return return
} }
@@ -12,7 +12,13 @@ struct BLEReceivedPacketContext: Equatable {
struct BLEReceivePipeline { struct BLEReceivePipeline {
static func context(for packet: BitchatPacket, localPeerID: PeerID) -> BLEReceivedPacketContext { static func context(for packet: BitchatPacket, localPeerID: PeerID) -> BLEReceivedPacketContext {
let senderID = PeerID(hexData: packet.senderID) let senderID = PeerID(hexData: packet.senderID)
let messageID = "\(senderID)-\(packet.timestamp)-\(packet.type)" // Include a payload digest so that distinct packets sharing the same
// sender/timestamp(ms)/type are not collapsed as duplicates. The
// post-handshake flush sends queued messages, delivery and read receipts
// back-to-back within a single millisecond; without the digest every
// packet after the first would be silently dropped.
let digestPrefix = packet.payload.sha256Hash().prefix(4).hexEncodedString()
let messageID = "\(senderID)-\(packet.timestamp)-\(packet.type)-\(digestPrefix)"
let messageType = MessageType(rawValue: packet.type) let messageType = MessageType(rawValue: packet.type)
let allowSelfSyncReplay = packet.ttl == 0 && senderID == localPeerID let allowSelfSyncReplay = packet.ttl == 0 && senderID == localPeerID
let shouldDeduplicate = messageType != .fragment && !allowSelfSyncReplay let shouldDeduplicate = messageType != .fragment && !allowSelfSyncReplay
+45 -13
View File
@@ -149,6 +149,13 @@ final class BLEService: NSObject {
private var maintenanceTimer: DispatchSourceTimer? // Single timer for all maintenance tasks private var maintenanceTimer: DispatchSourceTimer? // Single timer for all maintenance tasks
private var maintenanceCounter = 0 // Track maintenance cycles private var maintenanceCounter = 0 // Track maintenance cycles
/// Whether real CoreBluetooth managers were initialized. When false (unit
/// tests), periodic mesh background work is not started the maintenance
/// timer and the gossip-sync timers only drain BLE writes/notifications,
/// re-announce, and sign/broadcast sync packets, all meaningless without
/// Bluetooth. Leaving them running in the test process is pure background
/// churn that aggravates flaky exit hangs.
private var meshBackgroundEnabled = false
// MARK: - Connection budget & scheduling (central role) // MARK: - Connection budget & scheduling (central role)
private var connectionScheduler = BLEConnectionScheduler<CBPeripheral>() private var connectionScheduler = BLEConnectionScheduler<CBPeripheral>()
@@ -247,16 +254,10 @@ final class BLEService: NSObject {
#endif #endif
} }
// Single maintenance timer for all periodic tasks (dispatch-based for determinism) // Single maintenance timer for all periodic tasks (dispatch-based for
let timer = DispatchSource.makeTimerSource(queue: bleQueue) // determinism). Only run it when real Bluetooth managers exist.
timer.schedule(deadline: .now() + TransportConfig.bleMaintenanceInterval, meshBackgroundEnabled = initializeBluetoothManagers
repeating: TransportConfig.bleMaintenanceInterval, startMaintenanceTimer()
leeway: .seconds(TransportConfig.bleMaintenanceLeewaySeconds))
timer.setEventHandler { [weak self] in
self?.performMaintenance()
}
timer.resume()
maintenanceTimer = timer
// Publish initial empty state // Publish initial empty state
requestPeerDataPublish() requestPeerDataPublish()
@@ -286,7 +287,12 @@ final class BLEService: NSObject {
let manager = GossipSyncManager(myPeerID: myPeerID, config: config, requestSyncManager: requestSyncManager) let manager = GossipSyncManager(myPeerID: myPeerID, config: config, requestSyncManager: requestSyncManager)
manager.delegate = self manager.delegate = self
manager.start() // Only start the periodic sync timers when real Bluetooth exists. In unit
// tests there is no mesh to sync with, and the periodic sign/broadcast
// churn just keeps the process busy and aggravates flaky exit hangs.
if meshBackgroundEnabled {
manager.start()
}
gossipSyncManager = manager gossipSyncManager = manager
} }
@@ -449,7 +455,29 @@ final class BLEService: NSObject {
// MARK: Lifecycle // MARK: Lifecycle
/// Creates and starts the periodic maintenance timer if it is not already
/// running. Idempotent so it can be called from both `init` and
/// `startServices()` the latter matters after a panic reset, where
/// `stopServices()` cancels and nils the timer.
private func startMaintenanceTimer() {
guard meshBackgroundEnabled, maintenanceTimer == nil else { return }
let timer = DispatchSource.makeTimerSource(queue: bleQueue)
timer.schedule(deadline: .now() + TransportConfig.bleMaintenanceInterval,
repeating: TransportConfig.bleMaintenanceInterval,
leeway: .seconds(TransportConfig.bleMaintenanceLeewaySeconds))
timer.setEventHandler { [weak self] in
self?.performMaintenance()
}
timer.resume()
maintenanceTimer = timer
}
func startServices() { func startServices() {
// Restart the maintenance timer if a prior stopServices() cancelled it
// (e.g. the panic flow), otherwise periodic announces, peer reconciliation
// and cache cleanup would never resume until app restart.
startMaintenanceTimer()
// Start BLE services if not already running // Start BLE services if not already running
if centralManager?.state == .poweredOn { if centralManager?.state == .poweredOn {
centralManager?.scanForPeripherals( centralManager?.scanForPeripherals(
@@ -1620,7 +1648,7 @@ private extension BLEService {
#if DEBUG #if DEBUG
// Test-only helper to inject packets into the receive pipeline // Test-only helper to inject packets into the receive pipeline
extension BLEService { extension BLEService {
func _test_handlePacket(_ packet: BitchatPacket, fromPeerID: PeerID, preseedPeer: Bool = true) { func _test_handlePacket(_ packet: BitchatPacket, fromPeerID: PeerID, preseedPeer: Bool = true, signingPublicKey: Data? = nil) {
if preseedPeer { if preseedPeer {
// Ensure the synthetic peer is known and marked verified for public-message tests // Ensure the synthetic peer is known and marked verified for public-message tests
let normalizedID = PeerID(hexData: packet.senderID) let normalizedID = PeerID(hexData: packet.senderID)
@@ -1628,6 +1656,7 @@ extension BLEService {
if var existing = peerRegistry.info(for: normalizedID) { if var existing = peerRegistry.info(for: normalizedID) {
existing.isConnected = true existing.isConnected = true
existing.isVerifiedNickname = true existing.isVerifiedNickname = true
if let signingPublicKey { existing.signingPublicKey = signingPublicKey }
existing.lastSeen = Date() existing.lastSeen = Date()
peerRegistry.upsert(existing) peerRegistry.upsert(existing)
} else { } else {
@@ -1636,7 +1665,7 @@ extension BLEService {
nickname: "TestPeer_\(fromPeerID.id.prefix(4))", nickname: "TestPeer_\(fromPeerID.id.prefix(4))",
isConnected: true, isConnected: true,
noisePublicKey: packet.senderID, noisePublicKey: packet.senderID,
signingPublicKey: nil, signingPublicKey: signingPublicKey,
isVerifiedNickname: true, isVerifiedNickname: true,
lastSeen: Date() lastSeen: Date()
)) ))
@@ -3267,6 +3296,9 @@ extension BLEService {
guard let self = self else { return [:] } guard let self = self else { return [:] }
return self.collectionsQueue.sync { self.peerRegistry.snapshotByID } return self.collectionsQueue.sync { self.peerRegistry.snapshotByID }
}, },
verifyPacketSignature: { [weak self] packet, signingPublicKey in
self?.noiseService.verifyPacketSignature(packet, publicKey: signingPublicKey) ?? false
},
signedSenderDisplayName: { [weak self] packet, peerID in signedSenderDisplayName: { [weak self] packet, peerID in
self?.signedSenderDisplayName(for: packet, from: peerID) self?.signedSenderDisplayName(for: packet, from: peerID)
}, },
@@ -594,6 +594,22 @@ final class LocationStateManager: NSObject, CLLocationManagerDelegate, Observabl
} }
} }
/// Removes all persisted location state and resets the in-memory view.
/// Used by the panic wipe selected channel, teleport set and bookmarks
/// (which reveal where the user has been) must not survive on device.
func panicWipe() {
storage.removeObject(forKey: selectedChannelKey)
storage.removeObject(forKey: teleportedStoreKey)
storage.removeObject(forKey: bookmarksKey)
storage.removeObject(forKey: bookmarkNamesKey)
teleportedSet.removeAll()
bookmarkMembership.removeAll()
bookmarks = []
bookmarkNames = [:]
teleported = false
selectedChannel = .mesh
}
private static func normalizeGeohash(_ s: String) -> String { private static func normalizeGeohash(_ s: String) -> String {
let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz") let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
return s return s
@@ -44,7 +44,11 @@ final class NetworkActivationService: ObservableObject {
private let permissionProvider: () -> LocationChannelManager.PermissionState private let permissionProvider: () -> LocationChannelManager.PermissionState
private let mutualFavoritesProvider: () -> Set<Data> private let mutualFavoritesProvider: () -> Set<Data>
private let torController: NetworkActivationTorControlling private let torController: NetworkActivationTorControlling
private let relayController: NetworkActivationRelayControlling // Resolved lazily: NostrRelayManager.init() reads NetworkActivationService.shared
// (via its live dependencies), so capturing NostrRelayManager.shared here would
// re-enter whichever singleton's dispatch_once started first and trap at launch.
private lazy var relayController: NetworkActivationRelayControlling = relayControllerProvider()
private let relayControllerProvider: () -> NetworkActivationRelayControlling
private let proxyController: NetworkActivationProxyControlling private let proxyController: NetworkActivationProxyControlling
private let notificationCenter: NotificationCenter private let notificationCenter: NotificationCenter
@@ -55,7 +59,7 @@ final class NetworkActivationService: ObservableObject {
permissionProvider = { LocationChannelManager.shared.permissionState } permissionProvider = { LocationChannelManager.shared.permissionState }
mutualFavoritesProvider = { FavoritesPersistenceService.shared.mutualFavorites } mutualFavoritesProvider = { FavoritesPersistenceService.shared.mutualFavorites }
torController = TorManager.shared torController = TorManager.shared
relayController = NostrRelayManager.shared relayControllerProvider = { NostrRelayManager.shared }
proxyController = TorURLSession.shared proxyController = TorURLSession.shared
notificationCenter = .default notificationCenter = .default
} }
@@ -77,7 +81,7 @@ final class NetworkActivationService: ObservableObject {
self.permissionProvider = permissionProvider self.permissionProvider = permissionProvider
self.mutualFavoritesProvider = mutualFavoritesProvider self.mutualFavoritesProvider = mutualFavoritesProvider
self.torController = torController self.torController = torController
self.relayController = relayController self.relayControllerProvider = { relayController }
self.proxyController = proxyController self.proxyController = proxyController
self.notificationCenter = notificationCenter self.notificationCenter = notificationCenter
} }
+37 -10
View File
@@ -1129,6 +1129,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele
userDefaults.removeObject(forKey: "bitchat.noiseIdentityKey") userDefaults.removeObject(forKey: "bitchat.noiseIdentityKey")
userDefaults.removeObject(forKey: "bitchat.messageRetentionKey") userDefaults.removeObject(forKey: "bitchat.messageRetentionKey")
// Wipe persisted location state (selected channel, teleport set,
// bookmarks). For an activist-safety wipe, where the user has been is
// exactly the data an adversary inspecting the device wants.
LocationStateManager.shared.panicWipe()
// Reset nickname to anonymous // Reset nickname to anonymous
nickname = "anon\(Int.random(in: 1000...9999))" nickname = "anon\(Int.random(in: 1000...9999))"
saveNickname() saveNickname()
@@ -1156,13 +1161,25 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele
// Clear selected private chat // Clear selected private chat
selectedPrivateChatPeer = nil selectedPrivateChatPeer = nil
// Clear live location/geohash session state. Persisted location state
// was wiped above, but the running view model can still be scoped to a
// geohash channel and hold subscriptions tied to the old Nostr identity.
activeChannel = .mesh
setGeoChatSubscriptionID(nil)
setGeoDmSubscriptionID(nil)
_ = clearGeoSamplingSubs()
cachedGeohashIdentity = nil
nostrKeyMapping.removeAll()
// Clear read receipt tracking // Clear read receipt tracking
sentReadReceipts.removeAll() sentReadReceipts.removeAll()
deduplicationService.clearAll() deduplicationService.clearAll()
// IMPORTANT: Clear Nostr-related state // IMPORTANT: Clear Nostr-related state
// Disconnect from Nostr relays and clear subscriptions // Drop relay subscriptions, handlers, pending sends, and replay state.
nostrRelayManager?.disconnect() // Geohash DM handlers can capture pre-wipe Nostr identities, so a plain
// disconnect is not enough here.
NostrRelayManager.shared.resetForPanicWipe()
nostrRelayManager = nil nostrRelayManager = nil
// Clear Nostr identity associations // Clear Nostr identity associations
@@ -1178,15 +1195,25 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele
// No need to force UserDefaults synchronization // No need to force UserDefaults synchronization
// Reinitialize Nostr with new identity // Reinitialize Nostr with new identity
// This will generate new Nostr keys derived from new Noise keys // This will generate new Nostr keys derived from new Noise keys.
Task { @MainActor in // Skipped under tests: connecting the shared relay singleton starts
// Small delay to ensure cleanup completes // real network/reconnect work that never completes and would keep the
try? await Task.sleep(nanoseconds: TransportConfig.uiAsyncShortSleepNs) // 0.1 seconds // test process alive (the singleton, unlike a discardable instance, is
// never deallocated to cancel it).
if !TestEnvironment.isRunningTests {
Task { @MainActor in
// Small delay to ensure cleanup completes
try? await Task.sleep(nanoseconds: TransportConfig.uiAsyncShortSleepNs) // 0.1 seconds
// Reinitialize Nostr relay manager with new identity // Reinitialize Nostr relay manager with new identity. Reuse the
nostrRelayManager = NostrRelayManager() // shared singleton every other component (NostrTransport, geohash
setupNostrMessageHandling() // subscriptions, AppRuntime observers) is bound to `.shared`, so
nostrRelayManager?.connect() // creating a fresh instance here would split relay state and leave
// sends running against a disconnected manager.
nostrRelayManager = NostrRelayManager.shared
setupNostrMessageHandling()
nostrRelayManager?.connect()
}
} }
// Delete ALL media files (incoming and outgoing) in background // Delete ALL media files (incoming and outgoing) in background
+10 -4
View File
@@ -14,23 +14,29 @@ import BitFoundation
struct BLEServiceCoreTests { struct BLEServiceCoreTests {
@Test @Test
func duplicatePacket_isDeduped() async { func duplicatePacket_isDeduped() async throws {
let ble = makeService() let ble = makeService()
let delegate = PublicCaptureDelegate() let delegate = PublicCaptureDelegate()
ble.delegate = delegate ble.delegate = delegate
// Public messages must carry a valid signature from the claimed sender;
// sign the packet and preseed the sender's signing key so the receiver
// can verify it (production `sendMessage` signs public broadcasts too).
let signer = NoiseEncryptionService(keychain: MockKeychain())
let sender = PeerID(str: "1122334455667788") let sender = PeerID(str: "1122334455667788")
let timestamp = UInt64(Date().timeIntervalSince1970 * 1000) let timestamp = UInt64(Date().timeIntervalSince1970 * 1000)
let packet = makePublicPacket(content: "Hello", sender: sender, timestamp: timestamp) let unsigned = makePublicPacket(content: "Hello", sender: sender, timestamp: timestamp)
let packet = try #require(signer.signPacket(unsigned), "Failed to sign public message")
let signingKey = signer.getSigningPublicKeyData()
ble._test_handlePacket(packet, fromPeerID: sender) ble._test_handlePacket(packet, fromPeerID: sender, signingPublicKey: signingKey)
let receivedFirst = await TestHelpers.waitUntil( let receivedFirst = await TestHelpers.waitUntil(
{ delegate.publicMessagesSnapshot().count == 1 }, { delegate.publicMessagesSnapshot().count == 1 },
timeout: TestConstants.defaultTimeout timeout: TestConstants.defaultTimeout
) )
#expect(receivedFirst) #expect(receivedFirst)
ble._test_handlePacket(packet, fromPeerID: sender) ble._test_handlePacket(packet, fromPeerID: sender, signingPublicKey: signingKey)
let receivedDuplicate = await TestHelpers.waitUntil( let receivedDuplicate = await TestHelpers.waitUntil(
{ delegate.publicMessagesSnapshot().count > 1 }, { delegate.publicMessagesSnapshot().count > 1 },
timeout: TestConstants.shortTimeout timeout: TestConstants.shortTimeout
+32
View File
@@ -1042,6 +1042,38 @@ struct ChatViewModelPanicTests {
#expect(viewModel.unreadPrivateMessages.isEmpty) #expect(viewModel.unreadPrivateMessages.isEmpty)
#expect(viewModel.selectedPrivateChatPeer == nil) #expect(viewModel.selectedPrivateChatPeer == nil)
} }
@Test @MainActor
func panicClearAllData_resetsLiveGeohashAndNostrState() async throws {
let (viewModel, _) = makeTestableViewModel()
let geohash = "u4pruy"
let channel = GeohashChannel(level: .city, geohash: geohash)
let identity = try NostrIdentity.generate()
let pubkey = String(repeating: "ab", count: 32)
let peerID = PeerID(nostr: pubkey)
viewModel.activeChannel = .location(channel)
viewModel.setGeoChatSubscriptionID("geo-\(geohash)")
viewModel.setGeoDmSubscriptionID("geo-dm-\(geohash)")
viewModel.addGeoSamplingSub("geo-sample-\(geohash)", forGeohash: geohash)
viewModel.cachedGeohashIdentity = (geohash, identity)
viewModel.registerNostrKeyMapping(pubkey, for: peerID)
viewModel.currentGeohash = geohash
viewModel.geoNicknames = [pubkey: "alice"]
viewModel.teleportedGeo = [pubkey]
viewModel.panicClearAllData()
#expect(viewModel.activeChannel == .mesh)
#expect(viewModel.geoSubscriptionID == nil)
#expect(viewModel.geoDmSubscriptionID == nil)
#expect(viewModel.geoSamplingSubs.isEmpty)
#expect(viewModel.cachedGeohashIdentity == nil)
#expect(viewModel.nostrKeyMapping.isEmpty)
#expect(viewModel.currentGeohash == nil)
#expect(viewModel.geoNicknames.isEmpty)
#expect(viewModel.teleportedGeo.isEmpty)
}
} }
// MARK: - Service Lifecycle Tests // MARK: - Service Lifecycle Tests
@@ -21,9 +21,16 @@ struct FragmentationTests {
let capture = CaptureDelegate() let capture = CaptureDelegate()
ble.delegate = capture ble.delegate = capture
// Construct a big packet (3KB) from a remote sender (not our own ID) // Construct a big SIGNED public packet (3KB) from a remote sender. Public
// messages must carry a valid signature, so the reassembled packet is
// signed and the sender's signing key is preseeded into the registry.
let signer = NoiseEncryptionService(keychain: MockKeychain())
let signingKey = signer.getSigningPublicKeyData()
let remoteShortID = PeerID(str: "1122334455667788") let remoteShortID = PeerID(str: "1122334455667788")
let original = makeLargePublicPacket(senderShortHex: remoteShortID, size: 3_000) let original = try #require(
signer.signPacket(makeLargePublicPacket(senderShortHex: remoteShortID, size: 3_000)),
"Failed to sign public packet"
)
// Use a small fragment size to ensure multiple pieces // Use a small fragment size to ensure multiple pieces
let fragments = fragmentPacket(original, fragmentSize: 400) let fragments = fragmentPacket(original, fragmentSize: 400)
@@ -36,7 +43,7 @@ struct FragmentationTests {
if i > 0 { if i > 0 {
try await Task.sleep(for: .milliseconds(5)) try await Task.sleep(for: .milliseconds(5))
} }
ble._test_handlePacket(fragment, fromPeerID: remoteShortID) ble._test_handlePacket(fragment, fromPeerID: remoteShortID, signingPublicKey: signingKey)
} }
// Wait for delegate callback with proper timeout // Wait for delegate callback with proper timeout
@@ -52,8 +59,13 @@ struct FragmentationTests {
let capture = CaptureDelegate() let capture = CaptureDelegate()
ble.delegate = capture ble.delegate = capture
let signer = NoiseEncryptionService(keychain: MockKeychain())
let signingKey = signer.getSigningPublicKeyData()
let remoteShortID = PeerID(str: "A1B2C3D4E5F60708") let remoteShortID = PeerID(str: "A1B2C3D4E5F60708")
let original = makeLargePublicPacket(senderShortHex: remoteShortID, size: 2048) let original = try #require(
signer.signPacket(makeLargePublicPacket(senderShortHex: remoteShortID, size: 2048)),
"Failed to sign public packet"
)
var frags = fragmentPacket(original, fragmentSize: 300) var frags = fragmentPacket(original, fragmentSize: 300)
// Duplicate one fragment // Duplicate one fragment
@@ -66,7 +78,7 @@ struct FragmentationTests {
if i > 0 { if i > 0 {
try await Task.sleep(for: .milliseconds(5)) try await Task.sleep(for: .milliseconds(5))
} }
ble._test_handlePacket(fragment, fromPeerID: remoteShortID) ble._test_handlePacket(fragment, fromPeerID: remoteShortID, signingPublicKey: signingKey)
} }
// Wait for delegate callback with proper timeout // Wait for delegate callback with proper timeout
+47
View File
@@ -120,6 +120,42 @@ struct NostrProtocolTests {
} }
} }
@Test func decryptRejectsInvalidSealSignature() throws {
let sender = try NostrIdentity.generate()
let recipient = try NostrIdentity.generate()
let giftWrap = try NostrProtocol.createPrivateMessageWithInvalidSealSignatureForTesting(
content: "forged signature",
recipientPubkey: recipient.publicKeyHex,
senderIdentity: sender
)
expectInvalidEvent {
_ = try NostrProtocol.decryptPrivateMessage(
giftWrap: giftWrap,
recipientIdentity: recipient
)
}
}
@Test func decryptRejectsSealRumorPubkeyMismatch() throws {
let claimedSender = try NostrIdentity.generate()
let sealSigner = try NostrIdentity.generate()
let recipient = try NostrIdentity.generate()
let giftWrap = try NostrProtocol.createPrivateMessageWithMismatchedSealRumorPubkeyForTesting(
content: "spoofed sender",
recipientPubkey: recipient.publicKeyHex,
rumorIdentity: claimedSender,
sealSignerIdentity: sealSigner
)
expectInvalidEvent {
_ = try NostrProtocol.decryptPrivateMessage(
giftWrap: giftWrap,
recipientIdentity: recipient
)
}
}
func testAckRoundTripNIP44V2_Delivered() throws { func testAckRoundTripNIP44V2_Delivered() throws {
// Identities // Identities
let sender = try NostrIdentity.generate() let sender = try NostrIdentity.generate()
@@ -260,4 +296,15 @@ struct NostrProtocolTests {
if rem > 0 { str.append(String(repeating: "=", count: 4 - rem)) } if rem > 0 { str.append(String(repeating: "=", count: 4 - rem)) }
return Data(base64Encoded: str) return Data(base64Encoded: str)
} }
private func expectInvalidEvent(_ operation: () throws -> Void) {
do {
try operation()
Issue.record("Expected NostrError.invalidEvent")
} catch NostrError.invalidEvent {
return
} catch {
Issue.record("Expected NostrError.invalidEvent, got \(error)")
}
}
} }
@@ -173,7 +173,10 @@ struct BLEAnnounceHandlerTests {
#expect(recorder.uiEventDeliveries.count == 1) #expect(recorder.uiEventDeliveries.count == 1)
#expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false) #expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false)
#expect(recorder.uiEventDeliveries.first?.scheduleInitialSync == false) #expect(recorder.uiEventDeliveries.first?.scheduleInitialSync == false)
#expect(recorder.persistedIdentities.count == 1) // Identity persistence MUST NOT occur for unverified announces:
// persisting would let an attacker who replays a victim's noisePublicKey
// overwrite the victim's stored signing key/nickname (identity poisoning).
#expect(recorder.persistedIdentities.isEmpty)
#expect(recorder.trackedPackets.count == 1) #expect(recorder.trackedPackets.count == 1)
#expect(recorder.announceBacks == 1) #expect(recorder.announceBacks == 1)
} }
@@ -8,10 +8,12 @@ struct BLEPublicMessageHandlerTests {
var localNickname = "Me" var localNickname = "Me"
var peers: [PeerID: BLEPeerInfo] = [:] var peers: [PeerID: BLEPeerInfo] = [:]
var signedName: String? var signedName: String?
var verifyPacketSignatureResult = false
var linkState: (hasPeripheral: Bool, hasCentral: Bool) = (false, false) var linkState: (hasPeripheral: Bool, hasCentral: Bool) = (false, false)
var selfBroadcastMessageID: String? var selfBroadcastMessageID: String?
var peersSnapshotReads = 0 var peersSnapshotReads = 0
var verifyPacketSignatureQueries: [PeerID] = []
var signedNameQueries: [PeerID] = [] var signedNameQueries: [PeerID] = []
var trackedPackets: [BitchatPacket] = [] var trackedPackets: [BitchatPacket] = []
var selfBroadcastTakes: [BitchatPacket] = [] var selfBroadcastTakes: [BitchatPacket] = []
@@ -35,6 +37,10 @@ struct BLEPublicMessageHandlerTests {
recorder.peersSnapshotReads += 1 recorder.peersSnapshotReads += 1
return recorder.peers return recorder.peers
}, },
verifyPacketSignature: { packet, _ in
recorder.verifyPacketSignatureQueries.append(PeerID(hexData: packet.senderID))
return recorder.verifyPacketSignatureResult
},
signedSenderDisplayName: { _, peerID in signedSenderDisplayName: { _, peerID in
recorder.signedNameQueries.append(peerID) recorder.signedNameQueries.append(peerID)
return recorder.signedName return recorder.signedName
@@ -59,13 +65,17 @@ struct BLEPublicMessageHandlerTests {
let now = Date(timeIntervalSince1970: 1_000) let now = Date(timeIntervalSince1970: 1_000)
let recorder = Recorder() let recorder = Recorder()
recorder.peers = [remotePeerID: makePeerInfo(remotePeerID, nickname: "Alice", isVerified: true)] recorder.peers = [remotePeerID: makePeerInfo(remotePeerID, nickname: "Alice", isVerified: true)]
// A valid packet signature is required even for a registry-verified peer:
// senderID is spoofable, so registry membership alone is not authentication.
recorder.signedName = "SignedAlice"
let handler = makeHandler(recorder: recorder, now: now) let handler = makeHandler(recorder: recorder, now: now)
let packet = makeMessagePacket(sender: remotePeerID, content: "hello mesh", timestamp: timestamp(now)) let packet = makeMessagePacket(sender: remotePeerID, content: "hello mesh", timestamp: timestamp(now))
handler.handle(packet, from: remotePeerID) handler.handle(packet, from: remotePeerID)
#expect(recorder.peersSnapshotReads == 1) #expect(recorder.peersSnapshotReads == 1)
#expect(recorder.signedNameQueries.isEmpty) // Signature is verified, then the registry's collision-resolved name is preferred.
#expect(recorder.signedNameQueries == [remotePeerID])
#expect(recorder.trackedPackets.count == 1) #expect(recorder.trackedPackets.count == 1)
#expect(recorder.selfBroadcastTakes.isEmpty) #expect(recorder.selfBroadcastTakes.isEmpty)
#expect(recorder.deliveries.count == 1) #expect(recorder.deliveries.count == 1)
@@ -133,6 +143,63 @@ struct BLEPublicMessageHandlerTests {
#expect(recorder.deliveries.isEmpty) #expect(recorder.deliveries.isEmpty)
} }
@Test
func registryVerifiedPeerDeliveredBeforeIdentityCachePersists() {
// A freshly verified announce updates the peer registry synchronously,
// but identity-cache persistence is async. A message arriving in that
// window has a valid signature and a registry signing key, yet the
// persisted-identity lookup (signedName) would still return nil. It must
// be verified against the registry key and delivered, not dropped.
let now = Date(timeIntervalSince1970: 1_000)
let recorder = Recorder()
recorder.peers = [remotePeerID: makePeerInfo(
remotePeerID,
nickname: "Alice",
isVerified: true,
signingPublicKey: Data(repeating: 0xAB, count: 32)
)]
recorder.verifyPacketSignatureResult = true
recorder.signedName = nil
let handler = makeHandler(recorder: recorder, now: now)
let packet = makeMessagePacket(sender: remotePeerID, content: "first msg", timestamp: timestamp(now))
handler.handle(packet, from: remotePeerID)
#expect(recorder.verifyPacketSignatureQueries == [remotePeerID])
// Verified via the registry key, so no fallback to the persisted lookup.
#expect(recorder.signedNameQueries.isEmpty)
#expect(recorder.trackedPackets.count == 1)
#expect(recorder.deliveries.count == 1)
#expect(recorder.deliveries.first?.nickname == "Alice")
#expect(recorder.deliveries.first?.content == "first msg")
}
@Test
func registryPeerWithInvalidSignatureFallsBackAndDrops() {
// Spoofed senderID: the peer is in the registry with a signing key, but
// the packet signature does not verify against it. The handler must fall
// back to the persisted lookup and, finding nothing, drop the message.
let now = Date(timeIntervalSince1970: 1_000)
let recorder = Recorder()
recorder.peers = [remotePeerID: makePeerInfo(
remotePeerID,
nickname: "Alice",
isVerified: true,
signingPublicKey: Data(repeating: 0xAB, count: 32)
)]
recorder.verifyPacketSignatureResult = false
recorder.signedName = nil
let handler = makeHandler(recorder: recorder, now: now)
let packet = makeMessagePacket(sender: remotePeerID, content: "spoofed", timestamp: timestamp(now))
handler.handle(packet, from: remotePeerID)
#expect(recorder.verifyPacketSignatureQueries == [remotePeerID])
#expect(recorder.signedNameQueries == [remotePeerID])
#expect(recorder.trackedPackets.isEmpty)
#expect(recorder.deliveries.isEmpty)
}
@Test @Test
func signedSenderFallbackDeliversWithSignedName() { func signedSenderFallbackDeliversWithSignedName() {
let now = Date(timeIntervalSince1970: 1_000) let now = Date(timeIntervalSince1970: 1_000)
@@ -154,6 +221,7 @@ struct BLEPublicMessageHandlerTests {
let now = Date(timeIntervalSince1970: 1_000) let now = Date(timeIntervalSince1970: 1_000)
let recorder = Recorder() let recorder = Recorder()
recorder.peers = [remotePeerID: makePeerInfo(remotePeerID, nickname: "Alice", isVerified: true)] recorder.peers = [remotePeerID: makePeerInfo(remotePeerID, nickname: "Alice", isVerified: true)]
recorder.signedName = "SignedAlice"
let handler = makeHandler(recorder: recorder, now: now) let handler = makeHandler(recorder: recorder, now: now)
let packet = makeMessagePacket(sender: remotePeerID, payload: Data([0xFF, 0xFE, 0xFD]), timestamp: timestamp(now)) let packet = makeMessagePacket(sender: remotePeerID, payload: Data([0xFF, 0xFE, 0xFD]), timestamp: timestamp(now))
@@ -187,6 +255,7 @@ struct BLEPublicMessageHandlerTests {
let now = Date(timeIntervalSince1970: 1_000) let now = Date(timeIntervalSince1970: 1_000)
let recorder = Recorder() let recorder = Recorder()
recorder.peers = [remotePeerID: makePeerInfo(remotePeerID, nickname: "Alice", isVerified: true)] recorder.peers = [remotePeerID: makePeerInfo(remotePeerID, nickname: "Alice", isVerified: true)]
recorder.signedName = "SignedAlice"
let handler = makeHandler(recorder: recorder, now: now) let handler = makeHandler(recorder: recorder, now: now)
let packet = makeMessagePacket( let packet = makeMessagePacket(
sender: remotePeerID, sender: remotePeerID,
@@ -213,14 +282,15 @@ struct BLEPublicMessageHandlerTests {
_ peerID: PeerID, _ peerID: PeerID,
nickname: String, nickname: String,
isVerified: Bool, isVerified: Bool,
isConnected: Bool = true isConnected: Bool = true,
signingPublicKey: Data? = nil
) -> BLEPeerInfo { ) -> BLEPeerInfo {
BLEPeerInfo( BLEPeerInfo(
peerID: peerID, peerID: peerID,
nickname: nickname, nickname: nickname,
isConnected: isConnected, isConnected: isConnected,
noisePublicKey: nil, noisePublicKey: nil,
signingPublicKey: nil, signingPublicKey: signingPublicKey,
isVerifiedNickname: isVerified, isVerifiedNickname: isVerified,
lastSeen: Date(timeIntervalSince1970: 999) lastSeen: Date(timeIntervalSince1970: 999)
) )
@@ -14,7 +14,10 @@ struct BLEReceivePipelineTests {
let context = BLEReceivePipeline.context(for: packet, localPeerID: local) let context = BLEReceivePipeline.context(for: packet, localPeerID: local)
#expect(context.senderID == sender) #expect(context.senderID == sender)
#expect(context.messageID == "\(sender)-1234-\(MessageType.message.rawValue)") // The message ID includes a payload digest so distinct packets sharing a
// sender/timestamp(ms)/type are not collapsed as duplicates.
let digest = packet.payload.sha256Hash().prefix(4).hexEncodedString()
#expect(context.messageID == "\(sender)-1234-\(MessageType.message.rawValue)-\(digest)")
#expect(context.messageType == .message) #expect(context.messageType == .message)
#expect(context.shouldDeduplicate) #expect(context.shouldDeduplicate)
#expect(context.logsHandlingDetails) #expect(context.logsHandlingDetails)
@@ -1328,6 +1328,68 @@ final class NostrRelayManagerTests: XCTestCase {
XCTAssertEqual(context.manager.debugPendingSubscriptionCount(for: relayURL), 0) XCTAssertEqual(context.manager.debugPendingSubscriptionCount(for: relayURL), 0)
} }
func test_resetForPanicWipe_dropsSessionRelayStateWithoutFiringCallbacks() async throws {
let relayURL = "wss://panic-reset.example"
let context = makeContext(permission: .denied, userTorEnabled: true, torEnforced: true, torIsReady: false)
let event = try makeSignedEvent(content: "queued before panic")
var handledEvents = 0
var eoseCount = 0
context.manager.subscribe(
filter: makeFilter(),
id: "panic-sub",
relayUrls: [relayURL],
handler: { _ in handledEvents += 1 },
onEOSE: { eoseCount += 1 }
)
context.manager.sendEvent(event, to: [relayURL])
XCTAssertEqual(context.manager.debugMessageHandlerCount, 1)
XCTAssertEqual(context.manager.debugSubscriptionRequestCount, 1)
XCTAssertEqual(context.manager.debugPendingSubscriptionCount(for: relayURL), 1)
XCTAssertEqual(context.manager.debugPendingEOSECallbackCount, 1)
XCTAssertEqual(context.manager.debugPendingMessageQueueCount, 1)
XCTAssertTrue(context.sessionFactory.requestedURLs.isEmpty)
context.manager.resetForPanicWipe()
XCTAssertEqual(context.manager.debugMessageHandlerCount, 0)
XCTAssertEqual(context.manager.debugSubscriptionRequestCount, 0)
XCTAssertEqual(context.manager.debugPendingSubscriptionCount(for: relayURL), 0)
XCTAssertEqual(context.manager.debugPendingEOSECallbackCount, 0)
XCTAssertEqual(context.manager.debugPendingMessageQueueCount, 0)
XCTAssertEqual(handledEvents, 0)
XCTAssertEqual(eoseCount, 0)
// Stale Tor wait and fallback callbacks from the pre-wipe generation
// must not resurrect connections or settle callbacks after reset.
context.torWaiter.resolve(true)
context.scheduler.runNext()
try? await Task.sleep(nanoseconds: 20_000_000)
XCTAssertTrue(context.sessionFactory.requestedURLs.isEmpty)
XCTAssertEqual(eoseCount, 0)
}
func test_resetForPanicWipe_marksConnectedRelaysDisconnected() async {
let relayURL = "wss://panic-connected.example"
let context = makeContext(permission: .denied)
context.manager.ensureConnections(to: [relayURL])
let connected = await waitUntil {
context.manager.isConnected &&
context.manager.relays.first(where: { $0.url == relayURL })?.isConnected == true
}
XCTAssertTrue(connected)
context.manager.resetForPanicWipe()
XCTAssertFalse(context.manager.isConnected)
XCTAssertEqual(context.manager.relays.first(where: { $0.url == relayURL })?.isConnected, false)
XCTAssertEqual(context.manager.relays.first(where: { $0.url == relayURL })?.reconnectAttempts, 0)
XCTAssertNil(context.manager.relays.first(where: { $0.url == relayURL })?.lastError)
}
func test_reconnectBackoff_appliesJitterWithinConfiguredBounds() async { func test_reconnectBackoff_appliesJitterWithinConfiguredBounds() async {
let relayURL = "wss://jitter-bounds.example" let relayURL = "wss://jitter-bounds.example"
// Pin the jitter source to the extremes and the midpoint of [0, 1). // Pin the jitter source to the extremes and the midpoint of [0, 1).
+408 -435
View File
@@ -1,441 +1,414 @@
Relay URL,Latitude,Longitude Relay URL,Latitude,Longitude
nostr.bitcoiner.social:443,47.6743,-117.112
relay-dev.satlantis.io:443,40.8302,-74.1299
relay.lightning.pub:443,39.0438,-77.4874
ribo.us.nostria.app:443,43.6532,-79.3832
relay.notoshi.win,13.3622,100.983
openrelay.ziomc.com,50.0755,14.4378
relay.agentry.com:443,42.8864,-78.8784
nostr-relay.xbytez.io,50.6924,3.20113
relay.gulugulu.moe,43.6532,-79.3832
nostr.thebiglake.org:443,32.71,-96.6745
relay.fountain.fm,43.6532,-79.3832
freelay.sovbit.host,60.1699,24.9384
nostr.girino.org:443,43.6532,-79.3832
relay.openresist.com,43.6532,-79.3832
nas01xanthosnet.synology.me:7778,47.1285,8.74735
relay.ohstr.com:443,43.6532,-79.3832
nostr-pub.wellorder.net,45.5201,-122.99
relay.nostrdice.com,-33.8688,151.209
bbw-nostr.xyz,41.5284,-87.4237
cs-relay.nostrdev.com:443,50.4754,12.3683
top.testrelay.top,43.6532,-79.3832
relay.trotters.cc,43.6532,-79.3832
blossom.gnostr.cloud,43.6532,-79.3832
ribo.eu.nostria.app:443,43.6532,-79.3832
public.crostr.com,43.6532,-79.3832
relay.nostar.org,43.6532,-79.3832
strfry.bonsai.com:443,39.0438,-77.4874
nostr.carroarmato0.be,50.914,3.21378
relay.endfiat.money,59.3327,18.0656
nostr.overmind.lol:443,43.6532,-79.3832
nostr.myshosholoza.co.za:443,52.3913,4.66545
relay.wellorder.net,45.5201,-122.99
nostr.tagomago.me,42.3601,-71.0589
relay.internationalright-wing.org,-22.5022,-48.7114
relay.fckstate.net,59.3293,18.0686
nostr.azzamo.net,52.2633,21.0283
relay.0xchat.com,43.6532,-79.3832
relayone.geektank.ai,39.1008,-94.5811
relay.homeinhk.xyz,35.694,139.754
nostr.nodesmap.com,59.3327,18.0656
relay.islandbitcoin.com:443,12.8498,77.6545
relay.mrmave.work,43.6532,-79.3832
relay.arx-ccn.com,50.4754,12.3683
fanfares.nostr1.com:443,40.7057,-74.0136
wot.shaving.kiwi,43.6532,-79.3832
relay.nearhood.co.uk,51.5072,-0.127586
relay.fundstr.me,42.3601,-71.0589
syb.lol:443,43.6532,-79.3832
dm-test-strfry-discovery.samt.st:443,43.6532,-79.3832
relay.nostx.io,43.6532,-79.3832
no.str.cr,10.6352,-85.4378
relay.jbnco.co,43.6532,-79.3832
ribo.nostria.app:443,43.6532,-79.3832
us-east.nostr.pikachat.org,39.0438,-77.4874
nexus.libernet.app,43.6532,-79.3832
nostr.dlcdevkit.com,40.0992,-83.1141
nostr.debate.report,50.1109,8.68213
str-define-contributing-jackets.trycloudflare.com,43.6532,-79.3832
nostr2.girino.org,43.6532,-79.3832
nostr.unkn0wn.world,46.8499,9.53287
nostr.spicyz.io:443,43.6532,-79.3832
relay.layer.systems:443,49.0291,8.35695
nostr-relay.amethyst.name,39.0067,-77.4291
slick.mjex.me,39.0418,-77.4744
nostr.girino.org,43.6532,-79.3832
nostr.quali.chat:443,60.1699,24.9384
purplerelay.com:443,43.6532,-79.3832
nostr.notribe.net,40.8302,-74.1299
relay.plebeian.market:443,50.1109,8.68213
relay.samt.st,40.8302,-74.1299
relay.mitchelltribe.com:443,39.0438,-77.4874
nostr.0x7e.xyz,47.4949,8.71954
relayone.soundhsa.com:443,39.1008,-94.5811
nostr.thalheim.io:443,60.1699,24.9384
relay.getsafebox.app:443,43.6532,-79.3832
nostr-relay.psfoundation.info,39.0438,-77.4874
bucket.coracle.social,37.7775,-122.397
nostr.carroarmato0.be:443,50.914,3.21378
relay.staging.commonshub.brussels,49.4543,11.0746
relay-dev.satlantis.io,40.8302,-74.1299
relay.lacompagniemaximus.com:443,45.3147,-73.8785
relay-rpi.edufeed.org,49.4521,11.0767
nostr.computingcache.com:443,34.0356,-118.442
relay.lanavault.space:443,60.1699,24.9384
relay.getsafebox.app,43.6532,-79.3832
nrs-02.darkcloudarcade.com:443,39.9526,-75.1652
nostr.hekster.org,37.3986,-121.964
node.kommonzenze.de,49.4521,11.0767
0x-nostr-relay.fly.dev,37.7648,-122.432
speakeasy.cellar.social,49.4543,11.0746
nostr.0x7e.xyz:443,47.4949,8.71954
nostr.vulpem.com,49.4543,11.0746
relay5.bitransfer.org,43.6532,-79.3832
relay.inforsupports.com,43.6532,-79.3832
relay.plebeian.market,50.1109,8.68213
shu02.shugur.net,21.4902,39.2246
nostr.easycryptosend.it,43.6532,-79.3832
nostr.spaceshell.xyz,43.6532,-79.3832
relay.minibolt.info,43.6532,-79.3832
nostr.pbfs.io:443,50.4754,12.3683
nos.lol:443,50.4754,12.3683
nostr.thebiglake.org,32.71,-96.6745
relay.nostriot.com,41.5695,-83.9786
strfry.shock.network:443,39.0438,-77.4874
relay01.lnfi.network,35.6764,139.65
r.0kb.io:443,32.789,-96.7989
bcast.girino.org,43.6532,-79.3832
nostr-relay.psfoundation.info:443,39.0438,-77.4874
dm-test-strfry-discovery.samt.st,43.6532,-79.3832
testnet.samt.st,43.6532,-79.3832
relay.bornheimer.app,51.5072,-0.127586
nrs-02.darkcloudarcade.com,39.9526,-75.1652
relay.binaryrobot.com:443,43.6532,-79.3832
nostr.computingcache.com,34.0356,-118.442
nostr-rs-relay-qj1h.onrender.com,37.7775,-122.397
schnorr.me,43.6532,-79.3832
nostr.twinkle.lol,51.902,7.6657
nostr.overmind.lol,43.6532,-79.3832
relay.mmwaves.de:443,48.8575,2.35138
relay2.veganostr.com,60.1699,24.9384
nostr.mom,50.4754,12.3683
nostr2.girino.org:443,43.6532,-79.3832
wot.sudocarlos.com,43.6532,-79.3832
dev.relay.stream,43.6532,-79.3832
nostr.thalheim.io,60.1699,24.9384
relay-dev.gulugulu.moe:443,43.6532,-79.3832
conduitl2.fly.dev,37.7648,-122.432
relay.bullishbounty.com,43.6532,-79.3832
myvoiceourstory.org,37.3598,-121.981
relay.angor.io,48.1046,11.6002
r.0kb.io,32.789,-96.7989
nexus.libernet.app:443,43.6532,-79.3832
us-east.nostr.pikachat.org:443,39.0438,-77.4874
nostr.bitcoiner.social,47.6743,-117.112
nostrelay.circum.space:443,52.6907,4.8181
relayone.soundhsa.com,39.1008,-94.5811
nostr.snowbla.de,60.1699,24.9384
relay1.orangesync.tech,44.7839,-106.941
nostr-2.21crypto.ch:443,47.5356,8.73209
espelho.girino.org,43.6532,-79.3832
nostr.blankfors.se,60.1699,24.9384
relay.sigit.io:443,50.4754,12.3683
relay.vrtmrz.net:443,43.6532,-79.3832
nostr.wecsats.io:443,43.6532,-79.3832
nostrcity-club.fly.dev,37.7648,-122.432
nostrelay.circum.space,52.6907,4.8181
nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832
relay.edufeed.org,49.4521,11.0767
nostr.dlcdevkit.com:443,40.0992,-83.1141
x.kojira.io,43.6532,-79.3832
relay.binaryrobot.com,43.6532,-79.3832
relay.nostrhub.fr,48.1045,11.6004
nostr.sathoarder.com,48.5734,7.75211
prl.plus,55.7628,37.5983
relay.cosmicbolt.net:443,37.3986,-121.964
relay.nostu.be:443,40.4167,-3.70329
cs-relay.nostrdev.com,50.4754,12.3683
satsage.xyz,37.3986,-121.964
relay.lab.rytswd.com:443,49.4543,11.0746
rilo.nostria.app:443,43.6532,-79.3832
portal-relay.pareto.space,49.0291,8.35696
relay.wavlake.com:443,41.2619,-95.8608
relay.beginningend.com,35.2227,-97.4786
relay.openresist.com:443,43.6532,-79.3832
bitcoiner.social:443,47.6743,-117.112
relay.notoshi.win:443,13.3622,100.983
dev.relay.edufeed.org:443,49.4521,11.0767
relay.cypherflow.ai:443,48.8575,2.35138
relay.ru.ac.th,13.7607,100.627
shu03.shugur.net,25.2048,55.2708
nostr.rtvslawenia.com:443,49.4543,11.0746
relay.agorist.space:443,52.3734,4.89406
relay02.lnfi.network,35.6764,139.65
relay-testnet.k8s.layer3.news:443,37.3387,-121.885
nostr.notribe.net:443,40.8302,-74.1299
relay.ditto.pub,43.6532,-79.3832
nostr.rikmeijer.nl,51.7111,5.36809
blossom.gnostr.cloud:443,43.6532,-79.3832
nostr.oxtr.dev,50.4754,12.3683
cache.trustr.ing,43.6548,-79.3885
nostr.bitczat.pl,60.1699,24.9384
relay.nostrverse.net,43.6532,-79.3832
shu04.shugur.net,25.2048,55.2708
eu.nostr.pikachat.org:443,49.4543,11.0746
ribo.us.nostria.app,43.6532,-79.3832
nostr-relay.cbrx.io,43.6532,-79.3832
nostr.bitczat.pl:443,60.1699,24.9384
nostr-relay.corb.net:443,38.8353,-104.822
relay.libernet.app,43.6532,-79.3832
nostr-verified.wellorder.net,45.5201,-122.99
relay.nostu.be,40.4167,-3.70329
rele.speyhard.fi,51.5072,-0.127586
relay.staging.plebeian.market,51.5072,-0.127586
nostr.spicyz.io,43.6532,-79.3832
nostrride.io,37.3986,-121.964
x.kojira.io:443,43.6532,-79.3832
relay.staging.plebeian.market:443,51.5072,-0.127586
relay.sigit.io,50.4754,12.3683
nostr.stakey.net:443,52.3676,4.90414
relay.nostr.blockhenge.com,39.0438,-77.4874 relay.nostr.blockhenge.com,39.0438,-77.4874
relay.comcomponent.com,43.6532,-79.3832 relay.agorist.space:443,52.3734,4.89406
wot.nostr.place,43.6532,-79.3832
relay.mostr.pub:443,43.6532,-79.3832
nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832
no.str.cr:443,10.6352,-85.4378
relay.chorus.community:443,48.5333,10.7
relay.aarpia.com,37.3986,-121.964
relay.nostrmap.net,60.1699,24.9384
relay.snotr.nl:49999,52.0195,4.42946
relay.bebond.net,43.6532,-79.3832
relay.illuminodes.com,43.6532,-79.3832
chat-relay.zap-work.com:443,43.6532,-79.3832
testr.nymble.world,40.8054,-74.0241
relay.underorion.se,50.1109,8.68213
fanfares.nostr1.com,40.7057,-74.0136
relay.damus.io,43.6532,-79.3832
relay.nostriches.club,43.6532,-79.3832
nostr-dev.wellorder.net,45.5201,-122.99
relay2.angor.io,48.1046,11.6002
relay.openfarmtools.org,60.1699,24.9384
wot.makenomistakes.ca,43.7064,-79.3986
relay.thecryptosquid.com,50.4754,12.3683
test.thedude.cloud,50.1109,8.68213
nostr.rtvslawenia.com,49.4543,11.0746
nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
relay.olas.app:443,60.1699,24.9384
strfry.bonsai.com,39.0438,-77.4874
relayrs.notoshi.win,43.6532,-79.3832
articles.layer3.news,37.3387,-121.885
wot.utxo.one,43.6532,-79.3832
relay.angor.io:443,48.1046,11.6002
relay.dwadziesciajeden.pl,52.2297,21.0122
soloco.nl,43.6532,-79.3832
armada.sharegap.net,43.6532,-79.3832
dm-test-strfry-generic.samt.st,43.6532,-79.3832
nostr.4rs.nl,49.0291,8.35696
nrs-01.darkcloudarcade.com,39.1008,-94.5811
relay.beginningend.com:443,35.2227,-97.4786
relay.nostr.place,43.6532,-79.3832
relay.layer.systems,49.0291,8.35695
adre.su,59.9311,30.3609
relay.0xchat.com:443,43.6532,-79.3832
nostr.infero.net,35.6764,139.65
relay.typedcypher.com:443,51.5072,-0.127586
relay.mostro.network:443,40.8302,-74.1299
relay-fra.zombi.cloudrodion.com,48.8566,2.35222
relay.mitchelltribe.com,39.0438,-77.4874
relay.mostr.pub,43.6532,-79.3832
bitcoiner.social,47.6743,-117.112
nostr.tac.lol:443,47.4748,-122.273
nostr.hekster.org:443,37.3986,-121.964
relay.satmaxt.xyz:443,43.6532,-79.3832
relay.cypherflow.ai,48.8575,2.35138
relay.zone667.com:443,60.1699,24.9384
relay.jeffg.fyi:443,43.6532,-79.3832
relay.agorist.space,52.3734,4.89406
nostr.spaceshell.xyz:443,43.6532,-79.3832
top.testrelay.top:443,43.6532,-79.3832
insta-relay.apps3.slidestr.net,40.4167,-3.70329
relay.nostrian-conquest.com:443,41.223,-111.974
relay.laantungir.net:443,-19.4692,-42.5315
relay.islandbitcoin.com,12.8498,77.6545
purplerelay.com,43.6532,-79.3832
nostr.tac.lol,47.4748,-122.273
nostr.wecsats.io,43.6532,-79.3832
nostr.2b9t.xyz,34.0549,-118.243
nostr.quali.chat,60.1699,24.9384
relay.dreamith.to,43.6532,-79.3832
nostr.islandarea.net:443,35.4669,-97.6473
relay.primal.net,43.6532,-79.3832
eu.nostr.pikachat.org,49.4543,11.0746
relay.nostr.net,43.6532,-79.3832
nostr.n7ekb.net,47.4941,-122.294
relay.mulatta.io,37.5665,126.978
nittom.nostr1.com,40.7057,-74.0136
relay2.orangesync.tech,40.7128,-74.006
relay.artx.market,43.6548,-79.3885
relay.wavefunc.live,41.8781,-87.6298
bitchat.nostr1.com,40.7057,-74.0136
relay.ditto.pub:443,43.6532,-79.3832
relay.wisp.talk,49.4543,11.0746
nostrelites.org,41.8781,-87.6298
relay.goodmorningbitcoin.com,43.6532,-79.3832
dm-test-nostr-rs-42-disabled.samt.st,43.6532,-79.3832
relay.chorus.community,48.5333,10.7
relay.plebchain.club,43.6532,-79.3832
nostr-relay.amethyst.name:443,39.0067,-77.4291
relay.lanacoin-eternity.com,40.8302,-74.1299
relay.edufeed.org:443,49.4521,11.0767
relay.lacompagniemaximus.com,45.3147,-73.8785
relay.dreamith.to:443,43.6532,-79.3832
nostr.stakey.net,52.3676,4.90414
nostr.n7ekb.net:443,47.4941,-122.294
relay.dyne.org,49.0291,8.35705
nostr.janx.com,43.6532,-79.3832
relay.trustr.ing,43.6548,-79.3885
relay.paulstephenborile.com:443,49.4543,11.0746
relay.wisp.talk:443,49.4543,11.0746
yabu.me,35.6092,139.73
bcast.seutoba.com.br,43.6532,-79.3832
nos.xmark.cc,50.6924,3.20113
nos.lol,50.4754,12.3683
relay.satmaxt.xyz,43.6532,-79.3832
relay.endfiat.money:443,59.3327,18.0656
relay.tapestry.ninja,40.8054,-74.0241
relay.lab.rytswd.com,49.4543,11.0746
nostr-kyomu-haskell.onrender.com,37.7775,-122.397
relay.damus.io:443,43.6532,-79.3832
treuzkas.branruz.com,48.8575,2.35138
nostr-01.yakihonne.com,1.32123,103.695
relay.nostriot.com:443,41.5695,-83.9786
nostr-relay.nextblockvending.com,47.2343,-119.853
nostr.aruku.ovh,1.27994,103.849
nostr.chaima.info,50.1109,8.68213
ynostr.yael.at,60.1699,24.9384
ynostr.yael.at:443,60.1699,24.9384
nostr-01.yakihonne.com:443,1.32123,103.695
spookstr2.nostr1.com,40.7057,-74.0136
relay.trustr.ing:443,43.6548,-79.3885
dev.relay.edufeed.org,49.4521,11.0767
relay2.angor.io:443,48.1046,11.6002
nostr.azzamo.net:443,52.2633,21.0283
offchain.bostr.online,43.6532,-79.3832
relay.zone667.com,60.1699,24.9384 relay.zone667.com,60.1699,24.9384
relay.veganostr.com,60.1699,24.9384
vault.iris.to:443,43.6532,-79.3832
relay.artx.market:443,43.6548,-79.3885
wot.rejecttheframe.xyz,43.6532,-79.3832
nostr.pbfs.io,50.4754,12.3683
relay.mostro.network,40.8302,-74.1299
strfry.shock.network,39.0438,-77.4874
relay.klabo.world,47.2343,-119.853
relay.fountain.fm:443,43.6532,-79.3832
nostrja-kari.heguro.com,43.6532,-79.3832
relaisnostr.trivaco.fr,48.5734,7.75211
offchain.pub,39.1585,-94.5728
relay.degmods.com,50.4754,12.3683
nostr-relay.xbytez.io:443,50.6924,3.20113
relay.paulstephenborile.com,49.4543,11.0746
nittom.nostr1.com:443,40.7057,-74.0136
dev-relay.nostreon.com,60.1699,24.9384
nostr-rs-relay.dev.fedibtc.com:443,39.0438,-77.4874
relay.jeffg.fyi,43.6532,-79.3832
relay.laantungir.net,-19.4692,-42.5315
nostr.data.haus:443,50.4754,12.3683
wot.codingarena.top,50.4754,12.3683
nostr.2b9t.xyz:443,34.0549,-118.243
relay.libernet.app:443,43.6532,-79.3832
nostr.ps1829.com,33.8851,130.883
wot.dergigi.com,64.1476,-21.9392
relay.olas.app,60.1699,24.9384
relay.lanacoin-eternity.com:443,40.8302,-74.1299
nostr.data.haus,50.4754,12.3683
relay-dev.gulugulu.moe,43.6532,-79.3832
relay.ohstr.com,43.6532,-79.3832
relay.lightning.pub,39.0438,-77.4874
relay.guggero.org,46.5971,9.59652
testnet-relay.samt.st:443,40.8302,-74.1299
thecitadel.nostr1.com,40.7057,-74.0136
nostr.ps1829.com:443,33.8851,130.883
nostr-relay.corb.net,38.8353,-104.822
relay.npubhaus.com,43.6532,-79.3832
nostr.21crypto.ch,47.5356,8.73209
nostr.tadryanom.me,43.6532,-79.3832
nostr.myshosholoza.co.za,52.3913,4.66545
relay.bitmacro.cloud,43.6532,-79.3832
ribo.nostria.app,43.6532,-79.3832
relay.vrtmrz.net,43.6532,-79.3832
syb.lol,43.6532,-79.3832
relay.bebond.net:443,43.6532,-79.3832
relay.agentry.com,42.8864,-78.8784
nostr-2.21crypto.ch,47.5356,8.73209
nostrcity-club.fly.dev:443,37.7648,-122.432
articles.layer3.news:443,37.3387,-121.885
relay.internationalright-wing.org:443,-22.5022,-48.7114
nostr-relay.zimage.com,34.0549,-118.243
chat-relay.zap-work.com,43.6532,-79.3832
relay.mwaters.net,50.9871,2.12554
nostr.liberty.fans,36.9104,-89.5875
spookstr2.nostr1.com:443,40.7057,-74.0136
nostr.chaima.info:443,50.1109,8.68213
schnorr.me:443,43.6532,-79.3832
ribo.eu.nostria.app,43.6532,-79.3832
nostr.bond,50.1109,8.68213
wot.nostr.party,36.1659,-86.7844
temp.iris.to,43.6532,-79.3832
relay.lotek-distro.com,43.6532,-79.3832
relay.minibolt.info:443,43.6532,-79.3832
relay.lanavault.space,60.1699,24.9384
relay.mmwaves.de,48.8575,2.35138
social.amanah.eblessing.co,48.1046,11.6002
nostr2.thalheim.io,49.4543,11.0746
relay.typedcypher.com,51.5072,-0.127586 relay.typedcypher.com,51.5072,-0.127586
relay.cosmicbolt.net,37.3986,-121.964 wot.nostr.place,43.6532,-79.3832
relayrs.notoshi.win:443,43.6532,-79.3832 nostr.myshosholoza.co.za:443,52.3913,4.66545
nostr-relay-1.trustlessenterprise.com:443,43.6532,-79.3832 relay.homeinhk.xyz,35.694,139.754
nostr.islandarea.net,35.4669,-97.6473 nostr.ps1829.com:443,33.8851,130.883
relay.nostrmap.net:443,60.1699,24.9384 nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
relay.bullishbounty.com:443,43.6532,-79.3832 relay.underorion.se,50.1109,8.68213
nostr.oxtr.dev:443,50.4754,12.3683 nostr-relay.amethyst.name:443,39.0067,-77.4291
srtrelay.c-stellar.net,43.6532,-79.3832 relay.mitchelltribe.com:443,39.0438,-77.4874
relay.mccormick.cx,52.3563,4.95714 bitchat.nostr1.com,40.7057,-74.0136
vault.iris.to,43.6532,-79.3832 0x-nostr-relay.fly.dev,37.7648,-122.432
relay.mccormick.cx:443,52.3563,4.95714 nostr.blankfors.se,60.1699,24.9384
relay.toastr.net,40.8054,-74.0241 relay.solife.me,43.6532,-79.3832
nostr.hifish.org,47.4244,8.57658 relay.snotr.nl:49999,52.0195,4.42946
speakeasy.cellar.social:443,49.4543,11.0746 nostr-relay.corb.net:443,38.8353,-104.822
relay.wavlake.com,41.2619,-95.8608 relay.klabo.world,47.2343,-119.853
testnet-relay.samt.st,40.8302,-74.1299 relay02.lnfi.network,35.6764,139.65
aeon.libretechsystems.xyz,55.486,9.86577 ribo.us.nostria.app,43.6532,-79.3832
mostro-p2p.tech,50.1109,8.68213 nostr.quali.chat:443,60.1699,24.9384
nostr.wild-vibes.ts.net,48.8566,2.35222
nostr.88mph.life,52.1941,-2.21905
relay.nostrcheck.me,43.6532,-79.3832
rilo.nostria.app,43.6532,-79.3832
relay.bowlafterbowl.com,32.9483,-96.7299
relay.nostr.place:443,43.6532,-79.3832
nostr.mom:443,50.4754,12.3683
relay.nostrian-conquest.com,41.223,-111.974
herbstmeister.com,34.0549,-118.243
nrs-01.darkcloudarcade.com:443,39.1008,-94.5811
nostr.red5d.dev,43.6532,-79.3832
nostrbtc.com,43.6532,-79.3832
strfry.apps3.slidestr.net,40.4167,-3.70329
relay.sharegap.net,43.6532,-79.3832
reraw.pbla2fish.cc,43.6532,-79.3832
relay-testnet.k8s.layer3.news,37.3387,-121.885
nostr.sathoarder.com:443,48.5734,7.75211
relay.veganostr.com:443,60.1699,24.9384
relay-fra.zombi.cloudrodion.com:443,48.8566,2.35222
premium.primal.net,43.6532,-79.3832
relay.wavefunc.live:443,41.8781,-87.6298
relay-rpi.edufeed.org:443,49.4521,11.0767
kotukonostr.onrender.com,37.7775,-122.397
bridge.tagomago.me,42.3601,-71.0589
nostr.tadryanom.me:443,43.6532,-79.3832
relay.gulugulu.moe:443,43.6532,-79.3832
offchain.pub:443,39.1585,-94.5728 offchain.pub:443,39.1585,-94.5728
relay.directsponsor.net,42.8864,-78.8784 pool.libernet.app,43.6532,-79.3832
nostr.bond,50.1109,8.68213
soloco.nl,43.6532,-79.3832
relay.ru.ac.th,13.7607,100.627
schnorr.me,43.6532,-79.3832
ec2.f7z.io,60.1699,24.9384
relay.gulugulu.moe:443,43.6532,-79.3832
nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832
nostr.vulpem.com,49.4543,11.0746
rilo.nostria.app,43.6532,-79.3832
adre.su,59.9311,30.3609
ribo.eu.nostria.app:443,43.6532,-79.3832
vault.iris.to,43.6532,-79.3832
strfry.shock.network:443,39.0438,-77.4874
x.kojira.io,43.6532,-79.3832
ribo.nostria.app:443,43.6532,-79.3832
nostr-verified.wellorder.net,45.5201,-122.99
relay.chorus.community:443,48.5333,10.7
nostr.aruku.ovh,1.27994,103.849
relay.internationalright-wing.org:443,-22.5022,-48.7114
relay.getsafebox.app,43.6532,-79.3832
relay-dev.satlantis.io:443,40.8302,-74.1299
kasztanowa.bieda.it,43.6532,-79.3832
relay.bullishbounty.com:443,43.6532,-79.3832
relay.nostrian-conquest.com:443,41.223,-111.974
relay.kilombino.com,43.6532,-79.3832
purplerelay.com,43.6532,-79.3832
relay.erybody.com,41.4513,-81.7021
nostr.na.social,43.6532,-79.3832
relay.wavefunc.live,41.8781,-87.6298
nostr.tagomago.me,42.3601,-71.0589
relay.routstr.com,59.4016,17.9455
relay.wellorder.net,45.5201,-122.99
node.kommonzenze.de,49.4521,11.0767
nostr.pbfs.io,50.4754,12.3683
fanfares.nostr1.com:443,40.7057,-74.0136
relay.btcforplebs.com,43.6532,-79.3832
relay.nostrian-conquest.com,41.223,-111.974
strfry.shock.network,39.0438,-77.4874
relay.satmaxt.xyz:443,43.6532,-79.3832
relay.fckstate.net,59.3293,18.0686
relayone.geektank.ai,39.1008,-94.5811
relayrs.notoshi.win:443,43.6532,-79.3832
wot.nostr.party,36.1659,-86.7844
testnet.samt.st,43.6532,-79.3832
nostr.stakey.net:443,52.3676,4.90414
nostr.spaceshell.xyz,43.6532,-79.3832
nostr.n7ekb.net,47.4941,-122.294
relay.snort.social,53.3498,-6.26031
social.amanah.eblessing.co,48.1046,11.6002
relay.veganostr.com:443,60.1699,24.9384
relay2.angor.io:443,48.1046,11.6002
nostr.overmind.lol:443,43.6532,-79.3832
nostr.ps1829.com,33.8851,130.883
relay-dev.gulugulu.moe:443,43.6532,-79.3832
wot.shaving.kiwi,43.6532,-79.3832
relay.typedcypher.com:443,51.5072,-0.127586
relay.openfarmtools.org,60.1699,24.9384
wot.brightbolt.net,47.6735,-116.781
relay.libernet.app,43.6532,-79.3832
slick.mjex.me,39.0418,-77.4744
relay.nostr.net,43.6532,-79.3832
nostr.chaima.info,50.1109,8.68213
relay.wavlake.com:443,41.2619,-95.8608
relay.cosmicbolt.net,37.3986,-121.964
relay.paulstephenborile.com:443,49.4543,11.0746
relay.nostr.place:443,43.6532,-79.3832
testnet-relay.samt.st,40.8302,-74.1299
testnet-relay.samt.st:443,40.8302,-74.1299
relay.islandbitcoin.com,12.8498,77.6545
dm-test-strfry-discovery.samt.st:443,43.6532,-79.3832
relay.ohstr.com:443,43.6532,-79.3832
nostr.88mph.life,52.1941,-2.21905
relay.layer.systems:443,49.0291,8.35695
schnorr.me:443,43.6532,-79.3832
nostr-relay.psfoundation.info:443,39.0438,-77.4874
nostr.snowbla.de:443,60.1699,24.9384 nostr.snowbla.de:443,60.1699,24.9384
relay.olas.app,60.1699,24.9384
espelho.girino.org,43.6532,-79.3832
nostr.unkn0wn.world,46.8499,9.53287
relayone.geektank.ai:443,39.1008,-94.5811
nostr.easycryptosend.it,43.6532,-79.3832
thecitadel.nostr1.com,40.7057,-74.0136
relay.gulugulu.moe,43.6532,-79.3832
relay.lab.rytswd.com:443,49.4543,11.0746
nos.lol,50.4754,12.3683
relay.plebeian.market:443,50.1109,8.68213
relay.getsafebox.app:443,43.6532,-79.3832
relay.mitchelltribe.com,39.0438,-77.4874
relay-dev.gulugulu.moe,43.6532,-79.3832
relay.trotters.cc,43.6532,-79.3832
relay.ditto.pub,43.6532,-79.3832
relay.ditto.pub:443,43.6532,-79.3832
relay.fountain.fm,43.6532,-79.3832
nostr.2b9t.xyz,34.0549,-118.243
herbstmeister.com,34.0549,-118.243
dev.relay.edufeed.org:443,49.4521,11.0767
relay2.veganostr.com,60.1699,24.9384
nostr.purpura.cloud,43.6532,-79.3832
nostr.quali.chat,60.1699,24.9384
relay.paulstephenborile.com,49.4543,11.0746
relay.0xchat.com,43.6532,-79.3832
articles.layer3.news,37.3387,-121.885
nostrcity-club.fly.dev:443,37.7648,-122.432
relay.endfiat.money:443,59.3327,18.0656
relay.samt.st,40.8302,-74.1299
relay.mostr.pub,43.6532,-79.3832
relay.mccormick.cx,52.3563,4.95714
nostrride.io,37.3986,-121.964
mostro-p2p.tech,50.1109,8.68213
relay.toastr.net,40.8054,-74.0241
reraw.pbla2fish.cc,43.6532,-79.3832
nostr.self-determined.de,53.495,10.2542
temp.iris.to,43.6532,-79.3832
relay.openresist.com:443,43.6532,-79.3832
relay.mostro.network:443,40.8302,-74.1299
srtrelay.c-stellar.net,43.6532,-79.3832
nostr.stakey.net,52.3676,4.90414
nostr.chaima.info:443,50.1109,8.68213
dev.relay.edufeed.org,49.4521,11.0767
nostr.computingcache.com:443,34.0356,-118.442
relay.sigit.io,50.4754,12.3683
nostr.oxtr.dev:443,50.4754,12.3683
relay.angor.io,48.1046,11.6002
kotukonostr.onrender.com,37.7775,-122.397
zealand-charts-craig-thru.trycloudflare.com,43.6532,-79.3832
shu01.shugur.net,21.4902,39.2246
relayone.soundhsa.com:443,39.1008,-94.5811
relay2.angor.io,48.1046,11.6002
nittom.nostr1.com,40.7057,-74.0136
nostr.girino.org:443,43.6532,-79.3832
nexus.libernet.app,43.6532,-79.3832
bitcoiner.social,47.6743,-117.112
nostr2.girino.org,43.6532,-79.3832
relaisnostr.trivaco.fr,49.4282,10.9796
relay.edufeed.org:443,49.4521,11.0767
relay.binaryrobot.com,43.6532,-79.3832
insta-relay.apps3.slidestr.net,40.4167,-3.70329
relay.endfiat.money,59.3327,18.0656
nostr-rs-relay-qj1h.onrender.com,37.7775,-122.397
nrs-01.darkcloudarcade.com:443,39.1008,-94.5811
nostr.liberty.fans,36.9104,-89.5875
relay.mmwaves.de,48.8575,2.35138
relay.inforsupports.com,43.6532,-79.3832
dev-nostr.bityacht.io,43.6532,-79.3832
yabu.me,35.6092,139.73
relay.0xchat.com:443,43.6532,-79.3832
relay.agentry.com,42.8864,-78.8784
relay.mrmave.work,43.6532,-79.3832
nostr.carroarmato0.be,50.914,3.21378
nostr.myshosholoza.co.za,52.3913,4.66545
nostr.thebiglake.org,32.71,-96.6745
relay.kalcafe.xyz,37.3986,-121.964
nostr.openhoofd.nl,51.5717,3.70417
relayone.soundhsa.com,39.1008,-94.5811
relay.bullishbounty.com,43.6532,-79.3832
satsage.xyz,37.3986,-121.964
relay-testnet.k8s.layer3.news,37.3387,-121.885
bridge.tagomago.me,42.3601,-71.0589
relay.sigit.io:443,50.4754,12.3683
relay.mypathtofire.de,42.8864,-78.8784
relay.dreamith.to,43.6532,-79.3832
relay.wavlake.com,41.2619,-95.8608
relay.lanacoin-eternity.com,40.8302,-74.1299
nostr.2b9t.xyz:443,34.0549,-118.243
nostr.notribe.net,40.8302,-74.1299
relay.nostrdice.com,-33.8688,151.209
nostr-relay.amethyst.name,39.0067,-77.4291
relay.mccormick.cx:443,52.3563,4.95714
nostr.4rs.nl,49.0291,8.35696
relay.dwadziesciajeden.pl,52.2297,21.0122
spookstr2.nostr1.com:443,40.7057,-74.0136
relay.vrtmrz.net:443,43.6532,-79.3832
relay.minibolt.info:443,43.6532,-79.3832
nostrelites.org,41.8781,-87.6298
relay.nostar.org,43.6532,-79.3832
nostriches.club,43.6532,-79.3832
rele.speyhard.fi,51.5072,-0.127586
wot.utxo.one,43.6532,-79.3832
relay1.orangesync.tech,44.7839,-106.941
damus.bostr.online,43.6532,-79.3832
relay.islandbitcoin.com:443,12.8498,77.6545
relay.mulatta.io,37.5665,126.978
strfry.openhoofd.nl,51.5717,3.70417
cs-relay.nostrdev.com,50.4754,12.3683
nostr.bitcoiner.social,47.6743,-117.112
relay.thecryptosquid.com,50.4754,12.3683
nostr.computingcache.com,34.0356,-118.442
nostr-relay.psfoundation.info,39.0438,-77.4874
relay5.bitransfer.org,43.6532,-79.3832
relay.damus.io,43.6532,-79.3832
strfry.ymir.cloud,43.6532,-79.3832
dev.relay.stream,43.6532,-79.3832
relay.vrtmrz.net,43.6532,-79.3832
myvoiceourstory.org,37.3598,-121.981
strfry.openhoofd.nl:443,51.5717,3.70417
spookstr2.nostr1.com,40.7057,-74.0136
bitcoiner.social:443,47.6743,-117.112
aeon.libretechsystems.xyz,55.486,9.86577
relayrs.notoshi.win,43.6532,-79.3832
nostr.pbfs.io:443,50.4754,12.3683
public.crostr.com,43.6532,-79.3832
nostr.islandarea.net,35.4669,-97.6473
rilo.nostria.app:443,43.6532,-79.3832
blossom.gnostr.cloud,43.6532,-79.3832
relay.aarpia.com,37.3986,-121.964
relay.arx-ccn.com,50.4754,12.3683
conduitl2.fly.dev,37.7648,-122.432
relay.nostrmap.net,60.1699,24.9384
dm-test-nostr-rs-42-disabled.samt.st,43.6532,-79.3832
nostrelay.circum.space,52.6907,4.8181
syb.lol:443,43.6532,-79.3832
relay.artx.market,43.6548,-79.3885
nostr-rs-relay.dev.fedibtc.com:443,39.0438,-77.4874
blossom.gnostr.cloud:443,43.6532,-79.3832
x.kojira.io:443,43.6532,-79.3832
nostr.tac.lol,47.4748,-122.273
relay-rpi.edufeed.org,49.4521,11.0767
testr.nymble.world,40.8054,-74.0241
relay.beginningend.com:443,35.2227,-97.4786
relay.openresist.com,43.6532,-79.3832
relay.minibolt.info,43.6532,-79.3832
nostr.thalheim.io,60.1699,24.9384
relay.notoshi.win:443,13.3622,100.983
vault.iris.to:443,43.6532,-79.3832
top.testrelay.top,43.6532,-79.3832
nostrbtc.com,43.6532,-79.3832
relay.nearhood.co.uk,51.5072,-0.127586
nostr.overmind.lol,43.6532,-79.3832
relay.agorist.space,52.3734,4.89406
nrs-01.darkcloudarcade.com,39.1008,-94.5811
relay.artx.market:443,43.6548,-79.3885
nos.lol:443,50.4754,12.3683
relay.illuminodes.com,43.6532,-79.3832
relay.laantungir.net,-19.4692,-42.5315
relay.plebchain.club,43.6532,-79.3832
nostr.girino.org,43.6532,-79.3832
nostrja-kari.heguro.com,43.6532,-79.3832
nostrsgp.notribe.net,1.32123,103.695
nostr.thalheim.io:443,60.1699,24.9384
prl.plus,55.7628,37.5983
nostr.carroarmato0.be:443,50.914,3.21378
nostr.nodesmap.com,59.3327,18.0656
bucket.coracle.social,37.7775,-122.397
relay.lightning.pub,39.0438,-77.4874
relay.dreamith.to:443,43.6532,-79.3832
nostr.na.social:443,43.6532,-79.3832
nostr.debate.report,50.1109,8.68213
relay2.orangesync.tech,40.7128,-74.006
relay.comcomponent.com,43.6532,-79.3832
nostr-check.me,43.6532,-79.3832
nostr.oxtr.dev,50.4754,12.3683
nostr.21crypto.ch,47.5356,8.73209
relay.sincensura.org,43.6532,-79.3832
relay.internationalright-wing.org,-22.5022,-48.7114
antiprimal.net,43.6532,-79.3832
nostr.rtvslawenia.com,49.4543,11.0746
relay.nostr.place,43.6532,-79.3832
nostr-kyomu-haskell.onrender.com,37.7775,-122.397
nostr.wecsats.io:443,43.6532,-79.3832
cs-relay.nostrdev.com:443,50.4754,12.3683
nostr.azzamo.net,52.2633,21.0283
relay.cypherflow.ai:443,48.8575,2.35138
nostr.hifish.org,47.4244,8.57658
relay.wavefunc.live:443,41.8781,-87.6298
nostr.tac.lol:443,47.4748,-122.273
wot.dergigi.com,64.1476,-21.9392
nostr.wecsats.io,43.6532,-79.3832
relay-dev.satlantis.io,40.8302,-74.1299
nostr.sathoarder.com:443,48.5734,7.75211
cache.trustr.ing,43.6548,-79.3885
relay.directsponsor.net,42.8864,-78.8784
strfry.bonsai.com:443,39.0438,-77.4874
nostr-01.yakihonne.com,1.32123,103.695
relay.satlantis.io,40.8054,-74.0241
nostr-2.21crypto.ch:443,47.5356,8.73209
offchain.bostr.online,43.6532,-79.3832
test.thedude.cloud,50.1109,8.68213
relay.mostr.pub:443,43.6532,-79.3832
relay.libernet.app:443,43.6532,-79.3832
nostr.mom,50.4754,12.3683
nostr.rtvslawenia.com:443,49.4543,11.0746
relay.plebeian.market,50.1109,8.68213
wot.makenomistakes.ca,43.7064,-79.3986
dm-test-strfry-discovery.samt.st,43.6532,-79.3832
ithurtswhenip.ee,51.5072,-0.127586
relay.staging.commonshub.brussels,49.4543,11.0746
relay.beginningend.com,35.2227,-97.4786
strfry.bonsai.com,39.0438,-77.4874
nostrelay.circum.space:443,52.6907,4.8181
relay.mostro.network,40.8302,-74.1299
dev-relay.nostreon.com,60.1699,24.9384
relay.fundstr.me,42.3601,-71.0589
relay.mmwaves.de:443,48.8575,2.35138
relay.nostu.be,40.4167,-3.70329
relay.nostx.io,43.6532,-79.3832
nostr.data.haus,50.4754,12.3683
relay.lightning.pub:443,39.0438,-77.4874
relay.nostr-check.me,43.6532,-79.3832
nostr.bitcoiner.social:443,47.6743,-117.112
ribo.nostria.app,43.6532,-79.3832
offchain.pub,39.1585,-94.5728
relay.layer.systems,49.0291,8.35695
nostr.dlcdevkit.com,40.0992,-83.1141
relay.angor.io:443,48.1046,11.6002
relay.mwaters.net,50.9871,2.12554
relay.nostriot.com:443,41.5695,-83.9786
nostrcity-club.fly.dev,37.7648,-122.432
nostr.notribe.net:443,40.8302,-74.1299
freelay.sovbit.host,60.1699,24.9384
syb.lol,43.6532,-79.3832
portal-relay.pareto.space,49.0291,8.35696
wot.codingarena.top,50.4754,12.3683
nostr-2.21crypto.ch,47.5356,8.73209
nostr.hifish.org:443,47.4244,8.57658
relay.zone667.com:443,60.1699,24.9384
relay.lab.rytswd.com,49.4543,11.0746
nas01xanthosnet.synology.me:7778,47.1285,8.74735
relay.primal.net,43.6532,-79.3832
relay.bitmacro.cloud,43.6532,-79.3832
chat-relay.zap-work.com:443,43.6532,-79.3832
relay.fountain.fm:443,43.6532,-79.3832
ribo.eu.nostria.app,43.6532,-79.3832
nexus.libernet.app:443,43.6532,-79.3832
nostr.sathoarder.com,48.5734,7.75211
nostr-pub.wellorder.net,45.5201,-122.99
nostr.snowbla.de,60.1699,24.9384
strfry.apps3.slidestr.net,40.4167,-3.70329
nostr.spicyz.io:443,43.6532,-79.3832
nostr.data.haus:443,50.4754,12.3683
nostr.dlcdevkit.com:443,40.0992,-83.1141
purplerelay.com:443,43.6532,-79.3832
nostr-relay.xbytez.io,50.6924,3.20113
relay.nostrmap.net:443,60.1699,24.9384
relay.ohstr.com,43.6532,-79.3832
r.0kb.io:443,32.789,-96.7989
uncouth-panning-paternal.ngrok-free.dev,40.0992,-83.1141
wot.sudocarlos.com,43.6532,-79.3832
relay.satmaxt.xyz,43.6532,-79.3832
ribo.us.nostria.app:443,43.6532,-79.3832
no.str.cr,10.6352,-85.4378
relay.notoshi.win,13.3622,100.983
nostr.spicyz.io,43.6532,-79.3832
relay.veganostr.com,60.1699,24.9384
r.0kb.io,32.789,-96.7989
relay-testnet.k8s.layer3.news:443,37.3387,-121.885
relay.cosmicbolt.net:443,37.3986,-121.964
nostr-relay.xbytez.io:443,50.6924,3.20113
relay.sharegap.net,43.6532,-79.3832
bcast.girino.org,43.6532,-79.3832
nostr2.girino.org:443,43.6532,-79.3832
relay.cypherflow.ai,48.8575,2.35138
nostr-relay.corb.net,38.8353,-104.822
relay.staging.plebeian.market,51.5072,-0.127586
relay.lanavault.space,60.1699,24.9384
relay.wisp.talk,49.4543,11.0746
relay.wisp.talk:443,49.4543,11.0746
no.str.cr:443,10.6352,-85.4378
nostr.hekster.org:443,37.3986,-121.964
nostr-01.yakihonne.com:443,1.32123,103.695
dm-test-strfry-generic.samt.st,43.6532,-79.3832
relay.guggero.org,46.5971,9.59652
nostr.twinkle.lol,51.902,7.6657
nostr.janx.com,43.6532,-79.3832
relay.nostrhub.fr,48.1045,11.6004
relay.nostriot.com,41.5695,-83.9786
nostr.hekster.org,37.3986,-121.964
fanfares.nostr1.com,40.7057,-74.0136
nostr-relay.cbrx.io,43.6532,-79.3832
relay.edufeed.org,49.4521,11.0767
relay.olas.app:443,60.1699,24.9384
armada.sharegap.net,43.6532,-79.3832
nostr.islandarea.net:443,35.4669,-97.6473
relay0.gfcom.info,13.8434,100.363
relay.chorus.community,48.5333,10.7
nittom.nostr1.com:443,40.7057,-74.0136
relay.staging.plebeian.market:443,51.5072,-0.127586
chat-relay.zap-work.com,43.6532,-79.3832
relay-rpi.edufeed.org:443,49.4521,11.0767
relay.lanavault.space:443,60.1699,24.9384
relay.binaryrobot.com:443,43.6532,-79.3832
nostr.mom:443,50.4754,12.3683
relay.bornheimer.app,51.5072,-0.127586
treuzkas.branruz.com,48.8575,2.35138
nostr-dev.wellorder.net,45.5201,-122.99
public.crostr.com:443,43.6532,-79.3832
relay.degmods.com,50.4754,12.3683
articles.layer3.news:443,37.3387,-121.885
nostr.azzamo.net:443,52.2633,21.0283
premium.primal.net,43.6532,-79.3832
1 Relay URL Latitude Longitude
nostr.bitcoiner.social:443 47.6743 -117.112
relay-dev.satlantis.io:443 40.8302 -74.1299
relay.lightning.pub:443 39.0438 -77.4874
ribo.us.nostria.app:443 43.6532 -79.3832
relay.notoshi.win 13.3622 100.983
openrelay.ziomc.com 50.0755 14.4378
relay.agentry.com:443 42.8864 -78.8784
nostr-relay.xbytez.io 50.6924 3.20113
relay.gulugulu.moe 43.6532 -79.3832
nostr.thebiglake.org:443 32.71 -96.6745
relay.fountain.fm 43.6532 -79.3832
freelay.sovbit.host 60.1699 24.9384
nostr.girino.org:443 43.6532 -79.3832
relay.openresist.com 43.6532 -79.3832
nas01xanthosnet.synology.me:7778 47.1285 8.74735
relay.ohstr.com:443 43.6532 -79.3832
nostr-pub.wellorder.net 45.5201 -122.99
relay.nostrdice.com -33.8688 151.209
bbw-nostr.xyz 41.5284 -87.4237
cs-relay.nostrdev.com:443 50.4754 12.3683
top.testrelay.top 43.6532 -79.3832
relay.trotters.cc 43.6532 -79.3832
blossom.gnostr.cloud 43.6532 -79.3832
ribo.eu.nostria.app:443 43.6532 -79.3832
public.crostr.com 43.6532 -79.3832
relay.nostar.org 43.6532 -79.3832
strfry.bonsai.com:443 39.0438 -77.4874
nostr.carroarmato0.be 50.914 3.21378
relay.endfiat.money 59.3327 18.0656
nostr.overmind.lol:443 43.6532 -79.3832
nostr.myshosholoza.co.za:443 52.3913 4.66545
relay.wellorder.net 45.5201 -122.99
nostr.tagomago.me 42.3601 -71.0589
relay.internationalright-wing.org -22.5022 -48.7114
relay.fckstate.net 59.3293 18.0686
nostr.azzamo.net 52.2633 21.0283
relay.0xchat.com 43.6532 -79.3832
relayone.geektank.ai 39.1008 -94.5811
relay.homeinhk.xyz 35.694 139.754
nostr.nodesmap.com 59.3327 18.0656
relay.islandbitcoin.com:443 12.8498 77.6545
relay.mrmave.work 43.6532 -79.3832
relay.arx-ccn.com 50.4754 12.3683
fanfares.nostr1.com:443 40.7057 -74.0136
wot.shaving.kiwi 43.6532 -79.3832
relay.nearhood.co.uk 51.5072 -0.127586
relay.fundstr.me 42.3601 -71.0589
syb.lol:443 43.6532 -79.3832
dm-test-strfry-discovery.samt.st:443 43.6532 -79.3832
relay.nostx.io 43.6532 -79.3832
no.str.cr 10.6352 -85.4378
relay.jbnco.co 43.6532 -79.3832
ribo.nostria.app:443 43.6532 -79.3832
us-east.nostr.pikachat.org 39.0438 -77.4874
nexus.libernet.app 43.6532 -79.3832
nostr.dlcdevkit.com 40.0992 -83.1141
nostr.debate.report 50.1109 8.68213
str-define-contributing-jackets.trycloudflare.com 43.6532 -79.3832
nostr2.girino.org 43.6532 -79.3832
nostr.unkn0wn.world 46.8499 9.53287
nostr.spicyz.io:443 43.6532 -79.3832
relay.layer.systems:443 49.0291 8.35695
nostr-relay.amethyst.name 39.0067 -77.4291
slick.mjex.me 39.0418 -77.4744
nostr.girino.org 43.6532 -79.3832
nostr.quali.chat:443 60.1699 24.9384
purplerelay.com:443 43.6532 -79.3832
nostr.notribe.net 40.8302 -74.1299
relay.plebeian.market:443 50.1109 8.68213
relay.samt.st 40.8302 -74.1299
relay.mitchelltribe.com:443 39.0438 -77.4874
nostr.0x7e.xyz 47.4949 8.71954
relayone.soundhsa.com:443 39.1008 -94.5811
nostr.thalheim.io:443 60.1699 24.9384
relay.getsafebox.app:443 43.6532 -79.3832
nostr-relay.psfoundation.info 39.0438 -77.4874
bucket.coracle.social 37.7775 -122.397
nostr.carroarmato0.be:443 50.914 3.21378
relay.staging.commonshub.brussels 49.4543 11.0746
relay-dev.satlantis.io 40.8302 -74.1299
relay.lacompagniemaximus.com:443 45.3147 -73.8785
relay-rpi.edufeed.org 49.4521 11.0767
nostr.computingcache.com:443 34.0356 -118.442
relay.lanavault.space:443 60.1699 24.9384
relay.getsafebox.app 43.6532 -79.3832
nrs-02.darkcloudarcade.com:443 39.9526 -75.1652
nostr.hekster.org 37.3986 -121.964
node.kommonzenze.de 49.4521 11.0767
0x-nostr-relay.fly.dev 37.7648 -122.432
speakeasy.cellar.social 49.4543 11.0746
nostr.0x7e.xyz:443 47.4949 8.71954
nostr.vulpem.com 49.4543 11.0746
relay5.bitransfer.org 43.6532 -79.3832
relay.inforsupports.com 43.6532 -79.3832
relay.plebeian.market 50.1109 8.68213
shu02.shugur.net 21.4902 39.2246
nostr.easycryptosend.it 43.6532 -79.3832
nostr.spaceshell.xyz 43.6532 -79.3832
relay.minibolt.info 43.6532 -79.3832
nostr.pbfs.io:443 50.4754 12.3683
nos.lol:443 50.4754 12.3683
nostr.thebiglake.org 32.71 -96.6745
relay.nostriot.com 41.5695 -83.9786
strfry.shock.network:443 39.0438 -77.4874
relay01.lnfi.network 35.6764 139.65
r.0kb.io:443 32.789 -96.7989
bcast.girino.org 43.6532 -79.3832
nostr-relay.psfoundation.info:443 39.0438 -77.4874
dm-test-strfry-discovery.samt.st 43.6532 -79.3832
testnet.samt.st 43.6532 -79.3832
relay.bornheimer.app 51.5072 -0.127586
nrs-02.darkcloudarcade.com 39.9526 -75.1652
relay.binaryrobot.com:443 43.6532 -79.3832
nostr.computingcache.com 34.0356 -118.442
nostr-rs-relay-qj1h.onrender.com 37.7775 -122.397
schnorr.me 43.6532 -79.3832
nostr.twinkle.lol 51.902 7.6657
nostr.overmind.lol 43.6532 -79.3832
relay.mmwaves.de:443 48.8575 2.35138
relay2.veganostr.com 60.1699 24.9384
nostr.mom 50.4754 12.3683
nostr2.girino.org:443 43.6532 -79.3832
wot.sudocarlos.com 43.6532 -79.3832
dev.relay.stream 43.6532 -79.3832
nostr.thalheim.io 60.1699 24.9384
relay-dev.gulugulu.moe:443 43.6532 -79.3832
conduitl2.fly.dev 37.7648 -122.432
relay.bullishbounty.com 43.6532 -79.3832
myvoiceourstory.org 37.3598 -121.981
relay.angor.io 48.1046 11.6002
r.0kb.io 32.789 -96.7989
nexus.libernet.app:443 43.6532 -79.3832
us-east.nostr.pikachat.org:443 39.0438 -77.4874
nostr.bitcoiner.social 47.6743 -117.112
nostrelay.circum.space:443 52.6907 4.8181
relayone.soundhsa.com 39.1008 -94.5811
nostr.snowbla.de 60.1699 24.9384
relay1.orangesync.tech 44.7839 -106.941
nostr-2.21crypto.ch:443 47.5356 8.73209
espelho.girino.org 43.6532 -79.3832
nostr.blankfors.se 60.1699 24.9384
relay.sigit.io:443 50.4754 12.3683
relay.vrtmrz.net:443 43.6532 -79.3832
nostr.wecsats.io:443 43.6532 -79.3832
nostrcity-club.fly.dev 37.7648 -122.432
nostrelay.circum.space 52.6907 4.8181
nostr-relay-1.trustlessenterprise.com 43.6532 -79.3832
relay.edufeed.org 49.4521 11.0767
nostr.dlcdevkit.com:443 40.0992 -83.1141
x.kojira.io 43.6532 -79.3832
relay.binaryrobot.com 43.6532 -79.3832
relay.nostrhub.fr 48.1045 11.6004
nostr.sathoarder.com 48.5734 7.75211
prl.plus 55.7628 37.5983
relay.cosmicbolt.net:443 37.3986 -121.964
relay.nostu.be:443 40.4167 -3.70329
cs-relay.nostrdev.com 50.4754 12.3683
satsage.xyz 37.3986 -121.964
relay.lab.rytswd.com:443 49.4543 11.0746
rilo.nostria.app:443 43.6532 -79.3832
portal-relay.pareto.space 49.0291 8.35696
relay.wavlake.com:443 41.2619 -95.8608
relay.beginningend.com 35.2227 -97.4786
relay.openresist.com:443 43.6532 -79.3832
bitcoiner.social:443 47.6743 -117.112
relay.notoshi.win:443 13.3622 100.983
dev.relay.edufeed.org:443 49.4521 11.0767
relay.cypherflow.ai:443 48.8575 2.35138
relay.ru.ac.th 13.7607 100.627
shu03.shugur.net 25.2048 55.2708
nostr.rtvslawenia.com:443 49.4543 11.0746
relay.agorist.space:443 52.3734 4.89406
relay02.lnfi.network 35.6764 139.65
relay-testnet.k8s.layer3.news:443 37.3387 -121.885
nostr.notribe.net:443 40.8302 -74.1299
relay.ditto.pub 43.6532 -79.3832
nostr.rikmeijer.nl 51.7111 5.36809
blossom.gnostr.cloud:443 43.6532 -79.3832
nostr.oxtr.dev 50.4754 12.3683
cache.trustr.ing 43.6548 -79.3885
nostr.bitczat.pl 60.1699 24.9384
relay.nostrverse.net 43.6532 -79.3832
shu04.shugur.net 25.2048 55.2708
eu.nostr.pikachat.org:443 49.4543 11.0746
ribo.us.nostria.app 43.6532 -79.3832
nostr-relay.cbrx.io 43.6532 -79.3832
nostr.bitczat.pl:443 60.1699 24.9384
nostr-relay.corb.net:443 38.8353 -104.822
relay.libernet.app 43.6532 -79.3832
nostr-verified.wellorder.net 45.5201 -122.99
relay.nostu.be 40.4167 -3.70329
rele.speyhard.fi 51.5072 -0.127586
relay.staging.plebeian.market 51.5072 -0.127586
nostr.spicyz.io 43.6532 -79.3832
nostrride.io 37.3986 -121.964
x.kojira.io:443 43.6532 -79.3832
relay.staging.plebeian.market:443 51.5072 -0.127586
relay.sigit.io 50.4754 12.3683
nostr.stakey.net:443 52.3676 4.90414
2 relay.nostr.blockhenge.com 39.0438 -77.4874
3 relay.comcomponent.com relay.agorist.space:443 43.6532 52.3734 -79.3832 4.89406
wot.nostr.place 43.6532 -79.3832
relay.mostr.pub:443 43.6532 -79.3832
nostr-rs-relay-ishosta.phamthanh.me 43.6532 -79.3832
no.str.cr:443 10.6352 -85.4378
relay.chorus.community:443 48.5333 10.7
relay.aarpia.com 37.3986 -121.964
relay.nostrmap.net 60.1699 24.9384
relay.snotr.nl:49999 52.0195 4.42946
relay.bebond.net 43.6532 -79.3832
relay.illuminodes.com 43.6532 -79.3832
chat-relay.zap-work.com:443 43.6532 -79.3832
testr.nymble.world 40.8054 -74.0241
relay.underorion.se 50.1109 8.68213
fanfares.nostr1.com 40.7057 -74.0136
relay.damus.io 43.6532 -79.3832
relay.nostriches.club 43.6532 -79.3832
nostr-dev.wellorder.net 45.5201 -122.99
relay2.angor.io 48.1046 11.6002
relay.openfarmtools.org 60.1699 24.9384
wot.makenomistakes.ca 43.7064 -79.3986
relay.thecryptosquid.com 50.4754 12.3683
test.thedude.cloud 50.1109 8.68213
nostr.rtvslawenia.com 49.4543 11.0746
nostr-rs-relay.dev.fedibtc.com 39.0438 -77.4874
relay.olas.app:443 60.1699 24.9384
strfry.bonsai.com 39.0438 -77.4874
relayrs.notoshi.win 43.6532 -79.3832
articles.layer3.news 37.3387 -121.885
wot.utxo.one 43.6532 -79.3832
relay.angor.io:443 48.1046 11.6002
relay.dwadziesciajeden.pl 52.2297 21.0122
soloco.nl 43.6532 -79.3832
armada.sharegap.net 43.6532 -79.3832
dm-test-strfry-generic.samt.st 43.6532 -79.3832
nostr.4rs.nl 49.0291 8.35696
nrs-01.darkcloudarcade.com 39.1008 -94.5811
relay.beginningend.com:443 35.2227 -97.4786
relay.nostr.place 43.6532 -79.3832
relay.layer.systems 49.0291 8.35695
adre.su 59.9311 30.3609
relay.0xchat.com:443 43.6532 -79.3832
nostr.infero.net 35.6764 139.65
relay.typedcypher.com:443 51.5072 -0.127586
relay.mostro.network:443 40.8302 -74.1299
relay-fra.zombi.cloudrodion.com 48.8566 2.35222
relay.mitchelltribe.com 39.0438 -77.4874
relay.mostr.pub 43.6532 -79.3832
bitcoiner.social 47.6743 -117.112
nostr.tac.lol:443 47.4748 -122.273
nostr.hekster.org:443 37.3986 -121.964
relay.satmaxt.xyz:443 43.6532 -79.3832
relay.cypherflow.ai 48.8575 2.35138
relay.zone667.com:443 60.1699 24.9384
relay.jeffg.fyi:443 43.6532 -79.3832
relay.agorist.space 52.3734 4.89406
nostr.spaceshell.xyz:443 43.6532 -79.3832
top.testrelay.top:443 43.6532 -79.3832
insta-relay.apps3.slidestr.net 40.4167 -3.70329
relay.nostrian-conquest.com:443 41.223 -111.974
relay.laantungir.net:443 -19.4692 -42.5315
relay.islandbitcoin.com 12.8498 77.6545
purplerelay.com 43.6532 -79.3832
nostr.tac.lol 47.4748 -122.273
nostr.wecsats.io 43.6532 -79.3832
nostr.2b9t.xyz 34.0549 -118.243
nostr.quali.chat 60.1699 24.9384
relay.dreamith.to 43.6532 -79.3832
nostr.islandarea.net:443 35.4669 -97.6473
relay.primal.net 43.6532 -79.3832
eu.nostr.pikachat.org 49.4543 11.0746
relay.nostr.net 43.6532 -79.3832
nostr.n7ekb.net 47.4941 -122.294
relay.mulatta.io 37.5665 126.978
nittom.nostr1.com 40.7057 -74.0136
relay2.orangesync.tech 40.7128 -74.006
relay.artx.market 43.6548 -79.3885
relay.wavefunc.live 41.8781 -87.6298
bitchat.nostr1.com 40.7057 -74.0136
relay.ditto.pub:443 43.6532 -79.3832
relay.wisp.talk 49.4543 11.0746
nostrelites.org 41.8781 -87.6298
relay.goodmorningbitcoin.com 43.6532 -79.3832
dm-test-nostr-rs-42-disabled.samt.st 43.6532 -79.3832
relay.chorus.community 48.5333 10.7
relay.plebchain.club 43.6532 -79.3832
nostr-relay.amethyst.name:443 39.0067 -77.4291
relay.lanacoin-eternity.com 40.8302 -74.1299
relay.edufeed.org:443 49.4521 11.0767
relay.lacompagniemaximus.com 45.3147 -73.8785
relay.dreamith.to:443 43.6532 -79.3832
nostr.stakey.net 52.3676 4.90414
nostr.n7ekb.net:443 47.4941 -122.294
relay.dyne.org 49.0291 8.35705
nostr.janx.com 43.6532 -79.3832
relay.trustr.ing 43.6548 -79.3885
relay.paulstephenborile.com:443 49.4543 11.0746
relay.wisp.talk:443 49.4543 11.0746
yabu.me 35.6092 139.73
bcast.seutoba.com.br 43.6532 -79.3832
nos.xmark.cc 50.6924 3.20113
nos.lol 50.4754 12.3683
relay.satmaxt.xyz 43.6532 -79.3832
relay.endfiat.money:443 59.3327 18.0656
relay.tapestry.ninja 40.8054 -74.0241
relay.lab.rytswd.com 49.4543 11.0746
nostr-kyomu-haskell.onrender.com 37.7775 -122.397
relay.damus.io:443 43.6532 -79.3832
treuzkas.branruz.com 48.8575 2.35138
nostr-01.yakihonne.com 1.32123 103.695
relay.nostriot.com:443 41.5695 -83.9786
nostr-relay.nextblockvending.com 47.2343 -119.853
nostr.aruku.ovh 1.27994 103.849
nostr.chaima.info 50.1109 8.68213
ynostr.yael.at 60.1699 24.9384
ynostr.yael.at:443 60.1699 24.9384
nostr-01.yakihonne.com:443 1.32123 103.695
spookstr2.nostr1.com 40.7057 -74.0136
relay.trustr.ing:443 43.6548 -79.3885
dev.relay.edufeed.org 49.4521 11.0767
relay2.angor.io:443 48.1046 11.6002
nostr.azzamo.net:443 52.2633 21.0283
offchain.bostr.online 43.6532 -79.3832
4 relay.zone667.com 60.1699 24.9384
relay.veganostr.com 60.1699 24.9384
vault.iris.to:443 43.6532 -79.3832
relay.artx.market:443 43.6548 -79.3885
wot.rejecttheframe.xyz 43.6532 -79.3832
nostr.pbfs.io 50.4754 12.3683
relay.mostro.network 40.8302 -74.1299
strfry.shock.network 39.0438 -77.4874
relay.klabo.world 47.2343 -119.853
relay.fountain.fm:443 43.6532 -79.3832
nostrja-kari.heguro.com 43.6532 -79.3832
relaisnostr.trivaco.fr 48.5734 7.75211
offchain.pub 39.1585 -94.5728
relay.degmods.com 50.4754 12.3683
nostr-relay.xbytez.io:443 50.6924 3.20113
relay.paulstephenborile.com 49.4543 11.0746
nittom.nostr1.com:443 40.7057 -74.0136
dev-relay.nostreon.com 60.1699 24.9384
nostr-rs-relay.dev.fedibtc.com:443 39.0438 -77.4874
relay.jeffg.fyi 43.6532 -79.3832
relay.laantungir.net -19.4692 -42.5315
nostr.data.haus:443 50.4754 12.3683
wot.codingarena.top 50.4754 12.3683
nostr.2b9t.xyz:443 34.0549 -118.243
relay.libernet.app:443 43.6532 -79.3832
nostr.ps1829.com 33.8851 130.883
wot.dergigi.com 64.1476 -21.9392
relay.olas.app 60.1699 24.9384
relay.lanacoin-eternity.com:443 40.8302 -74.1299
nostr.data.haus 50.4754 12.3683
relay-dev.gulugulu.moe 43.6532 -79.3832
relay.ohstr.com 43.6532 -79.3832
relay.lightning.pub 39.0438 -77.4874
relay.guggero.org 46.5971 9.59652
testnet-relay.samt.st:443 40.8302 -74.1299
thecitadel.nostr1.com 40.7057 -74.0136
nostr.ps1829.com:443 33.8851 130.883
nostr-relay.corb.net 38.8353 -104.822
relay.npubhaus.com 43.6532 -79.3832
nostr.21crypto.ch 47.5356 8.73209
nostr.tadryanom.me 43.6532 -79.3832
nostr.myshosholoza.co.za 52.3913 4.66545
relay.bitmacro.cloud 43.6532 -79.3832
ribo.nostria.app 43.6532 -79.3832
relay.vrtmrz.net 43.6532 -79.3832
syb.lol 43.6532 -79.3832
relay.bebond.net:443 43.6532 -79.3832
relay.agentry.com 42.8864 -78.8784
nostr-2.21crypto.ch 47.5356 8.73209
nostrcity-club.fly.dev:443 37.7648 -122.432
articles.layer3.news:443 37.3387 -121.885
relay.internationalright-wing.org:443 -22.5022 -48.7114
nostr-relay.zimage.com 34.0549 -118.243
chat-relay.zap-work.com 43.6532 -79.3832
relay.mwaters.net 50.9871 2.12554
nostr.liberty.fans 36.9104 -89.5875
spookstr2.nostr1.com:443 40.7057 -74.0136
nostr.chaima.info:443 50.1109 8.68213
schnorr.me:443 43.6532 -79.3832
ribo.eu.nostria.app 43.6532 -79.3832
nostr.bond 50.1109 8.68213
wot.nostr.party 36.1659 -86.7844
temp.iris.to 43.6532 -79.3832
relay.lotek-distro.com 43.6532 -79.3832
relay.minibolt.info:443 43.6532 -79.3832
relay.lanavault.space 60.1699 24.9384
relay.mmwaves.de 48.8575 2.35138
social.amanah.eblessing.co 48.1046 11.6002
nostr2.thalheim.io 49.4543 11.0746
5 relay.typedcypher.com 51.5072 -0.127586
6 relay.cosmicbolt.net wot.nostr.place 37.3986 43.6532 -121.964 -79.3832
7 relayrs.notoshi.win:443 nostr.myshosholoza.co.za:443 43.6532 52.3913 -79.3832 4.66545
8 nostr-relay-1.trustlessenterprise.com:443 relay.homeinhk.xyz 43.6532 35.694 -79.3832 139.754
9 nostr.islandarea.net nostr.ps1829.com:443 35.4669 33.8851 -97.6473 130.883
10 relay.nostrmap.net:443 nostr-rs-relay.dev.fedibtc.com 60.1699 39.0438 24.9384 -77.4874
11 relay.bullishbounty.com:443 relay.underorion.se 43.6532 50.1109 -79.3832 8.68213
12 nostr.oxtr.dev:443 nostr-relay.amethyst.name:443 50.4754 39.0067 12.3683 -77.4291
13 srtrelay.c-stellar.net relay.mitchelltribe.com:443 43.6532 39.0438 -79.3832 -77.4874
14 relay.mccormick.cx bitchat.nostr1.com 52.3563 40.7057 4.95714 -74.0136
15 vault.iris.to 0x-nostr-relay.fly.dev 43.6532 37.7648 -79.3832 -122.432
16 relay.mccormick.cx:443 nostr.blankfors.se 52.3563 60.1699 4.95714 24.9384
17 relay.toastr.net relay.solife.me 40.8054 43.6532 -74.0241 -79.3832
18 nostr.hifish.org relay.snotr.nl:49999 47.4244 52.0195 8.57658 4.42946
19 speakeasy.cellar.social:443 nostr-relay.corb.net:443 49.4543 38.8353 11.0746 -104.822
20 relay.wavlake.com relay.klabo.world 41.2619 47.2343 -95.8608 -119.853
21 testnet-relay.samt.st relay02.lnfi.network 40.8302 35.6764 -74.1299 139.65
22 aeon.libretechsystems.xyz ribo.us.nostria.app 55.486 43.6532 9.86577 -79.3832
23 mostro-p2p.tech nostr.quali.chat:443 50.1109 60.1699 8.68213 24.9384
nostr.wild-vibes.ts.net 48.8566 2.35222
nostr.88mph.life 52.1941 -2.21905
relay.nostrcheck.me 43.6532 -79.3832
rilo.nostria.app 43.6532 -79.3832
relay.bowlafterbowl.com 32.9483 -96.7299
relay.nostr.place:443 43.6532 -79.3832
nostr.mom:443 50.4754 12.3683
relay.nostrian-conquest.com 41.223 -111.974
herbstmeister.com 34.0549 -118.243
nrs-01.darkcloudarcade.com:443 39.1008 -94.5811
nostr.red5d.dev 43.6532 -79.3832
nostrbtc.com 43.6532 -79.3832
strfry.apps3.slidestr.net 40.4167 -3.70329
relay.sharegap.net 43.6532 -79.3832
reraw.pbla2fish.cc 43.6532 -79.3832
relay-testnet.k8s.layer3.news 37.3387 -121.885
nostr.sathoarder.com:443 48.5734 7.75211
relay.veganostr.com:443 60.1699 24.9384
relay-fra.zombi.cloudrodion.com:443 48.8566 2.35222
premium.primal.net 43.6532 -79.3832
relay.wavefunc.live:443 41.8781 -87.6298
relay-rpi.edufeed.org:443 49.4521 11.0767
kotukonostr.onrender.com 37.7775 -122.397
bridge.tagomago.me 42.3601 -71.0589
nostr.tadryanom.me:443 43.6532 -79.3832
relay.gulugulu.moe:443 43.6532 -79.3832
24 offchain.pub:443 39.1585 -94.5728
25 relay.directsponsor.net pool.libernet.app 42.8864 43.6532 -78.8784 -79.3832
26 nostr.bond 50.1109 8.68213
27 soloco.nl 43.6532 -79.3832
28 relay.ru.ac.th 13.7607 100.627
29 schnorr.me 43.6532 -79.3832
30 ec2.f7z.io 60.1699 24.9384
31 relay.gulugulu.moe:443 43.6532 -79.3832
32 nostr-rs-relay-ishosta.phamthanh.me 43.6532 -79.3832
33 nostr.vulpem.com 49.4543 11.0746
34 rilo.nostria.app 43.6532 -79.3832
35 adre.su 59.9311 30.3609
36 ribo.eu.nostria.app:443 43.6532 -79.3832
37 vault.iris.to 43.6532 -79.3832
38 strfry.shock.network:443 39.0438 -77.4874
39 x.kojira.io 43.6532 -79.3832
40 ribo.nostria.app:443 43.6532 -79.3832
41 nostr-verified.wellorder.net 45.5201 -122.99
42 relay.chorus.community:443 48.5333 10.7
43 nostr.aruku.ovh 1.27994 103.849
44 relay.internationalright-wing.org:443 -22.5022 -48.7114
45 relay.getsafebox.app 43.6532 -79.3832
46 relay-dev.satlantis.io:443 40.8302 -74.1299
47 kasztanowa.bieda.it 43.6532 -79.3832
48 relay.bullishbounty.com:443 43.6532 -79.3832
49 relay.nostrian-conquest.com:443 41.223 -111.974
50 relay.kilombino.com 43.6532 -79.3832
51 purplerelay.com 43.6532 -79.3832
52 relay.erybody.com 41.4513 -81.7021
53 nostr.na.social 43.6532 -79.3832
54 relay.wavefunc.live 41.8781 -87.6298
55 nostr.tagomago.me 42.3601 -71.0589
56 relay.routstr.com 59.4016 17.9455
57 relay.wellorder.net 45.5201 -122.99
58 node.kommonzenze.de 49.4521 11.0767
59 nostr.pbfs.io 50.4754 12.3683
60 fanfares.nostr1.com:443 40.7057 -74.0136
61 relay.btcforplebs.com 43.6532 -79.3832
62 relay.nostrian-conquest.com 41.223 -111.974
63 strfry.shock.network 39.0438 -77.4874
64 relay.satmaxt.xyz:443 43.6532 -79.3832
65 relay.fckstate.net 59.3293 18.0686
66 relayone.geektank.ai 39.1008 -94.5811
67 relayrs.notoshi.win:443 43.6532 -79.3832
68 wot.nostr.party 36.1659 -86.7844
69 testnet.samt.st 43.6532 -79.3832
70 nostr.stakey.net:443 52.3676 4.90414
71 nostr.spaceshell.xyz 43.6532 -79.3832
72 nostr.n7ekb.net 47.4941 -122.294
73 relay.snort.social 53.3498 -6.26031
74 social.amanah.eblessing.co 48.1046 11.6002
75 relay.veganostr.com:443 60.1699 24.9384
76 relay2.angor.io:443 48.1046 11.6002
77 nostr.overmind.lol:443 43.6532 -79.3832
78 nostr.ps1829.com 33.8851 130.883
79 relay-dev.gulugulu.moe:443 43.6532 -79.3832
80 wot.shaving.kiwi 43.6532 -79.3832
81 relay.typedcypher.com:443 51.5072 -0.127586
82 relay.openfarmtools.org 60.1699 24.9384
83 wot.brightbolt.net 47.6735 -116.781
84 relay.libernet.app 43.6532 -79.3832
85 slick.mjex.me 39.0418 -77.4744
86 relay.nostr.net 43.6532 -79.3832
87 nostr.chaima.info 50.1109 8.68213
88 relay.wavlake.com:443 41.2619 -95.8608
89 relay.cosmicbolt.net 37.3986 -121.964
90 relay.paulstephenborile.com:443 49.4543 11.0746
91 relay.nostr.place:443 43.6532 -79.3832
92 testnet-relay.samt.st 40.8302 -74.1299
93 testnet-relay.samt.st:443 40.8302 -74.1299
94 relay.islandbitcoin.com 12.8498 77.6545
95 dm-test-strfry-discovery.samt.st:443 43.6532 -79.3832
96 relay.ohstr.com:443 43.6532 -79.3832
97 nostr.88mph.life 52.1941 -2.21905
98 relay.layer.systems:443 49.0291 8.35695
99 schnorr.me:443 43.6532 -79.3832
100 nostr-relay.psfoundation.info:443 39.0438 -77.4874
101 nostr.snowbla.de:443 60.1699 24.9384
102 relay.olas.app 60.1699 24.9384
103 espelho.girino.org 43.6532 -79.3832
104 nostr.unkn0wn.world 46.8499 9.53287
105 relayone.geektank.ai:443 39.1008 -94.5811
106 nostr.easycryptosend.it 43.6532 -79.3832
107 thecitadel.nostr1.com 40.7057 -74.0136
108 relay.gulugulu.moe 43.6532 -79.3832
109 relay.lab.rytswd.com:443 49.4543 11.0746
110 nos.lol 50.4754 12.3683
111 relay.plebeian.market:443 50.1109 8.68213
112 relay.getsafebox.app:443 43.6532 -79.3832
113 relay.mitchelltribe.com 39.0438 -77.4874
114 relay-dev.gulugulu.moe 43.6532 -79.3832
115 relay.trotters.cc 43.6532 -79.3832
116 relay.ditto.pub 43.6532 -79.3832
117 relay.ditto.pub:443 43.6532 -79.3832
118 relay.fountain.fm 43.6532 -79.3832
119 nostr.2b9t.xyz 34.0549 -118.243
120 herbstmeister.com 34.0549 -118.243
121 dev.relay.edufeed.org:443 49.4521 11.0767
122 relay2.veganostr.com 60.1699 24.9384
123 nostr.purpura.cloud 43.6532 -79.3832
124 nostr.quali.chat 60.1699 24.9384
125 relay.paulstephenborile.com 49.4543 11.0746
126 relay.0xchat.com 43.6532 -79.3832
127 articles.layer3.news 37.3387 -121.885
128 nostrcity-club.fly.dev:443 37.7648 -122.432
129 relay.endfiat.money:443 59.3327 18.0656
130 relay.samt.st 40.8302 -74.1299
131 relay.mostr.pub 43.6532 -79.3832
132 relay.mccormick.cx 52.3563 4.95714
133 nostrride.io 37.3986 -121.964
134 mostro-p2p.tech 50.1109 8.68213
135 relay.toastr.net 40.8054 -74.0241
136 reraw.pbla2fish.cc 43.6532 -79.3832
137 nostr.self-determined.de 53.495 10.2542
138 temp.iris.to 43.6532 -79.3832
139 relay.openresist.com:443 43.6532 -79.3832
140 relay.mostro.network:443 40.8302 -74.1299
141 srtrelay.c-stellar.net 43.6532 -79.3832
142 nostr.stakey.net 52.3676 4.90414
143 nostr.chaima.info:443 50.1109 8.68213
144 dev.relay.edufeed.org 49.4521 11.0767
145 nostr.computingcache.com:443 34.0356 -118.442
146 relay.sigit.io 50.4754 12.3683
147 nostr.oxtr.dev:443 50.4754 12.3683
148 relay.angor.io 48.1046 11.6002
149 kotukonostr.onrender.com 37.7775 -122.397
150 zealand-charts-craig-thru.trycloudflare.com 43.6532 -79.3832
151 shu01.shugur.net 21.4902 39.2246
152 relayone.soundhsa.com:443 39.1008 -94.5811
153 relay2.angor.io 48.1046 11.6002
154 nittom.nostr1.com 40.7057 -74.0136
155 nostr.girino.org:443 43.6532 -79.3832
156 nexus.libernet.app 43.6532 -79.3832
157 bitcoiner.social 47.6743 -117.112
158 nostr2.girino.org 43.6532 -79.3832
159 relaisnostr.trivaco.fr 49.4282 10.9796
160 relay.edufeed.org:443 49.4521 11.0767
161 relay.binaryrobot.com 43.6532 -79.3832
162 insta-relay.apps3.slidestr.net 40.4167 -3.70329
163 relay.endfiat.money 59.3327 18.0656
164 nostr-rs-relay-qj1h.onrender.com 37.7775 -122.397
165 nrs-01.darkcloudarcade.com:443 39.1008 -94.5811
166 nostr.liberty.fans 36.9104 -89.5875
167 relay.mmwaves.de 48.8575 2.35138
168 relay.inforsupports.com 43.6532 -79.3832
169 dev-nostr.bityacht.io 43.6532 -79.3832
170 yabu.me 35.6092 139.73
171 relay.0xchat.com:443 43.6532 -79.3832
172 relay.agentry.com 42.8864 -78.8784
173 relay.mrmave.work 43.6532 -79.3832
174 nostr.carroarmato0.be 50.914 3.21378
175 nostr.myshosholoza.co.za 52.3913 4.66545
176 nostr.thebiglake.org 32.71 -96.6745
177 relay.kalcafe.xyz 37.3986 -121.964
178 nostr.openhoofd.nl 51.5717 3.70417
179 relayone.soundhsa.com 39.1008 -94.5811
180 relay.bullishbounty.com 43.6532 -79.3832
181 satsage.xyz 37.3986 -121.964
182 relay-testnet.k8s.layer3.news 37.3387 -121.885
183 bridge.tagomago.me 42.3601 -71.0589
184 relay.sigit.io:443 50.4754 12.3683
185 relay.mypathtofire.de 42.8864 -78.8784
186 relay.dreamith.to 43.6532 -79.3832
187 relay.wavlake.com 41.2619 -95.8608
188 relay.lanacoin-eternity.com 40.8302 -74.1299
189 nostr.2b9t.xyz:443 34.0549 -118.243
190 nostr.notribe.net 40.8302 -74.1299
191 relay.nostrdice.com -33.8688 151.209
192 nostr-relay.amethyst.name 39.0067 -77.4291
193 relay.mccormick.cx:443 52.3563 4.95714
194 nostr.4rs.nl 49.0291 8.35696
195 relay.dwadziesciajeden.pl 52.2297 21.0122
196 spookstr2.nostr1.com:443 40.7057 -74.0136
197 relay.vrtmrz.net:443 43.6532 -79.3832
198 relay.minibolt.info:443 43.6532 -79.3832
199 nostrelites.org 41.8781 -87.6298
200 relay.nostar.org 43.6532 -79.3832
201 nostriches.club 43.6532 -79.3832
202 rele.speyhard.fi 51.5072 -0.127586
203 wot.utxo.one 43.6532 -79.3832
204 relay1.orangesync.tech 44.7839 -106.941
205 damus.bostr.online 43.6532 -79.3832
206 relay.islandbitcoin.com:443 12.8498 77.6545
207 relay.mulatta.io 37.5665 126.978
208 strfry.openhoofd.nl 51.5717 3.70417
209 cs-relay.nostrdev.com 50.4754 12.3683
210 nostr.bitcoiner.social 47.6743 -117.112
211 relay.thecryptosquid.com 50.4754 12.3683
212 nostr.computingcache.com 34.0356 -118.442
213 nostr-relay.psfoundation.info 39.0438 -77.4874
214 relay5.bitransfer.org 43.6532 -79.3832
215 relay.damus.io 43.6532 -79.3832
216 strfry.ymir.cloud 43.6532 -79.3832
217 dev.relay.stream 43.6532 -79.3832
218 relay.vrtmrz.net 43.6532 -79.3832
219 myvoiceourstory.org 37.3598 -121.981
220 strfry.openhoofd.nl:443 51.5717 3.70417
221 spookstr2.nostr1.com 40.7057 -74.0136
222 bitcoiner.social:443 47.6743 -117.112
223 aeon.libretechsystems.xyz 55.486 9.86577
224 relayrs.notoshi.win 43.6532 -79.3832
225 nostr.pbfs.io:443 50.4754 12.3683
226 public.crostr.com 43.6532 -79.3832
227 nostr.islandarea.net 35.4669 -97.6473
228 rilo.nostria.app:443 43.6532 -79.3832
229 blossom.gnostr.cloud 43.6532 -79.3832
230 relay.aarpia.com 37.3986 -121.964
231 relay.arx-ccn.com 50.4754 12.3683
232 conduitl2.fly.dev 37.7648 -122.432
233 relay.nostrmap.net 60.1699 24.9384
234 dm-test-nostr-rs-42-disabled.samt.st 43.6532 -79.3832
235 nostrelay.circum.space 52.6907 4.8181
236 syb.lol:443 43.6532 -79.3832
237 relay.artx.market 43.6548 -79.3885
238 nostr-rs-relay.dev.fedibtc.com:443 39.0438 -77.4874
239 blossom.gnostr.cloud:443 43.6532 -79.3832
240 x.kojira.io:443 43.6532 -79.3832
241 nostr.tac.lol 47.4748 -122.273
242 relay-rpi.edufeed.org 49.4521 11.0767
243 testr.nymble.world 40.8054 -74.0241
244 relay.beginningend.com:443 35.2227 -97.4786
245 relay.openresist.com 43.6532 -79.3832
246 relay.minibolt.info 43.6532 -79.3832
247 nostr.thalheim.io 60.1699 24.9384
248 relay.notoshi.win:443 13.3622 100.983
249 vault.iris.to:443 43.6532 -79.3832
250 top.testrelay.top 43.6532 -79.3832
251 nostrbtc.com 43.6532 -79.3832
252 relay.nearhood.co.uk 51.5072 -0.127586
253 nostr.overmind.lol 43.6532 -79.3832
254 relay.agorist.space 52.3734 4.89406
255 nrs-01.darkcloudarcade.com 39.1008 -94.5811
256 relay.artx.market:443 43.6548 -79.3885
257 nos.lol:443 50.4754 12.3683
258 relay.illuminodes.com 43.6532 -79.3832
259 relay.laantungir.net -19.4692 -42.5315
260 relay.plebchain.club 43.6532 -79.3832
261 nostr.girino.org 43.6532 -79.3832
262 nostrja-kari.heguro.com 43.6532 -79.3832
263 nostrsgp.notribe.net 1.32123 103.695
264 nostr.thalheim.io:443 60.1699 24.9384
265 prl.plus 55.7628 37.5983
266 nostr.carroarmato0.be:443 50.914 3.21378
267 nostr.nodesmap.com 59.3327 18.0656
268 bucket.coracle.social 37.7775 -122.397
269 relay.lightning.pub 39.0438 -77.4874
270 relay.dreamith.to:443 43.6532 -79.3832
271 nostr.na.social:443 43.6532 -79.3832
272 nostr.debate.report 50.1109 8.68213
273 relay2.orangesync.tech 40.7128 -74.006
274 relay.comcomponent.com 43.6532 -79.3832
275 nostr-check.me 43.6532 -79.3832
276 nostr.oxtr.dev 50.4754 12.3683
277 nostr.21crypto.ch 47.5356 8.73209
278 relay.sincensura.org 43.6532 -79.3832
279 relay.internationalright-wing.org -22.5022 -48.7114
280 antiprimal.net 43.6532 -79.3832
281 nostr.rtvslawenia.com 49.4543 11.0746
282 relay.nostr.place 43.6532 -79.3832
283 nostr-kyomu-haskell.onrender.com 37.7775 -122.397
284 nostr.wecsats.io:443 43.6532 -79.3832
285 cs-relay.nostrdev.com:443 50.4754 12.3683
286 nostr.azzamo.net 52.2633 21.0283
287 relay.cypherflow.ai:443 48.8575 2.35138
288 nostr.hifish.org 47.4244 8.57658
289 relay.wavefunc.live:443 41.8781 -87.6298
290 nostr.tac.lol:443 47.4748 -122.273
291 wot.dergigi.com 64.1476 -21.9392
292 nostr.wecsats.io 43.6532 -79.3832
293 relay-dev.satlantis.io 40.8302 -74.1299
294 nostr.sathoarder.com:443 48.5734 7.75211
295 cache.trustr.ing 43.6548 -79.3885
296 relay.directsponsor.net 42.8864 -78.8784
297 strfry.bonsai.com:443 39.0438 -77.4874
298 nostr-01.yakihonne.com 1.32123 103.695
299 relay.satlantis.io 40.8054 -74.0241
300 nostr-2.21crypto.ch:443 47.5356 8.73209
301 offchain.bostr.online 43.6532 -79.3832
302 test.thedude.cloud 50.1109 8.68213
303 relay.mostr.pub:443 43.6532 -79.3832
304 relay.libernet.app:443 43.6532 -79.3832
305 nostr.mom 50.4754 12.3683
306 nostr.rtvslawenia.com:443 49.4543 11.0746
307 relay.plebeian.market 50.1109 8.68213
308 wot.makenomistakes.ca 43.7064 -79.3986
309 dm-test-strfry-discovery.samt.st 43.6532 -79.3832
310 ithurtswhenip.ee 51.5072 -0.127586
311 relay.staging.commonshub.brussels 49.4543 11.0746
312 relay.beginningend.com 35.2227 -97.4786
313 strfry.bonsai.com 39.0438 -77.4874
314 nostrelay.circum.space:443 52.6907 4.8181
315 relay.mostro.network 40.8302 -74.1299
316 dev-relay.nostreon.com 60.1699 24.9384
317 relay.fundstr.me 42.3601 -71.0589
318 relay.mmwaves.de:443 48.8575 2.35138
319 relay.nostu.be 40.4167 -3.70329
320 relay.nostx.io 43.6532 -79.3832
321 nostr.data.haus 50.4754 12.3683
322 relay.lightning.pub:443 39.0438 -77.4874
323 relay.nostr-check.me 43.6532 -79.3832
324 nostr.bitcoiner.social:443 47.6743 -117.112
325 ribo.nostria.app 43.6532 -79.3832
326 offchain.pub 39.1585 -94.5728
327 relay.layer.systems 49.0291 8.35695
328 nostr.dlcdevkit.com 40.0992 -83.1141
329 relay.angor.io:443 48.1046 11.6002
330 relay.mwaters.net 50.9871 2.12554
331 relay.nostriot.com:443 41.5695 -83.9786
332 nostrcity-club.fly.dev 37.7648 -122.432
333 nostr.notribe.net:443 40.8302 -74.1299
334 freelay.sovbit.host 60.1699 24.9384
335 syb.lol 43.6532 -79.3832
336 portal-relay.pareto.space 49.0291 8.35696
337 wot.codingarena.top 50.4754 12.3683
338 nostr-2.21crypto.ch 47.5356 8.73209
339 nostr.hifish.org:443 47.4244 8.57658
340 relay.zone667.com:443 60.1699 24.9384
341 relay.lab.rytswd.com 49.4543 11.0746
342 nas01xanthosnet.synology.me:7778 47.1285 8.74735
343 relay.primal.net 43.6532 -79.3832
344 relay.bitmacro.cloud 43.6532 -79.3832
345 chat-relay.zap-work.com:443 43.6532 -79.3832
346 relay.fountain.fm:443 43.6532 -79.3832
347 ribo.eu.nostria.app 43.6532 -79.3832
348 nexus.libernet.app:443 43.6532 -79.3832
349 nostr.sathoarder.com 48.5734 7.75211
350 nostr-pub.wellorder.net 45.5201 -122.99
351 nostr.snowbla.de 60.1699 24.9384
352 strfry.apps3.slidestr.net 40.4167 -3.70329
353 nostr.spicyz.io:443 43.6532 -79.3832
354 nostr.data.haus:443 50.4754 12.3683
355 nostr.dlcdevkit.com:443 40.0992 -83.1141
356 purplerelay.com:443 43.6532 -79.3832
357 nostr-relay.xbytez.io 50.6924 3.20113
358 relay.nostrmap.net:443 60.1699 24.9384
359 relay.ohstr.com 43.6532 -79.3832
360 r.0kb.io:443 32.789 -96.7989
361 uncouth-panning-paternal.ngrok-free.dev 40.0992 -83.1141
362 wot.sudocarlos.com 43.6532 -79.3832
363 relay.satmaxt.xyz 43.6532 -79.3832
364 ribo.us.nostria.app:443 43.6532 -79.3832
365 no.str.cr 10.6352 -85.4378
366 relay.notoshi.win 13.3622 100.983
367 nostr.spicyz.io 43.6532 -79.3832
368 relay.veganostr.com 60.1699 24.9384
369 r.0kb.io 32.789 -96.7989
370 relay-testnet.k8s.layer3.news:443 37.3387 -121.885
371 relay.cosmicbolt.net:443 37.3986 -121.964
372 nostr-relay.xbytez.io:443 50.6924 3.20113
373 relay.sharegap.net 43.6532 -79.3832
374 bcast.girino.org 43.6532 -79.3832
375 nostr2.girino.org:443 43.6532 -79.3832
376 relay.cypherflow.ai 48.8575 2.35138
377 nostr-relay.corb.net 38.8353 -104.822
378 relay.staging.plebeian.market 51.5072 -0.127586
379 relay.lanavault.space 60.1699 24.9384
380 relay.wisp.talk 49.4543 11.0746
381 relay.wisp.talk:443 49.4543 11.0746
382 no.str.cr:443 10.6352 -85.4378
383 nostr.hekster.org:443 37.3986 -121.964
384 nostr-01.yakihonne.com:443 1.32123 103.695
385 dm-test-strfry-generic.samt.st 43.6532 -79.3832
386 relay.guggero.org 46.5971 9.59652
387 nostr.twinkle.lol 51.902 7.6657
388 nostr.janx.com 43.6532 -79.3832
389 relay.nostrhub.fr 48.1045 11.6004
390 relay.nostriot.com 41.5695 -83.9786
391 nostr.hekster.org 37.3986 -121.964
392 fanfares.nostr1.com 40.7057 -74.0136
393 nostr-relay.cbrx.io 43.6532 -79.3832
394 relay.edufeed.org 49.4521 11.0767
395 relay.olas.app:443 60.1699 24.9384
396 armada.sharegap.net 43.6532 -79.3832
397 nostr.islandarea.net:443 35.4669 -97.6473
398 relay0.gfcom.info 13.8434 100.363
399 relay.chorus.community 48.5333 10.7
400 nittom.nostr1.com:443 40.7057 -74.0136
401 relay.staging.plebeian.market:443 51.5072 -0.127586
402 chat-relay.zap-work.com 43.6532 -79.3832
403 relay-rpi.edufeed.org:443 49.4521 11.0767
404 relay.lanavault.space:443 60.1699 24.9384
405 relay.binaryrobot.com:443 43.6532 -79.3832
406 nostr.mom:443 50.4754 12.3683
407 relay.bornheimer.app 51.5072 -0.127586
408 treuzkas.branruz.com 48.8575 2.35138
409 nostr-dev.wellorder.net 45.5201 -122.99
410 public.crostr.com:443 43.6532 -79.3832
411 relay.degmods.com 50.4754 12.3683
412 articles.layer3.news:443 37.3387 -121.885
413 nostr.azzamo.net:443 52.2633 21.0283
414 premium.primal.net 43.6532 -79.3832