@metalabel/dfos-protocol 0.0.3 → 0.1.0

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.
@@ -0,0 +1,45 @@
1
+ declare const hexToBytes: (hex: string) => Uint8Array;
2
+ /**
3
+ * Hash a leaf node — SHA-256 of UTF-8 encoded contentId
4
+ */
5
+ declare const hashLeaf: (contentId: string) => Promise<Uint8Array>;
6
+ /**
7
+ * Build a sorted binary Merkle tree over content identifiers
8
+ *
9
+ * ContentIds are sorted lexicographically, hashed to leaves, then paired
10
+ * and hashed up to the root. Odd nodes are promoted to the next level.
11
+ *
12
+ * Returns null root for empty input. Deduplicates contentIds.
13
+ */
14
+ declare const buildMerkleTree: (contentIds: string[]) => Promise<{
15
+ root: string | null;
16
+ leafCount: number;
17
+ }>;
18
+
19
+ interface MerkleProof {
20
+ /** The contentId being proven */
21
+ contentId: string;
22
+ /** Hex SHA-256 root of the tree */
23
+ root: string;
24
+ /** Sibling hashes along the path from leaf to root */
25
+ path: Array<{
26
+ /** Hex SHA-256 of sibling node */
27
+ hash: string;
28
+ /** Position of the sibling relative to the current node */
29
+ position: 'left' | 'right';
30
+ }>;
31
+ }
32
+ /**
33
+ * Generate an inclusion proof for a contentId in the set
34
+ *
35
+ * Returns null if the contentId is not in the set.
36
+ */
37
+ declare const generateMerkleProof: (contentIds: string[], targetId: string) => Promise<MerkleProof | null>;
38
+ /**
39
+ * Verify a Merkle inclusion proof
40
+ *
41
+ * Recomputes the root from the leaf and proof path, compares to the claimed root.
42
+ */
43
+ declare const verifyMerkleProof: (proof: MerkleProof) => Promise<boolean>;
44
+
45
+ export { type MerkleProof, buildMerkleTree, generateMerkleProof, hashLeaf, hexToBytes, verifyMerkleProof };
@@ -0,0 +1,14 @@
1
+ import {
2
+ buildMerkleTree,
3
+ generateMerkleProof,
4
+ hashLeaf,
5
+ hexToBytes,
6
+ verifyMerkleProof
7
+ } from "../chunk-E5CFQG2B.js";
8
+ export {
9
+ buildMerkleTree,
10
+ generateMerkleProof,
11
+ hashLeaf,
12
+ hexToBytes,
13
+ verifyMerkleProof
14
+ };
@@ -2,8 +2,8 @@
2
2
  "description": "Content chain: create + delete",
3
3
  "type": "content",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWE1ejd6eGtuYWU1ZHM3MmV1aWh1ZjJyZzNpeGw2dDRmYnpqZWZoY29nZzNucXBweW9ncXUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZG9jdW1lbnRDSUQiOiJiYWZ5cmVpZnB2d3Vhcm1sNjJzZm9nZHBpMnZsbHR2ZzJldjZvNHh0dzc0emZ1ZDdjcGtnNzQyNnpuZSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.t_DDkJ_TmNekIGUFO22G-W78QoE4XTg9LKQ4gzAQHaK3B6491Tir9b-wtp-hcwmENu2Hqnieqv5ASiqfFrEbDw",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWVwamFzN3JzdHV5aWI0eDY0NHYyeXJxN3c3MmV0dG5xcWozbzdhcmRhcWN1NnVzZml6b3kifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGVsZXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpYTV6N3p4a25hZTVkczcyZXVpaHVmMnJnM2l4bDZ0NGZiemplZmhjb2dnM25xcHB5b2dxdSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDM6MDAuMDAwWiIsIm5vdGUiOiJyZW1vdmluZyBjb250ZW50In0.VkfUjFIz40nBOMzCTtoO1nhExzQHB4QX9PIOe2MmDwC-nilMStTwdiRcxRskPRg0gw8eM5Mz9hNQ8dZfCJ9JAg"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.Rv6vlz5MfrwqDUrSVIGs4ZfeBbkQUSBcXhxwZ6hfudSr5MxhYl08hTqLDOA0W1NMjN0Hs0IW9jXTwLwP1dMDBg",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWQydjUyN2lxMzZ2dnUyNWc3b3BjczJudjRjbmRhcWdrd29naHEzdHB3Z215cnBxZWVuZ2EifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGVsZXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoicmVtb3ZpbmcgY29udGVudCJ9.9DTnVeYjVV6Kc971etvWeNn9iMme7RnrRZRmYBO0iaYeePJDblL_p2uuEIuxtAQQp1bATpsQ475zpK6N2A0eAg"
