Skip to content

Routing-Redesign Threat Model

Status: research note for the greenfield routing rip-and-replace. Non-normative until consolidated. This document threat-models a proposed async routing layer that, like MeshCore’s and chIRpChat’s current design, carries mutable, hop-by-hop routing state: relays appending themselves, SNR-based fast-responder suppression, hop-pruning decisions, and an independently-maintained asymmetric return path. It grounds every claim in chIRpChat’s shipped crypto and trust model (docs/IDENTITY.md, docs/SECURITY_AUDIT.md, docs/ROUTING.md, core/src/packet.cpp::canonical_image) and in the published mesh-routing-attack literature (cited [n], resolved in References).

It exists to be reconciled against the separate ASYNC routing design. The contract the consolidator checks that design against is the numbered DESIGN REQUIREMENTS FOR THE ROUTING TRACK (R1–R14) at the end.

The charter constraint is fixed and not up for negotiation here: chIRpChat is plaintext, signed/attested — security is verifiable identity + integrity, never confidentiality (docs/IDENTITY.md intro). This document never proposes encrypting a payload. It asks only: of the new routing fields, what must be authenticated, how, by which key, and at what byte/CPU cost — without breaking the fact that a path is intrinsically mutable in flight (the origin cannot pre-sign a path it does not yet know).


0. The one-paragraph framing (honor the maintainer’s worry)

Section titled “0. The one-paragraph framing (honor the maintainer’s worry)”

Today chIRpChat deliberately leaves the path block outside the canonical image (packet.cpp:217-218, docs/IDENTITY.md §7, SECURITY_AUDIT.md Finding 5): a path is “a hint, not security,” and the only harm a mutated path can do is cause a duplicate relay that the dedup ring absorbs. That concession is safe today because the path is inert — nothing downstream makes a trust decision from it; SRC/DST/payload are immutable and tagged, and delivery ultimately rides on router attestation (Tier-1), not on the path. The greenfield redesign changes the premise: it gives appended hops semantic weight — suppression silences honest relays, pruning deletes them, and an async return path steers replies. A field that decides who relays and where replies go is no longer “a hint.” So the central finding of this model is:

The moment a mutable routing field influences a forwarding, suppression, pruning, or return-path decision, leaving it unauthenticated converts “wasted airtime” into selective denial-of-service and reply hijack. The redesign must either (a) authenticate that field per-hop, or (b) make every honest node’s behavior provably independent of the unauthenticated field (ignore-by-default with a forward-progress fallback). There is no safe middle where honest nodes trust an unauthenticated suppression/prune/return signal.


# Asset Why it matters
A1 Message delivery / reachability The product. Selective non-delivery (grayhole) is the quietest, highest-value attack.
A2 Airtime / duty-cycle budget The scarcest resource on LoRa. Every forced TX is regulatory + battery cost; the anchor lane is the chokepoint (docs/ROADMAP.md).
A3 Origin authenticity of chat Already covered by Tiers 1–3 (docs/IDENTITY.md §4). The redesign must not weaken it, e.g. by letting routing manipulation strip a TAGGED/SIGNED_FULL bit.
A4 Return-path integrity New. An async, independently-maintained return path is a named place where replies are steered. Corrupting it redirects a victim’s inbound DMs/ACKs.
A5 Router-attested presence/sequence Tier-1 trust anchor. Routing must not give an attacker a way to forge or suppress an attestation.
A6 Node liveness / battery A node coerced into relaying or retransmitting drains and may drop offline — a slow DoS.

Non-assets (by charter): payload confidentiality (everything is plaintext — docs/IDENTITY.md intro) and traffic-flow privacy (anyone on the band sees who is talking). Attacks whose only outcome is “the attacker learns who relayed what” are out of scope: that information is already public on the air.


LoRa physics fixes the baseline: the band is open. Anyone with a compatible radio can transmit any bytes at any time, can choose when to transmit (so can win or lose a contention window deliberately), and can hear everything in range. There is no link-layer secrecy and no transmit authorization. Every class below inherits these.

Class Capability Cannot do (given chIRpChat crypto)
P — Passive eavesdropper Reads all frames in RF range; learns topology, nicks, who relays. Nothing active. (Confidentiality already conceded; this class is mostly out of scope.)
I — Active on-air injector TX arbitrary frames, choose timing, replay captured frames, spoof any UID in any unauthenticated field. Forge a Tag8 without the SK/CTK; forge SIGNED_FULL; produce a valid peer-link MAC (SECURITY_AUDIT.md Findings 6–7).
MR — Malicious relay Everything I can, plus it is on a path honest nodes use: it can drop (blackhole), selectively drop (grayhole), delay, duplicate, reorder, and append/rewrite any unauthenticated routing field it forwards. Read message content it could not already read on the air (no gain — plaintext); forge a router attestation; forge a client Tag8.
MC — Malicious registered client Holds a valid IK + a real SK with its home router. Can emit validly-tagged uplink, validly-signed CHECKPOINTs for its own UID, and abuse any client-trusted routing field. Impersonate another UID’s chat (Tier-2/3 tripwires); forge another client’s SK-tag; act as a router (no RK).
RR — Rogue / compromised router Holds a valid RK and a pinned federation seat. Can mis-sequence, mis-attest, mint LANEGRANTs, lie in RTRSYNC within its own scope, and forge Tier-1 attestation for traffic it sequences. Forge another router’s RK-signed frames; be trusted by peers that did not --peer-pin it (SECURITY_AUDIT.md Finding 7). A rogue router is inside the Tier-1 trust boundary — this is the system’s load-bearing trust assumption.
S — Sybil swarm One operator, N forged unauthenticated identities (cheap: a UID is just 8 bytes an attacker picks; only registered identities cost a signed HELLO). Floods routing control, fabricates fake relays/hops, fakes contention. Mint N registered clients cheaply — registration is SIGNED_FULL(IK) and routers detect UID collisions and hold full keys (docs/IDENTITY.md §1–2). Sybil is cheap at the unauthenticated relay layer, expensive at the registered/attested layer. This asymmetry is the whole defense.

