@bedrock/vc-delivery 4.6.0 → 4.7.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.
@@ -0,0 +1,5 @@
1
+ /*!
2
+ * Copyright (c) 2024 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ // maximum number of issuer instances that can be associated with a workflow
5
+ export const MAX_ISSUER_INSTANCES = 10;
package/lib/helpers.js CHANGED
@@ -48,6 +48,20 @@ export async function generateRandom() {
48
48
  });
49
49
  }
50
50
 
51
+ export function getWorkflowIssuerInstances({workflow} = {}) {
52
+ let {issuerInstances} = workflow;
53
+ if(!issuerInstances && workflow.zcaps.issue) {
54
+ // generate dynamic issuer instance config
55
+ issuerInstances = [{
56
+ supportedFormats: ['application/vc', 'ldp_vc'],
57
+ zcapReferenceIds: {
58
+ issue: 'issue'
59
+ }
60
+ }];
61
+ }
62
+ return issuerInstances;
63
+ }
64
+
51
65
  export async function getZcapClient({workflow} = {}) {
52
66
  // get service agent for communicating with the issuer instance
53
67
  const {pathname} = new URL(workflow.id);
package/lib/http.js CHANGED
@@ -88,31 +88,46 @@ export async function addRoutes({app, service} = {}) {
88
88
  });
89
89
  }
90
90
 
91
- // perform key generation if requested
92
- if(openId?.oauth2?.generateKeyPair) {
93
- const {oauth2} = openId;
94
- const {algorithm} = oauth2.generateKeyPair;
95
- const kp = await generateKeyPair(algorithm, {extractable: true});
96
- const [privateKeyJwk, publicKeyJwk] = await Promise.all([
97
- exportJWK(kp.privateKey),
98
- exportJWK(kp.publicKey),
99
- ]);
100
- oauth2.keyPair = {privateKeyJwk, publicKeyJwk};
101
- delete oauth2.generateKeyPair;
102
- } else if(openId) {
103
- // ensure key pair can be imported
104
- try {
105
- const {oauth2: {keyPair}} = openId;
106
- await Promise.all([
107
- importJWK(keyPair.privateKeyJwk),
108
- importJWK(keyPair.publicKeyJwk)
91
+ if(openId) {
92
+ // either issuer instances or a single issuer zcap be given if
93
+ // any expected credential requests are given
94
+ const {expectedCredentialRequests} = openId;
95
+ if(expectedCredentialRequests &&
96
+ !(config.issuerInstances || config.zcaps.issue)) {
97
+ throw new BedrockError(
98
+ 'Credential requests are not supported by this workflow.', {
99
+ name: 'DataError',
100
+ details: {httpStatusCode: 400, public: true}
101
+ });
102
+ }
103
+
104
+ // perform key generation if requested
105
+ if(openId.oauth2?.generateKeyPair) {
106
+ const {oauth2} = openId;
107
+ const {algorithm} = oauth2.generateKeyPair;
108
+ const kp = await generateKeyPair(algorithm, {extractable: true});
109
+ const [privateKeyJwk, publicKeyJwk] = await Promise.all([
110
+ exportJWK(kp.privateKey),
111
+ exportJWK(kp.publicKey),
109
112
  ]);
110
- } catch(e) {
111
- throw new BedrockError('Could not import OpenID OAuth2 key pair.', {
112
- name: 'DataError',
113
- details: {httpStatusCode: 400, public: true},
114
- cause: e
115
- });
113
+ oauth2.keyPair = {privateKeyJwk, publicKeyJwk};
114
+ delete oauth2.generateKeyPair;
115
+ } else {
116
+ // ensure key pair can be imported
117
+ try {
118
+ const {oauth2: {keyPair}} = openId;
119
+ await Promise.all([
120
+ importJWK(keyPair.privateKeyJwk),
121
+ importJWK(keyPair.publicKeyJwk)
122
+ ]);
123
+ } catch(e) {
124
+ throw new BedrockError(
125
+ 'Could not import OpenID OAuth2 key pair.', {
126
+ name: 'DataError',
127
+ details: {httpStatusCode: 400, public: true},
128
+ cause: e
129
+ });
130
+ }
116
131
  }
117
132
  }
118
133
 
package/lib/index.js CHANGED
@@ -7,6 +7,7 @@ import {createService, schemas} from '@bedrock/service-core';
7
7
  import {addRoutes} from './http.js';
8
8
  import {initializeServiceAgent} from '@bedrock/service-agent';
9
9
  import {klona} from 'klona';
10
+ import {MAX_ISSUER_INSTANCES} from './constants.js';
10
11
  import {parseLocalId} from './helpers.js';
11
12
  import '@bedrock/express';
12
13
 
@@ -26,12 +27,20 @@ async function _initService({serviceType, routePrefix}) {
26
27
  const createConfigBody = klona(schemas.createConfigBody);
27
28
  const updateConfigBody = klona(schemas.updateConfigBody);
28
29
  const schemasToUpdate = [createConfigBody, updateConfigBody];
29
- const {credentialTemplates, steps, initialStep} = workflowSchemas;
30
+ const {
31
+ credentialTemplates, steps, initialStep, issuerInstances
32
+ } = workflowSchemas;
30
33
  for(const schema of schemasToUpdate) {
31
34
  // add config requirements to workflow configs
32
35
  schema.properties.credentialTemplates = credentialTemplates;
33
36
  schema.properties.steps = steps;
34
37
  schema.properties.initialStep = initialStep;
38
+ schema.properties.issuerInstances = issuerInstances;
39
+ // allow zcaps by custom reference ID
40
+ schema.properties.zcaps = klona(schemas.zcaps);
41
+ // max of 4 basic zcaps + max issuer instances
42
+ schema.properties.zcaps.maxProperties = 4 + MAX_ISSUER_INSTANCES;
43
+ schema.properties.zcaps.additionalProperties = schemas.delegatedZcap;
35
44
  // note: credential templates are not required; if any other properties
36
45
  // become required, add them here
37
46
  // schema.required.push('credentialTemplates');
@@ -117,17 +126,36 @@ async function validateConfigFn({config, op, routePrefix} = {}) {
117
126
 
118
127
  // if credential templates are specified, then `zcaps` MUST include at
119
128
  // least `issue`
120
- const {credentialTemplates = [], zcaps = {}} = config;
121
- if(credentialTemplates.length > 0 && !zcaps.issue) {
122
- throw new BedrockError(
123
- 'A capability to issue credentials is required when credential ' +
124
- 'templates are provided.', {
125
- name: 'DataError',
126
- details: {
127
- httpStatusCode: 400,
128
- public: true
129
- }
130
- });
129
+ const {credentialTemplates = [], zcaps = {}, issuerInstances} = config;
130
+ if(credentialTemplates.length > 0) {
131
+ if(!(zcaps.issue || issuerInstances)) {
132
+ throw new BedrockError(
133
+ 'A capability to issue credentials is required when credential ' +
134
+ 'templates are provided.', {
135
+ name: 'DataError',
136
+ details: {
137
+ httpStatusCode: 400,
138
+ public: true
139
+ }
140
+ });
141
+ }
142
+ // ensure that, if `issuerInstances` is given, that every zcap
143
+ // referenced in each issuer instance config's `zcapReferenceIds` can
144
+ // be found in `zcaps`
145
+ if(issuerInstances) {
146
+ if(!issuerInstances.every(
147
+ instance => !!zcaps[instance.zcapReferenceIds.issue])) {
148
+ throw new BedrockError(
149
+ 'An issuer instance configuration zcap reference ID is not ' +
150
+ 'present in "config.zcaps".', {
151
+ name: 'DataError',
152
+ details: {
153
+ httpStatusCode: 400,
154
+ public: true
155
+ }
156
+ });
157
+ }
158
+ }
131
159
  }
132
160
 
133
161
  // if `steps` are specified, then `initialStep` MUST be included
package/lib/issue.js CHANGED
@@ -1,24 +1,30 @@
1
1
  /*!
2
2
  * Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
3
3
  */
