├── .github ├── dependabot.yml ├── settings.yml └── workflows │ ├── build-test-publish-on-push-cached.yaml │ ├── release.yml │ └── static.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── CHANGELOG.md ├── CNAME ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── SECURITY.md ├── biome.json ├── docs ├── 0.x │ ├── README.md │ ├── claims.md │ ├── disclosureframe.md │ ├── encode-decode.md │ ├── issue.md │ ├── keybinding.md │ ├── present.md │ ├── sdjwt-instance.md │ ├── validate.md │ └── verify.md ├── README.md ├── imgs │ └── concept.png ├── index.html └── sdjwt.html ├── examples ├── README.md ├── decode-example │ ├── README.md │ ├── decode.ts │ ├── package.json │ └── tsconfig.json ├── present-example │ ├── README.md │ ├── package.json │ ├── present.ts │ └── tsconfig.json ├── sd-jwt-example │ ├── README.md │ ├── all.ts │ ├── basic.ts │ ├── custom.ts │ ├── custom_header.ts │ ├── decode.ts │ ├── decoy.ts │ ├── flattenJSON.ts │ ├── generalJSON.ts │ ├── kb.ts │ ├── package.json │ ├── sdjwtobject.ts │ ├── tsconfig.json │ └── utils.ts └── sd-jwt-vc-example │ ├── README.md │ ├── all.ts │ ├── basic.ts │ ├── custom.ts │ ├── custom_header.ts │ ├── decode.ts │ ├── decoy.ts │ ├── kb.ts │ ├── package.json │ ├── sdjwtobject.ts │ ├── tsconfig.json │ └── utils.ts ├── images └── diagram.png ├── lerna.json ├── package.json ├── packages ├── browser-crypto │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── crypto.ts │ │ ├── index.ts │ │ └── test │ │ │ └── crypto.spec.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── core │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── decoy.ts │ │ ├── flattenJSON.ts │ │ ├── generalJSON.ts │ │ ├── index.ts │ │ ├── jwt.ts │ │ ├── kbjwt.ts │ │ ├── sdjwt.ts │ │ └── test │ │ │ ├── decoy.spec.ts │ │ │ ├── flattenJSON.spec.ts │ │ │ ├── generalJSON.spec.ts │ │ │ ├── index.spec.ts │ │ │ ├── jwt.spec.ts │ │ │ ├── kbjwt.spec.ts │ │ │ ├── pass.spec.ts │ │ │ └── sdjwt.spec.ts │ ├── test │ │ ├── app-e2e.spec.ts │ │ ├── array_data_types.json │ │ ├── array_full_sd.json │ │ ├── array_in_sd.json │ │ ├── array_nested_in_plain.json │ │ ├── array_none_disclosed.json │ │ ├── array_of_nulls.json │ │ ├── array_of_objects.json │ │ ├── array_of_scalars.json │ │ ├── array_recursive_sd.json │ │ ├── array_recursive_sd_some_disclosed.json │ │ ├── complex.json │ │ ├── header_mod.json │ │ ├── json_serialization.json │ │ ├── key_binding.json │ │ ├── no_sd.json │ │ ├── object_data_types.json │ │ └── recursions.json │ ├── tsconfig.json │ └── vitest.config.mts ├── decode │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── decode.ts │ │ ├── index.ts │ │ └── test │ │ │ └── decode.spec.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── hash │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── sha256.ts │ │ └── test │ │ │ └── sha256.spec.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── jwt-status-list │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── status-list-jwt.ts │ │ ├── status-list.ts │ │ ├── test │ │ │ ├── status-list-jwt.spec.ts │ │ │ └── status-list.spec.ts │ │ └── types.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── node-crypto │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── crypto.ts │ │ ├── index.ts │ │ └── test │ │ │ └── crypto.spec.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── present │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── present.ts │ │ └── test │ │ │ └── present.spec.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── sd-jwt-vc │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── sd-jwt-vc-config.ts │ │ ├── sd-jwt-vc-instance.ts │ │ ├── sd-jwt-vc-payload.ts │ │ ├── sd-jwt-vc-status-reference.ts │ │ ├── sd-jwt-vc-type-metadata-format.ts │ │ ├── sd-jwt-vc-vct.ts │ │ ├── test │ │ │ ├── index.spec.ts │ │ │ └── vct.spec.ts │ │ └── verification-result.ts │ ├── test │ │ ├── app-e2e.spec.ts │ │ ├── array_data_types.json │ │ ├── array_full_sd.json │ │ ├── array_in_sd.json │ │ ├── array_nested_in_plain.json │ │ ├── array_none_disclosed.json │ │ ├── array_of_nulls.json │ │ ├── array_of_objects.json │ │ ├── array_of_scalars.json │ │ ├── array_recursive_sd.json │ │ ├── array_recursive_sd_some_disclosed.json │ │ ├── complex.json │ │ ├── header_mod.json │ │ ├── json_serialization.json │ │ ├── key_binding.json │ │ ├── no_sd.json │ │ ├── object_data_types.json │ │ └── recursions.json │ ├── tsconfig.json │ └── vitest.config.mts ├── types │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── test │ │ │ └── type.spec.ts │ │ └── type.ts │ ├── tsconfig.json │ └── vitest.config.mts └── utils │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── base64url.ts │ ├── disclosure.ts │ ├── error.ts │ ├── index.ts │ └── test │ │ ├── base64url.spec.ts │ │ ├── disclosure.spec.ts │ │ └── error.spec.ts │ ├── tsconfig.json │ └── vitest.config.mts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json ├── vitest.shared.js └── vitest.workspace.ts /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: "npm" 5 | # Look for `package.json` and `pnpm-lock.yaml` files in the root directory 6 | directory: "/" 7 | # Check the npm registry for updates every day (you can choose your own schedule) 8 | schedule: 9 | interval: "daily" 10 | # Lerna-specific configuration 11 | - package-ecosystem: "npm" 12 | # Assuming Lerna packages are in the 'packages' directory, adjust if different 13 | directory: "/packages/*" 14 | schedule: 15 | interval: "daily" 16 | # Additional configuration for monorepos 17 | allow: 18 | # Allow updates to devDependencies, runtime dependencies, etc. 19 | - dependency-type: "all" 20 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | 5 | # Documentation: https://github.com/apps/settings 6 | 7 | repository: 8 | # Repository name 9 | name: sd-jwt-js 10 | # description: A JavaScript implementation of the Selective Disclosure JWT (SD-JWT) spec. 11 | description: 12 | A JavaScript implementation of the Selective Disclosure JWT (SD-JWT) spec. 13 | # A URL with more information about the repository 14 | homepage: https://sdjwt.js.org/ 15 | # A comma-separated list of topics to set on the repository 16 | topics: sd-jwt, jwt 17 | default_branch: main 18 | 19 | # Labels: define labels for Issues and Pull Requests 20 | labels: 21 | - name: bug 22 | color: CC0000 23 | description: An issue with the system 🐛. 24 | 25 | - name: feature 26 | # If including a `#`, make sure to wrap it with quotes! 27 | color: '#336699' 28 | description: New functionality. 29 | 30 | - name: Help Wanted 31 | # Provide a new name to rename an existing label 32 | new_name: first-timers-only 33 | 34 | branches: 35 | - name: next 36 | protection: 37 | # Required. Require at least one approving review on a pull request, before merging. Set to null to disable. 38 | required_pull_request_reviews: 39 | # The number of approvals required. (1-6) 40 | required_approving_review_count: 1 41 | # Dismiss approved reviews automatically when a new commit is pushed. 42 | dismiss_stale_reviews: true 43 | required_status_checks: 44 | # Required. Require branches to be up to date before merging. 45 | strict: true 46 | # Required. The list of status checks to require in order to merge into this branch 47 | contexts: [] 48 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. 49 | enforce_admins: true 50 | - name: main 51 | protection: 52 | # Required. Require at least one approving review on a pull request, before merging. Set to null to disable. 53 | required_pull_request_reviews: 54 | # The number of approvals required. (1-6) 55 | required_approving_review_count: 1 56 | # Dismiss approved reviews automatically when a new commit is pushed. 57 | dismiss_stale_reviews: true 58 | required_status_checks: 59 | # Required. Require branches to be up to date before merging. 60 | strict: true 61 | # Required. The list of status checks to require in order to merge into this branch 62 | contexts: [] 63 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. 64 | enforce_admins: true -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v4 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | # Upload entire repository 40 | path: './docs' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | .vscode 5 | coverage 6 | .npmrc -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run format 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | sdjwt.js.org -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # @pensivej: Jaehoon (Ace) Shim, Hopae Inc. 2 | # @zustkeeper: Byungjoo Kim, Hopae Inc. 3 | # @lukasjhan: Lukas.J.Han, Hopae Inc. 4 | 5 | * @lukasjhan @aceshim @zustkeeper @berendsliedrecht @cre8 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SD-JWT 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | 13 | We use github to host code, to track issues and feature requests, as well as accept pull requests. 14 | 15 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 16 | 17 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 18 | 19 | 1. Fork the repo and create your branch from `main`. 20 | 2. If you've added code that should be tested, add tests. 21 | 3. If you've changed APIs, update the documentation. 22 | 4. Ensure the test suite passes. 23 | 5. Make sure your code lints. 24 | 6. Issue that pull request! 25 | 26 | ## Any contributions you make will be under the Apache 2.0 Software License 27 | 28 | In short, when you submit code changes, your submissions are understood to be under the same [Apache 2.0 License](http://www.apache.org/licenses/) that covers the project. Feel free to contact the maintainers if that's a concern. 29 | 30 | ## Report bugs using Github's [issues](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues) 31 | 32 | We use GitHub issues to track public bugs. Report a bug by opening a new issue it's that easy! 33 | 34 | ## Write bug reports with detail, background, and sample code 35 | 36 | **Great Bug Reports** tend to have: 37 | 38 | - A quick summary and/or background 39 | - Steps to reproduce 40 | - Be specific! 41 | - Give sample code if you can. 42 | - What you expected would happen 43 | - What actually happens 44 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 45 | 46 | ## Release procedure 47 | 48 | Each PR to the `main` branch has to pass the `build`, `test`, `lint` and `code coverage` steps from the CI. The PR also needs a review from one authorized person. 49 | All commits needs to be signed to pass the DCO check. 50 | 51 | After the PR is merged, a new `next` version is build and deployed to `npmjs` for all packages with the `next` tag. 52 | 53 | The release of a new version is done by running the `release` workflow manually. This workflow can only be triggered successfully by an authorized person that is listed inside the `CODEOWNERS` file. The test and coverage steps are executed again and the new version is published to `npmjs` for all packages with the `latest` tag. The version number is calculated based on the commits since the last release and the `semver` rules. -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | | Maintainer | GitHub ID | LFID | Email | Chat ID | Company Affiliation | Scope | 4 | | ----------------- | ---------------- | --------- | --------------------- | ----------- | ------------------- | --------- | 5 | | Ace | pensivej | - | Ace@hopae.io | Ace | Hopae Inc. | sd-jwt-js | 6 | | Gavin | zustkeeper | - | Gavin@hopae.io | Gavin | Hopae Inc. | sd-jwt-js | 7 | | Lukas | lukasjhan | Lukas.Han | lukas.j.han@gmail.com | lukas.j.han | Hopae Inc. | sd-jwt-js | 8 | | Berend Sliedrecht | berendsliedrecht | beri14 | sliedrecht@berend.io | - | Animo Solutions | sd-jwt-js | 9 | | Mirko Mollik | cre8 | - | mirkomollik@gmail.com | Mirko | Fraunhofer FIT | sd-jwt-js | 10 | 11 | ## 1. What Does Being a Maintainer Entail 12 | 13 | - Reviewing code contributions. 14 | - Managing issues and bugs. 15 | - Maintaining documentation. 16 | - Communicating with the community. 17 | - Managing version control. 18 | - Participating in project decisions. 19 | - Building and sustaining a contributor community. 20 | 21 | ## 2. How to Become a Maintainer 22 | 23 | Before being considered as a maintainer, contributors should meet the following requirements: 24 | 25 | - A history of substantial and consistent contributions to the project. 26 | - A deep understanding of the project's goals, codebase, and best practices. 27 | - Active involvement in the community, including helping others and engaging in discussions. 28 | - Ultimately, the maintainers decide who will become the new maintainer through a majority vote. 29 | 30 | ## 3. How Maintainers are Removed or Moved to Emeritus Status 31 | 32 | - Inactivity or consensus decision can lead to removal. 33 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | --------- | 7 | | 1.x.x | yes | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | We take the security of our project seriously. If you have discovered a security vulnerability, we appreciate your help in disclosing it to us in a responsible manner. 12 | 13 | Please report any security vulnerabilities by sending an email to [lukas.j.han@gmail.com](mailto:lukas.j.han@gmail.com). 14 | 15 | We will acknowledge receipt of your vulnerability report, commence an investigation, and work on a fix. 16 | 17 | ### Response Timeline: 18 | 19 | - Within 1-2 days: Acknowledge the report. 20 | - Within 7-10 days: Confirm the vulnerability and determine its impact. 21 | - Within 14-30 days: Patch the vulnerability, release an update, and publish a security advisory if necessary. 22 | 23 | Please refrain from publicly disclosing the vulnerability until we have addressed it. 24 | 25 | Thank you for helping to keep our project and its users safe. 26 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", 3 | "organizeImports": { 4 | "enabled": false 5 | }, 6 | "files": { 7 | "ignore": ["**/dist/**", "**/coverage/**", "package.json", "lerna.json"] 8 | }, 9 | "formatter": { 10 | "enabled": true, 11 | "indentStyle": "space", 12 | "indentWidth": 2, 13 | "lineEnding": "lf" 14 | }, 15 | "javascript": { 16 | "formatter": { 17 | "quoteStyle": "single" 18 | } 19 | }, 20 | "linter": { 21 | "enabled": true, 22 | "rules": { 23 | "recommended": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/0.x/README.md: -------------------------------------------------------------------------------- 1 | # 0.x version Documentation 2 | 3 | ## Contents 4 | 5 | - [sdjwt Instance & config](./sdjwt-instance.md) 6 | - [Disclosure Frame](./disclosureframe.md) 7 | - [Issue API](./issue.md) 8 | - [Present API](./present.md) 9 | - [Validation API](./validate.md) 10 | - [Verify API](./verify.md) 11 | - [Encode & Decode](./encode-decode.md) 12 | - [Get Claims](./claims.md) 13 | - [Key Binding](./keybinding.md) 14 | -------------------------------------------------------------------------------- /docs/0.x/claims.md: -------------------------------------------------------------------------------- 1 | ## Claims 2 | 3 | You can get claims by using `getClaims` method 4 | 5 | ```ts 6 | const claims = sdjwt.getClaims(encodedSdjwt); 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/0.x/disclosureframe.md: -------------------------------------------------------------------------------- 1 | To issue claims into a valid SD-JWT we use Disclosure Frame to define which properties should be selectively diclosable. 2 | We use two special property that you can't use in your claim. 3 | 4 | ```ts 5 | { 6 | _sd: string[], 7 | _sd_decoy: number, 8 | } 9 | ``` 10 | 11 | - `_sd`: the property name that can be selectively diclosable. 12 | - `_sd_decoy`: an optional property that defines the number of decoy digests to add. 13 | 14 | ## Examples 15 | 16 | - Object 17 | 18 | ```ts 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe' 22 | } 23 | const diclosureFrame = { 24 | _sd: ['firstname'] // set firstname as selectively discloseable 25 | } 26 | 27 | const result = { 28 | _sd: ['JoQEib3CnAVoYzYLSk6E9I1ZPR4HbMzHt8qL671Si4o'] 29 | lastname: 'Doe', 30 | } 31 | ``` 32 | 33 | - Array 34 | 35 | ```ts 36 | const claims = { 37 | data: ['A', 'B'], 38 | }; 39 | 40 | const disclosureFrame = { 41 | data: { 42 | _sd: [0, 1], // index of 'data' Array 43 | }, 44 | }; 45 | 46 | const result = { 47 | data: [ 48 | { '...': 'zrdMe3fQZCTNK4eb-5tlPcXP9Ea17fcD3FuGPx06C04' }, 49 | { '...': 'dVM4-VFC0txjO1UnVZ7DALN0thT3UXgXI8krWFL5Nj8' }, 50 | ], 51 | }; 52 | ``` 53 | 54 | - Nested Object 55 | 56 | ```ts 57 | const claims = { 58 | color: { 59 | title: '#232323', 60 | footer: '#121212', 61 | button: '#fefefe', 62 | }, 63 | }; 64 | 65 | const disclosureFrame = { 66 | color: { 67 | // set color.title and color.footer as selectively discloseable 68 | _sd: ['title', 'footer'], 69 | }, 70 | }; 71 | 72 | const result = { 73 | color: { 74 | _sd: [ 75 | '02d7bUYevjfAzJ0Gr42ymHy66ezQVL7huNGBO68xSfs', 76 | 'ai7P4vgPZ-Jk1QwL55BLQqtN2gwWy31-pi2VGWiIggs', 77 | ], 78 | button: '#fefefe', 79 | }, 80 | }; 81 | ``` 82 | 83 | - Array in array 84 | 85 | ```ts 86 | const claims = { 87 | data: [ 88 | ['A', 'B', 'C'], 89 | ['D', 'E', 'F', 'G'], 90 | ], 91 | }; 92 | 93 | const disclosureFrame = { 94 | data: { 95 | 0: { 96 | _sd: [0, 2], // `A` and `C` in data[0] 97 | }, 98 | }, 99 | }; 100 | 101 | const result = { 102 | colors: [ 103 | [ 104 | { '...': 'kQv_QULrikI6mBs-1WmNeJZKNvf8dJNqio5QSJA_ZZY' }, 105 | 'B', 106 | { '...': 'zZ9am-i8OcoLC7p_Mc7jOm2ibr_6gklO57NCrxabR_0' }, 107 | ], 108 | ['D', 'E', 'F', 'G'], 109 | ], 110 | }; 111 | ``` 112 | 113 | - Object in array 114 | 115 | ```ts 116 | const claims = { 117 | foods: [ 118 | { 119 | type: 'apple', 120 | number: 2, 121 | }, 122 | 'beef', 123 | 'juice', 124 | ], 125 | }; 126 | 127 | const disclosureFrame = { 128 | foods: { 129 | 0: { 130 | _sd: ['type'], // `type` property of items[0] 131 | }, 132 | }, 133 | }; 134 | 135 | const result = { 136 | foods: [ 137 | { 138 | _sd: ['7aGqCE9HepzELBi59BvxxriDiV7uiB4yHTyN1im_m4M'], 139 | number: 2, 140 | }, 141 | 'beef', 142 | 'juice', 143 | ], 144 | }; 145 | ``` 146 | 147 | - Decoy 148 | 149 | ```ts 150 | const claims = { 151 | color: { 152 | title: '#232323', 153 | footer: '#121212', 154 | button: '#fefefe', 155 | }, 156 | }; 157 | 158 | const disclosureFrame = { 159 | color: { 160 | _sd: ['title', 'footer'], 161 | _sd_decoy: 1, 162 | }, 163 | }; 164 | 165 | const result = { 166 | color: { 167 | _sd: [ 168 | 'ErJnMnGG9-pyfTod0UvVKHGzVvU4h-VEZhOw2-Oi39Q', 169 | 'A544ERLAEA5JKXFr62mg-G8fmxqgTFDigqYuiYpz_4E', 170 | 'vH6Ut_jfOnYGphLIbFuiZFu4Uh0osveZ0npBbaim6n8', 171 | ], 172 | button: '#fefefe', 173 | }, 174 | }; 175 | ``` 176 | 177 | **Note**: We are using JSON stringify for [disclosure format](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#disclosure_format_considerations) 178 | -------------------------------------------------------------------------------- /docs/0.x/encode-decode.md: -------------------------------------------------------------------------------- 1 | ## Decode 2 | 3 | You can decode SD JWT encoded credential to SDJwt Object. 4 | 5 | ```ts 6 | // decoded variable is SDJwt Object 7 | const decoded = sdjwt.decode(encodedSdjwt); 8 | 9 | // You can access inside of SDJwt object 10 | console.log(decoded.jwt, decoded.disclosures); 11 | ``` 12 | 13 | ## Encode 14 | 15 | You can encode SD JWT object instance to encoded credential 16 | 17 | ```ts 18 | // encode SDJwt object to string 19 | const encodedSdjwt = sdjwt.encode(decoded); 20 | // return base64url string 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/0.x/issue.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | ```ts 4 | const encodedSdjwt = await sdjwt.issue(payload, disclosureFrame, options); 5 | ``` 6 | 7 | ## Parameters 8 | 9 | - payload: the payload of SD JWT [Object] 10 | - disclosureFrame: to define which properties should be selectively diclosable (optional, if not provided, there is no disclosure) 11 | - options: (optional) 12 | 13 | ```ts 14 | options?: { 15 | header: object; // this is custom header of JWT. 16 | }, 17 | ``` 18 | 19 | ## Returns 20 | 21 | encoded SDJWT string 22 | 23 | - hash_alg: sha-256 24 | -------------------------------------------------------------------------------- /docs/0.x/keybinding.md: -------------------------------------------------------------------------------- 1 | You can make Key Binding JWT and verify it. 2 | 3 | ## Issue 4 | 5 | Assume that you have SD-JWT from issuer like this. 6 | 7 | ```ts 8 | const claims = { 9 | firstname: 'John', 10 | lastname: 'Doe', 11 | ssn: '123-45-6789', 12 | id: '1234', 13 | }; 14 | const disclosureFrame: DisclosureFrame = { 15 | _sd: ['firstname', 'id'], 16 | }; 17 | const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame); 18 | ``` 19 | 20 | ## Key Binding 21 | 22 | ```ts 23 | const kbPayload = { 24 | iat: Math.floor(Date.now() / 1000), 25 | aud: 'https://example.com', 26 | nonce: '1234', 27 | custom: 'data', 28 | }; 29 | const presentedSdJwt = await sdjwt.present( 30 | encodedSdjwt, 31 | { id: true }, 32 | { 33 | kb: { 34 | payload: kbPayload, 35 | }, 36 | }, 37 | ); 38 | ``` 39 | 40 | ## Verify 41 | 42 | ```ts 43 | const verified = await sdjwt.verify(presentedSdJwt, ['id', 'ssn'], true); 44 | console.log(verified.kb); // key binding header and payload is in kb object 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/0.x/present.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | const presentedSDJwt = await sdjwt.present(encodedSdjwt, presentationFrame, options); 3 | ``` 4 | 5 | ## Parameters 6 | 7 | - encodedSdjwt: encoded SD JWT [string] 8 | - presentationFrame: Represent the properties that should be selectively disclosed [object] 9 | - options: (optional) 10 | 11 | ```ts 12 | options?: { 13 | kb?: KBOptions; // options for Key Binding 14 | }, 15 | ``` 16 | 17 | ### PresentationFrame 18 | 19 | ```ts 20 | const claims = { 21 | data: { 22 | arr: 'value'; 23 | } 24 | } 25 | 26 | // To present 'arr' property 27 | const presentationFrame = { 28 | data: { 29 | arr: true 30 | } 31 | } 32 | ``` 33 | 34 | ```ts 35 | const claims = { 36 | data: { 37 | arr: 'value'; 38 | } 39 | } 40 | 41 | // To present 'data' property 42 | const presentationFrame = { 43 | data: true, 44 | } 45 | ``` 46 | 47 | ```ts 48 | const claims = { 49 | data: ['A', 'B'], 50 | }; 51 | 52 | // To present 1st element of 'data' property 53 | const presentationFrame = { 54 | data: { 55 | 0: true, 56 | }, 57 | }; 58 | ``` 59 | 60 | ## Returns 61 | 62 | selectively disclosed encoded SD JWT string. 63 | 64 | ## presentationKeys 65 | 66 | You can check the available presentationKeys by using `presentableKeys` method 67 | 68 | ```ts 69 | const keys = await sdjwt.presentableKeys(); 70 | // return string[] 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/0.x/sdjwt-instance.md: -------------------------------------------------------------------------------- 1 | ## Create new Instance of SDJwtInstance 2 | 3 | You can create a new instance of sdjwt with a custom config. 4 | 5 | ```ts 6 | import sdjwtInstance from '@sd-jwt/core'; 7 | 8 | const sdjwt = new SDJwtInstance({ 9 | signer, 10 | verifier, 11 | signAlg: 'EdDSA', 12 | hasher: digest, 13 | hashAlg: 'sha-256', 14 | saltGenerator: generateSalt, 15 | }); 16 | ``` 17 | 18 | You can change the instances config by using config method. 19 | 20 | ## Configurations 21 | 22 | ```ts 23 | sdjwt.config({ 24 | hasher: CustomHasher, 25 | }); 26 | ``` 27 | 28 | - The config type 29 | 30 | ```ts 31 | type SDJWTConfig = { 32 | // omit typ property in JWT header 33 | omitTyp?: boolean; 34 | // hash function: (data: string) => Promise or string; 35 | hasher?: Hasher; 36 | // hash algorithm string (e.g. 'sha-256') 37 | hashAlg?: string; 38 | // salt generate function: (length: number) => Promise or string; 39 | saltGenerator?: SaltGenerator; 40 | // sign function: (data: string) => Promise or string; 41 | signer?: Signer; 42 | // sign algorithm string (e.g. 'EdDSA') 43 | signAlg?: string; 44 | // verify function: (data: string, signature: string) => Promise or boolean; 45 | verifier?: Verifier; 46 | // optional key binding sign function 47 | kbSigner?: Signer; 48 | // optional key binding sign algorithm 49 | kbSignAlg?: string; 50 | // optional key binding verify function: (data: string, sig: string, payload: JwtPayload) => Promise or boolean; 51 | // JwtPayload: { cnf?: { jwk: JsonWebKey } } 52 | kbVerifier?: KbVerifier; 53 | }; 54 | ``` 55 | 56 | ## Methods 57 | 58 | - issue(payload[, disclosureFrame, options]) 59 | - present(encodedSDJwt[, presentationFrame, options]) 60 | - validate(encodedSDJwt) 61 | - verify(encodedSDJwt[, requiredClaimKeys, requireKeyBindings]) 62 | - config(newConfig) 63 | - encode(sdJwt) 64 | - decode(encodedSDJwt) 65 | - keys(encodedSDJwt) 66 | - presentableKeys(encodedSDJwt) 67 | - getClaims(encodedSDJwt) 68 | -------------------------------------------------------------------------------- /docs/0.x/validate.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | const validated = await sdjwt.validate(encodedSdjwt); 3 | ``` 4 | 5 | ## Parameters 6 | 7 | - encodedSdjwt: encoded SD JWT [string] 8 | 9 | ## Return 10 | 11 | - boolean: true if SD JWT is validated 12 | -------------------------------------------------------------------------------- /docs/0.x/verify.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | const verified = await sdjwt.verify( 3 | encodedSdjwt, 4 | requiredClaimKeys, 5 | requireKeyBindings, 6 | ); 7 | ``` 8 | 9 | ## Parameters 10 | 11 | - encodedSdjwt: encoded SD JWT [string] 12 | - requiredClaimKeys: required JSON properties to verify [Array] (optional) 13 | - requireKeyBindings: required verify Key Binding JWT [boolean] (optional) 14 | 15 | ```ts 16 | { 17 | data: { 18 | arr: ['value']; 19 | } 20 | } 21 | 22 | // The JSON Path of value 'value' is 'data.arr.0' 23 | ``` 24 | 25 | ## Return 26 | 27 | - object 28 | - header: header of SD JWT [object] 29 | - payload: payload of SD JWT [object] 30 | - kb: keybinding JWT [object] (optional) 31 | - header: header of keybinding JWT [object] 32 | - payload: payload of keybinding JWT [object] 33 | 34 | if verify failed, throw exception. 35 | 36 | ## Keys 37 | 38 | You can check all JSON Path by `keys` method 39 | 40 | ```ts 41 | const keys = await sdjwt.keys(encodedSdjwt); 42 | // return all JSON Path keys in claims [string[]] 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## What is Selective Disclosure for JWTs? 2 | 3 | Selective Disclosure for JWTs (JSON Web Tokens) is a concept aimed at enhancing privacy and data minimization in digital transactions. It allows the holder of a JWT to reveal only a subset of the information contained in the token, rather than disclosing the full contents. 4 | 5 | You can check the details of the standard here: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html 6 | 7 | ## What is SD-JWT-JS? 8 | 9 | SD-JWT-JS is a [promise-based](https://javascript.info/promise-basics) SD JWT Client for [node.js](https://nodejs.org/) and the browser. It is [isomorphic](https://www.lullabot.com/articles/what-is-an-isomorphic-application) (= it can run in the browser and nodejs with the same codebase). On the server-side it uses the native [crypto](https://nodejs.org/api/crypto.html) module, while on the client (browser) it uses [broswer crypto module](https://developer.mozilla.org/en-US/docs/Web/API/Crypto). 10 | 11 | ## Features 12 | 13 | - Issuer 14 | - Issue SD JWT Token 15 | - Add Key Binding in SD JWT Token 16 | - Holder 17 | - Validate SD JWT Token 18 | - Selectively present SD JWT Token 19 | - Verifier 20 | - Verify SD JWT Token 21 | - Verify Key Binding 22 | 23 | ## Installing 24 | 25 | ### If you want to use SD-JWT VC for credentials 26 | 27 | Using npm: 28 | 29 | ```bash 30 | npm install @sd-jwt/sd-jwt-vc 31 | ``` 32 | 33 | Using yarn: 34 | 35 | ```bash 36 | yarn add @sd-jwt/sd-jwt-vc 37 | ``` 38 | 39 | Using pnpm: 40 | 41 | ```bash 42 | pnpm install @sd-jwt/sd-jwt-vc 43 | ``` 44 | 45 | ### Using SD JWT Features Only 46 | 47 | Using npm: 48 | 49 | ```bash 50 | npm install @sd-jwt/core 51 | ``` 52 | 53 | Using yarn: 54 | 55 | ```bash 56 | yarn add @sd-jwt/core 57 | ``` 58 | 59 | Using pnpm: 60 | 61 | ```bash 62 | pnpm install @sd-jwt/core 63 | ``` 64 | 65 | ## Usage & Documentation 66 | 67 | You can find the documentation and usage examples in the directory by versions 68 | 69 | - [0.x documentation](0.x/README.md) 70 | -------------------------------------------------------------------------------- /docs/imgs/concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwallet-foundation/sd-jwt-js/d2f2cb5a4d9f40e5d90209f572665a9bf1f0844b/docs/imgs/concept.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # SD JWT Examples 2 | 3 | This directory contains examples of how to use the SD JWT(sd-jwt-js) library. 4 | 5 | ## How to run the example 6 | 7 | ```bash 8 | pnpm run {example_file_name} 9 | 10 | # example 11 | pnpm run all 12 | ``` 13 | 14 | ### Example lists 15 | 16 | - core: Example of basic usage(issue, validate, present, verify) of SD JWT 17 | - decode: Decoding example of a SD JWT (only use decode package) 18 | - present: Example of presenting the SD JWT (only use present, decode package) 19 | -------------------------------------------------------------------------------- /examples/decode-example/README.md: -------------------------------------------------------------------------------- 1 | # SD JWT Core Examples 2 | 3 | This directory contains decoding example of a SD JWT (only use decode package) 4 | 5 | ## Run the example 6 | 7 | ```bash 8 | pnpm run decode 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/decode-example/decode.ts: -------------------------------------------------------------------------------- 1 | import { decodeSdJwt, getClaims } from '@sd-jwt/decode'; 2 | import { digest } from '@sd-jwt/crypto-nodejs'; 3 | 4 | (async () => { 5 | const sdjwt = 6 | 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; 7 | const decodedSdJwt = await decodeSdJwt(sdjwt, digest); 8 | console.log('The decoded SD JWT is:'); 9 | console.log(JSON.stringify(decodedSdJwt, null, 2)); 10 | console.log( 11 | '================================================================', 12 | ); 13 | 14 | // Get the claims from the SD JWT 15 | const claims = await getClaims( 16 | decodedSdJwt.jwt.payload, 17 | decodedSdJwt.disclosures, 18 | digest, 19 | ); 20 | 21 | console.log('The claims are:'); 22 | console.log(JSON.stringify(claims, null, 2)); 23 | })(); 24 | -------------------------------------------------------------------------------- /examples/decode-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdjwt-decode-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "decode": "ts-node decode.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@types/node": "^20.10.4", 15 | "ts-node": "^10.9.2", 16 | "typescript": "^5.3.3" 17 | }, 18 | "dependencies": { 19 | "@sd-jwt/decode": "workspace:*", 20 | "@sd-jwt/types": "workspace:*", 21 | "@sd-jwt/crypto-nodejs": "workspace:*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/decode-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/decode" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/present-example/README.md: -------------------------------------------------------------------------------- 1 | # SD JWT Core Examples 2 | 3 | This directory contains example of presenting the SD JWT (only use present, decode package) 4 | 5 | ## Run the example 6 | 7 | ```bash 8 | pnpm run present 9 | 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/present-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdjwt-present-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "present": "ts-node present.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@types/node": "^20.10.4", 15 | "ts-node": "^10.9.2", 16 | "typescript": "^5.3.3" 17 | }, 18 | "dependencies": { 19 | "@sd-jwt/present": "workspace:*", 20 | "@sd-jwt/decode": "workspace:*", 21 | "@sd-jwt/types": "workspace:*", 22 | "@sd-jwt/crypto-nodejs": "workspace:*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/present-example/present.ts: -------------------------------------------------------------------------------- 1 | import { present, presentableKeys } from '@sd-jwt/present'; 2 | import { decodeSdJwt, getClaims } from '@sd-jwt/decode'; 3 | import { digest } from '@sd-jwt/crypto-nodejs'; 4 | 5 | (async () => { 6 | const sdjwt = 7 | 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; 8 | const decodedSdJwt = await decodeSdJwt(sdjwt, digest); 9 | console.log('The decoded Disclosures are:'); 10 | console.log(JSON.stringify(decodedSdJwt.disclosures, null, 2)); 11 | console.log( 12 | '================================================================', 13 | ); 14 | 15 | const claims = await getClaims( 16 | decodedSdJwt.jwt.payload, 17 | decodedSdJwt.disclosures, 18 | digest, 19 | ); 20 | 21 | console.log('The claims are:'); 22 | console.log(JSON.stringify(claims, null, 2)); 23 | 24 | // You can get presentable keys from the decoded SD JWT 25 | const keys = await presentableKeys( 26 | decodedSdJwt.jwt.payload, 27 | decodedSdJwt.disclosures, 28 | digest, 29 | ); 30 | console.log('The presentable keys are:', keys); 31 | 32 | // You can present the SD JWT with the combination of presentable keys 33 | const presentedSdJwt = await present<{ 34 | foo: string; 35 | test: { zzz: string }; 36 | arr: (string | { a: string })[]; 37 | }>( 38 | sdjwt, 39 | { 40 | foo: true, 41 | arr: { 42 | 0: true, 43 | }, 44 | test: { 45 | zzz: true, 46 | }, 47 | }, 48 | digest, 49 | ); 50 | 51 | console.log('The presented SD JWT is:', presentedSdJwt); 52 | 53 | console.log( 54 | '================================================================', 55 | ); 56 | 57 | // If you decoded the presented SD JWT, you can see the presented disclosures 58 | // It only contains the disclosed keys you presented 59 | const presentedDecodedSdJwt = await decodeSdJwt(presentedSdJwt, digest); 60 | 61 | console.log('The decoded Disclosures are:'); 62 | console.log(JSON.stringify(presentedDecodedSdJwt.disclosures, null, 2)); 63 | 64 | const presentedClaims = await getClaims( 65 | presentedDecodedSdJwt.jwt.payload, 66 | presentedDecodedSdJwt.disclosures, 67 | digest, 68 | ); 69 | 70 | console.log('The presented claims are:'); 71 | console.log(JSON.stringify(presentedClaims, null, 2)); 72 | })(); 73 | -------------------------------------------------------------------------------- /examples/present-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/present" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/README.md: -------------------------------------------------------------------------------- 1 | # SD JWT Core Examples 2 | 3 | This directory contains example of basic usage(issue, validate, present, verify) of SD JWT 4 | 5 | ## Run the example 6 | 7 | ```bash 8 | pnpm run {example_file_name} 9 | 10 | # example 11 | pnpm run all 12 | ``` 13 | 14 | ### Example lists 15 | 16 | - basic: Example of basic usage(issue, validate, present, verify) of SD JWT 17 | - all: Example of issue, present and verify the comprehensive data. 18 | - custom: Example of using custom hasher and salt generator for SD JWT 19 | - custom_header: Example of using custom header for SD JWT 20 | - sdjwtobject: Example of using SD JWT Object 21 | - decoy: Example of adding decoy digest in SD JWT 22 | - kb: key binding example in SD JWT 23 | - decode: Decoding example of a SD JWT sample 24 | 25 | ### Variables In Examples 26 | 27 | - claims: the user's information 28 | - disclosureFrame: specify which claims should be disclosed 29 | - credential: Issued Encoded SD JWT. 30 | - validated: result of SD JWT validation 31 | - presentationFrame: specify which claims should be presented 32 | - presentation: Presented Encoded SD JWT. 33 | - requiredClaims: specify which claims should be verified 34 | - verified: result of verification 35 | - sdJwtToken: SD JWT Token Object 36 | - SDJwtInstance: SD JWT Instance 37 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/all.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, generateSalt, ES256 } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | data: { 25 | firstname: 'John', 26 | lastname: 'Doe', 27 | ssn: '123-45-6789', 28 | list: [{ r: '1' }, 'b', 'c'], 29 | }, 30 | data2: { 31 | hi: 'bye', 32 | }, 33 | }; 34 | 35 | // Issuer Define the disclosure frame to specify which claims can be disclosed 36 | const disclosureFrame: DisclosureFrame = { 37 | _sd: ['firstname', 'id', 'data2'], 38 | data: { 39 | _sd: ['list'], 40 | _sd_decoy: 2, 41 | list: { 42 | _sd: [0, 2], 43 | _sd_decoy: 1, 44 | 0: { 45 | _sd: ['r'], 46 | }, 47 | }, 48 | }, 49 | data2: { 50 | _sd: ['hi'], 51 | }, 52 | }; 53 | 54 | // Issue a signed JWT credential with the specified claims and disclosures 55 | // Return a Encoded SD JWT. Issuer send the credential to the holder 56 | const credential = await sdjwt.issue(claims, disclosureFrame); 57 | console.log('encodedJwt:', credential); 58 | 59 | // Holder Receive the credential from the issuer and validate it 60 | // Return a result of header and payload 61 | const validated = await sdjwt.validate(credential); 62 | console.log('validated:', validated); 63 | 64 | // You can decode the SD JWT to get the payload and the disclosures 65 | const sdJwtToken = await sdjwt.decode(credential); 66 | 67 | // You can get the keys of the claims from the decoded SD JWT 68 | const keys = await sdJwtToken.keys(digest); 69 | console.log({ keys }); 70 | 71 | // You can get the claims from the decoded SD JWT 72 | const payloads = await sdJwtToken.getClaims(digest); 73 | 74 | // You can get the presentable keys from the decoded SD JWT 75 | const presentableKeys = await sdJwtToken.presentableKeys(digest); 76 | 77 | console.log({ 78 | payloads: JSON.stringify(payloads, null, 2), 79 | disclosures: JSON.stringify(sdJwtToken.disclosures, null, 2), 80 | claim: JSON.stringify(sdJwtToken.jwt?.payload, null, 2), 81 | presentableKeys, 82 | }); 83 | 84 | console.log( 85 | '================================================================', 86 | ); 87 | 88 | // Holder Define the presentation frame to specify which claims should be presented 89 | // The list of presented claims must be a subset of the disclosed claims 90 | // the presentation frame is determined by the verifier or the protocol that was agreed upon between the holder and the verifier 91 | const presentationFrame = { firstname: true, id: true }; 92 | 93 | // Create a presentation using the issued credential and the presentation frame 94 | // return a Encoded SD JWT. Holder send the presentation to the verifier 95 | const presentation = await sdjwt.present( 96 | credential, 97 | presentationFrame, 98 | ); 99 | console.log('presentedSDJwt:', presentation); 100 | 101 | // Verifier Define the required claims that need to be verified in the presentation 102 | const requiredClaims = ['firstname', 'id', 'data.ssn']; 103 | 104 | // Verify the presentation using the public key and the required claims 105 | // return a boolean result 106 | const verified = await sdjwt.verify(presentation, requiredClaims); 107 | console.log('verified:', verified); 108 | })(); 109 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/basic.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | 26 | // Issuer Define the disclosure frame to specify which claims can be disclosed 27 | const disclosureFrame: DisclosureFrame = { 28 | _sd: ['firstname', 'lastname', 'ssn'], 29 | }; 30 | 31 | // Issue a signed JWT credential with the specified claims and disclosures 32 | // Return a Encoded SD JWT. Issuer send the credential to the holder 33 | const credential = await sdjwt.issue(claims, disclosureFrame); 34 | 35 | // Holder Receive the credential from the issuer and validate it 36 | // Return a result of header and payload 37 | const valid = await sdjwt.validate(credential); 38 | 39 | // Holder Define the presentation frame to specify which claims should be presented 40 | // The list of presented claims must be a subset of the disclosed claims 41 | // the presentation frame is determined by the verifier or the protocol that was agreed upon between the holder and the verifier 42 | const presentationFrame = { firstname: true, id: true, ssn: true }; 43 | 44 | // Create a presentation using the issued credential and the presentation frame 45 | // return a Encoded SD JWT. Holder send the presentation to the verifier 46 | const presentation = await sdjwt.present( 47 | credential, 48 | presentationFrame, 49 | ); 50 | 51 | // Verifier Define the required claims that need to be verified in the presentation 52 | const requiredClaims = ['firstname', 'ssn', 'id']; 53 | 54 | // Verify the presentation using the public key and the required claims 55 | // return a boolean result 56 | const verified = await sdjwt.verify(presentation, requiredClaims); 57 | console.log(verified); 58 | })(); 59 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/custom.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | 26 | // Issuer Define the disclosure frame to specify which claims can be disclosed 27 | const disclosureFrame: DisclosureFrame = { 28 | _sd: ['firstname', 'id'], 29 | }; 30 | 31 | // Issue a signed JWT credential with the specified claims and disclosures 32 | // Return a Encoded SD JWT. Issuer send the credential to the holder 33 | const credential = await sdjwt.issue(claims, disclosureFrame); 34 | console.log('encodedJwt:', credential); 35 | 36 | // Holder Receive the credential from the issuer and validate it 37 | // Return a result of header and payload 38 | const validated = await sdjwt.validate(credential); 39 | console.log('validated:', validated); 40 | 41 | // You can decode the SD JWT to get the payload and the disclosures 42 | const sdJwtToken = await sdjwt.decode(credential); 43 | 44 | // You can get the keys of the claims from the decoded SD JWT 45 | const keys = await sdJwtToken.keys(digest); 46 | console.log({ keys }); 47 | 48 | // You can get the claims from the decoded SD JWT 49 | const payloads = await sdJwtToken.getClaims(digest); 50 | 51 | // You can get the presentable keys from the decoded SD JWT 52 | const presentableKeys = await sdJwtToken.presentableKeys(digest); 53 | 54 | console.log({ 55 | payloads: JSON.stringify(payloads, null, 2), 56 | disclosures: JSON.stringify(sdJwtToken.disclosures, null, 2), 57 | claim: JSON.stringify(sdJwtToken.jwt?.payload, null, 2), 58 | presentableKeys, 59 | }); 60 | 61 | console.log( 62 | '================================================================', 63 | ); 64 | 65 | // Holder Define the presentation frame to specify which claims should be presented 66 | // The list of presented claims must be a subset of the disclosed claims 67 | // the presentation frame is determined by the verifier or the protocol that was agreed upon between the holder and the verifier 68 | const presentationFrame = { firstname: true, id: true }; 69 | 70 | // Create a presentation using the issued credential and the presentation frame 71 | // return a Encoded SD JWT. Holder send the presentation to the verifier 72 | const presentation = await sdjwt.present( 73 | credential, 74 | presentationFrame, 75 | ); 76 | console.log('presentedSDJwt:', presentation); 77 | 78 | // Verifier Define the required claims that need to be verified in the presentation 79 | const requiredClaims = ['firstname', 'id']; 80 | 81 | // Verify the presentation using the public key and the required claims 82 | // return a boolean result 83 | const verified = await sdjwt.verify(presentation, requiredClaims); 84 | console.log('verified:', verified); 85 | })(); 86 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/custom_header.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | 26 | // Issuer Define the disclosure frame to specify which claims can be disclosed 27 | const disclosureFrame: DisclosureFrame = { 28 | _sd: ['firstname', 'id'], 29 | }; 30 | 31 | // Issue a signed JWT credential with the specified claims and disclosures 32 | // Return a Encoded SD JWT. Issuer send the credential to the holder 33 | const credential = await sdjwt.issue(claims, disclosureFrame, { 34 | header: { typ: 'dc+sd-jwt', custom: 'data' }, // You can add custom header data to the SD JWT 35 | }); 36 | console.log('encodedSdjwt:', credential); 37 | 38 | // You can check the custom header data by decoding the SD JWT 39 | const sdJwtToken = await sdjwt.decode(credential); 40 | console.log(sdJwtToken); 41 | })(); 42 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/decode.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 3 | 4 | (async () => { 5 | const { signer, verifier } = await createSignerVerifier(); 6 | 7 | // Create SDJwt instance for use 8 | const sdjwt = new SDJwtInstance({ 9 | signer, 10 | signAlg: ES256.alg, 11 | verifier, 12 | hasher: digest, 13 | saltGenerator: generateSalt, 14 | kbSigner: signer, 15 | kbSignAlg: 'EdDSA', 16 | kbVerifier: verifier, 17 | }); 18 | 19 | // this is an example of SD JWT 20 | const data = 21 | 'eyJhbGciOiAiRVMyNTYiLCAia2lkIjogImRvYy1zaWduZXItMDUtMjUtMjAyMiIsICJ0eXAiOiAidmMrc2Qtand0In0.eyJfc2QiOiBbIjA5dktySk1PbHlUV00wc2pwdV9wZE9CVkJRMk0xeTNLaHBINTE1blhrcFkiLCAiMnJzakdiYUMwa3k4bVQwcEpyUGlvV1RxMF9kYXcxc1g3NnBvVWxnQ3diSSIsICJFa084ZGhXMGRIRUpidlVIbEVfVkNldUM5dVJFTE9pZUxaaGg3WGJVVHRBIiwgIklsRHpJS2VpWmREd3BxcEs2WmZieXBoRnZ6NUZnbldhLXNONndxUVhDaXciLCAiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBLdVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiamRyVEU4WWNiWTRFaWZ1Z2loaUFlX0JQZWt4SlFaSUNlaVVRd1k5UXF4SSIsICJqc3U5eVZ1bHdRUWxoRmxNXzNKbHpNYVNGemdsaFFHMERwZmF5UXdMVUs0Il0sICJpc3MiOiAiaHR0cHM6Ly9leGFtcGxlLmNvbS9pc3N1ZXIiLCAiaWF0IjogMTY4MzAwMDAwMCwgImV4cCI6IDE4ODMwMDAwMDAsICJkY3QiOiAiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIiwgIl9zZF9hbGciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIkVDIiwgImNydiI6ICJQLTI1NiIsICJ4IjogIlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTGlsRGxzN3ZDZUdlbWMiLCAieSI6ICJaeGppV1diWk1RR0hWV0tWUTRoYlNJaXJzVmZ1ZWNDRTZ0NGpUOUYySFpRIn19fQ.b036DutqQ72WszrCq0GuqZnbws3MApQyzA41I5DSJmenUfsADtqW8FbI_N04FP1wZDF_JtV6a6Ke3Z7apkoTLA~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImlzX292ZXJfMTgiLCB0cnVlXQ~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImlzX292ZXJfMjEiLCB0cnVlXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImlzX292ZXJfNjUiLCB0cnVlXQ~'; 22 | const decodedObject = await sdjwt.decode(data); 23 | console.log(decodedObject); 24 | 25 | const claims = await sdjwt.getClaims(data); 26 | console.log(claims); 27 | })(); 28 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/decoy.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | // Issuer Define the claims object with the user's information 18 | const claims = { 19 | lastname: 'Doe', 20 | ssn: '123-45-6789', 21 | id: '1234', 22 | }; 23 | 24 | // Issuer Define the disclosure frame to specify which claims can be disclosed 25 | const disclosureFrame: DisclosureFrame = { 26 | _sd: ['id'], 27 | _sd_decoy: 1, // 1 decoy digest will be added in SD JWT 28 | }; 29 | const credential = await sdjwt.issue(claims, disclosureFrame); 30 | console.log('encodedSdjwt:', credential); 31 | 32 | // You can check the decoy digest in the SD JWT by decoding it 33 | const sdJwtToken = await sdjwt.decode(credential); 34 | console.log(sdJwtToken); 35 | })(); 36 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/flattenJSON.ts: -------------------------------------------------------------------------------- 1 | import { FlattenJSON, SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, generateSalt, ES256 } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | signAlg: ES256.alg, 12 | verifier, 13 | hasher: digest, 14 | saltGenerator: generateSalt, 15 | kbSigner: signer, 16 | kbSignAlg: ES256.alg, 17 | kbVerifier: verifier, 18 | }); 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | const disclosureFrame: DisclosureFrame = { 26 | _sd: ['firstname', 'id'], 27 | }; 28 | 29 | const kbPayload = { 30 | iat: Math.floor(Date.now() / 1000), 31 | aud: 'https://example.com', 32 | nonce: '1234', 33 | custom: 'data', 34 | }; 35 | 36 | const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame); 37 | console.log('encodedSdjwt:', encodedSdjwt); 38 | 39 | const flattenJSON = FlattenJSON.fromEncode(encodedSdjwt); 40 | console.log('flattenJSON(credential): ', flattenJSON.toJson()); 41 | 42 | const presentedSdJwt = await sdjwt.present( 43 | encodedSdjwt, 44 | { id: true }, 45 | { 46 | kb: { 47 | payload: kbPayload, 48 | }, 49 | }, 50 | ); 51 | 52 | const flattenPresentationJSON = FlattenJSON.fromEncode(presentedSdJwt); 53 | console.log('flattenJSON(presentation): ', flattenPresentationJSON.toJson()); 54 | 55 | const verified = await sdjwt.verify(presentedSdJwt, ['id', 'ssn'], true); 56 | console.log(verified); 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/generalJSON.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GeneralJSON, 3 | SDJwtGeneralJSONInstance, 4 | SDJwtInstance, 5 | } from '@sd-jwt/core'; 6 | import type { DisclosureFrame } from '@sd-jwt/types'; 7 | import { createSignerVerifier, digest, generateSalt, ES256 } from './utils'; 8 | 9 | (async () => { 10 | const { signer, verifier } = await createSignerVerifier(); 11 | 12 | // Create SDJwt instance for use 13 | const sdjwt = new SDJwtInstance({ 14 | signer, 15 | signAlg: ES256.alg, 16 | verifier, 17 | hasher: digest, 18 | saltGenerator: generateSalt, 19 | kbSigner: signer, 20 | kbSignAlg: ES256.alg, 21 | kbVerifier: verifier, 22 | }); 23 | const generalJSONSdJwt = new SDJwtGeneralJSONInstance({ 24 | hasher: digest, 25 | verifier, 26 | }); 27 | const claims = { 28 | firstname: 'John', 29 | lastname: 'Doe', 30 | ssn: '123-45-6789', 31 | id: '1234', 32 | }; 33 | const disclosureFrame: DisclosureFrame = { 34 | _sd: ['firstname', 'id'], 35 | }; 36 | 37 | const kbPayload = { 38 | iat: Math.floor(Date.now() / 1000), 39 | aud: 'https://example.com', 40 | nonce: '1234', 41 | custom: 'data', 42 | }; 43 | 44 | const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame); 45 | console.log('encodedSdjwt:', encodedSdjwt); 46 | 47 | const generalJSON = GeneralJSON.fromEncode(encodedSdjwt); 48 | console.log('generalJSON(credential): ', generalJSON.toJson()); 49 | 50 | const presentedSdJwt = await sdjwt.present( 51 | encodedSdjwt, 52 | { id: true }, 53 | { 54 | kb: { 55 | payload: kbPayload, 56 | }, 57 | }, 58 | ); 59 | 60 | const generalPresentationJSON = GeneralJSON.fromEncode(presentedSdJwt); 61 | 62 | await generalPresentationJSON.addSignature( 63 | { 64 | alg: 'ES256', 65 | typ: 'sd+jwt', 66 | kid: 'key-1', 67 | }, 68 | signer, 69 | 'key-1', 70 | ); 71 | 72 | console.log( 73 | 'flattenJSON(presentation): ', 74 | JSON.stringify(generalPresentationJSON.toJson(), null, 2), 75 | ); 76 | 77 | const verified = await sdjwt.verify(presentedSdJwt, ['id', 'ssn'], true); 78 | console.log(verified); 79 | 80 | const generalVerified = await generalJSONSdJwt.verify(generalJSON); 81 | console.log(generalVerified); 82 | })(); 83 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/kb.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | signAlg: ES256.alg, 12 | verifier, 13 | hasher: digest, 14 | saltGenerator: generateSalt, 15 | kbSigner: signer, 16 | kbSignAlg: ES256.alg, 17 | kbVerifier: verifier, 18 | }); 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | const disclosureFrame: DisclosureFrame = { 26 | _sd: ['firstname', 'id'], 27 | }; 28 | 29 | const kbPayload = { 30 | iat: Math.floor(Date.now() / 1000), 31 | aud: 'https://example.com', 32 | nonce: '1234', 33 | custom: 'data', 34 | }; 35 | 36 | const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame); 37 | console.log('encodedSdjwt:', encodedSdjwt); 38 | const sdjwttoken = await sdjwt.decode(encodedSdjwt); 39 | console.log(sdjwttoken); 40 | 41 | const presentedSdJwt = await sdjwt.present( 42 | encodedSdjwt, 43 | { id: true }, 44 | { 45 | kb: { 46 | payload: kbPayload, 47 | }, 48 | }, 49 | ); 50 | 51 | const verified = await sdjwt.verify(presentedSdJwt, ['id', 'ssn'], true); 52 | console.log(verified); 53 | })(); 54 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdjwt-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "basic": "ts-node basic.ts", 9 | "all": "ts-node all.ts", 10 | "sdjwtobject": "ts-node sdjwtobject.ts", 11 | "custom": "ts-node custom.ts", 12 | "decoy": "ts-node decoy.ts", 13 | "custom_header": "ts-node custom_header.ts", 14 | "kb": "ts-node kb.ts", 15 | "decode": "ts-node decode.ts", 16 | "flattenJSON": "ts-node flattenJSON.ts", 17 | "generalJSON": "ts-node generalJSON.ts" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "@types/node": "^20.10.4", 24 | "ts-node": "^10.9.2", 25 | "typescript": "^5.3.3" 26 | }, 27 | "dependencies": { 28 | "@sd-jwt/core": "workspace:*", 29 | "@sd-jwt/types": "workspace:*", 30 | "@sd-jwt/crypto-nodejs": "workspace:*" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/sdjwtobject.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtInstance } from '@sd-jwt/core'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | signer, 11 | signAlg: ES256.alg, 12 | verifier, 13 | hasher: digest, 14 | saltGenerator: generateSalt, 15 | kbSigner: signer, 16 | kbSignAlg: ES256.alg, 17 | kbVerifier: verifier, 18 | }); 19 | // Issuer Define the claims object with the user's information 20 | const claims = { 21 | firstname: 'John', 22 | lastname: 'Doe', 23 | ssn: '123-45-6789', 24 | id: '1234', 25 | }; 26 | 27 | // Issuer Define the disclosure frame to specify which claims can be disclosed 28 | const disclosureFrame: DisclosureFrame = { 29 | _sd: ['firstname', 'id'], 30 | }; 31 | 32 | // Issue a signed JWT credential with the specified claims and disclosures 33 | // Return a Encoded SD JWT. Issuer send the credential to the holder 34 | const credential = await sdjwt.issue(claims, disclosureFrame); 35 | console.log('encodedSdjwt:', credential); 36 | 37 | // You can decode the SD JWT to get the payload and the disclosures 38 | const sdJwtToken = await sdjwt.decode(credential); 39 | console.log(sdJwtToken); 40 | 41 | // You can get the keys of the claims from the decoded SD JWT 42 | const keys = await sdJwtToken.keys(digest); 43 | console.log({ keys }); 44 | 45 | // You can get the claims from the decoded SD JWT 46 | const payloads = await sdJwtToken.getClaims(digest); 47 | 48 | // You can get the presentable keys from the decoded SD JWT 49 | const presentableKeys = await sdJwtToken.presentableKeys(digest); 50 | 51 | console.log({ 52 | payloads: JSON.stringify(payloads, null, 2), 53 | disclosures: JSON.stringify(sdJwtToken.disclosures, null, 2), 54 | claim: JSON.stringify(sdJwtToken.jwt?.payload, null, 2), 55 | presentableKeys, 56 | }); 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/core" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/utils.ts: -------------------------------------------------------------------------------- 1 | import { ES256, digest, generateSalt } from '@sd-jwt/crypto-nodejs'; 2 | export { digest, generateSalt, ES256 }; 3 | 4 | export const createSignerVerifier = async () => { 5 | const { privateKey, publicKey } = await ES256.generateKeyPair(); 6 | return { 7 | signer: await ES256.getSigner(privateKey), 8 | verifier: await ES256.getVerifier(publicKey), 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/README.md: -------------------------------------------------------------------------------- 1 | # SD JWT Core Examples 2 | 3 | This directory contains example of basic usage(issue, validate, present, verify) of SD JWT 4 | 5 | ## Run the example 6 | 7 | ```bash 8 | pnpm run {example_file_name} 9 | 10 | # example 11 | pnpm run all 12 | ``` 13 | 14 | ### Example lists 15 | 16 | - basic: Example of basic usage(issue, validate, present, verify) of SD JWT 17 | - all: Example of issue, present and verify the comprehensive data. 18 | - custom: Example of using custom hasher and salt generator for SD JWT 19 | - custom_header: Example of using custom header for SD JWT 20 | - sdjwtobject: Example of using SD JWT Object 21 | - decoy: Example of adding decoy digest in SD JWT 22 | - kb: key binding example in SD JWT 23 | - decode: Decoding example of a SD JWT sample 24 | 25 | ### Variables In Examples 26 | 27 | - claims: the user's information 28 | - disclosureFrame: specify which claims should be disclosed 29 | - credential: Issued Encoded SD JWT. 30 | - validated: result of SD JWT validation 31 | - presentationFrame: specify which claims should be presented 32 | - presentation: Presented Encoded SD JWT. 33 | - requiredClaims: specify which claims should be verified 34 | - verified: result of verification 35 | - sdJwtToken: SD JWT Token Object 36 | - SDJwtInstance: SD JWT Instance 37 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/basic.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtVcInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | 26 | // Issuer Define the disclosure frame to specify which claims can be disclosed 27 | const disclosureFrame: DisclosureFrame = { 28 | _sd: ['firstname', 'lastname', 'ssn'], 29 | }; 30 | 31 | // Issue a signed JWT credential with the specified claims and disclosures 32 | // Return a Encoded SD JWT. Issuer send the credential to the holder 33 | const credential = await sdjwt.issue( 34 | { 35 | iss: 'Issuer', 36 | iat: new Date().getTime(), 37 | vct: 'ExampleCredentials', 38 | ...claims, 39 | }, 40 | disclosureFrame, 41 | ); 42 | 43 | // Holder Receive the credential from the issuer and validate it 44 | // Return a result of header and payload 45 | const valid = await sdjwt.validate(credential); 46 | 47 | // Holder Define the presentation frame to specify which claims should be presented 48 | // The list of presented claims must be a subset of the disclosed claims 49 | // the presentation frame is determined by the verifier or the protocol that was agreed upon between the holder and the verifier 50 | const presentationFrame = { firstname: true, id: true, ssn: true }; 51 | 52 | // Create a presentation using the issued credential and the presentation frame 53 | // return a Encoded SD JWT. Holder send the presentation to the verifier 54 | const presentation = await sdjwt.present( 55 | credential, 56 | presentationFrame, 57 | ); 58 | 59 | // Verifier Define the required claims that need to be verified in the presentation 60 | const requiredClaims = ['firstname', 'ssn', 'id']; 61 | 62 | // Verify the presentation using the public key and the required claims 63 | // return a boolean result 64 | const verified = await sdjwt.verify(presentation, requiredClaims); 65 | console.log(verified); 66 | })(); 67 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/custom.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtVcInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | 26 | // Issuer Define the disclosure frame to specify which claims can be disclosed 27 | const disclosureFrame: DisclosureFrame = { 28 | _sd: ['firstname', 'id'], 29 | }; 30 | 31 | // Issue a signed JWT credential with the specified claims and disclosures 32 | // Return a Encoded SD JWT. Issuer send the credential to the holder 33 | const credential = await sdjwt.issue( 34 | { 35 | iss: 'Issuer', 36 | iat: new Date().getTime(), 37 | vct: 'ExampleCredentials', 38 | ...claims, 39 | }, 40 | disclosureFrame, 41 | ); 42 | console.log('encodedJwt:', credential); 43 | 44 | // Holder Receive the credential from the issuer and validate it 45 | // Return a result of header and payload 46 | const validated = await sdjwt.validate(credential); 47 | console.log('validated:', validated); 48 | 49 | // You can decode the SD JWT to get the payload and the disclosures 50 | const sdJwtToken = await sdjwt.decode(credential); 51 | 52 | // You can get the keys of the claims from the decoded SD JWT 53 | const keys = await sdJwtToken.keys(digest); 54 | console.log({ keys }); 55 | 56 | // You can get the claims from the decoded SD JWT 57 | const payloads = await sdJwtToken.getClaims(digest); 58 | 59 | // You can get the presentable keys from the decoded SD JWT 60 | const presentableKeys = await sdJwtToken.presentableKeys(digest); 61 | 62 | console.log({ 63 | payloads: JSON.stringify(payloads, null, 2), 64 | disclosures: JSON.stringify(sdJwtToken.disclosures, null, 2), 65 | claim: JSON.stringify(sdJwtToken.jwt?.payload, null, 2), 66 | presentableKeys, 67 | }); 68 | 69 | console.log( 70 | '================================================================', 71 | ); 72 | 73 | // Holder Define the presentation frame to specify which claims should be presented 74 | // The list of presented claims must be a subset of the disclosed claims 75 | // the presentation frame is determined by the verifier or the protocol that was agreed upon between the holder and the verifier 76 | const presentationFrame = { firstname: true, id: true }; 77 | 78 | // Create a presentation using the issued credential and the presentation frame 79 | // return a Encoded SD JWT. Holder send the presentation to the verifier 80 | const presentation = await sdjwt.present( 81 | credential, 82 | presentationFrame, 83 | ); 84 | console.log('presentedSDJwt:', presentation); 85 | 86 | // Verifier Define the required claims that need to be verified in the presentation 87 | const requiredClaims = ['firstname', 'id']; 88 | 89 | // Verify the presentation using the public key and the required claims 90 | // return a boolean result 91 | const verified = await sdjwt.verify(presentation, requiredClaims); 92 | console.log('verified:', verified); 93 | })(); 94 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/custom_header.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtVcInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | 18 | // Issuer Define the claims object with the user's information 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | 26 | // Issuer Define the disclosure frame to specify which claims can be disclosed 27 | const disclosureFrame: DisclosureFrame = { 28 | _sd: ['firstname', 'id'], 29 | }; 30 | 31 | // Issue a signed JWT credential with the specified claims and disclosures 32 | // Return a Encoded SD JWT. Issuer send the credential to the holder 33 | const credential = await sdjwt.issue( 34 | { 35 | iss: 'Issuer', 36 | iat: new Date().getTime(), 37 | vct: 'ExampleCredentials', 38 | ...claims, 39 | }, 40 | disclosureFrame, 41 | { 42 | header: { typ: 'dc+sd-jwt', custom: 'data' }, // You can add custom header data to the SD JWT 43 | }, 44 | ); 45 | console.log('encodedSdjwt:', credential); 46 | 47 | // You can check the custom header data by decoding the SD JWT 48 | const sdJwtToken = await sdjwt.decode(credential); 49 | console.log(sdJwtToken); 50 | })(); 51 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/decode.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 3 | 4 | (async () => { 5 | const { signer, verifier } = await createSignerVerifier(); 6 | 7 | // Create SDJwt instance for use 8 | const sdjwt = new SDJwtVcInstance({ 9 | signer, 10 | signAlg: ES256.alg, 11 | verifier, 12 | hasher: digest, 13 | saltGenerator: generateSalt, 14 | kbSigner: signer, 15 | kbSignAlg: ES256.alg, 16 | kbVerifier: verifier, 17 | }); 18 | 19 | // this is an example of SD JWT 20 | const data = 21 | 'eyJhbGciOiAiRVMyNTYiLCAia2lkIjogImRvYy1zaWduZXItMDUtMjUtMjAyMiIsICJ0eXAiOiAidmMrc2Qtand0In0.eyJfc2QiOiBbIjA5dktySk1PbHlUV00wc2pwdV9wZE9CVkJRMk0xeTNLaHBINTE1blhrcFkiLCAiMnJzakdiYUMwa3k4bVQwcEpyUGlvV1RxMF9kYXcxc1g3NnBvVWxnQ3diSSIsICJFa084ZGhXMGRIRUpidlVIbEVfVkNldUM5dVJFTE9pZUxaaGg3WGJVVHRBIiwgIklsRHpJS2VpWmREd3BxcEs2WmZieXBoRnZ6NUZnbldhLXNONndxUVhDaXciLCAiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBLdVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiamRyVEU4WWNiWTRFaWZ1Z2loaUFlX0JQZWt4SlFaSUNlaVVRd1k5UXF4SSIsICJqc3U5eVZ1bHdRUWxoRmxNXzNKbHpNYVNGemdsaFFHMERwZmF5UXdMVUs0Il0sICJpc3MiOiAiaHR0cHM6Ly9leGFtcGxlLmNvbS9pc3N1ZXIiLCAiaWF0IjogMTY4MzAwMDAwMCwgImV4cCI6IDE4ODMwMDAwMDAsICJkY3QiOiAiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIiwgIl9zZF9hbGciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIkVDIiwgImNydiI6ICJQLTI1NiIsICJ4IjogIlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTGlsRGxzN3ZDZUdlbWMiLCAieSI6ICJaeGppV1diWk1RR0hWV0tWUTRoYlNJaXJzVmZ1ZWNDRTZ0NGpUOUYySFpRIn19fQ.b036DutqQ72WszrCq0GuqZnbws3MApQyzA41I5DSJmenUfsADtqW8FbI_N04FP1wZDF_JtV6a6Ke3Z7apkoTLA~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImlzX292ZXJfMTgiLCB0cnVlXQ~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImlzX292ZXJfMjEiLCB0cnVlXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImlzX292ZXJfNjUiLCB0cnVlXQ~'; 22 | const decodedObject = await sdjwt.decode(data); 23 | console.log(decodedObject); 24 | 25 | const claims = await sdjwt.getClaims(data); 26 | console.log(claims); 27 | })(); 28 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/decoy.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtVcInstance({ 10 | signer, 11 | verifier, 12 | signAlg: ES256.alg, 13 | hasher: digest, 14 | hashAlg: 'sha-256', 15 | saltGenerator: generateSalt, 16 | }); 17 | // Issuer Define the claims object with the user's information 18 | const claims = { 19 | lastname: 'Doe', 20 | ssn: '123-45-6789', 21 | id: '1234', 22 | }; 23 | 24 | // Issuer Define the disclosure frame to specify which claims can be disclosed 25 | const disclosureFrame: DisclosureFrame = { 26 | _sd: ['id'], 27 | _sd_decoy: 1, // 1 decoy digest will be added in SD JWT 28 | }; 29 | const credential = await sdjwt.issue( 30 | { 31 | iss: 'Issuer', 32 | iat: new Date().getTime(), 33 | vct: 'ExampleCredentials', 34 | ...claims, 35 | }, 36 | disclosureFrame, 37 | ); 38 | console.log('encodedSdjwt:', credential); 39 | 40 | // You can check the decoy digest in the SD JWT by decoding it 41 | const sdJwtToken = await sdjwt.decode(credential); 42 | console.log(sdJwtToken); 43 | })(); 44 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/kb.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtVcInstance({ 10 | signer, 11 | signAlg: ES256.alg, 12 | verifier, 13 | hasher: digest, 14 | saltGenerator: generateSalt, 15 | kbSigner: signer, 16 | kbSignAlg: ES256.alg, 17 | kbVerifier: verifier, 18 | }); 19 | const claims = { 20 | firstname: 'John', 21 | lastname: 'Doe', 22 | ssn: '123-45-6789', 23 | id: '1234', 24 | }; 25 | const disclosureFrame: DisclosureFrame = { 26 | _sd: ['firstname', 'id'], 27 | }; 28 | 29 | const kbPayload = { 30 | iat: Math.floor(Date.now() / 1000), 31 | aud: 'https://example.com', 32 | nonce: '1234', 33 | custom: 'data', 34 | }; 35 | 36 | const encodedSdjwt = await sdjwt.issue( 37 | { 38 | iss: 'Issuer', 39 | iat: new Date().getTime(), 40 | vct: 'ExampleCredentials', 41 | ...claims, 42 | }, 43 | disclosureFrame, 44 | ); 45 | console.log('encodedSdjwt:', encodedSdjwt); 46 | const sdjwttoken = await sdjwt.decode(encodedSdjwt); 47 | console.log(sdjwttoken); 48 | 49 | const presentedSdJwt = await sdjwt.present( 50 | encodedSdjwt, 51 | { id: true }, 52 | { 53 | kb: { 54 | payload: kbPayload, 55 | }, 56 | }, 57 | ); 58 | 59 | const verified = await sdjwt.verify(presentedSdJwt, ['id', 'ssn'], true); 60 | console.log(verified); 61 | })(); 62 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdjwt-vc-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "basic": "ts-node basic.ts", 9 | "all": "ts-node all.ts", 10 | "sdjwtobject": "ts-node sdjwtobject.ts", 11 | "custom": "ts-node custom.ts", 12 | "decoy": "ts-node decoy.ts", 13 | "custom_header": "ts-node custom_header.ts", 14 | "kb": "ts-node kb.ts", 15 | "decode": "ts-node decode.ts" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "@types/node": "^20.10.4", 22 | "ts-node": "^10.9.2", 23 | "typescript": "^5.3.3" 24 | }, 25 | "dependencies": { 26 | "@sd-jwt/sd-jwt-vc": "workspace:*", 27 | "@sd-jwt/types": "workspace:*", 28 | "@sd-jwt/crypto-nodejs": "workspace:*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/sdjwtobject.ts: -------------------------------------------------------------------------------- 1 | import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; 2 | import type { DisclosureFrame } from '@sd-jwt/types'; 3 | import { createSignerVerifier, digest, ES256, generateSalt } from './utils'; 4 | 5 | (async () => { 6 | const { signer, verifier } = await createSignerVerifier(); 7 | 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtVcInstance({ 10 | signer, 11 | signAlg: ES256.alg, 12 | verifier, 13 | hasher: digest, 14 | saltGenerator: generateSalt, 15 | kbSigner: signer, 16 | kbSignAlg: ES256.alg, 17 | kbVerifier: verifier, 18 | }); 19 | // Issuer Define the claims object with the user's information 20 | const claims = { 21 | firstname: 'John', 22 | lastname: 'Doe', 23 | ssn: '123-45-6789', 24 | id: '1234', 25 | }; 26 | 27 | // Issuer Define the disclosure frame to specify which claims can be disclosed 28 | const disclosureFrame: DisclosureFrame = { 29 | _sd: ['firstname', 'id'], 30 | }; 31 | 32 | // Issue a signed JWT credential with the specified claims and disclosures 33 | // Return a Encoded SD JWT. Issuer send the credential to the holder 34 | const credential = await sdjwt.issue(claims, disclosureFrame); 35 | console.log('encodedSdjwt:', credential); 36 | 37 | // You can decode the SD JWT to get the payload and the disclosures 38 | const sdJwtToken = await sdjwt.decode(credential); 39 | console.log(sdJwtToken); 40 | 41 | // You can get the keys of the claims from the decoded SD JWT 42 | const keys = await sdJwtToken.keys(digest); 43 | console.log({ keys }); 44 | 45 | // You can get the claims from the decoded SD JWT 46 | const payloads = await sdJwtToken.getClaims(digest); 47 | 48 | // You can get the presentable keys from the decoded SD JWT 49 | const presentableKeys = await sdJwtToken.presentableKeys(digest); 50 | 51 | console.log({ 52 | payloads: JSON.stringify(payloads, null, 2), 53 | disclosures: JSON.stringify(sdJwtToken.disclosures, null, 2), 54 | claim: JSON.stringify(sdJwtToken.jwt?.payload, null, 2), 55 | presentableKeys, 56 | }); 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/core" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/utils.ts: -------------------------------------------------------------------------------- 1 | import { ES256, digest, generateSalt } from '@sd-jwt/crypto-nodejs'; 2 | export { digest, generateSalt, ES256 }; 3 | 4 | export const createSignerVerifier = async () => { 5 | const { privateKey, publicKey } = await ES256.generateKeyPair(); 6 | return { 7 | signer: await ES256.getSigner(privateKey), 8 | verifier: await ES256.getVerifier(publicKey), 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwallet-foundation/sd-jwt-js/d2f2cb5a4d9f40e5d90209f572665a9bf1f0844b/images/diagram.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "version": "0.10.0", 4 | "npmClient": "pnpm", 5 | "exact": true, 6 | "message": "chore(release): %s", 7 | "packages": [ 8 | "packages/*" 9 | ], 10 | "command": { 11 | "publish": { 12 | "conventionalCommits": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/sd-jwt-vc", 3 | "description": "sd-jwt implementation in typescript", 4 | "scripts": { 5 | "build": "lerna run build --stream", 6 | "lint": "biome lint ./packages", 7 | "format": "biome format . --write", 8 | "biome:ci": "biome ci .", 9 | "test": "vitest run --coverage.enabled=true --coverage.include=packages/*", 10 | "test:watch": "vitest", 11 | "clean": "lerna clean -y", 12 | "publish:latest": "lerna publish --no-private --conventional-commits --include-merged-tags --create-release github --yes --dist-tag latest", 13 | "publish:next": "lerna publish --no-private --conventional-prerelease --force-publish --canary --no-git-tag-version --include-merged-tags --preid next --pre-dist-tag next --yes", 14 | "prepare": "husky" 15 | }, 16 | "keywords": [ 17 | "sd-jwt", 18 | "sdjwt", 19 | "sd-jwt-vc" 20 | ], 21 | "engines": { 22 | "node": ">=18", 23 | "pnpm": ">=9" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 28 | }, 29 | "author": "Lukas.J.Han ", 30 | "homepage": "https://sdjwt.js.org", 31 | "bugs": { 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 33 | }, 34 | "license": "Apache-2.0", 35 | "devDependencies": { 36 | "@biomejs/biome": "^1.6.0", 37 | "@types/node": "^20.10.2", 38 | "@vitest/coverage-v8": "^1.2.2", 39 | "husky": "^9.0.11", 40 | "jose": "^5.2.2", 41 | "jsdom": "^24.0.0", 42 | "lerna": "^8.1.2", 43 | "ts-node": "^10.9.1", 44 | "tsup": "^8.0.2", 45 | "typescript": "^5.3.2", 46 | "vite": "^5.1.1", 47 | "vitest": "^1.2.2" 48 | }, 49 | "packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a" 50 | } 51 | -------------------------------------------------------------------------------- /packages/browser-crypto/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/crypto-browser 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/crypto-browser 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/crypto-browser 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/crypto-browser 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/crypto-browser 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/crypto-browser 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/crypto-browser 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | **Note:** Version bump only for package @sd-jwt/crypto-browser 65 | 66 | 67 | 68 | 69 | 70 | ## [0.6.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 71 | 72 | **Note:** Version bump only for package @sd-jwt/crypto-browser 73 | 74 | 75 | 76 | 77 | 78 | # [0.6.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 79 | 80 | **Note:** Version bump only for package @sd-jwt/crypto-browser 81 | 82 | 83 | 84 | 85 | 86 | # [0.5.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 87 | 88 | 89 | ### Features 90 | 91 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 92 | 93 | 94 | 95 | 96 | 97 | # 0.4.0 (2024-03-08) 98 | 99 | 100 | ### Features 101 | 102 | * es crypto features to nodejs and browser ([#106](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/106)) ([3ba74e9](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/3ba74e936dbc39698d47c6c8c1da956430e937f8)) 103 | -------------------------------------------------------------------------------- /packages/browser-crypto/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fcrypto-browser) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Browser Crypto 9 | 10 | ### About 11 | 12 | Browser Crypto support for SD JWT 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/crypto-browser 23 | 24 | # using yarn 25 | yarn add @sd-jwt/crypto-browser 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/crypto-browser 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | None 40 | -------------------------------------------------------------------------------- /packages/browser-crypto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/crypto-browser", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:browser && pnpm run test:cov", 18 | "test:browser": "vitest run ./src/test/*.spec.ts", 19 | "test:cov": "vitest run --coverage" 20 | }, 21 | "keywords": [ 22 | "sd-jwt", 23 | "sdjwt", 24 | "sd-jwt-vc" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 29 | }, 30 | "author": "Lukas.J.Han ", 31 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 32 | "bugs": { 33 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 34 | }, 35 | "license": "Apache-2.0", 36 | "publishConfig": { 37 | "access": "public" 38 | }, 39 | "tsup": { 40 | "entry": [ 41 | "./src/index.ts" 42 | ], 43 | "sourceMap": true, 44 | "splitting": false, 45 | "clean": true, 46 | "dts": true, 47 | "format": [ 48 | "cjs", 49 | "esm" 50 | ] 51 | }, 52 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 53 | } 54 | -------------------------------------------------------------------------------- /packages/browser-crypto/src/crypto.ts: -------------------------------------------------------------------------------- 1 | export const generateSalt = (length: number): string => { 2 | if (length <= 0) { 3 | return ''; 4 | } 5 | // a hex is represented by 2 characters, so we split the length by 2 6 | const array = new Uint8Array(length / 2); 7 | window.crypto.getRandomValues(array); 8 | 9 | const salt = Array.from(array, (byte) => 10 | byte.toString(16).padStart(2, '0'), 11 | ).join(''); 12 | 13 | return salt; 14 | }; 15 | 16 | export async function digest( 17 | data: string | ArrayBuffer, 18 | algorithm = 'sha-256', 19 | ): Promise { 20 | const ec = new TextEncoder(); 21 | const digest = await window.crypto.subtle.digest( 22 | algorithm, 23 | typeof data === 'string' ? ec.encode(data) : data, 24 | ); 25 | return new Uint8Array(digest); 26 | } 27 | 28 | export const getHasher = (algorithm = 'sha-256') => { 29 | return (data: string) => digest(data, algorithm); 30 | }; 31 | 32 | export const ES256 = { 33 | alg: 'ES256', 34 | 35 | async generateKeyPair() { 36 | const keyPair = await window.crypto.subtle.generateKey( 37 | { 38 | name: 'ECDSA', 39 | namedCurve: 'P-256', // ES256 40 | }, 41 | true, // whether the key is extractable (i.e., can be used in exportKey) 42 | ['sign', 'verify'], // can be used to sign and verify signatures 43 | ); 44 | 45 | // Export the public and private keys in JWK format 46 | const publicKeyJWK = await window.crypto.subtle.exportKey( 47 | 'jwk', 48 | keyPair.publicKey, 49 | ); 50 | const privateKeyJWK = await window.crypto.subtle.exportKey( 51 | 'jwk', 52 | keyPair.privateKey, 53 | ); 54 | 55 | return { publicKey: publicKeyJWK, privateKey: privateKeyJWK }; 56 | }, 57 | 58 | async getSigner(privateKeyJWK: object) { 59 | const privateKey = await window.crypto.subtle.importKey( 60 | 'jwk', 61 | privateKeyJWK, 62 | { 63 | name: 'ECDSA', 64 | namedCurve: 'P-256', // Must match the curve used to generate the key 65 | }, 66 | true, // whether the key is extractable (i.e., can be used in exportKey) 67 | ['sign'], 68 | ); 69 | 70 | return async (data: string) => { 71 | const encoder = new TextEncoder(); 72 | const signature = await window.crypto.subtle.sign( 73 | { 74 | name: 'ECDSA', 75 | hash: { name: 'sha-256' }, // Required for ES256 76 | }, 77 | privateKey, 78 | encoder.encode(data), 79 | ); 80 | 81 | return window 82 | .btoa(String.fromCharCode(...new Uint8Array(signature))) 83 | .replace(/\+/g, '-') 84 | .replace(/\//g, '_') 85 | .replace(/=+$/, ''); // Convert to base64url format 86 | }; 87 | }, 88 | 89 | async getVerifier(publicKeyJWK: object) { 90 | const publicKey = await window.crypto.subtle.importKey( 91 | 'jwk', 92 | publicKeyJWK, 93 | { 94 | name: 'ECDSA', 95 | namedCurve: 'P-256', // Must match the curve used to generate the key 96 | }, 97 | true, // whether the key is extractable (i.e., can be used in exportKey) 98 | ['verify'], 99 | ); 100 | 101 | return async (data: string, signatureBase64url: string) => { 102 | const encoder = new TextEncoder(); 103 | const signature = Uint8Array.from( 104 | atob(signatureBase64url.replace(/-/g, '+').replace(/_/g, '/')), 105 | (c) => c.charCodeAt(0), 106 | ); 107 | const isValid = await window.crypto.subtle.verify( 108 | { 109 | name: 'ECDSA', 110 | hash: { name: 'sha-256' }, // Required for ES256 111 | }, 112 | publicKey, 113 | signature, 114 | encoder.encode(data), 115 | ); 116 | 117 | return isValid; 118 | }; 119 | }, 120 | }; 121 | -------------------------------------------------------------------------------- /packages/browser-crypto/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crypto'; 2 | -------------------------------------------------------------------------------- /packages/browser-crypto/src/test/crypto.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { generateSalt, digest, getHasher, ES256 } from '../index'; 3 | 4 | // Extract the major version as a number 5 | const nodeVersionMajor = Number.parseInt( 6 | process.version.split('.')[0].substring(1), 7 | 10, 8 | ); 9 | 10 | describe('This file is for utility functions', () => { 11 | (nodeVersionMajor < 20 ? test.skip : test)('generateSalt', async () => { 12 | const salt = generateSalt(8); 13 | expect(salt).toBeDefined(); 14 | expect(salt.length).toBe(8); 15 | }); 16 | 17 | (nodeVersionMajor < 20 ? test.skip : test)( 18 | 'generateSalt 0 length', 19 | async () => { 20 | const salt = generateSalt(0); 21 | expect(salt).toBeDefined(); 22 | expect(salt.length).toBe(0); 23 | }, 24 | ); 25 | 26 | (nodeVersionMajor < 20 ? test.skip : test)('digest', async () => { 27 | const payload = 'test1'; 28 | const s1 = await digest(payload); 29 | expect(s1).toBeDefined(); 30 | expect(s1.length).toBe(32); 31 | }); 32 | 33 | (nodeVersionMajor < 20 ? test.skip : test)('digest', async () => { 34 | const payload = 'test1'; 35 | const s1 = await digest(payload, 'SHA-512'); 36 | expect(s1).toBeDefined(); 37 | expect(s1.length).toBe(64); 38 | }); 39 | 40 | (nodeVersionMajor < 20 ? test.skip : test)('get hasher', async () => { 41 | const hash = await getHasher('SHA-512')('test1'); 42 | expect(hash).toBeDefined(); 43 | }); 44 | 45 | (nodeVersionMajor < 20 ? test.skip : test)('ES256 alg', () => { 46 | expect(ES256.alg).toBe('ES256'); 47 | }); 48 | 49 | (nodeVersionMajor < 20 ? test.skip : test)('ES256', async () => { 50 | const { privateKey, publicKey } = await ES256.generateKeyPair(); 51 | expect(privateKey).toBeDefined(); 52 | expect(publicKey).toBeDefined(); 53 | expect(typeof privateKey).toBe('object'); 54 | expect(typeof publicKey).toBe('object'); 55 | 56 | const data = 57 | 'In cryptography, a salt is random data that is used as an additional input to a one-way function that hashes data, a password or passphrase.'; 58 | const signature = await (await ES256.getSigner(privateKey))(data); 59 | expect(signature).toBeDefined(); 60 | expect(typeof signature).toBe('string'); 61 | 62 | const result = await (await ES256.getVerifier(publicKey))(data, signature); 63 | expect(result).toBe(true); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/browser-crypto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser-crypto/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { browserConfig } from '../../vitest.shared'; 3 | 4 | export default browserConfig; 5 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fcore) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Core 9 | 10 | ### About 11 | 12 | Core library for selective disclosure JWTs 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/core 23 | 24 | # using yarn 25 | yarn add @sd-jwt/core 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/core 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | The library can be used to create sd-jwt based credentials. To be compliant with the `sd-jwt-vc` standard, you can use the `@sd-jwt/sd-jwt-vc` that is implementing this spec. 36 | If you want to use the pure sd-jwt class or implement your own sd-jwt credential approach, you can use this library. 37 | 38 | ### Dependencies 39 | 40 | - @sd-jwt/decode 41 | - @sd-jwt/present 42 | - @sd-jwt/types 43 | - @sd-jwt/utils 44 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/core", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:cov": "vitest run --coverage" 21 | }, 22 | "keywords": [ 23 | "sd-jwt", 24 | "sdjwt", 25 | "sd-jwt-vc" 26 | ], 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 33 | }, 34 | "author": "Lukas.J.Han ", 35 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "devDependencies": { 41 | "@sd-jwt/crypto-nodejs": "workspace:*" 42 | }, 43 | "dependencies": { 44 | "@sd-jwt/decode": "workspace:*", 45 | "@sd-jwt/present": "workspace:*", 46 | "@sd-jwt/types": "workspace:*", 47 | "@sd-jwt/utils": "workspace:*" 48 | }, 49 | "publishConfig": { 50 | "access": "public" 51 | }, 52 | "tsup": { 53 | "entry": [ 54 | "./src/index.ts" 55 | ], 56 | "sourceMap": true, 57 | "splitting": false, 58 | "clean": true, 59 | "dts": true, 60 | "format": [ 61 | "cjs", 62 | "esm" 63 | ] 64 | }, 65 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/decoy.ts: -------------------------------------------------------------------------------- 1 | import type { HasherAndAlg, SaltGenerator } from '@sd-jwt/types'; 2 | import { uint8ArrayToBase64Url } from '@sd-jwt/utils'; 3 | 4 | // This function creates a decoy value that can be used to obscure SD JWT payload. 5 | // The value is basically a hash of a random salt. So the value is not predictable. 6 | // return value is a base64url encoded string. 7 | export const createDecoy = async ( 8 | hash: HasherAndAlg, 9 | saltGenerator: SaltGenerator, 10 | ): Promise => { 11 | const { hasher, alg } = hash; 12 | const salt = await saltGenerator(16); 13 | const decoy = await hasher(salt, alg); 14 | return uint8ArrayToBase64Url(decoy); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/core/src/flattenJSON.ts: -------------------------------------------------------------------------------- 1 | import { SDJWTException } from '@sd-jwt/utils'; 2 | import { splitSdJwt } from '@sd-jwt/decode'; 3 | import { SD_SEPARATOR } from '@sd-jwt/types'; 4 | 5 | export type FlattenJSONData = { 6 | jwtData: { 7 | protected: string; 8 | payload: string; 9 | signature: string; 10 | }; 11 | disclosures: Array; 12 | kb_jwt?: string; 13 | }; 14 | 15 | export type FlattenJSONSerialized = { 16 | payload: string; 17 | signature: string; 18 | protected: string; 19 | header: { 20 | disclosures: Array; 21 | kb_jwt?: string; 22 | }; 23 | }; 24 | 25 | export class FlattenJSON { 26 | public disclosures: Array; 27 | public kb_jwt?: string; 28 | 29 | public payload: string; 30 | public signature: string; 31 | public protected: string; 32 | 33 | constructor(data: FlattenJSONData) { 34 | this.disclosures = data.disclosures; 35 | this.kb_jwt = data.kb_jwt; 36 | this.payload = data.jwtData.payload; 37 | this.signature = data.jwtData.signature; 38 | this.protected = data.jwtData.protected; 39 | } 40 | 41 | public static fromEncode(encodedSdJwt: string) { 42 | const { jwt, disclosures, kbJwt } = splitSdJwt(encodedSdJwt); 43 | 44 | const { 0: protectedHeader, 1: payload, 2: signature } = jwt.split('.'); 45 | if (!protectedHeader || !payload || !signature) { 46 | throw new SDJWTException('Invalid JWT'); 47 | } 48 | 49 | return new FlattenJSON({ 50 | jwtData: { 51 | protected: protectedHeader, 52 | payload, 53 | signature, 54 | }, 55 | disclosures, 56 | kb_jwt: kbJwt, 57 | }); 58 | } 59 | 60 | public static fromSerialized(json: FlattenJSONSerialized) { 61 | return new FlattenJSON({ 62 | jwtData: { 63 | protected: json.protected, 64 | payload: json.payload, 65 | signature: json.signature, 66 | }, 67 | disclosures: json.header.disclosures, 68 | kb_jwt: json.header.kb_jwt, 69 | }); 70 | } 71 | 72 | public toJson(): FlattenJSONSerialized { 73 | return { 74 | payload: this.payload, 75 | signature: this.signature, 76 | protected: this.protected, 77 | header: { 78 | disclosures: this.disclosures, 79 | kb_jwt: this.kb_jwt, 80 | }, 81 | }; 82 | } 83 | 84 | public toEncoded() { 85 | const data: string[] = []; 86 | 87 | const jwt = `${this.protected}.${this.payload}.${this.signature}`; 88 | data.push(jwt); 89 | 90 | if (this.disclosures && this.disclosures.length > 0) { 91 | const disclosures = this.disclosures.join(SD_SEPARATOR); 92 | data.push(disclosures); 93 | } 94 | 95 | const kb_jwt = this.kb_jwt ?? ''; 96 | data.push(kb_jwt); 97 | return data.join(SD_SEPARATOR); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/core/src/kbjwt.ts: -------------------------------------------------------------------------------- 1 | import { SDJWTException } from '@sd-jwt/utils'; 2 | import { Jwt } from './jwt'; 3 | import { 4 | type JwtPayload, 5 | KB_JWT_TYP, 6 | type kbHeader, 7 | type kbPayload, 8 | type KbVerifier, 9 | } from '@sd-jwt/types'; 10 | 11 | export class KBJwt< 12 | Header extends kbHeader = kbHeader, 13 | Payload extends kbPayload = kbPayload, 14 | > extends Jwt { 15 | // Checking the validity of the key binding jwt 16 | // the type unknown is not good, but we don't know at this point how to get the public key of the signer, this is defined in the kbVerifier 17 | public async verifyKB(values: { verifier: KbVerifier; payload: JwtPayload }) { 18 | if (!this.header || !this.payload || !this.signature) { 19 | throw new SDJWTException('Verify Error: Invalid JWT'); 20 | } 21 | 22 | if ( 23 | !this.header.alg || 24 | this.header.alg === 'none' || 25 | !this.header.typ || 26 | this.header.typ !== KB_JWT_TYP || 27 | !this.payload.iat || 28 | !this.payload.aud || 29 | !this.payload.nonce || 30 | // this is for backward compatibility with version 06 31 | !( 32 | this.payload.sd_hash || 33 | (this.payload as Record | undefined)?._sd_hash 34 | ) 35 | ) { 36 | throw new SDJWTException('Invalid Key Binding Jwt'); 37 | } 38 | 39 | const data = this.getUnsignedToken(); 40 | const verified = await values.verifier( 41 | data, 42 | this.signature, 43 | values.payload, 44 | ); 45 | if (!verified) { 46 | throw new SDJWTException('Verify Error: Invalid JWT Signature'); 47 | } 48 | return { payload: this.payload, header: this.header }; 49 | } 50 | 51 | // This function is for creating KBJwt object for verify properly 52 | public static fromKBEncode< 53 | Header extends kbHeader = kbHeader, 54 | Payload extends kbPayload = kbPayload, 55 | >(encodedJwt: string): KBJwt { 56 | const { header, payload, signature } = Jwt.decodeJWT( 57 | encodedJwt, 58 | ); 59 | 60 | const jwt = new KBJwt({ 61 | header, 62 | payload, 63 | signature, 64 | encoded: encodedJwt, 65 | }); 66 | 67 | return jwt; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/core/src/test/decoy.spec.ts: -------------------------------------------------------------------------------- 1 | import { createDecoy } from '../decoy'; 2 | import { describe, expect, test } from 'vitest'; 3 | import { base64urlEncode } from '@sd-jwt/utils'; 4 | import { digest, generateSalt } from '@sd-jwt/crypto-nodejs'; 5 | 6 | const hash = { 7 | hasher: digest, 8 | alg: 'SHA256', 9 | }; 10 | 11 | describe('Decoy', () => { 12 | test('decoy', async () => { 13 | const decoyValue = await createDecoy(hash, generateSalt); 14 | expect(decoyValue.length).toBe(43); 15 | }); 16 | 17 | // ref https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/07/ 18 | // *Claim email*: 19 | // * SHA-256 Hash: JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE 20 | // * Disclosure: WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ 21 | // * Contents: ["6Ij7tM-a5iVPGboS5tmvVA", "email", "johndoe@example.com"] 22 | test('apply hasher and saltGenerator', async () => { 23 | const decoyValue = await createDecoy(hash, () => 24 | base64urlEncode( 25 | '["6Ij7tM-a5iVPGboS5tmvVA", "email", "johndoe@example.com"]', 26 | ), 27 | ); 28 | expect(decoyValue).toBe('JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/core/src/test/pass.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | describe('Should always pass', () => { 3 | test('adds 1 + 2 to equal 3', () => { 4 | expect(1 + 2).toBe(3); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/core/test/array_data_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }] 4 | }, 5 | "disclosureFrame": { 6 | "data_types": { 7 | "_sd": [0, 1, 2, 3, 4, 5] 8 | } 9 | }, 10 | "presentationFrames": { 11 | "data_types": { 12 | "0": true, 13 | "1": true, 14 | "2": true, 15 | "3": true, 16 | "4": true, 17 | "5": true 18 | } 19 | }, 20 | "presenatedClaims": { 21 | "data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }] 22 | }, 23 | "requiredClaimKeys": [ 24 | "data_types.0", 25 | "data_types.1", 26 | "data_types.2", 27 | "data_types.3", 28 | "data_types.4", 29 | "data_types.5" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/test/array_full_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "is_over": { 4 | "13": true, 5 | "18": false, 6 | "21": false 7 | } 8 | }, 9 | "disclosureFrame": { 10 | "is_over": { 11 | "_sd": ["13", "18", "21"] 12 | } 13 | }, 14 | "presentationFrames": { "is_over": { "18": true } }, 15 | "presenatedClaims": { 16 | "is_over": { 17 | "18": false 18 | } 19 | }, 20 | "requiredClaimKeys": ["is_over.18"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/test/array_in_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sd_array": ["32", "23"] 4 | }, 5 | "disclosureFrame": { 6 | "_sd": ["sd_array"] 7 | }, 8 | "presentationFrames": { "sd_array": true }, 9 | "presenatedClaims": { 10 | "sd_array": ["32", "23"] 11 | }, 12 | "requiredClaimKeys": ["sd_array"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/test/array_nested_in_plain.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "nested_array": [["foo", "bar"], ["baz", "qux"]] 4 | }, 5 | "disclosureFrame": { 6 | "nested_array": { 7 | "0": { 8 | "_sd": [0, 1] 9 | }, 10 | "1": { 11 | "_sd": [0, 1] 12 | } 13 | } 14 | }, 15 | "presentationFrames": { 16 | "nested_array": { 17 | "0": { 18 | "0": true 19 | }, 20 | "1": { 21 | "1": true 22 | } 23 | } 24 | }, 25 | "presenatedClaims": { 26 | "nested_array": [["foo"], ["qux"]] 27 | }, 28 | "requiredClaimKeys": ["nested_array.0.0", "nested_array.1.0"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/test/array_none_disclosed.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "is_over": { 4 | "13": false, 5 | "18": true, 6 | "21": false 7 | } 8 | }, 9 | "disclosureFrame": { 10 | "is_over": { 11 | "_sd": ["13", "18", "21"] 12 | } 13 | }, 14 | "presentationFrames": {}, 15 | "presenatedClaims": {}, 16 | "requiredClaimKeys": [] 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/test/array_of_nulls.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "null_values": [null, null, null, null] 4 | }, 5 | "disclosureFrame": { 6 | "null_values": { 7 | "_sd": [1, 2] 8 | } 9 | }, 10 | "presentationFrames": {}, 11 | "presenatedClaims": { 12 | "null_values": [null, null] 13 | }, 14 | "requiredClaimKeys": ["null_values.0", "null_values.1"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/test/array_of_objects.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "addresses": [ 4 | { 5 | "street": "123 Main St", 6 | "city": "Anytown", 7 | "state": "NY", 8 | "zip": "12345", 9 | "type": "main_address" 10 | }, 11 | { 12 | "street": "456 Main St", 13 | "city": "Anytown", 14 | "state": "NY", 15 | "zip": "12345", 16 | "type": "secondary_address" 17 | } 18 | ], 19 | "array_with_one_sd_object": { 20 | "foo": "bar" 21 | } 22 | }, 23 | "disclosureFrame": { 24 | "addresses": { 25 | "_sd": [1] 26 | }, 27 | "array_with_one_sd_object": { 28 | "_sd": ["foo"] 29 | } 30 | }, 31 | "presentationFrames": { 32 | "addresses": { 33 | "1": true 34 | }, 35 | "array_with_one_sd_object": { 36 | "foo": true 37 | } 38 | }, 39 | "presenatedClaims": { 40 | "addresses": [ 41 | { 42 | "street": "123 Main St", 43 | "city": "Anytown", 44 | "state": "NY", 45 | "zip": "12345", 46 | "type": "main_address" 47 | }, 48 | { 49 | "street": "456 Main St", 50 | "city": "Anytown", 51 | "state": "NY", 52 | "zip": "12345", 53 | "type": "secondary_address" 54 | } 55 | ], 56 | "array_with_one_sd_object": { 57 | "foo": "bar" 58 | } 59 | }, 60 | "requiredClaimKeys": [ 61 | "addresses.0.type", 62 | "addresses.1.city", 63 | "array_with_one_sd_object.foo" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /packages/core/test/array_of_scalars.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "nationalities": ["US", "CA", "DE"] 4 | }, 5 | "disclosureFrame": { 6 | "nationalities": { 7 | "_sd": [0, 1] 8 | } 9 | }, 10 | "presentationFrames": { 11 | "nationalities": { 12 | "1": true 13 | } 14 | }, 15 | "presenatedClaims": { 16 | "nationalities": ["CA", "DE"] 17 | }, 18 | "requiredClaimKeys": ["nationalities.0", "nationalities.1"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/test/array_recursive_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "array_with_recursive_sd": [ 4 | "boring", 5 | { 6 | "foo": "bar", 7 | "baz": { 8 | "qux": "quxx" 9 | } 10 | }, 11 | ["foo", "bar"] 12 | ], 13 | "test2": ["foo", "bar"] 14 | }, 15 | "disclosureFrame": { 16 | "array_with_recursive_sd": { 17 | "_sd": [1], 18 | "1": { 19 | "_sd": ["baz"] 20 | }, 21 | "2": { 22 | "_sd": [0, 1] 23 | } 24 | }, 25 | "test2": { 26 | "_sd": [0, 1] 27 | } 28 | }, 29 | "presentationFrames": {}, 30 | "presenatedClaims": { 31 | "array_with_recursive_sd": ["boring", []], 32 | "test2": [] 33 | }, 34 | "requiredClaimKeys": ["array_with_recursive_sd.0", "test2"] 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/test/array_recursive_sd_some_disclosed.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "array_with_recursive_sd": [ 4 | "boring", 5 | { 6 | "foo": "bar", 7 | "baz": { 8 | "qux": "quxx" 9 | } 10 | }, 11 | ["foo", "bar"] 12 | ], 13 | "test2": ["foo", "bar"] 14 | }, 15 | "disclosureFrame": { 16 | "array_with_recursive_sd": { 17 | "1": { 18 | "_sd": ["baz"] 19 | }, 20 | "2": { 21 | "_sd": [0, 1] 22 | } 23 | }, 24 | "test2": { 25 | "_sd": [0, 1] 26 | } 27 | }, 28 | "presentationFrames": { 29 | "array_with_recursive_sd": { 30 | "1": { 31 | "baz": true 32 | }, 33 | "2": { 34 | "1": true 35 | } 36 | }, 37 | "test2": { 38 | "0": true, 39 | "1": true 40 | } 41 | }, 42 | "presenatedClaims": { 43 | "array_with_recursive_sd": [ 44 | "boring", 45 | { 46 | "foo": "bar", 47 | "baz": { 48 | "qux": "quxx" 49 | } 50 | }, 51 | ["bar"] 52 | ], 53 | "test2": ["foo", "bar"] 54 | }, 55 | "requiredClaimKeys": [ 56 | "array_with_recursive_sd.1", 57 | "array_with_recursive_sd.2", 58 | "array_with_recursive_sd.1.baz", 59 | "array_with_recursive_sd.2.1", 60 | "test2.0", 61 | "test2.1" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /packages/core/test/complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "firstname": "John", 4 | "lastname": "Doe", 5 | "ssn": "123-45-6789", 6 | "id": "1234", 7 | "data": { 8 | "firstname": "John", 9 | "lastname": "Doe", 10 | "ssn": "123-45-6789", 11 | "list": [{ "r": "1" }, "b", "c"] 12 | }, 13 | "data2": { 14 | "hi": "bye" 15 | } 16 | }, 17 | "disclosureFrame": { 18 | "_sd": ["firstname", "id", "data2"], 19 | "data": { 20 | "_sd": ["list"], 21 | "_sd_decoy": 2, 22 | "list": { 23 | "_sd": [0, 2], 24 | "_sd_decoy": 1, 25 | "0": { 26 | "_sd": ["r"] 27 | } 28 | } 29 | }, 30 | "data2": { 31 | "_sd": ["hi"] 32 | } 33 | }, 34 | "presentationFrames": { "firstname": true, "id": true }, 35 | "presenatedClaims": { 36 | "lastname": "Doe", 37 | "ssn": "123-45-6789", 38 | "data": { "firstname": "John", "lastname": "Doe", "ssn": "123-45-6789" }, 39 | "id": "1234", 40 | "firstname": "John" 41 | }, 42 | "requiredClaimKeys": ["firstname", "id", "data.ssn"] 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/test/header_mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sub": "john_deo_42", 4 | "given_name": "John", 5 | "family_name": "Deo", 6 | "email": "johndeo@example.com", 7 | "phone": "+1-202-555-0101", 8 | "address": { 9 | "street_address": "123 Main St", 10 | "locality": "Anytown", 11 | "region": "Anystate", 12 | "country": "US" 13 | }, 14 | "birthdate": "1940-01-01" 15 | }, 16 | "disclosureFrame": { 17 | "_sd": [ 18 | "sub", 19 | "given_name", 20 | "family_name", 21 | "email", 22 | "phone", 23 | "address", 24 | "birthdate" 25 | ] 26 | }, 27 | "presentationFrames": { 28 | "given_name": true, 29 | "family_name": true, 30 | "address": true 31 | }, 32 | "presenatedClaims": { 33 | "given_name": "John", 34 | "family_name": "Deo", 35 | "address": { 36 | "street_address": "123 Main St", 37 | "locality": "Anytown", 38 | "region": "Anystate", 39 | "country": "US" 40 | } 41 | }, 42 | "requiredClaimKeys": [ 43 | "given_name", 44 | "family_name", 45 | "address.street_address", 46 | "address.country" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/test/json_serialization.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sub": "john_deo_42", 4 | "given_name": "John", 5 | "family_name": "Deo", 6 | "email": "johndeo@example.com", 7 | "phone": "+1-202-555-0101", 8 | "address": { 9 | "street_address": "123 Main St", 10 | "locality": "Anytown", 11 | "region": "Anystate", 12 | "country": "US" 13 | }, 14 | "birthdate": "1940-01-01" 15 | }, 16 | "disclosureFrame": { 17 | "_sd": [ 18 | "sub", 19 | "given_name", 20 | "family_name", 21 | "email", 22 | "phone", 23 | "address", 24 | "birthdate" 25 | ] 26 | }, 27 | "presentationFrames": { 28 | "given_name": true, 29 | "family_name": true, 30 | "address": true 31 | }, 32 | "presenatedClaims": { 33 | "given_name": "John", 34 | "family_name": "Deo", 35 | "address": { 36 | "street_address": "123 Main St", 37 | "locality": "Anytown", 38 | "region": "Anystate", 39 | "country": "US" 40 | } 41 | }, 42 | "requiredClaimKeys": [ 43 | "given_name", 44 | "family_name", 45 | "address.street_address", 46 | "address.country" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/test/key_binding.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sub": "john_deo_42", 4 | "given_name": "John", 5 | "family_name": "Deo", 6 | "email": "johndeo@example.com", 7 | "phone": "+1-202-555-0101", 8 | "address": { 9 | "street_address": "123 Main St", 10 | "locality": "Anytown", 11 | "region": "Anystate", 12 | "country": "US" 13 | }, 14 | "birthdate": "1940-01-01" 15 | }, 16 | "disclosureFrame": { 17 | "_sd": [ 18 | "sub", 19 | "given_name", 20 | "family_name", 21 | "email", 22 | "phone", 23 | "address", 24 | "birthdate" 25 | ] 26 | }, 27 | "presentationFrames": { 28 | "given_name": true, 29 | "family_name": true, 30 | "address": true 31 | }, 32 | "presenatedClaims": { 33 | "given_name": "John", 34 | "family_name": "Deo", 35 | "address": { 36 | "street_address": "123 Main St", 37 | "locality": "Anytown", 38 | "region": "Anystate", 39 | "country": "US" 40 | } 41 | }, 42 | "requiredClaimKeys": [ 43 | "given_name", 44 | "family_name", 45 | "address.street_address", 46 | "address.country" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/test/no_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "recursive": [ 4 | "boring", 5 | { 6 | "foo": "bar", 7 | "baz": { 8 | "qux": "quxx" 9 | } 10 | }, 11 | ["foo", "bar"] 12 | ], 13 | "test2": ["foo", "bar"] 14 | }, 15 | "disclosureFrame": {}, 16 | "presentationFrames": {}, 17 | "presenatedClaims": { 18 | "recursive": [ 19 | "boring", 20 | { 21 | "foo": "bar", 22 | "baz": { 23 | "qux": "quxx" 24 | } 25 | }, 26 | ["foo", "bar"] 27 | ], 28 | "test2": ["foo", "bar"] 29 | }, 30 | "requiredClaimKeys": [ 31 | "recursive.0", 32 | "recursive.1.baz.qux", 33 | "recursive.2.1", 34 | "test2.1" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/test/object_data_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "value_Data_types": { 4 | "test_null": null, 5 | "test_int": 42, 6 | "test_float": 3.14, 7 | "test_str": "foo", 8 | "test_bool": true, 9 | "test_arr": ["Test"], 10 | "test_object": { 11 | "foo": "bar" 12 | } 13 | } 14 | }, 15 | "disclosureFrame": { 16 | "value_Data_types": { 17 | "_sd": [ 18 | "test_null", 19 | "test_int", 20 | "test_float", 21 | "test_str", 22 | "test_bool", 23 | "test_arr", 24 | "test_object" 25 | ] 26 | } 27 | }, 28 | "presentationFrames": { 29 | "value_Data_types": { 30 | "test_null": true, 31 | "test_int": true, 32 | "test_float": true, 33 | "test_str": true, 34 | "test_bool": true, 35 | "test_arr": true, 36 | "test_object": true 37 | } 38 | }, 39 | "presenatedClaims": { 40 | "value_Data_types": { 41 | "test_null": null, 42 | "test_int": 42, 43 | "test_float": 3.14, 44 | "test_str": "foo", 45 | "test_bool": true, 46 | "test_arr": ["Test"], 47 | "test_object": { 48 | "foo": "bar" 49 | } 50 | } 51 | }, 52 | "requiredClaimKeys": [ 53 | "value_Data_types.test_null", 54 | "value_Data_types.test_int", 55 | "value_Data_types.test_float", 56 | "value_Data_types.test_str", 57 | "value_Data_types.test_bool", 58 | "value_Data_types.test_arr", 59 | "value_Data_types.test_object", 60 | "value_Data_types.test_object.foo" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /packages/core/test/recursions.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "foo": ["one", "two"], 4 | "bar": { 5 | "red": 1, 6 | "green": 2 7 | }, 8 | "qux": [["blue", "yellow"]], 9 | "baz": [["orange", "purple"], ["black", "white"]], 10 | "animals": { 11 | "snake": { 12 | "name": "python", 13 | "age": 10 14 | }, 15 | "bird": { 16 | "name": "eagle", 17 | "age": 20 18 | } 19 | } 20 | }, 21 | "disclosureFrame": { 22 | "foo": { 23 | "_sd": [0, 1] 24 | }, 25 | "bar": { 26 | "_sd": ["red", "green"] 27 | }, 28 | "qux": { 29 | "_sd": [0], 30 | "0": { 31 | "_sd": [0, 1] 32 | } 33 | }, 34 | "baz": { 35 | "_sd": [0, 1], 36 | "0": { 37 | "_sd": [0, 1] 38 | }, 39 | "1": { 40 | "_sd": [0, 1] 41 | } 42 | }, 43 | "animals": { 44 | "_sd": ["snake", "bird"], 45 | "snake": { 46 | "_sd": ["name", "age"] 47 | }, 48 | "bird": { 49 | "_sd": ["name", "age"] 50 | } 51 | } 52 | }, 53 | "presentationFrames": { 54 | "foo": { 55 | "1": true 56 | }, 57 | "bar": { 58 | "green": true 59 | }, 60 | "qux": { 61 | "0": { 62 | "0": true, 63 | "1": true 64 | }, 65 | "1": { 66 | "0": true, 67 | "1": true 68 | } 69 | }, 70 | "baz": { 71 | "0": { 72 | "0": true, 73 | "1": true 74 | }, 75 | "1": { 76 | "0": true, 77 | "1": true 78 | } 79 | }, 80 | "animals": { 81 | "snake": { 82 | "age": true 83 | }, 84 | "bird": { 85 | "age": true 86 | } 87 | } 88 | }, 89 | "presenatedClaims": { 90 | "foo": ["two"], 91 | "bar": { 92 | "green": 2 93 | }, 94 | "qux": [["blue", "yellow"]], 95 | "baz": [["orange", "purple"], ["black", "white"]], 96 | "animals": { 97 | "snake": { 98 | "age": 10 99 | }, 100 | "bird": { 101 | "age": 20 102 | } 103 | } 104 | }, 105 | "requiredClaimKeys": [ 106 | "foo.1", 107 | "bar.green", 108 | "qux.0.0", 109 | "qux.0.1", 110 | "baz.0.0", 111 | "baz.0.1", 112 | "baz.1.0", 113 | "baz.1.1", 114 | "animals.snake.age", 115 | "animals.bird.age" 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/decode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/decode 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/decode 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/decode 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/decode 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/decode 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/decode 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/decode 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | 65 | ### Features 66 | 67 | * add clone ojbect logic in unpackObj ([#181](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/181)) ([2e92cb3](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/2e92cb3abc27f6dbde19c7c016bc1f8ba60f9ff6)) 68 | 69 | 70 | 71 | 72 | 73 | ## [0.6.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * base64url convention ([#179](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/179)) ([f8db275](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/f8db275690dab88000a039838680a3478b3b61ec)) 79 | 80 | 81 | 82 | 83 | 84 | # [0.6.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 85 | 86 | **Note:** Version bump only for package @sd-jwt/decode 87 | 88 | 89 | 90 | 91 | 92 | # [0.5.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 93 | 94 | 95 | ### Features 96 | 97 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 98 | 99 | 100 | 101 | 102 | 103 | # 0.4.0 (2024-03-08) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * add publish config ([#93](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/93)) ([2e4c5c1](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/2e4c5c176dc88e58e49d06783b7658d8ad872313)) 109 | * change SDJwtPayload to SdJwtPayload ([9f06ef7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/9f06ef7bd31a1dff4e9bf988e425200a5e1aa82d)) 110 | * convert any usage into or typed version ([#80](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/80)) ([de4df54](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/de4df54f2a0a77fdbf97e10abac555a98e70c6e0)) 111 | * make sdjwt instance non abstract ([#122](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/122)) ([e85aae8](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/e85aae89910f5d9468e29ef14ef3b3d3215b86fd)) 112 | -------------------------------------------------------------------------------- /packages/decode/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fdecode) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Decode 9 | 10 | ### About 11 | 12 | Decode SD JWT into objects 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/decode 23 | 24 | # using yarn 25 | yarn add @sd-jwt/decode 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/decode 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | - @sd-jwt/types 40 | - @sd-jwt/utils 41 | -------------------------------------------------------------------------------- /packages/decode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/decode", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts --coverage", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom --coverage" 20 | }, 21 | "keywords": [ 22 | "sd-jwt", 23 | "sdjwt", 24 | "sd-jwt-vc" 25 | ], 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 37 | }, 38 | "license": "Apache-2.0", 39 | "devDependencies": { 40 | "@sd-jwt/crypto-nodejs": "workspace:*" 41 | }, 42 | "dependencies": { 43 | "@sd-jwt/types": "workspace:*", 44 | "@sd-jwt/utils": "workspace:*" 45 | }, 46 | "publishConfig": { 47 | "access": "public" 48 | }, 49 | "tsup": { 50 | "entry": [ 51 | "./src/index.ts" 52 | ], 53 | "sourceMap": true, 54 | "splitting": false, 55 | "clean": true, 56 | "dts": true, 57 | "format": [ 58 | "cjs", 59 | "esm" 60 | ] 61 | }, 62 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 63 | } 64 | -------------------------------------------------------------------------------- /packages/decode/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decode'; 2 | -------------------------------------------------------------------------------- /packages/decode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/decode/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/hash/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/hash 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/hash 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/hash 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/hash 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/hash 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/hash 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/hash 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | **Note:** Version bump only for package @sd-jwt/hash 65 | 66 | 67 | 68 | 69 | 70 | ## [0.6.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 71 | 72 | **Note:** Version bump only for package @sd-jwt/hash 73 | 74 | 75 | 76 | 77 | 78 | # [0.6.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 79 | 80 | **Note:** Version bump only for package @sd-jwt/hash 81 | 82 | 83 | 84 | 85 | 86 | # [0.5.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 87 | 88 | 89 | ### Features 90 | 91 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 92 | 93 | 94 | 95 | 96 | 97 | # 0.4.0 (2024-03-08) 98 | 99 | 100 | ### Bug Fixes 101 | 102 | * add publish config ([#93](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/93)) ([2e4c5c1](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/2e4c5c176dc88e58e49d06783b7658d8ad872313)) 103 | -------------------------------------------------------------------------------- /packages/hash/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fhash) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Hash 9 | 10 | ### About 11 | 12 | SHA-256 support for SD JWT 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/hash 23 | 24 | # using yarn 25 | yarn add @sd-jwt/hash 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/hash 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | - "@noble/hashes": "1.0.0", 40 | - pure js hash algorithm implementation with security audit (v1.0.0) 41 | -------------------------------------------------------------------------------- /packages/hash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/hash", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:cov": "vitest run --coverage" 21 | }, 22 | "keywords": [ 23 | "sd-jwt", 24 | "sdjwt", 25 | "sd-jwt-vc" 26 | ], 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 33 | }, 34 | "author": "Lukas.J.Han ", 35 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "devDependencies": { 41 | "@sd-jwt/crypto-nodejs": "workspace:*" 42 | }, 43 | "dependencies": { 44 | "@noble/hashes": "1.0.0", 45 | "@sd-jwt/utils": "workspace:*" 46 | }, 47 | "publishConfig": { 48 | "access": "public" 49 | }, 50 | "tsup": { 51 | "entry": [ 52 | "./src/index.ts" 53 | ], 54 | "sourceMap": true, 55 | "splitting": false, 56 | "clean": true, 57 | "dts": true, 58 | "format": [ 59 | "cjs", 60 | "esm" 61 | ] 62 | }, 63 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 64 | } 65 | -------------------------------------------------------------------------------- /packages/hash/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sha256'; 2 | -------------------------------------------------------------------------------- /packages/hash/src/sha256.ts: -------------------------------------------------------------------------------- 1 | import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; 2 | import { SDJWTException } from '@sd-jwt/utils'; 3 | 4 | export const sha256 = (text: string): Uint8Array => { 5 | const uint8Array = toUTF8Array(text); 6 | const hashBytes = nobleSha256(uint8Array); 7 | return hashBytes; 8 | }; 9 | 10 | export const hasher = (data: string, algorithm: string) => { 11 | if (toCryptoAlg(algorithm) !== 'sha256') { 12 | throw new SDJWTException('Not implemented'); 13 | } 14 | return sha256(data); 15 | }; 16 | 17 | const toCryptoAlg = (hashAlg: string): string => 18 | // To cover sha-256, sha256, SHA-256, SHA256 19 | hashAlg 20 | .replace('-', '') 21 | .toLowerCase(); 22 | 23 | function toUTF8Array(str: string) { 24 | const utf8: Array = []; 25 | for (let i = 0; i < str.length; i++) { 26 | let charcode = str.charCodeAt(i); 27 | if (charcode < 0x80) utf8.push(charcode); 28 | else if (charcode < 0x800) { 29 | utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f)); 30 | } else if (charcode < 0xd800 || charcode >= 0xe000) { 31 | utf8.push( 32 | 0xe0 | (charcode >> 12), 33 | 0x80 | ((charcode >> 6) & 0x3f), 34 | 0x80 | (charcode & 0x3f), 35 | ); 36 | } 37 | // surrogate pair 38 | else { 39 | i++; 40 | // UTF-16 encodes 0x10000-0x10FFFF by 41 | // subtracting 0x10000 and splitting the 42 | // 20 bits of 0x0-0xFFFFF into two halves 43 | charcode = 44 | 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); 45 | utf8.push( 46 | 0xf0 | (charcode >> 18), 47 | 0x80 | ((charcode >> 12) & 0x3f), 48 | 0x80 | ((charcode >> 6) & 0x3f), 49 | 0x80 | (charcode & 0x3f), 50 | ); 51 | } 52 | } 53 | return new Uint8Array(utf8); 54 | } 55 | -------------------------------------------------------------------------------- /packages/hash/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/hash/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/jwt-status-list/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 65 | -------------------------------------------------------------------------------- /packages/jwt-status-list/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fhash) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## jwt-status-list 9 | An implementation of the [Token Status List](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/) for a JWT representation, not for CBOR. 10 | This library helps to verify the status of a specific entry in a JWT, and to generate a status list and pack it into a signed JWT. It does not provide any functions to manage the status list itself. 11 | 12 | 13 | 14 | ## Installation 15 | 16 | To install this project, run the following command: 17 | 18 | ```bash 19 | # using npm 20 | npm install @sd-jwt/jwt-status-list 21 | 22 | # using yarn 23 | yarn add @sd-jwt/jwt-status-list 24 | 25 | # using pnpm 26 | pnpm install @sd-jwt/jwt-status-list 27 | ``` 28 | 29 | Ensure you have Node.js installed as a prerequisite. 30 | ## Usage 31 | 32 | Creation of a JWT Status List: 33 | ```typescript 34 | // pass the list as an array and the amount of bits per entry. 35 | const list = new StatusList([1, 0, 1, 1, 1], 1); 36 | const iss = 'https://example.com'; 37 | const payload: JWTPayload = { 38 | iss, 39 | sub: `${iss}/statuslist/1`, 40 | iat: new Date().getTime() / 1000, 41 | ttl: 3000, // time to live in seconds, optional 42 | exp: new Date().getTime() / 1000 + 3600, // optional 43 | }; 44 | const header: JWTHeaderParameters = { alg: 'ES256' }; 45 | 46 | const jwt = createHeaderAndPayload(list, payload, header); 47 | 48 | // Sign the JWT with the private key, e.g. using the `jose` library 49 | const jwt = await new SignJWT(values.payload) 50 | .setProtectedHeader(values.header) 51 | .sign(privateKey); 52 | 53 | ``` 54 | 55 | Interaction with a JWT status list on low level: 56 | ```typescript 57 | //validation of the JWT is not provided by this library!!! 58 | 59 | // jwt that includes the status list reference 60 | const reference = getStatusListFromJWT(jwt); 61 | 62 | // download the status list 63 | const list = await fetch(reference.uri); 64 | 65 | //TODO: validate that the list jwt is signed by the issuer and is not expired!!! 66 | 67 | //extract the status list 68 | const statusList = getListFromStatusListJWT(list); 69 | 70 | //get the status of a specific entry 71 | const status = statusList.getStatus(reference.idx); 72 | ``` 73 | 74 | ### Integration into sd-jwt-vc 75 | The status list can be integrated into the [sd-jwt-vc](../sd-jwt-vc/README.md) library to provide a way to verify the status of a credential. In the [test folder](../sd-jwt-vc/src/test/index.spec.ts) you will find an example how to add the status reference to a credential and also how to verify the status of a credential. 76 | 77 | ### Caching the status list 78 | Depending on the `ttl` field if provided the status list can be cached for a certain amount of time. This library has no internal cache mechanism, so it is up to the user to implement it for example by providing a custom `fetchStatusList` function. 79 | 80 | ## Development 81 | 82 | Install the dependencies: 83 | 84 | ```bash 85 | pnpm install 86 | ``` 87 | 88 | Run the tests: 89 | 90 | ```bash 91 | pnpm test 92 | ``` 93 | -------------------------------------------------------------------------------- /packages/jwt-status-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/jwt-status-list", 3 | "version": "0.10.0", 4 | "description": "Implementation based on https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:cov": "vitest run --coverage" 21 | }, 22 | "keywords": [ 23 | "sd-jwt-vc", 24 | "status-list", 25 | "sd-jwt" 26 | ], 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 33 | }, 34 | "author": "Mirko Mollik ", 35 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "devDependencies": { 41 | "@types/pako": "^2.0.3", 42 | "jose": "^5.2.2" 43 | }, 44 | "dependencies": { 45 | "@sd-jwt/types": "workspace:*", 46 | "base64url": "^3.0.1", 47 | "pako": "^2.1.0" 48 | }, 49 | "publishConfig": { 50 | "access": "public" 51 | }, 52 | "tsup": { 53 | "entry": [ 54 | "./src/index.ts" 55 | ], 56 | "sourceMap": true, 57 | "splitting": false, 58 | "clean": true, 59 | "dts": true, 60 | "format": [ 61 | "cjs", 62 | "esm" 63 | ] 64 | }, 65 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 66 | } 67 | -------------------------------------------------------------------------------- /packages/jwt-status-list/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './status-list'; 2 | export * from './status-list-jwt'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/jwt-status-list/src/status-list-jwt.ts: -------------------------------------------------------------------------------- 1 | import type { JwtPayload } from '@sd-jwt/types'; 2 | import { StatusList } from './status-list'; 3 | import type { 4 | JWTwithStatusListPayload, 5 | StatusListJWTHeaderParameters, 6 | StatusListEntry, 7 | StatusListJWTPayload, 8 | } from './types'; 9 | import base64Url from 'base64url'; 10 | 11 | /** 12 | * Decode a JWT and return the payload. 13 | * @param jwt JWT token in compact JWS serialization. 14 | * @returns Payload of the JWT. 15 | */ 16 | function decodeJwt(jwt: string): T { 17 | const parts = jwt.split('.'); 18 | return JSON.parse(base64Url.decode(parts[1])); 19 | } 20 | 21 | /** 22 | * Adds the status list to the payload and header of a JWT. 23 | * @param list 24 | * @param payload 25 | * @param header 26 | * @returns The header and payload with the status list added. 27 | */ 28 | export function createHeaderAndPayload( 29 | list: StatusList, 30 | payload: JwtPayload, 31 | header: StatusListJWTHeaderParameters, 32 | ) { 33 | // validate if the required fieds are present based on https://www.ietf.org/archive/id/draft-ietf-oauth-status-list-02.html#section-5.1 34 | 35 | if (!payload.iss) { 36 | throw new Error('iss field is required'); 37 | } 38 | if (!payload.sub) { 39 | throw new Error('sub field is required'); 40 | } 41 | if (!payload.iat) { 42 | throw new Error('iat field is required'); 43 | } 44 | //exp and tll are optional. We will not validate the business logic of the values like exp > iat etc. 45 | 46 | header.typ = 'statuslist+jwt'; 47 | payload.status_list = { 48 | bits: list.getBitsPerStatus(), 49 | lst: list.compressStatusList(), 50 | }; 51 | return { header, payload }; 52 | } 53 | 54 | /** 55 | * Get the status list from a JWT, but do not verify the signature. 56 | * @param jwt 57 | * @returns 58 | */ 59 | export function getListFromStatusListJWT(jwt: string): StatusList { 60 | const payload = decodeJwt(jwt); 61 | const statusList = payload.status_list; 62 | return StatusList.decompressStatusList(statusList.lst, statusList.bits); 63 | } 64 | 65 | /** 66 | * Get the status list entry from a JWT, but do not verify the signature. 67 | * @param jwt 68 | * @returns 69 | */ 70 | export function getStatusListFromJWT(jwt: string): StatusListEntry { 71 | const payload = decodeJwt(jwt); 72 | return payload.status.status_list; 73 | } 74 | -------------------------------------------------------------------------------- /packages/jwt-status-list/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { JwtPayload } from '@sd-jwt/types'; 2 | 3 | /** 4 | * Reference to a status list entry. 5 | */ 6 | export interface StatusListEntry { 7 | idx: number; 8 | uri: string; 9 | } 10 | 11 | /** 12 | * Payload for a JWT 13 | */ 14 | export interface JWTwithStatusListPayload extends JwtPayload { 15 | status: { 16 | status_list: StatusListEntry; 17 | }; 18 | } 19 | 20 | /** 21 | * Payload for a JWT with a status list. 22 | */ 23 | export interface StatusListJWTPayload extends JwtPayload { 24 | ttl?: number; 25 | status_list: { 26 | bits: BitsPerStatus; 27 | lst: string; 28 | }; 29 | } 30 | 31 | /** 32 | * BitsPerStatus type. 33 | */ 34 | export type BitsPerStatus = 1 | 2 | 4 | 8; 35 | 36 | /** 37 | * Header parameters for a JWT. 38 | */ 39 | export type StatusListJWTHeaderParameters = { 40 | alg: string; 41 | typ: 'statuslist+jwt'; 42 | [key: string]: unknown; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/jwt-status-list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/jwt-status-list/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/node-crypto/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 65 | 66 | 67 | 68 | 69 | 70 | ## [0.6.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 71 | 72 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 73 | 74 | 75 | 76 | 77 | 78 | # [0.6.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 79 | 80 | **Note:** Version bump only for package @sd-jwt/crypto-nodejs 81 | 82 | 83 | 84 | 85 | 86 | # [0.5.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 87 | 88 | 89 | ### Features 90 | 91 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 92 | 93 | 94 | 95 | 96 | 97 | # 0.4.0 (2024-03-08) 98 | 99 | 100 | ### Bug Fixes 101 | 102 | * add publish config ([#93](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/93)) ([2e4c5c1](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/2e4c5c176dc88e58e49d06783b7658d8ad872313)) 103 | * implement kbverify function ([#127](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/127)) ([e5609f2](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/e5609f26fab8c4991d3bd6c36066a95a30cfb972)) 104 | 105 | 106 | ### Features 107 | 108 | * es crypto features to nodejs and browser ([#106](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/106)) ([3ba74e9](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/3ba74e936dbc39698d47c6c8c1da956430e937f8)) 109 | -------------------------------------------------------------------------------- /packages/node-crypto/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fcrypto-nodejs) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Nodejs Crypto 9 | 10 | ### About 11 | 12 | Nodejs Crypto support for SD JWT 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/crypto-nodejs 23 | 24 | # using yarn 25 | yarn add @sd-jwt/crypto-nodejs 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/crypto-nodejs 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | None 40 | -------------------------------------------------------------------------------- /packages/node-crypto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/crypto-nodejs", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:cov": "vitest run --coverage" 20 | }, 21 | "keywords": [ 22 | "sd-jwt", 23 | "sdjwt", 24 | "sd-jwt-vc" 25 | ], 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 37 | }, 38 | "license": "Apache-2.0", 39 | "publishConfig": { 40 | "access": "public" 41 | }, 42 | "tsup": { 43 | "entry": [ 44 | "./src/index.ts" 45 | ], 46 | "sourceMap": true, 47 | "splitting": false, 48 | "clean": true, 49 | "dts": true, 50 | "format": [ 51 | "cjs", 52 | "esm" 53 | ] 54 | }, 55 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 56 | } 57 | -------------------------------------------------------------------------------- /packages/node-crypto/src/crypto.ts: -------------------------------------------------------------------------------- 1 | import { createHash, randomBytes, subtle } from 'node:crypto'; 2 | 3 | export const generateSalt = (length: number): string => { 4 | if (length <= 0) { 5 | return ''; 6 | } 7 | const saltBytes = randomBytes(length); 8 | const salt = saltBytes.toString('hex'); 9 | return salt.substring(0, length); 10 | }; 11 | 12 | export const digest = ( 13 | data: string | ArrayBuffer, 14 | algorithm = 'sha-256', 15 | ): Uint8Array => { 16 | const nodeAlg = toNodeCryptoAlg(algorithm); 17 | const hash = createHash(nodeAlg); 18 | if (typeof data === 'string') { 19 | hash.update(data); 20 | } else { 21 | hash.update(Buffer.from(data)); 22 | } 23 | const hashBuffer = hash.digest(); 24 | return new Uint8Array(hashBuffer); 25 | }; 26 | 27 | const toNodeCryptoAlg = (hashAlg: string): string => 28 | hashAlg.replace('-', '').toLowerCase(); 29 | 30 | export const ES256 = { 31 | alg: 'ES256', 32 | 33 | async generateKeyPair() { 34 | const keyPair = await subtle.generateKey( 35 | { 36 | name: 'ECDSA', 37 | namedCurve: 'P-256', // ES256 38 | }, 39 | true, // whether the key is extractable (i.e., can be used in exportKey) 40 | ['sign', 'verify'], // can be used to sign and verify signatures 41 | ); 42 | 43 | // Export the public and private keys in JWK format 44 | const publicKeyJWK = await subtle.exportKey('jwk', keyPair.publicKey); 45 | const privateKeyJWK = await subtle.exportKey('jwk', keyPair.privateKey); 46 | 47 | return { publicKey: publicKeyJWK, privateKey: privateKeyJWK }; 48 | }, 49 | 50 | async getSigner(privateKeyJWK: object) { 51 | const privateKey = await subtle.importKey( 52 | 'jwk', 53 | privateKeyJWK, 54 | { 55 | name: 'ECDSA', 56 | namedCurve: 'P-256', // Must match the curve used to generate the key 57 | }, 58 | true, // whether the key is extractable (i.e., can be used in exportKey) 59 | ['sign'], 60 | ); 61 | 62 | return async (data: string) => { 63 | const encoder = new TextEncoder(); 64 | const signature = await subtle.sign( 65 | { 66 | name: 'ECDSA', 67 | hash: { name: 'sha-256' }, // Required for ES256 68 | }, 69 | privateKey, 70 | encoder.encode(data), 71 | ); 72 | 73 | return btoa(String.fromCharCode(...new Uint8Array(signature))) 74 | .replace(/\+/g, '-') 75 | .replace(/\//g, '_') 76 | .replace(/=+$/, ''); // Convert to base64url format 77 | }; 78 | }, 79 | 80 | async getVerifier(publicKeyJWK: object) { 81 | const publicKey = await subtle.importKey( 82 | 'jwk', 83 | publicKeyJWK, 84 | { 85 | name: 'ECDSA', 86 | namedCurve: 'P-256', // Must match the curve used to generate the key 87 | }, 88 | true, // whether the key is extractable (i.e., can be used in exportKey) 89 | ['verify'], 90 | ); 91 | 92 | return async (data: string, signatureBase64url: string) => { 93 | const encoder = new TextEncoder(); 94 | const signature = Uint8Array.from( 95 | atob(signatureBase64url.replace(/-/g, '+').replace(/_/g, '/')), 96 | (c) => c.charCodeAt(0), 97 | ); 98 | const isValid = await subtle.verify( 99 | { 100 | name: 'ECDSA', 101 | hash: { name: 'sha-256' }, // Required for ES256 102 | }, 103 | publicKey, 104 | signature, 105 | encoder.encode(data), 106 | ); 107 | 108 | return isValid; 109 | }; 110 | }, 111 | }; 112 | -------------------------------------------------------------------------------- /packages/node-crypto/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crypto'; 2 | -------------------------------------------------------------------------------- /packages/node-crypto/src/test/crypto.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { generateSalt, digest, ES256 } from '../index'; 3 | 4 | // Extract the major version as a number 5 | const nodeVersionMajor = Number.parseInt( 6 | process.version.split('.')[0].substring(1), 7 | 10, 8 | ); 9 | 10 | describe('This file is for utility functions', () => { 11 | test('generateSalt', async () => { 12 | const salt = generateSalt(8); 13 | expect(salt).toBeDefined(); 14 | expect(salt.length).toBe(8); 15 | }); 16 | 17 | test('generateSalt 0 length', async () => { 18 | const salt = generateSalt(0); 19 | expect(salt).toBeDefined(); 20 | expect(salt.length).toBe(0); 21 | }); 22 | 23 | test('digest', async () => { 24 | const payload = 'test1'; 25 | const s1 = await digest(payload); 26 | expect(s1).toBeDefined(); 27 | expect(s1.length).toBe(32); 28 | }); 29 | 30 | test('digest', async () => { 31 | const payload = 'test1'; 32 | const s1 = await digest(payload, 'SHA512'); 33 | expect(s1).toBeDefined(); 34 | expect(s1.length).toBe(64); 35 | }); 36 | 37 | (nodeVersionMajor < 20 ? test.skip : test)('ES256', async () => { 38 | const { privateKey, publicKey } = await ES256.generateKeyPair(); 39 | expect(privateKey).toBeDefined(); 40 | expect(publicKey).toBeDefined(); 41 | expect(typeof privateKey).toBe('object'); 42 | expect(typeof publicKey).toBe('object'); 43 | 44 | const data = 45 | 'In cryptography, a salt is random data that is used as an additional input to a one-way function that hashes data, a password or passphrase.'; 46 | const signer = await ES256.getSigner(privateKey); 47 | const signature = await signer(data); 48 | expect(signature).toBeDefined(); 49 | expect(typeof signature).toBe('string'); 50 | 51 | const verifier = await ES256.getVerifier(publicKey); 52 | const result = await verifier(data, signature); 53 | expect(result).toBe(true); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/node-crypto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/node-crypto/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { nodeConfig } from '../../vitest.shared'; 3 | 4 | export default nodeConfig; 5 | -------------------------------------------------------------------------------- /packages/present/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/present 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/present 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/present 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/present 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/present 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/present 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/present 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | **Note:** Version bump only for package @sd-jwt/present 65 | 66 | 67 | 68 | 69 | 70 | ## [0.6.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 71 | 72 | **Note:** Version bump only for package @sd-jwt/present 73 | 74 | 75 | 76 | 77 | 78 | # [0.6.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * add missing export ([#159](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/159)) ([f92b3e2](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/f92b3e246371a83c51c5450b9bccb04753c58bf4)) 84 | 85 | 86 | ### Features 87 | 88 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([9baa93e](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/9baa93efb612ee2fe73c5872766cbc37e541c4dc)) 89 | 90 | 91 | 92 | 93 | 94 | # [0.5.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 95 | 96 | 97 | ### Features 98 | 99 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 100 | 101 | 102 | 103 | 104 | 105 | # 0.4.0 (2024-03-08) 106 | 107 | 108 | ### Bug Fixes 109 | 110 | * add publish config ([#93](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/93)) ([2e4c5c1](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/2e4c5c176dc88e58e49d06783b7658d8ad872313)) 111 | * convert any usage into or typed version ([#80](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/80)) ([de4df54](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/de4df54f2a0a77fdbf97e10abac555a98e70c6e0)) 112 | -------------------------------------------------------------------------------- /packages/present/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Fpresent) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Present 9 | 10 | ### About 11 | 12 | Present SD JWT 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/present 23 | 24 | # using yarn 25 | yarn add @sd-jwt/present 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/present 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | - @sd-jwt/decode 40 | - @sd-jwt/types 41 | - @sd-jwt/utils 42 | -------------------------------------------------------------------------------- /packages/present/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/present", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:cov": "vitest run --coverage" 21 | }, 22 | "keywords": [ 23 | "sd-jwt", 24 | "sdjwt", 25 | "sd-jwt-vc" 26 | ], 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 33 | }, 34 | "author": "Lukas.J.Han ", 35 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "devDependencies": { 41 | "@sd-jwt/crypto-nodejs": "workspace:*" 42 | }, 43 | "dependencies": { 44 | "@sd-jwt/decode": "workspace:*", 45 | "@sd-jwt/types": "workspace:*", 46 | "@sd-jwt/utils": "workspace:*" 47 | }, 48 | "publishConfig": { 49 | "access": "public" 50 | }, 51 | "tsup": { 52 | "entry": [ 53 | "./src/index.ts" 54 | ], 55 | "sourceMap": true, 56 | "splitting": false, 57 | "clean": true, 58 | "dts": true, 59 | "format": [ 60 | "cjs", 61 | "esm" 62 | ] 63 | }, 64 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 65 | } 66 | -------------------------------------------------------------------------------- /packages/present/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './present'; 2 | -------------------------------------------------------------------------------- /packages/present/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/present/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/sd-jwt-vc", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:e2e && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:e2e": "vitest run ./test/*e2e.spec.ts --environment node", 21 | "test:cov": "vitest run --coverage" 22 | }, 23 | "keywords": [ 24 | "sd-jwt", 25 | "sdjwt", 26 | "sd-jwt-vc" 27 | ], 28 | "engines": { 29 | "node": ">=18" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 34 | }, 35 | "author": "Lukas.J.Han ", 36 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 37 | "bugs": { 38 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 39 | }, 40 | "license": "Apache-2.0", 41 | "dependencies": { 42 | "@sd-jwt/core": "workspace:*", 43 | "@sd-jwt/jwt-status-list": "workspace:*", 44 | "@sd-jwt/utils": "workspace:*", 45 | "ajv": "^8.17.1", 46 | "ajv-formats": "^3.0.1" 47 | }, 48 | "devDependencies": { 49 | "@sd-jwt/crypto-nodejs": "workspace:*", 50 | "@sd-jwt/types": "workspace:*", 51 | "jose": "^5.2.2", 52 | "msw": "^2.3.5" 53 | }, 54 | "publishConfig": { 55 | "access": "public" 56 | }, 57 | "tsup": { 58 | "entry": [ 59 | "./src/index.ts" 60 | ], 61 | "sourceMap": true, 62 | "splitting": false, 63 | "clean": true, 64 | "dts": true, 65 | "format": [ 66 | "cjs", 67 | "esm" 68 | ] 69 | }, 70 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 71 | } 72 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sd-jwt-vc-config'; 2 | export * from './sd-jwt-vc-instance'; 3 | export * from './sd-jwt-vc-payload'; 4 | export * from './sd-jwt-vc-status-reference'; 5 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/sd-jwt-vc-config.ts: -------------------------------------------------------------------------------- 1 | import type { SDJWTConfig } from '@sd-jwt/types'; 2 | import type { VcTFetcher } from './sd-jwt-vc-vct'; 3 | 4 | export type StatusListFetcher = (uri: string) => Promise; 5 | export type StatusValidator = (status: number) => Promise; 6 | 7 | /** 8 | * Configuration for SD-JWT-VC 9 | */ 10 | export type SDJWTVCConfig = SDJWTConfig & { 11 | // A function that fetches the status list from the uri. If not provided, the library will assume that the response is a compact JWT. 12 | statusListFetcher?: StatusListFetcher; 13 | // validte the status and decide if the status is valid or not. If not provided, the code will continue if it is 0, otherwise it will throw an error. 14 | statusValidator?: StatusValidator; 15 | // a function that fetches the type metadata format from the uri. If not provided, the library will assume that the response is a TypeMetadataFormat. Caching has to be implemented in this function. If the integrity value is passed, it to be validated according to https://www.w3.org/TR/SRI/ 16 | vctFetcher?: VcTFetcher; 17 | // if set to true, it will load the metadata format based on the vct value. If not provided, it will default to false. 18 | loadTypeMetadataFormat?: boolean; 19 | // timeout value in milliseconds when to abort the fetch request. If not provided, it will default to 10000. 20 | timeout?: number; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/sd-jwt-vc-payload.ts: -------------------------------------------------------------------------------- 1 | import type { SdJwtPayload } from '@sd-jwt/core'; 2 | import type { SDJWTVCStatusReference } from './sd-jwt-vc-status-reference'; 3 | 4 | export interface SdJwtVcPayload extends SdJwtPayload { 5 | // REQUIRED. The Issuer of the Verifiable Credential. The value of iss MUST be a URI. See [RFC7519] for more information. 6 | iss: string; 7 | // OPTIONAL. The time before which the Verifiable Credential MUST NOT be accepted before validating. See [RFC7519] for more information. 8 | nbf?: number; 9 | // OPTIONAL. The expiry time of the Verifiable Credential after which the Verifiable Credential is no longer valid. See [RFC7519] for more information. 10 | exp?: number; 11 | // OPTIONAL unless cryptographic Key Binding is to be supported, in which case it is REQUIRED. Contains the confirmation method identifying the proof of possession key as defined in [RFC7800]. It is RECOMMENDED that this contains a JWK as defined in Section 3.2 of [RFC7800]. For proof of cryptographic Key Binding, the Key Binding JWT in the presentation of the SD-JWT MUST be signed by the key identified in this claim. 12 | cnf?: unknown; 13 | // REQUIRED. The type of the Verifiable Credential, e.g., https://credentials.example.com/identity_credential, as defined in Section 3.2.2.1.1. 14 | vct: string; 15 | // OPTIONAL. If passed, the loaded type metadata format has to be validated according to https://www.w3.org/TR/SRI/ 16 | 'vct#Integrity'?: string; 17 | // OPTIONAL. The information on how to read the status of the Verifiable Credential. See [https://www.ietf.org/archive/id/draft-ietf-oauth-status-list-02.html] for more information. 18 | status?: SDJWTVCStatusReference; 19 | // OPTIONAL. The identifier of the Subject of the Verifiable Credential. The Issuer MAY use it to provide the Subject identifier known by the Issuer. There is no requirement for a binding to exist between sub and cnf claims. 20 | sub?: string; 21 | // OPTIONAL. The time of issuance of the Verifiable Credential. See [RFC7519] for more information. 22 | iat?: number; 23 | } 24 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/sd-jwt-vc-status-reference.ts: -------------------------------------------------------------------------------- 1 | export interface SDJWTVCStatusReference { 2 | // REQUIRED. implenentation according to https://www.ietf.org/archive/id/draft-ietf-oauth-status-list-02.html 3 | status_list: { 4 | // REQUIRED. index in the list of statuses 5 | idx: number; 6 | // REQUIRED. the reference to fetch the status list 7 | uri: string; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/sd-jwt-vc-type-metadata-format.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-08.html#name-type-metadata-format 3 | */ 4 | export type TypeMetadataFormat = { 5 | vct: string; // REQUIRED. A URI that uniquely identifies the type. This URI MUST be dereferenceable to a JSON document that describes the type. 6 | name?: string; // OPTIONAL. A human-readable name for the type, intended for developers reading the JSON document. 7 | description?: string; // OPTIONAL. A human-readable description for the type, intended for developers reading the JSON document. 8 | extends?: string; // OPTIONAL. A URI of another type that this type extends, as described in Section 6.4. 9 | 'extends#Integrity'?: string; // OPTIONAL. Validating the ingegrity of the extends field 10 | schema?: object; // OPTIONAL. An embedded JSON Schema document describing the structure of the Verifiable Credential as described in Section 6.5.1. schema MUST NOT be used if schema_uri is present. 11 | schema_uri?: string; // OPTIONAL. A URL pointing to a JSON Schema document describing the structure of the Verifiable Credential as described in Section 6.5.1. schema_uri MUST NOT be used if schema is present. 12 | 'schema_uri#Integrity'?: string; // OPTIONAL. Validating the integrity of the schema_uri field. 13 | }; 14 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/sd-jwt-vc-vct.ts: -------------------------------------------------------------------------------- 1 | import type { TypeMetadataFormat } from './sd-jwt-vc-type-metadata-format'; 2 | 3 | export type VcTFetcher = ( 4 | uri: string, 5 | integrity?: string, 6 | ) => Promise; 7 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/verification-result.ts: -------------------------------------------------------------------------------- 1 | import type { kbPayload, kbHeader } from '@sd-jwt/types'; 2 | import type { SdJwtVcPayload } from './sd-jwt-vc-payload'; 3 | import type { TypeMetadataFormat } from './sd-jwt-vc-type-metadata-format'; 4 | 5 | export type VerificationResult = { 6 | payload: SdJwtVcPayload; 7 | header: Record | undefined; 8 | kb: 9 | | { 10 | payload: kbPayload; 11 | header: kbHeader; 12 | } 13 | | undefined; 14 | typeMetadataFormat?: TypeMetadataFormat; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_data_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }] 4 | }, 5 | "disclosureFrame": { 6 | "data_types": { 7 | "_sd": [0, 1, 2, 3, 4, 5] 8 | } 9 | }, 10 | "presentationFrames": { 11 | "data_types": { 12 | "0": true, 13 | "1": true, 14 | "2": true, 15 | "3": true, 16 | "4": true, 17 | "5": true 18 | } 19 | }, 20 | "presenatedClaims": { 21 | "data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }] 22 | }, 23 | "requiredClaimKeys": [ 24 | "data_types.0", 25 | "data_types.1", 26 | "data_types.2", 27 | "data_types.3", 28 | "data_types.4", 29 | "data_types.5" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_full_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "is_over": { 4 | "13": true, 5 | "18": false, 6 | "21": false 7 | } 8 | }, 9 | "disclosureFrame": { 10 | "is_over": { 11 | "_sd": ["13", "18", "21"] 12 | } 13 | }, 14 | "presentationFrames": { "is_over": { "18": true } }, 15 | "presenatedClaims": { 16 | "is_over": { 17 | "18": false 18 | } 19 | }, 20 | "requiredClaimKeys": ["is_over.18"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_in_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sd_array": ["32", "23"] 4 | }, 5 | "disclosureFrame": { 6 | "_sd": ["sd_array"] 7 | }, 8 | "presentationFrames": { "sd_array": true }, 9 | "presenatedClaims": { 10 | "sd_array": ["32", "23"] 11 | }, 12 | "requiredClaimKeys": ["sd_array"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_nested_in_plain.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "nested_array": [["foo", "bar"], ["baz", "qux"]] 4 | }, 5 | "disclosureFrame": { 6 | "nested_array": { 7 | "0": { 8 | "_sd": [0, 1] 9 | }, 10 | "1": { 11 | "_sd": [0, 1] 12 | } 13 | } 14 | }, 15 | "presentationFrames": { 16 | "nested_array": { 17 | "0": { 18 | "0": true 19 | }, 20 | "1": { 21 | "1": true 22 | } 23 | } 24 | }, 25 | "presenatedClaims": { 26 | "nested_array": [["foo"], ["qux"]] 27 | }, 28 | "requiredClaimKeys": ["nested_array.0.0", "nested_array.1.0"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_none_disclosed.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "is_over": { 4 | "13": false, 5 | "18": true, 6 | "21": false 7 | } 8 | }, 9 | "disclosureFrame": { 10 | "is_over": { 11 | "_sd": ["13", "18", "21"] 12 | } 13 | }, 14 | "presentationFrames": {}, 15 | "presenatedClaims": {}, 16 | "requiredClaimKeys": [] 17 | } 18 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_of_nulls.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "null_values": [null, null, null, null] 4 | }, 5 | "disclosureFrame": { 6 | "null_values": { 7 | "_sd": [1, 2] 8 | } 9 | }, 10 | "presentationFrames": {}, 11 | "presenatedClaims": { 12 | "null_values": [null, null] 13 | }, 14 | "requiredClaimKeys": ["null_values.0", "null_values.1"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_of_objects.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "addresses": [ 4 | { 5 | "street": "123 Main St", 6 | "city": "Anytown", 7 | "state": "NY", 8 | "zip": "12345", 9 | "type": "main_address" 10 | }, 11 | { 12 | "street": "456 Main St", 13 | "city": "Anytown", 14 | "state": "NY", 15 | "zip": "12345", 16 | "type": "secondary_address" 17 | } 18 | ], 19 | "array_with_one_sd_object": { 20 | "foo": "bar" 21 | } 22 | }, 23 | "disclosureFrame": { 24 | "addresses": { 25 | "_sd": [1] 26 | }, 27 | "array_with_one_sd_object": { 28 | "_sd": ["foo"] 29 | } 30 | }, 31 | "presentationFrames": { 32 | "addresses": { 33 | "1": true 34 | }, 35 | "array_with_one_sd_object": { 36 | "foo": true 37 | } 38 | }, 39 | "presenatedClaims": { 40 | "addresses": [ 41 | { 42 | "street": "123 Main St", 43 | "city": "Anytown", 44 | "state": "NY", 45 | "zip": "12345", 46 | "type": "main_address" 47 | }, 48 | { 49 | "street": "456 Main St", 50 | "city": "Anytown", 51 | "state": "NY", 52 | "zip": "12345", 53 | "type": "secondary_address" 54 | } 55 | ], 56 | "array_with_one_sd_object": { 57 | "foo": "bar" 58 | } 59 | }, 60 | "requiredClaimKeys": [ 61 | "addresses.0.type", 62 | "addresses.1.city", 63 | "array_with_one_sd_object.foo" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_of_scalars.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "nationalities": ["US", "CA", "DE"] 4 | }, 5 | "disclosureFrame": { 6 | "nationalities": { 7 | "_sd": [0, 1] 8 | } 9 | }, 10 | "presentationFrames": { 11 | "nationalities": { 12 | "1": true 13 | } 14 | }, 15 | "presenatedClaims": { 16 | "nationalities": ["CA", "DE"] 17 | }, 18 | "requiredClaimKeys": ["nationalities.0", "nationalities.1"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_recursive_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "array_with_recursive_sd": [ 4 | "boring", 5 | { 6 | "foo": "bar", 7 | "baz": { 8 | "qux": "quxx" 9 | } 10 | }, 11 | ["foo", "bar"] 12 | ], 13 | "test2": ["foo", "bar"] 14 | }, 15 | "disclosureFrame": { 16 | "array_with_recursive_sd": { 17 | "_sd": [1], 18 | "1": { 19 | "_sd": ["baz"] 20 | }, 21 | "2": { 22 | "_sd": [0, 1] 23 | } 24 | }, 25 | "test2": { 26 | "_sd": [0, 1] 27 | } 28 | }, 29 | "presentationFrames": {}, 30 | "presenatedClaims": { 31 | "array_with_recursive_sd": ["boring", []], 32 | "test2": [] 33 | }, 34 | "requiredClaimKeys": ["array_with_recursive_sd.0", "test2"] 35 | } 36 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/array_recursive_sd_some_disclosed.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "array_with_recursive_sd": [ 4 | "boring", 5 | { 6 | "foo": "bar", 7 | "baz": { 8 | "qux": "quxx" 9 | } 10 | }, 11 | ["foo", "bar"] 12 | ], 13 | "test2": ["foo", "bar"] 14 | }, 15 | "disclosureFrame": { 16 | "array_with_recursive_sd": { 17 | "1": { 18 | "_sd": ["baz"] 19 | }, 20 | "2": { 21 | "_sd": [0, 1] 22 | } 23 | }, 24 | "test2": { 25 | "_sd": [0, 1] 26 | } 27 | }, 28 | "presentationFrames": { 29 | "array_with_recursive_sd": { 30 | "1": { 31 | "baz": true 32 | }, 33 | "2": { 34 | "1": true 35 | } 36 | }, 37 | "test2": { 38 | "0": true, 39 | "1": true 40 | } 41 | }, 42 | "presenatedClaims": { 43 | "array_with_recursive_sd": [ 44 | "boring", 45 | { 46 | "foo": "bar", 47 | "baz": { 48 | "qux": "quxx" 49 | } 50 | }, 51 | ["bar"] 52 | ], 53 | "test2": ["foo", "bar"] 54 | }, 55 | "requiredClaimKeys": [ 56 | "array_with_recursive_sd.1", 57 | "array_with_recursive_sd.2", 58 | "array_with_recursive_sd.1.baz", 59 | "array_with_recursive_sd.2.1", 60 | "test2.0", 61 | "test2.1" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "firstname": "John", 4 | "lastname": "Doe", 5 | "ssn": "123-45-6789", 6 | "id": "1234", 7 | "data": { 8 | "firstname": "John", 9 | "lastname": "Doe", 10 | "ssn": "123-45-6789", 11 | "list": [{ "r": "1" }, "b", "c"] 12 | }, 13 | "data2": { 14 | "hi": "bye" 15 | } 16 | }, 17 | "disclosureFrame": { 18 | "_sd": ["firstname", "id", "data2"], 19 | "data": { 20 | "_sd": ["list"], 21 | "_sd_decoy": 2, 22 | "list": { 23 | "_sd": [0, 2], 24 | "_sd_decoy": 1, 25 | "0": { 26 | "_sd": ["r"] 27 | } 28 | } 29 | }, 30 | "data2": { 31 | "_sd": ["hi"] 32 | } 33 | }, 34 | "presentationFrames": { "firstname": true, "id": true }, 35 | "presenatedClaims": { 36 | "lastname": "Doe", 37 | "ssn": "123-45-6789", 38 | "data": { "firstname": "John", "lastname": "Doe", "ssn": "123-45-6789" }, 39 | "id": "1234", 40 | "firstname": "John" 41 | }, 42 | "requiredClaimKeys": ["firstname", "id", "data.ssn"] 43 | } 44 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/header_mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sub": "john_deo_42", 4 | "given_name": "John", 5 | "family_name": "Deo", 6 | "email": "johndeo@example.com", 7 | "phone": "+1-202-555-0101", 8 | "address": { 9 | "street_address": "123 Main St", 10 | "locality": "Anytown", 11 | "region": "Anystate", 12 | "country": "US" 13 | }, 14 | "birthdate": "1940-01-01" 15 | }, 16 | "disclosureFrame": { 17 | "_sd": [ 18 | "sub", 19 | "given_name", 20 | "family_name", 21 | "email", 22 | "phone", 23 | "address", 24 | "birthdate" 25 | ] 26 | }, 27 | "presentationFrames": { 28 | "given_name": true, 29 | "family_name": true, 30 | "address": true 31 | }, 32 | "presenatedClaims": { 33 | "given_name": "John", 34 | "family_name": "Deo", 35 | "address": { 36 | "street_address": "123 Main St", 37 | "locality": "Anytown", 38 | "region": "Anystate", 39 | "country": "US" 40 | } 41 | }, 42 | "requiredClaimKeys": [ 43 | "given_name", 44 | "family_name", 45 | "address.street_address", 46 | "address.country" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/json_serialization.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sub": "john_deo_42", 4 | "given_name": "John", 5 | "family_name": "Deo", 6 | "email": "johndeo@example.com", 7 | "phone": "+1-202-555-0101", 8 | "address": { 9 | "street_address": "123 Main St", 10 | "locality": "Anytown", 11 | "region": "Anystate", 12 | "country": "US" 13 | }, 14 | "birthdate": "1940-01-01" 15 | }, 16 | "disclosureFrame": { 17 | "_sd": [ 18 | "sub", 19 | "given_name", 20 | "family_name", 21 | "email", 22 | "phone", 23 | "address", 24 | "birthdate" 25 | ] 26 | }, 27 | "presentationFrames": { 28 | "given_name": true, 29 | "family_name": true, 30 | "address": true 31 | }, 32 | "presenatedClaims": { 33 | "given_name": "John", 34 | "family_name": "Deo", 35 | "address": { 36 | "street_address": "123 Main St", 37 | "locality": "Anytown", 38 | "region": "Anystate", 39 | "country": "US" 40 | } 41 | }, 42 | "requiredClaimKeys": [ 43 | "given_name", 44 | "family_name", 45 | "address.street_address", 46 | "address.country" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/key_binding.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "sub": "john_deo_42", 4 | "given_name": "John", 5 | "family_name": "Deo", 6 | "email": "johndeo@example.com", 7 | "phone": "+1-202-555-0101", 8 | "address": { 9 | "street_address": "123 Main St", 10 | "locality": "Anytown", 11 | "region": "Anystate", 12 | "country": "US" 13 | }, 14 | "birthdate": "1940-01-01" 15 | }, 16 | "disclosureFrame": { 17 | "_sd": [ 18 | "sub", 19 | "given_name", 20 | "family_name", 21 | "email", 22 | "phone", 23 | "address", 24 | "birthdate" 25 | ] 26 | }, 27 | "presentationFrames": { 28 | "given_name": true, 29 | "family_name": true, 30 | "address": true 31 | }, 32 | "presenatedClaims": { 33 | "given_name": "John", 34 | "family_name": "Deo", 35 | "address": { 36 | "street_address": "123 Main St", 37 | "locality": "Anytown", 38 | "region": "Anystate", 39 | "country": "US" 40 | } 41 | }, 42 | "requiredClaimKeys": [ 43 | "given_name", 44 | "family_name", 45 | "address.street_address", 46 | "address.country" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/no_sd.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "recursive": [ 4 | "boring", 5 | { 6 | "foo": "bar", 7 | "baz": { 8 | "qux": "quxx" 9 | } 10 | }, 11 | ["foo", "bar"] 12 | ], 13 | "test2": ["foo", "bar"] 14 | }, 15 | "disclosureFrame": {}, 16 | "presentationFrames": {}, 17 | "presenatedClaims": { 18 | "recursive": [ 19 | "boring", 20 | { 21 | "foo": "bar", 22 | "baz": { 23 | "qux": "quxx" 24 | } 25 | }, 26 | ["foo", "bar"] 27 | ], 28 | "test2": ["foo", "bar"] 29 | }, 30 | "requiredClaimKeys": [ 31 | "recursive.0", 32 | "recursive.1.baz.qux", 33 | "recursive.2.1", 34 | "test2.1" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/object_data_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "value_Data_types": { 4 | "test_null": null, 5 | "test_int": 42, 6 | "test_float": 3.14, 7 | "test_str": "foo", 8 | "test_bool": true, 9 | "test_arr": ["Test"], 10 | "test_object": { 11 | "foo": "bar" 12 | } 13 | } 14 | }, 15 | "disclosureFrame": { 16 | "value_Data_types": { 17 | "_sd": [ 18 | "test_null", 19 | "test_int", 20 | "test_float", 21 | "test_str", 22 | "test_bool", 23 | "test_arr", 24 | "test_object" 25 | ] 26 | } 27 | }, 28 | "presentationFrames": { 29 | "value_Data_types": { 30 | "test_null": true, 31 | "test_int": true, 32 | "test_float": true, 33 | "test_str": true, 34 | "test_bool": true, 35 | "test_arr": true, 36 | "test_object": true 37 | } 38 | }, 39 | "presenatedClaims": { 40 | "value_Data_types": { 41 | "test_null": null, 42 | "test_int": 42, 43 | "test_float": 3.14, 44 | "test_str": "foo", 45 | "test_bool": true, 46 | "test_arr": ["Test"], 47 | "test_object": { 48 | "foo": "bar" 49 | } 50 | } 51 | }, 52 | "requiredClaimKeys": [ 53 | "value_Data_types.test_null", 54 | "value_Data_types.test_int", 55 | "value_Data_types.test_float", 56 | "value_Data_types.test_str", 57 | "value_Data_types.test_bool", 58 | "value_Data_types.test_arr", 59 | "value_Data_types.test_object", 60 | "value_Data_types.test_object.foo" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/test/recursions.json: -------------------------------------------------------------------------------- 1 | { 2 | "claims": { 3 | "foo": ["one", "two"], 4 | "bar": { 5 | "red": 1, 6 | "green": 2 7 | }, 8 | "qux": [["blue", "yellow"]], 9 | "baz": [["orange", "purple"], ["black", "white"]], 10 | "animals": { 11 | "snake": { 12 | "name": "python", 13 | "age": 10 14 | }, 15 | "bird": { 16 | "name": "eagle", 17 | "age": 20 18 | } 19 | } 20 | }, 21 | "disclosureFrame": { 22 | "foo": { 23 | "_sd": [0, 1] 24 | }, 25 | "bar": { 26 | "_sd": ["red", "green"] 27 | }, 28 | "qux": { 29 | "_sd": [0], 30 | "0": { 31 | "_sd": [0, 1] 32 | } 33 | }, 34 | "baz": { 35 | "_sd": [0, 1], 36 | "0": { 37 | "_sd": [0, 1] 38 | }, 39 | "1": { 40 | "_sd": [0, 1] 41 | } 42 | }, 43 | "animals": { 44 | "_sd": ["snake", "bird"], 45 | "snake": { 46 | "_sd": ["name", "age"] 47 | }, 48 | "bird": { 49 | "_sd": ["name", "age"] 50 | } 51 | } 52 | }, 53 | "presentationFrames": { 54 | "foo": { 55 | "1": true 56 | }, 57 | "bar": { 58 | "green": true 59 | }, 60 | "qux": { 61 | "0": { 62 | "0": true, 63 | "1": true 64 | }, 65 | "1": { 66 | "0": true, 67 | "1": true 68 | } 69 | }, 70 | "baz": { 71 | "0": { 72 | "0": true, 73 | "1": true 74 | }, 75 | "1": { 76 | "0": true, 77 | "1": true 78 | } 79 | }, 80 | "animals": { 81 | "snake": { 82 | "age": true 83 | }, 84 | "bird": { 85 | "age": true 86 | } 87 | } 88 | }, 89 | "presenatedClaims": { 90 | "foo": ["two"], 91 | "bar": { 92 | "green": 2 93 | }, 94 | "qux": [["blue", "yellow"]], 95 | "baz": [["orange", "purple"], ["black", "white"]], 96 | "animals": { 97 | "snake": { 98 | "age": 10 99 | }, 100 | "bird": { 101 | "age": 20 102 | } 103 | } 104 | }, 105 | "requiredClaimKeys": [ 106 | "foo.1", 107 | "bar.green", 108 | "qux.0.0", 109 | "qux.0.1", 110 | "baz.0.0", 111 | "baz.0.1", 112 | "baz.1.0", 113 | "baz.1.1", 114 | "animals.snake.age", 115 | "animals.bird.age" 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/types/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Ftypes) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Browser Types 9 | 10 | ### About 11 | 12 | Types for SD JWT 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/types 23 | 24 | # using yarn 25 | yarn add @sd-jwt/types 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/types 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | None 40 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/types", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:cov": "vitest run --coverage" 21 | }, 22 | "keywords": [ 23 | "sd-jwt", 24 | "sdjwt", 25 | "sd-jwt-vc" 26 | ], 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 33 | }, 34 | "author": "Lukas.J.Han ", 35 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "publishConfig": { 41 | "access": "public" 42 | }, 43 | "tsup": { 44 | "entry": [ 45 | "./src/index.ts" 46 | ], 47 | "sourceMap": true, 48 | "splitting": false, 49 | "clean": true, 50 | "dts": true, 51 | "format": [ 52 | "cjs", 53 | "esm" 54 | ] 55 | }, 56 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 57 | } 58 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type'; 2 | -------------------------------------------------------------------------------- /packages/types/src/test/type.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { 3 | SD_SEPARATOR, 4 | SD_LIST_KEY, 5 | SD_DIGEST, 6 | SD_DECOY, 7 | KB_JWT_TYP, 8 | type DisclosureFrame, 9 | type PresentationFrame, 10 | } from '../index'; 11 | 12 | const claims = { 13 | firstname: 'John', 14 | lastname: 'Doe', 15 | ssn: '123-45-6789', 16 | id: '1234', 17 | data: { 18 | firstname: 'John', 19 | lastname: 'Doe', 20 | ssn: '123-45-6789', 21 | list: [{ r: 'd' }, 'b', 'c'], 22 | list2: ['1', '2', '3'], 23 | list3: ['1', null, 2], 24 | }, 25 | data2: { 26 | hi: 'bye', 27 | }, 28 | }; 29 | 30 | describe('Variable tests', () => { 31 | test('SD_SEPARATOR', () => { 32 | expect(SD_SEPARATOR).toBe('~'); 33 | }); 34 | 35 | test('SD_LIST_KEY', () => { 36 | expect(SD_LIST_KEY).toBe('...'); 37 | }); 38 | 39 | test('SD_DIGEST', () => { 40 | expect(SD_DIGEST).toBe('_sd'); 41 | }); 42 | 43 | test('SD_DECOY', () => { 44 | expect(SD_DECOY).toBe('_sd_decoy'); 45 | }); 46 | 47 | test('KB_JWT_TYP', () => { 48 | expect(KB_JWT_TYP).toBe('kb+jwt'); 49 | }); 50 | 51 | test('DisclosureFrameType test', () => { 52 | const disclosureFrame: DisclosureFrame = { 53 | _sd: ['data', 'firstname', 'data2'], 54 | data: { 55 | _sd: ['list', 'ssn'], 56 | _sd_decoy: 2, 57 | list: { 58 | _sd: [0, 2], 59 | 0: { 60 | _sd: ['r'], 61 | }, 62 | }, 63 | }, 64 | }; 65 | expect(disclosureFrame).toBeDefined(); 66 | }); 67 | 68 | test('PresentationFrameType test', () => { 69 | const presentationFrame: PresentationFrame = { 70 | firstname: true, 71 | data: { 72 | firstname: true, 73 | list: { 74 | 1: true, 75 | 0: { 76 | r: true, 77 | }, 78 | }, 79 | list2: { 80 | 1: true, 81 | }, 82 | list3: true, 83 | }, 84 | data2: true, 85 | }; 86 | expect(presentationFrame).toBeDefined(); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/types/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.10.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 7 | 8 | **Note:** Version bump only for package @sd-jwt/utils 9 | 10 | 11 | 12 | 13 | 14 | ## [0.9.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 15 | 16 | **Note:** Version bump only for package @sd-jwt/utils 17 | 18 | 19 | 20 | 21 | 22 | ## [0.9.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 23 | 24 | **Note:** Version bump only for package @sd-jwt/utils 25 | 26 | 27 | 28 | 29 | 30 | # [0.9.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 31 | 32 | **Note:** Version bump only for package @sd-jwt/utils 33 | 34 | 35 | 36 | 37 | 38 | # [0.8.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 39 | 40 | **Note:** Version bump only for package @sd-jwt/utils 41 | 42 | 43 | 44 | 45 | 46 | ## [0.7.2](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 47 | 48 | **Note:** Version bump only for package @sd-jwt/utils 49 | 50 | 51 | 52 | 53 | 54 | ## [0.7.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 55 | 56 | **Note:** Version bump only for package @sd-jwt/utils 57 | 58 | 59 | 60 | 61 | 62 | # [0.7.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 63 | 64 | **Note:** Version bump only for package @sd-jwt/utils 65 | 66 | 67 | 68 | 69 | 70 | ## [0.6.1](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * base64url convention ([#179](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/179)) ([f8db275](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/f8db275690dab88000a039838680a3478b3b61ec)) 76 | 77 | 78 | 79 | 80 | 81 | # [0.6.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 82 | 83 | **Note:** Version bump only for package @sd-jwt/utils 84 | 85 | 86 | 87 | 88 | 89 | # [0.5.0](https://github.com/openwallet-foundation-labs/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 90 | 91 | 92 | ### Features 93 | 94 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 95 | 96 | 97 | 98 | 99 | 100 | # 0.4.0 (2024-03-08) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * add publish config ([#93](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/93)) ([2e4c5c1](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/2e4c5c176dc88e58e49d06783b7658d8ad872313)) 106 | * convert any usage into or typed version ([#80](https://github.com/openwallet-foundation-labs/sd-jwt-js/issues/80)) ([de4df54](https://github.com/openwallet-foundation-labs/sd-jwt-js/commit/de4df54f2a0a77fdbf97e10abac555a98e70c6e0)) 107 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation-labs/sd-jwt-js.svg) 2 | ![NPM](https://img.shields.io/npm/v/%40sd-jwt%2Futils) 3 | ![Release](https://img.shields.io/github/v/release/openwallet-foundation-labs/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation-labs/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT Utils 9 | 10 | ### About 11 | 12 | Utility functions for SD JWT 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation-labs/sd-jwt-js). 15 | 16 | ### Installation 17 | 18 | To install this project, run the following command: 19 | 20 | ```bash 21 | # using npm 22 | npm install @sd-jwt/utils 23 | 24 | # using yarn 25 | yarn add @sd-jwt/utils 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/utils 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | - @sd-jwt/types 40 | - "js-base64": "^3.7.6" 41 | - pure js base64 implementation 42 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/utils", 3 | "version": "0.10.0", 4 | "description": "sd-jwt draft 7 implementation in typescript", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "rm -rf **/dist && tsup", 16 | "lint": "biome lint ./src", 17 | "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", 18 | "test:node": "vitest run ./src/test/*.spec.ts", 19 | "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", 20 | "test:cov": "vitest run --coverage" 21 | }, 22 | "keywords": [ 23 | "sd-jwt", 24 | "sdjwt", 25 | "sd-jwt-vc" 26 | ], 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" 33 | }, 34 | "author": "Lukas.J.Han ", 35 | "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "devDependencies": { 41 | "@sd-jwt/crypto-nodejs": "workspace:*" 42 | }, 43 | "dependencies": { 44 | "@sd-jwt/types": "workspace:*", 45 | "js-base64": "^3.7.6" 46 | }, 47 | "publishConfig": { 48 | "access": "public" 49 | }, 50 | "tsup": { 51 | "entry": [ 52 | "./src/index.ts" 53 | ], 54 | "sourceMap": true, 55 | "splitting": false, 56 | "clean": true, 57 | "dts": true, 58 | "format": [ 59 | "cjs", 60 | "esm" 61 | ] 62 | }, 63 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 64 | } 65 | -------------------------------------------------------------------------------- /packages/utils/src/base64url.ts: -------------------------------------------------------------------------------- 1 | import { Base64 } from 'js-base64'; 2 | 3 | export const base64urlEncode = Base64.encodeURI; 4 | 5 | export const base64urlDecode = Base64.decode; 6 | 7 | export const uint8ArrayToBase64Url = (input: Uint8Array): string => 8 | Base64.fromUint8Array(input, true); 9 | -------------------------------------------------------------------------------- /packages/utils/src/disclosure.ts: -------------------------------------------------------------------------------- 1 | import { 2 | uint8ArrayToBase64Url, 3 | base64urlDecode, 4 | base64urlEncode, 5 | } from './base64url'; 6 | import { SDJWTException } from './error'; 7 | import type { 8 | HasherAndAlg, 9 | DisclosureData, 10 | HasherAndAlgSync, 11 | } from '@sd-jwt/types'; 12 | 13 | export class Disclosure { 14 | public salt: string; 15 | public key?: string; 16 | public value: T; 17 | public _digest: string | undefined; 18 | private _encoded: string | undefined; 19 | 20 | public constructor( 21 | data: DisclosureData, 22 | _meta?: { digest: string; encoded: string }, 23 | ) { 24 | // If the meta is provided, then we assume that the data is already encoded and digested 25 | this._digest = _meta?.digest; 26 | this._encoded = _meta?.encoded; 27 | 28 | if (data.length === 2) { 29 | this.salt = data[0]; 30 | this.value = data[1]; 31 | return; 32 | } 33 | if (data.length === 3) { 34 | this.salt = data[0]; 35 | this.key = data[1] as string; 36 | this.value = data[2]; 37 | return; 38 | } 39 | throw new SDJWTException('Invalid disclosure data'); 40 | } 41 | 42 | // We need to digest of the original encoded data. 43 | // After decode process, we use JSON.stringify to encode the data. 44 | // This can be different from the original encoded data. 45 | public static async fromEncode(s: string, hash: HasherAndAlg) { 46 | const { hasher, alg } = hash; 47 | const digest = await hasher(s, alg); 48 | const digestStr = uint8ArrayToBase64Url(digest); 49 | const item = JSON.parse(base64urlDecode(s)) as DisclosureData; 50 | return Disclosure.fromArray(item, { digest: digestStr, encoded: s }); 51 | } 52 | 53 | public static fromEncodeSync(s: string, hash: HasherAndAlgSync) { 54 | const { hasher, alg } = hash; 55 | const digest = hasher(s, alg); 56 | const digestStr = uint8ArrayToBase64Url(digest); 57 | const item = JSON.parse(base64urlDecode(s)) as DisclosureData; 58 | return Disclosure.fromArray(item, { digest: digestStr, encoded: s }); 59 | } 60 | 61 | public static fromArray( 62 | item: DisclosureData, 63 | _meta?: { digest: string; encoded: string }, 64 | ) { 65 | return new Disclosure(item, _meta); 66 | } 67 | 68 | public encode() { 69 | if (!this._encoded) { 70 | // we use JSON.stringify to encode the data 71 | // It's the most reliable and universal way to encode JSON object 72 | this._encoded = base64urlEncode(JSON.stringify(this.decode())); 73 | } 74 | return this._encoded; 75 | } 76 | 77 | public decode(): DisclosureData { 78 | return this.key 79 | ? [this.salt, this.key, this.value] 80 | : [this.salt, this.value]; 81 | } 82 | 83 | public async digest(hash: HasherAndAlg): Promise { 84 | const { hasher, alg } = hash; 85 | if (!this._digest) { 86 | const hash = await hasher(this.encode(), alg); 87 | this._digest = uint8ArrayToBase64Url(hash); 88 | } 89 | 90 | return this._digest; 91 | } 92 | 93 | public digestSync(hash: HasherAndAlgSync): string { 94 | const { hasher, alg } = hash; 95 | if (!this._digest) { 96 | const hash = hasher(this.encode(), alg); 97 | this._digest = uint8ArrayToBase64Url(hash); 98 | } 99 | 100 | return this._digest; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /packages/utils/src/error.ts: -------------------------------------------------------------------------------- 1 | export class SDJWTException extends Error { 2 | public details?: unknown; 3 | 4 | constructor(message: string, details?: unknown) { 5 | super(message); 6 | Object.setPrototypeOf(this, SDJWTException.prototype); 7 | this.name = 'SDJWTException'; 8 | this.details = details; 9 | } 10 | 11 | getFullMessage(): string { 12 | return `${this.name}: ${this.message} ${ 13 | this.details ? `- ${JSON.stringify(this.details)}` : '' 14 | }`; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base64url'; 2 | export * from './error'; 3 | export * from './disclosure'; 4 | -------------------------------------------------------------------------------- /packages/utils/src/test/base64url.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { 3 | base64urlDecode, 4 | base64urlEncode, 5 | uint8ArrayToBase64Url, 6 | } from '../base64url'; 7 | 8 | describe('Base64url', () => { 9 | const raw = 'abcdefghijklmnopqrstuvwxyz'; 10 | const encoded = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo'; 11 | test('Encode', () => { 12 | expect(base64urlEncode(raw)).toStrictEqual(encoded); 13 | }); 14 | test('Decode', () => { 15 | expect(base64urlDecode(encoded)).toStrictEqual(raw); 16 | }); 17 | test('Encode and decode', () => { 18 | const str = 'hello world'; 19 | expect(base64urlDecode(base64urlEncode(str))).toStrictEqual(str); 20 | }); 21 | test('Uint8Array', () => { 22 | const str = 'hello world'; 23 | const uint8 = new TextEncoder().encode(str); 24 | expect(uint8ArrayToBase64Url(uint8)).toStrictEqual(base64urlEncode(str)); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/utils/src/test/error.spec.ts: -------------------------------------------------------------------------------- 1 | import { SDJWTException } from '../error'; 2 | import { describe, expect, test } from 'vitest'; 3 | 4 | describe('Error tests', () => { 5 | test('Detail', () => { 6 | try { 7 | throw new SDJWTException('msg', { info: 'details' }); 8 | } catch (e: unknown) { 9 | const exception = e as SDJWTException; 10 | expect(exception.getFullMessage()).toEqual( 11 | 'SDJWTException: msg - {"info":"details"}', 12 | ); 13 | } 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | -------------------------------------------------------------------------------- /vitest.shared.js: -------------------------------------------------------------------------------- 1 | import { defineProject, mergeConfig } from 'vitest/config'; 2 | export const browserConfig = defineProject({ 3 | test: { 4 | globals: true, 5 | environment: 'jsdom', 6 | }, 7 | }); 8 | 9 | export const nodeConfig = defineProject({ 10 | test: { 11 | globals: true, 12 | environment: 'node', 13 | }, 14 | }); 15 | 16 | export const allEnvs = mergeConfig(browserConfig, nodeConfig); 17 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | export default ['packages/*/vitest.config.mts']; 2 | --------------------------------------------------------------------------------