@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/README.md CHANGED
@@ -13,19 +13,22 @@ npm install @metalabel/dfos-protocol
13
13
  ```ts
14
14
  // Chain verification
15
15
  import { verifyContentChain, verifyIdentityChain } from '@metalabel/dfos-protocol/chain';
16
+ // Credentials (auth tokens + VC-JWT)
17
+ import { createAuthToken, verifyCredential } from '@metalabel/dfos-protocol/credentials';
16
18
  // Crypto primitives
17
19
  import { createJws, dagCborCanonicalEncode, verifyJws } from '@metalabel/dfos-protocol/crypto';
18
- // Reference registry server
19
- import { createRegistryServer } from '@metalabel/dfos-protocol/registry';
20
+ // Merkle trees
21
+ import { buildMerkleTree, verifyMerkleProof } from '@metalabel/dfos-protocol/merkle';
20
22
  ```
21
23
 
22
24
  ## Subpath Exports
23
25
 
24
- | Export | Description |
25
- | ----------------------------------- | ------------------------------------------------------------------- |
26
- | `@metalabel/dfos-protocol/chain` | Identity and content chain signing, verification, multikey encoding |
27
- | `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
28
- | `@metalabel/dfos-protocol/registry` | Hono-based registry server, in-memory store, Zod schemas |
26
+ | Export | Description |
27
+ | -------------------------------------- | ----------------------------------------------------------------------- |
28
+ | `@metalabel/dfos-protocol/chain` | Identity and content chain signing, verification, beacons, countersigns |
29
+ | `@metalabel/dfos-protocol/credentials` | Auth tokens (DID-signed JWT) and VC-JWT credentials for authorization |
30
+ | `@metalabel/dfos-protocol/crypto` | Ed25519, JWS, JWT, dag-cbor, base64url, ID generation |
31
+ | `@metalabel/dfos-protocol/merkle` | SHA-256 binary merkle tree, inclusion proofs |
29
32
 
30
33
  ## Specifications
31
34
 
@@ -33,19 +36,22 @@ import { createRegistryServer } from '@metalabel/dfos-protocol/registry';
33
36
  | -------------------------------------- | -------------------------------------------------------------- |
34
37
  | [PROTOCOL.md](./PROTOCOL.md) | Core protocol — chains, signatures, verification, test vectors |
35
38
  | [DID-METHOD.md](./DID-METHOD.md) | W3C DID method specification for `did:dfos` |
36
- | [CONTENT-MODEL.md](./CONTENT-MODEL.md) | Standard content schemas (post, profile, media) |
37
- | [REGISTRY-API.md](./REGISTRY-API.md) | HTTP API for chain storage and resolution |
38
- | [openapi.yaml](./openapi.yaml) | OpenAPI 3.1 machine-readable registry spec |
39
+ | [CONTENT-MODEL.md](./CONTENT-MODEL.md) | Standard content schemas (post, profile, manifest) |
39
40
 
40
41
  ## Examples
41
42
 
42
- The `examples/` directory contains deterministic reference chain fixtures that can be independently verified by any Ed25519 + dag-cbor implementation:
43
+ The `examples/` directory contains deterministic reference fixtures that can be independently verified by any Ed25519 + dag-cbor implementation:
43
44
 
44
45
  - `identity-genesis.json` — single create operation
45
46
  - `identity-rotation.json` — genesis + key rotation
46
47
  - `identity-delete.json` — genesis + delete (terminal)
47
48
  - `content-lifecycle.json` — create + update (with both documents)
48
49
  - `content-delete.json` — create + delete
50
+ - `content-delegated.json` — creator genesis + delegated update with DFOSContentWrite VC-JWT
51
+ - `credential-write.json` — DFOSContentWrite VC-JWT (broad + content-narrowed)
52
+ - `credential-read.json` — DFOSContentRead VC-JWT
53
+ - `merkle-tree.json` — 5 content IDs → sorted tree → root, with inclusion proof
54
+ - `beacon.json` — signed merkle root announcement with witness countersignature
49
55
 
50
56
  ## License
51
57
 
@@ -77,24 +77,42 @@ type VerifiedIdentity = z.infer<typeof VerifiedIdentity>;
77
77
  declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
78
78
  version: z.ZodLiteral<1>;
79
79
  type: z.ZodLiteral<"create">;
80
+ did: z.ZodString;
80
81
  documentCID: z.ZodString;
82
+ baseDocumentCID: z.ZodNullable<z.ZodString>;
81
83
  createdAt: z.ZodISODateTime;
82
84
  note: z.ZodNullable<z.ZodString>;
83
85
  }, z.core.$strict>, z.ZodObject<{
84
86
  version: z.ZodLiteral<1>;
85
87
  type: z.ZodLiteral<"update">;
88
+ did: z.ZodString;
86
89
  previousOperationCID: z.ZodString;
87
90
  documentCID: z.ZodNullable<z.ZodString>;
91
+ baseDocumentCID: z.ZodNullable<z.ZodString>;
88
92
  createdAt: z.ZodISODateTime;
89
93
  note: z.ZodNullable<z.ZodString>;
94
+ /** VC-JWT authorizing this operation when signer is not the chain creator */
95
+ authorization: z.ZodOptional<z.ZodString>;
90
96
  }, z.core.$strict>, z.ZodObject<{
91
97
  version: z.ZodLiteral<1>;
92
98
  type: z.ZodLiteral<"delete">;
99
+ did: z.ZodString;
93
100
  previousOperationCID: z.ZodString;
94
101
  createdAt: z.ZodISODateTime;
95
102
  note: z.ZodNullable<z.ZodString>;
103
+ /** VC-JWT authorizing this operation when signer is not the chain creator */
104
+ authorization: z.ZodOptional<z.ZodString>;
96
105
  }, z.core.$strict>], "type">;
97
106
  type ContentOperation = z.infer<typeof ContentOperation>;
107
+ /** Beacon: floating signed merkle root announcement */
108
+ declare const BeaconPayload: z.ZodObject<{
109
+ version: z.ZodLiteral<1>;
110
+ type: z.ZodLiteral<"beacon">;
111
+ did: z.ZodString;
112
+ merkleRoot: z.ZodString;
113
+ createdAt: z.ZodISODateTime;
114
+ }, z.core.$strict>;
115
+ type BeaconPayload = z.infer<typeof BeaconPayload>;
98
116
 
99
117
  /** Ed25519 public key multicodec value */
100
118
  declare const ED25519_PUB_MULTICODEC = 237;
@@ -168,6 +186,8 @@ interface VerifiedContentChain {
168
186
  currentDocumentCID: string | null;
169
187
  /** Number of operations in the chain */
170
188
  length: number;
189
+ /** The DID that created the chain (signer of genesis operation) */
190
+ creatorDID: string;
171
191
  }
172
192
  /**
173
193
  * Sign a content chain operation as a JWS and derive the operation CID
@@ -182,15 +202,108 @@ declare const signContentOperation: (input: {
182
202
  operationCID: string;
183
203
  }>;
184
204
  /**
185
- * Verify a content chain's structural integrity and signatures
205
+ * Verify a content chain's structural integrity, signatures, and authorization
186
206
  *
187
207
  * The caller provides a key resolver to look up public keys from kid values.
188
208
  * This keeps the content chain protocol independent of identity resolution.
209
+ *
210
+ * Authorization rules:
211
+ * - Genesis (create) operation: the signer is the chain creator, always authorized
212
+ * - Subsequent operations signed by the creator DID: authorized (no credential needed)
213
+ * - Subsequent operations signed by a different DID: must include an `authorization`
214
+ * field containing a valid DFOSContentWrite VC-JWT issued by the creator DID
189
215
  */
190
216
  declare const verifyContentChain: (input: {
191
217
  log: string[];
192
218
  /** Resolve a kid (DID URL) to the raw Ed25519 public key bytes */
193
219
  resolveKey: (kid: string) => Promise<Uint8Array>;
220
+ /**
221
+ * Enforce creator-sovereignty authorization. When true, non-creator signers
222
+ * must include a DFOSContentWrite VC-JWT in the operation's `authorization`
223
+ * field. When false (default), any signer with a valid signature is accepted.
224
+ *
225
+ * Web relays should set this to true. Applications migrating to VC-based
226
+ * authorization can enable this once all chains include authorization fields.
227
+ */
228
+ enforceAuthorization?: boolean;
194
229
  }) => Promise<VerifiedContentChain>;
195
230
 
196
- export { ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type VerifiedContentChain, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signContentOperation, signIdentityOperation, verifyContentChain, verifyIdentityChain };
231
+ /**
232
+ * Sign a beacon announcement as a JWS
233
+ */
234
+ declare const signBeacon: (input: {
235
+ payload: BeaconPayload;
236
+ signer: Signer;
237
+ kid: string;
238
+ }) => Promise<{
239
+ jwsToken: string;
240
+ beaconCID: string;
241
+ }>;
242
+ interface VerifiedBeacon {
243
+ payload: BeaconPayload;
244
+ beaconCID: string;
245
+ }
246
+ /**
247
+ * Verify a beacon JWS — signature, CID, payload schema, clock skew
248
+ */
249
+ declare const verifyBeacon: (input: {
250
+ jwsToken: string;
251
+ resolveKey: (kid: string) => Promise<Uint8Array>;
252
+ /** Current time for clock skew check (defaults to Date.now()) */
253
+ now?: number;
254
+ }) => Promise<VerifiedBeacon>;
255
+
256
+ /**
257
+ * Sign an existing content operation as a countersignature (witness JWS)
258
+ *
259
+ * The witness signs the same payload as the author, producing a different
260
+ * JWS token with their own kid. The CID is identical because the payload
261
+ * is identical.
262
+ */
263
+ declare const signCountersignature: (input: {
264
+ /** The original operation payload (must include did of the author) */
265
+ operationPayload: ContentOperation;
266
+ /** Witness signer */
267
+ signer: Signer;
268
+ /** Witness kid — DID URL of the witness (must differ from payload.did) */
269
+ kid: string;
270
+ }) => Promise<{
271
+ jwsToken: string;
272
+ operationCID: string;
273
+ }>;
274
+ interface VerifiedCountersignature {
275
+ operationCID: string;
276
+ /** The DID that authored the operation (payload.did) */
277
+ authorDID: string;
278
+ /** The DID that witnessed the operation (kid DID) */
279
+ witnessDID: string;
280
+ }
281
+ /**
282
+ * Verify a countersignature JWS against an expected operation CID
283
+ *
284
+ * Checks: valid signature, CID matches, kid DID differs from payload did
285
+ */
286
+ declare const verifyCountersignature: (input: {
287
+ jwsToken: string;
288
+ expectedCID: string;
289
+ resolveKey: (kid: string) => Promise<Uint8Array>;
290
+ }) => Promise<VerifiedCountersignature>;
291
+ interface VerifiedBeaconCountersignature {
292
+ beaconCID: string;
293
+ /** The DID that controls the beacon (payload.did) */
294
+ controllerDID: string;
295
+ /** The DID that witnessed the beacon (kid DID) */
296
+ witnessDID: string;
297
+ }
298
+ /**
299
+ * Verify a beacon countersignature JWS against an expected beacon CID
300
+ *
301
+ * Checks: valid signature, CID matches, kid DID differs from payload did
302
+ */
303
+ declare const verifyBeaconCountersignature: (input: {
304
+ jwsToken: string;
305
+ expectedCID: string;
306
+ resolveKey: (kid: string) => Promise<Uint8Array>;
307
+ }) => Promise<VerifiedBeaconCountersignature>;
308
+
309
+ export { BeaconPayload, ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type VerifiedBeacon, type VerifiedBeaconCountersignature, type VerifiedContentChain, type VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyBeacon, verifyBeaconCountersignature, verifyContentChain, verifyCountersignature, verifyIdentityChain };
@@ -1,4 +1,5 @@
1
1
  import {
2
+ BeaconPayload,
2
3
  ContentOperation,
3
4
  ED25519_PRIV_MULTICODEC,
4
5
  ED25519_PUB_MULTICODEC,
@@ -9,13 +10,20 @@ import {
9
10
  deriveChainIdentifier,
10
11
  deriveContentId,
11
12
  encodeEd25519Multikey,
13
+ signBeacon,
12
14
  signContentOperation,
15
+ signCountersignature,
13
16
  signIdentityOperation,
17
+ verifyBeacon,
18
+ verifyBeaconCountersignature,
14
19
  verifyContentChain,
20
+ verifyCountersignature,
15
21
  verifyIdentityChain
16
- } from "../chunk-VEBMLR37.js";
22
+ } from "../chunk-GEVJ3SEV.js";
23
+ import "../chunk-CZSEEZLL.js";
17
24
  import "../chunk-ZXXP5W5N.js";
18
25
  export {
26
+ BeaconPayload,
19
27
  ContentOperation,
20
28
  ED25519_PRIV_MULTICODEC,
21
29
  ED25519_PUB_MULTICODEC,
@@ -26,8 +34,13 @@ export {
26
34
  deriveChainIdentifier,
27
35
  deriveContentId,
28
36
  encodeEd25519Multikey,
37
+ signBeacon,
29
38
  signContentOperation,
39
+ signCountersignature,
30
40
  signIdentityOperation,
41
+ verifyBeacon,
42
+ verifyBeaconCountersignature,
31
43
  verifyContentChain,
44
+ verifyCountersignature,
32
45
  verifyIdentityChain
33
46
  };
@@ -0,0 +1,258 @@
1
+ import {
2
+ base64urlDecode,
3
+ base64urlEncode,
4
+ createJwt,
5
+ isValidEd25519Signature,
6
+ verifyJwt
7
+ } from "./chunk-ZXXP5W5N.js";
8
+
9
+ // src/credentials/schemas.ts
10
+ import { z } from "zod";
11
+ var MAX_DID = 256;
12
+ var MAX_AUD = 512;
13
+ var MAX_CONTENT_ID = 256;
14
+ var VC_TYPE_CONTENT_WRITE = "DFOSContentWrite";
15
+ var VC_TYPE_CONTENT_READ = "DFOSContentRead";
16
+ var DFOSCredentialType = z.enum([VC_TYPE_CONTENT_WRITE, VC_TYPE_CONTENT_READ]);
17
+ var AuthTokenClaims = z.strictObject({
18
+ /** Issuer — the DID proving identity */
19
+ iss: z.string().max(MAX_DID),
20
+ /** Subject — same as iss for auth tokens */
21
+ sub: z.string().max(MAX_DID),
22
+ /** Audience — target relay hostname (prevents cross-relay replay) */
23
+ aud: z.string().max(MAX_AUD),
24
+ /** Expiration — unix seconds, short-lived (minutes) */
25
+ exp: z.number().int().positive(),
26
+ /** Issued at — unix seconds */
27
+ iat: z.number().int().positive()
28
+ });
29
+ var ContentWriteSubject = z.strictObject({
30
+ /** Optional content chain narrowing — if absent, grants broad write access */
31
+ contentId: z.string().max(MAX_CONTENT_ID).optional()
32
+ });
33
+ var ContentReadSubject = z.strictObject({
34
+ /** Optional content chain narrowing — if absent, grants broad read access */
35
+ contentId: z.string().max(MAX_CONTENT_ID).optional()
36
+ });
37
+ var VCClaim = z.strictObject({
38
+ "@context": z.tuple([z.literal("https://www.w3.org/ns/credentials/v2")]),
39
+ type: z.tuple([z.literal("VerifiableCredential"), DFOSCredentialType]).transform((t) => t),
40
+ credentialSubject: z.union([ContentWriteSubject, ContentReadSubject])
41
+ });
42
+ var CredentialClaims = z.strictObject({
43
+ /** Issuer — the DID granting the credential */
44
+ iss: z.string().max(MAX_DID),
45
+ /** Subject — the DID receiving the credential */
46
+ sub: z.string().max(MAX_DID),
47
+ /** Expiration — unix seconds */
48
+ exp: z.number().int().positive(),
49
+ /** Issued at — unix seconds */
50
+ iat: z.number().int().positive(),
51
+ /** Verifiable credential claim */
52
+ vc: VCClaim
53
+ });
54
+
55
+ // src/credentials/auth-token.ts
56
+ var createAuthToken = async (options) => {
57
+ if (!options.kid.includes("#")) {
58
+ throw new Error("kid must be a DID URL (did:dfos:xxx#key_yyy)");
59
+ }
60
+ const kidDid = options.kid.substring(0, options.kid.indexOf("#"));
61
+ if (kidDid !== options.iss) {
62
+ throw new Error("kid DID does not match iss");
63
+ }
64
+ const now = options.iat ?? Math.floor(Date.now() / 1e3);
65
+ const header = { alg: "EdDSA", typ: "JWT", kid: options.kid };
66
+ const payload = {
67
+ iss: options.iss,
68
+ sub: options.iss,
69
+ aud: options.aud,
70
+ exp: options.exp,
71
+ iat: now
72
+ };
73
+ return createJwt({ header, payload, sign: options.sign });
74
+ };
75
+ var verifyAuthToken = (options) => {
76
+ const { header, payload } = verifyJwt({
77
+ token: options.token,
78
+ publicKey: options.publicKey,
79
+ audience: options.audience,
80
+ ...options.currentTime !== void 0 ? { currentTime: options.currentTime } : {}
81
+ });
82
+ const result = AuthTokenClaims.safeParse(payload);
83
+ if (!result.success) {
84
+ const messages = result.error.issues.map((e) => e.message).join(", ");
85
+ throw new AuthTokenVerificationError(`invalid auth token claims: ${messages}`);
86
+ }
87
+ const currentTime = options.currentTime ?? Math.floor(Date.now() / 1e3);
88
+ if (result.data.iat > currentTime) {
89
+ throw new AuthTokenVerificationError("auth token not yet valid (iat is in the future)");
90
+ }
91
+ const kid = header.kid;
92
+ if (!kid || !kid.includes("#")) {
93
+ throw new AuthTokenVerificationError("auth token kid must be a DID URL");
94
+ }
95
+ const kidDid = kid.substring(0, kid.indexOf("#"));
96
+ if (kidDid !== result.data.iss) {
97
+ throw new AuthTokenVerificationError("auth token kid DID does not match iss");
98
+ }
99
+ return {
100
+ iss: result.data.iss,
101
+ aud: result.data.aud,
102
+ exp: result.data.exp,
103
+ kid
104
+ };
105
+ };
106
+ var AuthTokenVerificationError = class extends Error {
107
+ constructor(message) {
108
+ super(message);
109
+ this.name = "AuthTokenVerificationError";
110
+ }
111
+ };
112
+
113
+ // src/credentials/credential.ts
114
+ var createCredential = async (options) => {
115
+ if (!options.kid.includes("#")) {
116
+ throw new Error("kid must be a DID URL (did:dfos:xxx#key_yyy)");
117
+ }
118
+ const kidDid = options.kid.substring(0, options.kid.indexOf("#"));
119
+ if (kidDid !== options.iss) {
120
+ throw new Error("kid DID does not match iss");
121
+ }
122
+ const now = options.iat ?? Math.floor(Date.now() / 1e3);
123
+ const header = { alg: "EdDSA", typ: "vc+jwt", kid: options.kid };
124
+ const credentialSubject = {};
125
+ if (options.contentId) {
126
+ credentialSubject.contentId = options.contentId;
127
+ }
128
+ const payload = {
129
+ iss: options.iss,
130
+ sub: options.sub,
131
+ exp: options.exp,
132
+ iat: now,
133
+ vc: {
134
+ "@context": ["https://www.w3.org/ns/credentials/v2"],
135
+ type: ["VerifiableCredential", options.type],
136
+ credentialSubject
137
+ }
138
+ };
139
+ const headerB64 = base64urlEncode(JSON.stringify(header));
140
+ const payloadB64 = base64urlEncode(JSON.stringify(payload));
141
+ const signingInput = `${headerB64}.${payloadB64}`;
142
+ const signingInputBytes = new TextEncoder().encode(signingInput);
143
+ const signatureBytes = await options.sign(signingInputBytes);
144
+ const signatureB64 = base64urlEncode(signatureBytes);
145
+ return `${signingInput}.${signatureB64}`;
146
+ };
147
+ var verifyCredential = (options) => {
148
+ const parts = options.token.split(".");
149
+ if (parts.length !== 3) {
150
+ throw new CredentialVerificationError("invalid token format");
151
+ }
152
+ const [headerB64, payloadB64, signatureB64] = parts;
153
+ let header;
154
+ let payload;
155
+ try {
156
+ header = JSON.parse(new TextDecoder().decode(base64urlDecode(headerB64)));
157
+ payload = JSON.parse(new TextDecoder().decode(base64urlDecode(payloadB64)));
158
+ } catch {
159
+ throw new CredentialVerificationError("failed to decode token");
160
+ }
161
+ if (header.alg !== "EdDSA") {
162
+ throw new CredentialVerificationError(`unsupported algorithm: ${header.alg}`);
163
+ }
164
+ if (header.typ !== "vc+jwt") {
165
+ throw new CredentialVerificationError(`invalid typ: ${header.typ}`);
166
+ }
167
+ const signingInput = `${headerB64}.${payloadB64}`;
168
+ const signingInputBytes = new TextEncoder().encode(signingInput);
169
+ let signatureBytes;
170
+ try {
171
+ signatureBytes = base64urlDecode(signatureB64);
172
+ } catch {
173
+ throw new CredentialVerificationError("failed to decode signature");
174
+ }
175
+ const isValid = isValidEd25519Signature(signingInputBytes, signatureBytes, options.publicKey);
176
+ if (!isValid) {
177
+ throw new CredentialVerificationError("invalid signature");
178
+ }
179
+ const result = CredentialClaims.safeParse(payload);
180
+ if (!result.success) {
181
+ const messages = result.error.issues.map((e) => e.message).join(", ");
182
+ throw new CredentialVerificationError(`invalid credential claims: ${messages}`);
183
+ }
184
+ const claims = result.data;
185
+ const kid = header.kid;
186
+ if (!kid || !kid.includes("#")) {
187
+ throw new CredentialVerificationError("credential kid must be a DID URL");
188
+ }
189
+ const kidDid = kid.substring(0, kid.indexOf("#"));
190
+ if (kidDid !== claims.iss) {
191
+ throw new CredentialVerificationError("credential kid DID does not match iss");
192
+ }
193
+ const currentTime = options.currentTime ?? Math.floor(Date.now() / 1e3);
194
+ if (claims.iat > currentTime) {
195
+ throw new CredentialVerificationError("credential not yet valid (iat is in the future)");
196
+ }
197
+ if (claims.exp <= currentTime) {
198
+ throw new CredentialVerificationError("credential expired");
199
+ }
200
+ if (options.subject !== void 0 && claims.sub !== options.subject) {
201
+ throw new CredentialVerificationError(
202
+ `subject mismatch: expected ${options.subject}, got ${claims.sub}`
203
+ );
204
+ }
205
+ const vcType = claims.vc.type[1];
206
+ if (options.expectedType !== void 0 && vcType !== options.expectedType) {
207
+ throw new CredentialVerificationError(
208
+ `type mismatch: expected ${options.expectedType}, got ${vcType}`
209
+ );
210
+ }
211
+ const contentId = claims.vc.credentialSubject.contentId;
212
+ return {
213
+ iss: claims.iss,
214
+ sub: claims.sub,
215
+ exp: claims.exp,
216
+ type: vcType,
217
+ kid,
218
+ ...contentId !== void 0 ? { contentId } : {}
219
+ };
220
+ };
221
+ var decodeCredentialUnsafe = (token) => {
222
+ const parts = token.split(".");
223
+ if (parts.length !== 3) return null;
224
+ try {
225
+ const [headerB64, payloadB64] = parts;
226
+ const header = JSON.parse(new TextDecoder().decode(base64urlDecode(headerB64)));
227
+ const payload = JSON.parse(new TextDecoder().decode(base64urlDecode(payloadB64)));
228
+ const result = CredentialClaims.safeParse(payload);
229
+ if (!result.success) return null;
230
+ return { header, claims: result.data };
231
+ } catch {
232
+ return null;
233
+ }
234
+ };
235
+ var CredentialVerificationError = class extends Error {
236
+ constructor(message) {
237
+ super(message);
238
+ this.name = "CredentialVerificationError";
239
+ }
240
+ };
241
+
242
+ export {
243
+ VC_TYPE_CONTENT_WRITE,
244
+ VC_TYPE_CONTENT_READ,
245
+ DFOSCredentialType,
246
+ AuthTokenClaims,
247
+ ContentWriteSubject,
248
+ ContentReadSubject,
249
+ VCClaim,
250
+ CredentialClaims,
251
+ createAuthToken,
252
+ verifyAuthToken,
253
+ AuthTokenVerificationError,
254
+ createCredential,
255
+ verifyCredential,
256
+ decodeCredentialUnsafe,
257
+ CredentialVerificationError
258
+ };
@@ -0,0 +1,99 @@
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
+ };