4
- import {evaluateTemplate, getZcapClient} from './helpers.js';
4
+ import {
5
+ evaluateTemplate,
6
+ getWorkflowIssuerInstances,
7
+ getZcapClient
8
+ } from './helpers.js';
5
9
  import {createPresentation} from '@digitalbazaar/vc';
6
10
 
7
- export async function issue({workflow, exchange} = {}) {
11
+ export async function issue({
12
+ workflow, exchange, format = 'application/vc'
13
+ } = {}) {
8
14
  // use any templates from workflow and variables from exchange to produce
9
15
  // credentials to be issued; issue via the configured issuer instance
10
16
  const verifiableCredential = [];
11
17
  const {credentialTemplates = []} = workflow;
12
18
  if(!credentialTemplates || credentialTemplates.length === 0) {
13
19
  // nothing to issue
14
- return {};
20
+ return {response: {}};
15
21
  }
16
22
 
17
23
  // evaluate template
18
- const credentialRequests = await Promise.all(credentialTemplates.map(
24
+ const issueRequests = await Promise.all(credentialTemplates.map(
19
25
  typedTemplate => evaluateTemplate({workflow, exchange, typedTemplate})));
20
26
  // issue all VCs
21
- const vcs = await _issue({workflow, credentialRequests});
27
+ const vcs = await _issue({workflow, issueRequests, format});
22
28
  verifiableCredential.push(...vcs);
23
29
 
24
30
  // generate VP to return VCs
@@ -29,15 +35,16 @@ export async function issue({workflow, exchange} = {}) {
29
35
  if(verifiableCredential.length > 0) {
30
36
  verifiablePresentation.verifiableCredential = verifiableCredential;
31
37
  }
32
- return {verifiablePresentation};
38
+ return {response: {verifiablePresentation}, format};
33
39
  }
34
40
 
35
- async function _issue({workflow, credentialRequests} = {}) {
41
+ async function _issue({workflow, issueRequests, format} = {}) {
36
42
  // create zcap client for issuing VCs
37
43
  const {zcapClient, zcaps} = await getZcapClient({workflow});
38
44
 
39
- // issue VCs in parallel
40
- const capability = zcaps.issue;
45
+ // get the zcap to use for the issue requests
46
+ const capability = _getIssueZcap({workflow, zcaps, format});
47
+
41
48
  // specify URL to `/credentials/issue` to handle case that capability
42
49
  // is not specific to it
43
50
  let url = capability.invocationTarget;
@@ -45,15 +52,32 @@ async function _issue({workflow, credentialRequests} = {}) {
45
52
  url += capability.invocationTarget.endsWith('/credentials') ?
46
53
  '/issue' : '/credentials/issue';
47
54
  }
48
- const results = await Promise.all(credentialRequests.map(request => {
49
- // normalize credential templates that return full VC API issue credential
50
- // requests and those that return only the `credential` param directly
51
- const json = request.credential ? request : {credential: request};
52
- return zcapClient.write({url, capability, json});
55
+
56
+ const issuedVCs = [];
57
+
58
+ // issue VCs in parallel
59
+ await Promise.all(issueRequests.map(async issueRequest => {
60
+ /* Note: Issue request formats can be any one of these:
61
+
62
+ 1. `{credential, options?}`
63
+ 2. `credential`
64
+
65
+ Normalize issue requests that use the full VC API issue request and those
66
+ that return only the `credential` param directly. */
67
+ const json = issueRequest.credential ?
68
+ issueRequest : {credential: issueRequest};
69
+ const {
70
+ data: {verifiableCredential}
71
+ } = await zcapClient.write({url, capability, json});
72
+ issuedVCs.push(verifiableCredential);
53
73
  }));
54
74
 
55
- // parse VCs from results
56
- const verifiableCredentials = results.map(
57
- ({data: {verifiableCredential}}) => verifiableCredential);
58
- return verifiableCredentials;
75
+ return issuedVCs;
76
+ }
77
+
78
+ function _getIssueZcap({workflow, zcaps, format}) {
79
+ const issuerInstances = getWorkflowIssuerInstances({workflow});
80
+ const {zcapReferenceIds: {issue: issueRefId}} = issuerInstances.find(
81
+ ({supportedFormats}) => supportedFormats.includes(format));
82
+ return zcaps[issueRefId];
59
83
  }
package/lib/openId.js CHANGED
@@ -6,6 +6,7 @@ import * as exchanges from './exchanges.js';
6
6
  import {
7
7
  compile, schemas, createValidateMiddleware as validate
8
8
  } from '@bedrock/validation';
9
+ import {evaluateTemplate, getWorkflowIssuerInstances} from './helpers.js';
9
10
  import {importJWK, SignJWT} from 'jose';
10
11
  import {
11
12
  openIdAuthorizationResponseBody,
@@ -19,7 +20,6 @@ import {asyncHandler} from '@bedrock/express';
19
20
  import bodyParser from 'body-parser';
20
21
  import {checkAccessToken} from '@bedrock/oauth2-verifier';
21
22
  import cors from 'cors';
22
- import {evaluateTemplate} from './helpers.js';
23
23
  import {issue} from './issue.js';
24
24
  import {klona} from 'klona';
25
25
  import {oid4vp} from '@digitalbazaar/oid4-client';
@@ -254,6 +254,7 @@ export async function createRoutes({
254
254
  }
255
255
  }
256
256
  */
257
+
257
258
  const result = await _processCredentialRequests(
258
259
  {req, res, isBatchRequest: false});
259
260
  if(!result) {
@@ -261,14 +262,27 @@ export async function createRoutes({
261
262
  return;
262
263
  }
263
264
 
264
- // send VC
265
+ /* Note: The `/credential` route only supports sending a single VC;
266
+ assume here that this workflow is configured for a single VC and an
267
+ error code would have been sent to the client to use the batch
268
+ endpoint if there was more than one VC to deliver. */
269
+ const {response, format} = result;
270
+ const {verifiablePresentation: {verifiableCredential: [vc]}} = response;
271
+
272
+ // parse any enveloped VC
273
+ let credential;
274
+ if(vc.type === 'EnvelopedVerifiableCredential' &&
275
+ vc.id?.startsWith('data:application/jwt,')) {
276
+ credential = vc.id.slice('data:application/jwt,'.length);
277
+ } else {
278
+ credential = vc;
279
+ }
280
+
281
+ // send OID4VCI response
265
282
  res.json({
266
- format: 'ldp_vc',
267
- /* Note: The `/credential` route only supports sending a single VC;
268
- assume here that this workflow is configured for a single VC and an
269
- error code would have been sent to the client to use the batch
270
- endpoint if there was more than one VC to deliver. */
271
- credential: result.verifiablePresentation.verifiableCredential[0]
283
+ // FIXME: this doesn't seem to be in the spec anymore (draft 14+)...
284
+ format,
285
+ credential
272
286
  });
273
287
  }));
274
288
 
@@ -317,8 +331,19 @@ export async function createRoutes({
317
331
  }
318
332
 
319
333
  // send VCs
320
- const responses = result.verifiablePresentation.verifiableCredential.map(
321
- vc => ({format: 'ldp_vc', credential: vc}));
334
+ const {response: {verifiablePresentation}, format} = result;
335
+ // FIXME: "format" doesn't seem to be in the spec anymore (draft 14+)...
336
+ const responses = verifiablePresentation.verifiableCredential.map(vc => {
337
+ // parse any enveloped VC
338
+ let credential;
339
+ if(vc.type === 'EnvelopedVerifiableCredential' &&
340
+ vc.id?.startsWith('data:application/jwt,')) {
341
+ credential = vc.id.slice('data:application/jwt,'.length);
342
+ } else {
343
+ credential = vc;
344
+ }
345
+ return {format, credential};
346
+ });
322
347
  res.json({credential_responses: responses});
323
348
  }));
324
349
 
@@ -528,11 +553,12 @@ async function _processCredentialRequests({req, res, isBatchRequest}) {
528
553
  }
529
554
 
530
555
  // before asserting, normalize credential requests to use `type` instead of
531
- // `types`; this is to allow for OID4VCI draft 20 implementers that followed
556
+ // `types`; this is to allow for OID4VCI draft implementers that followed
532
557
  // the non-normative examples
533
558
  _normalizeCredentialDefinitionTypes({credentialRequests});
534
- _assertCredentialRequests(
535
- {credentialRequests, expectedCredentialRequests});
559
+ const {format} = _assertCredentialRequests({
560
+ workflow, credentialRequests, expectedCredentialRequests
561
+ });
536
562
 
537
563
  // process exchange step if present
538
564
  const currentStep = exchange.step;
@@ -617,7 +643,7 @@ async function _processCredentialRequests({req, res, isBatchRequest}) {
617
643
  // replay attack detected) after exchange has been marked complete
618
644
 
619
645
  // issue VCs
620
- return issue({workflow, exchange});
646
+ return issue({workflow, exchange, format});
621
647
  }
622
648
 
623
649
  async function _requestDidProof({res, exchangeRecord}) {
@@ -813,24 +839,66 @@ async function _getAuthorizationRequest({req}) {
813
839
  }
814
840
 
815
841
  function _assertCredentialRequests({
816
- credentialRequests, expectedCredentialRequests
842
+ workflow, credentialRequests, expectedCredentialRequests
817
843
  }) {
844
+ // ensure that every credential request is for the same format
845
+ /* credential requests look like:
846
+ {
847
+ format: 'ldp_vc',
848
+ credential_definition: { '@context': [Array], type: [Array] }
849
+ }
850
+ */
851
+ let sharedFormat;
852
+ if(!credentialRequests.every(({format}) => {
853
+ if(sharedFormat === undefined) {
854
+ sharedFormat = format;
855
+ }
856
+ return sharedFormat === format;
857
+ })) {
858
+ throw new BedrockError(
859
+ 'Credential requests must all use the same format in this workflow.', {
860
+ name: 'DataError',
861
+ details: {httpStatusCode: 400, public: true}
862
+ });
863
+ }
864
+
865
+ // get all supported formats from available issuer instances; for simple
866
+ // workflow configs, a single issuer instance is used with only
867
+ // ensure that every credential request uses a format supported by
868
+ // issuer instances
869
+ const supportedFormats = new Set();
870
+ const issuerInstances = getWorkflowIssuerInstances({workflow});
871
+ issuerInstances.forEach(
872
+ instance => instance.supportedFormats.forEach(
873
+ supportedFormats.add, supportedFormats));
874
+ if(!supportedFormats.has(sharedFormat)) {
875
+ throw new BedrockError(
876
+ `Credential request format "${sharedFormat}" is not supported ` +
877
+ 'by this workflow.', {
878
+ name: 'DataError',
879
+ details: {httpStatusCode: 400, public: true}
880
+ });
881
+ }
882
+
818
883
  // ensure every credential request matches against an expected one and none
819
- // are missing
884
+ // are missing; `expectedCredentialRequests` formats are ignored based on the
885
+ // issuer instance supported formats and have already been checked
820
886
  if(!(credentialRequests.length === expectedCredentialRequests.length &&
821
887
  credentialRequests.every(cr => expectedCredentialRequests.some(
822
- ecr => _matchCredentialRequest(ecr, cr))))) {
823
- // FIXME: improve error
824
- throw new Error('unexpected credential request');
888
+ expected => _matchCredentialRequest(expected, cr))))) {
889
+ throw new BedrockError(
890
+ 'Unexpected credential request.', {
891
+ name: 'DataError',
892
+ details: {httpStatusCode: 400, public: true}
893
+ });
825
894
  }
895
+
896
+ return {format: sharedFormat};
826
897
  }
827
898
 
828
- function _matchCredentialRequest(a, b) {
829
- if(a.format !== b.format) {
830
- return false;
831
- }
832
- const {credential_definition: {'@context': c1, type: t1}} = a;
833
- const {credential_definition: {'@context': c2, type: t2}} = b;
899
+ function _matchCredentialRequest(expected, cr) {
900
+ const {credential_definition: {'@context': c1, type: t1}} = expected;
901
+ const {credential_definition: {'@context': c2, type: t2}} = cr;
834
902
  // contexts must match exact order but types can have different order
835
903
  return (c1.length === c2.length && t1.length === t2.length &&
836
904
  c1.every((c, i) => c === c2[i]) && t1.every(t => t2.some(x => t === x)));
package/lib/vcapi.js CHANGED
@@ -156,15 +156,15 @@ export async function processExchange({req, res, workflow, exchange}) {
156
156
  // FIXME: decide what the best recovery path is if delivery fails (but no
157
157
  // replay attack detected) after exchange has been marked complete
158
158
 
159
- // issue any VCs; may return an empty result if the step defines no
159
+ // issue any VCs; may return an empty response if the step defines no
160
160
  // VCs to issue
161
- const result = await issue({workflow, exchange});
161
+ const {response} = await issue({workflow, exchange});
162
162
 
163
163
  // if last `step` has a redirect URL, include it in the response
164
164
  if(step?.redirectUrl) {
165
- result.redirectUrl = step.redirectUrl;
165
+ response.redirectUrl = step.redirectUrl;
166
166
  }
167
167
 
168
- // send result
169
- res.json(result);
168
+ // send response
169
+ res.json(response);
170
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/vc-delivery",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock Verifiable Credential Delivery",
6
6
  "main": "./lib/index.js",
@@ -1,6 +1,7 @@
1
1
  /*!
2
2
  * Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
3
3
  */
4
+ import {MAX_ISSUER_INSTANCES} from '../lib/constants.js';
4
5
  import {schemas} from '@bedrock/validation';
5
6
 
6
7
  const credentialDefinition = {
@@ -34,6 +35,19 @@ const credentialDefinition = {
34
35
  }
35
36
  };
36
37
 
38
+ const expectedCredentialRequest = {
39
+ type: 'object',
40
+ additionalProperties: false,
41
+ required: ['credential_definition'],
42
+ properties: {
43
+ credential_definition: credentialDefinition,
44
+ format: {
45
+ type: 'string',
46
+ enum: ['ldp_vc', 'jwt_vc_json-ld']
47
+ }
48
+ }
49
+ };
50
+
37
51
  const openIdExchangeOptions = {
38
52
  title: 'OpenID Exchange options',
39
53
  type: 'object',
@@ -44,18 +58,7 @@ const openIdExchangeOptions = {
44
58
  title: 'OpenID Expected Credential Requests',
45
59
  type: 'array',
46
60
  minItems: 1,
47
- items: {
48
- type: 'object',
49
- additionalProperties: false,
50
- required: ['credential_definition', 'format'],
51
- properties: {
52
- credential_definition: credentialDefinition,
53
- format: {
54
- type: 'string',
55
- enum: ['ldp_vc']
56
- }
57
- }
58
- }
61
+ items: expectedCredentialRequest
59
62
  },
60
63
  preAuthorizedCode: {
61
64
  type: 'string'
@@ -123,6 +126,9 @@ const typedTemplate = {
123
126
  required: ['type', 'template'],
124
127
  additionalProperties: false,
125
128
  properties: {
129
+ id: {
130
+ type: 'string'
131
+ },
126
132
  type: {
127
133
  type: 'string',
128
134
  enum: ['jsonata']
@@ -140,6 +146,52 @@ export const credentialTemplates = {
140
146
  items: typedTemplate
141
147
  };
142
148
 
149
+ // to be updated in specific locations with `properties` and `required`
150
+ const zcapReferenceIds = {
151
+ title: 'Authorization Capability Reference IDs',
152
+ type: 'object',
153
+ additionalProperties: false
154
+ };
155
+
156
+ const vcFormats = {
157
+ title: 'Verifiable Credential formats',
158
+ type: 'array',
159
+ minItems: 1,
160
+ items: {
161
+ type: 'string'
162
+ }
163
+ };
164
+
165
+ const issuerInstance = {
166
+ title: 'Issuer Instance',
167
+ type: 'object',
168
+ required: ['zcapReferenceIds'],
169
+ additionalProperties: false,
170
+ properties: {
171
+ id: {
172
+ type: 'string'
173
+ },
174
+ supportedFormats: vcFormats,
175
+ zcapReferenceIds: {
176
+ ...zcapReferenceIds,
177
+ required: ['issue'],
178
+ properties: {
179
+ issue: {
180
+ type: 'string'
181
+ }
182
+ }
183
+ }
184
+ }
185
+ };
186
+
187
+ export const issuerInstances = {
188
+ title: 'Issuer Instances',
189
+ type: 'array',
190
+ minItems: 1,
191
+ maxItems: MAX_ISSUER_INSTANCES,
192
+ items: issuerInstance
193
+ };
194
+
143
195
  const step = {
144
196
  title: 'Exchange Step',
145
197
  type: 'object',
@@ -276,7 +328,7 @@ const openIdCredentialRequest = {
276
328
  credential_definition: credentialDefinition,
277
329
  format: {
278
330
  type: 'string',
279
- enum: ['ldp_vc']
331
+ enum: ['ldp_vc', 'jwt_vc_json-ld']
280
332
  },
281
333
  did: {
282
334
  type: 'string'