Evidence-free blacklist accusations let f validators blacklist an honest node after one timeout
- Dominant language
- Go
- Stars
- 22
- Forks
- 4
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 34
Description
## Details
Blacklist suspect updates embedded in proposed blocks are unverifiable assertions: common/blacklist.go verifyBlacklistUpdates accepts any well-formed NodeSuspected update for a not-currently-suspected node, without requiring evidence that the accused node actually timed out (such as the empty notarization for its round) and without comparing against the verifier's own timedOutRounds/redeemedRounds state. The f+1 SuspectingCount threshold is therefore met by f fabricated accusations from a Byzantine minority plus exactly one honest accusation.
That one honest accusation is produced by any single missed round of the victim: if honest validator V's leader round is empty-notarized once (a routine event — a network hiccup, a node restart, or an adversary briefly delaying V's proposal past MaxProposalWait), every honest node records timedOutRounds[V] (epoch.go:1595-1611), and the first honest leader afterwards includes a NodeSuspected update for V (ComputeBlacklistUpdates). V immediately redeems itself in the eyes of honest proposers by signing the next notarization, so in an all-honest network V's SuspectingCount stays at 1-2 and is garbage-collected at the orbit boundary — V is never blacklisted.
With f Byzantine validators, however, each of them includes a NodeSuspected update for V in its own block within V's orbit window (every validator leads exactly once per victim-orbit, and honest verifiers accept these updates as well-formed). Total: 1 honest + f Byzantine = f+1 = threshold, and V is blacklisted. V then cannot propose (self-censor at epoch.go:2509-2514) and its rounds are skipped immediately by everyone (epoch.go:2806-2822) for roughly a full orbit until f+1 honest redeem votes accumulate. The attack is repeatable on every subsequent timeout of V, and can be run against multiple victims in parallel (subject to the f cap, whose separate bypass is reported in another finding).
A Byzantine minority thus converts a single, transient fault of an honest validator — which the protocol is explicitly designed to tolerate without penalty — into repeated orbit-long exclusion of that validator from block production: a targeted, minority-driven denial of participation.
## Evidence
1. [common/blacklist.go:380–414](https://github.com/ava-labs/Simplex/blob/main/common/blacklist.go#L380-L414)
verifyBlacklistUpdates is the only check verifiers apply to a proposer's suspect/redeem updates. It validates bounds, duplicates, type, and current suspect state, but requires NO evidence (e.g., the empty notarization for the victim's round) and never cross-checks the accusation against the verifier's own timedOutRounds/redeemedRounds observations. A Byzantine proposer's accusation against any non-suspected node is accepted at face value by every honest verifier.
2. [common/blacklist.go:229–266](https://github.com/ava-labs/Simplex/blob/main/common/blacklist.go#L229-L266)
setNodeSuspected counts one accusation per accusing block per orbit; SuspectingCount reaching threshold f+1 (computeThreshold) blacklists the node. Since each validator leads exactly one round per orbit of the victim, f Byzantine proposers contribute f accusations per orbit, so a single honest accusation completes the threshold.
3. [simplex/epoch.go:1595–1611](https://github.com/ava-labs/Simplex/blob/main/simplex/epoch.go#L1595-L1611)
A single empty-notarized round for victim V's leader slot causes every honest node at that round to record timedOutRounds[V]. The first honest leader after that round then legitimately emits a NodeSuspected update for V (ComputeBlacklistUpdates), providing the one honest accusation the Byzantine coalition needs.
4. [common/blacklist.go:416–483](https://github.com/ava-labs/Simplex/blob/main/common/blacklist.go#L416-L483)
ComputeBlacklistUpdates shows that an honest proposer accuses V whenever V's last observed timeout is within the past orbit and not followed by a redeem observation. Because empty rounds carry no QC signatures, the first block proposer after V's empty round always accuses V; only afterwards do V's QC signatures (redeemedRounds) stop further honest accusations — but by then the honest accusation is already on chain.
5. [simplex/epoch.go:2806–2822](https://github.com/ava-labs/Simplex/blob/main/simplex/epoch.go#L2806-L2822)
Impact of being blacklisted: all honest nodes immediately trigger empty-block agreement for the victim's leader rounds instead of waiting for its proposal, so the victim's blocks are skipped.
6. [simplex/epoch.go:2509–2514](https://github.com/ava-labs/Simplex/blob/main/simplex/epoch.go#L2509-L2514)
Additionally the victim self-censors: an honest blacklisted node refuses to build blocks at all until redeemed, so its proposal capability is fully suppressed for the duration of the blacklisting.
## Impact
Availability: the targeted honest validator is excluded from block production for about one orbit per incident, repeatable at every timeout — bounded, self-healing (redemption via f+1 honest redeem votes) but attacker-directed degradation of the victim's participation and of overall proposal fairness. Integrity: marginal skew of proposer distribution toward the Byzantine coalition during the blacklisted window. No confidentiality impact.
## Reproduction steps
1. Attacker controls f Byzantine validators (validator keys — high privilege but within the BFT fault budget). It needs one timeout of the victim's leader round (routine natural event, or induced by delaying the victim's proposal past MaxProposalWait — a runtime condition outside the attacker's full control). The f Byzantine leaders then each embed one fabricated NodeSuspected update for the victim in their next blocks; honest verifiers accept them, reaching the f+1 threshold. No user interaction; ordinary consensus messages over the network.
## Recommended fix
NodeSuspected updates are accepted by verifiers based solely on well-formedness; the f+1 threshold can be met by f fabricated Byzantine accusations plus the single honest accusation that any one-off timeout produces, letting a minority blacklist an honest validator that the honest network alone would never have blacklisted. Fix criteria: A validator must not become blacklisted unless its sustained failure is attested in a way a Byzantine minority cannot fabricate — e.g., accusations carry verifiable evidence (the empty notarization for the accused round) and/or the threshold guarantees more honest corroborations than a single transient fault can generate. Verify: with f Byzantine proposers accusing arbitrarily and an honest victim that misses exactly one round, the victim must never satisfy IsNodeSuspected.
---
**Severity:** LOW
**Status:** Open
**Category:** Insufficient verification of data authenticity
**CWE:** [CWE-345](https://cwe.mitre.org/data/definitions/345.html)
**Repository:** ava-labs/Simplex
**Branch:** main
**Date created:** 2026-08-21
---
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with common/blacklist.go:380–414 and trace verifyBlacklistUpdates into setNodeSuspected at lines 229–266. Compare this flow with ComputeBlacklistUpdates at lines 416–483 and the timedOutRounds/redeemedRounds handling in simplex/epoch.go:1595–1611. Done means the supplied one-timeout reproduction cannot make IsNodeSuspected true through fabricated accusations, while legitimate sustained failures remain detectable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- distributed-systems, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100