@metalabel/dfos-protocol 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PROTOCOL.md CHANGED
@@ -1,9 +1,10 @@
1
- # DFOS Protocol: Complete Reference
1
+ # DFOS Protocol
2
2
 
3
- - **Date**: 2026-03-10
4
- - **Status**: Implemented and tested — TypeScript + Go + Python + Rust + Swift cross-language verification. This spec is currently under review. Discuss the DFOS protocol in the [clear.txt](https://clear.dfos.com) space.
5
- - **Source**: `packages/dfos-protocol` (self-contained, zero monorepo dependencies, OSS-ready)
6
- - **Gist**: https://gist.github.com/bvalosek/ed4c96fd4b841302de544ffaee871648 (synced from this file)
3
+ Verifiable identity and content chains — Ed25519 signatures, content-addressed CIDs, W3C DIDs. Cross-language verification in TypeScript, Go, Python, Rust, and Swift.
4
+
5
+ This spec is under active review. Discuss it in the [clear.txt](https://clear.dfos.com) space on DFOS.
6
+
7
+ [Source](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol) · [npm](https://www.npmjs.com/package/@metalabel/dfos-protocol) · [Gist](https://gist.github.com/bvalosek/ed4c96fd4b841302de544ffaee871648)
7
8
 
8
9
  ---
9
10
 
@@ -13,7 +14,7 @@ DFOS is a dark forest operating system. Content lives in private spaces — visi
13
14
 
14
15
  But the cryptographic proof layer is public and verifiable. Every piece of content, every identity, every edit has a signed chain of commitments that anyone can independently verify. You don't need to trust the platform. You don't need access to the database. You need a public key and a chain of JWS tokens.
15
16
 
16
- If you have content — from the official app, from an API export, from a screenshot someone sent you, from a pirate mirror, from anywhere — you can verify it's authentic. Hash the content, check the CID, walk the chain, verify the signature. The content is dark; the proof is light.
17
+ If you have content — from the official app, from an API export, from a pirate mirror, from anywhere — you can verify it's authentic. Hash the content, check the CID, walk the chain, verify the signature. The content is dark; the proof is light.
17
18
 
18
19
  The protocol makes this verification radically simple. Two chain types — identity and content — using the same mechanics: Ed25519 signatures, JWS compact tokens, content-addressed CIDs. The protocol is deliberately minimal. It knows about keys and document hashes. It doesn't know about posts, profiles, or any application concept. Document semantics are entirely application layer — free to evolve without protocol changes.
19
20
 
@@ -23,23 +24,20 @@ The result: a signed content ledger that any standard EdDSA library can verify,
23
24
 
24
25
  ---
25
26
 
26
- This document is a complete, self-contained reference for the DFOS protocol. All artifacts are deterministic and reproducible from fixed seeds. An independent implementer can verify every value using standard Ed25519 + dag-cbor libraries.
27
-
28
- **To regenerate**: `pnpm --filter @metalabel/dfos-protocol exec vitest run tests/protocol-reference.spec.ts`
27
+ All artifacts in this document are deterministic and reproducible from fixed seeds. An independent implementer can verify every value using standard Ed25519 + dag-cbor libraries.
29
28
 
30
29
  ---
31
30
 
32
31
  ## Protocol Overview
33
32
 
34
- The DFOS protocol has three layers:
33
+ The DFOS protocol has two layers:
35
34
 
36
35
  | Layer | Concern |
37
36
  | --------------------- | ---------------------------------------------------------------------------- |
38
37
  | **Crypto core** | Identity chains + content chains — Ed25519 signatures, JWS tokens, CID links |
39
38
  | **Document envelope** | Standard wrapper: `content` + `baseDocumentCID` + `createdByDID` + timestamp |
40
- | **Content schemas** | JSON Schema definitions for what goes inside `content` (post, profile, etc.) |
41
39
 
42
- The crypto core is the trust boundary — everything below it is cryptographically verified. The document envelope provides structural metadata (attribution, edit lineage, timestamps). Content schemas define the application-level semantics.
40
+ The crypto core is the trust boundary — everything below it is cryptographically verified. The document envelope provides structural metadata (attribution, edit lineage, timestamps). What goes inside the envelope's `content` field is application-defined — see the [DFOS Content Model](https://protocol.dfos.com/content-model) for the standard schema library.
43
41
 
44
42
  ### Crypto Core: Two Chain Types
45
43
 
@@ -60,7 +58,7 @@ Every document committed to by a content chain uses a standard envelope, defined
60
58
  ```json
61
59
  {
62
60
  "content": { "$schema": "https://schemas.dfos.com/post/v1", ... },
63
- "baseDocumentCID": "bafyrei..." | null,
61
+ "baseDocumentCID": null,
64
62
  "createdByDID": "did:dfos:...",
65
63
  "createdAt": "2026-03-07T00:02:00.000Z"
66
64
  }
@@ -69,25 +67,27 @@ Every document committed to by a content chain uses a standard envelope, defined
69
67
  | Field | Type | Description |
70
68
  | ----------------- | ------------ | --------------------------------------------------------------------------- |
71
69
  | `content` | object | Application-defined content — must include `$schema` URI, opaque to chains |
72
- | `baseDocumentCID` | string\|null | CID of the previous document version (edit lineage). Null for first version |
70
+ | `baseDocumentCID` | string\|null | Optional CID of a prior document version. Semantics are application-defined |
73
71
  | `createdByDID` | string | DID of the identity that created this document version |
74
72
  | `createdAt` | ISO 8601 | When this document version was created |
75
73
 
76
- The `documentCID` in a content chain operation is `CID(dagCborEncode(envelope))`. The envelope provides attribution and edit history at the protocol level. The `content` field is where application-defined JSON Schema types live. The `content` object must include a `$schema` property identifying its content type — this makes every document self-describing and its schema cryptographically committed via the CID.
77
-
78
- ### Content Schemas
79
-
80
- The `content` field inside the document envelope is validated by JSON Schema. The protocol ships a standard library of schemas (post, profile) — see [Standard Document Schemas](#standard-document-schemas). These are conventions, not requirements. Any implementation can define custom schemas.
74
+ The `documentCID` in a content chain operation is `CID(dagCborEncode(envelope))`. The envelope provides attribution at the protocol level. The `content` object must include a `$schema` property identifying its content type — this makes every document self-describing and its schema cryptographically committed via the CID.
81
75
 
82
76
  ### Addressing
83
77
 
84
78
  Three canonical representations:
85
79
 
86
- | Thing | Form | Example |
87
- | ---------------------- | -------------------------- | ------------------------------------------------------------- |
88
- | Operation or document | CID (dag-cbor + SHA-256) | `bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy` |
89
- | Entity (content chain) | `<hash>` (bare, no prefix) | `67t27rzc83v7c22n9t6z7c` |
90
- | Identity (key chain) | `did:dfos:<hash>` | `did:dfos:e3vvtck42d4eacdnzvtrn6` |
80
+ | Thing | Form | Example |
81
+ | ---------------------- | -------------------------- | --------------------------------- |
82
+ | Operation or document | CID (dag-cbor + SHA-256) | See below |
83
+ | Entity (content chain) | `<hash>` (bare, no prefix) | `67t27rzc83v7c22n9t6z7c` |
84
+ | Identity (key chain) | `did:dfos:<hash>` | `did:dfos:e3vvtck42d4eacdnzvtrn6` |
85
+
86
+ Example CID:
87
+
88
+ ```
89
+ bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy
90
+ ```
91
91
 
92
92
  Operations and documents are CIDs — standard IPLD content addresses. Entities and identities are derived identifiers — `customAlpha(SHA-256(genesis CID bytes))`. Same derivation for both. Identity chains prepend `did:dfos:` (W3C DID spec). Entity identifiers are bare — just the 22-char hash, no prefix.
93
93
 
@@ -139,31 +139,17 @@ This is a deliberate asymmetry with identity chains. Identity chains are self-so
139
139
  - Whether a chain must have a single signer or may have multiple signers
140
140
  - Ownership or attribution semantics between signers and entities
141
141
 
142
- ### Terminal States
143
-
144
- **`delete` is the only terminal state.** No valid operations may follow a delete in either chain type. An implementation MUST reject any operation that appears after a delete.
142
+ ### Terminal States and Special Operations
145
143
 
146
- `delete` is a terminal marker that prevents future operations on the chain but does NOT remove data. The complete chain including all prior operations and their signatures — MUST remain intact for verification. Any party holding the chain can still walk it, verify every signature, and confirm the history up to and including the delete. Data removal (e.g., purging content from storage) is an application-layer concern, not a protocol operation.
144
+ **`delete` is the only terminal state.** No valid operations may follow a delete. An implementation MUST reject any operation after a delete. Delete prevents future operations but does NOT remove data the complete chain remains intact for verification. Data removal is an application concern.
147
145
 
148
- ### Controller Key Requirement
146
+ **Controller key requirement:** `update` operations on identity chains MUST include at least one controller key. If decommissioning is intended, `delete` is the correct terminal operation.
149
147
 
150
- `update` operations on identity chains MUST include at least one controller key. Validation MUST reject any `update` with an empty `controllerKeys` array. This ensures that an identity always has a path forward if decommissioning is intended, `delete` is the correct terminal operation.
151
-
152
- ### Content-Null Semantics
153
-
154
- An `update` operation on a content chain with `documentCID: null` means **the entity exists but its current content is cleared**. This is not a delete — the chain continues, and a subsequent update can set content again. Think of it as "unpublish" rather than "destroy."
148
+ **Content-null:** An `update` on a content chain with `documentCID: null` means the entity exists but its content is cleared. The chain continues a subsequent update can set content again.
155
149
 
156
150
  ### `typ` Header
157
151
 
158
- The JWS `typ` header (`did:dfos:identity-op`, `did:dfos:content-op`) is advisory — it aids routing and dispatch but is not a security-critical field. Verification checks the signature and chain integrity, not the `typ` value. Implementations SHOULD validate `typ` for correctness but MUST NOT rely on it for security decisions.
159
-
160
- ### JWT `kid` vs Operation `kid`
161
-
162
- JWT tokens (for device auth, MCP sessions, etc.) use `kid` as a simple key identifier for lookup — e.g., `key_ez9a874tckr3dv933d3ckd`. This does NOT follow the same DID URL convention used in operation JWS headers. Operation `kid` uses bare key ID for identity genesis and DID URL (`did:dfos:xxx#key_id`) for everything else. JWT `kid` is always a bare key ID — the JWT's `sub` claim carries the DID separately.
163
-
164
- ### ID Modulo Bias
165
-
166
- The ID encoding uses `byte % 19` where each byte ranges 0-255. Since 256 is not evenly divisible by 19, values 0-8 (alphabet positions) appear with probability ~5.26% while values 9-18 appear with probability ~5.22%. This is a ~0.3% bias — not security-relevant for identifiers but acknowledged here for completeness. A rejection-sampling approach (retry if `byte >= 247`) would eliminate the bias entirely.
152
+ The JWS `typ` header (`did:dfos:identity-op`, `did:dfos:content-op`) aids routing but is not security-critical. Implementations SHOULD validate it but MUST NOT rely on it for security decisions.
167
153
 
168
154
  ### Operation Field Limits
169
155
 
@@ -188,84 +174,6 @@ The protocol does NOT limit:
188
174
 
189
175
  ---
190
176
 
191
- ## Standard Document Schemas
192
-
193
- The crypto core commits to `documentCID` values without inspecting their contents. The document envelope provides structural metadata. The **content** inside the envelope is where JSON Schema validation applies.
194
-
195
- The protocol ships a standard library of content schemas as JSON Schema (draft 2020-12) definitions. These are not required — any implementation can define its own content types. They are provided as a starting point for content built on the DFOS protocol, and they are what DFOS uses internally.
196
-
197
- ### Schema Convention
198
-
199
- Documents declare their type via a `$schema` field pointing to a schema URI:
200
-
201
- ```json
202
- {
203
- "$schema": "https://schemas.dfos.com/post/v1",
204
- "format": "short-post",
205
- "body": "Hello world."
206
- }
207
- ```
208
-
209
- Because the `$schema` field is part of the document, it is behind the `documentCID` — cryptographically committed in the content chain. Any verifier can resolve the document, read `$schema`, and validate against the schema.
210
-
211
- ### Schema Evolution
212
-
213
- Schemas are versioned via the URI path (`/post/v1`, `/post/v2`). Evolution rules:
214
-
215
- - **Strictly additive within a version** — new optional fields can be added to an existing version at any time without breaking existing documents
216
- - **Breaking changes require a new version** — removing fields, changing types, or adding new required fields means a new version URI
217
- - **Implementations declare which versions they understand** — a registry or application can accept `post/v1` and `post/v2` simultaneously, or only `post/v1`
218
-
219
- ### Standard Schemas
220
-
221
- Schema files live in `schemas/` in the protocol package. Each is a standalone JSON Schema (draft 2020-12).
222
-
223
- #### Post (`https://schemas.dfos.com/post/v1`)
224
-
225
- The primary content type. Covers short posts, long-form posts, comments, and replies via the `format` discriminator.
226
-
227
- | Field | Type | Required | Description |
228
- | ------------- | -------- | -------- | ---------------------------------------------------------------------------------- |
229
- | `$schema` | string | yes | `"https://schemas.dfos.com/post/v1"` |
230
- | `format` | enum | yes | `"short-post"`, `"long-post"`, `"comment"`, `"reply"` — immutable, set at creation |
231
- | `title` | string | no | Post title (typically for long-post format) |
232
- | `body` | string | no | Post body content |
233
- | `cover` | media | no | Cover image |
234
- | `attachments` | media[] | no | Attached media objects |
235
- | `topics` | string[] | no | Topic names (stored as names for portability) |
236
-
237
- #### Profile (`https://schemas.dfos.com/profile/v1`)
238
-
239
- The displayable identity for any agent, person, group, or space.
240
-
241
- | Field | Type | Required | Description |
242
- | ------------- | ------ | -------- | --------------------------------------- |
243
- | `$schema` | string | yes | `"https://schemas.dfos.com/profile/v1"` |
244
- | `name` | string | no | Display name |
245
- | `description` | string | no | Short bio or description |
246
- | `avatar` | media | no | Avatar image |
247
- | `banner` | media | no | Banner image |
248
- | `background` | media | no | Background image |
249
-
250
- ### Media Object
251
-
252
- Several schemas reference media objects. The standard representation:
253
-
254
- ```json
255
- {
256
- "id": "media_abc123",
257
- "uri": "https://cdn.example.com/media/abc123.jpg"
258
- }
259
- ```
260
-
261
- `id` is required (opaque identifier). `uri` is optional.
262
-
263
- ### Custom Schemas
264
-
265
- Any implementation can define custom document schemas following the same pattern — a JSON Schema with a `$schema` const field pointing to a unique URI. The protocol will commit to the document via CID regardless of what's inside. The standard schemas are conventions, not constraints.
266
-
267
- ---
268
-
269
177
  ## Standards and Dependencies
270
178
 
271
179
  | Component | Standard / Library |
@@ -275,7 +183,6 @@ Any implementation can define custom document schemas following the same pattern
275
183
  | Key encoding | W3C Multikey (multicodec `0xed01` + base58btc multibase) |
276
184
  | Signed envelopes | JWS Compact Serialization (RFC 7515) with `alg: "EdDSA"` |
277
185
  | Content addressing | CIDv1 with dag-cbor codec (`0x71`) + SHA-256 multihash (`0x12`) |
278
- | Auth tokens | JWT (RFC 7519) with `alg: "EdDSA"` |
279
186
  | ID encoding | SHA-256 → custom 19-char alphabet, 22 characters |
280
187
 
281
188
  ### ID Alphabet
@@ -286,7 +193,7 @@ Length: 22 characters
286
193
  Entropy: ~93.4 bits (19^22)
287
194
  ```
288
195
 
289
- Process: `SHA-256(input) → for each of first 22 bytes: alphabet[byte % 19]`
196
+ Process: `SHA-256(input) → for each of first 22 bytes: alphabet[byte % 19]`. The modulo introduces a ~0.3% bias (256 is not evenly divisible by 19) — not security-relevant for identifiers.
290
197
 
291
198
  DIDs: `did:dfos:` + 22-char ID derived from `SHA-256(genesis CID raw bytes)`
292
199
  Key IDs: `key_` + 22-char ID. Convention: derive from public key hash (`key_` + `customAlpha(SHA-256(publicKey))`), making key IDs deterministic and verifiable. Not a protocol requirement — key IDs can be any string.
@@ -443,11 +350,18 @@ token = signingInput + "." + base64url(signature)
443
350
 
444
351
  ### kid Rules
445
352
 
446
- | Context | kid format | Example |
447
- | ------------------------- | ----------- | ------------------------------------------------------------ |
448
- | Identity create (genesis) | Bare key ID | `key_r9ev34fvc23z999veaaft8` |
449
- | Identity update/delete | DID URL | `did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8` |
450
- | All content ops | DID URL | `did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd` |
353
+ | Context | kid format | Example |
354
+ | ------------------------- | ----------- | ---------------------------- |
355
+ | Identity create (genesis) | Bare key ID | `key_r9ev34fvc23z999veaaft8` |
356
+ | Identity update/delete | DID URL | See below |
357
+ | All content ops | DID URL | See below |
358
+
359
+ DID URL examples:
360
+
361
+ ```
362
+ did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8
363
+ did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd
364
+ ```
451
365
 
452
366
  ### `cid` Header
453
367
 
@@ -599,7 +513,11 @@ JWS Token:
599
513
  eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw
600
514
  ```
601
515
 
602
- Operation CID: `bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy`
516
+ Operation CID:
517
+
518
+ ```
519
+ bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy
520
+ ```
603
521
 
604
522
  **Derived DID: `did:dfos:e3vvtck42d4eacdnzvtrn6`**
605
523
 
@@ -660,7 +578,11 @@ JWS Token:
660
578
  eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiNrZXlfcjlldjM0ZnZjMjN6OTk5dmVhYWZ0OCIsImNpZCI6ImJhZnlyZWljeW00Y3lpZWRubGQ3M3NtYngzMnN6YWVpN3hkdWxxbjRnM3N0ZTVlMncydWxhanIzb3FtIn0.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSIsImF1dGhLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJhc3NlcnRLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJjb250cm9sbGVyS2V5cyI6W3siaWQiOiJrZXlfZXo5YTg3NHRja3IzZHY5MzNkM2NrZCIsInR5cGUiOiJNdWx0aWtleSIsInB1YmxpY0tleU11bHRpYmFzZSI6Ino2TWtmVWQ2NUpyQWhmZGdGdU1DY2NVOVRoUXZqQjJmSkFNVUhrdXVhakY5OTJnSyJ9XSwiY3JlYXRlZEF0IjoiMjAyNi0wMy0wN1QwMDowMTowMC4wMDBaIn0.MScuoBlgOK3j5QX9tFcw1ou0o4LgJziGJEsZ5pvqiBr1SagAyAv5h-wajQhtg8IP7dLlM0U4leW2iRra945cDg
661
579
  ```
662
580
 
663
- Operation CID: `bafyreicym4cyiednld73smbx32szaei7xdulqn4g3ste5e2w2ulajr3oqm`
581
+ Operation CID:
582
+
583
+ ```
584
+ bafyreicym4cyiednld73smbx32szaei7xdulqn4g3ste5e2w2ulajr3oqm
585
+ ```
664
586
 
665
587
  Post-rotation: DID unchanged (`did:dfos:e3vvtck42d4eacdnzvtrn6`), controller rotated to `key_ez9a874tckr3dv933d3ckd`.
666
588
 
@@ -682,7 +604,11 @@ Document (application layer):
682
604
  }
683
605
  ```
684
606
 
685
- Document CID: `bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne`
607
+ Document CID:
608
+
609
+ ```
610
+ bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne
611
+ ```
686
612
 
687
613
  Content Create JWS Header:
688
614
 
@@ -719,7 +645,11 @@ Content Create JWS Token:
719
645
  eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWE1ejd6eGtuYWU1ZHM3MmV1aWh1ZjJyZzNpeGw2dDRmYnpqZWZoY29nZzNucXBweW9ncXUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZG9jdW1lbnRDSUQiOiJiYWZ5cmVpZnB2d3Vhcm1sNjJzZm9nZHBpMnZsbHR2ZzJldjZvNHh0dzc0emZ1ZDdjcGtnNzQyNnpuZSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.t_DDkJ_TmNekIGUFO22G-W78QoE4XTg9LKQ4gzAQHaK3B6491Tir9b-wtp-hcwmENu2Hqnieqv5ASiqfFrEbDw
720
646
  ```
721
647
 
722
- Content Operation CID: `bafyreia5z7zxknae5ds72euihuf2rg3ixl6t4fbzjefhcogg3nqppyogqu`
648
+ Content Operation CID:
649
+
650
+ ```
651
+ bafyreia5z7zxknae5ds72euihuf2rg3ixl6t4fbzjefhcogg3nqppyogqu
652
+ ```
723
653
 
724
654
  ### Content Chain: Update
725
655
 
@@ -752,34 +682,16 @@ Updated document:
752
682
  }
753
683
  ```
754
684
 
755
- Document CID (edited): `bafyreieuo26zfmjxwpmw5jk6bqzqhvivxcbckgxtyeuc7ypf3p4sihgq4q`
756
- Content Update CID: `bafyreibb4lsvqmz4j76rsvhkqw3v2b4vp23t7dimm6vl5g5wlninvkemxq`
757
-
758
- ### EdDSA JWT
759
-
760
- Header:
685
+ Document CID (edited):
761
686
 
762
- ```json
763
- { "alg": "EdDSA", "typ": "JWT", "kid": "key_ez9a874tckr3dv933d3ckd" }
764
687
  ```
765
-
766
- Payload:
767
-
768
- ```json
769
- {
770
- "iss": "dfos",
771
- "sub": "did:dfos:e3vvtck42d4eacdnzvtrn6",
772
- "aud": "dfos-api",
773
- "exp": 1772902800,
774
- "iat": 1772899200,
775
- "jti": "session_ref_example_01"
776
- }
688
+ bafyreieuo26zfmjxwpmw5jk6bqzqhvivxcbckgxtyeuc7ypf3p4sihgq4q
777
689
  ```
778
690
 
779
- JWT Token:
691
+ Content Update CID:
780
692
 
781
693
  ```
782
- eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIn0.eyJpc3MiOiJkZm9zIiwic3ViIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImF1ZCI6ImRmb3MtYXBpIiwiZXhwIjoxNzcyOTAyODAwLCJpYXQiOjE3NzI4OTkyMDAsImp0aSI6InNlc3Npb25fcmVmX2V4YW1wbGVfMDEifQ.zhKeXJHHF7a1-MwF4QoUTRptCplAwh20-rLnuWGDFT6uJheN4E_SA5NhqvMNflLHxd7h97gdaVnMZGE67SXEBA
694
+ bafyreibb4lsvqmz4j76rsvhkqw3v2b4vp23t7dimm6vl5g5wlninvkemxq
783
695
  ```
784
696
 
785
697
  ---
@@ -788,86 +700,87 @@ eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tk
788
700
 
789
701
  Given the artifacts above, verify:
790
702
 
791
- 1. **Multikey decode**: `z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb` → strip `z`, base58btc decode, strip `[0xed, 0x01]` → public key `ba421e272fad4f941c221e47f87d9253bdc04f7d4ad2625ae667ab9f0688ce32`
703
+ 1. **Multikey decode**: strip `z`, base58btc decode, strip `[0xed, 0x01]` prefix raw public key:
704
+
705
+ ```
706
+ z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb
707
+ → ba421e272fad4f941c221e47f87d9253bdc04f7d4ad2625ae667ab9f0688ce32
708
+ ```
792
709
 
793
710
  2. **Genesis JWS verify**: split token on `.`, take first two segments as signing input (UTF-8 bytes), base64url-decode third segment as 64-byte signature, `ed25519.verify(signature, signingInputBytes, publicKey)` → true. Note the header now contains `cid` alongside `alg`, `typ`, and `kid`.
794
711
 
795
- 3. **Genesis CID**: base64url-decode JWS payload → parse JSON → dag-cbor canonical encode → SHA-256 → CIDv1 → should be `bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy`
712
+ 3. **Genesis CID**: base64url-decode JWS payload → parse JSON → dag-cbor canonical encode → SHA-256 → CIDv1 → should be:
713
+
714
+ ```
715
+ bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy
716
+ ```
796
717
 
797
718
  4. **CID header**: Verify each operation JWS header contains `cid` matching the derived operation CID
798
719
 
799
720
  5. **DID derivation**: take raw CID bytes of genesis CID → SHA-256 → first 22 bytes → `byte % 19` → alphabet lookup → should be `e3vvtck42d4eacdnzvtrn6` → DID = `did:dfos:e3vvtck42d4eacdnzvtrn6`
800
721
 
801
- 6. **Rotation JWS**: kid = `did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8` — signed by OLD controller key (key 1). Verify with key 1's public key.
722
+ 6. **Rotation JWS**: signed by OLD controller key (key 1). Verify with key 1's public key. kid:
802
723
 
803
- 7. **Content create JWS**: kid = `did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd` — signed by NEW controller key (key 2, post-rotation). Verify with key 2's public key.
724
+ ```
725
+ did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8
726
+ ```
804
727
 
805
- 8. **Document CID**: dag-cbor canonical encode the document JSON SHA-256 CIDv1 should be `bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne`
728
+ 7. **Content create JWS**: signed by NEW controller key (key 2, post-rotation). Verify with key 2's public key. kid:
806
729
 
807
- 9. **Content chain integrity**: update's `previousOperationCID` matches create's operation CID
730
+ ```
731
+ did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd
732
+ ```
808
733
 
809
- 10. **JWT verify**: same signing mechanics as JWS `ed25519.verify(signature, UTF8(header.payload), key2_publicKey)` true. Check `exp > currentTime`, `iss == "dfos"`, `aud == "dfos-api"`.
734
+ 8. **Document CID**: dag-cbor canonical encode the document JSON SHA-256CIDv1 should be:
810
735
 
811
- ---
736
+ ```
737
+ bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne
738
+ ```
812
739
 
813
- ## Source Code Reference
814
-
815
- All source lives in `packages/dfos-protocol/` self-contained, zero monorepo dependencies.
816
-
817
- | File | Contents |
818
- | ----------------------------------- | -------------------------------------------------------------------------------------------------- |
819
- | `src/crypto/ed25519.ts` | `createNewEd25519Keypair`, `importEd25519Keypair`, `signPayloadEd25519`, `isValidEd25519Signature` |
820
- | `src/crypto/jws.ts` | `createJws`, `verifyJws`, `decodeJwsUnsafe`, `JwsVerificationError` |
821
- | `src/crypto/jwt.ts` | `createJwt`, `verifyJwt`, `decodeJwtUnsafe` (EdDSA only) |
822
- | `src/crypto/base64url.ts` | `base64urlEncode`, `base64urlDecode` |
823
- | `src/crypto/multiformats.ts` | `dagCborCanonicalEncode`, `dagCborCanonicalEqual` |
824
- | `src/crypto/id.ts` | `generateId`, `generateIdNoPrefix`, `isValidId` |
825
- | `src/chain/multikey.ts` | `encodeEd25519Multikey`, `decodeMultikey` |
826
- | `src/chain/schemas.ts` | `IdentityOperation`, `ContentOperation`, `MultikeyPublicKey`, `VerifiedIdentity` |
827
- | `src/chain/identity-chain.ts` | `signIdentityOperation`, `verifyIdentityChain` |
828
- | `src/chain/content-chain.ts` | `signContentOperation`, `verifyContentChain` |
829
- | `src/chain/derivation.ts` | `deriveChainIdentifier` |
830
- | `src/registry/schemas.ts` | Registry API Zod types (wire contract) |
831
- | `src/registry/server.ts` | Reference Hono registry server |
832
- | `src/registry/store.ts` | In-memory chain store with linear enforcement |
833
- | `openapi.yaml` | OpenAPI 3.1 spec for registry API |
834
- | `schemas/document-envelope.v1.json` | JSON Schema for the document envelope wrapper |
835
- | `schemas/post.v1.json` | JSON Schema for post documents |
836
- | `schemas/profile.v1.json` | JSON Schema for profile documents |
837
- | `tests/protocol-reference.spec.ts` | Deterministic artifact generator (this doc's source) |
838
- | `verify/go/` | Go cross-language verification (9 tests) |
839
- | `verify/python/` | Python cross-language verification (32 checks) |
840
- | `verify/rust/` | Rust cross-language verification (9 tests) |
841
- | `verify/swift/` | Swift cross-language verification (8 tests) |
740
+ 9. **Content chain integrity**: update's `previousOperationCID` matches create's operation CID
741
+
742
+ 10. **Chain completeness**: all operation CIDs, DID derivation, key rotation, and content chain linkage verified end-to-end.
842
743
 
843
744
  ---
844
745
 
845
- ## Test Coverage (160 checks across 5 languages)
746
+ ## Source and Verification
846
747
 
847
- ### TypeScript dfos-protocol (99 tests)
748
+ All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol) — self-contained, zero monorepo dependencies. 160 checks across 5 languages.
848
749
 
849
- - `tests/crypto.spec.ts` (13): ed25519 keypair/sign/verify, JWS round-trip/wrong key/tampered/decode/malformed
850
- - `tests/chain.spec.ts` (39): multikey encoding, identity chain (genesis, DID, rotation, delete, cid header, errors), content chain (lifecycle, clear, delete, cid header, errors)
851
- - `tests/registry.spec.ts` (18): HTTP contract submission, resubmission, extension, fork rejection, pagination, cross-chain key resolution, 404s
852
- - `tests/schemas.spec.ts` (28): JSON Schema compilation + validation for document envelope, post, profile conforming documents, missing fields, invalid values, additional properties
853
- - `tests/protocol-reference.spec.ts` (1): deterministic artifact generator
750
+ - [`crypto/ed25519`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/ed25519.ts) `createNewEd25519Keypair`, `importEd25519Keypair`, `signPayloadEd25519`, `isValidEd25519Signature`
751
+ - [`crypto/jws`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/jws.ts) `createJws`, `verifyJws`, `decodeJwsUnsafe`
752
+ - [`crypto/base64url`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/base64url.ts) — `base64urlEncode`, `base64urlDecode`
753
+ - [`crypto/multiformats`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/multiformats.ts) — `dagCborCanonicalEncode`, `dagCborCanonicalEqual`
754
+ - [`crypto/id`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/id.ts) `generateId`, `generateIdNoPrefix`, `isValidId`
755
+ - [`chain/multikey`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/multikey.ts) — `encodeEd25519Multikey`, `decodeMultikey`
756
+ - [`chain/schemas`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/schemas.ts) — `IdentityOperation`, `ContentOperation`, `MultikeyPublicKey`, `VerifiedIdentity`
757
+ - [`chain/identity-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/identity-chain.ts) — `signIdentityOperation`, `verifyIdentityChain`
758
+ - [`chain/content-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/content-chain.ts) — `signContentOperation`, `verifyContentChain`
759
+ - [`chain/derivation`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/derivation.ts) — `deriveChainIdentifier`
854
760
 
855
- ### Python — verify/ (35 checks)
761
+ ### Related Specifications
856
762
 
857
- - Key derivation, multikey, dag-cbor bytes, CID/DID derivation, JWS/JWT signatures, CID header verification, document CID
858
- - Dependencies: `pynacl`, `dag-cbor`, `base58`
763
+ - [DID Method: `did:dfos`](https://protocol.dfos.com/did-method) W3C DID method specification for identity chains
764
+ - [Content Model](https://protocol.dfos.com/content-model) — Standard content schemas (post, profile) for the document envelope
765
+ - [Registry API](https://protocol.dfos.com/registry-api) — HTTP API for chain storage and resolution
859
766
 
860
- ### Go — verify/ (9 tests)
767
+ ### Cross-Language Verification
861
768
 
862
- - Key derivation, multikey, dag-cbor, CID/DID derivation, JWS/JWT signatures, CID header verification
863
- - Dependencies: `fxamacker/cbor/v2`, `mr-tron/base58`
769
+ | Language | Tests | Source |
770
+ | ---------- | ----- | ---------------------------------------------------------------------------------------------------- |
771
+ | TypeScript | 99 | [`tests/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/tests) |
772
+ | Python | 35 | [`verify/python/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/python) |
773
+ | Go | 9 | [`verify/go/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/go) |
774
+ | Rust | 9 | [`verify/rust/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/rust) |
775
+ | Swift | 8 | [`verify/swift/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/swift) |
864
776
 
865
- ### Rust — verify/ (9 tests)
777
+ ---
866
778
 
867
- - Key derivation, multikey, dag-cbor, CID/DID derivation, JWS/JWT signatures, CID header verification
868
- - Dependencies: `ed25519-dalek`, `ciborium`, `sha2`, `bs58`, `base64`, `data-encoding`
779
+ ## Special Thanks
869
780
 
870
- ### Swiftverify/ (8 tests)
781
+ - **Vinny Bellavia** [stcisgood.com](https://stcisgood.com)
782
+ - **Allison Clift-Jennings** — [Jura Labs](https://juralabs.com)
783
+
784
+ ---
871
785
 
872
- - Key derivation, multikey, CID/DID derivation, JWS/JWT signatures, CID header verification
873
- - Dependencies: Apple `Crypto` (swift-crypto)
786
+ Yancey · Ilya · Brandon · Lena
package/README.md CHANGED
@@ -27,9 +27,15 @@ import { createRegistryServer } from '@metalabel/dfos-protocol/registry';
27
27
  | `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
28
28
  | `@metalabel/dfos-protocol/registry` | Hono-based registry server, in-memory store, Zod schemas |
29
29
 
30
- ## Protocol Specification
30
+ ## Specifications
31
31
 
32
- See [PROTOCOL.md](./PROTOCOL.md) for the complete protocol specification with worked examples and test vectors.
32
+ | Document | Description |
33
+ | -------------------------------------- | -------------------------------------------------------------- |
34
+ | [PROTOCOL.md](./PROTOCOL.md) | Core protocol — chains, signatures, verification, test vectors |
35
+ | [DID-METHOD.md](./DID-METHOD.md) | W3C DID method specification for `did:dfos` |
36
+ | [CONTENT-MODEL.md](./CONTENT-MODEL.md) | Standard content schemas (post, profile, media) |
37
+ | [REGISTRY-API.md](./REGISTRY-API.md) | HTTP API for chain storage and resolution |
38
+ | [openapi.yaml](./openapi.yaml) | OpenAPI 3.1 machine-readable registry spec |
33
39
 
34
40
  ## Examples
35
41
 
@@ -41,10 +47,6 @@ The `examples/` directory contains deterministic reference chain fixtures that c
41
47
  - `content-lifecycle.json` — create + update (with both documents)
42
48
  - `content-delete.json` — create + delete
43
49
 
44
- ## API Documentation
45
-
46
- See [openapi.yaml](./openapi.yaml) for the registry API specification.
47
-
48
50
  ## License
49
51
 
50
52
  MIT