[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]\"\nlabels: bug\nassignees: ''\n\n---\n\n### Prerequisites\n\nPlease answer the following questions for yourself before submitting an issue.\n\n- [ ] I am running the latest version\n- [ ] I checked the documentation and found no answer\n- [ ] I checked to make sure that this issue has not already been filed\n\n**YOU MAY DELETE THE PREREQUISITES SECTION** if you're sure you checked all the boxes.\n\n### Current Behavior\n\nWhat is the current behavior?\n\n### Expected Behavior\n\nPlease describe the behavior you are expecting\n\n### Failure Information\n\nPlease help provide information about the failure.\n\n#### Steps to Reproduce\n\nPlease provide detailed steps for reproducing the issue.\n\n1. step 1\n2. step 2\n3. you get it...\n\n#### Environment Details\n\nPlease provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.\n\n* node/browser version:\n* OS Version:\n* Device details:\n\n#### Failure Logs/Screenshots\n\nPlease include any relevant log snippets or files here.\nCreate a [GIST](https://gist.github.com) which is a paste of your _full or sanitized_ logs, and link them here.\nPlease do _NOT_ paste your full logs here, as it will make this issue very long and hard to read!\n\n#### Alternatives you considered\n\nPlease provide details about an environment where this bug does not occur.\n\n---\n\n> **Don't paste private keys anywhere public!**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: '[proposal]'\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 70\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 14\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n  - in-progress\n  - planned-feature\n  - good-first-issue\n  - triage\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Build, Test and Publish\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - 'master'\n      - 'alpha'\n      - 'main'\n      - 'next'\njobs:\n  build-test-publish:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          token: ${{ secrets.GH_TOKEN }}\n\n      - name: \"Setup node with cache\"\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'yarn'\n\n      - run: yarn install --frozen-lockfile\n      - run: yarn run format\n      - run: yarn run build\n\n      - name: \"Setup git coordinates\"\n        run: |\n          git config user.name ${{secrets.GH_USER}}\n          git config user.email ${{secrets.GH_EMAIL}}\n\n      - name: \"Run semantic-release\"\n        env:\n          GH_TOKEN: ${{secrets.GH_TOKEN}}\n          NPM_TOKEN: ${{secrets.NPM_TOKEN}}\n        if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/alpha'\n        run: yarn run release\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build and Test NODE\non: [pull_request, workflow_dispatch, push]\njobs:\n  build-test:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: \"Setup node with cache\"\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'yarn'\n\n      - run: yarn install --frozen-lockfile\n      - run: yarn run build\n      - run: yarn run lint\n      - run: yarn run test:ci\n\n      - name: \"Upload coverage reports\"\n        uses: codecov/codecov-action@v6\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          fail_ci_if_error: true\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nlib\nesm\ndist\n.vscode/settings.json\ncoverage\n\n*.log\n\n*.tgz\n*/**/.DS_Store\n/.DS_Store\n.rts2_cache_cjs\n.rts2_cache_es\n.rts2_cache_umd\n\n.idea/\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"jsxBracketSameLine\": false,\n  \"tabWidth\": 2,\n  \"printWidth\": 120,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\",\n  \"semi\": false\n}\n"
  },
  {
    "path": ".releaserc",
    "content": "{\n  \"tagFormat\": \"${version}\",\n  \"plugins\": [\n    \"@semantic-release/commit-analyzer\",\n    \"@semantic-release/release-notes-generator\",\n    [\n      \"@semantic-release/changelog\",\n      {\n        \"changelogFile\": \"CHANGELOG.md\"\n      }\n    ],\n    \"@semantic-release/npm\",\n    [\n      \"@semantic-release/git\",\n      {\n        \"assets\": [\n          \"CHANGELOG.md\",\n          \"docs\",\n          \"package.json\",\n          \"yarn.lock\"\n        ],\n        \"message\": \"chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}\"\n      }\n    ],\n    \"@semantic-release/github\"\n  ],\n  \"branches\": [\n    \"master\",\n    \"next\",\n    {\n      \"name\": \"alpha\",\n      \"prerelease\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [4.0.16](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.15...4.0.16) (2025-08-26)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.18 ([9594524](https://github.com/decentralized-identity/did-jwt-vc/commit/9594524293bc5081b81a0dfc114ff5cf8260ebc8))\n\n## [4.0.15](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.14...4.0.15) (2025-06-07)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.17 ([bd308a2](https://github.com/decentralized-identity/did-jwt-vc/commit/bd308a2b5998d1f0ac4b660041f3759533405a90))\n\n## [4.0.14](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.13...4.0.14) (2025-05-30)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.16 ([5a4be32](https://github.com/decentralized-identity/did-jwt-vc/commit/5a4be32d89ac825320bca02eec358d84bcf82238))\n\n## [4.0.13](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.12...4.0.13) (2025-05-17)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.15 ([732561c](https://github.com/decentralized-identity/did-jwt-vc/commit/732561c60aa16e2764ffee15c4c014ca83f56a4b))\n\n## [4.0.12](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.11...4.0.12) (2025-04-25)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.14 ([315da9a](https://github.com/decentralized-identity/did-jwt-vc/commit/315da9a2c8c7df7dcf0c90e41bd4f9025289a603))\n\n## [4.0.11](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.10...4.0.11) (2025-04-23)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.13 ([bce1e35](https://github.com/decentralized-identity/did-jwt-vc/commit/bce1e35395aa6a8b499dc494b8309ca49693443e))\n\n## [4.0.10](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.9...4.0.10) (2025-04-22)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.12 ([2045b6c](https://github.com/decentralized-identity/did-jwt-vc/commit/2045b6c8aa121ac1a48a1d7cfc3414009dd4a3cf))\n\n## [4.0.9](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.8...4.0.9) (2025-04-15)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.11 ([2f43370](https://github.com/decentralized-identity/did-jwt-vc/commit/2f43370a8bc4d0ce94cf6ea7d1981f4008f97aeb))\n\n## [4.0.8](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.7...4.0.8) (2025-04-15)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.10 ([63c2c20](https://github.com/decentralized-identity/did-jwt-vc/commit/63c2c207f41ca3ffcbf661fc567f81c94290f2bc))\n\n## [4.0.7](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.6...4.0.7) (2025-03-19)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.9 ([b58b7a8](https://github.com/decentralized-identity/did-jwt-vc/commit/b58b7a8d1b93750154f80eb34a692900378c991e))\n\n## [4.0.6](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.5...4.0.6) (2025-01-28)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.8 ([#152](https://github.com/decentralized-identity/did-jwt-vc/issues/152)) ([80b7ff7](https://github.com/decentralized-identity/did-jwt-vc/commit/80b7ff7a7b54cb773b2e6a7f96d8bdb0d1cebed0))\n\n## [4.0.5](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.4...4.0.5) (2025-01-19)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.7 ([#151](https://github.com/decentralized-identity/did-jwt-vc/issues/151)) ([bb4f6e1](https://github.com/decentralized-identity/did-jwt-vc/commit/bb4f6e1948572c9821adb00130e189354b079654))\n\n## [4.0.4](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.3...4.0.4) (2024-03-26)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.4 ([c41d3a4](https://github.com/decentralized-identity/did-jwt-vc/commit/c41d3a4d4131c70366931c80cb10ab4ccf8323bf))\n\n## [4.0.3](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.2...4.0.3) (2024-03-20)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.3 ([3b83ac5](https://github.com/decentralized-identity/did-jwt-vc/commit/3b83ac56fe42284eac3a85a82ad1ca837e386ccf))\n\n## [4.0.2](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.1...4.0.2) (2024-03-14)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.2 ([a5ed5a4](https://github.com/decentralized-identity/did-jwt-vc/commit/a5ed5a466936a5dd4c2f82c74401e2ea4efd8e7b))\n\n## [4.0.1](https://github.com/decentralized-identity/did-jwt-vc/compare/4.0.0...4.0.1) (2024-02-28)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8.0.1 ([a091e1a](https://github.com/decentralized-identity/did-jwt-vc/commit/a091e1a0a315c00e1f80d0225e82862a88bd06d5))\n\n# [4.0.0](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.15...4.0.0) (2024-01-18)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v8 ([#146](https://github.com/decentralized-identity/did-jwt-vc/issues/146)) ([7f3caaa](https://github.com/decentralized-identity/did-jwt-vc/commit/7f3caaab51d0278beb910f5e2fee38aa244956a4))\n\n\n### BREAKING CHANGES\n\n* **deps:** the updated `did-jwt` library includes some breaking changes so we are bumping the major version here too for safety\n\n## [3.2.15](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.14...3.2.15) (2023-12-12)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.4.6 ([b1880eb](https://github.com/decentralized-identity/did-jwt-vc/commit/b1880ebc760bed8892fb1f141569424d66881208))\n\n## [3.2.14](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.13...3.2.14) (2023-11-01)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.4.5 ([#142](https://github.com/decentralized-identity/did-jwt-vc/issues/142)) ([e15adb9](https://github.com/decentralized-identity/did-jwt-vc/commit/e15adb9e6304abac6b76395fd8bdb7170395bdae)), closes [#135](https://github.com/decentralized-identity/did-jwt-vc/issues/135)\n\n## [3.2.13](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.12...3.2.13) (2023-10-25)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.4.4 ([053d735](https://github.com/decentralized-identity/did-jwt-vc/commit/053d735b11d7812927029cfb7f6a8782b4056e08))\n\n## [3.2.12](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.11...3.2.12) (2023-10-23)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.4.3 ([448b295](https://github.com/decentralized-identity/did-jwt-vc/commit/448b295c8b97bf291a0b1dd698b360ab3c2301f5))\n\n## [3.2.11](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.10...3.2.11) (2023-10-03)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.4.2 ([b696345](https://github.com/decentralized-identity/did-jwt-vc/commit/b696345b57be5edf0fd1d03f06cda978ed452483))\n\n## [3.2.10](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.9...3.2.10) (2023-09-27)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.4.1 ([#140](https://github.com/decentralized-identity/did-jwt-vc/issues/140)) ([65bc254](https://github.com/decentralized-identity/did-jwt-vc/commit/65bc254f68dddbcd40c16a5e9b2fe05e80f7ddd4))\n\n## [3.2.9](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.8...3.2.9) (2023-09-24)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.3.0 ([b46ea30](https://github.com/decentralized-identity/did-jwt-vc/commit/b46ea3014ea3f51ffa314a1df088db3003429814))\n\n## [3.2.8](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.7...3.2.8) (2023-09-18)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.8 ([49819ee](https://github.com/decentralized-identity/did-jwt-vc/commit/49819ee80e3db724b00508e986bd12db62a1a08b))\n\n## [3.2.7](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.6...3.2.7) (2023-09-04)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.7 ([920aa69](https://github.com/decentralized-identity/did-jwt-vc/commit/920aa69750364c3835ea812d6bc0d4755537cd1b))\n\n## [3.2.6](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.5...3.2.6) (2023-08-24)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.6 ([61964e9](https://github.com/decentralized-identity/did-jwt-vc/commit/61964e9a717f607115faa3404977e9769f5e46b8))\n\n## [3.2.5](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.4...3.2.5) (2023-08-04)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.5 ([cbf34d2](https://github.com/decentralized-identity/did-jwt-vc/commit/cbf34d279f8c3a1e23b17a33563e6b9298251478))\n\n## [3.2.4](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.3...3.2.4) (2023-06-27)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.4 ([0591be0](https://github.com/decentralized-identity/did-jwt-vc/commit/0591be01e8b00e31dd20d6937398cae69a98c9ff))\n\n## [3.2.3](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.2...3.2.3) (2023-06-08)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.2 ([3e48606](https://github.com/decentralized-identity/did-jwt-vc/commit/3e48606f83c6a854fff801b032c77c547967256a))\n\n## [3.2.2](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.1...3.2.2) (2023-06-04)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.1 ([f0275a2](https://github.com/decentralized-identity/did-jwt-vc/commit/f0275a22a5a52edbbe9ac2557df47dba06158c56))\n\n## [3.2.1](https://github.com/decentralized-identity/did-jwt-vc/compare/3.2.0...3.2.1) (2023-05-18)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7.2.0 ([ddf3914](https://github.com/decentralized-identity/did-jwt-vc/commit/ddf39140896f2aec00d5aeebedfe51f86bde951c))\n\n# [3.2.0](https://github.com/decentralized-identity/did-jwt-vc/compare/3.1.4...3.2.0) (2023-05-09)\n\n\n### Features\n\n* support credentials using ConditionalProof2022 ([#133](https://github.com/decentralized-identity/did-jwt-vc/issues/133)) ([6cc9bed](https://github.com/decentralized-identity/did-jwt-vc/commit/6cc9bed2bda35d60ef29e1e4cd9318f89e2d2f70))\n\n## [3.1.4](https://github.com/decentralized-identity/did-jwt-vc/compare/3.1.3...3.1.4) (2023-05-04)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v7 ([1e81dea](https://github.com/decentralized-identity/did-jwt-vc/commit/1e81deae1abbe31327f3fca0392cf5d2c6bbb21f))\n\n## [3.1.3](https://github.com/decentralized-identity/did-jwt-vc/compare/3.1.2...3.1.3) (2023-04-03)\n\n\n### Bug Fixes\n\n* **deps:** Update dependency did-jwt to v6.11.6 ([de29a5c](https://github.com/decentralized-identity/did-jwt-vc/commit/de29a5c3bcef74daf407f6b08874923259d60adb))\n\n## [3.1.2](https://github.com/decentralized-identity/did-jwt-vc/compare/3.1.1...3.1.2) (2023-03-16)\n\n\n### Bug Fixes\n\n* **deps:** Update did-dependencies ([8255a50](https://github.com/decentralized-identity/did-jwt-vc/commit/8255a50038f395db8d718fbc34773eeccb89a4b5))\n\n## [3.1.1](https://github.com/decentralized-identity/did-jwt-vc/compare/3.1.0...3.1.1) (2023-02-10)\n\n\n### Bug Fixes\n\n* **deps:** bump did-jwt to v6.11 ([#126](https://github.com/decentralized-identity/did-jwt-vc/issues/126)) ([eb14e31](https://github.com/decentralized-identity/did-jwt-vc/commit/eb14e311f909e0ddb61e3d589bd6847bfe9ed1ac))\n\n# [3.1.0](https://github.com/decentralized-identity/did-jwt-vc/compare/3.0.1...3.1.0) (2022-08-19)\n\n\n### Features\n\n* export VC_JWT_ERROR as plain object ([#121](https://github.com/decentralized-identity/did-jwt-vc/issues/121)) ([5f48d2a](https://github.com/decentralized-identity/did-jwt-vc/commit/5f48d2af1d8e2d57a3af46e4be5c6199f715734c)), closes [#120](https://github.com/decentralized-identity/did-jwt-vc/issues/120)\n\n## [3.0.1](https://github.com/decentralized-identity/did-jwt-vc/compare/3.0.0...3.0.1) (2022-08-18)\n\n\n### Bug Fixes\n\n* fix error type exports ([abebb0c](https://github.com/decentralized-identity/did-jwt-vc/commit/abebb0c2b746d9aef63434052e9cc075438100aa))\n\n# [3.0.0](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.14...3.0.0) (2022-08-18)\n\n\n### Features\n\n* add verification policies ([#119](https://github.com/decentralized-identity/did-jwt-vc/issues/119)) ([d44e803](https://github.com/decentralized-identity/did-jwt-vc/commit/d44e80355a4e266fbf30bed3ef5df4401861de6b)), closes [#118](https://github.com/decentralized-identity/did-jwt-vc/issues/118)\n\n\n### BREAKING CHANGES\n\n* The error messages are now prefixed with recognizable error codes. This is a breaking change if you were checking the exact error messages being returned before. Otherwise, it is safe to upgrade without issues.\n\n## [2.1.14](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.13...2.1.14) (2022-08-03)\n\n\n### Bug Fixes\n\n* **deps:** update did-resolver and did-jwt dependencies ([#117](https://github.com/decentralized-identity/did-jwt-vc/issues/117)) ([d07dd45](https://github.com/decentralized-identity/did-jwt-vc/commit/d07dd459d8f949463f728833e631629a92dbc009))\n\n## [2.1.13](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.12...2.1.13) (2022-06-29)\n\n\n### Bug Fixes\n\n* **deps:** Bump `did-jwt` to 6.2.0 ([#115](https://github.com/decentralized-identity/did-jwt-vc/issues/115)) ([ff4e0b1](https://github.com/decentralized-identity/did-jwt-vc/commit/ff4e0b1d7bf86db29390c01ce3787af6c1312507))\n\n## [2.1.12](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.11...2.1.12) (2022-06-06)\n\n\n### Bug Fixes\n\n* **ci:** groom the build scripts and dependencies ([#114](https://github.com/decentralized-identity/did-jwt-vc/issues/114)) ([381d973](https://github.com/decentralized-identity/did-jwt-vc/commit/381d9731d043672da992c4ce704cd8b0f16c2a2c))\n\n## [2.1.11](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.10...2.1.11) (2022-05-22)\n\n\n### Bug Fixes\n\n* **deps:** bump did-jwt to v6.1.0 ([#112](https://github.com/decentralized-identity/did-jwt-vc/issues/112)) ([540b8d5](https://github.com/decentralized-identity/did-jwt-vc/commit/540b8d505c9aebdab405f5931009658b34cca189))\n\n## [2.1.10](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.9...2.1.10) (2022-05-20)\n\n\n### Bug Fixes\n\n* **deps:** bump did-jwt to v6 ([#108](https://github.com/decentralized-identity/did-jwt-vc/issues/108)) ([ad0e3f9](https://github.com/decentralized-identity/did-jwt-vc/commit/ad0e3f906822f2d11d1c9ef7c66fd436cbee9ab6))\n\n## [2.1.9](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.8...2.1.9) (2022-01-13)\n\n\n### Bug Fixes\n\n* **deps:** bump dependencies to latest ([7cb7a88](https://github.com/decentralized-identity/did-jwt-vc/commit/7cb7a88d31cbfc388da1363eefb641212505ca19))\n\n## [2.1.8](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.7...2.1.8) (2021-11-23)\n\n\n### Bug Fixes\n\n* normalizeCredential overwrite of \"evidence\" when \"vc\" is missing ([#86](https://github.com/decentralized-identity/did-jwt-vc/issues/86)) ([0039298](https://github.com/decentralized-identity/did-jwt-vc/commit/003929800f5310cf88af8618f3a19a41c05e859e))\n\n## [2.1.7](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.6...2.1.7) (2021-08-31)\n\n\n### Bug Fixes\n\n* forward JWT options when creating a VC or VP ([#90](https://github.com/decentralized-identity/did-jwt-vc/issues/90)) ([13cea08](https://github.com/decentralized-identity/did-jwt-vc/commit/13cea0868a8126b9314b27283e72f6f3325eb943)), closes [#89](https://github.com/decentralized-identity/did-jwt-vc/issues/89)\n\n## [2.1.6](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.5...2.1.6) (2021-07-22)\n\n\n### Bug Fixes\n\n* **build:** revert back to microbundle and module output ([d28729a](https://github.com/decentralized-identity/did-jwt-vc/commit/d28729abb906e817e6822dd38abdbdd3f89f1c66)), closes [#84](https://github.com/decentralized-identity/did-jwt-vc/issues/84)\n\n## [2.1.5](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.4...2.1.5) (2021-07-21)\n\n\n### Bug Fixes\n\n* **build:** revert to tsc for compilation ([#85](https://github.com/decentralized-identity/did-jwt-vc/issues/85)) ([b7dedea](https://github.com/decentralized-identity/did-jwt-vc/commit/b7dedea4847ea402fd049ee4e15d144650e90a94)), closes [#84](https://github.com/decentralized-identity/did-jwt-vc/issues/84)\n\n## [2.1.4](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.3...2.1.4) (2021-07-13)\n\n\n### Bug Fixes\n\n* allow VPs without VCs ([#83](https://github.com/decentralized-identity/did-jwt-vc/issues/83)) ([e534b4d](https://github.com/decentralized-identity/did-jwt-vc/commit/e534b4dc1747da1071d489c2301e18e28f24f56b))\n\n## [2.1.3](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.2...2.1.3) (2021-06-16)\n\n\n### Bug Fixes\n\n* w3c additional props should not be overwritten inside JWT ([#78](https://github.com/decentralized-identity/did-jwt-vc/issues/78)) ([d2a5c44](https://github.com/decentralized-identity/did-jwt-vc/commit/d2a5c443f3ca9696fe9e15b9e8d5af8e004851a0))\n\n## [2.1.2](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.1...2.1.2) (2021-05-28)\n\n\n### Bug Fixes\n\n* mapp additional W3C spec fields to and from JWT VC ([#75](https://github.com/decentralized-identity/did-jwt-vc/issues/75)) ([e0482dc](https://github.com/decentralized-identity/did-jwt-vc/commit/e0482dcdf6e079dc4d55e253edc87ab231818c19)), closes [#73](https://github.com/decentralized-identity/did-jwt-vc/issues/73)\n\n## [2.1.1](https://github.com/decentralized-identity/did-jwt-vc/compare/2.1.0...2.1.1) (2021-05-18)\n\n\n### Bug Fixes\n\n* **deps:** bump did-jwt@5.4.0 ([#76](https://github.com/decentralized-identity/did-jwt-vc/issues/76)) ([bfd3eaa](https://github.com/decentralized-identity/did-jwt-vc/commit/bfd3eaad146ee3c812f6ea9961d5fdf36c8fd781))\n\n# [2.1.0](https://github.com/decentralized-identity/did-jwt-vc/compare/2.0.2...2.1.0) (2021-04-08)\n\n\n### Features\n\n* add ability to add to the JWT header of the VC/VP ([#71](https://github.com/decentralized-identity/did-jwt-vc/issues/71)) ([79d2d76](https://github.com/decentralized-identity/did-jwt-vc/commit/79d2d7696a5e929a574d2e0671fc1258f768e90e)), closes [#69](https://github.com/decentralized-identity/did-jwt-vc/issues/69)\n\n## [2.0.2](https://github.com/decentralized-identity/did-jwt-vc/compare/2.0.1...2.0.2) (2021-03-26)\n\n\n### Bug Fixes\n\n* **deps:** use Resolvable type from did-resolver ([e731e7e](https://github.com/decentralized-identity/did-jwt-vc/commit/e731e7e0549392d14909c1f78fbce343620fdfa3))\n\n## [2.0.1](https://github.com/decentralized-identity/did-jwt-vc/compare/2.0.0...2.0.1) (2021-03-25)\n\n\n### Bug Fixes\n\n* **deps:** update did-jwt@5.1.1 ([#68](https://github.com/decentralized-identity/did-jwt-vc/issues/68)) ([50e3f7c](https://github.com/decentralized-identity/did-jwt-vc/commit/50e3f7c5a6fe30d659811090b45762487c5582a2))\n\n# [2.0.0](https://github.com/decentralized-identity/did-jwt-vc/compare/1.2.0...2.0.0) (2021-03-11)\n\n\n### Bug Fixes\n\n* **deps:** update did-jwt@5.0.1 and did-resolver@3.0 ([#67](https://github.com/decentralized-identity/did-jwt-vc/issues/67)) ([07bfc6b](https://github.com/decentralized-identity/did-jwt-vc/commit/07bfc6bcf6855e16e992e1fb89f99d8ab7b2c99c))\n\n\n### BREAKING CHANGES\n\n* **deps:** the type of Resolver used for verification has been upgraded to the latest spec and no longer returns just the DID Document\n\n# [1.2.0](https://github.com/decentralized-identity/did-jwt-vc/compare/1.1.0...1.2.0) (2021-03-11)\n\n\n### Features\n\n* add option to keep original fields when transforming JWT<->JOSE payload formats ([#63](https://github.com/decentralized-identity/did-jwt-vc/issues/63)) ([cf59b6f](https://github.com/decentralized-identity/did-jwt-vc/commit/cf59b6f149dae3b94fa0c6dceada432be80c3b6a)), closes [#62](https://github.com/decentralized-identity/did-jwt-vc/issues/62) [#22](https://github.com/decentralized-identity/did-jwt-vc/issues/22)\n\n# [1.1.0](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.7...1.1.0) (2021-02-24)\n\n\n### Features\n\n* support challenge & domain in Presentation creation and verification ([#61](https://github.com/decentralized-identity/did-jwt-vc/issues/61)) ([3a75c47](https://github.com/decentralized-identity/did-jwt-vc/commit/3a75c4708f545165cd5d483de9ec3d390a95e14e)), closes [#60](https://github.com/decentralized-identity/did-jwt-vc/issues/60) [#22](https://github.com/decentralized-identity/did-jwt-vc/issues/22)\n\n## [1.0.7](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.6...1.0.7) (2021-01-18)\n\n\n### Bug Fixes\n\n* **deps:** bump dependencies and fix type issues ([#55](https://github.com/decentralized-identity/did-jwt-vc/issues/55)) ([a169cd3](https://github.com/decentralized-identity/did-jwt-vc/commit/a169cd38c975c62e402d5c96b7c010c30b86ff35)), closes [#52](https://github.com/decentralized-identity/did-jwt-vc/issues/52) [#53](https://github.com/decentralized-identity/did-jwt-vc/issues/53)\n\n## [1.0.6](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.5...1.0.6) (2020-08-18)\n\n\n### Bug Fixes\n\n* **deps:** update dependency did-resolver@2.1.0 & did-jwt@4.4.2 ([#48](https://github.com/decentralized-identity/did-jwt-vc/issues/48)) ([6a98103](https://github.com/decentralized-identity/did-jwt-vc/commit/6a981033f9a4aa5317a238af45022332cd57a306))\n\n## [1.0.5](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.4...1.0.5) (2020-08-18)\n\n\n### Bug Fixes\n\n* set credentialSubject.id as optional ([#45](https://github.com/decentralized-identity/did-jwt-vc/issues/45)) ([c31ee17](https://github.com/decentralized-identity/did-jwt-vc/commit/c31ee1715a7aa749b9419aeed79ee584a645bea3))\n\n## [1.0.4](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.3...1.0.4) (2020-07-20)\n\n\n### Bug Fixes\n\n* **build:** use commonjs module format ([#46](https://github.com/decentralized-identity/did-jwt-vc/issues/46)) ([76e503b](https://github.com/decentralized-identity/did-jwt-vc/commit/76e503bc4307d313681a245665250932c98bcd64)), closes [#47](https://github.com/decentralized-identity/did-jwt-vc/issues/47)\n\n## [1.0.3](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.2...1.0.3) (2020-07-02)\n\n\n### Bug Fixes\n\n* stop input from being mutated by converter methods ([#41](https://github.com/decentralized-identity/did-jwt-vc/issues/41)) ([346e6f7](https://github.com/decentralized-identity/did-jwt-vc/commit/346e6f7f61ade7c669f38bc1e6dcb42ad8a0ba34))\n\n## [1.0.2](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.1...1.0.2) (2020-06-29)\n\n\n### Bug Fixes\n\n* converting to jwt ([d7f9578](https://github.com/decentralized-identity/did-jwt-vc/commit/d7f95783c73eaaa521eab1fb352881884b59f42c)), closes [#37](https://github.com/decentralized-identity/did-jwt-vc/issues/37)\n* **types:** widen types used as input ([c3e7a2e](https://github.com/decentralized-identity/did-jwt-vc/commit/c3e7a2e745145e985f60da7c343d9889d50e76dc))\n\n## [1.0.1](https://github.com/decentralized-identity/did-jwt-vc/compare/1.0.0...1.0.1) (2020-06-26)\n\n\n### Bug Fixes\n\n* **build:** Use tsc instead of microbundle ([#38](https://github.com/decentralized-identity/did-jwt-vc/issues/38)) ([f75a967](https://github.com/decentralized-identity/did-jwt-vc/commit/f75a96767f413cecbcd3c09954b04c11be3db24d))\n\n# [1.0.0](https://github.com/decentralized-identity/did-jwt-vc/compare/0.2.0...1.0.0) (2020-06-25)\n\n\n### Bug Fixes\n\n* **build:** add babel plugin to fix microbundle 0.12 build error ([e14c2aa](https://github.com/decentralized-identity/did-jwt-vc/commit/e14c2aa695a8155c9eab4591ebc82233694dcab5))\n* use `ES256K` as the default JWT algorithm ([a097c69](https://github.com/decentralized-identity/did-jwt-vc/commit/a097c69e1c182d448007cbe834a56621f33cdb82))\n\n\n### Code Refactoring\n\n* rename creation and validation methods to reflect JWT target ([829956f](https://github.com/decentralized-identity/did-jwt-vc/commit/829956f1e063930e47866d5bbd0208dbc1e57d83))\n* rename credential validation methods ([2bb2e5a](https://github.com/decentralized-identity/did-jwt-vc/commit/2bb2e5a02c28e2ff24da84f5e8fc9fe3525cb57c))\n* rename existing payload types to reflect JWT target ([af74207](https://github.com/decentralized-identity/did-jwt-vc/commit/af742074341e5b2345c3139da7fc6e4d642fe76d))\n\n\n### Features\n\n* add a method to convert a credential payload from W3C to JWT ([f7e86f0](https://github.com/decentralized-identity/did-jwt-vc/commit/f7e86f0b8696ea407a9820e59f0e3472d928666e))\n* add a normalizer method to an unambiguous `Verifiable<Credential>` ([ffbd67f](https://github.com/decentralized-identity/did-jwt-vc/commit/ffbd67fffed6e977399cd4946e8c7c5da14d5dbd))\n* add methods to convert to unambiguous `Verifiable<Presentation>` and JWTPresentationPayload ([1721e4a](https://github.com/decentralized-identity/did-jwt-vc/commit/1721e4a34ba42112b4b6df359c11a896c874e703))\n* define W3C compatible data types for credentials and presentations ([adb27e9](https://github.com/decentralized-identity/did-jwt-vc/commit/adb27e98171fbead4b903add1d00349e50ed92b0))\n* homogenize `verifyCredential()`/`verifyPresentation()` API ([e9fbb99](https://github.com/decentralized-identity/did-jwt-vc/commit/e9fbb9941e0717fb3358e26f977ba6a22005942a))\n* homogenize createCredentialJwt/PresentationJwt API ([3999382](https://github.com/decentralized-identity/did-jwt-vc/commit/39993820e0e3bfef8d706515f7a256cd5c2655fd))\n\n\n### BREAKING CHANGES\n\n* removed `Verifiable` from the credential validation methods since the parameter is only the payload\nvalidateJwtVerifiableCredentialPayload -> validateJwtCredentialPayload\nvalidateVerifiableCredentialPayload -> validateCredentialPayload\n* renamed `createPresentationJWT` to `createVerifiablePresentationAJwt`\n* the following methods have been renamed:\n`createVerifiableCredential` -> `createVerifiableCredentialJwt`\n`createPresentation` -> `createPresentationJwt`\n`validateVerifiableCredentialAttributes` -> `validateJwtVerifiableCredentialPayload`\n`validatePresentationAttributes` -> `validateJwtPresentationPayload`\n\nAlso exporting the `JWT` type which maps to `string`\n* the following interface definitions have been renamed:\n`VerifiableCredentialPayload` -> `JwtVerifiableCredentialPayload`\n`PresentationPayload` -> `JwtPresentationPayload`\n\n# [0.2.0](https://github.com/decentralized-identity/did-jwt-vc/compare/0.1.6...0.2.0) (2020-04-30)\n\n\n### Features\n\n* remove explicit declaration of the nullable `credentialStatus` ([078ba82](https://github.com/decentralized-identity/did-jwt-vc/commit/078ba8215353dbcea08045383e1a970b9dd79851))\n\n## [0.1.6](https://github.com/decentralized-identity/did-jwt-vc/compare/0.1.5...0.1.6) (2020-04-28)\n\n\n### Bug Fixes\n\n* Issuer alg is optional ([5a4b016](https://github.com/decentralized-identity/did-jwt-vc/commit/5a4b016e0884af69362cfb10c73e6e89296739c2))\n"
  },
  {
    "path": "Contributing.md",
    "content": "# How to contribute to this library\n\nWe love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:\n\n- Reporting a bug\n- Discussing the current state of the code\n- Submitting a fix\n- Proposing new features\n\n## Report a bug with detail, background and sample code\n**Great Bug Reports** tend to have:\n\n- A quick summary and/or background\n- Steps to reproduce\n  - Be specific!\n  - Give sample code if you can.\n- What you expected would happen\n- What actually happens\n- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)\n- You get extra kudos if you attach a failing test demonstrating that bug\n\n## Submitting improvements\n\n### Commit messages\nWe use github to host code, to track issues and feature requests, as well as accept pull requests.\nWe Use [semantic-release](https://github.com/semantic-release/semantic-release) and\n[commitlint](https://github.com/conventional-changelog/commitlint) to automate our release process.\nVersioning, changelogs and publication is all covered by this automation.\nPlease see some [commit message examples](https://github.com/semantic-release/semantic-release#commit-message-format)\n\nCommit messages are really important in this process, and the build will fail if your commits don't adhere to this.\n\n### Code style\nUse the built in code formatter (`npm run format`) before committing code. It makes lives much easier.  \n\n### Submitting a fix\n- Branch off of `master`\n- Wherever possible, commit at least one test to demonstrate the bug\n- Commit your code to fix that bug\n- Create a PR for it\n    - Mention the issue you're fixing in the PR (Example: __Closes #17__) \n\n### Submitting a proposal\nWe prefer to discuss proposals before accepting them into the codebase.\nOpen an issue with as much detail and background as possible to make your case. \nSmall proposals can come in directly as PRs, but it's generally better to discuss before starting work.\n\nAny contributions you make will be under the Apache-2.0 License\n\n### Posting PRs\n- Describe your changes in the PR description.\n- Mention issues that should be fixed or closed when the PR is merged.\n- Make sure any new code has tests associated!\n- Make sure the documentation is still valid if your changes get included.\n\nThank you for your contribution!"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2019 ConsenSys AG\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm](https://img.shields.io/npm/dt/did-jwt-vc.svg)](https://www.npmjs.com/package/did-jwt-vc)\n[![npm](https://img.shields.io/npm/v/did-jwt-vc.svg)](https://www.npmjs.com/package/did-jwt-vc)\n[![codecov](https://codecov.io/gh/decentralized-identity/did-jwt-vc/branch/master/graph/badge.svg)](https://codecov.io/gh/decentralized-identity/did-jwt-vc)\n\n# did-jwt-vc\n\nCreate and verify W3C Verifiable Credentials and Presentations in JWT format\n\n## Installation\n\n```\nnpm install did-jwt-vc\n```\n\n## Usage\n\n### Creating JWTs\n\n#### Prerequisites\n\nCreate an `Issuer` object to sign JWTs using, for example [ethr-did](https://github.com/uport-project/ethr-did)\n\n```typescript\nimport { EthrDID } from 'ethr-did'\nimport { Issuer } from 'did-jwt-vc'\n\nconst issuer = new EthrDID({\n  identifier: '0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198',\n  privateKey: 'd8b595680851765f38ea5405129244ba3cbad84467d190859f4c8b20c1ff6c75'\n}) as Issuer\n```\n\nThe `Issuer` object must contain a `did` attribute, an `alg` property that is used in the JWT header and a `signer`\nfunction to generate the signature.\n\n#### Creating a Verifiable Credential\n\nSpecify a `payload` matching the `CredentialPayload` or `JwtCredentialPayload` interfaces. Create a JWT by signing it\nwith the previously configured `issuer` using the `createVerifiableCredentialJwt` function:\n\n```typescript\nimport { JwtCredentialPayload, createVerifiableCredentialJwt } from 'did-jwt-vc'\n\nconst vcPayload: JwtCredentialPayload = {\n  sub: 'did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4',\n  nbf: 1562950282,\n  vc: {\n    '@context': ['https://www.w3.org/2018/credentials/v1'],\n    type: ['VerifiableCredential'],\n    credentialSubject: {\n      degree: {\n        type: 'BachelorDegree',\n        name: 'Baccalauréat en musiques numériques'\n      }\n    }\n  }\n}\n\nconst vcJwt = await createVerifiableCredentialJwt(vcPayload, issuer)\nconsole.log(vcJwt)\n// eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQi...0CQmqB14NnN5XxD0d_glLRs1Myc_LBJjnuNwE\n```\n\n#### Creating a Verifiable Presentation\n\nSpecify a `payload` matching the `PresentationPayload` or `JwtPresentationPayload` interfaces, including the VC JWTs to\nbe presented in the `vp.verifiableCredential` array. Create a JWT by signing it with the previously configured `issuer`\nusing the `createVerifiablePresentationJwt` function:\n\n```typescript\nimport { JwtPresentationPayload, createVerifiablePresentationJwt } from 'did-jwt-vc'\n\nconst vpPayload: JwtPresentationPayload = {\n  vp: {\n    '@context': ['https://www.w3.org/2018/credentials/v1'],\n    type: ['VerifiablePresentation'],\n    verifiableCredential: [vcJwt]\n  }\n}\n\nconst vpJwt = await createVerifiablePresentationJwt(vpPayload, issuer)\nconsole.log(vpJwt)\n// eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1ODI1NDc...JNMUzZ6naacuWNGdZGuU0ZDwmgpUMUqIzMqFFRmge0R8QA\n```\n\n### Verifying JWTs\n\n#### Prerequisites\n\nCreate a `Resolver` using [did-resolver](https://github.com/decentralized-identity/did-resolver) and register the\n[ethr-did-resolver](https://github.com/decentralized-identity/ethr-did-resolver). When verifying a JWT signed by a DID,\nit is necessary to resolve its DID Document to check for keys that can validate the signature.\n\n```typescript\nimport { Resolver } from 'did-resolver'\nimport { getResolver } from 'ethr-did-resolver'\n\n// see also https://github.com/decentralized-identity/ethr-did-resolver#multi-network-configuration\nconst providerConfig = {\n  rpcUrl: 'https://mainnet.infura.io/v3/<YOUR infura.io PROJECT ID>',\n  registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b'\n}\nconst resolver = new Resolver(getResolver(providerConfig))\n```\n\n#### Verifying a Verifiable Credential\n\nPass in a VC JWT along with the resolver to verify using the `verifyCredential` function:\n\n```typescript\nimport { verifyCredential } from 'did-jwt-vc'\n\nconst verifiedVC = await verifyCredential(vcJwt, resolver)\nconsole.log(verifiedVC)\n/*\n{\n  \"payload\": {\n    // the original payload of the signed credential\n  },\n  \"doc\": {\n    // the DID document of the credential issuer (as returned by the `resolver`)\n  },\n  \"issuer\": \"did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198\", //the credential issuer\n  \"signer\": {\n    //the publicKey entry of the `doc` that has signed the credential\n  },\n  \"jwt\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY...Sx3Y2IdWaUpatJQA\", // the original credential\n  \n  //parsed payload aligned to the W3C data model\n  \"verifiableCredential\": {\n    \"@context\": [Array],\n    \"type\": [ \"VerifiableCredential\", \"UniversityDegreeCredential\" ],\n    \"issuer\": {\n      \"id\": \"did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198\"\n    },\n    \"issuanceDate\": \"2019-07-12T16:51:22.000Z\",\n    \"credentialSubject\": {\n      \"id\": \"did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4\"\n      \"degree\": {\n        \"type\": \"BachelorDegree\",\n        \"name\": \"Baccalauréat en musiques numériques\"\n      },\n    },\n    \"proof\": {\n      //  proof type for internal use, NOT a registered vc-data-model type\n      \"type\": \"JwtProof2020\",\n      \"jwt\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY...Sx3Y2IdWaUpatJQA\"\n    }\n  }\n}\n*/\n```\n\n#### Verifying a Verifiable Presentation\n\nPass in a VP JWT along with the resolver to verify using the `verifyPresentation` function:\n\n```typescript\nimport { verifyPresentation } from 'did-jwt-vc'\n\nconst verifiedVP = await verifyPresentation(vpJwt, resolver)\nconsole.log(verifiedVP)\n/*\n{\n  //original JWT payload\n  payload: {\n    iat: 1568045263,\n    vp: {\n      '@context': [Array],\n      type: ['VerifiablePresentation'],\n      verifiableCredential: [\n        'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5...lpNm51cqSx3Y2IdWaUpatJQA'\n      ]\n    },\n    iss: 'did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198'\n  },\n  \n  doc: {\n    // the DID document of the presentation issuer (as returned by the `resolver`)\n  },\n  \n  signer: {\n    //the publicKey entry of the `doc` that has signed the presentation\n  },\n  \n  issuer: 'did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198',\n\n  jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjgwNDUyNjMsInZwIjp7...ViNNCvoTQ-swSHwbELW7-EGPAcHLOMiIwE',\n\n  // parsed payload aligned to the W3C data model\n  verifiablePresentation: {\n    verifiableCredential: [\n      {\n        iat: 1566923269,\n        credentialSubject: {\n          degree: { type: 'BachelorDegree', name: 'Baccalauréat en musiques numériques' },\n          id: 'did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4'\n        },\n        issuer: { id: 'did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198' },\n        type: ['VerifiableCredential', 'UniversityDegreeCredential'],\n        '@context': [Array],\n        issuanceDate: '2019-07-12T16:51:22.000Z',\n        proof: {\n          type: 'JwtProof2020',\n          jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5...lpNm51cqSx3Y2IdWaUpatJQA'\n        }\n      }\n    ],\n    holder: 'did:ethr:0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198',\n    type: ['VerifiablePresentation'],\n    '@context': [Array],\n    issuanceDate: '2019-09-09T16:07:43.000Z',\n    proof: {\n      // proof type for internal use, NOT a registered W3C vc-data-model proof type\n      type: 'JwtProof2020',\n      jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjgwNDUyNjMsInZwI...ViNNCvoTQ-swSHwbELW7-EGPAcHLOMiIwE'\n    }\n  }\n}\n*/\n```\n\n#### Notes on verification and proof properties\n\nThe result of the verification methods, when successful, also conveniently contain the decoded and parsed payloads, in a\nformat that closely matches the [W3C data model](https://www.w3.org/TR/vc-data-model/) for verifiable credentials and\npresentations. This makes it easier to work with both credential encodings in the same system. This parsed payload also\nshows a `proof` property that lists the full JWT credential or presentation.\n\nThe `JwtProof2020` is a synthetic proof type, usable for differentiating credentials by type. It is not a registered W3C\nVC Data Model algorithm and should not be treated as such.\n\nAlso note that the `@context` fields that appear in this parsed payload are the same as the ones in the incoming JWT.\nThis means that the parsed payload will probably not be suitable for an LD-processor.\n\nPlease see #54 for more information.\n"
  },
  {
    "path": "babel.config.json",
    "content": "{\n  \"presets\": [[\"@babel/preset-env\", { \"targets\": { \"node\": \"current\" } }], \"@babel/preset-typescript\"]\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import typescriptEslint from \"@typescript-eslint/eslint-plugin\";\nimport jest from \"eslint-plugin-jest\";\nimport globals from \"globals\";\nimport tsParser from \"@typescript-eslint/parser\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport js from \"@eslint/js\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst compat = new FlatCompat({\n    baseDirectory: __dirname,\n    recommendedConfig: js.configs.recommended,\n    allConfig: js.configs.all\n});\n\nexport default [...compat.extends(\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:prettier/recommended\",\n), {\n    plugins: {\n        \"@typescript-eslint\": typescriptEslint,\n        jest,\n    },\n\n    languageOptions: {\n        globals: {\n            ...globals.node,\n            ...jest.environments.globals.globals,\n        },\n\n        parser: tsParser,\n        ecmaVersion: 2018,\n        sourceType: \"module\",\n    },\n\n    rules: {},\n}];"
  },
  {
    "path": "jest.config.mjs",
    "content": "import { defaults } from 'jest-config'\n\nconst config = {\n  moduleFileExtensions: [...defaults.moduleFileExtensions, 'mts'],\n  transform: {\n    '^.+\\\\.m?tsx?$': [\n      'ts-jest',\n      {\n        useESM: true,\n        tsconfig: './tsconfig.json',\n      },\n    ],\n  },\n  // // typescript 5 removes the need to specify relative imports as .js, so we should no longer need this workaround\n  // // but webpack still requires .js specifiers, so we are keeping it for now\n  moduleNameMapper: {\n    '^(\\\\.{1,2}/.*)\\\\.js$': '$1',\n  },\n  extensionsToTreatAsEsm: ['.ts'],\n  testMatch: ['**/__tests__/**/*.test.ts'],\n  testEnvironment: 'node',\n  coverageProvider: 'v8'\n}\n\nexport default config\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"did-jwt-vc\",\n  \"version\": \"4.0.16\",\n  \"description\": \"Create and verify W3C Verifiable Credentials and Presentations in JWT format\",\n  \"type\": \"module\",\n  \"source\": \"src/index.ts\",\n  \"main\": \"./lib/index.cjs\",\n  \"module\": \"./lib/index.module.js\",\n  \"types\": \"./lib/index.d.ts\",\n  \"files\": [\n    \"lib\",\n    \"dist\",\n    \"src\",\n    \"LICENSE\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./lib/index.d.ts\",\n      \"require\": \"./lib/index.cjs\",\n      \"import\": \"./lib/index.module.js\"\n    }\n  },\n  \"scripts\": {\n    \"test\": \"cross-env NODE_OPTIONS=\\\"--experimental-vm-modules\\\" jest\",\n    \"test:ci\": \"yarn test --coverage\",\n    \"build\": \"microbundle --compress=false\",\n    \"format\": \"prettier --write \\\"src/**/*.ts\\\"\",\n    \"lint\": \"eslint --ignore-pattern \\\"src/**/*.test.[jt]s\\\" \\\"src/**/*.[jt]s\\\"\",\n    \"prepublishOnly\": \"yarn test:ci && yarn format && yarn lint\",\n    \"release\": \"semantic-release --debug\"\n  },\n  \"author\": \"mi-xu\",\n  \"contributors\": [\n    \"Mircea Nistor\"\n  ],\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"did-jwt\": \"^8.0.0\",\n    \"did-resolver\": \"^4.1.0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/decentralized-identity/did-jwt-vc.git\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"7.26.9\",\n    \"@babel/preset-env\": \"7.26.9\",\n    \"@babel/preset-typescript\": \"7.26.0\",\n    \"@noble/curves\": \"1.8.1\",\n    \"@semantic-release/changelog\": \"6.0.3\",\n    \"@semantic-release/git\": \"10.0.1\",\n    \"@types/elliptic\": \"6.4.18\",\n    \"@types/faker\": \"6.6.11\",\n    \"@types/jest\": \"29.5.14\",\n    \"@types/node\": \"22.13.5\",\n    \"@typescript-eslint/eslint-plugin\": \"8.24.1\",\n    \"@typescript-eslint/parser\": \"8.24.1\",\n    \"cross-env\": \"7.0.3\",\n    \"eslint\": \"9.21.0\",\n    \"eslint-config-prettier\": \"10.0.1\",\n    \"eslint-plugin-jest\": \"28.11.0\",\n    \"eslint-plugin-prettier\": \"5.2.3\",\n    \"ethr-did\": \"3.0.25\",\n    \"@faker-js/faker\": \"9.5.0\",\n    \"jest\": \"29.7.0\",\n    \"microbundle\": \"0.15.1\",\n    \"prettier\": \"3.5.2\",\n    \"semantic-release\": \"24.2.3\",\n    \"ts-jest\": \"29.2.6\",\n    \"typescript\": \"5.7.3\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"packageManager\": \"yarn@1.22.22\"\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\",\n    \"group:allNonMajor\"\n  ],\n  \"labels\": [\n    \"maintenance\"\n  ],\n  \"automergeType\": \"branch\",\n  \"automerge\": true,\n  \"packageRules\": [\n    {\n      \"matchPackagePatterns\": [\n        \"did\"\n      ],\n      \"matchUpdateTypes\": [\n        \"bump\",\n        \"patch\",\n        \"minor\",\n        \"major\"\n      ],\n      \"groupName\": \"did-dependencies\",\n      \"commitMessagePrefix\": \"fix(deps):\"\n    },\n    {\n      \"matchDepTypes\": [\n        \"devDependencies\"\n      ],\n      \"groupName\": \"devDeps\",\n      \"extends\": [\n        \"schedule:earlyMondays\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/__tests__/__snapshots__/index.test.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`createPresentation creates a valid Presentation JWT and does not overwrite an existing nonce property 1`] = `\n{\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"nonce\": \"EXISTING_NONCE\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n    \"verifiableCredential\": [\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA\",\n    ],\n  },\n}\n`;\n\nexports[`createPresentation creates a valid Presentation JWT if there are no credentials 1`] = `\n{\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n  },\n}\n`;\n\nexports[`createPresentation creates a valid Presentation JWT with challenge option 1`] = `\n{\n  \"extra\": 42,\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"nonce\": \"TEST_CHALLENGE\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n    \"verifiableCredential\": [\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA\",\n    ],\n  },\n}\n`;\n\nexports[`createPresentation creates a valid Presentation JWT with domain option 1`] = `\n{\n  \"aud\": [\n    \"TEST_DOMAIN\",\n  ],\n  \"extra\": 42,\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n    \"verifiableCredential\": [\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA\",\n    ],\n  },\n}\n`;\n\nexports[`createPresentation creates a valid Presentation JWT with domain option and existing aud 1`] = `\n{\n  \"aud\": [\n    \"TEST_DOMAIN\",\n    \"EXISTING_AUD\",\n  ],\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n    \"verifiableCredential\": [\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA\",\n    ],\n  },\n}\n`;\n\nexports[`createPresentation creates a valid Presentation JWT with extra optional fields 1`] = `\n{\n  \"extra\": 42,\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n    \"verifiableCredential\": [\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA\",\n    ],\n  },\n}\n`;\n\nexports[`createPresentation creates a valid Presentation JWT with required fields 1`] = `\n{\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"vp\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"type\": [\n      \"VerifiablePresentation\",\n    ],\n    \"verifiableCredential\": [\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA\",\n    ],\n  },\n}\n`;\n\nexports[`createVerifiableCredential creates a valid Verifiable Credential JWT with extra optional fields 1`] = `\n{\n  \"extra\": 42,\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"nbf\": 1562950282,\n  \"sub\": \"did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4\",\n  \"vc\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"credentialSubject\": {\n      \"degree\": {\n        \"name\": \"Baccalauréat en musiques numériques\",\n        \"type\": \"BachelorDegree\",\n      },\n    },\n    \"type\": [\n      \"VerifiableCredential\",\n      \"UniversityDegreeCredential\",\n    ],\n  },\n}\n`;\n\nexports[`createVerifiableCredential creates a valid Verifiable Credential JWT with required fields 1`] = `\n{\n  \"iss\": \"did:ethr:0xF1232F840f3aD7d23FcDaA84d6C66dac24EFb198\",\n  \"nbf\": 1562950282,\n  \"sub\": \"did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4\",\n  \"vc\": {\n    \"@context\": [\n      \"https://www.w3.org/2018/credentials/v1\",\n      \"https://www.w3.org/2018/credentials/examples/v1\",\n    ],\n    \"credentialSubject\": {\n      \"degree\": {\n        \"name\": \"Baccalauréat en musiques numériques\",\n        \"type\": \"BachelorDegree\",\n      },\n    },\n    \"type\": [\n      \"VerifiableCredential\",\n      \"UniversityDegreeCredential\",\n    ],\n  },\n}\n`;\n"
  },
  {
    "path": "src/__tests__/converters.test.ts",
    "content": "// noinspection JSUnusedLocalSymbols\n\nimport {\n  normalizeCredential,\n  transformCredentialInput,\n  normalizePresentation,\n  transformPresentationInput,\n} from '../converters'\nimport { DEFAULT_JWT_PROOF_TYPE } from '../types'\nimport { CredentialPayload, PresentationPayload } from '../types'\nimport { validateJwtCredentialPayload, validateJwtPresentationPayload } from '..'\nimport { bytesToBase64url } from 'did-jwt'\nimport { utf8ToBytes } from '@noble/curves/abstract/utils'\n\nfunction encodeBase64url(s: string): string {\n  return bytesToBase64url(utf8ToBytes(s))\n}\n\ndescribe('credential', () => {\n  describe('transform W3C/JWT VC => W3C VC', () => {\n    it('passes through empty payload', () => {\n      const result = normalizeCredential({})\n      expect(result).toMatchObject({})\n    })\n\n    it('passes through app specific properties', () => {\n      const result = normalizeCredential({ foo: 'bar' })\n      expect(result).toMatchObject({ foo: 'bar' })\n    })\n\n    it('clears empty vc property', () => {\n      const result = normalizeCredential({ foo: 'bar', vc: {} })\n      expect(result).toMatchObject({ foo: 'bar' })\n      expect(result).not.toHaveProperty('vc')\n    })\n\n    it('keeps vc property after normalize', () => {\n      const result = normalizeCredential({ foo: 'bar', vc: {} }, false)\n      expect(result).toMatchObject({ foo: 'bar' })\n      expect(result).toHaveProperty('vc')\n    })\n\n    it('passes through app specific properties in vc', () => {\n      const result = normalizeCredential({ foo: 'bar', vc: { bar: 'baz' } })\n      expect(result).toMatchObject({ foo: 'bar', vc: { bar: 'baz' } })\n    })\n\n    describe('normalizeCredential deep copy of input', () => {\n      it('keeps proof property if proof is jwt', () => {\n        const input = {\n          foo: 'bar',\n          proof: {\n            jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjcwMjQ5NzQsIm5hbWUiOiJib2IiLCJpc3MiOiJkaWQ6ZXRocjoweGYzYmVhYzMwYzQ5OGQ5ZTI2ODY1ZjM0ZmNhYTU3ZGJiOTM1YjBkNzQifQ.2lP3YDOBj9pirxmPAJojQ-q6Rp7w4wA59ZLm19HdqC2leuxlZEQ5w8y0tzpH8n2I25aQ0vVB6j6TimCNLFasqQE',\n            bar: 'baz',\n          },\n        }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.sub', () => {\n        const input = { sub: 'foo', bar: 'baz' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc.credentialSubject', () => {\n        const input = { vc: { credentialSubject: { bar: 'foo' }, bar: 'baz' }, baz: 'bak' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.iss', () => {\n        const input = { iss: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.jti', () => {\n        const input = { jti: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc.type array', () => {\n        const input = { vc: { type: ['foo'] } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc.type string', () => {\n        const input = { vc: { type: 'foo' } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.context array', () => {\n        const input = { context: ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.context string', () => {\n        const input = { context: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc.@context array', () => {\n        const input = { vc: { '@context': ['foo'] } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc.@context string', () => {\n        const input = { vc: { '@context': 'foo' } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.nbf', () => {\n        const input = { nbf: 123 }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.iat', () => {\n        const input = { iat: 123 }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.exp', () => {\n        const input = { exp: 123 }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc when it is filtered from output', () => {\n        const input = { vc: {} }\n        const frozen = JSON.stringify(input)\n        const unused = normalizeCredential(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n    })\n\n    describe('credentialSubject', () => {\n      it('keeps credentialSubject object', () => {\n        const result = normalizeCredential({ credentialSubject: { foo: 'bar' } })\n        expect(result).toMatchObject({ credentialSubject: { foo: 'bar' } })\n      })\n\n      it('interprets JWT sub as credential subject id', () => {\n        const result = normalizeCredential({ sub: 'example.com' })\n        expect(result).toMatchObject({ credentialSubject: { id: 'example.com' } })\n        expect(result).not.toHaveProperty('sub')\n      })\n\n      it('interprets JWT sub as credential subject id and keeps original', () => {\n        const result = normalizeCredential({ sub: 'example.com' }, false)\n        expect(result).toMatchObject({ sub: 'example.com', credentialSubject: { id: 'example.com' } })\n      })\n\n      it('interprets JWT sub as credential subject id without overwriting existing', () => {\n        const result = normalizeCredential({ sub: 'foo', credentialSubject: { id: 'bar' } })\n        expect(result).toMatchObject({ sub: 'foo', credentialSubject: { id: 'bar' } })\n      })\n\n      it('merges credentialSubject objects', () => {\n        const result = normalizeCredential({\n          credentialSubject: { foo: 'bar' },\n          vc: { credentialSubject: { bar: 'baz' }, '@context': [], type: [] },\n        })\n        expect(result).toMatchObject({ credentialSubject: { foo: 'bar', bar: 'baz' } })\n      })\n\n      it('merges credentialSubject objects while keeping originals', () => {\n        const result = normalizeCredential(\n          {\n            credentialSubject: { foo: 'bar' },\n            vc: { credentialSubject: { bar: 'baz' }, '@context': [], type: [] },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          credentialSubject: { foo: 'bar', bar: 'baz' },\n          vc: { credentialSubject: { bar: 'baz' }, '@context': [], type: [] },\n        })\n      })\n\n      it('merges credentialSubject objects with JWT precedence', () => {\n        const result = normalizeCredential({\n          credentialSubject: { foo: 'bar' },\n          vc: { credentialSubject: { foo: 'bazzz' }, '@context': [], type: [] },\n        })\n        expect(result).toMatchObject({ credentialSubject: { foo: 'bazzz' } })\n      })\n\n      it('merges credentialSubject objects with JWT precedence while keeping originals', () => {\n        const result = normalizeCredential(\n          {\n            credentialSubject: { foo: 'bar' },\n            vc: { credentialSubject: { foo: 'bazzz' }, '@context': [], type: [] },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          credentialSubject: { foo: 'bazzz' },\n          vc: { credentialSubject: { foo: 'bazzz' }, '@context': [], type: [] },\n        })\n      })\n    })\n\n    describe('issuer', () => {\n      it('accepts null issuer', () => {\n        const result = normalizeCredential({\n          issuer: null,\n        })\n        expect(result).toMatchObject({})\n      })\n\n      it('parses iss as issuer id', () => {\n        const result = normalizeCredential({\n          iss: 'foo',\n        })\n        expect(result).toMatchObject({ issuer: { id: 'foo' } })\n        expect(result).not.toHaveProperty('iss')\n      })\n\n      it('parses iss as issuer id but keeps originals', () => {\n        const result = normalizeCredential(\n          {\n            iss: 'foo',\n          },\n          false\n        )\n        expect(result).toMatchObject({ issuer: { id: 'foo' }, iss: 'foo' })\n      })\n\n      it('keeps iss if issuer already has id', () => {\n        const result = normalizeCredential({\n          iss: 'foo',\n          issuer: {\n            id: 'bar',\n          },\n        })\n        expect(result).toMatchObject({ iss: 'foo', issuer: { id: 'bar' } })\n      })\n\n      it('keeps issuer claims', () => {\n        const result = normalizeCredential({\n          iss: 'foo',\n          issuer: {\n            bar: 'baz',\n          },\n        })\n        expect(result).toMatchObject({ issuer: { id: 'foo', bar: 'baz' } })\n        expect(result).not.toHaveProperty('iss')\n      })\n\n      it('keeps issuer claims and originals', () => {\n        const result = normalizeCredential(\n          {\n            iss: 'foo',\n            issuer: {\n              bar: 'baz',\n            },\n          },\n          false\n        )\n        expect(result).toMatchObject({ issuer: { id: 'foo', bar: 'baz' }, iss: 'foo' })\n      })\n\n      it('keeps issuer if it is not an object', () => {\n        const result = normalizeCredential({\n          iss: 'foo',\n          issuer: 'baz',\n        })\n        expect(result).toMatchObject({ issuer: 'baz', iss: 'foo' })\n      })\n    })\n\n    describe('jti', () => {\n      it('transforms jti to id', () => {\n        const result = normalizeCredential({ jti: 'foo' })\n        expect(result).toMatchObject({ id: 'foo' })\n        expect(result).not.toHaveProperty('jti')\n      })\n\n      it('transforms jti to id, keeping originals', () => {\n        const result = normalizeCredential({ jti: 'foo' }, false)\n        expect(result).toMatchObject({ id: 'foo', jti: 'foo' })\n      })\n\n      it('transforms jti to id if it is not present', () => {\n        const result = normalizeCredential({ jti: 'foo', id: 'bar' })\n        expect(result).toMatchObject({ id: 'bar', jti: 'foo' })\n      })\n    })\n\n    describe('type', () => {\n      it('uses type from vc', () => {\n        const result = normalizeCredential({ vc: { type: ['foo'] } })\n        expect(result).toMatchObject({ type: ['foo'] })\n      })\n\n      it('uses type from vc, keeping originals', () => {\n        const result = normalizeCredential({ vc: { type: ['foo'] } }, false)\n        expect(result).toMatchObject({ type: ['foo'], vc: { type: ['foo'] } })\n      })\n\n      it('merges type arrays', () => {\n        const result = normalizeCredential({ type: ['bar'], vc: { type: ['foo'] } })\n        expect(result).toMatchObject({ type: ['bar', 'foo'] })\n      })\n\n      it('merges type arrays, keeping originals', () => {\n        const result = normalizeCredential({ type: ['bar'], vc: { type: ['foo'] } }, false)\n        expect(result).toMatchObject({ type: ['bar', 'foo'], vc: { type: ['foo'] } })\n      })\n\n      it('merges type as arrays for single items', () => {\n        const result = normalizeCredential({ type: 'bar', vc: { type: 'foo', '@context': [], credentialSubject: {} } })\n        expect(result).toMatchObject({ type: ['bar', 'foo'] })\n      })\n\n      it('merges type as arrays uniquely', () => {\n        const result = normalizeCredential({ type: 'foo', vc: { type: 'foo', '@context': [], credentialSubject: {} } })\n        expect(result).toMatchObject({ type: ['foo'] })\n        expect(result).not.toHaveProperty('vc')\n      })\n\n      it('merges type as arrays uniquely, keeping originals', () => {\n        const result = normalizeCredential(\n          { type: 'foo', vc: { type: 'foo', '@context': [], credentialSubject: {} } },\n          false\n        )\n        expect(result).toMatchObject({ type: ['foo'], vc: { type: 'foo', '@context': [], credentialSubject: {} } })\n      })\n    })\n\n    describe('context', () => {\n      it('uses @context from vc', () => {\n        const result = normalizeCredential({ vc: { '@context': ['foo'] } })\n        expect(result).toMatchObject({ '@context': ['foo'] })\n      })\n\n      it('uses @context from vc, keeping originals', () => {\n        const result = normalizeCredential({ vc: { '@context': ['foo'] } }, false)\n        expect(result).toMatchObject({ '@context': ['foo'] })\n      })\n\n      it('merges @context arrays', () => {\n        const result = normalizeCredential({ context: ['baz'], '@context': ['bar'], vc: { '@context': ['foo'] } })\n        expect(result).toMatchObject({ '@context': ['baz', 'bar', 'foo'] })\n      })\n\n      it('merges @context as arrays for single items', () => {\n        const result = normalizeCredential({\n          context: 'baz',\n          '@context': 'bar',\n          vc: { '@context': 'foo', type: [], credentialSubject: {} },\n        })\n        expect(result).toMatchObject({ '@context': ['baz', 'bar', 'foo'] })\n      })\n\n      it('merges @context as arrays uniquely', () => {\n        const result = normalizeCredential({\n          context: 'baz',\n          '@context': ['bar'],\n          vc: { '@context': ['foo', 'baz', 'bar'] },\n        })\n        expect(result).toMatchObject({ '@context': ['baz', 'bar', 'foo'] })\n        expect(result).not.toHaveProperty('vc')\n      })\n    })\n\n    describe('issuanceDate', () => {\n      it('keeps issuanceDate property when present', () => {\n        const result = normalizeCredential({ issuanceDate: 'yesterday', nbf: 1234567890, iat: 1111111111 })\n        expect(result).toMatchObject({ issuanceDate: 'yesterday', nbf: 1234567890, iat: 1111111111 })\n      })\n\n      it('uses nbf as issuanceDate when present', () => {\n        const result = normalizeCredential({ nbf: 1234567890, iat: 1111111111 })\n        expect(result).toMatchObject({ issuanceDate: '2009-02-13T23:31:30.000Z', iat: 1111111111 })\n        expect(result).not.toHaveProperty('nbf')\n      })\n\n      it('uses iat as issuanceDate when no nbf and no issuanceDate present', () => {\n        const result = normalizeCredential({ iat: 1111111111 })\n        expect(result).toMatchObject({ issuanceDate: '2005-03-18T01:58:31.000Z' })\n        expect(result).not.toHaveProperty('iat')\n      })\n    })\n\n    describe('issuanceDate keeping originals', () => {\n      it('keeps issuanceDate property when present', () => {\n        const result = normalizeCredential({ issuanceDate: 'yesterday', nbf: 1234567890, iat: 1111111111 }, false)\n        expect(result).toMatchObject({ issuanceDate: 'yesterday', nbf: 1234567890, iat: 1111111111 })\n      })\n\n      it('uses nbf as issuanceDate when present', () => {\n        const result = normalizeCredential({ nbf: 1234567890, iat: 1111111111 }, false)\n        expect(result).toMatchObject({ issuanceDate: '2009-02-13T23:31:30.000Z', iat: 1111111111, nbf: 1234567890 })\n      })\n\n      it('uses iat as issuanceDate when no nbf and no issuanceDate present', () => {\n        const result = normalizeCredential({ iat: 1111111111 }, false)\n        expect(result).toMatchObject({ issuanceDate: '2005-03-18T01:58:31.000Z', iat: 1111111111 })\n      })\n    })\n\n    describe('expirationDate', () => {\n      it('keeps expirationDate property when present', () => {\n        const result = normalizeCredential({ expirationDate: 'tomorrow', exp: 1222222222 })\n        expect(result).toMatchObject({ expirationDate: 'tomorrow', exp: 1222222222 })\n      })\n\n      it('uses exp as issuanceDate when present', () => {\n        const result = normalizeCredential({ exp: 1222222222 })\n        expect(result).toMatchObject({ expirationDate: '2008-09-24T02:10:22.000Z' })\n        expect(result).not.toHaveProperty('exp')\n      })\n\n      it('uses exp as issuanceDate when present, keeping original', () => {\n        const result = normalizeCredential({ exp: 1222222222 }, false)\n        expect(result).toMatchObject({ expirationDate: '2008-09-24T02:10:22.000Z', exp: 1222222222 })\n      })\n    })\n\n    describe('other W3C fields', () => {\n      it('uses evidence from vc', () => {\n        const result = normalizeCredential({ vc: { evidence: 'foo' } })\n        expect(result).toMatchObject({ evidence: 'foo' })\n      })\n\n      it('uses evidence from vc, keeping originals', () => {\n        const result = normalizeCredential({ vc: { evidence: 'foo' } }, false)\n        expect(result).toMatchObject({ evidence: 'foo', vc: { evidence: 'foo' } })\n      })\n\n      it('does not insert evidence when using credentialSubject instead of vc object', () => {\n        const result = normalizeCredential({\n          credentialSubject: {\n            id: 'did:example:ebfeb1f712ebc6f1c276e12ec21',\n          },\n        })\n        expect(result).not.toHaveProperty('evidence')\n      })\n\n      it('does not overwrite evidence when passed at top-level', () => {\n        const result = normalizeCredential({\n          credentialSubject: {\n            id: 'did:example:ebfeb1f712ebc6f1c276e12ec21',\n          },\n          evidence: {\n            foo: 'bar',\n          },\n        })\n        expect(result.evidence).toMatchObject({ foo: 'bar' })\n      })\n\n      it('uses credentialStatus from vc', () => {\n        const result = normalizeCredential({ vc: { credentialStatus: 'foo' } })\n        expect(result).toMatchObject({ credentialStatus: 'foo' })\n      })\n\n      it('uses credentialStatus from vc, keeping originals', () => {\n        const result = normalizeCredential({ vc: { credentialStatus: 'foo' } }, false)\n        expect(result).toMatchObject({ credentialStatus: 'foo', vc: { credentialStatus: 'foo' } })\n      })\n\n      it('uses termsOfUse from vc', () => {\n        const result = normalizeCredential({ vc: { termsOfUse: 'foo' } })\n        expect(result).toMatchObject({ termsOfUse: 'foo' })\n      })\n\n      it('uses termsOfUse from vc, keeping originals', () => {\n        const result = normalizeCredential({ vc: { termsOfUse: 'foo' } }, false)\n        expect(result).toMatchObject({ termsOfUse: 'foo', vc: { termsOfUse: 'foo' } })\n      })\n    })\n\n    describe('JWT payload', () => {\n      it('rejects unknown JSON string payload', () => {\n        expect(() => {\n          normalizeCredential('aaa')\n        }).toThrowError(/unknown credential format/)\n      })\n\n      it('rejects malformed JWT string payload 1', () => {\n        expect(() => {\n          normalizeCredential('a.b.c')\n        }).toThrowError(/unknown credential format/)\n      })\n\n      it('rejects malformed JWT string payload 2', () => {\n        expect(() => {\n          normalizeCredential('aaa.b.c')\n        }).toThrowError(/unknown credential format/)\n      })\n\n      const complexInput = {\n        context: 'top context',\n        '@context': ['also top'],\n        type: ['A'],\n        issuer: {\n          claim: 'issuer claim',\n        },\n        iss: 'foo',\n        sub: 'bar',\n        vc: {\n          '@context': ['vc context'],\n          type: ['B'],\n          credentialSubject: {\n            something: 'nothing',\n          },\n          appSpecific: 'some app specific field',\n        },\n        nbf: 1234567890,\n        iat: 1111111111,\n        exp: 1231231231,\n        appSpecific: 'another app specific field',\n      }\n\n      const expectedComplexOutput = {\n        '@context': ['top context', 'also top', 'vc context'],\n        type: ['A', 'B'],\n        issuer: {\n          id: 'foo',\n          claim: 'issuer claim',\n        },\n        credentialSubject: {\n          id: 'bar',\n          something: 'nothing',\n        },\n        issuanceDate: '2009-02-13T23:31:30.000Z',\n        expirationDate: '2009-01-06T08:40:31.000Z',\n        iat: 1111111111,\n        vc: {\n          appSpecific: 'some app specific field',\n        },\n        appSpecific: 'another app specific field',\n      }\n\n      const expectedComplexOutputKeepingOriginals = {\n        context: 'top context',\n        '@context': ['top context', 'also top', 'vc context'],\n        type: ['A', 'B'],\n        iss: 'foo',\n        sub: 'bar',\n        issuer: {\n          id: 'foo',\n          claim: 'issuer claim',\n        },\n        credentialSubject: {\n          id: 'bar',\n          something: 'nothing',\n        },\n        issuanceDate: '2009-02-13T23:31:30.000Z',\n        expirationDate: '2009-01-06T08:40:31.000Z',\n        nbf: 1234567890,\n        iat: 1111111111,\n        exp: 1231231231,\n        vc: {\n          '@context': ['vc context'],\n          type: ['B'],\n          credentialSubject: {\n            something: 'nothing',\n          },\n          appSpecific: 'some app specific field',\n        },\n        appSpecific: 'another app specific field',\n      }\n\n      it('accepts VerifiableCredential as string', () => {\n        const credential = JSON.stringify(complexInput)\n\n        const result = normalizeCredential(credential)\n\n        expect(result).toMatchObject(expectedComplexOutput)\n\n        expect(result).not.toHaveProperty('nbf')\n        expect(result).not.toHaveProperty('exp')\n        expect(result).not.toHaveProperty('sub')\n        expect(result).not.toHaveProperty('context')\n        expect(result.vc).not.toHaveProperty('@context')\n        expect(result.vc).not.toHaveProperty('type')\n        expect(result.vc).not.toHaveProperty('credentialSubject')\n      })\n\n      it('accepts VerifiableCredential as string, keeping originals', () => {\n        const credential = JSON.stringify(complexInput)\n\n        const result = normalizeCredential(credential, false)\n\n        expect(result).toMatchObject(expectedComplexOutputKeepingOriginals)\n      })\n\n      it('accepts VerifiableCredential as JWT', () => {\n        const payload = JSON.stringify(complexInput)\n        const header = '{}'\n\n        const credential = `${encodeBase64url(header)}.${encodeBase64url(payload)}.signature`\n\n        const result = normalizeCredential(credential)\n\n        expect(result).toMatchObject(expectedComplexOutput)\n        expect(result).toHaveProperty('proof', { type: DEFAULT_JWT_PROOF_TYPE, jwt: credential })\n\n        expect(result).not.toHaveProperty('nbf')\n        expect(result).not.toHaveProperty('exp')\n        expect(result).not.toHaveProperty('sub')\n        expect(result).not.toHaveProperty('context')\n        expect(result.vc).not.toHaveProperty('@context')\n        expect(result.vc).not.toHaveProperty('type')\n        expect(result.vc).not.toHaveProperty('credentialSubject')\n      })\n\n      it('accepts VerifiableCredential as JWT, keeping originals', () => {\n        const payload = JSON.stringify(complexInput)\n        const header = '{}'\n\n        const credential = `${encodeBase64url(header)}.${encodeBase64url(payload)}.signature`\n\n        const result = normalizeCredential(credential, false)\n\n        expect(result).toMatchObject(expectedComplexOutputKeepingOriginals)\n        expect(result).toHaveProperty('proof', { type: DEFAULT_JWT_PROOF_TYPE, jwt: credential })\n      })\n    })\n  })\n\n  describe('transform W3C/JWT VC => JWT payload', () => {\n    it('passes through empty payload with empty vc field', () => {\n      const result = transformCredentialInput({})\n      expect(result).toMatchObject({ vc: {} })\n    })\n\n    it('passes through app specific properties', () => {\n      const result = transformCredentialInput({ foo: 'bar' })\n      expect(result).toMatchObject({ foo: 'bar' })\n    })\n\n    it('passes through app specific vc properties', () => {\n      const result = transformCredentialInput({ vc: { foo: 'bar' } })\n      expect(result).toMatchObject({ vc: { foo: 'bar' } })\n    })\n\n    it('transforms to a valid payload', () => {\n      const credential: CredentialPayload = {\n        '@context': ['https://www.w3.org/2018/credentials/v1'],\n        type: ['VerifiableCredential', 'PublicProfile'],\n        issuer: { id: 'did:example:123' },\n        issuanceDate: new Date().toISOString(),\n        expirationDate: new Date(Date.now() + 365 * 24 * 3600 * 1000).toISOString(),\n        id: 'vc1',\n        credentialSubject: {\n          id: 'did:example:123',\n          name: 'Alice',\n        },\n      }\n      const transformed = transformCredentialInput(credential)\n      expect(() => {\n        validateJwtCredentialPayload(transformed)\n      }).not.toThrow()\n    })\n\n    describe('transformCredentialInput deep copy', () => {\n      it('keeps input.credentialSubject.id', () => {\n        const input = { credentialSubject: { id: 'foo' } }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.vc.credentialSubject.id', () => {\n        const input = { vc: { credentialSubject: { id: 'foo' } } }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.context', () => {\n        const input = { context: ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.@context', () => {\n        const input = { '@context': ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.type', () => {\n        const input = { type: ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.id', () => {\n        const input = { id: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.issuanceDate', () => {\n        const input = { issuanceDate: new Date().toISOString() }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.expirationDate', () => {\n        const input = { expirationDate: new Date().toISOString() }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.issuer object when filtered from output', () => {\n        const input = { issuer: {} }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.issuer.id', () => {\n        const input = { issuer: { id: 'foo' } }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n      it('keeps input.issuer string', () => {\n        const input = { issuer: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = transformCredentialInput(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n    })\n\n    describe('credentialSubject', () => {\n      it('uses credentialSubject.id as sub', () => {\n        const result = transformCredentialInput({ credentialSubject: { id: 'foo' } })\n        expect(result).toMatchObject({ sub: 'foo', vc: { credentialSubject: {} } })\n        expect(result.vc.credentialSubject).not.toHaveProperty('id')\n      })\n\n      it('preserves existing sub property if present', () => {\n        const result = transformCredentialInput({ sub: 'bar', credentialSubject: { id: 'foo' } })\n        expect(result).toMatchObject({ sub: 'bar', vc: { credentialSubject: { id: 'foo' } } })\n      })\n\n      it('merges credentialSubject properties', () => {\n        const result = transformCredentialInput({\n          vc: { credentialSubject: { foo: 'bar' } },\n          credentialSubject: { bar: 'baz' },\n        })\n        expect(result).toMatchObject({ vc: { credentialSubject: { foo: 'bar', bar: 'baz' } } })\n      })\n\n      it('merges credentialSubject properties, keeping originals', () => {\n        const result = transformCredentialInput(\n          {\n            vc: { credentialSubject: { foo: 'bar' } },\n            credentialSubject: { bar: 'baz' },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          vc: { credentialSubject: { foo: 'bar', bar: 'baz' } },\n          credentialSubject: { bar: 'baz' },\n        })\n      })\n    })\n\n    describe('context', () => {\n      it('merges @context fields', () => {\n        const result = transformCredentialInput({ context: ['AA'], '@context': ['BB'], vc: { '@context': ['CC'] } })\n        expect(result).toMatchObject({ vc: { '@context': ['AA', 'BB', 'CC'] } })\n        expect(result).not.toHaveProperty('context')\n        expect(result).not.toHaveProperty('@context')\n      })\n\n      it('merges @context fields when not array types', () => {\n        const result = transformCredentialInput({ context: 'AA', '@context': 'BB', vc: { '@context': ['CC'] } })\n        expect(result).toMatchObject({ vc: { '@context': ['AA', 'BB', 'CC'] } })\n        expect(result).not.toHaveProperty('context')\n        expect(result).not.toHaveProperty('@context')\n      })\n\n      it('keeps only unique entries in vc.@context', () => {\n        const result = transformCredentialInput({\n          context: ['AA', 'BB'],\n          '@context': ['BB', 'CC'],\n          vc: { '@context': ['CC', 'DD'] },\n        })\n        expect(result).toMatchObject({ vc: { '@context': ['AA', 'BB', 'CC', 'DD'] } })\n        expect(result).not.toHaveProperty('context')\n        expect(result).not.toHaveProperty('@context')\n      })\n\n      it('keeps only unique entries in vc.@context, and originals', () => {\n        const result = transformCredentialInput(\n          {\n            context: ['AA', 'BB'],\n            '@context': ['BB', 'CC'],\n            vc: { '@context': ['CC', 'DD'] },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          vc: { '@context': ['AA', 'BB', 'CC', 'DD'] },\n          context: ['AA', 'BB'],\n          '@context': ['BB', 'CC'],\n        })\n      })\n\n      it('removes undefined entries from @context', () => {\n        const result = transformCredentialInput({})\n        expect(result.vc['@context'].length).toBe(0)\n      })\n    })\n\n    describe('type', () => {\n      it('merges type fields keeping originals', () => {\n        const result = transformCredentialInput({ type: ['AA'], vc: { type: ['BB'] } }, false)\n        expect(result).toMatchObject({ vc: { type: ['AA', 'BB'] }, type: ['AA'] })\n      })\n\n      it('merges type fields', () => {\n        const result = transformCredentialInput({ type: ['AA'], vc: { type: ['BB'] } })\n        expect(result).toMatchObject({ vc: { type: ['AA', 'BB'] } })\n        expect(result).not.toHaveProperty('type')\n      })\n\n      it('merges type fields when not array types', () => {\n        const result = transformCredentialInput({ type: 'AA', vc: { type: ['BB'] } })\n        expect(result).toMatchObject({ vc: { type: ['AA', 'BB'] } })\n        expect(result).not.toHaveProperty('type')\n      })\n\n      it('keeps only unique entries in vc.type', () => {\n        const result = transformCredentialInput({ type: ['AA', 'BB'], vc: { type: ['BB', 'CC'] } })\n        expect(result).toMatchObject({ vc: { type: ['AA', 'BB', 'CC'] } })\n      })\n\n      it('removes undefined entries from vc.type', () => {\n        const result = transformCredentialInput({})\n        expect(result.vc.type.length).toBe(0)\n      })\n    })\n\n    describe('jti', () => {\n      it('uses the id property as jti', () => {\n        const result = transformCredentialInput({ id: 'foo' })\n        expect(result).toMatchObject({ jti: 'foo' })\n        expect(result).not.toHaveProperty('id')\n      })\n\n      it('uses the id property as jti, keeping original', () => {\n        const result = transformCredentialInput({ id: 'foo' }, false)\n        expect(result).toMatchObject({ jti: 'foo', id: 'foo' })\n      })\n\n      it('preserves jti entry if present', () => {\n        const result = transformCredentialInput({ jti: 'bar', id: 'foo' })\n        expect(result).toMatchObject({ jti: 'bar', id: 'foo' })\n      })\n    })\n\n    describe('issuanceDate', () => {\n      it('transforms the issuanceDate property to nbf, keeping original', () => {\n        const result = transformCredentialInput({ issuanceDate: '2009-02-13T23:31:30.000Z' }, false)\n        expect(result).toMatchObject({ nbf: 1234567890, issuanceDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('transforms the issuanceDate property to nbf', () => {\n        const result = transformCredentialInput({ issuanceDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ nbf: 1234567890 })\n        expect(result).not.toHaveProperty('issuanceDate')\n      })\n\n      it('preserves the issuanceDate property if it fails to be parsed as a Date', () => {\n        const result = transformCredentialInput({ issuanceDate: 'tomorrow' })\n        expect(result).toMatchObject({ issuanceDate: 'tomorrow' })\n      })\n\n      it('preserves nbf entry if present', () => {\n        const result = transformCredentialInput({ nbf: 123, issuanceDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ nbf: 123, issuanceDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('preserves nbf entry if explicitly undefined', () => {\n        const result = transformCredentialInput({ nbf: undefined, issuanceDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ nbf: undefined, issuanceDate: '2009-02-13T23:31:30.000Z' })\n      })\n    })\n\n    describe('expirationDate', () => {\n      it('transforms the expirationDate property to exp, keeping original', () => {\n        const result = transformCredentialInput({ expirationDate: '2009-02-13T23:31:30.000Z' }, false)\n        expect(result).toMatchObject({ exp: 1234567890, expirationDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('transforms the expirationDate property to exp', () => {\n        const result = transformCredentialInput({ expirationDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ exp: 1234567890 })\n        expect(result).not.toHaveProperty('expirationDate')\n      })\n\n      it('preserves the expirationDate property if it fails to be parsed as a Date', () => {\n        const result = transformCredentialInput({ expirationDate: 'tomorrow' })\n        expect(result).toMatchObject({ expirationDate: 'tomorrow' })\n      })\n\n      it('preserves exp entry if present', () => {\n        const result = transformCredentialInput({ exp: 123, expirationDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ exp: 123, expirationDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('preserves exp entry if explicitly undefined', () => {\n        const result = transformCredentialInput({ exp: undefined, expirationDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ exp: undefined, expirationDate: '2009-02-13T23:31:30.000Z' })\n      })\n    })\n\n    describe('issuer', () => {\n      it('uses issuer.id as iss, keeping original', () => {\n        const result = transformCredentialInput({ issuer: { id: 'foo' } }, false)\n        expect(result).toMatchObject({ iss: 'foo', issuer: { id: 'foo' } })\n      })\n\n      it('uses issuer.id as iss', () => {\n        const result = transformCredentialInput({ issuer: { id: 'foo' } })\n        expect(result).toMatchObject({ iss: 'foo' })\n        expect(result).not.toHaveProperty('issuer')\n      })\n\n      it('uses issuer as iss when of type string', () => {\n        const result = transformCredentialInput({ issuer: 'foo' })\n        expect(result).toMatchObject({ iss: 'foo' })\n        expect(result).not.toHaveProperty('issuer')\n      })\n\n      it('uses issuer as iss when of type string, keeping original', () => {\n        const result = transformCredentialInput({ issuer: 'foo' }, false)\n        expect(result).toMatchObject({ iss: 'foo', issuer: 'foo' })\n      })\n\n      it('ignores issuer property if neither string or object', () => {\n        const result = transformCredentialInput({ issuer: 12 })\n        expect(result).toMatchObject({ issuer: 12 })\n      })\n\n      it('ignores issuer property if iss is present', () => {\n        const result = transformCredentialInput({ iss: 'foo', issuer: 'bar' })\n        expect(result).toMatchObject({ iss: 'foo', issuer: 'bar' })\n      })\n\n      it('ignores issuer.id property if iss is present', () => {\n        const result = transformCredentialInput({ iss: 'foo', issuer: { id: 'bar' } })\n        expect(result).toMatchObject({ iss: 'foo', issuer: { id: 'bar' } })\n      })\n\n      it('preserves issuer claims if present', () => {\n        const result = transformCredentialInput({ issuer: { id: 'foo', bar: 'baz' } })\n        expect(result).toMatchObject({ iss: 'foo', issuer: { bar: 'baz' } })\n        expect(result.issuer).not.toHaveProperty('id')\n      })\n      it('does not mutate the input object', () => {\n        const input = {\n          '@context': ['https://www.w3.org/2018/credentials/v1'],\n          type: ['VerifiableCredential'],\n          issuer: { id: 'did:example:567' },\n          issuanceDate: '2020-07-02T09:58:10.284Z',\n          credentialSubject: { id: 'did:example:123', foo: 'bar' },\n        }\n        const result = transformCredentialInput(input)\n        expect(input['@context']).toEqual(['https://www.w3.org/2018/credentials/v1'])\n        expect(input.type).toEqual(['VerifiableCredential'])\n        expect(input.issuanceDate).toEqual('2020-07-02T09:58:10.284Z')\n        expect(input.credentialSubject).toEqual({ id: 'did:example:123', foo: 'bar' })\n        expect(input.issuer.id).toEqual('did:example:567')\n      })\n    })\n\n    describe('other W3C fields', () => {\n      it('maps evidence to vc', () => {\n        const result = transformCredentialInput({ evidence: 'foo' })\n        expect(result).toMatchObject({ vc: { evidence: 'foo' } })\n        expect(result).not.toHaveProperty('evidence')\n      })\n\n      it('maps evidence to vc, keeping originals', () => {\n        const result = transformCredentialInput({ evidence: 'foo' }, false)\n        expect(result).toMatchObject({ evidence: 'foo', vc: { evidence: 'foo' } })\n      })\n\n      it('does not overwrite existing evidence', () => {\n        const result = transformCredentialInput({ vc: { evidence: 'foo' } })\n        expect(result).toMatchObject({ vc: { evidence: 'foo' } })\n      })\n\n      it('maps credentialStatus to vc', () => {\n        const result = transformCredentialInput({ credentialStatus: 'foo' })\n        expect(result).toMatchObject({ vc: { credentialStatus: 'foo' } })\n        expect(result).not.toHaveProperty('credentialStatus')\n      })\n\n      it('maps credentialStatus to vc, keeping originals', () => {\n        const result = transformCredentialInput({ credentialStatus: 'foo' }, false)\n        expect(result).toMatchObject({ credentialStatus: 'foo', vc: { credentialStatus: 'foo' } })\n      })\n\n      it('maps termsOfUse to vc', () => {\n        const result = transformCredentialInput({ termsOfUse: 'foo' })\n        expect(result).toMatchObject({ vc: { termsOfUse: 'foo' } })\n        expect(result).not.toHaveProperty('termsOfUse')\n      })\n\n      it('maps termsOfUse to vc, keeping originals', () => {\n        const result = transformCredentialInput({ termsOfUse: 'foo' }, false)\n        expect(result).toMatchObject({ termsOfUse: 'foo', vc: { termsOfUse: 'foo' } })\n      })\n\n      it('does not overwrite existing termsOfUse', () => {\n        const result = transformCredentialInput({ vc: { termsOfUse: 'foo' } })\n        expect(result).toMatchObject({ vc: { termsOfUse: 'foo' } })\n      })\n\n      it('does not introduce new keys', () => {\n        const result = transformCredentialInput({ vc: { foo: 'bar' } })\n        expect(result.vc).not.toHaveProperty('credentialSchema')\n        expect(result.vc).not.toHaveProperty('credentialStatus')\n        expect(result.vc).not.toHaveProperty('evidence')\n        expect(result.vc).not.toHaveProperty('termsOfUse')\n      })\n    })\n  })\n})\n\ndescribe('presentation', () => {\n  describe('transform JWT/W3C VP => W3C VP', () => {\n    it('passes through empty payload', () => {\n      const result = normalizePresentation({})\n      expect(result).toMatchObject({})\n    })\n\n    it('passes through app specific properties', () => {\n      const result = normalizePresentation({ foo: 'bar' })\n      expect(result).toMatchObject({ foo: 'bar' })\n    })\n\n    it('clear vp prop if empty', () => {\n      const result = normalizePresentation({ foo: 'bar', vp: {} })\n      expect(result).toMatchObject({ foo: 'bar' })\n      expect(result).not.toHaveProperty('vp')\n    })\n\n    it('preserves app specific props in vp', () => {\n      const result = normalizePresentation({ foo: 'bar', vp: { bar: 'baz' } })\n      expect(result).toMatchObject({ foo: 'bar', vp: { bar: 'baz' } })\n    })\n\n    describe('normalizePresentation deep copy', () => {\n      it('keeps input.proof if jwt', () => {\n        const input = {\n          proof: {\n            jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjgwNDUyNjMsInZwIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCJdLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpGVXpJMU5rc3RVaUo5LmV5SnBZWFFpT2pFMU5qWTVNak15Tmprc0luTjFZaUk2SW1ScFpEcGxkR2h5T2pCNE5ETTFaR1l6WldSaE5UY3hOVFJqWmpoalpqYzVNall3TnprNE9ERm1Namt4TW1ZMU5HUmlOQ0lzSW01aVppSTZNVFUyTWprMU1ESTRNaXdpZG1NaU9uc2lRR052Ym5SbGVIUWlPbHNpYUhSMGNITTZMeTkzZDNjdWR6TXViM0puTHpJd01UZ3ZZM0psWkdWdWRHbGhiSE12ZGpFaUxDSm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OWxlR0Z0Y0d4bGN5OTJNU0pkTENKMGVYQmxJanBiSWxabGNtbG1hV0ZpYkdWRGNtVmtaVzUwYVdGc0lpd2lWVzVwZG1WeWMybDBlVVJsWjNKbFpVTnlaV1JsYm5ScFlXd2lYU3dpWTNKbFpHVnVkR2xoYkZOMVltcGxZM1FpT25zaVpHVm5jbVZsSWpwN0luUjVjR1VpT2lKQ1lXTm9aV3h2Y2tSbFozSmxaU0lzSW01aGJXVWlPaUpDWVdOallXeGhkWExEcVdGMElHVnVJRzExYzJseGRXVnpJRzUxYmNPcGNtbHhkV1Z6SW4xOWZTd2lhWE56SWpvaVpHbGtPbVYwYUhJNk1IaG1NVEl6TW1ZNE5EQm1NMkZrTjJReU0yWmpaR0ZoT0RSa05tTTJObVJoWXpJMFpXWmlNVGs0SW4wLnJGUlpVQ3czR3UwRV9JNVpKYnJicHVIVjFKTkF3cFhhaUZadUo1OWlKLVROcXVmcjRjdUdDQkVFQ0ZiZ1FGLWxwTm01MWNxU3gzWTJJZFdhVXBhdEpRQSJdfSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.bWZyEpLsx0u6v-UIcQf9TVMde1gTFsn091BY-TViUuRoUNsNQFzN-ViNNCvoTQ-swSHwbELW7-EGPAcHLOMiIwE',\n          },\n        }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.vp.verifiableCredential', () => {\n        const input = { vp: { verifiableCredential: [] } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.iss', () => {\n        const input = { iss: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.aud', () => {\n        const input = { aud: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.jti', () => {\n        const input = { jti: '1234' }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.vp.type', () => {\n        const input = { vp: { type: ['foo'] } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.vp.@context', () => {\n        const input = { vp: { '@context': ['foo'] } }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.context', () => {\n        const input = { context: ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.iat', () => {\n        const input = { iat: 123 }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.nbf', () => {\n        const input = { nbf: 123 }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.exp', () => {\n        const input = { exp: 123 }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n\n      it('keeps input.vp when filtered from output', () => {\n        const input = { vp: {} }\n        const frozen = JSON.stringify(input)\n        const unused = normalizePresentation(input)\n        expect(frozen).toBe(JSON.stringify(input))\n      })\n    })\n\n    describe('verifiableCredential', () => {\n      it('merges the verifiableCredential fields as an array, keeping original', () => {\n        const result = normalizePresentation(\n          {\n            verifiableCredential: { foo: 'bar' },\n            vp: { verifiableCredential: [{ foo: 'baz' }] },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          verifiableCredential: [{ foo: 'bar' }, { foo: 'baz' }],\n          vp: { verifiableCredential: [{ foo: 'baz' }] },\n        })\n      })\n\n      it('merges the verifiableCredential fields as an array', () => {\n        const result = normalizePresentation({\n          verifiableCredential: { foo: 'bar' },\n          vp: { verifiableCredential: [{ foo: 'baz' }] },\n        })\n        expect(result).toMatchObject({\n          verifiableCredential: [{ foo: 'bar' }, { foo: 'baz' }],\n        })\n      })\n\n      it('parses the underlying credentials', () => {\n        const result = normalizePresentation({\n          vp: {\n            verifiableCredential: [\n              'e30.eyJjb250ZXh0IjoidG9wIGNvbnRleHQiLCJAY29udGV4dCI6WyJhbHNvIHRvcCJdLCJ0eXBlIjpbIkEiXSwiaXNzdWVyIjp7ImNsYWltIjoiaXNzdWVyIGNsYWltIn0sImlzcyI6ImZvbyIsInN1YiI6ImJhciIsInZjIjp7IkBjb250ZXh0IjpbInZjIGNvbnRleHQiXSwidHlwZSI6WyJCIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InNvbWV0aGluZyI6Im5vdGhpbmcifSwiYXBwU3BlY2lmaWMiOiJzb21lIGFwcCBzcGVjaWZpYyBmaWVsZCJ9LCJuYmYiOjEyMzQ1Njc4OTAsImlhdCI6MTExMTExMTExMSwiZXhwIjoxMjMxMjMxMjMxLCJhcHBTcGVjaWZpYyI6ImFub3RoZXIgYXBwIHNwZWNpZmljIGZpZWxkIn0.signature',\n            ],\n          },\n        })\n        expect(result).toMatchObject({\n          verifiableCredential: [\n            {\n              '@context': ['top context', 'also top', 'vc context'],\n              type: ['A', 'B'],\n              issuer: { id: 'foo', claim: 'issuer claim' },\n              vc: { appSpecific: 'some app specific field' },\n              iat: 1111111111,\n              appSpecific: 'another app specific field',\n              credentialSubject: { something: 'nothing', id: 'bar' },\n              issuanceDate: '2009-02-13T23:31:30.000Z',\n              expirationDate: '2009-01-06T08:40:31.000Z',\n              proof: {\n                type: 'JwtProof2020',\n                jwt: 'e30.eyJjb250ZXh0IjoidG9wIGNvbnRleHQiLCJAY29udGV4dCI6WyJhbHNvIHRvcCJdLCJ0eXBlIjpbIkEiXSwiaXNzdWVyIjp7ImNsYWltIjoiaXNzdWVyIGNsYWltIn0sImlzcyI6ImZvbyIsInN1YiI6ImJhciIsInZjIjp7IkBjb250ZXh0IjpbInZjIGNvbnRleHQiXSwidHlwZSI6WyJCIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InNvbWV0aGluZyI6Im5vdGhpbmcifSwiYXBwU3BlY2lmaWMiOiJzb21lIGFwcCBzcGVjaWZpYyBmaWVsZCJ9LCJuYmYiOjEyMzQ1Njc4OTAsImlhdCI6MTExMTExMTExMSwiZXhwIjoxMjMxMjMxMjMxLCJhcHBTcGVjaWZpYyI6ImFub3RoZXIgYXBwIHNwZWNpZmljIGZpZWxkIn0.signature',\n              },\n            },\n          ],\n        })\n      })\n    })\n\n    describe('holder', () => {\n      it('uses the iss property as holder', () => {\n        const result = normalizePresentation({ iss: 'foo' })\n        expect(result).toMatchObject({ holder: 'foo' })\n        expect(result).not.toHaveProperty('iss')\n      })\n\n      it('uses the iss property as holder, keeping original', () => {\n        const result = normalizePresentation({ iss: 'foo' }, false)\n        expect(result).toMatchObject({ holder: 'foo', iss: 'foo' })\n      })\n\n      it('preserves the holder property if present', () => {\n        const result = normalizePresentation({ iss: 'foo', holder: 'bar' })\n        expect(result).toMatchObject({ holder: 'bar', iss: 'foo' })\n      })\n    })\n\n    describe('verifier', () => {\n      it('merges the verifier and aud properties', () => {\n        const result = normalizePresentation({ verifier: ['foo'], aud: ['bar'] })\n        expect(result).toMatchObject({ verifier: ['foo', 'bar'] })\n        expect(result).not.toHaveProperty('aud')\n      })\n\n      it('merges the verifier and aud properties, keeping originals', () => {\n        const result = normalizePresentation({ verifier: ['foo'], aud: ['bar'] }, false)\n        expect(result).toMatchObject({ verifier: ['foo', 'bar'], aud: ['bar'] })\n      })\n\n      it('merges the verifier and aud as arrays', () => {\n        const result = normalizePresentation({ verifier: 'foo', aud: 'bar' })\n        expect(result).toMatchObject({ verifier: ['foo', 'bar'] })\n        expect(result).not.toHaveProperty('aud')\n      })\n\n      it('unique entries in the verifier array', () => {\n        const result = normalizePresentation({ verifier: ['foo', 'bar'], aud: ['bar', 'baz'] })\n        expect(result).toMatchObject({ verifier: ['foo', 'bar', 'baz'] })\n        expect(result).not.toHaveProperty('aud')\n      })\n\n      it('preserves the holder property if present', () => {\n        const result = normalizePresentation({ iss: 'foo', holder: 'bar' })\n        expect(result).toMatchObject({ holder: 'bar', iss: 'foo' })\n      })\n    })\n\n    describe('id', () => {\n      it('uses jti property as id, keeping originals', () => {\n        const result = normalizePresentation({ jti: 'foo' }, false)\n        expect(result).toMatchObject({ id: 'foo', jti: 'foo' })\n      })\n\n      it('uses jti property as id', () => {\n        const result = normalizePresentation({ jti: 'foo' })\n        expect(result).toMatchObject({ id: 'foo' })\n        expect(result).not.toHaveProperty('jti')\n      })\n\n      it('preserves id property if present', () => {\n        const result = normalizePresentation({ jti: 'foo', id: 'bar' })\n        expect(result).toMatchObject({ jti: 'foo', id: 'bar' })\n      })\n    })\n\n    describe('type', () => {\n      it('merges type arrays, keeping original', () => {\n        const result = normalizePresentation({ type: ['foo'], vp: { type: ['bar'] } }, false)\n        expect(result).toMatchObject({ type: ['foo', 'bar'], vp: { type: ['bar'] } })\n      })\n\n      it('merges type arrays', () => {\n        const result = normalizePresentation({ type: ['foo'], vp: { type: ['bar'] } })\n        expect(result).toMatchObject({ type: ['foo', 'bar'] })\n        expect(result).not.toHaveProperty('vp')\n      })\n\n      it('merges type arrays for non-array types', () => {\n        const result = normalizePresentation({ type: 'foo', vp: { type: 'bar' } })\n        expect(result).toMatchObject({ type: ['foo', 'bar'] })\n        expect(result).not.toHaveProperty('vp')\n      })\n\n      it('unique entries in type array', () => {\n        const result = normalizePresentation({ type: ['foo', 'bar'], vp: { type: ['bar', 'baz'] } })\n        expect(result).toMatchObject({ type: ['foo', 'bar', 'baz'] })\n      })\n    })\n\n    describe('@context', () => {\n      it('merges @context arrays, keeping originals', () => {\n        const result = normalizePresentation(\n          { context: ['foo'], '@context': ['bar'], vp: { '@context': ['baz'] } },\n          false\n        )\n        expect(result).toMatchObject({\n          '@context': ['foo', 'bar', 'baz'],\n          context: ['foo'],\n          vp: { '@context': ['baz'] },\n        })\n      })\n\n      it('merges @context arrays', () => {\n        const result = normalizePresentation({ context: ['foo'], '@context': ['bar'], vp: { '@context': ['baz'] } })\n        expect(result).toMatchObject({ '@context': ['foo', 'bar', 'baz'] })\n        expect(result).not.toHaveProperty('vp')\n        expect(result).not.toHaveProperty('context')\n      })\n\n      it('merges @context arrays for non-array contexts', () => {\n        const result = normalizePresentation({ '@context': 'foo', context: 'bar', vp: { '@context': 'baz' } })\n        expect(result).toMatchObject({ '@context': ['bar', 'foo', 'baz'] })\n        expect(result).not.toHaveProperty('vp')\n        expect(result).not.toHaveProperty('context')\n      })\n\n      it('unique entries in @context array', () => {\n        const result = normalizePresentation({\n          '@context': ['foo', 'bar'],\n          context: ['bar', 'baz', undefined, null],\n          vp: { '@context': ['bar', 'baz', 'bak'], type: [], verifiableCredential: [] },\n        })\n        expect(result).toMatchObject({ '@context': ['bar', 'baz', 'foo', 'bak'] })\n      })\n    })\n\n    describe('issuanceDate', () => {\n      it('keeps issuanceDate property when present', () => {\n        const result = normalizePresentation({ issuanceDate: 'yesterday', nbf: 1234567890, iat: 1111111111 })\n        expect(result).toMatchObject({ issuanceDate: 'yesterday', nbf: 1234567890, iat: 1111111111 })\n      })\n\n      it('uses nbf as issuanceDate when present', () => {\n        const result = normalizePresentation({ nbf: 1234567890, iat: 1111111111 })\n        expect(result).toMatchObject({ issuanceDate: '2009-02-13T23:31:30.000Z', iat: 1111111111 })\n        expect(result).not.toHaveProperty('nbf')\n      })\n\n      it('uses nbf as issuanceDate when present, keeping original', () => {\n        const result = normalizePresentation({ nbf: 1234567890, iat: 1111111111 }, false)\n        expect(result).toMatchObject({ issuanceDate: '2009-02-13T23:31:30.000Z', iat: 1111111111, nbf: 1234567890 })\n      })\n\n      it('uses iat as issuanceDate when no nbf and no issuanceDate present', () => {\n        const result = normalizePresentation({ iat: 1111111111 })\n        expect(result).toMatchObject({ issuanceDate: '2005-03-18T01:58:31.000Z' })\n        expect(result).not.toHaveProperty('iat')\n      })\n    })\n\n    describe('expirationDate', () => {\n      it('keeps expirationDate property when present', () => {\n        const result = normalizePresentation({ expirationDate: 'tomorrow', exp: 1222222222 })\n        expect(result).toMatchObject({ expirationDate: 'tomorrow', exp: 1222222222 })\n      })\n\n      it('uses exp as issuanceDate when present, keeping original', () => {\n        const result = normalizePresentation({ exp: 1222222222 }, false)\n        expect(result).toMatchObject({ expirationDate: '2008-09-24T02:10:22.000Z', exp: 1222222222 })\n      })\n\n      it('uses exp as issuanceDate when present', () => {\n        const result = normalizePresentation({ exp: 1222222222 })\n        expect(result).toMatchObject({ expirationDate: '2008-09-24T02:10:22.000Z' })\n        expect(result).not.toHaveProperty('exp')\n      })\n    })\n\n    describe('JWT payload', () => {\n      it('rejects unknown JSON string payload', () => {\n        expect(() => {\n          normalizePresentation('aaa')\n        }).toThrowError(/unknown presentation format/)\n      })\n\n      it('rejects malformed JWT string payload 1', () => {\n        expect(() => {\n          normalizePresentation('a.b.c')\n        }).toThrowError(/unknown presentation format/)\n      })\n\n      it('rejects malformed JWT string payload 2', () => {\n        expect(() => {\n          normalizePresentation('aaa.b.c')\n        }).toThrowError(/unknown presentation format/)\n      })\n    })\n  })\n\n  describe('transform W3C/JWT VP => JWT payload', () => {\n    it('passes through empty payload with empty vp field', () => {\n      const result = transformPresentationInput({})\n      expect(result).toMatchObject({ vp: {} })\n    })\n\n    it('passes through app specific properties', () => {\n      const result = transformPresentationInput({ foo: 'bar' })\n      expect(result).toMatchObject({ foo: 'bar' })\n    })\n\n    it('passes through app specific vp properties', () => {\n      const result = transformPresentationInput({ vp: { foo: 'bar' } })\n      expect(result).toMatchObject({ vp: { foo: 'bar' } })\n    })\n\n    it('transforms to a valid payload, when keeping originals', () => {\n      const presentation: PresentationPayload = {\n        '@context': ['https://www.w3.org/2018/credentials/v1'],\n        type: ['VerifiablePresentation', 'PublicProfile'],\n        holder: 'did:example:123',\n        verifier: ['did:example:234'],\n        issuanceDate: new Date().toISOString(),\n        expirationDate: new Date(Date.now() + 365 * 24 * 3600 * 1000).toISOString(),\n        id: 'vp1',\n        verifiableCredential: ['header.payload.signature'],\n      }\n      const transformed = transformPresentationInput(presentation, false)\n      expect(() => {\n        validateJwtPresentationPayload(transformed)\n      }).not.toThrow()\n    })\n\n    it('transforms to a valid payload', () => {\n      const presentation: PresentationPayload = {\n        '@context': ['https://www.w3.org/2018/credentials/v1'],\n        type: ['VerifiablePresentation', 'PublicProfile'],\n        holder: 'did:example:123',\n        verifier: ['did:example:234'],\n        issuanceDate: new Date().toISOString(),\n        expirationDate: new Date(Date.now() + 365 * 24 * 3600 * 1000).toISOString(),\n        id: 'vp1',\n        verifiableCredential: ['header.payload.signature'],\n      }\n      const transformed = transformPresentationInput(presentation)\n      expect(() => {\n        validateJwtPresentationPayload(transformed)\n      }).not.toThrow()\n    })\n\n    describe('transformPresentationInput deep copy', () => {\n      it('keeps input.context', () => {\n        const input = { context: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.@context', () => {\n        const input = { '@context': ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.vp.@context', () => {\n        const input = { context: 'bar', vp: { '@context': ['foo'] } }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.type', () => {\n        const input = { type: ['foo'] }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.vp.type', () => {\n        const input = { type: 'bar', vp: { type: ['foo'] } }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.id', () => {\n        const input = { id: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.holder', () => {\n        const input = { holder: 'foo' }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.verifier', () => {\n        const input = { verifier: 'foo', aud: 'bar' }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.issuanceDate', () => {\n        const input = { issuanceDate: new Date().toISOString() }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.expirationDate', () => {\n        const input = { expirationDate: new Date().toISOString() }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n      it('keeps input.vp.verifiableCredential', () => {\n        const input = {\n          verifiableCredential:\n            'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA',\n          vp: {\n            verifiableCredential:\n              'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjcwMjQ5NzQsIm5hbWUiOiJib2IiLCJpc3MiOiJkaWQ6ZXRocjoweGYzYmVhYzMwYzQ5OGQ5ZTI2ODY1ZjM0ZmNhYTU3ZGJiOTM1YjBkNzQifQ.2lP3YDOBj9pirxmPAJojQ-q6Rp7w4wA59ZLm19HdqC2leuxlZEQ5w8y0tzpH8n2I25aQ0vVB6j6TimCNLFasqQE',\n          },\n        }\n        const frozen = JSON.stringify(input)\n        const unused = transformPresentationInput(input)\n        expect(JSON.stringify(input)).toBe(frozen)\n      })\n    })\n\n    describe('verifiableCredential', () => {\n      it('merges verifiableCredentials arrays, keeping originals', () => {\n        const result = transformPresentationInput(\n          {\n            verifiableCredential: [{ id: 'foo' }],\n            vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload.signature'] },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          vp: { verifiableCredential: [{ id: 'foo' }, { foo: 'bar' }, 'header.payload.signature'] },\n          verifiableCredential: [{ id: 'foo' }],\n        })\n      })\n\n      it('merges verifiableCredentials arrays', () => {\n        const result = transformPresentationInput({\n          verifiableCredential: [{ id: 'foo' }],\n          vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload.signature'] },\n        })\n        expect(result).toMatchObject({\n          vp: { verifiableCredential: [{ id: 'foo' }, { foo: 'bar' }, 'header.payload.signature'] },\n        })\n        expect(result).not.toHaveProperty('verifiableCredential')\n      })\n\n      it('merges verifiableCredential arrays when not array types, keeping originals', () => {\n        const result = transformPresentationInput(\n          {\n            verifiableCredential: { id: 'foo' },\n            vp: { verifiableCredential: { foo: 'bar' } },\n          },\n          false\n        )\n        expect(result).toMatchObject({\n          vp: { verifiableCredential: [{ id: 'foo' }, { foo: 'bar' }] },\n          verifiableCredential: { id: 'foo' },\n        })\n      })\n\n      it('merges verifiableCredential arrays when not array types', () => {\n        const result = transformPresentationInput({\n          verifiableCredential: { id: 'foo' },\n          vp: { verifiableCredential: { foo: 'bar' } },\n        })\n        expect(result).toMatchObject({ vp: { verifiableCredential: [{ id: 'foo' }, { foo: 'bar' }] } })\n        expect(result).not.toHaveProperty('verifiableCredential')\n      })\n\n      it('condenses JWT credentials', () => {\n        const result = transformPresentationInput({\n          verifiableCredential: { id: 'foo', proof: { jwt: 'header.payload1.signature' } },\n          vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload2.signature'] },\n        })\n        expect(result).toMatchObject({\n          vp: { verifiableCredential: ['header.payload1.signature', { foo: 'bar' }, 'header.payload2.signature'] },\n        })\n        expect(result).not.toHaveProperty('verifiableCredential')\n      })\n\n      it('filters empty credentials', () => {\n        const result = transformPresentationInput({\n          verifiableCredential: undefined,\n          vp: { verifiableCredential: [null, { foo: 'bar' }, 'header.payload2.signature'] },\n        })\n        expect(result).toMatchObject({ vp: { verifiableCredential: [{ foo: 'bar' }, 'header.payload2.signature'] } })\n        expect(result).not.toHaveProperty('verifiableCredential')\n      })\n    })\n\n    describe('context', () => {\n      it('merges @context fields, keeping originals', () => {\n        const result = transformPresentationInput(\n          { context: ['AA'], '@context': ['BB'], vp: { '@context': ['CC'] } },\n          false\n        )\n        expect(result).toMatchObject({ context: ['AA'], '@context': ['BB'], vp: { '@context': ['AA', 'BB', 'CC'] } })\n      })\n\n      it('merges @context fields', () => {\n        const result = transformPresentationInput({ context: ['AA'], '@context': ['BB'], vp: { '@context': ['CC'] } })\n        expect(result).toMatchObject({ vp: { '@context': ['AA', 'BB', 'CC'] } })\n        expect(result).not.toHaveProperty('context')\n        expect(result).not.toHaveProperty('@context')\n      })\n\n      it('merges @context fields when not array types', () => {\n        const result = transformPresentationInput({\n          context: 'AA',\n          '@context': 'BB',\n          vp: { '@context': ['CC'] },\n        })\n        expect(result).toMatchObject({ vp: { '@context': ['AA', 'BB', 'CC'] } })\n        expect(result).not.toHaveProperty('context')\n        expect(result).not.toHaveProperty('@context')\n      })\n\n      it('keeps only unique entries in vp.@context', () => {\n        const result = transformPresentationInput({\n          context: ['AA', 'BB'],\n          '@context': ['BB', 'CC'],\n          vp: { '@context': ['CC', 'DD'] },\n        })\n        expect(result).toMatchObject({ vp: { '@context': ['AA', 'BB', 'CC', 'DD'] } })\n        expect(result).not.toHaveProperty('context')\n        expect(result).not.toHaveProperty('@context')\n      })\n\n      it('removes undefined entries from @context', () => {\n        const result = transformPresentationInput({})\n        expect(result.vp['@context'].length).toBe(0)\n      })\n    })\n\n    describe('type', () => {\n      it('merges type fields, while keeping originals', () => {\n        const result = transformPresentationInput({ type: ['AA'], vp: { type: ['BB'] } }, false)\n        expect(result).toMatchObject({ vp: { type: ['AA', 'BB'] }, type: ['AA'] })\n      })\n\n      it('merges type fields', () => {\n        const result = transformPresentationInput({ type: ['AA'], vp: { type: ['BB'] } })\n        expect(result).toMatchObject({ vp: { type: ['AA', 'BB'] } })\n        expect(result).not.toHaveProperty('type')\n      })\n\n      it('merges type fields when not array types', () => {\n        const result = transformPresentationInput({ type: 'AA', vp: { type: ['BB'] } })\n        expect(result).toMatchObject({ vp: { type: ['AA', 'BB'] } })\n        expect(result).not.toHaveProperty('type')\n      })\n\n      it('keeps only unique entries in vc.type', () => {\n        const result = transformPresentationInput({ type: ['AA', 'BB'], vp: { type: ['BB', 'CC'] } })\n        expect(result).toMatchObject({ vp: { type: ['AA', 'BB', 'CC'] } })\n      })\n\n      it('removes undefined entries from vc.type', () => {\n        const result = transformPresentationInput({})\n        expect(result.vp.type.length).toBe(0)\n      })\n    })\n\n    describe('jti', () => {\n      it('uses the id property as jti, keeping original', () => {\n        const result = transformPresentationInput({ id: 'foo' }, false)\n        expect(result).toMatchObject({ jti: 'foo', id: 'foo' })\n      })\n\n      it('uses the id property as jti', () => {\n        const result = transformPresentationInput({ id: 'foo' })\n        expect(result).toMatchObject({ jti: 'foo' })\n        expect(result).not.toHaveProperty('id')\n      })\n\n      it('preserves jti entry if present', () => {\n        const result = transformPresentationInput({ jti: 'bar', id: 'foo' })\n        expect(result).toMatchObject({ jti: 'bar', id: 'foo' })\n      })\n    })\n\n    describe('issuanceDate', () => {\n      it('transforms the issuanceDate property to nbf, keeping original', () => {\n        const result = transformPresentationInput({ issuanceDate: '2009-02-13T23:31:30.000Z' }, false)\n        expect(result).toMatchObject({ nbf: 1234567890, issuanceDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('transforms the issuanceDate property to nbf', () => {\n        const result = transformPresentationInput({ issuanceDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ nbf: 1234567890 })\n        expect(result).not.toHaveProperty('issuanceDate')\n      })\n\n      it('preserves the issuanceDate property if it fails to be parsed as a Date', () => {\n        const result = transformPresentationInput({ issuanceDate: 'tomorrow' })\n        expect(result).toMatchObject({ issuanceDate: 'tomorrow' })\n      })\n\n      it('preserves nbf entry if present', () => {\n        const result = transformPresentationInput({ nbf: 123, issuanceDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ nbf: 123, issuanceDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('preserves nbf entry if explicitly undefined', () => {\n        const result = transformPresentationInput({ nbf: undefined, issuanceDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ nbf: undefined, issuanceDate: '2009-02-13T23:31:30.000Z' })\n      })\n    })\n\n    describe('expirationDate', () => {\n      it('transforms the expirationDate property to exp, keeping original', () => {\n        const result = transformPresentationInput({ expirationDate: '2009-02-13T23:31:30.000Z' }, false)\n        expect(result).toMatchObject({ exp: 1234567890, expirationDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('transforms the expirationDate property to exp', () => {\n        const result = transformPresentationInput({ expirationDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ exp: 1234567890 })\n        expect(result).not.toHaveProperty('expirationDate')\n      })\n\n      it('preserves the expirationDate property if it fails to be parsed as a Date', () => {\n        const result = transformPresentationInput({ expirationDate: 'tomorrow' })\n        expect(result).toMatchObject({ expirationDate: 'tomorrow' })\n      })\n\n      it('preserves exp entry if present', () => {\n        const result = transformPresentationInput({ exp: 123, expirationDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ exp: 123, expirationDate: '2009-02-13T23:31:30.000Z' })\n      })\n\n      it('preserves exp entry if explicitly undefined', () => {\n        const result = transformPresentationInput({ exp: undefined, expirationDate: '2009-02-13T23:31:30.000Z' })\n        expect(result).toMatchObject({ exp: undefined, expirationDate: '2009-02-13T23:31:30.000Z' })\n      })\n    })\n\n    describe('holder', () => {\n      it('uses holder as iss when of type string, keeping original', () => {\n        const result = transformPresentationInput({ holder: 'foo' }, false)\n        expect(result).toMatchObject({ iss: 'foo', holder: 'foo' })\n      })\n\n      it('uses holder as iss when of type string', () => {\n        const result = transformPresentationInput({ holder: 'foo' })\n        expect(result).toMatchObject({ iss: 'foo' })\n        expect(result).not.toHaveProperty('holder')\n      })\n\n      it('preserves holder property if not string type', () => {\n        const result = transformPresentationInput({ holder: 12 })\n        expect(result).toMatchObject({ holder: 12 })\n      })\n\n      it('preserves holder property if iss is present', () => {\n        const result = transformPresentationInput({ iss: 'foo', holder: 'bar' })\n        expect(result).toMatchObject({ iss: 'foo', holder: 'bar' })\n      })\n    })\n\n    describe('verifier', () => {\n      it('merges verifier and aud props into aud array, keeping original', () => {\n        const result = transformPresentationInput({ verifier: ['foo'], aud: ['bar'] }, false)\n        expect(result).toMatchObject({ aud: ['foo', 'bar'], verifier: ['foo'] })\n      })\n\n      it('merges verifier and aud props into aud array', () => {\n        const result = transformPresentationInput({ verifier: ['foo'], aud: ['bar'] })\n        expect(result).toMatchObject({ aud: ['foo', 'bar'] })\n        expect(result).not.toHaveProperty('verifier')\n      })\n\n      it('merges verifier and aud props into aud array when different types', () => {\n        const result = transformPresentationInput({ verifier: 'foo', aud: 'bar' })\n        expect(result).toMatchObject({ aud: ['foo', 'bar'] })\n      })\n\n      it('filters null or undefined values in aud', () => {\n        const result = transformPresentationInput({ verifier: ['foo', null], aud: ['bar', undefined] as any })\n        expect(result).toMatchObject({ aud: ['foo', 'bar'] })\n      })\n\n      it('unique values in aud', () => {\n        const result = transformPresentationInput({ verifier: ['foo', 'bar'], aud: ['bar', 'baz'] })\n        expect(result).toMatchObject({ aud: ['foo', 'bar', 'baz'] })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/index.mocked.test.ts",
    "content": "import { DEFAULT_CONTEXT, DEFAULT_VC_TYPE, DEFAULT_VP_TYPE } from '../types.js'\nimport type { Issuer } from '../index.js'\nimport { EthrDID } from 'ethr-did'\n\nimport { jest } from '@jest/globals'\n\nimport * as actualValidators from '../validators.js'\n\njest.unstable_mockModule('../validators.js', async () => {\n  return {\n    ...actualValidators,\n    validateTimestamp: jest.fn(),\n    validateVcType: jest.fn(),\n    validateVpType: jest.fn(),\n    validateContext: jest.fn(),\n    validateJwtFormat: jest.fn(),\n    validateCredentialSubject: jest.fn(),\n  }\n})\n\nconst {\n  validateTimestamp,\n  validateVcType,\n  validateVpType,\n  validateContext,\n  validateJwtFormat,\n  validateCredentialSubject,\n} = await import('../validators.js')\n\n// must be done after the unstable_mockModule call to use the mocked version\nconst { createVerifiableCredentialJwt, createVerifiablePresentationJwt } = await import('../index.js')\n\nconst DID_B = 'did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4'\nconst EXTRA_CONTEXT_A = 'https://www.w3.org/2018/credentials/examples/v1'\nconst EXTRA_TYPE_A = 'UniversityDegreeCredential'\nconst VC_JWT =\n  // tslint:disable-next-line: max-line-length\n  'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA'\n\nconst ethrDidIssuer = new EthrDID({\n  identifier: '0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198',\n  privateKey: 'd8b595680851765f38ea5405129244ba3cbad84467d190859f4c8b20c1ff6c75',\n}) as Issuer\n\nconst verifiableCredentialPayload = {\n  sub: DID_B,\n  nbf: 1562950282,\n  vc: {\n    '@context': [DEFAULT_CONTEXT, EXTRA_CONTEXT_A],\n    type: [DEFAULT_VC_TYPE, EXTRA_TYPE_A],\n    credentialSubject: {\n      degree: {\n        type: 'BachelorDegree',\n        name: 'Baccalauréat en musiques numériques',\n      },\n    },\n  },\n}\nconst presentationPayload = {\n  vp: {\n    '@context': [DEFAULT_CONTEXT, EXTRA_CONTEXT_A],\n    type: [DEFAULT_VP_TYPE],\n    verifiableCredential: [VC_JWT],\n  },\n}\ndescribe('createVerifiableCredential', () => {\n  const issuer = ethrDidIssuer\n\n  it('calls functions to validate required fields', async () => {\n    expect.assertions(4)\n    await createVerifiableCredentialJwt(verifiableCredentialPayload, issuer)\n    expect(validateTimestamp).toHaveBeenCalledWith(verifiableCredentialPayload.nbf)\n    expect(validateContext).toHaveBeenCalledWith(verifiableCredentialPayload.vc['@context'])\n    expect(validateVcType).toHaveBeenCalledWith(verifiableCredentialPayload.vc.type)\n    expect(validateCredentialSubject).toHaveBeenCalledWith(verifiableCredentialPayload.vc.credentialSubject)\n  })\n  it('calls functions to validate optional fields if they are present', async () => {\n    expect.assertions(1)\n\n    const timestamp = Math.floor(new Date().getTime())\n    await createVerifiableCredentialJwt({ ...verifiableCredentialPayload, exp: timestamp }, issuer)\n    expect(validateTimestamp).toHaveBeenCalledWith(timestamp)\n  })\n})\n\ndescribe('createPresentation', () => {\n  const holder = ethrDidIssuer\n\n  it('calls functions to validate required fields', async () => {\n    expect.assertions(2 + presentationPayload.vp.verifiableCredential.length)\n\n    await createVerifiablePresentationJwt(presentationPayload, holder)\n    expect(validateContext).toHaveBeenCalledWith(presentationPayload.vp['@context'])\n    expect(validateVpType).toHaveBeenCalledWith(presentationPayload.vp.type)\n    for (const vc of presentationPayload.vp.verifiableCredential) {\n      expect(validateJwtFormat).toHaveBeenCalledWith(vc)\n    }\n  })\n\n  it('calls functions to validate optional fields if they are present', async () => {\n    expect.assertions(1)\n    const timestamp = Math.floor(new Date().getTime())\n\n    await createVerifiablePresentationJwt(\n      {\n        ...presentationPayload,\n        exp: timestamp,\n      },\n      holder\n    )\n    expect(validateTimestamp).toHaveBeenCalledWith(timestamp)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/index.test.ts",
    "content": "import { EthrDID } from 'ethr-did'\nimport type { Issuer, JwtCredentialPayload } from '../index.js'\nimport {\n  createVerifiableCredentialJwt,\n  createVerifiablePresentationJwt,\n  verifyCredential,\n  verifyPresentation,\n  verifyPresentationPayloadOptions,\n} from '../index.js'\n\nimport { bytesToBase64url, decodeJWT, ES256KSigner, hexToBytes } from 'did-jwt'\nimport type { Resolvable } from 'did-resolver'\nimport {\n  type CreatePresentationOptions,\n  DEFAULT_CONTEXT,\n  DEFAULT_VC_TYPE,\n  DEFAULT_VP_TYPE,\n  type VerifyPresentationOptions,\n} from '../types.js'\nimport { secp256k1 } from '@noble/curves/secp256k1'\n\nimport { jest } from '@jest/globals'\n\nconst DID_B = 'did:ethr:0x435df3eda57154cf8cf7926079881f2912f54db4'\nconst EXTRA_CONTEXT_A = 'https://www.w3.org/2018/credentials/examples/v1'\nconst EXTRA_TYPE_A = 'UniversityDegreeCredential'\nconst VC_JWT =\n  // tslint:disable-next-line: max-line-length\n  'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA'\nconst PRESENTATION_JWT =\n  // tslint:disable-next-line: max-line-length\n  'eyJhbGciOiJFUzI1NkstUiIsInR5cCI6IkpXVCJ9.eyJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZVByZXNlbnRhdGlvbiJdLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpGVXpJMU5rc3RVaUo5LmV5SnBZWFFpT2pFMU5qWTVNak15Tmprc0luTjFZaUk2SW1ScFpEcGxkR2h5T2pCNE5ETTFaR1l6WldSaE5UY3hOVFJqWmpoalpqYzVNall3TnprNE9ERm1Namt4TW1ZMU5HUmlOQ0lzSW01aVppSTZNVFUyTWprMU1ESTRNaXdpZG1NaU9uc2lRR052Ym5SbGVIUWlPbHNpYUhSMGNITTZMeTkzZDNjdWR6TXViM0puTHpJd01UZ3ZZM0psWkdWdWRHbGhiSE12ZGpFaUxDSm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OWxlR0Z0Y0d4bGN5OTJNU0pkTENKMGVYQmxJanBiSWxabGNtbG1hV0ZpYkdWRGNtVmtaVzUwYVdGc0lpd2lWVzVwZG1WeWMybDBlVVJsWjNKbFpVTnlaV1JsYm5ScFlXd2lYU3dpWTNKbFpHVnVkR2xoYkZOMVltcGxZM1FpT25zaVpHVm5jbVZsSWpwN0luUjVjR1VpT2lKQ1lXTm9aV3h2Y2tSbFozSmxaU0lzSW01aGJXVWlPaUpDWVdOallXeGhkWExEcVdGMElHVnVJRzExYzJseGRXVnpJRzUxYmNPcGNtbHhkV1Z6SW4xOWZTd2lhWE56SWpvaVpHbGtPbVYwYUhJNk1IaG1NVEl6TW1ZNE5EQm1NMkZrTjJReU0yWmpaR0ZoT0RSa05tTTJObVJoWXpJMFpXWmlNVGs0SW4wLnJGUlpVQ3czR3UwRV9JNVpKYnJicHVIVjFKTkF3cFhhaUZadUo1OWlKLVROcXVmcjRjdUdDQkVFQ0ZiZ1FGLWxwTm01MWNxU3gzWTJJZFdhVXBhdEpRQSJdfSwiaXNzIjoiZGlkOmV0aHI6MHhGMTIzMkY4NDBmM2FEN2QyM0ZjRGFBODRkNkM2NmRhYzI0RUZiMTk4In0.oAZju9YNgYQz_RELnlK0KPizXvP90le4Tw7kRqjrXAzY3ZcRMS6EJt58iC3wehVnt680FO0HvFXDrLk3eLfY8QA'\n\nconst ethrDidIssuer = new EthrDID({\n  identifier: '0xf1232f840f3ad7d23fcdaa84d6c66dac24efb198',\n  privateKey: 'd8b595680851765f38ea5405129244ba3cbad84467d190859f4c8b20c1ff6c75',\n}) as Issuer\n\nconst verifiableCredentialPayload = {\n  sub: DID_B,\n  nbf: 1562950282,\n  vc: {\n    '@context': [DEFAULT_CONTEXT, EXTRA_CONTEXT_A],\n    type: [DEFAULT_VC_TYPE, EXTRA_TYPE_A],\n    credentialSubject: {\n      degree: {\n        type: 'BachelorDegree',\n        name: 'Baccalauréat en musiques numériques',\n      },\n    },\n  },\n}\nconst presentationPayload = {\n  vp: {\n    '@context': [DEFAULT_CONTEXT, EXTRA_CONTEXT_A],\n    type: [DEFAULT_VP_TYPE],\n    verifiableCredential: [VC_JWT],\n  },\n}\nconst resolver: Resolvable = {\n  resolve: (did: string) =>\n    Promise.resolve({\n      didDocument: {\n        '@context': 'https://w3id.org/did/v1',\n        id: `${did}`,\n        publicKey: [\n          {\n            id: `${did}#owner`,\n            type: 'EcdsaSecp256k1RecoveryMethod2020',\n            ethereumAddress: `${did.substring(9)}`,\n            controller: did,\n          },\n        ],\n      },\n      didDocumentMetadata: {},\n      didResolutionMetadata: {},\n    }),\n}\n\nbeforeEach(() => {\n  jest.resetAllMocks()\n})\n\ndescribe('createVerifiableCredential', () => {\n  const issuer = ethrDidIssuer\n  it('creates a valid Verifiable Credential JWT with required fields', async () => {\n    expect.assertions(1)\n    const vcJwt = await createVerifiableCredentialJwt(verifiableCredentialPayload, issuer)\n    const decodedVc = await decodeJWT(vcJwt)\n    const { iat, ...payload } = decodedVc.payload\n    expect(payload).toMatchSnapshot()\n  })\n  it('creates a valid Verifiable Credential JWT with extra optional fields', async () => {\n    expect.assertions(1)\n    const vcJwt = await createVerifiableCredentialJwt({ ...verifiableCredentialPayload, extra: 42 }, issuer)\n    const decodedVc = await decodeJWT(vcJwt)\n    const { iat, ...payload } = decodedVc.payload\n    expect(payload).toMatchSnapshot()\n  })\n  it('creates a Verifiable Credential JWT with custom JWT alg', async () => {\n    expect.assertions(1)\n    const customIssuer = { ...issuer, alg: 'ES256K-R' }\n    const vcJwt = await createVerifiableCredentialJwt({ ...verifiableCredentialPayload, extra: 42 }, customIssuer)\n    const decodedVc = await decodeJWT(vcJwt)\n    expect(decodedVc.header).toEqual({ alg: 'ES256K-R', typ: 'JWT' })\n  })\n  it('creates a Verifiable Credential JWT with custom JWT header fields', async () => {\n    expect.assertions(1)\n    const vcJwt = await createVerifiableCredentialJwt({ ...verifiableCredentialPayload, extra: 42 }, issuer, {\n      header: { alg: 'ES256K-R', custom: 'field' },\n    })\n    const decodedVc = await decodeJWT(vcJwt)\n    expect(decodedVc.header).toEqual({ alg: 'ES256K-R', custom: 'field', typ: 'JWT' })\n  })\n  it('creates a Verifiable Credential JWT with exp field using expiresIn of did-jwt', async () => {\n    expect.assertions(1)\n    const nbf = Math.floor(Date.now() / 1000)\n    const expiresIn = 86400\n    const vcJwt = await createVerifiableCredentialJwt({ ...verifiableCredentialPayload, nbf }, issuer, {\n      expiresIn,\n      header: { alg: 'ES256K-R' },\n    })\n    const decodedVc = await decodeJWT(vcJwt)\n    expect(decodedVc.payload.exp).toEqual(nbf + expiresIn)\n  })\n})\n\ndescribe('createPresentation', () => {\n  const holder = ethrDidIssuer\n\n  it('creates a valid Presentation JWT with required fields', async () => {\n    expect.assertions(1)\n    const presentationJwt = await createVerifiablePresentationJwt(presentationPayload, holder)\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n  })\n\n  it('creates a valid Presentation JWT with extra optional fields', async () => {\n    expect.assertions(2)\n    const presentationJwt = await createVerifiablePresentationJwt({ ...presentationPayload, extra: 42 }, holder)\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n    expect(payload.extra).toBe(42)\n  })\n\n  it('creates a valid Presentation JWT with domain option', async () => {\n    expect.assertions(4)\n    const options: CreatePresentationOptions = {\n      domain: 'TEST_DOMAIN',\n    }\n\n    const presentationJwt = await createVerifiablePresentationJwt(\n      { ...presentationPayload, extra: 42 },\n      holder,\n      options\n    )\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n    expect(payload).toHaveProperty('aud', ['TEST_DOMAIN'])\n    expect(payload).toHaveProperty('extra', 42)\n    expect(payload).not.toHaveProperty('nonce')\n  })\n\n  it('creates a valid Presentation JWT with domain option and existing aud', async () => {\n    expect.assertions(3)\n    const options: CreatePresentationOptions = {\n      domain: 'TEST_DOMAIN',\n    }\n\n    const presentationJwt = await createVerifiablePresentationJwt(\n      { ...presentationPayload, aud: ['EXISTING_AUD'] },\n      holder,\n      options\n    )\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n    expect(payload).toHaveProperty('aud', ['TEST_DOMAIN', 'EXISTING_AUD'])\n    expect(payload).not.toHaveProperty('nonce')\n  })\n\n  it('creates a valid Presentation JWT with challenge option', async () => {\n    expect.assertions(4)\n    const options: CreatePresentationOptions = {\n      challenge: 'TEST_CHALLENGE',\n    }\n\n    const presentationJwt = await createVerifiablePresentationJwt(\n      { ...presentationPayload, extra: 42 },\n      holder,\n      options\n    )\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n    expect(payload).not.toHaveProperty('aud')\n    expect(payload).toHaveProperty('nonce', 'TEST_CHALLENGE')\n    expect(payload).toHaveProperty('extra', 42)\n  })\n\n  it('creates a Presentation JWT with custom holder alg', async () => {\n    const customHolder = { ...holder, alg: 'ES256K-R' }\n    expect.assertions(1)\n    const presentationJwt = await createVerifiablePresentationJwt({ ...presentationPayload, extra: 42 }, customHolder)\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    expect(decodedPresentation.header).toEqual({ alg: 'ES256K-R', typ: 'JWT' })\n  })\n\n  it('creates a Presentation JWT with custom header options', async () => {\n    expect.assertions(1)\n    const options: CreatePresentationOptions = {\n      header: {\n        alg: 'ES256K-R',\n        custom: 'field',\n      },\n    }\n\n    const presentationJwt = await createVerifiablePresentationJwt(\n      { ...presentationPayload, extra: 42 },\n      holder,\n      options\n    )\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    expect(decodedPresentation.header).toEqual({\n      alg: 'ES256K-R',\n      custom: 'field',\n      typ: 'JWT',\n    })\n  })\n\n  it('creates a valid Presentation JWT and does not overwrite an existing nonce property', async () => {\n    expect.assertions(3)\n    const options: CreatePresentationOptions = {\n      challenge: 'TEST_CHALLENGE',\n    }\n\n    const presentationJwt = await createVerifiablePresentationJwt(\n      { ...presentationPayload, nonce: 'EXISTING_NONCE' },\n      holder,\n      options\n    )\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n    expect(payload).not.toHaveProperty('aud')\n    expect(payload).toHaveProperty('nonce', 'EXISTING_NONCE')\n  })\n  it('creates a valid Presentation JWT if there are no credentials', async () => {\n    expect.assertions(1)\n    const presentationJwt = await createVerifiablePresentationJwt(\n      {\n        ...presentationPayload,\n        vp: {\n          '@context': presentationPayload.vp['@context'],\n          type: presentationPayload.vp.type,\n        },\n      },\n      holder\n    )\n    const decodedPresentation = await decodeJWT(presentationJwt)\n    const { iat, ...payload } = decodedPresentation.payload\n    expect(payload).toMatchSnapshot()\n  })\n})\n\ndescribe('verifyCredential', () => {\n  it('verifies a valid Verifiable Credential', async () => {\n    expect.assertions(2)\n    const verified = await verifyCredential(VC_JWT, resolver)\n    expect(verified.payload.vc).toBeDefined()\n    expect(verified.verifiableCredential).toBeDefined()\n  })\n\n  it('verifies and converts a legacy format attestation into a Verifiable Credential', async () => {\n    expect.assertions(1)\n    // tslint:disable-next-line: max-line-length\n    const LEGACY_FORMAT_ATTESTATION =\n      'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjM4MjQ4MDksImV4cCI6OTk2Mjk1MDI4Miwic3ViIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4IiwiY2xhaW0iOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19LCJpc3MiOiJkaWQ6ZXRocjoweGYzYmVhYzMwYzQ5OGQ5ZTI2ODY1ZjM0ZmNhYTU3ZGJiOTM1YjBkNzQifQ.OsKmaxoA2pt3_ixWK61BaMDc072g2PymBX_CCUSo-irvtIRUP5qBCcerhpASe5hOcTg5nNpNg0XYXnqyF9I4XwE'\n    const verified = await verifyCredential(LEGACY_FORMAT_ATTESTATION, resolver, { policies: { format: false } })\n    // expect(verified.payload.vc).toBeDefined()\n    expect(verified.verifiableCredential).toBeDefined()\n  })\n\n  it('rejects an invalid JWT', () => {\n    expect(verifyCredential('not a jwt', resolver)).rejects.toThrow()\n  })\n})\n\ndescribe('verifyPresentation', () => {\n  it('verifies a valid Presentation', async () => {\n    expect.assertions(2)\n    const verified = await verifyPresentation(PRESENTATION_JWT, resolver)\n    expect(verified.payload.vp).toBeDefined()\n    expect(verified.verifiablePresentation).toBeDefined()\n  })\n\n  it('rejects a Presentation without matching challenge', () => {\n    const options: VerifyPresentationOptions = {\n      challenge: 'TEST_CHALLENGE',\n    }\n    expect(verifyPresentation(PRESENTATION_JWT, resolver, options)).rejects.toThrow(/^auth_error:.*/)\n  })\n\n  it('rejects a Presentation without matching domain', () => {\n    const options: VerifyPresentationOptions = {\n      domain: 'TEST_DOMAIN',\n    }\n    expect(verifyPresentation(PRESENTATION_JWT, resolver, options)).rejects.toThrow(/^auth_error:.*/)\n  })\n\n  it('rejects an invalid JWT', () => {\n    expect(verifyPresentation('not a jwt', resolver)).rejects.toThrow()\n  })\n})\n\ndescribe('verifyPresentationPayloadOptions', () => {\n  it('verifies a payload with no options present', () => {\n    expect(() => verifyPresentationPayloadOptions(presentationPayload, {})).not.toThrow()\n  })\n\n  it('verifies a payload with challenge options present', () => {\n    const options: VerifyPresentationOptions = {\n      challenge: 'TEST_CHALLENGE',\n    }\n\n    const payload = { nonce: 'TEST_CHALLENGE', ...presentationPayload }\n\n    expect(() => verifyPresentationPayloadOptions(payload, options)).not.toThrow()\n  })\n\n  it('verifies a payload with domain options present (single aud)', () => {\n    const options: VerifyPresentationOptions = {\n      domain: 'TEST_DOMAIN',\n    }\n\n    const payload = { aud: 'TEST_DOMAIN', ...presentationPayload }\n\n    expect(() => verifyPresentationPayloadOptions(payload, options)).not.toThrow()\n  })\n\n  it('verifies a payload with domain options present (array aud)', () => {\n    const options: VerifyPresentationOptions = {\n      domain: 'TEST_DOMAIN',\n    }\n\n    const payload = { aud: ['OTHER_AUD', 'TEST_DOMAIN'], ...presentationPayload }\n\n    expect(() => verifyPresentationPayloadOptions(payload, options)).not.toThrow()\n  })\n\n  it('throws if payload is missing challenge', () => {\n    const options: VerifyPresentationOptions = {\n      challenge: 'TEST_CHALLENGE',\n    }\n    expect(() => verifyPresentationPayloadOptions(presentationPayload, options)).toThrow(/^auth_error:.*/)\n  })\n\n  it('throws if payload is missing domain', () => {\n    const options: VerifyPresentationOptions = {\n      domain: 'TEST_DOMAIN',\n    }\n    expect(() => verifyPresentationPayloadOptions(presentationPayload, options)).toThrow(/^auth_error:.*/)\n  })\n})\n\ndescribe('github #98', () => {\n  it('verifies a JWT issued by a DID with publicKeyJwk', async () => {\n    const did = `did:ion:long-form-mock`\n    const privateKeyHex = '278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f'\n    const pubKeyBytes = secp256k1.getPublicKey(privateKeyHex, false)\n    const point = secp256k1.ProjectivePoint.fromHex(pubKeyBytes).toAffine()\n    const publicKeyJwk = {\n      kty: 'EC',\n      crv: 'secp256k1',\n      x: bytesToBase64url(hexToBytes(point.x.toString(16))),\n      y: bytesToBase64url(hexToBytes(point.y.toString(16))),\n    }\n\n    const localResolver: Resolvable = {\n      resolve: (did: string) =>\n        Promise.resolve({\n          '@context': 'https://w3id.org/did-resolution/v1',\n          didDocument: {\n            id: did,\n            '@context': ['https://www.w3.org/ns/did/v1'],\n            verificationMethod: [\n              {\n                id: '#key-1',\n                controller: '',\n                type: 'EcdsaSecp256k1VerificationKey2019',\n                publicKeyJwk,\n              },\n            ],\n            authentication: ['#key-1'],\n          },\n          didDocumentMetadata: {},\n          didResolutionMetadata: {},\n        }),\n    }\n\n    const issuer: Issuer = {\n      did,\n      signer: ES256KSigner(hexToBytes(privateKeyHex), false),\n      alg: 'ES256K',\n    }\n\n    const vcPayload: JwtCredentialPayload = {\n      nbf: 1562950282,\n      vc: {\n        '@context': ['https://www.w3.org/2018/credentials/v1'],\n        type: ['VerifiableCredential'],\n        credentialSubject: {\n          degree: {\n            type: 'Stemgerechtigd',\n            name: 'Je mag stemmen',\n          },\n        },\n      },\n    }\n\n    const vcJwt = await createVerifiableCredentialJwt(vcPayload, issuer, { header: { alg: 'ES256K' } })\n\n    const verifiedVC = await verifyCredential(vcJwt, localResolver, { header: { alg: 'ES256K' } })\n    expect(verifiedVC.issuer).toEqual('did:ion:long-form-mock')\n  })\n})\n"
  },
  {
    "path": "src/__tests__/validators.test.ts",
    "content": "import * as validators from '../validators'\nimport { DEFAULT_CONTEXT, DEFAULT_VC_TYPE, DEFAULT_VP_TYPE } from '../types'\nexport const EXTRA_CONTEXT_A = 'https://www.w3.org/2018/credentials/examples/v1'\nexport const EXTRA_CONTEXT_B = 'custom vc context'\nexport const EXTRA_TYPE_A = 'UniversityDegreeCredential'\nexport const EXTRA_TYPE_B = 'custom vc type'\nexport const VC_JWT =\n  // tslint:disable-next-line: max-line-length\n  'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE1NjY5MjMyNjksInN1YiI6ImRpZDpldGhyOjB4NDM1ZGYzZWRhNTcxNTRjZjhjZjc5MjYwNzk4ODFmMjkxMmY1NGRiNCIsIm5iZiI6MTU2Mjk1MDI4MiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiZGVncmVlIjp7InR5cGUiOiJCYWNoZWxvckRlZ3JlZSIsIm5hbWUiOiJCYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzIn19fSwiaXNzIjoiZGlkOmV0aHI6MHhmMTIzMmY4NDBmM2FkN2QyM2ZjZGFhODRkNmM2NmRhYzI0ZWZiMTk4In0.rFRZUCw3Gu0E_I5ZJbrbpuHV1JNAwpXaiFZuJ59iJ-TNqufr4cuGCBEECFbgQF-lpNm51cqSx3Y2IdWaUpatJQA'\n\ndescribe('validators', () => {\n  describe('validateTimestamp', () => {\n    it('does not throw if the value is a valid unix timestamp in seconds', () => {\n      expect(() => validators.validateTimestamp(Math.floor(new Date().getTime() / 1000))).not.toThrow()\n    })\n    it('throws a TypeError if the value is a millisecond timestamp', () => {\n      expect(() => validators.validateTimestamp(new Date().getTime())).toThrow(/^schema_error:.*/)\n    })\n    it('throws a TypeError if the value is not an integer', () => {\n      expect(() => validators.validateTimestamp(1653060380105 / 1000)).toThrow(/^schema_error:.*/)\n    })\n  })\n\n  describe('validateContext', () => {\n    it('does not throw if the value contains only the default context', () => {\n      expect(() => validators.validateContext([DEFAULT_CONTEXT])).not.toThrow()\n    })\n    it('does not throw if the value contains the default context and some user-defined ones', () => {\n      expect(() => validators.validateContext([DEFAULT_CONTEXT, EXTRA_CONTEXT_A, EXTRA_CONTEXT_B])).not.toThrow()\n    })\n    it('throws a TypeError the value contains no contexts', () => {\n      expect(() => validators.validateContext([])).toThrow(/^schema_error:.*/)\n    })\n    it('throws a TypeError the value is missing the default context', () => {\n      expect(() => validators.validateContext([EXTRA_CONTEXT_A, EXTRA_CONTEXT_B])).toThrow(/^schema_error:.*/)\n    })\n  })\n\n  describe('validateVcType', () => {\n    it('does not throw if the value contains only the default type', () => {\n      expect(() => validators.validateVcType([DEFAULT_VC_TYPE])).not.toThrow()\n    })\n    it('does not throw if the value contains the default type and some user-defined ones', () => {\n      expect(() => validators.validateVcType([DEFAULT_VC_TYPE, EXTRA_TYPE_A, EXTRA_TYPE_B])).not.toThrow()\n    })\n    it('throws a TypeError the value contains no types', () => {\n      expect(() => validators.validateVcType([])).toThrow(/^schema_error:.*/)\n    })\n    it('throws a TypeError the value is missing the default type', () => {\n      expect(() => validators.validateVcType([EXTRA_TYPE_A, EXTRA_TYPE_B])).toThrow(/^schema_error:.*/)\n    })\n  })\n\n  describe('validateVpType', () => {\n    it('does not throw if the value contains only the default type', () => {\n      expect(() => validators.validateVpType([DEFAULT_VP_TYPE])).not.toThrow()\n    })\n    it('does not throw if the value contains the default type and some user-defined ones', () => {\n      expect(() => validators.validateVpType([DEFAULT_VP_TYPE, EXTRA_TYPE_A, EXTRA_TYPE_B])).not.toThrow()\n    })\n    it('throws a TypeError the value contains no types', () => {\n      expect(() => validators.validateVpType([])).toThrow(/^schema_error:.*/)\n    })\n    it('throws a TypeError the value is missing the default type', () => {\n      expect(() => validators.validateVpType([EXTRA_TYPE_A, EXTRA_TYPE_B])).toThrow(/^schema_error:.*/)\n    })\n  })\n\n  describe('validateJwtFormat', () => {\n    it('does not throw if the value is a valid JWT format', () => {\n      expect(() => validators.validateJwtFormat(VC_JWT)).not.toThrow()\n    })\n    it('throws a TypeError if the value is not a valid JWT format', () => {\n      expect(() => validators.validateJwtFormat('not a jwt')).toThrow(/^format_error:.*/)\n    })\n  })\n\n  describe('validateCredentialSubject', () => {\n    it('does not throw if the value is an object with at least one attribute', () => {\n      expect(() => validators.validateCredentialSubject({ name: 'test' })).not.toThrow()\n    })\n    it('throws a TypeError if the value is an object with no attributes', () => {\n      expect(() => validators.validateCredentialSubject({})).toThrow(/^schema_error:.*/)\n    })\n  })\n})\n"
  },
  {
    "path": "src/converters.ts",
    "content": "import {\n  VerifiableCredential,\n  JWT,\n  JwtPresentationPayload,\n  JwtCredentialPayload,\n  JWT_FORMAT,\n  DEFAULT_JWT_PROOF_TYPE,\n  DEFAULT_CONTEXT,\n  DEFAULT_VC_TYPE,\n  CredentialPayload,\n  W3CCredential,\n  Verifiable,\n  PresentationPayload,\n  W3CPresentation,\n} from './types'\nimport { decodeJWT } from 'did-jwt'\n\n/**\n * Additional W3C VC fields:\n * These are defined as optional top-level properties in the W3C spec but are not mapped to top-level JWT names,\n * so they should be moved inside the \"vc\" object when transforming to a JWT.\n * Conversely, they should be moved out of the \"vc\" object when transforming from a JWT to W3C JSON.\n */\nconst additionalPropNames = ['evidence', 'termsOfUse', 'refreshService', 'credentialSchema', 'credentialStatus']\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function asArray(arg: any | any[]): any[] {\n  return Array.isArray(arg) ? arg : [arg]\n}\n\nfunction deepCopy<T>(source: T): T {\n  return Array.isArray(source)\n    ? source.map((item) => deepCopy(item))\n    : source instanceof Date\n      ? new Date(source.getTime())\n      : source && typeof source === 'object'\n        ? Object.getOwnPropertyNames(source).reduce(\n            (o, prop) => {\n              Object.defineProperty(\n                o,\n                prop,\n                Object.getOwnPropertyDescriptor(source, prop) as NonNullable<PropertyDescriptor>\n              )\n              o[prop] = deepCopy(source[prop as keyof T])\n              return o\n            },\n            Object.create(Object.getPrototypeOf(source))\n          )\n        : (source as T)\n}\n\nexport function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {\n  return value !== null && value !== undefined\n}\n\nfunction cleanUndefined<T>(input: T): T {\n  if (typeof input !== 'object' || input === null) {\n    return input\n  }\n  const obj = { ...input }\n  Object.keys(obj).forEach((key) => obj[key as keyof T] === undefined && delete obj[key as keyof T])\n  return obj\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isLegacyAttestationFormat(payload: Record<string, any>): boolean {\n  // payload is an object and has all the required fields of old attestation format\n  return typeof payload === 'object' && payload.sub && payload.iss && payload.claim && payload.iat\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function attestationToVcFormat(payload: Record<string, any>): JwtCredentialPayload {\n  const { iat, nbf, claim, vc, ...rest } = payload\n  const result: JwtCredentialPayload = {\n    ...rest,\n    nbf: nbf ? nbf : iat,\n    vc: {\n      '@context': [DEFAULT_CONTEXT],\n      type: [DEFAULT_VC_TYPE],\n      credentialSubject: claim,\n    },\n  }\n  if (vc) payload.issVc = vc\n  return result\n}\n\nfunction normalizeJwtCredentialPayload(\n  input: Partial<JwtCredentialPayload>,\n  removeOriginalFields = true\n): W3CCredential {\n  let result: Partial<CredentialPayload> = deepCopy(input)\n\n  if (isLegacyAttestationFormat(input)) {\n    result = attestationToVcFormat(input)\n  }\n\n  // FIXME: handle case when credentialSubject(s) are not object types\n  result.credentialSubject = { ...input.credentialSubject, ...input.vc?.credentialSubject }\n  if (input.sub && !input.credentialSubject?.id && result.credentialSubject) {\n    result.credentialSubject.id = input.sub\n    if (removeOriginalFields) {\n      delete result.sub\n    }\n  }\n  if (removeOriginalFields) {\n    delete result.vc?.credentialSubject\n  }\n\n  if (typeof input.issuer === 'undefined' || typeof input.issuer === 'object') {\n    result.issuer = cleanUndefined({ id: input.iss, ...input.issuer })\n    if (removeOriginalFields && !input.issuer?.id) {\n      delete result.iss\n    }\n  }\n\n  if (!input.id && input.jti) {\n    result.id = result.id || result.jti\n    if (removeOriginalFields) {\n      delete result.jti\n    }\n  }\n\n  const types = [...asArray(result.type), ...asArray(result.vc?.type)].filter(notEmpty)\n  result.type = [...new Set(types)]\n  if (removeOriginalFields) {\n    delete result.vc?.type\n  }\n\n  for (const prop of additionalPropNames) {\n    if (input.vc && input.vc[prop]) {\n      if (!result[prop]) {\n        result[prop] = input.vc[prop]\n      }\n      if (removeOriginalFields) {\n        delete result.vc[prop]\n      }\n    }\n  }\n\n  const contextArray: string[] = [\n    ...asArray(input.context),\n    ...asArray(input['@context']),\n    ...asArray(input.vc?.['@context']),\n  ].filter(notEmpty)\n  result['@context'] = [...new Set(contextArray)]\n  if (removeOriginalFields) {\n    delete result.context\n    delete result.vc?.['@context']\n  }\n\n  if (!input.issuanceDate && (input.iat || input.nbf)) {\n    result.issuanceDate = new Date((input.nbf || input.iat) * 1000).toISOString()\n    if (removeOriginalFields) {\n      if (input.nbf) {\n        delete result.nbf\n      } else {\n        delete result.iat\n      }\n    }\n  }\n\n  if (!input.expirationDate && input.exp) {\n    result.expirationDate = new Date(input.exp * 1000).toISOString()\n    if (removeOriginalFields) {\n      delete result.exp\n    }\n  }\n\n  if (removeOriginalFields) {\n    if (result.vc && Object.keys(result.vc).length === 0) {\n      delete result.vc\n    }\n  }\n\n  // FIXME: interpret `aud` property as `verifier`\n\n  return result as W3CCredential\n}\n\nfunction normalizeJwtCredential(input: JWT, removeOriginalFields = true): Verifiable<W3CCredential> {\n  let decoded\n  try {\n    decoded = decodeJWT(input)\n  } catch {\n    throw new TypeError('unknown credential format')\n  }\n  return {\n    ...normalizeJwtCredentialPayload(decoded.payload, removeOriginalFields),\n    proof: {\n      type: DEFAULT_JWT_PROOF_TYPE,\n      jwt: input,\n    },\n  }\n}\n\n/**\n * Normalizes a credential payload into an unambiguous W3C credential data type In case of conflict, existing W3C\n * Credential specific properties take precedence, except for arrays and object types which get merged.\n *\n * @param input - either a JWT or JWT payload, or a VerifiableCredential\n * @param removeOriginalFields - if true, removes all fields that were transformed according to the W3C mapping\n *\n * @see {@link https://www.w3.org/TR/vc-data-model/#jwt-encoding | VC JWT encoding }\n */\nexport function normalizeCredential(\n  input: Partial<VerifiableCredential> | Partial<JwtCredentialPayload>,\n  removeOriginalFields = true\n): Verifiable<W3CCredential> {\n  if (typeof input === 'string') {\n    if (JWT_FORMAT.test(input)) {\n      return normalizeJwtCredential(input, removeOriginalFields)\n    } else {\n      let parsed: Record<string, unknown>\n      try {\n        parsed = JSON.parse(input)\n      } catch {\n        throw new TypeError('unknown credential format')\n      }\n      return normalizeCredential(parsed, removeOriginalFields)\n    }\n  } else if (input.proof?.jwt) {\n    // TODO: test that it correctly propagates app specific proof properties\n    return deepCopy({ ...normalizeJwtCredential(input.proof.jwt, removeOriginalFields), proof: input.proof })\n  } else {\n    // TODO: test that it accepts JWT payload, CredentialPayload, VerifiableCredential\n    // TODO: test that it correctly propagates proof, if any\n    return { proof: {}, ...normalizeJwtCredentialPayload(input, removeOriginalFields) }\n  }\n}\n\n/**\n * type used to signal a very loose input is accepted\n */\ntype DeepPartial<T> = T extends Record<string, unknown> ? { [K in keyof T]?: DeepPartial<T[K]> } : T\n\n/**\n * Transforms a W3C Credential payload into a JWT compatible encoding.\n * The method accepts app specific fields and in case of collision, existing JWT properties will take precedence.\n * Also, `nbf`, `exp` and `jti` properties can be explicitly set to `undefined` and they will be kept intact.\n * @param input - either a JWT payload or a CredentialPayloadInput\n * @param removeOriginalFields - if true, removes original W3C fields from the resulting object\n *\n * @see {@link https://www.w3.org/TR/vc-data-model/#jwt-encoding | VC JWT encoding }\n */\nexport function transformCredentialInput(\n  input: Partial<CredentialPayload> | DeepPartial<JwtCredentialPayload>,\n  removeOriginalFields = true\n): JwtCredentialPayload {\n  if (Array.isArray(input.credentialSubject)) throw Error('credentialSubject of type array not supported')\n\n  const result: Partial<JwtCredentialPayload> = deepCopy({\n    vc: { ...input.vc },\n    ...input,\n  }) as Partial<JwtCredentialPayload>\n  result.vc = result.vc as NonNullable<typeof result.vc>\n\n  const credentialSubject = { ...input.credentialSubject, ...input.vc?.credentialSubject }\n  if (!input.sub) {\n    result.sub = input.credentialSubject?.id\n    if (removeOriginalFields) {\n      delete credentialSubject.id\n    }\n  }\n\n  const contextEntries = [\n    ...asArray(input.context),\n    ...asArray(input['@context']),\n    ...asArray(input.vc?.['@context']),\n  ].filter(notEmpty)\n  result.vc['@context'] = [...new Set(contextEntries)]\n  if (removeOriginalFields) {\n    delete result.context\n    delete result['@context']\n  }\n\n  const types = [...asArray(input.type), ...asArray(input.vc?.type)].filter(notEmpty)\n  result.vc.type = [...new Set(types)]\n  if (removeOriginalFields) {\n    delete result.type\n  }\n\n  if (input.id && Object.getOwnPropertyNames(input).indexOf('jti') === -1) {\n    result.jti = input.id\n    if (removeOriginalFields) {\n      delete result.id\n    }\n  }\n\n  if (input.issuanceDate && Object.getOwnPropertyNames(input).indexOf('nbf') === -1) {\n    const converted = Date.parse(input.issuanceDate)\n    if (!isNaN(converted)) {\n      result.nbf = Math.floor(converted / 1000)\n      if (removeOriginalFields) {\n        delete result.issuanceDate\n      }\n    }\n  }\n\n  if (input.expirationDate && Object.getOwnPropertyNames(input).indexOf('exp') === -1) {\n    const converted = Date.parse(input.expirationDate)\n    if (!isNaN(converted)) {\n      result.exp = Math.floor(converted / 1000)\n      if (removeOriginalFields) {\n        delete result.expirationDate\n      }\n    }\n  }\n\n  if (input.issuer && Object.getOwnPropertyNames(input).indexOf('iss') === -1) {\n    if (typeof input.issuer === 'object') {\n      result.iss = input.issuer?.id\n      if (removeOriginalFields) {\n        delete result.issuer.id\n        if (Object.keys(result.issuer).length === 0) {\n          delete result.issuer\n        }\n      }\n    } else if (typeof input.issuer === 'string') {\n      result.iss = input.iss || '' + input.issuer\n      if (removeOriginalFields) {\n        delete result.issuer\n      }\n    } else {\n      // nop\n    }\n  }\n\n  result.vc.credentialSubject = credentialSubject\n  if (removeOriginalFields) {\n    delete result.credentialSubject\n  }\n\n  for (const prop of additionalPropNames) {\n    if (input[prop]) {\n      if (!result.vc[prop]) {\n        result.vc[prop] = input[prop]\n      }\n      if (removeOriginalFields) {\n        delete result[prop]\n      }\n    }\n  }\n\n  return result as JwtCredentialPayload\n}\n\nfunction normalizeJwtPresentationPayload(\n  input: DeepPartial<JwtPresentationPayload>,\n  removeOriginalFields = true\n): W3CPresentation {\n  const result: Partial<PresentationPayload> = deepCopy(input)\n\n  result.verifiableCredential = [\n    ...asArray(input.verifiableCredential),\n    ...asArray(input.vp?.verifiableCredential),\n  ].filter(notEmpty)\n  result.verifiableCredential = result.verifiableCredential.map((cred) => {\n    return normalizeCredential(cred, removeOriginalFields)\n  })\n  if (removeOriginalFields) {\n    delete result.vp?.verifiableCredential\n  }\n\n  if (input.iss && !input.holder) {\n    result.holder = input.iss\n    if (removeOriginalFields) {\n      delete result.iss\n    }\n  }\n\n  if (input.aud) {\n    result.verifier = [...asArray(input.verifier), ...asArray(input.aud)].filter(notEmpty)\n    result.verifier = [...new Set(result.verifier)]\n    if (removeOriginalFields) {\n      delete result.aud\n    }\n  }\n\n  if (input.jti && Object.getOwnPropertyNames(input).indexOf('id') === -1) {\n    result.id = input.id || input.jti\n    if (removeOriginalFields) {\n      delete result.jti\n    }\n  }\n\n  const types = [...asArray(input.type), ...asArray(input.vp?.type)].filter(notEmpty)\n  result.type = [...new Set(types)]\n  if (removeOriginalFields) {\n    delete result.vp?.type\n  }\n\n  const contexts = [\n    ...asArray(input.context),\n    ...asArray(input['@context']),\n    ...asArray(input.vp?.['@context']),\n  ].filter(notEmpty)\n  result['@context'] = [...new Set(contexts)]\n  if (removeOriginalFields) {\n    delete result.context\n    delete result.vp?.['@context']\n  }\n\n  if (!input.issuanceDate && (input.iat || input.nbf)) {\n    result.issuanceDate = new Date((input.nbf || input.iat) * 1000).toISOString()\n    if (removeOriginalFields) {\n      if (input.nbf) {\n        delete result.nbf\n      } else {\n        delete result.iat\n      }\n    }\n  }\n\n  if (!input.expirationDate && input.exp) {\n    result.expirationDate = new Date(input.exp * 1000).toISOString()\n    if (removeOriginalFields) {\n      delete result.exp\n    }\n  }\n\n  if (result.vp && Object.keys(result.vp).length === 0) {\n    if (removeOriginalFields) {\n      delete result.vp\n    }\n  }\n\n  return result as W3CPresentation\n}\n\nfunction normalizeJwtPresentation(input: JWT, removeOriginalFields = true): Verifiable<W3CPresentation> {\n  let decoded\n  try {\n    decoded = decodeJWT(input)\n  } catch {\n    throw new TypeError('unknown presentation format')\n  }\n  return {\n    ...normalizeJwtPresentationPayload(decoded.payload, removeOriginalFields),\n    proof: {\n      type: DEFAULT_JWT_PROOF_TYPE,\n      jwt: input,\n    },\n  }\n}\n\n/**\n * Normalizes a presentation payload into an unambiguous W3C Presentation data type.\n *\n * @see {@link https://www.w3.org/TR/vc-data-model/#jwt-encoding | VP JWT encoding }\n *\n * @param input - either a JWT or JWT payload, or a VerifiablePresentation\n * @param removeOriginalFields - if true, removes all fields that were transformed according to the W3C mapping\n */\nexport function normalizePresentation(\n  input: Partial<PresentationPayload> | DeepPartial<JwtPresentationPayload> | JWT,\n  removeOriginalFields = true\n): Verifiable<W3CPresentation> {\n  if (typeof input === 'string') {\n    if (JWT_FORMAT.test(input)) {\n      return normalizeJwtPresentation(input, removeOriginalFields)\n    } else {\n      let parsed: Record<string, unknown>\n      try {\n        parsed = JSON.parse(input)\n      } catch {\n        throw new TypeError('unknown presentation format')\n      }\n      return normalizePresentation(parsed, removeOriginalFields)\n    }\n  } else if (input.proof?.jwt) {\n    // TODO: test that it correctly propagates app specific proof properties\n    return { ...normalizeJwtPresentation(input.proof.jwt, removeOriginalFields), proof: input.proof }\n  } else {\n    // TODO: test that it accepts JWT payload, PresentationPayload, VerifiablePresentation\n    // TODO: test that it correctly propagates proof, if any\n    return { proof: {}, ...normalizeJwtPresentationPayload(input, removeOriginalFields) }\n  }\n}\n\n/**\n * Transforms a W3C Presentation payload into a JWT compatible encoding.\n * The method accepts app specific fields and in case of collision, existing JWT properties will take precedence.\n * Also, `nbf`, `exp` and `jti` properties can be explicitly set to `undefined` and they will be kept intact.\n * @param input - either a JWT payload or a CredentialPayloadInput\n * @param removeOriginalFields - when true, removes the original W3C fields from the resulting object\n *\n * @see {@link https://www.w3.org/TR/vc-data-model/#jwt-encoding | VP JWT encoding }\n */\nexport function transformPresentationInput(\n  input: Partial<PresentationPayload> | DeepPartial<JwtPresentationPayload>,\n  removeOriginalFields = true\n): JwtPresentationPayload {\n  const result: Partial<JwtPresentationPayload> = deepCopy({\n    vp: { ...input.vp },\n    ...input,\n  }) as Partial<JwtPresentationPayload>\n  result.vp = result.vp as NonNullable<typeof result.vp>\n\n  const contextEntries = [\n    ...asArray(input.context),\n    ...asArray(input['@context']),\n    ...asArray(input.vp?.['@context']),\n  ].filter(notEmpty)\n  result.vp['@context'] = [...new Set(contextEntries)]\n  if (removeOriginalFields) {\n    delete result.context\n    delete result['@context']\n  }\n\n  const types = [...asArray(input.type), ...asArray(input.vp?.type)].filter(notEmpty)\n  result.vp.type = [...new Set(types)]\n  if (removeOriginalFields) {\n    delete result.type\n  }\n\n  if (input.id && Object.getOwnPropertyNames(input).indexOf('jti') === -1) {\n    result.jti = input.id\n    if (removeOriginalFields) {\n      delete result.id\n    }\n  }\n\n  if (input.issuanceDate && Object.getOwnPropertyNames(input).indexOf('nbf') === -1) {\n    const converted = Date.parse(input.issuanceDate)\n    if (!isNaN(converted)) {\n      result.nbf = Math.floor(converted / 1000)\n      if (removeOriginalFields) {\n        delete result.issuanceDate\n      }\n    }\n  }\n\n  if (input.expirationDate && Object.getOwnPropertyNames(input).indexOf('exp') === -1) {\n    const converted = Date.parse(input.expirationDate)\n    if (!isNaN(converted)) {\n      result.exp = Math.floor(converted / 1000)\n      if (removeOriginalFields) {\n        delete result.expirationDate\n      }\n    }\n  }\n\n  if (result.verifiableCredential || result.vp?.verifiableCredential) {\n    result.vp.verifiableCredential = [\n      ...asArray(result.verifiableCredential),\n      ...asArray(result.vp?.verifiableCredential),\n    ]\n      .filter(notEmpty)\n      .map((credential: VerifiableCredential) => {\n        if (typeof credential === 'object' && credential.proof?.jwt) {\n          return credential.proof.jwt\n        } else {\n          return credential\n        }\n      })\n  }\n\n  if (removeOriginalFields) {\n    delete result.verifiableCredential\n  }\n\n  if (input.holder && Object.getOwnPropertyNames(input).indexOf('iss') === -1) {\n    if (typeof input.holder === 'string') {\n      result.iss = input.holder\n      if (removeOriginalFields) {\n        delete result.holder\n      }\n    } else {\n      // nop\n    }\n  }\n\n  if (input.verifier) {\n    const audience = [...asArray(input.verifier), ...asArray(input.aud)].filter(notEmpty)\n    result.aud = [...new Set(audience)]\n    if (removeOriginalFields) {\n      delete result.verifier\n    }\n  }\n\n  return result as JwtPresentationPayload\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import { createJWT, createMultisignatureJWT, verifyJWT } from 'did-jwt'\nimport type { Resolvable } from 'did-resolver'\nimport * as validators from './validators.js'\nimport { VC_ERROR } from './validators.js'\nimport type {\n  CreateCredentialOptions,\n  CreatePresentationOptions,\n  CredentialPayload,\n  Issuer,\n  JWT,\n  JwtCredentialPayload,\n  JwtPresentationPayload,\n  PresentationPayload,\n  Verifiable,\n  VerifiableCredential,\n  VerifiablePresentation,\n  VerifiedCredential,\n  VerifiedPresentation,\n  VerifyCredentialOptions,\n  VerifyCredentialPolicies,\n  VerifyPresentationOptions,\n  W3CCredential,\n  W3CPresentation,\n} from './types.js'\nimport { JWT_ALG } from './types.js'\nimport {\n  asArray,\n  normalizeCredential,\n  normalizePresentation,\n  notEmpty,\n  transformCredentialInput,\n  transformPresentationInput,\n} from './converters.js'\n\nexport { VC_ERROR, VC_JWT_ERROR } from './validators.js'\n\nexport type {\n  Issuer,\n  CredentialPayload,\n  PresentationPayload,\n  JwtCredentialPayload,\n  JwtPresentationPayload,\n  VerifiableCredential,\n  VerifiablePresentation,\n  VerifiedCredential,\n  VerifiedPresentation,\n  Verifiable,\n  W3CCredential,\n  W3CPresentation,\n  CreateCredentialOptions,\n  CreatePresentationOptions,\n  VerifyCredentialOptions,\n  VerifyCredentialPolicies,\n  VerifyPresentationOptions,\n}\n\nexport { transformCredentialInput, transformPresentationInput, normalizeCredential, normalizePresentation }\n\n/**\n * Creates a VerifiableCredential given a `CredentialPayload` or `JwtCredentialPayload` and an `Issuer`.\n *\n * This method transforms the payload into the [JWT encoding](https://www.w3.org/TR/vc-data-model/#jwt-encoding)\n * described in the [W3C VC spec](https://www.w3.org/TR/vc-data-model) and then validated to conform to the minimum\n * spec\n * required spec.\n *\n * The `issuer` is then used to assign an algorithm, override the `iss` field of the payload and then sign the JWT.\n *\n * @param payload - `CredentialPayload` or `JwtCredentialPayload`\n * @param issuer - `Issuer` the DID, signer and algorithm that will sign the token\n * @param options - Use these options to tweak the creation of the JWT Credential. These are forwarded to did-jwt.\n * @return a `Promise` that resolves to the JWT encoded verifiable credential or rejects with `TypeError` if the\n * `payload` is not W3C compliant\n */\nexport async function createVerifiableCredentialJwt(\n  payload: JwtCredentialPayload | CredentialPayload,\n  issuer: Issuer | Issuer[],\n  options: CreateCredentialOptions = {}\n): Promise<JWT> {\n  const parsedPayload: JwtCredentialPayload = {\n    iat: undefined,\n    ...transformCredentialInput(payload, options.removeOriginalFields),\n  }\n  validateJwtCredentialPayload(parsedPayload)\n\n  if (!Array.isArray(issuer)) {\n    return createJWT(\n      parsedPayload,\n      {\n        ...options,\n        issuer: issuer.did || parsedPayload.iss || '',\n        signer: issuer.signer,\n      },\n      {\n        ...options.header,\n        alg: issuer.alg || options.header?.alg || JWT_ALG,\n      }\n    )\n  } else {\n    const did = issuer[0].did\n    const issuers = []\n    for (const iss of issuer) {\n      if (iss.did !== did) {\n        throw new Error('All issuers must be the same did to comply with the Verifiable Conditions spec')\n      }\n      issuers.push({\n        issuer: iss.did || parsedPayload.iss || '',\n        signer: iss.signer,\n        alg: iss.alg || options.header?.alg || JWT_ALG,\n      })\n    }\n\n    return createMultisignatureJWT(parsedPayload, { ...options }, issuers)\n  }\n}\n\n/**\n * Creates a VerifiablePresentation JWT given a `PresentationPayload` or `JwtPresentationPayload` and an `Issuer`.\n *\n * This method transforms the payload into the [JWT encoding](https://www.w3.org/TR/vc-data-model/#jwt-encoding)\n * described in the [W3C VC spec](https://www.w3.org/TR/vc-data-model) and then validated to conform to the minimum\n * spec\n * required spec.\n *\n * The `holder` is then used to assign an algorithm, override the `iss` field of the payload and then sign the JWT.\n *\n * @param payload - `PresentationPayload` or `JwtPresentationPayload`\n * @param holder - `Issuer` of the Presentation JWT (holder of the VC), signer and algorithm that will sign the token\n * @param options - `CreatePresentationOptions` allows to pass additional values to the resulting JWT payload. These\n *   options are forwarded to did-jwt.\n * @return a `Promise` that resolves to the JWT encoded verifiable presentation or rejects with `TypeError` if the\n * `payload` is not W3C compliant\n */\nexport async function createVerifiablePresentationJwt(\n  payload: JwtPresentationPayload | PresentationPayload,\n  holder: Issuer,\n  options: CreatePresentationOptions = {}\n): Promise<JWT> {\n  const parsedPayload: JwtPresentationPayload = {\n    iat: undefined,\n    ...transformPresentationInput(payload, options?.removeOriginalFields),\n  }\n\n  // add challenge to nonce\n  if (options.challenge && Object.getOwnPropertyNames(parsedPayload).indexOf('nonce') === -1) {\n    parsedPayload.nonce = options.challenge\n  }\n\n  // add domain to audience.\n  if (options.domain) {\n    const audience = [...asArray(options.domain), ...asArray(parsedPayload.aud)].filter(notEmpty)\n    parsedPayload.aud = [...new Set(audience)]\n  }\n\n  validateJwtPresentationPayload(parsedPayload)\n  return createJWT(\n    parsedPayload,\n    {\n      ...options,\n      issuer: holder.did || parsedPayload.iss || '',\n      signer: holder.signer,\n    },\n    {\n      ...options.header,\n      alg: holder.alg || options.header?.alg || JWT_ALG,\n    }\n  )\n}\n\nexport function validateJwtCredentialPayload(payload: JwtCredentialPayload): void {\n  validators.validateContext(payload.vc['@context'])\n  validators.validateVcType(payload.vc.type)\n  validators.validateCredentialSubject(payload.vc.credentialSubject)\n  if (payload.nbf) validators.validateTimestamp(payload.nbf)\n  if (payload.exp) validators.validateTimestamp(payload.exp)\n}\n\nexport function validateCredentialPayload(payload: CredentialPayload): void {\n  validators.validateContext(payload['@context'])\n  validators.validateVcType(payload.type)\n  validators.validateCredentialSubject(payload.credentialSubject)\n  if (payload.issuanceDate) validators.validateTimestamp(payload.issuanceDate)\n  if (payload.expirationDate) validators.validateTimestamp(payload.expirationDate)\n}\n\nexport function validateJwtPresentationPayload(payload: JwtPresentationPayload): void {\n  validators.validateContext(payload.vp['@context'])\n  validators.validateVpType(payload.vp.type)\n  // empty credential array is allowed\n  if (payload.vp.verifiableCredential && payload.vp.verifiableCredential.length >= 1) {\n    for (const vc of asArray(payload.vp.verifiableCredential)) {\n      if (typeof vc === 'string') {\n        validators.validateJwtFormat(vc)\n      } else {\n        validateCredentialPayload(vc)\n      }\n    }\n  }\n  if (payload.exp) validators.validateTimestamp(payload.exp)\n}\n\nexport function validatePresentationPayload(payload: PresentationPayload): void {\n  validators.validateContext(payload['@context'])\n  validators.validateVpType(payload.type)\n  // empty credential array is allowed\n  if (payload.verifiableCredential && payload.verifiableCredential.length >= 1) {\n    for (const vc of payload.verifiableCredential) {\n      if (typeof vc === 'string') {\n        validators.validateJwtFormat(vc)\n      } else {\n        validateCredentialPayload(vc)\n      }\n    }\n  }\n  if (payload.expirationDate) validators.validateTimestamp(payload.expirationDate)\n}\n\n/**\n * Verifies and validates a VerifiableCredential that is encoded as a JWT according to the W3C spec.\n *\n * @return a `Promise` that resolves to a `VerifiedCredential` or rejects with `TypeError` if the input is not\n * W3C compliant\n * @param vc - the credential to be verified. Currently only the JWT encoding is supported by this library\n * @param resolver - a configured `Resolver` (or an implementation of `Resolvable`) that can provide the DID document\n *   of the JWT issuer\n * @param options - optional tweaks to the verification process. These are forwarded to did-jwt.\n */\nexport async function verifyCredential(\n  vc: JWT,\n  resolver: Resolvable,\n  options: VerifyCredentialOptions = {}\n): Promise<VerifiedCredential> {\n  const nbf = options?.policies?.issuanceDate === false ? false : undefined\n  const exp = options?.policies?.expirationDate === false ? false : undefined\n  options = { ...options, policies: { ...options?.policies, nbf, exp, iat: nbf } }\n  const verified: Partial<VerifiedCredential> = await verifyJWT(vc, { resolver, ...options })\n  verified.verifiableCredential = normalizeCredential(verified.jwt as string, options?.removeOriginalFields)\n  if (options?.policies?.format !== false) {\n    validateCredentialPayload(verified.verifiableCredential)\n  }\n  return verified as VerifiedCredential\n}\n\n/**\n * Verifies that the given JwtPresentationPayload contains the appropriate options from VerifyPresentationOptions\n *\n * @param payload - the JwtPresentationPayload to verify against\n * @param options - the VerifyPresentationOptions that contain the optional values to verify.\n * @throws {Error} If VerifyPresentationOptions are not satisfied\n */\nexport function verifyPresentationPayloadOptions(\n  payload: JwtPresentationPayload,\n  options: VerifyPresentationOptions\n): void {\n  if (options.challenge && options.challenge !== payload.nonce) {\n    throw new Error(\n      `${VC_ERROR.AUTH_ERROR}: Presentation does not contain the mandatory challenge (JWT: nonce) for : ${options.challenge}`\n    )\n  }\n\n  if (options.domain) {\n    // aud might be an array\n    let matchedAudience\n    if (payload.aud) {\n      const audArray = Array.isArray(payload.aud) ? payload.aud : [payload.aud]\n      matchedAudience = audArray.find((item) => options.domain === item)\n    }\n\n    if (typeof matchedAudience === 'undefined') {\n      throw new Error(\n        `${VC_ERROR.AUTH_ERROR}: Presentation does not contain the mandatory domain (JWT: aud) for : ${options.domain}`\n      )\n    }\n  }\n}\n\n/**\n * Verifies and validates a VerifiablePresentation that is encoded as a JWT according to the W3C spec.\n *\n * @return a `Promise` that resolves to a `VerifiedPresentation` or rejects with `TypeError` if the input is\n * not W3C compliant or the VerifyPresentationOptions are not satisfied.\n * @param presentation - the presentation to be verified. Currently only the JWT encoding is supported by this library\n * @param resolver - a configured `Resolver` or an implementation of `Resolvable` that can provide the DID document of\n *   the JWT issuer (presentation holder)\n * @param options - optional verification options that need to be satisfied. These are also forwarded to did-jwt.\n */\nexport async function verifyPresentation(\n  presentation: JWT,\n  resolver: Resolvable,\n  options: VerifyPresentationOptions = {}\n): Promise<VerifiedPresentation> {\n  const nbf = options?.policies?.issuanceDate === false ? false : undefined\n  const exp = options?.policies?.expirationDate === false ? false : undefined\n  options = { audience: options.domain, ...options, policies: { ...options?.policies, nbf, exp, iat: nbf } }\n  const verified: Partial<VerifiedPresentation> = await verifyJWT(presentation, {\n    resolver,\n    ...options,\n  })\n  verifyPresentationPayloadOptions(verified.payload as JwtPresentationPayload, options)\n  verified.verifiablePresentation = normalizePresentation(verified.jwt as string, options?.removeOriginalFields)\n  if (options?.policies?.format !== false) {\n    validatePresentationPayload(verified.verifiablePresentation)\n  }\n  return verified as VerifiedPresentation\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "import type { Signer, JWTVerified, JWTHeader, JWTOptions, JWTVerifyOptions } from 'did-jwt'\n\nexport const JWT_ALG = 'ES256K'\nexport const JWT_FORMAT = /^[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]*$/\nexport const DEFAULT_CONTEXT = 'https://www.w3.org/2018/credentials/v1'\nexport const DEFAULT_VC_TYPE = 'VerifiableCredential'\nexport const DEFAULT_VP_TYPE = 'VerifiablePresentation'\n/**\n * The `JwtProof2020` is a synthetic proof type, usable for differentiating credentials by proof type when representing\n * JWT credentials as W3C VC JSON. It is not a registered W3C VC Data Model algorithm and should not be treated as\n * such.\n *\n * This proof type is only intended as a convenience and does not actually prove the validity of a VC/VP in JSON\n * representation. The actual verifiable credential or presentation is represented in the `jwt` property.\n */\nexport const DEFAULT_JWT_PROOF_TYPE = 'JwtProof2020'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type JwtCredentialSubject = Record<string, any>\n\nexport interface CredentialStatus {\n  id: string\n  type: string\n}\n\n/**\n * A JWT payload representation of a Credential\n * @see https://www.w3.org/TR/vc-data-model/#jwt-encoding\n */\nexport interface JwtCredentialPayload {\n  iss?: string\n  sub?: string\n  vc: Extensible<{\n    '@context': string[] | string\n    type: string[] | string\n    credentialSubject: JwtCredentialSubject\n    credentialStatus?: CredentialStatus\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    evidence?: any\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    termsOfUse?: any\n  }>\n  nbf?: number\n  aud?: string | string[]\n  exp?: number\n  jti?: string\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  [x: string]: any\n}\n\n/**\n * A JWT payload representation of a Presentation\n * @see https://www.w3.org/TR/vc-data-model/#jwt-encoding\n */\nexport interface JwtPresentationPayload {\n  vp: Extensible<{\n    '@context': string[] | string\n    type: string[] | string\n    verifiableCredential?: VerifiableCredential[] | VerifiableCredential\n  }>\n  iss?: string\n  aud?: string | string[]\n  nbf?: number\n  exp?: number\n  jti?: string\n  nonce?: string\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  [x: string]: any\n}\n\nexport type IssuerType = Extensible<{ id: string }> | string\nexport type DateType = string | Date\n\n/**\n * Used as input when creating Verifiable Credentials\n */\ninterface FixedCredentialPayload {\n  '@context': string | string[]\n  id?: string\n  type: string | string[]\n  issuer: IssuerType\n  issuanceDate: DateType\n  expirationDate?: DateType\n  credentialSubject: Extensible<{\n    id?: string\n  }>\n  credentialStatus?: CredentialStatus\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  evidence?: any\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  termsOfUse?: any\n}\n\n/**\n * A more flexible representation of a {@link W3CCredential} that can be used as input to methods\n * that expect it.\n */\nexport type CredentialPayload = Extensible<FixedCredentialPayload>\n\n/**\n * This is meant to reflect unambiguous types for the properties in `CredentialPayload`\n */\ninterface NarrowCredentialDefinitions {\n  '@context': string[]\n  type: string[]\n  issuer: Exclude<IssuerType, string>\n  issuanceDate: string\n  expirationDate?: string\n}\n\n/**\n * Replaces the matching property types of T with the ones in U\n */\ntype Replace<T, U> = Omit<T, keyof U> & U\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Extensible<T> = T & { [x: string]: any }\n\n/**\n * This data type represents a parsed VerifiableCredential.\n * It is meant to be an unambiguous representation of the properties of a Credential and is usually the result of a\n * transformation method. See `transformCredentialInput()` for more details.\n *\n * `issuer` is always an object with an `id` property and potentially other app specific issuer claims\n * `issuanceDate` is an ISO DateTime string\n * `expirationDate`, is a nullable ISO DateTime string\n *\n * Any JWT specific properties are transformed to the broader W3C variant and any app specific properties are left\n * intact\n */\nexport type W3CCredential = Extensible<Replace<FixedCredentialPayload, NarrowCredentialDefinitions>>\n\n/**\n * used as input when creating Verifiable Presentations\n */\nexport interface FixedPresentationPayload {\n  '@context': string | string[]\n  type: string | string[]\n  id?: string\n  verifiableCredential?: VerifiableCredential[]\n  holder: string\n  verifier?: string | string[]\n  issuanceDate?: string\n  expirationDate?: string\n}\n\n/**\n * A more flexible representation of a {@link W3CPresentation} that can be used as input to methods\n * that expect it.\n */\nexport type PresentationPayload = Extensible<FixedPresentationPayload>\n\ninterface NarrowPresentationDefinitions {\n  '@context': string[]\n  type: string[]\n  verifier: string[]\n  verifiableCredential?: Verifiable<W3CCredential>[]\n}\n\n/**\n * This data type represents a parsed Presentation payload.\n * It is meant to be an unambiguous representation of the properties of a Presentation and is usually the result of a\n * transformation method. See `transformPresentationInput()` for more details.\n *\n * The `verifiableCredential` array should contain parsed `Verifiable<Credential>` elements.\n * Any JWT specific properties are transformed to the broader W3C variant and any other app specific properties are\n * left intact.\n */\nexport type W3CPresentation = Extensible<Replace<FixedPresentationPayload, NarrowPresentationDefinitions>>\n\nexport interface Proof {\n  type?: string\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  [x: string]: any\n}\n\n/**\n * Represents a readonly representation of a verifiable object, including the {@link Proof}\n * property that can be used to verify it.\n */\nexport type Verifiable<T> = Readonly<T> & { readonly proof: Proof }\nexport type JWT = string\n\n/**\n * A union type for both possible representations of a Credential (JWT and W3C standard)\n *\n * @see https://www.w3.org/TR/vc-data-model/#proof-formats\n */\nexport type VerifiableCredential = JWT | Verifiable<W3CCredential>\n\n/**\n * A union type for both possible representations of a Presentation (JWT and W3C standard)\n *\n * @see https://www.w3.org/TR/vc-data-model/#proof-formats\n */\nexport type VerifiablePresentation = JWT | Verifiable<W3CPresentation>\n\nexport type VerifiedJWT = JWTVerified\n\n/**\n * Represents the result of a Presentation verification.\n * It includes the properties produced by `did-jwt` and a W3C compliant representation of\n * the Presentation that was just verified.\n *\n * This is usually the result of a verification method and not meant to be created by generic code.\n */\nexport type VerifiedPresentation = VerifiedJWT & {\n  verifiablePresentation: Verifiable<W3CPresentation>\n}\n\n/**\n * Represents the result of a Credential verification.\n * It includes the properties produced by `did-jwt` and a W3C compliant representation of\n * the Credential that was just verified.\n *\n * This is usually the result of a verification method and not meant to be created by generic code.\n */\nexport type VerifiedCredential = VerifiedJWT & {\n  verifiableCredential: Verifiable<W3CCredential>\n}\n\n/**\n * Represents a tuple of a DID-URL with a `Signer` and associated algorithm.\n */\nexport interface Issuer {\n  did: string\n  signer: Signer\n  alg?: string\n}\n\n/**\n * Represents the Creation Options that can be passed to the createVerifiableCredentialJwt method.\n */\nexport interface CreateCredentialOptions extends Partial<JWTOptions> {\n  /**\n   * Determines whether the JSON->JWT transformation will remove the original fields from the input payload.\n   * See https://www.w3.org/TR/vc-data-model/#jwt-encoding\n   *\n   * @default true\n   */\n  removeOriginalFields?: boolean\n\n  /**\n   * Allows including or overriding some header parameters for the resulting JWT.\n   * If the issuer or holder does not list an `alg`, then the one specified in `header` will be used\n   */\n  header?: Partial<JWTHeader>\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  [x: string]: any\n}\n\n/**\n * Represents the Verification Options that can be passed to the verifyCredential method.\n * These options are forwarded to the lower level verification code\n */\nexport interface VerifyCredentialOptions extends JWTVerifyOptions {\n  /**\n   * When transforming the result of the verification into a W3C VerifiableCredential, this property dictates whether\n   * the JWT specific properties are removed from the payload or not. Defaults to `true`.\n   */\n  removeOriginalFields?: boolean\n\n  /**\n   * Use this to override the default checks performed during verification\n   */\n  policies?: VerifyCredentialPolicies\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  [x: string]: any\n}\n\nexport interface VerifyCredentialPolicies {\n  // tweak the time at which the credential should be valid (UNIX timestamp, in seconds)\n  now?: number\n  // when false skips issuanceDate check\n  issuanceDate?: boolean\n  // when false skips expirationDate check\n  expirationDate?: boolean\n  // when false skips format checks\n  format?: boolean\n\n  /**\n   * Other policies are forwarded to lower level libs\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  [x: string]: any\n}\n\n/**\n * Represents the Verification Options that can be passed to the verifyPresentation method.\n * The verification will fail if given options are NOT satisfied.\n */\nexport interface VerifyPresentationOptions extends VerifyCredentialOptions {\n  domain?: string\n  challenge?: string\n}\n\n/**\n * Represents the Creation Options that can be passed to the createVerifiablePresentationJwt method.\n */\nexport interface CreatePresentationOptions extends CreateCredentialOptions {\n  domain?: string\n  challenge?: string\n}\n"
  },
  {
    "path": "src/validators.ts",
    "content": "import { DEFAULT_CONTEXT, DEFAULT_VC_TYPE, DEFAULT_VP_TYPE, JWT_FORMAT } from './types'\nimport { JwtCredentialSubject, DateType } from './types'\nimport { VerifiableCredential } from '.'\nimport { asArray } from './converters'\nimport { JWT_ERROR } from 'did-jwt'\n\n/**\n * Error prefixes used for known verification failure cases related to the\n * {@link https://www.w3.org/TR/vc-data-model/ | Verifiable Credential data model }\n */\nexport const VC_ERROR = {\n  /**\n   * Thrown when the credential or presentation being verified does not conform to the data model defined by\n   * {@link https://www.w3.org/TR/vc-data-model/ | the spec}\n   */\n  SCHEMA_ERROR: 'schema_error',\n\n  /**\n   * Thrown when the input is not a JWT string\n   */\n  FORMAT_ERROR: 'format_error',\n\n  /**\n   * Thrown when verifying a presentation where `challenge` and/or `domain` don't match the expected values.\n   */\n  AUTH_ERROR: 'auth_error',\n}\n\n/**\n * Known validation or verification error prefixes.\n */\nexport const VC_JWT_ERROR = { ...VC_ERROR, ...JWT_ERROR }\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction isDateObject(input: any): input is Date {\n  return input && !isNaN(input) && Object.prototype.toString.call(input) === '[object Date]'\n}\n\nexport function validateJwtFormat(value: VerifiableCredential): void {\n  if (typeof value === 'string' && !value.match(JWT_FORMAT)) {\n    throw new TypeError(`${VC_ERROR.FORMAT_ERROR}: \"${value}\" is not a valid JWT format`)\n  }\n}\n\n// The main scenario we want to guard against is having a timestamp in milliseconds\n// instead of seconds (ex: from new Date().getTime()).\n// We will check the number of digits and assume that any number with 12 or more\n// digits is a millisecond timestamp.\n// 10 digits max is 9999999999 -> 11/20/2286 @ 5:46pm (UTC)\n// 11 digits max is 99999999999 -> 11/16/5138 @ 9:46am (UTC)\n// 12 digits max is 999999999999 -> 09/27/33658 @ 1:46am (UTC)\nexport function validateTimestamp(value: number | DateType): void {\n  if (typeof value === 'number') {\n    if (!(Number.isInteger(value) && value < 100000000000)) {\n      throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: \"${value}\" is not a unix timestamp in seconds`)\n    }\n  } else if (typeof value === 'string') {\n    validateTimestamp(Math.floor(new Date(value).valueOf() / 1000))\n  } else if (!isDateObject(value)) {\n    throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: \"${value}\" is not a valid time`)\n  }\n}\n\nexport function validateContext(value: string | string[]): void {\n  const input = asArray(value)\n  if (input.length < 1 || input.indexOf(DEFAULT_CONTEXT) === -1) {\n    throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: @context is missing default context \"${DEFAULT_CONTEXT}\"`)\n  }\n}\n\nexport function validateVcType(value: string | string[]): void {\n  const input = asArray(value)\n  if (input.length < 1 || input.indexOf(DEFAULT_VC_TYPE) === -1) {\n    throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: type is missing default \"${DEFAULT_VC_TYPE}\"`)\n  }\n}\n\nexport function validateVpType(value: string | string[]): void {\n  const input = asArray(value)\n  if (input.length < 1 || input.indexOf(DEFAULT_VP_TYPE) === -1) {\n    throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: type is missing default \"${DEFAULT_VP_TYPE}\"`)\n  }\n}\n\nexport function validateCredentialSubject(value: JwtCredentialSubject): void {\n  if (Object.keys(value).length === 0) {\n    throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: credentialSubject must not be empty`)\n  }\n}\n"
  },
  {
    "path": "stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n# Label to use when marking an issue as stale\nstaleLabel: wontfix\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"esnext\",\n    \"lib\": [\n      \"dom\",\n      \"es6\",\n      \"es2015.promise\"\n    ],\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"lib\",\n    \"strict\": true,\n    \"moduleResolution\": \"node\",\n    \"types\": [\n      \"jest\"\n    ],\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true\n  },\n  \"exclude\": [\n    \"**/__tests__/*\",\n    \"node_modules\",\n    \"lib\"\n  ]\n}\n"
  }
]