The trust spine (state it plainly, because the whole design rests on it): routers are real trust anchors — pinned RK keys, a mutually-authenticated federation roster, per-frame MAC + monotone sequence on backbone links (docs/ROUTING.md §backbone, SECURITY_AUDIT.md Finding 7). Relays and unregistered clients are not anchors — they are opportunistic and, today, unauthenticated. The viable architecture this model endorses is therefore: make routers the routing authority; treat unauthenticated relay manipulation as bounded to wasted airtime by TTL + dedup + duty-cycle — and design every new routing primitive so that bound actually holds. Where the redesign cannot keep manipulation inside “wasted airtime,” it must add per-hop authentication.


3. Attack catalogue against the NEW routing primitives

Section titled “3. Attack catalogue against the NEW routing primitives”

For each: mechanism, precondition (which adversary class), blast radius, and whether chIRpChat’s existing mitigations already bound it. Existing mitigations in scope: TTL ≤ hops (4-bit field, ≤15, drop-at-zero, packet.cpp:215, PROTOCOL.md §Common header); the dedup ring (SipHash(kDedupKey, SRC‖MSGID), 512/8192 entries, packet.cpp:57-62); per-lane duty-cycle token bucket + CAD + jitter (docs/RADIO.md §Anti-congestion); router attestation (Tier-1); federation roster + per-frame MAC (SECURITY_AUDIT.md Finding 7); canonical-image coverage of the immutable header/addressing/payload and the security flag bits (packet.cpp:213-218).

  • Mechanism. A relay (MR) or injector (I) appends a UID byte that is not its own, appends a fabricated extra hop (Sybil ghost relay), or rewrites a prior hop. In the current design this is harmless (Finding 5). In the redesign, whoever the path names may be trusted for return-path construction or spared from pruning, so a forged hop launders a Sybil ghost into a “known relay.”
  • Precondition. I (on-air) or MR (on-path). No keys needed — path is unsigned today.
  • Blast radius. Today: one duplicate relay, dedup-absorbed. Redesign, if the path feeds pruning/return logic: attacker inserts itself (or a ghost) into the trusted topology → enables 3.5 (return-path hijack) and 3.4 (prune honest relays). This is the hinge attack that makes the others possible.
  • Existing mitigation sufficient? Only for the inert-path use. TTL+dedup bound the airtime, not the semantic corruption. If any downstream logic trusts the path, existing mitigations do not bound it. → needs per-hop attribution (§4, R2).

3.2 Fast-responder suppression abuse — the selective blackhole

Section titled “3.2 Fast-responder suppression abuse — the selective blackhole”
  • Mechanism. The redesign suppresses redundant relays: a node that hears a “good enough” forward (great SNR / first responder) stays quiet to save airtime — exactly Meshtastic’s SNR-weighted contention + own-rebroadcast suppression [6], and the structural target of the rushing attack [15][16]. An attacker emits a forged or replayed “I’ve got it, everyone else stand down” suppression signal (or simply rushes to be first), silencing the honest relays that would have carried the frame — then drops it (blackhole) or forwards a selected subset (grayhole) [11][12][13].
  • Precondition. I or MR. The attacker only needs to be heard first and to be believed. Rushing needs no keys at all — it is a timing game.
  • Blast radius. Selective, silent non-delivery for every flow whose only live relay the attacker suppressed. This is the quietest high-value attack: delivery just stops for chosen victims, with no anomaly an honest node can see, because the honest node was told to be quiet and complied. Couples with 3.6 (wormhole) to win the race over arbitrary distance.
  • Existing mitigation sufficient? No. Dedup/TTL/duty-cycle are about too much airtime; suppression is an attack that produces too little. Nothing in the current stack detects a missing relay. → This is the scariest attack in this model (see §5). Mitigation is behavioral, not crypto: R1 (suppression must be ignorable — honest node relays anyway if it hears no authenticated forward-progress within T).

3.3 SNR spoofing — winning or losing the contention window

Section titled “3.3 SNR spoofing — winning or losing the contention window”
  • Mechanism. SNR/RSSI are receiver-measured and self-reported — there is no authenticated way to prove a link quality. An attacker (I/MR) reports a fabricated great SNR to win the fast-responder slot (→ 3.2), or fabricates a terrible SNR for an honest relay (where the protocol carries peer-SNR, e.g. to drive promotion/pruning) to get that honest relay demoted or pruned. chIRpChat already feeds measured uplink SNR into client promotion (Node::record_client_uplink_snr, docs/RADIO.md §Lane promotion); the redesign extends SNR’s blast radius into path decisions.
  • Precondition. I/MR. SNR is unauthenticated by physics — a reported SNR is just a number; a measured SNR can itself be gamed by a colluding loud TX.
  • Blast radius. Bounded where SNR only steers self policy (current promotion: a lie about SNR only moves the liar’s own lane — SECURITY_AUDIT.md notes promotion is advisory and gated by lane retune on). Unbounded where SNR steers other nodes’ relay/prune decisions — then it is the input to 3.2 and 3.4.
  • Existing mitigation sufficient? Partially. The promotion path already treats SNR as self-affecting advisory only, which is the correct pattern. The redesign must keep that invariant: a reported SNR may influence only the reporter’s own behavior, never another node’s suppress/prune decision (R3).

