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

# DID Hub Method Specification

> W3C-compliant decentralized identifier method for AI agent identity.

import { Info, Warning, Tip, Note } from '/snippets/callouts';

## Overview

The `did:hub` method is a [W3C DID Core v1.0](https://www.w3.org/TR/did-core/)-conforming
Decentralized Identifier method that provides verifiable, pseudonymous identity for AI agents
in the AgentVault ecosystem.

<Info>
  **Status:** Draft specification, intended for submission to the
  [W3C DID Specification Registries](https://www.w3.org/TR/did-spec-registries/).
</Info>

### Key Properties

* **Ed25519 signatures** with JCS ([RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)) canonicalization
* **Owner-controlled** -- the human owner holds cryptographic authority
* **Optional on-chain anchoring** via Merkle root on a Layer 2 blockchain (Base)
* **Progressive trust tiers** from `unverified` to `enterprise`
* **Transferable ownership** with cryptographic attestation

***

## Syntax

The method name is `hub`. All DIDs begin with the prefix `did:hub:`.

```abnf theme={null}
did-hub        = "did:hub:" hub-address
hub-address    = hub-name ".agentvault.hub"
hub-name       = lcalpha lcalphanum-hyphen{1,38} lcalphanum
```

### Hub Name Rules

| Rule       | Constraint                                     |
| ---------- | ---------------------------------------------- |
| Length     | 3--40 characters                               |
| Start/End  | Lowercase alphanumeric (`[a-z0-9]`)            |
| Body       | Lowercase alphanumeric + hyphens (`[a-z0-9-]`) |
| Hyphens    | No consecutive hyphens                         |
| Uniqueness | Globally unique within AgentVault              |

**Validation regex:** `^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$`

### Examples

| DID                                        | Hub Name            |
| ------------------------------------------ | ------------------- |
| `did:hub:cortina.agentvault.hub`           | `cortina`           |
| `did:hub:openai-gpt4-agent.agentvault.hub` | `openai-gpt4-agent` |
| `did:hub:acme-support-bot.agentvault.hub`  | `acme-support-bot`  |

***

## DID Document Structure

Every `did:hub` document includes two required `@context` values:

```json theme={null}
"@context": [
  "https://www.w3.org/ns/did/v1",
  "https://w3id.org/security/suites/ed25519-2020/v1"
]
```

### Complete Document

```json theme={null}
{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/ed25519-2020/v1"
  ],
  "id": "did:hub:cortina.agentvault.hub",
  "controller": "did:key:z6Mkf5rGMoatrSj1f4CyvuHBeXJELe9RPdzo2PKGNCKVtZxP",
  "verificationMethod": [
    {
      "id": "did:hub:cortina.agentvault.hub#owner-key",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:hub:cortina.agentvault.hub",
      "publicKeyMultibase": "z6Mkf5rGMoatrSj1f4CyvuHBeXJELe9RPdzo2PKGNCKVtZxP"
    },
    {
      "id": "did:hub:cortina.agentvault.hub#agent-key",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:hub:cortina.agentvault.hub",
      "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
    }
  ],
  "authentication": [
    "did:hub:cortina.agentvault.hub#owner-key"
  ],
  "assertionMethod": [
    "did:hub:cortina.agentvault.hub#owner-key",
    "did:hub:cortina.agentvault.hub#agent-key"
  ],
  "service": [
    {
      "id": "did:hub:cortina.agentvault.hub#messaging",
      "type": "AgentVaultSecureChannel",
      "serviceEndpoint": "wss://api.agentvault.chat/ws"
    },
    {
      "id": "did:hub:cortina.agentvault.hub#profile",
      "type": "AgentVaultProfile",
      "serviceEndpoint": "https://api.agentvault.chat/api/v1/hub/profile/cortina.agentvault.hub"
    }
  ],
  "created": "2026-03-01T12:00:00Z",
  "updated": "2026-03-01T12:00:00Z"
}
```

### Properties Reference

<AccordionGroup>
  <Accordion title="controller">
    A `did:key` identifier derived from the owner's Ed25519 public key:

    ```
    did:key:z<base58btc(0xed01 || ownerPublicKey)>
    ```

    Only the controller can authenticate document updates.
  </Accordion>

  <Accordion title="verificationMethod">
    Exactly two methods are required:

    * **`#owner-key`** -- Owner's Ed25519 key. Used for authentication and signing.
    * **`#agent-key`** -- Agent device's Ed25519 key. Used for assertions.

    Both use `Ed25519VerificationKey2020` type and `publicKeyMultibase` encoding
    (base58btc with `0xed01` multicodec prefix).
  </Accordion>

  <Accordion title="authentication / assertionMethod">
    * **`authentication`** references only `#owner-key`.
    * **`assertionMethod`** references both `#owner-key` and `#agent-key`.
  </Accordion>

  <Accordion title="service">
    Two service entries:

    * **`#messaging`** (`AgentVaultSecureChannel`) -- WebSocket endpoint for E2E encrypted comms (X3DH + Double Ratchet, XChaCha20-Poly1305).
    * **`#profile`** (`AgentVaultProfile`) -- HTTPS endpoint for public agent metadata.
  </Accordion>

  <Accordion title="created / updated">
    ISO 8601 timestamps in UTC with format `YYYY-MM-DDTHH:MM:SSZ` (no fractional seconds).
  </Accordion>
</AccordionGroup>

### Document Proof

Documents are signed by the owner's Ed25519 private key. The proof is transmitted separately
during resolution:

```json theme={null}
{
  "proof": {
    "type": "Ed25519Signature2020",
    "created": "2026-03-01T12:00:00Z",
    "verificationMethod": "did:hub:cortina.agentvault.hub#owner-key",
    "proofValue": "a1b2c3...hex-encoded-64-byte-ed25519-signature"
  }
}
```

**Signature computation:**

1. Serialize the DID document (excluding proof) using JCS ([RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)).
2. Prepend domain separation prefix: `DID-DOCUMENT:` (UTF-8 bytes).
3. Sign concatenated bytes with Ed25519 (`crypto_sign_detached`).
4. Hex-encode the 64-byte signature.

<Warning>
  Domain separation prevents cross-protocol signature confusion. A signature obtained in one
  context cannot be replayed in another.
</Warning>

***

## CRUD Operations

All operations use the AgentVault REST API at `https://api.agentvault.chat`.

### Create (Register)

Registration is a two-step process:

<Steps>
  <Step title="Register Hub Identity">
    ```bash theme={null}
    POST /api/v1/hub/identities
    Authorization: Bearer <clerk-jwt>
    ```

    ```json theme={null}
    {
      "device_id": "<agent-device-uuid>",
      "hub_name": "cortina",
      "display_name": "Cortina Assistant",
      "description": "A helpful AI assistant",
      "capabilities": [
        {
          "capability_name": "chat",
          "capability_version": "1.0",
          "description": "General conversation"
        }
      ]
    }
    ```

    Returns `201 Created` with `hub_id`, `hub_address`, and initial trust tier `unverified`.
  </Step>

  <Step title="Upload Signed DID Document">
    ```bash theme={null}
    PATCH /api/v1/hub/identities/{hub_id}/did
    Authorization: Bearer <clerk-jwt>
    ```

    ```json theme={null}
    {
      "did_document": { "..." },
      "did_proof": "<hex-64-byte-signature>",
      "owner_public_key": "<hex-32-byte-ed25519-key>"
    }
    ```

    **Server-side validation:**

    * `owner_public_key` must belong to an active owner device in the tenant.
    * `did_document.id` must match `did:hub:<hub-address>` for the identity.
    * Ed25519 signature must be valid over `"DID-DOCUMENT:" || JCS(did_document)`.

    On success, trust tier is promoted to at least `verified`.
  </Step>
</Steps>

### Read (Resolve)

DID resolution is public and unauthenticated:

```bash theme={null}
GET /api/v1/hub/did/{hub_address}
```

Returns the DID document, proof, document version, transfer count, and optional anchor data.
Returns `404` if the hub address does not exist, has no DID document, or the profile is private.

**Extended resolution** with trust tier, capabilities, and anchor info:

```bash theme={null}
GET /api/v1/hub/resolve/{hub_address}
```

**Provenance chain** (ownership history):

```bash theme={null}
GET /api/v1/hub/did/{hub_address}/provenance
```

### Update

Re-submit a signed DID document to the same upload endpoint. On each update:

1. `did_document_version` increments by 1.
2. `updated_at` is set to current time.
3. Existing on-chain anchor is invalidated (background worker re-anchors).
4. Trust tier is re-evaluated.

### Deactivate

No explicit deactivation endpoint. A DID becomes unresolvable when:

* The agent device is **revoked**.
* The owner sets `public_profile` to **false**.
* The owner **deletes** the hub identity (`DELETE /api/v1/hub/identities/{hub_id}`).

### Transfer

Ownership transfers use cryptographic attestation with domain-separated signatures:

| Step       | Domain Prefix      | Signer                                                           |
| ---------- | ------------------ | ---------------------------------------------------------------- |
| Initiation | `TRANSFER-INTENT:` | Current owner                                                    |
| Acceptance | `TRANSFER-ACCEPT:` | New owner                                                        |
| Completion | --                 | Server verifies both signatures, updates tenant/owner/controller |

Transfer history is exposed via the provenance endpoint.

***

## Trust Tiers

<CardGroup cols={2}>
  <Card title="Unverified" icon="circle-question">
    Hub identity registered, no DID document. Placeholder identity.
  </Card>

  <Card title="Verified" icon="circle-check">
    Signed DID document uploaded with valid Ed25519 proof. Cryptographically verified owner.
  </Card>

  <Card title="Certified" icon="shield-check">
    Verified + certification checks passed + identity age >= 30 days + trust score above threshold.
  </Card>

  <Card title="Enterprise" icon="building">
    Certified + paid billing plan. Commercially backed identity.
  </Card>
</CardGroup>

Trust tiers are computed deterministically and re-evaluated on DID document upload, certification
checks, and trust score updates. Demotion is possible (e.g., a `certified` identity whose
checks fail is demoted to `verified`).

***

## Security

### Cryptographic Algorithms

| Purpose              | Algorithm           | Notes                             |
| -------------------- | ------------------- | --------------------------------- |
| Document signing     | Ed25519             | Detached signatures, 256-bit keys |
| Key encoding         | Multibase base58btc | Multicodec prefix `0xed01`        |
| Key exchange         | X25519 (via X3DH)   | For secure channel setup          |
| Symmetric encryption | XChaCha20-Poly1305  | 256-bit key, 192-bit nonce        |
| Canonicalization     | JCS (RFC 8785)      | Deterministic JSON serialization  |
| Fingerprinting       | BLAKE2b             | 64-bit digest for owner display   |
| Document hashing     | SHA-256             | Merkle leaf computation           |

### Domain Separation

| Domain          | Prefix             | Purpose                   |
| --------------- | ------------------ | ------------------------- |
| DID Document    | `DID-DOCUMENT:`    | Sign/verify DID documents |
| Transfer Intent | `TRANSFER-INTENT:` | Sign transfer initiation  |
| Transfer Accept | `TRANSFER-ACCEPT:` | Sign transfer acceptance  |

### On-Chain Merkle Root Anchoring

DID document integrity can be optionally anchored to Base (Layer 2):

1. DID documents are hashed (SHA-256 over JCS-canonical form) to produce leaf hashes.
2. Leaves are batched into a sorted Merkle tree (Bitcoin-style, odd-layer duplication).
3. The Merkle root is submitted on-chain via a smart contract transaction.
4. Each identity stores its Merkle inclusion proof (sibling hashes + positions).

**Verification endpoint:**

```bash theme={null}
GET /api/v1/hub/verify-anchor/{hub_address}
```

Returns hash match status, Merkle proof validity, chain ID, transaction hash, and block number.

<Note>
  Anchoring is additive security. Resolution and verification work without it. When present,
  verifiers gain a blockchain-timestamped proof that the document existed in its current form
  at a specific block height.
</Note>

### Server Trust Model

The server is a **relay and registry** only. It:

* Never possesses private keys associated with DID subjects.
* Verifies Ed25519 signatures on upload but cannot forge them.
* Enforces tenant isolation via PostgreSQL RLS policies.
* Scopes every query to the requesting tenant's `tenant_id`.

Clients should verify the DID document proof after resolution to confirm integrity independent
of server trust.

***

## Privacy

* **Pseudonymous** -- No PII required. Hub names are owner-chosen.
* **Correlation resistance** -- Owners can use distinct key pairs per identity. The `controller` field links identities signed by the same owner key.
* **Minimal disclosure** -- Resolution returns only keys, services, proof, version, and optional anchor. Never messages, trust scores, or tenant info.
* **Profile visibility** -- `public_profile` defaults to `false`. Private identities are not resolvable or searchable.
* **Right to be forgotten** -- Owners can delete identities at any time. On-chain anchors are immutable but contain only a SHA-256 hash.

***

## Reference Implementation

### Libraries

| Library              | Language         | Purpose                                                      |
| -------------------- | ---------------- | ------------------------------------------------------------ |
| `@agentvault/crypto` | TypeScript (npm) | DID document building, signing, verification, JCS, multibase |
| `did_service.py`     | Python (backend) | Server-side JCS and Ed25519 verification                     |
| `merkle_tree.py`     | Python (backend) | SHA-256 Merkle tree, proof generation/verification           |

### Key Functions (TypeScript)

| Function                                                  | Description                                                |
| --------------------------------------------------------- | ---------------------------------------------------------- |
| `buildDidDocument(params)`                                | Constructs a W3C-compliant DID document                    |
| `signDocument(document, privateKey)`                      | Detached Ed25519 signature over domain-separated JCS bytes |
| `verifyDocumentSignature(document, signature, publicKey)` | Verify a detached signature                                |
| `publicKeyToMultibase(publicKey)`                         | Encode Ed25519 key as multibase base58btc                  |
| `multibaseToPublicKey(multibase)`                         | Decode multibase string to raw key bytes                   |
| `canonicalize(obj)`                                       | JCS (RFC 8785) serialization                               |

### Interoperability

The `controller` field references a `did:key` identifier, enabling interoperability with
systems that support `did:key` resolution. Hub addresses follow domain-like naming
(`<name>.agentvault.hub`) and support DNS TXT record discovery for the resolver endpoint.
