@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/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 two layers:
23
+ The DFOS protocol has five components:
24
24
 
25
- | Layer | Concern |
25
+ | Component | Concern |
26
26
  | --------------------- | ---------------------------------------------------------------------------- |
27
27
  | **Crypto core** | Identity chains + content chains — Ed25519 signatures, JWS tokens, CID links |
28
- | **Document envelope** | Standard wrapper: `content` + `baseDocumentCID` + `createdByDID` + timestamp |
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. The document envelope provides structural metadata (attribution, edit lineage, timestamps). What goes inside the envelope's `content` field is application-defined — see the [DFOS Content Model](https://protocol.dfos.com/content-model) for the standard schema library.
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 document envelope.
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 canonical representations:
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 CID:
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
- Identity chains are self-sovereign they define their own valid signers via `controllerKeys`. Content chains are externally signed a content chain with operations signed by multiple different identities is valid at the protocol level, as long as each signature verifies against the resolved key.
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
- - Whether a chain must have a single signer or may have multiple signers
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 (`did:dfos:identity-op`, `did:dfos:content-op`) aids routing but is not security-critical. Implementations SHOULD validate it but MUST NOT rely on it for security decisions.
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
- documentCID: string, // CID of document content
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 (device auth) do NOT include a `cid` header — this field is specific to operation JWS 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. Resolve `kid` via external key resolver (caller provides)
418
- 6. Verify EdDSA JWS signature
419
- 7. Apply state change (set document, clear, or delete)
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.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw
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 (application layer):
881
+ Document (flat content object):
579
882
 
580
883
  ```json
581
884
  {
582
- "content": {
583
- "$schema": "https://schemas.dfos.com/post/v1",
584
- "format": "short-post",
585
- "title": "Hello World",
586
- "body": "First post on the protocol."
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
- bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne
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": "bafyreia5z7zxknae5ds72euihuf2rg3ixl6t4fbzjefhcogg3nqppyogqu"
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
- "documentCID": "bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne",
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
- b7f0c3909fd398d7a42065053b6d86f96efc4281385d383d2ca4388330101da2b707ae3dd538abf5bfb0b69fa173098436ed87aa789eaafe404a2a9f16b11b0f
927
+ 46feaf973e4c7ebc2a0d4ad25481ace197de05b91051205c5e1c7067a85fb9d4abe4cc61625d3c853a8b0ce0345b534c8cdd07b34216f635d3c0bc0fd5d30306
627
928
  ```
628
929
 
629
930
  Content Create JWS Token:
630
931
 
631
932
  ```
632
- eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWE1ejd6eGtuYWU1ZHM3MmV1aWh1ZjJyZzNpeGw2dDRmYnpqZWZoY29nZzNucXBweW9ncXUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZG9jdW1lbnRDSUQiOiJiYWZ5cmVpZnB2d3Vhcm1sNjJzZm9nZHBpMnZsbHR2ZzJldjZvNHh0dzc0emZ1ZDdjcGtnNzQyNnpuZSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.t_DDkJ_TmNekIGUFO22G-W78QoE4XTg9LKQ4gzAQHaK3B6491Tir9b-wtp-hcwmENu2Hqnieqv5ASiqfFrEbDw
933
+ eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.Rv6vlz5MfrwqDUrSVIGs4ZfeBbkQUSBcXhxwZ6hfudSr5MxhYl08hTqLDOA0W1NMjN0Hs0IW9jXTwLwP1dMDBg
633
934
  ```
634
935
 
635
936
  Content Operation CID:
636
937
 
637
938
  ```
638
- bafyreia5z7zxknae5ds72euihuf2rg3ixl6t4fbzjefhcogg3nqppyogqu
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
- "previousOperationCID": "bafyreia5z7zxknae5ds72euihuf2rg3ixl6t4fbzjefhcogg3nqppyogqu",
650
- "documentCID": "bafyreieuo26zfmjxwpmw5jk6bqzqhvivxcbckgxtyeuc7ypf3p4sihgq4q",
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
- "content": {
661
- "$schema": "https://schemas.dfos.com/post/v1",
662
- "format": "short-post",
663
- "title": "Hello World (edited)",
664
- "body": "Updated content."
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
- bafyreieuo26zfmjxwpmw5jk6bqzqhvivxcbckgxtyeuc7ypf3p4sihgq4q
974
+ bafyreidh7e36cvwy3uw5ypitcqk7uoktbkkkj7e6hxhky4o75rxn7kxilu
676
975
  ```
677
976
 
678
977
  Content Update CID:
679
978
 
680
979
  ```
681
- bafyreibb4lsvqmz4j76rsvhkqw3v2b4vp23t7dimm6vl5g5wlninvkemxq
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 document JSON → SHA-256 → CIDv1 → should be:
1028
+ 8. **Document CID**: dag-cbor canonical encode the flat content object → SHA-256 → CIDv1 → should be:
722
1029
 
723
1030
  ```
724
- bafyreifpvwuarml62sfogdpi2vlltvg2ev6o4xtw74zfud7cpkg7426zne
1031
+ bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4
725
1032
  ```
726
1033
 
727
- 9. **Content chain integrity**: update's `previousOperationCID` matches create's operation CID
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
- 10. **Chain completeness**: all operation CIDs, DID derivation, key rotation, and content chain linkage verified end-to-end.
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. 160 checks across 5 languages.
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 the document envelope
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 | 99 | [`tests/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/tests) |
759
- | Python | 35 | [`verify/python/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/python) |
760
- | Go | 9 | [`verify/go/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/go) |
761
- | Rust | 9 | [`verify/rust/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/rust) |
762
- | Swift | 8 | [`verify/swift/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/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