@metalabel/dfos-protocol 0.4.0 → 0.6.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
@@ -32,11 +32,11 @@ import { buildMerkleTree, verifyMerkleProof } from '@metalabel/dfos-protocol/mer
32
32
 
33
33
  ## Specifications
34
34
 
35
- | Document | Description |
36
- | -------------------------------------- | -------------------------------------------------------------- |
37
- | [PROTOCOL.md](./PROTOCOL.md) | Core protocol — chains, signatures, verification, test vectors |
38
- | [DID-METHOD.md](./DID-METHOD.md) | W3C DID method specification for `did:dfos` |
39
- | [CONTENT-MODEL.md](./CONTENT-MODEL.md) | Standard content schemas (post, profile, manifest) |
35
+ | Document | Description |
36
+ | ------------------------------------------------ | -------------------------------------------------------------- |
37
+ | [PROTOCOL.md](../../specs/PROTOCOL.md) | Core protocol — chains, signatures, verification, test vectors |
38
+ | [DID-METHOD.md](../../specs/DID-METHOD.md) | W3C DID method specification for `did:dfos` |
39
+ | [CONTENT-MODEL.md](../../specs/CONTENT-MODEL.md) | Standard content schemas (post, profile, manifest) |
40
40
 
41
41
  ## Examples
42
42
 
@@ -53,6 +53,19 @@ The `examples/` directory contains deterministic reference fixtures that can be
53
53
  - `merkle-tree.json` — 5 content IDs → sorted tree → root, with inclusion proof
54
54
  - `beacon.json` — signed merkle root announcement with witness countersignature
55
55
 
56
+ ## Cross-Language Verification
57
+
58
+ The `verify/` directory contains independent verification suites that re-derive CIDs and verify signatures from the reference fixtures — proving protocol correctness across implementations:
59
+
60
+ | Language | Path | Status |
61
+ | -------- | ---------------- | ------- |
62
+ | Go | `verify/go/` | Passing |
63
+ | Python | `verify/python/` | Passing |
64
+ | Rust | `verify/rust/` | Passing |
65
+ | Swift | `verify/swift/` | Passing |
66
+
67
+ Each suite uses only its language's native Ed25519, dag-cbor, and multihash implementations — no shared code with the TypeScript reference.
68
+
56
69
  ## License
57
70
 
58
71
  MIT
@@ -113,6 +113,28 @@ declare const BeaconPayload: z.ZodObject<{
113
113
  createdAt: z.ZodISODateTime;
114
114
  }, z.core.$strict>;
115
115
  type BeaconPayload = z.infer<typeof BeaconPayload>;
116
+ /** Max CBOR-encoded payload size for artifacts (bytes) — protocol constant */
117
+ declare const MAX_ARTIFACT_PAYLOAD_SIZE = 16384;
118
+ /** Artifact: standalone signed inline document, immutable, CID-addressable */
119
+ declare const ArtifactPayload: z.ZodObject<{
120
+ version: z.ZodLiteral<1>;
121
+ type: z.ZodLiteral<"artifact">;
122
+ did: z.ZodString;
123
+ content: z.ZodObject<{
124
+ $schema: z.ZodString;
125
+ }, z.core.$catchall<z.ZodUnknown>>;
126
+ createdAt: z.ZodISODateTime;
127
+ }, z.core.$strict>;
128
+ type ArtifactPayload = z.infer<typeof ArtifactPayload>;
129
+ /** Countersign: standalone witness attestation referencing a target operation by CID */
130
+ declare const CountersignPayload: z.ZodObject<{
131
+ version: z.ZodLiteral<1>;
132
+ type: z.ZodLiteral<"countersign">;
133
+ did: z.ZodString;
134
+ targetCID: z.ZodString;
135
+ createdAt: z.ZodISODateTime;
136
+ }, z.core.$strict>;
137
+ type CountersignPayload = z.infer<typeof CountersignPayload>;
116
138
 
117
139
  /** Ed25519 public key multicodec value */
118
140
  declare const ED25519_PUB_MULTICODEC = 237;
@@ -172,6 +194,33 @@ declare const verifyIdentityChain: (input: {
172
194
  didPrefix: string;
173
195
  log: string[];
174
196
  }) => Promise<VerifiedIdentity>;
