@metalabel/dfos-protocol 0.8.1 → 0.10.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/README.md CHANGED
@@ -17,8 +17,6 @@ import { verifyContentChain, verifyIdentityChain } from '@metalabel/dfos-protoco
17
17
  import { createAuthToken, createDFOSCredential } from '@metalabel/dfos-protocol/credentials';
18
18
  // Crypto primitives
19
19
  import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-protocol/crypto';
20
- // Merkle trees
21
- import { buildMerkleTree, verifyMerkleProof } from '@metalabel/dfos-protocol/merkle';
22
20
  ```
23
21
 
24
22
  ## Subpath Exports
@@ -28,7 +26,6 @@ import { buildMerkleTree, verifyMerkleProof } from '@metalabel/dfos-protocol/mer
28
26
  | `@metalabel/dfos-protocol/chain` | Identity and content chain signing, verification, beacons, countersigns |
29
27
  | `@metalabel/dfos-protocol/credentials` | Auth tokens (DID-signed JWT) and DFOS credentials for authorization |
30
28
  | `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
31
- | `@metalabel/dfos-protocol/merkle` | SHA-256 binary merkle tree, inclusion proofs |
32
29
 
33
30
  ## Specifications
34
31
 
@@ -50,7 +47,6 @@ The `examples/` directory contains deterministic reference fixtures that can be
50
47
  - `content-delegated.json` — creator genesis + delegated update with DFOS write credential
51
48
  - `credential-write.json` — DFOS write credential (broad + content-narrowed)
52
49
  - `credential-read.json` — DFOS read credential
53
- - `merkle-tree.json` — 5 content IDs → sorted tree → root, with inclusion proof
54
50
  - `beacon.json` — signed manifest pointer announcement with witness countersignature
55
51
 
56
52
  ## License
@@ -1,5 +1,5 @@
1
- import { I as IdentityOperation, S as Signer, V as VerifiedIdentity, C as ContentOperation, B as BeaconPayload, a as CountersignPayload, A as ArtifactPayload } from '../schemas-BEl38wrI.js';
2
- export { M as MAX_ARTIFACT_PAYLOAD_SIZE, b as MultikeyPublicKey, R as RevocationPayload } from '../schemas-BEl38wrI.js';
1
+ import { I as IdentityOperation, S as Signer, V as VerifiedIdentity, C as ContentOperation, B as BeaconPayload, a as CountersignPayload, A as ArtifactPayload } from '../schemas-BhikXSf_.js';
2
+ export { M as MAX_ARTIFACT_PAYLOAD_SIZE, b as MultikeyPublicKey, R as RevocationPayload } from '../schemas-BhikXSf_.js';
3
3
  import 'zod';
4
4
 
5
5
  /** Ed25519 public key multicodec value */
@@ -32,7 +32,7 @@ declare const deriveChainIdentifier: (cidBytes: Uint8Array, prefix: string) => s
32
32
  /**
33
33
  * Derive a bare content identifier from CID bytes
34
34
  *
35
- * Returns the raw 22-char hash with no prefix. Applications may add
35
+ * Returns the raw 31-char hash with no prefix. Applications may add
36
36
  * their own prefix for routing (e.g., post_xxxx) — that's semantic sugar.
37
37
  */
38
38
  declare const deriveContentId: (cidBytes: Uint8Array) => string;
