@interop/vc 11.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.
- package/LICENSE +29 -0
- package/README.md +542 -0
- package/dist/CredentialIssuancePurpose.d.ts +50 -0
- package/dist/CredentialIssuancePurpose.d.ts.map +1 -0
- package/dist/CredentialIssuancePurpose.js +67 -0
- package/dist/CredentialIssuancePurpose.js.map +1 -0
- package/dist/contexts/index.d.ts +2 -0
- package/dist/contexts/index.d.ts.map +1 -0
- package/dist/contexts/index.js +15 -0
- package/dist/contexts/index.js.map +1 -0
- package/dist/documentLoader.d.ts +6 -0
- package/dist/documentLoader.d.ts.map +1 -0
- package/dist/documentLoader.js +14 -0
- package/dist/documentLoader.js.map +1 -0
- package/dist/helpers.d.ts +42 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +79 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +279 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +736 -0
- package/dist/index.js.map +1 -0
- package/package.json +99 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2021, Digital Bazaar, Inc.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
# Verifiable Credentials JS Library _(@interop/vc)_
|
|
2
|
+
|
|
3
|
+
[](https://github.com/interop-alliance/vc/actions?query=workflow%3A%22CI%22)
|
|
4
|
+
[](https://npm.im/@interop/vc)
|
|
5
|
+
|
|
6
|
+
> A TypeScript/JS library for issuing and verifying Verifiable Credentials for Node.js, browsers, and React Native.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Security](#security)
|
|
11
|
+
- [Background](#background)
|
|
12
|
+
- [Install](#install)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [Testing](#testing)
|
|
15
|
+
- [Contribute](#contribute)
|
|
16
|
+
- [Commercial Support](#commercial-support)
|
|
17
|
+
- [License](#license)
|
|
18
|
+
|
|
19
|
+
## Security
|
|
20
|
+
|
|
21
|
+
As with most security- and cryptography-related tools, the overall security of
|
|
22
|
+
your system will largely depend on your design decisions (which key types
|
|
23
|
+
you will use, where you'll store the private keys, what you put into your
|
|
24
|
+
credentials, and so on.)
|
|
25
|
+
|
|
26
|
+
## Background
|
|
27
|
+
|
|
28
|
+
(Forked from
|
|
29
|
+
[`@digitalcredentials/vc@10.0.0`](https://github.com/digitalcredentials/vc), which was
|
|
30
|
+
in turn forked from
|
|
31
|
+
[`@digitalbazaar/vc@1.0.0`](https://github.com/digitalbazaar/vc-js) to provide
|
|
32
|
+
React Native compatibility.)
|
|
33
|
+
|
|
34
|
+
This library is a TypeScript (Node.js and browser) implementation of the
|
|
35
|
+
[Verifiable Credentials Data Model](https://w3c.github.io/vc-data-model/)
|
|
36
|
+
specification, supporting both VC Data Model 1.0 and 2.0 (the JWT serialization
|
|
37
|
+
is not currently supported).
|
|
38
|
+
|
|
39
|
+
It allows you to perform the following basic operations:
|
|
40
|
+
|
|
41
|
+
1. Signing (issuing) a Verifiable Credential (VC).
|
|
42
|
+
2. Creating a Verifiable Presentation (VP), signed or unsigned
|
|
43
|
+
3. Verifying a VP
|
|
44
|
+
4. Verifying a standalone VC
|
|
45
|
+
|
|
46
|
+
**Pre-requisites:** Usage of this library assumes you have the ability to do
|
|
47
|
+
the following:
|
|
48
|
+
|
|
49
|
+
* [Generate LD key pairs and signature suites](BACKGROUND.md#generating-keys-and-suites)
|
|
50
|
+
* Publish the corresponding public keys somewhere that is accessible to the
|
|
51
|
+
verifier.
|
|
52
|
+
* Make sure your custom `@context`s, verification methods (such as public keys)
|
|
53
|
+
and their corresponding controller documents, and any other resolvable
|
|
54
|
+
objects, are reachable via a `documentLoader`.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
- Browsers and Node.js 24+ are supported.
|
|
59
|
+
|
|
60
|
+
To install from NPM:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
npm install @interop/vc
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To install locally (for development), using [pnpm](https://pnpm.io/):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
git clone https://github.com/interop-alliance/vc.git
|
|
70
|
+
cd vc
|
|
71
|
+
pnpm install
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Setting up a signature suite
|
|
77
|
+
|
|
78
|
+
For signing, when setting up a signature suite, you will need to pass in
|
|
79
|
+
a key pair containing a private key.
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
import * as vc from '@interop/vc';
|
|
83
|
+
|
|
84
|
+
// Required to set up a suite instance with private key
|
|
85
|
+
import {Ed25519VerificationKey} from '@interop/ed25519-verification-key';
|
|
86
|
+
import {Ed25519Signature2020} from '@interop/ed25519-signature';
|
|
87
|
+
|
|
88
|
+
const keyPair = await Ed25519VerificationKey.generate();
|
|
89
|
+
|
|
90
|
+
const suite = new Ed25519Signature2020({signer: keyPair.signer()});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Issuing a Verifiable Credential
|
|
94
|
+
|
|
95
|
+
Pre-requisites:
|
|
96
|
+
|
|
97
|
+
* You have a private key (with id and controller) and corresponding suite
|
|
98
|
+
* If you're using a custom `@context`, make sure it's resolvable
|
|
99
|
+
* (Recommended) You have a strategy for where to publish your Controller
|
|
100
|
+
Document and Public Key
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import * as vc from '@interop/vc';
|
|
104
|
+
|
|
105
|
+
// Sample unsigned credential
|
|
106
|
+
const credential = {
|
|
107
|
+
"@context": [
|
|
108
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
109
|
+
"https://www.w3.org/2018/credentials/examples/v1"
|
|
110
|
+
],
|
|
111
|
+
"id": "https://example.com/credentials/1872",
|
|
112
|
+
"type": ["VerifiableCredential", "AlumniCredential"],
|
|
113
|
+
"issuer": "https://example.edu/issuers/565049",
|
|
114
|
+
"issuanceDate": "2010-01-01T19:23:24Z",
|
|
115
|
+
"credentialSubject": {
|
|
116
|
+
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
|
|
117
|
+
"alumniOf": "Example University"
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const signedVC = await vc.issue({credential, suite, documentLoader});
|
|
122
|
+
console.log(JSON.stringify(signedVC, null, 2));
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Issuing a Selective Disclosure Verifiable Credential
|
|
126
|
+
|
|
127
|
+
Pre-requisites:
|
|
128
|
+
|
|
129
|
+
* You have a private key (with id and controller) and corresponding suite
|
|
130
|
+
* You have are using a cryptosuite that supports selective disclosure, such
|
|
131
|
+
as `ecdsa-sd-2023`
|
|
132
|
+
* If you're using a custom `@context`, make sure it's resolvable
|
|
133
|
+
* (Recommended) You have a strategy for where to publish your Controller
|
|
134
|
+
Document and Public Key
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
import * as vc from '@interop/vc';
|
|
138
|
+
import * as ecdsaSd2023Cryptosuite from
|
|
139
|
+
'@digitalbazaar/ecdsa-sd-2023-cryptosuite';
|
|
140
|
+
import {DataIntegrityProof} from '@interop/data-integrity-proof';
|
|
141
|
+
|
|
142
|
+
const ecdsaKeyPair = await EcdsaMultikey.generate({
|
|
143
|
+
curve: 'P-256',
|
|
144
|
+
id: 'https://example.edu/issuers/keys/2',
|
|
145
|
+
controller: 'https://example.edu/issuers/565049'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// sample unsigned credential
|
|
149
|
+
const credential = {
|
|
150
|
+
"@context": [
|
|
151
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
152
|
+
"https://www.w3.org/2018/credentials/examples/v1"
|
|
153
|
+
],
|
|
154
|
+
"id": "https://example.com/credentials/1872",
|
|
155
|
+
"type": ["VerifiableCredential", "AlumniCredential"],
|
|
156
|
+
"issuer": "https://example.edu/issuers/565049",
|
|
157
|
+
"issuanceDate": "2010-01-01T19:23:24Z",
|
|
158
|
+
"credentialSubject": {
|
|
159
|
+
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
|
|
160
|
+
"alumniOf": "Example University"
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// setup ecdsa-sd-2023 suite for signing selective disclosure VCs
|
|
165
|
+
const suite = new DataIntegrityProof({
|
|
166
|
+
signer: ecdsaKeyPair.signer(),
|
|
167
|
+
cryptosuite: createSignCryptosuite({
|
|
168
|
+
// require the `issuer` and `issuanceDate` fields to always be disclosed
|
|
169
|
+
// by the holder (presenter)
|
|
170
|
+
mandatoryPointers: [
|
|
171
|
+
'/issuanceDate',
|
|
172
|
+
'/issuer'
|
|
173
|
+
]
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
// use a proof ID to enable it to be found and transformed into a disclosure
|
|
177
|
+
// proof by the holder later
|
|
178
|
+
const proofId = `urn:uuid:${uuid()}`;
|
|
179
|
+
suite.proof = {id: proofId};
|
|
180
|
+
|
|
181
|
+
const signedVC = await vc.issue({credential, suite, documentLoader});
|
|
182
|
+
console.log(JSON.stringify(signedVC, null, 2));
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Deriving a Selective Disclosure Verifiable Credential
|
|
186
|
+
|
|
187
|
+
Note: This step is performed as a holder of a verifiable credential, not as
|
|
188
|
+
an issuer.
|
|
189
|
+
|
|
190
|
+
Pre-requisites:
|
|
191
|
+
|
|
192
|
+
* You have a verifiable credential that was issued using a cryptosuite that
|
|
193
|
+
supports selective disclosure, such as `ecdsa-sd-2023`
|
|
194
|
+
* If you're using a custom `@context`, make sure it's resolvable
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
import * as vc from '@interop/vc';
|
|
198
|
+
import * as ecdsaSd2023Cryptosuite from
|
|
199
|
+
'@digitalbazaar/ecdsa-sd-2023-cryptosuite';
|
|
200
|
+
import {DataIntegrityProof} from '@interop/data-integrity-proof';
|
|
201
|
+
|
|
202
|
+
const {
|
|
203
|
+
createDiscloseCryptosuite,
|
|
204
|
+
createSignCryptosuite,
|
|
205
|
+
createVerifyCryptosuite
|
|
206
|
+
} = ecdsaSd2023Cryptosuite;
|
|
207
|
+
|
|
208
|
+
// sample signed credential
|
|
209
|
+
const credential = {
|
|
210
|
+
"@context": [
|
|
211
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
212
|
+
"https://www.w3.org/2018/credentials/examples/v1",
|
|
213
|
+
"https://w3id.org/security/data-integrity/v2"
|
|
214
|
+
],
|
|
215
|
+
"id": "http://example.edu/credentials/1872",
|
|
216
|
+
"type": [
|
|
217
|
+
"VerifiableCredential",
|
|
218
|
+
"AlumniCredential"
|
|
219
|
+
],
|
|
220
|
+
"issuer": "https://example.edu/issuers/565049",
|
|
221
|
+
"issuanceDate": "2010-01-01T19:23:24Z",
|
|
222
|
+
"credentialSubject": {
|
|
223
|
+
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
|
|
224
|
+
"alumniOf": "<span lang=\"en\">Example University</span>"
|
|
225
|
+
},
|
|
226
|
+
"proof": {
|
|
227
|
+
"id": "urn:uuid:2ef8c7ce-a4da-44b4-ba7f-3d43eaf1e50c",
|
|
228
|
+
"type": "DataIntegrityProof",
|
|
229
|
+
"created": "2023-11-13T22:58:06Z",
|
|
230
|
+
"verificationMethod": "https://example.edu/issuers/keys/2",
|
|
231
|
+
"cryptosuite": "ecdsa-sd-2023",
|
|
232
|
+
"proofPurpose": "assertionMethod",
|
|
233
|
+
"proofValue": "u2V0AhVhAtYPKUQxwULXzMdsAfqtipsiX6YEPURYSBFYxoFY-v0vCPyAs1Ckyy61Wtk3xZWyBGNaEr3w0wQiJHHd5B9uR-1gjgCQCVtFPMk-ECi0CJFYv_GTjCChf8St0FQjuExTAnwP0-ipYIOHSun3YqabOfNe2DYFkHBTZa0Csf1a7YUDW8hhsOHqTglhA8aqnyanT-Ybo2-aHBTcI-UmHX0iluGb2IxoHLLhQoOPm2rDW0eB04Fa2Dh6WMKoOl_Bz3wZZDGQ31XoGrQvgIlhAo8qspvC-QQ-xI3KADiA12sO5LRsZ7hl9ozoJEECVsDOKlxWd-dhices5b2ZQIiiRE9XxxJx8YuwCMoD2bRLbOIJtL2lzc3VhbmNlRGF0ZWcvaXNzdWVy"
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// note no `signer` needed; the selective disclosure credential will be
|
|
238
|
+
// derived from the base proof already provided by the issuer
|
|
239
|
+
const suite = new DataIntegrityProof({
|
|
240
|
+
cryptosuite: createDiscloseCryptosuite({
|
|
241
|
+
// the ID of the base proof to convert to a disclosure proof
|
|
242
|
+
proofId: 'urn:uuid:da088899-3439-41ea-a580-af3f1cf98cd3',
|
|
243
|
+
// selectively disclose the entire credential subject; different JSON
|
|
244
|
+
// pointers could be provided to selectively disclose different information;
|
|
245
|
+
// the issuer will have mandatory fields that will be automatically
|
|
246
|
+
// disclosed such as the `issuer` and `issuanceDate` fields
|
|
247
|
+
selectivePointers: [
|
|
248
|
+
'/credentialSubject'
|
|
249
|
+
]
|
|
250
|
+
})
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const derivedVC = await vc.derive({
|
|
254
|
+
verifiableCredential, suite, documentLoader
|
|
255
|
+
});
|
|
256
|
+
console.log(JSON.stringify(derivedVC, null, 2));
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Creating a Verifiable Presentation
|
|
260
|
+
|
|
261
|
+
Pre-requisites:
|
|
262
|
+
|
|
263
|
+
* You have the requisite private keys (with id and controller) and
|
|
264
|
+
corresponding suites
|
|
265
|
+
* If you're using a custom `@context`, make sure it's resolvable
|
|
266
|
+
* (Recommended) You have a strategy for where to publish your Controller
|
|
267
|
+
Documents and Public Keys
|
|
268
|
+
|
|
269
|
+
#### Creating an unsigned presentation
|
|
270
|
+
|
|
271
|
+
To create a presentation out of one or more verifiable credentials, you can
|
|
272
|
+
use the `createPresentation()` convenience function. Alternatively, you can
|
|
273
|
+
create the presentation object manually (don't forget to set the `@context` and
|
|
274
|
+
`type` properties).
|
|
275
|
+
|
|
276
|
+
To create a verifiable presentation with a custom `@context` field use a
|
|
277
|
+
[custom documentLoader](#custom-documentLoader)
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
const verifiableCredential = [vc1, vc2]; // either array or single object
|
|
281
|
+
|
|
282
|
+
// optional `id` and `holder`
|
|
283
|
+
const id = 'ebc6f1c2';
|
|
284
|
+
const holder = 'did:ex:12345';
|
|
285
|
+
|
|
286
|
+
const presentation = vc.createPresentation({
|
|
287
|
+
verifiableCredential, id, holder
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
console.log(JSON.stringify(presentation, null, 2));
|
|
291
|
+
// ->
|
|
292
|
+
{
|
|
293
|
+
"@context": [
|
|
294
|
+
"https://www.w3.org/2018/credentials/v1"
|
|
295
|
+
],
|
|
296
|
+
"type": [
|
|
297
|
+
"VerifiablePresentation"
|
|
298
|
+
],
|
|
299
|
+
"id": "ebc6f1c2",
|
|
300
|
+
"holder": "did:ex:12345",
|
|
301
|
+
"verifiableCredential": [
|
|
302
|
+
// vc1:
|
|
303
|
+
{
|
|
304
|
+
"@context": [
|
|
305
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
306
|
+
"https://www.w3.org/2018/credentials/examples/v1"
|
|
307
|
+
],
|
|
308
|
+
"id": "http://example.edu/credentials/1872",
|
|
309
|
+
"type": [
|
|
310
|
+
"VerifiableCredential",
|
|
311
|
+
"AlumniCredential"
|
|
312
|
+
],
|
|
313
|
+
"issuer": "https://example.edu/issuers/565049",
|
|
314
|
+
"issuanceDate": "2010-01-01T19:23:24Z",
|
|
315
|
+
"credentialSubject": {
|
|
316
|
+
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
|
|
317
|
+
"alumniOf": "<span lang=\"en\">Example University</span>"
|
|
318
|
+
},
|
|
319
|
+
"proof": {
|
|
320
|
+
"type": "Ed25519Signature2018",
|
|
321
|
+
"created": "2020-02-03T17:23:49Z",
|
|
322
|
+
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..AUQ3AJ23WM5vMOWNtYKuqZBekRAOUibOMH9XuvOd39my1sO-X9R4QyAXLD2ospssLvIuwmQVhJa-F0xMOnkvBg",
|
|
323
|
+
"proofPurpose": "assertionMethod",
|
|
324
|
+
"verificationMethod": "https://example.edu/issuers/keys/1"
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
// vc2 goes here ...
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Note that this creates an _unsigned_ presentation (which may be valid
|
|
333
|
+
for some use cases).
|
|
334
|
+
|
|
335
|
+
### Custom documentLoader
|
|
336
|
+
|
|
337
|
+
Pre-requisites:
|
|
338
|
+
|
|
339
|
+
* You have an existing valid JSON-LD `@context`.
|
|
340
|
+
* Your custom context is resolvable at an address.
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
// jsonld-signatures has a secure context loader
|
|
344
|
+
// by requiring this first you ensure security
|
|
345
|
+
// contexts are loaded from jsonld-signatures
|
|
346
|
+
// and not an insecure source.
|
|
347
|
+
import * as vc from '@interop/vc';
|
|
348
|
+
import { securityLoader } from '@interop/security-document-loader';
|
|
349
|
+
|
|
350
|
+
const documentLoader = securityLoader().build();
|
|
351
|
+
|
|
352
|
+
const vp = await vc.signPresentation({
|
|
353
|
+
presentation, suite, challenge, documentLoader
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// or
|
|
357
|
+
const signedVC = await vc.issue({credential, suite, documentLoader});
|
|
358
|
+
|
|
359
|
+
// or
|
|
360
|
+
const result = await vc.verifyCredential({credential: signedVC, suite, documentLoader});
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Signing the Presentation
|
|
365
|
+
|
|
366
|
+
Once you've created the presentation (either via `createPresentation()` or
|
|
367
|
+
manually), you can sign it using `signPresentation()`:
|
|
368
|
+
|
|
369
|
+
```js
|
|
370
|
+
const vp = await vc.signPresentation({
|
|
371
|
+
presentation, suite, challenge, documentLoader
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
console.log(JSON.stringify(vp, null, 2));
|
|
375
|
+
// ->
|
|
376
|
+
{
|
|
377
|
+
"@context": [
|
|
378
|
+
"https://www.w3.org/2018/credentials/v1"
|
|
379
|
+
],
|
|
380
|
+
"type": [
|
|
381
|
+
"VerifiablePresentation"
|
|
382
|
+
],
|
|
383
|
+
"verifiableCredential": [
|
|
384
|
+
{
|
|
385
|
+
"@context": [
|
|
386
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
387
|
+
"https://www.w3.org/2018/credentials/examples/v1"
|
|
388
|
+
],
|
|
389
|
+
"id": "http://example.edu/credentials/1872",
|
|
390
|
+
"type": [
|
|
391
|
+
"VerifiableCredential",
|
|
392
|
+
"AlumniCredential"
|
|
393
|
+
],
|
|
394
|
+
"issuer": "https://example.edu/issuers/565049",
|
|
395
|
+
"issuanceDate": "2010-01-01T19:23:24Z",
|
|
396
|
+
"credentialSubject": {
|
|
397
|
+
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
|
|
398
|
+
"alumniOf": "<span lang=\"en\">Example University</span>"
|
|
399
|
+
},
|
|
400
|
+
"proof": {
|
|
401
|
+
"type": "Ed25519Signature2018",
|
|
402
|
+
"created": "2020-02-03T17:23:49Z",
|
|
403
|
+
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..AUQ3AJ23WM5vMOWNtYKuqZBekRAOUibOMH9XuvOd39my1sO-X9R4QyAXLD2ospssLvIuwmQVhJa-F0xMOnkvBg",
|
|
404
|
+
"proofPurpose": "assertionMethod",
|
|
405
|
+
"verificationMethod": "https://example.edu/issuers/keys/1"
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
],
|
|
409
|
+
"id": "ebc6f1c2",
|
|
410
|
+
"holder": "did:ex:holder123",
|
|
411
|
+
"proof": {
|
|
412
|
+
"type": "Ed25519Signature2018",
|
|
413
|
+
"created": "2019-02-03T17:23:49Z",
|
|
414
|
+
"challenge": "12ec21",
|
|
415
|
+
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ZO4Lkq8-fOruE4oUvuMaxepGX-vLD2gPyNIsz-iA7X0tzC3_96djaBYDxxl6wD1xKrx0h60NjI9i9p_MxoXkDQ",
|
|
416
|
+
"proofPurpose": "authentication",
|
|
417
|
+
"verificationMethod": "https://example.edu/issuers/keys/1"
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Verifying a Verifiable Presentation
|
|
423
|
+
|
|
424
|
+
Pre-requisites:
|
|
425
|
+
|
|
426
|
+
* Your custom `@context`s, verification methods (like public keys) and their
|
|
427
|
+
corresponding controller documents are reachable via a `documentLoader`.
|
|
428
|
+
|
|
429
|
+
To verify a verifiable presentation:
|
|
430
|
+
|
|
431
|
+
```js
|
|
432
|
+
// challenge has been received from the requesting party - see 'challenge'
|
|
433
|
+
// section below
|
|
434
|
+
import * as vc from '@interop/vc';
|
|
435
|
+
import { securityLoader } from '@interop/security-document-loader';
|
|
436
|
+
|
|
437
|
+
const documentLoader = securityLoader().build();
|
|
438
|
+
|
|
439
|
+
const result = await vc.verify({presentation, challenge, suite, documentLoader});
|
|
440
|
+
// {valid: true}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
By default, `verify()` will throw an error if the `proof` section is missing.
|
|
444
|
+
To verify an unsigned presentation, you must set the `unsignedPresentation`
|
|
445
|
+
flag:
|
|
446
|
+
|
|
447
|
+
```js
|
|
448
|
+
const result = await vc.verify({
|
|
449
|
+
presentation, suite, documentLoader, unsignedPresentation: true
|
|
450
|
+
});
|
|
451
|
+
// {valid: true}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
#### `challenge` parameter
|
|
455
|
+
|
|
456
|
+
Verifiable Presentations are typically used for authentication purposes.
|
|
457
|
+
A `challenge` param (similar to a `nonce` in OAuth2/OpenID Connect) is provided
|
|
458
|
+
by the party that's receiving the VP, and serves to prevent presentation replay
|
|
459
|
+
attacks. The workflow is:
|
|
460
|
+
|
|
461
|
+
1. Receiving party asks for the VerifiablePresentation, and provides a
|
|
462
|
+
`challenge` parameter.
|
|
463
|
+
2. The client code creating the VP passes in that challenge (from the requesting
|
|
464
|
+
party), and it gets included in the VP.
|
|
465
|
+
3. The client code passes the VP to the receiving party, which then checks to
|
|
466
|
+
make sure the `challenge` is the same as the one it provided in the request
|
|
467
|
+
in 1).
|
|
468
|
+
|
|
469
|
+
### Verifying a Verifiable Credential
|
|
470
|
+
For most situations, Verifiable Credentials will be wrapped in a Verifiable
|
|
471
|
+
Presentation and the entire VP should be verified. However, this library
|
|
472
|
+
provides a utility function to verify a Verifiable Credential on its own.
|
|
473
|
+
|
|
474
|
+
Pre-requisites:
|
|
475
|
+
|
|
476
|
+
* Your custom `@context`s, verification methods (like public keys) and their
|
|
477
|
+
corresponding controller documents are reachable via a `documentLoader`.
|
|
478
|
+
|
|
479
|
+
To verify a verifiable credential:
|
|
480
|
+
|
|
481
|
+
```js
|
|
482
|
+
const suite = new Ed25519Signature2020();
|
|
483
|
+
const result = await vc.verifyCredential({credential: signedVC, suite, documentLoader});
|
|
484
|
+
// {valid: true}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
To verify a selective disclosure verifiable credential ensure the suite
|
|
488
|
+
supports it, for example:
|
|
489
|
+
|
|
490
|
+
```js
|
|
491
|
+
import * as ecdsaSd2023Cryptosuite from
|
|
492
|
+
'@digitalbazaar/ecdsa-sd-2023-cryptosuite';
|
|
493
|
+
import {DataIntegrityProof} from '@interop/data-integrity-proof';
|
|
494
|
+
|
|
495
|
+
const {
|
|
496
|
+
createDiscloseCryptosuite,
|
|
497
|
+
createSignCryptosuite,
|
|
498
|
+
createVerifyCryptosuite
|
|
499
|
+
} = ecdsaSd2023Cryptosuite;
|
|
500
|
+
|
|
501
|
+
const suite = new DataIntegrityProof({
|
|
502
|
+
cryptosuite: createVerifyCryptosuite()
|
|
503
|
+
});
|
|
504
|
+
const result = await vc.verifyCredential({credential, suite, documentLoader});
|
|
505
|
+
// {valid: true}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
To verify a verifiable credential with a custom `@context` field use a
|
|
509
|
+
[custom documentLoader](#custom-documentLoader)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
## Testing
|
|
513
|
+
|
|
514
|
+
To run the [Vitest](https://vitest.dev/) Node tests:
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
pnpm run test-node
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
To run the [Playwright](https://playwright.dev/) (in-browser) tests:
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
pnpm run test-browser
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
To run the full suite (lint, Node, and browser tests):
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
pnpm test
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## Contribute
|
|
533
|
+
|
|
534
|
+
PRs accepted.
|
|
535
|
+
|
|
536
|
+
Note: If editing the Readme, please conform to the
|
|
537
|
+
[standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
538
|
+
|
|
539
|
+
## License
|
|
540
|
+
|
|
541
|
+
* MIT License - DCC - TypeScript compatibility.
|
|
542
|
+
* New BSD License (3-clause) © 2020-2021 Digital Bazaar - Initial implementation.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2023 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import jsigs from '@interop/jsonld-signatures';
|
|
5
|
+
import type { DocumentLoader, LinkedDataProof, ProofValidateResult } from '@interop/jsonld-signatures';
|
|
6
|
+
declare const AssertionProofPurpose: typeof jsigs.AssertionProofPurpose;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a proof purpose that will validate whether the verification
|
|
9
|
+
* method in a proof was authorized by its declared controller for the
|
|
10
|
+
* proof's purpose.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CredentialIssuancePurpose extends AssertionProofPurpose {
|
|
13
|
+
/**
|
|
14
|
+
* @param options - The options to use.
|
|
15
|
+
* @param options.controller - The description of the controller, if it is
|
|
16
|
+
* not to be dereferenced via a `documentLoader`.
|
|
17
|
+
* @param options.date - The expected date for the creation of the proof.
|
|
18
|
+
* @param options.maxTimestampDelta - A maximum number of seconds that the
|
|
19
|
+
* date on the signature can deviate from.
|
|
20
|
+
*/
|
|
21
|
+
constructor({ controller, date, maxTimestampDelta }?: {
|
|
22
|
+
controller?: object;
|
|
23
|
+
date?: string | Date | number;
|
|
24
|
+
maxTimestampDelta?: number;
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Validates the purpose of a proof. This method is called during
|
|
28
|
+
* proof verification, after the proof value has been checked against the
|
|
29
|
+
* given verification method (in the case of a digital signature, the
|
|
30
|
+
* signature has been cryptographically verified against the public key).
|
|
31
|
+
*
|
|
32
|
+
* @param proof - The proof to validate.
|
|
33
|
+
* @param options - The options to use.
|
|
34
|
+
* @param options.document - The document whose signature is being verified.
|
|
35
|
+
* @param options.suite - Signature suite used in the proof.
|
|
36
|
+
* @param options.verificationMethod - Key id URL to the paired public key.
|
|
37
|
+
* @param options.documentLoader - A document loader.
|
|
38
|
+
*
|
|
39
|
+
* @throws {Error} If verification method not authorized by controller.
|
|
40
|
+
* @throws {Error} If proof's created timestamp is out of range.
|
|
41
|
+
*/
|
|
42
|
+
validate(proof: object, { document, suite, verificationMethod, documentLoader }: {
|
|
43
|
+
document?: object;
|
|
44
|
+
suite?: LinkedDataProof;
|
|
45
|
+
verificationMethod?: object;
|
|
46
|
+
documentLoader?: DocumentLoader;
|
|
47
|
+
}): Promise<ProofValidateResult>;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=CredentialIssuancePurpose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CredentialIssuancePurpose.d.ts","sourceRoot":"","sources":["../src/CredentialIssuancePurpose.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,MAAM,4BAA4B,CAAA;AAC9C,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,4BAA4B,CAAA;AAGnC,QAAA,MACc,qBAAqB,oCAC1B,CAAA;AAET;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,qBAAqB;IAClE;;;;;;;OAOG;gBACS,EACV,UAAU,EACV,IAAI,EACJ,iBAAiB,EAClB,GAAE;QACD,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAA;QAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAA;KACtB;IAIN;;;;;;;;;;;;;;;OAeG;IACG,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,EACE,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,cAAc,EACf,EAAE;QACD,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,KAAK,CAAC,EAAE,eAAe,CAAA;QACvB,kBAAkB,CAAC,EAAE,MAAM,CAAA;QAC3B,cAAc,CAAC,EAAE,cAAc,CAAA;KAChC,GACA,OAAO,CAAC,mBAAmB,CAAC;CAiChC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2023 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import jsigs from '@interop/jsonld-signatures';
|
|
5
|
+
import jsonld from '@interop/jsonld';
|
|
6
|
+
const { purposes: { AssertionProofPurpose } } = jsigs;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a proof purpose that will validate whether the verification
|
|
9
|
+
* method in a proof was authorized by its declared controller for the
|
|
10
|
+
* proof's purpose.
|
|
11
|
+
*/
|
|
12
|
+
export class CredentialIssuancePurpose extends AssertionProofPurpose {
|
|
13
|
+
/**
|
|
14
|
+
* @param options - The options to use.
|
|
15
|
+
* @param options.controller - The description of the controller, if it is
|
|
16
|
+
* not to be dereferenced via a `documentLoader`.
|
|
17
|
+
* @param options.date - The expected date for the creation of the proof.
|
|
18
|
+
* @param options.maxTimestampDelta - A maximum number of seconds that the
|
|
19
|
+
* date on the signature can deviate from.
|
|
20
|
+
*/
|
|
21
|
+
constructor({ controller, date, maxTimestampDelta } = {}) {
|
|
22
|
+
super({ controller, date, maxTimestampDelta });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validates the purpose of a proof. This method is called during
|
|
26
|
+
* proof verification, after the proof value has been checked against the
|
|
27
|
+
* given verification method (in the case of a digital signature, the
|
|
28
|
+
* signature has been cryptographically verified against the public key).
|
|
29
|
+
*
|
|
30
|
+
* @param proof - The proof to validate.
|
|
31
|
+
* @param options - The options to use.
|
|
32
|
+
* @param options.document - The document whose signature is being verified.
|
|
33
|
+
* @param options.suite - Signature suite used in the proof.
|
|
34
|
+
* @param options.verificationMethod - Key id URL to the paired public key.
|
|
35
|
+
* @param options.documentLoader - A document loader.
|
|
36
|
+
*
|
|
37
|
+
* @throws {Error} If verification method not authorized by controller.
|
|
38
|
+
* @throws {Error} If proof's created timestamp is out of range.
|
|
39
|
+
*/
|
|
40
|
+
async validate(proof, { document, suite, verificationMethod, documentLoader }) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await super.validate(proof, {
|
|
43
|
+
document,
|
|
44
|
+
suite,
|
|
45
|
+
verificationMethod,
|
|
46
|
+
documentLoader
|
|
47
|
+
});
|
|
48
|
+
if (!result.valid) {
|
|
49
|
+
throw result.error;
|
|
50
|
+
}
|
|
51
|
+
const issuer = jsonld.getValues(document, 'issuer');
|
|
52
|
+
if (!issuer || issuer.length === 0) {
|
|
53
|
+
throw new Error('Credential issuer is required.');
|
|
54
|
+
}
|
|
55
|
+
const issuerId = typeof issuer[0] === 'string' ? issuer[0] : issuer[0].id;
|
|
56
|
+
const controller = result.controller;
|
|
57
|
+
if (controller?.id !== issuerId) {
|
|
58
|
+
throw new Error('Credential issuer must match the verification method controller.');
|
|
59
|
+
}
|
|
60
|
+
return { valid: true };
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return { valid: false, error: error };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=CredentialIssuancePurpose.js.map
|