197
+ /**
198
+ * Verify a single new operation against already-verified identity state
199
+ *
200
+ * The caller guarantees that `currentState` was produced by a correct prior
201
+ * verification (full chain replay or a chain of trusted extensions from a
202
+ * verified genesis). This function performs one signature verification and one
203
+ * state transition — constant time regardless of chain length.
204
+ *
205
+ * Note: key-ID consistency across the full chain history is NOT checked here.
206
+ * That invariant is established during genesis verification and maintained by
207
+ * the protocol's key consistency rules. Periodic full re-verification can
208
+ * audit this property.
209
+ */
210
+ declare const verifyIdentityExtensionFromTrustedState: (input: {
211
+ /** Previously verified identity state */
212
+ currentState: VerifiedIdentity;
213
+ /** CID of the most recent operation in the chain */
214
+ headCID: string;
215
+ /** createdAt timestamp of the most recent operation */
216
+ lastCreatedAt: string;
217
+ /** The new JWS operation to verify */
218
+ newOp: string;
219
+ }) => Promise<{
220
+ state: VerifiedIdentity;
221
+ operationCID: string;
222
+ createdAt: string;
223
+ }>;
175
224
 
176
225
  interface VerifiedContentChain {
177
226
  /** Content identifier — bare 22-char hash derived from genesis CID */
@@ -227,6 +276,29 @@ declare const verifyContentChain: (input: {
227
276
  */
228
277
  enforceAuthorization?: boolean;
229
278
  }) => Promise<VerifiedContentChain>;
279
+ /**
280
+ * Verify a single new content operation against already-verified chain state
281
+ *
282
+ * Same trust model as verifyIdentityExtensionFromTrustedState — the caller
283
+ * guarantees `currentState` was correctly verified. One signature verification,
284
+ * one key resolution, one state transition.
285
+ */
286
+ declare const verifyContentExtensionFromTrustedState: (input: {
287
+ /** Previously verified content chain state */
288
+ currentState: VerifiedContentChain;
289
+ /** createdAt timestamp of the most recent operation */
290
+ lastCreatedAt: string;
291
+ /** The new JWS operation to verify */
292
+ newOp: string;
293
+ /** Resolve a kid (DID URL) to the raw Ed25519 public key bytes */
294
+ resolveKey: (kid: string) => Promise<Uint8Array>;
295
+ /** Enforce creator-sovereignty authorization (see verifyContentChain) */
296
+ enforceAuthorization?: boolean;
297
+ }) => Promise<{
298
+ state: VerifiedContentChain;
299
+ operationCID: string;
300
+ createdAt: string;
301
+ }>;
230
302
 
231
303
  /**
232
304
  * Sign a beacon announcement as a JWS
@@ -253,57 +325,60 @@ declare const verifyBeacon: (input: {
253
325
  now?: number;
254
326
  }) => Promise<VerifiedBeacon>;
255
327
 
328
+ interface VerifiedCountersignature {
329
+ /** CID of this countersign operation (distinct from the target) */
330
+ countersignCID: string;
331
+ /** The witness DID (payload.did — the DID that signed this attestation) */
332
+ witnessDID: string;
333
+ /** The CID being attested to */
334
+ targetCID: string;
335
+ }
256
336
  /**
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.
337
+ * Sign a countersignature attesting to a target operation by CID
262
338
  */
263
339
  declare const signCountersignature: (input: {
264
- /** The original operation payload (must include did of the author) */
265
- operationPayload: ContentOperation;
266
- /** Witness signer */
340
+ payload: CountersignPayload;
267
341
  signer: Signer;
268
- /** Witness kid — DID URL of the witness (must differ from payload.did) */
269
342
  kid: string;
270
343
  }) => Promise<{
271
344
  jwsToken: string;
272
- operationCID: string;
345
+ countersignCID: string;
273
346
  }>;
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
347
  /**
282
- * Verify a countersignature JWS against an expected operation CID
348
+ * Verify a countersignature JWS stateless verification
283
349
  *
284
- * Checks: valid signature, CID matches, kid DID differs from payload did
350
+ * Checks: valid signature, CID integrity, payload schema. Does NOT check
351
+ * whether the target exists or whether the witness differs from the target
352
+ * author — those are relay-level semantic checks.
285
353
  */
