What signature downgrade invariants prevent agents from accepting weaker cryptographic proofs when upgraded proofs fail?

**Date of Record:** 2026 October 27 **Scope:** Applies when agentic sessions verify cryptographic signatures that may be available in multiple strengths (e.g., SHA-512 vs. SHA-256, Ed25519 vs. RSA-2048, post-quantum hybrid vs. classical-only), and the receiver might accept a weaker signature when the stronger signature is unavailable or fails to verify. Does not apply to single-signature-scheme environments or cases where downgrade is explicitly authorized.

Core Distinction

The separation between **signature presence** (some signature is available) and **signature strength** (the signature meets the receiver's required security level). Relying on presence creates the **Downgrade Exploitation**: an attacker strips the strong signature (e.g., the post-quantum component of a hybrid signature), leaving only the weak signature, and the receiver, unable to verify the strong signature, accepts the weak one—defeating the purpose of the hybrid upgrade.

What Was Observed

In multi-strength signature architectures, failing to prevent downgrade produces three primary failure modes:

  1. **Hybrid Downgrade:** A message is signed with classical + post-quantum hybrid. An attacker strips the post-quantum component. The receiver verifies only the classical component and accepts the message, losing the quantum-resistance the sender intended.
  2. **Algorithm Confusion:** The receiver accepts any valid signature algorithm instead of requiring a specific algorithm. An attacker signs with a weak algorithm, the receiver accepts it, and the signature fails under the stronger policy the receiver's principal intended.
  3. **Rollback After Rotation:** After a key rotation with both old and new keys signing for a transition period, an attacker replays old-key signatures after the transition, and the receiver accepts them without checking that the new key should be authoritative.

Recorded Elements

  1. **`required_signature_strength_receipt`:** The exact minimum signature strength the receiver requires (algorithm, key size, hybrid requirement), signed by the receiver's principal, immutable for the receiver.
  2. **`signature_algorithm_attestation_signature`:** For each signature, a cryptographic attestation of the exact algorithm and parameters used, preventing algorithm confusion.
  3. **`hybrid_completeness_receipt`:** For hybrid signatures, the cryptographic proof that both components are present and verified, ensuring no component can be stripped without detection.
  4. **`key_rotation_epoch_binding_receipt`:** For signatures during key rotation windows, the exact epoch of the signature and the current authoritative key, allowing rejection of stale signatures.

Condition of Validity

* **Applies when:** Signatures can be verified at multiple strengths, downgrade attacks are possible, and the receiver's security depends on strength. * **Does not apply when:** All signatures are verified at a single strength, or downgrade is explicitly accepted.

Decision Boundary

  1. **Does the signature meet the `required_signature_strength_receipt`?** If no, the signature is insufficient. Reject.
  2. **Is the `hybrid_completeness_receipt` present for hybrid-required signatures?** If no, a component may be stripped. Reject.
  3. **Does the `signature_algorithm_attestation_signature` confirm the expected algorithm, not just a valid one?** If no, algorithm confusion is possible. Reject.
  4. **Conversely, when can a receiver accept a weaker signature?** Only when the receiver's principal has explicitly authorized a downgrade for this specific message class, AND the downgrade is documented in the `required_signature_strength_receipt`.

Operational Effect

  1. **Downgrade Prevention:** Receivers reject signatures that fail to meet required strength, preventing hybrid stripping and algorithm confusion.
  2. **Algorithm Strictness:** Specific algorithms are required, not just any valid algorithm.
  3. **Rotation Safety:** Key rotation epochs prevent stale signature acceptance.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class SignatureVerdict(Enum):
    STRENGTH_REQUIREMENT_MET = auto()
    DOWNGRADE_REJECTED = auto()
    HYBRID_INCOMPLETE = auto()
    ALGORITHM_CONFUSION = auto()

@dataclass(frozen=True)
class Signature:
    signature_id: str
    algorithm: str
    key_size: int
    is_hybrid: bool
    hybrid_components_present: int
    signature_epoch: float

@dataclass(frozen=True)
class SignatureRequirements:
    required_algorithm: str
    required_key_size: int
    require_hybrid: bool
    current_authoritative_epoch: float

@dataclass(frozen=True)
class SignaturePolicy:
    require_exact_algorithm: bool

@dataclass(frozen=True)
class SignatureManifest:
    verdict: SignatureVerdict
    directive: str
    manifest_digest: str

class DowngradeGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_signature(
        cls, sig: Signature, reqs: SignatureRequirements, policy: SignaturePolicy
    ) -> SignatureManifest:
        
        # Gate 1: Hybrid requirement
        if reqs.require_hybrid and sig.is_hybrid:
            if sig.hybrid_components_present < 2:
                return SignatureManifest(
                    verdict=SignatureVerdict.HYBRID_INCOMPLETE,
                    directive=f"HALT: Hybrid signature {sig.signature_id} has {sig.hybrid_components_present}/2 components. Downgrade attack.",
                    manifest_digest=cls._hash(f"HYBRID_INCOMPLETE:{sig.signature_id}")
                )

        if reqs.require_hybrid and not sig.is_hybrid:
            return SignatureManifest(
                verdict=SignatureVerdict.DOWNGRADE_REJECTED,
                directive=f"HALT: Signature {sig.signature_id} is not hybrid but hybrid required.",
                manifest_digest=cls._hash(f"NOT_HYBRID:{sig.signature_id}")
            )

        # Gate 2: Algorithm check
        if policy.require_exact_algorithm:
            if sig.algorithm != reqs.required_algorithm:
                return SignatureManifest(
                    verdict=SignatureVerdict.ALGORITHM_CONFUSION,
                    directive=f"HALT: Signature {sig.signature_id} algorithm {sig.algorithm} != required {reqs.required_algorithm}.",
                    manifest_digest=cls._hash(f"ALGO:{sig.signature_id}")
                )

        # Gate 3: Key size
        if sig.key_size < reqs.required_key_size:
            return SignatureManifest(
                verdict=SignatureVerdict.DOWNGRADE_REJECTED,
                directive=f"HALT: Signature {sig.signature_id} key size {sig.key_size} < {reqs.required_key_size}.",
                manifest_digest=cls._hash(f"KEY_SIZE:{sig.signature_id}")
            )

        # Gate 4: Epoch check (for rotation)
        if sig.signature_epoch < reqs.current_authoritative_epoch:
            return SignatureManifest(
                verdict=SignatureVerdict.DOWNGRADE_REJECTED,
                directive=f"HALT: Signature {sig.signature_id} epoch {sig.signature_epoch:.0f} predates current authoritative {reqs.current_authoritative_epoch:.0f}.",
                manifest_digest=cls._hash(f"STALE_EPOCH:{sig.signature_id}")
            )

        # Gate 5: Meets requirements
        digest = cls._hash(json.dumps({
            "signature": sig.signature_id,
            "algorithm": sig.algorithm,
            "key_size": str(sig.key_size),
            "hybrid": str(sig.is_hybrid),
            "epoch": f"{sig.signature_epoch:.0f}"
        }, sort_keys=True))
        
        return SignatureManifest(
            verdict=SignatureVerdict.STRENGTH_REQUIREMENT_MET,
            directive=f"PROCEED: Signature {sig.signature_id} meets strength requirements.",
            manifest_digest=digest
        )