@metalabel/dfos-protocol 0.7.0 → 0.8.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 +8 -21
- package/dist/chain/index.d.ts +54 -148
- package/dist/chain/index.js +15 -8
- package/dist/{chunk-QKHP7UVL.js → chunk-LQ56P4SU.js} +137 -110
- package/dist/chunk-MEV6QVLC.js +402 -0
- package/dist/credentials/index.d.ts +133 -117
- package/dist/credentials/index.js +17 -21
- package/dist/index.d.ts +3 -2
- package/dist/index.js +30 -28
- package/dist/schemas-BEl38wrI.d.ts +148 -0
- package/examples/beacon.json +5 -5
- package/examples/content-delegated.json +3 -3
- package/examples/credential-read.json +4 -5
- package/examples/credential-write.json +5 -6
- package/package.json +2 -2
- package/dist/chunk-CZSEEZLL.js +0 -258
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
decodeDFOSCredentialUnsafe,
|
|
3
|
+
decodeMultikey,
|
|
4
|
+
matchesResource,
|
|
5
|
+
verifyDFOSCredential,
|
|
6
|
+
verifyDelegationChain
|
|
7
|
+
} from "./chunk-MEV6QVLC.js";
|
|
6
8
|
import {
|
|
7
9
|
createJws,
|
|
8
10
|
dagCborCanonicalEncode,
|
|
@@ -79,7 +81,7 @@ var ContentUpdate = z.strictObject({
|
|
|
79
81
|
baseDocumentCID: CIDString.nullable(),
|
|
80
82
|
createdAt: Iso8601,
|
|
81
83
|
note: z.string().max(MAX_NOTE).nullable(),
|
|
82
|
-
/**
|
|
84
|
+
/** DFOS credential authorizing this operation when signer is not the chain creator */
|
|
83
85
|
authorization: z.string().optional()
|
|
84
86
|
});
|
|
85
87
|
var ContentDelete = z.strictObject({
|
|
@@ -89,7 +91,7 @@ var ContentDelete = z.strictObject({
|
|
|
89
91
|
previousOperationCID: CIDString,
|
|
90
92
|
createdAt: Iso8601,
|
|
91
93
|
note: z.string().max(MAX_NOTE).nullable(),
|
|
92
|
-
/**
|
|
94
|
+
/** DFOS credential authorizing this operation when signer is not the chain creator */
|
|
93
95
|
authorization: z.string().optional()
|
|
94
96
|
});
|
|
95
97
|
var ContentOperation = z.discriminatedUnion("type", [
|
|
@@ -101,7 +103,7 @@ var BeaconPayload = z.strictObject({
|
|
|
101
103
|
version: z.literal(1),
|
|
102
104
|
type: z.literal("beacon"),
|
|
103
105
|
did: z.string().max(MAX_DID),
|
|
104
|
-
|
|
106
|
+
manifestContentId: z.string().max(MAX_CID),
|
|
105
107
|
createdAt: Iso8601
|
|
106
108
|
});
|
|
107
109
|
var MAX_SCHEMA = 256;
|
|
@@ -121,45 +123,13 @@ var CountersignPayload = z.strictObject({
|
|
|
121
123
|
targetCID: CIDString,
|
|
122
124
|
createdAt: Iso8601
|
|
123
125
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
var encodeEd25519Multikey = (publicKeyBytes) => {
|
|
132
|
-
if (publicKeyBytes.length !== 32) {
|
|
133
|
-
throw new Error(`expected 32-byte Ed25519 public key, got ${publicKeyBytes.length}`);
|
|
134
|
-
}
|
|
135
|
-
const prefixed = new Uint8Array(ED25519_PUB_PREFIX.length + publicKeyBytes.length);
|
|
136
|
-
prefixed.set(ED25519_PUB_PREFIX);
|
|
137
|
-
prefixed.set(publicKeyBytes, ED25519_PUB_PREFIX.length);
|
|
138
|
-
return base58btc.encode(prefixed);
|
|
139
|
-
};
|
|
140
|
-
var decodeMultikey = (multibase) => {
|
|
141
|
-
const bytes = base58btc.decode(multibase);
|
|
142
|
-
if (bytes.length < 2) {
|
|
143
|
-
throw new Error("multikey too short");
|
|
144
|
-
}
|
|
145
|
-
if (bytes[0] === ED25519_PUB_PREFIX[0] && bytes[1] === ED25519_PUB_PREFIX[1]) {
|
|
146
|
-
const keyBytes = bytes.slice(2);
|
|
147
|
-
if (keyBytes.length !== 32) {
|
|
148
|
-
throw new Error(`expected 32-byte Ed25519 public key, got ${keyBytes.length}`);
|
|
149
|
-
}
|
|
150
|
-
return { keyBytes, codec: ED25519_PUB_MULTICODEC };
|
|
151
|
-
}
|
|
152
|
-
if (bytes[0] === ED25519_PRIV_PREFIX[0] && bytes[1] === ED25519_PRIV_PREFIX[1]) {
|
|
153
|
-
const keyBytes = bytes.slice(2);
|
|
154
|
-
if (keyBytes.length !== 32) {
|
|
155
|
-
throw new Error(`expected 32-byte Ed25519 private key, got ${keyBytes.length}`);
|
|
156
|
-
}
|
|
157
|
-
return { keyBytes, codec: ED25519_PRIV_MULTICODEC };
|
|
158
|
-
}
|
|
159
|
-
throw new Error(
|
|
160
|
-
`unsupported multikey codec: [0x${bytes[0]?.toString(16)}, 0x${bytes[1]?.toString(16)}]`
|
|
161
|
-
);
|
|
162
|
-
};
|
|
126
|
+
var RevocationPayload = z.strictObject({
|
|
127
|
+
version: z.literal(1),
|
|
128
|
+
type: z.literal("revocation"),
|
|
129
|
+
did: z.string().max(MAX_DID),
|
|
130
|
+
credentialCID: CIDString,
|
|
131
|
+
createdAt: Iso8601
|
|
132
|
+
});
|
|
163
133
|
|
|
164
134
|
// src/chain/derivation.ts
|
|
165
135
|
var deriveChainIdentifier = (cidBytes, prefix) => {
|
|
@@ -400,8 +370,39 @@ var signContentOperation = async (input) => {
|
|
|
400
370
|
});
|
|
401
371
|
return { jwsToken, operationCID };
|
|
402
372
|
};
|
|
373
|
+
var verifyOperationAuthorization = async (input) => {
|
|
374
|
+
const decoded = decodeDFOSCredentialUnsafe(input.authorization);
|
|
375
|
+
if (!decoded) {
|
|
376
|
+
throw new Error("failed to decode authorization credential");
|
|
377
|
+
}
|
|
378
|
+
if (decoded.header.typ !== "did:dfos:credential") {
|
|
379
|
+
throw new Error(`invalid authorization typ: ${decoded.header.typ}`);
|
|
380
|
+
}
|
|
381
|
+
const opCreatedAtUnix = Math.floor(new Date(input.createdAt).getTime() / 1e3);
|
|
382
|
+
const credential = await verifyDFOSCredential(input.authorization, {
|
|
383
|
+
resolveIdentity: input.resolveIdentity,
|
|
384
|
+
now: opCreatedAtUnix
|
|
385
|
+
});
|
|
386
|
+
await verifyDelegationChain(credential, {
|
|
387
|
+
resolveIdentity: input.resolveIdentity,
|
|
388
|
+
rootDID: input.creatorDID,
|
|
389
|
+
now: opCreatedAtUnix
|
|
390
|
+
});
|
|
391
|
+
if (credential.aud !== "*" && credential.aud !== input.operationDID) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`credential audience ${credential.aud} does not match operation signer ${input.operationDID}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const covers = await matchesResource(credential.att, `chain:${input.contentId}`, "write");
|
|
397
|
+
if (!covers) {
|
|
398
|
+
throw new Error(`credential does not cover write access to chain:${input.contentId}`);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
403
401
|
var verifyContentChain = async (input) => {
|
|
404
402
|
if (input.log.length === 0) throw new Error("log must have at least one operation");
|
|
403
|
+
if (input.enforceAuthorization && !input.resolveIdentity) {
|
|
404
|
+
throw new Error("resolveIdentity is required when enforceAuthorization is true");
|
|
405
|
+
}
|
|
405
406
|
const state = {
|
|
406
407
|
contentId: null,
|
|
407
408
|
genesisCID: null,
|
|
@@ -459,40 +460,18 @@ var verifyContentChain = async (input) => {
|
|
|
459
460
|
const authorization = op.type !== "create" ? op.authorization : void 0;
|
|
460
461
|
if (!authorization) {
|
|
461
462
|
throw new Error(
|
|
462
|
-
`log[${idx}]: signer ${op.did} is not the chain creator \u2014 authorization
|
|
463
|
+
`log[${idx}]: signer ${op.did} is not the chain creator \u2014 authorization credential required`
|
|
463
464
|
);
|
|
464
465
|
}
|
|
465
|
-
const vcDecoded = decodeCredentialUnsafe(authorization);
|
|
466
|
-
if (!vcDecoded) {
|
|
467
|
-
throw new Error(`log[${idx}]: failed to decode authorization VC`);
|
|
468
|
-
}
|
|
469
|
-
const vcKid = vcDecoded.header.kid;
|
|
470
|
-
if (!vcKid || !vcKid.includes("#")) {
|
|
471
|
-
throw new Error(`log[${idx}]: authorization VC kid must be a DID URL`);
|
|
472
|
-
}
|
|
473
|
-
let creatorPublicKey;
|
|
474
|
-
try {
|
|
475
|
-
creatorPublicKey = await input.resolveKey(vcKid);
|
|
476
|
-
} catch {
|
|
477
|
-
throw new Error(`log[${idx}]: cannot resolve creator key for authorization verification`);
|
|
478
|
-
}
|
|
479
|
-
const opCreatedAtUnix = Math.floor(new Date(op.createdAt).getTime() / 1e3);
|
|
480
466
|
try {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
467
|
+
await verifyOperationAuthorization({
|
|
468
|
+
authorization,
|
|
469
|
+
operationDID: op.did,
|
|
470
|
+
creatorDID: state.creatorDID,
|
|
471
|
+
contentId: state.contentId,
|
|
472
|
+
createdAt: op.createdAt,
|
|
473
|
+
resolveIdentity: input.resolveIdentity
|
|
487
474
|
});
|
|
488
|
-
if (credential.iss !== state.creatorDID) {
|
|
489
|
-
throw new Error("VC issuer is not the chain creator");
|
|
490
|
-
}
|
|
491
|
-
if (credential.contentId && credential.contentId !== state.contentId) {
|
|
492
|
-
throw new Error(
|
|
493
|
-
`VC contentId ${credential.contentId} does not match chain ${state.contentId}`
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
475
|
} catch (err) {
|
|
497
476
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
498
477
|
throw new Error(`log[${idx}]: authorization verification failed: ${message}`);
|
|
@@ -541,6 +520,9 @@ var verifyContentExtensionFromTrustedState = async (input) => {
|
|
|
541
520
|
if (currentState.isDeleted) {
|
|
542
521
|
throw new Error("cannot extend a deleted chain");
|
|
543
522
|
}
|
|
523
|
+
if (input.enforceAuthorization && !input.resolveIdentity) {
|
|
524
|
+
throw new Error("resolveIdentity is required when enforceAuthorization is true");
|
|
525
|
+
}
|
|
544
526
|
const decoded = decodeJwsUnsafe(newOp);
|
|
545
527
|
if (!decoded) throw new Error("failed to decode JWS");
|
|
546
528
|
const result = ContentOperation.safeParse(decoded.payload);
|
|
@@ -577,37 +559,19 @@ var verifyContentExtensionFromTrustedState = async (input) => {
|
|
|
577
559
|
if (op.did !== currentState.creatorDID && input.enforceAuthorization) {
|
|
578
560
|
const authorization = op.authorization;
|
|
579
561
|
if (!authorization) {
|
|
580
|
-
throw new Error(
|
|
581
|
-
|
|
582
|
-
|
|
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");
|
|
562
|
+
throw new Error(
|
|
563
|
+
`signer ${op.did} is not the chain creator \u2014 authorization credential required`
|
|
564
|
+
);
|
|
593
565
|
}
|
|
594
|
-
const opCreatedAtUnix = Math.floor(new Date(op.createdAt).getTime() / 1e3);
|
|
595
566
|
try {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
567
|
+
await verifyOperationAuthorization({
|
|
568
|
+
authorization,
|
|
569
|
+
operationDID: op.did,
|
|
570
|
+
creatorDID: currentState.creatorDID,
|
|
571
|
+
contentId: currentState.contentId,
|
|
572
|
+
createdAt: op.createdAt,
|
|
573
|
+
resolveIdentity: input.resolveIdentity
|
|
602
574
|
});
|
|
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
575
|
} catch (err) {
|
|
612
576
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
613
577
|
throw new Error(`authorization verification failed: ${message}`);
|
|
@@ -675,7 +639,13 @@ var verifyBeacon = async (input) => {
|
|
|
675
639
|
if (beaconTime > now + MAX_FUTURE_MS) {
|
|
676
640
|
throw new Error("beacon createdAt is too far in the future");
|
|
677
641
|
}
|
|
678
|
-
return {
|
|
642
|
+
return {
|
|
643
|
+
did: payload.did,
|
|
644
|
+
manifestContentId: payload.manifestContentId,
|
|
645
|
+
createdAt: payload.createdAt,
|
|
646
|
+
signerKeyId: kid,
|
|
647
|
+
beaconCID
|
|
648
|
+
};
|
|
679
649
|
};
|
|
680
650
|
|
|
681
651
|
// src/chain/countersign.ts
|
|
@@ -778,6 +748,64 @@ var verifyArtifact = async (input) => {
|
|
|
778
748
|
return { payload, artifactCID };
|
|
779
749
|
};
|
|
780
750
|
|
|
751
|
+
// src/chain/revocation.ts
|
|
752
|
+
var signRevocation = async (input) => {
|
|
753
|
+
const kid = `${input.issuerDID}#${input.keyId}`;
|
|
754
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\d{3}Z$/, "000Z");
|
|
755
|
+
const payload = {
|
|
756
|
+
version: 1,
|
|
757
|
+
type: "revocation",
|
|
758
|
+
did: input.issuerDID,
|
|
759
|
+
credentialCID: input.credentialCID,
|
|
760
|
+
createdAt: now
|
|
761
|
+
};
|
|
762
|
+
const encoded = await dagCborCanonicalEncode(payload);
|
|
763
|
+
const revocationCID = encoded.cid.toString();
|
|
764
|
+
const jwsToken = await createJws({
|
|
765
|
+
header: { alg: "EdDSA", typ: "did:dfos:revocation", kid, cid: revocationCID },
|
|
766
|
+
payload,
|
|
767
|
+
sign: input.signer
|
|
768
|
+
});
|
|
769
|
+
return { jwsToken, revocationCID };
|
|
770
|
+
};
|
|
771
|
+
var verifyRevocation = async (input) => {
|
|
772
|
+
const decoded = decodeJwsUnsafe(input.jwsToken);
|
|
773
|
+
if (!decoded) throw new Error("failed to decode revocation JWS");
|
|
774
|
+
const result = RevocationPayload.safeParse(decoded.payload);
|
|
775
|
+
if (!result.success) {
|
|
776
|
+
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
777
|
+
throw new Error(`invalid revocation payload: ${messages}`);
|
|
778
|
+
}
|
|
779
|
+
const payload = result.data;
|
|
780
|
+
if (decoded.header.typ !== "did:dfos:revocation") {
|
|
781
|
+
throw new Error(`invalid revocation typ: ${decoded.header.typ}`);
|
|
782
|
+
}
|
|
783
|
+
const kid = decoded.header.kid;
|
|
784
|
+
const hashIdx = kid.indexOf("#");
|
|
785
|
+
if (hashIdx < 0) throw new Error("revocation kid must be a DID URL");
|
|
786
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
787
|
+
if (kidDid !== payload.did) {
|
|
788
|
+
throw new Error("revocation kid DID does not match payload did");
|
|
789
|
+
}
|
|
790
|
+
const publicKey = await input.resolveKey(kid);
|
|
791
|
+
try {
|
|
792
|
+
verifyJws({ token: input.jwsToken, publicKey });
|
|
793
|
+
} catch {
|
|
794
|
+
throw new Error("invalid revocation signature");
|
|
795
|
+
}
|
|
796
|
+
const encoded = await dagCborCanonicalEncode(payload);
|
|
797
|
+
const revocationCID = encoded.cid.toString();
|
|
798
|
+
if (!decoded.header.cid) throw new Error("missing cid in revocation header");
|
|
799
|
+
if (decoded.header.cid !== revocationCID) throw new Error("revocation cid mismatch");
|
|
800
|
+
return {
|
|
801
|
+
did: payload.did,
|
|
802
|
+
credentialCID: payload.credentialCID,
|
|
803
|
+
createdAt: payload.createdAt,
|
|
804
|
+
signerKeyId: kid,
|
|
805
|
+
revocationCID
|
|
806
|
+
};
|
|
807
|
+
};
|
|
808
|
+
|
|
781
809
|
export {
|
|
782
810
|
MultikeyPublicKey,
|
|
783
811
|
IdentityOperation,
|
|
@@ -787,10 +815,7 @@ export {
|
|
|
787
815
|
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
788
816
|
ArtifactPayload,
|
|
789
817
|
CountersignPayload,
|
|
790
|
-
|
|
791
|
-
ED25519_PRIV_MULTICODEC,
|
|
792
|
-
encodeEd25519Multikey,
|
|
793
|
-
decodeMultikey,
|
|
818
|
+
RevocationPayload,
|
|
794
819
|
deriveChainIdentifier,
|
|
795
820
|
deriveContentId,
|
|
796
821
|
signIdentityOperation,
|
|
@@ -804,5 +829,7 @@ export {
|
|
|
804
829
|
signCountersignature,
|
|
805
830
|
verifyCountersignature,
|
|
806
831
|
signArtifact,
|
|
807
|
-
verifyArtifact
|
|
832
|
+
verifyArtifact,
|
|
833
|
+
signRevocation,
|
|
834
|
+
verifyRevocation
|
|
808
835
|
};
|