286
354
  declare const verifyCountersignature: (input: {
287
355
  jwsToken: string;
288
- expectedCID: string;
289
356
  resolveKey: (kid: string) => Promise<Uint8Array>;
290
357
  }) => 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;
358
+
359
+ interface VerifiedArtifact {
360
+ payload: ArtifactPayload;
361
+ artifactCID: string;
297
362
  }
298
363
  /**
299
- * Verify a beacon countersignature JWS against an expected beacon CID
364
+ * Sign an artifact as a JWS
300
365
  *
301
- * Checks: valid signature, CID matches, kid DID differs from payload did
366
+ * Enforces the protocol size limit on the CBOR-encoded payload.
367
+ */
368
+ declare const signArtifact: (input: {
369
+ payload: ArtifactPayload;
370
+ signer: Signer;
371
+ kid: string;
372
+ }) => Promise<{
373
+ jwsToken: string;
374
+ artifactCID: string;
375
+ }>;
376
+ /**
377
+ * Verify an artifact JWS — signature, CID, payload schema, size limit
302
378
  */
303
- declare const verifyBeaconCountersignature: (input: {
379
+ declare const verifyArtifact: (input: {
304
380
  jwsToken: string;
305
- expectedCID: string;
306
381
  resolveKey: (kid: string) => Promise<Uint8Array>;
307
- }) => Promise<VerifiedBeaconCountersignature>;
382
+ }) => Promise<VerifiedArtifact>;
308
383
 
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 };
384
+ export { ArtifactPayload, BeaconPayload, ContentOperation, CountersignPayload, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MAX_ARTIFACT_PAYLOAD_SIZE, MultikeyPublicKey, type Signer, type VerifiedArtifact, type VerifiedBeacon, type VerifiedContentChain, type VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signArtifact, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyArtifact, verifyBeacon, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState };
@@ -1,46 +1,58 @@
1
1
  import {
2
+ ArtifactPayload,
2
3
  BeaconPayload,
3
4
  ContentOperation,
5
+ CountersignPayload,
4
6
  ED25519_PRIV_MULTICODEC,
5
7
  ED25519_PUB_MULTICODEC,
6
8
  IdentityOperation,
9
+ MAX_ARTIFACT_PAYLOAD_SIZE,
7
10
  MultikeyPublicKey,
8
11
  VerifiedIdentity,
9
12
  decodeMultikey,
10
13
  deriveChainIdentifier,
11
14
  deriveContentId,
12
15
  encodeEd25519Multikey,
16
+ signArtifact,
13
17
  signBeacon,
14
18
  signContentOperation,
15
19
  signCountersignature,
16
20
  signIdentityOperation,
21
+ verifyArtifact,
17
22
  verifyBeacon,
18
- verifyBeaconCountersignature,
19
23
  verifyContentChain,
24
+ verifyContentExtensionFromTrustedState,
20
25
  verifyCountersignature,
21
- verifyIdentityChain
22
- } from "../chunk-GEVJ3SEV.js";
26
+ verifyIdentityChain,
27
+ verifyIdentityExtensionFromTrustedState
28
+ } from "../chunk-QKHP7UVL.js";
23
29
  import "../chunk-CZSEEZLL.js";
24
30
  import "../chunk-ZXXP5W5N.js";
25
31
  export {
32
+ ArtifactPayload,
26
33
  BeaconPayload,
27
34
  ContentOperation,
35
+ CountersignPayload,
28
36
  ED25519_PRIV_MULTICODEC,
29
37
  ED25519_PUB_MULTICODEC,
30
38
  IdentityOperation,
39
+ MAX_ARTIFACT_PAYLOAD_SIZE,
31
40
  MultikeyPublicKey,
32
41
  VerifiedIdentity,
33
42
  decodeMultikey,
34
43
  deriveChainIdentifier,
35
44
  deriveContentId,
36
45
  encodeEd25519Multikey,
46
+ signArtifact,
37
47
  signBeacon,
38
48
  signContentOperation,
39
49
  signCountersignature,
40
50
  signIdentityOperation,
51
+ verifyArtifact,
41
52
  verifyBeacon,
42
- verifyBeaconCountersignature,
43
53
  verifyContentChain,
54
+ verifyContentExtensionFromTrustedState,
44
55
  verifyCountersignature,
45
- verifyIdentityChain
56
+ verifyIdentityChain,
57
+ verifyIdentityExtensionFromTrustedState
46
58
  };
