> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cryptographic attestations

> Generate and verify cryptographically signed JWT proofs of verification using ES256 signatures. Store attestations on-chain or verify independently.

## 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

```python theme={null}
result = client.verify(
    "2+2=4",
    include_attestation=True
)

print(result.attestation)
# eyJhbGciOiJFUzI1NiIsInR5cCI6InF3ZWQtYXR0ZXN...
```

## Fail-closed contract

<Warning>
  **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`.
</Warning>

### `AttestationResult`

<ParamField path="status" type="AttestationStatus" required>
  Lifecycle state of the attestation. One of `ISSUED`, `BLOCKED`, or `UNVERIFIABLE`.
</ParamField>

<ParamField path="token" type="string | None">
  Signed JWT string. Present only when `status == ISSUED`; `None` otherwise.
</ParamField>

<ParamField path="error_code" type="string | None">
  Machine-readable failure code: `"SIGNING_FAILURE"` when signing failed, `"CRYPTO_UNAVAILABLE"` when the `cryptography` / `PyJWT` package is not installed, `None` on success.
</ParamField>

<ParamField path="error" type="string | None">
  Human-readable failure detail. `None` on success.
</ParamField>

<ParamField path="is_issued" type="bool">
  Property. `True` only when `status is AttestationStatus.ISSUED`. You must check this flag before you use `token`.
</ParamField>

### `AttestationStatus` values

| Status                          | Meaning                                                                    | `token`    | `error_code`         |
| ------------------------------- | -------------------------------------------------------------------------- | ---------- | -------------------- |
| `ISSUED`                        | QWED signed the attestation.                                               | JWT string | `None`               |
| `BLOCKED`                       | Crypto is available but signing failed (key error, JWT error). Hard block. | `None`     | `SIGNING_FAILURE`    |
| `UNVERIFIABLE`                  | `cryptography` / `PyJWT` not installed. QWED did not attempt signing.      | `None`     | `CRYPTO_UNAVAILABLE` |
| `VALID` / `EXPIRED` / `REVOKED` | Lifecycle states for previously issued attestations during verification.   | —          | —                    |

### Caller pattern

```python theme={null}
from src.qwed_new.core.attestation import create_verification_attestation

result = create_verification_attestation(
    status="VERIFIED",
    verified=True,
    engine="math",
    query="2+2=4",
)

if not result.is_issued:
    # Fail-closed: do not proceed as VERIFIED without a proof artifact
    raise RuntimeError(f"Attestation unavailable [{result.error_code}]: {result.error}")

use(result.token)
```

### 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.

<ParamField path="key_continuity_policy" type="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`.
</ParamField>

`AttestationService.get_issuer_info()` now includes `key_generated_at` and `key_continuity_policy` alongside the existing issuer registry fields.

## Attestation structure

### Header

```json theme={null}
{
  "alg": "ES256",
  "typ": "qwed-attestation+jwt",
  "kid": "did:qwed:node:production#signing-key-2024"
}
```

### Payload

```json theme={null}
{
  "iss": "did:qwed:node:production",
  "sub": "sha256:abc123...",
  "iat": 1703073600,
  "exp": 1734609600,
  "jti": "att_xyz789",
  "qwed": {
    "version": "1.0",
    "result": {
      "status": "VERIFIED",
      "verified": true,
      "engine": "math",
      "confidence": 1.0
    },
    "query_hash": "sha256:def456...",
    "proof_hash": "sha256:ghi789..."
  }
}
```

## Verifying attestations

### Using the API

```python theme={null}
valid, claims, error = client.verify_attestation(jwt)
if valid:
    print(f"Verified by: {claims['iss']}")
```

### Using the SDK

```python theme={null}
from qwed_sdk import verify_attestation

is_valid = verify_attestation(
    jwt="eyJhbGci...",
    trusted_issuers=["did:qwed:node:production"]
)
```

## Trust anchors

QWED maintains a registry of trusted attestation issuers:

| Issuer DID                 | Name            | Status   |
| -------------------------- | --------------- | -------- |
| `did:qwed:node:production` | QWED Production | ✅ Active |
| `did:qwed:node:staging`    | QWED Staging    | ✅ Active |

## Attestation chains

Link multiple attestations together:

```python theme={null}
attestation1 = client.verify("step1", include_attestation=True)
attestation2 = client.verify(
    "step2",
    include_attestation=True,
    chain_id="chain_abc",
    chain_index=1,
    previous_attestation=attestation1.jti
)
```

## 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:

```markdown theme={null}
![Verified](https://api.qwedai.com/badge/attestation/att_xyz789)
```
