@bedrock/vc-delivery 4.5.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';
@@ -107,7 +107,8 @@ export async function createRoutes({
107
107
  jwks_uri: `${exchangeId}/openid/jwks`,
108
108
  token_endpoint: `${exchangeId}/openid/token`,
109
109
  credential_endpoint: `${exchangeId}/openid/credential`,
110
- batch_credential_endpoint: `${exchangeId}/openid/batch_credential`
110
+ batch_credential_endpoint: `${exchangeId}/openid/batch_credential`,
111
+ 'pre-authorized_grant_anonymous_access_supported': true
111
112
  };
112
113
  res.json(oauth2Config);
113
114
  }));
@@ -128,7 +129,8 @@ export async function createRoutes({
128
129
  jwks_uri: `${exchangeId}/openid/jwks`,
129
130
  token_endpoint: `${exchangeId}/openid/token`,
130
131
  credential_endpoint: `${exchangeId}/openid/credential`,
131
- batch_credential_endpoint: `${exchangeId}/openid/batch_credential`
132
+ batch_credential_endpoint: `${exchangeId}/openid/batch_credential`,
133
+ 'pre-authorized_grant_anonymous_access_supported': true
132
134
  };
133
135
  res.json(oauth2Config);
134
136
  }));
@@ -252,6 +254,7 @@ export async function createRoutes({
252
254
  }
253
255
  }
254
256
  */
257
+
255
258
  const result = await _processCredentialRequests(
256
259
  {req, res, isBatchRequest: false});
257
260
  if(!result) {
@@ -259,14 +262,27 @@ export async function createRoutes({
259
262
  return;
260
263
  }
261
264
 
262
- // 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
263
282
  res.json({
264
- format: 'ldp_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
- credential: result.verifiablePresentation.verifiableCredential[0]
283
+ // FIXME: this doesn't seem to be in the spec anymore (draft 14+)...
284
+ format,
285
+ credential
270
286
  });
271
287
  }));
272
288
 
@@ -315,8 +331,19 @@ export async function createRoutes({
315
331
  }
316
332
 
317
333
  // send VCs
318
- const responses = result.verifiablePresentation.verifiableCredential.map(
319
- 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
+ });
320
347
  res.json({credential_responses: responses});
321
348
  }));
322
349
 
@@ -526,11 +553,12 @@ async function _processCredentialRequests({req, res, isBatchRequest}) {
526
553
  }
527
554
 
528
555
  // before asserting, normalize credential requests to use `type` instead of
529
- // `types`; this is to allow for OID4VCI draft 20 implementers that followed
556
+ // `types`; this is to allow for OID4VCI draft implementers that followed
530
557
  // the non-normative examples
531
558
  _normalizeCredentialDefinitionTypes({credentialRequests});
532
- _assertCredentialRequests(
533
- {credentialRequests, expectedCredentialRequests});
559
+ const {format} = _assertCredentialRequests({
560
+ workflow, credentialRequests, expectedCredentialRequests
561
+ });
534
562
 
535
563
  // process exchange step if present
536
564
  const currentStep = exchange.step;
@@ -615,7 +643,7 @@ async function _processCredentialRequests({req, res, isBatchRequest}) {
615
643
  // replay attack detected) after exchange has been marked complete
616
644
 
617
645
  // issue VCs
618
- return issue({workflow, exchange});
646
+ return issue({workflow, exchange, format});
619
647
  }
620
648
 
621
649
  async function _requestDidProof({res, exchangeRecord}) {
@@ -811,24 +839,66 @@ async function _getAuthorizationRequest({req}) {
811
839
  }
812
840
 
813
841
  function _assertCredentialRequests({
814
- credentialRequests, expectedCredentialRequests
842
+ workflow, credentialRequests, expectedCredentialRequests
815
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
+
816
883
  // ensure every credential request matches against an expected one and none
817
- // are missing
884
+ // are missing; `expectedCredentialRequests` formats are ignored based on the
885
+ // issuer instance supported formats and have already been checked
818
886
  if(!(credentialRequests.length === expectedCredentialRequests.length &&
819
887
  credentialRequests.every(cr => expectedCredentialRequests.some(
820
- ecr => _matchCredentialRequest(ecr, cr))))) {
821
- // FIXME: improve error
822
- 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
+ });
823
894
  }
895
+
896
+ return {format: sharedFormat};
824
897
  }
825
898
 
826
- function _matchCredentialRequest(a, b) {
827
- if(a.format !== b.format) {
828
- return false;
829
- }
830
- const {credential_definition: {'@context': c1, type: t1}} = a;
831
- 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;
832
902
  // contexts must match exact order but types can have different order
833
903
  return (c1.length === c2.length && t1.length === t2.length &&
834
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.5.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'