@@ -104,6 +104,23 @@ var BeaconPayload = z.strictObject({
104
104
  merkleRoot: z.string().regex(/^[0-9a-f]{64}$/),
105
105
  createdAt: Iso8601
106
106
  });
107
+ var MAX_SCHEMA = 256;
108
+ var MAX_ARTIFACT_PAYLOAD_SIZE = 16384;
109
+ var ArtifactContent = z.object({ $schema: z.string().max(MAX_SCHEMA) }).catchall(z.unknown());
110
+ var ArtifactPayload = z.strictObject({
111
+ version: z.literal(1),
112
+ type: z.literal("artifact"),
113
+ did: z.string().max(MAX_DID),
114
+ content: ArtifactContent,
115
+ createdAt: Iso8601
116
+ });
117
+ var CountersignPayload = z.strictObject({
118
+ version: z.literal(1),
119
+ type: z.literal("countersign"),
120
+ did: z.string().max(MAX_DID),
121
+ targetCID: CIDString,
122
+ createdAt: Iso8601
123
+ });
107
124
 
108
125
  // src/chain/multikey.ts
109
126
  import { base58btc } from "multiformats/bases/base58";
@@ -299,6 +316,78 @@ var verifyIdentityChain = async (input) => {
299
316
  controllerKeys: state.controllerKeys
300
317
  };
301
318
  };
319
+ var verifyIdentityExtensionFromTrustedState = async (input) => {
320
+ const { currentState, headCID, lastCreatedAt, newOp } = input;
321
+ if (currentState.isDeleted) {
322
+ throw new Error("cannot extend a deleted identity");
323
+ }
324
+ const decoded = decodeJwsUnsafe(newOp);
325
+ if (!decoded) throw new Error("failed to decode JWS");
326
+ const result = IdentityOperation.safeParse(decoded.payload);
327
+ if (!result.success) {
328
+ const messages = result.error.issues.map((e) => e.message).join(", ");
329
+ throw new Error(messages);
330
+ }
331
+ const op = result.data;
332
+ if (decoded.header.typ !== "did:dfos:identity-op") {
333
+ throw new Error(`invalid typ: ${decoded.header.typ}`);
334
+ }
335
+ if (op.type === "create") {
336
+ throw new Error("extension cannot be a create operation");
337
+ }
338
+ if (op.previousOperationCID !== headCID) {
339
+ throw new Error("previousCID is incorrect");
340
+ }
341
+ if (op.createdAt <= lastCreatedAt) {
342
+ throw new Error("createdAt must be after last op");
343
+ }
344
+ const encoded = await dagCborCanonicalEncode(op);
345
+ const operationCID = encoded.cid.toString();
346
+ if (!decoded.header.cid) throw new Error("missing cid in protected header");
347
+ if (decoded.header.cid !== operationCID) throw new Error("cid mismatch in protected header");
348
+ const kid = decoded.header.kid;
349
+ if (!kid.includes("#")) {
350
+ throw new Error("non-genesis op kid must be DID URL, got bare key ID");
351
+ }
352
+ const hashIdx = kid.indexOf("#");
353
+ const signingKeyId = kid.substring(hashIdx + 1);
354
+ const kidDid = kid.substring(0, hashIdx);
355
+ if (kidDid !== currentState.did) {
356
+ throw new Error("kid DID does not match identity DID");
357
+ }
358
+ const signingKey = currentState.controllerKeys.find((k) => k.id === signingKeyId);
359
+ if (!signingKey) {
360
+ throw new Error(`kid references unknown key: ${signingKeyId}`);
361
+ }
362
+ const { keyBytes } = decodeMultikey(signingKey.publicKeyMultibase);
363
+ try {
364
+ verifyJws({ token: newOp, publicKey: keyBytes });
365
+ } catch {
366
+ throw new Error("invalid signature");
367
+ }
368
+ if (op.type === "update") {
369
+ [op.authKeys, op.assertKeys, op.controllerKeys].forEach((keys) => {
370
+ const set = new Set(keys.map((k) => k.id));
371
+ if (set.size !== keys.length) {
372
+ throw new Error("cannot repeat key ids in same usage");
373
+ }
374
+ });
375
+ }
376
+ const newState = op.type === "update" ? {
377
+ did: currentState.did,
378
+ isDeleted: false,
379
+ authKeys: op.authKeys,
380
+ assertKeys: op.assertKeys,
381
+ controllerKeys: op.controllerKeys
382
+ } : {
383
+ did: currentState.did,
384
+ isDeleted: true,
385
+ authKeys: currentState.authKeys,
386
+ assertKeys: currentState.assertKeys,
387
+ controllerKeys: currentState.controllerKeys
388
+ };
389
+ return { state: newState, operationCID, createdAt: op.createdAt };
390
+ };
302
391
 