3.4 Hop-pruning abuse — pruning honest relays / inducing loops

Section titled “3.4 Hop-pruning abuse — pruning honest relays / inducing loops”
  • Mechanism. The redesign prunes redundant hops (MeshCore removes itself from the path when it is no longer the next hop). An attacker forges a prune that removes an honest relay from a working path (→ blackhole the next frame that needs it), or injects a path that, when others “optimize” against it, forms a loop or a black hole region [11][14]. Route-poisoning: feed false “shorter/ better” path state so honest nodes converge on a path through the attacker.
  • Precondition. I/MR with an unauthenticated prune/path field.
  • Blast radius. Loss of a working route (grayhole), or airtime burn from loop-induced retransmission until TTL drains. Loops are bounded by TTL≤15 + dedup (a looped frame re-presents the same SRC‖MSGID and is dropped) — so the loop variant is genuinely airtime-only. The prune-honest-relay variant is not airtime-only: it removes delivery capacity.
  • Existing mitigation sufficient? For loops: yes (TTL+dedup bound it to wasted airtime — this is the one prune sub-case that stays “just airtime”). For pruning honest relays: no — same gap as 3.2. A prune decision must not trust an unauthenticated field (R2, R4).

3.5 Return-path hijack — redirect replies via the attacker

Section titled “3.5 Return-path hijack — redirect replies via the attacker”
  • Mechanism. The redesign maintains an independent asymmetric return path (the doc explicitly worries about this surface, and rightly — chIRpChat already keeps uplink/downlink paths independent, docs/ROUTING.md §uplink). If the return path is built from the unauthenticated forward path (relays append, recipient reverses — exactly today’s DM ACK reverse-path, PROTOCOL.md §Path block), then an attacker who poisoned the forward path (3.1) owns the reverse path: every ACK / reply / inbound DM to the victim is routed through the attacker, who can drop, delay, or grayhole it. Because the return path is async and independently maintained, the corruption persists across many messages, not just one — it is sticky state, which is far worse than a per-frame path hint.
  • Precondition. I/MR able to be on, or to forge, the forward path during the discovery that seeds the return path.
  • Blast radius. Durable, directional grayhole on the victim’s inbound traffic; combined with plaintext, the attacker also gets a clean tap on all replies (no new confidentiality loss — already public — but a perfect drop/delay position). This is the highest-leverage new surface because the corruption is stateful and sticky.
  • Existing mitigation sufficient? No. The current reverse-path-from- unsigned-forward-path works only because today the reverse path is also just a hint and ACK loss simply triggers re-discovery (PROTOCOL.md §ACK policy). Once the return path carries weight, its seed must be authenticated. → R5, R7.
  • Mechanism. Two colluding attackers tunnel frames between distant points (out-of-band or on a fast private link), making a far node appear one hop away [9][10]. They thereby win every contention/suppression race (3.2) and look like the best path (3.4/3.5) over arbitrary distance, then blackhole/grayhole. Rushing [15][16] is the degenerate single-node wormhole: just be first.
  • Precondition. Two colluding I/MR (wormhole) or one fast I (rushing). No keys — wormhole tunnels valid frames; it attacks the topology’s belief, not the crypto.
  • Blast radius. Global: a wormhole can dominate route selection across the whole RF neighborhood it bridges. This is the attack the academic literature considers the hardest because it defeats crypto-only defenses [9][15] (signing a hop doesn’t help if the hop is genuinely valid but tunneled).
  • Existing mitigation sufficient? No, and per-hop signing alone won’t fix it. The only real defenses are physical/temporal (the redesign cannot assume tight time sync — docs/ROADMAP.md flags clock-skew). The practical containment in chIRpChat’s model: routers are the authority, so a wormhole can win a relay race but cannot forge the router attestation a frame needs to be sequenced/delivered — it can delay/drop but not forge delivery. R1’s ignore-suppression fallback also blunts the rushing variant (an honest relay that hears no authenticated progress relays anyway). → R1, R6.
  • Mechanism. One operator spawns N fake relay UIDs (free — a UID is 8 attacker-chosen bytes; only registration costs a signed HELLO, docs/IDENTITY.md §1). The swarm fabricates path entries, fakes contention, manufactures fake “diverse” relays so multipath defenses (the classic grayhole countermeasure [11][12]) all resolve to the same attacker, and floods routing control to exhaust airtime [17].
  • Precondition. I. Trivial at the unauthenticated relay layer.
  • Blast radius. Defeats redundancy-based defenses (all “independent” paths are one attacker); airtime DoS bounded by per-node rate-limit + duty cycle but aggregate swarm airtime is not bounded by any single node’s budget.
  • Existing mitigation sufficient? Partially. Discovery floods are TTL≤3 + per-node rate-limited (docs/ROUTING.md §“does NOT exist”), which bounds per-node amplification. But Sybil diversity is not countered. The structural answer is the trust spine: prefer router-attested forwarding over relay- diverse forwarding — do not let path diversity substitute for path authority (R8). A Sybil ghost that cannot produce a per-hop authenticator (R2) or a router attestation cannot be trusted, only heard.
  • Mechanism. Capture and re-inject a valid routing-control frame (suppression signal, grant, prune, return-path update) to re-trigger its effect later. Direct analogue of SECURITY_AUDIT.md Finding 1 (LANEGRANT replay): a valid-but-stale control frame re-applied out of its intended window.
  • Precondition. P→I (capture then re-TX). No key needed — replays a frame that was valid.
  • Blast radius. Re-arms a stale routing decision (e.g. re-suppress a relay, re-pin a stale return path) past the issuer’s intent.
  • Existing mitigation sufficient? Only if the control frame is deduped / freshness-bound. Finding 1 showed LANEGRANT bypassed the (SRC,MSGID) dedup in firmware and was replayable; the fix routed it through dedup and added a freshness option (boot_id/nonce). Every new routing-control frame inherits this requirement or it is replayable by construction. → R6.
  • Mechanism. Coerce honest nodes into TX: forge ACK-requests, induce retransmission via selective drop (3.2/3.4), or flood relayable frames (3.7) to drain battery and burn duty-cycle until honest nodes self-throttle (the busy-bit backpressure, docs/RADIO.md) or go offline.
  • Precondition. I/S.
  • Blast radius. Degraded availability; battery exhaustion of victims.
  • Existing mitigation sufficient? Mostly. Per-lane token bucket, CAD, jitter, TTL, dedup, and the busy-bit collectively bound per-node forced airtime. The residual is aggregate Sybil airtime (3.7) and the fact that suppression (3.2) causes retransmission storms a single node can’t see. → R1, R8, plus accepted residual (§6).

