@twin.org/identity-connector-entity-storage 0.9.2 → 0.9.3-next.1

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.
@@ -1,9 +1,9 @@
1
1
  // Copyright 2024 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
- import { ArrayHelper, BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper, Url, Urn } from "@twin.org/core";
3
+ import { ArrayHelper, BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, LruCache, NotFoundError, ObjectHelper, RandomHelper, Url, Urn } from "@twin.org/core";
4
4
  import { JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
5
5
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
6
- import { DocumentHelper } from "@twin.org/identity-models";
6
+ import { DocumentHelper, VerificationHelper } from "@twin.org/identity-models";
7
7
  import { DidContexts, DidTypes, DidVerificationMethodType, JwsAlgorithms, ProofHelper, ProofTypes } from "@twin.org/standards-w3c-did";
8
8
  import { VaultConnectorFactory, VaultConnectorHelper, VaultKeyType } from "@twin.org/vault-models";
9
9
  import { Jwk, Jwt } from "@twin.org/web";
@@ -34,6 +34,28 @@ export class EntityStorageIdentityConnector {
34
34
  * @internal
35
35
  */
36
36
  _vaultConnector;
37
+ /**
38
+ * TTL in ms for caching DID documents resolved for this connector's own sign/mutate
39
+ * operations. 0 disables caching (see resolveOwnDocumentCached). Never applied to proof or
40
+ * credential verification of third-party claims.
41
+ * @internal
42
+ */
43
+ _didResolutionCacheTtlMs;
44
+ /**
45
+ * Maximum number of own DID documents retained in cache.
46
+ * @internal
47
+ */
48
+ _didResolutionCacheCapacity;
49
+ /**
50
+ * Maximum wait time for own DID cache getOrSet mutex acquisition in milliseconds.
51
+ * @internal
52
+ */
53
+ _didResolutionCacheMutexTimeoutMs;
54
+ /**
55
+ * LRU cache for own DID documents. Undefined when caching is disabled (ttl is 0).
56
+ * @internal
57
+ */
58
+ _didResolutionCache;
37
59
  /**
38
60
  * Create a new instance of EntityStorageIdentityConnector.
39
61
  * @param options The options for the identity connector.
@@ -41,6 +63,18 @@ export class EntityStorageIdentityConnector {
41
63
  constructor(options) {
42
64
  this._didDocumentEntityStorage = EntityStorageConnectorFactory.get(options?.didDocumentEntityStorageType ?? "identity-document");
43
65
  this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
66
+ const config = options?.config ?? {};
67
+ this._didResolutionCacheTtlMs = config.didResolutionCacheTtlMs ?? 30_000;
68
+ this._didResolutionCacheCapacity = config.didResolutionCacheCapacity ?? 1000;
69
+ this._didResolutionCacheMutexTimeoutMs = config.didResolutionCacheMutexTimeoutMs;
70
+ this._didResolutionCache =
71
+ this._didResolutionCacheTtlMs > 0
72
+ ? new LruCache({
73
+ capacity: this._didResolutionCacheCapacity,
74
+ ttiMs: this._didResolutionCacheTtlMs,
75
+ mutexTimeoutMs: this._didResolutionCacheMutexTimeoutMs
76
+ })
77
+ : undefined;
44
78
  }
45
79
  /**
46
80
  * Build the key name to access the specified key in the vault.
@@ -75,6 +109,15 @@ export class EntityStorageIdentityConnector {
75
109
  className() {
76
110
  return EntityStorageIdentityConnector.CLASS_NAME;
77
111
  }
112
+ /**
113
+ * Stop the service.
114
+ * Destroys in-memory resources owned by this component.
115
+ * @param nodeLoggingComponentType The node logging component type.
116
+ * @returns A promise that resolves when the service has stopped.
117
+ */
118
+ async stop(nodeLoggingComponentType) {
119
+ this._didResolutionCache?.destroy();
120
+ }
78
121
  /**
79
122
  * Create a new document.
80
123
  * @param controller The controller of the identity who can make changes.
@@ -122,6 +165,7 @@ export class EntityStorageIdentityConnector {
122
165
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
123
166
  }
124
167
  await this._didDocumentEntityStorage.remove(documentId);
168
+ this._didResolutionCache?.delete(this.ownDidCacheKey(documentId));
125
169
  if (options?.removeKeys ?? false) {
126
170
  const methods = this.getAllMethods(didDocument.document);
127
171
  for (const { method } of methods) {
@@ -158,11 +202,7 @@ export class EntityStorageIdentityConnector {
158
202
  Guards.arrayOneOf(EntityStorageIdentityConnector.CLASS_NAME, "verificationMethodType", verificationMethodType, Object.values(DidVerificationMethodType));
159
203
  let tempKeyId;
160
204
  try {
161
- const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
162
- if (Is.undefined(didIdentityDocument)) {
163
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
164
- }
165
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
205
+ const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
166
206
  const didDocument = didIdentityDocument.document;
167
207
  let methodKeyPublic;
168
208
  if (Is.stringValue(verificationMethodId)) {
@@ -250,11 +290,7 @@ export class EntityStorageIdentityConnector {
250
290
  if (Is.empty(idParts.fragment)) {
251
291
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
252
292
  }
253
- const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
254
- if (Is.undefined(didIdentityDocument)) {
255
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
256
- }
257
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
293
+ const didIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
258
294
  const didDocument = didIdentityDocument.document;
259
295
  const methods = this.getAllMethods(didDocument);
260
296
  const existingMethodIndex = methods.findIndex(m => {
@@ -314,11 +350,7 @@ export class EntityStorageIdentityConnector {
314
350
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "serviceEndpoint", serviceEndpoint);
315
351
  }
316
352
  try {
317
- const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
318
- if (Is.undefined(didIdentityDocument)) {
319
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
320
- }
321
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
353
+ const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
322
354
  const didDocument = didIdentityDocument.document;
323
355
  const fullServiceId = serviceId.includes("#") ? serviceId : `${documentId}#${serviceId}`;
324
356
  if (Is.array(didDocument.service)) {
@@ -356,11 +388,7 @@ export class EntityStorageIdentityConnector {
356
388
  if (Is.empty(idParts.fragment)) {
357
389
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", serviceId);
358
390
  }
359
- const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
360
- if (Is.undefined(didIdentityDocument)) {
361
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
362
- }
363
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
391
+ const didIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
364
392
  const didDocument = didIdentityDocument.document;
365
393
  if (Is.array(didDocument.service)) {
366
394
  const existingServiceIndex = didDocument.service.findIndex(s => s.id === serviceId);
@@ -401,11 +429,7 @@ export class EntityStorageIdentityConnector {
401
429
  throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
402
430
  }
403
431
  try {
404
- const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
405
- if (Is.undefined(didIdentityDocument)) {
406
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
407
- }
408
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
432
+ const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
409
433
  const didDocument = didIdentityDocument.document;
410
434
  const existing = Is.array(didDocument.alsoKnownAs) ? didDocument.alsoKnownAs : [];
411
435
  if (existing.includes(alias)) {
@@ -436,11 +460,7 @@ export class EntityStorageIdentityConnector {
436
460
  throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
437
461
  }
438
462
  try {
439
- const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
440
- if (Is.undefined(didIdentityDocument)) {
441
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
442
- }
443
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
463
+ const didIdentityDocument = await this.resolveOwnDocumentCached(documentId);
444
464
  const didDocument = didIdentityDocument.document;
445
465
  if (!Is.array(didDocument.alsoKnownAs) || !didDocument.alsoKnownAs.includes(alias)) {
446
466
  return;
@@ -487,11 +507,7 @@ export class EntityStorageIdentityConnector {
487
507
  if (Is.empty(idParts.fragment)) {
488
508
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
489
509
  }
490
- const issuerIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
491
- if (Is.undefined(issuerIdentityDocument)) {
492
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
493
- }
494
- await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
510
+ const issuerIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
495
511
  const issuerDidDocument = issuerIdentityDocument.document;
496
512
  const methods = this.getAllMethods(issuerDidDocument);
497
513
  const methodAndArray = methods.find(m => {
@@ -552,7 +568,7 @@ export class EntityStorageIdentityConnector {
552
568
  ]);
553
569
  // Add the proof to the VC after extracting the jwt data
554
570
  // as the jwt does not include the proof
555
- verifiableCredential.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiableCredential));
571
+ verifiableCredential.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiableCredential), issuerDidDocument);
556
572
  // As we are adding the receipt to the data we update the JSON-LD context
557
573
  const proofContext = verifiableCredential.proof["@context"];
558
574
  if (!Is.empty(proofContext)) {
@@ -598,14 +614,28 @@ export class EntityStorageIdentityConnector {
598
614
  if (Is.object(credential)) {
599
615
  Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
600
616
  Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential.proof", credential.proof);
617
+ VerificationHelper.checkValidityPeriod(credential);
601
618
  const { proof, ...doc } = credential;
602
- const credentialVerified = await this.verifyProof(JsonLdHelper.toNodeObject(doc), ArrayHelper.fromObjectOrArray(proof)[0]);
619
+ const proofEntry = ArrayHelper.fromObjectOrArray(proof)[0];
620
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "proofEntry.verificationMethod", proofEntry.verificationMethod);
621
+ const issuer = Is.object(doc.issuer) ? doc.issuer.id : doc.issuer;
622
+ const signerDid = DocumentHelper.parseId(proofEntry.verificationMethod).id;
623
+ if (Is.stringValue(issuer) && issuer !== signerDid) {
624
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "issuerMismatch", {
625
+ issuer,
626
+ method: proofEntry.verificationMethod
627
+ });
628
+ }
629
+ const issuerDidDocument = await this.resolveAssertionMethodDocument(proofEntry.verificationMethod);
630
+ const publicKeyJwk = DocumentHelper.getJwk(issuerDidDocument, proofEntry.verificationMethod, DidVerificationMethodType.AssertionMethod);
631
+ const credentialVerified = await ProofHelper.verifyProof(JsonLdHelper.toNodeObject(doc), proofEntry, publicKeyJwk);
603
632
  if (!credentialVerified) {
604
633
  throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "signatureVerificationFailed");
605
634
  }
635
+ const revoked = await this.checkCredentialStatusRevoked(issuerDidDocument, doc.credentialStatus);
606
636
  return {
607
- revoked: false,
608
- verifiableCredential: doc
637
+ revoked,
638
+ verifiableCredential: revoked ? undefined : doc
609
639
  };
610
640
  }
611
641
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
@@ -627,25 +657,9 @@ export class EntityStorageIdentityConnector {
627
657
  }
628
658
  await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
629
659
  const issuerDidDocument = issuerIdentityDocument.document;
630
- const methods = this.getAllMethods(issuerDidDocument);
631
- const methodAndArray = methods.find(m => {
632
- if (Is.string(m.method)) {
633
- return m.method === jwtHeader.kid;
634
- }
635
- return m.method.id === jwtHeader.kid;
636
- });
637
- if (!methodAndArray) {
638
- throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "methodMissing", {
639
- method: jwtHeader.kid
640
- });
641
- }
642
- const didMethod = methodAndArray.method;
643
- if (!Is.stringValue(didMethod.publicKeyJwk?.x)) {
644
- throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "publicKeyJwkMissing", {
645
- method: jwtHeader.kid
646
- });
647
- }
648
- await Jwt.verifySignature(credential, await Jwk.toCryptoKey(didMethod.publicKeyJwk));
660
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "jwtHeader.kid", jwtHeader.kid);
661
+ const publicKeyJwk = DocumentHelper.getJwk(issuerDidDocument, jwtHeader.kid, DidVerificationMethodType.AssertionMethod);
662
+ await Jwt.verifySignature(credential, await Jwk.toCryptoKey(publicKeyJwk));
649
663
  const verifiableCredential = jwtPayload.vc;
650
664
  if (Is.object(verifiableCredential)) {
651
665
  if (Is.string(jwtPayload.jti)) {
@@ -655,6 +669,9 @@ export class EntityStorageIdentityConnector {
655
669
  if (Is.number(jwtPayload.nbf)) {
656
670
  verifiableCredential.issuanceDate = new Date(jwtPayload.nbf * 1000).toISOString();
657
671
  }
672
+ if (Is.number(jwtPayload.exp)) {
673
+ verifiableCredential.expirationDate = new Date(jwtPayload.exp * 1000).toISOString();
674
+ }
658
675
  if (Is.array(verifiableCredential.credentialSubject)) {
659
676
  verifiableCredential.credentialSubject = verifiableCredential.credentialSubject.map(c => {
660
677
  ObjectHelper.propertySet(c, "id", jwtPayload.sub);
@@ -665,19 +682,8 @@ export class EntityStorageIdentityConnector {
665
682
  ObjectHelper.propertySet(verifiableCredential.credentialSubject, "id", jwtPayload.sub);
666
683
  }
667
684
  }
668
- const credentialStatus = verifiableCredential.credentialStatus;
669
- let revoked = false;
670
- if (Is.object(credentialStatus)) {
671
- revoked = await this.checkRevocation(issuerDidDocument, credentialStatus.revocationBitmapIndex);
672
- }
673
- else if (Is.arrayValue(credentialStatus)) {
674
- for (let i = 0; i < credentialStatus.length; i++) {
675
- revoked = await this.checkRevocation(issuerDidDocument, credentialStatus[i].revocationBitmapIndex);
676
- if (revoked) {
677
- break;
678
- }
679
- }
680
- }
685
+ VerificationHelper.checkValidityPeriod(verifiableCredential);
686
+ const revoked = await this.checkCredentialStatusRevoked(issuerDidDocument, verifiableCredential.credentialStatus);
681
687
  return {
682
688
  revoked,
683
689
  verifiableCredential: revoked ? undefined : verifiableCredential
@@ -699,11 +705,7 @@ export class EntityStorageIdentityConnector {
699
705
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "issuerDocumentId", issuerDocumentId);
700
706
  Guards.arrayValue(EntityStorageIdentityConnector.CLASS_NAME, "credentialIndices", credentialIndices);
701
707
  try {
702
- const issuerIdentityDocument = await this._didDocumentEntityStorage.get(issuerDocumentId);
703
- if (Is.undefined(issuerIdentityDocument)) {
704
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", issuerDocumentId);
705
- }
706
- await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
708
+ const issuerIdentityDocument = await this.resolveOwnDocumentCached(issuerDocumentId);
707
709
  const issuerDidDocument = issuerIdentityDocument.document;
708
710
  const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
709
711
  if (revocationService &&
@@ -739,11 +741,7 @@ export class EntityStorageIdentityConnector {
739
741
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "issuerDocumentId", issuerDocumentId);
740
742
  Guards.arrayValue(EntityStorageIdentityConnector.CLASS_NAME, "credentialIndices", credentialIndices);
741
743
  try {
742
- const issuerIdentityDocument = await this._didDocumentEntityStorage.get(issuerDocumentId);
743
- if (Is.undefined(issuerIdentityDocument)) {
744
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", issuerDocumentId);
745
- }
746
- await EntityStorageIdentityConnector.verifyDocument(issuerIdentityDocument, this._vaultConnector);
744
+ const issuerIdentityDocument = await this.resolveOwnDocumentCached(issuerDocumentId);
747
745
  const issuerDidDocument = issuerIdentityDocument.document;
748
746
  const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
749
747
  if (revocationService &&
@@ -800,11 +798,7 @@ export class EntityStorageIdentityConnector {
800
798
  if (Is.empty(idParts.fragment)) {
801
799
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
802
800
  }
803
- const holderIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
804
- if (Is.undefined(holderIdentityDocument)) {
805
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
806
- }
807
- await EntityStorageIdentityConnector.verifyDocument(holderIdentityDocument, this._vaultConnector);
801
+ const holderIdentityDocument = await this.resolveOwnDocumentCached(idParts.id);
808
802
  const holderDidDocument = holderIdentityDocument.document;
809
803
  const methods = this.getAllMethods(holderDidDocument);
810
804
  const methodAndArray = methods.find(m => {
@@ -853,7 +847,7 @@ export class EntityStorageIdentityConnector {
853
847
  ]);
854
848
  // Add the proof to the VP after extracting the jwt data
855
849
  // as the jwt does not include the proof
856
- verifiablePresentation.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiablePresentation));
850
+ verifiablePresentation.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiablePresentation), holderDidDocument);
857
851
  const jwtPayload = {
858
852
  ...options?.jwtPayloadFields,
859
853
  iss: verifiablePresentation.holder,
@@ -887,7 +881,15 @@ export class EntityStorageIdentityConnector {
887
881
  if (!presentationVerified) {
888
882
  throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "signatureVerificationFailed");
889
883
  }
890
- return { revoked: false, verifiablePresentation: doc };
884
+ let revoked = false;
885
+ for (const embeddedCredential of doc.verifiableCredential ?? []) {
886
+ const credentialCheck = await this.checkVerifiableCredential(embeddedCredential);
887
+ if (credentialCheck.revoked) {
888
+ revoked = true;
889
+ break;
890
+ }
891
+ }
892
+ return { revoked, verifiablePresentation: doc };
891
893
  }
892
894
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "presentation", presentation);
893
895
  const presentationJwt = presentation;
@@ -914,7 +916,7 @@ export class EntityStorageIdentityConnector {
914
916
  if (Is.object(verifiablePresentation) &&
915
917
  Is.array(verifiablePresentation.verifiableCredential)) {
916
918
  for (const vcJwt of verifiablePresentation.verifiableCredential) {
917
- let revoked = true;
919
+ let revoked = false;
918
920
  if (Is.stringValue(vcJwt)) {
919
921
  const jwt = await Jwt.decode(vcJwt);
920
922
  if (Is.string(jwt.payload?.iss)) {
@@ -929,31 +931,9 @@ export class EntityStorageIdentityConnector {
929
931
  "@context": DidContexts.Context,
930
932
  ...issuerDidDocument
931
933
  });
932
- const vc = jwt.payload.vc;
933
- if (Is.object(vc)) {
934
- const credentialStatus = vc.credentialStatus;
935
- if (Is.object(credentialStatus)) {
936
- revoked = await this.checkRevocation({
937
- "@context": DidContexts.Context,
938
- ...issuerDidDocument
939
- }, credentialStatus.revocationBitmapIndex);
940
- }
941
- else if (Is.arrayValue(credentialStatus)) {
942
- for (let i = 0; i < credentialStatus.length; i++) {
943
- revoked = await this.checkRevocation({
944
- "@context": DidContexts.Context,
945
- ...issuerDidDocument
946
- }, credentialStatus[i].revocationBitmapIndex);
947
- if (revoked) {
948
- break;
949
- }
950
- }
951
- }
952
- }
953
934
  }
954
- }
955
- else {
956
- revoked = false;
935
+ const credentialCheck = await this.checkVerifiableCredential(vcJwt);
936
+ revoked = credentialCheck.revoked;
957
937
  }
958
938
  tokensRevoked.push(revoked);
959
939
  }
@@ -981,26 +961,29 @@ export class EntityStorageIdentityConnector {
981
961
  * @param verificationMethodId The verification method id to use.
982
962
  * @param proofType The type of proof to create.
983
963
  * @param unsecureDocument The unsecure document to create the proof for.
964
+ * @param resolvedDocument Optional already-resolved document for the DID, so a caller that
965
+ * just resolved it (e.g. createVerifiableCredential) skips a redundant re-resolve. Resolves
966
+ * it itself if omitted.
984
967
  * @returns The proof.
985
968
  * @throws NotFoundError if the identity or method is not found.
986
969
  * @throws GeneralError if algorithm doesn't match key type or proof creation fails.
987
970
  */
988
- async createProof(controller, verificationMethodId, proofType, unsecureDocument) {
971
+ async createProof(controller, verificationMethodId, proofType, unsecureDocument, resolvedDocument) {
989
972
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
990
973
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "verificationMethodId", verificationMethodId);
991
974
  Guards.arrayOneOf(EntityStorageIdentityConnector.CLASS_NAME, "proofType", proofType, Object.values(ProofTypes));
992
975
  Guards.object(EntityStorageIdentityConnector.CLASS_NAME, "unsecureDocument", unsecureDocument);
976
+ if (!Is.undefined(resolvedDocument)) {
977
+ Guards.object(EntityStorageIdentityConnector.CLASS_NAME, "resolvedDocument", resolvedDocument);
978
+ }
993
979
  try {
994
980
  const idParts = DocumentHelper.parseId(verificationMethodId);
995
981
  if (Is.empty(idParts.fragment)) {
996
982
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
997
983
  }
998
- const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
999
- if (Is.undefined(didIdentityDocument)) {
1000
- throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
1001
- }
1002
- await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
1003
- const didDocument = didIdentityDocument.document;
984
+ const didDocument = Is.undefined(resolvedDocument)
985
+ ? (await this.resolveOwnDocumentCached(idParts.id)).document
986
+ : resolvedDocument;
1004
987
  const methods = this.getAllMethods(didDocument);
1005
988
  const methodAndArray = methods.find(m => {
1006
989
  if (Is.string(m.method)) {
@@ -1121,6 +1104,92 @@ export class EntityStorageIdentityConnector {
1121
1104
  }
1122
1105
  return methods;
1123
1106
  }
1107
+ /**
1108
+ * Resolve the issuer document for a verification method, verified against its stored signature.
1109
+ * @param verificationMethodId The verification method id whose owning DID to resolve.
1110
+ * @returns The resolved document.
1111
+ * @throws NotFoundError if the id has no DID, or the DID cannot be resolved.
1112
+ * @internal
1113
+ */
1114
+ async resolveAssertionMethodDocument(verificationMethodId) {
1115
+ const idParts = DocumentHelper.parseId(verificationMethodId);
1116
+ if (Is.empty(idParts.fragment)) {
1117
+ throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "missingDid", verificationMethodId);
1118
+ }
1119
+ const identityDocument = await this._didDocumentEntityStorage.get(idParts.id);
1120
+ if (Is.undefined(identityDocument)) {
1121
+ throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", idParts.id);
1122
+ }
1123
+ await EntityStorageIdentityConnector.verifyDocument(identityDocument, this._vaultConnector);
1124
+ return identityDocument.document;
1125
+ }
1126
+ /**
1127
+ * Cache key for a DID resolved for this connector's own sign/mutate operations (role 1).
1128
+ * Deliberately namespaced ("own") and never shared with proof/credential verification of
1129
+ * third-party claims (role 2/3), which this connector never caches.
1130
+ * @param documentId The DID being resolved.
1131
+ * @returns The cache key.
1132
+ * @internal
1133
+ */
1134
+ ownDidCacheKey(documentId) {
1135
+ return `${EntityStorageIdentityConnector.CLASS_NAME}:own:${documentId}`;
1136
+ }
1137
+ /**
1138
+ * Load and verify a document's own storage entity, uncached.
1139
+ * @param documentId The DID to load.
1140
+ * @returns The verified storage entity.
1141
+ * @throws NotFoundError if the DID could not be resolved.
1142
+ * @internal
1143
+ */
1144
+ async loadOwnDocument(documentId) {
1145
+ const identityDocument = await this._didDocumentEntityStorage.get(documentId);
1146
+ if (Is.undefined(identityDocument)) {
1147
+ throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
1148
+ }
1149
+ await EntityStorageIdentityConnector.verifyDocument(identityDocument, this._vaultConnector);
1150
+ return identityDocument;
1151
+ }
1152
+ /**
1153
+ * Resolve a document's own storage entity for this connector's own sign/mutate operations,
1154
+ * cached for didResolutionCacheTtlMs (0 disables caching and resolves fresh every call).
1155
+ * Only ever used for the connector's own create/update/revoke paths - never for verifyProof
1156
+ * or credential/presentation verification, which must stay uncached.
1157
+ * A clone is always returned: mutators edit the entity in place before storing it, and the
1158
+ * cached instance must never be handed out by reference.
1159
+ * @param documentId The DID to resolve.
1160
+ * @returns The verified storage entity.
1161
+ * @throws NotFoundError if the DID could not be resolved.
1162
+ * @internal
1163
+ */
1164
+ async resolveOwnDocumentCached(documentId) {
1165
+ if (Is.undefined(this._didResolutionCache)) {
1166
+ return this.loadOwnDocument(documentId);
1167
+ }
1168
+ const identityDocument = await this._didResolutionCache.getOrSet(this.ownDidCacheKey(documentId), async () => this.loadOwnDocument(documentId));
1169
+ return ObjectHelper.clone(identityDocument);
1170
+ }
1171
+ /**
1172
+ * Check whether a credential's status entry or entries report it as revoked.
1173
+ * @param document The issuer document owning the revocation bitmap service.
1174
+ * @param credentialStatus The credential's status entry or entries to check.
1175
+ * @returns True if any entry is reported revoked.
1176
+ * @internal
1177
+ */
1178
+ async checkCredentialStatusRevoked(document, credentialStatus) {
1179
+ let statuses = [];
1180
+ if (Is.array(credentialStatus)) {
1181
+ statuses = credentialStatus;
1182
+ }
1183
+ else if (Is.object(credentialStatus)) {
1184
+ statuses = [credentialStatus];
1185
+ }
1186
+ for (const status of statuses) {
1187
+ if (await this.checkRevocation(document, status.revocationBitmapIndex)) {
1188
+ return true;
1189
+ }
1190
+ }
1191
+ return false;
1192
+ }
1124
1193
  /**
1125
1194
  * Check if a revocation index is revoked.
1126
1195
  * @param document The document to check.
@@ -1129,20 +1198,18 @@ export class EntityStorageIdentityConnector {
1129
1198
  * @internal
1130
1199
  */
1131
1200
  async checkRevocation(document, revocationBitmapIndex) {
1132
- if (Is.stringValue(revocationBitmapIndex)) {
1133
- const revocationIndex = Coerce.number(revocationBitmapIndex);
1134
- if (Is.number(revocationIndex)) {
1135
- const revocationService = document.service?.find(s => s.id.endsWith("#revocation"));
1136
- if (revocationService &&
1137
- Is.string(revocationService.serviceEndpoint) &&
1138
- revocationService.type === "BitstringStatusList") {
1139
- const revocationParts = revocationService.serviceEndpoint.split(",");
1140
- if (revocationParts.length === 2) {
1141
- const compressedRevocationBytes = Converter.base64UrlToBytes(revocationParts[1]);
1142
- const decompressed = await Compression.decompress(compressedRevocationBytes, CompressionType.Gzip);
1143
- const bitString = BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
1144
- return bitString.getBit(revocationIndex);
1145
- }
1201
+ const revocationIndex = Coerce.number(revocationBitmapIndex);
1202
+ if (Is.number(revocationIndex)) {
1203
+ const revocationService = document.service?.find(s => s.id.endsWith("#revocation"));
1204
+ if (revocationService &&
1205
+ Is.string(revocationService.serviceEndpoint) &&
1206
+ revocationService.type === "BitstringStatusList") {
1207
+ const revocationParts = revocationService.serviceEndpoint.split(",");
1208
+ if (revocationParts.length === 2) {
1209
+ const compressedRevocationBytes = Converter.base64UrlToBytes(revocationParts[1]);
1210
+ const decompressed = await Compression.decompress(compressedRevocationBytes, CompressionType.Gzip);
1211
+ const bitString = BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
1212
+ return bitString.getBit(revocationIndex);
1146
1213
  }
1147
1214
  }
1148
1215
  }
@@ -1159,12 +1226,14 @@ export class EntityStorageIdentityConnector {
1159
1226
  const stringifiedDocument = JsonHelper.canonicalize(didDocument);
1160
1227
  const docBytes = Converter.utf8ToBytes(stringifiedDocument);
1161
1228
  const signature = await this._vaultConnector.sign(EntityStorageIdentityConnector.buildVaultKey(didDocument.id, "did"), docBytes);
1162
- await this._didDocumentEntityStorage.set({
1229
+ const identityDocument = {
1163
1230
  id: didDocument.id,
1164
1231
  document: didDocument,
1165
1232
  signature: Converter.bytesToBase64(signature),
1166
1233
  controller
1167
- });
1234
+ };
1235
+ await this._didDocumentEntityStorage.set(identityDocument);
1236
+ this._didResolutionCache?.set(this.ownDidCacheKey(didDocument.id), ObjectHelper.clone(identityDocument));
1168
1237
  }
1169
1238
  }
1170
1239
  //# sourceMappingURL=entityStorageIdentityConnector.js.map