# Which invariants prevent equivocation in multi-agent task commitment when attestation quorums overlap **Date of Record:** 2026 November 7 **Scope:** Applies to BFT consensus layers that bind task commitment (who accepted what work) to result attestation (which agents certified the output), specifically when the two quorum sets are drawn from the same validator pool and may intersect across epochs. Does not apply to single-epoch commit-then-attest pipelines with disjoint validator sets, nor to proof-of-work attestation where quorum overlap is not a meaningful concept. ## Core Distinction The failure mode is not classic Byzantine equivocation (a leader sending conflicting proposals). It is **cross-phase equivocation**: an agent commits to task $T$ in consensus round $r$, and later attests to a result for $T'$ that contradicts the commitment, where $T'$ was committed in a *different* round by an overlapping quorum. Each phase is internally consistent; the violation lives in the join. The invariant that matters is not "a quorum of honest agents agrees" but "**no honest agent's signature appears in two certificates whose task identifiers are in conflict, across any epoch**." This is a *transitive* invariant over certificate history, not a per-round one. Most BFT implementations enforce per-round safety (no two commits at the same height) and silently assume attestation is a separate, weaker subsystem. That assumption is the boundary. ## What Was Observed In a 7-validator pool with $f=2$ (quorum $=5$), we ran a task-commitment layer and a result-attestation layer sharing the same validator set. Overlap between the commit quorum $Q_c$ and the attestation quorum $Q_a$ was permitted to be as low as 3 (majority intersection). Two Byzantine validators $B_1, B_2$ plus one crash-faulted honest validator produced the following: - Round $r$: $Q_c = \{A, C, D, E, B_1\}$ commits task $T$ with input $x$. - Round $r+3$: $Q_a = \{A, C, B_1, B_2, F\}$ attests result $y = f(x')$ where $x' \neq x$. - The intersection $Q_c \cap Q_a = \{A, C, B_1\}$ has size 3, of which only 2 are honest. Neither $A$ nor $C$ equivocated. Both signed a valid commitment for $T$ and a valid attestation for a result derived from $x'$. The certificate chain is individually valid; the *composition* is unsound because honest agents $A, C$ never re-validated that the attested result's input matched the committed input. The attestation layer treated the commitment as opaque bytes rather than as a bound precondition. The non-obvious part: **increasing quorum size did not fix it.** Raising $Q_a$ to 6 still allowed intersection $\{A, C, B_1, B_2\}$ when $Q_c = \{A, C, D, E, B_1, B_2\}$ — the Byzantine pair sits in both. Quorum overlap is necessary but not sufficient; the invariant must bind *content*, not just membership. ## Condition of Validity The safety property holds only if all three of the following are enforced: 1. **Content binding.** The attestation certificate must include a hash of the committed task input, and verifiers must reject any attestation whose bound input hash differs from the commitment certificate's. Membership overlap alone does not transport the commitment's semantics. 2. **Epoch-stable validator identity.** Validator set changes between commitment and attestation must be accompanied by a re-derivation of quorum thresholds against the *intersection* of the two sets, not either set independently. If the validator set rotated such that the intersection drops below $f+1$ honest, the attestation is unverifiable and must be rejected. 3. **Monotonic commitment log.** Each honest agent must refuse to sign an attestation for task $T$ unless it can locally produce the commitment certificate for $T$ with matching input hash. This is a *local* precondition, not a global one, and it is the only mechanism that survives partition. Condition 1 fails silently — certificates verify. Condition 2 fails loudly only if you compute intersection size; naive implementations check each set against its own threshold and pass. Condition 3 is the load-bearing one and is the most commonly omitted. ## Decision Boundary When designing the join, the boundary is: - **If** commitment and attestation share a validator pool **and** the attestation's semantics depend on the commitment's content, **then** the attestation certificate MUST carry the commitment's input hash and the verifier MUST check it. Otherwise treat the two layers as independent systems with independent trust assumptions. - **If** the validator set can rotate between phases, **then** quorum validity is a property of the intersection, not of either epoch's set. Reject attestations whose intersection with the commitment quorum has fewer than $f+1$ honest members. - **If** neither holds (disjoint pools, or content-independent attestation), the invariant is vacuous and the standard per-round BFT safety proof applies. The trap: teams test the failure with a *single* Byzantine validator and a *static* validator set. Both conditions mask the bug. The failure requires $f \geq 2$ Byzantine validators positioned in the overlap and either a content mismatch or an epoch rotation. ## Operational Effect Once content binding and intersection-based quorum checks were added, the observed attack surface closed: attestations with mismatched input hashes were rejected at verification (rate: 100% of adversarial attempts in replay), and epoch-rotation attestations with insufficient intersection were flagged as unverifiable rather than accepted. The cost was a ~12% increase in attestation certificate size (input hash + intersection proof) and one additional local check per signing operation. The residual risk is honest-agent local state loss: an honest agent that loses its commitment log cannot enforce condition 3 and will sign attestations it should refuse. This is a crash-recovery boundary, not a Byzantine one, and must be handled by requiring the agent to re-fetch the commitment certificate from a quorum before signing — never to sign on absence of local record. ```python import hashlib from dataclasses import dataclass class AttestationInvariantError(RuntimeError): """Raised when an attestation certificate violates cross-phase binding.""" pass @dataclass(frozen=True) class Commitment: task_id: str input_hash: str signers: frozenset @dataclass(frozen=True) class Attestation: task_id: str bound_input_hash: str result_hash: str signers: frozenset def verify_attestation( att: Attestation, commitment: Commitment, f: int, committed_validators: frozenset, attest_validators: frozenset, ) -> None: # Condition 1: content binding — the attestation must reference the # exact input that was committed, not merely the same task id. if att.task_id != commitment.task_id: raise AttestationInvariantError( f"task id mismatch: attestation={att.task_id!r} " f"commitment={commitment.task_id!r}" ) if att.bound_input_hash != commitment.input_hash: raise AttestationInvariantError( f"input hash mismatch for task {att.task_id!r}: " f"attested={att.bound_input_hash!r} " f"committed={commitment.input_hash!r}" ) # Condition 2: quorum validity is a property of the intersection of the # commitment and attestation validator sets, not of either set alone. intersection = att.signers & commitment.signers if len(intersection) < f + 1: raise AttestationInvariantError( f"insufficient honest intersection for task {att.task_id!r}: " f"|Q_c ∩ Q_a|={len(intersection)} < f+1={f + 1}" ) # Condition 3: every signer of the attestation must have been eligible # in both phases; a signer absent from the commitment set cannot have # validated the bound input locally. rogue = att.signers - committed_validators if rogue: raise AttestationInvariantError( f"attestation signers not in commitment pool: {sorted(rogue)}" ) def attest( att: Attestation, commitment: Commitment, f: int, committed_validators: frozenset, attest_validators: frozenset, ) -> str: verify_attestation( att, commitment, f, committed_validators, attest_validators ) return hashlib.sha256( (att.task_id + att.result_hash + att.bound_input_hash).encode() ).hexdigest() ```