@@ -89,7 +89,7 @@ declare const verifyIdentityExtensionFromTrustedState: (input: {
89
89
  }>;
90
90
 
91
91
  interface VerifiedContentChain {
92
- /** Content identifier — bare 22-char hash derived from genesis CID */
92
+ /** Content identifier — bare 31-char hash derived from genesis CID */
93
93
  contentId: string;
94
94
  /** CID of the genesis operation */
95
95
  genesisCID: string;
@@ -144,6 +144,11 @@ declare const verifyContentChain: (input: {
144
144
  * is true, as credential verification needs identity resolution.
145
145
  */
146
146
  resolveIdentity?: (did: string) => Promise<VerifiedIdentity | undefined>;
147
+ /**
148
+ * Check whether a credential (leaf or parent) has been revoked. Threaded onto
149
+ * the WRITE path so revoked credentials no longer authorize writes.
150
+ */
151
+ isRevoked?: (issuerDID: string, credentialCID: string) => Promise<boolean>;
147
152
  }) => Promise<VerifiedContentChain>;
148
153
  /**
149
154
  * Verify a single new content operation against already-verified chain state
@@ -165,6 +170,8 @@ declare const verifyContentExtensionFromTrustedState: (input: {
165
170
  enforceAuthorization?: boolean;
166
171
  /** Resolve a DID to a VerifiedIdentity. Required when enforceAuthorization is true. */
167
172
  resolveIdentity?: (did: string) => Promise<VerifiedIdentity | undefined>;
173
+ /** Check whether a credential (leaf or parent) has been revoked. */
174
+ isRevoked?: (issuerDID: string, credentialCID: string) => Promise<boolean>;
168
175
  }) => Promise<{
169
176
  state: VerifiedContentChain;
170
177
  operationCID: string;
@@ -24,14 +24,14 @@ import {
24
24
  verifyIdentityChain,
25
25
  verifyIdentityExtensionFromTrustedState,
26
26
  verifyRevocation
27
- } from "../chunk-LQ56P4SU.js";
27
+ } from "../chunk-GZ7ZAIRD.js";
28
28
  import {
29
29
  ED25519_PRIV_MULTICODEC,
30
30
  ED25519_PUB_MULTICODEC,
31
31
  decodeMultikey,
32
32
  encodeEd25519Multikey
33
- } from "../chunk-MEV6QVLC.js";
34
- import "../chunk-ZXXP5W5N.js";
33
+ } from "../chunk-LQFOBE6X.js";
34
+ import "../chunk-GQOZJKKO.js";
35
35
  export {
36
36
  ArtifactPayload,
37
37
  BeaconPayload,
@@ -40,7 +40,7 @@ var isValidEd25519Signature = (payload, signature, publicKey) => {
40
40
  // src/crypto/id.ts
41
41
  import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
42
42
  var alphabet = "2346789acdefhknrtvz";
43
- var idLength = 22;
43
+ var idLength = 31;
44
44
  var getRandomBytes = (length) => {
45
45
  const bytes = new Uint8Array(length);
46
46
  globalThis.crypto.getRandomValues(bytes);
@@ -75,6 +75,22 @@ var normalizedId = (prefix, id) => {
75
75
  return `${prefixLowered}_${idLowered}`;
76
76
  };
77
77
 
78
+ // src/crypto/jws-profile.ts
79
+ var assertJwsProfile = (header, makeError) => {
80
+ if (header.alg !== "EdDSA") {
81
+ throw makeError(`Unsupported algorithm: ${String(header.alg)}`);
82
+ }
83
+ if ("crit" in header) {
84
+ throw makeError("crit header is not supported");
85
+ }
86
+ if ("jwk" in header) {
87
+ throw makeError("jwk header is not allowed (key is resolved from kid)");
88
+ }
89
+ if ("x5c" in header) {
90
+ throw makeError("x5c header is not allowed (key is resolved from kid)");
91
+ }
92
+ };
93
+
78
94
  // src/crypto/jws.ts
79
95
  var createJws = async (options) => {
80
96
  const headerB64 = base64urlEncode(JSON.stringify(options.header));
@@ -99,9 +115,10 @@ var verifyJws = (options) => {
99
115
  } catch {
100
116
  throw new JwsVerificationError("Failed to decode token");
101
117
  }
102
- if (header.alg !== "EdDSA") {
103
- throw new JwsVerificationError(`Unsupported algorithm: ${header.alg}`);
104
- }
118
+ assertJwsProfile(
119
+ header,
120
+ (m) => new JwsVerificationError(m)
121
+ );
105
122
  const signingInput = `${headerB64}.${payloadB64}`;
106
123
  const signingInputBytes = new TextEncoder().encode(signingInput);
107
124
  const signatureBytes = base64urlDecode(signatureB64);
@@ -172,12 +189,13 @@ var verifyJwt = (options) => {
172
189
  } catch {
173
190
  throw new JwtVerificationError("Failed to decode token");
174
191
  }
192
+ assertJwsProfile(
193
+ header,
194
+ (m) => new JwtVerificationError(m)
195
+ );
175
196
  const signingInput = `${headerB64}.${payloadB64}`;
176
197
  const signingInputBytes = new TextEncoder().encode(signingInput);
177
198
  const signatureBytes = base64urlDecode(signatureB64);
178
- if (header.alg !== "EdDSA") {
179
- throw new JwtVerificationError(`Unsupported algorithm: ${header.alg}`);
180
- }
181
199
  const isValid = isValidEd25519Signature(signingInputBytes, signatureBytes, options.publicKey);
182
200
  if (!isValid) throw new JwtVerificationError("Invalid signature");
183
201
  const currentTime = options.currentTime ?? Math.floor(Date.now() / 1e3);
@@ -209,14 +227,43 @@ import * as Block from "multiformats/block";
209
227
  import { CID } from "multiformats/cid";
210
228
  import { sha256 } from "multiformats/hashes/sha2";
211
229
  var dagCborCanonicalEncode = async (value) => {
230
+ assertCanonicalNumbers(value);
231
+ const serialized = JSON.parse(JSON.stringify(value));
232
+ assertCanonicalNumbers(serialized);
212
233
  return await Block.encode({
213
- // removes any undefineds or other non-serializable values, kinda whack but
214
- // it works for now
215
- value: JSON.parse(JSON.stringify(value)),
234
+ // removes any undefineds or other non-serializable values (and normalizes
235
+ // -0 to 0)
236
+ value: serialized,
216
237
  codec: dagCborCodec,
217
238
  hasher: sha256
218
239
  });
219
240
  };
241
+ var MAX_SAFE_CANONICAL_INTEGER = 9007199254740991;
242
+ var assertCanonicalNumbers = (value) => {
243
+ if (typeof value === "number") {
244
+ if (!Number.isFinite(value)) {
245
+ throw new Error(`non-finite number is not canonicalizable: ${value}`);
246
+ }
247
+ if (!Number.isInteger(value)) {
248
+ throw new Error(
249
+ `non-integer number is not canonicalizable: ${value} (encode it as a string)`
250
+ );
251
+ }
252
+ if (value > MAX_SAFE_CANONICAL_INTEGER || value < -MAX_SAFE_CANONICAL_INTEGER) {
253
+ throw new Error(
254
+ `integer out of safe range is not canonicalizable: ${value} (encode it as a string)`
255
+ );
256
+ }
257
+ return;
258
+ }
259
+ if (Array.isArray(value)) {
260
+ for (const entry of value) assertCanonicalNumbers(entry);
261
+ return;
262
+ }
263
+ if (value !== null && typeof value === "object") {
264
+ for (const entry of Object.values(value)) assertCanonicalNumbers(entry);
265
+ }
266
+ };
220
267
  var parseDagCborCID = (cid) => {
221
268
  return CID.parse(cid);
222
269
  };
@@ -237,6 +284,7 @@ export {
237
284
  generateId,
238
285
  isValidId,
239
286
  normalizedId,
287
+ assertJwsProfile,
240
288
  createJws,
241
289
  verifyJws,
242
290
  decodeJwsUnsafe,
@@ -4,14 +4,14 @@ import {
4
4
  matchesResource,
5
5
  verifyDFOSCredential,
6
6
  verifyDelegationChain
7
- } from "./chunk-MEV6QVLC.js";
7
+ } from "./chunk-LQFOBE6X.js";
8
8
  import {
9
9
  createJws,
10
10
  dagCborCanonicalEncode,
11
11
  decodeJwsUnsafe,
12
12
  generateIdNoPrefix,
13
13
  verifyJws
14
- } from "./chunk-ZXXP5W5N.js";
14
+ } from "./chunk-GQOZJKKO.js";
15
15
 
16
16
  // src/chain/schemas.ts
17
17
  import { z } from "zod";
@@ -383,10 +383,14 @@ var verifyOperationAuthorization = async (input) => {
383
383
  resolveIdentity: input.resolveIdentity,
384
384
  now: opCreatedAtUnix
385
385
  });
386
+ if (input.isRevoked && await input.isRevoked(credential.iss, credential.credentialCID)) {
387
+ throw new Error("credential is revoked");
388
+ }
386
389
  await verifyDelegationChain(credential, {
387
390
  resolveIdentity: input.resolveIdentity,
388
391
  rootDID: input.creatorDID,
389
- now: opCreatedAtUnix
392
+ now: opCreatedAtUnix,
393
+ ...input.isRevoked ? { isRevoked: input.isRevoked } : {}
390
394
  });
391
395
  if (credential.aud !== "*" && credential.aud !== input.operationDID) {
392
396
  throw new Error(
@@ -470,7 +474,8 @@ var verifyContentChain = async (input) => {
470
474
  creatorDID: state.creatorDID,
471
475
  contentId: state.contentId,
472
476
  createdAt: op.createdAt,
473
- resolveIdentity: input.resolveIdentity
477
+ resolveIdentity: input.resolveIdentity,
478
+ ...input.isRevoked ? { isRevoked: input.isRevoked } : {}
474
479
  });
475
480
  } catch (err) {
476
481
  const message = err instanceof Error ? err.message : "unknown error";
@@ -570,7 +575,8 @@ var verifyContentExtensionFromTrustedState = async (input) => {
570
575
  creatorDID: currentState.creatorDID,
571
576
  contentId: currentState.contentId,
572
577
  createdAt: op.createdAt,
573
- resolveIdentity: input.resolveIdentity
578
+ resolveIdentity: input.resolveIdentity,
579
+ ...input.isRevoked ? { isRevoked: input.isRevoked } : {}
574
580
  });
575
581
  } catch (err) {
576
582
  const message = err instanceof Error ? err.message : "unknown error";
@@ -5,7 +5,7 @@ import {
5
5
  decodeJwsUnsafe,
6
6
  verifyJws,
7
7
  verifyJwt
8
- } from "./chunk-ZXXP5W5N.js";
8
+ } from "./chunk-GQOZJKKO.js";
9
9
 
10
10
  // src/credentials/schemas.ts
11
11
  import { z } from "zod";
@@ -14,7 +14,7 @@ var MAX_AUD = 512;
14
14
  var MAX_RESOURCE = 512;
15
15
  var MAX_ACTION = 64;
16
16
  var MAX_ATT = 32;
17
- var MAX_PRF = 8;
17
+ var MAX_PRF = 1;
18
18
  var Attenuation = z.strictObject({
19
19
  resource: z.string().min(1).max(MAX_RESOURCE),
20
20
  action: z.string().min(1).max(MAX_ACTION)
@@ -96,6 +96,7 @@ var verifyAuthToken = (options) => {
96
96
  iss: result.data.iss,
97
97
  aud: result.data.aud,
98
98
  exp: result.data.exp,
99
+ iat: result.data.iat,
99
100
  kid
100
101
  };
101
102
  };
@@ -256,41 +257,38 @@ var verifyDelegationChain = async (credential, options) => {
256
257
  }
257
258
  return { credential, chain, rootDID: options.rootDID };
258
259
  }
259
- const parents = [];
260
- for (const parentJws of current.prf) {
261
- const parent = await verifyDFOSCredential(parentJws, {
262
- resolveIdentity: options.resolveIdentity,
263
- ...options.now !== void 0 ? { now: options.now } : {}
264
- });
265
- if (options.isRevoked) {
266
- const revoked = await options.isRevoked(parent.iss, parent.credentialCID);
267
- if (revoked) {
268
- throw new CredentialVerificationError("parent credential in delegation chain is revoked");
269
- }
260
+ if (current.prf.length > 1) {
261
+ throw new CredentialVerificationError(
262
+ "delegation chain: multi-parent credentials are not supported (prf must have at most one entry)"
263
+ );
264
+ }
265
+ const parent = await verifyDFOSCredential(current.prf[0], {
266
+ resolveIdentity: options.resolveIdentity,
267
+ ...options.now !== void 0 ? { now: options.now } : {}
268
+ });
269
+ if (options.isRevoked) {
270
+ const revoked = await options.isRevoked(parent.iss, parent.credentialCID);
271
+ if (revoked) {
272
+ throw new CredentialVerificationError("parent credential in delegation chain is revoked");
270
273
  }
271
- parents.push(parent);
272
274
  }
273
- const matchingParent = parents.find((p) => p.aud === "*" || p.aud === current.iss);
274
- if (!matchingParent) {
275
+ if (parent.aud !== "*" && parent.aud !== current.iss) {
275
276
  throw new CredentialVerificationError(
276
- `delegation gap: no parent credential has audience matching child issuer ${current.iss}`
277
+ `delegation gap: parent credential audience ${parent.aud} does not match child issuer ${current.iss}`
277
278
  );
278
279
  }
279
- for (const parent of parents) {
280
- if (current.exp > parent.exp) {
281
- throw new CredentialVerificationError(
282
- "delegation chain: child credential expiry exceeds parent expiry"
283
- );
284
- }
280
+ if (current.exp > parent.exp) {
281
+ throw new CredentialVerificationError(
282
+ "delegation chain: child credential expiry exceeds parent expiry"
283
+ );
285
284
  }
286
- const parentAttUnion = parents.flatMap((p) => p.att);
287
- if (!isAttenuated(parentAttUnion, current.att)) {
285
+ if (!isAttenuated(parent.att, current.att)) {
288
286
  throw new CredentialVerificationError(
289
287
  "delegation chain: child credential scope exceeds parent scope"
290
288
  );
291
289
  }
292
- chain.push(...parents);
293
- current = parents[0];
290
+ chain.push(parent);
291
+ current = parent;
294
292
  }
295
293
  throw new CredentialVerificationError("delegation chain too deep (max 16 hops)");
296
294
  };
@@ -315,7 +313,7 @@ var isAttenuated = (parentAtt, childAtt) => {
315
313
  if (!parentActions.has(a)) return false;
316
314
  }
317
315
  if (parentRes.type === "chain" && parentRes.id === "*") {
318
- return true;
316
+ return childRes.type === "chain";
319
317
  }
320
318
  if (childRes.type === "chain" && childRes.id === "*") {
321
319
  return false;
@@ -323,17 +321,11 @@ var isAttenuated = (parentAtt, childAtt) => {
323
321
  if (childRes.type === "chain" && parentRes.type === "chain") {
324
322
  return childRes.id === parentRes.id;
325
323
  }
326
- if (childRes.type === "chain" && parentRes.type === "manifest") {
327
- return true;
328
- }
329
- if (childRes.type === "manifest" && parentRes.type === "manifest") {
330
- return childRes.id === parentRes.id;
331
- }
332
324
  return false;
333
325
  });
334
326
  });
335
327
  };
336
- var matchesResource = async (att, resource, action, options) => {
328
+ var matchesResource = async (att, resource, action) => {
337
329
  const requestedRes = parseResource(resource);
338
330
  if (!requestedRes) return false;
339
331
  const requestedActions = parseActions(action);
@@ -355,12 +347,6 @@ var matchesResource = async (att, resource, action, options) => {
355
347
  if (entryRes.type === requestedRes.type && entryRes.id === requestedRes.id) {
356
348
  return true;
357
349
  }
358
- if (entryRes.type === "manifest" && requestedRes.type === "chain") {
359
- if (options?.manifestLookup) {
360
- const indexed = await options.manifestLookup(entryRes.id);
361
- if (indexed.includes(requestedRes.id)) return true;
362
- }
363
- }
364
350
  }
365
351
  return false;
366
352
  };
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { V as VerifiedIdentity } from '../schemas-BEl38wrI.js';
2
+ import { V as VerifiedIdentity } from '../schemas-BhikXSf_.js';
3
3
 
4
4
  /** Single attenuation entry — resource + action pair */
5
5
  declare const Attenuation: z.ZodObject<{
@@ -11,34 +11,23 @@ type Attenuation = z.infer<typeof Attenuation>;
11
11
  declare const DFOSCredentialPayload: z.ZodObject<{
12
12
  version: z.ZodLiteral<1>;
13
13
  type: z.ZodLiteral<"DFOSCredential">;
14
- /** Issuer DID */
15
14
  iss: z.ZodString;
16
- /** Audience DID or "*" for public credentials */
17
15
  aud: z.ZodString;
18
- /** Attenuations — resource + action pairs */
19
16
  att: z.ZodArray<z.ZodObject<{
20
17
  resource: z.ZodString;
21
18
  action: z.ZodString;
22
19
  }, z.core.$strict>>;
23
- /** Parent credential JWS tokens (for delegation chains) */
24
20
  prf: z.ZodDefault<z.ZodArray<z.ZodString>>;
25
- /** Expiration — unix seconds */
26
21
  exp: z.ZodNumber;
27
- /** Issued at — unix seconds */
28
22
  iat: z.ZodNumber;
29
23
  }, z.core.$strict>;
30
24
  type DFOSCredentialPayload = z.infer<typeof DFOSCredentialPayload>;
31
25
  /** Claims for a DID-signed auth token (relay AuthN) */
32
26
  declare const AuthTokenClaims: z.ZodObject<{
33
- /** Issuer — the DID proving identity */
34
27
  iss: z.ZodString;
35
- /** Subject — same as iss for auth tokens */
36
28
  sub: z.ZodString;
37
- /** Audience — target relay hostname (prevents cross-relay replay) */
38
29
  aud: z.ZodString;
39
- /** Expiration — unix seconds, short-lived (minutes) */
40
30
  exp: z.ZodNumber;
41
- /** Issued at — unix seconds */
42
31
  iat: z.ZodNumber;
43
32
  }, z.core.$strict>;
44
33
  type AuthTokenClaims = z.infer<typeof AuthTokenClaims>;
@@ -74,6 +63,8 @@ interface VerifiedAuthToken {
74
63
  aud: string;
75
64
  /** Token expiration (unix seconds) */
76
65
  exp: number;
66
+ /** Token issued-at (unix seconds) */
67
+ iat: number;
77
68
  /** kid from the JWT header */
78
69
  kid: string;
79
70
  }
@@ -179,12 +170,8 @@ declare const verifyDelegationChain: (credential: VerifiedDFOSCredential, option
179
170
  *
180
171
  * - `chain:X` covered by `chain:X` (exact match)
181
172
  * - `chain:X` covered by `chain:*` (narrowing from wildcard — valid)
182
- * - `chain:X` covered by `manifest:M` (narrowing from manifest — valid structurally)
183
- * - `manifest:M` covered by `chain:*` (narrowing from wildcard — valid)
184
- * - `manifest:M` covered by `manifest:M` (exact match)
185
- * - `manifest:M` NOT covered by `chain:X` (widening — invalid)
186
173
  * - `chain:*` covered by `chain:*` (exact match)
187
- * - `chain:*` NOT covered by `chain:X` or `manifest:M` (widening — invalid)
174
+ * - `chain:*` NOT covered by `chain:X` (widening — invalid)
188
175
  * - Actions: child action set must be a subset of parent action set
189
176
  */
190
177
  declare const isAttenuated: (parentAtt: Attenuation[], childAtt: Attenuation[]) => boolean;
@@ -193,14 +180,8 @@ declare const isAttenuated: (parentAtt: Attenuation[], childAtt: Attenuation[])
193
180
  *
194
181
  * Used at the relay to determine if a credential authorizes access to a
195
182
  * specific content chain.
196
- *
197
- * For `manifest:` resources, requires a `manifestLookup` callback to resolve
198
- * which contentIds the manifest indexes. Without the callback, `manifest:`
199
- * resources can only match exact `manifest:` requests, not `chain:` requests.
200
183
  */
201
- declare const matchesResource: (att: Attenuation[], resource: string, action: string, options?: {
202
- manifestLookup?: (manifestContentId: string) => Promise<string[]>;
203
- }) => Promise<boolean>;
184
+ declare const matchesResource: (att: Attenuation[], resource: string, action: string) => Promise<boolean>;
204
185
  /**
205
186
  * Decode a DFOS credential JWS without verifying the signature
206
187
  *
@@ -12,8 +12,8 @@ import {
12
12
  verifyAuthToken,
13
13
  verifyDFOSCredential,
14
14
  verifyDelegationChain
15
- } from "../chunk-MEV6QVLC.js";
16
- import "../chunk-ZXXP5W5N.js";
15
+ } from "../chunk-LQFOBE6X.js";
16
+ import "../chunk-GQOZJKKO.js";
17
17
  export {
18
18
  Attenuation,
19
19
  AuthTokenClaims,
@@ -14,22 +14,22 @@ declare const base64urlDecode: (str: string) => Uint8Array;
14
14
  * Generate a new random Ed25519 keypair
15
15
  */
16
16
  declare const createNewEd25519Keypair: () => {
17
- privateKey: Uint8Array<ArrayBufferLike>;
18
- publicKey: Uint8Array<ArrayBufferLike>;
17
+ privateKey: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
18
+ publicKey: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
19
19
  };
20
20
  /**
21
21
  * Generate an Ed25519 keypair from a private key
22
22
  */
23
23
  declare const importEd25519Keypair: (privateKey: Uint8Array) => {
24
24
  privateKey: Uint8Array<ArrayBufferLike>;
25
- publicKey: Uint8Array<ArrayBufferLike>;
25
+ publicKey: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
26
26
  };
27
27
  /**
28
28
  * Sign a payload with an Ed25519 private key
29
29
  *
30
30
  * Ed25519 handles hashing internally (SHA-512) — no external prehash needed
31
31
  */
32
- declare const signPayloadEd25519: (payload: Uint8Array, privateKey: Uint8Array) => Uint8Array<ArrayBufferLike>;
32
+ declare const signPayloadEd25519: (payload: Uint8Array, privateKey: Uint8Array) => Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
33
33
  /**
34
34
  * Check that a signature is valid for a given payload and Ed25519 public key
35
35
  */
@@ -51,7 +51,7 @@ declare const generateIdNoPrefix: (options?: {
51
51
  /**
52
52
  * Generate a prefixed ID
53
53
  *
54
- * Without options: generates random 22-char ID
54
+ * Without options: generates random 31-char ID
55
55
  * With { seed }: generates deterministic ID from seed (for external ID mapping)
56
56
  *
57
57
  * @example
@@ -66,7 +66,7 @@ declare const generateId: <T extends string>(prefix: T, options?: {
66
66
  *
67
67
  * @param prefix - Expected prefix (e.g., 'msg', 'post')
68
68
  * @param id - ID to validate
69
- * @returns true if ID has correct prefix and length (prefix + _ + 22 chars)
69
+ * @returns true if ID has correct prefix and length (prefix + _ + 31 chars)
70
70
  *
71
71
  * @example
72
72
  * isValidId('msg', 'msg_abc123...') // true
@@ -125,6 +125,15 @@ declare class JwsVerificationError extends Error {
125
125
  constructor(message: string);
126
126
  }
127
127
 
128
+ /**
129
+ * Apply the DFOS signature verification profile to a decoded protected header.
130
+ *
131
+ * Throws the provided error type with a precise message on any violation. The
132
+ * caller invokes this BEFORE verifying the signature so that an out-of-profile
133
+ * token is rejected regardless of whether its signature would have verified.
134
+ */
135
+ declare const assertJwsProfile: (header: Record<string, unknown>, makeError: (message: string) => Error) => void;
136
+
128
137
  interface JwtHeader {
129
138
  alg: 'EdDSA';
130
139
  typ: 'JWT';
@@ -202,4 +211,4 @@ declare const parseDagCborCID: (cid: string) => CID<unknown, number, number, mul
202
211
  */
203
212
  declare const isCanonicallyEqual: (data1: unknown, data2: unknown) => Promise<boolean>;
204
213
 
205
- export { type JwsHeader, JwsVerificationError, type JwtClaims, type JwtCreateOptions, type JwtHeader, JwtVerificationError, type JwtVerifyOptions, type PrefixedID, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, signPayloadEd25519, verifyJws, verifyJwt };
214
+ export { type JwsHeader, JwsVerificationError, type JwtClaims, type JwtCreateOptions, type JwtHeader, JwtVerificationError, type JwtVerifyOptions, type PrefixedID, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, signPayloadEd25519, verifyJws, verifyJwt };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  JwsVerificationError,
3
3
  JwtVerificationError,
4
+ assertJwsProfile,
4
5
  base64urlDecode,
5
6
  base64urlEncode,
6
7
  createJws,
@@ -20,10 +21,11 @@ import {
20
21
  signPayloadEd25519,
21
22
  verifyJws,
22
23
  verifyJwt
23
- } from "../chunk-ZXXP5W5N.js";
24
+ } from "../chunk-GQOZJKKO.js";
24
25
  export {
25
26
  JwsVerificationError,
26
27
  JwtVerificationError,
28
+ assertJwsProfile,
27
29
  base64urlDecode,
28
30
  base64urlEncode,
29
31
  createJws,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
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 { A as ArtifactPayload, B as BeaconPayload, C as ContentOperation, a as CountersignPayload, I as IdentityOperation, M as MAX_ARTIFACT_PAYLOAD_SIZE, b as MultikeyPublicKey, R as RevocationPayload, S as Signer, V as VerifiedIdentity } from './schemas-BEl38wrI.js';
1
+ export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, assertJwsProfile, 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 { A as ArtifactPayload, B as BeaconPayload, C as ContentOperation, a as CountersignPayload, I as IdentityOperation, M as MAX_ARTIFACT_PAYLOAD_SIZE, b as MultikeyPublicKey, R as RevocationPayload, S as Signer, V as VerifiedIdentity } from './schemas-BhikXSf_.js';
3
3
  export { ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, VerifiedArtifact, VerifiedBeacon, VerifiedContentChain, VerifiedCountersignature, VerifiedRevocation, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signArtifact, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, signRevocation, verifyArtifact, verifyBeacon, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation } from './chain/index.js';
4
- export { MerkleProof, buildMerkleTree, generateMerkleProof, hashLeaf, hexToBytes, verifyMerkleProof } from './merkle/index.js';
5
4
  export { Attenuation, AuthTokenClaims, AuthTokenCreateOptions, AuthTokenVerificationError, AuthTokenVerifyOptions, CredentialVerificationError, DFOSCredentialPayload, VerifiedAuthToken, VerifiedDFOSCredential, VerifiedDelegationChain, createAuthToken, createDFOSCredential, decodeDFOSCredentialUnsafe, isAttenuated, matchesResource, verifyAuthToken, verifyDFOSCredential, verifyDelegationChain } from './credentials/index.js';
6
5
  import 'multiformats';
7
6
  import 'multiformats/cid';
package/dist/index.js CHANGED
@@ -24,14 +24,7 @@ import {
24
24
  verifyIdentityChain,
25
25
  verifyIdentityExtensionFromTrustedState,
26
26
  verifyRevocation
27
- } from "./chunk-LQ56P4SU.js";
28
- import {
29
- buildMerkleTree,
30
- generateMerkleProof,
31
- hashLeaf,
32
- hexToBytes,
33
- verifyMerkleProof
34
- } from "./chunk-E5CFQG2B.js";
27
+ } from "./chunk-GZ7ZAIRD.js";
35
28
  import {
36
29
  Attenuation,
37
30
  AuthTokenClaims,
@@ -50,10 +43,11 @@ import {
50
43
  verifyAuthToken,
51
44
  verifyDFOSCredential,
52
45
  verifyDelegationChain
53
- } from "./chunk-MEV6QVLC.js";
46
+ } from "./chunk-LQFOBE6X.js";
54
47
  import {
55
48
  JwsVerificationError,
56
49
  JwtVerificationError,
50
+ assertJwsProfile,
57
51
  base64urlDecode,
58
52
  base64urlEncode,
59
53
  createJws,
@@ -73,7 +67,7 @@ import {
73
67
  signPayloadEd25519,
74
68
  verifyJws,
75
69
  verifyJwt
76
- } from "./chunk-ZXXP5W5N.js";
70
+ } from "./chunk-GQOZJKKO.js";
77
71
  export {
78
72
  ArtifactPayload,
79
73
  Attenuation,
@@ -93,9 +87,9 @@ export {
93
87
  MultikeyPublicKey,
94
88
  RevocationPayload,
95
89
  VerifiedIdentity,
90
+ assertJwsProfile,
96
91
  base64urlDecode,
97
92
  base64urlEncode,
98
- buildMerkleTree,
99
93
  createAuthToken,
100
94
  createDFOSCredential,
101
95
  createJws,
@@ -111,9 +105,6 @@ export {
111
105
  encodeEd25519Multikey,
112
106
  generateId,
113
107
  generateIdNoPrefix,
114
- generateMerkleProof,
115
- hashLeaf,
116
- hexToBytes,
117
108
  importEd25519Keypair,
118
109
  isAttenuated,
119
110
  isCanonicallyEqual,
@@ -141,6 +132,5 @@ export {
141
132
  verifyIdentityExtensionFromTrustedState,
142
133
  verifyJws,
143
134
  verifyJwt,
144
- verifyMerkleProof,
145
135
  verifyRevocation
146
136
  };
@@ -91,7 +91,6 @@ declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
91
91
  baseDocumentCID: z.ZodNullable<z.ZodString>;
92
92
  createdAt: z.ZodISODateTime;
93
93
  note: z.ZodNullable<z.ZodString>;
94
- /** DFOS credential authorizing this operation when signer is not the chain creator */
95
94
  authorization: z.ZodOptional<z.ZodString>;
96
95
  }, z.core.$strict>, z.ZodObject<{
97
96
  version: z.ZodLiteral<1>;
@@ -100,7 +99,6 @@ declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
100
99
  previousOperationCID: z.ZodString;
101
100
  createdAt: z.ZodISODateTime;
102
101
  note: z.ZodNullable<z.ZodString>;
103
- /** DFOS credential authorizing this operation when signer is not the chain creator */
104
102
  authorization: z.ZodOptional<z.ZodString>;
105
103
  }, z.core.$strict>], "type">;
106
104
  type ContentOperation = z.infer<typeof ContentOperation>;
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "description": "Beacon: signed manifest content ID announcement with witness countersignature",
3
3
  "type": "beacon",
4
- "controllerJws": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmJlYWNvbiIsImtpZCI6ImRpZDpkZm9zOmUzdnZ0Y2s0MmQ0ZWFjZG56dnRybjYja2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYzJtdXg0cGxpNXFmZDVzYnAyeXh5MmdqbTU0Zmc1Z2NpNm02YnBldm9pdXdmZGc2cG91NCJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiYmVhY29uIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsIm1hbmlmZXN0Q29udGVudElkIjoiYTgyejkyYTNobmRrNmM5N3RoY3JuOCIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDU6MDAuMDAwWiJ9._EKV036utOU-oMHwMyJ1Om1QhJzN-g9DTRbMz0U7L9SzQR-sHIeC6iNreYN-oV-mBvo5RPLg4TJ0UNv-PNBzDQ",
5
- "witnessJws": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmJlYWNvbiIsImtpZCI6ImRpZDpkZm9zOmUzdnZ0Y2s0MmQ0ZWFjZG56dnRybjYja2V5X2V6OWE4NzR0Y2tyM2R2OTMzZDNja2QiLCJjaWQiOiJiYWZ5cmVpYzJtdXg0cGxpNXFmZDVzYnAyeXh5MmdqbTU0Zmc1Z2NpNm02YnBldm9pdXdmZGc2cG91NCJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiYmVhY29uIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsIm1hbmlmZXN0Q29udGVudElkIjoiYTgyejkyYTNobmRrNmM5N3RoY3JuOCIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDU6MDAuMDAwWiJ9.a2BN31Mqi296FJ8wIVOwy7zdTR4fEL2TVy2A6xG6SGUBmJdUdnlqro5JbjIOF-h5RSA1SW0i4WvIK-AeiB27BQ",
4
+ "controllerJws": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmJlYWNvbiIsImtpZCI6ImRpZDpkZm9zOmNubm5mdDlmOGEycm45MzhkNm5rejM4cjg0N3Yya3Ija2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJjaWQiOiJiYWZ5cmVpYjR3MnAydTZ0bHc3N3NidGtwdnc3ZnF2d3ZrNnJ3MzdweWFtM29zb2JvNXhwM29vZWt1cSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiYmVhY29uIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsIm1hbmlmZXN0Q29udGVudElkIjoiY3Y3bjh2a3ZyNjRjY3RmMzI5NGg5azRlYW5oZmY4eiIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDU6MDAuMDAwWiJ9.exr0Dfb_asVXeMpnUOaql9ppeO2pifzEdId8ocXHQ6-v_XUwccQdJaL4MhKzJGUbRAa0hfRVSFRndhjJ4NN1DA",
5
+ "witnessJws": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmJlYWNvbiIsImtpZCI6ImRpZDpkZm9zOmNubm5mdDlmOGEycm45MzhkNm5rejM4cjg0N3Yya3Ija2V5X2V6OWE4NzR0Y2tyM2R2OTMzZDNja2RuN3o2enJjdDgiLCJjaWQiOiJiYWZ5cmVpYjR3MnAydTZ0bHc3N3NidGtwdnc3ZnF2d3ZrNnJ3MzdweWFtM29zb2JvNXhwM29vZWt1cSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiYmVhY29uIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsIm1hbmlmZXN0Q29udGVudElkIjoiY3Y3bjh2a3ZyNjRjY3RmMzI5NGg5azRlYW5oZmY4eiIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDU6MDAuMDAwWiJ9.-49R4npkmKMJtnK4sVS_x7MFOgB1RhjkZAzwycLp80g_o6y0gV0JjnUAj12as8NglccBXEk_5DdZTFs17ygKCA",
6
6
  "controllerPublicKey": "z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb",
7
7
  "witnessPublicKey": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK",
8
8
  "expected": {
9
- "beaconCID": "bafyreic2mux4pli5qfd5sbp2yxy2gjm54fg5gci6m6bpevoiuwfdg6pou4",
10
- "did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
11
- "manifestContentId": "a82z92a3hndk6c97thcrn8",
9
+ "beaconCID": "bafyreib4w2p2u6tlw77sbtkpvw7fqvwvk6rw37pyam3osobo5xp3ooekuq",
10
+ "did": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
11
+ "manifestContentId": "cv7n8vkvr64cctf3294h9k4eanhff8z",
12
12
  "createdAt": "2026-03-07T00:05:00.000Z"
13
13
  }
14
14
  }
@@ -2,8 +2,8 @@
2
2
  "description": "Content chain: creator signs genesis, delegate signs update with write credential",
3
3
  "type": "content-delegated",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwiY2lkIjoiYmFmeXJlaWFmeG9ka290ZzJpZGE1eHBjZWR2MzdrMmtqZDdqbmJ5Z2hhamhub3VvYm9qaWF4eGo1eGkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWduN2dmdXU1NHdvMmdlZzN0ZjV0ZXBlcGxibGt0YWhqZnpxanB1aml6YzdhMmJvZXljY2EiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MTA6MDAuMDAwWiIsIm5vdGUiOm51bGx9.oanyS9rbkqrxd1j8T172jVG4ILKFwW2t1JhkQLxsFqHMMURqA5Hl-0GzGFrfSkQT02Nq5RMYXzhcToqbjoV8AQ",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczpuemtmODM4ZWZyNDI0NDMzcm4ycnprI2tleV9hOHIyNzQzNGFhcjc2YWU3MmM4NzdmIiwiY2lkIjoiYmFmeXJlaWE1M3JheWl5djNuY2Z6ZnNmdzJ2cXczeG5sZ2ZibGFiNjNnN2xrbG9laXFhb2Jxc25yenEifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwiZGlkIjoiZGlkOmRmb3M6bnprZjgzOGVmcjQyNDQzM3JuMnJ6ayIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFmeG9ka290ZzJpZGE1eHBjZWR2MzdrMmtqZDdqbmJ5Z2hhamhub3VvYm9qaWF4eGo1eGkiLCJkb2N1bWVudENJRCI6ImJhZnlyZWlmbml5bmQ2aGV2NjQ2dW9hamFjM2VnYWs3cDJxZmhyaGhvNG1mbXBwYTNydGNqcGV0Z2RlIiwiYmFzZURvY3VtZW50Q0lEIjoiYmFmeXJlaWduN2dmdXU1NHdvMmdlZzN0ZjV0ZXBlcGxibGt0YWhqZnpxanB1aml6YzdhMmJvZXljY2EiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjExOjAwLjAwMFoiLCJub3RlIjoiZGVsZWdhdGVkIGVkaXQgYnkga2V5MyIsImF1dGhvcml6YXRpb24iOiJleUpoYkdjaU9pSkZaRVJUUVNJc0luUjVjQ0k2SW1ScFpEcGtabTl6T21OeVpXUmxiblJwWVd3aUxDSnJhV1FpT2lKa2FXUTZaR1p2Y3pwbE0zWjJkR05yTkRKa05HVmhZMlJ1ZW5aMGNtNDJJMnRsZVY5eU9XVjJNelJtZG1NeU0zbzVPVGwyWldGaFpuUTRJaXdpWTJsa0lqb2lZbUZtZVhKbGFXVnNjemRvYmpkd2NXWjRlV1JvZFhWbmJXWnFiV2t5WTJWb2QyTjNiV3R1WVRJM2IyUjFhWGxvTm5VMmJYcHZaR051YnpRaWZRLmV5SjJaWEp6YVc5dUlqb3hMQ0owZVhCbElqb2lSRVpQVTBOeVpXUmxiblJwWVd3aUxDSnBjM01pT2lKa2FXUTZaR1p2Y3pwbE0zWjJkR05yTkRKa05HVmhZMlJ1ZW5aMGNtNDJJaXdpWVhWa0lqb2laR2xrT21SbWIzTTZibnByWmpnek9HVm1jalF5TkRRek0zSnVNbko2YXlJc0ltRjBkQ0k2VzNzaWNtVnpiM1Z5WTJVaU9pSmphR0ZwYmpwaFl6STJlamg2YTJ0bE5qZDZORGhoWmpOeWFHZzVJaXdpWVdOMGFXOXVJam9pZDNKcGRHVWlmVjBzSW5CeVppSTZXMTBzSW1WNGNDSTZNVGM1T0RjMk1UWXdNQ3dpYVdGMElqb3hOemN5T0RReE5qQXdmUS41ak5SeWdvSUNvX05iMWhoR3JTRkU0c2JEaWgzQU92dWhVVUFVNzBZOThDejdkaEkzakVNclJqZGZpbHh5OTgtOEJBU2VzbEpsMGtEd0ZJc1VUM0lEdyJ9.pmlU0CwctVJJL4eUlrrkUQ52266UtGnV1liETpZ6N-Ui8K-Aob58LdPbAJsk_NpiiFmyeyRiJfUnp-ADCkWqAQ"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwiY2lkIjoiYmFmeXJlaWRkc2pnZGk3dmZib2tlejVyZWl4enRmcm9kNnhjbmh0Ymc0NmI3dnZmZGRwb2Z4czVtaXkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWRyd2V4NWRjYjJ1c3NqNmJ0eGJjMzQyM3U1d2VzNnJyd29tbXhhemt1bHRzcG9oN2EzdGkiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MTA6MDAuMDAwWiIsIm5vdGUiOm51bGx9.cxfEOGZ08zCQVuqiiljZNCxZudQi3CGqANtFnWLVyh0m9Sdq2yFk_fl4jCcw_4lTDvkYOkMgEOm_VvkpnqdMDA",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczo5NGFoNzk2M24yMjNrOGM5ODg0aGgyN2VraDQybmVhI2tleV9hOHIyNzQzNGFhcjc2YWU3MmM4NzdmYTQ3a2FyOHJuIiwiY2lkIjoiYmFmeXJlaWd3ZjI0aGhkN2N3dGlhMndocWl3bDVndHZwbGRxNnZtZmozY2ZvdGV5cmJpdjNxMmJhbGkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwiZGlkIjoiZGlkOmRmb3M6OTRhaDc5NjNuMjIzazhjOTg4NGhoMjdla2g0Mm5lYSIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWRkc2pnZGk3dmZib2tlejVyZWl4enRmcm9kNnhjbmh0Ymc0NmI3dnZmZGRwb2Z4czVtaXkiLCJkb2N1bWVudENJRCI6ImJhZnlyZWllanNzaWhrZGZsamphYmRhbWhrdXlpYm1jNzQzaG4zaTM2YTdmbnV6c2plb2FydHBlNXdhIiwiYmFzZURvY3VtZW50Q0lEIjoiYmFmeXJlaWRyd2V4NWRjYjJ1c3NqNmJ0eGJjMzQyM3U1d2VzNnJyd29tbXhhemt1bHRzcG9oN2EzdGkiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjExOjAwLjAwMFoiLCJub3RlIjoiZGVsZWdhdGVkIGVkaXQgYnkga2V5MyIsImF1dGhvcml6YXRpb24iOiJleUpoYkdjaU9pSkZaRVJUUVNJc0luUjVjQ0k2SW1ScFpEcGtabTl6T21OeVpXUmxiblJwWVd3aUxDSnJhV1FpT2lKa2FXUTZaR1p2Y3pwamJtNXVablE1WmpoaE1uSnVPVE00WkRadWEzb3pPSEk0TkRkMk1tdHlJMnRsZVY5eU9XVjJNelJtZG1NeU0zbzVPVGwyWldGaFpuUTRNMjV1TWpsNmRtaGxJaXdpWTJsa0lqb2lZbUZtZVhKbGFXUmtaVzkyZFRJeVpYTjVhSE5pTlRaa2FHRTJaVFJ4TkhZeU5tbHVObmQ1YjNsMGRYbDJjVFV5TlhjMWFqTnlZMlp0Tm1raWZRLmV5SjJaWEp6YVc5dUlqb3hMQ0owZVhCbElqb2lSRVpQVTBOeVpXUmxiblJwWVd3aUxDSnBjM01pT2lKa2FXUTZaR1p2Y3pwamJtNXVablE1WmpoaE1uSnVPVE00WkRadWEzb3pPSEk0TkRkMk1tdHlJaXdpWVhWa0lqb2laR2xrT21SbWIzTTZPVFJoYURjNU5qTnVNakl6YXpoak9UZzROR2hvTWpkbGEyZzBNbTVsWVNJc0ltRjBkQ0k2VzNzaWNtVnpiM1Z5WTJVaU9pSmphR0ZwYmpvemNqSTBNMmczTnpsbGEzSnVjbUZoWTJVMk16ZzRNbVoyT0dRNE16ZzVJaXdpWVdOMGFXOXVJam9pZDNKcGRHVWlmVjBzSW5CeVppSTZXMTBzSW1WNGNDSTZNVGM1T0RjMk1UWXdNQ3dpYVdGMElqb3hOemN5T0RReE5qQXdmUS5oWko1LXpLTXNKR0tQaEtVdjVCaEt4QnJYLWlaUjNJazNJNTB2b1NEUTVrbWFLOXlvNkI3MTZBY05ibnhlc01tclpXX0FfNzJuZDVYQ29iSTZUbFJBZyJ9.0eNH6RC6O-OkFDBLEWk3YugPUzc9WGtZnG8wRzEEjz0SEwTx5z4FkNoP8Fygu7nd9DZbel9flZL-f0Lg_yD6BA"
7
7
  ],
8
8
  "creatorPublicKey": "z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb",
9
9
  "delegatePublicKey": "z6MkvsvmSh2dGnu2qw1Tnw7M5fz98ycfuYGxqnpfgmPkLv7o",
@@ -14,10 +14,10 @@
14
14
  "format": "short-post",
15
15
  "title": "Original Post",
16
16
  "body": "Content created by the chain owner.",
17
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
17
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr"
18
18
  },
19
19
  "baseDocumentCID": null,
20
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
20
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
21
21
  "createdAt": "2026-03-07T00:10:00.000Z"
22
22
  },
23
23
  {
@@ -26,19 +26,19 @@
26
26
  "format": "short-post",
27
27
  "title": "Delegated Edit",
28
28
  "body": "Content updated by an authorized delegate.",
29
- "createdByDID": "did:dfos:nzkf838efr424433rn2rzk"
29
+ "createdByDID": "did:dfos:94ah7963n223k8c9884hh27ekh42nea"
30
30
  },
31
- "baseDocumentCID": "bafyreign7gfuu54wo2geg3tf5tepeplblktahjfzqjpujizc7a2boeycca",
32
- "createdByDID": "did:dfos:nzkf838efr424433rn2rzk",
31
+ "baseDocumentCID": "bafyreidrwex5dcb2ussj6btxbc3423u5wes6rrwommxazkultspoh7a3ti",
32
+ "createdByDID": "did:dfos:94ah7963n223k8c9884hh27ekh42nea",
33
33
  "createdAt": "2026-03-07T00:11:00.000Z"
34
34
  }
35
35
  ],
36
- "authorization": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwiY2lkIjoiYmFmeXJlaWVsczdobjdwcWZ4eWRodXVnbWZqbWkyY2Vod2N3bWtuYTI3b2R1aXloNnU2bXpvZGNubzQifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42IiwiYXVkIjoiZGlkOmRmb3M6bnprZjgzOGVmcjQyNDQzM3JuMnJ6ayIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjphYzI2ejh6a2tlNjd6NDhhZjNyaGg5IiwiYWN0aW9uIjoid3JpdGUifV0sInByZiI6W10sImV4cCI6MTc5ODc2MTYwMCwiaWF0IjoxNzcyODQxNjAwfQ.5jNRygoICo_Nb1hhGrSFE4sbDih3AOvuhUUAU70Y98Cz7dhI3jEMrRjdfilxy98-8BASeslJl0kDwFIsUT3IDw",
36
+ "authorization": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwiY2lkIjoiYmFmeXJlaWRkZW92dTIyZXN5aHNiNTZkaGE2ZTRxNHYyNmluNnd5b3l0dXl2cTUyNXc1ajNyY2ZtNmkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyIiwiYXVkIjoiZGlkOmRmb3M6OTRhaDc5NjNuMjIzazhjOTg4NGhoMjdla2g0Mm5lYSIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjozcjI0M2g3Nzlla3JucmFhY2U2Mzg4MmZ2OGQ4Mzg5IiwiYWN0aW9uIjoid3JpdGUifV0sInByZiI6W10sImV4cCI6MTc5ODc2MTYwMCwiaWF0IjoxNzcyODQxNjAwfQ.hZJ5-zKMsJGKPhKUv5BhKxBrX-iZR3Ik3I50voSDQ5kmaK9yo6B716AcNbnxesMmrZW_A_72nd5XCobI6TlRAg",
37
37
  "expected": {
38
- "contentId": "ac26z8zkke67z48af3rhh9",
39
- "creatorDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
38
+ "contentId": "3r243h779ekrnraace63882fv8d8389",
39
+ "creatorDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
40
40
  "isDeleted": false,
41
- "currentDocumentCID": "bafyreifniynd6hev646uoajac3egak7p2qfhrhho4mfmppa3rtcjpetgde",
41
+ "currentDocumentCID": "bafyreiejssihkdfljjabdamhkuyibmc743hn3i36a7fnuzsjeoartpe5wa",
42
42
  "length": 2
43
43
  }
44
44
  }