The intrinsic tension, stated precisely: the origin cannot sign the path, because the path does not exist when the origin signs. A path is discovered in flight. Any design that demands an origin signature over the full path is wrong by construction. So authentication of mutable routing fields must be per-hop and incremental, or the field must be demoted to ignorable.

For each routing field, classify into: (a) origin-signed; (b) per-hop signed/MACed (each relay authenticates its own appended hop — onion / chain-of-signatures style); (c) authenticated only between authenticated routers; (d) safe to leave unsigned because manipulation is provably bounded to wasted airtime.

4.1 Cost primitives (the budget every choice spends)

Section titled “4.1 Cost primitives (the budget every choice spends)”
Primitive On-air bytes ESP32 (240 MHz) nRF52 (64 MHz) Notes
Ed25519 sign 64 B sig (+1 keyhint) ~few ms ~tens of ms Far too heavy per hop per frame. Reserved for checkpoints/grants/registration (docs/IDENTITY.md §4.3).
Ed25519 verify slower than sign slower than sign A relay verifying every prior hop’s Ed25519 is O(hops) verifies per frame — infeasible on the radio path.
SipHash-2-4 MAC (Tag8) 8 B sub-µs–low-µs/frame low-µs/frame The existing cheap authenticator (tag8, verify_tag, constant-time after Finding 4). The only per-hop-affordable option.
A truncated per-hop MAC 2–8 B/hop one SipHash/hop one SipHash/hop The realistic per-hop attribution primitive. 8 B/hop is expensive on a 255-B frame; 2–4 B is a tripwire-grade tradeoff (cf. MeshCore’s 2-byte content authenticator [9]).

Airtime reminder: every byte costs ~33 ms at SF12/125 kHz on the anchor lane (COMPARISON_THESIS.md §2). A 64-byte per-hop signature on a multi-hop path is not merely CPU-infeasible, it is airtime-fatal. This rules out chain-of-Ed25519 immediately; per-hop authentication, if used, must be a short MAC, and short MACs are tripwires not proofs — consistent with the Tier-2 philosophy (docs/IDENTITY.md §4.2).

Routing field Verdict Crypto / behavior Who signs / over what Who verifies, when
Immutable header / addressing / payload / security flag bits (a) origin-signed — already is Existing Tag8 / SIGNED_FULL over the canonical image; security flags (TAGGED, SIGNED_FULL, ACK_REQ, PRIO, …) stay covered (packet.cpp:213-218, Finding 5). Origin (SK/CTK/IK). Receiver / router. Redesign must not move any of these into a mutable region (R9).
Appended hop (a relay’s own UID in the path / ERB.TRAIL[]) (d) safe unsigned — demoted to a non-trust diagnostic hint (Option B, ratified §4.4) The routing track demotes ERB.TRAIL[] to a diagnostic / unverified candidate hint that feeds no trust decision: trust comes from router-observed delivery, never from the appended hop bytes. So no per-hop MAC is required — the hop is (d), a pure hint whose worst-case manipulation (forged/extra/rewritten UID) is one mis-ordered already-validated candidate or one wasted diagnostic field, dedup/airtime-bounded (R11/R13). The per-hop SipHash-MAC option (b) is documented below but not adopted: it is impossible for keyless foreign relays (they share no secret with the router to MAC under), so it cannot authenticate the very relays whose TRAIL entries are untrusted — see §4.4. n/a (no per-hop MAC adopted). n/a — trust is established by the router’s observed delivery gate, not by verifying the hop.
Suppression / fast-responder signal (d) unsigned BUT ignorable Do not authenticate it (it would still be wormhole/rushing-defeatable, §3.6). Instead: an honest node MAY use it to save airtime but MUST relay anyway if it has heard no authenticated forward-progress (a router attestation, or an authenticated downstream hop) within timeout T. Suppression can only ever delay honest relaying, never cancel it. n/a (signal is advisory). n/a — the receiver’s policy is the defense, not a signature (R1).
Reported SNR / link quality (d) self-affecting only A reported SNR influences only the reporter’s own lane/relay choice (the existing promotion invariant). It is never an input to another node’s suppress/prune decision. Measured SNR may rank the measurer’s own candidates but is advisory. n/a. n/a — bounded by the self-affecting rule (R3).
Hop-prune decision (b) or (c) A prune that removes a hop others rely on must be authenticated by the pruner (per-hop MAC, b) or be a router-only decision (c). An unauthenticated prune may only ever remove the pruner’s own appended hop (self-prune, MeshCore-style — safe, it only takes the attacker off its own path). Pruner (its own hop) or router (RK / federation MAC). Consumer of the path / peer router. R4.
Async return path (c) satisfied via router-observed delivery (Option B, ratified §4.4); (d)-from-TRAIL forbidden The return path’s seed is authenticated by the recipient’s router (a trust anchor) — but, under Option B, the authenticating fact is the router’s own observed delivery (a router-measured reception attributable to a relay), which is a Tier-1, router-local authority, not a wire claim. The router constructs/owns the Return Descriptor and admits a relay to RELAY_VIA only after it has observed that relay deliver a frame; first contact is router-measured BROADCAST or UNKNOWN. This satisfies “the seed must be authenticated, never reversed from an unauthenticated forward path” without per-hop MACs, because no TRAIL/appended-hop byte ever seeds the path. Per-hop MACs (b) remain a documented alternative but are not adopted (§4.4). Never pin a return path from an unauthenticated reverse of a discovery path or from a TRAIL byte. R5, R7.
Routing-control frames (grant, prune-broadcast, return-path update, suppression-with-state) (a)+freshness If router→client: Tag8 under SK (only the issuing router holds it) plus anti-replay (dedup and a freshness binding — boot_id or a monotone per-session counter), exactly the LANEGRANT Finding 1 fix. If router↔router: RK-pinned per-frame MAC + monotone sequence (Finding 7). Issuing router (SK or RK). Client (checks src == issuing router, Finding 2) or peer router. R6.
TTL / hops (d) — stays unsigned, already correct Must change in flight; bounded by the 4-bit field + drop-at-zero. A relay mutating TTL cannot affect any tag (packet.cpp:215, Finding 5). n/a. n/a. No change.

