@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/CONTENT-MODEL.md +107 -0
- package/DID-METHOD.md +326 -0
- package/PROTOCOL.md +123 -210
- package/README.md +8 -6
- package/REGISTRY-API.md +242 -0
- package/dist/{chunk-3PB644X2.js → chunk-4MMWM2PC.js} +0 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -3
- package/dist/registry/index.d.ts +1 -9
- package/dist/registry/index.js +1 -3
- package/openapi.yaml +0 -32
- package/package.json +11 -6
package/PROTOCOL.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# DFOS Protocol
|
|
1
|
+
# DFOS Protocol
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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).
|
|
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":
|
|
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
|
|
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
|
|
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) |
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
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`)
|
|
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 |
|
|
450
|
-
| All content ops | DID URL |
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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):
|
|
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
|
-
|
|
691
|
+
Content Update CID:
|
|
780
692
|
|
|
781
693
|
```
|
|
782
|
-
|
|
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**:
|
|
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
|
|
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**:
|
|
722
|
+
6. **Rotation JWS**: signed by OLD controller key (key 1). Verify with key 1's public key. kid:
|
|
802
723
|
|
|
803
|
-
|
|
724
|
+
```
|
|
725
|
+
did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8
|
|
726
|
+
```
|
|
804
727
|
|
|
805
|
-
|
|
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
|
-
|
|
730
|
+
```
|
|
731
|
+
did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd
|
|
732
|
+
```
|
|
808
733
|
|
|
809
|
-
|
|
734
|
+
8. **Document CID**: dag-cbor canonical encode the document JSON → SHA-256 → CIDv1 → should be:
|
|
810
735
|
|
|
811
|
-
|
|
736
|
+
```
|
|
737
|
+
bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne
|
|
738
|
+
```
|
|
812
739
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
##
|
|
746
|
+
## Source and Verification
|
|
846
747
|
|
|
847
|
-
|
|
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
|
-
- `
|
|
850
|
-
- `
|
|
851
|
-
- `
|
|
852
|
-
- `
|
|
853
|
-
- `
|
|
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
|
-
###
|
|
761
|
+
### Related Specifications
|
|
856
762
|
|
|
857
|
-
-
|
|
858
|
-
-
|
|
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
|
-
###
|
|
767
|
+
### Cross-Language Verification
|
|
861
768
|
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
777
|
+
---
|
|
866
778
|
|
|
867
|
-
|
|
868
|
-
- Dependencies: `ed25519-dalek`, `ciborium`, `sha2`, `bs58`, `base64`, `data-encoding`
|
|
779
|
+
## Special Thanks
|
|
869
780
|
|
|
870
|
-
|
|
781
|
+
- **Vinny Bellavia** — [stcisgood.com](https://stcisgood.com)
|
|
782
|
+
- **Allison Clift-Jennings** — [Jura Labs](https://juralabs.com)
|
|
783
|
+
|
|
784
|
+
---
|
|
871
785
|
|
|
872
|
-
|
|
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
|
-
##
|
|
30
|
+
## Specifications
|
|
31
31
|
|
|
32
|
-
|
|
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
|