@@ -2,8 +2,8 @@
2
2
  "description": "Content chain: create + delete",
3
3
  "type": "content",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.Rv6vlz5MfrwqDUrSVIGs4ZfeBbkQUSBcXhxwZ6hfudSr5MxhYl08hTqLDOA0W1NMjN0Hs0IW9jXTwLwP1dMDBg",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWQydjUyN2lxMzZ2dnUyNWc3b3BjczJudjRjbmRhcWdrd29naHEzdHB3Z215cnBxZWVuZ2EifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGVsZXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoicmVtb3ZpbmcgY29udGVudCJ9.9DTnVeYjVV6Kc971etvWeNn9iMme7RnrRZRmYBO0iaYeePJDblL_p2uuEIuxtAQQp1bATpsQ475zpK6N2A0eAg"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkbjd6NnpyY3Q4IiwiY2lkIjoiYmFmeXJlaWFxYXRnZGd3Z2d1Zmd5NHRzejZldXJ3dWR0ZHh5Z3V6dHQ3bnE1d2dkN3FpNDQ1bnY1NnkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWV2Y3FybXZ0ejJwaXM1dGRpenQ3c2pvdG9xcW9nbDZ2cnJxZ2E2NHcydG53a3Eycm51ZHkiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.POTcvRbYb5r_P6ZpJRNAufCkEHmfebUyc1jb_USg7xdG8bwq520HMsg6wWjfrhU8Y-7_86IcLwpldD03_L0-Ag",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkbjd6NnpyY3Q4IiwiY2lkIjoiYmFmeXJlaWFsaXlvenNqaW4yaTd4NHl4ejJwcmJnaGIybGU1YzYycGxsbWxhc3YyaGlhZ3l5ZnZjc2kifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGVsZXRlIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFxYXRnZGd3Z2d1Zmd5NHRzejZldXJ3dWR0ZHh5Z3V6dHQ3bnE1d2dkN3FpNDQ1bnY1NnkiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoicmVtb3ZpbmcgY29udGVudCJ9.k_zTEiRy1aPc1d39SQJOCf8yCVaRoYhenun4RJTxBsmDu6TZ5EvBAvXnbFS0DYLE3X1wV1G6YQG6rpxYElG_Ag"
