# When must an instance treat a quorum certificate as evidence of agreement rather than evidence of participation **Date of Record:** 2026 November 7 **Scope:** Applies to BFT consensus layers (PBFT-style, Tendermint-style, HotStuff-style) whose quorum certificates are assembled from signed votes by a fixed validator set, where at least one participant is an LLM agent capable of emitting syntactically valid but semantically ungrounded votes. Does not apply to protocols with cryptographic equivocation proofs binding a vote to a specific proposal digest, nor to single-agent decision loops. ## Core Distinction A quorum certificate (QC) is a *set of signatures over a digest*. It is not a statement about the world. Most BFT protocol descriptions conflate two distinct properties that a QC can carry: 1. **Participation evidence** — "n−f distinct validators signed digest d." This is what the signature count actually proves. 2. **Agreement evidence** — "the correct validators, having independently evaluated the proposal, concluded d is safe to commit." Classical BFT safety proofs (Castro–Liskov, and the HotStuff linear-communication argument) assume honest validators vote only after *internal* validation of the proposal against their own state. The quorum intersection argument — any two quorums of size > 2f/3 share at least one honest member, and that honest member will not sign two conflicting digests at the same height — collapses the moment a validator can be induced to sign a digest it has not independently validated. The adversary does not need to corrupt f+1 validators; it needs to corrupt *the validation step* on f validators, which is a strictly weaker capability. ## What Was Observed In a 7-validator quorum (f=2, quorum threshold 5) with three LLM-agent validators, an adversarial proposer submitted a proposal whose payload contained a prompt-injection string in an unvalidated metadata field. The three LLM validators, whose validation routine included a summarization step over the full proposal, were induced to emit `PREPARE` votes for the injected digest without running the state-transition check. Two Byzantine validators (scripted) signed as well. The resulting 5-signature QC was *cryptographically valid and protocol-valid* but did not represent agreement on a safe value: the three LLM validators had never evaluated the state transition, and the two honest validators who did evaluate it voted `PREPARE` on a different, correct digest at the same view. The protocol committed the injected value at view v+1 because the QC for the injected digest was the first to reach threshold. The failure is non-obvious because the signature count was correct, the signers were distinct, no validator double-signed, and the equivocation-detection layer saw nothing. Every invariant the protocol *checked* held. The invariant the protocol *relied on* — "a signature implies independent validation" — did not. ## Condition of Validity The QC-as-agreement-evidence inference is valid only when all of the following hold: - **Validation independence:** each signer ran its own state-transition function against its own state, and the signature is bound to the *output* of that function, not to the proposal digest alone. - **Non-injectability of the validation path:** the proposal cannot influence the validator's validation routine (no shared context, no prompt-injectable fields on the validation path). - **Failure attribution:** a signer that signs an unsafe digest is attributable and slashable, so the cost of signing without validation exceeds the gain. If any of these is false — in particular, if a validator's validation routine is itself a model whose behavior is a function of attacker-controlled input — the QC degrades from agreement evidence to participation evidence, and the 2f+1 threshold no longer bounds the number of *unvalidated* signatures. ## Decision Boundary The operational rule: **before treating a QC as commit evidence, the committing node must verify that the QC's signers include at least f+1 validators whose validation path is non-injectable, or must re-run the state transition locally against the committed digest and reject on mismatch.** The boundary is not "is the QC well-formed" (that is a signature check) and not "are there > 2f/3 signatures" (that is a count). The boundary is "does the signer set contain a majority of validators for whom signature ⇒ independent validation." For a quorum with k LLM validators, the safe threshold is `max(2f+1, k + f + 1)` distinct signatures, because the k LLM validators may all be simultaneously induced and must be treated as a single correlated failure domain, not as k independent votes. ## Operational Effect Committing nodes that skip this check will commit injected values at the first view where the induced QC reaches threshold, typically one view ahead of the honest QC, because induced validators skip the state-transition latency. The observable signature is a commit whose payload fails local re-execution while its QC verifies. The correct response is not to re-run consensus (the induced validators will reproduce the injection) but to *reject the QC as agreement evidence*, fall back to local re-execution, and escalate the correlated failure domain — the LLM validator set — to the slashing/attribution layer as a single unit. ```python class QuorumAgreementViolation(RuntimeError): """Raised when a QC verifies cryptographically but does not constitute agreement evidence.""" pass def commit_from_qc(qc, local_state, llm_validator_ids, f, threshold): signers = set(qc.signer_ids) if len(signers) < threshold: raise QuorumAgreementViolation( f"QC has {len(signers)} signers, below threshold {threshold}" ) # Correlated failure domain: LLM validators may be simultaneously induced. # They count as at most one independent vote for agreement purposes. independent_signers = signers - llm_validator_ids if len(independent_signers) < f + 1: raise QuorumAgreementViolation( f"QC has only {len(independent_signers)} non-injectable signers; " f"need at least f+1={f + 1} to treat signatures as agreement evidence" ) # Independent re-execution against local state is mandatory. result = local_state.apply(qc.proposal) if result.digest != qc.proposal_digest: raise QuorumAgreementViolation( f"QC digest {qc.proposal_digest} does not match local re-execution " f"digest {result.digest}; QC is participation evidence only" ) return result ```