mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-27 04:45:20 +00:00
Bridge dedup: content-derived stable mesh message ID (#1419)
* Bridge dedup keys on a content-derived stable mesh message ID Public mesh messages carry no message ID on the BLE wire, so every non-origin device minted a fresh UUID and the bridge's m-tag dedup only matched on the origin device: duplicate bridged rows, misattributed "across the bridge" counts, redundant downlink rebroadcasts, and an m-tag spoof vector (an attacker could claim a victim's message ID). Every device now derives the same stable ID from the signed wire fields (sender ID + ms timestamp + trimmed content, SHA256/32 hex) via the new MeshMessageIdentity — zero BLE wire change. The bridge event's m tag carries the origin coordinates ["m", senderIDHex, timestampMs] and receivers recompute the key from those plus the event's own content instead of trusting a claimed ID; old-format/absent tags fall back to the event ID as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix mixed-version message loss: m tag leads with the derived stable ID The previous layout (["m", senderIDHex, timestampMs]) broke v1.7.0 receivers: their parser takes m[1] unconditionally as the timeline dedup key whenever the tag has >= 2 elements, so every bridged message from a new-version sender keyed on the CONSTANT sender hex and inject-dedup dropped all but the first. The tag is now ["m", <derived stable ID>, senderIDHex, timestampMs]: old parsers get a per-message-unique m[1] (exactly today's semantics), while the new parser recomputes the ID from elements 2-3 plus the event's own content and never trusts element 1, keeping the recompute-don't-trust property. Also: - Soften the overstated security claim in MeshMessageIdentity and the BridgeService classify comment: forging a chosen ID onto different content is infeasible, but all three hash inputs are cleartext on the radio, so identical-content front-running by a radio-local attacker remains possible (no worse than the unbridged mesh). - Fix the stale archivedEchoKeys rationale: re-synced copies of others' messages now carry the derived stable ID (insert-by-ID catches them); the content key remains for echo--prefixed archive rows + self echoes. - Tests: old-parser semantics on the new tag (m[1] per-message-unique and equal to the derived ID) and a forged-m[1] event that cannot pre-poison a genuine message's dedup slot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
jack
Claude Opus 4.8
parent
dd6b624cae
commit
84d315e62d
@@ -264,22 +264,33 @@ struct NostrProtocol {
|
||||
|
||||
/// Create a mesh-bridge public message (kind 20000) for a geohash-cell
|
||||
/// rendezvous. The distinct `r` tag keeps bridge traffic out of geohash
|
||||
/// channel subscriptions (which filter on `#g`); `m` carries the original
|
||||
/// mesh message ID so receivers dedup the bridged copy against the radio
|
||||
/// copy by timeline ID.
|
||||
/// channel subscriptions (which filter on `#g`); `m` is
|
||||
/// `[stable ID, mesh sender ID, wire timestamp in ms]`. Element 1 is the
|
||||
/// content-stable mesh message ID (`MeshMessageIdentity`) for v1.7.0
|
||||
/// parsers, which key their dedup on `m[1]` unconditionally and need it
|
||||
/// per-message-unique; current parsers ignore it and recompute the ID
|
||||
/// from the origin coordinates (elements 2-3) plus the event's own
|
||||
/// content to dedup the bridged copy against the radio copy by
|
||||
/// timeline ID.
|
||||
static func createBridgeMeshEvent(
|
||||
content: String,
|
||||
cell: String,
|
||||
senderIdentity: NostrIdentity,
|
||||
nickname: String? = nil,
|
||||
meshMessageID: String? = nil
|
||||
meshSenderID: String? = nil,
|
||||
meshTimestampMs: UInt64? = nil
|
||||
) throws -> NostrEvent {
|
||||
var tags = [["r", cell]]
|
||||
if let nickname = nickname?.trimmedOrNilIfEmpty {
|
||||
tags.append(["n", nickname])
|
||||
}
|
||||
if let meshMessageID = meshMessageID?.trimmedOrNilIfEmpty {
|
||||
tags.append(["m", meshMessageID])
|
||||
if let meshSenderID = meshSenderID?.trimmedOrNilIfEmpty, let meshTimestampMs {
|
||||
let stableID = MeshMessageIdentity.stableID(
|
||||
senderIDHex: meshSenderID,
|
||||
timestampMs: meshTimestampMs,
|
||||
content: content
|
||||
)
|
||||
tags.append(["m", stableID, meshSenderID, String(meshTimestampMs)])
|
||||
}
|
||||
let event = NostrEvent(
|
||||
pubkey: senderIdentity.publicKeyHex,
|
||||
|
||||
Reference in New Issue
Block a user