7
7
  ],
8
8
  "signerPublicKey": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK",
9
9
  "documents": [
@@ -12,7 +12,8 @@
12
12
  "$schema": "https://schemas.dfos.com/post/v1",
13
13
  "format": "short-post",
14
14
  "title": "Hello World",
15
- "body": "First post on the protocol."
15
+ "body": "First post on the protocol.",
16
+ "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
16
17
  },
17
18
  "baseDocumentCID": null,
18
19
  "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
@@ -20,7 +21,7 @@
20
21
  }
21
22
  ],
22
23
  "expected": {
23
- "contentId": "67t27rzc83v7c22n9t6z7c",
24
+ "contentId": "a82z92a3hndk6c97thcrn8",
24
25
  "isDeleted": true,
25
26
  "currentDocumentCID": null,
26
27
  "length": 2
@@ -2,8 +2,8 @@
2
2
  "description": "Content chain: create + update (with both documents)",
3
3
  "type": "content",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWE1ejd6eGtuYWU1ZHM3MmV1aWh1ZjJyZzNpeGw2dDRmYnpqZWZoY29nZzNucXBweW9ncXUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZG9jdW1lbnRDSUQiOiJiYWZ5cmVpZnB2d3Vhcm1sNjJzZm9nZHBpMnZsbHR2ZzJldjZvNHh0dzc0emZ1ZDdjcGtnNzQyNnpuZSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.t_DDkJ_TmNekIGUFO22G-W78QoE4XTg9LKQ4gzAQHaK3B6491Tir9b-wtp-hcwmENu2Hqnieqv5ASiqfFrEbDw",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWJiNGxzdnFtejRqNzZyc3Zoa3F3M3YyYjR2cDIzdDdkaW1tNnZsNWc1d2xuaW52a2VteHEifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpYTV6N3p4a25hZTVkczcyZXVpaHVmMnJnM2l4bDZ0NGZiemplZmhjb2dnM25xcHB5b2dxdSIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWV1bzI2emZtanh3cG13NWprNmJxenFodml2eGNiY2tneHR5ZXVjN3lwZjNwNHNpaGdxNHEiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoiZWRpdGVkIHRpdGxlIGFuZCBib2R5In0.v2BnXflq3lAiDqCpCuAoXUURY1scOGb9AMHHfCMxj1bD0GJOgdV_klv7KfqDLlSfF3Sn6CK1RDeLniF_UTKXAQ"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.Rv6vlz5MfrwqDUrSVIGs4ZfeBbkQUSBcXhxwZ6hfudSr5MxhYl08hTqLDOA0W1NMjN0Hs0IW9jXTwLwP1dMDBg",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWg2ZTVjYmppdHBvemh6aGdtZmt0bWlvaG14eW4zdWN3aHFkM21qaXhpenZ3bWxodjdobTQifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUiLCJkb2N1bWVudENJRCI6ImJhZnlyZWlkaDdlMzZjdnd5M3V3NXlwaXRjcWs3dW9rdGJra2tqN2U2aHhoa3k0bzc1cnhuN2t4aWx1IiwiYmFzZURvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoiZWRpdGVkIHRpdGxlIGFuZCBib2R5In0.46H3WBKigCzUrIOvS2ekYJFdK1OeLhqvcZTj8FBqFNKO1ivs3oC0ui_hzTKkKO-8uevMjf9-k0GOjyw2u16PDA"
7
7
  ],