303
392
  // src/chain/content-chain.ts
304
393
  var signContentOperation = async (input) => {
@@ -447,6 +536,98 @@ var verifyContentChain = async (input) => {
447
536
  creatorDID: state.creatorDID
448
537
  };
449
538
  };
539
+ var verifyContentExtensionFromTrustedState = async (input) => {
540
+ const { currentState, lastCreatedAt, newOp, resolveKey } = input;
541
+ if (currentState.isDeleted) {
542
+ throw new Error("cannot extend a deleted chain");
543
+ }
544
+ const decoded = decodeJwsUnsafe(newOp);
545
+ if (!decoded) throw new Error("failed to decode JWS");
546
+ const result = ContentOperation.safeParse(decoded.payload);
547
+ if (!result.success) {
548
+ const messages = result.error.issues.map((e) => e.message).join(", ");
549
+ throw new Error(messages);
550
+ }
551
+ const op = result.data;
552
+ if (decoded.header.typ !== "did:dfos:content-op") {
553
+ throw new Error(`invalid typ: ${decoded.header.typ}`);
554
+ }
555
+ if (op.type === "create") {
556
+ throw new Error("extension cannot be a create operation");
557
+ }
558
+ if (op.previousOperationCID !== currentState.headCID) {
559
+ throw new Error("previousOperationCID is incorrect");
560
+ }
561
+ if (op.createdAt <= lastCreatedAt) {
562
+ throw new Error("createdAt must be after last op");
563
+ }
564
+ const kid = decoded.header.kid;
565
+ const hashIdx = kid.indexOf("#");
566
+ if (hashIdx < 0) throw new Error("kid must be a DID URL");
567
+ const kidDid = kid.substring(0, hashIdx);
568
+ if (kidDid !== op.did) {
569
+ throw new Error("kid DID does not match operation did");
570
+ }
571
+ const publicKey = await resolveKey(kid);
572
+ try {
573
+ verifyJws({ token: newOp, publicKey });
574
+ } catch {
575
+ throw new Error("invalid signature");
576
+ }
577
+ if (op.did !== currentState.creatorDID && input.enforceAuthorization) {
578
+ const authorization = op.authorization;
579
+ if (!authorization) {
580
+ throw new Error(`signer ${op.did} is not the chain creator \u2014 authorization VC required`);
581
+ }
582
+ const vcDecoded = decodeCredentialUnsafe(authorization);
583
+ if (!vcDecoded) throw new Error("failed to decode authorization VC");
584
+ const vcKid = vcDecoded.header.kid;
585
+ if (!vcKid || !vcKid.includes("#")) {
586
+ throw new Error("authorization VC kid must be a DID URL");
587
+ }
588
+ let creatorPublicKey;
589
+ try {
590
+ creatorPublicKey = await resolveKey(vcKid);
591
+ } catch {
592
+ throw new Error("cannot resolve creator key for authorization verification");
593
+ }
594
+ const opCreatedAtUnix = Math.floor(new Date(op.createdAt).getTime() / 1e3);
595
+ try {
596
+ const credential = verifyCredential({
597
+ token: authorization,
598
+ publicKey: creatorPublicKey,
599
+ subject: op.did,
600
+ expectedType: VC_TYPE_CONTENT_WRITE,
601
+ currentTime: opCreatedAtUnix
602
+ });
603
+ if (credential.iss !== currentState.creatorDID) {
604
+ throw new Error("VC issuer is not the chain creator");
605
+ }
606
+ if (credential.contentId && credential.contentId !== currentState.contentId) {
607
+ throw new Error(
608
+ `VC contentId ${credential.contentId} does not match chain ${currentState.contentId}`
609
+ );
610
+ }
611
+ } catch (err) {
612
+ const message = err instanceof Error ? err.message : "unknown error";
613
+ throw new Error(`authorization verification failed: ${message}`);
614
+ }
615
+ }
616
+ const encoded = await dagCborCanonicalEncode(op);
617
+ const operationCID = encoded.cid.toString();
618
+ if (!decoded.header.cid) throw new Error("missing cid in protected header");
619
+ if (decoded.header.cid !== operationCID) throw new Error("cid mismatch in protected header");
620
+ const newState = {
621
+ contentId: currentState.contentId,
622
+ genesisCID: currentState.genesisCID,
623
+ headCID: operationCID,
624
+ isDeleted: op.type === "delete",
625
+ currentDocumentCID: op.type === "update" ? op.documentCID : null,
626
+ length: currentState.length + 1,
627
+ creatorDID: currentState.creatorDID
628
+ };
629
+ return { state: newState, operationCID, createdAt: op.createdAt };
630
+ };
450
631
 