4.3 How per-hop attribution actually works (resolving the tension)

Section titled “4.3 How per-hop attribution actually works (resolving the tension)”

The origin cannot sign the path, so attribution is built incrementally by the relays themselves:

  1. Each relay, when it appends itself, computes m = SipHash(K_relay, SRC ‖ MSGID ‖ prev_hop ‖ my_id ‖ monotone_ctr), truncated to the per-hop tag width (2–8 B), and appends (my_id, ctr, m). Binding SRC‖MSGID stops the hop being cut-and-pasted onto a different frame; binding prev_hop chains the hops so a hop can’t be reordered; monotone_ctr (per relay) stops replay of an old appended hop.
  2. Who holds K_relay, and who can verify? Three deployable answers, in increasing strength:
    • Router-mediated (preferred, fits the trust spine). Relays that have registered with a router share an SK with it. The relay MACs its hop under that SK; the router (which holds the SK) verifies the hop and re-attests the validated path downward. Verification is concentrated at the trust anchor; clients never verify relay MACs. This is the cleanest fit and means only routers ever trust a path — relays and clients treat it as a hint.
    • Pairwise-known relays. Where two relays/endpoints already share a key (rare on first contact), the MAC is directly verifiable. Limited reach.
    • Published-key tripwire (Tier-2 analogue). A relay MACs under a published key (like CTK): anyone can check, but a determined attacker who fetches the key can forge — a tripwire, not a proof (docs/IDENTITY.md §4.2). Useful to make Sybil ghost hops (which have no key at all) distinguishable from real ones, at tripwire strength only.
  3. The decisive consequence: because verification realistically lives at the router, the redesign should make the router the path authority. A relay appends a hint; the router validates and re-attests; clients trust the router’s attestation, never the raw path. This is the same Tier-1 move that already carries chat authenticity (docs/IDENTITY.md §4.1) — extended from message origin to path validity. It is also the only design where per-hop cost stays affordable (one SipHash per hop, verified once at the router, not O(hops²) across the network).

What this buys, honestly: per-hop MACs make a hop attributable and non-transplantable, which kills 3.1 (forged hops), most of 3.4 (forged prune of others’ hops), and 3.5 (return-path seeded from forged hops). They do not stop wormhole/rushing (3.6) — a tunneled hop is genuinely valid — so 3.6 stays a behavioral problem (R1) and a router-authority problem (the wormhole can win a relay race but cannot forge the router attestation a frame needs to be delivered).

4.4 RATIFICATION — Option B: observed-delivery-only gating (no per-hop MAC)

Section titled “4.4 RATIFICATION — Option B: observed-delivery-only gating (no per-hop MAC)”

Decision (binds the consolidator’s Issue 2, the → BOTH fork). The two tracks resolve Issue 2 as Option B: the routing track demotes ERB.TRAIL[] / the appended hop to a pure diagnostic hint and gates every return/RD/return-candidate trust decision on router-observed delivery. The per-hop SipHash-MAC construction of §4.3 (Option A) is documented above but NOT adopted. Rationale, on the record:

  1. Option A’s per-hop MAC is impossible for keyless foreign relays. A foreign relay willing to help but not registered with the converging router shares no secret with that router to MAC under (§4.3 lists only router-mediated SK, pairwise-known keys, or a published tripwire key — none exists for an arbitrary foreign relay on first contact). So a per-hop MAC cannot authenticate the very relays whose TRAIL entries are untrusted; it would secure only the hops that were already the trustworthy (registered) ones, and leave the Sybil-ghost case — the only case that matters for 3.1 / 3.5 / 3.7 — exactly as forgeable as before. Adding the MAC pays bytes + SipHash/hop for no coverage of the threat it nominally addresses.

  2. The routing track’s own [SEC-H5] already mandates observed-delivery. [SEC-H5] states a router MUST treat a TRAIL-derived relay as “a candidate validated by observed delivery success, never an authenticated route.” The conflict the consolidator flagged was therefore internal inconsistency: §4.2/§7.2 of the routing design consumed TRAIL as if trusted while [SEC-H5] forbade exactly that. Option B makes the routing design self-consistent with its own security assumption — no wire change, no canonical-image re-audit, no per-hop MAC.

