@metalabel/dfos-protocol 0.0.1 → 0.0.3
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 +137 -241
- package/README.md +9 -7
- package/REGISTRY-API.md +242 -0
- package/dist/chain/index.d.ts +5 -5
- package/dist/chain/index.js +3 -3
- package/dist/{chunk-3PB644X2.js → chunk-U6DANYPT.js} +32 -51
- package/dist/{chunk-LWC4PWGZ.js → chunk-VEBMLR37.js} +5 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +10 -12
- package/dist/registry/index.d.ts +12 -20
- package/dist/registry/index.js +8 -10
- package/examples/content-delete.json +1 -1
- package/examples/content-lifecycle.json +1 -1
- package/openapi.yaml +20 -52
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @metalabel/dfos-protocol
|
|
2
2
|
|
|
3
|
-
Ed25519 signed
|
|
3
|
+
Cryptographic identity and content proof — Ed25519 signed chains, content-addressed CIDs, W3C DIDs. The protocol knows about keys and document hashes. It doesn't know about posts, profiles, or any application concept.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -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
|
package/REGISTRY-API.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
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/chain/index.d.ts
CHANGED
|
@@ -124,12 +124,12 @@ declare const decodeMultikey: (multibase: string) => {
|
|
|
124
124
|
*/
|
|
125
125
|
declare const deriveChainIdentifier: (cidBytes: Uint8Array, prefix: string) => string;
|
|
126
126
|
/**
|
|
127
|
-
* Derive a bare
|
|
127
|
+
* Derive a bare content identifier from CID bytes
|
|
128
128
|
*
|
|
129
129
|
* Returns the raw 22-char hash with no prefix. Applications may add
|
|
130
130
|
* their own prefix for routing (e.g., post_xxxx) — that's semantic sugar.
|
|
131
131
|
*/
|
|
132
|
-
declare const
|
|
132
|
+
declare const deriveContentId: (cidBytes: Uint8Array) => string;
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* Sign an identity operation as a JWS and derive the operation CID
|
|
@@ -156,8 +156,8 @@ declare const verifyIdentityChain: (input: {
|
|
|
156
156
|
}) => Promise<VerifiedIdentity>;
|
|
157
157
|
|
|
158
158
|
interface VerifiedContentChain {
|
|
159
|
-
/**
|
|
160
|
-
|
|
159
|
+
/** Content identifier — bare 22-char hash derived from genesis CID */
|
|
160
|
+
contentId: string;
|
|
161
161
|
/** CID of the genesis operation */
|
|
162
162
|
genesisCID: string;
|
|
163
163
|
/** CID of the most recent operation */
|
|
@@ -193,4 +193,4 @@ declare const verifyContentChain: (input: {
|
|
|
193
193
|
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
194
194
|
}) => Promise<VerifiedContentChain>;
|
|
195
195
|
|
|
196
|
-
export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier,
|
|
196
|
+
export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signContentOperation, signIdentityOperation, verifyContentChain, verifyIdentityChain };
|
package/dist/chain/index.js
CHANGED
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
VerifiedIdentity,
|
|
8
8
|
decodeMultikey,
|
|
9
9
|
deriveChainIdentifier,
|
|
10
|
-
|
|
10
|
+
deriveContentId,
|
|
11
11
|
encodeEd25519Multikey,
|
|
12
12
|
signContentOperation,
|
|
13
13
|
signIdentityOperation,
|
|
14
14
|
verifyContentChain,
|
|
15
15
|
verifyIdentityChain
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-VEBMLR37.js";
|
|
17
17
|
import "../chunk-ZXXP5W5N.js";
|
|
18
18
|
export {
|
|
19
19
|
ContentOperation,
|
|
@@ -24,7 +24,7 @@ export {
|
|
|
24
24
|
VerifiedIdentity,
|
|
25
25
|
decodeMultikey,
|
|
26
26
|
deriveChainIdentifier,
|
|
27
|
-
|
|
27
|
+
deriveContentId,
|
|
28
28
|
encodeEd25519Multikey,
|
|
29
29
|
signContentOperation,
|
|
30
30
|
signIdentityOperation,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
decodeMultikey,
|
|
4
4
|
verifyContentChain,
|
|
5
5
|
verifyIdentityChain
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VEBMLR37.js";
|
|
7
7
|
import {
|
|
8
8
|
dagCborCanonicalEncode,
|
|
9
9
|
decodeJwsUnsafe
|
|
@@ -41,23 +41,19 @@ var SubmitContentChainRequest = z.strictObject({
|
|
|
41
41
|
chain: z.array(z.string()).min(1)
|
|
42
42
|
});
|
|
43
43
|
var SubmitContentChainResponse = z.strictObject({
|
|
44
|
-
|
|
44
|
+
contentId: z.string(),
|
|
45
45
|
isDeleted: z.boolean(),
|
|
46
46
|
currentDocumentCID: z.string().nullable(),
|
|
47
47
|
genesisCID: z.string(),
|
|
48
48
|
headCID: z.string()
|
|
49
49
|
});
|
|
50
|
-
var
|
|
51
|
-
var
|
|
52
|
-
var
|
|
50
|
+
var ResolveContentResponse = SubmitContentChainResponse;
|
|
51
|
+
var ContentOperationsParams = PaginationParams;
|
|
52
|
+
var ContentOperationsResponse = PaginatedOperations;
|
|
53
53
|
var ResolveOperationResponse = z.strictObject({
|
|
54
54
|
cid: z.string(),
|
|
55
55
|
jwsToken: z.string()
|
|
56
56
|
});
|
|
57
|
-
var ResolveDocumentResponse = z.strictObject({
|
|
58
|
-
cid: z.string(),
|
|
59
|
-
content: z.unknown()
|
|
60
|
-
});
|
|
61
57
|
var RegistryError = z.strictObject({
|
|
62
58
|
error: z.string(),
|
|
63
59
|
message: z.string()
|
|
@@ -68,12 +64,11 @@ import { Hono } from "hono";
|
|
|
68
64
|
|
|
69
65
|
// src/registry/store.ts
|
|
70
66
|
var ChainStore = class {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// --- identities ---
|
|
67
|
+
identityChains = /* @__PURE__ */ new Map();
|
|
68
|
+
contentChains = /* @__PURE__ */ new Map();
|
|
69
|
+
// --- identityChains ---
|
|
75
70
|
getIdentityChain(did) {
|
|
76
|
-
return this.
|
|
71
|
+
return this.identityChains.get(did);
|
|
77
72
|
}
|
|
78
73
|
/**
|
|
79
74
|
* Submit an identity chain. Returns 'accepted' | 'noop' | 'conflict'.
|
|
@@ -82,29 +77,22 @@ var ChainStore = class {
|
|
|
82
77
|
* - conflict: submitted chain diverges from stored chain (fork)
|
|
83
78
|
*/
|
|
84
79
|
submitIdentityChain(did, operations) {
|
|
85
|
-
return this.submitChain(this.
|
|
86
|
-
}
|
|
87
|
-
// --- entities ---
|
|
88
|
-
getEntityChain(entityId) {
|
|
89
|
-
return this.entities.get(entityId);
|
|
80
|
+
return this.submitChain(this.identityChains, did, operations);
|
|
90
81
|
}
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
// --- contentChains ---
|
|
83
|
+
getContentChain(contentId) {
|
|
84
|
+
return this.contentChains.get(contentId);
|
|
93
85
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return this.documents.get(cid);
|
|
97
|
-
}
|
|
98
|
-
setDocument(cid, content) {
|
|
99
|
-
this.documents.set(cid, content);
|
|
86
|
+
submitContentChain(contentId, operations) {
|
|
87
|
+
return this.submitChain(this.contentChains, contentId, operations);
|
|
100
88
|
}
|
|
101
89
|
// --- operations (lookup across all chains) ---
|
|
102
90
|
getOperation(cid) {
|
|
103
|
-
for (const chain of this.
|
|
91
|
+
for (const chain of this.identityChains.values()) {
|
|
104
92
|
const op = chain.operations.find((o) => o.cid === cid);
|
|
105
93
|
if (op) return op;
|
|
106
94
|
}
|
|
107
|
-
for (const chain of this.
|
|
95
|
+
for (const chain of this.contentChains.values()) {
|
|
108
96
|
const op = chain.operations.find((o) => o.cid === cid);
|
|
109
97
|
if (op) return op;
|
|
110
98
|
}
|
|
@@ -245,7 +233,7 @@ var createRegistryServer = (store = new ChainStore()) => {
|
|
|
245
233
|
const { cursor, limit } = paginationParams(c.req);
|
|
246
234
|
return c.json(paginate(chain.operations, cursor, limit));
|
|
247
235
|
});
|
|
248
|
-
app.post("/
|
|
236
|
+
app.post("/content", async (c) => {
|
|
249
237
|
const body = await c.req.json();
|
|
250
238
|
if (!body.chain || !Array.isArray(body.chain) || body.chain.length === 0) {
|
|
251
239
|
return c.json(err("BAD_REQUEST", "chain must be a non-empty array of JWS tokens"), 400);
|
|
@@ -257,13 +245,13 @@ var createRegistryServer = (store = new ChainStore()) => {
|
|
|
257
245
|
} catch (e) {
|
|
258
246
|
return c.json(err("BAD_REQUEST", `chain verification failed: ${e.message}`), 400);
|
|
259
247
|
}
|
|
260
|
-
const result = store.
|
|
248
|
+
const result = store.submitContentChain(verified.contentId, operations);
|
|
261
249
|
if (result === "conflict") {
|
|
262
250
|
return c.json(err("CONFLICT", "submitted chain conflicts with stored chain"), 409);
|
|
263
251
|
}
|
|
264
252
|
return c.json(
|
|
265
253
|
{
|
|
266
|
-
|
|
254
|
+
contentId: verified.contentId,
|
|
267
255
|
isDeleted: verified.isDeleted,
|
|
268
256
|
currentDocumentCID: verified.currentDocumentCID,
|
|
269
257
|
genesisCID: verified.genesisCID,
|
|
@@ -272,27 +260,27 @@ var createRegistryServer = (store = new ChainStore()) => {
|
|
|
272
260
|
result === "accepted" ? 201 : 200
|
|
273
261
|
);
|
|
274
262
|
});
|
|
275
|
-
app.get("/
|
|
276
|
-
const
|
|
277
|
-
const chain = store.
|
|
278
|
-
if (!chain) return c.json(err("NOT_FOUND", "
|
|
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);
|
|
279
267
|
const genesis = chain.operations[0];
|
|
280
268
|
const head = chain.operations[chain.operations.length - 1];
|
|
281
269
|
const headDecoded = decodeJwsUnsafe(head.jwsToken);
|
|
282
270
|
const headPayload = headDecoded?.payload;
|
|
283
271
|
const headType = headPayload?.type;
|
|
284
272
|
return c.json({
|
|
285
|
-
|
|
273
|
+
contentId,
|
|
286
274
|
isDeleted: headType === "delete",
|
|
287
275
|
currentDocumentCID: headType === "delete" ? null : headPayload?.documentCID ?? null,
|
|
288
276
|
genesisCID: genesis.cid,
|
|
289
277
|
headCID: head.cid
|
|
290
278
|
});
|
|
291
279
|
});
|
|
292
|
-
app.get("/
|
|
293
|
-
const
|
|
294
|
-
const chain = store.
|
|
295
|
-
if (!chain) return c.json(err("NOT_FOUND", "
|
|
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);
|
|
296
284
|
const { cursor, limit } = paginationParams(c.req);
|
|
297
285
|
return c.json(paginate(chain.operations, cursor, limit));
|
|
298
286
|
});
|
|
@@ -302,12 +290,6 @@ var createRegistryServer = (store = new ChainStore()) => {
|
|
|
302
290
|
if (!op) return c.json(err("NOT_FOUND", "operation not found"), 404);
|
|
303
291
|
return c.json({ cid: op.cid, jwsToken: op.jwsToken });
|
|
304
292
|
});
|
|
305
|
-
app.get("/documents/:cid", (c) => {
|
|
306
|
-
const cid = c.req.param("cid");
|
|
307
|
-
const content = store.getDocument(cid);
|
|
308
|
-
if (content === void 0) return c.json(err("NOT_FOUND", "document not found"), 404);
|
|
309
|
-
return c.json({ cid, content });
|
|
310
|
-
});
|
|
311
293
|
return { app, store };
|
|
312
294
|
};
|
|
313
295
|
|
|
@@ -319,11 +301,10 @@ export {
|
|
|
319
301
|
IdentityOperationsResponse,
|
|
320
302
|
SubmitContentChainRequest,
|
|
321
303
|
SubmitContentChainResponse,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
304
|
+
ResolveContentResponse,
|
|
305
|
+
ContentOperationsParams,
|
|
306
|
+
ContentOperationsResponse,
|
|
325
307
|
ResolveOperationResponse,
|
|
326
|
-
ResolveDocumentResponse,
|
|
327
308
|
RegistryError,
|
|
328
309
|
ChainStore,
|
|
329
310
|
createRegistryServer
|
|
@@ -127,7 +127,7 @@ var deriveChainIdentifier = (cidBytes, prefix) => {
|
|
|
127
127
|
const id = generateIdNoPrefix({ seed: cidBytes });
|
|
128
128
|
return `${prefix}:${id}`;
|
|
129
129
|
};
|
|
130
|
-
var
|
|
130
|
+
var deriveContentId = (cidBytes) => {
|
|
131
131
|
return generateIdNoPrefix({ seed: cidBytes });
|
|
132
132
|
};
|
|
133
133
|
|
|
@@ -289,7 +289,7 @@ var signContentOperation = async (input) => {
|
|
|
289
289
|
var verifyContentChain = async (input) => {
|
|
290
290
|
if (input.log.length === 0) throw new Error("log must have at least one operation");
|
|
291
291
|
const state = {
|
|
292
|
-
|
|
292
|
+
contentId: null,
|
|
293
293
|
genesisCID: null,
|
|
294
294
|
headCID: null,
|
|
295
295
|
isDeleted: false,
|
|
@@ -339,7 +339,7 @@ var verifyContentChain = async (input) => {
|
|
|
339
339
|
}
|
|
340
340
|
if (idx === 0) {
|
|
341
341
|
state.genesisCID = operationCID;
|
|
342
|
-
state.
|
|
342
|
+
state.contentId = deriveContentId(encoded.cid.bytes);
|
|
343
343
|
}
|
|
344
344
|
state.headCID = operationCID;
|
|
345
345
|
state.previousCID = operationCID;
|
|
@@ -358,7 +358,7 @@ var verifyContentChain = async (input) => {
|
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
return {
|
|
361
|
-
|
|
361
|
+
contentId: state.contentId,
|
|
362
362
|
genesisCID: state.genesisCID,
|
|
363
363
|
headCID: state.headCID,
|
|
364
364
|
isDeleted: state.isDeleted,
|
|
@@ -377,7 +377,7 @@ export {
|
|
|
377
377
|
encodeEd25519Multikey,
|
|
378
378
|
decodeMultikey,
|
|
379
379
|
deriveChainIdentifier,
|
|
380
|
-
|
|
380
|
+
deriveContentId,
|
|
381
381
|
signIdentityOperation,
|
|
382
382
|
verifyIdentityChain,
|
|
383
383
|
signContentOperation,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
|
|
2
|
-
export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, Signer, VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier,
|
|
3
|
-
export { ChainStore,
|
|
2
|
+
export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, Signer, VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signContentOperation, signIdentityOperation, verifyContentChain, verifyIdentityChain } from './chain/index.js';
|
|
3
|
+
export { ChainStore, ContentOperationsParams, ContentOperationsResponse, IdentityOperationsParams, IdentityOperationsResponse, OperationEntry, PaginationParams, RegistryError, ResolveContentResponse, ResolveIdentityResponse, ResolveOperationResponse, StoredChain, SubmitContentChainRequest, SubmitContentChainResponse, SubmitIdentityChainRequest, SubmitIdentityChainResponse, createRegistryServer } from './registry/index.js';
|
|
4
4
|
import 'multiformats';
|
|
5
5
|
import 'multiformats/cid';
|
|
6
6
|
import 'zod';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChainStore,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
ContentOperationsParams,
|
|
4
|
+
ContentOperationsResponse,
|
|
5
5
|
IdentityOperationsParams,
|
|
6
6
|
IdentityOperationsResponse,
|
|
7
7
|
RegistryError,
|
|
8
|
-
|
|
9
|
-
ResolveEntityResponse,
|
|
8
|
+
ResolveContentResponse,
|
|
10
9
|
ResolveIdentityResponse,
|
|
11
10
|
ResolveOperationResponse,
|
|
12
11
|
SubmitContentChainRequest,
|
|
@@ -14,7 +13,7 @@ import {
|
|
|
14
13
|
SubmitIdentityChainRequest,
|
|
15
14
|
SubmitIdentityChainResponse,
|
|
16
15
|
createRegistryServer
|
|
17
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-U6DANYPT.js";
|
|
18
17
|
import {
|
|
19
18
|
ContentOperation,
|
|
20
19
|
ED25519_PRIV_MULTICODEC,
|
|
@@ -24,13 +23,13 @@ import {
|
|
|
24
23
|
VerifiedIdentity,
|
|
25
24
|
decodeMultikey,
|
|
26
25
|
deriveChainIdentifier,
|
|
27
|
-
|
|
26
|
+
deriveContentId,
|
|
28
27
|
encodeEd25519Multikey,
|
|
29
28
|
signContentOperation,
|
|
30
29
|
signIdentityOperation,
|
|
31
30
|
verifyContentChain,
|
|
32
31
|
verifyIdentityChain
|
|
33
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-VEBMLR37.js";
|
|
34
33
|
import {
|
|
35
34
|
JwsVerificationError,
|
|
36
35
|
JwtVerificationError,
|
|
@@ -57,10 +56,10 @@ import {
|
|
|
57
56
|
export {
|
|
58
57
|
ChainStore,
|
|
59
58
|
ContentOperation,
|
|
59
|
+
ContentOperationsParams,
|
|
60
|
+
ContentOperationsResponse,
|
|
60
61
|
ED25519_PRIV_MULTICODEC,
|
|
61
62
|
ED25519_PUB_MULTICODEC,
|
|
62
|
-
EntityOperationsParams,
|
|
63
|
-
EntityOperationsResponse,
|
|
64
63
|
IdentityOperation,
|
|
65
64
|
IdentityOperationsParams,
|
|
66
65
|
IdentityOperationsResponse,
|
|
@@ -68,8 +67,7 @@ export {
|
|
|
68
67
|
JwtVerificationError,
|
|
69
68
|
MultikeyPublicKey,
|
|
70
69
|
RegistryError,
|
|
71
|
-
|
|
72
|
-
ResolveEntityResponse,
|
|
70
|
+
ResolveContentResponse,
|
|
73
71
|
ResolveIdentityResponse,
|
|
74
72
|
ResolveOperationResponse,
|
|
75
73
|
SubmitContentChainRequest,
|
|
@@ -88,7 +86,7 @@ export {
|
|
|
88
86
|
decodeJwtUnsafe,
|
|
89
87
|
decodeMultikey,
|
|
90
88
|
deriveChainIdentifier,
|
|
91
|
-
|
|
89
|
+
deriveContentId,
|
|
92
90
|
encodeEd25519Multikey,
|
|
93
91
|
generateId,
|
|
94
92
|
generateIdNoPrefix,
|