A2A Extension: UCAN Security Scheme (Maya v1)

Status: Draft v1 — 2026-05-18 Extension URI: https://projectmaya.deoscomputing.io/a2a/ext/ucan/v1 Maintainer: DEOS Computing (did:web:deos.computing) Depends on: A2A v1.0 (a2a-protocol.org), UCAN v0.10+ (ucan-wg/spec)


1. Motivation

A2A v1.0 ships five SecurityScheme types: APIKey, HTTPAuth, OAuth2, OIDC, mTLS. All five are identity-rooted — the credential proves "I am Alice" and the server looks up an internal policy to decide what Alice may do. In agent-to-agent flows the principal is a chain (user → personal agent → service agent), so identity-rooted auth pushes per-user policy state into every service.

UCAN (ucan-wg/spec) is capability-rooted: the credential names the authorized action inline, signed and delegated down a chain. The server verifies the chain offline and acts. There is no per-user policy lookup at the service.

This extension defines a UcanSecurityScheme type so A2A clients can authenticate with UCAN chains using A2A's existing scheme-negotiation surface.

2. Wire format

2.1 Agent Card declaration

A server advertises UCAN support by including a UcanSecurityScheme entry in its Agent Card securitySchemes:

{
  "securitySchemes": [
    {
      "type": "https://projectmaya.deoscomputing.io/a2a/ext/ucan/v1#UcanSecurityScheme",
      "scheme_id": "maya-ucan",
      "audience_did": "did:web:amazon.com",
      "accepted_capabilities": [
        { "with": "commerce.purchase", "max_amount_usd": 200, "constraints": "amazon.com" },
        { "with": "commerce.return",   "constraints": "amazon.com" }
      ],
      "chain_max_depth": 4,
      "revocation_check_url": "https://amazon.com/a2a/ucan/revocations"
    }
  ],
  "security": ["maya-ucan"],
  "extensions": ["https://projectmaya.deoscomputing.io/a2a/ext/ucan/v1"]
}

Fields:

2.2 Client request

The client presents the UCAN delegation chain via the Authorization HTTP header (per A2A's per-scheme credential carriage):

Authorization: UCAN <comma-separated-token-chain>
A2A-Version: 1.0

Where <comma-separated-token-chain> is the chain of UCAN tokens, leaf-first (the leaf is the UCAN the bearer is invoking; preceding entries are its proof chain back to a root). Each token is base64url-nopad(JCS(token-json)) where token-json is the inline-signature UCAN shape shown below:

{
  "iss": "did:deos:...",
  "aud": "did:deos:...",
  "att": [{"with": "<resource>", "can": "<verb>", ...caveats}],
  "exp": 1746201600,
  "nbf": 1746000000,
  "nnc": "<nonce>",
  "prf": ["<parent-hash-hex>"],
  "s": { "alg": "ed25519", "kid": "did:deos:...#sig-1", "sig": "<base64url-nopad 64 bytes>" }
}

The signature payload is "deos-ucan-v1:" || JCS(token-without-s). The shape is translatable to ucan-wg/spec v1.0 JWTs but is carried inline so a substrate receipt can embed the UCAN in capability.chain[] without re-canonicalization.

Clients MAY alternatively place the chain in the Message.metadata map under https://projectmaya.deoscomputing.io/a2a/ext/ucan/v1#chain as an array of base64url-encoded token strings. Header form is preferred for HTTP; metadata form is required for gRPC.

2.3 Server verification

A conforming server MUST, for every incoming SendMessage/SendStreamingMessage/SubscribeToTask:

  1. Parse the chain. Reject with UNAUTHENTICATED (401) if malformed.
  2. Verify each token's Ed25519 signature against the issuer's DID document, with signature payload computed as "deos-ucan-v1:" || JCS(token-without-s). Reject with UNAUTHENTICATED if any signature is invalid.
  3. Walk the chain leaf-to-root:
    • Each token's aud MUST equal the next token's iss (delegation continuity).
    • Each token's att[] MUST be a subset of the prior token's att[] (capability attenuation).
    • Each token's nbf/exp MUST cover the current time.
  4. The leaf's aud MUST equal the server's audience_did.
  5. The leaf's att[] MUST contain at least one capability matching one entry in accepted_capabilities (matching uses prefix on with and AND-conjunction on caveats).
  6. If revocation_check_url is configured, fetch the current revocation list and reject if any token's CID appears.
  7. If chain length > chain_max_depth, reject with PERMISSION_DENIED (403).

Any failure MUST return one of UNAUTHENTICATED (401), PERMISSION_DENIED (403), or INVALID_ARGUMENT (400) per A2A v1.0 §7.5. Servers MUST NOT reveal which step failed beyond the category — the error message field is opaque diagnostic only.

2.4 Mid-task re-auth

If the server determines mid-task that the current chain doesn't cover a follow-on action (e.g., the task escalated to require a wider scope), it MUST set the task to AUTH_REQUIRED state (per A2A §7.6) and include a Message with metadata containing:

{
  "https://projectmaya.deoscomputing.io/a2a/ext/ucan/v1#required_capability": {
    "with": "commerce.refund",
    "max_amount_usd": 500
  }
}

The client responds via the standard A2A mid-task message flow, including a new UCAN chain that covers the requested capability.

3. Conformance

A v1 server MUST:

A v1 server SHOULD:

A v1 client MUST:

4. Security considerations

5. Reference implementations

All three live in DEOS Computing's working repo, access-controlled during v1 alpha. A public crate / npm package will follow once the wire format stabilizes. §2 is self-contained for implementers who want to start now.

6. Versioning

…/ucan/v1 is immutable. Breaking changes land at …/ucan/v2; v1 stays alive per A2A's backward-compatibility rules.