8
8
  "signerPublicKey": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK",
9
9
  "documents": [
@@ -12,7 +12,8 @@
12
12
  "$schema": "https://schemas.dfos.com/post/v1",
13
13
  "format": "short-post",
14
14
  "title": "Hello World",
15
- "body": "First post on the protocol."
15
+ "body": "First post on the protocol.",
16
+ "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
16
17
  },
17
18
  "baseDocumentCID": null,
18
19
  "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
@@ -23,17 +24,18 @@
23
24
  "$schema": "https://schemas.dfos.com/post/v1",
24
25
  "format": "short-post",
25
26
  "title": "Hello World (edited)",
26
- "body": "Updated content."
27
+ "body": "Updated content.",
28
+ "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
27
29
  },
28
- "baseDocumentCID": "bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne",
30
+ "baseDocumentCID": "bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4",
29
31
  "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
30
32
  "createdAt": "2026-03-07T00:03:00.000Z"
31
33
  }
32
34
  ],
33
35
  "expected": {
34
- "contentId": "67t27rzc83v7c22n9t6z7c",
36
+ "contentId": "a82z92a3hndk6c97thcrn8",
35
37
  "isDeleted": false,
36
- "currentDocumentCID": "bafyreieuo26zfmjxwpmw5jk6bqzqhvivxcbckgxtyeuc7ypf3p4sihgq4q",
38
+ "currentDocumentCID": "bafyreidh7e36cvwy3uw5ypitcqk7uoktbkkkj7e6hxhky4o75rxn7kxilu",
37
39
  "length": 2
38
40
  }
39
41
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-protocol",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
- "description": "DFOS Protocol — Ed25519 signed chain primitives, registry, and verification",
5
+ "description": "DFOS Protocol — Ed25519 signed chain primitives, beacons, merkle trees, and verification",
6
6
  "license": "MIT",
7
7
  "author": "Metalabel <hello@metalabel.com> (https://metalabel.com)",
8
8
  "repository": {
@@ -37,9 +37,9 @@
37
37
  "import": "./dist/chain/index.js",
38
38
  "types": "./dist/chain/index.d.ts"
39
39
  },
40
- "./registry": {
41
- "import": "./dist/registry/index.js",
42
- "types": "./dist/registry/index.d.ts"
40
+ "./merkle": {
41
+ "import": "./dist/merkle/index.js",
42
+ "types": "./dist/merkle/index.d.ts"
43
43
  }
44
44
  },
45
45
  "files": [
@@ -49,8 +49,6 @@
49
49
  "PROTOCOL.md",
50
50
  "DID-METHOD.md",
51
51
  "CONTENT-MODEL.md",
52
- "REGISTRY-API.md",
53
- "openapi.yaml",
54
52
  "LICENSE",
55
53
  "README.md"
56
54
  ],
@@ -58,7 +56,6 @@
58
56
  "@ipld/dag-cbor": "^9.2.5",
59
57
  "@noble/curves": "^2.0.1",
60
58
  "@noble/hashes": "^2.0.1",
61
- "hono": "^4.12.3",
62
59
  "multiformats": "^13.4.2",
63
60
  "zod": "^4.3.6"
64
61
  },
@@ -75,6 +72,6 @@
75
72
  "clean": "rm -rf dist",
76
73
  "typecheck": "tsc --noEmit",
77
74
  "test": "vitest run",
78
- "generate-examples": "vitest run --config vitest.scripts.config.ts"
75
+ "generate-examples": "tsx scripts/generate-examples.ts"
79
76
  }
80
77
  }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.dfos.com/manifest/v1",
