@digitalbazaar/vc 6.0.1 → 6.1.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.
Files changed (3) hide show
  1. package/README.md +91 -1
  2. package/lib/index.js +56 -12
  3. package/package.json +16 -11
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Verifiable Credentials JS Library _(@digitalbazaar/vc)_
2
2
 
3
- [![Build Status](https://img.shields.io/github/workflow/status/digitalbazaar/vc/Node.js%20CI)](https://github.com/digitalbazaar/vc/actions?query=workflow%3A%22Node.js+CI%22)
3
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/digitalbazaar/vc/main.yml)](https://github.com/digitalbazaar/vc/actions/workflow/main.yml)
4
4
  [![NPM Version](https://img.shields.io/npm/v/@digitalbazaar/vc.svg)](https://npm.im/@digitalbazaar/vc)
5
5
 
6
6
  > A Javascript library for issuing and verifying Verifiable Credentials.
@@ -116,6 +116,75 @@ const signedVC = await vc.issue({credential, suite, documentLoader});
116
116
  console.log(JSON.stringify(signedVC, null, 2));
117
117
  ```
118
118
 
119
+ ### Deriving a Selective Disclosure Verifiable Credential
120
+
121
+ Pre-requisites:
122
+
123
+ * You have a verifiable credential that was issued using a cryptosuite that
124
+ support selective disclosure, such as `ecdsa-sd-2023`
125
+ * If you're using a custom `@context`, make sure it's resolvable
126
+
127
+ ```js
128
+ import * as vc from '@digitalbazaar/vc';
129
+ import * as ecdsaSd2023Cryptosuite from
130
+ '@digitalbazaar/ecdsa-sd-2023-cryptosuite';
131
+ import {DataIntegrityProof} from '@digitalbazaar/data-integrity';
132
+
133
+ const {
134
+ createDiscloseCryptosuite,
135
+ createSignCryptosuite,
136
+ createVerifyCryptosuite
137
+ } = ecdsaSd2023Cryptosuite;
138
+
139
+ // Sample signed credential
140
+ const credential = {
141
+ "@context": [
142
+ "https://www.w3.org/2018/credentials/v1",
143
+ "https://www.w3.org/2018/credentials/examples/v1",
144
+ "https://w3id.org/security/data-integrity/v2"
145
+ ],
146
+ "id": "http://example.edu/credentials/1872",
147
+ "type": [
148
+ "VerifiableCredential",
149
+ "AlumniCredential"
150
+ ],
151
+ "issuer": "https://example.edu/issuers/565049",
152
+ "issuanceDate": "2010-01-01T19:23:24Z",
153
+ "credentialSubject": {
154
+ "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
155
+ "alumniOf": "<span lang=\"en\">Example University</span>"
156
+ },
157
+ "proof": {
158
+ "id": "urn:uuid:2ef8c7ce-a4da-44b4-ba7f-3d43eaf1e50c",
159
+ "type": "DataIntegrityProof",
160
+ "created": "2023-11-13T22:58:06Z",
161
+ "verificationMethod": "https://example.edu/issuers/keys/2",
162
+ "cryptosuite": "ecdsa-sd-2023",
163
+ "proofPurpose": "assertionMethod",
164
+ "proofValue": "u2V0AhVhAtYPKUQxwULXzMdsAfqtipsiX6YEPURYSBFYxoFY-v0vCPyAs1Ckyy61Wtk3xZWyBGNaEr3w0wQiJHHd5B9uR-1gjgCQCVtFPMk-ECi0CJFYv_GTjCChf8St0FQjuExTAnwP0-ipYIOHSun3YqabOfNe2DYFkHBTZa0Csf1a7YUDW8hhsOHqTglhA8aqnyanT-Ybo2-aHBTcI-UmHX0iluGb2IxoHLLhQoOPm2rDW0eB04Fa2Dh6WMKoOl_Bz3wZZDGQ31XoGrQvgIlhAo8qspvC-QQ-xI3KADiA12sO5LRsZ7hl9ozoJEECVsDOKlxWd-dhices5b2ZQIiiRE9XxxJx8YuwCMoD2bRLbOIJtL2lzc3VhbmNlRGF0ZWcvaXNzdWVy"
165
+ }
166
+ };
167
+
168
+ // note no `signer` needed; the selective disclosure credential will be
169
+ // derived from the base proof already provided by the issuer
170
+ const ecdsaSdDeriveSuite = new DataIntegrityProof({
171
+ cryptosuite: createDiscloseCryptosuite({
172
+ // the ID of the base proof to convert to a disclosure proof
173
+ proofId: 'urn:uuid:da088899-3439-41ea-a580-af3f1cf98cd3',
174
+ // selectively disclose the entire credential subject; different JSON
175
+ // pointers could be provided to selectively disclose different information;
176
+ // the issuer will have mandatory fields that will be automatically
177
+ // disclosed such as the `issuer` and `issuanceDate` fields
178
+ selectivePointers: [
179
+ '/credentialSubject'
180
+ ]
181
+ })
182
+ });
183
+
184
+ const derivedVC = await vc.derive({credential, suite, documentLoader});
185
+ console.log(JSON.stringify(derivedVC, null, 2));
186
+ ```
187
+
119
188
  ### Creating a Verifiable Presentation
120
189
 
121
190
  Pre-requisites:
@@ -355,6 +424,27 @@ const result = await vc.verifyCredential({credential, suite, documentLoader});
355
424
  // {valid: true}
356
425
  ```
357
426
 
427
+ To verify a selective disclosure verifiable credential ensure the suite
428
+ supports it, for example:
429
+
430
+ ```js
431
+ import * as ecdsaSd2023Cryptosuite from
432
+ '@digitalbazaar/ecdsa-sd-2023-cryptosuite';
433
+ import {DataIntegrityProof} from '@digitalbazaar/data-integrity';
434
+
435
+ const {
436
+ createDiscloseCryptosuite,
437
+ createSignCryptosuite,
438
+ createVerifyCryptosuite
439
+ } = ecdsaSd2023Cryptosuite;
440
+
441
+ const suite = new DataIntegrityProof({
442
+ cryptosuite: createVerifyCryptosuite()
443
+ });
444
+ const result = await vc.verifyCredential({credential, suite, documentLoader});
445
+ // {valid: true}
446
+ ```
447
+
358
448
  To verify a verifiable credential with a custom `@context` field use a
359
449
  [custom documentLoader](#custom-documentLoader)
360
450
 
package/lib/index.js CHANGED
@@ -42,7 +42,7 @@ export const defaultDocumentLoader =
42
42
  jsigs.extendContextLoader(_documentLoader);
43
43
  import * as credentialsContext from 'credentials-context';
44
44
 
45
- const {AuthenticationProofPurpose} = jsigs.purposes;
45
+ const {AssertionProofPurpose, AuthenticationProofPurpose} = jsigs.purposes;
46
46
  const {constants: {CREDENTIALS_CONTEXT_V1_URL}} = credentialsContext;
47
47
 
48
48
  export {CredentialIssuancePurpose};
@@ -99,7 +99,7 @@ export const dateRegex = new RegExp('^(\\d{4})-(0[1-9]|1[0-2])-' +
99
99
  *
100
100
  * @param {object} options.credential - Base credential document.
101
101
  * @param {LinkedDataSignature} options.suite - Signature suite (with private
102
- * key material), passed in to sign().
102
+ * key material or an API to use it), passed in to sign().
103
103
  *
104
104
  * @param {ProofPurpose} [options.purpose] - A ProofPurpose. If not specified,
105
105
  * a default purpose will be created.
@@ -135,15 +135,54 @@ export async function issue({
135
135
  // Set the issuance date to now(), if missing
136
136
  if(!credential.issuanceDate) {
137
137
  const now = (new Date()).toJSON();
138
- credential.issuanceDate = `${now.substr(0, now.length - 5)}Z`;
138
+ credential.issuanceDate = `${now.slice(0, now.length - 5)}Z`;
139
139
  }
140
140
 
141
141
  // run common credential checks
142
- _checkCredential({credential, now});
142
+ _checkCredential({credential, now, mode: 'issue'});
143
143
 
144
144
  return jsigs.sign(credential, {purpose, documentLoader, suite});
145
145
  }
146
146
 
147
+ /**
148
+ * Derives a proof from the given verifiable credential, resulting in a new
149
+ * verifiable credential. This method is usually used to generate selective
150
+ * disclosure and / or unlinkable proofs.
151
+ *
152
+ * @param {object} [options={}] - The options to use.
153
+ *
154
+ * @param {object} options.verifiableCredential - The verifiable credential
155
+ * containing a base proof to derive another proof from.
156
+ * @param {LinkedDataSignature} options.suite - Derived proof signature suite.
157
+ *
158
+ * Other optional params passed to `derive()`:
159
+ * @param {object} [options.documentLoader] - A document loader.
160
+ *
161
+ * @throws {Error} If missing required properties.
162
+ *
163
+ * @returns {Promise<VerifiableCredential>} Resolves on completion.
164
+ */
165
+ export async function derive({
166
+ verifiableCredential, suite,
167
+ documentLoader = defaultDocumentLoader
168
+ } = {}) {
169
+ if(!verifiableCredential) {
170
+ throw new TypeError('"credential" parameter is required for deriving.');
171
+ }
172
+ if(!suite) {
173
+ throw new TypeError('"suite" parameter is required for deriving.');
174
+ }
175
+
176
+ // run common credential checks
177
+ _checkCredential({credential: verifiableCredential, mode: 'issue'});
178
+
179
+ return jsigs.derive(verifiableCredential, {
180
+ purpose: new AssertionProofPurpose(),
181
+ documentLoader,
182
+ suite
183
+ });
184
+ }
185
+
147
186
  /**
148
187
  * Verifies a verifiable presentation:
149
188
  * - Checks that the presentation is well-formed
@@ -484,7 +523,6 @@ async function _verifyPresentation(options = {}) {
484
523
  * or a string that is an id.
485
524
  * @returns {string|undefined} Either an id or undefined.
486
525
  * @private
487
- *
488
526
  */
489
527
  function _getId(obj) {
490
528
  if(typeof obj === 'string') {
@@ -532,11 +570,15 @@ export function _checkPresentation(presentation) {
532
570
  * VerifiableCredential.
533
571
  * @param {string|Date} [options.now] - A string representing date time in
534
572
  * ISO 8601 format or an instance of Date. Defaults to current date time.
573
+ * @param {string} [options.mode] - The mode of operation for this
574
+ * validation function, either `issue` or `verify`.
535
575
  *
536
576
  * @throws {Error}
537
577
  * @private
538
578
  */
539
- export function _checkCredential({credential, now = new Date()}) {
579
+ export function _checkCredential({
580
+ credential, now = new Date(), mode = 'verify'
581
+ } = {}) {
540
582
  if(typeof now === 'string') {
541
583
  now = new Date(now);
542
584
  }
@@ -586,12 +628,14 @@ export function _checkCredential({credential, now = new Date()}) {
586
628
  if(!dateRegex.test(issuanceDate)) {
587
629
  throw new Error(`"issuanceDate" must be a valid date: ${issuanceDate}`);
588
630
  }
589
- // check if `now` is before `issuanceDate`
590
- issuanceDate = new Date(issuanceDate);
591
- if(now < issuanceDate) {
592
- throw new Error(
593
- `The current date time (${now.toISOString()}) is before the ` +
594
- `"issuanceDate" (${issuanceDate.toISOString()}).`);
631
+ // check if `now` is before `issuanceDate` on verification
632
+ if(mode === 'verify') {
633
+ issuanceDate = new Date(issuanceDate);
634
+ if(now < issuanceDate) {
635
+ throw new Error(
636
+ `The current date time (${now.toISOString()}) is before the ` +
637
+ `"issuanceDate" (${issuanceDate.toISOString()}).`);
638
+ }
595
639
  }
596
640
  }
597
641
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalbazaar/vc",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "description": "Verifiable Credentials JavaScript library.",
5
5
  "homepage": "https://github.com/digitalbazaar/vc",
6
6
  "author": {
@@ -29,29 +29,34 @@
29
29
  ],
30
30
  "dependencies": {
31
31
  "credentials-context": "^2.0.0",
32
- "jsonld": "^8.1.0",
33
- "jsonld-signatures": "^11.0.0"
32
+ "jsonld": "^8.3.1",
33
+ "jsonld-signatures": "^11.2.1"
34
34
  },
35
35
  "devDependencies": {
36
+ "@digitalbazaar/credentials-examples-context": "^1.0.0",
37
+ "@digitalbazaar/data-integrity": "^2.0.0",
38
+ "@digitalbazaar/data-integrity-context": "^2.0.0",
39
+ "@digitalbazaar/ecdsa-multikey": "^1.6.0",
40
+ "@digitalbazaar/ecdsa-sd-2023-cryptosuite": "^3.0.0",
36
41
  "@digitalbazaar/ed25519-signature-2018": "^4.0.0",
37
42
  "@digitalbazaar/ed25519-verification-key-2018": "^4.0.0",
38
- "@digitalbazaar/credentials-examples-context": "^1.0.0",
43
+ "@digitalbazaar/multikey-context": "^1.0.0",
39
44
  "@digitalbazaar/odrl-context": "^1.0.0",
40
- "c8": "^7.12.0",
45
+ "c8": "^8.0.1",
41
46
  "chai": "^4.3.7",
42
47
  "cross-env": "^7.0.3",
43
48
  "did-context": "^3.1.1",
44
49
  "did-veres-one": "^16.0.0",
45
- "eslint": "^8.32.0",
46
- "eslint-config-digitalbazaar": "^4.2.0",
47
- "eslint-plugin-jsdoc": "^39.6.4",
48
- "eslint-plugin-unicorn": "^45.0.2",
50
+ "eslint": "^8.53.0",
51
+ "eslint-config-digitalbazaar": "^5.0.1",
52
+ "eslint-plugin-jsdoc": "^46.9.0",
53
+ "eslint-plugin-unicorn": "^49.0.0",
49
54
  "karma": "^6.4.1",
50
55
  "karma-chai": "^0.1.0",
51
56
  "karma-chrome-launcher": "^3.1.1",
52
57
  "karma-mocha": "^2.0.1",
53
58
  "karma-mocha-reporter": "^2.2.5",
54
- "karma-sourcemap-loader": "^0.3.8",
59
+ "karma-sourcemap-loader": "^0.4.0",
55
60
  "karma-webpack": "^5.0.0",
56
61
  "mocha": "^10.2.0",
57
62
  "mocha-lcov-reporter": "^1.3.0",
@@ -82,7 +87,7 @@
82
87
  "test": "npm run test-node",
83
88
  "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 test/*.spec.js",
84
89
  "test-karma": "karma start karma.conf.cjs",
85
- "lint": "eslint .",
90
+ "lint": "eslint 'lib/**/*.js' 'test/**/*.js'",
86
91
  "coverage": "cross-env NODE_ENV=test c8 npm run test-node",
87
92
  "coverage-ci": "cross-env NODE_ENV=test c8 --reporter=lcovonly --reporter=text-summary --reporter=text npm run test-node",
88
93
  "coverage-report": "c8 report"