@digitalbazaar/vc 6.3.0 → 7.0.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.
@@ -1,12 +1,10 @@
1
1
  /*!
2
- * Copyright (c) 2019-2023 Digital Bazaar, Inc. All rights reserved.
2
+ * Copyright (c) 2019-2024 Digital Bazaar, Inc. All rights reserved.
3
3
  */
4
4
  import {
5
- contexts as credentialContexts
6
- } from 'credentials-context';
5
+ contexts as credentialsContexts
6
+ } from '@digitalbazaar/credentials-context';
7
7
 
8
- export const contexts = new Map();
9
-
10
- for(const [url, context] of credentialContexts.entries()) {
11
- contexts.set(url, context);
12
- }
8
+ export const contexts = new Map([
9
+ ...credentialsContexts
10
+ ]);
package/lib/helpers.js ADDED
@@ -0,0 +1,103 @@
1
+ /*!
2
+ * Copyright (c) 2023 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import {named as vcNamedContexts} from '@digitalbazaar/credentials-context';
5
+
6
+ // Z and T must be uppercase
7
+ // xml schema date time RegExp
8
+ // @see https://www.w3.org/TR/xmlschema11-2/#dateTime
9
+ export const dateRegex = new RegExp(
10
+ '-?([1-9][0-9]{3,}|0[0-9]{3})' +
11
+ '-(0[1-9]|1[0-2])' +
12
+ '-(0[1-9]|[12][0-9]|3[01])' +
13
+ 'T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?))' +
14
+ '(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?');
15
+
16
+ const CREDENTIALS_CONTEXT_V1_URL = vcNamedContexts.get('v1').id;
17
+ const CREDENTIALS_CONTEXT_V2_URL = vcNamedContexts.get('v2').id;
18
+
19
+ // mappings between credentials contexts and version numbers
20
+ const credentialsContextUrlToVersion = new Map([
21
+ [CREDENTIALS_CONTEXT_V1_URL, 1.0],
22
+ [CREDENTIALS_CONTEXT_V2_URL, 2.0]
23
+ ]);
24
+ const credentialsVersionToContextUrl = new Map([
25
+ [1.0, CREDENTIALS_CONTEXT_V1_URL],
26
+ [2.0, CREDENTIALS_CONTEXT_V2_URL]
27
+ ]);
28
+
29
+ /**
30
+ * Asserts that a context array's first item is a credentials context.
31
+ *
32
+ * @param {object} options - Options.
33
+ * @param {Array} options.context - An array of contexts.
34
+ *
35
+ * @throws {Error} - Throws if the first context
36
+ * is not a credentials context.
37
+ *
38
+ * @returns {undefined}
39
+ */
40
+ export function assertCredentialContext({context}) {
41
+ // ensure first context is credentials context url
42
+ if(!credentialsContextUrlToVersion.has(context[0])) {
43
+ // throw if the first context is not a credentials context
44
+ throw new Error(
45
+ `"${CREDENTIALS_CONTEXT_V1_URL}" or "${CREDENTIALS_CONTEXT_V2_URL}"` +
46
+ ' needs to be first in the list of contexts.');
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Throws if a Date is not in the correct format.
52
+ *
53
+ * @param {object} options - Options.
54
+ * @param {object} options.credential - A VC.
55
+ * @param {string} options.prop - A prop in the object.
56
+ *
57
+ * @throws {Error} Throws if the date is not a proper date string.
58
+ * @returns {undefined}
59
+ */
60
+ export function assertDateString({credential, prop}) {
61
+ const value = credential[prop];
62
+ if(!dateRegex.test(value)) {
63
+ throw new Error(`"${prop}" must be a valid date: ${value}`);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Turns the first context in a VC into a numbered version.
69
+ *
70
+ * @param {object} options - Options.
71
+ * @param {object} options.credential - A VC.
72
+ *
73
+ * @returns {number} A number representing the version.
74
+ */
75
+ function getContextVersion({credential} = {}) {
76
+ const firstContext = credential?.['@context']?.[0];
77
+ return credentialsContextUrlToVersion.get(firstContext);
78
+ }
79
+
80
+ /**
81
+ * Turns the first context in a VC into a numbered version.
82
+ *
83
+ * @param {object} options - Options.
84
+ * @param {number} options.version - A credentials context version.
85
+ *
86
+ * @returns {number} A number representing the version.
87
+ */
88
+ export function getContextForVersion({version}) {
89
+ return credentialsVersionToContextUrl.get(version);
90
+ }
91
+
92
+ /**
93
+ * Checks if a VC is using a specific context version.
94
+ *
95
+ * @param {object} options - Options.
96
+ * @param {object} options.credential - A VC.
97
+ * @param {number} options.version - A VC Context version
98
+ *
99
+ * @returns {boolean} If the first context matches the version.
100
+ */
101
+ export function checkContextVersion({credential, version}) {
102
+ return getContextVersion({credential}) === version;
103
+ }
package/lib/index.js CHANGED
@@ -34,27 +34,22 @@
34
34
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35
35
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
36
  */
37
+ import {
38
+ assertCredentialContext,
39
+ assertDateString,
40
+ checkContextVersion,
41
+ getContextForVersion
42
+ } from './helpers.js';
37
43
  import {documentLoader as _documentLoader} from './documentLoader.js';
38
44
  import {CredentialIssuancePurpose} from './CredentialIssuancePurpose.js';
39
45
  import jsigs from 'jsonld-signatures';
40
46
  import jsonld from 'jsonld';
41
- export const defaultDocumentLoader =
42
- jsigs.extendContextLoader(_documentLoader);
43
- import * as credentialsContext from 'credentials-context';
44
47
 
45
48
  const {AssertionProofPurpose, AuthenticationProofPurpose} = jsigs.purposes;
46
- const {constants: {CREDENTIALS_CONTEXT_V1_URL}} = credentialsContext;
47
-
49
+ export {dateRegex} from './helpers.js';
50
+ export const defaultDocumentLoader = jsigs.extendContextLoader(_documentLoader);
48
51
  export {CredentialIssuancePurpose};
49
52
 
50
- // Z and T can be lowercase
51
- // RFC3339 regex
52
- export const dateRegex = new RegExp('^(\\d{4})-(0[1-9]|1[0-2])-' +
53
- '(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):' +
54
- '([0-5][0-9]):([0-5][0-9]|60)' +
55
- '(\\.[0-9]+)?(Z|(\\+|-)([01][0-9]|2[0-3]):' +
56
- '([0-5][0-9]))$', 'i');
57
-
58
53
  /**
59
54
  * @typedef {object} LinkedDataSignature
60
55
  */
@@ -131,9 +126,10 @@ export async function issue({
131
126
  if(!credential) {
132
127
  throw new TypeError('"credential" parameter is required for issuing.');
133
128
  }
134
-
135
- // Set the issuance date to now(), if missing
136
- if(!credential.issuanceDate) {
129
+ if(checkContextVersion({
130
+ credential,
131
+ version: 1.0
132
+ }) && !credential.issuanceDate) {
137
133
  const now = (new Date()).toJSON();
138
134
  credential.issuanceDate = `${now.slice(0, now.length - 5)}Z`;
139
135
  }
@@ -343,7 +339,6 @@ async function _verifyCredential(options = {}) {
343
339
  result.verified = false;
344
340
  }
345
341
  }
346
-
347
342
  return result;
348
343
  }
349
344
 
@@ -357,6 +352,7 @@ async function _verifyCredential(options = {}) {
357
352
  * @param {string} [options.holder] - Optional presentation holder url.
358
353
  * @param {string|Date} [options.now] - A string representing date time in
359
354
  * ISO 8601 format or an instance of Date. Defaults to current date time.
355
+ * @param {number} [options.version = 2.0] - The VC context version to use.
360
356
  *
361
357
  * @throws {TypeError} If verifiableCredential param is missing.
362
358
  * @throws {Error} If the credential (or the presentation params) are missing
@@ -366,10 +362,11 @@ async function _verifyCredential(options = {}) {
366
362
  * VerifiablePresentation.
367
363
  */
368
364
  export function createPresentation({
369
- verifiableCredential, id, holder, now
365
+ verifiableCredential, id, holder, now, version = 2.0
370
366
  } = {}) {
367
+ const initialContext = getContextForVersion({version});
371
368
  const presentation = {
372
- '@context': [CREDENTIALS_CONTEXT_V1_URL],
369
+ '@context': [initialContext],
373
370
  type: ['VerifiablePresentation']
374
371
  };
375
372
  if(verifiableCredential) {
@@ -548,13 +545,7 @@ export function _checkPresentation(presentation) {
548
545
  // normalize to an array to allow the common case of context being a string
549
546
  const context = Array.isArray(presentation['@context']) ?
550
547
  presentation['@context'] : [presentation['@context']];
551
-
552
- // ensure first context is 'https://www.w3.org/2018/credentials/v1'
553
- if(context[0] !== CREDENTIALS_CONTEXT_V1_URL) {
554
- throw new Error(
555
- `"${CREDENTIALS_CONTEXT_V1_URL}" needs to be first in the ` +
556
- 'list of contexts.');
557
- }
548
+ assertCredentialContext({context});
558
549
 
559
550
  const types = jsonld.getValues(presentation, 'type');
560
551
 
@@ -564,6 +555,15 @@ export function _checkPresentation(presentation) {
564
555
  }
565
556
  }
566
557
 
558
+ // these props of a VC must be an object with a type
559
+ // if present in a VC or VP
560
+ const mustHaveType = [
561
+ 'proof',
562
+ 'credentialStatus',
563
+ 'termsOfUse',
564
+ 'evidence'
565
+ ];
566
+
567
567
  // export for testing
568
568
  /**
569
569
  * @param {object} options - The options.
@@ -583,12 +583,7 @@ export function _checkCredential({
583
583
  if(typeof now === 'string') {
584
584
  now = new Date(now);
585
585
  }
586
- // ensure first context is 'https://www.w3.org/2018/credentials/v1'
587
- if(credential['@context'][0] !== CREDENTIALS_CONTEXT_V1_URL) {
588
- throw new Error(
589
- `"${CREDENTIALS_CONTEXT_V1_URL}" needs to be first in the ` +
590
- 'list of contexts.');
591
- }
586
+ assertCredentialContext({context: credential['@context']});
592
587
 
593
588
  // check type presence and cardinality
594
589
  if(!credential.type) {
@@ -599,47 +594,71 @@ export function _checkCredential({
599
594
  throw new Error('"type" must include `VerifiableCredential`.');
600
595
  }
601
596
 
602
- if(!credential.credentialSubject) {
603
- throw new Error('"credentialSubject" property is required.');
604
- }
605
-
606
- // If credentialSubject.id is present and is not a URI, reject it
607
- if(credential.credentialSubject.id) {
608
- _validateUriId({
609
- id: credential.credentialSubject.id, propertyName: 'credentialSubject.id'
610
- });
611
- }
597
+ _checkCredentialSubjects({credential});
612
598
 
613
599
  if(!credential.issuer) {
614
600
  throw new Error('"issuer" property is required.');
615
601
  }
602
+ if(checkContextVersion({credential, version: 1.0})) {
603
+ // check issuanceDate exists
604
+ if(!credential.issuanceDate) {
605
+ throw new Error('"issuanceDate" property is required.');
606
+ }
607
+ // check issuanceDate format on issue
608
+ assertDateString({credential, prop: 'issuanceDate'});
616
609
 
617
- // check issuanceDate cardinality
618
- if(jsonld.getValues(credential, 'issuanceDate').length > 1) {
619
- throw new Error('"issuanceDate" property can only have one value.');
620
- }
621
-
622
- // check issued is a date
623
- if(!credential.issuanceDate) {
624
- throw new Error('"issuanceDate" property is required.');
625
- }
626
-
627
- if('issuanceDate' in credential) {
628
- let {issuanceDate} = credential;
629
- if(!dateRegex.test(issuanceDate)) {
630
- throw new Error(`"issuanceDate" must be a valid date: ${issuanceDate}`);
610
+ // check issuanceDate cardinality
611
+ if(jsonld.getValues(credential, 'issuanceDate').length > 1) {
612
+ throw new Error('"issuanceDate" property can only have one value.');
613
+ }
614
+ // optionally check expirationDate
615
+ if('expirationDate' in credential) {
616
+ // check if `expirationDate` property is a date
617
+ assertDateString({credential, prop: 'expirationDate'});
618
+ if(mode === 'verify') {
619
+ // check if `now` is after `expirationDate`
620
+ if(now > new Date(credential.expirationDate)) {
621
+ throw new Error('Credential has expired.');
622
+ }
623
+ }
631
624
  }
632
625
  // check if `now` is before `issuanceDate` on verification
633
626
  if(mode === 'verify') {
634
- issuanceDate = new Date(issuanceDate);
627
+ const issuanceDate = new Date(credential.issuanceDate);
635
628
  if(now < issuanceDate) {
636
629
  throw new Error(
637
630
  `The current date time (${now.toISOString()}) is before the ` +
638
- `"issuanceDate" (${issuanceDate.toISOString()}).`);
631
+ `"issuanceDate" (${credential.issuanceDate}).`);
632
+ }
633
+ }
634
+ }
635
+ if(checkContextVersion({credential, version: 2.0})) {
636
+ // check if 'validUntil' and 'validFrom'
637
+ let {validUntil, validFrom} = credential;
638
+ if(validUntil) {
639
+ assertDateString({credential, prop: 'validUntil'});
640
+ if(mode === 'verify') {
641
+ validUntil = new Date(credential.validUntil);
642
+ if(now > validUntil) {
643
+ throw new Error(
644
+ `The current date time (${now.toISOString()}) is after ` +
645
+ `"validUntil" (${credential.validUntil}).`);
646
+ }
647
+ }
648
+ }
649
+ if(validFrom) {
650
+ assertDateString({credential, prop: 'validFrom'});
651
+ if(mode === 'verify') {
652
+ // check if `now` is before `validFrom`
653
+ validFrom = new Date(credential.validFrom);
654
+ if(now < validFrom) {
655
+ throw new Error(
656
+ `The current date time (${now.toISOString()}) is before ` +
657
+ `"validFrom" (${credential.validFrom}).`);
658
+ }
639
659
  }
640
660
  }
641
661
  }
642
-
643
662
  // check issuer cardinality
644
663
  if(jsonld.getValues(credential, 'issuer').length > 1) {
645
664
  throw new Error('"issuer" property can only have one value.');
@@ -654,17 +673,18 @@ export function _checkCredential({
654
673
  _validateUriId({id: issuer, propertyName: 'issuer'});
655
674
  }
656
675
 
657
- if('credentialStatus' in credential) {
658
- const {credentialStatus} = credential;
659
- if(Array.isArray(credentialStatus) ?
660
- credentialStatus.some(cs => !cs.id) : !credentialStatus.id) {
661
- throw new Error('"credentialStatus" must include an id.');
676
+ // check credentialStatus
677
+ jsonld.getValues(credential, 'credentialStatus').forEach(cs => {
678
+ // check if optional "id" is a URL
679
+ if('id' in cs) {
680
+ _validateUriId({id: cs.id, propertyName: 'credentialStatus.id'});
662
681
  }
663
- if(Array.isArray(credentialStatus) ?
664
- credentialStatus.some(cs => !cs.type) : !credentialStatus.type) {
682
+
683
+ // check "type" present
684
+ if(!cs.type) {
665
685
  throw new Error('"credentialStatus" must include a type.');
666
686
  }
667
- }
687
+ });
668
688
 
669
689
  // check evidences are URLs
670
690
  jsonld.getValues(credential, 'evidence').forEach(evidence => {
@@ -674,20 +694,144 @@ export function _checkCredential({
674
694
  }
675
695
  });
676
696
 
677
- if('expirationDate' in credential) {
678
- const {expirationDate} = credential;
679
- // check if `expirationDate` property is a date
680
- if(!dateRegex.test(expirationDate)) {
681
- throw new Error(
682
- `"expirationDate" must be a valid date: ${expirationDate}`);
683
- }
684
- // check if `now` is after `expirationDate`
685
- if(now > new Date(expirationDate)) {
686
- throw new Error('Credential has expired.');
697
+ // check if properties that require a type are
698
+ // defined, objects, and objects with types
699
+ for(const prop of mustHaveType) {
700
+ if(prop in credential) {
701
+ const _value = credential[prop];
702
+ if(Array.isArray(_value)) {
703
+ _value.forEach(entry => _checkTypedObject(entry, prop));
704
+ continue;
705
+ }
706
+ _checkTypedObject(_value, prop);
687
707
  }
688
708
  }
689
709
  }
690
710
 
711
+ /**
712
+ * @private
713
+ * Checks that a property is non-empty object with
714
+ * property type.
715
+ *
716
+ * @param {object} obj - A potential object.
717
+ * @param {string} name - The name of the property.
718
+ *
719
+ * @throws {Error} if the property is not an object with a type.
720
+ *
721
+ * @returns {undefined} - Returns on success.
722
+ */
723
+ function _checkTypedObject(obj, name) {
724
+ if(!isObject(obj)) {
725
+ throw new Error(`property "${name}" must be an object.`);
726
+ }
727
+ if(_emptyObject(obj)) {
728
+ throw new Error(`property "${name}" can not be an empty object.`);
729
+ }
730
+ if(!('type' in obj)) {
731
+ throw new Error(`property "${name}" must have property type.`);
732
+ }
733
+ }
734
+
735
+ /**
736
+ * @private
737
+ * Takes in a credential and checks the credentialSubject(s)
738
+ *
739
+ * @param {object} options - Options.
740
+ * @param {object} options.credential - The credential to check.
741
+ *
742
+ * @throws {Error} error - Throws on errors in the credential subject.
743
+ *
744
+ * @returns {undefined} - Returns on success.
745
+ */
746
+ function _checkCredentialSubjects({credential}) {
747
+ if(!credential?.credentialSubject) {
748
+ throw new Error('"credentialSubject" property is required.');
749
+ }
750
+ if(Array.isArray(credential?.credentialSubject)) {
751
+ return credential?.credentialSubject.map(
752
+ subject => _checkCredentialSubject({subject}));
753
+ }
754
+ return _checkCredentialSubject({subject: credential?.credentialSubject});
755
+ }
756
+
757
+ /**
758
+ * @private
759
+ *
760
+ * Checks a credential subject is valid.
761
+ *
762
+ * @param {object} options - Options.
763
+ * @param {object} options.subject - A potential credential subject.
764
+ *
765
+ * @throws {Error} If the credentialSubject is not valid.
766
+ *
767
+ * @returns {undefined} Returns on success.
768
+ */
769
+ function _checkCredentialSubject({subject}) {
770
+ if(isObject(subject) === false) {
771
+ throw new Error('"credentialSubject" must be a non-null object.');
772
+ }
773
+ if(_emptyObject(subject)) {
774
+ throw new Error('"credentialSubject" must make a claim.');
775
+ }
776
+ // If credentialSubject.id is present and is not a URI, reject it
777
+ if(subject.id) {
778
+ _validateUriId({
779
+ id: subject.id, propertyName: 'credentialSubject.id'
780
+ });
781
+ }
782
+ }
783
+
784
+ /**
785
+ * @private
786
+ * Checks if parameter is an object.
787
+ *
788
+ * @param {object} obj - A potential object.
789
+ *
790
+ * @returns {boolean} - Returns false if not an object or null.
791
+ */
792
+ function isObject(obj) {
793
+ // return false for null even though it has type object
794
+ if(obj === null) {
795
+ return false;
796
+ }
797
+ // if something has type object and is not null return true
798
+ if((typeof obj) === 'object') {
799
+ return true;
800
+ }
801
+ // return false for strings, symbols, etc.
802
+ return false;
803
+ }
804
+
805
+ /**
806
+ * @private
807
+ * Is it an empty object?
808
+ *
809
+ * @param {object} obj - A potential object.
810
+ *
811
+ * @returns {boolean} - Is it empty?
812
+ */
813
+ function _emptyObject(obj) {
814
+ // if the parameter is not an object return true
815
+ // as a non-object is an empty object
816
+ if(!isObject(obj)) {
817
+ return true;
818
+ }
819
+ return Object.keys(obj).length === 0;
820
+ }
821
+
822
+ /**
823
+ * @private
824
+ *
825
+ * Validates if an ID is a URL.
826
+ *
827
+ * @param {object} options - Options.
828
+ * @param {string} options.id - the id.
829
+ * @param {string} options.propertyName - The property name.
830
+ *
831
+ * @throws {Error} Throws if an id is not a URL.
832
+ *
833
+ * @returns {undefined} Returns on success.
834
+ */
691
835
  function _validateUriId({id, propertyName}) {
692
836
  let parsed;
693
837
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalbazaar/vc",
3
- "version": "6.3.0",
3
+ "version": "7.0.0",
4
4
  "description": "Verifiable Credentials JavaScript library.",
5
5
  "homepage": "https://github.com/digitalbazaar/vc",
6
6
  "author": {
@@ -28,43 +28,45 @@
28
28
  "lib/**/*.js"
29
29
  ],
30
30
  "dependencies": {
31
- "credentials-context": "^2.0.0",
31
+ "@digitalbazaar/credentials-context": "^3.1.0",
32
+ "ed25519-signature-2018-context": "^1.1.0",
32
33
  "jsonld": "^8.3.1",
33
34
  "jsonld-signatures": "^11.2.1"
34
35
  },
35
36
  "devDependencies": {
36
- "@digitalbazaar/bbs-2023-cryptosuite": "^1.0.0",
37
- "@digitalbazaar/bls12-381-multikey": "^1.1.1",
37
+ "@digitalbazaar/bbs-2023-cryptosuite": "^1.2.0",
38
+ "@digitalbazaar/bls12-381-multikey": "^1.3.0",
38
39
  "@digitalbazaar/credentials-examples-context": "^1.0.0",
39
- "@digitalbazaar/data-integrity": "^2.0.0",
40
- "@digitalbazaar/data-integrity-context": "^2.0.0",
41
- "@digitalbazaar/ecdsa-multikey": "^1.6.0",
42
- "@digitalbazaar/ecdsa-sd-2023-cryptosuite": "^3.0.0",
40
+ "@digitalbazaar/data-integrity": "^2.2.0",
41
+ "@digitalbazaar/data-integrity-context": "^2.0.1",
42
+ "@digitalbazaar/ecdsa-multikey": "^1.7.0",
43
+ "@digitalbazaar/ecdsa-sd-2023-cryptosuite": "^3.2.1",
43
44
  "@digitalbazaar/ed25519-signature-2018": "^4.0.0",
44
45
  "@digitalbazaar/ed25519-verification-key-2018": "^4.0.0",
45
- "@digitalbazaar/multikey-context": "^1.0.0",
46
+ "@digitalbazaar/multikey-context": "^2.0.1",
46
47
  "@digitalbazaar/odrl-context": "^1.0.0",
47
- "c8": "^8.0.1",
48
- "chai": "^4.3.7",
48
+ "c8": "^10.1.2",
49
+ "chai": "^4.5.0",
49
50
  "cross-env": "^7.0.3",
50
51
  "did-context": "^3.1.1",
51
52
  "did-veres-one": "^16.0.0",
52
- "eslint": "^8.53.0",
53
- "eslint-config-digitalbazaar": "^5.0.1",
54
- "eslint-plugin-jsdoc": "^46.9.0",
55
- "eslint-plugin-unicorn": "^49.0.0",
56
- "karma": "^6.4.1",
53
+ "eslint": "^8.57.0",
54
+ "eslint-config-digitalbazaar": "^5.2.0",
55
+ "eslint-plugin-jsdoc": "^48.10.2",
56
+ "eslint-plugin-unicorn": "^55.0.0",
57
+ "karma": "^6.4.4",
57
58
  "karma-chai": "^0.1.0",
58
- "karma-chrome-launcher": "^3.1.1",
59
+ "karma-chrome-launcher": "^3.2.0",
59
60
  "karma-mocha": "^2.0.1",
60
61
  "karma-mocha-reporter": "^2.2.5",
61
62
  "karma-sourcemap-loader": "^0.4.0",
62
- "karma-webpack": "^5.0.0",
63
- "mocha": "^10.2.0",
63
+ "karma-webpack": "^5.0.1",
64
+ "klona": "^2.0.6",
65
+ "mocha": "^10.7.0",
64
66
  "mocha-lcov-reporter": "^1.3.0",
65
- "uuid": "^9.0.0",
67
+ "uuid": "^10.0.0",
66
68
  "veres-one-context": "^12.0.0",
67
- "webpack": "^5.75.0"
69
+ "webpack": "^5.93.0"
68
70
  },
69
71
  "c8": {
70
72
  "reporter": [