@digitalbazaar/vc 6.0.2 → 6.2.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 +155 -0
  2. package/lib/index.js +49 -7
  3. package/package.json +15 -10
package/README.md CHANGED
@@ -116,6 +116,140 @@ const signedVC = await vc.issue({credential, suite, documentLoader});
116
116
  console.log(JSON.stringify(signedVC, null, 2));
117
117
  ```
118
118
 
119
+ ### Issuing a Selective Disclosure Verifiable Credential
120
+
121
+ Pre-requisites:
122
+
123
+ * You have a private key (with id and controller) and corresponding suite
124
+ * You have are using a cryptosuite that supports selective disclosure, such
125
+ as `ecdsa-sd-2023`
126
+ * If you're using a custom `@context`, make sure it's resolvable
127
+ * (Recommended) You have a strategy for where to publish your Controller
128
+ Document and Public Key
129
+
130
+ ```js
131
+ import * as vc from '@digitalbazaar/vc';
132
+ import * as ecdsaSd2023Cryptosuite from
133
+ '@digitalbazaar/ecdsa-sd-2023-cryptosuite';
134
+ import {DataIntegrityProof} from '@digitalbazaar/data-integrity';
135
+
136
+ const ecdsaKeyPair = await EcdsaMultikey.generate({
137
+ curve: 'P-256',
138
+ id: 'https://example.edu/issuers/keys/2',
139
+ controller: 'https://example.edu/issuers/565049'
140
+ });
141
+
142
+ // sample unsigned credential
143
+ const credential = {
144
+ "@context": [
145
+ "https://www.w3.org/2018/credentials/v1",
146
+ "https://www.w3.org/2018/credentials/examples/v1"
147
+ ],
148
+ "id": "https://example.com/credentials/1872",
149
+ "type": ["VerifiableCredential", "AlumniCredential"],
150
+ "issuer": "https://example.edu/issuers/565049",
151
+ "issuanceDate": "2010-01-01T19:23:24Z",
152
+ "credentialSubject": {
153
+ "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
154
+ "alumniOf": "Example University"
155
+ }
156
+ };
157
+
158
+ // setup ecdsa-sd-2023 suite for signing selective disclosure VCs
159
+ const suite = new DataIntegrityProof({
160
+ signer: ecdsaKeyPair.signer(),
161
+ cryptosuite: createSignCryptosuite({
162
+ // require the `issuer` and `issuanceDate` fields to always be disclosed
163
+ // by the holder (presenter)
164
+ mandatoryPointers: [
165
+ '/issuanceDate',
166
+ '/issuer'
167
+ ]
168
+ })
169
+ });
170
+ // use a proof ID to enable it to be found and transformed into a disclosure
171
+ // proof by the holder later
172
+ const proofId = `urn:uuid:${uuid()}`;
173
+ suite.proof = {id: proofId};
174
+
175
+ const signedVC = await vc.issue({credential, suite, documentLoader});
176
+ console.log(JSON.stringify(signedVC, null, 2));
177
+ ```
178
+
179
+ ### Deriving a Selective Disclosure Verifiable Credential
180
+
181
+ Note: This step is performed as a holder of a verifiable credential, not as
182
+ an issuer.
183
+
184
+ Pre-requisites:
185
+
186
+ * You have a verifiable credential that was issued using a cryptosuite that
187
+ supports selective disclosure, such as `ecdsa-sd-2023`
188
+ * If you're using a custom `@context`, make sure it's resolvable
189
+
190
+ ```js
191
+ import * as vc from '@digitalbazaar/vc';
192
+ import * as ecdsaSd2023Cryptosuite from
193
+ '@digitalbazaar/ecdsa-sd-2023-cryptosuite';
194
+ import {DataIntegrityProof} from '@digitalbazaar/data-integrity';
195
+
196
+ const {
197
+ createDiscloseCryptosuite,
198
+ createSignCryptosuite,
199
+ createVerifyCryptosuite
200
+ } = ecdsaSd2023Cryptosuite;
201
+
202
+ // sample signed credential
203
+ const credential = {
204
+ "@context": [
205
+ "https://www.w3.org/2018/credentials/v1",
206
+ "https://www.w3.org/2018/credentials/examples/v1",
207
+ "https://w3id.org/security/data-integrity/v2"
208
+ ],
209
+ "id": "http://example.edu/credentials/1872",
210
+ "type": [
211
+ "VerifiableCredential",
212
+ "AlumniCredential"
213
+ ],
214
+ "issuer": "https://example.edu/issuers/565049",
215
+ "issuanceDate": "2010-01-01T19:23:24Z",
216
+ "credentialSubject": {
217
+ "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
218
+ "alumniOf": "<span lang=\"en\">Example University</span>"
219
+ },
220
+ "proof": {
221
+ "id": "urn:uuid:2ef8c7ce-a4da-44b4-ba7f-3d43eaf1e50c",
222
+ "type": "DataIntegrityProof",
223
+ "created": "2023-11-13T22:58:06Z",
224
+ "verificationMethod": "https://example.edu/issuers/keys/2",
225
+ "cryptosuite": "ecdsa-sd-2023",
226
+ "proofPurpose": "assertionMethod",
227
+ "proofValue": "u2V0AhVhAtYPKUQxwULXzMdsAfqtipsiX6YEPURYSBFYxoFY-v0vCPyAs1Ckyy61Wtk3xZWyBGNaEr3w0wQiJHHd5B9uR-1gjgCQCVtFPMk-ECi0CJFYv_GTjCChf8St0FQjuExTAnwP0-ipYIOHSun3YqabOfNe2DYFkHBTZa0Csf1a7YUDW8hhsOHqTglhA8aqnyanT-Ybo2-aHBTcI-UmHX0iluGb2IxoHLLhQoOPm2rDW0eB04Fa2Dh6WMKoOl_Bz3wZZDGQ31XoGrQvgIlhAo8qspvC-QQ-xI3KADiA12sO5LRsZ7hl9ozoJEECVsDOKlxWd-dhices5b2ZQIiiRE9XxxJx8YuwCMoD2bRLbOIJtL2lzc3VhbmNlRGF0ZWcvaXNzdWVy"
228
+ }
229
+ };
230
+
231
+ // note no `signer` needed; the selective disclosure credential will be
232
+ // derived from the base proof already provided by the issuer
233
+ const suite = new DataIntegrityProof({
234
+ cryptosuite: createDiscloseCryptosuite({
235
+ // the ID of the base proof to convert to a disclosure proof
236
+ proofId: 'urn:uuid:da088899-3439-41ea-a580-af3f1cf98cd3',
237
+ // selectively disclose the entire credential subject; different JSON
238
+ // pointers could be provided to selectively disclose different information;
239
+ // the issuer will have mandatory fields that will be automatically
240
+ // disclosed such as the `issuer` and `issuanceDate` fields
241
+ selectivePointers: [
242
+ '/credentialSubject'
243
+ ]
244
+ })
245
+ });
246
+
247
+ const derivedVC = await vc.derive({
248
+ verifiableCredential, suite, documentLoader
249
+ });
250
+ console.log(JSON.stringify(derivedVC, null, 2));
251
+ ```
252
+
119
253
  ### Creating a Verifiable Presentation
120
254
 
121
255
  Pre-requisites:
@@ -355,6 +489,27 @@ const result = await vc.verifyCredential({credential, suite, documentLoader});
355
489
  // {valid: true}
356
490
  ```