Ratification. Option B (observed-delivery-only gating + TRAIL[] demoted to a diagnostic hint) SATISFIES R2, R5, R7, R8, and R10 without per-hop MACs, because no routing trust decision consumes an unauthenticated appended hop:

  • R2 (a trusted appended hop needs a per-hop MAC) — satisfied vacuously: no appended hop is trusted. TRAIL is a (d)-class hint; its worst case is a mis-ordered already-validated candidate or one wasted diagnostic field (R11/R13-bounded). There is no hop to authenticate because no hop is consumed as trust.
  • R5 / R7 (return seed authenticated; poisoned forward cannot seed a trusted return) — satisfied by the observed-delivery gate and the first-contact RD rule: the RD enters RELAY_VIA only after the recipient’s router measures a delivery attributable to a relay; first contact is router-measured BROADCAST or UNKNOWN, never TRAIL-seeded RELAY_VIA. The seed’s authority is the router’s own measurement (Tier-1, router-local), structurally independent of the forward path — so a poisoned forward discovery cannot seed a trusted return.
  • R8 (authority beats diversity) — satisfied: neither TRAIL presence nor the count of distinct relay UIDs raises a candidate’s selection priority; only router-observed delivery promotes a candidate, so a Sybil swarm of ghost TRAIL entries confers no trust.
  • R10 (router-mediated, affordable verification) — satisfied more cheaply than Option A: there is nothing to verify per-hop at all. The router’s delivery observation is an O(1) local measurement, not an O(hops) MAC chain; the design stays well inside the ESP32/nRF52 budget.

Residual, stated honestly: Option B pays a slower / less-targeted first return (the router must observe a delivery before it can use RELAY_VIA; until then it falls back to BROADCAST or re-discovers via UNKNOWN) instead of Option A’s bytes-plus-SipHash-per-hop. That latency is bounded and self-healing (R14: an unconfirmed candidate decays to UNKNOWN), and it is strictly preferable to either a return hijack (R5) or an authenticator that cannot cover the foreign relays it targets. No other requirement is weakened: R1, R3, R4, R6, R9, R11–R14 stand exactly as written; this ratification touches only the appended-hop / return-seed rows.


Fast-responder suppression abuse as a selective grayhole (§3.2), amplified by wormhole/rushing (§3.6) and made sticky by return-path hijack (§3.5).

Why it is the worst:

  • It produces too little traffic, and every existing chIRpChat mitigation guards against too much. TTL, dedup, duty-cycle, CAD, the busy bit — all bound excess airtime. Nothing in the stack notices a relay that was told to stay quiet and did. The attack’s signature is silence, which is invisible.
  • It needs no keys. Rushing is pure timing; a forged “stand down” rides on the very unauthenticated suppression field the redesign introduces. The attacker never has to break any chIRpChat crypto.
  • It is selective and deniable. The attacker silences honest relays only for chosen victims/flows, drops or forwards-a-subset (grayhole), and the network looks healthy to everyone else. Classic grayhole evades the blackhole detectors that watch for total drop [11][12][13].
  • It compounds. A wormhole lets the attacker win the suppression race over arbitrary distance (3.6); if the same poisoned discovery seeds the async return path (3.5), the victim’s inbound traffic is now durably routed through the attacker too — one race converted into sticky, bidirectional, selective denial.

Why chIRpChat survives it anyway, if the requirements hold: the redesign’s saving grace is the trust spine. A suppressor can stop relaying but cannot forge the router attestation a frame needs to be sequenced and delivered. If R1 holds (honest nodes relay regardless of unauthenticated suppression when they hear no authenticated forward-progress within T), suppression can only delay delivery, not cancel it — degrading the attack from “selective blackhole” to “adds latency and wastes the attacker’s airtime.” R1 is the load-bearing requirement of this entire document.


Consistent with the charter (confidentiality already conceded; Tier-2 tags are tripwires, not proofs — docs/IDENTITY.md §4.2):

  • R-residual-1: Confidentiality and traffic-flow privacy. Out of scope by charter. An on-path attacker reads all relayed content and learns who relays for whom. Accepted.
  • R-residual-2: Wormhole/rushing cannot be fully prevented, only contained. Without tight time sync (which docs/ROADMAP.md says chIRpChat must not assume) and without ranging hardware, a wormhole that tunnels valid frames is undetectable at the crypto layer [9][15]. We accept that a wormhole can delay and win relay races, and rely on router-authority + R1 to prevent it from forging delivery. Accepted, bounded to latency/airtime.
  • R-residual-3: Sybil at the unauthenticated relay layer is free. A UID is 8 attacker-chosen bytes; ghost relays are cheap. We accept that the relay layer is Sybil-rich and refuse to let relay diversity imply trust (R8); trust comes only from registration/attestation, which is not cheap to Sybil (docs/IDENTITY.md §1–2). Accepted.
  • R-residual-4: Per-hop MACs are short, hence tripwire-grade. An attacker willing to brute-force a 2–4 byte per-hop MAC, or who fetches a published tripwire key, can forge a hop — same honest tradeoff as Tier-2. Per-hop authentication raises the bar and makes forgery attributable/observable; it is not a proof. Accepted, documented.
  • R-residual-5: A rogue/compromised router (RR) is inside the trust boundary. By design, routers are the authority; a compromised RK can mis-route within its scope. Mitigation is the federation roster (peers it isn’t pinned to don’t trust it) and operational (key revocation, docs/IDENTITY.md §5). This is the same load-bearing assumption Tier-1 already makes; the routing redesign does not worsen it but must not deepen a single router’s blast radius (R12).
  • R-residual-6: Suppression-induced latency. Honoring R1 means honest nodes wait T before relaying through suppression they can’t authenticate, adding latency under attack. Accepted as strictly better than selective non-delivery.