7
7
  ],
8
8
  "signerPublicKey": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK",
9
9
  "documents": [
@@ -13,15 +13,15 @@
13
13
  "format": "short-post",
14
14
  "title": "Hello World",
15
15
  "body": "First post on the protocol.",
16
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
16
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr"
17
17
  },
18
18
  "baseDocumentCID": null,
19
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
19
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
20
20
  "createdAt": "2026-03-07T00:02:00.000Z"
21
21
  }
22
22
  ],
23
23
  "expected": {
24
- "contentId": "a82z92a3hndk6c97thcrn8",
24
+ "contentId": "cv7n8vkvr64cctf3294h9k4eanhff8z",
25
25
  "isDeleted": true,
26
26
  "currentDocumentCID": null,
27
27
  "length": 2
@@ -2,8 +2,8 @@
2
2
  "description": "Content chain: create + update (with both documents)",
3
3
  "type": "content",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.Rv6vlz5MfrwqDUrSVIGs4ZfeBbkQUSBcXhxwZ6hfudSr5MxhYl08hTqLDOA0W1NMjN0Hs0IW9jXTwLwP1dMDBg",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwiY2lkIjoiYmFmeXJlaWg2ZTVjYmppdHBvemh6aGdtZmt0bWlvaG14eW4zdWN3aHFkM21qaXhpenZ3bWxodjdobTQifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwiZGlkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFlZGhqcTY0YWFqcHdvY2lhaGw1dzM3ajZ1b3hyNW1vam9xNWRuYWg2ZnB2eHI1ZDRseHUiLCJkb2N1bWVudENJRCI6ImJhZnlyZWlkaDdlMzZjdnd5M3V3NXlwaXRjcWs3dW9rdGJra2tqN2U2aHhoa3k0bzc1cnhuN2t4aWx1IiwiYmFzZURvY3VtZW50Q0lEIjoiYmFmeXJlaWh6d3VvdXBmZzNkeGlwNnhtZ3pteHN5d3lpaTJqZW94eHpiZ3gzenhtMmluN2tub2kzZzQiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoiZWRpdGVkIHRpdGxlIGFuZCBib2R5In0.46H3WBKigCzUrIOvS2ekYJFdK1OeLhqvcZTj8FBqFNKO1ivs3oC0ui_hzTKkKO-8uevMjf9-k0GOjyw2u16PDA"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkbjd6NnpyY3Q4IiwiY2lkIjoiYmFmeXJlaWFxYXRnZGd3Z2d1Zmd5NHRzejZldXJ3dWR0ZHh5Z3V6dHQ3bnE1d2dkN3FpNDQ1bnY1NnkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsImRvY3VtZW50Q0lEIjoiYmFmeXJlaWV2Y3FybXZ0ejJwaXM1dGRpenQ3c2pvdG9xcW9nbDZ2cnJxZ2E2NHcydG53a3Eycm51ZHkiLCJiYXNlRG9jdW1lbnRDSUQiOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDI6MDAuMDAwWiIsIm5vdGUiOm51bGx9.POTcvRbYb5r_P6ZpJRNAufCkEHmfebUyc1jb_USg7xdG8bwq520HMsg6wWjfrhU8Y-7_86IcLwpldD03_L0-Ag",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNvbnRlbnQtb3AiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkbjd6NnpyY3Q4IiwiY2lkIjoiYmFmeXJlaWJweDRjZ2I0ajZuM216NzY0cHlscmRnNnE3YTQ2bmpuaHg2cDRjcTJybGdldWUzczNldnEifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwiZGlkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciIsInByZXZpb3VzT3BlcmF0aW9uQ0lEIjoiYmFmeXJlaWFxYXRnZGd3Z2d1Zmd5NHRzejZldXJ3dWR0ZHh5Z3V6dHQ3bnE1d2dkN3FpNDQ1bnY1NnkiLCJkb2N1bWVudENJRCI6ImJhZnlyZWlmZXRwdXRreTRmbnp2N3NyZzdsN3luaWg2ajR5dHplcWlicmNwNXVpZXB2b2x4cWhjYmN5IiwiYmFzZURvY3VtZW50Q0lEIjoiYmFmeXJlaWV2Y3FybXZ0ejJwaXM1dGRpenQ3c2pvdG9xcW9nbDZ2cnJxZ2E2NHcydG53a3Eycm51ZHkiLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAzOjAwLjAwMFoiLCJub3RlIjoiZWRpdGVkIHRpdGxlIGFuZCBib2R5In0.yTkJOiiCsjIBHmR7773W1pdjC5in_Fl3WUXHl8Y_Hyb47RgfiWVufPirIolnuI-IOOl6zHNNmDfmy6kFROwOAg"
7
7
  ],
