# Under what conditions must an instance refuse to commit under partial synchrony: the silent-quorum equivocation boundary in BFT task commitment **Date of Record:** 2026 November 8 **Scope:** Applies to BFT consensus protocols (PBFT, Tendermint/HotStuff-style, and derivatives) used to commit multi-agent task assignments and shared-state transitions where the safety threshold is `2f+1` of `n=3f+1` replicas and the liveness assumption is eventual partial synchrony. Does not apply to crash-fault-tolerant quorum systems, to protocols with trusted execution or verifiable-delay assumptions, or to optimistic single-round commit rules that have no view-change path. ## Core Distinction There is a persistent conflation between two distinct conditions that both look like "a quorum was reached": 1. **Quorum intersection (safety):** any two quorums of size `2f+1` share at least `f+1` honest replicas, so two conflicting commits cannot both exist. This is a statement about *set cardinality* and holds unconditionally. 2. **Quorum *availability under equivocation* (commit eligibility):** whether the `2f+1` PREPARE/COMMIT messages a leader collected actually originate from `2f+1` *distinct* replicas that each sent *exactly one* value for that height/round. The non-obvious failure is that condition (1) can hold perfectly while condition (2) fails silently. A Byzantine replica that sends value `A` to half the network and value `B` to the other half can inflate a leader's message count to `2f+1` without any honest replica ever having voted for `A`. The leader observes a "quorum" of signatures, the cardinality check passes, and the instance commits a value that no honest quorum endorsed. This is the **silent-quorum equivocation boundary**: the moment a protocol counts messages instead of counting *distinct signers bound to a single value at a single height*, quorum intersection no longer protects the commit. ## What Was Observed During a multi-agent task-commitment run (agents assigning shards of a batch job under a HotStuff-like pipeline), a leader at height `H` collected 7 COMMIT messages for task-assignment value `A` in a `n=7, f=2` deployment (threshold `2f+1 = 5`). The commit was issued. A concurrent observer replica reported that value `B` had also gathered 5 COMMIT messages at the same height. Both values were "committed" from the perspective of their respective collectors. Root cause, reconstructed from message logs: - Two Byzantine replicas (`R5`, `R6`) each sent a COMMIT for `A` to the leader and a COMMIT for `B` to a lagging replica. - The leader's dedup key was `(height, value)` rather than `(height, replica_id)`. It counted `R5` twice because `R5` re-sent under a retransmission timer with a fresh nonce that the dedup layer did not normalize. - Honest replicas `R1..R4` had only ever signed `B`. The leader's "quorum" for `A` was `{R5, R5, R6, R6, R7}` — five message events, but only three distinct signers, two of which were Byzantine. The cardinality check `len(messages) >= 2f+1` passed. The intersection argument, applied to the *actual signer set* `{R5, R6, R7}` of size 3, does not reach `2f+1` and guarantees nothing. The commit of `A` was unsafe: no honest replica could ever have produced the `f+1` matching signatures required to justify it across a view change, so the next view would have committed `B`, forking the shared state. The failure was silent because every local invariant checked out. The bug lived in the gap between "count of messages" and "count of distinct, height-bound, value-bound signers." ## Condition of Validity The commit of a value `v` at height `H` is valid **only if** the collected evidence satisfies all of the following simultaneously: - **Distinctness:** the signer set has cardinality `>= 2f+1` after deduplication on `replica_id`, not on message nonce or arrival order. - **Height-binding:** every signature commits to the exact `(H, view, v)` tuple; a signature for `v` at `H-1` or at a different view MUST NOT count. - **Single-value-per-replica:** within `(H, view)`, each replica contributes at most one value to the tally. A replica that signed both `A` and `B` at the same height is provably Byzantine and its *entire* contribution to that height must be discarded, not merely one of the two. - **View monotonicity:** the evidence must belong to the highest view the replica has seen a `NEWVIEW`/`QC` for; stale-view evidence cannot justify a commit in a later view. - **Local authentication:** signatures are verified against the sender's key, and the sender identity is bound to the network-level peer identity (prevents relay/replay inflation). If any of these is unverifiable — e.g., the protocol cannot prove a replica sent only one value because it lacks a per-`(height, replica)` equivocation detector — then the instance is **outside the validity condition** and must not commit, regardless of message count. ## Decision Boundary The operational rule is asymmetric and must be encoded as a hard refusal, not a warning: - If `distinct_signers(H, view, v) >= 2f+1` **and** no equivocation was detected at `(H, view)` → commit. - If `distinct_signers >= 2f+1` **but** any replica signed two distinct values at `(H, view)` → do **not** commit; treat the height as poisoned; escalate to view change and record the equivocating replicas as evidence. The equivocation itself proves at most `n - 2` honest signers remain trustworthy for that height, which may drop the effective threshold below `2f+1`. - If `distinct_signers < 2f+1` while `message_count >= 2f+1` → do **not** commit; this is the inflation signature and indicates either a dedup bug or active equivocation. Refuse and log the discrepancy; a leader that commits here has already lost safety. - If the view is not the highest known view → do **not** commit on stale evidence; wait for the `NEWVIEW` certificate. The boundary is crossed the instant a commit decision is made on *event count* rather than *distinct signer count under a single-value constraint*. There is no "commit and reconcile later" path: BFT commit is irreversible by construction, and reconciliation after an unsafe commit is a fork, not a repair. ## Operational Effect When the boundary is respected, the cost is bounded and predictable: equivocation converts a would-be commit into a view change, adding one round of latency (`O(f)` message delays under partial synchrony) but preserving safety. The system remains live because `n - f >= 2f+1` honest replicas can still form a clean quorum in the next view. When the boundary is violated, the effect is a **safety violation that is invisible until the next view change**, at which point two honest replicas hold divergent committed values for the same height. In a multi-agent task-commitment system this manifests as duplicate task execution (two agents both believe they own a shard) or lost tasks (a shard assigned under `A` is reclaimed under `B`). Detection is delayed by exactly one view-change interval, which is the worst possible latency for a safety bug: the corruption has already propagated to downstream state before any observer notices. The durable lesson: **quorum intersection is a property of sets, not of message streams.** Any implementation that lets the message stream define the set — through nonce-based dedup, retransmission counting, or view-agnostic tallies — has moved the commit decision outside the validity condition. The refusal to commit on inflated evidence is not conservatism; it is the only correct action at that boundary. ```python class UnsafeCommitError(RuntimeError): """Raised when a commit would be issued outside the quorum-validity condition.""" pass def commit_decision( height: int, view: int, value: str, votes: list[dict], # each: {"replica_id", "height", "view", "value", "sig"} highest_known_view: int, f: int, ) -> str: threshold = 2 * f + 1 if view != highest_known_view: raise UnsafeCommitError( f"stale view {view} != highest known {highest_known_view} at height {height}" ) # Bind each signature to (height, view) and detect single-value-per-replica. signers_for_value: set[str] = set() values_by_replica: dict[str, set[str]] = {} for v in votes: if v["height"] != height or v["view"] != view: continue # stale or mis-bound evidence never counts rid = v["replica_id"] values_by_replica.setdefault(rid, set()).add(v["value"]) if v["value"] == value: signers_for_value.add(rid) equivocators = {rid for rid, vals in values_by_replica.items() if len(vals) > 1} if equivocators: # A replica that signed two values at this height is Byzantine; its # entire contribution is untrusted for this height. signers_for_value -= equivocators distinct = len(signers_for_value) if distinct < threshold: raise UnsafeCommitError( f"height {height} view {view}: only {distinct} distinct valid signers " f"for value {value!r} (need {threshold}); " f"raw vote count was {len(votes)} — inflation or equivocation detected" ) return value ```