4
+ "title": "Manifest",
5
+ "description": "A manifest — a named map of protocol object references for semantic navigation. Keys are path-like labels. Values are protocol references: content chain identifiers (22-char bare hash), DIDs (did:dfos:...), or CIDs (bafyrei...). The document layer of the dark forest. Discovery is social or out-of-band.",
6
+ "type": "object",
7
+ "required": ["$schema", "entries"],
8
+ "properties": {
9
+ "$schema": {
10
+ "const": "https://schemas.dfos.com/manifest/v1"
11
+ },
12
+ "entries": {
13
+ "type": "object",
14
+ "propertyNames": {
15
+ "pattern": "^[a-z0-9][a-z0-9._/-]*[a-z0-9]$",
16
+ "minLength": 2,
17
+ "maxLength": 128
18
+ },
19
+ "additionalProperties": {
20
+ "type": "string",
21
+ "pattern": "^[a-z0-9][a-z0-9:._/-]*$",
22
+ "minLength": 1,
23
+ "maxLength": 512
24
+ },
25
+ "description": "Named entries mapping path-like keys to protocol object references (contentId, DID, or CID)."
26
+ }
27
+ },
28
+ "additionalProperties": false
29
+ }
@@ -35,6 +35,11 @@
35
35
  "type": "array",
36
36
  "items": { "type": "string" },
37
37
  "description": "Topic names this post belongs to. Stored as names for portability."
38
+ },
39
+ "createdByDID": {
40
+ "type": "string",
41
+ "pattern": "^did:",
42
+ "description": "DID of the identity that authored this content. Optional — omit when the signer IS the author."
38
43
  }
39
44
  },
40
45
  "additionalProperties": false,
@@ -28,6 +28,11 @@
28
28
  "background": {
29
29
  "$ref": "#/$defs/media",
30
30
  "description": "Background image."
31
+ },
32
+ "createdByDID": {
33
+ "type": "string",
34
+ "pattern": "^did:",
35
+ "description": "DID of the identity that authored this content. Optional — omit when the signer IS the author."
31
36
  }
32
37
  },
33
38
  "additionalProperties": false,