8
8
  "signerPublicKey": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK",
9
9
  "documents": [
@@ -13,10 +13,10 @@
13
13
  "format": "short-post",
14
14
  "title": "Hello World",
15
15
  "body": "First post on the protocol.",
16
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
16
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr"
17
17
  },
18
18
  "baseDocumentCID": null,
19
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
19
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
20
20
  "createdAt": "2026-03-07T00:02:00.000Z"
21
21
  },
22
22
  {
@@ -25,17 +25,17 @@
25
25
  "format": "short-post",
26
26
  "title": "Hello World (edited)",
27
27
  "body": "Updated content.",
28
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6"
28
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr"
29
29
  },
30
- "baseDocumentCID": "bafyreihzwuoupfg3dxip6xmgzmxsywyii2jeoxxzbgx3zxm2in7knoi3g4",
31
- "createdByDID": "did:dfos:e3vvtck42d4eacdnzvtrn6",
30
+ "baseDocumentCID": "bafyreievcqrmvtz2pis5tdizt7sjotoqqogl6vrrqga64w2tnwkq2rnudy",
31
+ "createdByDID": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
32
32
  "createdAt": "2026-03-07T00:03:00.000Z"
33
33
  }
34
34
  ],
35
35
  "expected": {
36
- "contentId": "a82z92a3hndk6c97thcrn8",
36
+ "contentId": "cv7n8vkvr64cctf3294h9k4eanhff8z",
37
37
  "isDeleted": false,
38
- "currentDocumentCID": "bafyreidh7e36cvwy3uw5ypitcqk7uoktbkkkj7e6hxhky4o75rxn7kxilu",
38
+ "currentDocumentCID": "bafyreifetputky4fnzv7srg7l7ynih6j4ytzeqibrcp5uiepvolxqhcbcy",
39
39
  "length": 2
40
40
  }
