@twin.org/identity-connector-entity-storage 0.0.3-next.9 → 0.9.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
@@ -1,6 +1,6 @@
1
- # TWIN Identity Connector Entity Storage
1
+ # TWIN Identity
2
2
 
3
- Identity connector implementation using entity storage.
3
+ The identity-connector-entity-storage package provides an entity storage backed connector for identity workflows, enabling reliable persistence and retrieval of identity records. It supports implementations that need consistent storage semantics while staying aligned with shared contracts across the ecosystem.
4
4
 
5
5
  ## Installation
6
6
 
@@ -1,10 +1,10 @@
1
1
  // Copyright 2024 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
- import { BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper } from "@twin.org/core";
4
- import { JsonLdProcessor } from "@twin.org/data-json-ld";
3
+ import { ArrayHelper, BaseError, BitString, Coerce, Compression, CompressionType, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper, Url, Urn } from "@twin.org/core";
4
+ import { JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
5
5
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
6
6
  import { DocumentHelper } from "@twin.org/identity-models";
7
- import { DidContexts, DidTypes, DidVerificationMethodType, ProofHelper, ProofTypes } from "@twin.org/standards-w3c-did";
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";
10
10
  /**
@@ -45,6 +45,7 @@ export class EntityStorageIdentityConnector {
45
45
  /**
46
46
  * Build the key name to access the specified key in the vault.
47
47
  * @param identity The identity of the user to access the vault keys.
48
+ * @param key The key to access in the vault.
48
49
  * @returns The vault key.
49
50
  * @internal
50
51
  */
@@ -54,6 +55,9 @@ export class EntityStorageIdentityConnector {
54
55
  /**
55
56
  * Verify the document in storage.
56
57
  * @param didDocument The did document that was stored.
58
+ * @param vaultConnector The vault connector to use for verification.
59
+ * @returns A promise that resolves when verification is complete.
60
+ * @throws GeneralError if the document signature is invalid.
57
61
  * @internal
58
62
  */
59
63
  static async verifyDocument(didDocument, vaultConnector) {
@@ -84,7 +88,7 @@ export class EntityStorageIdentityConnector {
84
88
  const bitString = new BitString(EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
85
89
  const compressed = await Compression.compress(bitString.getBits(), CompressionType.Gzip);
86
90
  const didDocument = {
87
- "@context": DidContexts.Namespace,
91
+ "@context": DidContexts.Context,
88
92
  id: did,
89
93
  service: [
90
94
  {
@@ -105,7 +109,7 @@ export class EntityStorageIdentityConnector {
105
109
  * Remove a document.
106
110
  * @param controller The controller of the identity who can make changes.
107
111
  * @param documentId The id of the document to remove.
108
- * @returns Nothing.
112
+ * @returns A promise that resolves when the document has been removed.
109
113
  */
110
114
  async removeDocument(controller, documentId) {
111
115
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
@@ -148,7 +152,7 @@ export class EntityStorageIdentityConnector {
148
152
  // If there is a verification method id, we will try to get the key from the vault.
149
153
  try {
150
154
  // If there is an existing key, we will use it.
151
- const existingKey = await this._vaultConnector.getKey(EntityStorageIdentityConnector.buildVaultKey(didDocument.id, verificationMethodId));
155
+ const existingKey = await this._vaultConnector.getKey(EntityStorageIdentityConnector.buildVaultKey(didDocument.id, verificationMethodId), "public");
152
156
  methodKeyPublic = existingKey.publicKey;
153
157
  }
154
158
  catch { }
@@ -187,7 +191,7 @@ export class EntityStorageIdentityConnector {
187
191
  const didVerificationMethod = {
188
192
  id: methodId,
189
193
  controller: documentId,
190
- type: "JsonWebKey",
194
+ type: "JsonWebKey2020",
191
195
  publicKeyJwk: {
192
196
  ...jwkParams,
193
197
  kid
@@ -215,7 +219,7 @@ export class EntityStorageIdentityConnector {
215
219
  * Remove a verification method from the document.
216
220
  * @param controller The controller of the identity who can make changes.
217
221
  * @param verificationMethodId The id of the verification method.
218
- * @returns Nothing.
222
+ * @returns A promise that resolves when the verification method has been removed.
219
223
  * @throws NotFoundError if the id can not be resolved.
220
224
  * @throws NotSupportedError if the platform does not support multiple revocable keys.
221
225
  */
@@ -316,7 +320,7 @@ export class EntityStorageIdentityConnector {
316
320
  * Remove a service from the document.
317
321
  * @param controller The controller of the identity who can make changes.
318
322
  * @param serviceId The id of the service.
319
- * @returns Nothing.
323
+ * @returns A promise that resolves when the service has been removed.
320
324
  * @throws NotFoundError if the id can not be resolved.
321
325
  */
322
326
  async removeService(controller, serviceId) {
@@ -341,6 +345,9 @@ export class EntityStorageIdentityConnector {
341
345
  delete didDocument.service;
342
346
  }
343
347
  }
348
+ else {
349
+ throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "serviceNotFound", serviceId);
350
+ }
344
351
  }
345
352
  else {
346
353
  throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "serviceNotFound", serviceId);
@@ -351,6 +358,81 @@ export class EntityStorageIdentityConnector {
351
358
  throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "removeServiceFailed", undefined, error);
352
359
  }
353
360
  }
361
+ /**
362
+ * Add an alias to the alsoKnownAs property on the document.
363
+ * If the alias is already present the operation is a no-op.
364
+ * @param controller The controller of the identity who can make changes.
365
+ * @param documentId The id of the document to update.
366
+ * @param alias The alias to add. Must be a Url or Urn (typically another DID).
367
+ * @returns A promise that resolves when the alias has been added.
368
+ * @throws GeneralError if the alias is not a Url or Urn.
369
+ * @throws NotFoundError if the id can not be resolved.
370
+ */
371
+ async addAlsoKnownAs(controller, documentId, alias) {
372
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
373
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "documentId", documentId);
374
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "alias", alias);
375
+ if (!Url.tryParseExact(alias) && !Urn.tryParseExact(alias)) {
376
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
377
+ }
378
+ try {
379
+ const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
380
+ if (Is.undefined(didIdentityDocument)) {
381
+ throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
382
+ }
383
+ await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
384
+ const didDocument = didIdentityDocument.document;
385
+ const existing = Is.array(didDocument.alsoKnownAs) ? didDocument.alsoKnownAs : [];
386
+ if (existing.includes(alias)) {
387
+ return;
388
+ }
389
+ didDocument.alsoKnownAs = [...existing, alias];
390
+ await this.updateDocument(controller, didDocument);
391
+ }
392
+ catch (error) {
393
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "addAlsoKnownAsFailed", undefined, error);
394
+ }
395
+ }
396
+ /**
397
+ * Remove an alias from the alsoKnownAs property on the document.
398
+ * If the alias is not present the operation is a no-op.
399
+ * @param controller The controller of the identity who can make changes.
400
+ * @param documentId The id of the document to update.
401
+ * @param alias The alias to remove. Must be a Url or Urn.
402
+ * @returns A promise that resolves when the alias has been removed.
403
+ * @throws GeneralError if the alias is not a Url or Urn.
404
+ * @throws NotFoundError if the id can not be resolved.
405
+ */
406
+ async removeAlsoKnownAs(controller, documentId, alias) {
407
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
408
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "documentId", documentId);
409
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "alias", alias);
410
+ if (!Url.tryParseExact(alias) && !Urn.tryParseExact(alias)) {
411
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "invalidAlias", { alias });
412
+ }
413
+ try {
414
+ const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
415
+ if (Is.undefined(didIdentityDocument)) {
416
+ throw new NotFoundError(EntityStorageIdentityConnector.CLASS_NAME, "documentNotFound", documentId);
417
+ }
418
+ await EntityStorageIdentityConnector.verifyDocument(didIdentityDocument, this._vaultConnector);
419
+ const didDocument = didIdentityDocument.document;
420
+ if (!Is.array(didDocument.alsoKnownAs) || !didDocument.alsoKnownAs.includes(alias)) {
421
+ return;
422
+ }
423
+ const filtered = didDocument.alsoKnownAs.filter(a => a !== alias);
424
+ if (filtered.length === 0) {
425
+ delete didDocument.alsoKnownAs;
426
+ }
427
+ else {
428
+ didDocument.alsoKnownAs = filtered;
429
+ }
430
+ await this.updateDocument(controller, didDocument);
431
+ }
432
+ catch (error) {
433
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "removeAlsoKnownAsFailed", undefined, error);
434
+ }
435
+ }
354
436
  /**
355
437
  * Create a verifiable credential for a verification method.
356
438
  * @param controller The controller of the identity who can make changes.
@@ -360,6 +442,8 @@ export class EntityStorageIdentityConnector {
360
442
  * @param options Additional options for creating the verifiable credential.
361
443
  * @param options.revocationIndex The bitmap revocation index of the credential, if undefined will not have revocation status.
362
444
  * @param options.expirationDate The date the verifiable credential is valid until.
445
+ * @param options.jwtHeaderFields Additional fields to add to the JWT header.
446
+ * @param options.jwtPayloadFields Additional fields to add to the JWT payload.
363
447
  * @returns The created verifiable credential and its token.
364
448
  * @throws NotFoundError if the id can not be resolved.
365
449
  */
@@ -370,6 +454,9 @@ export class EntityStorageIdentityConnector {
370
454
  if (!Is.undefined(options?.revocationIndex)) {
371
455
  Guards.number(EntityStorageIdentityConnector.CLASS_NAME, "options.revocationIndex", options.revocationIndex);
372
456
  }
457
+ if (!Is.undefined(options?.expirationDate)) {
458
+ Guards.date(EntityStorageIdentityConnector.CLASS_NAME, "options.expirationDate", options.expirationDate);
459
+ }
373
460
  try {
374
461
  const idParts = DocumentHelper.parseId(verificationMethodId);
375
462
  if (Is.empty(idParts.fragment)) {
@@ -401,19 +488,15 @@ export class EntityStorageIdentityConnector {
401
488
  }
402
489
  const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
403
490
  const subjectClone = ObjectHelper.clone(subject);
404
- const finalTypes = [DidTypes.VerifiableCredential];
405
491
  const credContext = ObjectHelper.extractProperty(subjectClone, [
406
492
  "@context"
407
493
  ]);
408
494
  const credId = ObjectHelper.extractProperty(subjectClone, ["@id", "id"], false);
409
- const credType = ObjectHelper.extractProperty(subjectClone, ["@type", "type"]);
410
- if (Is.stringValue(credType)) {
411
- finalTypes.push(credType);
412
- }
413
495
  const verifiableCredential = {
414
- "@context": JsonLdProcessor.combineContexts(DidContexts.NamespaceVCv1, credContext),
496
+ "@context": (JsonLdProcessor.combineContexts(DidContexts.ContextVCv1, credContext) ??
497
+ DidContexts.ContextVCv1),
415
498
  id,
416
- type: finalTypes,
499
+ type: DidTypes.VerifiableCredential,
417
500
  credentialSubject: subjectClone,
418
501
  issuer: issuerDidDocument.id,
419
502
  issuanceDate: new Date(Date.now()).toISOString(),
@@ -431,9 +514,10 @@ export class EntityStorageIdentityConnector {
431
514
  : undefined
432
515
  };
433
516
  const jwtHeader = {
517
+ ...options?.jwtHeaderFields,
434
518
  kid: verificationDidMethod.id,
435
519
  typ: "JWT",
436
- alg: "EdDSA"
520
+ alg: JwsAlgorithms.EdDSA
437
521
  };
438
522
  const jwtVc = ObjectHelper.pick(ObjectHelper.clone(verifiableCredential), [
439
523
  "@context",
@@ -441,6 +525,15 @@ export class EntityStorageIdentityConnector {
441
525
  "credentialSubject",
442
526
  "credentialStatus"
443
527
  ]);
528
+ // Add the proof to the VC after extracting the jwt data
529
+ // as the jwt does not include the proof
530
+ verifiableCredential.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiableCredential));
531
+ // As we are adding the receipt to the data we update the JSON-LD context
532
+ const proofContext = verifiableCredential.proof["@context"];
533
+ if (!Is.empty(proofContext)) {
534
+ verifiableCredential["@context"] = (JsonLdProcessor.combineContexts(verifiableCredential["@context"], proofContext) ?? verifiableCredential["@context"]);
535
+ delete verifiableCredential.proof["@context"];
536
+ }
444
537
  if (Is.array(jwtVc.credentialSubject)) {
445
538
  jwtVc.credentialSubject = jwtVc.credentialSubject.map(c => {
446
539
  ObjectHelper.propertyDelete(c, "id");
@@ -451,12 +544,16 @@ export class EntityStorageIdentityConnector {
451
544
  ObjectHelper.propertyDelete(jwtVc.credentialSubject, "id");
452
545
  }
453
546
  const jwtPayload = {
547
+ ...options?.jwtPayloadFields,
454
548
  iss: idParts.id,
455
549
  nbf: Math.floor(Date.now() / 1000),
456
550
  jti: verifiableCredential.id,
457
551
  sub: credId,
458
552
  vc: jwtVc
459
553
  };
554
+ if (Is.date(options?.expirationDate)) {
555
+ jwtPayload.exp = Math.floor(options.expirationDate.getTime() / 1000);
556
+ }
460
557
  const signature = await Jwt.encodeWithSigner(jwtHeader, jwtPayload, async (header, payload) => VaultConnectorHelper.jwtSigner(this._vaultConnector, EntityStorageIdentityConnector.buildVaultKey(idParts.id, idParts.fragment ?? ""), header, payload));
461
558
  return {
462
559
  verifiableCredential,
@@ -469,13 +566,23 @@ export class EntityStorageIdentityConnector {
469
566
  }
470
567
  /**
471
568
  * Check a verifiable credential is valid.
472
- * @param credentialJwt The credential to verify.
569
+ * @param credential The credential to verify.
473
570
  * @returns The credential stored in the jwt and the revocation status.
474
571
  */
475
- async checkVerifiableCredential(credentialJwt) {
476
- Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "credentialJwt", credentialJwt);
572
+ async checkVerifiableCredential(credential) {
573
+ if (Is.object(credential)) {
574
+ Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
575
+ Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "credential.proof", credential.proof);
576
+ const { proof, ...doc } = credential;
577
+ await this.verifyProof(JsonLdHelper.toNodeObject(doc), ArrayHelper.fromObjectOrArray(proof)[0]);
578
+ return {
579
+ revoked: false,
580
+ verifiableCredential: doc
581
+ };
582
+ }
583
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "credential", credential);
477
584
  try {
478
- const jwtDecoded = await Jwt.decode(credentialJwt);
585
+ const jwtDecoded = await Jwt.decode(credential);
479
586
  const jwtHeader = jwtDecoded.header;
480
587
  const jwtPayload = jwtDecoded.payload;
481
588
  const jwtSignature = jwtDecoded.signature;
@@ -510,7 +617,7 @@ export class EntityStorageIdentityConnector {
510
617
  method: jwtHeader.kid
511
618
  });
512
619
  }
513
- await Jwt.verifySignature(credentialJwt, await Jwk.toCryptoKey(didMethod.publicKeyJwk));
620
+ await Jwt.verifySignature(credential, await Jwk.toCryptoKey(didMethod.publicKeyJwk));
514
621
  const verifiableCredential = jwtPayload.vc;
515
622
  if (Is.object(verifiableCredential)) {
516
623
  if (Is.string(jwtPayload.jti)) {
@@ -557,7 +664,7 @@ export class EntityStorageIdentityConnector {
557
664
  * @param controller The controller of the identity who can make changes.
558
665
  * @param issuerDocumentId The id of the document to update the revocation list for.
559
666
  * @param credentialIndices The revocation bitmap index or indices to revoke.
560
- * @returns Nothing.
667
+ * @returns A promise that resolves when the credentials have been revoked.
561
668
  */
562
669
  async revokeVerifiableCredentials(controller, issuerDocumentId, credentialIndices) {
563
670
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
@@ -597,7 +704,7 @@ export class EntityStorageIdentityConnector {
597
704
  * @param controller The controller of the identity who can make changes.
598
705
  * @param issuerDocumentId The id of the document to update the revocation list for.
599
706
  * @param credentialIndices The revocation bitmap index or indices to un revoke.
600
- * @returns Nothing.
707
+ * @returns A promise that resolves when the credentials have been unrevoked.
601
708
  */
602
709
  async unrevokeVerifiableCredentials(controller, issuerDocumentId, credentialIndices) {
603
710
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
@@ -640,11 +747,14 @@ export class EntityStorageIdentityConnector {
640
747
  * @param contexts The contexts for the data stored in the verifiable credential.
641
748
  * @param types The types for the data stored in the verifiable credential.
642
749
  * @param verifiableCredentials The credentials to use for creating the presentation in jwt format.
643
- * @param expiresInMinutes The time in minutes for the presentation to expire.
750
+ * @param options Additional options for creating the verifiable presentation.
751
+ * @param options.expirationDate The date the verifiable presentation is valid until.
752
+ * @param options.jwtHeaderFields Additional fields to add to the JWT header.
753
+ * @param options.jwtPayloadFields Additional fields to add to the JWT payload.
644
754
  * @returns The created verifiable presentation and its token.
645
755
  * @throws NotFoundError if the id can not be resolved.
646
756
  */
647
- async createVerifiablePresentation(controller, verificationMethodId, presentationId, contexts, types, verifiableCredentials, expiresInMinutes) {
757
+ async createVerifiablePresentation(controller, verificationMethodId, presentationId, contexts, types, verifiableCredentials, options) {
648
758
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
649
759
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "verificationMethodId", verificationMethodId);
650
760
  if (Is.array(types)) {
@@ -654,8 +764,8 @@ export class EntityStorageIdentityConnector {
654
764
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "types", types);
655
765
  }
656
766
  Guards.arrayValue(EntityStorageIdentityConnector.CLASS_NAME, "verifiableCredentials", verifiableCredentials);
657
- if (!Is.undefined(expiresInMinutes)) {
658
- Guards.integer(EntityStorageIdentityConnector.CLASS_NAME, "expiresInMinutes", expiresInMinutes);
767
+ if (!Is.undefined(options?.expirationDate)) {
768
+ Guards.date(EntityStorageIdentityConnector.CLASS_NAME, "options.expirationDate", options.expirationDate);
659
769
  }
660
770
  try {
661
771
  const idParts = DocumentHelper.parseId(verificationMethodId);
@@ -693,31 +803,37 @@ export class EntityStorageIdentityConnector {
693
803
  else if (Is.stringValue(types)) {
694
804
  finalTypes.push(types);
695
805
  }
806
+ const combinedContext = JsonLdProcessor.combineContexts(DidContexts.ContextVCv1, contexts) ??
807
+ DidContexts.ContextVCv1;
696
808
  const verifiablePresentation = {
697
- "@context": JsonLdProcessor.combineContexts(DidContexts.NamespaceVCv1, contexts),
809
+ "@context": combinedContext,
698
810
  id: presentationId,
699
811
  type: finalTypes,
700
812
  verifiableCredential: verifiableCredentials,
701
813
  holder: idParts.id
702
814
  };
703
815
  const jwtHeader = {
816
+ ...options?.jwtHeaderFields,
704
817
  kid: didMethod.id,
705
818
  typ: "JWT",
706
- alg: "EdDSA"
819
+ alg: JwsAlgorithms.EdDSA
707
820
  };
708
821
  const jwtVp = ObjectHelper.pick(ObjectHelper.clone(verifiablePresentation), [
709
822
  "@context",
710
823
  "type",
711
824
  "verifiableCredential"
712
825
  ]);
826
+ // Add the proof to the VP after extracting the jwt data
827
+ // as the jwt does not include the proof
828
+ verifiablePresentation.proof = await this.createProof(controller, verificationMethodId, ProofTypes.DataIntegrityProof, JsonLdHelper.toNodeObject(verifiablePresentation));
713
829
  const jwtPayload = {
714
- iss: idParts.id,
830
+ ...options?.jwtPayloadFields,
831
+ iss: verifiablePresentation.holder,
715
832
  nbf: Math.floor(Date.now() / 1000),
716
833
  vp: jwtVp
717
834
  };
718
- if (Is.integer(expiresInMinutes)) {
719
- const expiresInSeconds = expiresInMinutes * 60;
720
- jwtPayload.exp = Math.floor(Date.now() / 1000) + expiresInSeconds;
835
+ if (Is.date(options?.expirationDate)) {
836
+ jwtPayload.exp = Math.floor(options.expirationDate.getTime() / 1000);
721
837
  }
722
838
  const signature = await Jwt.encodeWithSigner(jwtHeader, jwtPayload, async (header, payload) => VaultConnectorHelper.jwtSigner(this._vaultConnector, EntityStorageIdentityConnector.buildVaultKey(idParts.id, idParts.fragment ?? ""), header, payload));
723
839
  return {
@@ -731,11 +847,19 @@ export class EntityStorageIdentityConnector {
731
847
  }
732
848
  /**
733
849
  * Check a verifiable presentation is valid.
734
- * @param presentationJwt The presentation to verify.
850
+ * @param presentation The presentation to verify.
735
851
  * @returns The presentation stored in the jwt and the revocation status.
736
852
  */
737
- async checkVerifiablePresentation(presentationJwt) {
738
- Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "presentationJwt", presentationJwt);
853
+ async checkVerifiablePresentation(presentation) {
854
+ if (Is.object(presentation)) {
855
+ const { proof, ...doc } = presentation;
856
+ const proofEntry = ArrayHelper.fromObjectOrArray(proof)[0];
857
+ Guards.objectValue(EntityStorageIdentityConnector.CLASS_NAME, "proofEntry", proofEntry);
858
+ await this.verifyProof(JsonLdHelper.toNodeObject(doc), proofEntry);
859
+ return { revoked: false, verifiablePresentation: doc };
860
+ }
861
+ Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "presentation", presentation);
862
+ const presentationJwt = presentation;
739
863
  try {
740
864
  const jwtDecoded = await Jwt.decode(presentationJwt);
741
865
  const jwtHeader = jwtDecoded.header;
@@ -771,7 +895,7 @@ export class EntityStorageIdentityConnector {
771
895
  }
772
896
  await EntityStorageIdentityConnector.verifyDocument(issuerDidDocument, this._vaultConnector);
773
897
  issuers.push({
774
- "@context": DidContexts.Namespace,
898
+ "@context": DidContexts.Context,
775
899
  ...issuerDidDocument
776
900
  });
777
901
  const vc = jwt.payload.vc;
@@ -779,14 +903,14 @@ export class EntityStorageIdentityConnector {
779
903
  const credentialStatus = vc.credentialStatus;
780
904
  if (Is.object(credentialStatus)) {
781
905
  revoked = await this.checkRevocation({
782
- "@context": DidContexts.Namespace,
906
+ "@context": DidContexts.Context,
783
907
  ...issuerDidDocument
784
908
  }, credentialStatus.revocationBitmapIndex);
785
909
  }
786
910
  else if (Is.arrayValue(credentialStatus)) {
787
911
  for (let i = 0; i < credentialStatus.length; i++) {
788
912
  revoked = await this.checkRevocation({
789
- "@context": DidContexts.Namespace,
913
+ "@context": DidContexts.Context,
790
914
  ...issuerDidDocument
791
915
  }, credentialStatus[i].revocationBitmapIndex);
792
916
  if (revoked) {
@@ -820,11 +944,15 @@ export class EntityStorageIdentityConnector {
820
944
  }
821
945
  /**
822
946
  * Create a proof for arbitrary data with the specified verification method.
947
+ * This method uses async signing to ensure the private key never leaves the vault,
948
+ * with algorithm validation to ensure key type compatibility.
823
949
  * @param controller The controller of the identity who can make changes.
824
950
  * @param verificationMethodId The verification method id to use.
825
951
  * @param proofType The type of proof to create.
826
952
  * @param unsecureDocument The unsecure document to create the proof for.
827
953
  * @returns The proof.
954
+ * @throws NotFoundError if the identity or method is not found.
955
+ * @throws GeneralError if algorithm doesn't match key type or proof creation fails.
828
956
  */
829
957
  async createProof(controller, verificationMethodId, proofType, unsecureDocument) {
830
958
  Guards.stringValue(EntityStorageIdentityConnector.CLASS_NAME, "controller", controller);
@@ -861,8 +989,14 @@ export class EntityStorageIdentityConnector {
861
989
  });
862
990
  }
863
991
  const vaultKey = EntityStorageIdentityConnector.buildVaultKey(didDocument.id, idParts.fragment ?? "");
864
- const key = await this._vaultConnector.getKey(vaultKey);
865
- const signedProof = await ProofHelper.createProof(proofType, unsecureDocument, ProofHelper.createUnsignedProof(proofType, verificationMethodId), await Jwk.fromEd25519Private(key.privateKey));
992
+ const keyType = await this._vaultConnector.getKeyType(vaultKey);
993
+ if (Is.undefined(keyType)) {
994
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "privateKeyMissing", {
995
+ keyId: vaultKey
996
+ });
997
+ }
998
+ const unsignedProof = ProofHelper.createUnsignedProof(proofType, verificationMethodId);
999
+ const signedProof = await ProofHelper.createProofWithSigner(proofType, unsecureDocument, unsignedProof, async (data, algorithm) => this.signWithVault(vaultKey, keyType, data, algorithm));
866
1000
  return signedProof;
867
1001
  }
868
1002
  catch (error) {
@@ -915,6 +1049,27 @@ export class EntityStorageIdentityConnector {
915
1049
  throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "verifyProofFailed", undefined, error);
916
1050
  }
917
1051
  }
1052
+ /**
1053
+ * Signs data using the vault connector with algorithm validation.
1054
+ * @param vaultKey The vault key identifier.
1055
+ * @param keyType The type of the key.
1056
+ * @param data The data to sign.
1057
+ * @param algorithm The signing algorithm.
1058
+ * @returns The signature bytes.
1059
+ * @throws GeneralError if algorithm doesn't match key type.
1060
+ * @internal
1061
+ */
1062
+ async signWithVault(vaultKey, keyType, data, algorithm) {
1063
+ if (algorithm === JwsAlgorithms.EdDSA && keyType !== VaultKeyType.Ed25519) {
1064
+ throw new GeneralError(EntityStorageIdentityConnector.CLASS_NAME, "algorithmKeyTypeMismatch", {
1065
+ algorithm,
1066
+ expectedKeyType: VaultKeyType.Ed25519,
1067
+ actualKeyType: keyType,
1068
+ keyId: vaultKey
1069
+ });
1070
+ }
1071
+ return this._vaultConnector.sign(vaultKey, data);
1072
+ }
918
1073
  /**
919
1074
  * Get all the methods from a document.
920
1075
  * @param document The document to get the methods from.
@@ -966,6 +1121,7 @@ export class EntityStorageIdentityConnector {
966
1121
  * Update the document in storage.
967
1122
  * @param controller The controller of the document.
968
1123
  * @param didDocument The did document to store.
1124
+ * @returns A promise that resolves when the document has been signed and persisted.
969
1125
  * @internal
970
1126
  */
971
1127
  async updateDocument(controller, didDocument) {