# CW1: The Warrant Specification

**Status:** Frozen. `CW1` is the immutable wire designator; any incompatible
change requires a new `CW2~` prefix. **Version:** 1.0 (2026-07).
**Conformance artifact:** [`spec/vectors.json`](../spec/vectors.json) — a
conforming implementation MUST reproduce every vector outcome exactly. The
TypeScript reference (`src/core`) and Python SDK (`sdk/python`) both do.

CW1 defines an **attenuable, offline-verifiable, revocable capability token**
("Warrant") for delegating scoped authority to AI agents, including safe
multi-hop agent-to-agent delegation.

## 1. Terminology

- **Principal** — the human or organization delegating authority.
- **Agent** — the party the authority is delegated to.
- **Issuer** — the service whose root key signs the authority block (`kid`).
- **Warrant** — the token: an ordered chain of signed blocks.
- **Caveat** — a restriction. Authority is the AND of all caveats in all blocks.
- **Attenuation** — appending a block of additional caveats (narrowing).
- **AttenuationKey** — the secret enabling the *next* narrowing. Never shown to
  verifiers; possession permits narrowing only, never widening.
- **Verifier** — any party checking a Warrant against a request. Needs only the
  issuer's public key.

## 2. Cryptographic primitives

- Signatures: **Ed25519** (RFC 8032). Raw 32-byte public keys, 64-byte signatures.
- Hashing (audit chains): **SHA-256**.
- Text encoding of binary values: **base64url without padding** (RFC 4648 §5).

## 3. Canonical JSON

All signed bytes are produced by canonical JSON serialization:

1. Object keys sorted lexicographically (code-unit order), recursively.
2. No whitespace: separators are `,` and `:`.
3. UTF-8 output; non-ASCII characters MUST NOT be escaped.
4. All numbers in CW1 structures are **integers** (timestamps in unix seconds,
   monetary amounts in minor units). Fractional numbers are not permitted
   anywhere in a Warrant.

## 4. Structure

```
Warrant       = { "v": 1, "blocks": [ SignedBlock, ... ] }
SignedBlock   = { "block": Block, "next_pk": b64url(32B), "sig": b64url(64B) }
Block         = { "meta"?: Meta, "caveats": [ Caveat, ... ] }
Meta          = { "v": 1, "jti": string, "principal": string,
                  "agent": string, "iat": int, "kid": string }
```

- `blocks[0]` is the **authority block** and MUST carry `meta`.
- Blocks after index 0 MUST NOT carry `meta`. A verifier MUST reject a chain
  where any later block carries it (a forged second authority).
- `jti` identifies the **root** warrant; every attenuation shares it. Revocation
  is by `jti` and therefore cascades to all sub-delegations.

### 4.1 Signing payload

```
payload(i) = canonical_json_bytes(blocks[i].block) || raw_bytes(blocks[i].next_pk)
```

- `blocks[0].sig` = Ed25519-sign(issuer root secret for `kid`, payload(0))
- `blocks[i].sig` (i>0) = Ed25519-sign(secret matching `blocks[i-1].next_pk`, payload(i))

Each block names a fresh `next_pk`; the matching secret is the AttenuationKey
returned to the holder. Because each signature covers the *next* public key,
blocks cannot be removed, reordered, inserted, or altered without breaking the
chain.

### 4.2 Token encoding

```
token = "CW1~" || base64url(canonical_json_bytes(Warrant))
```

Decoders MUST enforce: token ≤ 131,072 chars; ≤ 64 blocks; ≤ 128 caveats per
block. Violations are malformed-input errors, not caveat denials.

## 5. Verification algorithm

Given a token, a request context, an issuer key resolver, an optional state
provider and an optional revocation oracle:

1. Decode (enforcing §4.2 limits). Reject if `v ≠ 1` or `blocks` empty.
2. Read `meta` from `blocks[0]`; reject if absent or `meta.v ≠ 1`.
3. Resolve the root public key by `meta.kid`; reject if unknown.
4. Walk the chain: for each block *i*, reject if (i>0 and `meta` present), or
   if Ed25519-verify(prev_pk, payload(i), sig) fails. Collect caveats.
   Set prev_pk := next_pk.