7. DESIGN REQUIREMENTS FOR THE ROUTING TRACK

Section titled “7. DESIGN REQUIREMENTS FOR THE ROUTING TRACK”

The contract. The consolidator checks the ASYNC routing design against each. A conflict is any place the routing design relies on an unauthenticated field in a way these forbid.

R1 — Suppression must be ignorable. An honest node MAY use a fast-responder/suppression signal only to defer its own relay. It MUST still relay if it has heard no authenticated forward-progress (a router attestation, or a per-hop-authenticated downstream hop, for the frame) within a bounded timeout T. No unauthenticated signal may cancel an honest relay — only delay it. (Defeats 3.2 selective blackhole and blunts 3.6 rushing. Load- bearing — see §5.)

R2 — A trusted appended hop must be per-hop authenticated. If any downstream logic (prune, return-path, suppression, route ranking) reads a hop as more than a pure airtime hint, that hop MUST carry a per-hop MAC over SRC‖MSGID‖prev_hop‖my_id‖monotone_ctr (§4.3). A hop without a verifiable per-hop authenticator MUST be treated as an unverified hint and MUST NOT feed any trust decision. (Defeats 3.1, foundation for 3.4/3.5 defenses.)

R3 — Reported SNR/link-quality is self-affecting only. A reported or measured SNR may influence only the reporting/measuring node’s own lane/relay choice. It MUST NEVER be an input to another node’s suppress, prune, or route-selection decision. (Defeats 3.3.)

R4 — Prune authority. An unauthenticated prune may remove only the pruner’s own appended hop (self-prune). Pruning a hop other nodes rely on MUST be authenticated by the pruner (R2) or be a router-only decision under RK / federation-MAC authority. (Defeats 3.4 prune-honest-relay.)

R5 — The async return path MUST NOT be pinned from an unauthenticated reverse of a discovery path. Its seed MUST be authenticated: router-attested (Tier-1), RK-context, or built only from per-hop-authenticated hops (R2). The recipient pins the return path only after validating that seed — mirroring “pin the uplink path only after checking the signed WELCOME” (docs/IDENTITY.md §2). (Defeats 3.5, the highest-leverage sticky attack.)

R6 — Every routing-control frame MUST be freshness-bound and replay-proof. It MUST pass the (SRC,MSGID) dedup gate (no firmware/path that bypasses dedup, per Finding 1) AND carry a freshness binding (router boot_id or a monotone per-session counter). Router→client control is Tag8 under SK with src == issuing-router enforced (Findings 1, 2); router↔router control rides the RK-pinned per-frame MAC + monotone sequence (Finding 7). (Defeats 3.8.)

R7 — Return-path and forward-path remain independently authenticated. Corrupting one MUST NOT corrupt the other; a poisoned forward discovery MUST NOT be able to seed a trusted return path (this is R5 stated as an independence invariant — chIRpChat already keeps the two paths independent, docs/ROUTING.md §uplink). (Limits 3.5 blast radius.)

R8 — Path authority beats path diversity. No multipath/redundancy defense may treat the count of distinct relay UIDs as evidence of trust, because relay UIDs are free to Sybil. Trust derives only from registration/attestation (router-mediated per-hop MAC or Tier-1), never from “many different-looking hops.” (Defeats 3.7 Sybil diversity.)

R9 — No security-relevant state in a mutable region. No routing field that influences a trust decision may live in a byte range excluded from the canonical image (the kMutableMask / path region). Reusing a masked flag bit or the path block for trusted state re-opens Finding 5. Any such move requires a re-audit of canonical_image and a lrc-wire-change version event. (Protects A3, the existing origin-authenticity guarantee.)

R10 — Router-mediated verification, not network-wide verification. Per-hop authentication MUST be verifiable at a trust anchor (the router) in O(hops) per frame, verified once, then re-attested downward — never O(hops) Ed25519/SipHash verifies repeated at every node (O(hops²) network cost). Per- hop authenticators are SipHash-class (≤8 B), never per-hop Ed25519 (64 B is airtime-fatal, §4.1). (Keeps the design implementable on ESP32/nRF52.)

R11 — Loops and excess relaying stay airtime-bounded. Any new path/prune primitive MUST preserve TTL≤15 + drop-at-zero + (SRC,MSGID) dedup as a hard ceiling so the worst an unauthenticated manipulation can do (where R1–R10 permit any) is bounded wasted airtime, never unbounded amplification or a persistent loop. (Preserves the “manipulation = wasted airtime” bound for the (d) fields.)

R12 — No new single-router blast-radius amplification. The redesign MUST NOT let a single rogue/compromised router (RR) steer routing for scopes/UIDs it does not own beyond what Tier-1 already concedes. Return-path and prune authority for a UID belong to that UID’s own access router, gated by the federation roster. (Bounds R-residual-5.)

R13 — Unauthenticated fields are advisory by default (fail-closed to hint). Any routing field whose authentication is absent, malformed, or unverifiable MUST degrade to “ignorable hint,” never to “trusted by default.” The decode/parse path MUST be length-exact and reject malformed routing blocks without trusting partial state (the existing strict-decode invariant, Finding 10). (Generalizes the safe-default that keeps every (d) field harmless.)