41
41
  }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "description": "DFOS credential: read access",
3
3
  "type": "credential",
4
- "credential": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwiY2lkIjoiYmFmeXJlaWMzbmJxemFicmxtbnl2a3o3cXI3Znk2cGd4NGFwdm52eWJvNWtzaGN6bXViaXFzemdod2EifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42IiwiYXVkIjoiZGlkOmRmb3M6bnprZjgzOGVmcjQyNDQzM3JuMnJ6ayIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjoqIiwiYWN0aW9uIjoicmVhZCJ9XSwicHJmIjpbXSwiZXhwIjoxNzk4NzYxNjAwLCJpYXQiOjE3NzI4NDE2MDB9.QB-qK89S-sYXaDUkJJSF5ZbsV2djFFvRQlHCj6UDyl-47LZI-ISwwyqRV-zi6MEGdHb0seSkPxpE4if6HHvvCw",
4
+ "credential": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwiY2lkIjoiYmFmeXJlaWN0aGNiaXp4dmdlbXN4djdrc2NvbzdhcGllYWFsM2Z5ZTM3bzQ1Zmt5a25lN2I0aG9icmEifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyIiwiYXVkIjoiZGlkOmRmb3M6OTRhaDc5NjNuMjIzazhjOTg4NGhoMjdla2g0Mm5lYSIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjoqIiwiYWN0aW9uIjoicmVhZCJ9XSwicHJmIjpbXSwiZXhwIjoxNzk4NzYxNjAwLCJpYXQiOjE3NzI4NDE2MDB9.UvTItuWFriA39FZIdB5TuXa_b07eyNLc-iR0cej2litSkjBYAZaLlDJUmyDQ-3dB7TmNVXDbB3SMbpvLnWW9Dw",
5
5
  "issuerPublicKey": "z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb",
6
6
  "audiencePublicKey": "z6MkvsvmSh2dGnu2qw1Tnw7M5fz98ycfuYGxqnpfgmPkLv7o",
7
7
  "expected": {
8
- "iss": "did:dfos:e3vvtck42d4eacdnzvtrn6",
9
- "aud": "did:dfos:nzkf838efr424433rn2rzk"
8
+ "iss": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
9
+ "aud": "did:dfos:94ah7963n223k8c9884hh27ekh42nea"
10
10
  }
11
11
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "description": "DFOS credential: write access (broad + narrowed)",
3
3
  "type": "credential",
