Skip to main content

What are attestations?

An attestation is a cryptographically signed proof that a verification occurred. It:
  • Uses ES256 (ECDSA P-256) signatures
  • Is formatted as a JWT
  • Can be verified independently
  • Can be stored on-chain

Requesting attestations

Fail-closed contract

Since PR #194 (Issue #188): create_verification_attestation() never returns None. It always returns an AttestationResult with status set to ISSUED, BLOCKED, or UNVERIFIABLE. You MUST check result.is_issued before you treat the attestation as valid. A missing or failed attestation must hard-block the verification path. Never downgrade it to VERIFIED.

AttestationResult

AttestationStatus
required
Lifecycle state of the attestation. One of ISSUED, BLOCKED, or UNVERIFIABLE.
string | None
Signed JWT string. Present only when status == ISSUED; None otherwise.
string | None
Machine-readable failure code: "SIGNING_FAILURE" when signing failed, "CRYPTO_UNAVAILABLE" when the cryptography / PyJWT package is not installed, "VERIFIED_WITHOUT_PROOF" when the caller asked to sign a VERIFIED result without a proof_data artifact, None on success.
string | None
Human-readable failure detail. None on success.
bool
Property. True only when status is AttestationStatus.ISSUED. You must check this flag before you use token.

AttestationStatus values

Caller pattern

Issuance-side proof enforcement

create_verification_attestation() will not sign a VERIFIED verdict that has no proof artifact. If you pass verified=True (or status="VERIFIED") with proof_data empty or omitted, the crypto layer short-circuits and returns:
This closes the “trusted VERIFIED without evidence” issuance path: a caller cannot obtain a signed attestation for a claim it has no proof of. UNVERIFIABLE and BLOCKED verdicts are unaffected — only VERIFIED requires a proof_data artifact. Pass the same JSON string (typically json.dumps(evidence, sort_keys=True)) that your engine used to derive the diagnostic’s proof_ref, so the token’s qwed.proof_hash claim matches the result on the consuming side.

Key lifecycle auditability

Every IssuerKeyPair records generated_at (epoch seconds) and key_continuity_policy. QWED emits a structured log entry (attestation.key_generated) on every new key generation, so you can audit continuity events.
string
default:"ephemeral"
Policy for the issuer key pair. Must be one of "ephemeral" (in-memory, non-persistent — default) or "persistent" (durably stored, e.g. external KMS). Any other value raises ValueError.
AttestationService.get_issuer_info() now includes key_generated_at and key_continuity_policy alongside the existing issuer registry fields.

Attestation structure

Payload

Verifying attestations

Using the API

Using the SDK

Enforce the trust boundary

Release gates and any code path that admits a VERIFIED result MUST route through enforce_trust_decision() before acting on it. It is the single consumption-side entry point that binds a verification result to its attestation token and fails closed on any mismatch — you should not consume DiagnosticResult.status == VERIFIED directly.

Parameters

DiagnosticResult
required
The verification result returned by the engine. enforce_trust_decision will pass through any already-fail-closed status (BLOCKED, UNVERIFIABLE, CORRECTION_NEEDED) unchanged, and only apply attestation checks to VERIFIED.
string | None
The JWT emitted by create_verification_attestation(). May be None — but if require_attestation=True and the result is VERIFIED, a missing token blocks the decision.
bool
default:"True"
When True (default, mandatory policy), a VERIFIED result without a valid token is downgraded to BLOCKED. When False (advisory policy), attestation is best-effort — a missing token still passes, but a present-and-invalid token still blocks.
list[str] | None
Optional list of trusted issuer DIDs. When set, only tokens signed by an issuer in this list are accepted. Defaults to the AttestationService’s trust anchors.
string | None
Optional original query string. When provided, the token’s qwed.query_hash claim must equal sha256(query), otherwise the decision is blocked. Without it, query binding is not checked.

Claims binding

When a token is present, enforce_trust_decision verifies three bindings against the result before admitting it: Every block decision is logged at WARNING with a structured trust_gate.blocked event (constraint_id, reason, policy) so auditors can replay the enforcement trail.

Fail-closed matrix

Start with require_attestation=False during the rollout window while engines are still being migrated to emit proof_data, then flip it to True once every code path that emits VERIFIED is signing with a proof artifact. That gives you the audit trail without breaking existing gates on day one.

Trust anchors

QWED maintains a registry of trusted attestation issuers:

Attestation chains

Link multiple attestations together:

Use cases

  1. Audit Trails - Prove AI outputs were verified
  2. Compliance - Regulatory verification records
  3. Blockchain - Anchor proofs on-chain
  4. Badges - Show verification status in UIs

Badge integration

Embed attestation badges: