Skip to content

MitM & Replay lockdown — every front, what locks it, and what the deployment must add

Threat model. An active network adversary on the path between signers, the (untrusted) aggregator, and verifiers: it can capture, modify, drop, reorder, inject, and re-send any message. Three concrete goals, per the deployment ask: (1) steal credentials from captured traffic; (2) fake a different request than the user authorized; (3) impersonate the user. Each front below is mapped to the mechanism that locks it, the evidence, and — honestly — whether the scheme closes it or the deployment must.

Front 1 — Steal credentials from captured traffic

Attack Locked by Evidence Closed?
Recover a signer's long-term/root key from captured transcripts Leakage register: every exposed channel ≥ native; root unrecoverable across unlimited cycles docs/51; ml_adsa_F_rootsafe.ec (root ⇒ PRF, Q-independent); estimator (offset 455–489, hint 387 ≥ native) scheme
Recover a spent per-content key Possible by design (transcript reveals it) but it is one-time ⇒ re-signing the already-agreed decision is a no-op; confined by the forward-secret refresh F20 key-leak model; ForwardSecretRatchet scheme ✓ (no value to attacker)
Passive eavesdropping needs to be prevented? No — the scheme is secure with fully public transcripts, so transport confidentiality is not required for credential safety docs/51 §2 scheme

Bottom line: captured traffic yields nothing usable — not the root, not any live/future key; only spent one-time keys whose job is done. Credential theft via MitM capture is closed by the scheme.

Front 2 — Fake a different request than the user authorized

Attack Locked by Evidence Closed?
Alter the decision in a finished aggregate (A→B) VerifyF = Verify(PkStar, bind(Payload,PartRoot,RegRoot,Epoch), Sig, Ctx) binds the payload TestMitM_Replay_AllBoundFieldsTamperEvident (payload tamper rejected) scheme
Reuse signers' A-contributions to assemble a valid B-aggregate Each response zᵢ is bound to the challenge c = H(μ*(payload), HighBits(ΣW)); a different payload ⇒ different c ⇒ responses don't combine challenge-binding (§Construction); TestMitM… (decision tamper) scheme
Get two different decisions signed for the same slot One-time guard keys on (signer, content); a slot/content is consumed once TestMitM_OneTimeGuardBlocksContributionReplay scheme
Make the signer authorize B while believing it is A (UI/display attack) WYSIWYS — the signing code must hash the displayed payload into μ*; the scheme binds whatever payload is hashed deployment ⚠ (see §Residuals)

Bottom line: a MitM cannot alter, re-target, or re-assemble a request into a different decision — every path fails verification or the one-time guard. The one residual is WYSIWYS at the wallet UI (true of every signature scheme): the application must hash exactly what the user sees.

Front 3 — Impersonate the user

Attack Locked by Evidence Closed?
Forge a signature/aggregate under the user's key EUF-CMA / SUF-CMA at ML-DSA-87 (Cat-5), ROM+QROM ml_adsa_euf/ml_adsa_suf/ml_adsa_qrom; docs/50 (267c/229q) scheme
Inject/substitute a chosen key into the cohort (rogue-key) Proof-of-Possession; a member without a valid PoP is never recognized TestMitM_RogueKeySubstitutionRejected; BuildRegistry/VerifyPoP (F-C5, ml_adsa_rogue_proof.ec) scheme
Corrupt the Fiat–Shamir challenge in transit to bias a response N/A by design — the challenge is SELF-COMPUTED by each signer from public data, never transmitted; there is no challenge channel to MitM non-interactivity ([[ml-adsa-noninteractive]]) scheme ✓ (no channel)
Aggregator equivocation (different aggregates to different verifiers) Each aggregate binds its exact decision/participant-set/epoch; non-equivocation epoch key-tree + provenance/audit F-C7/F-C9 (ml_adsa_F_provenance), Audit scheme

Bottom line: impersonation requires breaking ML-DSA-87 (Cat-5) or forging a PoP for a key not possessed — neither feasible; and the non-interactive self-computed challenge removes the classic MitM-the-challenge vector.

Replay, specifically

An aggregate is valid only for its exact (payload, part-root, reg-root, epoch, ctx-domain) tuple — all bound by VerifyF and all tamper-rejected (test, 7/7). A verbatim replay still verifies (it is a valid signature), so the application closes replay by consuming each (epoch, decision) once — which the binding makes sufficient and the one-time guard enforces at aggregation. Cross-domain/cross-chain replay is blocked by the ctx domain tag (e.g. "ZOND" / "ZOND:<chain>"); cross-epoch by the epoch field + forward-secret ratchet.

Residuals the DEPLOYMENT must lock (honest)

These are outside the signature scheme (true for any scheme) and must be handled by the QRL client: 1. WYSIWYS: hash exactly the payload the user approved into μ* (wallet-UI integrity). The scheme binds it; it cannot police what the UI displays. 2. Anti-DoS / liveness: a MitM can drop/delay messages → a liveness problem (no decision reached), never a safety one (cannot forge or change a decision). Handled by the consensus layer's standard timeouts/retries. 3. Unique decision identifier: the payload/epoch must include a unique per-decision id (slot/height/chain-id) so the consume-once check is well-defined. QRL binds slot+epoch already. 4. Transport integrity (optional): not required for safety (binding catches tampering at verify time), but a transport MAC/TLS reduces wasted work from injected garbage. Confidentiality is not required.

Net

Fronts 1–3 are closed by the scheme (credential theft, decision-faking, impersonation, replay), evidenced by machine-checked proofs (EUF/SUF/QROM, rogue-key, root-safety, non-equivocation) and falsifiable tests (construction_offset_mitm_replay_test.go, byzantine_test.go). The only items left to the deployment are the universal application-layer ones (WYSIWYS, anti-DoS, unique decision id) — none of which any signature scheme can close on its own.