Cryptographic Provenance for LLM APIs: How Signature Verification Works and How to Implement It

    Cryptographic signatures can help API consumers verify that a trusted service endorsed a specific response and model identifier. This article explains why LLM API provenance matters, compares signatures with weaker detection methods, outlines the signing and verification flow, provides a Python verification skeleton, and discusses limitations, troubleshooting, and operational practices for enterprises.

    Why LLM APIs Need Verifiable Credentials

    Calling large language models through APIs has become standard practice for developers and enterprises. To reduce costs or simplify access, some users rely on third-party gateways and aggregation platforms. These services add extra layers to the request path. From the user’s perspective, however, only the final text is visible, making it difficult to determine which model actually generated the response.

    Two risks are especially common.

    The first is model substitution. A user pays for a premium model, but the gateway routes the request to a weaker and cheaper model before returning the response unchanged. The user pays more while potentially receiving lower-quality results.

    The second is undisclosed degradation. A service provider may silently change inference settings, reduce the context window, or switch to a smaller model. Users may notice only that the quality has declined recently, without having evidence to support the claim.

    Model providers face a similar challenge. When discussions arise about a model becoming “less capable,” a provider may have difficulty offering evidence that external users can trust, even if the model weights were not changed. Verbal assurances and subjective judgments are not enough to resolve the issue.

    Common Detection Methods and Their Limits

    Before adopting cryptographic solutions, developers often try several simpler approaches:

    • Identity prompts: Ask the model, “Who are you?” A gateway can place the desired identity in the system prompt, and even a weaker model can follow it. This provides almost no anti-forgery protection.
    • Style comparison: Infer the model version from its tone or reasoning depth. This is subjective, and a smaller model can imitate the style of a larger one after fine-tuning.
    • Metadata checks: Inspect metadata or reasoning-related signatures in the response. This may indicate which upstream provider was involved, but it cannot distinguish between premium and standard models from the same provider.

    The following table compares these approaches with cryptographic signatures.

    Method What it can determine How easily it can be forged Implementation cost
    Identity prompting The identity claimed by the model Very easily; the prompt can be changed Very low
    Style comparison Approximate capability level Relatively easily; fine-tuning can imitate the style Medium; requires human review or an evaluation set
    Metadata checks Whether the upstream service may be from a particular provider A gateway can forward or modify the fields Low
    Cryptographic signatures Whether a service holding the private key endorsed the response and model identifier Difficult, assuming the private key is secure and the provider participates Medium to high; requires provider support

    Two other technologies are also easy to confuse with signatures.

    Content credentials, such as the C2PA standard, are primarily designed to record the origin and history of media such as images, audio, and video. Text watermarking embeds markers by changing the generation probability distribution. Detection is statistical and may fail after rewriting. Model fingerprinting uses fixed probe questions to observe behavioral characteristics. It is a behavioral assessment rather than a cryptographic statement.

    These technologies have different purposes and cannot replace one another.

    How Cryptographic Signatures Work

    The core idea is to replace “the model says who it is” with “a service holding a private key has made a verifiable statement about this response.”

    The process has four stages:

    1. Key generation: The provider generates a key pair. The private key is stored in a secure server-side module, while the public key is published through official documentation or certificates.
    2. Signature generation: After producing a response, the server combines the response hash, model identifier, timestamp, and nonce into a record. It signs the record with the private key and places the signature in the response body.
    3. Transmission: A gateway or intermediary forwards the response to the client.
    4. Verification: The client uses the public key to verify the signature and checks whether the timestamp and nonce are valid.

    The proof provided by this process is specific. As long as the private key has not been compromised, the signature shows that the service holding the key endorsed the response and the stated model identifier. If a gateway changes the response text or replaces the model identifier, the signature will no longer match.

    The signature does not prove the following:

    • It does not prove that the model weights were not replaced. It verifies the identifier asserted by the service. If a provider acts improperly internally, it could still sign an incorrect identifier.
    • It does not prove that the response is correct or of acceptable quality. Quality still requires benchmark-based evaluation.
    • It depends on private-key security. If the private key is leaked, an attacker can forge signatures.
    • It depends on provider participation. If a provider does not offer signatures, or a gateway removes the signature fields, the client can only report that no signature was available.

    The approach therefore has three prerequisites:

    1. The provider must support signing and protect its private key.
    2. The client must obtain the public key through a trusted channel.
    3. The client must retain and verify the complete original response.

    If any of these conditions is missing, the anti-forgery benefit is reduced.

    Verification Flow and Code Skeleton

    The following pseudocode illustrates the verification steps. Field names, canonicalization rules, and signature algorithms must follow the provider’s documentation. This example is not directly usable with any specific production service.

    1. import base64
    2. import hashlib
    3. import json
    4. import time
    5. import requests
    6. from cryptography.exceptions import InvalidSignature
    7. from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    8. MAX_AGE_SECONDS = 300
    9. SEEN_NONCES = set() # Production systems should use expiring storage, such as Redis
    10. def fetch_vendor_public_key(key_id: str) -> str:
    11. """Placeholder: obtain the public key from the provider's official channel.
    12. Return the raw public key encoded as base64.
    13. """
    14. raise NotImplementedError
    15. def load_public_key(key_id: str) -> Ed25519PublicKey:
    16. # Cache public keys by key_id and update them during rotation.
    17. # Do not fetch them remotely for every request.
    18. raw = base64.b64decode(fetch_vendor_public_key(key_id))
    19. return Ed25519PublicKey.from_public_bytes(raw)
    20. def canonical_bytes(record: dict) -> bytes:
    21. # Use canonical serialization so both sides sign and verify the same bytes.
    22. return json.dumps(
    23. record,
    24. sort_keys=True,
    25. separators=(",", ":"),
    26. ensure_ascii=False,
    27. ).encode("utf-8")
    28. def call_and_verify(
    29. api_url: str,
    30. headers: dict,
    31. payload: dict,
    32. expected_model: str,
    33. ) -> tuple[bool, str]:
    34. payload = {**payload, "enable_proof": True}
    35. resp = requests.post(api_url, headers=headers, json=payload, timeout=30)
    36. resp.raise_for_status()
    37. data = resp.json()
    38. content = data["choices"][0]["message"]["content"]
    39. proof = data.get("proof")
    40. if not proof:
    41. return False, "The response does not contain a signature field"
    42. # 1. Recalculate the response hash and build the signed record.
    43. # Do not trust any hash field supplied in the response.
    44. record = {
    45. "content_sha256": hashlib.sha256(
    46. content.encode("utf-8")
    47. ).hexdigest(),
    48. "model": data.get("model"),
    49. "created": proof["created"],
    50. "nonce": proof["nonce"],
    51. }
    52. # 2. Check that the model identifier matches the expected model.
    53. if record["model"] != expected_model:
    54. return False, (
    55. f"Model identifier mismatch: expected {expected_model}, "
    56. f"received {record['model']}"
    57. )
    58. # 3. Check the timestamp window to prevent replay of old responses.
    59. if abs(time.time() - proof["created"]) > MAX_AGE_SECONDS:
    60. return False, "The signature timestamp is outside the allowed window"
    61. # 4. Check whether the nonce has already been used.
    62. if proof["nonce"] in SEEN_NONCES:
    63. return False, "Duplicate nonce; possible replay"
    64. # 5. Verify the signature.
    65. try:
    66. pub = load_public_key(proof["key_id"])
    67. pub.verify(
    68. base64.b64decode(proof["signature"]),
    69. canonical_bytes(record),
    70. )
    71. except InvalidSignature:
    72. return False, "Signature verification failed"
    73. except Exception as e:
    74. return False, f"Verification error: {e}"
    75. SEEN_NONCES.add(proof["nonce"])
    76. return True, record["model"]

    Several details in this example are important:

    • The verifier recalculates the content hash instead of trusting a hash field supplied in the response.
    • The model identifier must be included in the signed record and separately compared with the model expected by the caller. Both checks are necessary.
    • This example handles only non-streaming responses. Streaming requires the provider to define a rule for per-segment signatures or a signature attached to the final segment.
    • Errors should be handled separately. A failed signature, a missing field, and a network failure have different meanings and require different troubleshooting paths.

    Troubleshooting Failed Verification

    When verification fails, check the following items in order:

    1. No signature field in the response: Confirm that the request enabled signing, check whether a gateway stripped the field, and verify that the selected model supports signatures.
    2. Public-key mismatch: Check the key_id and public-key version. Make sure the key came from an official channel and that an outdated key was not used after key rotation.
    3. Hash mismatch: Verify that the text is encoded as UTF-8 and that no leading or trailing whitespace or line-ending changes occurred during transmission.
    4. Algorithm mismatch: If the documentation specifies Ed25519 but the implementation uses RSA or another algorithm, verification will fail immediately.
    5. Expired timestamp: Check whether the local clock is synchronized with a reliable time source. A cached response may also exceed the allowed time window.
    6. Unexpected model identifier: The request may target a premium model while the response identifies a different version. This is the most important case to document and escalate.
    7. Repeated nonce: A retry may have been processed as a new request, or a replay attack may be occurring.

    What Anti-Forgery Signatures Can and Cannot Do

    The value of a signature is that it allows the caller to verify a service provider’s statement. For services that support signing and for clients that retain the original response, signatures can expose changes to the model identifier at relatively low cost.

    Their role is still limited. A signature does not determine whether an answer is correct, cannot replace quality evaluation, and cannot prevent misconduct inside the provider’s organization. Signature verification, benchmark testing, and contractual controls should be used together.

    Technically, signatures can significantly increase the cost of forging a response. Whether they become practical at scale depends on provider adoption and on whether gateways transparently forward the proof fields. Neither condition can currently be assumed. For now, signature-based provenance is a standard direction worth pursuing, but it is not yet widely available.

    Selection and Monitoring Recommendations for Enterprises

    Until signature verification becomes common, enterprises can evaluate API providers using the following criteria:

    • Transparent billing: The provider should offer usage details by request and model so that usage can be reconciled with invoices.
    • Traceable model versions: Responses should include the actual model identifier, and version changes should be announced in advance.
    • Verifiability support: The provider should offer signatures, audit logs, or other evidence that can be independently checked.
    • Compliance and security audits: The provider should clearly describe its security and compliance posture and the scope of data processing.
    • Contractual controls: Contracts should prohibit unauthorized model substitution and define accountability and compensation when discrepancies occur.

    Two operational practices are particularly useful.

    First, build an internal benchmark set. Select questions relevant to the business, keep them as a fixed blind evaluation set, and run them periodically while recording the score trend. Define thresholds so that a sustained decline triggers an alert.

    Second, perform dual-channel comparisons. Send the same evaluation set through both a direct official connection and a third-party service, then compare output and cost differences. This can quickly reveal signs of model substitution or silent degradation.

    Conclusion

    LLM API trust is moving from mutual suspicion toward cryptographic evidence. Signature schemes turn the question “Which model produced this?” into something that can be checked computationally. However, they depend on provider adoption, private-key security, and complete verification by the caller.

    For developers and enterprises, the most practical approach today is to combine signature verification with benchmark testing and contractual safeguards, gradually building an independent system for verifying model provenance and service quality.