4
- "broadCredential": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwiY2lkIjoiYmFmeXJlaWh6dDV3Nmt4YnlsZWZ1N2R3ZDRmbnZxdnlueHphNnhud3N6bXpoYml6anVjNnhjeHFkNmEifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42IiwiYXVkIjoiZGlkOmRmb3M6bnprZjgzOGVmcjQyNDQzM3JuMnJ6ayIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjoqIiwiYWN0aW9uIjoid3JpdGUifV0sInByZiI6W10sImV4cCI6MTc5ODc2MTYwMCwiaWF0IjoxNzcyODQxNjAwfQ.brsN3WSdTLhN5-c0mhDriiKa2FuDD3eW5Mlj3KJYcj0cKQH0RDSACMp3qLeN2DGop-kfOtqtxlS7SAMIuCZGAw",
5
- "narrowCredential": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42I2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwiY2lkIjoiYmFmeXJlaWFreDQ1ZTJnZm5udmF2a25la3YzMnJleTU3a2lybXA3cTV2YW5teHZ0ajc0NjRqbWJpcXUifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczplM3Z2dGNrNDJkNGVhY2RuenZ0cm42IiwiYXVkIjoiZGlkOmRmb3M6bnprZjgzOGVmcjQyNDQzM3JuMnJ6ayIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjphODJ6OTJhM2huZGs2Yzk3dGhjcm44IiwiYWN0aW9uIjoid3JpdGUifV0sInByZiI6W10sImV4cCI6MTc5ODc2MTYwMCwiaWF0IjoxNzcyODQxNjAwfQ.ep3NgoKLuKQBohx0Gn6FmroBxEJiV1A3PzKWoBqIJAt6UZqw6f2sLOFNSTb8t9PnONHh5aXuG99OmF7Fyv4mBA",
4
+ "broadCredential": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwiY2lkIjoiYmFmeXJlaWZ5aW5ieGhicml0NTZtM2FhdjY2bXc0eGQ2YWRxamFzdmNmaG11NjZnNnRudXFncnljbG0ifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyIiwiYXVkIjoiZGlkOmRmb3M6OTRhaDc5NjNuMjIzazhjOTg4NGhoMjdla2g0Mm5lYSIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjoqIiwiYWN0aW9uIjoid3JpdGUifV0sInByZiI6W10sImV4cCI6MTc5ODc2MTYwMCwiaWF0IjoxNzcyODQxNjAwfQ.A-EygURAN2bALVwI2AZKFEuy30ZnWJFBaD4jCTf1d7A90rYELStjTWJ1iI7OulihTCfaVtlvj5HtX6Dwv1VxAg",
5
+ "narrowCredential": "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmNyZWRlbnRpYWwiLCJraWQiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyI2tleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwiY2lkIjoiYmFmeXJlaWNiNGVoZWl6eHNrM3N6Ymgyb2dvbXU0cmV1NGd0a2kycHB4NjR6ZW8zeGFrMnh1bWZ2dWkifQ.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiREZPU0NyZWRlbnRpYWwiLCJpc3MiOiJkaWQ6ZGZvczpjbm5uZnQ5ZjhhMnJuOTM4ZDZua3ozOHI4NDd2MmtyIiwiYXVkIjoiZGlkOmRmb3M6OTRhaDc5NjNuMjIzazhjOTg4NGhoMjdla2g0Mm5lYSIsImF0dCI6W3sicmVzb3VyY2UiOiJjaGFpbjpjdjduOHZrdnI2NGNjdGYzMjk0aDlrNGVhbmhmZjh6IiwiYWN0aW9uIjoid3JpdGUifV0sInByZiI6W10sImV4cCI6MTc5ODc2MTYwMCwiaWF0IjoxNzcyODQxNjAwfQ.xwsGleofAr78xbT4jCrK402PEI8G_OzvxJITvAjL5ltr7_2bdkX4sCjmZbnjl9m9QcEXnWyWbBgYO8KFl2r8CQ",
6
6
  "issuerPublicKey": "z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb",
7
7
  "audiencePublicKey": "z6MkvsvmSh2dGnu2qw1Tnw7M5fz98ycfuYGxqnpfgmPkLv7o",
8
8
  "expected": {
9
- "iss": "did:dfos:e3vvtck42d4eacdnzvtrn6",
10
- "aud": "did:dfos:nzkf838efr424433rn2rzk",
11
- "narrowContentId": "a82z92a3hndk6c97thcrn8"
9
+ "iss": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
10
+ "aud": "did:dfos:94ah7963n223k8c9884hh27ekh42nea",
11
+ "narrowContentId": "cv7n8vkvr64cctf3294h9k4eanhff8z"
12
12
  }
13
13
  }
@@ -2,15 +2,15 @@
2
2
  "description": "Identity chain: genesis + delete (terminal)",
3
3
  "type": "identity",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiNrZXlfcjlldjM0ZnZjMjN6OTk5dmVhYWZ0OCIsImNpZCI6ImJhZnlyZWloZ214ZmlnY2JvZjQ2czRrcGtteXhicXJ3dGxybmE0b2NibmxybzNzN29wanljYW15b3g0In0.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGVsZXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDE6MDAuMDAwWiJ9.l3X4ojfmfSd2N2tcLL4LlQbP2iaNrsxrgJ-1Wh2T3xNeZ77jgoWYIN6NHI-oYfi9GnwHBPKsOPKoBOoEFUPlAw"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJjaWQiOiJiYWZ5cmVpY29naHZqem52bGl1bG94eG1iZjU0dHB6cXdhaG5xcGlsazduY3hlcGppbmVkcGtnYTNuZSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.TeznHnzrtKOGTr0FzkDL2z-luMWnAbKXrmDbi-Exgw_xMPCnYwGHORMjw-BM28f0RoTirIAeD7d20W5RSuGuBg",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciNrZXlfcjlldjM0ZnZjMjN6OTk5dmVhYWZ0ODNubjI5enZoZSIsImNpZCI6ImJhZnlyZWlnaWJ5anN2bGt0ZTQ2Zzd6NHIydXd5eHA2eDU1b3RlbzRmcjVuZGE3emFvbHFvbDIyM3hhIn0.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGVsZXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpY29naHZqem52bGl1bG94eG1iZjU0dHB6cXdhaG5xcGlsazduY3hlcGppbmVkcGtnYTNuZSIsImNyZWF0ZWRBdCI6IjIwMjYtMDMtMDdUMDA6MDE6MDAuMDAwWiJ9.7SNEk43hmCoN4vpUELfHNMsHO_H1QBPurEFPH3n2xf3cVR3EvJ1A7-xwf8_f3AmawFNI712jMQTpj9r4jQvQCg"
7
7
  ],