5. Reject if the revocation oracle reports `meta.jti` revoked.
6. Evaluate the conjunction of ALL collected caveats against the request (§6).
7. The Warrant authorizes the request iff every step passed.

Verification MUST be total (no exceptions escape) and SHOULD return structured
denial reasons.

## 6. Caveats

Authority = AND of every caveat. **Unknown caveat types MUST fail closed.**

| Type | Fields | Semantics |
|---|---|---|
| `resource` | `patterns: string[]` | request.resource must glob-match ≥1 pattern (§6.1) |
| `action` | `actions: string[]` | request.action ∈ actions |
| `expires` | `at: int` | reject if now > at |
| `not_before` | `at: int` | reject if now < at |
| `spend_limit` | `currency, max: int` | stateful: prior allowed spend under this `jti` in `currency` + request.amount ≤ max. Non-spending requests (no amount) pass; currency mismatch rejects |
| `rate_limit` | `max: int, per: int` | stateful: allowed calls under this `jti` in the last `per` seconds < max |
| `pop` | `key: b64url(32B)` | holder-binding; see §7 |
| `assert` | `key, value: string` | request.facts[key] === value |

### 6.1 Glob matching

`*` matches any run of characters (including `/`); all other characters are
literal. Anchored at both ends. Complexity limits (fail-closed — an over-complex
pattern matches nothing): pattern length ≤ 256; at most 8 `*`.

### 6.2 Stateful caveats

`spend_limit` and `rate_limit` are evaluated against **history** supplied by a
state provider (in the reference, the audit ledger), counting only previously
*allowed* decisions for the same root `jti`. This is what makes caps cumulative
across every action ever taken under a warrant and all its attenuations.

## 7. Proof-of-possession

A `pop` caveat pins a holder public key. The presenter supplies `{sig, ts}`;
the verifier reconstructs the challenge from the **concrete request**:

```
"LICEAT-POP" \n ts \n resource \n action \n (amount|"") \n (currency|"")
```

(The literal `LICEAT-POP` tag is frozen with the wire format.)

Checks: |now − ts| ≤ 120s; Ed25519-verify(pinned key, utf8(challenge), sig).
Binding the challenge to the request prevents a captured proof for one action
being replayed onto another; enforcement points SHOULD additionally reject an
identical `sig` seen twice within the freshness window (replay cache).

`pop` pins one key, so it is intended for **leaf** warrants held by a specific
agent, not for chains that will be further delegated.

## 8. Revocation

Revocation is by root `jti` and cascades to every attenuation. Registries SHOULD
expose a monotonic `epoch` so edge verifiers can cache and refresh cheaply.

## 9. Audit chain (informative)

The reference enforcement point records every decision as a hash-chained entry:
`hash(i) = SHA-256(hash(i−1) || canonical_json_bytes(entry))`, genesis prev = "".
Any mutation of history breaks the chain from that index forward.

## 10. Security considerations

- **Monotonicity** is structural: attenuation appends caveats and evaluation is
  a conjunction, so a sub-warrant can never authorize what its parent denies.
- **Bearer risk**: without `pop`, a Warrant is a bearer token — keep TTLs short,
  scopes narrow, and revocation reachable. With `pop`, theft of the token alone
  is useless.
- **AttenuationKey compromise** permits unwanted *narrowing* only — a nuisance,
  never an escalation.
- **Fail closed everywhere**: unknown caveats, over-complex globs, malformed
  encodings, unknown issuer keys, and (in the reference gateway) a detected
  invariant violation (quarantine) all refuse authority.

## 11. Conformance

An implementation is conforming iff it (a) reproduces every outcome in
`spec/vectors.json`, and (b) fails closed on the malformed/tampered classes in
§4.2, §5 and §6. The vectors are regenerated deterministically by
`scripts/gen-vectors.js`; the committed file is normative.
