@metalabel/dfos-protocol 0.0.3 → 0.2.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.
- package/CONTENT-MODEL.md +63 -22
- package/DID-METHOD.md +7 -8
- package/PROTOCOL.md +414 -94
- package/README.md +17 -11
- package/dist/chain/index.d.ts +115 -2
- package/dist/chain/index.js +14 -1
- package/dist/chunk-CZSEEZLL.js +258 -0
- package/dist/chunk-E5CFQG2B.js +99 -0
- package/dist/{chunk-VEBMLR37.js → chunk-GEVJ3SEV.js} +232 -6
- package/dist/credentials/index.d.ts +206 -0
- package/dist/credentials/index.js +35 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +58 -32
- package/dist/merkle/index.d.ts +45 -0
- package/dist/merkle/index.js +14 -0
- package/examples/beacon.json +14 -0
- package/examples/content-delegated.json +44 -0
- package/examples/content-delete.json +5 -4
- package/examples/content-lifecycle.json +9 -7
- package/examples/credential-read.json +12 -0
- package/examples/credential-write.json +14 -0
- package/examples/merkle-tree.json +28 -0
- package/package.json +10 -9
- package/schemas/manifest.v1.json +29 -0
- package/schemas/post.v1.json +5 -0
- package/schemas/profile.v1.json +5 -0
- package/REGISTRY-API.md +0 -242
- package/dist/chunk-U6DANYPT.js +0 -311
- package/dist/registry/index.d.ts +0 -143
- package/dist/registry/index.js +0 -34
- package/openapi.yaml +0 -376
- package/schemas/document-envelope.v1.json +0 -37
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
|
package/dist/chunk-U6DANYPT.js
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MultikeyPublicKey,
|
|
3
|
-
decodeMultikey,
|
|
4
|
-
verifyContentChain,
|
|
5
|
-
verifyIdentityChain
|
|
6
|
-
} from "./chunk-VEBMLR37.js";
|
|
7
|
-
import {
|
|
8
|
-
dagCborCanonicalEncode,
|
|
9
|
-
decodeJwsUnsafe
|
|
10
|
-
} from "./chunk-ZXXP5W5N.js";
|
|
11
|
-
|
|
12
|
-
// src/registry/schemas.ts
|
|
13
|
-
import { z } from "zod";
|
|
14
|
-
var OperationEntry = z.strictObject({
|
|
15
|
-
cid: z.string(),
|
|
16
|
-
jwsToken: z.string(),
|
|
17
|
-
createdAt: z.string()
|
|
18
|
-
});
|
|
19
|
-
var PaginatedOperations = z.strictObject({
|
|
20
|
-
operations: z.array(OperationEntry),
|
|
21
|
-
nextCursor: z.string().nullable()
|
|
22
|
-
});
|
|
23
|
-
var PaginationParams = z.object({
|
|
24
|
-
cursor: z.string().optional(),
|
|
25
|
-
limit: z.coerce.number().int().min(1).max(100).default(25)
|
|
26
|
-
});
|
|
27
|
-
var SubmitIdentityChainRequest = z.strictObject({
|
|
28
|
-
chain: z.array(z.string()).min(1)
|
|
29
|
-
});
|
|
30
|
-
var SubmitIdentityChainResponse = z.strictObject({
|
|
31
|
-
did: z.string(),
|
|
32
|
-
isDeleted: z.boolean(),
|
|
33
|
-
authKeys: z.array(MultikeyPublicKey),
|
|
34
|
-
assertKeys: z.array(MultikeyPublicKey),
|
|
35
|
-
controllerKeys: z.array(MultikeyPublicKey)
|
|
36
|
-
});
|
|
37
|
-
var ResolveIdentityResponse = SubmitIdentityChainResponse;
|
|
38
|
-
var IdentityOperationsParams = PaginationParams;
|
|
39
|
-
var IdentityOperationsResponse = PaginatedOperations;
|
|
40
|
-
var SubmitContentChainRequest = z.strictObject({
|
|
41
|
-
chain: z.array(z.string()).min(1)
|
|
42
|
-
});
|
|
43
|
-
var SubmitContentChainResponse = z.strictObject({
|
|
44
|
-
contentId: z.string(),
|
|
45
|
-
isDeleted: z.boolean(),
|
|
46
|
-
currentDocumentCID: z.string().nullable(),
|
|
47
|
-
genesisCID: z.string(),
|
|
48
|
-
headCID: z.string()
|
|
49
|
-
});
|
|
50
|
-
var ResolveContentResponse = SubmitContentChainResponse;
|
|
51
|
-
var ContentOperationsParams = PaginationParams;
|
|
52
|
-
var ContentOperationsResponse = PaginatedOperations;
|
|
53
|
-
var ResolveOperationResponse = z.strictObject({
|
|
54
|
-
cid: z.string(),
|
|
55
|
-
jwsToken: z.string()
|
|
56
|
-
});
|
|
57
|
-
var RegistryError = z.strictObject({
|
|
58
|
-
error: z.string(),
|
|
59
|
-
message: z.string()
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// src/registry/server.ts
|
|
63
|
-
import { Hono } from "hono";
|
|
64
|
-
|
|
65
|
-
// src/registry/store.ts
|
|
66
|
-
var ChainStore = class {
|
|
67
|
-
identityChains = /* @__PURE__ */ new Map();
|
|
68
|
-
contentChains = /* @__PURE__ */ new Map();
|
|
69
|
-
// --- identityChains ---
|
|
70
|
-
getIdentityChain(did) {
|
|
71
|
-
return this.identityChains.get(did);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Submit an identity chain. Returns 'accepted' | 'noop' | 'conflict'.
|
|
75
|
-
* - accepted: chain was stored or extended
|
|
76
|
-
* - noop: submitted chain is same as or prefix of stored chain
|
|
77
|
-
* - conflict: submitted chain diverges from stored chain (fork)
|
|
78
|
-
*/
|
|
79
|
-
submitIdentityChain(did, operations) {
|
|
80
|
-
return this.submitChain(this.identityChains, did, operations);
|
|
81
|
-
}
|
|
82
|
-
// --- contentChains ---
|
|
83
|
-
getContentChain(contentId) {
|
|
84
|
-
return this.contentChains.get(contentId);
|
|
85
|
-
}
|
|
86
|
-
submitContentChain(contentId, operations) {
|
|
87
|
-
return this.submitChain(this.contentChains, contentId, operations);
|
|
88
|
-
}
|
|
89
|
-
// --- operations (lookup across all chains) ---
|
|
90
|
-
getOperation(cid) {
|
|
91
|
-
for (const chain of this.identityChains.values()) {
|
|
92
|
-
const op = chain.operations.find((o) => o.cid === cid);
|
|
93
|
-
if (op) return op;
|
|
94
|
-
}
|
|
95
|
-
for (const chain of this.contentChains.values()) {
|
|
96
|
-
const op = chain.operations.find((o) => o.cid === cid);
|
|
97
|
-
if (op) return op;
|
|
98
|
-
}
|
|
99
|
-
return void 0;
|
|
100
|
-
}
|
|
101
|
-
// --- shared chain submission logic ---
|
|
102
|
-
submitChain(store, id, operations) {
|
|
103
|
-
const existing = store.get(id);
|
|
104
|
-
if (!existing) {
|
|
105
|
-
store.set(id, { operations });
|
|
106
|
-
return "accepted";
|
|
107
|
-
}
|
|
108
|
-
if (operations.length <= existing.operations.length) {
|
|
109
|
-
for (let i = 0; i < operations.length; i++) {
|
|
110
|
-
if (operations[i].cid !== existing.operations[i].cid) {
|
|
111
|
-
return "conflict";
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return "noop";
|
|
115
|
-
}
|
|
116
|
-
for (let i = 0; i < existing.operations.length; i++) {
|
|
117
|
-
if (operations[i].cid !== existing.operations[i].cid) {
|
|
118
|
-
return "conflict";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
store.set(id, { operations });
|
|
122
|
-
return "accepted";
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// src/registry/server.ts
|
|
127
|
-
var err = (code, message) => ({
|
|
128
|
-
error: code,
|
|
129
|
-
message
|
|
130
|
-
});
|
|
131
|
-
var extractOperationEntries = async (chain) => {
|
|
132
|
-
const entries = [];
|
|
133
|
-
for (const jwsToken of chain) {
|
|
134
|
-
const decoded = decodeJwsUnsafe(jwsToken);
|
|
135
|
-
if (!decoded) throw new Error("invalid JWS token");
|
|
136
|
-
const payload = decoded.payload;
|
|
137
|
-
const encoded = await dagCborCanonicalEncode(payload);
|
|
138
|
-
entries.push({
|
|
139
|
-
cid: encoded.cid.toString(),
|
|
140
|
-
jwsToken,
|
|
141
|
-
createdAt: payload.createdAt ?? ""
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
return entries;
|
|
145
|
-
};
|
|
146
|
-
var paginate = (operations, cursor, limit) => {
|
|
147
|
-
const reversed = [...operations].reverse();
|
|
148
|
-
let startIdx = 0;
|
|
149
|
-
if (cursor) {
|
|
150
|
-
const cursorIdx = reversed.findIndex((op) => op.cid === cursor);
|
|
151
|
-
if (cursorIdx >= 0) startIdx = cursorIdx + 1;
|
|
152
|
-
}
|
|
153
|
-
const page = reversed.slice(startIdx, startIdx + limit);
|
|
154
|
-
const hasMore = startIdx + limit < reversed.length;
|
|
155
|
-
return {
|
|
156
|
-
operations: page,
|
|
157
|
-
nextCursor: hasMore ? page[page.length - 1].cid : null
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
var paginationParams = (c) => ({
|
|
161
|
-
cursor: c.query("cursor"),
|
|
162
|
-
limit: Math.min(Math.max(parseInt(c.query("limit") ?? "25"), 1), 100)
|
|
163
|
-
});
|
|
164
|
-
var createKeyResolver = (store) => async (kid) => {
|
|
165
|
-
const hashIdx = kid.indexOf("#");
|
|
166
|
-
if (hashIdx < 0) throw new Error(`invalid kid format: ${kid}`);
|
|
167
|
-
const did = kid.substring(0, hashIdx);
|
|
168
|
-
const keyId = kid.substring(hashIdx + 1);
|
|
169
|
-
const identityChain = store.getIdentityChain(did);
|
|
170
|
-
if (!identityChain) throw new Error(`identity not found: ${did}`);
|
|
171
|
-
const identity = await verifyIdentityChain({
|
|
172
|
-
didPrefix: "did:dfos",
|
|
173
|
-
log: identityChain.operations.map((o) => o.jwsToken)
|
|
174
|
-
});
|
|
175
|
-
const allKeys = [...identity.authKeys, ...identity.assertKeys, ...identity.controllerKeys];
|
|
176
|
-
const key = allKeys.find((k) => k.id === keyId);
|
|
177
|
-
if (!key) throw new Error(`key not found: ${keyId}`);
|
|
178
|
-
return decodeMultikey(key.publicKeyMultibase).keyBytes;
|
|
179
|
-
};
|
|
180
|
-
var createRegistryServer = (store = new ChainStore()) => {
|
|
181
|
-
const app = new Hono();
|
|
182
|
-
const resolveKey = createKeyResolver(store);
|
|
183
|
-
app.post("/identities", async (c) => {
|
|
184
|
-
const body = await c.req.json();
|
|
185
|
-
if (!body.chain || !Array.isArray(body.chain) || body.chain.length === 0) {
|
|
186
|
-
return c.json(err("BAD_REQUEST", "chain must be a non-empty array of JWS tokens"), 400);
|
|
187
|
-
}
|
|
188
|
-
let verified;
|
|
189
|
-
try {
|
|
190
|
-
verified = await verifyIdentityChain({
|
|
191
|
-
didPrefix: "did:dfos",
|
|
192
|
-
log: body.chain
|
|
193
|
-
});
|
|
194
|
-
} catch (e) {
|
|
195
|
-
return c.json(err("BAD_REQUEST", `chain verification failed: ${e.message}`), 400);
|
|
196
|
-
}
|
|
197
|
-
const operations = await extractOperationEntries(body.chain);
|
|
198
|
-
const result = store.submitIdentityChain(verified.did, operations);
|
|
199
|
-
if (result === "conflict") {
|
|
200
|
-
return c.json(err("CONFLICT", "submitted chain conflicts with stored chain"), 409);
|
|
201
|
-
}
|
|
202
|
-
return c.json(
|
|
203
|
-
{
|
|
204
|
-
did: verified.did,
|
|
205
|
-
isDeleted: verified.isDeleted,
|
|
206
|
-
authKeys: verified.authKeys,
|
|
207
|
-
assertKeys: verified.assertKeys,
|
|
208
|
-
controllerKeys: verified.controllerKeys
|
|
209
|
-
},
|
|
210
|
-
result === "accepted" ? 201 : 200
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
app.get("/identities/:did", async (c) => {
|
|
214
|
-
const did = c.req.param("did");
|
|
215
|
-
const chain = store.getIdentityChain(did);
|
|
216
|
-
if (!chain) return c.json(err("NOT_FOUND", "identity not found"), 404);
|
|
217
|
-
const verified = await verifyIdentityChain({
|
|
218
|
-
didPrefix: "did:dfos",
|
|
219
|
-
log: chain.operations.map((o) => o.jwsToken)
|
|
220
|
-
});
|
|
221
|
-
return c.json({
|
|
222
|
-
did: verified.did,
|
|
223
|
-
isDeleted: verified.isDeleted,
|
|
224
|
-
authKeys: verified.authKeys,
|
|
225
|
-
assertKeys: verified.assertKeys,
|
|
226
|
-
controllerKeys: verified.controllerKeys
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
app.get("/identities/:did/operations", (c) => {
|
|
230
|
-
const did = c.req.param("did");
|
|
231
|
-
const chain = store.getIdentityChain(did);
|
|
232
|
-
if (!chain) return c.json(err("NOT_FOUND", "identity not found"), 404);
|
|
233
|
-
const { cursor, limit } = paginationParams(c.req);
|
|
234
|
-
return c.json(paginate(chain.operations, cursor, limit));
|
|
235
|
-
});
|
|
236
|
-
app.post("/content", async (c) => {
|
|
237
|
-
const body = await c.req.json();
|
|
238
|
-
if (!body.chain || !Array.isArray(body.chain) || body.chain.length === 0) {
|
|
239
|
-
return c.json(err("BAD_REQUEST", "chain must be a non-empty array of JWS tokens"), 400);
|
|
240
|
-
}
|
|
241
|
-
const operations = await extractOperationEntries(body.chain);
|
|
242
|
-
let verified;
|
|
243
|
-
try {
|
|
244
|
-
verified = await verifyContentChain({ log: body.chain, resolveKey });
|
|
245
|
-
} catch (e) {
|
|
246
|
-
return c.json(err("BAD_REQUEST", `chain verification failed: ${e.message}`), 400);
|
|
247
|
-
}
|
|
248
|
-
const result = store.submitContentChain(verified.contentId, operations);
|
|
249
|
-
if (result === "conflict") {
|
|
250
|
-
return c.json(err("CONFLICT", "submitted chain conflicts with stored chain"), 409);
|
|
251
|
-
}
|
|
252
|
-
return c.json(
|
|
253
|
-
{
|
|
254
|
-
contentId: verified.contentId,
|
|
255
|
-
isDeleted: verified.isDeleted,
|
|
256
|
-
currentDocumentCID: verified.currentDocumentCID,
|
|
257
|
-
genesisCID: verified.genesisCID,
|
|
258
|
-
headCID: verified.headCID
|
|
259
|
-
},
|
|
260
|
-
result === "accepted" ? 201 : 200
|
|
261
|
-
);
|
|
262
|
-
});
|
|
263
|
-
app.get("/content/:contentId", (c) => {
|
|
264
|
-
const contentId = c.req.param("contentId");
|
|
265
|
-
const chain = store.getContentChain(contentId);
|
|
266
|
-
if (!chain) return c.json(err("NOT_FOUND", "content not found"), 404);
|
|
267
|
-
const genesis = chain.operations[0];
|
|
268
|
-
const head = chain.operations[chain.operations.length - 1];
|
|
269
|
-
const headDecoded = decodeJwsUnsafe(head.jwsToken);
|
|
270
|
-
const headPayload = headDecoded?.payload;
|
|
271
|
-
const headType = headPayload?.type;
|
|
272
|
-
return c.json({
|
|
273
|
-
contentId,
|
|
274
|
-
isDeleted: headType === "delete",
|
|
275
|
-
currentDocumentCID: headType === "delete" ? null : headPayload?.documentCID ?? null,
|
|
276
|
-
genesisCID: genesis.cid,
|
|
277
|
-
headCID: head.cid
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
app.get("/content/:contentId/operations", (c) => {
|
|
281
|
-
const contentId = c.req.param("contentId");
|
|
282
|
-
const chain = store.getContentChain(contentId);
|
|
283
|
-
if (!chain) return c.json(err("NOT_FOUND", "content not found"), 404);
|
|
284
|
-
const { cursor, limit } = paginationParams(c.req);
|
|
285
|
-
return c.json(paginate(chain.operations, cursor, limit));
|
|
286
|
-
});
|
|
287
|
-
app.get("/operations/:cid", (c) => {
|
|
288
|
-
const cid = c.req.param("cid");
|
|
289
|
-
const op = store.getOperation(cid);
|
|
290
|
-
if (!op) return c.json(err("NOT_FOUND", "operation not found"), 404);
|
|
291
|
-
return c.json({ cid: op.cid, jwsToken: op.jwsToken });
|
|
292
|
-
});
|
|
293
|
-
return { app, store };
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
export {
|
|
297
|
-
SubmitIdentityChainRequest,
|
|
298
|
-
SubmitIdentityChainResponse,
|
|
299
|
-
ResolveIdentityResponse,
|
|
300
|
-
IdentityOperationsParams,
|
|
301
|
-
IdentityOperationsResponse,
|
|
302
|
-
SubmitContentChainRequest,
|
|
303
|
-
SubmitContentChainResponse,
|
|
304
|
-
ResolveContentResponse,
|
|
305
|
-
ContentOperationsParams,
|
|
306
|
-
ContentOperationsResponse,
|
|
307
|
-
ResolveOperationResponse,
|
|
308
|
-
RegistryError,
|
|
309
|
-
ChainStore,
|
|
310
|
-
createRegistryServer
|
|
311
|
-
};
|
package/dist/registry/index.d.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import * as hono_types from 'hono/types';
|
|
3
|
-
import { Hono } from 'hono';
|
|
4
|
-
|
|
5
|
-
declare const OperationEntry: z.ZodObject<{
|
|
6
|
-
cid: z.ZodString;
|
|
7
|
-
jwsToken: z.ZodString;
|
|
8
|
-
createdAt: z.ZodString;
|
|
9
|
-
}, z.core.$strict>;
|
|
10
|
-
type OperationEntry = z.infer<typeof OperationEntry>;
|
|
11
|
-
declare const PaginationParams: z.ZodObject<{
|
|
12
|
-
cursor: z.ZodOptional<z.ZodString>;
|
|
13
|
-
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
14
|
-
}, z.core.$strip>;
|
|
15
|
-
type PaginationParams = z.infer<typeof PaginationParams>;
|
|
16
|
-
declare const SubmitIdentityChainRequest: z.ZodObject<{
|
|
17
|
-
chain: z.ZodArray<z.ZodString>;
|
|
18
|
-
}, z.core.$strict>;
|
|
19
|
-
type SubmitIdentityChainRequest = z.infer<typeof SubmitIdentityChainRequest>;
|
|
20
|
-
declare const SubmitIdentityChainResponse: z.ZodObject<{
|
|
21
|
-
did: z.ZodString;
|
|
22
|
-
isDeleted: z.ZodBoolean;
|
|
23
|
-
authKeys: z.ZodArray<z.ZodObject<{
|
|
24
|
-
id: z.ZodString;
|
|
25
|
-
type: z.ZodLiteral<"Multikey">;
|
|
26
|
-
publicKeyMultibase: z.ZodString;
|
|
27
|
-
}, z.core.$strict>>;
|
|
28
|
-
assertKeys: z.ZodArray<z.ZodObject<{
|
|
29
|
-
id: z.ZodString;
|
|
30
|
-
type: z.ZodLiteral<"Multikey">;
|
|
31
|
-
publicKeyMultibase: z.ZodString;
|
|
32
|
-
}, z.core.$strict>>;
|
|
33
|
-
controllerKeys: z.ZodArray<z.ZodObject<{
|
|
34
|
-
id: z.ZodString;
|
|
35
|
-
type: z.ZodLiteral<"Multikey">;
|
|
36
|
-
publicKeyMultibase: z.ZodString;
|
|
37
|
-
}, z.core.$strict>>;
|
|
38
|
-
}, z.core.$strict>;
|
|
39
|
-
type SubmitIdentityChainResponse = z.infer<typeof SubmitIdentityChainResponse>;
|
|
40
|
-
declare const ResolveIdentityResponse: z.ZodObject<{
|
|
41
|
-
did: z.ZodString;
|
|
42
|
-
isDeleted: z.ZodBoolean;
|
|
43
|
-
authKeys: z.ZodArray<z.ZodObject<{
|
|
44
|
-
id: z.ZodString;
|
|
45
|
-
type: z.ZodLiteral<"Multikey">;
|
|
46
|
-
publicKeyMultibase: z.ZodString;
|
|
47
|
-
}, z.core.$strict>>;
|
|
48
|
-
assertKeys: z.ZodArray<z.ZodObject<{
|
|
49
|
-
id: z.ZodString;
|
|
50
|
-
type: z.ZodLiteral<"Multikey">;
|
|
51
|
-
publicKeyMultibase: z.ZodString;
|
|
52
|
-
}, z.core.$strict>>;
|
|
53
|
-
controllerKeys: z.ZodArray<z.ZodObject<{
|
|
54
|
-
id: z.ZodString;
|
|
55
|
-
type: z.ZodLiteral<"Multikey">;
|
|
56
|
-
publicKeyMultibase: z.ZodString;
|
|
57
|
-
}, z.core.$strict>>;
|
|
58
|
-
}, z.core.$strict>;
|
|
59
|
-
type ResolveIdentityResponse = SubmitIdentityChainResponse;
|
|
60
|
-
declare const IdentityOperationsParams: z.ZodObject<{
|
|
61
|
-
cursor: z.ZodOptional<z.ZodString>;
|
|
62
|
-
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
63
|
-
}, z.core.$strip>;
|
|
64
|
-
declare const IdentityOperationsResponse: z.ZodObject<{
|
|
65
|
-
operations: z.ZodArray<z.ZodObject<{
|
|
66
|
-
cid: z.ZodString;
|
|
67
|
-
jwsToken: z.ZodString;
|
|
68
|
-
createdAt: z.ZodString;
|
|
69
|
-
}, z.core.$strict>>;
|
|
70
|
-
nextCursor: z.ZodNullable<z.ZodString>;
|
|
71
|
-
}, z.core.$strict>;
|
|
72
|
-
type IdentityOperationsResponse = z.infer<typeof IdentityOperationsResponse>;
|
|
73
|
-
declare const SubmitContentChainRequest: z.ZodObject<{
|
|
74
|
-
chain: z.ZodArray<z.ZodString>;
|
|
75
|
-
}, z.core.$strict>;
|
|
76
|
-
type SubmitContentChainRequest = z.infer<typeof SubmitContentChainRequest>;
|
|
77
|
-
declare const SubmitContentChainResponse: z.ZodObject<{
|
|
78
|
-
contentId: z.ZodString;
|
|
79
|
-
isDeleted: z.ZodBoolean;
|
|
80
|
-
currentDocumentCID: z.ZodNullable<z.ZodString>;
|
|
81
|
-
genesisCID: z.ZodString;
|
|
82
|
-
headCID: z.ZodString;
|
|
83
|
-
}, z.core.$strict>;
|
|
84
|
-
type SubmitContentChainResponse = z.infer<typeof SubmitContentChainResponse>;
|
|
85
|
-
declare const ResolveContentResponse: z.ZodObject<{
|
|
86
|
-
contentId: z.ZodString;
|
|
87
|
-
isDeleted: z.ZodBoolean;
|
|
88
|
-
currentDocumentCID: z.ZodNullable<z.ZodString>;
|
|
89
|
-
genesisCID: z.ZodString;
|
|
90
|
-
headCID: z.ZodString;
|
|
91
|
-
}, z.core.$strict>;
|
|
92
|
-
type ResolveContentResponse = SubmitContentChainResponse;
|
|
93
|
-
declare const ContentOperationsParams: z.ZodObject<{
|
|
94
|
-
cursor: z.ZodOptional<z.ZodString>;
|
|
95
|
-
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
96
|
-
}, z.core.$strip>;
|
|
97
|
-
declare const ContentOperationsResponse: z.ZodObject<{
|
|
98
|
-
operations: z.ZodArray<z.ZodObject<{
|
|
99
|
-
cid: z.ZodString;
|
|
100
|
-
jwsToken: z.ZodString;
|
|
101
|
-
createdAt: z.ZodString;
|
|
102
|
-
}, z.core.$strict>>;
|
|
103
|
-
nextCursor: z.ZodNullable<z.ZodString>;
|
|
104
|
-
}, z.core.$strict>;
|
|
105
|
-
type ContentOperationsResponse = z.infer<typeof ContentOperationsResponse>;
|
|
106
|
-
declare const ResolveOperationResponse: z.ZodObject<{
|
|
107
|
-
cid: z.ZodString;
|
|
108
|
-
jwsToken: z.ZodString;
|
|
109
|
-
}, z.core.$strict>;
|
|
110
|
-
type ResolveOperationResponse = z.infer<typeof ResolveOperationResponse>;
|
|
111
|
-
declare const RegistryError: z.ZodObject<{
|
|
112
|
-
error: z.ZodString;
|
|
113
|
-
message: z.ZodString;
|
|
114
|
-
}, z.core.$strict>;
|
|
115
|
-
type RegistryError = z.infer<typeof RegistryError>;
|
|
116
|
-
|
|
117
|
-
interface StoredChain {
|
|
118
|
-
/** Ordered array of operations, genesis first */
|
|
119
|
-
operations: OperationEntry[];
|
|
120
|
-
}
|
|
121
|
-
declare class ChainStore {
|
|
122
|
-
private identityChains;
|
|
123
|
-
private contentChains;
|
|
124
|
-
getIdentityChain(did: string): StoredChain | undefined;
|
|
125
|
-
/**
|
|
126
|
-
* Submit an identity chain. Returns 'accepted' | 'noop' | 'conflict'.
|
|
127
|
-
* - accepted: chain was stored or extended
|
|
128
|
-
* - noop: submitted chain is same as or prefix of stored chain
|
|
129
|
-
* - conflict: submitted chain diverges from stored chain (fork)
|
|
130
|
-
*/
|
|
131
|
-
submitIdentityChain(did: string, operations: OperationEntry[]): 'accepted' | 'noop' | 'conflict';
|
|
132
|
-
getContentChain(contentId: string): StoredChain | undefined;
|
|
133
|
-
submitContentChain(contentId: string, operations: OperationEntry[]): 'accepted' | 'noop' | 'conflict';
|
|
134
|
-
getOperation(cid: string): OperationEntry | undefined;
|
|
135
|
-
private submitChain;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
declare const createRegistryServer: (store?: ChainStore) => {
|
|
139
|
-
app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
140
|
-
store: ChainStore;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
export { ChainStore, ContentOperationsParams, ContentOperationsResponse, IdentityOperationsParams, IdentityOperationsResponse, OperationEntry, PaginationParams, RegistryError, ResolveContentResponse, ResolveIdentityResponse, ResolveOperationResponse, type StoredChain, SubmitContentChainRequest, SubmitContentChainResponse, SubmitIdentityChainRequest, SubmitIdentityChainResponse, createRegistryServer };
|