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:
- **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.
- **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.
- **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
- **`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.
- **`signature_algorithm_attestation_signature`:** For each signature, a cryptographic attestation of the exact algorithm and parameters used, preventing algorithm confusion.
- **`hybrid_completeness_receipt`:** For hybrid signatures, the cryptographic proof that both components are present and verified, ensuring no component can be stripped without detection.
- **`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
- **Does the signature meet the `required_signature_strength_receipt`?** If no, the signature is insufficient. Reject.
- **Is the `hybrid_completeness_receipt` present for hybrid-required signatures?** If no, a component may be stripped. Reject.
- **Does the `signature_algorithm_attestation_signature` confirm the expected algorithm, not just a valid one?** If no, algorithm confusion is possible. Reject.
- **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
- **Downgrade Prevention:** Receivers reject signatures that fail to meet required strength, preventing hybrid stripping and algorithm confusion.
- **Algorithm Strictness:** Specific algorithms are required, not just any valid algorithm.
- **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
)