package/REGISTRY-API.md DELETED
@@ -1,242 +0,0 @@
1
- # DFOS Registry API
2
-
3
- Minimal HTTP API for chain storage, retrieval, and resolution. Any server implementing these endpoints with these semantics is a compatible DFOS registry.
4
-
5
- The protocol is transport-agnostic — chains can be exchanged through any mechanism. This API defines one standard transport binding: a REST interface for submitting chains, resolving identities and content, and retrieving operations and documents.
6
-
7
- [Protocol Specification](https://protocol.dfos.com/spec) · [OpenAPI Spec](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/openapi.yaml) · [Reference Implementation](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/registry/server.ts)
8
-
9
- ---
10
-
11
- ## Overview
12
-
13
- The registry stores verified chains and serves resolved state. It enforces **linear chain integrity** — it accepts chains that are the same length or longer than what's stored, and rejects forks (two different operations at the same chain position).
14
-
15
- Six endpoints, two concerns:
16
-
17
- | Concern | Endpoints |
18
- | ------------------- | ----------------------------------------------- |
19
- | **Identity chains** | Submit chain, resolve identity, list operations |
20
- | **Content chains** | Submit chain, resolve content, list operations |
21
- | **Lookup** | Resolve operation by CID |
22
-
23
- All request and response bodies are JSON (`application/json`).
24
-
25
- ---
26
-
27
- ## Identity Endpoints
28
-
29
- ### `POST /identities` — Submit or extend an identity chain
30
-
31
- Submit an ordered array of JWS tokens (genesis-first). The registry verifies the chain, derives the DID from the genesis CID, and stores it.
32
-
33
- **Request:**
34
-
35
- ```json
36
- {
37
- "chain": ["eyJhbGciOiJFZERTQSI...", "eyJhbGciOiJFZERTQSI..."]
38
- }
39
- ```
40
-
41
- | Field | Type | Description |
42
- | ------- | -------- | ---------------------------------------------------------- |
43
- | `chain` | string[] | Ordered JWS compact tokens, genesis-first. Minimum 1 item. |
44
-
45
- **Responses:**
46
-
47
- | Status | Meaning |
48
- | ------ | ---------------------------------------------------------- |
49
- | `201` | Chain accepted (new or extended) — returns `IdentityState` |
50
- | `200` | Chain already stored, no change — returns `IdentityState` |
51
- | `400` | Invalid chain (verification failed) |
52
- | `409` | Fork conflict with stored chain |
53
-
54
- ---
55
-
56
- ### `GET /identities/{did}` — Resolve current identity state
57
-
58
- Returns the current key state for a DID.
59
-
60
- **Parameters:**
61
-
62
- | Name | In | Pattern | Example |
63
- | ----- | ---- | ------------------------------------ | --------------------------------- |
64
- | `did` | path | `did:dfos:[2346789acdefhknrtvz]{22}` | `did:dfos:e3vvtck42d4eacdnzvtrn6` |
65
-
66
- **Response (`IdentityState`):**
67
-
68
- ```json
69
- {
70
- "did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
71
- "isDeleted": false,
72
- "authKeys": [{ "id": "key_...", "type": "Multikey", "publicKeyMultibase": "z6Mk..." }],
73
- "assertKeys": [{ "id": "key_...", "type": "Multikey", "publicKeyMultibase": "z6Mk..." }],
74
- "controllerKeys": [{ "id": "key_...", "type": "Multikey", "publicKeyMultibase": "z6Mk..." }]
75
- }
76
- ```
77
-
78
- ---
79
-
80
- ### `GET /identities/{did}/operations` — List identity chain operations
81
-
82
- Returns operations newest-first, paginated.
83
-
84
- **Parameters:**
85
-
86
- | Name | In | Description |
87
- | -------- | ----- | ------------------------------------------------------------ |
88
- | `did` | path | The DID to list operations for |
89
- | `cursor` | query | Opaque pagination cursor (CID of last item on previous page) |
90
- | `limit` | query | Page size, 1–100, default 25 |
91
-
92
- **Response (`PaginatedOperations`):**
93
-
94
- ```json
95
- {
96
- "operations": [
97
- { "cid": "bafyrei...", "jwsToken": "eyJ...", "createdAt": "2026-03-07T00:01:00.000Z" },
98
- { "cid": "bafyrei...", "jwsToken": "eyJ...", "createdAt": "2026-03-07T00:00:00.000Z" }
99
- ],
100
- "nextCursor": null
101
- }
102
- ```
103
-
104
- ---
105
-
106
- ## Content Endpoints
107
-
108
- ### `POST /content` — Submit or extend a content chain
109
-
110
- Same mechanics as identity submission. The registry verifies the chain (resolving signing keys from stored identity chains), derives the content ID from the genesis CID, and stores it.
111
-
112
- **Request:** Same `{ "chain": [...] }` format as identity submission.
113
-
114
- **Responses:** Same status code semantics — `201` accepted, `200` noop, `400` invalid, `409` fork.
115
-
116
- ---
117
-
118
- ### `GET /content/{contentId}` — Resolve current content state
119
-
120
- Returns the current state for a content chain.
121
-
122
- **Parameters:**
123
-
124
- | Name | In | Pattern | Example |
125
- | ----------- | ---- | --------------------------- | ------------------------ |
126
- | `contentId` | path | `[2346789acdefhknrtvz]{22}` | `67t27rzc83v7c22n9t6z7c` |
127
-
128
- **Response (`ContentState`):**
129
-
130
- ```json
131
- {
132
- "contentId": "67t27rzc83v7c22n9t6z7c",
133
- "isDeleted": false,
134
- "currentDocumentCID": "bafyrei...",
135
- "genesisCID": "bafyrei...",
136
- "headCID": "bafyrei..."
137
- }
138
- ```
139
-
140
- | Field | Description |
141
- | -------------------- | --------------------------------------------------- |
142
- | `currentDocumentCID` | CID of current document, null if cleared or deleted |
143
- | `genesisCID` | CID of the genesis operation |
144
- | `headCID` | CID of the most recent operation |
145
-
146
- ---
147
-
148
- ### `GET /content/{contentId}/operations` — List content chain operations
149
-
150
- Same pagination mechanics as identity operations.
151
-
152
- ---
153
-
154
- ## Lookup Endpoints
155
-
156
- ### `GET /operations/{cid}` — Resolve a single operation by CID
157
-
158
- Returns the JWS token for any operation (identity or content) by its CID.
159
-
160
- **Response:**
161
-
162
- ```json
163
- {
164
- "cid": "bafyrei...",
165
- "jwsToken": "eyJ..."
166
- }
167
- ```
168
-
169
- ---
170
-
171
- ## Errors
172
-
173
- All error responses follow a standard shape:
174
-
175
- ```json
176
- {
177
- "error": "BAD_REQUEST",
178
- "message": "Human-readable description"
179
- }
180
- ```
181
-
182
- | Error Code | Used By |
183
- | ------------- | ---------------------------------------------------------- |
184
- | `BAD_REQUEST` | Invalid chain, malformed request |
185
- | `NOT_FOUND` | Identity, content, or operation not found |
186
- | `CONFLICT` | Fork detected — submitted chain diverges from stored chain |
187
-
188
- ---
189
-
190
- ## Chain Submission Semantics
191
-
192
- The registry enforces **linear chain extension**:
193
-
194
- - **New chain** — genesis operation not seen before → store and return `201`
195
- - **Extension** — submitted chain is longer than stored, shares the same prefix → replace stored chain, return `201`
196
- - **Noop** — submitted chain is identical to stored → return `200`
197
- - **Fork** — submitted chain diverges from stored chain at some operation → reject with `409`
198
-
199
- This means a registry is **eventually consistent with the longest valid chain** it receives. It does not implement consensus — if two registries receive different valid extensions, they may diverge. Fork detection is the caller's responsibility.
200
-
201
- ---
202
-
203
- ## Authentication
204
-
205
- Registry endpoints that require authentication use **EdDSA JWTs** signed with the same Ed25519 keys from identity chains. The JWT convention is not part of the chain protocol — it's an application-layer auth mechanism for services.
206
-
207
- ```json
208
- {
209
- "alg": "EdDSA",
210
- "typ": "JWT",
211
- "kid": "key_ez9a874tckr3dv933d3ckd"
212
- }
213
- ```
214
-
215
- ```json
216
- {
217
- "iss": "dfos",
218
- "sub": "did:dfos:e3vvtck42d4eacdnzvtrn6",
219
- "aud": "dfos-api",
220
- "exp": 1772902800,
221
- "iat": 1772899200,
222
- "jti": "session_ref_example_01"
223
- }
224
- ```
225
-
226
- | Field | Description |
227
- | ----- | ------------------------------------------------------------- |
228
- | `kid` | Bare key ID (not a DID URL — the `sub` claim carries the DID) |
229
- | `sub` | The DID of the authenticating identity |
230
- | `aud` | Target audience (e.g., `"dfos-api"`) |
231
-
232
- The signing mechanics are identical to operation JWS — `ed25519.sign(UTF8(base64url(header) + "." + base64url(payload)), privateKey)`. The key is resolved from the identity chain via `kid` + `sub`.
233
-
234
- ---
235
-
236
- ## Reference Implementation
237
-
238
- A complete reference server is available in the protocol package:
239
-
240
- - [`registry/server.ts`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/registry/server.ts) — Hono-based HTTP server implementing all endpoints
241
- - [`registry/store.ts`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/registry/store.ts) — In-memory chain store with linear enforcement
242
- - [`openapi.yaml`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/openapi.yaml) — OpenAPI 3.1 machine-readable specification