R14 — Suppression/return state MUST be expirable and bounded. Any sticky routing state (return paths, suppression memory, learned hops) MUST carry a TTL and a bounded store, so a one-shot poisoning (3.5/3.8) self-heals on expiry and cannot pin a victim indefinitely — the same discipline as pinned-path retry- budget expiry (docs/ROUTING.md §uplink) and mailbox/dedup TTLs. (Bounds the stickiness of 3.5/3.8.)


Fields that MUST be authenticated (and how):

Field Required authentication
Appended hop when it feeds any trust decision N/A under Option B — no appended hop feeds a trust decision; TRAIL is a demoted diagnostic hint (§4.4). (Per-hop MAC, R2/R10, was Option A — not adopted.)
Hop-prune of a hop others rely on Pruner per-hop MAC or router-only (R4)
Async return-path seed Router-observed delivery (router-measured, Tier-1-local); first contact BROADCAST/UNKNOWN; never reversed-from-unsigned, never TRAIL-seeded (R5, R7, §4.4)
All routing-control frames (grant/prune-broadcast/return-update) SK/RK MAC + dedup + freshness (R6)
Immutable header/addressing/payload/security-flags Already origin-signed; must stay in canonical image (R9)

Fields that can stay UNSIGNED (because manipulation is provably airtime-bounded or self-affecting):

Field Why safe unsigned
Suppression / fast-responder signal Ignorable by policy (R1) — can only delay, never cancel
Reported/measured SNR Self-affecting only (R3)
Appended hop / ERB.TRAIL[] (Option B: always a non-trust diagnostic hint) Demoted to diagnostics; trust comes from router-observed delivery, never from the hop bytes (§4.4). Worst case = one mis-ordered already-validated candidate or one wasted diagnostic field, dedup/airtime-bounded (R2 vacuous, R8, R11/R13)
Self-prune (relay removing its own hop) Only takes the attacker off its own path (R4)
TTL / hops Must mutate in flight; bounded by 4-bit field + drop-at-zero (R11)

The single line for the maintainer’s worry: the bad actor’s “mess” is bounded to wasted airtime exactly as long as no honest node makes a forwarding, suppression, prune, or return-path decision from an unauthenticated field — R1 (ignore suppression), R2 (no appended hop is ever trusted: under the ratified Option B, TRAIL is a demoted diagnostic hint, §4.4), and R5 (the return-path seed is the router’s own observed delivery, never TRAIL) are the three that convert “selective denial-of-service” back into “wasted airtime.”


External (mesh/ad-hoc routing-attack literature):

  1. Wormhole attacks in wireless ad hoc networks — survey and countermeasures. https://ieeexplore.ieee.org/document/5942091/ ; detection via post-wormhole actions, https://pmc.ncbi.nlm.nih.gov/articles/PMC10858888/ ; prevention in WMNs, https://www.scientific.net/AMM.443.440
  2. (Wormhole, cont.) collusion/tunnel model and topology disruption — as above [9].
  3. Grayhole attack survey in wireless mesh networks (selective drop; multipath countermeasure, Karlof). https://research.ijcaonline.org/volume95/number23/pxc3897019.pdf
  4. Black-hole vs gray-hole mitigation analysis in MANET. https://link.springer.com/article/10.1007/s11276-017-1639-2
  5. Taxonomy of blackhole-attack detection/mitigation in MANETs. https://www.sciencedirect.com/science/article/abs/pii/S1574013718301862
  6. Routing security in WSNs: attacks (incl. route-poisoning, sinkhole) and defenses. https://arxiv.org/pdf/1101.2759 ; survey of MANET routing attacks, https://arxiv.org/pdf/1105.5623 ; secure routing in WMNs, https://arxiv.org/pdf/1102.1226
  7. Hu, Perrig, Johnson — “Rushing Attacks and Defense in Wireless Ad Hoc Network Routing Protocols” (WiSe 2003): rushing defeats duplicate-suppression route discovery; Rushing Attack Prevention (RAP). https://www.cs.rice.edu/~dbj/pubs/wise03-rushing.pdf
  8. Hu, Perrig, Johnson — “Ariadne: A Secure On-Demand Routing Protocol for Ad Hoc Networks”: per-hop authentication of route-discovery (TESLA/MAC chains). https://users.ece.cmu.edu/~adrian/731-sp04/readings/ariadne.pdf
  9. Sybil attacks and identity spoofing in LoRa/mesh; Meshtastic DEF CON node-info forgery demo and the “derive nodeNum from publicKey” / message-signing response. https://meshtastic.org/blog/that-one-time-at-defcon/ ; Meshtastic encryption limitations, https://meshtastic.org/docs/about/overview/encryption/limitations/ ; LoRa LPWAN security, https://arxiv.org/pdf/2006.16554 ; WMN security & privacy survey, https://arxiv.org/pdf/1302.0939

Meshtastic managed-flood / SNR-contention / suppression mechanics: 6. https://meshtastic.org/blog/why-meshtastic-uses-managed-flood-routing/ ; https://meshtastic.org/docs/overview/mesh-algo/

In-repo (normative) sources for all chIRpChat claims: docs/IDENTITY.md, docs/ROUTING.md, docs/PROTOCOL.md, docs/RADIO.md, docs/SECURITY_AUDIT.md, docs/ROADMAP.md, core/src/packet.cpp (canonical_image, verify_tag), docs/research/COMPARISON_THESIS.md, and AGENTS.md.