451
632
  // src/chain/beacon.ts
452
633
  var signBeacon = async (input) => {
@@ -499,92 +680,102 @@ var verifyBeacon = async (input) => {
499
680
 
500
681
  // src/chain/countersign.ts
501
682
  var signCountersignature = async (input) => {
502
- const encoded = await dagCborCanonicalEncode(input.operationPayload);
503
- const operationCID = encoded.cid.toString();
683
+ const encoded = await dagCborCanonicalEncode(input.payload);
684
+ const countersignCID = encoded.cid.toString();
504
685
  const jwsToken = await createJws({
505
- header: { alg: "EdDSA", typ: "did:dfos:content-op", kid: input.kid, cid: operationCID },
506
- payload: input.operationPayload,
686
+ header: { alg: "EdDSA", typ: "did:dfos:countersign", kid: input.kid, cid: countersignCID },
687
+ payload: input.payload,
507
688
  sign: input.signer
508
689
  });
509
- return { jwsToken, operationCID };
690
+ return { jwsToken, countersignCID };
510
691
  };
511
692
  var verifyCountersignature = async (input) => {
512
693
  const decoded = decodeJwsUnsafe(input.jwsToken);
513
694
  if (!decoded) throw new Error("failed to decode countersignature JWS");
514
- if (decoded.header.typ !== "did:dfos:content-op") {
695
+ if (decoded.header.typ !== "did:dfos:countersign") {
515
696
  throw new Error(`invalid countersignature typ: ${decoded.header.typ}`);
516
697
  }
517
- const result = ContentOperation.safeParse(decoded.payload);
698
+ const result = CountersignPayload.safeParse(decoded.payload);
518
699
  if (!result.success) {
519
700
  const messages = result.error.issues.map((e) => e.message).join(", ");
520
- throw new Error(`invalid operation payload: ${messages}`);
521
- }
522
- const op = result.data;
523
- const encoded = await dagCborCanonicalEncode(op);
524
- const operationCID = encoded.cid.toString();
525
- if (operationCID !== input.expectedCID) {
526
- throw new Error("countersignature CID does not match expected CID");
527
- }
528
- if (decoded.header.cid !== operationCID) {
529
- throw new Error("countersignature header cid mismatch");
701
+ throw new Error(`invalid countersignature payload: ${messages}`);
530
702
  }
703
+ const payload = result.data;
531
704
  const kid = decoded.header.kid;
705
+ const hashIdx = kid.indexOf("#");
706
+ if (hashIdx < 0) throw new Error("countersignature kid must be a DID URL");
707
+ const kidDid = kid.substring(0, hashIdx);
708
+ if (kidDid !== payload.did) {
709
+ throw new Error("countersignature kid DID does not match payload did");
710
+ }
532
711
  const publicKey = await input.resolveKey(kid);
533
712
  try {
534
713
  verifyJws({ token: input.jwsToken, publicKey });
535
714
  } catch {
536
- throw new Error("invalid countersignature");
537
- }
538
- const hashIdx = kid.indexOf("#");
539
- if (hashIdx < 0) throw new Error("countersignature kid must be a DID URL");
540
- const witnessDID = kid.substring(0, hashIdx);
541
- if (witnessDID === op.did) {
542
- throw new Error("countersignature kid DID must differ from operation did (not a witness)");
715
+ throw new Error("invalid countersignature signature");
543
716
  }
717
+ const encoded = await dagCborCanonicalEncode(decoded.payload);
718
+ const countersignCID = encoded.cid.toString();
719
+ if (!decoded.header.cid) throw new Error("missing cid in countersignature header");
720
+ if (decoded.header.cid !== countersignCID) throw new Error("countersignature cid mismatch");
544
721
  return {
545
- operationCID,
546
- authorDID: op.did,
547
- witnessDID
722
+ countersignCID,
723
+ witnessDID: payload.did,
724
+ targetCID: payload.targetCID
548
725
  };
549
726
  };
550
- var verifyBeaconCountersignature = async (input) => {
551
- const decoded = decodeJwsUnsafe(input.jwsToken);
552
- if (!decoded) throw new Error("failed to decode beacon countersignature JWS");
553
- if (decoded.header.typ !== "did:dfos:beacon") {
554
- throw new Error(`invalid beacon countersignature typ: ${decoded.header.typ}`);
727
+
728
+ // src/chain/artifact.ts
729
+ var signArtifact = async (input) => {
730
+ const encoded = await dagCborCanonicalEncode(input.payload);
731
+ const artifactCID = encoded.cid.toString();
732
+ if (encoded.bytes.length > MAX_ARTIFACT_PAYLOAD_SIZE) {
733
+ throw new Error(
734
+ `artifact payload exceeds max size: ${encoded.bytes.length} > ${MAX_ARTIFACT_PAYLOAD_SIZE}`
735
+ );
555
736
  }
556
- const result = BeaconPayload.safeParse(decoded.payload);
737
+ const jwsToken = await createJws({
738
+ header: { alg: "EdDSA", typ: "did:dfos:artifact", kid: input.kid, cid: artifactCID },
739
+ payload: input.payload,
740
+ sign: input.signer
741
+ });
742
+ return { jwsToken, artifactCID };
743
+ };
744
+ var verifyArtifact = async (input) => {
745
+ const decoded = decodeJwsUnsafe(input.jwsToken);
746
+ if (!decoded) throw new Error("failed to decode artifact JWS");
747
+ const result = ArtifactPayload.safeParse(decoded.payload);
557
748
  if (!result.success) {
558
749
  const messages = result.error.issues.map((e) => e.message).join(", ");
559
- throw new Error(`invalid beacon payload: ${messages}`);
750
+ throw new Error(`invalid artifact payload: ${messages}`);
560
751
  }
561
- const beacon = result.data;
562
- const encoded = await dagCborCanonicalEncode(beacon);
563
- const beaconCID = encoded.cid.toString();
564
- if (beaconCID !== input.expectedCID) {
565
- throw new Error("beacon countersignature CID does not match expected CID");
566
- }
567
- if (decoded.header.cid !== beaconCID) {
568
- throw new Error("beacon countersignature header cid mismatch");
752
+ const payload = result.data;
753
+ if (decoded.header.typ !== "did:dfos:artifact") {
754
+ throw new Error(`invalid artifact typ: ${decoded.header.typ}`);
569
755
  }
570
756
  const kid = decoded.header.kid;
757
+ const hashIdx = kid.indexOf("#");
758
+ if (hashIdx < 0) throw new Error("artifact kid must be a DID URL");
759
+ const kidDid = kid.substring(0, hashIdx);
760
+ if (kidDid !== payload.did) {
761
+ throw new Error("artifact kid DID does not match payload did");
762
+ }
571
763
  const publicKey = await input.resolveKey(kid);
572
764
  try {
573
765
  verifyJws({ token: input.jwsToken, publicKey });
574
766
  } catch {
575
- throw new Error("invalid beacon countersignature");
767
+ throw new Error("invalid artifact signature");
576
768
  }
577
- const hashIdx = kid.indexOf("#");
578
- if (hashIdx < 0) throw new Error("beacon countersignature kid must be a DID URL");
579
- const witnessDID = kid.substring(0, hashIdx);
580
- if (witnessDID === beacon.did) {
581
- throw new Error("beacon countersignature kid DID must differ from beacon did (not a witness)");
769
+ const encoded = await dagCborCanonicalEncode(decoded.payload);
770
+ const artifactCID = encoded.cid.toString();
771
+ if (!decoded.header.cid) throw new Error("missing cid in artifact header");
772
+ if (decoded.header.cid !== artifactCID) throw new Error("artifact cid mismatch");
773
+ if (encoded.bytes.length > MAX_ARTIFACT_PAYLOAD_SIZE) {
774
+ throw new Error(
775
+ `artifact payload exceeds max size: ${encoded.bytes.length} > ${MAX_ARTIFACT_PAYLOAD_SIZE}`
776
+ );
582
777
  }
583
- return {
584
- beaconCID,
585
- controllerDID: beacon.did,
586
- witnessDID
587
- };
778
+ return { payload, artifactCID };
588
779
  };
589
780
 
590
781
  export {
@@ -593,6 +784,9 @@ export {
593
784
  VerifiedIdentity,
594
785
  ContentOperation,
595
786
  BeaconPayload,
787
+ MAX_ARTIFACT_PAYLOAD_SIZE,
788
+ ArtifactPayload,
789
+ CountersignPayload,
596
790
  ED25519_PUB_MULTICODEC,
597
791
  ED25519_PRIV_MULTICODEC,
598
792
  encodeEd25519Multikey,
@@ -601,11 +795,14 @@ export {
601
795
  deriveContentId,
602
796
  signIdentityOperation,
603
797
  verifyIdentityChain,
798
+ verifyIdentityExtensionFromTrustedState,
604
799
  signContentOperation,
605
800
  verifyContentChain,
801
+ verifyContentExtensionFromTrustedState,
606
802
  signBeacon,
607
803
  verifyBeacon,
608
804
  signCountersignature,
609
805
  verifyCountersignature,
610
- verifyBeaconCountersignature
806
+ signArtifact,
807
+ verifyArtifact
611
808
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
2
- export { BeaconPayload, ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, Signer, VerifiedBeacon, VerifiedBeaconCountersignature, VerifiedContentChain, VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyBeacon, verifyBeaconCountersignature, verifyContentChain, verifyCountersignature, verifyIdentityChain } from './chain/index.js';
2
+ export { ArtifactPayload, BeaconPayload, ContentOperation, CountersignPayload, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MAX_ARTIFACT_PAYLOAD_SIZE, MultikeyPublicKey, Signer, VerifiedArtifact, VerifiedBeacon, VerifiedContentChain, VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signArtifact, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyArtifact, verifyBeacon, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState } from './chain/index.js';
3
3
  export { MerkleProof, buildMerkleTree, generateMerkleProof, hashLeaf, hexToBytes, verifyMerkleProof } from './merkle/index.js';
4
4
  export { AuthTokenClaims, AuthTokenCreateOptions, AuthTokenVerificationError, AuthTokenVerifyOptions, ContentReadSubject, ContentWriteSubject, CredentialClaims, CredentialCreateOptions, CredentialVerificationError, CredentialVerifyOptions, DFOSCredentialType, VCClaim, VC_TYPE_CONTENT_READ, VC_TYPE_CONTENT_WRITE, VerifiedAuthToken, VerifiedCredential, createAuthToken, createCredential, decodeCredentialUnsafe, verifyAuthToken, verifyCredential } from './credentials/index.js';
5
5
  import 'multiformats';