├── .npmrc ├── test ├── .eslintrc.cjs └── Example.spec.js ├── .eslintrc.cjs ├── implementations ├── LearnCard.json ├── EWF.json ├── DTLab.json ├── ACA-Py.json ├── SecureKey.json ├── ApiCatalog.json ├── Trinsic.json ├── GS1US.json ├── Mavennet.json ├── index.js ├── DanubeTech.json ├── SpruceID.json └── DigitalBazaar.json ├── lib ├── constants.js ├── Implementation.js ├── Endpoint.js ├── oauth2.js ├── main.js └── requests.js ├── .github └── workflows │ └── main.yml ├── CHANGELOG.md ├── LICENSE ├── .gitignore ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /test/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/Example.spec.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import chai from 'chai'; 5 | const should = chai.should(); 6 | 7 | import {allImplementations} from '../lib/main.js'; 8 | 9 | describe('implementations', () => { 10 | it('should exist', async () => { 11 | should.exist(allImplementations); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint-config-digitalbazaar', 5 | 'eslint-config-digitalbazaar/jsdoc', 6 | 'digitalbazaar/module', 7 | ], 8 | env: { 9 | node: true 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 'latest', 13 | sourceType: 'module' 14 | }, 15 | rules: { 16 | 'jsdoc/check-examples': 'off' 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /implementations/LearnCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LearnCard", 3 | "implementation": "LearnCard", 4 | "issuers": [{ 5 | "id": "did:key:z6MkjSz4mYqcn7dePGuktJ5PxecMkXQQHWRg8Lm6okATyFVh", 6 | "endpoint": "https://bridge.learncard.com/credentials/issue", 7 | "tags": ["vc-api"] 8 | 9 | }], 10 | "verifiers": [{ 11 | "id": "", 12 | "endpoint": "https://bridge.learncard.com/credentials/verify", 13 | "tags": ["vc-api"] 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /implementations/EWF.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EWF", 3 | "implementation": "Energy Web Foundation VC-API", 4 | "issuers": [{ 5 | "id": "did:key:z6MksoRPRqnMWiivV4weRGGS9SiHXuqfJEYu95EiYtbvBxW6", 6 | "endpoint": "https://vc-api-dev.energyweb.org/v1/vc-api/credentials/issue", 7 | "tags": ["vc-api"] 8 | 9 | }], 10 | "verifiers": [{ 11 | "id": "", 12 | "endpoint": "https://vc-api-dev.energyweb.org/v1/vc-api/credentials/verify", 13 | "tags": ["vc-api"] 14 | }] 15 | } 16 | -------------------------------------------------------------------------------- /implementations/DTLab.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DTLab", 3 | "implementation": "DTT", 4 | "verifiers": [{ 5 | "id": "did:key:z6MkssSXBRQSLTRrSeaUqZsXGw8rKbeTtaRC3WLuLAEbspNS", 6 | "endpoint": "https://api.dtlab-labcn.app/credentials/verify", 7 | "options": {}, 8 | "tags": [] 9 | }], 10 | "issuers": [{ 11 | "id": "did:key:z6MkssSXBRQSLTRrSeaUqZsXGw8rKbeTtaRC3WLuLAEbspNS", 12 | "endpoint": "https://api.dtlab-labcn.app/credentials/issue", 13 | "options": {}, 14 | "tags": [] 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /implementations/ACA-Py.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ACA-py", 3 | "implementation": "ACA-Py", 4 | "verifiers": [ 5 | { 6 | "id": "https://aca-py.test-suite.app", 7 | "endpoint": "https://aca-py.test-suite.app/vc/credentials/verify", 8 | "tags": ["vc-api"] 9 | } 10 | ], 11 | "issuers": [ 12 | { 13 | "id": "did:key:z6MkmiYjSToh5NdEf9xNuh5mDGXGYinzcAy1s4feRHtkEQJr", 14 | "endpoint": "https://aca-py.test-suite.app/vc/credentials/issue", 15 | "tags": ["vc-api"] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /implementations/SecureKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SecureKey", 3 | "implementation": "TrustBloc (Sandbox)", 4 | "issuers": [{ 5 | "id": "did:key:z6MkjRagNiMu91DduvCvgEsqLZDVzrJzFrwahc4tXLt9DoHd", 6 | "endpoint": "https://issuer-vcs.sandbox.trustbloc.dev/vc-issuer-interop-key/credentials/issue", 7 | "tags": ["vc-api"] 8 | }], 9 | "verifiers": [{ 10 | "id": "", 11 | "endpoint": "https://verifier-vcs.sandbox.trustbloc.dev/vc-verifier-interop/verifier/credentials/verify", 12 | "tags": ["vc-api"] 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /implementations/ApiCatalog.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apicatalog.com", 3 | "implementation": "Iron Verifiable Credentials", 4 | "verifiers": [{ 5 | "id": "https://vc.apicatalog.com", 6 | "endpoint": "https://vc.apicatalog.com/credentials/verify", 7 | "tags": ["vc-api"] 8 | }], 9 | "issuers": [{ 10 | "id": "did:key:z6Mkska8oQD7QQQWxqa7L5ai4mH98HfAdSwomPFYKuqNyE2y", 11 | "endpoint": "https://vc.apicatalog.com/credentials/issue", 12 | "options": { 13 | "type": "Ed25519Signature2020" 14 | }, 15 | "tags": ["vc-api"] 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /implementations/Trinsic.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Trinsic", 3 | "implementation": "Trinsic", 4 | "issuers": [{ 5 | "id": "did:key:z6MkqbpLSbqnY1pxVyhBCDYcFsv4ZgGgqP32kzNrf5deWVPU", 6 | "endpoint": "https://interop.connect.trinsic.cloud/vc-api/credentials/issue", 7 | "options": { 8 | "type": "Ed25519Signature2020" 9 | }, 10 | "tags": ["vc-api"] 11 | }], 12 | "verifiers": [{ 13 | "id": "https://trinsic.id", 14 | "endpoint": "https://interop.connect.trinsic.cloud/vc-api/credentials/verify", 15 | "tags": ["vc-api"] 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /implementations/GS1US.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GS1 US", 3 | "implementation": "GS1 US DID-VC-API", 4 | "oauth2": { 5 | "clientId": "73c9bee3-1371-409e-981f-349e8753d0fe", 6 | "clientSecret": "CLIENT_SECRET_GS1US", 7 | "tokenAudience": "api://f0c5b99d-e3ac-49e4-8787-bf36cabcf47f", 8 | "tokenEndpoint": "https://login.microsoftonline.com/e5e65807-3d2a-4f67-a262-72f255868da0/oauth2/v2.0/token", 9 | "scopes": ["api://f0c5b99d-e3ac-49e4-8787-bf36cabcf47f/.default"] 10 | }, 11 | "issuers": [{ 12 | "id": "did:web:cbpvsvip-vc.gs1us.org", 13 | "endpoint": "https://vc.cbpsvip.gs1us.org/cbpsvip/credentials/issue", 14 | "tags": ["vc-api"] 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import * as didKey from '@digitalbazaar/did-method-key'; 5 | import https from 'https'; 6 | 7 | export const agent = new https.Agent({rejectUnauthorized: false}); 8 | 9 | export const headers = { 10 | Accept: 'application/ld+json,application/json', 11 | 'Content-Type': 'application/json', 12 | }; 13 | 14 | export const didKeyDriver = didKey.driver(); 15 | 16 | export const defaultHeaders = { 17 | Accept: 'application/json, application/ld+json, */*' 18 | }; 19 | 20 | export const postHeaders = { 21 | 'Content-Type': 'application/json', 22 | ...defaultHeaders 23 | }; 24 | 25 | export const sanitizeHeaders = ['Authorization']; 26 | -------------------------------------------------------------------------------- /implementations/Mavennet.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mavennet", 3 | "implementation": "Neoflow", 4 | "oauth2": { 5 | "clientId": "1skggb4216e0ohgjjq4aast9i1", 6 | "clientSecret": "CLIENT_SECRET_MAVENNET", 7 | "tokenAudience": "https://api.staging.refiner.neoflow.energy/v1", 8 | "tokenEndpoint": "https://api.staging.refiner.neoflow.energy/v1/auth" 9 | }, 10 | "issuers": [{ 11 | "id": "did:key:z6MkfFjyzk5CKMdnLacqay3kLMMaZEvKr8yxhks2HezmF4X3", 12 | "endpoint": "https://api.staging.refiner.neoflow.energy/credentials/issue", 13 | "options": { 14 | "type": "Ed25519Signature2018" 15 | }, 16 | "tags": ["vc-api"] 17 | }], 18 | "verifiers": [{ 19 | "id": "", 20 | "endpoint": "https://api.staging.refiner.neoflow.energy/credentials/verify", 21 | "tags": ["vc-api"] 22 | }] 23 | } 24 | -------------------------------------------------------------------------------- /implementations/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import appRoot from 'app-root-path'; 5 | import {createRequire} from 'node:module'; 6 | import {join} from 'node:path'; 7 | 8 | const require = createRequire(import.meta.url); 9 | const requireDir = require('require-dir'); 10 | 11 | const dir = requireDir('./'); 12 | 13 | // gets local implementations from an optional config file 14 | const getLocalImplementations = () => { 15 | try { 16 | const path = join( 17 | appRoot.toString(), '.localImplementationsConfig.cjs'); 18 | return require(path); 19 | } catch(e) { 20 | if(e?.code === 'MODULE_NOT_FOUND') { 21 | return []; 22 | } 23 | throw e; 24 | } 25 | }; 26 | 27 | export const implementerFiles = Object.values(dir) 28 | .concat(getLocalImplementations()); 29 | -------------------------------------------------------------------------------- /implementations/DanubeTech.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Danube Tech", 3 | "implementation": "uni", 4 | "issuers": [{ 5 | "id": "did:key:z6MksvTdeczd92YQ5W2V2gL9kXdRvD2AEiyJPoWieP95HUDo", 6 | "endpoint": "https://uniissuer.io/1.0/credentials/issue", 7 | "options": { 8 | "type": "Ed25519Signature2020" 9 | }, 10 | "tags": ["vc-api"] 11 | }, { 12 | "id": "did:key:z6MksvTdeczd92YQ5W2V2gL9kXdRvD2AEiyJPoWieP95HUDo", 13 | "endpoint": "https://uniissuer.io/1.0/credentials/issue", 14 | "options": { 15 | "type": "JsonWebSignature2020" 16 | }, 17 | "tags": ["JsonWebSignature2020"] 18 | }], 19 | "verifiers": [{ 20 | "id": "", 21 | "endpoint": "https://univerifier.io/1.0/credentials/verify", 22 | "tags": ["vc-api"] 23 | }], 24 | "didResolvers": [{ 25 | "id": "", 26 | "endpoint": "https://dev.uniresolver.io/1.0/identifiers", 27 | "tags": ["did-key"] 28 | }] 29 | } 30 | -------------------------------------------------------------------------------- /lib/Implementation.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import {Endpoint} from './Endpoint.js'; 5 | 6 | export class Implementation { 7 | constructor(settings) { 8 | this.settings = settings; 9 | const {oauth2} = settings; 10 | // these are properties we don't want to cast to 11 | // Endpoints 12 | const skip = ['oauth2']; 13 | for(const key in settings) { 14 | if(skip.includes(key)) { 15 | continue; 16 | } 17 | const settingProperty = settings[key]; 18 | if(!Array.isArray(settingProperty)) { 19 | continue; 20 | } 21 | // create a getter for each endpoint in the manifest 22 | Object.defineProperty(this, key, {get: () => { 23 | return settingProperty.map(setting => new Endpoint({ 24 | settings: setting, 25 | oauth2 26 | })); 27 | }}); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 10 9 | strategy: 10 | matrix: 11 | node-version: [20.x] 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - run: npm install --legacy-peer-deps 19 | - name: Run eslint 20 | run: npm run lint 21 | test-node: 22 | runs-on: ubuntu-latest 23 | needs: [lint] 24 | timeout-minutes: 10 25 | strategy: 26 | matrix: 27 | node-version: [20.x] 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Use Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | - run: npm install --legacy-peer-deps 35 | - name: Run test with Node.js ${{ matrix.node-version }} 36 | run: npm run test-node 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # vc-test-suite-implementations Changelog 2 | 3 | ## 4.0.0 - 2024-01-TBD 4 | 5 | ### Changed 6 | - **BREAKING**: Rename the tag to run Bitstring Status List test suite from 7 | `StatusList2021` to `BitstringStatusList`. 8 | 9 | ## 3.0.0 - 2024-01-02 10 | 11 | ### Changed 12 | - **BREAKING**: Rename `.vcApiTestImplementationsConfig.cjs` config for testing 13 | localhost implementations to `.localImplementationsConfig.cjs`. 14 | 15 | ## 2.1.0 - 2023-12-18 16 | 17 | ### Removed 18 | - Remove unsupported `Ed25519Signature2018` tag from implementations. 19 | - Remove `Mattr` from implementations list. 20 | 21 | ## 2.0.0 - 2023-12-14 22 | 23 | ### Changed 24 | - **BREAKING**: Renamed package name to `vc-test-suite-implementations`. 25 | - Update `README.md` to add additional information about requirements for 26 | opting into a test suite. 27 | 28 | ### Removed 29 | - Remove tags for `ecdsa`, `eddsa`, `ed25519signature2020`, and `vc2.0` test 30 | suites that are not associated with this implementations repo. 31 | 32 | ## 1.0.1 - 2023-12-07 33 | 34 | ### Removed 35 | - Removed unused "method" property from implementations' config. 36 | 37 | ## 1.0.0 38 | 39 | ### Added 40 | - Class `Endpoint` for wrapping generic endpoints. 41 | - API `filterByTag({property: 'verifiers', tags: ['VC_API']})` 42 | - See individual commits for history. 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, 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 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. 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 | 3. 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. 30 | -------------------------------------------------------------------------------- /lib/Endpoint.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import {makeHttpsRequest, zcapRequest} from './requests.js'; 5 | 6 | /** 7 | * Wraps settings from an implementation's json manifest 8 | * in an endpoint for testing with tags. 9 | * 10 | * @param {object} options - Options to use. 11 | * @param {object} [options.oauth2] - Oauth2 options. 12 | * @param {object} options.settings - Settings for the endpoint. 13 | */ 14 | export class Endpoint { 15 | constructor({oauth2, settings}) { 16 | this.settings = {...settings}; 17 | if(oauth2) { 18 | // add global oauth2 settings to endpoint settings 19 | this.settings.oauth2 = {...oauth2}; 20 | // scopes are endpoint specific so we need to add them 21 | // to the global oauth credentials 22 | if(Array.isArray(this.settings.scopes)) { 23 | this.settings.oauth2.scopes = [...this.settings.scopes]; 24 | } 25 | } 26 | } 27 | // ensure tags are unique 28 | get tags() { 29 | return new Set(this.settings.tags); 30 | } 31 | post({headers = {}, url, ...args}) { 32 | const {headers: _headers = {}, endpoint, oauth2, zcap} = this.settings; 33 | if(zcap) { 34 | return zcapRequest({ 35 | endpoint: url || endpoint, 36 | zcap, 37 | headers: {..._headers, ...headers}, 38 | ...args 39 | }); 40 | } 41 | return makeHttpsRequest({ 42 | url: url || endpoint, 43 | method: 'POST', 44 | oauth2, 45 | headers: {..._headers, ...headers}, 46 | ...args 47 | }); 48 | } 49 | put({headers = {}, url, ...args}) { 50 | const {headers: _headers = {}, endpoint, oauth2} = this.settings; 51 | return makeHttpsRequest({ 52 | url: url || endpoint, 53 | method: 'PUT', 54 | oauth2, 55 | headers: {..._headers, ...headers}, 56 | ...args 57 | }); 58 | } 59 | get({headers, url, ...args} = {}) { 60 | const {headers: _headers = {}, endpoint, oauth2} = this.settings; 61 | return makeHttpsRequest({ 62 | method: 'GET', 63 | url: url || endpoint, 64 | oauth2, 65 | headers: {..._headers, ...headers}, 66 | ...args 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Editor files 107 | *~ 108 | *.sw[nop] 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vc-test-suite-implementations", 3 | "version": "0.0.1", 4 | "description": "", 5 | "homepage": "https://github.com/w3c-ccg/vc-test-suite-implementations", 6 | "author": { 7 | "name": "Digital Bazaar, Inc.", 8 | "email": "support@digitalbazaar.com", 9 | "url": "https://digitalbazaar.com/" 10 | }, 11 | "type": "module", 12 | "main": "./lib/main.js", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/w3c-ccg/vc-test-suite-implementations" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/w3c-ccg/vc-test-suite-implementations/issues", 19 | "email": "support@digitalbazaar.com" 20 | }, 21 | "license": "BSD-3-Clause", 22 | "files": [ 23 | "lib/*.js", 24 | "implementations/*" 25 | ], 26 | "dependencies": { 27 | "@digitalbazaar/did-method-key": "^3.0.0", 28 | "@digitalbazaar/ed25519-signature-2020": "^4.0.1", 29 | "@digitalbazaar/ezcap": "^3.0.1", 30 | "@digitalbazaar/http-client": "^3.2.0", 31 | "app-root-path": "^3.0.0", 32 | "bnid": "^3.0.0", 33 | "lru-cache": "^7.14.0", 34 | "require-dir": "^1.2.0" 35 | }, 36 | "devDependencies": { 37 | "chai": "^4.3.3", 38 | "cross-env": "^7.0.3", 39 | "eslint": "^8.55.0", 40 | "eslint-config-digitalbazaar": "^5.0.1", 41 | "eslint-plugin-jsdoc": "^46.9.0", 42 | "eslint-plugin-unicorn": "^49.0.0", 43 | "mocha": "^10.0.0", 44 | "nyc": "^15.1.0" 45 | }, 46 | "nyc": { 47 | "exclude": [ 48 | "tests" 49 | ], 50 | "reporter": [ 51 | "html", 52 | "text-summary" 53 | ] 54 | }, 55 | "browser": { 56 | "buffer": false, 57 | "crypto": false, 58 | "util": false 59 | }, 60 | "engines": { 61 | "node": ">=18" 62 | }, 63 | "keywords": [ 64 | "Decentralized", 65 | "Linked Data" 66 | ], 67 | "scripts": { 68 | "test": "npm run test-node && npm run lint", 69 | "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 test/*.spec.js", 70 | "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text-summary npm run test-node", 71 | "coverage-ci": "cross-env NODE_ENV=test nyc --reporter=lcovonly npm run test-node", 72 | "coverage-report": "nyc report", 73 | "lint": "eslint ." 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/oauth2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import LruCache from 'lru-cache'; 5 | import {makeHttpsRequest} from './requests.js'; 6 | 7 | // the default expires_in time for access tokens in seconds 8 | const _defaultExpirationTimeInSeconds = 60 * 60; 9 | // caches 50 access tokens max 10 | const accessTokenCache = new LruCache({max: 50}); 11 | 12 | export async function constructOAuthHeader({ 13 | clientId, 14 | clientSecret, 15 | tokenAudience, 16 | tokenEndpoint, 17 | scopes 18 | }) { 19 | const client_secret = process.env[clientSecret]; 20 | if(!client_secret) { 21 | console.warn(`Env variable ${clientSecret} not set.`); 22 | return; 23 | } 24 | const {accessToken} = await _getNewAccessToken({ 25 | client_id: clientId, 26 | client_secret, 27 | token_endpoint: tokenEndpoint, 28 | audience: tokenAudience, 29 | grant_type: 'client_credentials', 30 | scopes 31 | }); 32 | return `Bearer ${accessToken}`; 33 | } 34 | 35 | /** 36 | * Gets a new access token from the provided URL. 37 | * 38 | * @param {object} options - Options to use. 39 | * @param {string} options.client_id - The ID of the client. 40 | * @param {string} options.client_secret - The client secret. 41 | * @param {string} options.token_endpoint - The URL to call. 42 | * @param {string} options.grant_type - The grant type. 43 | * @param {number} options.maxRetries - The maximum number of times to retry 44 | * the request. 45 | * @param {string} options.audience - The URL of resource server. 46 | * @param {Array} options.scopes - Scopes for the request. 47 | * 48 | * @returns {object} The access token. 49 | */ 50 | async function _getNewAccessToken({ 51 | client_id, 52 | client_secret, 53 | token_endpoint, 54 | grant_type, 55 | audience, 56 | scopes, 57 | maxRetries = 3 58 | }) { 59 | const cacheKey = _getCacheKey({clientId: client_id, scopes}); 60 | const cachedAccessToken = accessTokenCache.get(cacheKey); 61 | if(cachedAccessToken) { 62 | return {accessToken: cachedAccessToken}; 63 | } 64 | const body = new URLSearchParams({ 65 | client_id, 66 | client_secret, 67 | grant_type, 68 | audience, 69 | }); 70 | // scopes are space separated 71 | if(scopes && scopes.length > 0) { 72 | body.append('scope', scopes.join(' ')); 73 | } 74 | for(; maxRetries >= 0; --maxRetries) { 75 | const { 76 | access_token, 77 | expires_in = _defaultExpirationTimeInSeconds 78 | } = await _requestAccessToken({url: token_endpoint, body}); 79 | if(access_token) { 80 | // convert seconds expires_in to milliseconds ttl for cache 81 | const options = {ttl: expires_in * 1000}; 82 | const cacheKey = _getCacheKey({clientId: client_id, scopes}); 83 | accessTokenCache.set(cacheKey, access_token, options); 84 | return {accessToken: access_token}; 85 | } 86 | } 87 | throw new Error( 88 | `Service Unavailable: Could not renew token for ${audience}.`); 89 | } 90 | 91 | async function _requestAccessToken({url, body}) { 92 | let data; 93 | try { 94 | const {data: responseData, error} = await makeHttpsRequest({ 95 | url, 96 | method: 'post', 97 | body 98 | }); 99 | // this error is sanitized and can be thrown 100 | if(error) { 101 | throw error; 102 | } 103 | data = responseData; 104 | } catch(error) { 105 | console.error('Error getting access token.', {error}); 106 | throw error; 107 | } 108 | if(data && data.access_token) { 109 | return data; 110 | } 111 | return {}; 112 | } 113 | 114 | function _getCacheKey({clientId, scopes}) { 115 | if(Array.isArray(scopes) && (scopes.length > 0)) { 116 | // sort could mutate the order of the scopes set in the manifest 117 | // which could cause issues with the request 118 | return `${clientId}-${[...scopes].sort()}`; 119 | } 120 | return clientId; 121 | } 122 | -------------------------------------------------------------------------------- /implementations/SpruceID.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SpruceID", 3 | "implementation": "SpruceID", 4 | "issuers": [{ 5 | "id": "did:key:z6MkgYAGxLBSXa6Ygk1PnUbK2F7zya8juE9nfsZhrvY7c9GD", 6 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 7 | "options": { 8 | "type": "Ed25519Signature2020" 9 | }, 10 | "supports": { 11 | "vc": ["1.1", "2.0"] 12 | }, 13 | "tags": ["vc-api", "Ed25519Signature2020", "JWT", "vc2.0"] 14 | }, { 15 | "id": "did:key:z6MkgYAGxLBSXa6Ygk1PnUbK2F7zya8juE9nfsZhrvY7c9GD", 16 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 17 | "options": { 18 | "type": "DataIntegrityProof", 19 | "cryptosuite": "eddsa-rdfc-2022" 20 | }, 21 | "supports": { 22 | "vc": ["1.1", "2.0"] 23 | }, 24 | "tags": ["vc-api", "eddsa-rdfc-2022", "JWT", "vc2.0"] 25 | }, { 26 | "id": "did:key:zDnaeqRNmCGRy8f4RgNSoj9YiwG697iWB7htXNX89G8Nu3Hxo", 27 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 28 | "options": { 29 | "type": "DataIntegrityProof", 30 | "cryptosuite": "ecdsa-rdfc-2019" 31 | }, 32 | "supports": { 33 | "vc": ["1.1", "2.0"] 34 | }, 35 | "supportedEcdsaKeyTypes": ["P-256"], 36 | "tags": ["vc-api", "ecdsa-rdfc-2019", "JWT", "vc2.0"] 37 | }, { 38 | "id": "did:key:zDnaeqRNmCGRy8f4RgNSoj9YiwG697iWB7htXNX89G8Nu3Hxo", 39 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 40 | "options": { 41 | "type": "DataIntegrityProof", 42 | "cryptosuite": "ecdsa-sd-2023" 43 | }, 44 | "supports": { 45 | "vc": ["1.1", "2.0"] 46 | }, 47 | "supportedEcdsaKeyTypes": ["P-256"], 48 | "tags": ["vc-api", "ecdsa-sd-2023", "JWT", "vc2.0"] 49 | }, { 50 | "id": "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce", 51 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 52 | "options": { 53 | "type": "DataIntegrityProof", 54 | "cryptosuite": "ecdsa-rdfc-2019" 55 | }, 56 | "supports": { 57 | "vc": ["1.1", "2.0"] 58 | }, 59 | "supportedEcdsaKeyTypes": ["P-384"], 60 | "tags": ["vc-api", "ecdsa-rdfc-2019", "JWT", "vc2.0"] 61 | }, { 62 | "id": "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce", 63 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 64 | "options": { 65 | "type": "DataIntegrityProof", 66 | "cryptosuite": "ecdsa-sd-2023" 67 | }, 68 | "supports": { 69 | "vc": ["1.1", "2.0"] 70 | }, 71 | "supportedEcdsaKeyTypes": ["P-384"], 72 | "tags": ["vc-api", "ecdsa-sd-2023", "JWT", "vc2.0"] 73 | }, { 74 | "id": "did:key:zUC7Ker8jsi8tkhwz9CN1MdmunYbgXg4B7iTWJoPFiPty3ZrFg8j3a5bBX1hozUZxck8C73UunuWBZBy7PtYDCe9XYqGjWzXRqyLFqxWGo5nGArAvndYVqSQJhULMJFq5KKgW2X", 75 | "endpoint": "https://vc.spruceid.xyz/credentials/issue", 76 | "options": { 77 | "type": "DataIntegrityProof" 78 | }, 79 | "supports": { 80 | "vc": ["1.1", "2.0"] 81 | }, 82 | "tags": ["vc-api", "bbs-2023", "JWT", "vc2.0"] 83 | }], 84 | "verifiers": [{ 85 | "id": "https://spruceid.com", 86 | "endpoint": "https://vc.spruceid.xyz/credentials/verify", 87 | "supports": { 88 | "vc": ["1.1", "2.0"] 89 | }, 90 | "supportedEcdsaKeyTypes": ["P-256", "P-384"], 91 | "tags": ["vc-api", "Ed25519Signature2020", "JWT", "ecdsa-rdfc-2019", "ecdsa-sd-2023", "eddsa-rdfc-2022", "bbs-2023", "vc2.0"] 92 | }], 93 | "vpVerifiers": [{ 94 | "id": "https://spruceid.com", 95 | "endpoint": "https://vc.spruceid.xyz/presentations/verify", 96 | "supports": { 97 | "vc": ["1.1", "2.0"] 98 | }, 99 | "supportedEcdsaKeyTypes": ["P-256", "P-384"], 100 | "tags": ["vc-api", "Ed25519Signature2020", "JWT", "ecdsa-rdfc-2019", "ecdsa-sd-2023", "eddsa-rdfc-2022", "bbs-2023", "vc2.0"] 101 | }], 102 | "didResolvers": [{ 103 | "id": "https://spruceid.com", 104 | "endpoint": "https://vc.spruceid.xyz/identifiers", 105 | "tags": ["did-key"] 106 | }] 107 | } 108 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import {Implementation} from './Implementation.js'; 5 | import {implementerFiles} from '../implementations/index.js'; 6 | export {Endpoint} from './Endpoint.js'; 7 | 8 | const keyValues = implementerFiles.map( 9 | implementation => [implementation.name, new Implementation(implementation)]); 10 | 11 | export const implementations = new Map(keyValues); 12 | export const allImplementations = implementations; 13 | 14 | /** 15 | * Takes in a Map and a predicate and returns 16 | * a map with matches and a map with non-matches. 17 | * 18 | * @example filterImplementations({filter: ( 19 | * {value}) => value.issuers.some(i => i.tags.has('foo'))}); 20 | * 21 | * @param {object} options - Options to use. 22 | * @param {Map} [options.implementations=allImplementations] - A Map of 23 | * implementations. 24 | * @param {Function} options.filter - A function to 25 | * filter the map's entries on that returns true or false. 26 | * 27 | * @returns {{match: Map, nonMatch: Map}} Returns an object with matching 28 | * and non-matching maps. 29 | */ 30 | export const filterImplementations = ({ 31 | implementations = allImplementations, 32 | filter 33 | }) => { 34 | const match = new Map(); 35 | const nonMatch = new Map(); 36 | for(const [key, value] of implementations) { 37 | const result = filter({key, value}); 38 | if(result === true) { 39 | match.set(key, value); 40 | } else { 41 | nonMatch.set(key, value); 42 | } 43 | } 44 | return {match, nonMatch}; 45 | }; 46 | 47 | /** 48 | * Filters implementations by tags on a property in the settings. 49 | * 50 | * @example filterByTag({property: 'issuers', tags: ['VC-HTTP-API']}) 51 | * 52 | * @param {object} options - Options to use. 53 | * @param {Map} [options.implementations=allImplementations] - 54 | * Implementations to use. 55 | * @param {Array} [options.tags=[]] - Tags to search for. 56 | * @param {string} [options.property='issuers'] - The property to search for on 57 | * an implementation. 58 | * 59 | * @returns {{match: Map, nonMatch: Map}} Returns an object with matching 60 | * and non-matching maps. 61 | */ 62 | export const filterByTag = ({ 63 | implementations = allImplementations, 64 | tags = [], 65 | property = 'issuers' 66 | } = {}) => { 67 | const filter = ({value}) => { 68 | // if the property doesn't exist just use some on an empty array 69 | return (value[property] || []).some( 70 | endpoint => tags.some(tag => endpoint.tags.has(tag))); 71 | }; 72 | return filterImplementations({implementations, filter}); 73 | }; 74 | 75 | export const endpoints = { 76 | /** 77 | * Takes in a Map and a filter and returns 78 | * an object with match and nonMatch Maps. 79 | * 80 | * @example endpoints.filter({filter: ( 81 | * {value}) => value.issuers.some(i => i.tags.has('foo'))}); 82 | * 83 | * @param {object} options - Options to use. 84 | * @param {Map} [options.implementations=allImplementations] - A Map of 85 | * implementations. 86 | * @param {Function} options.filter - A function to 87 | * filter the map's entries that returns true or false. 88 | * 89 | * @returns {{match: Map, nonMatch: Map}} Returns an object with matching 90 | * and non-matching Maps with respective endpoints. 91 | */ 92 | filter({ 93 | implementations = allImplementations, 94 | filter 95 | }) { 96 | const match = new Map(); 97 | const nonMatch = new Map(); 98 | for(const [implementationName, implementation] of implementations) { 99 | const endpoints = filter({name: implementationName, implementation}); 100 | if(endpoints.length > 0) { 101 | match.set(implementationName, {endpoints, implementation}); 102 | } else { 103 | nonMatch.set(implementationName, {endpoints, implementation}); 104 | } 105 | } 106 | return {match, nonMatch}; 107 | }, 108 | /** 109 | * Filters endpoints by tags on a property in the settings. 110 | * 111 | * @example endpoints.filterByTag({property: 'issuers', tags: ['vc-api']}) 112 | * 113 | * @param {object} options - Options to use. 114 | * @param {Map} [options.implementations=allImplementations] - 115 | * Implementations to use. 116 | * @param {Array} [options.tags=[]] - Tags to search for. 117 | * @param {string} [options.property='issuers'] - The property to search for 118 | * on an implementation. 119 | * 120 | * @returns {{match: Map, nonMatch: Map}} Returns an object with matching 121 | * and non-matching Maps with the endpoints matching the property and 122 | * filter. 123 | */ 124 | filterByTag({ 125 | implementations = allImplementations, 126 | tags = [], 127 | property = 'issuers' 128 | } = {}) { 129 | const filter = ({implementation}) => { 130 | // if the property doesn't exist just use an empty array 131 | return (implementation[property] || []).filter( 132 | endpoint => tags.some(tag => endpoint.tags.has(tag))); 133 | }; 134 | return endpoints.filter({implementations, filter}); 135 | } 136 | }; 137 | -------------------------------------------------------------------------------- /lib/requests.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved. 3 | */ 4 | import { 5 | agent, 6 | defaultHeaders, 7 | didKeyDriver, 8 | postHeaders, 9 | sanitizeHeaders 10 | } from './constants.js'; 11 | import {constructOAuthHeader} from './oauth2.js'; 12 | import {decodeSecretKeySeed} from 'bnid'; 13 | import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020'; 14 | import {httpClient} from '@digitalbazaar/http-client'; 15 | import {ZcapClient} from '@digitalbazaar/ezcap'; 16 | 17 | /** 18 | * Makes an https request. 19 | * 20 | * @param {object} options - Options to use. 21 | * @param {URL} options.url - A url. 22 | * @param {object} [options.json] - JSON for the request. 23 | * @param {object} [options.body] - A body for the request. 24 | * @param {object} [options.headers] - Headers for the request. 25 | * @param {string} options.method - The HTTP method for the request. 26 | * @param {object} [options.oauth2] - OAuth2 credentialss. 27 | * @param {object} [options.searchParams] - URL Queries for the request. 28 | * 29 | * @returns {object} The results from the request. 30 | */ 31 | export async function makeHttpsRequest({ 32 | url, 33 | json, 34 | body, 35 | headers, 36 | method, 37 | oauth2, 38 | searchParams 39 | }) { 40 | let result; 41 | let error; 42 | if(oauth2) { 43 | headers.Authorization = await constructOAuthHeader({...oauth2}); 44 | } 45 | try { 46 | result = await httpClient(url, { 47 | body, method, json, headers, agent, searchParams 48 | }); 49 | } catch(e) { 50 | error = _sanitizeErrorHeaders({error: e}); 51 | } 52 | const {data, statusCode} = _getDataAndStatus({result, error}); 53 | // if a result is returned sanitize it 54 | if(result) { 55 | result = _sanitizeResponseHeaders({response: result, data}); 56 | } 57 | return {result, error, data, statusCode}; 58 | } 59 | 60 | export async function zcapRequest({ 61 | endpoint, 62 | json, 63 | zcap, 64 | headers = defaultHeaders 65 | }) { 66 | let result; 67 | let error; 68 | let capability = zcap.capability; 69 | // we are storing the zcaps stringified right now 70 | if(typeof capability === 'string') { 71 | capability = JSON.parse(capability); 72 | } 73 | try { 74 | // assume that the keySeed is set in the test environment 75 | const secretKeySeed = process.env[zcap.keySeed]; 76 | if(!secretKeySeed) { 77 | console.warn(`ENV variable ${zcap.keySeed} is required.`); 78 | } 79 | const zcapClient = await _getZcapClient({secretKeySeed}); 80 | result = await zcapClient.write({ 81 | url: endpoint, 82 | json, 83 | headers: { 84 | ...postHeaders, 85 | // passed in headers will overwrite postHeaders 86 | ...headers 87 | }, 88 | capability 89 | }); 90 | } catch(e) { 91 | error = _sanitizeErrorHeaders({error: e}); 92 | } 93 | const {data, statusCode} = _getDataAndStatus({result, error}); 94 | // if a result is returned sanitize it 95 | if(result) { 96 | result = _sanitizeResponseHeaders({response: result, data}); 97 | } 98 | return {result, error, data, statusCode}; 99 | } 100 | 101 | async function _getZcapClient({secretKeySeed}) { 102 | const seed = await decodeSecretKeySeed({secretKeySeed}); 103 | const didKey = await didKeyDriver.generate({seed}); 104 | const {didDocument: {capabilityInvocation}} = didKey; 105 | return new ZcapClient({ 106 | SuiteClass: Ed25519Signature2020, 107 | invocationSigner: didKey.keyPairs.get(capabilityInvocation[0]).signer(), 108 | agent 109 | }); 110 | } 111 | 112 | function _getDataAndStatus({result = {}, error = {}}) { 113 | let data = result.data || error.data; 114 | // FIXME remove this once VC-API returns from the issuer 115 | // are finalized. 116 | if(data && data.verifiableCredential) { 117 | data = data.verifiableCredential; 118 | } 119 | const statusCode = result.status || error.status; 120 | return {data, statusCode}; 121 | } 122 | 123 | function _sanitizeErrorHeaders({error}) { 124 | if(error.response) { 125 | error.response = _sanitizeResponseHeaders({ 126 | response: error.response, 127 | data: error.data 128 | }); 129 | } 130 | if(error.request) { 131 | error.request = new global.Request(error.request, { 132 | headers: _sanitizeHeaders({httpMessage: error.request}) 133 | }); 134 | } 135 | return error; 136 | } 137 | 138 | function _sanitizeResponseHeaders({response, data}) { 139 | const newResponse = new global.Response(JSON.stringify(data), { 140 | headers: _sanitizeHeaders({httpMessage: response}), 141 | status: response.status, 142 | statusText: response.statusText 143 | }); 144 | if(data) { 145 | // transfer the already parsed data to the newResponse 146 | // we are overwriting the data getter in Response here 147 | Object.defineProperty(newResponse, 'data', {value: data}); 148 | } 149 | return newResponse; 150 | } 151 | 152 | /** 153 | * Takes in either a response or request & sanitizes the headers. 154 | * 155 | * @private 156 | * 157 | * @param {object} options - Options to use. 158 | * @param {global.Response|global.Request} options.httpMessage - A http message. 159 | * @param {Array} [options.headers=sanitizeHeaders] - A list of headers 160 | * to sanitize from the http message. 161 | * 162 | * @returns {global.Headers} - Returns http headers. 163 | */ 164 | function _sanitizeHeaders({httpMessage, headers = sanitizeHeaders}) { 165 | if(!httpMessage) { 166 | return new global.Headers(); 167 | } 168 | const {headers: messageHeaders} = httpMessage; 169 | // Clone the response headers 170 | const newHeaders = new global.Headers(messageHeaders); 171 | for(const header of headers) { 172 | // sanitize the headers to prevent 173 | // authn tokens / information potentially in logs 174 | newHeaders.set(header, '** SANITIZED TO PREVENT EXPOSING OF SECRETS ***'); 175 | } 176 | return newHeaders; 177 | } 178 | -------------------------------------------------------------------------------- /implementations/DigitalBazaar.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Digital Bazaar", 3 | "implementation": "Veres (Q/A)", 4 | "issuers": [{ 5 | "id": "https://issuer.qa.veres.app/issuers/z19vkDXghpYCFp7GE8ZCpvJn8", 6 | "endpoint": "https://issuer.qa.veres.app/issuers/z19vkDXghpYCFp7GE8ZCpvJn8/credentials/issue", 7 | "zcap": { 8 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:cedbbd4b-f49f-46de-a102-2ade0ec24b8c\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz19vkDXghpYCFp7GE8ZCpvJn8\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z19vkDXghpYCFp7GE8ZCpvJn8/credentials\",\"expires\":\"2024-11-19T13:57:42Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-20T13:57:43Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz19vkDXghpYCFp7GE8ZCpvJn8\"],\"proofValue\":\"zvupVcxzQqaUMXStHGWxHAJ8KiMiaWJcqSuZvpt8tjm1fpdDM3ihVQ1LRdEnVRnMuNR75WGdBhE4Vs5BNtotWopx\"}}", 9 | "keySeed": "KEY_SEED_DB" 10 | }, 11 | "tags": ["BitstringStatusList", "Revocation"] 12 | }, { 13 | "id": "https://issuer.qa.veres.app/issuers/z1ACapdWkek1Kqw87GZM5c9D6", 14 | "endpoint": "https://issuer.qa.veres.app/issuers/z1ACapdWkek1Kqw87GZM5c9D6/credentials/issue", 15 | "zcap": { 16 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:0551e7ef-b30b-42ed-9e3c-dd8c45fdd657\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1ACapdWkek1Kqw87GZM5c9D6\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z1ACapdWkek1Kqw87GZM5c9D6/credentials\",\"expires\":\"2024-11-19T13:59:45Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-20T13:59:46Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1ACapdWkek1Kqw87GZM5c9D6\"],\"proofValue\":\"zWEnRPYfPCXSSJqpt2PCZNo8ZfKTDdfJTubxrT4vdqW36GoHq6QQTP1Z2cRUhr2Rvq76h9xSF184FY5NM15E9q43\"}}", 17 | "keySeed": "KEY_SEED_DB" 18 | }, 19 | "tags": ["BitstringStatusList", "Suspension"] 20 | }, { 21 | "id": "https://issuer.qa.veres.app/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR", 22 | "endpoint": "https://issuer.qa.veres.app/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR/credentials/issue", 23 | "zcap": { 24 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:2432d7c1-8082-410d-b9fb-0c40ae0dbce4\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1AEwLo7tZ3TrsPgRcgLJqQvR\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR/credentials/issue\",\"expires\":\"2024-11-15T19:06:05Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-16T19:06:06Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1AEwLo7tZ3TrsPgRcgLJqQvR\"],\"proofValue\":\"z2x1TjQQHHS82K76CV9JzTom42CHhDhq2ShiPvqfCJWqvq2iQuveFW8mik9f4XytctzQDQpiYsuDx3Y27dxKJbbbd\"}}", 25 | "keySeed": "KEY_SEED_DB" 26 | }, 27 | "tags": ["vc-api"] 28 | }], 29 | "publishStatusLists": [{ 30 | "zcap": { 31 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:6b68dcde-7303-42af-b2d0-2166e483707f\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz19vkDXghpYCFp7GE8ZCpvJn8\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z19vkDXghpYCFp7GE8ZCpvJn8/slcs\",\"expires\":\"2024-11-19T13:57:42Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-20T13:57:43Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz19vkDXghpYCFp7GE8ZCpvJn8\"],\"proofValue\":\"z4UkGkftbxb6LqzyAR5rGWByTRcj1FpX9zXMwiyJhrGrh3TWaXKjmU9EEjLAbcrLb27yywMt3hx3KrhbJHqSPBg9q\"}}", 32 | "keySeed": "KEY_SEED_DB" 33 | }, 34 | "tags": ["BitstringStatusList", "Revocation"] 35 | }, { 36 | "zcap": { 37 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:f3d00c42-c539-48bc-8ffd-f067c7c91e1d\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1ACapdWkek1Kqw87GZM5c9D6\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z1ACapdWkek1Kqw87GZM5c9D6/slcs\",\"expires\":\"2024-11-19T13:59:45Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-20T13:59:46Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1ACapdWkek1Kqw87GZM5c9D6\"],\"proofValue\":\"z4aSSuEnyg6mDmCRdmd9ZBaVpodr8mjMyGDzuMPSLu8iCVBrhVxm4aTc2xpikCthuJR66KUj3PwwbXB74hAN7FkXy\"}}", 38 | "keySeed": "KEY_SEED_DB" 39 | }, 40 | "tags": ["BitstringStatusList", "Suspension"] 41 | }], 42 | "setStatusLists": [{ 43 | "endpoint": "https://issuer.qa.veres.app/issuers/z19vkDXghpYCFp7GE8ZCpvJn8/credentials/status", 44 | "zcap": { 45 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:cedbbd4b-f49f-46de-a102-2ade0ec24b8c\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz19vkDXghpYCFp7GE8ZCpvJn8\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z19vkDXghpYCFp7GE8ZCpvJn8/credentials\",\"expires\":\"2024-11-19T13:57:42Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-20T13:57:43Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz19vkDXghpYCFp7GE8ZCpvJn8\"],\"proofValue\":\"zvupVcxzQqaUMXStHGWxHAJ8KiMiaWJcqSuZvpt8tjm1fpdDM3ihVQ1LRdEnVRnMuNR75WGdBhE4Vs5BNtotWopx\"}}", 46 | "keySeed": "KEY_SEED_DB" 47 | }, 48 | "tags": ["BitstringStatusList", "Revocation"] 49 | }, { 50 | "endpoint": "https://issuer.qa.veres.app/issuers/z1ACapdWkek1Kqw87GZM5c9D6/credentials/status", 51 | "zcap": { 52 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:0551e7ef-b30b-42ed-9e3c-dd8c45fdd657\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1ACapdWkek1Kqw87GZM5c9D6\",\"invocationTarget\":\"https://issuer.qa.veres.app/issuers/z1ACapdWkek1Kqw87GZM5c9D6/credentials\",\"expires\":\"2024-11-19T13:59:45Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-20T13:59:46Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fissuer.qa.veres.app%2Fissuers%2Fz1ACapdWkek1Kqw87GZM5c9D6\"],\"proofValue\":\"zWEnRPYfPCXSSJqpt2PCZNo8ZfKTDdfJTubxrT4vdqW36GoHq6QQTP1Z2cRUhr2Rvq76h9xSF184FY5NM15E9q43\"}}", 53 | "keySeed": "KEY_SEED_DB" 54 | }, 55 | "tags": ["BitstringStatusList", "Suspension"] 56 | }], 57 | "verifiers": [{ 58 | "id": "https://verifier.qa.veres.app/verifiers/z19jXQPi819fJVaBRFxZXqXay", 59 | "endpoint": "https://verifier.qa.veres.app/verifiers/z19jXQPi819fJVaBRFxZXqXay/credentials/verify", 60 | "zcap": { 61 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:73c6ab6d-fd9d-48d8-8260-c0839555e824\",\"controller\":\"did:key:z6MkptjaoxjyKQFSqf1dHXswP6EayYhPQBYzprVCPmGBHz9S\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fverifier.qa.veres.app%2Fverifiers%2Fz19jXQPi819fJVaBRFxZXqXay\",\"invocationTarget\":\"https://verifier.qa.veres.app/verifiers/z19jXQPi819fJVaBRFxZXqXay/credentials/verify\",\"expires\":\"2024-11-15T17:57:43Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-11-16T17:57:44Z\",\"verificationMethod\":\"did:key:z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b#z6MkmMeWhZTJr8mhA2n48FoKV1kLqJAm9eYdu4cdTtTC6Y5b\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fverifier.qa.veres.app%2Fverifiers%2Fz19jXQPi819fJVaBRFxZXqXay\"],\"proofValue\":\"z2Jcv3ZMfKX4FgXwnK7g4ix8WpWxhtiPdFdL1qjw7Y8XsNrEbeK5CosBPcZwLm9SrCafahF4tkufUmg5QtX51kE11\"}}", 62 | "keySeed": "KEY_SEED_DB" 63 | }, 64 | "tags": ["vc-api", "BitstringStatusList"] 65 | }], 66 | "didResolvers": [{ 67 | "id": "", 68 | "endpoint": "https://qa.veresresolver.dev/1.0/identifiers/resolve", 69 | "tags": ["did-key"] 70 | }] 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VC Test Suite Implementations _(vc-test-suite-implementations)_ 2 | 3 | [![Build status](https://img.shields.io/github/workflow/status/w3c-ccg/vc-test-suite-implementations/Node.js%20CI)](https://github.com/w3c-ccg/vc-test-suite-implementations/actions?query=workflow%3A%22Node.js+CI%22) 4 | 5 | > Implementations list used across various W3C CCG test suites. 6 | 7 | ## Table of Contents 8 | 9 | - [VC Test Suite Implementations _(vc-test-suite-implementations)_](#vc-test-suite-implementations-vc-test-suite-implementations) 10 | - [Table of Contents](#table-of-contents) 11 | - [Background](#background) 12 | - [Security](#security) 13 | - [Install](#install) 14 | - [NPM](#npm) 15 | - [Development](#development) 16 | - [Usage](#usage) 17 | - [Adding a new implementation](#adding-a-new-implementation) 18 | - [Testing locally](#testing-locally) 19 | - [Opting into a Test Suite](#opting-into-a-test-suite) 20 | - [Contribute](#contribute) 21 | - [License](#license) 22 | 23 | ## Background 24 | 25 | Implementations added to this package are tested against various test suites 26 | in order to demonstrate interoperability. 27 | 28 | ## Security 29 | 30 | Please do not commit any sensitive materials such as oauth2 client secret or 31 | client secrets used for signing zcaps or HTTP Signature Headers. 32 | 33 | ## Install 34 | 35 | - Node.js 18+ is required. 36 | 37 | ### NPM 38 | 39 | To install via NPM: 40 | 41 | ``` 42 | npm install w3c-ccg/vc-test-suite-implementations 43 | ``` 44 | 45 | ### Development 46 | 47 | To install locally (for development): 48 | 49 | ``` 50 | git clone https://github.com/w3c-ccg/vc-test-suite-implementations.git 51 | cd vc-test-suite-implementations 52 | npm install 53 | ``` 54 | 55 | ## Usage 56 | 57 | 58 | ### Adding a new implementation 59 | Please add implementations to the `./implementations` directory. 60 | Implementation configuration files are expressed in JSON and use roughly the 61 | following form: 62 | 63 | ```json 64 | { 65 | "name": "My Company", 66 | "implementation": "My Implementation Name", 67 | "oauth2": { 68 | "clientId": "bar", 69 | "clientSecret": "CLIENT_SECRET_MY_COMPANY", 70 | "tokenAudience": "https://product.example.com", 71 | "tokenEndpoint": "https://product.example.com/oauth/token" 72 | }, 73 | "issuers": [{ 74 | "id": "urn:uuid:my:implementation:issuer:id", 75 | "endpoint": "https://product.example.com/issuers/foo/credentials/issue", 76 | "zcap": { 77 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:4d44084c-334e-46dc-ac23-5e26f75262b6\",\"controller\":\"did:key:zFoo\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fmy.implementation.net%2Fissuers%2Fz19wCeJafpsTzvA6hZksz7TYF\",\"invocationTarget\":\"https://my.implementation.net/issuers/z19wCeJafpsTzvA6hZksz7TYF/credentials/issue\",\"expires\":\"2022-05-29T17:26:30Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2022-02-28T17:26:30Z\",\"verificationMethod\":\"did:key:z6Mkk2x1J4jCmaHDyYRRW1NB7CzeKYbjo3boGfRiefPzZjLQ#z6Mkk2x1J4jCmaHDyYRRW1NB7CzeKYbjo3boGfRiefPzZjLQ\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fmy.implementation.net%2Fissuers%2Fz19wCeJafpsTzvA6hZksz7TYF\"],\"proofValue\":\"zBar\"}}", 78 | "keySeed": "KEY_SEED_DB" 79 | }, 80 | "tags": ["vc-api"] 81 | }], 82 | "verifiers": [{ 83 | "id": "https://product.example.com/verifiers/z19uokPn3b1Z4XDbQSHo7VhFR", 84 | "endpoint": "https://product.example.com/verifiers/z19uokPn3b1Z4XDbQSHo7VhFR/credentials/verify", 85 | "zcap": { 86 | "capability": "{\"@context\":[\"https://w3id.org/zcap/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"],\"id\":\"urn:uuid:41473f9f-9e44-4ac9-9ac2-c86a6f695703\",\"controller\":\"did:key:zFoo\",\"parentCapability\":\"urn:zcap:root:https%3A%2F%2Fmy.implementation.net%3A40443%2Fverifiers%2Fz19uokPn3b1Z4XDbQSHo7VhFR\",\"invocationTarget\":\"https://my.implementation.net/verifiers/zBar/credentials/verify\",\"expires\":\"2023-03-17T17:39:49Z\",\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2022-03-17T17:39:49Z\",\"verificationMethod\":\"did:key:zFoo#zBar\",\"proofPurpose\":\"capabilityDelegation\",\"capabilityChain\":[\"urn:zcap:root:https%3A%2F%2Fmy.application.net%2Fverifiers%2FzFoo\"],\"proofValue\":\"zBar\"}}", 87 | "keySeed": "KEY_SEED_DB" 88 | }, 89 | "tags": ["vc-api"] 90 | }] 91 | } 92 | ``` 93 | 94 | Please note: implementations may have security using oauth2 or zcaps, but not 95 | both. 96 | Implementations may also contain no security (do not add a OAUTH2 or ZCAP 97 | section in that case). 98 | 99 | ### Testing locally 100 | 101 | If you need to test implementations for endpoints running locally, create a 102 | config file in the root dir of the test suite: 103 | 104 | ``` 105 | .localImplementationsConfig.cjs 106 | ``` 107 | 108 | This file must be a CommonJS module that exports an array of implementations: 109 | 110 | ```js 111 | // file .localImplementationsConfig.cjs 112 | module.exports = [{ 113 | "name": "My Company", 114 | "implementation": "My Implementation Name", 115 | "issuers": [{ 116 | "id": "urn:uuid:my:implementation:issuer:id", 117 | "endpoint": "https://localhost:40443/issuers/foo/credentials/issue", 118 | "tags": ["vc-api", "localhost"] 119 | }], 120 | "verifiers": [{ 121 | "id": "https://localhost:40443/verifiers/z19uokPn3b1Z4XDbQSHo7VhFR", 122 | "endpoint": "https://localhost:40443/verifiers/z19uokPn3b1Z4XDbQSHo7VhFR/credentials/verify", 123 | "tags": ["vc-api", "localhost"] 124 | }] 125 | }]; 126 | ``` 127 | 128 | To run the tests only against the localhost implementation, update the test 129 | suite to filter implementations using the specified tag in your config file. 130 | 131 | For instance, if your `.localImplementationsConfig.cjs` looks like above 132 | in the `vc-api-issuer-test-suite`, you can adjust the 133 | [tag](https://github.com/w3c-ccg/vc-api-issuer-test-suite/blob/main/tests/10-issuer.js#L14) 134 | to filter the implementation by `localhost` rather than `vc-api` and 135 | then run the tests. 136 | 137 | ### Opting into a Test Suite 138 | 139 | Please Note: 140 | 141 | 1. Tags serve as identifiers to determine which test suites to run on your 142 | issuers and verifiers. The first issuer/verifier found with a specific 143 | tag from your list will be run against the test suite, while subsequent issuers 144 | and verifiers bearing the same tag won't. This applies to most of the test 145 | suites associated with this implementations repository listed below. 146 | 147 | For instance, if you have added the tag `vc-api` to all your issuers and 148 | verifiers to run them with vc api issuer and verifier test suites, only the 149 | first match will be used in the test suites. In the sample configuration below, 150 | only the issuer and verifier with the IDs 151 | https://product.example.com/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR and 152 | https://product.example.com/verifiers/z1AEwLo7tZ3TrsPgRcgLJqQvR will be selected. 153 | Therefore, please avoid adding duplicate tags. 154 | 155 | Example 156 | ```js 157 | // Only the first match https://product.example.com/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR 158 | // and https://product.example.com/verifiers/z1AEwLo7tZ3TrsPgRcgLJqQvR will be 159 | // run against the VC API issuer and verifier test suites. 160 | { 161 | "issuers": [{ 162 | "id": "https://product.example.com/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR", 163 | "endpoint": "https://product.example.com/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR/credentials/issue", 164 | "tags": ["vc-api"] 165 | }, { 166 | "id": "https://product.example.com/issuers/z4Rq7N1lT6zVwFgXk8JYdCcKpU", 167 | "endpoint": "https://product.example.com/issuers/z4Rq7N1lT6zVwFgXk8JYdCcKpU/credentials/issue", 168 | "tags": ["vc-api"] 169 | }], 170 | "verifiers": [{ 171 | "id": "https://product.example.com/verifiers/z1AEwLo7tZ3TrsPgRcgLJqQvR", 172 | "endpoint": "https://product.example.com/verifiers/z1AEwLo7tZ3TrsPgRcgLJqQvR/credentials/verify", 173 | "tags": ["vc-api"] 174 | }, { 175 | "id": "https://product.example.com/verifiers/z4Rq7N1lT6zVwFgXk8JYdCcKpU", 176 | "endpoint": "https://product.example.com/verifiers/z4Rq7N1lT6zVwFgXk8JYdCcKpU/credentials/verify", 177 | "tags": ["vc-api"] 178 | }] 179 | } 180 | ``` 181 | 182 | 2. If you want your issuer or verifier to run against multiple test suites, you 183 | can assign multiple tags to them, eliminating the need for redundant entries. 184 | For instance, if an issuer with the ID 185 | https://product.example.com/issuers/z1AEwLo7tZ3TrsPgRcgLJqQvR can be run with 186 | both the VC Bitstring Status List and VC API test suites, a single entry with 187 | multiple tags can be used. This consolidated entry, containing tags for 188 | both VC API and VC Bitstring Status List test suites, ensures that the issuer and 189 | the verifier will be run against both test suites. Here is an example of how to 190 | structure the entry: 191 | 192 | For Example: 193 | ```js 194 | { 195 | "issuers": [{ 196 | "id": "https://product.example.com/issuers/z4Rq7N1lT6zVwFgXk8JYdCcKpU", 197 | "endpoint": "https://product.example.com/issuers/z4Rq7N1lT6zVwFgXk8JYdCcKpU/credentials/issue", 198 | "tags": ["vc-api", "BitstringStatusList", "Suspension"] 199 | }], 200 | "verifiers": [{ 201 | "id": "https://product.example.com/verifiers/z4Rq7N1lT6zVwFgXk8JYdCcKpU", 202 | "endpoint": "https://product.example.com/verifiers/z4Rq7N1lT6zVwFgXk8JYdCcKpU/credentials/verify", 203 | "tags": ["vc-api"] 204 | }] 205 | } 206 | ``` 207 | 208 | #### VC API Issuer and Verifier Test Suites 209 | 210 | * `vc-api` - This tag will run the [vc-api-issuer tests](https://github.com/w3c-ccg/vc-api-issuer-test-suite) 211 | on your issuer and the [vc-api-verifier tests](https://github.com/w3c-ccg/vc-api-verifier-test-suite) 212 | on your verifier. 213 | 214 | NOTE: Currently the vc api verifier test suite uses `Ed25519Signature2020` as 215 | the default signature for the mock VCs that are sent to the verifiers since it 216 | is most widely implemented. So, the verifier you add for `vc-api` must support 217 | verification of VCs with `Ed25519Signature2020` signature to pass verification 218 | tests. 219 | 220 | As of 2023, `Ed25519Signature2018` is no longer supported, so please update 221 | your existing implementations to use `Ed25519Signature2020` if you were 222 | previously using `Ed25519Signature2018`. 223 | 224 | #### Status List 2021 Test Suite 225 | 226 | * `BitstringStatusList ` and `Revocation`/`Suspension`- Combining the 227 | `BitstringStatusList` tag with `Revocation` or `Suspension` tags will run the 228 | [Bitstring Status List tests](https://github.com/w3c-ccg/vc-bitstring-status-list-test-suite) 229 | on your issuer and/or verifier. 230 | * Adding the `Revocation` tag alongside `BitstringStatusList` runs tests for 231 | issuers or verifiers issuing/verifying VCs with the revocation status purpose. 232 | * Adding the `Suspension` tag alongside `BitstringStatusList` runs tests for 233 | issuers or verifiers issuing/verifying VCs with the suspension status purpose. 234 | * Note: To update the status of VCs and publish the updated status, additional 235 | endpoints `setStatusLists` - `/credentials/status` and `publishStatusLists` 236 | - `/credentials/publish` may also need to be specified. 237 | 238 | #### DID Key Test Suite 239 | 240 | * `did-key` - This tag will run the [DID Key Test Suite](https://github.com/w3c-ccg/did-key-test-suite) 241 | on your DID resolver endpoint. 242 | 243 | ## Contribute 244 | 245 | See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)! 246 | 247 | PRs accepted. 248 | 249 | If editing the Readme, please conform to the 250 | [standard-readme](https://github.com/RichardLitt/standard-readme) specification. 251 | 252 | ## License 253 | 254 | [New BSD License (3-clause)](LICENSE) © Digital Bazaar 255 | --------------------------------------------------------------------------------