├── .github └── workflows │ ├── render-specs.yml │ └── run-tests.yml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── agenda.md ├── agenda2021-1.md ├── agenda2022-1.md ├── agenda2023-1.md ├── contributing.md ├── package-lock.json ├── package.json ├── playground ├── css │ ├── bulma.css │ ├── global.css │ ├── normalize.css │ ├── prism.css │ ├── tagify.css │ └── web-components │ │ ├── detail-box.css │ │ ├── item-lists.css │ │ ├── modal-overlay.css │ │ ├── notice-bar.css │ │ ├── slide-panels.css │ │ └── tab-panels.css ├── index.html └── js │ ├── ace-custom-element.js │ ├── ace │ └── index.min.js │ ├── global.js │ ├── modules │ ├── dom.js │ ├── json-print.js │ └── uuid.js │ ├── prism.js │ ├── tagify.js │ └── web-components │ ├── detail-box.js │ ├── modal-overlay.js │ ├── notice-bar.js │ ├── render-list.js │ ├── slide-panels.js │ ├── tab-panels.js │ └── tagify-input.js ├── sample-implementation ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── __fixtures__ │ │ ├── index.ts │ │ └── test-vectors │ │ │ ├── jwt-decoded.json │ │ │ ├── jwt.txt │ │ │ ├── presentation_definition.json │ │ │ ├── vc-jwt-decoded.json │ │ │ ├── vc-jwt.txt │ │ │ ├── vc-ld.json │ │ │ ├── vp-jwt-decoded.json │ │ │ ├── vp-jwt.txt │ │ │ └── vp-ld.json │ ├── __tests__ │ │ ├── jwt.sanity.test.ts │ │ ├── ld.sanity.test.ts │ │ └── validate_presentation_definition.test.ts │ └── index.ts └── tsconfig.json ├── schemas ├── input-descriptor.json ├── presentation-definition-envelope.json ├── presentation-definition.json ├── presentation-submission.json ├── submission-requirement.json ├── submission-requirements.json └── v2.0.0 │ ├── input-descriptor.json │ ├── presentation-definition-envelope.json │ ├── presentation-definition.json │ ├── presentation-submission.json │ ├── submission-requirement.json │ └── submission-requirements.json ├── spec ├── spec.md ├── v1.0.0 │ └── spec.md ├── v2.0.0 │ └── spec.md ├── v2.1.0 │ └── spec.md └── v2.1.1 │ └── spec.md ├── specs.json ├── submission └── v1 │ └── index.json └── test ├── __fixtures__ └── ajv.js ├── presentation-definition ├── VC_expiration_example.json ├── VC_revocation_example.json ├── basic_example.json ├── format_example.json ├── input_descriptor_id_tokens_example.json ├── input_descriptors_example.json ├── minimal_example.json ├── multi_group_example.json ├── pd_filter.json ├── pd_filter2.json ├── pd_filter2_simplified.json ├── single_group_example.json └── test.js ├── presentation-submission ├── appendix_CHAPI_example.json ├── appendix_DIDComm_example.json ├── appendix_JWT_example.json ├── appendix_OIDC_example.json ├── appendix_VP_example.json ├── example.json ├── nested_submission_example.json └── test.js ├── submission-requirements ├── all_example.json ├── example.json ├── pick_1_example.json ├── pick_2_example.json ├── pick_3_example.json ├── schema.json └── test.js ├── v1.0.0 ├── presentation-definition │ ├── VC_expiration_example.json │ ├── VC_revocation_example.json │ ├── basic_example.json │ ├── format_example.json │ ├── input_descriptor_id_tokens_example.json │ ├── input_descriptors_example.json │ ├── minimal_example.json │ ├── multi_group_example.json │ ├── schema.json │ ├── single_group_example.json │ └── test.js ├── presentation-submission │ ├── appendix_CHAPI_example.json │ ├── appendix_DIDComm_example.json │ ├── appendix_OIDC_example.json │ ├── appendix_VP_example.json │ ├── example.json │ ├── nested_submission_example.json │ ├── schema.json │ └── test.js └── submission-requirements │ ├── all_example.json │ ├── example.json │ ├── pick_1_example.json │ ├── pick_2_example.json │ ├── pick_3_example.json │ ├── schema.json │ └── test.js └── v2.0.0 ├── presentation-definition ├── VC_expiration_example.json ├── VC_revocation_example.json ├── basic_example.json ├── format_example.json ├── input_descriptor_id_tokens_example.json ├── input_descriptors_example.json ├── minimal_example.json ├── multi_group_example.json ├── pd_filter.json ├── pd_filter2.json ├── single_group_example.json └── test.js ├── presentation-submission ├── appendix_CHAPI_example.json ├── appendix_DIDComm_example.json ├── appendix_JWT_example.json ├── appendix_OIDC_example.json ├── appendix_VP_example.json ├── example.json ├── nested_submission_example.json └── test.js └── submission-requirements ├── all_example.json ├── example.json ├── pick_1_example.json ├── pick_2_example.json ├── pick_3_example.json ├── schema.json └── test.js /.github/workflows/render-specs.yml: -------------------------------------------------------------------------------- 1 | 2 | name: render-specs 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - main 9 | 10 | jobs: 11 | build-and-deploy-spec: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 🛎️ 15 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 16 | with: 17 | persist-credentials: false 18 | 19 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 20 | run: | 21 | npm install 22 | npm run build 23 | 24 | - name: Deploy 25 | uses: peaceiris/actions-gh-pages@v3.7.3 26 | with: 27 | github_token: ${{ secrets.GITHUB_TOKEN }} 28 | publish_dir: ./build 29 | allow_empty_commit: true 30 | force_orphan: true 31 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | 2 | name: run-tests 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build-and-deploy-spec: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 🛎️ 17 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 18 | with: 19 | persist-credentials: false 20 | 21 | - name: Install and Test 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 22 | run: | 23 | npm install 24 | npm test 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .python-version 4 | .DS_Store 5 | .vscode 6 | build 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | 2 | # These owners will be the default owners for everything in 3 | # the repo. Unless a later match takes precedence, 4 | # they will be requested for review when someone opens a 5 | # pull request. 6 | * @rado0x54 @csuwildcat @kimdhamilton @brentzundel 7 | 8 | # See CODEOWNERS syntax here: https://help.github.com/articles/about-codeowners/#codeowners-syntax 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Presentation Exchange 2 | 3 | ### [Latest Editor's Draft](https://identity.foundation/presentation-exchange/) 4 | 5 | ## Getting Started 6 | 7 | ``` 8 | git clone git@github.com:decentralized-identity/presentation-exchange.git 9 | cd presentation-exchange 10 | npm i 11 | npm run edit 12 | npx serve build 13 | ``` 14 | 15 | Visit http://localhost:5000/ 16 | -------------------------------------------------------------------------------- /agenda.md: -------------------------------------------------------------------------------- 1 | # DIF C&C - Presentation Exchange, Credential Manifest and Friends Weekly (C&C WG) – Rolling Agenda & Minutes 2 | 3 | ## Meeting - 14 Mar 2024 - (1300 ET) 4 | 5 | ### Agenda 6 | 7 | 1. Recap of OIDC4VP / PE discussion 8 | 2. Gary's issues with PE implementation 9 | 3. Review outstanding PRs and Issues 10 | 4. Check in on 2.1 scope 11 | 5. What can we do to help comparison of PE approach to alternate format-specific approach 12 | 6. Other topics? 13 | 14 | ### Discussion 15 | 16 | - PE Profile for OIDC4VP: https://docs.google.com/document/d/1r7S36RFNsnCOrYbkyTFE5ybPBalT-hERSnzKQ2rzcX8/edit 17 | - Discussed https://github.com/decentralized-identity/presentation-exchange/issues/472; making MUST -> MAY would imply `input_descriptors` is now MAY 18 | 19 | ### Decisions / Actions 20 | - Kim to email everyone to draw attention to last call issues 21 | - Daniel check with Gabe on #472 22 | - (5) not discussed; move to next week 23 | 24 | ### Attendees 25 | Daniel Buchner, Gary De Beer, Kim Duffy, Martin Riedel 26 | -------------------------------------------------------------------------------- /agenda2022-1.md: -------------------------------------------------------------------------------- 1 | # Presentation Exchange / Credential Manifest joint calls 2 | 3 | [![hackmd-github-sync-badge](https://hackmd.io/mA8G4iRsT4OkWC4cs8SMhg/badge)](https://hackmd.io/mA8G4iRsT4OkWC4cs8SMhg) 4 | 5 | (DIF Claims and Credentials WG) 6 | 7 | ## Code Owners 8 | 9 | PE Editors 10 | - Daniel Buchner (Microsoft) 11 | - Brent Zundel (Evernym) 12 | - Martin Riedel (Consensys Mesh) 13 | - Kim Hamilton Duffy (Centre Consortium) 14 | 15 | CM Editors 16 | - Daniel Buchner (Microsoft) 17 | - Brent Zundel (Evernym) 18 | - Jace Hensley (Bloom) 19 | - Daniel McGrogan (Workday) 20 | 21 | ## Useful links 22 | 23 | - [PE](https://identity.foundation/presentation-exchange/) & [CM](https://identity.foundation/credential-manifest/) 24 | 25 | ## 16 December 26 | 27 | Agenda: 28 | 29 | ## 9 December 30 | 31 | ? 32 | 33 | ## 2 December 34 | 35 | Agenda: 36 | - [David's Minimalist Profile in the Discussions](https://github.com/decentralized-identity/presentation-exchange/discussions/269) 37 | 38 | ## 25 November - TurkeyDay Holiday 39 | 40 | ## 18 November - More PE, less CM 41 | - David Chadwick- Presentation Exchange and VC data model issues 42 | - ambiguity around iss/issuer and other cross-representation redundancies - "Orie does both, I do only one" 43 | - see VC issue [#832](https://github.com/w3c/vc-data-model/issues/832) 44 | - 230 and other outstanding PRs for v2 45 | - mostly Daniel B 46 | - David Chadwick - limited profile should live where? upload? 47 | - Jace: GH [Discussions](https://github.com/decentralized-identity/presentation-exchange/discussions 48 | ) probably best 49 | - Brent: TSC is currently discussing profiles, so how this work item relates to a profile is a little TBD-- let's discuss in Discussions in the meantime (and remind us to address in future PE/CM calls!) 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dif-presentation-exchange", 3 | "description": "DIF Presentation Exchange", 4 | "version": "2.0.0", 5 | "homepage": "http://identity.foundation", 6 | "license": "Apache 2", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/decentralized-identity/presentation-exchange" 10 | }, 11 | "dependencies": { 12 | "spec-up": "^0.10.6" 13 | }, 14 | "devDependencies": { 15 | "ace-custom-element": "1.6.5", 16 | "ajv": "^8.10.0", 17 | "ajv-formats": "^2.1.1", 18 | "del-cli": "^4.0.1", 19 | "mocha": "^10.1.0" 20 | }, 21 | "scripts": { 22 | "render": "npm run build", 23 | "edit": "npm run dev", 24 | "test": "mocha 'test/**/*.js'", 25 | "build": "npm run clean && npm run resources && npm run build:specs", 26 | "build:specs": "node -e \"require('spec-up')({ nowatch: true })\"", 27 | "dev": "npm run clean && npm run resources && npm run dev:specs", 28 | "dev:specs": "node -e \"require('spec-up')()\"", 29 | "resources": "cp -r submission build/. && cp -r playground build/. && cp -r schemas build/.", 30 | "clean": "del build && mkdir build" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /playground/css/global.css: -------------------------------------------------------------------------------- 1 | 2 | :root { 3 | --panels-width: 45vw; 4 | --panels-min: 450px; 5 | --panels-max: 650px; 6 | } 7 | 8 | html, body { 9 | box-sizing: border-box; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | h1, h2, h3, h4, h5, h6 { 15 | margin: 1em 0 0.75em !important; 16 | } 17 | 18 | tags[required] { 19 | border-color: #f14668 !important; 20 | } 21 | 22 | /* COMPONENTS */ 23 | 24 | .constraint-fields ace-editor { 25 | height: auto; 26 | min-height: 5em; 27 | border: 1px solid #ddd; 28 | border-radius: 2px; 29 | } 30 | 31 | .ace_placeholder { 32 | padding: 0 0.6em; 33 | margin: 0; 34 | opacity: 0.5; 35 | transform: none; 36 | font-style: italic; 37 | } 38 | 39 | .ace_active-line { 40 | display: none; 41 | } 42 | 43 | .is-checkbox { 44 | cursor: pointer; 45 | } 46 | 47 | .revealing-checkbox + .select { 48 | opacity: 0; 49 | pointer-events: none; 50 | transition: opacity 0.25s ease; 51 | } 52 | 53 | .revealing-checkbox[checked] + .select { 54 | opacity: 1; 55 | pointer-events: all; 56 | } 57 | 58 | .input-descriptor { 59 | margin: 0; 60 | padding: 0 0 1.25em; 61 | } 62 | 63 | .input-descriptor > detail-box > header { 64 | display: flex; 65 | position: sticky; 66 | top: 1em; 67 | margin: 0 0 1em; 68 | background: #ddd; 69 | padding: 0.5em; 70 | border-radius: 3px; 71 | box-shadow: 0 1px 3px 0px rgb(0 0 0 / 45%), 0 -0.6em 0 0.4em rgb(255 255 255); 72 | z-index: 2; 73 | } 74 | 75 | .input-descriptor > detail-box > header .field { 76 | flex: 1; 77 | margin: 0; 78 | } 79 | 80 | .input-descriptor > detail-box > header label { 81 | margin: auto 0.5em auto 0; 82 | } 83 | 84 | .input-descriptor > detail-box > header [detail-box-toggle] { 85 | display: flex; 86 | align-items: center; 87 | justify-content: center; 88 | height: auto; 89 | width: 2.75em; 90 | margin: -0.5em 1em -0.5em -0.5em; 91 | border-radius: 3px 0 0 3px; 92 | border-right: 1px solid rgba(0,0,0,0.15); 93 | } 94 | 95 | .input-descriptor > detail-box > section { 96 | padding: 0 0 0 1em; 97 | } 98 | 99 | .constraint-fields { 100 | margin-bottom: 0.75em; 101 | } 102 | 103 | .constraint-fields > header { 104 | display: flex; 105 | align-items: center; 106 | justify-content: center; 107 | padding: 0.4em 0; 108 | color: #000; 109 | background: #d0d0d0; 110 | border-radius: 2px; 111 | opacity: 0.7; 112 | transition: opacity 0.3s ease; 113 | cursor: pointer; 114 | } 115 | 116 | .constraint-fields[open] > header { 117 | border-radius: 2px 2px 0 0; 118 | } 119 | 120 | .constraint-fields > header:hover { 121 | opacity: 1; 122 | } 123 | 124 | .constraint-fields section > div { 125 | padding: 1em 1em 0.8em; 126 | background: #fafafa; 127 | border: 1px solid #e9e9e9; 128 | } 129 | 130 | .add-field { 131 | margin: 1em 0; 132 | } 133 | 134 | .submission-requirement { 135 | margin: 1.5em 0 0; 136 | padding: 0 0 2em; 137 | } 138 | 139 | .submission-requirement:not(:last-of-type) { 140 | border-bottom: 3px solid #ccc; 141 | } 142 | 143 | 144 | .submission-rule { 145 | margin-right: 1em; 146 | } 147 | 148 | .submission-requirement .select ~ .control { 149 | display: none; 150 | } 151 | 152 | .submission-requirement .select:not([value]) ~ .control, 153 | .submission-requirement .select[value="pick"] ~ .control { 154 | display: block; 155 | } 156 | 157 | #definition_panels { 158 | position: absolute; 159 | top: 0; 160 | left: 0; 161 | box-sizing: border-box; 162 | width: var(--panels-width); 163 | min-width: var(--panels-min); 164 | max-width: var(--panels-max); 165 | padding: 1em; 166 | } 167 | 168 | #definition_panels > nav { 169 | margin: 0 0 1em; 170 | } 171 | 172 | #definition_panels > nav a { 173 | display: flex; 174 | align-items: center; 175 | margin: 0; 176 | border: none; 177 | border-radius: 0; 178 | background: none; 179 | color: unset; 180 | text-align: center; 181 | border-bottom: 3px solid transparent; 182 | transition: border-color 0.3s ease; 183 | } 184 | 185 | #definition_panels > nav a:hover { 186 | border-color: #ddd; 187 | } 188 | 189 | #definition_panels > nav a[selected] { 190 | color: #073255; 191 | border-color: rgb(30 111 176); 192 | transition: none; 193 | } 194 | 195 | #definition_panels > section { 196 | padding: 0 0.75em; 197 | } 198 | 199 | #outputs { 200 | box-sizing: border-box; 201 | position: absolute; 202 | top: 0; 203 | right: 0; 204 | padding: 1em; 205 | width: calc(100% - var(--panels-width)); 206 | min-width: calc(100% - var(--panels-max)); 207 | max-width: calc(100% - var(--panels-min)); 208 | } 209 | 210 | #presentation_definition { 211 | box-sizing: border-box; 212 | width: 100%; 213 | margin: 0; 214 | border-radius: 4px; 215 | } 216 | 217 | /*** PANELS ***/ 218 | 219 | #format_panel > div { 220 | margin-bottom: 2.5em; 221 | } 222 | 223 | /*** OVERRIDES ***/ 224 | 225 | .token.number { 226 | align-items: unset; 227 | background-color: unset; 228 | border-radius: unset; 229 | display: unset; 230 | font-size: unset; 231 | height: unset; 232 | justify-content: unset; 233 | margin-right: unset; 234 | min-width: unset; 235 | padding: unset; 236 | text-align: unset; 237 | vertical-align: unset; 238 | } 239 | -------------------------------------------------------------------------------- /playground/css/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.23.0 2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+json+json5+uri */ 3 | /** 4 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 5 | * Based on https://github.com/chriskempson/tomorrow-theme 6 | * @author Rose Pritchard 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: #ccc; 12 | background: none; 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | font-size: 1em; 15 | text-align: left; 16 | white-space: pre; 17 | word-spacing: normal; 18 | word-break: normal; 19 | word-wrap: normal; 20 | line-height: 1.5; 21 | 22 | -moz-tab-size: 4; 23 | -o-tab-size: 4; 24 | tab-size: 4; 25 | 26 | -webkit-hyphens: none; 27 | -moz-hyphens: none; 28 | -ms-hyphens: none; 29 | hyphens: none; 30 | 31 | } 32 | 33 | /* Code blocks */ 34 | pre[class*="language-"] { 35 | padding: 1em; 36 | margin: .5em 0; 37 | overflow: auto; 38 | } 39 | 40 | :not(pre) > code[class*="language-"], 41 | pre[class*="language-"] { 42 | background: #2d2d2d; 43 | } 44 | 45 | /* Inline code */ 46 | :not(pre) > code[class*="language-"] { 47 | padding: .1em; 48 | border-radius: .3em; 49 | white-space: normal; 50 | } 51 | 52 | .token.comment, 53 | .token.block-comment, 54 | .token.prolog, 55 | .token.doctype, 56 | .token.cdata { 57 | color: #999; 58 | } 59 | 60 | .token.punctuation { 61 | color: #ccc; 62 | } 63 | 64 | .token.tag, 65 | .token.attr-name, 66 | .token.namespace, 67 | .token.deleted { 68 | color: #e2777a; 69 | } 70 | 71 | .token.function-name { 72 | color: #6196cc; 73 | } 74 | 75 | .token.boolean, 76 | .token.number, 77 | .token.function { 78 | color: #f08d49; 79 | } 80 | 81 | .token.property, 82 | .token.class-name, 83 | .token.constant, 84 | .token.symbol { 85 | color: #f8c555; 86 | } 87 | 88 | .token.selector, 89 | .token.important, 90 | .token.atrule, 91 | .token.keyword, 92 | .token.builtin { 93 | color: #cc99cd; 94 | } 95 | 96 | .token.string, 97 | .token.char, 98 | .token.attr-value, 99 | .token.regex, 100 | .token.variable { 101 | color: #7ec699; 102 | } 103 | 104 | .token.operator, 105 | .token.entity, 106 | .token.url { 107 | color: #67cdcc; 108 | } 109 | 110 | .token.important, 111 | .token.bold { 112 | font-weight: bold; 113 | } 114 | .token.italic { 115 | font-style: italic; 116 | } 117 | 118 | .token.entity { 119 | cursor: help; 120 | } 121 | 122 | .token.inserted { 123 | color: green; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /playground/css/web-components/detail-box.css: -------------------------------------------------------------------------------- 1 | 2 | detail-box { 3 | display: block; 4 | } 5 | 6 | detail-box > header [detail-box-toggle] { 7 | width: 2em; 8 | height: 2em; 9 | text-align: center; 10 | cursor: pointer; 11 | } 12 | 13 | detail-box > header[detail-box-toggle]:after, 14 | detail-box > header [detail-box-toggle]:after { 15 | content: " "; 16 | display: inline-block; 17 | width: 0; 18 | height: 0; 19 | border-left: 0.55em solid transparent; 20 | border-right: 0.55em solid transparent; 21 | border-top: 0.8em solid; 22 | vertical-align: sub; 23 | cursor: pointer; 24 | } 25 | 26 | detail-box[open] > header[detail-box-toggle]:after, 27 | detail-box[open] > header [detail-box-toggle]:after { 28 | border-top: none; 29 | border-bottom: 0.8em solid; 30 | } 31 | 32 | detail-box > section { 33 | box-sizing: border-box; 34 | height: 0; 35 | opacity: 0; 36 | min-width: 100%; 37 | transition: height 0.3s ease, opacity 0.3s; 38 | overflow: hidden; 39 | } 40 | 41 | detail-box[open] > section { 42 | opacity: 1; 43 | } 44 | -------------------------------------------------------------------------------- /playground/css/web-components/item-lists.css: -------------------------------------------------------------------------------- 1 | 2 | /* PERSONA-LIST */ 3 | 4 | persona-list { 5 | text-align: center; 6 | } 7 | 8 | persona-list > ul { 9 | flex-wrap: wrap; 10 | } 11 | 12 | persona-list > ul > li { 13 | flex: 1; 14 | width: 8em; 15 | max-width: 7em; 16 | margin: 0 1em 2em; 17 | cursor: pointer; 18 | will-change: transform, opacity; 19 | } 20 | 21 | persona-list > ul > div[persona-state="add"] { 22 | opacity: 0; 23 | transform: scale(0.25); 24 | animation: scale-in 0.5s cubic-bezier(.47,1.64,.41,.8) 0.3s forwards; 25 | } 26 | 27 | persona-list i { 28 | font-size: 3em; 29 | } 30 | 31 | persona-list h3 { 32 | margin: 0.5em 0 0; 33 | } 34 | 35 | /* CONNECTION-LIST */ 36 | 37 | connection-list { 38 | text-align: left; 39 | } 40 | 41 | connection-list table { 42 | table-layout: fixed; 43 | width: 100%; 44 | } 45 | 46 | connection-list th, 47 | connection-list td { 48 | padding: 0.5em; 49 | overflow: hidden; 50 | text-overflow: ellipsis; 51 | } 52 | 53 | /* DATA-BROWSER */ 54 | 55 | data-browser > ul > li { 56 | padding: 0.4em 0.45em; 57 | cursor: pointer; 58 | } 59 | 60 | /* Animations */ 61 | 62 | @keyframes scale-in { 63 | to { 64 | opacity: 1; 65 | transform: scale(1); 66 | } 67 | } -------------------------------------------------------------------------------- /playground/css/web-components/modal-overlay.css: -------------------------------------------------------------------------------- 1 | 2 | modal-overlay { 3 | display: flex; 4 | align-items: center; 5 | justify-items: center; 6 | box-sizing: border-box; 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | height: 100%; 11 | width: 100%; 12 | opacity: 1; 13 | transition: opacity 0.2s ease; 14 | } 15 | 16 | modal-overlay:not([open]) { 17 | opacity: 0; 18 | pointer-events: none; 19 | } 20 | 21 | modal-overlay:before { 22 | content: " "; 23 | display: block; 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | right: 0; 28 | bottom: 0; 29 | background: rgba(0,0,0,0.5); 30 | } 31 | 32 | modal-overlay > article { 33 | display: flex; 34 | flex: 1; 35 | flex-direction: column; 36 | box-sizing: border-box; 37 | max-height: 100%; 38 | margin: auto; 39 | background: #fff; 40 | z-index: 1; 41 | } 42 | 43 | modal-overlay > article > header [modal-close]:before { 44 | content: "⨉"; 45 | } 46 | 47 | modal-overlay > article > footer { 48 | display: flex; 49 | /* flex: none; */ 50 | justify-content: flex-end; 51 | } 52 | 53 | modal-overlay > article > header, 54 | modal-overlay > article > footer { 55 | padding: 0.8em; 56 | } 57 | 58 | modal-overlay > article > header:empty, 59 | modal-overlay > article > footer:empty { 60 | padding: 0.4em 0; 61 | } 62 | 63 | modal-overlay > article > section { 64 | padding: 0 1em; 65 | overflow: auto; 66 | } -------------------------------------------------------------------------------- /playground/css/web-components/notice-bar.css: -------------------------------------------------------------------------------- 1 | 2 | notice-bar { 3 | display: block; 4 | box-sizing: border-box; 5 | position: fixed; 6 | bottom: 0; 7 | left: 0; 8 | right: 0; 9 | width: 100%; 10 | padding: 0.9em 1em; 11 | color: #000; 12 | background: #fff; 13 | opacity: 0; 14 | transform: translateY(100%); 15 | --notice-duration: 3s; 16 | --notice-interaction-exit: 1s; 17 | transition: transform ease, opacity ease; 18 | transition-delay: var(--notice-duration); 19 | transition-duration: 0.4s; 20 | z-index: 101; 21 | } 22 | 23 | notice-bar:hover, 24 | notice-bar[notice-state="show"] { 25 | opacity: 1; 26 | transform: translateY(0%); 27 | --notice-duration: 0s; 28 | } 29 | 30 | notice-bar[notice-interaction]:not(:hover) { 31 | transition-delay: var(--notice-interaction-exit); 32 | } 33 | 34 | notice-bar[notice-position="top"] { 35 | bottom: auto; 36 | top: 0; 37 | transform: translateY(-100%); 38 | } 39 | 40 | notice-bar > header:not(:empty) { 41 | font-weight: bold; 42 | } 43 | 44 | notice-bar > header:not(:empty) + section:not(:empty) { 45 | margin: 0.4em 0 0; 46 | } 47 | 48 | notice-bar > section:not(:empty) + footer:not(:empty) { 49 | margin: 0.5em 0 0; 50 | } -------------------------------------------------------------------------------- /playground/css/web-components/slide-panels.css: -------------------------------------------------------------------------------- 1 | 2 | slide-panels { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | height: 100%; 7 | width: 100%; 8 | pointer-events: none; 9 | overflow: hidden; 10 | z-index: 100; 11 | } 12 | 13 | slide-panels:before { 14 | content: " "; 15 | display: block; 16 | position: fixed; 17 | top: 0; 18 | left: 0; 19 | width: 100%; 20 | height: 100%; 21 | background: rgba(0,0,0,0.3); 22 | transition: opacity 0.35s ease; 23 | opacity: 0; 24 | cursor: pointer; 25 | pointer-events: none; 26 | } 27 | 28 | slide-panels[open]:before { 29 | opacity: 1; 30 | pointer-events: all; 31 | } 32 | 33 | slide-panel { 34 | display: flex; 35 | flex-direction: column; 36 | box-sizing: border-box; 37 | position: absolute; 38 | top: 0; 39 | left: 0; 40 | height: 100%; 41 | background: #fff; 42 | box-shadow: 0 0 5px 1px rgba(0,0,0,0.15); 43 | transform: translate3d(-100%, 0%, 0); 44 | transition: transform 0.35s ease, opacity 0.35s ease; 45 | z-index: 1; 46 | opacity: 1; 47 | pointer-events: all; 48 | } 49 | 50 | slide-panel[options~="right"] { 51 | left: auto; 52 | right: 0; 53 | transform: translate3d(100%, 0%, 0); 54 | } 55 | 56 | slide-panel[options~="bottom"] { 57 | transform: translate3d(0%, 100%, 0); 58 | } 59 | 60 | slide-panel[options~="fade"]:not([open]) { 61 | opacity: 0; 62 | } 63 | 64 | slide-panel[open] { 65 | transform: translate3d(0%, 0%, 0); 66 | } 67 | 68 | slide-panel:not([open]):not([options~="pointer-events"]) { 69 | pointer-events: none; 70 | } 71 | 72 | slide-panel > * { 73 | box-sizing: border-box; 74 | } 75 | 76 | /* slide-panel > *:not(header):not(footer) { 77 | flex: 1; 78 | } */ 79 | 80 | slide-panel > section { 81 | overflow: auto; 82 | } 83 | 84 | slide-panel > header, 85 | slide-panel > footer { 86 | flex: none; 87 | z-index: 1; 88 | } 89 | -------------------------------------------------------------------------------- /playground/css/web-components/tab-panels.css: -------------------------------------------------------------------------------- 1 | 2 | tab-panels { 3 | display: block; 4 | } 5 | 6 | tab-panels > nav { 7 | display: flex; 8 | } 9 | 10 | tab-panels > nav > * { 11 | padding: 0.5em 1em; 12 | background: #e0e0e0; 13 | border: 1px solid #aaa; 14 | border-radius: 0px; 15 | margin-left: -2px; 16 | cursor: pointer; 17 | } 18 | 19 | tab-panels > nav > *:focus { 20 | outline: none; 21 | background: #ccc; 22 | } 23 | 24 | tab-panels > nav > *:first-child { 25 | border-top-left-radius: 5px; 26 | border-bottom-left-radius: 5px; 27 | } 28 | 29 | tab-panels > nav > *:last-child { 30 | border-top-right-radius: 5px; 31 | border-bottom-right-radius: 5px; 32 | } 33 | 34 | tab-panels > nav > *[selected] { 35 | color: #fff; 36 | background: #005ca7; 37 | border-color: #004c8a; 38 | opacity: 0.9999; 39 | } 40 | 41 | tab-panels > section { 42 | display: none; 43 | } 44 | 45 | tab-panels > section[selected] { 46 | display: block; 47 | } 48 | -------------------------------------------------------------------------------- /playground/js/modules/dom.js: -------------------------------------------------------------------------------- 1 | 2 | const createProps = { 3 | attributes: (node, attr) => { for (let z in attr) node.setAttribute(z, attr[z]) } 4 | }; 5 | 6 | var DOM = { 7 | ready: new Promise(resolve => { 8 | document.addEventListener('DOMContentLoaded', e => resolve(e)); 9 | }), 10 | create(tag, props = {}){ 11 | let node = document.createElement(tag); 12 | for (let z in props) { 13 | if (createProps[z]) createProps[z](node, props[z]); 14 | else typeof node[z] === 'function' ? node[z](props[z]) : node[z] = props[z]; 15 | } 16 | return node; 17 | }, 18 | debounce: (fn, ms = 100, args = []) => { 19 | fn._throttleArgs = args; 20 | fn._throttleTimeout = fn._throttleTimeout || setTimeout(() => { 21 | fn(...fn._throttleArgs); 22 | delete fn._throttleArgs; 23 | delete fn._throttleTimeout; 24 | }, ms); 25 | }, 26 | skipAnimationFrame: fn => requestAnimationFrame(() => requestAnimationFrame(fn)), 27 | fireEvent(node, type, options = {}){ 28 | return node.dispatchEvent(new CustomEvent(type, Object.assign({ 29 | bubbles: true 30 | }, options))) 31 | }, 32 | delegateEvent(type, selector, fn, options = {}){ 33 | let listener = e => { 34 | let match = e.target.closest(selector); 35 | if (match) fn(e, match); 36 | } 37 | (options.container || document).addEventListener(type, listener, options); 38 | return listener; 39 | }, 40 | setOptions(node, options = {}){ 41 | ((node.getAttribute('options') || '').match(/[^\s]+/ig) || []).forEach(option => { 42 | options[option] = true; 43 | }); 44 | return node.options = options; 45 | }, 46 | popup(url, options = {}){ 47 | // Fixes dual-screen position Most browsers Firefox 48 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX; 49 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY; 50 | 51 | const w = window.innerWidth || document.documentElement.clientWidth || screen.width; 52 | const h = window.innerHeight || document.documentElement.clientHeight || screen.height; 53 | 54 | const width = options.width || 500; 55 | const height = options.height || 650; 56 | const zoom = w / window.screen.availWidth; 57 | const popup = window.open(url, options.title || '', ` 58 | width=${width / zoom}, 59 | height=${height / zoom}, 60 | top=${(h - height) / 2 / zoom + dualScreenTop}, 61 | left=${(w - width) / 2 / zoom + dualScreenLeft}, 62 | status=no,scrollbars=no,toolbar=no,menubar=no,location=no,resizable=no 63 | `); 64 | 65 | if (options.closeOnBlur) { 66 | popup.addEventListener('blur', e => popup.close()) 67 | } 68 | 69 | if (options.onBeforeUnload) { 70 | popup.addEventListener('beforeunload', options.onBeforeUnload) 71 | } 72 | 73 | popup.invocationData = options.invocationData; 74 | popup.focus(); 75 | 76 | return popup; 77 | } 78 | } 79 | 80 | globalThis.DOM = DOM; 81 | 82 | export { DOM }; 83 | -------------------------------------------------------------------------------- /playground/js/modules/json-print.js: -------------------------------------------------------------------------------- 1 | var defaultParams = { 2 | tab : ' ', 3 | spaceBeforeColon : ' ', 4 | spaceAfterColon : ' ', 5 | spaceAfterComma : ' ', 6 | spaceInsideObject : ' ', 7 | spaceInsideArray : '', 8 | shouldExpand : function(obj, level, index) { 9 | return JSON.stringify(obj).length > 25; 10 | } 11 | } 12 | 13 | function stringify(object, params, level, index) { 14 | if (!level) { 15 | var fullParams = {}; 16 | Object.keys(defaultParams).forEach(function(key) { 17 | fullParams[key] = (params && key in params)? params[key] : defaultParams[key]; 18 | }); 19 | params = fullParams; 20 | object = JSON.parse(JSON.stringify(object)); 21 | level = 0; 22 | } 23 | 24 | if (typeof object !== 'object' || object === null) return JSON.stringify(object); 25 | 26 | var isArray = Array.isArray(object), 27 | bra = isArray? '[' : '{', 28 | ket = isArray? ']' : '}'; 29 | 30 | if (!Object.keys(object).length) return bra + ket; 31 | 32 | var hasExpandedChildren = false, 33 | children = (isArray? object : Object.keys(object)).reduce(function(children, item, itemIndex) { 34 | if (!isArray) { 35 | itemIndex = item; 36 | item = object[item]; 37 | } 38 | if (!hasExpandedChildren && typeof item === 'object' && item !== null && params.shouldExpand(item, level + 1, itemIndex)) { 39 | hasExpandedChildren = true; 40 | } 41 | children[itemIndex] = stringify(item, params, level + 1, itemIndex); 42 | return children; 43 | }, isArray? [] : {}), 44 | shouldExpand = hasExpandedChildren || params.shouldExpand(object, level, index), 45 | indent = new Array(level + 1).join(params.tab), 46 | spaceInside = shouldExpand? 47 | '\n' + indent : 48 | params['spaceInside' + (isArray? 'Array' : 'Object')], 49 | joiner = shouldExpand? 50 | ',\n' + indent + params.tab : 51 | ',' + params.spaceAfterComma; 52 | 53 | if (!isArray) { 54 | children = Object.keys(children).reduce(function(res, key) { 55 | return res.concat(JSON.stringify(key) + params.spaceBeforeColon + ':' + params.spaceAfterColon + children[key]); 56 | }, []); 57 | } 58 | 59 | return bra + spaceInside + (shouldExpand? params.tab : '') + children.join(joiner) + spaceInside + ket; 60 | } 61 | 62 | if (typeof module == 'object' && typeof module.exports == 'object') { 63 | module.exports = stringify; 64 | } else if (typeof define == 'function') { 65 | define(function() { return stringify; }); 66 | } else { 67 | globalThis.jsonPrint = stringify; 68 | } -------------------------------------------------------------------------------- /playground/js/modules/uuid.js: -------------------------------------------------------------------------------- 1 | 2 | var UUID = { 3 | randomBytes(length = 16, format) { 4 | let bytes = crypto.getRandomValues(new Uint8Array(length)); 5 | switch (format) { 6 | case 'raw': return bytes; 7 | case 'base64Url': return; 8 | default: return bytes.join(''); 9 | } 10 | }, 11 | v4() { 12 | function getRandomSymbol (symbol) { 13 | var array; 14 | if (symbol === 'y') { 15 | array = ['8', '9', 'a', 'b']; 16 | return array[Math.floor(Math.random() * array.length)]; 17 | } 18 | array = new Uint8Array(1); 19 | window.crypto.getRandomValues(array); 20 | return (array[0] % 16).toString(16); 21 | } 22 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, getRandomSymbol); 23 | } 24 | }; 25 | 26 | export {UUID} -------------------------------------------------------------------------------- /playground/js/web-components/detail-box.js: -------------------------------------------------------------------------------- 1 | 2 | import '../modules/dom.js'; 3 | 4 | var DetailBox = globalThis.DetailBox = class DetailBox extends HTMLElement { 5 | static get observedAttributes() { 6 | return ['open']; 7 | } 8 | constructor() { 9 | super(); 10 | 11 | this.addEventListener('pointerup', e => { 12 | if (e.target.closest('[detail-box-toggle]')) { 13 | e.stopPropagation(); 14 | this.toggle(); 15 | } 16 | }); 17 | 18 | this.addEventListener('transitionend', e => { 19 | let node = e.target; 20 | if (node.parentElement === this && node.tagName === 'SECTION' && e.propertyName === 'height') { 21 | node.style.height = this.hasAttribute('open') ? 'auto' : null; 22 | } 23 | }); 24 | } 25 | open (){ 26 | this.setAttribute('open', '') 27 | } 28 | close (){ 29 | this.removeAttribute('open'); 30 | } 31 | toggle(){ 32 | this.toggleAttribute('open'); 33 | } 34 | attributeChangedCallback(attr, last, current) { 35 | switch(attr) { 36 | case 'open': 37 | DOM.ready.then(e => { 38 | for (let node of this.children) { 39 | if (node.tagName === 'SECTION') { 40 | if (current !== null) { 41 | if (node.offsetHeight < node.scrollHeight) { 42 | node.style.height = node.scrollHeight + 'px'; 43 | DOM.fireEvent(this, 'detailboxtoggle', { detail: { open: true } }); 44 | } 45 | } 46 | else if (node.offsetHeight > 0) { 47 | node.style.height = node.offsetHeight + 'px'; 48 | let scroll = this.scrollHeight; 49 | node.style.height = 0; 50 | DOM.fireEvent(this, 'detailboxtoggle', { detail: { open: false } }); 51 | } 52 | break; 53 | } 54 | } 55 | }); 56 | } 57 | } 58 | }; 59 | 60 | customElements.define('detail-box', DetailBox); 61 | 62 | export { DetailBox }; -------------------------------------------------------------------------------- /playground/js/web-components/modal-overlay.js: -------------------------------------------------------------------------------- 1 | 2 | import '../modules/dom.js'; 3 | 4 | var ModalOverlay = globalThis.ModalOverlay = class ModalOverlay extends HTMLElement { 5 | static get observedAttributes() { 6 | return ['open']; 7 | } 8 | constructor() { 9 | super(); 10 | 11 | this.addEventListener('pointerup', e => { 12 | if (e.target === this || e.target.hasAttribute('modal-close')) this.close(); 13 | }); 14 | 15 | this.addEventListener('transitionend', e => { 16 | if (e.target === this && e.propertyName === 'opacity') { 17 | DOM.fireEvent(this, this.isOpen ? 'modalopened' : 'modalclosed') 18 | } 19 | }); 20 | } 21 | get isOpen (){ 22 | return this.hasAttribute('open'); 23 | } 24 | open (){ 25 | this.setAttribute('open', ''); 26 | } 27 | close (){ 28 | this.removeAttribute('open'); 29 | } 30 | attributeChangedCallback(attr, last, current) { 31 | switch(attr) { 32 | case 'open': 33 | DOM.ready.then(e => { 34 | DOM.fireEvent(this, current !== null ? 'modalopen' : 'modalclose') 35 | }) 36 | } 37 | } 38 | }; 39 | 40 | customElements.define('modal-overlay', ModalOverlay) 41 | 42 | export { ModalOverlay }; -------------------------------------------------------------------------------- /playground/js/web-components/notice-bar.js: -------------------------------------------------------------------------------- 1 | 2 | import '../modules/dom.js'; 3 | 4 | var NoticeBar = globalThis.NoticeBar = class NoticeBar extends HTMLElement { 5 | static get observedAttributes() { 6 | return ['open']; 7 | } 8 | constructor(options = {}) { 9 | super(); 10 | this.options = options; 11 | this.addEventListener('transitionend', e => { 12 | if (e.target === this && e.propertyName === 'transform') { 13 | let showing = this.getAttribute('notice-state') === 'show'; 14 | if (showing) this.setAttribute('notice-state', 'hide'); 15 | else if (!this.options.retain && this.parentElement) this.parentElement.removeChild(this); 16 | DOM.fireEvent(this, showing ? 'noticeshow' : 'noticehide'); 17 | } 18 | }); 19 | this.addEventListener('pointerenter', e => this.setAttribute('notice-interaction', '')); 20 | } 21 | render(options){ 22 | this.setAttribute('notice-type', options.type || 'default'); 23 | this.innerHTML = `
${options.title || ''}
24 |
${options.body || ''}
25 | `; 26 | (options.container || document.body || document.documentElement).appendChild(this); 27 | } 28 | notify (options){ 29 | this.options = options || this.options; 30 | this.render(this.options); 31 | DOM.skipAnimationFrame(() => this.setAttribute('notice-state', 'show')); 32 | } 33 | }; 34 | 35 | customElements.define('notice-bar', NoticeBar) 36 | 37 | export { NoticeBar }; -------------------------------------------------------------------------------- /playground/js/web-components/render-list.js: -------------------------------------------------------------------------------- 1 | 2 | import { DOM } from '../modules/dom.js'; 3 | import { Data } from '/js/modules/data.js'; 4 | import { Storage } from '/js/modules/storage.js'; 5 | 6 | class RenderList extends HTMLElement { 7 | constructor (options = {}) { 8 | super(); 9 | DOM.setOptions(this, options); 10 | this.storageBucket = options.storageBucket; 11 | if (options.autoload) this.load(); 12 | } 13 | async renderItem(item, options = {}){ 14 | let node = document.createElement('li'); 15 | node.innerHTML = item; 16 | return node; 17 | } 18 | async renderItems(items, options = {}){ 19 | let frag = document.createDocumentFragment(); 20 | return Promise.all( 21 | items.map(item => this.renderItem(item, options)) 22 | ).then(nodes => nodes.reduce((frag, node) => { 23 | frag.appendChild(node); 24 | return frag; 25 | }, frag)); 26 | } 27 | async renderList(options = {}){ 28 | let list = document.createElement('ul'); 29 | list.setAttribute('render-list-container', ''); 30 | list.appendChild(await this.renderItems(this.items, options)); 31 | return list; 32 | } 33 | async load (options = {}){ 34 | let items = !options.data || typeof options.data === 'string' ? await Storage.getAll(options.data || this.storageBucket) : data; 35 | if (!items) { 36 | this.setAttribute('empty', ''); 37 | return; 38 | }; 39 | this.items = Array.isArray(items) ? items : Object.values(items); 40 | let list = this.querySelector('[render-list-container]'); 41 | let _list = await this.renderList(options); 42 | this.items.length ? this.removeAttribute('empty') : this.setAttribute('empty', ''); 43 | list ? list.replaceWith(_list) : this.prepend(_list); 44 | } 45 | async add (item){ 46 | let items = Array.isArray(item) ? item : arguments.length ? Array.from(arguments) : [item]; 47 | this.items = (this.items || []).concat(items); 48 | let list = this.querySelector('[render-list-container]'); 49 | this.items.length ? this.removeAttribute('empty') : this.setAttribute('empty', ''); 50 | if (list) list.appendChild(await this.renderItems(items, { state: 'add' })); 51 | else this.prepend(await this.renderList()); 52 | } 53 | }; 54 | 55 | class PersonaList extends RenderList { 56 | constructor (options = {}) { 57 | options.storageBucket = 'personas'; 58 | super(options); 59 | } 60 | async renderList(options = {}){ 61 | let list = await super.renderList(options); 62 | list.setAttribute('list', 'block'); 63 | return list; 64 | } 65 | async renderItem(persona, options = {}) { 66 | let node = document.createElement('li'); 67 | node.setAttribute('persona-id', persona.id); 68 | if (options.state) node.setAttribute('persona-state', options.state); 69 | node.innerHTML = `