357
491
 
492
+ To verify a selective disclosure verifiable credential ensure the suite
493
+ supports it, for example:
494
+
495
+ ```js
496
+ import * as ecdsaSd2023Cryptosuite from
497
+ '@digitalbazaar/ecdsa-sd-2023-cryptosuite';
498
+ import {DataIntegrityProof} from '@digitalbazaar/data-integrity';
499
+
500
+ const {
501
+ createDiscloseCryptosuite,
502
+ createSignCryptosuite,
503
+ createVerifyCryptosuite
504
+ } = ecdsaSd2023Cryptosuite;
505
+
506
+ const suite = new DataIntegrityProof({
507
+ cryptosuite: createVerifyCryptosuite()
508
+ });
509
+ const result = await vc.verifyCredential({credential, suite, documentLoader});
510
+ // {valid: true}
511
+ ```
512
+
358
513
  To verify a verifiable credential with a custom `@context` field use a
359
514
  [custom documentLoader](#custom-documentLoader)
360
515
 
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,7 +135,7 @@ 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
@@ -144,6 +144,45 @@ export async function issue({
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(), mode = 'verify'}) {
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
  }
@@ -612,10 +654,10 @@ export function _checkCredential({credential, now = new Date(), mode = 'verify'}
612
654
  }
613
655
 
614
656
  if('credentialStatus' in credential) {
615
- if(!credential.credentialStatus.id) {
657
+ if(Array.isArray(credential.credentialStatus) ? credential.credentialStatus.some(cs => !cs.id) : !credential.credentialStatus.id) {
616
658
  throw new Error('"credentialStatus" must include an id.');
617
659
  }
618
- if(!credential.credentialStatus.type) {
660
+ if(Array.isArray(credential.credentialStatus) ? credential.credentialStatus.some(cs => !cs.type) : !credential.credentialStatus.type) {
619
661
  throw new Error('"credentialStatus" must include a type.');
620
662
  }
621
663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalbazaar/vc",
3
- "version": "6.0.2",
3
+ "version": "6.2.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",