@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/PROTOCOL.md
CHANGED
|
@@ -20,14 +20,17 @@ The protocol is not coupled to the DFOS platform. Any system implementing the sa
|
|
|
20
20
|
|
|
21
21
|
## Protocol Overview
|
|
22
22
|
|
|
23
|
-
The DFOS protocol has
|
|
23
|
+
The DFOS protocol has five components:
|
|
24
24
|
|
|
25
|
-
|
|
|
25
|
+
| Component | Concern |
|
|
26
26
|
| --------------------- | ---------------------------------------------------------------------------- |
|
|
27
27
|
| **Crypto core** | Identity chains + content chains — Ed25519 signatures, JWS tokens, CID links |
|
|
28
|
-
| **
|
|
28
|
+
| **Credentials** | Auth tokens (DID-signed JWT) and VC-JWT credentials for authorization |
|
|
29
|
+
| **Beacons** | Signed merkle root announcements — periodic commitment over content sets |
|
|
30
|
+
| **Countersignatures** | Witness attestation — third-party signatures over existing chain operations |
|
|
31
|
+
| **Merkle trees** | SHA-256 binary trees over content IDs — inclusion proofs for beacon roots |
|
|
29
32
|
|
|
30
|
-
The crypto core is the trust boundary — everything below it is cryptographically verified.
|
|
33
|
+
The crypto core is the trust boundary — everything below it is cryptographically verified. Documents are flat content objects, content-addressed directly: `documentCID = CID(dagCborCanonicalEncode(contentObject))`. What goes inside the content object is application-defined — see the [DFOS Content Model](https://protocol.dfos.com/content-model) for the standard schema library.
|
|
31
34
|
|
|
32
35
|
### Crypto Core: Two Chain Types
|
|
33
36
|
|
|
@@ -39,45 +42,19 @@ The crypto core is the trust boundary — everything below it is cryptographical
|
|
|
39
42
|
| JWS typ | `did:dfos:identity-op` | `did:dfos:content-op` |
|
|
40
43
|
| Self-sovereign | Yes (signs own operations) | No (signed by external identity) |
|
|
41
44
|
|
|
42
|
-
Both chains are signed linked lists of state commitments. Identity chains embed their state (key sets). Content chains reference their state via `documentCID` — a content-addressed pointer to a
|
|
43
|
-
|
|
44
|
-
### Document Envelope
|
|
45
|
-
|
|
46
|
-
Every document committed to by a content chain uses a standard envelope, defined by JSON Schema at [`schemas/document-envelope.v1.json`](schemas/document-envelope.v1.json) (`https://schemas.dfos.com/document-envelope/v1`):
|
|
47
|
-
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"content": { "$schema": "https://schemas.dfos.com/post/v1", ... },
|
|
51
|
-
"baseDocumentCID": null,
|
|
52
|
-
"createdByDID": "did:dfos:...",
|
|
53
|
-
"createdAt": "2026-03-07T00:02:00.000Z"
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
| Field | Type | Description |
|
|
58
|
-
| ----------------- | ------------ | --------------------------------------------------------------------------- |
|
|
59
|
-
| `content` | object | Application-defined content — must include `$schema` URI, opaque to chains |
|
|
60
|
-
| `baseDocumentCID` | string\|null | Optional CID of a prior document version. Semantics are application-defined |
|
|
61
|
-
| `createdByDID` | string | DID of the identity that created this document version |
|
|
62
|
-
| `createdAt` | ISO 8601 | When this document version was created |
|
|
63
|
-
|
|
64
|
-
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.
|
|
45
|
+
Both chains are signed linked lists of state commitments. Identity chains embed their state (key sets). Content chains reference their state via `documentCID` — a content-addressed pointer to a flat content object.
|
|
65
46
|
|
|
66
47
|
### Addressing
|
|
67
48
|
|
|
68
|
-
Three
|
|
69
|
-
|
|
70
|
-
| Thing | Form | Example |
|
|
71
|
-
| --------------------- | -------------------------- | --------------------------------- |
|
|
72
|
-
| Operation or document | CID (dag-cbor + SHA-256) | See below |
|
|
73
|
-
| Content chain | `<hash>` (bare, no prefix) | `67t27rzc83v7c22n9t6z7c` |
|
|
74
|
-
| Identity chain | `did:dfos:<hash>` | `did:dfos:e3vvtck42d4eacdnzvtrn6` |
|
|
49
|
+
Three addressing modes, self-describing by format:
|
|
75
50
|
|
|
76
|
-
Example
|
|
51
|
+
| Thing | Form | Example |
|
|
52
|
+
| --------------------- | ------------------------ | --------------------------------- |
|
|
53
|
+
| Operation or document | CID (dag-cbor + SHA-256) | `bafyrei...` (base32lower) |
|
|
54
|
+
| Content chain | contentId (22-char hash) | `a82z92a3hndk6c97thcrn8` |
|
|
55
|
+
| Identity chain | DID | `did:dfos:e3vvtck42d4eacdnzvtrn6` |
|
|
77
56
|
|
|
78
|
-
|
|
79
|
-
bafyreibanjpgcqffcfhr4sptzjfthh5szohhbo5tjfulemkw7uhden5uqy
|
|
80
|
-
```
|
|
57
|
+
CIDs are specific immutable artifacts — a pointer to an exact operation or document. Content IDs are living content chain entities — the 22-char bare hash derived from the genesis CID. DIDs are living identity chain entities.
|
|
81
58
|
|
|
82
59
|
Operations and documents are CIDs — standard IPLD content addresses. Content chains and identity chains use derived identifiers — `customAlpha(SHA-256(genesis CID bytes))`. Same derivation for both. Identity chains prepend `did:dfos:` (W3C DID spec). Content identifiers are bare — just the 22-char hash, no prefix.
|
|
83
60
|
|
|
@@ -116,19 +93,21 @@ This is a self-sovereign invariant: the identity chain defines its own valid sig
|
|
|
116
93
|
|
|
117
94
|
Content chain verification requires a **valid EdDSA signature** and delegates key resolution to the caller. The `kid` in each operation's JWS header is a DID URL (`did:dfos:<id>#<keyId>`). The verifier calls `resolveKey(kid)` to obtain the raw Ed25519 public key bytes for that key on that identity. How the resolver obtains and validates the identity's key state is application-defined.
|
|
118
95
|
|
|
119
|
-
|
|
96
|
+
**Creator sovereignty**: The DID that signs the genesis (create) operation is the **chain creator** and permanently owns the chain. The creator can sign subsequent operations directly — no credential needed. Other DIDs require a **DFOSContentWrite** VC-JWT credential in the operation's `authorization` field, issued by the creator DID. See [Credentials](#credentials) for the VC-JWT format.
|
|
97
|
+
|
|
98
|
+
**Signer-payload consistency**: The `kid` DID in the JWS header MUST match the `did` field in the content operation payload. This enables discrimination between author operations and countersignatures — if the kid DID differs from the payload `did`, it is a countersignature (witness attestation), not a chain operation.
|
|
120
99
|
|
|
121
100
|
**What the protocol enforces:**
|
|
122
101
|
|
|
123
102
|
- The EdDSA signature on each operation is valid against the key returned by `resolveKey(kid)`
|
|
124
103
|
- Chain integrity (CID links, timestamp ordering, terminal state)
|
|
104
|
+
- The `kid` DID matches the payload `did` for chain operations
|
|
105
|
+
- Creator-sovereignty authorization (when `enforceAuthorization` is enabled): non-creator signers must present a valid DFOSContentWrite VC-JWT issued by the creator
|
|
125
106
|
|
|
126
107
|
**What the protocol does NOT enforce (application concerns):**
|
|
127
108
|
|
|
128
|
-
- Which identities are authorized to sign operations on a given chain
|
|
129
109
|
- Which key role (auth, assert, controller) the signing key must have
|
|
130
|
-
-
|
|
131
|
-
- Ownership or attribution semantics between signers and content chains
|
|
110
|
+
- Ownership or attribution semantics beyond creator sovereignty
|
|
132
111
|
|
|
133
112
|
### Terminal States and Special Operations
|
|
134
113
|
|
|
@@ -140,7 +119,17 @@ Identity chains are self-sovereign — they define their own valid signers via `
|
|
|
140
119
|
|
|
141
120
|
### `typ` Header
|
|
142
121
|
|
|
143
|
-
The JWS `typ` header
|
|
122
|
+
The JWS `typ` header uses protocol-specific values (not IANA media types):
|
|
123
|
+
|
|
124
|
+
| `typ` value | Usage |
|
|
125
|
+
| ---------------------- | --------------------------------------------- |
|
|
126
|
+
| `did:dfos:identity-op` | Identity chain operations |
|
|
127
|
+
| `did:dfos:content-op` | Content chain operations |
|
|
128
|
+
| `did:dfos:beacon` | Beacon announcements |
|
|
129
|
+
| `JWT` | Auth tokens (DID-signed relay authentication) |
|
|
130
|
+
| `vc+jwt` | VC-JWT credentials (W3C VC Data Model v2) |
|
|
131
|
+
|
|
132
|
+
Protocol-specific `typ` values are non-standard per JOSE convention, documented intentionally. `JWT` and `vc+jwt` follow IANA conventions. The `typ` header aids routing but is not security-critical. Implementations SHOULD validate it but MUST NOT rely on it for security decisions.
|
|
144
133
|
|
|
145
134
|
### Operation Field Limits
|
|
146
135
|
|
|
@@ -148,6 +137,7 @@ The protocol defines maximum sizes for all operation fields as abuse-prevention
|
|
|
148
137
|
|
|
149
138
|
| Field | Max | Rationale |
|
|
150
139
|
| -------------------------------------------- | --------- | -------------------------------------- |
|
|
140
|
+
| `did` | 256 chars | ~8× typical `did:dfos:` (~31 chars) |
|
|
151
141
|
| `key.id` | 64 chars | ~3× typical key ID (`key_` + 22 chars) |
|
|
152
142
|
| `key.publicKeyMultibase` | 128 chars | ~2× Ed25519 multikey (~50 chars) |
|
|
153
143
|
| `authKeys` / `assertKeys` / `controllerKeys` | 16 items | Generous for key rotation |
|
|
@@ -301,22 +291,29 @@ DID: did:dfos:e3vvtck42d4eacdnzvtrn6
|
|
|
301
291
|
```typescript
|
|
302
292
|
// Genesis — starts the content chain, commits initial document
|
|
303
293
|
{ version: 1, type: "create",
|
|
304
|
-
|
|
294
|
+
did: string, // author DID, committed to by CID
|
|
295
|
+
documentCID: string, // CID of flat content object
|
|
296
|
+
baseDocumentCID: string | null, // edit lineage — CID of prior document version
|
|
305
297
|
createdAt: string,
|
|
306
298
|
note: string | null }
|
|
307
299
|
|
|
308
300
|
// Content change (null documentCID = clear content)
|
|
309
301
|
{ version: 1, type: "update",
|
|
302
|
+
did: string, // author DID
|
|
310
303
|
previousOperationCID: string,
|
|
311
304
|
documentCID: string | null,
|
|
305
|
+
baseDocumentCID: string | null,
|
|
312
306
|
createdAt: string,
|
|
313
|
-
note: string | null
|
|
307
|
+
note: string | null,
|
|
308
|
+
authorization?: string } // VC-JWT for delegated operations
|
|
314
309
|
|
|
315
310
|
// Permanent destruction
|
|
316
311
|
{ version: 1, type: "delete",
|
|
312
|
+
did: string, // author DID
|
|
317
313
|
previousOperationCID: string,
|
|
318
314
|
createdAt: string,
|
|
319
|
-
note: string | null
|
|
315
|
+
note: string | null,
|
|
316
|
+
authorization?: string } // VC-JWT for delegated operations
|
|
320
317
|
```
|
|
321
318
|
|
|
322
319
|
### MultikeyPublicKey
|
|
@@ -372,7 +369,7 @@ Every operation JWS (identity-op and content-op) includes a `cid` field in the p
|
|
|
372
369
|
|
|
373
370
|
A CID mismatch between header and derived value immediately surfaces dag-cbor encoding disagreements across implementations.
|
|
374
371
|
|
|
375
|
-
Note: JWT tokens
|
|
372
|
+
Note: JWT auth tokens and VC-JWT credentials do NOT include a `cid` header — this field is specific to operation JWS tokens and beacons.
|
|
376
373
|
|
|
377
374
|
### CID Derivation
|
|
378
375
|
|
|
@@ -392,6 +389,310 @@ Where `idEncode` is the 19-char alphabet encoding described above.
|
|
|
392
389
|
|
|
393
390
|
---
|
|
394
391
|
|
|
392
|
+
## Credentials
|
|
393
|
+
|
|
394
|
+
Two credential types handle authentication and authorization. Both are DID-signed JWTs using Ed25519 (`alg: "EdDSA"`).
|
|
395
|
+
|
|
396
|
+
### Auth Tokens (Relay Authentication)
|
|
397
|
+
|
|
398
|
+
A DID-signed JWT proving the caller controls a DID. Short-lived, scoped to a specific relay via the `aud` (audience) claim. Used for relay AuthN — establishing identity before making requests.
|
|
399
|
+
|
|
400
|
+
**JWT Header:**
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"alg": "EdDSA",
|
|
405
|
+
"typ": "JWT",
|
|
406
|
+
"kid": "did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8"
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**JWT Payload:**
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"iss": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
415
|
+
"sub": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
416
|
+
"aud": "relay.example.com",
|
|
417
|
+
"exp": 1772845200,
|
|
418
|
+
"iat": 1772841600
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
| Field | Type | Description |
|
|
423
|
+
| ----- | ------ | ---------------------------------------------------------- |
|
|
424
|
+
| `iss` | string | DID proving identity (the signer) |
|
|
425
|
+
| `sub` | string | Same as `iss` for auth tokens |
|
|
426
|
+
| `aud` | string | Target relay hostname (prevents cross-relay replay) |
|
|
427
|
+
| `exp` | number | Expiration — unix seconds (short-lived, typically minutes) |
|
|
428
|
+
| `iat` | number | Issued-at — unix seconds |
|
|
429
|
+
|
|
430
|
+
**Verification:** Standard JWT verification — EdDSA signature check, temporal validity (`iat` must not be in the future, `exp` must be after current time), audience match. The `kid` MUST be a DID URL (`did:dfos:xxx#key_yyy`) and the `kid` DID MUST match `iss`.
|
|
431
|
+
|
|
432
|
+
Auth tokens do NOT include a `cid` header — they are ephemeral session tokens, not content-addressed artifacts.
|
|
433
|
+
|
|
434
|
+
### VC-JWT Credentials (Authorization)
|
|
435
|
+
|
|
436
|
+
W3C Verifiable Credential Data Model v2 credentials encoded as JWT (`typ: "vc+jwt"`). Two credential types:
|
|
437
|
+
|
|
438
|
+
| Credential Type | Purpose |
|
|
439
|
+
| ------------------ | ------------------------------------------------------------ |
|
|
440
|
+
| `DFOSContentWrite` | Authorize extending a content chain (embedded in operations) |
|
|
441
|
+
| `DFOSContentRead` | Authorize reading content plane data (presented to relay) |
|
|
442
|
+
|
|
443
|
+
**VC-JWT Header:**
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"alg": "EdDSA",
|
|
448
|
+
"typ": "vc+jwt",
|
|
449
|
+
"kid": "did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8"
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**VC-JWT Payload:**
|
|
454
|
+
|
|
455
|
+
```json
|
|
456
|
+
{
|
|
457
|
+
"iss": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
458
|
+
"sub": "did:dfos:nzkf838efr424433rn2rzk",
|
|
459
|
+
"exp": 1798761600,
|
|
460
|
+
"iat": 1772841600,
|
|
461
|
+
"vc": {
|
|
462
|
+
"@context": ["https://www.w3.org/ns/credentials/v2"],
|
|
463
|
+
"type": ["VerifiableCredential", "DFOSContentWrite"],
|
|
464
|
+
"credentialSubject": {}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
| Field | Type | Description |
|
|
470
|
+
| ---------------------- | -------- | -------------------------------------------------------- |
|
|
471
|
+
| `iss` | string | DID granting the credential (content creator/controller) |
|
|
472
|
+
| `sub` | string | DID receiving the credential (collaborator/reader) |
|
|
473
|
+
| `exp` | number | Expiration — unix seconds |
|
|
474
|
+
| `iat` | number | Issued-at — unix seconds |
|
|
475
|
+
| `vc.@context` | string[] | Must be `["https://www.w3.org/ns/credentials/v2"]` |
|
|
476
|
+
| `vc.type` | string[] | `["VerifiableCredential", "<DFOSCredentialType>"]` |
|
|
477
|
+
| `vc.credentialSubject` | object | Optional narrowing — see scope narrowing below |
|
|
478
|
+
|
|
479
|
+
**Scope narrowing:** The `credentialSubject` object may contain a `contentId` field. If absent, the credential grants broad access to all content by the issuer. If present, the credential is narrowed to the specific content chain.
|
|
480
|
+
|
|
481
|
+
```json
|
|
482
|
+
// Broad — all content by this DID
|
|
483
|
+
{ "credentialSubject": {} }
|
|
484
|
+
|
|
485
|
+
// Narrow — specific content chain only
|
|
486
|
+
{ "credentialSubject": { "contentId": "a82z92a3hndk6c97thcrn8" } }
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Verification:** EdDSA signature check, temporal validity (`iat` must not be in the future, `exp` must be after current time — using operation `createdAt` for chain-embedded VCs, wall clock for relay-presented VCs), `kid` DID URL format, `kid` DID matches `iss`, payload structure via Zod schema. Optionally verify `sub` and credential type match expectations.
|
|
490
|
+
|
|
491
|
+
### Content Chain Authorization
|
|
492
|
+
|
|
493
|
+
When `enforceAuthorization` is enabled on content chain verification:
|
|
494
|
+
|
|
495
|
+
1. **Genesis operation**: The signer is the chain creator, always authorized
|
|
496
|
+
2. **Creator signs subsequent ops**: Authorized directly — no credential needed
|
|
497
|
+
3. **Different DID signs**: Must include an `authorization` field containing a valid `DFOSContentWrite` VC-JWT where:
|
|
498
|
+
- `iss` matches the chain creator DID
|
|
499
|
+
- `sub` matches the signing DID
|
|
500
|
+
- The credential is temporally valid (`iat <= op.createdAt < exp`, not wall clock)
|
|
501
|
+
- If `contentId` is present in `credentialSubject`, it must match this chain's contentId
|
|
502
|
+
- The credential type is `DFOSContentWrite`
|
|
503
|
+
|
|
504
|
+
The `authorization` field is available on `update` and `delete` content operations. It is absent for creator-signed operations.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Beacons
|
|
509
|
+
|
|
510
|
+
A beacon is a signed announcement of a merkle root — a periodic commitment over a set of content IDs. Beacons are floating signed artifacts, not chained. They provide a compact, verifiable snapshot of an identity's content set at a point in time.
|
|
511
|
+
|
|
512
|
+
### Beacon Payload
|
|
513
|
+
|
|
514
|
+
```json
|
|
515
|
+
{
|
|
516
|
+
"version": 1,
|
|
517
|
+
"type": "beacon",
|
|
518
|
+
"did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
519
|
+
"merkleRoot": "7e80d4780f454e0fca0b090d8c646f572b49354f54154531606105aad2fda28e",
|
|
520
|
+
"createdAt": "2026-03-07T00:05:00.000Z"
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
| Field | Type | Description |
|
|
525
|
+
| ------------ | ------ | ------------------------------------------------------- |
|
|
526
|
+
| `version` | 1 | Protocol version |
|
|
527
|
+
| `type` | string | Literal `"beacon"` |
|
|
528
|
+
| `did` | string | DID of the identity publishing the beacon |
|
|
529
|
+
| `merkleRoot` | string | Hex-encoded SHA-256 root (64 chars, `/^[0-9a-f]{64}$/`) |
|
|
530
|
+
| `createdAt` | string | ISO 8601 timestamp |
|
|
531
|
+
|
|
532
|
+
### Beacon JWS Header
|
|
533
|
+
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"alg": "EdDSA",
|
|
537
|
+
"typ": "did:dfos:beacon",
|
|
538
|
+
"kid": "did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8",
|
|
539
|
+
"cid": "bafyreihholuui7s7ns74iem6ahfxsb472hwogbqd32yrrp5fztc3kxa5qu"
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Worked Example: Beacon
|
|
544
|
+
|
|
545
|
+
Using the reference identity (`did:dfos:e3vvtck42d4eacdnzvtrn6`) and key 1 from the identity chain examples. The beacon commits to a merkle root over 5 content IDs (see Merkle Tree worked example below).
|
|
546
|
+
|
|
547
|
+
**Beacon CID** (dag-cbor canonical encode → CIDv1):
|
|
548
|
+
|
|
549
|
+
```
|
|
550
|
+
bafyreihholuui7s7ns74iem6ahfxsb472hwogbqd32yrrp5fztc3kxa5qu
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**Controller JWS** (key 1 signs):
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
kid: did:dfos:e3vvtck42d4eacdnzvtrn6#key_r9ev34fvc23z999veaaft8
|
|
557
|
+
typ: did:dfos:beacon
|
|
558
|
+
cid: bafyreihholuui7s7ns74iem6ahfxsb472hwogbqd32yrrp5fztc3kxa5qu
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Witness countersignature** (key 2 signs the same payload — same CID, different kid):
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
kid: did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd
|
|
565
|
+
typ: did:dfos:beacon
|
|
566
|
+
cid: bafyreihholuui7s7ns74iem6ahfxsb472hwogbqd32yrrp5fztc3kxa5qu
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Both JWS tokens commit to identical bytes (same CID). The controller/witness distinction is determined at verification time by comparing the `kid` DID to the payload `did`.
|
|
570
|
+
|
|
571
|
+
Full JWS tokens are in [`examples/beacon.json`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/examples/beacon.json).
|
|
572
|
+
|
|
573
|
+
### Beacon Semantics
|
|
574
|
+
|
|
575
|
+
Beacons are not chained — there is no `previousOperationCID`. For a given DID, the latest beacon with a strictly-greater `createdAt` timestamp wins. Beacons replace, not accumulate.
|
|
576
|
+
|
|
577
|
+
**Clock skew tolerance**: Implementations MUST reject beacons with a `createdAt` more than 5 minutes in the future relative to the verifier's clock. This prevents pre-dating attacks while accommodating reasonable clock drift.
|
|
578
|
+
|
|
579
|
+
**merkleRoot**: A hex-encoded SHA-256 hash (64 characters). This is a commitment, not a CID — it uses raw SHA-256, not dag-cbor encoding. See the Merkle Tree section below for construction. An empty content set produces a `null` merkle root (no beacon needed).
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Merkle Trees
|
|
584
|
+
|
|
585
|
+
Beacons commit to a set of content IDs via a pure SHA-256 binary Merkle tree. The tree has no dag-cbor dependency — it uses only SHA-256 over raw bytes.
|
|
586
|
+
|
|
587
|
+
### Construction
|
|
588
|
+
|
|
589
|
+
1. **Collect** all content IDs (22-char bare hashes) in the set
|
|
590
|
+
2. **Sort** content IDs lexicographically (UTF-8 byte order)
|
|
591
|
+
3. **Hash leaves**: for each content ID, `SHA-256(UTF-8(contentId))` → 32-byte leaf hash
|
|
592
|
+
4. **Build tree**: recursively pair adjacent hashes. For each pair, `SHA-256(left || right)` → 32 bytes. If a level has an odd number of nodes, the last node is promoted to the next level unpaired.
|
|
593
|
+
5. **Root**: the final 32-byte hash, hex-encoded to a 64-character string
|
|
594
|
+
|
|
595
|
+
An empty set of content IDs produces a `null` root. A single content ID produces a root equal to `hex(SHA-256(UTF-8(contentId)))`.
|
|
596
|
+
|
|
597
|
+
### Worked Example: Merkle Tree
|
|
598
|
+
|
|
599
|
+
5 content IDs: `["alpha", "bravo", "charlie", "delta", "echo"]`
|
|
600
|
+
|
|
601
|
+
Already sorted lexicographically. Hash each leaf:
|
|
602
|
+
|
|
603
|
+
```
|
|
604
|
+
alpha → SHA-256("alpha") → 8ed3f6ad685b959ead7022518e1af76cd816f8e8ec7ccdda1ed4018e8f2223f8
|
|
605
|
+
bravo → SHA-256("bravo") → 4f4a9410ffcdf895c4adb880659e9b5c0dd1f23a30790684340b3eaacb045398
|
|
606
|
+
charlie → SHA-256("charlie") → 36ef585cd42d49706cd2827a77d86c91bfdaf87a3f22b8f0e0308bd2c16cf85f
|
|
607
|
+
delta → SHA-256("delta") → 18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4
|
|
608
|
+
echo → SHA-256("echo") → 092c79e8f80e559e404bcf660c48f3522b67aba9ff1484b0367e1a4ddef7431d
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
Build tree bottom-up, pairing left-to-right. Odd nodes promote unpaired:
|
|
612
|
+
|
|
613
|
+
```
|
|
614
|
+
Level 0 (leaves): [alpha] [bravo] [charlie] [delta] [echo]
|
|
615
|
+
Level 1: [alpha‖bravo] [charlie‖delta] [echo] ← promoted
|
|
616
|
+
Level 2: [L1-left‖L1-mid] [echo] ← promoted
|
|
617
|
+
Level 3 (root): [L2-left‖echo]
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Interior hashes:
|
|
621
|
+
|
|
622
|
+
```
|
|
623
|
+
SHA-256(alpha‖bravo) → 90d39555bb3c223e12f5a375c3011d2462fe2e1e36b8416a0b623d5831a9b4f3
|
|
624
|
+
SHA-256(charlie‖delta) → 6b55e77bef32937d9ccce2bd4b18127b0483f0be8e5b63c30bcc2b0d09f7dd44
|
|
625
|
+
SHA-256(alpha‖bravo ‖ charlie‖delta) → 23c83cb862e3b6a86eb2dfa0ea8ba0edcf1c3f3b8f14abc5eb9d72eab2edc2f7
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**Root** (level 3):
|
|
629
|
+
|
|
630
|
+
```
|
|
631
|
+
SHA-256(23c83c...edc2f7 ‖ 092c79...f7431d) → 7e80d4780f454e0fca0b090d8c646f572b49354f54154531606105aad2fda28e
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Inclusion Proofs
|
|
635
|
+
|
|
636
|
+
A Merkle inclusion proof demonstrates that a specific content ID is part of the committed set without revealing the full set. The proof consists of sibling hashes along the path from leaf to root, plus a direction (left/right) for each step.
|
|
637
|
+
|
|
638
|
+
### Worked Example: Inclusion Proof for "charlie"
|
|
639
|
+
|
|
640
|
+
Starting from the leaf hash of "charlie" (`36ef58...`), walk to the root using sibling hashes:
|
|
641
|
+
|
|
642
|
+
```
|
|
643
|
+
Step 1: charlie (index 2) paired with delta (index 3)
|
|
644
|
+
sibling: 4f4a9410...045398 (delta leaf) position: right
|
|
645
|
+
→ SHA-256(charlie ‖ delta) → 6b55e77b...f7dd44
|
|
646
|
+
|
|
647
|
+
Step 2: charlie‖delta paired with alpha‖bravo
|
|
648
|
+
sibling: 90d39555...a9b4f3 (alpha‖bravo) position: left
|
|
649
|
+
→ SHA-256(alpha‖bravo ‖ charlie‖delta) → 23c83cb8...edc2f7
|
|
650
|
+
|
|
651
|
+
Step 3: L2-left paired with echo (promoted)
|
|
652
|
+
sibling: 092c79e8...f7431d (echo leaf) position: right
|
|
653
|
+
→ SHA-256(L2-left ‖ echo) → 7e80d478...fda28e ✓ matches root
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Proof path (from [`examples/merkle-tree.json`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/examples/merkle-tree.json)):
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
[
|
|
660
|
+
{
|
|
661
|
+
"hash": "4f4a9410ffcdf895c4adb880659e9b5c0dd1f23a30790684340b3eaacb045398",
|
|
662
|
+
"position": "right"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
"hash": "90d39555bb3c223e12f5a375c3011d2462fe2e1e36b8416a0b623d5831a9b4f3",
|
|
666
|
+
"position": "left"
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
"hash": "092c79e8f80e559e404bcf660c48f3522b67aba9ff1484b0367e1a4ddef7431d",
|
|
670
|
+
"position": "right"
|
|
671
|
+
}
|
|
672
|
+
]
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## Countersignatures
|
|
678
|
+
|
|
679
|
+
A countersignature is a witness attestation — a third-party identity signing the same CID-committed bytes as an existing chain operation. Countersignatures use the same JWS format and `typ` (`did:dfos:content-op`) as the original operation.
|
|
680
|
+
|
|
681
|
+
### Discrimination Rule
|
|
682
|
+
|
|
683
|
+
The protocol distinguishes author operations from countersignatures by comparing the `kid` DID in the JWS header to the `did` field in the operation payload:
|
|
684
|
+
|
|
685
|
+
- **`kid` DID === payload `did`** → author operation (chain operation)
|
|
686
|
+
- **`kid` DID !== payload `did`** → witness countersignature
|
|
687
|
+
|
|
688
|
+
### Semantics
|
|
689
|
+
|
|
690
|
+
A countersignature proves that a witness identity has seen and attested to a specific operation. The witness signs the exact same payload (same CID), but with their own key. The countersignature's JWS header will contain the witness's `kid` (their DID URL), while the payload's `did` field remains the original author's DID.
|
|
691
|
+
|
|
692
|
+
Countersignatures are not part of the chain — they do not have `previousOperationCID` links and do not affect chain state. They are auxiliary attestations stored alongside chain operations.
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
395
696
|
## Verification
|
|
396
697
|
|
|
397
698
|
### Identity Chain
|
|
@@ -411,12 +712,14 @@ Where `idEncode` is the 19-char alphabet encoding described above.
|
|
|
411
712
|
### Content Chain
|
|
412
713
|
|
|
413
714
|
1. Decode each JWS, parse payload as ContentOperation
|
|
414
|
-
2. First op must be `type: "create"`
|
|
715
|
+
2. First op must be `type: "create"` — the signer is the chain creator
|
|
415
716
|
3. For each subsequent op: verify `previousOperationCID` matches, verify `createdAt` increasing
|
|
416
717
|
4. Derive the operation CID via dag-cbor canonical encoding. Verify `header.cid` matches the derived CID.
|
|
417
|
-
5.
|
|
418
|
-
6.
|
|
419
|
-
7.
|
|
718
|
+
5. Verify the `kid` DID matches the payload `did` field (mismatches indicate a countersignature, not a chain operation)
|
|
719
|
+
6. Resolve `kid` via external key resolver (caller provides)
|
|
720
|
+
7. Verify EdDSA JWS signature
|
|
721
|
+
8. If `enforceAuthorization` is enabled and the signer DID differs from the chain creator: verify the `authorization` field contains a valid `DFOSContentWrite` VC-JWT issued by the creator DID, with `sub` matching the signer, not expired at `op.createdAt`, and `contentId` (if present) matching this chain
|
|
722
|
+
9. Apply state change (set document, clear, or delete)
|
|
420
723
|
|
|
421
724
|
---
|
|
422
725
|
|
|
@@ -497,7 +800,7 @@ JWS Signature (hex):
|
|
|
497
800
|
JWS Token:
|
|
498
801
|
|
|
499
802
|
```
|
|
500
|
-
eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.
|
|
803
|
+
eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTF2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw
|
|
501
804
|
```
|
|
502
805
|
|
|
503
806
|
Operation CID:
|
|
@@ -575,26 +878,22 @@ Post-rotation: DID unchanged (`did:dfos:e3vvtck42d4eacdnzvtrn6`), controller rot
|
|
|
575
878
|
|
|
576
879
|
### Content Chain: Document + Create
|
|
577
880
|
|
|
578
|
-
Document (
|
|
881
|
+
Document (flat content object):
|
|
579
882
|
|
|
580
883
|
```json
|
|
581
884
|
{
|
|
582
|
-
"
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
},
|
|
588
|
-
"baseDocumentCID": null,
|
|
589
|
-
"createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
590
|
-
"createdAt": "2026-03-07T00:02:00.000Z"
|
|
885
|
+
"$schema": "https://schemas.dfos.com/post/v1",
|
|
886
|
+
"format": "short-post",
|
|
887
|
+
"title": "Hello World",
|
|
888
|
+
"body": "First post on the protocol.",
|
|
889
|
+
"createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
|
|
591
890
|
}
|
|
592
891
|
```
|
|
593
892
|
|
|
594
893
|
Document CID:
|
|
595
894
|
|
|
596
895
|
```
|
|
597
|
-
|
|
896
|
+
bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4
|
|
598
897
|
```
|
|
599
898
|
|
|
600
899
|
Content Create JWS Header:
|
|
@@ -604,7 +903,7 @@ Content Create JWS Header:
|
|
|
604
903
|
"alg": "EdDSA",
|
|
605
904
|
"typ": "did:dfos:content-op",
|
|
606
905
|
"kid": "did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd",
|
|
607
|
-
"cid": "
|
|
906
|
+
"cid": "bafyreiaedhjq64aajpwociahl5w37j6uoxr5mojoq5dnah6fpvxr5d4lxu"
|
|
608
907
|
}
|
|
609
908
|
```
|
|
610
909
|
|
|
@@ -614,7 +913,9 @@ Content Create Payload:
|
|
|
614
913
|
{
|
|
615
914
|
"version": 1,
|
|
616
915
|
"type": "create",
|
|
617
|
-
"
|
|
916
|
+
"did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
917
|
+
"documentCID": "bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4",
|
|
918
|
+
"baseDocumentCID": null,
|
|
618
919
|
"createdAt": "2026-03-07T00:02:00.000Z",
|
|
619
920
|
"note": null
|
|
620
921
|
}
|
|
@@ -623,19 +924,19 @@ Content Create Payload:
|
|
|
623
924
|
Content Create JWS Signature (hex):
|
|
624
925
|
|
|
625
926
|
```
|
|
626
|
-
|
|
927
|
+
46feaf973e4c7ebc2a0d4ad25481ace197de05b91051205c5e1c7067a85fb9d4abe4cc61625d3c853a8b0ce0345b534c8cdd07b34216f635d3c0bc0fd5d30306
|
|
627
928
|
```
|
|
628
929
|
|
|
629
930
|
Content Create JWS Token:
|
|
630
931
|
|
|
631
932
|
```
|
|
632
|
-
|
|
933
|
+
eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.Rv6vlz5MfrwqDUrSVIGs4ZfeBbkQUSBcXhxwZ6hfudSr5MxhYl08hTqLDOA0W1NMjN0Hs0IW9jXTwLwP1dMDBg
|
|
633
934
|
```
|
|
634
935
|
|
|
635
936
|
Content Operation CID:
|
|
636
937
|
|
|
637
938
|
```
|
|
638
|
-
|
|
939
|
+
bafyreiaedhjq64aajpwociahl5w37j6uoxr5mojoq5dnah6fpvxr5d4lxu
|
|
639
940
|
```
|
|
640
941
|
|
|
641
942
|
### Content Chain: Update
|
|
@@ -646,39 +947,45 @@ Content Update Payload:
|
|
|
646
947
|
{
|
|
647
948
|
"version": 1,
|
|
648
949
|
"type": "update",
|
|
649
|
-
"
|
|
650
|
-
"
|
|
950
|
+
"did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
951
|
+
"previousOperationCID": "bafyreiaedhjq64aajpwociahl5w37j6uoxr5mojoq5dnah6fpvxr5d4lxu",
|
|
952
|
+
"documentCID": "bafyreidh7e36cvwy3uw5ypitcqk7uoktbkkkj7e6hxhky4o75rxn7kxilu",
|
|
953
|
+
"baseDocumentCID": "bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4",
|
|
651
954
|
"createdAt": "2026-03-07T00:03:00.000Z",
|
|
652
955
|
"note": "edited title and body"
|
|
653
956
|
}
|
|
654
957
|
```
|
|
655
958
|
|
|
656
|
-
Updated document:
|
|
959
|
+
Updated document (flat content object):
|
|
657
960
|
|
|
658
961
|
```json
|
|
659
962
|
{
|
|
660
|
-
"
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
},
|
|
666
|
-
"baseDocumentCID": "bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne",
|
|
667
|
-
"createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
|
|
668
|
-
"createdAt": "2026-03-07T00:03:00.000Z"
|
|
963
|
+
"$schema": "https://schemas.dfos.com/post/v1",
|
|
964
|
+
"format": "short-post",
|
|
965
|
+
"title": "Hello World (edited)",
|
|
966
|
+
"body": "Updated content.",
|
|
967
|
+
"createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
|
|
669
968
|
}
|
|
670
969
|
```
|
|
671
970
|
|
|
672
971
|
Document CID (edited):
|
|
673
972
|
|
|
674
973
|
```
|
|
675
|
-
|
|
974
|
+
bafyreidh7e36cvwy3uw5ypitcqk7uoktbkkkj7e6hxhky4o75rxn7kxilu
|
|
676
975
|
```
|
|
677
976
|
|
|
678
977
|
Content Update CID:
|
|
679
978
|
|
|
680
979
|
```
|
|
681
|
-
|
|
980
|
+
bafyreih6e5cbjitpozhzhgmfktmiohmxyn3ucwhqd3mjixizvwmlhv7hm4
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
### Content Chain Verified State
|
|
984
|
+
|
|
985
|
+
```
|
|
986
|
+
Content ID: a82z92a3hndk6c97thcrn8
|
|
987
|
+
Genesis CID: bafyreiaedhjq64aajpwociahl5w37j6uoxr5mojoq5dnah6fpvxr5d4lxu
|
|
988
|
+
Head CID: bafyreih6e5cbjitpozhzhgmfktmiohmxyn3ucwhqd3mjixizvwmlhv7hm4
|
|
682
989
|
```
|
|
683
990
|
|
|
684
991
|
---
|
|
@@ -718,24 +1025,31 @@ Given the artifacts above, verify:
|
|
|
718
1025
|
did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd
|
|
719
1026
|
```
|
|
720
1027
|
|
|
721
|
-
8. **Document CID**: dag-cbor canonical encode the
|
|
1028
|
+
8. **Document CID**: dag-cbor canonical encode the flat content object → SHA-256 → CIDv1 → should be:
|
|
722
1029
|
|
|
723
1030
|
```
|
|
724
|
-
|
|
1031
|
+
bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4
|
|
725
1032
|
```
|
|
726
1033
|
|
|
727
|
-
9. **Content
|
|
1034
|
+
9. **Content operation `did` field**: verify the `did` field in each content operation matches the `kid` DID in the JWS header
|
|
1035
|
+
|
|
1036
|
+
10. **Content chain integrity**: update's `previousOperationCID` matches create's operation CID
|
|
1037
|
+
|
|
1038
|
+
11. **Chain completeness**: all operation CIDs, DID derivation, key rotation, and content chain linkage verified end-to-end.
|
|
1039
|
+
|
|
1040
|
+
12. **VC-JWT credential verify**: using the issuer's public key, verify a `DFOSContentWrite` or `DFOSContentRead` credential: check EdDSA signature, `typ: "vc+jwt"`, expiration, `kid` DID URL format, `kid` DID matches `iss`, `vc` claim structure matches W3C VC Data Model v2, credential type matches expected DFOS type. Test vectors in [`examples/credential-write.json`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/examples/credential-write.json) and [`examples/credential-read.json`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/examples/credential-read.json).
|
|
728
1041
|
|
|
729
|
-
|
|
1042
|
+
13. **Delegated content chain verify**: using [`examples/content-delegated.json`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/examples/content-delegated.json), verify a content chain where the genesis is signed by the creator and a subsequent update is signed by a delegate with an embedded `DFOSContentWrite` VC-JWT in the `authorization` field. The VC must be issued by the creator DID, with `sub` matching the delegate DID.
|
|
730
1043
|
|
|
731
1044
|
---
|
|
732
1045
|
|
|
733
1046
|
## Source and Verification
|
|
734
1047
|
|
|
735
|
-
All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol) — self-contained, zero monorepo dependencies.
|
|
1048
|
+
All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol) — self-contained, zero monorepo dependencies. 293 checks across 5 languages.
|
|
736
1049
|
|
|
737
1050
|
- [`crypto/ed25519`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/ed25519.ts) — `createNewEd25519Keypair`, `importEd25519Keypair`, `signPayloadEd25519`, `isValidEd25519Signature`
|
|
738
1051
|
- [`crypto/jws`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/jws.ts) — `createJws`, `verifyJws`, `decodeJwsUnsafe`
|
|
1052
|
+
- [`crypto/jwt`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/jwt.ts) — `createJwt`, `verifyJwt`
|
|
739
1053
|
- [`crypto/base64url`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/base64url.ts) — `base64urlEncode`, `base64urlDecode`
|
|
740
1054
|
- [`crypto/multiformats`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/multiformats.ts) — `dagCborCanonicalEncode`, `dagCborCanonicalEqual`
|
|
741
1055
|
- [`crypto/id`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/id.ts) — `generateId`, `generateIdNoPrefix`, `isValidId`
|
|
@@ -744,22 +1058,28 @@ All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfo
|
|
|
744
1058
|
- [`chain/identity-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/identity-chain.ts) — `signIdentityOperation`, `verifyIdentityChain`
|
|
745
1059
|
- [`chain/content-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/content-chain.ts) — `signContentOperation`, `verifyContentChain`
|
|
746
1060
|
- [`chain/derivation`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/derivation.ts) — `deriveChainIdentifier`, `deriveContentId`
|
|
1061
|
+
- [`chain/beacon`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/beacon.ts) — `signBeacon`, `verifyBeacon`
|
|
1062
|
+
- [`chain/countersign`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/countersign.ts) — `signCountersignature`, `verifyCountersignature`
|
|
1063
|
+
- [`credentials/auth-token`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/credentials/auth-token.ts) — `createAuthToken`, `verifyAuthToken`
|
|
1064
|
+
- [`credentials/credential`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/credentials/credential.ts) — `createCredential`, `verifyCredential`, `decodeCredentialUnsafe`
|
|
1065
|
+
- [`credentials/schemas`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/credentials/schemas.ts) — `AuthTokenClaims`, `CredentialClaims`, `VCClaim`, `DFOSCredentialType`
|
|
1066
|
+
- [`merkle/tree`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/merkle/tree.ts) — `buildMerkleTree`, `hashLeaf`
|
|
1067
|
+
- [`merkle/proof`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/merkle/proof.ts) — `generateMerkleProof`, `verifyMerkleProof`
|
|
747
1068
|
|
|
748
1069
|
### Related Specifications
|
|
749
1070
|
|
|
750
1071
|
- [DID Method: `did:dfos`](https://protocol.dfos.com/did-method) — W3C DID method specification for identity chains
|
|
751
|
-
- [Content Model](https://protocol.dfos.com/content-model) — Standard content schemas (post, profile) for
|
|
752
|
-
- [Registry API](https://protocol.dfos.com/registry-api) — HTTP API for chain storage and resolution
|
|
1072
|
+
- [Content Model](https://protocol.dfos.com/content-model) — Standard content schemas (post, profile) for document content objects
|
|
753
1073
|
|
|
754
1074
|
### Cross-Language Verification
|
|
755
1075
|
|
|
756
1076
|
| Language | Tests | Source |
|
|
757
1077
|
| ---------- | ----- | ---------------------------------------------------------------------------------------------------- |
|
|
758
|
-
| TypeScript |
|
|
759
|
-
| Python |
|
|
760
|
-
| Go |
|
|
761
|
-
| Rust |
|
|
762
|
-
| Swift |
|
|
1078
|
+
| TypeScript | 190 | [`tests/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/tests) |
|
|
1079
|
+
| Python | 59 | [`verify/python/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/python) |
|
|
1080
|
+
| Go | 15 | [`verify/go/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/go) |
|
|
1081
|
+
| Rust | 15 | [`verify/rust/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/rust) |
|
|
1082
|
+
| Swift | 14 | [`verify/swift/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/swift) |
|
|
763
1083
|
|
|
764
1084
|
---
|
|
765
1085
|
|