${persona.name}

`; 70 | return node; 71 | } 72 | }; 73 | customElements.define('persona-list', PersonaList); 74 | 75 | 76 | class ConnectionList extends RenderList { 77 | constructor (options = {}) { 78 | options.storageBucket = 'connections'; 79 | super(options); 80 | } 81 | async renderList(){ 82 | let table = document.createElement('table'); 83 | table.setAttribute('render-list-container', ''); 84 | table.innerHTML = ` 85 | 86 | ConnectionDID 87 | 88 | 89 | `; 90 | table.lastElementChild.appendChild(await this.renderItems(this.items)); 91 | return table; 92 | } 93 | async renderItem(cxn, options = {}) { 94 | let tr = document.createElement('tr'); 95 | tr.setAttribute('connection-id', cxn.id); 96 | if (options.state) tr.setAttribute('connection-state', options.state); 97 | tr.innerHTML = `${cxn.id}${cxn.did || ''}`; 98 | return tr; 99 | } 100 | 101 | }; 102 | customElements.define('connection-list', ConnectionList); 103 | 104 | 105 | class AppList extends RenderList { 106 | constructor (options = {}) { 107 | options.storageBucket = 'apps'; 108 | super(options); 109 | } 110 | }; 111 | customElements.define('app-list', AppList); 112 | 113 | class DataBrowser extends RenderList { 114 | constructor (options = {}) { 115 | options.storageBucket = 'data'; 116 | super(options); 117 | } 118 | async renderItem(item, options = {}) { 119 | let node = await Data.renderDataView(item.data, 'listItem', { inflate: true }); 120 | if (node) { 121 | node.firstElementChild.setAttribute('data-object-id', item.id); 122 | } 123 | else{ 124 | node = document.createElement('li'); 125 | node.setAttribute('data-object-id', item.id); 126 | node.textContent = item.id + ' - type: ' + item.type; 127 | } 128 | return node; 129 | } 130 | async renderList(options = {}){ 131 | let list = await super.renderList(options); 132 | list.setAttribute('list', 'rows'); 133 | return list; 134 | } 135 | }; 136 | customElements.define('data-browser', DataBrowser); 137 | 138 | export { 139 | RenderList, 140 | PersonaList, 141 | ConnectionList 142 | }; -------------------------------------------------------------------------------- /playground/js/web-components/slide-panels.js: -------------------------------------------------------------------------------- 1 | 2 | import '../modules/dom.js'; 3 | 4 | var SlidePanels = globalThis.SlidePanels = class SlidePanels extends HTMLElement { 5 | static get observedAttributes() { 6 | return ['open']; 7 | } 8 | constructor() { 9 | super(); 10 | 11 | this.addEventListener('pointerup', e => { 12 | if (e.target === this) this.close(); 13 | }); 14 | this.addEventListener('transitionend', e => { 15 | if (e.target.parentElement === this && e.propertyName === 'opacity') { 16 | DOM.fireEvent(this, this.id === this.active ? 'panelopened' : 'panelclosed') 17 | } 18 | }); 19 | } 20 | get active (){ 21 | return this.getAttribute('open'); 22 | } 23 | toggle(panel){ 24 | this.active === panel ? this.close() : this.open(panel) 25 | } 26 | open (panel){ 27 | this.setAttribute('open', panel); 28 | } 29 | close (){ 30 | this.removeAttribute('open'); 31 | } 32 | attributeChangedCallback(attr, last, current) { 33 | switch(attr) { 34 | case 'open': for (let child of this.children) { 35 | if (child.id === current) { 36 | DOM.fireEvent(child, 'panelopen'); 37 | child.setAttribute('open', ''); 38 | } 39 | else if (child.hasAttribute('open')) { 40 | DOM.fireEvent(child, 'panelclose'); 41 | child.removeAttribute('open', ''); 42 | } 43 | } 44 | break; 45 | } 46 | } 47 | } 48 | 49 | customElements.define('slide-panels', SlidePanels); 50 | 51 | export { SlidePanels }; -------------------------------------------------------------------------------- /playground/js/web-components/tab-panels.js: -------------------------------------------------------------------------------- 1 | 2 | import '../modules/dom.js'; 3 | 4 | var TabPanels = globalThis.TabPanels = class TabPanels extends HTMLElement { 5 | constructor() { 6 | super(); 7 | DOM.delegateEvent('click', 'tab-panels > nav > *', (e, delegate) => { 8 | let nav = delegate.parentElement; 9 | if (nav.parentElement === this) { 10 | this.setAttribute('selected-index', Array.prototype.indexOf.call(nav.children, delegate)) 11 | } 12 | }, { container: this, passive: true }); 13 | } 14 | static get observedAttributes() { 15 | return ['selected-index']; 16 | } 17 | connectedCallback(){ 18 | let target = this.querySelector(`nav [href="${location.hash}"]`); 19 | if (target && target.parentNode?.parentNode === this) { 20 | this.setAttribute('selected-index', Array.from(target.parentNode.children).indexOf(target)) 21 | } 22 | } 23 | attributeChangedCallback(attr, last, current) { 24 | DOM.ready.then(() => { 25 | switch(attr) { 26 | case 'selected-index': 27 | let index = current || 0; 28 | let nav = this.querySelector('nav'); 29 | if (nav.parentElement === this) { 30 | let tabs = nav.children; 31 | let selected = tabs[index]; 32 | for (let tab of tabs) tab.removeAttribute('selected'); 33 | if (selected) selected.setAttribute('selected', ''); 34 | let panel = Array.prototype.filter.call(this.children, node => { 35 | if (node.tagName === 'SECTION') { 36 | node.removeAttribute('selected'); 37 | return true; 38 | } 39 | })[index]; 40 | if (panel) panel.setAttribute('selected', ''); 41 | DOM.fireEvent(this, 'tabselected', { detail: { index: index, tab: selected, panel: panel }}); 42 | } 43 | break; 44 | } 45 | }); 46 | } 47 | }; 48 | 49 | customElements.define('tab-panels', TabPanels); 50 | 51 | export { TabPanels }; -------------------------------------------------------------------------------- /playground/js/web-components/tagify-input.js: -------------------------------------------------------------------------------- 1 | 2 | import '../modules/dom.js'; 3 | 4 | var TagifyInput = globalThis.TagifyInput = class TagifyInput extends HTMLElement { 5 | static get observedAttributes() { 6 | return ['mode']; 7 | } 8 | constructor(options = {}) { 9 | super(); 10 | this.options = options; 11 | let input = this.firstElementChild; 12 | if (!input || input.tagName !== 'INPUT') this.innerHTML = ``; 13 | new Tagify(this.firstElementChild, options.tagify || { 14 | mode: this.getAttribute('mode') || null 15 | }); 16 | } 17 | get value(){ 18 | return this?.firstElementChild?.value; 19 | } 20 | }; 21 | 22 | customElements.define('tagify-input', TagifyInput) 23 | 24 | export { TagifyInput }; -------------------------------------------------------------------------------- /sample-implementation/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /sample-implementation/README.md: -------------------------------------------------------------------------------- 1 | # Presentation Exchange Sample Implementation 2 | 3 | ### Getting Started 4 | 5 | ``` 6 | npm i 7 | npm run test 8 | ``` 9 | -------------------------------------------------------------------------------- /sample-implementation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-implementation", 3 | "author": "Orie Steele", 4 | "module": "dist/sample-implementation.esm.js", 5 | "version": "0.1.0", 6 | "license": "Apache-2.0", 7 | "main": "dist/index.js", 8 | "typings": "dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "src" 12 | ], 13 | "engines": { 14 | "node": ">=10" 15 | }, 16 | "scripts": { 17 | "start": "tsdx watch", 18 | "build": "tsdx build", 19 | "test": "tsdx test", 20 | "lint": "tsdx lint src --fix", 21 | "prepare": "tsdx build" 22 | }, 23 | "peerDependencies": {}, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "npm run lint" 27 | } 28 | }, 29 | "prettier": { 30 | "printWidth": 80, 31 | "semi": true, 32 | "singleQuote": true, 33 | "trailingComma": "es5" 34 | }, 35 | "devDependencies": { 36 | "husky": "^4.3.8", 37 | "jose": "^1.28.2", 38 | "tsdx": "^0.14.1", 39 | "tslib": "^2.0.0", 40 | "typescript": "^3.9.6" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | const jwt = fs 5 | .readFileSync(path.resolve(__dirname, './test-vectors/jwt.txt')) 6 | .toString(); 7 | 8 | const jwt_decoded = fs 9 | .readFileSync(path.resolve(__dirname, './test-vectors/jwt-decoded.json')) 10 | .toString(); 11 | 12 | const vc_jwt = fs 13 | .readFileSync(path.resolve(__dirname, './test-vectors/vc-jwt.txt')) 14 | .toString(); 15 | 16 | const vc_jwt_decoded = fs 17 | .readFileSync(path.resolve(__dirname, './test-vectors/vc-jwt-decoded.json')) 18 | .toString(); 19 | 20 | const vp_jwt = fs 21 | .readFileSync(path.resolve(__dirname, './test-vectors/vp-jwt.txt')) 22 | .toString(); 23 | 24 | const vp_jwt_decoded = fs 25 | .readFileSync(path.resolve(__dirname, './test-vectors/vp-jwt-decoded.json')) 26 | .toString(); 27 | 28 | const vc_ld = fs 29 | .readFileSync(path.resolve(__dirname, './test-vectors/vc-ld.json')) 30 | .toString(); 31 | 32 | const vp_ld = fs 33 | .readFileSync(path.resolve(__dirname, './test-vectors/vp-ld.json')) 34 | .toString(); 35 | 36 | const presentation_definition = fs 37 | .readFileSync( 38 | path.resolve(__dirname, './test-vectors/presentation_definition.json') 39 | ) 40 | .toString(); 41 | 42 | export { 43 | jwt, 44 | jwt_decoded, 45 | vc_jwt, 46 | vc_jwt_decoded, 47 | vp_jwt, 48 | vp_jwt_decoded, 49 | vc_ld, 50 | vp_ld, 51 | presentation_definition, 52 | }; 53 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/jwt-decoded.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { "alg": "ES256", "typ": "JWT" }, 3 | "payload": { 4 | "sub": "1234567890", 5 | "name": "John Doe", 6 | "admin": true, 7 | "iat": 1516239022 8 | }, 9 | "signature": "tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA" 10 | } 11 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/jwt.txt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/presentation_definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_descriptors": [ 3 | { 4 | "id": "banking_input", 5 | "schema": { 6 | "uri": ["https://bank-standards.com/customer.json"], 7 | "name": "Bank Account Information", 8 | "purpose": "We need your bank and account information." 9 | }, 10 | "constraints": { 11 | "limit_disclosure": "required", 12 | "fields": [ 13 | { 14 | "path": ["$.issuer", "$.vc.issuer", "$.iss"], 15 | "purpose": "The credential must be from one of the specified issuers", 16 | "filter": { 17 | "type": "string", 18 | "pattern": "did:example:123|did:example:456" 19 | } 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "id": "citizenship_input", 26 | "schema": { 27 | "uri": ["hub://did:foo:123/Collections/schema.us.gov/passport.json"], 28 | "name": "US Passport" 29 | }, 30 | "constraints": { 31 | "fields": [ 32 | { 33 | "path": [ 34 | "$.credentialSubject.birth_date", 35 | "$.vc.credentialSubject.birth_date", 36 | "$.birth_date" 37 | ], 38 | "filter": { 39 | "type": "date", 40 | "minimum": "1999-05-16" 41 | } 42 | } 43 | ] 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/vc-jwt-decoded.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "kid": "did:example:123llJtlNS6AEAUd7_cozAN4nWVV2pdubbMa4-gtanKUS0", 4 | "alg": "EdDSA" 5 | }, 6 | "payload": { 7 | "iss": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ", 8 | "sub": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ", 9 | "vc": { 10 | "@context": [ 11 | "https://www.w3.org/2018/credentials/v1", 12 | "https://www.w3.org/2018/credentials/examples/v1" 13 | ], 14 | "id": "http://example.gov/credentials/3732", 15 | "type": ["VerifiableCredential", "UniversityDegreeCredential"], 16 | "issuer": { 17 | "id": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ" 18 | }, 19 | "issuanceDate": "2020-03-10T04:24:12.164Z", 20 | "credentialSubject": { 21 | "id": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ", 22 | "degree": { 23 | "type": "BachelorDegree", 24 | "name": "Bachelor of Science and Arts" 25 | } 26 | } 27 | }, 28 | "jti": "http://example.gov/credentials/3732", 29 | "nbf": 1583814252 30 | }, 31 | "signature": "qSv6dpZJGFybtcifLwGf4ujzlEu-fam_M7HPxinCbVhz9iIJCg70UMeQbPa1ex6BmQ2tnSS7F11FHnMB2bJRAw" 32 | } 33 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/vc-jwt.txt: -------------------------------------------------------------------------------- 1 | eyJraWQiOiJkaWQ6ZXhhbXBsZToxMjNsbEp0bE5TNkFFQVVkN19jb3pBTjRuV1ZWMnBkdWJiTWE0LWd0YW5LVVMwIiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6a2V5Ono2TWtwUDU2OEpma2MxbjUxdmRFdXQyRWVidHZoRlhrb2Q3UzZMTVpUVlBHc1ppWiIsInN1YiI6ImRpZDprZXk6ejZNa3BQNTY4SmZrYzFuNTF2ZEV1dDJFZWJ0dmhGWGtvZDdTNkxNWlRWUEdzWmlaIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJpZCI6Imh0dHA6Ly9leGFtcGxlLmdvdi9jcmVkZW50aWFscy8zNzMyIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImlzc3VlciI6eyJpZCI6ImRpZDprZXk6ejZNa3BQNTY4SmZrYzFuNTF2ZEV1dDJFZWJ0dmhGWGtvZDdTNkxNWlRWUEdzWmlaIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjAtMDMtMTBUMDQ6MjQ6MTIuMTY0WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rcFA1NjhKZmtjMW41MXZkRXV0MkVlYnR2aEZYa29kN1M2TE1aVFZQR3NaaVoiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJqdGkiOiJodHRwOi8vZXhhbXBsZS5nb3YvY3JlZGVudGlhbHMvMzczMiIsIm5iZiI6MTU4MzgxNDI1Mn0.qSv6dpZJGFybtcifLwGf4ujzlEu-fam_M7HPxinCbVhz9iIJCg70UMeQbPa1ex6BmQ2tnSS7F11FHnMB2bJRAw -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/vc-ld.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2018/credentials/v1", 4 | "https://www.w3.org/2018/credentials/examples/v1" 5 | ], 6 | "id": "http://example.gov/credentials/3732", 7 | "type": ["VerifiableCredential", "UniversityDegreeCredential"], 8 | "issuer": { 9 | "id": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ" 10 | }, 11 | "issuanceDate": "2020-03-10T04:24:12.164Z", 12 | "credentialSubject": { 13 | "id": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ", 14 | "degree": { 15 | "type": "BachelorDegree", 16 | "name": "Bachelor of Science and Arts" 17 | } 18 | }, 19 | "proof": { 20 | "type": "Ed25519Signature2018", 21 | "created": "2019-12-11T03:50:55Z", 22 | "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..sW-8OqzPxieDbJvnFX2WKTsXz2HP5JAvNaIpvTneqbZxSnGlvdpBNwFLQPLNi4L0kYqWl5o8BE1f0Z04Ard9BQ", 23 | "proofPurpose": "assertionMethod", 24 | "verificationMethod": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ#z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/vp-jwt-decoded.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "kid": "did:example:123llJtlNS6AEAUd7_cozAN4nWVV2pdubbMa4-gtanKUS0", 4 | "alg": "EdDSA" 5 | }, 6 | "payload": { 7 | "iss": "did:example:456", 8 | "sub": "did:example:456", 9 | "vp": { 10 | "@context": ["https://www.w3.org/2018/credentials/v1"], 11 | "type": ["VerifiablePresentation"], 12 | "verifiableCredential": [ 13 | "eyJraWQiOiJkaWQ6ZXhhbXBsZToxMjNsbEp0bE5TNkFFQVVkN19jb3pBTjRuV1ZWMnBkdWJiTWE0LWd0YW5LVVMwIiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6a2V5Ono2TWtwUDU2OEpma2MxbjUxdmRFdXQyRWVidHZoRlhrb2Q3UzZMTVpUVlBHc1ppWiIsInN1YiI6ImRpZDprZXk6ejZNa3BQNTY4SmZrYzFuNTF2ZEV1dDJFZWJ0dmhGWGtvZDdTNkxNWlRWUEdzWmlaIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJpZCI6Imh0dHA6Ly9leGFtcGxlLmdvdi9jcmVkZW50aWFscy8zNzMyIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImlzc3VlciI6eyJpZCI6ImRpZDprZXk6ejZNa3BQNTY4SmZrYzFuNTF2ZEV1dDJFZWJ0dmhGWGtvZDdTNkxNWlRWUEdzWmlaIn0sImlzc3VhbmNlRGF0ZSI6IjIwMjAtMDMtMTBUMDQ6MjQ6MTIuMTY0WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rcFA1NjhKZmtjMW41MXZkRXV0MkVlYnR2aEZYa29kN1M2TE1aVFZQR3NaaVoiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJqdGkiOiJodHRwOi8vZXhhbXBsZS5nb3YvY3JlZGVudGlhbHMvMzczMiIsIm5iZiI6MTU4MzgxNDI1Mn0.qSv6dpZJGFybtcifLwGf4ujzlEu-fam_M7HPxinCbVhz9iIJCg70UMeQbPa1ex6BmQ2tnSS7F11FHnMB2bJRAw" 14 | ], 15 | "holder": "did:example:456" 16 | }, 17 | "nonce": "7cec01f7-82ee-4474-a4e6-feaaa7351e48", 18 | "aud": "verifier.com" 19 | }, 20 | "signature": "ALTaYcTlSm2KfivTgxKlIpHCpYpCZaE2ZIWriN4dLEcCqpkWlCrSyc1oKVZrYRt33QvgywN-xWgY_YlDvGkFDQ" 21 | } 22 | -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/vp-jwt.txt: -------------------------------------------------------------------------------- 1 | eyJraWQiOiJkaWQ6ZXhhbXBsZToxMjNsbEp0bE5TNkFFQVVkN19jb3pBTjRuV1ZWMnBkdWJiTWE0LWd0YW5LVVMwIiwiYWxnIjoiRWREU0EifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTo0NTYiLCJzdWIiOiJkaWQ6ZXhhbXBsZTo0NTYiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKcmFXUWlPaUprYVdRNlpYaGhiWEJzWlRveE1qTnNiRXAwYkU1VE5rRkZRVlZrTjE5amIzcEJUalJ1VjFaV01uQmtkV0ppVFdFMExXZDBZVzVMVlZNd0lpd2lZV3huSWpvaVJXUkVVMEVpZlEuZXlKcGMzTWlPaUprYVdRNmEyVjVPbm8yVFd0d1VEVTJPRXBtYTJNeGJqVXhkbVJGZFhReVJXVmlkSFpvUmxocmIyUTNVelpNVFZwVVZsQkhjMXBwV2lJc0luTjFZaUk2SW1ScFpEcHJaWGs2ZWpaTmEzQlFOVFk0U21acll6RnVOVEYyWkVWMWRESkZaV0owZG1oR1dHdHZaRGRUTmt4TldsUldVRWR6V21sYUlpd2lkbU1pT25zaVFHTnZiblJsZUhRaU9sc2lhSFIwY0hNNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TVRndlkzSmxaR1Z1ZEdsaGJITXZkakVpTENKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5bGVHRnRjR3hsY3k5Mk1TSmRMQ0pwWkNJNkltaDBkSEE2THk5bGVHRnRjR3hsTG1kdmRpOWpjbVZrWlc1MGFXRnNjeTh6TnpNeUlpd2lkSGx3WlNJNld5SldaWEpwWm1saFlteGxRM0psWkdWdWRHbGhiQ0lzSWxWdWFYWmxjbk5wZEhsRVpXZHlaV1ZEY21Wa1pXNTBhV0ZzSWwwc0ltbHpjM1ZsY2lJNmV5SnBaQ0k2SW1ScFpEcHJaWGs2ZWpaTmEzQlFOVFk0U21acll6RnVOVEYyWkVWMWRESkZaV0owZG1oR1dHdHZaRGRUTmt4TldsUldVRWR6V21sYUluMHNJbWx6YzNWaGJtTmxSR0YwWlNJNklqSXdNakF0TURNdE1UQlVNRFE2TWpRNk1USXVNVFkwV2lJc0ltTnlaV1JsYm5ScFlXeFRkV0pxWldOMElqcDdJbWxrSWpvaVpHbGtPbXRsZVRwNk5rMXJjRkExTmpoS1ptdGpNVzQxTVhaa1JYVjBNa1ZsWW5SMmFFWllhMjlrTjFNMlRFMWFWRlpRUjNOYWFWb2lMQ0prWldkeVpXVWlPbnNpZEhsd1pTSTZJa0poWTJobGJHOXlSR1ZuY21WbElpd2libUZ0WlNJNklrSmhZMmhsYkc5eUlHOW1JRk5qYVdWdVkyVWdZVzVrSUVGeWRITWlmWDE5TENKcWRHa2lPaUpvZEhSd09pOHZaWGhoYlhCc1pTNW5iM1l2WTNKbFpHVnVkR2xoYkhNdk16Y3pNaUlzSW01aVppSTZNVFU0TXpneE5ESTFNbjAucVN2NmRwWkpHRnlidGNpZkx3R2Y0dWp6bEV1LWZhbV9NN0hQeGluQ2JWaHo5aUlKQ2c3MFVNZVFiUGExZXg2Qm1RMnRuU1M3RjExRkhuTUIyYkpSQXciXSwiaG9sZGVyIjoiZGlkOmV4YW1wbGU6NDU2In0sIm5vbmNlIjoiN2NlYzAxZjctODJlZS00NDc0LWE0ZTYtZmVhYWE3MzUxZTQ4IiwiYXVkIjoidmVyaWZpZXIuY29tIn0.ALTaYcTlSm2KfivTgxKlIpHCpYpCZaE2ZIWriN4dLEcCqpkWlCrSyc1oKVZrYRt33QvgywN-xWgY_YlDvGkFDQ -------------------------------------------------------------------------------- /sample-implementation/src/__fixtures__/test-vectors/vp-ld.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": ["https://www.w3.org/2018/credentials/v1"], 3 | "type": ["VerifiablePresentation"], 4 | "verifiableCredential": [ 5 | { 6 | "@context": [ 7 | "https://www.w3.org/2018/credentials/v1", 8 | "https://www.w3.org/2018/credentials/examples/v1" 9 | ], 10 | "id": "http://example.gov/credentials/3732", 11 | "type": ["VerifiableCredential", "UniversityDegreeCredential"], 12 | "issuer": { 13 | "id": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ" 14 | }, 15 | "issuanceDate": "2020-03-10T04:24:12.164Z", 16 | "credentialSubject": { 17 | "id": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ", 18 | "degree": { 19 | "type": "BachelorDegree", 20 | "name": "Bachelor of Science and Arts" 21 | } 22 | }, 23 | "proof": { 24 | "type": "Ed25519Signature2018", 25 | "created": "2019-12-11T03:50:55Z", 26 | "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..sW-8OqzPxieDbJvnFX2WKTsXz2HP5JAvNaIpvTneqbZxSnGlvdpBNwFLQPLNi4L0kYqWl5o8BE1f0Z04Ard9BQ", 27 | "proofPurpose": "assertionMethod", 28 | "verificationMethod": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ#z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ" 29 | } 30 | } 31 | ], 32 | "id": "ebc6f1c2", 33 | "holder": "did:ex:12345", 34 | "proof": { 35 | "type": "Ed25519Signature2018", 36 | "created": "2019-12-11T03:50:55Z", 37 | "challenge": "123", 38 | "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..aJivp-p1DCKUdDiehok6VHluA48QJLph8zpSNfmEJjHbs-CK-Np5xTAAMhLLEZir2cYx-rvJgNMwyqKa4P8ACA", 39 | "proofPurpose": "authentication", 40 | "verificationMethod": "did:key:z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ#z6MkpP568Jfkc1n51vdEut2EebtvhFXkod7S6LMZTVPGsZiZ" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sample-implementation/src/__tests__/jwt.sanity.test.ts: -------------------------------------------------------------------------------- 1 | import * as fixtures from '../__fixtures__'; 2 | import jose from 'jose'; 3 | 4 | // console.log(JSON.stringify(decoded, null, 2)); 5 | 6 | it('can decode jwt so its readable', () => { 7 | const decoded = jose.JWT.decode(fixtures.jwt, { complete: true }); 8 | expect(decoded).toEqual(JSON.parse(fixtures.jwt_decoded)); 9 | }); 10 | 11 | it('can decode vc-jwt so its readable', () => { 12 | const decoded = jose.JWT.decode(fixtures.vc_jwt, { complete: true }); 13 | expect(decoded).toEqual(JSON.parse(fixtures.vc_jwt_decoded)); 14 | }); 15 | 16 | it('can decode vp-jwt so its readable', () => { 17 | const decoded = jose.JWT.decode(fixtures.vp_jwt, { complete: true }); 18 | expect(decoded).toEqual(JSON.parse(fixtures.vp_jwt_decoded)); 19 | }); 20 | -------------------------------------------------------------------------------- /sample-implementation/src/__tests__/ld.sanity.test.ts: -------------------------------------------------------------------------------- 1 | import * as fixtures from '../__fixtures__'; 2 | 3 | it('ld vc is parsed as json', () => { 4 | const parsed = JSON.parse(fixtures.vc_ld); 5 | expect(parsed.id).toEqual('http://example.gov/credentials/3732'); 6 | }); 7 | 8 | it('ld vp is parsed as json', () => { 9 | const parsed = JSON.parse(fixtures.vp_ld); 10 | expect(parsed.holder).toEqual('did:ex:12345'); 11 | }); 12 | -------------------------------------------------------------------------------- /sample-implementation/src/__tests__/validate_presentation_definition.test.ts: -------------------------------------------------------------------------------- 1 | import { validate_presentation_definition } from '..'; 2 | import { presentation_definition } from '../__fixtures__'; 3 | 4 | it('can validate_presentation_definition', () => { 5 | const valid = validate_presentation_definition( 6 | JSON.parse(presentation_definition) 7 | ); 8 | expect(valid).toBe(true); 9 | }); 10 | -------------------------------------------------------------------------------- /sample-implementation/src/index.ts: -------------------------------------------------------------------------------- 1 | const validate_presentation_definition = (presentation_definition: any) => { 2 | console.warn( 3 | 'TODO: verify ', 4 | JSON.stringify(presentation_definition, null, 2) 5 | ); 6 | return false; 7 | }; 8 | 9 | export { validate_presentation_definition }; 10 | -------------------------------------------------------------------------------- /sample-implementation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /schemas/input-descriptor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Input Descriptor", 4 | "definitions": { 5 | "status_directive": { 6 | "type": "object", 7 | "additionalProperties": false, 8 | "properties": { 9 | "directive": { 10 | "type": "string", 11 | "enum": [ 12 | "required", 13 | "allowed", 14 | "disallowed" 15 | ] 16 | }, 17 | "type": { 18 | "type": "array", 19 | "minItems": 1, 20 | "items": { 21 | "type": "string" 22 | } 23 | } 24 | } 25 | }, 26 | "field": { 27 | "type": "object", 28 | "oneOf": [ 29 | { 30 | "properties": { 31 | "id": { 32 | "type": "string" 33 | }, 34 | "optional": { 35 | "type": "boolean" 36 | }, 37 | "path": { 38 | "type": "array", 39 | "items": { 40 | "type": "string" 41 | } 42 | }, 43 | "purpose": { 44 | "type": "string" 45 | }, 46 | "intent_to_retain": { 47 | "type": "boolean" 48 | }, 49 | "name": { 50 | "type": "string" 51 | }, 52 | "filter": { 53 | "$ref": "http://json-schema.org/draft-07/schema#" 54 | } 55 | }, 56 | "required": [ 57 | "path" 58 | ], 59 | "additionalProperties": false 60 | }, 61 | { 62 | "properties": { 63 | "id": { 64 | "type": "string" 65 | }, 66 | "optional": { 67 | "type": "boolean" 68 | }, 69 | "path": { 70 | "type": "array", 71 | "items": { 72 | "type": "string" 73 | } 74 | }, 75 | "purpose": { 76 | "type": "string" 77 | }, 78 | "intent_to_retain": { 79 | "type": "boolean" 80 | }, 81 | "filter": { 82 | "$ref": "http://json-schema.org/draft-07/schema#" 83 | }, 84 | "name": { 85 | "type": "string" 86 | }, 87 | "predicate": { 88 | "type": "string", 89 | "enum": [ 90 | "required", 91 | "preferred" 92 | ] 93 | } 94 | }, 95 | "required": [ 96 | "path", 97 | "filter", 98 | "predicate" 99 | ], 100 | "additionalProperties": false 101 | } 102 | ] 103 | } 104 | }, 105 | "type": "object", 106 | "additionalProperties": false, 107 | "properties": { 108 | "id": { 109 | "type": "string" 110 | }, 111 | "name": { 112 | "type": "string" 113 | }, 114 | "purpose": { 115 | "type": "string" 116 | }, 117 | "group": { 118 | "type": "array", 119 | "items": { 120 | "type": "string" 121 | } 122 | }, 123 | "constraints": { 124 | "type": "object", 125 | "additionalProperties": false, 126 | "properties": { 127 | "limit_disclosure": { 128 | "type": "string", 129 | "enum": [ 130 | "required", 131 | "preferred" 132 | ] 133 | }, 134 | "statuses": { 135 | "type": "object", 136 | "additionalProperties": false, 137 | "properties": { 138 | "active": { 139 | "$ref": "#/definitions/status_directive" 140 | }, 141 | "suspended": { 142 | "$ref": "#/definitions/status_directive" 143 | }, 144 | "revoked": { 145 | "$ref": "#/definitions/status_directive" 146 | } 147 | } 148 | }, 149 | "fields": { 150 | "type": "array", 151 | "items": { 152 | "$ref": "#/definitions/field" 153 | } 154 | }, 155 | "subject_is_issuer": { 156 | "type": "string", 157 | "enum": [ 158 | "required", 159 | "preferred" 160 | ] 161 | }, 162 | "is_holder": { 163 | "type": "array", 164 | "items": { 165 | "type": "object", 166 | "additionalProperties": false, 167 | "properties": { 168 | "field_id": { 169 | "type": "array", 170 | "items": { 171 | "type": "string" 172 | } 173 | }, 174 | "directive": { 175 | "type": "string", 176 | "enum": [ 177 | "required", 178 | "preferred" 179 | ] 180 | } 181 | }, 182 | "required": [ 183 | "field_id", 184 | "directive" 185 | ] 186 | } 187 | }, 188 | "same_subject": { 189 | "type": "array", 190 | "items": { 191 | "type": "object", 192 | "additionalProperties": false, 193 | "properties": { 194 | "field_id": { 195 | "type": "array", 196 | "items": { 197 | "type": "string" 198 | } 199 | }, 200 | "directive": { 201 | "type": "string", 202 | "enum": [ 203 | "required", 204 | "preferred" 205 | ] 206 | } 207 | }, 208 | "required": [ 209 | "field_id", 210 | "directive" 211 | ] 212 | } 213 | } 214 | } 215 | } 216 | }, 217 | "required": [ 218 | "id" 219 | ] 220 | } 221 | -------------------------------------------------------------------------------- /schemas/presentation-submission.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Presentation Submission", 4 | "type": "object", 5 | "properties": { 6 | "presentation_submission": { 7 | "type": "object", 8 | "properties": { 9 | "id": { "type": "string" }, 10 | "definition_id": { "type": "string" }, 11 | "descriptor_map": { 12 | "type": "array", 13 | "items": { "$ref": "#/definitions/descriptor" } 14 | } 15 | }, 16 | "required": ["id", "definition_id", "descriptor_map"], 17 | "additionalProperties": false 18 | } 19 | }, 20 | "definitions": { 21 | "descriptor": { 22 | "type": "object", 23 | "properties": { 24 | "id": { "type": "string" }, 25 | "path": { "type": "string" }, 26 | "path_nested": { 27 | "type": "object", 28 | "$ref": "#/definitions/descriptor" 29 | }, 30 | "format": { 31 | "$ref": "https://identity.foundation/claim-format-registry/schemas/presentation-submission-claim-format-designations.json#/definitions/format" 32 | } 33 | }, 34 | "required": ["id", "path", "format"], 35 | "additionalProperties": false 36 | } 37 | }, 38 | "required": ["presentation_submission"], 39 | "additionalProperties": false 40 | } -------------------------------------------------------------------------------- /schemas/submission-requirement.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Presentation Submission Requirement", 4 | "definitions": { 5 | "submission_requirement": { 6 | "type": "object", 7 | "oneOf": [ 8 | { 9 | "properties": { 10 | "name": { "type": "string" }, 11 | "purpose": { "type": "string" }, 12 | "rule": { 13 | "type": "string", 14 | "enum": ["all", "pick"] 15 | }, 16 | "count": { "type": "integer", "minimum": 1 }, 17 | "min": { "type": "integer", "minimum": 0 }, 18 | "max": { "type": "integer", "minimum": 0 }, 19 | "from": { "type": "string" } 20 | }, 21 | "required": ["rule", "from"], 22 | "additionalProperties": false 23 | }, 24 | { 25 | "properties": { 26 | "name": { "type": "string" }, 27 | "purpose": { "type": "string" }, 28 | "rule": { 29 | "type": "string", 30 | "enum": ["all", "pick"] 31 | }, 32 | "count": { "type": "integer", "minimum": 1 }, 33 | "min": { "type": "integer", "minimum": 0 }, 34 | "max": { "type": "integer", "minimum": 0 }, 35 | "from_nested": { 36 | "type": "array", 37 | "minItems": 1, 38 | "items": { 39 | "$ref": "#/definitions/submission_requirement" 40 | } 41 | } 42 | }, 43 | "required": ["rule", "from_nested"], 44 | "additionalProperties": false 45 | } 46 | ] 47 | } 48 | }, 49 | "$ref": "#/definitions/submission_requirement" 50 | } -------------------------------------------------------------------------------- /schemas/submission-requirements.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Submission Requirements", 4 | "definitions": { 5 | "submission_requirements": { 6 | "type": "object", 7 | "oneOf": [ 8 | { 9 | "properties": { 10 | "name": { 11 | "type": "string" 12 | }, 13 | "purpose": { 14 | "type": "string" 15 | }, 16 | "rule": { 17 | "type": "string", 18 | "enum": [ 19 | "all", 20 | "pick" 21 | ] 22 | }, 23 | "count": { 24 | "type": "integer", 25 | "minimum": 1 26 | }, 27 | "min": { 28 | "type": "integer", 29 | "minimum": 0 30 | }, 31 | "max": { 32 | "type": "integer", 33 | "minimum": 0 34 | }, 35 | "from": { 36 | "type": "string" 37 | } 38 | }, 39 | "required": [ 40 | "rule", 41 | "from" 42 | ], 43 | "additionalProperties": false 44 | }, 45 | { 46 | "properties": { 47 | "name": { 48 | "type": "string" 49 | }, 50 | "purpose": { 51 | "type": "string" 52 | }, 53 | "rule": { 54 | "type": "string", 55 | "enum": [ 56 | "all", 57 | "pick" 58 | ] 59 | }, 60 | "count": { 61 | "type": "integer", 62 | "minimum": 1 63 | }, 64 | "min": { 65 | "type": "integer", 66 | "minimum": 0 67 | }, 68 | "max": { 69 | "type": "integer", 70 | "minimum": 0 71 | }, 72 | "from_nested": { 73 | "type": "array", 74 | "minItems": 1, 75 | "items": { 76 | "$ref": "#/definitions/submission_requirements" 77 | } 78 | } 79 | }, 80 | "required": [ 81 | "rule", 82 | "from_nested" 83 | ], 84 | "additionalProperties": false 85 | } 86 | ] 87 | } 88 | }, 89 | "type": "object", 90 | "properties": { 91 | "submission_requirements": { 92 | "type": "array", 93 | "items": { 94 | "$ref": "#/definitions/submission_requirements" 95 | } 96 | } 97 | }, 98 | "required": [ 99 | "submission_requirements" 100 | ], 101 | "additionalProperties": false 102 | } -------------------------------------------------------------------------------- /schemas/v2.0.0/input-descriptor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Input Descriptor", 4 | "definitions": { 5 | "status_directive": { 6 | "type": "object", 7 | "additionalProperties": false, 8 | "properties": { 9 | "directive": { 10 | "type": "string", 11 | "enum": [ 12 | "required", 13 | "allowed", 14 | "disallowed" 15 | ] 16 | }, 17 | "type": { 18 | "type": "array", 19 | "minItems": 1, 20 | "items": { 21 | "type": "string" 22 | } 23 | } 24 | } 25 | }, 26 | "field": { 27 | "type": "object", 28 | "oneOf": [ 29 | { 30 | "properties": { 31 | "id": { 32 | "type": "string" 33 | }, 34 | "optional": { 35 | "type": "boolean" 36 | }, 37 | "path": { 38 | "type": "array", 39 | "items": { 40 | "type": "string" 41 | } 42 | }, 43 | "purpose": { 44 | "type": "string" 45 | }, 46 | "intent_to_retain": { 47 | "type": "boolean" 48 | }, 49 | "name": { 50 | "type": "string" 51 | }, 52 | "filter": { 53 | "$ref": "http://json-schema.org/draft-07/schema#" 54 | } 55 | }, 56 | "required": [ 57 | "path" 58 | ], 59 | "additionalProperties": false 60 | }, 61 | { 62 | "properties": { 63 | "id": { 64 | "type": "string" 65 | }, 66 | "optional": { 67 | "type": "boolean" 68 | }, 69 | "path": { 70 | "type": "array", 71 | "items": { 72 | "type": "string" 73 | } 74 | }, 75 | "purpose": { 76 | "type": "string" 77 | }, 78 | "intent_to_retain": { 79 | "type": "boolean" 80 | }, 81 | "filter": { 82 | "$ref": "http://json-schema.org/draft-07/schema#" 83 | }, 84 | "name": { 85 | "type": "string" 86 | }, 87 | "predicate": { 88 | "type": "string", 89 | "enum": [ 90 | "required", 91 | "preferred" 92 | ] 93 | } 94 | }, 95 | "required": [ 96 | "path", 97 | "filter", 98 | "predicate" 99 | ], 100 | "additionalProperties": false 101 | } 102 | ] 103 | } 104 | }, 105 | "type": "object", 106 | "additionalProperties": false, 107 | "properties": { 108 | "id": { 109 | "type": "string" 110 | }, 111 | "name": { 112 | "type": "string" 113 | }, 114 | "purpose": { 115 | "type": "string" 116 | }, 117 | "group": { 118 | "type": "array", 119 | "items": { 120 | "type": "string" 121 | } 122 | }, 123 | "constraints": { 124 | "type": "object", 125 | "additionalProperties": false, 126 | "properties": { 127 | "limit_disclosure": { 128 | "type": "string", 129 | "enum": [ 130 | "required", 131 | "preferred" 132 | ] 133 | }, 134 | "statuses": { 135 | "type": "object", 136 | "additionalProperties": false, 137 | "properties": { 138 | "active": { 139 | "$ref": "#/definitions/status_directive" 140 | }, 141 | "suspended": { 142 | "$ref": "#/definitions/status_directive" 143 | }, 144 | "revoked": { 145 | "$ref": "#/definitions/status_directive" 146 | } 147 | } 148 | }, 149 | "fields": { 150 | "type": "array", 151 | "items": { 152 | "$ref": "#/definitions/field" 153 | } 154 | }, 155 | "subject_is_issuer": { 156 | "type": "string", 157 | "enum": [ 158 | "required", 159 | "preferred" 160 | ] 161 | }, 162 | "is_holder": { 163 | "type": "array", 164 | "items": { 165 | "type": "object", 166 | "additionalProperties": false, 167 | "properties": { 168 | "field_id": { 169 | "type": "array", 170 | "items": { 171 | "type": "string" 172 | } 173 | }, 174 | "directive": { 175 | "type": "string", 176 | "enum": [ 177 | "required", 178 | "preferred" 179 | ] 180 | } 181 | }, 182 | "required": [ 183 | "field_id", 184 | "directive" 185 | ] 186 | } 187 | }, 188 | "same_subject": { 189 | "type": "array", 190 | "items": { 191 | "type": "object", 192 | "additionalProperties": false, 193 | "properties": { 194 | "field_id": { 195 | "type": "array", 196 | "items": { 197 | "type": "string" 198 | } 199 | }, 200 | "directive": { 201 | "type": "string", 202 | "enum": [ 203 | "required", 204 | "preferred" 205 | ] 206 | } 207 | }, 208 | "required": [ 209 | "field_id", 210 | "directive" 211 | ] 212 | } 213 | } 214 | } 215 | } 216 | }, 217 | "required": [ 218 | "id" 219 | ] 220 | } 221 | -------------------------------------------------------------------------------- /schemas/v2.0.0/presentation-submission.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Presentation Submission", 4 | "type": "object", 5 | "properties": { 6 | "presentation_submission": { 7 | "type": "object", 8 | "properties": { 9 | "id": { "type": "string" }, 10 | "definition_id": { "type": "string" }, 11 | "descriptor_map": { 12 | "type": "array", 13 | "items": { "$ref": "#/definitions/descriptor" } 14 | } 15 | }, 16 | "required": ["id", "definition_id", "descriptor_map"], 17 | "additionalProperties": false 18 | } 19 | }, 20 | "definitions": { 21 | "descriptor": { 22 | "type": "object", 23 | "properties": { 24 | "id": { "type": "string" }, 25 | "path": { "type": "string" }, 26 | "path_nested": { 27 | "type": "object", 28 | "$ref": "#/definitions/descriptor" 29 | }, 30 | "format": { 31 | "$ref": "https://identity.foundation/claim-format-registry/schemas/presentation-submission-claim-format-designations.json#/definitions/format" 32 | } 33 | }, 34 | "required": ["id", "path", "format"], 35 | "additionalProperties": false 36 | } 37 | }, 38 | "required": ["presentation_submission"], 39 | "additionalProperties": false 40 | } -------------------------------------------------------------------------------- /schemas/v2.0.0/submission-requirement.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Presentation Submission Requirement", 4 | "definitions": { 5 | "submission_requirement": { 6 | "type": "object", 7 | "oneOf": [ 8 | { 9 | "properties": { 10 | "name": { "type": "string" }, 11 | "purpose": { "type": "string" }, 12 | "rule": { 13 | "type": "string", 14 | "enum": ["all", "pick"] 15 | }, 16 | "count": { "type": "integer", "minimum": 1 }, 17 | "min": { "type": "integer", "minimum": 0 }, 18 | "max": { "type": "integer", "minimum": 0 }, 19 | "from": { "type": "string" } 20 | }, 21 | "required": ["rule", "from"], 22 | "additionalProperties": false 23 | }, 24 | { 25 | "properties": { 26 | "name": { "type": "string" }, 27 | "purpose": { "type": "string" }, 28 | "rule": { 29 | "type": "string", 30 | "enum": ["all", "pick"] 31 | }, 32 | "count": { "type": "integer", "minimum": 1 }, 33 | "min": { "type": "integer", "minimum": 0 }, 34 | "max": { "type": "integer", "minimum": 0 }, 35 | "from_nested": { 36 | "type": "array", 37 | "minItems": 1, 38 | "items": { 39 | "$ref": "#/definitions/submission_requirement" 40 | } 41 | } 42 | }, 43 | "required": ["rule", "from_nested"], 44 | "additionalProperties": false 45 | } 46 | ] 47 | } 48 | }, 49 | "$ref": "#/definitions/submission_requirement" 50 | } -------------------------------------------------------------------------------- /schemas/v2.0.0/submission-requirements.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Submission Requirements", 4 | "definitions": { 5 | "submission_requirements": { 6 | "type": "object", 7 | "oneOf": [ 8 | { 9 | "properties": { 10 | "name": { 11 | "type": "string" 12 | }, 13 | "purpose": { 14 | "type": "string" 15 | }, 16 | "rule": { 17 | "type": "string", 18 | "enum": [ 19 | "all", 20 | "pick" 21 | ] 22 | }, 23 | "count": { 24 | "type": "integer", 25 | "minimum": 1 26 | }, 27 | "min": { 28 | "type": "integer", 29 | "minimum": 0 30 | }, 31 | "max": { 32 | "type": "integer", 33 | "minimum": 0 34 | }, 35 | "from": { 36 | "type": "string" 37 | } 38 | }, 39 | "required": [ 40 | "rule", 41 | "from" 42 | ], 43 | "additionalProperties": false 44 | }, 45 | { 46 | "properties": { 47 | "name": { 48 | "type": "string" 49 | }, 50 | "purpose": { 51 | "type": "string" 52 | }, 53 | "rule": { 54 | "type": "string", 55 | "enum": [ 56 | "all", 57 | "pick" 58 | ] 59 | }, 60 | "count": { 61 | "type": "integer", 62 | "minimum": 1 63 | }, 64 | "min": { 65 | "type": "integer", 66 | "minimum": 0 67 | }, 68 | "max": { 69 | "type": "integer", 70 | "minimum": 0 71 | }, 72 | "from_nested": { 73 | "type": "array", 74 | "minItems": 1, 75 | "items": { 76 | "$ref": "#/definitions/submission_requirements" 77 | } 78 | } 79 | }, 80 | "required": [ 81 | "rule", 82 | "from_nested" 83 | ], 84 | "additionalProperties": false 85 | } 86 | ] 87 | } 88 | }, 89 | "type": "object", 90 | "properties": { 91 | "submission_requirements": { 92 | "type": "array", 93 | "items": { 94 | "$ref": "#/definitions/submission_requirements" 95 | } 96 | } 97 | }, 98 | "required": [ 99 | "submission_requirements" 100 | ], 101 | "additionalProperties": false 102 | } -------------------------------------------------------------------------------- /specs.json: -------------------------------------------------------------------------------- 1 | { 2 | "specs": [ 3 | { 4 | "title": "DIF Presentation Exchange", 5 | "spec_directory": "./spec/v1.0.0", 6 | "output_path": "./build/spec/v1.0.0", 7 | "logo": "https://rawcdn.githack.com/decentralized-identity/decentralized-identity.github.io/a3ca39717e440302d1fd99a796e7f00e1c42eb2d/images/logo-flat.svg", 8 | "logo_link": "https://identity.foundation", 9 | "source": { 10 | "host": "github", 11 | "account": "decentralized-identity", 12 | "repo": "presentation-exchange" 13 | } 14 | }, 15 | { 16 | "title": "DIF Presentation Exchange", 17 | "spec_directory": "./spec/v2.0.0", 18 | "output_path": "./build/spec/v2.0.0", 19 | "logo": "https://rawcdn.githack.com/decentralized-identity/decentralized-identity.github.io/a3ca39717e440302d1fd99a796e7f00e1c42eb2d/images/logo-flat.svg", 20 | "logo_link": "https://identity.foundation", 21 | "source": { 22 | "host": "github", 23 | "account": "decentralized-identity", 24 | "repo": "presentation-exchange" 25 | } 26 | }, 27 | { 28 | "title": "DIF Presentation Exchange", 29 | "spec_directory": "./spec/v2.1.0", 30 | "output_path": "./build/spec/v2.1.0", 31 | "logo": "https://rawcdn.githack.com/decentralized-identity/decentralized-identity.github.io/a3ca39717e440302d1fd99a796e7f00e1c42eb2d/images/logo-flat.svg", 32 | "logo_link": "https://identity.foundation", 33 | "source": { 34 | "host": "github", 35 | "account": "decentralized-identity", 36 | "repo": "presentation-exchange" 37 | } 38 | }, 39 | { 40 | "title": "DIF Presentation Exchange", 41 | "spec_directory": "./spec/v2.1.1", 42 | "output_path": "./build/spec/v2.1.1", 43 | "logo": "https://rawcdn.githack.com/decentralized-identity/decentralized-identity.github.io/a3ca39717e440302d1fd99a796e7f00e1c42eb2d/images/logo-flat.svg", 44 | "logo_link": "https://identity.foundation", 45 | "source": { 46 | "host": "github", 47 | "account": "decentralized-identity", 48 | "repo": "presentation-exchange" 49 | } 50 | }, 51 | { 52 | "title": "DIF Presentation Exchange", 53 | "spec_directory": "./spec", 54 | "output_path": "./build", 55 | "logo": "https://rawcdn.githack.com/decentralized-identity/decentralized-identity.github.io/a3ca39717e440302d1fd99a796e7f00e1c42eb2d/images/logo-flat.svg", 56 | "logo_link": "https://identity.foundation", 57 | "source": { 58 | "host": "github", 59 | "account": "decentralized-identity", 60 | "repo": "presentation-exchange" 61 | } 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /submission/v1/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": { 3 | "@version": 1.1, 4 | "PresentationSubmission": { 5 | "@id": "https://identity.foundation/presentation-exchange/#presentation-submission", 6 | "@context": { 7 | "@version": 1.1, 8 | "presentation_submission": { 9 | "@id": "https://identity.foundation/presentation-exchange/#presentation-submission", 10 | "@type": "@json" 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/__fixtures__/ajv.js: -------------------------------------------------------------------------------- 1 | const Ajv = require('ajv'); 2 | const addFormats = require('ajv-formats'); 3 | 4 | const schemas = { 5 | "https://identity.foundation/claim-format-registry/schemas/presentation-definition-claim-format-designations.json": { 6 | "$schema": "http://json-schema.org/draft-07/schema#", 7 | "title": "Presentation Definition Claim Format Designations", 8 | "type": "object", 9 | "additionalProperties": false, 10 | "patternProperties": { 11 | "^jwt$|^jwt_vc$|^jwt_vp$": { 12 | "type": "object", 13 | "additionalProperties": false, 14 | "properties": { 15 | "alg": { 16 | "type": "array", 17 | "minItems": 1, 18 | "items": { "type": "string" } 19 | } 20 | } 21 | }, 22 | "^ldp_vc$|^ldp_vp$|^ldp$": { 23 | "type": "object", 24 | "additionalProperties": false, 25 | "properties": { 26 | "proof_type": { 27 | "type": "array", 28 | "minItems": 1, 29 | "items": { "type": "string" } 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | "https://identity.foundation/claim-format-registry/schemas/presentation-submission-claim-format-designations.json": { 36 | "$schema": "http://json-schema.org/draft-07/schema#", 37 | "title": "Presentation Submission Claim Format Designations", 38 | "type": "object", 39 | "definitions": { 40 | "format": { 41 | "type": "string", 42 | "enum": ["jwt", "jwt_vc", "jwt_vp", "ldp", "ldp_vc", "ldp_vp"] 43 | } 44 | } 45 | } 46 | } 47 | 48 | const ajv = addFormats(new Ajv({allErrors: true})); 49 | 50 | Object.keys(schemas).forEach((uri) => ajv.addSchema(schemas[uri], uri)) 51 | 52 | module.exports = ajv 53 | -------------------------------------------------------------------------------- /test/presentation-definition/VC_expiration_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drivers_license_information", 3 | "name": "Verify Valid License", 4 | "purpose": "We need you to show that your driver's license will be valid through December of this year.", 5 | "constraints": { 6 | "fields": [ 7 | { 8 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 9 | "filter": { 10 | "type": "string", 11 | "const": "https://yourwatchful.gov/drivers-license-schema.json" 12 | } 13 | }, 14 | { 15 | "path": ["$.expirationDate"], 16 | "filter": { 17 | "type": "string", 18 | "format": "date-time" 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/presentation-definition/VC_revocation_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drivers_license_information", 3 | "name": "Verify Valid License", 4 | "purpose": "We need to know that your license has not been revoked.", 5 | "constraints": { 6 | "fields": [ 7 | { 8 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 9 | "filter": { 10 | "type": "string", 11 | "const": "https://yourwatchful.gov/drivers-license-schema.json" 12 | } 13 | }, 14 | { 15 | "path": ["$.credentialStatus"] 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/presentation-definition/basic_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "input_descriptors": [ 6 | { 7 | "id": "bankaccount_input", 8 | "name": "Full Bank Account Routing Information", 9 | "purpose": "We can only remit payment to a currently-valid bank account, submitted as an ABA RTN + Acct # or IBAN.", 10 | "constraints": { 11 | "limit_disclosure": "required", 12 | "fields": [ 13 | { 14 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 15 | "filter": { 16 | "type": "string", 17 | "const": "https://bank-standards.example.com/fullaccountroute.json" 18 | } 19 | }, 20 | { 21 | "path": [ 22 | "$.issuer", 23 | "$.vc.issuer", 24 | "$.iss" 25 | ], 26 | "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor, or regulatory authority.", 27 | "filter": { 28 | "type": "string", 29 | "pattern": "^did:example:123$|^did:example:456$" 30 | }, 31 | "intent_to_retain": true 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "id": "us_passport_input", 38 | "name": "US Passport", 39 | "constraints": { 40 | "fields": [ 41 | { 42 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 43 | "filter": { 44 | "type": "string", 45 | "const": "hub://did:foo:123/Collections/schema.us.gov/passport.json" 46 | } 47 | }, 48 | { 49 | "path": ["$.credentialSubject.birth_date", "$.vc.credentialSubject.birth_date", "$.birth_date"], 50 | "filter": { 51 | "type": "string", 52 | "format": "date" 53 | } 54 | } 55 | ] 56 | } 57 | } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/presentation-definition/format_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [], 5 | "format": { 6 | "jwt": { 7 | "alg": ["EdDSA", "ES256K", "ES384"] 8 | }, 9 | "jwt_vc": { 10 | "alg": ["ES256K", "ES384"] 11 | }, 12 | "jwt_vp": { 13 | "alg": ["EdDSA", "ES256K"] 14 | }, 15 | "ldp_vc": { 16 | "proof_type": [ 17 | "JsonWebSignature2020", 18 | "Ed25519Signature2018", 19 | "EcdsaSecp256k1Signature2019", 20 | "RsaSignature2018" 21 | ] 22 | }, 23 | "ldp_vp": { 24 | "proof_type": ["Ed25519Signature2018"] 25 | }, 26 | "ldp": { 27 | "proof_type": ["RsaSignature2018"] 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/presentation-definition/input_descriptor_id_tokens_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [ 5 | { 6 | "id": "employment_input_xyz_gov", 7 | "group": ["B"], 8 | "name": "Verify XYZ Government Employment", 9 | "purpose": "Verifying current employment at XYZ Government agency as proxy for permission to access this resource", 10 | "constraints": { 11 | "fields": [ 12 | { 13 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 14 | "filter": { 15 | "type": "string", 16 | "const": "https://login.idp.com/xyz.gov/.well-known/openid-configuration" 17 | } 18 | }, 19 | { 20 | "path": ["$.status"], 21 | "filter": { 22 | "type": "string", 23 | "pattern": "^active$" 24 | } 25 | } 26 | ] 27 | } 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/presentation-definition/input_descriptors_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [ 5 | { 6 | "id": "banking_input_1", 7 | "name": "Bank Account Information", 8 | "purpose": "We can only remit payment to a currently-valid bank account.", 9 | "group": [ 10 | "A" 11 | ], 12 | "constraints": { 13 | "fields": [ 14 | { 15 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 16 | "filter": { 17 | "type": "string", 18 | "pattern": "^https://bank-schemas.org/1.0.0/accounts.json|https://bank-schemas.org/2.0.0/accounts.json$" 19 | } 20 | }, 21 | { 22 | "path": [ 23 | "$.issuer", 24 | "$.vc.issuer", 25 | "$.iss" 26 | ], 27 | "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", 28 | "filter": { 29 | "type": "string", 30 | "pattern": "^did:example:123$|^did:example:456$" 31 | }, 32 | "intent_to_retain": true 33 | }, 34 | { 35 | "path": [ 36 | "$.credentialSubject.account[*].id", 37 | "$.vc.credentialSubject.account[*].id", 38 | "$.account[*].id" 39 | ], 40 | "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", 41 | "filter": { 42 | "type": "string", 43 | "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}$" 44 | }, 45 | "intent_to_retain": true 46 | }, 47 | { 48 | "path": [ 49 | "$.credentialSubject.account[*].route", 50 | "$.vc.credentialSubject.account[*].route", 51 | "$.account[*].route" 52 | ], 53 | "purpose": "We can only remit payment to a currently-valid account at a US, Japanese, or German federally-accredited bank, submitted as an ABA RTN or SWIFT code.", 54 | "filter": { 55 | "type": "string", 56 | "pattern": "^[0-9]{9}|^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?$" 57 | }, 58 | "intent_to_retain": true 59 | } 60 | ] 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/presentation-definition/minimal_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "input_descriptors": [ 6 | { 7 | "id": "wa_driver_license", 8 | "name": "Washington State Business License", 9 | "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", 10 | "constraints": { 11 | "fields": [ 12 | { 13 | "path": [ 14 | "$.credentialSubject.dateOfBirth", 15 | "$.credentialSubject.dob", 16 | "$.vc.credentialSubject.dateOfBirth", 17 | "$.vc.credentialSubject.dob" 18 | ] 19 | } 20 | ] 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/presentation-definition/pd_filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "first simple example", 4 | "input_descriptors": [ 5 | { 6 | "id": "A specific type of VC", 7 | "name": "A specific type of VC", 8 | "purpose": "We want a VC of this type", 9 | "constraints": { 10 | "fields": [ 11 | { 12 | "path": [ 13 | "$.type" 14 | ], 15 | "filter": { 16 | "type": "array", 17 | "contains": { 18 | "type": "string", 19 | "pattern": "^$" 20 | } 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/presentation-definition/pd_filter2.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "Scalable trust example", 4 | "input_descriptors": [ 5 | { 6 | "id": "any type of credit card from any bank", 7 | "name": "any type of credit card from any bank", 8 | "purpose": "Please provide your credit card details", 9 | "constraints": { 10 | "fields": [ 11 | { 12 | "path": [ 13 | "$.termsOfUse" 14 | ], 15 | "filter": { 16 | "$defs": { 17 | "typeString": { 18 | "type": "string", 19 | "pattern": "^https://train.trust-scheme.de/info$" 20 | }, 21 | "typeStringOrArray": { 22 | "anyOf": [ 23 | { 24 | "$ref": "#/$defs/typeString" 25 | }, 26 | { 27 | "type": "array", 28 | "contains": { 29 | "$ref": "#/$defs/typeString" 30 | } 31 | } 32 | ] 33 | }, 34 | "trustSchemeString": { 35 | "type": "string", 36 | "pattern": "^worldbankfederation.com$" 37 | }, 38 | "trustSchemeStringOrArray": { 39 | "anyOf": [ 40 | { 41 | "$ref": "#/$defs/trustSchemeString" 42 | }, 43 | { 44 | "type": "array", 45 | "contains": { 46 | "$ref": "#/$defs/trustSchemeString" 47 | } 48 | } 49 | ] 50 | }, 51 | "tosObject": { 52 | "type": "object", 53 | "required": [ 54 | "type", 55 | "trustScheme" 56 | ], 57 | "properties": { 58 | "type": { 59 | "$ref": "#/$defs/typeStringOrArray" 60 | }, 61 | "trustScheme": { 62 | "$ref": "#/$defs/trustSchemeStringOrArray" 63 | } 64 | } 65 | }, 66 | "tosObjectOrArray": { 67 | "anyOf": [ 68 | { 69 | "$ref": "#/$defs/tosObject" 70 | }, 71 | { 72 | "type": "array", 73 | "contains": { 74 | "$ref": "#/$defs/tosObject" 75 | } 76 | } 77 | ] 78 | } 79 | }, 80 | "$ref": "#/$defs/tosObjectOrArray" 81 | } 82 | }, 83 | { 84 | "path": [ 85 | "$.type" 86 | ], 87 | "filter": { 88 | "type": "array", 89 | "contains": { 90 | "type": "string", 91 | "pattern": "^creditCard$" 92 | } 93 | } 94 | } 95 | ] 96 | } 97 | } 98 | ] 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/presentation-definition/pd_filter2_simplified.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "Scalable trust example", 4 | "input_descriptors": [ 5 | { 6 | "id": "any type of credit card from any bank", 7 | "name": "any type of credit card from any bank", 8 | "purpose": "Please provide your credit card details", 9 | "constraints": { 10 | "fields": [ 11 | { 12 | "path": [ 13 | "$.termsOfUse.type" 14 | ], 15 | "filter": { 16 | "type": "string", 17 | "pattern": "^https://train.trust-scheme.de/info$" 18 | } 19 | }, 20 | { 21 | "path": [ 22 | "$.termsOfUse.trustScheme" 23 | ], 24 | "filter": { 25 | "type": "string", 26 | "pattern": "^worldbankfederation.com$" 27 | } 28 | }, 29 | { 30 | "path": [ 31 | "$.type" 32 | ], 33 | "filter": { 34 | "type": "string", 35 | "pattern": "^creditCard$" 36 | } 37 | } 38 | ] 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/presentation-definition/single_group_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "VP, OIDC, DIDComm, or CHAPI outer wrapper here", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "submission_requirements": [{ 6 | "name": "Citizenship Information", 7 | "rule": "pick", 8 | "count": 1, 9 | "from": "A" 10 | }], 11 | "input_descriptors": [ 12 | { 13 | "id": "citizenship_input_1", 14 | "name": "EU Driver's License", 15 | "group": ["A"], 16 | "constraints": { 17 | "fields": [ 18 | { 19 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 20 | "filter": { 21 | "type": "string", 22 | "const": "https://eu.com/claims/DriversLicense.json" 23 | } 24 | }, 25 | { 26 | "path": ["$.issuer", "$.vc.issuer", "$.iss"], 27 | "purpose": "We can only accept digital driver's licenses issued by national authorities of member states or trusted notarial auditors.", 28 | "filter": { 29 | "type": "string", 30 | "pattern": "^did:example:gov1$|^did:example:gov2$" 31 | } 32 | }, 33 | { 34 | "path": ["$.credentialSubject.dob", "$.vc.credentialSubject.dob", "$.dob"], 35 | "filter": { 36 | "type": "string", 37 | "format": "date" 38 | } 39 | } 40 | ] 41 | } 42 | }, 43 | { 44 | "id": "citizenship_input_2", 45 | "name": "US Passport", 46 | "group": ["A"], 47 | "constraints": { 48 | "fields": [ 49 | { 50 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 51 | "filter": { 52 | "type": "string", 53 | "const": "hub://did:foo:123/Collections/schema.us.gov/passport.json" 54 | } 55 | }, 56 | { 57 | "path": ["$.credentialSubject.birth_date", "$.vc.credentialSubject.birth_date", "$.birth_date"], 58 | "filter": { 59 | "type": "string", 60 | "format": "date" 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/presentation-definition/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('../__fixtures__/ajv'); 4 | 5 | const presentationDefinitionEnvelopeSchema = "/../../schemas/presentation-definition-envelope.json" 6 | 7 | describe('Presentation Definition', function () { 8 | describe('JSON Schema', function () { 9 | it('should validate the basic example object using JSON Schema Draft 7', function () { 10 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 11 | const data = JSON.parse(fs.readFileSync(__dirname + '/basic_example.json')); 12 | const validate = ajv.compile(schema); 13 | const valid = validate(data); 14 | 15 | assert.equal(null, validate.errors); 16 | assert.equal(true, valid); 17 | }); 18 | 19 | it('should validate the single group example object using JSON Schema Draft 7', function () { 20 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 21 | const data = JSON.parse(fs.readFileSync(__dirname + '/single_group_example.json')); 22 | const validate = ajv.compile(schema); 23 | const valid = validate(data); 24 | 25 | assert.equal(null, validate.errors); 26 | assert.equal(true, valid); 27 | }); 28 | 29 | it('should validate the multi group example object using JSON Schema Draft 7', function () { 30 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 31 | const data = JSON.parse(fs.readFileSync(__dirname + '/multi_group_example.json')); 32 | const validate = ajv.compile(schema); 33 | const valid = validate(data); 34 | 35 | assert.equal(null, validate.errors); 36 | assert.equal(true, valid); 37 | }); 38 | 39 | it('should validate the format example object using JSON Schema Draft 7', function () { 40 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 41 | const data = JSON.parse(fs.readFileSync(__dirname + '/format_example.json')); 42 | const validate = ajv.compile(schema); 43 | const valid = validate(data); 44 | 45 | assert.equal(null, validate.errors); 46 | assert.equal(true, valid); 47 | }); 48 | 49 | it('should validate the input description sample example object using JSON Schema Draft 7', function () { 50 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 51 | const data = JSON.parse(fs.readFileSync(__dirname + '/input_descriptors_example.json')); 52 | const validate = ajv.compile(schema); 53 | const valid = validate(data); 54 | 55 | assert.equal(null, validate.errors); 56 | assert.equal(true, valid); 57 | }); 58 | 59 | it('should validate the input description id tokens example object using JSON Schema Draft 7', function () { 60 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 61 | const data = JSON.parse(fs.readFileSync(__dirname + '/input_descriptor_id_tokens_example.json')); 62 | const validate = ajv.compile(schema); 63 | const valid = validate(data); 64 | 65 | assert.equal(null, validate.errors); 66 | assert.equal(true, valid); 67 | }); 68 | 69 | it('should validate the VC expiration example object using JSON Schema Draft 7', function () { 70 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 71 | const data = JSON.parse(fs.readFileSync(__dirname + '/VC_expiration_example.json')); 72 | const validate = ajv.compile(schema); 73 | const valid = validate(data); 74 | 75 | assert.equal(null, validate.errors); 76 | assert.equal(true, valid); 77 | }); 78 | 79 | it('should validate the VC revocation example object using JSON Schema Draft 7', function () { 80 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 81 | const data = JSON.parse(fs.readFileSync(__dirname + '/VC_revocation_example.json')); 82 | const validate = ajv.compile(schema); 83 | const valid = validate(data); 84 | 85 | assert.equal(null, validate.errors); 86 | assert.equal(true, valid); 87 | }); 88 | 89 | it('should validate the Filter By Credential Type example using JSON Schema Draft 7', function () { 90 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 91 | const data = JSON.parse(fs.readFileSync(__dirname + '/pd_filter.json')); 92 | const validate = ajv.compile(schema); 93 | const valid = validate(data); 94 | 95 | assert.equal(null, validate.errors); 96 | assert.equal(true, valid); 97 | }); 98 | 99 | it('should validate the Two Filters example object using JSON Schema Draft 7', function () { 100 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 101 | const data = JSON.parse(fs.readFileSync(__dirname + '/pd_filter2.json')); 102 | const validate = ajv.compile(schema); 103 | const valid = validate(data); 104 | 105 | assert.equal(null, validate.errors); 106 | assert.equal(true, valid); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/presentation-submission/appendix_CHAPI_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "web", 3 | "dataType": "VerifiablePresentation", 4 | "data": { 5 | "comment": "Presentation Submission goes here" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/presentation-submission/appendix_DIDComm_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "https://didcomm.org/present-proof/%VER/presentation", 3 | "@id": "f1ca8245-ab2d-4d9c-8d7d-94bf310314ef", 4 | "comment": "some comment", 5 | "formats" : [{ 6 | "attach_id" : "2a3f1c4c-623c-44e6-b159-179048c51260", 7 | "format" : "dif/presentation-exchange/submission@v1.0" 8 | }], 9 | "presentations~attach": [{ 10 | "@id": "2a3f1c4c-623c-44e6-b159-179048c51260", 11 | "mime-type": "application/ld+json", 12 | "data": { 13 | "json": { 14 | "comment": "Presentation Submission goes here" 15 | } 16 | } 17 | }] 18 | } -------------------------------------------------------------------------------- /test/presentation-submission/appendix_JWT_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "iat": 1673585101, 3 | "iss": "did:key:z6MkgbfWabZ1xqdVSBCXsaMTbL8aupZ9t7eVptKkXgrp4mQ7", 4 | "vp": { 5 | "@context": [ 6 | "https://www.w3.org/2018/credentials/v1" 7 | ], 8 | "holder": "did:key:z6MkgbfWabZ1xqdVSBCXsaMTbL8aupZ9t7eVptKkXgrp4mQ7", 9 | "presentation_submission": { 10 | "definition_id": "64422289-12de-49b9-bbf0-005bc610c2ad", 11 | "descriptor_map": [ 12 | { 13 | "format": "jwt_vp", 14 | "id": "wa_driver_license", 15 | "path": "$.verifiableCredential[0]" 16 | } 17 | ], 18 | "id": "0f28b372-72d5-493e-bc86-b0f71ba0b87b" 19 | }, 20 | "type": [ 21 | "VerifiablePresentation" 22 | ], 23 | "verifiableCredential": [ 24 | "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa21YMXY4TjE2WEdnSlVFQjJxYmFXWTZ1S1Nuc2NEckdkc01xeGZVZzNrRnB0IiwidHlwIjoiSldUIn0.eyJleHAiOjI1ODAxMzAwODAsImlzcyI6ImRpZDprZXk6ejZNa21YMXY4TjE2WEdnSlVFQjJxYmFXWTZ1S1Nuc2NEckdkc01xeGZVZzNrRnB0IiwianRpIjoiMzRiMWI0NWUtYmI2Yy00ZTU4LWI2NzUtMjkzODNjODI4Mjk0IiwibmJmIjoxNjczNTg1MTAxLCJzdWIiOiJkaWQ6a2V5Ono2TWtnYmZXYWJaMXhxZFZTQkNYc2FNVGJMOGF1cFo5dDdlVnB0S2tYZ3JwNG1RNyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImlkIjoiMzRiMWI0NWUtYmI2Yy00ZTU4LWI2NzUtMjkzODNjODI4Mjk0IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWttWDF2OE4xNlhHZ0pVRUIycWJhV1k2dUtTbnNjRHJHZHNNcXhmVWcza0ZwdCIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDEtMTNUMDQ6NDU6MDFaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDUxLTEwLTA1VDE0OjQ4OjAwLjAwMFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsTmFtZSI6Ik1jbG92aW4iLCJkYXRlT2ZCaXJ0aCI6IjE5ODctMDEtMDIiLCJmYW1pbHlOYW1lIjoiQW5kcmVzIiwiZ2l2ZW5OYW1lIjoiVXJpYmUiLCJpZCI6ImRpZDprZXk6ejZNa2diZldhYloxeHFkVlNCQ1hzYU1UYkw4YXVwWjl0N2VWcHRLa1hncnA0bVE3In19fQ.gsSv1glZ8puaOmIid9_a-iGS9pznGYI9BFUqIJOpK-Ep9H7KFB5FfyKaCLQymklxJwqv3Ftr8axnj-UPnOzjCg" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/presentation-submission/appendix_OIDC_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "iss": "https://self-issued.me", 3 | "sub": "248289761001", 4 | "preferred_username": "superman445", 5 | "presentation_submission": { 6 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 7 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 8 | "descriptor_map": [ 9 | { 10 | "id": "banking_input_2", 11 | "format": "jwt", 12 | "path": "$._claim_sources.banking_input_2.JWT" 13 | }, 14 | { 15 | "id": "employment_input", 16 | "format": "jwt_vc", 17 | "path": "$._claim_sources.employment_input.VC_JWT" 18 | }, 19 | { 20 | "id": "citizenship_input_1", 21 | "format": "ldp_vc", 22 | "path": "$._claim_sources.citizenship_input_1.VC" 23 | } 24 | ] 25 | }, 26 | "_claim_names": { 27 | "verified_claims": [ 28 | "banking_input_2", 29 | "employment_input", 30 | "citizenship_input_1" 31 | ] 32 | }, 33 | "_claim_sources": { 34 | "banking_input_2": { 35 | "JWT": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3NlcnZlci5vdGhlcm9wLmNvbSIsInN1YiI6ImU4MTQ4NjAzLTg5MzQtNDI0NS04MjViLWMxMDhiOGI2Yjk0NSIsInZlcmlmaWVkX2NsYWltcyI6eyJ2ZXJpZmljYXRpb24iOnsidHJ1c3RfZnJhbWV3b3JrIjoiaWFsX2V4YW1wbGVfZ29sZCJ9LCJjbGFpbXMiOnsiZ2l2ZW5fbmFtZSI6Ik1heCIsImZhbWlseV9uYW1lIjoiTWVpZXIiLCJiaXJ0aGRhdGUiOiIxOTU2LTAxLTI4In19fQ.FArlPUtUVn95HCExePlWJQ6ctVfVpQyeSbe3xkH9MH1QJjnk5GVbBW0qe1b7R3lE-8iVv__0mhRTUI5lcFhLjoGjDS8zgWSarVsEEjwBK7WD3r9cEw6ZAhfEkhHL9eqAaED2rhhDbHD5dZWXkJCuXIcn65g6rryiBanxlXK0ZmcK4fD9HV9MFduk0LRG_p4yocMaFvVkqawat5NV9QQ3ij7UBr3G7A4FojcKEkoJKScdGoozir8m5XD83Sn45_79nCcgWSnCX2QTukL8NywIItu_K48cjHiAGXXSzydDm_ccGCe0sY-Ai2-iFFuQo2PtfuK2SqPPmAZJxEFrFoLY4g" 36 | }, 37 | "employment_input": { 38 | "VC": { 39 | "@context": "https://www.w3.org/2018/credentials/v1", 40 | "id": "https://business-standards.org/schemas/employment-history.json", 41 | "type": ["VerifiableCredential", "GenericEmploymentCredential"], 42 | "issuer": "did:foo:123", 43 | "issuanceDate": "2010-01-01T19:73:24Z", 44 | "credentialSubject": { 45 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 46 | "active": true 47 | }, 48 | "proof": { 49 | "type": "EcdsaSecp256k1VerificationKey2019", 50 | "created": "2017-06-18T21:19:10Z", 51 | "proofPurpose": "assertionMethod", 52 | "verificationMethod": "https://example.edu/issuers/keys/1", 53 | "jws": "..." 54 | } 55 | } 56 | }, 57 | "citizenship_input_1": { 58 | "VC": { 59 | "@context": "https://www.w3.org/2018/credentials/v1", 60 | "id": "https://eu.com/claims/DriversLicense", 61 | "type": ["EUDriversLicense"], 62 | "issuer": "did:foo:123", 63 | "issuanceDate": "2010-01-01T19:73:24Z", 64 | "credentialSubject": { 65 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 66 | "license": { 67 | "number": "34DGE352", 68 | "dob": "07/13/80" 69 | } 70 | }, 71 | "proof": { 72 | "type": "EcdsaSecp256k1VerificationKey2019", 73 | "created": "2017-06-18T21:19:10Z", 74 | "proofPurpose": "assertionMethod", 75 | "verificationMethod": "https://example.edu/issuers/keys/1", 76 | "jws": "..." 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/presentation-submission/appendix_VP_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2018/credentials/v1", 4 | "https://identity.foundation/presentation-exchange/submission/v1" 5 | ], 6 | "type": [ 7 | "VerifiablePresentation", 8 | "PresentationSubmission" 9 | ], 10 | "presentation_submission": { 11 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 12 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 13 | "descriptor_map": [ 14 | { 15 | "id": "banking_input_2", 16 | "format": "jwt_vc", 17 | "path": "$.verifiableCredential[0]" 18 | }, 19 | { 20 | "id": "employment_input", 21 | "format": "ldp_vc", 22 | "path": "$.verifiableCredential[1]" 23 | }, 24 | { 25 | "id": "citizenship_input_1", 26 | "format": "ldp_vc", 27 | "path": "$.verifiableCredential[2]" 28 | } 29 | ] 30 | }, 31 | "verifiableCredential": [ 32 | { 33 | "comment": "IN REALWORLD VPs, THIS WILL BE A BIG UGLY OBJECT INSTEAD OF THE DECODED JWT PAYLOAD THAT FOLLOWS", 34 | "vc": { 35 | "@context": "https://www.w3.org/2018/credentials/v1", 36 | "id": "https://eu.com/claims/DriversLicense", 37 | "type": ["EUDriversLicense"], 38 | "issuer": "did:example:123", 39 | "issuanceDate": "2010-01-01T19:73:24Z", 40 | "credentialSubject": { 41 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 42 | "accounts": [ 43 | { 44 | "id": "1234567890", 45 | "route": "DE-9876543210" 46 | }, 47 | { 48 | "id": "2457913570", 49 | "route": "DE-0753197542" 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | { 56 | "@context": "https://www.w3.org/2018/credentials/v1", 57 | "id": "https://business-standards.org/schemas/employment-history.json", 58 | "type": ["VerifiableCredential", "GenericEmploymentCredential"], 59 | "issuer": "did:foo:123", 60 | "issuanceDate": "2010-01-01T19:73:24Z", 61 | "credentialSubject": { 62 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 63 | "active": true 64 | }, 65 | "proof": { 66 | "type": "EcdsaSecp256k1VerificationKey2019", 67 | "created": "2017-06-18T21:19:10Z", 68 | "proofPurpose": "assertionMethod", 69 | "verificationMethod": "https://example.edu/issuers/keys/1", 70 | "jws": "..." 71 | } 72 | }, 73 | { 74 | "@context": "https://www.w3.org/2018/credentials/v1", 75 | "id": "https://eu.com/claims/DriversLicense", 76 | "type": ["EUDriversLicense"], 77 | "issuer": "did:foo:123", 78 | "issuanceDate": "2010-01-01T19:73:24Z", 79 | "credentialSubject": { 80 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 81 | "license": { 82 | "number": "34DGE352", 83 | "dob": "07/13/80" 84 | } 85 | }, 86 | "proof": { 87 | "type": "RsaSignature2018", 88 | "created": "2017-06-18T21:19:10Z", 89 | "proofPurpose": "assertionMethod", 90 | "verificationMethod": "https://example.edu/issuers/keys/1", 91 | "jws": "..." 92 | } 93 | } 94 | ], 95 | "proof": { 96 | "type": "RsaSignature2018", 97 | "created": "2018-09-14T21:19:10Z", 98 | "proofPurpose": "authentication", 99 | "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", 100 | "challenge": "1f44d55f-f161-4938-a659-f8026467f126", 101 | "domain": "4jt78h47fh47", 102 | "jws": "..." 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/presentation-submission/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_submission": { 3 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 4 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "descriptor_map": [ 6 | { 7 | "id": "banking_input_2", 8 | "path": "$.verifiableCredential[0]", 9 | "format": "jwt" 10 | }, 11 | { 12 | "id": "employment_input", 13 | "path": "$.verifiableCredential[1]", 14 | "format": "ldp_vc" 15 | }, 16 | { 17 | "id": "citizenship_input_1", 18 | "path": "$.verifiableCredential[2]", 19 | "format": "jwt_vp" 20 | }, 21 | { 22 | "id": "banking_input_2", 23 | "format": "jwt_vp", 24 | "path": "$.outerCredential[0]", 25 | "path_nested": { 26 | "id": "banking_input_2", 27 | "format": "ldp_vc", 28 | "path": "$.innerCredential[1]", 29 | "path_nested": { 30 | "id": "banking_input_2", 31 | "format": "jwt_vc", 32 | "path": "$.mostInnerCredential[2]" 33 | } 34 | } 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /test/presentation-submission/nested_submission_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_submission": { 3 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 4 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "descriptor_map": [ 6 | { 7 | "id": "banking_input_2", 8 | "format": "jwt_vp", 9 | "path": "$.outerClaim[0]", 10 | "path_nested": { 11 | "id": "banking_input_2", 12 | "format": "ldp_vc", 13 | "path": "$.innerClaim[1]", 14 | "path_nested": { 15 | "id": "banking_input_2", 16 | "format": "jwt_vc", 17 | "path": "$.mostInnerClaim[2]" 18 | } 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/presentation-submission/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('../__fixtures__/ajv'); 4 | 5 | 6 | const presentationSubmissionSchema = '/../../schemas/presentation-submission.json' 7 | 8 | describe('Presentation Submission', function () { 9 | describe('JSON Schema', function () { 10 | it('should validate the example object using JSON Schema Draft 7', function () { 11 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 12 | const data = JSON.parse(fs.readFileSync(__dirname + '/example.json')); 13 | const validate = ajv.compile(schema); 14 | const valid = validate(data); 15 | 16 | assert.equal(null, validate.errors); 17 | assert.equal(true, valid); 18 | }); 19 | 20 | it('should validate the nested submission example object using JSON Schema Draft 7', function () { 21 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 22 | const data = JSON.parse(fs.readFileSync(__dirname + '/nested_submission_example.json')); 23 | const validate = ajv.compile(schema); 24 | const valid = validate(data); 25 | 26 | assert.equal(null, validate.errors); 27 | assert.equal(true, valid); 28 | }); 29 | 30 | it('should validate the appendix VP example object using JSON Schema Draft 7', function () { 31 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 32 | // Allow additional properties for this 33 | schema.additionalProperties = true 34 | const data = JSON.parse(fs.readFileSync(__dirname + '/appendix_VP_example.json')); 35 | const validate = ajv.compile(schema); 36 | const valid = validate(data); 37 | 38 | assert.equal(null, validate.errors); 39 | assert.equal(true, valid); 40 | }); 41 | 42 | it('should validate the appendix OIDC example object using JSON Schema Draft 7', function () { 43 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 44 | // Allow additional properties for this 45 | schema.additionalProperties = true 46 | const data = JSON.parse(fs.readFileSync(__dirname + '/appendix_OIDC_example.json')); 47 | const validate = ajv.compile(schema); 48 | const valid = validate(data); 49 | 50 | assert.equal(null, validate.errors); 51 | assert.equal(true, valid); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/submission-requirements/all_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Submission of educational transcripts", 5 | "purpose": "We need your complete educational transcripts to process your application", 6 | "rule": "all", 7 | "from": "A" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/submission-requirements/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Banking Information", 5 | "purpose": "We need you to prove you currently hold a bank account older than 12months.", 6 | "rule": "pick", 7 | "count": 1, 8 | "from": "A" 9 | }, 10 | { 11 | "name": "Employment Information", 12 | "purpose": "We are only verifying one current employment relationship, not any other information about employment.", 13 | "rule": "all", 14 | "from": "B" 15 | }, 16 | { 17 | "name": "Citizenship Information", 18 | "rule": "pick", 19 | "count": 1, 20 | "from_nested": [ 21 | { 22 | "name": "United States Citizenship Proofs", 23 | "purpose": "We need you to prove your US citizenship.", 24 | "rule": "all", 25 | "from": "C" 26 | }, 27 | { 28 | "name": "European Union Citizenship Proofs", 29 | "purpose": "We need you to prove you are a citizen of an EU member state.", 30 | "rule": "all", 31 | "from": "D" 32 | } 33 | ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /test/submission-requirements/pick_1_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Citizenship Proof", 5 | "purpose": "We need to confirm you are a citizen of one of the following countries before accepting your application", 6 | "rule": "pick", 7 | "count": 1, 8 | "from": "B" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /test/submission-requirements/pick_2_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Eligibility to Work Proof", 5 | "purpose": "We need to prove you are eligible for full-time employment in 2 or more of the following countries", 6 | "rule": "pick", 7 | "min": 2, 8 | "from": "B" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/submission-requirements/pick_3_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Confirm banking relationship or employment and residence proofs", 5 | "purpose": "Recent bank statements or proofs of both employment and residence will be validated to initiate your loan application but not stored", 6 | "rule": "pick", 7 | "count": 1, 8 | "from_nested": [ 9 | { "rule": "all", "from": "A" }, 10 | { "rule": "pick", "count": 2, "from": "B" } 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/submission-requirements/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "definitions": { 4 | "submission_requirements": { 5 | "type": "object", 6 | "oneOf": [ 7 | { 8 | "properties": { 9 | "name": { "type": "string" }, 10 | "purpose": { "type": "string" }, 11 | "rule": { 12 | "type": "string", 13 | "enum": ["all", "pick"] 14 | }, 15 | "count": { "type": "integer", "minimum": 1 }, 16 | "min": { "type": "integer", "minimum": 0 }, 17 | "max": { "type": "integer", "minimum": 0 }, 18 | "from": { "type": "string" } 19 | }, 20 | "required": ["rule", "from"], 21 | "additionalProperties": false 22 | }, 23 | { 24 | "properties": { 25 | "name": { "type": "string" }, 26 | "purpose": { "type": "string" }, 27 | "rule": { 28 | "type": "string", 29 | "enum": ["all", "pick"] 30 | }, 31 | "count": { "type": "integer", "minimum": 1 }, 32 | "min": { "type": "integer", "minimum": 0 }, 33 | "max": { "type": "integer", "minimum": 0 }, 34 | "from_nested": { 35 | "type": "array", 36 | "minItems": 1, 37 | "items": { 38 | "$ref": "#/definitions/submission_requirements" 39 | } 40 | } 41 | }, 42 | "required": ["rule", "from_nested"], 43 | "additionalProperties": false 44 | } 45 | ] 46 | } 47 | }, 48 | "type": "object", 49 | "properties": { 50 | "submission_requirements": { 51 | "type": "array", 52 | "items": { 53 | "$ref": "#/definitions/submission_requirements" 54 | } 55 | } 56 | }, 57 | "required": ["submission_requirements"], 58 | "additionalProperties": false 59 | } 60 | -------------------------------------------------------------------------------- /test/submission-requirements/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('../__fixtures__/ajv'); 4 | 5 | /** 6 | * https://github.com/decentralized-identity/presentation-exchange/pull/18/files#diff-9308a2575ae14de31b25f4459ecfc604R57 7 | * 8 | * from dhh1128: 9 | * how do we express boolean options? I can see how we do the trivial ones 10 | * (pick any N out of M). But that's not what I'm asking about. When I 11 | * applied for citizenship for my daughter, I had to present any 2 proofs 12 | * from category A, and any 1 proof from category B, OR I had to present 3 13 | * or more proofs from category B. I don't see a way to model that 14 | * real-world use case. It think that's because we imagine the rules to 15 | * only exist *within* a single requirement, never across requirements. 16 | * And we don't imagine arbitrary combinations of booleans. 17 | * 18 | * reponse here: 19 | * we will support all of the above except for the "or more" specifier for 20 | * now. The "or more" specifier can be implemented as a new rule as 21 | * necessary. 22 | **/ 23 | 24 | const submissionRequirementsSchema = '/../../schemas/submission-requirements.json' 25 | 26 | describe('Submission Requirements', function () { 27 | describe('JSON Schema', function () { 28 | it('should validate the example object using JSON Schema Draft 7', function () { 29 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 30 | const data = JSON.parse(fs.readFileSync(__dirname + '/example.json')); 31 | const validate = ajv.compile(schema); 32 | const valid = validate(data); 33 | 34 | assert.equal(null, validate.errors); 35 | assert.equal(true, valid); 36 | }); 37 | 38 | it('should validate the all example object using JSON Schema Draft 7', function () { 39 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 40 | const data = JSON.parse(fs.readFileSync(__dirname + '/all_example.json')); 41 | const validate = ajv.compile(schema); 42 | const valid = validate(data); 43 | 44 | assert.equal(null, validate.errors); 45 | assert.equal(true, valid); 46 | }); 47 | 48 | it('should validate the pick 1 example object using JSON Schema Draft 7', function () { 49 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 50 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_1_example.json')); 51 | const validate = ajv.compile(schema); 52 | const valid = validate(data); 53 | 54 | assert.equal(null, validate.errors); 55 | assert.equal(true, valid); 56 | }); 57 | 58 | it('should validate the pick 2 example object using JSON Schema Draft 7', function () { 59 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 60 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_2_example.json')); 61 | const validate = ajv.compile(schema); 62 | const valid = validate(data); 63 | 64 | assert.equal(null, validate.errors); 65 | assert.equal(true, valid); 66 | }); 67 | 68 | it('should validate the pick 3 example object using JSON Schema Draft 7', function () { 69 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 70 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_3_example.json')); 71 | const validate = ajv.compile(schema); 72 | const valid = validate(data); 73 | 74 | assert.equal(null, validate.errors); 75 | assert.equal(true, valid); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/VC_expiration_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drivers_license_information", 3 | "name": "Verify Valid License", 4 | "purpose": "We need you to show that your driver's license will be valid through December of this year.", 5 | "schema": [ 6 | { 7 | "uri": "https://yourwatchful.gov/drivers-license-schema.json", 8 | "required": true 9 | } 10 | ], 11 | "constraints": { 12 | "fields": [ 13 | { 14 | "path": ["$.expirationDate"], 15 | "filter": { 16 | "type": "string", 17 | "format": "date-time", 18 | "minimum": "2020-12-31T23:59:59.000Z" 19 | } 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/VC_revocation_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drivers_license_information", 3 | "name": "Verify Valid License", 4 | "purpose": "We need to know that your license has not been revoked.", 5 | "schema": [ 6 | { 7 | "uri": "https://yourwatchful.gov/drivers-license-schema.json" 8 | } 9 | ], 10 | "constraints": { 11 | "fields": [ 12 | { 13 | "path": ["$.credentialStatus"] 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/basic_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "input_descriptors": [ 6 | { 7 | "id": "bankaccount_input", 8 | "name": "Full Bank Account Routing Information", 9 | "purpose": "We can only remit payment to a currently-valid bank account, submitted as an ABA RTN + Acct # or IBAN.", 10 | "schema": [{ 11 | "uri": "https://bank-standards.example.com/fullaccountroute.json" 12 | }], 13 | "constraints": { 14 | "limit_disclosure": "required", 15 | "fields": [ 16 | { 17 | "path": [ 18 | "$.issuer", 19 | "$.vc.issuer", 20 | "$.iss" 21 | ], 22 | "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor, or regulatory authority.", 23 | "filter": { 24 | "type": "string", 25 | "pattern": "did:example:123|did:example:456" 26 | } 27 | } 28 | ] 29 | } 30 | }, 31 | { 32 | "id": "us_passport_input", 33 | "name": "US Passport", 34 | "schema": [ 35 | { 36 | "uri": "hub://did:foo:123/Collections/schema.us.gov/passport.json" 37 | } 38 | ], 39 | "constraints": { 40 | "fields": [ 41 | { 42 | "path": ["$.credentialSubject.birth_date", "$.vc.credentialSubject.birth_date", "$.birth_date"], 43 | "filter": { 44 | "type": "string", 45 | "format": "date", 46 | "minimum": "1999-05-16" 47 | } 48 | } 49 | ] 50 | } 51 | 52 | } 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/format_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [], 5 | "format": { 6 | "jwt": { 7 | "alg": ["EdDSA", "ES256K", "ES384"] 8 | }, 9 | "jwt_vc": { 10 | "alg": ["ES256K", "ES384"] 11 | }, 12 | "jwt_vp": { 13 | "alg": ["EdDSA", "ES256K"] 14 | }, 15 | "ldp_vc": { 16 | "proof_type": [ 17 | "JsonWebSignature2020", 18 | "Ed25519Signature2018", 19 | "EcdsaSecp256k1Signature2019", 20 | "RsaSignature2018" 21 | ] 22 | }, 23 | "ldp_vp": { 24 | "proof_type": ["Ed25519Signature2018"] 25 | }, 26 | "ldp": { 27 | "proof_type": ["RsaSignature2018"] 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/input_descriptor_id_tokens_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [ 5 | { 6 | "id": "employment_input_xyz_gov", 7 | "group": ["B"], 8 | "schema": [ 9 | { 10 | "uri": "https://login.idp.com/xyz.gov/.well-known/openid-configuration", 11 | "required": true 12 | } 13 | ], 14 | "name": "Verify XYZ Government Employment", 15 | "purpose": "Verifying current employment at XYZ Government agency as proxy for permission to access this resource", 16 | "constraints": { 17 | "fields": [ 18 | { 19 | "path": ["$.status"], 20 | "filter": { 21 | "type": "string", 22 | "pattern": "active" 23 | } 24 | } 25 | ] 26 | } 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/input_descriptors_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [ 5 | { 6 | "id": "banking_input_1", 7 | "name": "Bank Account Information", 8 | "purpose": "We can only remit payment to a currently-valid bank account.", 9 | "group": [ 10 | "A" 11 | ], 12 | "schema": [ 13 | { 14 | "uri": "https://bank-schemas.org/1.0.0/accounts.json" 15 | }, 16 | { 17 | "uri": "https://bank-schemas.org/2.0.0/accounts.json" 18 | } 19 | ], 20 | "constraints": { 21 | "fields": [ 22 | { 23 | "path": [ 24 | "$.issuer", 25 | "$.vc.issuer", 26 | "$.iss" 27 | ], 28 | "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", 29 | "filter": { 30 | "type": "string", 31 | "pattern": "did:example:123|did:example:456" 32 | } 33 | }, 34 | { 35 | "path": [ 36 | "$.credentialSubject.account[*].id", 37 | "$.vc.credentialSubject.account[*].id", 38 | "$.account[*].id" 39 | ], 40 | "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", 41 | "filter": { 42 | "type": "string", 43 | "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}$" 44 | } 45 | }, 46 | { 47 | "path": [ 48 | "$.credentialSubject.account[*].route", 49 | "$.vc.credentialSubject.account[*].route", 50 | "$.account[*].route" 51 | ], 52 | "purpose": "We can only remit payment to a currently-valid account at a US, Japanese, or German federally-accredited bank, submitted as an ABA RTN or SWIFT code.", 53 | "filter": { 54 | "type": "string", 55 | "pattern": "^[0-9]{9}|^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?$" 56 | } 57 | } 58 | ] 59 | } 60 | } 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/minimal_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "input_descriptors": [ 6 | { 7 | "id": "wa_driver_license", 8 | "name": "Washington State Business License", 9 | "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", 10 | "schema": [{ 11 | "uri": "https://licenses.example.com/business-license.json" 12 | }] 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/single_group_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "VP, OIDC, DIDComm, or CHAPI outer wrapper here", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "submission_requirements": [{ 6 | "name": "Citizenship Information", 7 | "rule": "pick", 8 | "count": 1, 9 | "from": "A" 10 | }], 11 | "input_descriptors": [ 12 | { 13 | "id": "citizenship_input_1", 14 | "name": "EU Driver's License", 15 | "group": ["A"], 16 | "schema": [ 17 | { 18 | "uri": "https://eu.com/claims/DriversLicense.json" 19 | } 20 | ], 21 | "constraints": { 22 | "fields": [ 23 | { 24 | "path": ["$.issuer", "$.vc.issuer", "$.iss"], 25 | "purpose": "We can only accept digital driver's licenses issued by national authorities of member states or trusted notarial auditors.", 26 | "filter": { 27 | "type": "string", 28 | "pattern": "did:example:gov1|did:example:gov2" 29 | } 30 | }, 31 | { 32 | "path": ["$.credentialSubject.dob", "$.vc.credentialSubject.dob", "$.dob"], 33 | "filter": { 34 | "type": "string", 35 | "format": "date", 36 | "maximum": "1999-06-15" 37 | } 38 | } 39 | ] 40 | } 41 | }, 42 | { 43 | "id": "citizenship_input_2", 44 | "name": "US Passport", 45 | "group": ["A"], 46 | "schema": [ 47 | { 48 | "uri": "hub://did:foo:123/Collections/schema.us.gov/passport.json" 49 | } 50 | ], 51 | "constraints": { 52 | "fields": [ 53 | { 54 | "path": ["$.credentialSubject.birth_date", "$.vc.credentialSubject.birth_date", "$.birth_date"], 55 | "filter": { 56 | "type": "string", 57 | "format": "date", 58 | "maximum": "1999-05-16" 59 | } 60 | } 61 | ] 62 | } 63 | } 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-definition/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('ajv'); 4 | 5 | 6 | describe('Presentation Definition', function () { 7 | describe('JSON Schema', function () { 8 | it('should validate the basic example object using JSON Schema Draft 7', function () { 9 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 10 | const data = JSON.parse(fs.readFileSync(__dirname + '/basic_example.json')); 11 | const jv = new ajv({allErrors: true}); 12 | const validate = jv.compile(schema); 13 | const valid = validate(data); 14 | 15 | assert.equal(null, validate.errors); 16 | assert.equal(true, valid); 17 | }); 18 | 19 | it('should validate the single group example object using JSON Schema Draft 7', function () { 20 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 21 | const data = JSON.parse(fs.readFileSync(__dirname + '/single_group_example.json')); 22 | const jv = new ajv({allErrors: true}); 23 | const validate = jv.compile(schema); 24 | const valid = validate(data); 25 | 26 | assert.equal(null, validate.errors); 27 | assert.equal(true, valid); 28 | }); 29 | 30 | it('should validate the multi group example object using JSON Schema Draft 7', function () { 31 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 32 | const data = JSON.parse(fs.readFileSync(__dirname + '/multi_group_example.json')); 33 | const jv = new ajv({allErrors: true}); 34 | const validate = jv.compile(schema); 35 | const valid = validate(data); 36 | 37 | assert.equal(null, validate.errors); 38 | assert.equal(true, valid); 39 | }); 40 | 41 | it('should validate the format example object using JSON Schema Draft 7', function () { 42 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 43 | const data = JSON.parse(fs.readFileSync(__dirname + '/format_example.json')); 44 | const jv = new ajv({allErrors: true}); 45 | const validate = jv.compile(schema); 46 | const valid = validate(data); 47 | 48 | assert.equal(null, validate.errors); 49 | assert.equal(true, valid); 50 | }); 51 | 52 | it('should validate the input description sample example object using JSON Schema Draft 7', function () { 53 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 54 | const data = JSON.parse(fs.readFileSync(__dirname + '/input_descriptors_example.json')); 55 | const jv = new ajv({allErrors: true}); 56 | const validate = jv.compile(schema); 57 | const valid = validate(data); 58 | 59 | assert.equal(null, validate.errors); 60 | assert.equal(true, valid); 61 | }); 62 | 63 | it('should validate the input description id tokens example object using JSON Schema Draft 7', function () { 64 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 65 | const data = JSON.parse(fs.readFileSync(__dirname + '/input_descriptor_id_tokens_example.json')); 66 | const jv = new ajv({allErrors: true}); 67 | const validate = jv.compile(schema); 68 | const valid = validate(data); 69 | 70 | assert.equal(null, validate.errors); 71 | assert.equal(true, valid); 72 | }); 73 | 74 | it('should validate the VC expiration example object using JSON Schema Draft 7', function () { 75 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 76 | const data = JSON.parse(fs.readFileSync(__dirname + '/VC_expiration_example.json')); 77 | const jv = new ajv({allErrors: true}); 78 | const validate = jv.compile(schema); 79 | const valid = validate(data); 80 | 81 | assert.equal(null, validate.errors); 82 | assert.equal(true, valid); 83 | }); 84 | 85 | it('should validate the VC revocation example object using JSON Schema Draft 7', function () { 86 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 87 | const data = JSON.parse(fs.readFileSync(__dirname + '/VC_revocation_example.json')); 88 | const jv = new ajv({allErrors: true}); 89 | const validate = jv.compile(schema); 90 | const valid = validate(data); 91 | 92 | assert.equal(null, validate.errors); 93 | assert.equal(true, valid); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/appendix_CHAPI_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "web", 3 | "dataType": "VerifiablePresentation", 4 | "data": { 5 | "comment": "Presentation Submission goes here" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/appendix_DIDComm_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "https://didcomm.org/present-proof/%VER/presentation", 3 | "@id": "f1ca8245-ab2d-4d9c-8d7d-94bf310314ef", 4 | "comment": "some comment", 5 | "formats" : [{ 6 | "attach_id" : "2a3f1c4c-623c-44e6-b159-179048c51260", 7 | "format" : "dif/presentation-exchange/submission@v1.0" 8 | }], 9 | "presentations~attach": [{ 10 | "@id": "2a3f1c4c-623c-44e6-b159-179048c51260", 11 | "mime-type": "application/ld+json", 12 | "data": { 13 | "json": { 14 | "comment": "Presentation Submission goes here" 15 | } 16 | } 17 | }] 18 | } -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/appendix_OIDC_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "iss": "https://self-issued.me", 3 | "sub": "248289761001", 4 | "preferred_username": "superman445", 5 | "presentation_submission": { 6 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 7 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 8 | "descriptor_map": [ 9 | { 10 | "id": "banking_input_2", 11 | "format": "jwt", 12 | "path": "$._claim_sources.banking_input_2.JWT" 13 | }, 14 | { 15 | "id": "employment_input", 16 | "format": "jwt_vc", 17 | "path": "$._claim_sources.employment_input.VC_JWT" 18 | }, 19 | { 20 | "id": "citizenship_input_1", 21 | "format": "ldp_vc", 22 | "path": "$._claim_sources.citizenship_input_1.VC" 23 | } 24 | ] 25 | }, 26 | "_claim_names": { 27 | "verified_claims": [ 28 | "banking_input_2", 29 | "employment_input", 30 | "citizenship_input_1" 31 | ] 32 | }, 33 | "_claim_sources": { 34 | "banking_input_2": { 35 | "JWT": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3NlcnZlci5vdGhlcm9wLmNvbSIsInN1YiI6ImU4MTQ4NjAzLTg5MzQtNDI0NS04MjViLWMxMDhiOGI2Yjk0NSIsInZlcmlmaWVkX2NsYWltcyI6eyJ2ZXJpZmljYXRpb24iOnsidHJ1c3RfZnJhbWV3b3JrIjoiaWFsX2V4YW1wbGVfZ29sZCJ9LCJjbGFpbXMiOnsiZ2l2ZW5fbmFtZSI6Ik1heCIsImZhbWlseV9uYW1lIjoiTWVpZXIiLCJiaXJ0aGRhdGUiOiIxOTU2LTAxLTI4In19fQ.FArlPUtUVn95HCExePlWJQ6ctVfVpQyeSbe3xkH9MH1QJjnk5GVbBW0qe1b7R3lE-8iVv__0mhRTUI5lcFhLjoGjDS8zgWSarVsEEjwBK7WD3r9cEw6ZAhfEkhHL9eqAaED2rhhDbHD5dZWXkJCuXIcn65g6rryiBanxlXK0ZmcK4fD9HV9MFduk0LRG_p4yocMaFvVkqawat5NV9QQ3ij7UBr3G7A4FojcKEkoJKScdGoozir8m5XD83Sn45_79nCcgWSnCX2QTukL8NywIItu_K48cjHiAGXXSzydDm_ccGCe0sY-Ai2-iFFuQo2PtfuK2SqPPmAZJxEFrFoLY4g" 36 | }, 37 | "employment_input": { 38 | "VC": { 39 | "@context": "https://www.w3.org/2018/credentials/v1", 40 | "id": "https://business-standards.org/schemas/employment-history.json", 41 | "type": ["VerifiableCredential", "GenericEmploymentCredential"], 42 | "issuer": "did:foo:123", 43 | "issuanceDate": "2010-01-01T19:73:24Z", 44 | "credentialSubject": { 45 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 46 | "active": true 47 | }, 48 | "proof": { 49 | "type": "EcdsaSecp256k1VerificationKey2019", 50 | "created": "2017-06-18T21:19:10Z", 51 | "proofPurpose": "assertionMethod", 52 | "verificationMethod": "https://example.edu/issuers/keys/1", 53 | "jws": "..." 54 | } 55 | } 56 | }, 57 | "citizenship_input_1": { 58 | "VC": { 59 | "@context": "https://www.w3.org/2018/credentials/v1", 60 | "id": "https://eu.com/claims/DriversLicense", 61 | "type": ["EUDriversLicense"], 62 | "issuer": "did:foo:123", 63 | "issuanceDate": "2010-01-01T19:73:24Z", 64 | "credentialSubject": { 65 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 66 | "license": { 67 | "number": "34DGE352", 68 | "dob": "07/13/80" 69 | } 70 | }, 71 | "proof": { 72 | "type": "EcdsaSecp256k1VerificationKey2019", 73 | "created": "2017-06-18T21:19:10Z", 74 | "proofPurpose": "assertionMethod", 75 | "verificationMethod": "https://example.edu/issuers/keys/1", 76 | "jws": "..." 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/appendix_VP_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2018/credentials/v1", 4 | "https://identity.foundation/presentation-exchange/submission/v1" 5 | ], 6 | "type": [ 7 | "VerifiablePresentation", 8 | "PresentationSubmission" 9 | ], 10 | "presentation_submission": { 11 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 12 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 13 | "descriptor_map": [ 14 | { 15 | "id": "banking_input_2", 16 | "format": "jwt_vc", 17 | "path": "$.verifiableCredential[0]" 18 | }, 19 | { 20 | "id": "employment_input", 21 | "format": "ldp_vc", 22 | "path": "$.verifiableCredential[1]" 23 | }, 24 | { 25 | "id": "citizenship_input_1", 26 | "format": "ldp_vc", 27 | "path": "$.verifiableCredential[2]" 28 | } 29 | ] 30 | }, 31 | "verifiableCredential": [ 32 | { 33 | "comment": "IN REALWORLD VPs, THIS WILL BE A BIG UGLY OBJECT INSTEAD OF THE DECODED JWT PAYLOAD THAT FOLLOWS", 34 | "vc": { 35 | "@context": "https://www.w3.org/2018/credentials/v1", 36 | "id": "https://eu.com/claims/DriversLicense", 37 | "type": ["EUDriversLicense"], 38 | "issuer": "did:example:123", 39 | "issuanceDate": "2010-01-01T19:73:24Z", 40 | "credentialSubject": { 41 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 42 | "accounts": [ 43 | { 44 | "id": "1234567890", 45 | "route": "DE-9876543210" 46 | }, 47 | { 48 | "id": "2457913570", 49 | "route": "DE-0753197542" 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | { 56 | "@context": "https://www.w3.org/2018/credentials/v1", 57 | "id": "https://business-standards.org/schemas/employment-history.json", 58 | "type": ["VerifiableCredential", "GenericEmploymentCredential"], 59 | "issuer": "did:foo:123", 60 | "issuanceDate": "2010-01-01T19:73:24Z", 61 | "credentialSubject": { 62 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 63 | "active": true 64 | }, 65 | "proof": { 66 | "type": "EcdsaSecp256k1VerificationKey2019", 67 | "created": "2017-06-18T21:19:10Z", 68 | "proofPurpose": "assertionMethod", 69 | "verificationMethod": "https://example.edu/issuers/keys/1", 70 | "jws": "..." 71 | } 72 | }, 73 | { 74 | "@context": "https://www.w3.org/2018/credentials/v1", 75 | "id": "https://eu.com/claims/DriversLicense", 76 | "type": ["EUDriversLicense"], 77 | "issuer": "did:foo:123", 78 | "issuanceDate": "2010-01-01T19:73:24Z", 79 | "credentialSubject": { 80 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 81 | "license": { 82 | "number": "34DGE352", 83 | "dob": "07/13/80" 84 | } 85 | }, 86 | "proof": { 87 | "type": "RsaSignature2018", 88 | "created": "2017-06-18T21:19:10Z", 89 | "proofPurpose": "assertionMethod", 90 | "verificationMethod": "https://example.edu/issuers/keys/1", 91 | "jws": "..." 92 | } 93 | } 94 | ], 95 | "proof": { 96 | "type": "RsaSignature2018", 97 | "created": "2018-09-14T21:19:10Z", 98 | "proofPurpose": "authentication", 99 | "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", 100 | "challenge": "1f44d55f-f161-4938-a659-f8026467f126", 101 | "domain": "4jt78h47fh47", 102 | "jws": "..." 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_submission": { 3 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 4 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "descriptor_map": [ 6 | { 7 | "id": "banking_input_2", 8 | "path": "$.verifiableCredential[0]", 9 | "format": "jwt" 10 | }, 11 | { 12 | "id": "employment_input", 13 | "path": "$.verifiableCredential[1]", 14 | "format": "ldp_vc" 15 | }, 16 | { 17 | "id": "citizenship_input_1", 18 | "path": "$.verifiableCredential[2]", 19 | "format": "jwt_vp" 20 | }, 21 | { 22 | "id": "banking_input_2", 23 | "format": "jwt_vp", 24 | "path": "$.outerCredential[0]", 25 | "path_nested": { 26 | "id": "banking_input_2", 27 | "format": "ldp_vc", 28 | "path": "$.innerCredential[1]", 29 | "path_nested": { 30 | "id": "banking_input_2", 31 | "format": "jwt_vc", 32 | "path": "$.mostInnerCredential[2]" 33 | } 34 | } 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/nested_submission_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_submission": { 3 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 4 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "descriptor_map": [ 6 | { 7 | "id": "banking_input_2", 8 | "format": "jwt_vp", 9 | "path": "$.outerClaim[0]", 10 | "path_nested": { 11 | "id": "banking_input_2", 12 | "format": "ldp_vc", 13 | "path": "$.innerClaim[1]", 14 | "path_nested": { 15 | "id": "banking_input_2", 16 | "format": "jwt_vc", 17 | "path": "$.mostInnerClaim[2]" 18 | } 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Presentation Submission", 4 | "type": "object", 5 | "properties": { 6 | "presentation_submission": { 7 | "type": "object", 8 | "properties": { 9 | "id": { "type": "string" }, 10 | "definition_id": { "type": "string" }, 11 | "descriptor_map": { 12 | "type": "array", 13 | "items": { "$ref": "#/definitions/descriptor" } 14 | } 15 | }, 16 | "required": ["id", "definition_id", "descriptor_map"], 17 | "additionalProperties": false 18 | } 19 | }, 20 | "definitions": { 21 | "descriptor": { 22 | "type": "object", 23 | "properties": { 24 | "id": { "type": "string" }, 25 | "path": { "type": "string" }, 26 | "path_nested": { 27 | "type": "object", 28 | "$ref": "#/definitions/descriptor" 29 | }, 30 | "format": { 31 | "type": "string", 32 | "enum": ["jwt", "jwt_vc", "jwt_vp", "ldp", "ldp_vc", "ldp_vp"] 33 | } 34 | }, 35 | "required": ["id", "path", "format"], 36 | "additionalProperties": false 37 | } 38 | }, 39 | "required": ["presentation_submission"], 40 | "additionalProperties": false 41 | } 42 | -------------------------------------------------------------------------------- /test/v1.0.0/presentation-submission/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('ajv'); 4 | 5 | 6 | describe('Presentation Submission', function () { 7 | describe('JSON Schema', function () { 8 | it('should validate the example object using JSON Schema Draft 7', function () { 9 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 10 | const data = JSON.parse(fs.readFileSync(__dirname + '/example.json')); 11 | const jv = new ajv({allErrors: true}); 12 | const validate = jv.compile(schema); 13 | const valid = validate(data); 14 | 15 | assert.equal(null, validate.errors); 16 | assert.equal(true, valid); 17 | }); 18 | 19 | it('should validate the nested submission example object using JSON Schema Draft 7', function () { 20 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 21 | const data = JSON.parse(fs.readFileSync(__dirname + '/nested_submission_example.json')); 22 | const jv = new ajv({allErrors: true}); 23 | const validate = jv.compile(schema); 24 | const valid = validate(data); 25 | 26 | assert.equal(null, validate.errors); 27 | assert.equal(true, valid); 28 | }); 29 | 30 | it('should validate the appendix VP example object using JSON Schema Draft 7', function () { 31 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 32 | // Allow additional properties for this 33 | schema.additionalProperties = true 34 | const data = JSON.parse(fs.readFileSync(__dirname + '/appendix_VP_example.json')); 35 | const jv = new ajv({allErrors: true}); 36 | const validate = jv.compile(schema); 37 | const valid = validate(data); 38 | 39 | assert.equal(null, validate.errors); 40 | assert.equal(true, valid); 41 | }); 42 | 43 | it('should validate the appendix OIDC example object using JSON Schema Draft 7', function () { 44 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 45 | // Allow additional properties for this 46 | schema.additionalProperties = true 47 | const data = JSON.parse(fs.readFileSync(__dirname + '/appendix_OIDC_example.json')); 48 | const jv = new ajv({allErrors: true}); 49 | const validate = jv.compile(schema); 50 | const valid = validate(data); 51 | 52 | assert.equal(null, validate.errors); 53 | assert.equal(true, valid); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/all_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Submission of educational transcripts", 5 | "purpose": "We need your complete educational transcripts to process your application", 6 | "rule": "all", 7 | "from": "A" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Banking Information", 5 | "purpose": "We need you to prove you currently hold a bank account older than 12months.", 6 | "rule": "pick", 7 | "count": 1, 8 | "from": "A" 9 | }, 10 | { 11 | "name": "Employment Information", 12 | "purpose": "We are only verifying one current employment relationship, not any other information about employment.", 13 | "rule": "all", 14 | "from": "B" 15 | }, 16 | { 17 | "name": "Citizenship Information", 18 | "rule": "pick", 19 | "count": 1, 20 | "from_nested": [ 21 | { 22 | "name": "United States Citizenship Proofs", 23 | "purpose": "We need you to prove your US citizenship.", 24 | "rule": "all", 25 | "from": "C" 26 | }, 27 | { 28 | "name": "European Union Citizenship Proofs", 29 | "purpose": "We need you to prove you are a citizen of an EU member state.", 30 | "rule": "all", 31 | "from": "D" 32 | } 33 | ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/pick_1_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Citizenship Proof", 5 | "purpose": "We need to confirm you are a citizen of one of the following countries before accepting your application", 6 | "rule": "pick", 7 | "count": 1, 8 | "from": "B" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/pick_2_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Eligibility to Work Proof", 5 | "purpose": "We need to prove you are eligible for full-time employment in 2 or more of the following countries", 6 | "rule": "pick", 7 | "min": 2, 8 | "from": "B" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/pick_3_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Confirm banking relationship or employment and residence proofs", 5 | "purpose": "Recent bank statements or proofs of both employment and residence will be validated to initiate your loan application but not stored", 6 | "rule": "pick", 7 | "count": 1, 8 | "from_nested": [ 9 | { "rule": "all", "from": "A" }, 10 | { "rule": "pick", "count": 2, "from": "B" } 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "definitions": { 4 | "submission_requirements": { 5 | "type": "object", 6 | "oneOf": [ 7 | { 8 | "properties": { 9 | "name": { "type": "string" }, 10 | "purpose": { "type": "string" }, 11 | "rule": { 12 | "type": "string", 13 | "enum": ["all", "pick"] 14 | }, 15 | "count": { "type": "integer", "minimum": 1 }, 16 | "min": { "type": "integer", "minimum": 0 }, 17 | "max": { "type": "integer", "minimum": 0 }, 18 | "from": { "type": "string" } 19 | }, 20 | "required": ["rule", "from"], 21 | "additionalProperties": false 22 | }, 23 | { 24 | "properties": { 25 | "name": { "type": "string" }, 26 | "purpose": { "type": "string" }, 27 | "rule": { 28 | "type": "string", 29 | "enum": ["all", "pick"] 30 | }, 31 | "count": { "type": "integer", "minimum": 1 }, 32 | "min": { "type": "integer", "minimum": 0 }, 33 | "max": { "type": "integer", "minimum": 0 }, 34 | "from_nested": { 35 | "type": "array", 36 | "minItems": 1, 37 | "items": { 38 | "$ref": "#/definitions/submission_requirements" 39 | } 40 | } 41 | }, 42 | "required": ["rule", "from_nested"], 43 | "additionalProperties": false 44 | } 45 | ] 46 | } 47 | }, 48 | "type": "object", 49 | "properties": { 50 | "submission_requirements": { 51 | "type": "array", 52 | "items": { 53 | "$ref": "#/definitions/submission_requirements" 54 | } 55 | } 56 | }, 57 | "required": ["submission_requirements"], 58 | "additionalProperties": false 59 | } 60 | -------------------------------------------------------------------------------- /test/v1.0.0/submission-requirements/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('ajv'); 4 | 5 | /** 6 | * https://github.com/decentralized-identity/presentation-exchange/pull/18/files#diff-9308a2575ae14de31b25f4459ecfc604R57 7 | * 8 | * from dhh1128: 9 | * how do we express boolean options? I can see how we do the trivial ones 10 | * (pick any N out of M). But that's not what I'm asking about. When I 11 | * applied for citizenship for my daughter, I had to present any 2 proofs 12 | * from category A, and any 1 proof from category B, OR I had to present 3 13 | * or more proofs from category B. I don't see a way to model that 14 | * real-world use case. It think that's because we imagine the rules to 15 | * only exist *within* a single requirement, never across requirements. 16 | * And we don't imagine arbitrary combinations of booleans. 17 | * 18 | * reponse here: 19 | * we will support all of the above except for the "or more" specifier for 20 | * now. The "or more" specifier can be implemented as a new rule as 21 | * necessary. 22 | **/ 23 | 24 | describe('Submission Requirement', function () { 25 | describe('JSON Schema', function () { 26 | it('should validate the example object using JSON Schema Draft 7', function () { 27 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 28 | const data = JSON.parse(fs.readFileSync(__dirname + '/example.json')); 29 | const jv = new ajv({allErrors: true}); 30 | const validate = jv.compile(schema); 31 | const valid = validate(data); 32 | 33 | assert.equal(null, validate.errors); 34 | assert.equal(true, valid); 35 | }); 36 | 37 | it('should validate the all example object using JSON Schema Draft 7', function () { 38 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 39 | const data = JSON.parse(fs.readFileSync(__dirname + '/all_example.json')); 40 | const jv = new ajv({allErrors: true}); 41 | const validate = jv.compile(schema); 42 | const valid = validate(data); 43 | 44 | assert.equal(null, validate.errors); 45 | assert.equal(true, valid); 46 | }); 47 | 48 | it('should validate the pick 1 example object using JSON Schema Draft 7', function () { 49 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 50 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_1_example.json')); 51 | const jv = new ajv({allErrors: true}); 52 | const validate = jv.compile(schema); 53 | const valid = validate(data); 54 | 55 | assert.equal(null, validate.errors); 56 | assert.equal(true, valid); 57 | }); 58 | 59 | it('should validate the pick 2 example object using JSON Schema Draft 7', function () { 60 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 61 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_2_example.json')); 62 | const jv = new ajv({allErrors: true}); 63 | const validate = jv.compile(schema); 64 | const valid = validate(data); 65 | 66 | assert.equal(null, validate.errors); 67 | assert.equal(true, valid); 68 | }); 69 | 70 | it('should validate the pick 3 example object using JSON Schema Draft 7', function () { 71 | const schema = JSON.parse(fs.readFileSync(__dirname + '/schema.json')); 72 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_3_example.json')); 73 | const jv = new ajv({allErrors: true}); 74 | const validate = jv.compile(schema); 75 | const valid = validate(data); 76 | 77 | assert.equal(null, validate.errors); 78 | assert.equal(true, valid); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/VC_expiration_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drivers_license_information", 3 | "name": "Verify Valid License", 4 | "purpose": "We need you to show that your driver's license will be valid through December of this year.", 5 | "constraints": { 6 | "fields": [ 7 | { 8 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 9 | "filter": { 10 | "type": "string", 11 | "const": "https://yourwatchful.gov/drivers-license-schema.json" 12 | } 13 | }, 14 | { 15 | "path": ["$.expirationDate"], 16 | "filter": { 17 | "type": "string", 18 | "format": "date-time" 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/VC_revocation_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drivers_license_information", 3 | "name": "Verify Valid License", 4 | "purpose": "We need to know that your license has not been revoked.", 5 | "constraints": { 6 | "fields": [ 7 | { 8 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 9 | "filter": { 10 | "type": "string", 11 | "const": "https://yourwatchful.gov/drivers-license-schema.json" 12 | } 13 | }, 14 | { 15 | "path": ["$.credentialStatus"] 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/basic_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "input_descriptors": [ 6 | { 7 | "id": "bankaccount_input", 8 | "name": "Full Bank Account Routing Information", 9 | "purpose": "We can only remit payment to a currently-valid bank account, submitted as an ABA RTN + Acct # or IBAN.", 10 | "constraints": { 11 | "limit_disclosure": "required", 12 | "fields": [ 13 | { 14 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 15 | "filter": { 16 | "type": "string", 17 | "const": "https://bank-standards.example.com/fullaccountroute.json" 18 | } 19 | }, 20 | { 21 | "path": [ 22 | "$.issuer", 23 | "$.vc.issuer", 24 | "$.iss" 25 | ], 26 | "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor, or regulatory authority.", 27 | "filter": { 28 | "type": "string", 29 | "pattern": "did:example:123|did:example:456" 30 | }, 31 | "intent_to_retain": true 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "id": "us_passport_input", 38 | "name": "US Passport", 39 | "constraints": { 40 | "fields": [ 41 | { 42 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 43 | "filter": { 44 | "type": "string", 45 | "const": "hub://did:foo:123/Collections/schema.us.gov/passport.json" 46 | } 47 | }, 48 | { 49 | "path": ["$.credentialSubject.birth_date", "$.vc.credentialSubject.birth_date", "$.birth_date"], 50 | "filter": { 51 | "type": "string", 52 | "format": "date" 53 | } 54 | } 55 | ] 56 | } 57 | } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/format_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [], 5 | "format": { 6 | "jwt": { 7 | "alg": ["EdDSA", "ES256K", "ES384"] 8 | }, 9 | "jwt_vc": { 10 | "alg": ["ES256K", "ES384"] 11 | }, 12 | "jwt_vp": { 13 | "alg": ["EdDSA", "ES256K"] 14 | }, 15 | "ldp_vc": { 16 | "proof_type": [ 17 | "JsonWebSignature2020", 18 | "Ed25519Signature2018", 19 | "EcdsaSecp256k1Signature2019", 20 | "RsaSignature2018" 21 | ] 22 | }, 23 | "ldp_vp": { 24 | "proof_type": ["Ed25519Signature2018"] 25 | }, 26 | "ldp": { 27 | "proof_type": ["RsaSignature2018"] 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/input_descriptor_id_tokens_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [ 5 | { 6 | "id": "employment_input_xyz_gov", 7 | "group": ["B"], 8 | "name": "Verify XYZ Government Employment", 9 | "purpose": "Verifying current employment at XYZ Government agency as proxy for permission to access this resource", 10 | "constraints": { 11 | "fields": [ 12 | { 13 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 14 | "filter": { 15 | "type": "string", 16 | "const": "https://login.idp.com/xyz.gov/.well-known/openid-configuration" 17 | } 18 | }, 19 | { 20 | "path": ["$.status"], 21 | "filter": { 22 | "type": "string", 23 | "pattern": "active" 24 | } 25 | } 26 | ] 27 | } 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/input_descriptors_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 4 | "input_descriptors": [ 5 | { 6 | "id": "banking_input_1", 7 | "name": "Bank Account Information", 8 | "purpose": "We can only remit payment to a currently-valid bank account.", 9 | "group": [ 10 | "A" 11 | ], 12 | "constraints": { 13 | "fields": [ 14 | { 15 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 16 | "filter": { 17 | "type": "string", 18 | "pattern": "https://bank-schemas.org/1.0.0/accounts.json|https://bank-schemas.org/2.0.0/accounts.json" 19 | } 20 | }, 21 | { 22 | "path": [ 23 | "$.issuer", 24 | "$.vc.issuer", 25 | "$.iss" 26 | ], 27 | "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", 28 | "filter": { 29 | "type": "string", 30 | "pattern": "did:example:123|did:example:456" 31 | }, 32 | "intent_to_retain": true 33 | }, 34 | { 35 | "path": [ 36 | "$.credentialSubject.account[*].id", 37 | "$.vc.credentialSubject.account[*].id", 38 | "$.account[*].id" 39 | ], 40 | "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", 41 | "filter": { 42 | "type": "string", 43 | "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}$" 44 | }, 45 | "intent_to_retain": true 46 | }, 47 | { 48 | "path": [ 49 | "$.credentialSubject.account[*].route", 50 | "$.vc.credentialSubject.account[*].route", 51 | "$.account[*].route" 52 | ], 53 | "purpose": "We can only remit payment to a currently-valid account at a US, Japanese, or German federally-accredited bank, submitted as an ABA RTN or SWIFT code.", 54 | "filter": { 55 | "type": "string", 56 | "pattern": "^[0-9]{9}|^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?$" 57 | }, 58 | "intent_to_retain": true 59 | } 60 | ] 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/minimal_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Note: VP, OIDC, DIDComm, or CHAPI outer wrapper would be here.", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "input_descriptors": [ 6 | { 7 | "id": "wa_driver_license", 8 | "name": "Washington State Business License", 9 | "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", 10 | "constraints": { 11 | "fields": [ 12 | { 13 | "path": [ 14 | "$.credentialSubject.dateOfBirth", 15 | "$.credentialSubject.dob", 16 | "$.vc.credentialSubject.dateOfBirth", 17 | "$.vc.credentialSubject.dob" 18 | ] 19 | } 20 | ] 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/pd_filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "first simple example", 4 | "input_descriptors": [ 5 | { 6 | "id": "A specific type of VC", 7 | "name": "A specific type of VC", 8 | "purpose": "We want a VC of this type", 9 | "constraints": { 10 | "fields": [ 11 | { 12 | "path": [ 13 | "$.type" 14 | ], 15 | "filter": { 16 | "type": "string", 17 | "pattern": "" 18 | } 19 | } 20 | ] 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/pd_filter2.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_definition": { 3 | "id": "Scalable trust example", 4 | "input_descriptors": [ 5 | { 6 | "id": "any type of credit card from any bank", 7 | "name": "any type of credit card from any bank", 8 | "purpose": "Please provide your credit card details", 9 | "constraints": { 10 | "fields": [ 11 | { 12 | "path": [ 13 | "$.termsOfUse.type" 14 | ], 15 | "filter": { 16 | "type": "string", 17 | "pattern": "https://train.trust-scheme.de/info" 18 | } 19 | }, 20 | { 21 | "path": [ 22 | "$.termsOfUse.trustScheme" 23 | ], 24 | "filter": { 25 | "type": "string", 26 | "pattern": "worldbankfederation.com" 27 | } 28 | }, 29 | { 30 | "path": [ 31 | "$.type" 32 | ], 33 | "filter": { 34 | "type": "string", 35 | "pattern": "creditCard" 36 | } 37 | } 38 | ] 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/single_group_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "VP, OIDC, DIDComm, or CHAPI outer wrapper here", 3 | "presentation_definition": { 4 | "id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "submission_requirements": [{ 6 | "name": "Citizenship Information", 7 | "rule": "pick", 8 | "count": 1, 9 | "from": "A" 10 | }], 11 | "input_descriptors": [ 12 | { 13 | "id": "citizenship_input_1", 14 | "name": "EU Driver's License", 15 | "group": ["A"], 16 | "constraints": { 17 | "fields": [ 18 | { 19 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 20 | "filter": { 21 | "type": "string", 22 | "const": "https://eu.com/claims/DriversLicense.json" 23 | } 24 | }, 25 | { 26 | "path": ["$.issuer", "$.vc.issuer", "$.iss"], 27 | "purpose": "We can only accept digital driver's licenses issued by national authorities of member states or trusted notarial auditors.", 28 | "filter": { 29 | "type": "string", 30 | "pattern": "did:example:gov1|did:example:gov2" 31 | } 32 | }, 33 | { 34 | "path": ["$.credentialSubject.dob", "$.vc.credentialSubject.dob", "$.dob"], 35 | "filter": { 36 | "type": "string", 37 | "format": "date" 38 | } 39 | } 40 | ] 41 | } 42 | }, 43 | { 44 | "id": "citizenship_input_2", 45 | "name": "US Passport", 46 | "group": ["A"], 47 | "constraints": { 48 | "fields": [ 49 | { 50 | "path": ["$.credentialSchema.id", "$.vc.credentialSchema.id"], 51 | "filter": { 52 | "type": "string", 53 | "const": "hub://did:foo:123/Collections/schema.us.gov/passport.json" 54 | } 55 | }, 56 | { 57 | "path": ["$.credentialSubject.birth_date", "$.vc.credentialSubject.birth_date", "$.birth_date"], 58 | "filter": { 59 | "type": "string", 60 | "format": "date" 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-definition/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('../__fixtures__/ajv'); 4 | 5 | const presentationDefinitionEnvelopeSchema = "/../../schemas/presentation-definition-envelope.json" 6 | 7 | describe('Presentation Definition', function () { 8 | describe('JSON Schema', function () { 9 | it('should validate the basic example object using JSON Schema Draft 7', function () { 10 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 11 | const data = JSON.parse(fs.readFileSync(__dirname + '/basic_example.json')); 12 | const validate = ajv.compile(schema); 13 | const valid = validate(data); 14 | 15 | assert.equal(null, validate.errors); 16 | assert.equal(true, valid); 17 | }); 18 | 19 | it('should validate the single group example object using JSON Schema Draft 7', function () { 20 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 21 | const data = JSON.parse(fs.readFileSync(__dirname + '/single_group_example.json')); 22 | const validate = ajv.compile(schema); 23 | const valid = validate(data); 24 | 25 | assert.equal(null, validate.errors); 26 | assert.equal(true, valid); 27 | }); 28 | 29 | it('should validate the multi group example object using JSON Schema Draft 7', function () { 30 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 31 | const data = JSON.parse(fs.readFileSync(__dirname + '/multi_group_example.json')); 32 | const validate = ajv.compile(schema); 33 | const valid = validate(data); 34 | 35 | assert.equal(null, validate.errors); 36 | assert.equal(true, valid); 37 | }); 38 | 39 | it('should validate the format example object using JSON Schema Draft 7', function () { 40 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 41 | const data = JSON.parse(fs.readFileSync(__dirname + '/format_example.json')); 42 | const validate = ajv.compile(schema); 43 | const valid = validate(data); 44 | 45 | assert.equal(null, validate.errors); 46 | assert.equal(true, valid); 47 | }); 48 | 49 | it('should validate the input description sample example object using JSON Schema Draft 7', function () { 50 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 51 | const data = JSON.parse(fs.readFileSync(__dirname + '/input_descriptors_example.json')); 52 | const validate = ajv.compile(schema); 53 | const valid = validate(data); 54 | 55 | assert.equal(null, validate.errors); 56 | assert.equal(true, valid); 57 | }); 58 | 59 | it('should validate the input description id tokens example object using JSON Schema Draft 7', function () { 60 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 61 | const data = JSON.parse(fs.readFileSync(__dirname + '/input_descriptor_id_tokens_example.json')); 62 | const validate = ajv.compile(schema); 63 | const valid = validate(data); 64 | 65 | assert.equal(null, validate.errors); 66 | assert.equal(true, valid); 67 | }); 68 | 69 | it('should validate the VC expiration example object using JSON Schema Draft 7', function () { 70 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 71 | const data = JSON.parse(fs.readFileSync(__dirname + '/VC_expiration_example.json')); 72 | const validate = ajv.compile(schema); 73 | const valid = validate(data); 74 | 75 | assert.equal(null, validate.errors); 76 | assert.equal(true, valid); 77 | }); 78 | 79 | it('should validate the VC revocation example object using JSON Schema Draft 7', function () { 80 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 81 | const data = JSON.parse(fs.readFileSync(__dirname + '/VC_revocation_example.json')); 82 | const validate = ajv.compile(schema); 83 | const valid = validate(data); 84 | 85 | assert.equal(null, validate.errors); 86 | assert.equal(true, valid); 87 | }); 88 | 89 | it('should validate the Filter By Credential Type example using JSON Schema Draft 7', function () { 90 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 91 | const data = JSON.parse(fs.readFileSync(__dirname + '/pd_filter.json')); 92 | const validate = ajv.compile(schema); 93 | const valid = validate(data); 94 | 95 | assert.equal(null, validate.errors); 96 | assert.equal(true, valid); 97 | }); 98 | 99 | it('should validate the Two Filters example object using JSON Schema Draft 7', function () { 100 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationDefinitionEnvelopeSchema)); 101 | const data = JSON.parse(fs.readFileSync(__dirname + '/pd_filter2.json')); 102 | const validate = ajv.compile(schema); 103 | const valid = validate(data); 104 | 105 | assert.equal(null, validate.errors); 106 | assert.equal(true, valid); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/appendix_CHAPI_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "web", 3 | "dataType": "VerifiablePresentation", 4 | "data": { 5 | "comment": "Presentation Submission goes here" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/appendix_DIDComm_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "@type": "https://didcomm.org/present-proof/%VER/presentation", 3 | "@id": "f1ca8245-ab2d-4d9c-8d7d-94bf310314ef", 4 | "comment": "some comment", 5 | "formats" : [{ 6 | "attach_id" : "2a3f1c4c-623c-44e6-b159-179048c51260", 7 | "format" : "dif/presentation-exchange/submission@v1.0" 8 | }], 9 | "presentations~attach": [{ 10 | "@id": "2a3f1c4c-623c-44e6-b159-179048c51260", 11 | "mime-type": "application/ld+json", 12 | "data": { 13 | "json": { 14 | "comment": "Presentation Submission goes here" 15 | } 16 | } 17 | }] 18 | } -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/appendix_JWT_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "iat": 1673585101, 3 | "iss": "did:key:z6MkgbfWabZ1xqdVSBCXsaMTbL8aupZ9t7eVptKkXgrp4mQ7", 4 | "vp": { 5 | "@context": [ 6 | "https://www.w3.org/2018/credentials/v1" 7 | ], 8 | "holder": "did:key:z6MkgbfWabZ1xqdVSBCXsaMTbL8aupZ9t7eVptKkXgrp4mQ7", 9 | "presentation_submission": { 10 | "definition_id": "64422289-12de-49b9-bbf0-005bc610c2ad", 11 | "descriptor_map": [ 12 | { 13 | "format": "jwt_vp", 14 | "id": "wa_driver_license", 15 | "path": "$.verifiableCredential[0]" 16 | } 17 | ], 18 | "id": "0f28b372-72d5-493e-bc86-b0f71ba0b87b" 19 | }, 20 | "type": [ 21 | "VerifiablePresentation" 22 | ], 23 | "verifiableCredential": [ 24 | "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa21YMXY4TjE2WEdnSlVFQjJxYmFXWTZ1S1Nuc2NEckdkc01xeGZVZzNrRnB0IiwidHlwIjoiSldUIn0.eyJleHAiOjI1ODAxMzAwODAsImlzcyI6ImRpZDprZXk6ejZNa21YMXY4TjE2WEdnSlVFQjJxYmFXWTZ1S1Nuc2NEckdkc01xeGZVZzNrRnB0IiwianRpIjoiMzRiMWI0NWUtYmI2Yy00ZTU4LWI2NzUtMjkzODNjODI4Mjk0IiwibmJmIjoxNjczNTg1MTAxLCJzdWIiOiJkaWQ6a2V5Ono2TWtnYmZXYWJaMXhxZFZTQkNYc2FNVGJMOGF1cFo5dDdlVnB0S2tYZ3JwNG1RNyIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImlkIjoiMzRiMWI0NWUtYmI2Yy00ZTU4LWI2NzUtMjkzODNjODI4Mjk0IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWttWDF2OE4xNlhHZ0pVRUIycWJhV1k2dUtTbnNjRHJHZHNNcXhmVWcza0ZwdCIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDEtMTNUMDQ6NDU6MDFaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDUxLTEwLTA1VDE0OjQ4OjAwLjAwMFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsTmFtZSI6Ik1jbG92aW4iLCJkYXRlT2ZCaXJ0aCI6IjE5ODctMDEtMDIiLCJmYW1pbHlOYW1lIjoiQW5kcmVzIiwiZ2l2ZW5OYW1lIjoiVXJpYmUiLCJpZCI6ImRpZDprZXk6ejZNa2diZldhYloxeHFkVlNCQ1hzYU1UYkw4YXVwWjl0N2VWcHRLa1hncnA0bVE3In19fQ.gsSv1glZ8puaOmIid9_a-iGS9pznGYI9BFUqIJOpK-Ep9H7KFB5FfyKaCLQymklxJwqv3Ftr8axnj-UPnOzjCg" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/appendix_OIDC_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "iss": "https://self-issued.me", 3 | "sub": "248289761001", 4 | "preferred_username": "superman445", 5 | "presentation_submission": { 6 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 7 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 8 | "descriptor_map": [ 9 | { 10 | "id": "banking_input_2", 11 | "format": "jwt", 12 | "path": "$._claim_sources.banking_input_2.JWT" 13 | }, 14 | { 15 | "id": "employment_input", 16 | "format": "jwt_vc", 17 | "path": "$._claim_sources.employment_input.VC_JWT" 18 | }, 19 | { 20 | "id": "citizenship_input_1", 21 | "format": "ldp_vc", 22 | "path": "$._claim_sources.citizenship_input_1.VC" 23 | } 24 | ] 25 | }, 26 | "_claim_names": { 27 | "verified_claims": [ 28 | "banking_input_2", 29 | "employment_input", 30 | "citizenship_input_1" 31 | ] 32 | }, 33 | "_claim_sources": { 34 | "banking_input_2": { 35 | "JWT": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3NlcnZlci5vdGhlcm9wLmNvbSIsInN1YiI6ImU4MTQ4NjAzLTg5MzQtNDI0NS04MjViLWMxMDhiOGI2Yjk0NSIsInZlcmlmaWVkX2NsYWltcyI6eyJ2ZXJpZmljYXRpb24iOnsidHJ1c3RfZnJhbWV3b3JrIjoiaWFsX2V4YW1wbGVfZ29sZCJ9LCJjbGFpbXMiOnsiZ2l2ZW5fbmFtZSI6Ik1heCIsImZhbWlseV9uYW1lIjoiTWVpZXIiLCJiaXJ0aGRhdGUiOiIxOTU2LTAxLTI4In19fQ.FArlPUtUVn95HCExePlWJQ6ctVfVpQyeSbe3xkH9MH1QJjnk5GVbBW0qe1b7R3lE-8iVv__0mhRTUI5lcFhLjoGjDS8zgWSarVsEEjwBK7WD3r9cEw6ZAhfEkhHL9eqAaED2rhhDbHD5dZWXkJCuXIcn65g6rryiBanxlXK0ZmcK4fD9HV9MFduk0LRG_p4yocMaFvVkqawat5NV9QQ3ij7UBr3G7A4FojcKEkoJKScdGoozir8m5XD83Sn45_79nCcgWSnCX2QTukL8NywIItu_K48cjHiAGXXSzydDm_ccGCe0sY-Ai2-iFFuQo2PtfuK2SqPPmAZJxEFrFoLY4g" 36 | }, 37 | "employment_input": { 38 | "VC": { 39 | "@context": "https://www.w3.org/2018/credentials/v1", 40 | "id": "https://business-standards.org/schemas/employment-history.json", 41 | "type": ["VerifiableCredential", "GenericEmploymentCredential"], 42 | "issuer": "did:foo:123", 43 | "issuanceDate": "2010-01-01T19:73:24Z", 44 | "credentialSubject": { 45 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 46 | "active": true 47 | }, 48 | "proof": { 49 | "type": "EcdsaSecp256k1VerificationKey2019", 50 | "created": "2017-06-18T21:19:10Z", 51 | "proofPurpose": "assertionMethod", 52 | "verificationMethod": "https://example.edu/issuers/keys/1", 53 | "jws": "..." 54 | } 55 | } 56 | }, 57 | "citizenship_input_1": { 58 | "VC": { 59 | "@context": "https://www.w3.org/2018/credentials/v1", 60 | "id": "https://eu.com/claims/DriversLicense", 61 | "type": ["EUDriversLicense"], 62 | "issuer": "did:foo:123", 63 | "issuanceDate": "2010-01-01T19:73:24Z", 64 | "credentialSubject": { 65 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 66 | "license": { 67 | "number": "34DGE352", 68 | "dob": "07/13/80" 69 | } 70 | }, 71 | "proof": { 72 | "type": "EcdsaSecp256k1VerificationKey2019", 73 | "created": "2017-06-18T21:19:10Z", 74 | "proofPurpose": "assertionMethod", 75 | "verificationMethod": "https://example.edu/issuers/keys/1", 76 | "jws": "..." 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/appendix_VP_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2018/credentials/v1", 4 | "https://identity.foundation/presentation-exchange/submission/v1" 5 | ], 6 | "type": [ 7 | "VerifiablePresentation", 8 | "PresentationSubmission" 9 | ], 10 | "presentation_submission": { 11 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 12 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 13 | "descriptor_map": [ 14 | { 15 | "id": "banking_input_2", 16 | "format": "jwt_vc", 17 | "path": "$.verifiableCredential[0]" 18 | }, 19 | { 20 | "id": "employment_input", 21 | "format": "ldp_vc", 22 | "path": "$.verifiableCredential[1]" 23 | }, 24 | { 25 | "id": "citizenship_input_1", 26 | "format": "ldp_vc", 27 | "path": "$.verifiableCredential[2]" 28 | } 29 | ] 30 | }, 31 | "verifiableCredential": [ 32 | { 33 | "comment": "IN REALWORLD VPs, THIS WILL BE A BIG UGLY OBJECT INSTEAD OF THE DECODED JWT PAYLOAD THAT FOLLOWS", 34 | "vc": { 35 | "@context": "https://www.w3.org/2018/credentials/v1", 36 | "id": "https://eu.com/claims/DriversLicense", 37 | "type": ["EUDriversLicense"], 38 | "issuer": "did:example:123", 39 | "issuanceDate": "2010-01-01T19:73:24Z", 40 | "credentialSubject": { 41 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 42 | "accounts": [ 43 | { 44 | "id": "1234567890", 45 | "route": "DE-9876543210" 46 | }, 47 | { 48 | "id": "2457913570", 49 | "route": "DE-0753197542" 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | { 56 | "@context": "https://www.w3.org/2018/credentials/v1", 57 | "id": "https://business-standards.org/schemas/employment-history.json", 58 | "type": ["VerifiableCredential", "GenericEmploymentCredential"], 59 | "issuer": "did:foo:123", 60 | "issuanceDate": "2010-01-01T19:73:24Z", 61 | "credentialSubject": { 62 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 63 | "active": true 64 | }, 65 | "proof": { 66 | "type": "EcdsaSecp256k1VerificationKey2019", 67 | "created": "2017-06-18T21:19:10Z", 68 | "proofPurpose": "assertionMethod", 69 | "verificationMethod": "https://example.edu/issuers/keys/1", 70 | "jws": "..." 71 | } 72 | }, 73 | { 74 | "@context": "https://www.w3.org/2018/credentials/v1", 75 | "id": "https://eu.com/claims/DriversLicense", 76 | "type": ["EUDriversLicense"], 77 | "issuer": "did:foo:123", 78 | "issuanceDate": "2010-01-01T19:73:24Z", 79 | "credentialSubject": { 80 | "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 81 | "license": { 82 | "number": "34DGE352", 83 | "dob": "07/13/80" 84 | } 85 | }, 86 | "proof": { 87 | "type": "RsaSignature2018", 88 | "created": "2017-06-18T21:19:10Z", 89 | "proofPurpose": "assertionMethod", 90 | "verificationMethod": "https://example.edu/issuers/keys/1", 91 | "jws": "..." 92 | } 93 | } 94 | ], 95 | "proof": { 96 | "type": "RsaSignature2018", 97 | "created": "2018-09-14T21:19:10Z", 98 | "proofPurpose": "authentication", 99 | "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", 100 | "challenge": "1f44d55f-f161-4938-a659-f8026467f126", 101 | "domain": "4jt78h47fh47", 102 | "jws": "..." 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_submission": { 3 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 4 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "descriptor_map": [ 6 | { 7 | "id": "banking_input_2", 8 | "path": "$.verifiableCredential[0]", 9 | "format": "jwt" 10 | }, 11 | { 12 | "id": "employment_input", 13 | "path": "$.verifiableCredential[1]", 14 | "format": "ldp_vc" 15 | }, 16 | { 17 | "id": "citizenship_input_1", 18 | "path": "$.verifiableCredential[2]", 19 | "format": "jwt_vp" 20 | }, 21 | { 22 | "id": "banking_input_2", 23 | "format": "jwt_vp", 24 | "path": "$.outerCredential[0]", 25 | "path_nested": { 26 | "id": "banking_input_2", 27 | "format": "ldp_vc", 28 | "path": "$.innerCredential[1]", 29 | "path_nested": { 30 | "id": "banking_input_2", 31 | "format": "jwt_vc", 32 | "path": "$.mostInnerCredential[2]" 33 | } 34 | } 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/nested_submission_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presentation_submission": { 3 | "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", 4 | "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", 5 | "descriptor_map": [ 6 | { 7 | "id": "banking_input_2", 8 | "format": "jwt_vp", 9 | "path": "$.outerClaim[0]", 10 | "path_nested": { 11 | "id": "banking_input_2", 12 | "format": "ldp_vc", 13 | "path": "$.innerClaim[1]", 14 | "path_nested": { 15 | "id": "banking_input_2", 16 | "format": "jwt_vc", 17 | "path": "$.mostInnerClaim[2]" 18 | } 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/v2.0.0/presentation-submission/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('../__fixtures__/ajv'); 4 | 5 | 6 | const presentationSubmissionSchema = '/../../schemas/presentation-submission.json' 7 | 8 | describe('Presentation Submission', function () { 9 | describe('JSON Schema', function () { 10 | it('should validate the example object using JSON Schema Draft 7', function () { 11 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 12 | const data = JSON.parse(fs.readFileSync(__dirname + '/example.json')); 13 | const validate = ajv.compile(schema); 14 | const valid = validate(data); 15 | 16 | assert.equal(null, validate.errors); 17 | assert.equal(true, valid); 18 | }); 19 | 20 | it('should validate the nested submission example object using JSON Schema Draft 7', function () { 21 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 22 | const data = JSON.parse(fs.readFileSync(__dirname + '/nested_submission_example.json')); 23 | const validate = ajv.compile(schema); 24 | const valid = validate(data); 25 | 26 | assert.equal(null, validate.errors); 27 | assert.equal(true, valid); 28 | }); 29 | 30 | it('should validate the appendix VP example object using JSON Schema Draft 7', function () { 31 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 32 | // Allow additional properties for this 33 | schema.additionalProperties = true 34 | const data = JSON.parse(fs.readFileSync(__dirname + '/appendix_VP_example.json')); 35 | const validate = ajv.compile(schema); 36 | const valid = validate(data); 37 | 38 | assert.equal(null, validate.errors); 39 | assert.equal(true, valid); 40 | }); 41 | 42 | it('should validate the appendix OIDC example object using JSON Schema Draft 7', function () { 43 | const schema = JSON.parse(fs.readFileSync(__dirname + presentationSubmissionSchema)); 44 | // Allow additional properties for this 45 | schema.additionalProperties = true 46 | const data = JSON.parse(fs.readFileSync(__dirname + '/appendix_OIDC_example.json')); 47 | const validate = ajv.compile(schema); 48 | const valid = validate(data); 49 | 50 | assert.equal(null, validate.errors); 51 | assert.equal(true, valid); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/all_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Submission of educational transcripts", 5 | "purpose": "We need your complete educational transcripts to process your application", 6 | "rule": "all", 7 | "from": "A" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Banking Information", 5 | "purpose": "We need you to prove you currently hold a bank account older than 12months.", 6 | "rule": "pick", 7 | "count": 1, 8 | "from": "A" 9 | }, 10 | { 11 | "name": "Employment Information", 12 | "purpose": "We are only verifying one current employment relationship, not any other information about employment.", 13 | "rule": "all", 14 | "from": "B" 15 | }, 16 | { 17 | "name": "Citizenship Information", 18 | "rule": "pick", 19 | "count": 1, 20 | "from_nested": [ 21 | { 22 | "name": "United States Citizenship Proofs", 23 | "purpose": "We need you to prove your US citizenship.", 24 | "rule": "all", 25 | "from": "C" 26 | }, 27 | { 28 | "name": "European Union Citizenship Proofs", 29 | "purpose": "We need you to prove you are a citizen of an EU member state.", 30 | "rule": "all", 31 | "from": "D" 32 | } 33 | ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/pick_1_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Citizenship Proof", 5 | "purpose": "We need to confirm you are a citizen of one of the following countries before accepting your application", 6 | "rule": "pick", 7 | "count": 1, 8 | "from": "B" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/pick_2_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Eligibility to Work Proof", 5 | "purpose": "We need to prove you are eligible for full-time employment in 2 or more of the following countries", 6 | "rule": "pick", 7 | "min": 2, 8 | "from": "B" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/pick_3_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "submission_requirements": [ 3 | { 4 | "name": "Confirm banking relationship or employment and residence proofs", 5 | "purpose": "Recent bank statements or proofs of both employment and residence will be validated to initiate your loan application but not stored", 6 | "rule": "pick", 7 | "count": 1, 8 | "from_nested": [ 9 | { "rule": "all", "from": "A" }, 10 | { "rule": "pick", "count": 2, "from": "B" } 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "definitions": { 4 | "submission_requirements": { 5 | "type": "object", 6 | "oneOf": [ 7 | { 8 | "properties": { 9 | "name": { "type": "string" }, 10 | "purpose": { "type": "string" }, 11 | "rule": { 12 | "type": "string", 13 | "enum": ["all", "pick"] 14 | }, 15 | "count": { "type": "integer", "minimum": 1 }, 16 | "min": { "type": "integer", "minimum": 0 }, 17 | "max": { "type": "integer", "minimum": 0 }, 18 | "from": { "type": "string" } 19 | }, 20 | "required": ["rule", "from"], 21 | "additionalProperties": false 22 | }, 23 | { 24 | "properties": { 25 | "name": { "type": "string" }, 26 | "purpose": { "type": "string" }, 27 | "rule": { 28 | "type": "string", 29 | "enum": ["all", "pick"] 30 | }, 31 | "count": { "type": "integer", "minimum": 1 }, 32 | "min": { "type": "integer", "minimum": 0 }, 33 | "max": { "type": "integer", "minimum": 0 }, 34 | "from_nested": { 35 | "type": "array", 36 | "minItems": 1, 37 | "items": { 38 | "$ref": "#/definitions/submission_requirements" 39 | } 40 | } 41 | }, 42 | "required": ["rule", "from_nested"], 43 | "additionalProperties": false 44 | } 45 | ] 46 | } 47 | }, 48 | "type": "object", 49 | "properties": { 50 | "submission_requirements": { 51 | "type": "array", 52 | "items": { 53 | "$ref": "#/definitions/submission_requirements" 54 | } 55 | } 56 | }, 57 | "required": ["submission_requirements"], 58 | "additionalProperties": false 59 | } 60 | -------------------------------------------------------------------------------- /test/v2.0.0/submission-requirements/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const ajv = require('../__fixtures__/ajv'); 4 | 5 | /** 6 | * https://github.com/decentralized-identity/presentation-exchange/pull/18/files#diff-9308a2575ae14de31b25f4459ecfc604R57 7 | * 8 | * from dhh1128: 9 | * how do we express boolean options? I can see how we do the trivial ones 10 | * (pick any N out of M). But that's not what I'm asking about. When I 11 | * applied for citizenship for my daughter, I had to present any 2 proofs 12 | * from category A, and any 1 proof from category B, OR I had to present 3 13 | * or more proofs from category B. I don't see a way to model that 14 | * real-world use case. It think that's because we imagine the rules to 15 | * only exist *within* a single requirement, never across requirements. 16 | * And we don't imagine arbitrary combinations of booleans. 17 | * 18 | * reponse here: 19 | * we will support all of the above except for the "or more" specifier for 20 | * now. The "or more" specifier can be implemented as a new rule as 21 | * necessary. 22 | **/ 23 | 24 | const submissionRequirementsSchema = '/../../schemas/submission-requirements.json' 25 | 26 | describe('Submission Requirements', function () { 27 | describe('JSON Schema', function () { 28 | it('should validate the example object using JSON Schema Draft 7', function () { 29 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 30 | const data = JSON.parse(fs.readFileSync(__dirname + '/example.json')); 31 | const validate = ajv.compile(schema); 32 | const valid = validate(data); 33 | 34 | assert.equal(null, validate.errors); 35 | assert.equal(true, valid); 36 | }); 37 | 38 | it('should validate the all example object using JSON Schema Draft 7', function () { 39 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 40 | const data = JSON.parse(fs.readFileSync(__dirname + '/all_example.json')); 41 | const validate = ajv.compile(schema); 42 | const valid = validate(data); 43 | 44 | assert.equal(null, validate.errors); 45 | assert.equal(true, valid); 46 | }); 47 | 48 | it('should validate the pick 1 example object using JSON Schema Draft 7', function () { 49 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 50 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_1_example.json')); 51 | const validate = ajv.compile(schema); 52 | const valid = validate(data); 53 | 54 | assert.equal(null, validate.errors); 55 | assert.equal(true, valid); 56 | }); 57 | 58 | it('should validate the pick 2 example object using JSON Schema Draft 7', function () { 59 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 60 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_2_example.json')); 61 | const validate = ajv.compile(schema); 62 | const valid = validate(data); 63 | 64 | assert.equal(null, validate.errors); 65 | assert.equal(true, valid); 66 | }); 67 | 68 | it('should validate the pick 3 example object using JSON Schema Draft 7', function () { 69 | const schema = JSON.parse(fs.readFileSync(__dirname + submissionRequirementsSchema)); 70 | const data = JSON.parse(fs.readFileSync(__dirname + '/pick_3_example.json')); 71 | const validate = ajv.compile(schema); 72 | const valid = validate(data); 73 | 74 | assert.equal(null, validate.errors); 75 | assert.equal(true, valid); 76 | }); 77 | }); 78 | }); 79 | --------------------------------------------------------------------------------