@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.
- package/README.md +155 -0
- package/lib/index.js +49 -7
- 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.
|
|
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({
|
|
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
|
|
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
|
|
33
|
-
"jsonld-signatures": "^11.
|
|
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/
|
|
43
|
+
"@digitalbazaar/multikey-context": "^1.0.0",
|
|
39
44
|
"@digitalbazaar/odrl-context": "^1.0.0",
|
|
40
|
-
"c8": "^
|
|
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.
|
|
46
|
-
"eslint-config-digitalbazaar": "^
|
|
47
|
-
"eslint-plugin-jsdoc": "^
|
|
48
|
-
"eslint-plugin-unicorn": "^
|
|
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.
|
|
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",
|