@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.
- package/README.md +91 -1
- package/lib/index.js +56 -12
- package/package.json +16 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Verifiable Credentials JS Library _(@digitalbazaar/vc)_
|
|
2
2
|
|
|
3
|
-
[](https://github.com/digitalbazaar/vc/actions/workflow/main.yml)
|
|
4
4
|
[](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.
|
|
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({
|
|
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
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
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
|
|
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",
|
|
@@ -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"
|