8
8
  "expected": {
9
- "did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
9
+ "did": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
10
10
  "isDeleted": true,
11
11
  "controllerKeys": [
12
12
  {
13
- "id": "key_r9ev34fvc23z999veaaft8",
13
+ "id": "key_r9ev34fvc23z999veaaft83nn29zvhe",
14
14
  "type": "Multikey",
15
15
  "publicKeyMultibase": "z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb"
16
16
  }
@@ -2,14 +2,14 @@
2
2
  "description": "Identity chain: genesis (single create operation)",
3
3
  "type": "identity",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJjaWQiOiJiYWZ5cmVpY29naHZqem52bGl1bG94eG1iZjU0dHB6cXdhaG5xcGlsazduY3hlcGppbmVkcGtnYTNuZSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.TeznHnzrtKOGTr0FzkDL2z-luMWnAbKXrmDbi-Exgw_xMPCnYwGHORMjw-BM28f0RoTirIAeD7d20W5RSuGuBg"
6
6
  ],
7
7
  "expected": {
8
- "did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
8
+ "did": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
9
9
  "isDeleted": false,
10
10
  "controllerKeys": [
11
11
  {
12
- "id": "key_r9ev34fvc23z999veaaft8",
12
+ "id": "key_r9ev34fvc23z999veaaft83nn29zvhe",
13
13
  "type": "Multikey",
14
14
  "publicKeyMultibase": "z6MkrzLMNwoJSV4P3YccWcbtk8vd9LtgMKnLeaDLUqLuASjb"
15
15
  }
@@ -2,15 +2,15 @@
2
2
  "description": "Identity chain: genesis + key rotation",
3
3
  "type": "identity",
4
4
  "chain": [
5
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw",
6
- "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiNrZXlfcjlldjM0ZnZjMjN6OTk5dmVhYWZ0OCIsImNpZCI6ImJhZnlyZWljeW00Y3lpZWRubGQ3M3NtYngzMnN6YWVpN3hkdWxxbjRnM3N0ZTVlMncydWxhanIzb3FtIn0.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSIsImF1dGhLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJhc3NlcnRLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJjb250cm9sbGVyS2V5cyI6W3siaWQiOiJrZXlfZXo5YTg3NHRja3IzZHY5MzNkM2NrZCIsInR5cGUiOiJNdWx0aWtleSIsInB1YmxpY0tleU11bHRpYmFzZSI6Ino2TWtmVWQ2NUpyQWhmZGdGdU1DY2NVOVRoUXZqQjJmSkFNVUhrdXVhakY5OTJnSyJ9XSwiY3JlYXRlZEF0IjoiMjAyNi0wMy0wN1QwMDowMTowMC4wMDBaIn0.MScuoBlgOK3j5QX9tFcw1ou0o4LgJziGJEsZ5pvqiBr1SagAyAv5h-wajQhtg8IP7dLlM0U4leW2iRra945cDg"
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJjaWQiOiJiYWZ5cmVpY29naHZqem52bGl1bG94eG1iZjU0dHB6cXdhaG5xcGlsazduY3hlcGppbmVkcGtnYTNuZSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgzbm4yOXp2aGUiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4M25uMjl6dmhlIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.TeznHnzrtKOGTr0FzkDL2z-luMWnAbKXrmDbi-Exgw_xMPCnYwGHORMjw-BM28f0RoTirIAeD7d20W5RSuGuBg",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoiZGlkOmRmb3M6Y25ubmZ0OWY4YTJybjkzOGQ2bmt6MzhyODQ3djJrciNrZXlfcjlldjM0ZnZjMjN6OTk5dmVhYWZ0ODNubjI5enZoZSIsImNpZCI6ImJhZnlyZWliZnVoNjN1djMzaTJpNWVvb2UzYm9pdDJydXlqZWh1YnNyeWVtdXV6Nm1ydGxlajI2cmVpIn0.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpY29naHZqem52bGl1bG94eG1iZjU0dHB6cXdhaG5xcGlsazduY3hlcGppbmVkcGtnYTNuZSIsImF1dGhLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkbjd6NnpyY3Q4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJhc3NlcnRLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkbjd6NnpyY3Q4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJjb250cm9sbGVyS2V5cyI6W3siaWQiOiJrZXlfZXo5YTg3NHRja3IzZHY5MzNkM2NrZG43ejZ6cmN0OCIsInR5cGUiOiJNdWx0aWtleSIsInB1YmxpY0tleU11bHRpYmFzZSI6Ino2TWtmVWQ2NUpyQWhmZGdGdU1DY2NVOVRoUXZqQjJmSkFNVUhrdXVhakY5OTJnSyJ9XSwiY3JlYXRlZEF0IjoiMjAyNi0wMy0wN1QwMDowMTowMC4wMDBaIn0.7fqvWGEVYW9atA1uqpp7lIUOWp4dATLpLjOmFWzJN-8gTL-QnXDCeyGcBu5AXhHzO52fauwUavh1KrB6wBYuCw"
7
7
  ],
8
8
  "expected": {
9
- "did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
9
+ "did": "did:dfos:cnnnft9f8a2rn938d6nkz38r847v2kr",
10
10
  "isDeleted": false,
11
11
  "controllerKeys": [
12
12
  {
13
- "id": "key_ez9a874tckr3dv933d3ckd",
13
+ "id": "key_ez9a874tckr3dv933d3ckdn7z6zrct8",
14
14
  "type": "Multikey",
15
15
  "publicKeyMultibase": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK"
16
16
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-protocol",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
- "description": "DFOS Protocol — Ed25519 signed chain primitives, beacons, merkle trees, and verification",
5
+ "description": "DFOS Protocol — Ed25519 signed chain primitives, beacons, credentials, and verification",
6
6
  "license": "MIT",
7
7
  "author": "Metalabel <hello@metalabel.com> (https://metalabel.com)",
8
8
  "repository": {
@@ -37,10 +37,6 @@
37
37
  "import": "./dist/chain/index.js",
38
38
  "types": "./dist/chain/index.d.ts"
39
39
  },
40
- "./merkle": {
41
- "import": "./dist/merkle/index.js",
42
- "types": "./dist/merkle/index.d.ts"
43
- },
44
40
  "./credentials": {
45
41
  "import": "./dist/credentials/index.js",
46
42
  "types": "./dist/credentials/index.d.ts"
@@ -54,19 +50,19 @@
54
50
  "README.md"
55
51
  ],
56
52
  "dependencies": {
57
- "@ipld/dag-cbor": "^9.2.5",
58
- "@noble/curves": "^2.0.1",
59
- "@noble/hashes": "^2.0.1",
60
- "multiformats": "^13.4.2",
61
- "zod": "^4.3.6"
53
+ "@ipld/dag-cbor": "^10.0.1",
54
+ "@noble/curves": "^2.2.0",
55
+ "@noble/hashes": "^2.2.0",
56
+ "multiformats": "^14.0.0",
57
+ "zod": "^4.4.3"
62
58
  },
63
59
  "devDependencies": {
64
60
  "@types/node": "^24.10.4",
65
- "ajv": "^8.18.0",
61
+ "ajv": "^8.20.0",
66
62
  "ajv-formats": "^3.0.1",
67
63
  "tsup": "^8.5.1",
68
- "tsx": "^4.21.0",
69
- "vitest": "^4.1.2"
64
+ "tsx": "^4.22.4",
65
+ "vitest": "^4.1.8"
70
66
  },
71
67
  "scripts": {
72
68
  "build": "tsup",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://schemas.dfos.com/manifest/v1",
4
4
  "title": "Manifest",
5
- "description": "A manifest — a named map of protocol object references for semantic navigation. Keys are path-like labels. Values are protocol references: content chain identifiers (22-char bare hash), DIDs (did:dfos:...), or CIDs (bafyrei...). The document layer of the dark forest. Discovery is social or out-of-band.",
5
+ "description": "A manifest — a named map of protocol object references for semantic navigation. Keys are path-like labels. Values are protocol references: content chain identifiers (31-char bare hash), DIDs (did:dfos:...), or CIDs (bafyrei...). The document layer of the dark forest. Discovery is social or out-of-band.",
6
6
  "type": "object",
7
7
  "required": ["$schema", "entries"],
8
8
  "properties": {
@@ -1,99 +0,0 @@
1
- // src/merkle/tree.ts
2
- var sha256 = async (data) => {
3
- const buf = await crypto.subtle.digest("SHA-256", data);
4
- return new Uint8Array(buf);
5
- };
6
- var toHex = (bytes) => {
7
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
8
- };
9
- var hexToBytes = (hex) => {
10
- const bytes = new Uint8Array(hex.length / 2);
11
- for (let i = 0; i < hex.length; i += 2) {
12
- bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
13
- }
14
- return bytes;
15
- };
16
- var concat = (a, b) => {
17
- const result = new Uint8Array(a.length + b.length);
18
- result.set(a, 0);
19
- result.set(b, a.length);
20
- return result;
21
- };
22
- var hashLeaf = async (contentId) => {
23
- return sha256(new TextEncoder().encode(contentId));
24
- };
25
- var hashInterior = async (left, right) => {
26
- return sha256(concat(left, right));
27
- };
28
- var buildMerkleTree = async (contentIds) => {
29
- const sorted = [...new Set(contentIds)].sort();
30
- if (sorted.length === 0) return { root: null, leafCount: 0 };
31
- let level = await Promise.all(sorted.map(hashLeaf));
32
- while (level.length > 1) {
33
- const nextLevel = [];
34
- for (let i = 0; i < level.length; i += 2) {
35
- if (i + 1 < level.length) {
36
- nextLevel.push(await hashInterior(level[i], level[i + 1]));
37
- } else {
38
- nextLevel.push(level[i]);
39
- }
40
- }
41
- level = nextLevel;
42
- }
43
- return { root: toHex(level[0]), leafCount: sorted.length };
44
- };
45
-
46
- // src/merkle/proof.ts
47
- var generateMerkleProof = async (contentIds, targetId) => {
48
- const sorted = [...new Set(contentIds)].sort();
49
- const targetIdx = sorted.indexOf(targetId);
50
- if (targetIdx < 0) return null;
51
- const { root } = await buildMerkleTree(sorted);
52
- if (!root) return null;
53
- const leaves = await Promise.all(sorted.map(hashLeaf));
54
- const path = [];
55
- let level = leaves;
56
- let idx = targetIdx;
57
- while (level.length > 1) {
58
- const nextLevel = [];
59
- const nextIdx = Math.floor(idx / 2);
60
- for (let i = 0; i < level.length; i += 2) {
61
- if (i + 1 < level.length) {
62
- if (i === idx || i + 1 === idx) {
63
- const siblingIdx = i === idx ? i + 1 : i;
64
- path.push({
65
- hash: toHex(level[siblingIdx]),
66
- position: siblingIdx < idx ? "left" : "right"
67
- });
68
- }
69
- const interior = await sha256(concat(level[i], level[i + 1]));
70
- nextLevel.push(interior);
71
- } else {
72
- nextLevel.push(level[i]);
73
- }
74
- }
75
- level = nextLevel;
76
- idx = nextIdx;
77
- }
78
- return { contentId: targetId, root, path };
79
- };
80
- var verifyMerkleProof = async (proof) => {
81
- let current = await hashLeaf(proof.contentId);
82
- for (const step of proof.path) {
83
- const sibling = hexToBytes(step.hash);
84
- if (step.position === "left") {
85
- current = await sha256(concat(sibling, current));
86
- } else {
87
- current = await sha256(concat(current, sibling));
88
- }
89
- }
90
- return toHex(current) === proof.root;
91
- };
92
-
93
- export {
94
- hexToBytes,
95
- hashLeaf,
96
- buildMerkleTree,
97
- generateMerkleProof,
98
- verifyMerkleProof
99
- };
@@ -1,45 +0,0 @@
1
- declare const hexToBytes: (hex: string) => Uint8Array;
2
- /**
3
- * Hash a leaf node — SHA-256 of UTF-8 encoded contentId
4
- */
5
- declare const hashLeaf: (contentId: string) => Promise<Uint8Array>;
6
- /**
7
- * Build a sorted binary Merkle tree over content identifiers
8
- *
9
- * ContentIds are sorted lexicographically, hashed to leaves, then paired
10
- * and hashed up to the root. Odd nodes are promoted to the next level.
11
- *
12
- * Returns null root for empty input. Deduplicates contentIds.
13
- */
14
- declare const buildMerkleTree: (contentIds: string[]) => Promise<{
15
- root: string | null;
16
- leafCount: number;
17
- }>;
18
-
19
- interface MerkleProof {
20
- /** The contentId being proven */
21
- contentId: string;
22
- /** Hex SHA-256 root of the tree */
23
- root: string;
24
- /** Sibling hashes along the path from leaf to root */
25
- path: Array<{
26
- /** Hex SHA-256 of sibling node */
27
- hash: string;
28
- /** Position of the sibling relative to the current node */
29
- position: 'left' | 'right';
30
- }>;
31
- }
32
- /**
33
- * Generate an inclusion proof for a contentId in the set
34
- *
35
- * Returns null if the contentId is not in the set.
36
- */
37
- declare const generateMerkleProof: (contentIds: string[], targetId: string) => Promise<MerkleProof | null>;
38
- /**
39
- * Verify a Merkle inclusion proof
40
- *
41
- * Recomputes the root from the leaf and proof path, compares to the claimed root.
42
- */
43
- declare const verifyMerkleProof: (proof: MerkleProof) => Promise<boolean>;
44
-
45
- export { type MerkleProof, buildMerkleTree, generateMerkleProof, hashLeaf, hexToBytes, verifyMerkleProof };
@@ -1,14 +0,0 @@
1
- import {
2
- buildMerkleTree,
3
- generateMerkleProof,
4
- hashLeaf,
5
- hexToBytes,
6
- verifyMerkleProof
7
- } from "../chunk-E5CFQG2B.js";
8
- export {
9
- buildMerkleTree,
10
- generateMerkleProof,
11
- hashLeaf,
12
- hexToBytes,
13
- verifyMerkleProof
14
- };
@@ -1,28 +0,0 @@
1
- {
2
- "description": "Merkle tree: 5 content IDs → sorted → leaf hashes → root, with inclusion proof for \"charlie\"",
3
- "type": "merkle",
4
- "contentIds": ["alpha", "bravo", "charlie", "delta", "echo"],
5
- "expected": {
6
- "sortedIds": ["alpha", "bravo", "charlie", "delta", "echo"],
7
- "root": "7e80d4780f454e0fca0b090d8c646f572b49354f54154531606105aad2fda28e",
8
- "leafCount": 5,
9
- "charlieProof": {
10
- "contentId": "charlie",
11
- "root": "7e80d4780f454e0fca0b090d8c646f572b49354f54154531606105aad2fda28e",
12
- "path": [
13
- {
14
- "hash": "4f4a9410ffcdf895c4adb880659e9b5c0dd1f23a30790684340b3eaacb045398",
15
- "position": "right"
16
- },
17
- {
18
- "hash": "90d39555bb3c223e12f5a375c3011d2462fe2e1e36b8416a0b623d5831a9b4f3",
19
- "position": "left"
20
- },
21
- {
22
- "hash": "092c79e8f80e559e404bcf660c48f3522b67aba9ff1484b0367e1a4ddef7431d",
23
- "position": "right"
24
- }
25
- ]
26
- }
27
- }
28
- }