├── CNAME ├── .husky └── pre-commit ├── packages ├── hash │ ├── src │ │ ├── index.ts │ │ └── sha.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── decode │ ├── src │ │ └── index.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── types │ ├── src │ │ ├── index.ts │ │ └── test │ │ │ └── type.spec.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── node-crypto │ ├── src │ │ ├── index.ts │ │ └── test │ │ │ └── crypto.spec.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── present │ ├── src │ │ └── index.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── browser-crypto │ ├── src │ │ ├── index.ts │ │ └── test │ │ │ └── crypto.spec.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── CHANGELOG.md ├── utils │ ├── src │ │ ├── index.ts │ │ ├── base64url.ts │ │ ├── error.ts │ │ ├── test │ │ │ ├── error.spec.ts │ │ │ └── base64url.spec.ts │ │ └── disclosure.ts │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── core │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── src │ │ ├── test │ │ │ ├── pass.spec.ts │ │ │ └── decoy.spec.ts │ │ ├── decoy.ts │ │ ├── kbjwt.ts │ │ ├── flattenJSON.ts │ │ └── generalJSON.ts │ ├── test │ │ ├── array_in_sd.json │ │ ├── array_none_disclosed.json │ │ ├── array_of_nulls.json │ │ ├── array_of_scalars.json │ │ ├── array_full_sd.json │ │ ├── array_nested_in_plain.json │ │ ├── array_data_types.json │ │ ├── no_sd.json │ │ ├── array_recursive_sd.json │ │ ├── complex.json │ │ ├── header_mod.json │ │ ├── key_binding.json │ │ ├── json_serialization.json │ │ ├── array_recursive_sd_some_disclosed.json │ │ ├── array_of_objects.json │ │ ├── object_data_types.json │ │ └── recursions.json │ ├── README.md │ └── package.json ├── sd-jwt-vc │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── src │ │ ├── sd-jwt-vc-vct.ts │ │ ├── index.ts │ │ ├── sd-jwt-vc-status-reference.ts │ │ ├── verification-result.ts │ │ ├── sd-jwt-vc-config.ts │ │ └── sd-jwt-vc-payload.ts │ ├── test │ │ ├── array_in_sd.json │ │ ├── array_none_disclosed.json │ │ ├── array_of_nulls.json │ │ ├── array_of_scalars.json │ │ ├── array_full_sd.json │ │ ├── array_nested_in_plain.json │ │ ├── array_data_types.json │ │ ├── no_sd.json │ │ ├── array_recursive_sd.json │ │ ├── complex.json │ │ ├── header_mod.json │ │ ├── key_binding.json │ │ ├── json_serialization.json │ │ ├── array_recursive_sd_some_disclosed.json │ │ ├── array_of_objects.json │ │ ├── object_data_types.json │ │ └── recursions.json │ ├── package.json │ └── README.md └── jwt-status-list │ ├── vitest.config.mts │ ├── tsconfig.json │ ├── src │ ├── index.ts │ ├── status-list-exception.ts │ ├── types.ts │ ├── status-list-jwt.ts │ └── test │ │ └── status-list-jwt.spec.ts │ ├── package.json │ ├── README.md │ └── CHANGELOG.md ├── .gitignore ├── pnpm-workspace.yaml ├── vitest.workspace.ts ├── .prettierrc ├── images └── diagram.png ├── docs ├── imgs │ └── concept.png ├── 0.x │ ├── claims.md │ ├── validate.md │ ├── README.md │ ├── encode-decode.md │ ├── issue.md │ ├── verify.md │ ├── present.md │ ├── keybinding.md │ ├── sdjwt-instance.md │ └── disclosureframe.md └── README.md ├── examples ├── decode-example │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── decode.ts ├── present-example │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── present.ts ├── sd-jwt-example │ ├── tsconfig.json │ ├── utils.ts │ ├── package.json │ ├── decoy.ts │ ├── README.md │ ├── custom_header.ts │ ├── kb.ts │ ├── flattenJSON.ts │ ├── sdjwtobject.ts │ ├── generalJSON.ts │ ├── basic.ts │ ├── decode.ts │ ├── custom.ts │ └── all.ts ├── sd-jwt-vc-example │ ├── tsconfig.json │ ├── utils.ts │ ├── package.json │ ├── README.md │ ├── decoy.ts │ ├── custom_header.ts │ ├── kb.ts │ ├── sdjwtobject.ts │ ├── basic.ts │ ├── decode.ts │ ├── custom.ts │ └── all.ts └── README.md ├── CODEOWNERS ├── lerna.json ├── tsconfig.test.json ├── vitest.shared.js ├── .github ├── dependabot.yml ├── workflows │ └── static.yml └── settings.yml ├── SECURITY.md ├── biome.json ├── MAINTAINERS.md ├── package.json └── CONTRIBUTING.md /CNAME: -------------------------------------------------------------------------------- 1 | sdjwt.js.org -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run format 2 | -------------------------------------------------------------------------------- /packages/hash/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sha'; 2 | -------------------------------------------------------------------------------- /packages/decode/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decode'; 2 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type'; 2 | -------------------------------------------------------------------------------- /packages/node-crypto/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crypto'; 2 | -------------------------------------------------------------------------------- /packages/present/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './present'; 2 | -------------------------------------------------------------------------------- /packages/browser-crypto/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crypto'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | .vscode 5 | coverage 6 | .npmrc -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | export default ['packages/*/vitest.config.mts']; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwallet-foundation/sd-jwt-js/HEAD/images/diagram.png -------------------------------------------------------------------------------- /docs/imgs/concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openwallet-foundation/sd-jwt-js/HEAD/docs/imgs/concept.png -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base64url'; 2 | export * from './disclosure'; 3 | export * from './error'; 4 | -------------------------------------------------------------------------------- /packages/core/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/hash/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/types/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/utils/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /packages/decode/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /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/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { allEnvs } from '../../vitest.shared'; 3 | 4 | export default allEnvs; 5 | -------------------------------------------------------------------------------- /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/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { nodeConfig } from '../../vitest.shared'; 3 | 4 | export default nodeConfig; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/browser-crypto/vitest.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | import { browserConfig } from '../../vitest.shared'; 3 | 4 | export default browserConfig; 5 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/decode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/hash/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/present/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/node-crypto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/browser-crypto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/jwt-status-list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/decode-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/decode" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /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/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/core" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "references": [ 4 | { 5 | "path": "../../packages/core" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/jwt-status-list/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './status-list'; 2 | export * from './status-list-exception'; 3 | export * from './status-list-jwt'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/core/src/test/pass.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | 3 | describe('Should always pass', () => { 4 | test('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | export * from './sd-jwt-vc-type-metadata-format'; 6 | export * from './sd-jwt-vc-vct'; 7 | export * from './verification-result'; 8 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 10 | "sd_array": ["32", "23"] 11 | }, 12 | "requiredClaimKeys": ["sd_array"] 13 | } 14 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 10 | "sd_array": ["32", "23"] 11 | }, 12 | "requiredClaimKeys": ["sd_array"] 13 | } 14 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "version": "0.17.1", 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 | } -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "rootDir": ".", 6 | // For tests we rely on vitest, so we configure it flexibly 7 | "module": "preserve", 8 | "target": "es2022", 9 | "moduleResolution": "bundler" 10 | }, 11 | "include": ["**/*.spec.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /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 | "presentedClaims": {}, 16 | "requiredClaimKeys": [] 17 | } 18 | -------------------------------------------------------------------------------- /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 | "presentedClaims": {}, 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 | "presentedClaims": { 12 | "null_values": [null, null] 13 | }, 14 | "requiredClaimKeys": ["null_values.0", "null_values.1"] 15 | } 16 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 12 | "null_values": [null, null] 13 | }, 14 | "requiredClaimKeys": ["null_values.0", "null_values.1"] 15 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/sd-jwt-example/utils.ts: -------------------------------------------------------------------------------- 1 | import { digest, ES256, 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 | -------------------------------------------------------------------------------- /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 | 10 | export const base64UrlToUint8Array = (input: string): Uint8Array => 11 | Base64.toUint8Array(input); 12 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/utils.ts: -------------------------------------------------------------------------------- 1 | import { digest, ES256, 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 16 | "nationalities": ["CA", "DE"] 17 | }, 18 | "requiredClaimKeys": ["nationalities.0", "nationalities.1"] 19 | } 20 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 16 | "nationalities": ["CA", "DE"] 17 | }, 18 | "requiredClaimKeys": ["nationalities.0", "nationalities.1"] 19 | } 20 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 16 | "is_over": { 17 | "18": false 18 | } 19 | }, 20 | "requiredClaimKeys": ["is_over.18"] 21 | } 22 | -------------------------------------------------------------------------------- /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 | "dependencies": { 14 | "@sd-jwt/crypto-nodejs": "workspace:*", 15 | "@sd-jwt/decode": "workspace:*", 16 | "@sd-jwt/types": "workspace:*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 16 | "is_over": { 17 | "18": false 18 | } 19 | }, 20 | "requiredClaimKeys": ["is_over.18"] 21 | } 22 | -------------------------------------------------------------------------------- /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/test/error.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { SDJWTException } from '../error'; 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 | -------------------------------------------------------------------------------- /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/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 | "dependencies": { 14 | "@sd-jwt/crypto-nodejs": "workspace:*", 15 | "@sd-jwt/decode": "workspace:*", 16 | "@sd-jwt/present": "workspace:*", 17 | "@sd-jwt/types": "workspace:*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/src/verification-result.ts: -------------------------------------------------------------------------------- 1 | import type { kbHeader, kbPayload } 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/jwt-status-list/src/status-list-exception.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * SLException is a custom error class for Status List related exceptions. 3 | */ 4 | export class SLException extends Error { 5 | public details?: unknown; 6 | 7 | constructor(message: string, details?: unknown) { 8 | super(message); 9 | Object.setPrototypeOf(this, SLException.prototype); 10 | this.name = 'SLException'; 11 | this.details = details; 12 | } 13 | 14 | getFullMessage(): string { 15 | return `${this.name}: ${this.message} ${ 16 | this.details ? `- ${JSON.stringify(this.details)}` : '' 17 | }`; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 26 | "nested_array": [["foo"], ["qux"]] 27 | }, 28 | "requiredClaimKeys": ["nested_array.0.0", "nested_array.1.0"] 29 | } 30 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 26 | "nested_array": [["foo"], ["qux"]] 27 | }, 28 | "requiredClaimKeys": ["nested_array.0.0", "nested_array.1.0"] 29 | } 30 | -------------------------------------------------------------------------------- /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/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 | "presentedClaims": { 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 | -------------------------------------------------------------------------------- /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 | "dependencies": { 21 | "@sd-jwt/crypto-nodejs": "workspace:*", 22 | "@sd-jwt/sd-jwt-vc": "workspace:*", 23 | "@sd-jwt/types": "workspace:*" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/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 | "presentedClaims": { 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/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 | "presentedClaims": { 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.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 | "presentedClaims": { 31 | "array_with_recursive_sd": ["boring", []], 32 | "test2": [] 33 | }, 34 | "requiredClaimKeys": ["array_with_recursive_sd.0", "test2"] 35 | } 36 | -------------------------------------------------------------------------------- /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 | "dependencies": { 23 | "@sd-jwt/core": "workspace:*", 24 | "@sd-jwt/crypto-nodejs": "workspace:*", 25 | "@sd-jwt/types": "workspace:*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/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 | "presentedClaims": { 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 | -------------------------------------------------------------------------------- /docs/0.x/verify.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | const verified = await sdjwt.verify( 3 | encodedSdjwt, 4 | { 5 | requiredClaims: ['id', 'ssn'], // required claims to verify 6 | keyBindingNonce: 'secure_none' 7 | } 8 | ); 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - encodedSdjwt: encoded SD JWT [string] 14 | - requiredClaimKeys: required JSON properties to verify [Array] (optional) 15 | - requireKeyBindings: required verify Key Binding JWT [boolean] (optional) 16 | 17 | ```ts 18 | { 19 | data: { 20 | arr: ['value']; 21 | } 22 | } 23 | 24 | // The JSON Path of value 'value' is 'data.arr.0' 25 | ``` 26 | 27 | ## Return 28 | 29 | - object 30 | - header: header of SD JWT [object] 31 | - payload: payload of SD JWT [object] 32 | - kb: keybinding JWT [object] (optional) 33 | - header: header of keybinding JWT [object] 34 | - payload: payload of keybinding JWT [object] 35 | 36 | if verify failed, throw exception. 37 | 38 | ## Keys 39 | 40 | You can check all JSON Path by `keys` method 41 | 42 | ```ts 43 | const keys = await sdjwt.keys(encodedSdjwt); 44 | // return all JSON Path keys in claims [string[]] 45 | ``` 46 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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 | "presentedClaims": { 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/types/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | None 40 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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 | "presentedClaims": { 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 | "presentedClaims": { 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/utils/src/test/base64url.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { 3 | base64UrlToUint8Array, 4 | base64urlDecode, 5 | base64urlEncode, 6 | uint8ArrayToBase64Url, 7 | } from '../base64url'; 8 | 9 | describe('Base64url', () => { 10 | const raw = 'abcdefghijklmnopqrstuvwxyz'; 11 | const encoded = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo'; 12 | test('Encode', () => { 13 | expect(base64urlEncode(raw)).toStrictEqual(encoded); 14 | }); 15 | test('Decode', () => { 16 | expect(base64urlDecode(encoded)).toStrictEqual(raw); 17 | }); 18 | test('Encode and decode', () => { 19 | const str = 'hello world'; 20 | expect(base64urlDecode(base64urlEncode(str))).toStrictEqual(str); 21 | }); 22 | test('Uint8Array', () => { 23 | const str = 'hello world'; 24 | const uint8 = new TextEncoder().encode(str); 25 | expect(uint8ArrayToBase64Url(uint8)).toStrictEqual(base64urlEncode(str)); 26 | }); 27 | 28 | test('Base64Url to Uint8Array', () => { 29 | const str = 'hello world'; 30 | const uint8 = new TextEncoder().encode(str); 31 | expect(base64UrlToUint8Array(base64urlEncode(str))).toStrictEqual(uint8); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/decode/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | - @sd-jwt/types 40 | - @sd-jwt/utils 41 | -------------------------------------------------------------------------------- /packages/core/src/test/decoy.spec.ts: -------------------------------------------------------------------------------- 1 | import { digest, generateSalt } from '@sd-jwt/crypto-nodejs'; 2 | import { base64urlEncode } from '@sd-jwt/utils'; 3 | import { describe, expect, test } from 'vitest'; 4 | import { createDecoy } from '../decoy'; 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/node-crypto/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | None 40 | -------------------------------------------------------------------------------- /packages/present/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | - @sd-jwt/decode 40 | - @sd-jwt/types 41 | - @sd-jwt/utils 42 | -------------------------------------------------------------------------------- /packages/browser-crypto/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/examples) 36 | 37 | ### Dependencies 38 | 39 | None 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/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/hash/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/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/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/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 | "presentedClaims": { 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 | -------------------------------------------------------------------------------- /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: Math.floor(Date.now() / 1000), 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /examples/decode-example/decode.ts: -------------------------------------------------------------------------------- 1 | import { digest } from '@sd-jwt/crypto-nodejs'; 2 | import { decodeSdJwt, getClaims } from '@sd-jwt/decode'; 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 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "useIgnoreFile": true 7 | }, 8 | "formatter": { 9 | "enabled": true, 10 | "formatWithErrors": false, 11 | "indentStyle": "space", 12 | "indentWidth": 2, 13 | "lineEnding": "lf", 14 | "lineWidth": 80 15 | }, 16 | "json": { 17 | "formatter": { 18 | "enabled": true 19 | }, 20 | "parser": { 21 | "allowComments": true 22 | } 23 | }, 24 | "javascript": { 25 | "formatter": { 26 | "quoteStyle": "single" 27 | }, 28 | "parser": { 29 | "unsafeParameterDecoratorsEnabled": false 30 | } 31 | }, 32 | "linter": { 33 | "enabled": true, 34 | "rules": { 35 | "recommended": true, 36 | "correctness": { 37 | "noUnusedVariables": "error" 38 | }, 39 | "suspicious": { 40 | "noExplicitAny": "warn" 41 | }, 42 | "style": { 43 | "useNamingConvention": "off" 44 | } 45 | } 46 | }, 47 | "files": { 48 | "includes": ["**"] 49 | }, 50 | "overrides": [ 51 | { 52 | "includes": [ 53 | "**/dist/**", 54 | "**/coverage/**", 55 | "package.json", 56 | "lerna.json" 57 | ], 58 | "linter": { 59 | "enabled": false 60 | }, 61 | "formatter": { 62 | "enabled": false 63 | } 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /packages/browser-crypto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/crypto-browser", 3 | "version": "0.17.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 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 28 | }, 29 | "author": "Lukas.J.Han ", 30 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 31 | "bugs": { 32 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 33 | }, 34 | "license": "Apache-2.0", 35 | "publishConfig": { 36 | "access": "public" 37 | }, 38 | "tsup": { 39 | "entry": [ 40 | "./src/index.ts" 41 | ], 42 | "sourceMap": true, 43 | "splitting": false, 44 | "clean": true, 45 | "dts": true, 46 | "format": [ 47 | "cjs", 48 | "esm" 49 | ] 50 | }, 51 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 52 | } 53 | -------------------------------------------------------------------------------- /docs/0.x/keybinding.md: -------------------------------------------------------------------------------- 1 | You can make Key Binding JWT and verify it. 2 | 3 | ## Prerequisites 4 | 5 | Prepare signer and verifier for key binding. 6 | 7 | ```ts 8 | // Create SDJwt instance for use 9 | const sdjwt = new SDJwtInstance({ 10 | hasher: digest, 11 | saltGenerator: generateSalt, 12 | kbSigner: signer, // signer for key binding 13 | kbSignAlg: ES256.alg, // algorithm for key binding 14 | kbVerifier: verifier, // verifier for key binding 15 | }); 16 | ``` 17 | 18 | ## Issue 19 | 20 | Assume that you have SD-JWT from issuer like this. 21 | 22 | ```ts 23 | const claims = { 24 | firstname: 'John', 25 | lastname: 'Doe', 26 | ssn: '123-45-6789', 27 | id: '1234', 28 | }; 29 | const disclosureFrame: DisclosureFrame = { 30 | _sd: ['firstname', 'id'], 31 | }; 32 | const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame); 33 | ``` 34 | 35 | ## Key Binding 36 | 37 | ```ts 38 | const kbPayload = { 39 | iat: Math.floor(Date.now() / 1000), 40 | aud: 'https://example.com', 41 | nonce: '1234', 42 | custom: 'data', 43 | }; 44 | const presentedSdJwt = await sdjwt.present( 45 | encodedSdjwt, 46 | { id: true }, 47 | { 48 | kb: { 49 | payload: kbPayload, 50 | }, 51 | }, 52 | ); 53 | ``` 54 | 55 | ## Verify 56 | 57 | ```ts 58 | const verified = await sdjwt.verify(presentedSdJwt, { 59 | requiredClaimKeys: ['id', 'ssn'], 60 | keyBindingNonce: '1234' 61 | }); 62 | console.log(verified.kb); // key binding header and payload is in kb object 63 | ``` 64 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/node-crypto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/crypto-nodejs", 3 | "version": "0.17.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": ">=20" 28 | }, 29 | "repository": { 30 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 31 | }, 32 | "author": "Lukas.J.Han ", 33 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 34 | "bugs": { 35 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 36 | }, 37 | "license": "Apache-2.0", 38 | "publishConfig": { 39 | "access": "public" 40 | }, 41 | "tsup": { 42 | "entry": [ 43 | "./src/index.ts" 44 | ], 45 | "sourceMap": true, 46 | "splitting": false, 47 | "clean": true, 48 | "dts": true, 49 | "format": [ 50 | "cjs", 51 | "esm" 52 | ] 53 | }, 54 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 55 | } 56 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/src/sd-jwt-vc-config.ts: -------------------------------------------------------------------------------- 1 | import type { SDJWTConfig, Verifier } 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 | // a function that verifies the status of the JWT. If not provided, the library will assume that the status is valid if it is 0. 18 | statusVerifier?: Verifier; 19 | // if set to true, it will load the metadata format based on the vct value. If not provided, it will default to false. 20 | loadTypeMetadataFormat?: boolean; 21 | // timeout value in milliseconds when to abort the fetch request. If not provided, it will default to 10000. 22 | timeout?: number; 23 | }; 24 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/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 | "presentedClaims": { 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 | -------------------------------------------------------------------------------- /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: ['ssn', '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, { 52 | requiredClaimKeys: ['id'], 53 | keyBindingNonce: '1234', 54 | }); 55 | console.log(verified); 56 | })(); 57 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/types", 3 | "version": "0.17.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": ">=20" 29 | }, 30 | "repository": { 31 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation/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 | -------------------------------------------------------------------------------- /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: Math.floor(Date.now() / 1000), 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/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: Math.floor(Date.now() / 1000), 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, { 60 | requiredClaimKeys: ['firstname', 'id'], 61 | keyBindingNonce: '1234', 62 | }); 63 | console.log(verified); 64 | })(); 65 | -------------------------------------------------------------------------------- /packages/decode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/decode", 3 | "version": "0.17.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": ">=20" 28 | }, 29 | "repository": { 30 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 31 | }, 32 | "author": "Lukas.J.Han ", 33 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 34 | "bugs": { 35 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 36 | }, 37 | "license": "Apache-2.0", 38 | "devDependencies": { 39 | "@sd-jwt/crypto-nodejs": "workspace:*" 40 | }, 41 | "dependencies": { 42 | "@sd-jwt/types": "workspace:*", 43 | "@sd-jwt/utils": "workspace:*" 44 | }, 45 | "publishConfig": { 46 | "access": "public" 47 | }, 48 | "tsup": { 49 | "entry": [ 50 | "./src/index.ts" 51 | ], 52 | "sourceMap": true, 53 | "splitting": false, 54 | "clean": true, 55 | "dts": true, 56 | "format": [ 57 | "cjs", 58 | "esm" 59 | ] 60 | }, 61 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 62 | } 63 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/utils", 3 | "version": "0.17.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": ">=20" 29 | }, 30 | "repository": { 31 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation/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 | "js-base64": "^3.7.8" 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/hash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/hash", 3 | "version": "0.17.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": ">=20" 29 | }, 30 | "repository": { 31 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 37 | }, 38 | "license": "Apache-2.0", 39 | "devDependencies": { 40 | "@sd-jwt/crypto-nodejs": "workspace:*" 41 | }, 42 | "dependencies": { 43 | "@noble/hashes": "^2.0.1", 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 | -------------------------------------------------------------------------------- /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, 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 | 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, { 56 | requiredClaimKeys: ['firstname', 'id'], 57 | keyBindingNonce: '1234', 58 | }); 59 | console.log(verified); 60 | })(); 61 | -------------------------------------------------------------------------------- /packages/present/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/present", 3 | "version": "0.17.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": ">=20" 29 | }, 30 | "repository": { 31 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 37 | }, 38 | "license": "Apache-2.0", 39 | "devDependencies": { 40 | "@sd-jwt/crypto-nodejs": "workspace:*" 41 | }, 42 | "dependencies": { 43 | "@sd-jwt/decode": "workspace:*", 44 | "@sd-jwt/types": "workspace:*", 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/core", 3 | "version": "0.17.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": ">=20" 29 | }, 30 | "repository": { 31 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 32 | }, 33 | "author": "Lukas.J.Han ", 34 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 37 | }, 38 | "license": "Apache-2.0", 39 | "devDependencies": { 40 | "@sd-jwt/crypto-nodejs": "workspace:*" 41 | }, 42 | "dependencies": { 43 | "@sd-jwt/decode": "workspace:*", 44 | "@sd-jwt/present": "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 | -------------------------------------------------------------------------------- /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 check .", 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 | "types:check:tests": "tsc --project tsconfig.test.json" 16 | }, 17 | "keywords": [ 18 | "sd-jwt", 19 | "sdjwt", 20 | "sd-jwt-vc" 21 | ], 22 | "engines": { 23 | "node": ">=20", 24 | "pnpm": ">=9" 25 | }, 26 | "repository": { 27 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 28 | }, 29 | "author": "Lukas.J.Han ", 30 | "homepage": "https://sdjwt.js.org", 31 | "bugs": { 32 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 33 | }, 34 | "license": "Apache-2.0", 35 | "devDependencies": { 36 | "@biomejs/biome": "^2.3.8", 37 | "@types/node": "^24.10.1", 38 | "@vitest/coverage-v8": "^4.0.14", 39 | "husky": "^9.1.7", 40 | "jose": "^6.1.2", 41 | "jsdom": "^27.2.0", 42 | "lerna": "^9.0.3", 43 | "ts-node": "^10.9.2", 44 | "tsup": "^8.5.1", 45 | "typescript": "^5.9.3", 46 | "vite": "^7.2.4", 47 | "vitest": "^4.0.14" 48 | }, 49 | "packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a" 50 | } 51 | -------------------------------------------------------------------------------- /packages/jwt-status-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/jwt-status-list", 3 | "version": "0.17.1", 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": ">=20" 29 | }, 30 | "repository": { 31 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 32 | }, 33 | "author": "Mirko Mollik ", 34 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 35 | "bugs": { 36 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 37 | }, 38 | "license": "Apache-2.0", 39 | "devDependencies": { 40 | "@types/pako": "^2.0.4", 41 | "jose": "^6.1.2" 42 | }, 43 | "dependencies": { 44 | "@sd-jwt/types": "workspace:*", 45 | "@sd-jwt/utils": "workspace:*", 46 | "pako": "^2.1.0" 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 | -------------------------------------------------------------------------------- /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[, verifierConfig]) 62 | - config(newConfig) 63 | - encode(sdJwt) 64 | - decode(encodedSDJwt) 65 | - keys(encodedSDJwt) 66 | - presentableKeys(encodedSDJwt) 67 | - getClaims(encodedSDJwt) 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sd-jwt/sd-jwt-vc", 3 | "version": "0.17.1", 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": ">=20" 30 | }, 31 | "repository": { 32 | "url": "https://github.com/openwallet-foundation/sd-jwt-js" 33 | }, 34 | "author": "Lukas.J.Han ", 35 | "homepage": "https://github.com/openwallet-foundation/sd-jwt-js/wiki", 36 | "bugs": { 37 | "url": "https://github.com/openwallet-foundation/sd-jwt-js/issues" 38 | }, 39 | "license": "Apache-2.0", 40 | "dependencies": { 41 | "@sd-jwt/core": "workspace:*", 42 | "@sd-jwt/jwt-status-list": "workspace:*", 43 | "@sd-jwt/utils": "workspace:*" 44 | }, 45 | "devDependencies": { 46 | "@sd-jwt/crypto-nodejs": "workspace:*", 47 | "@sd-jwt/types": "workspace:*", 48 | "jose": "^6.1.2", 49 | "msw": "^2.12.3" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | }, 54 | "tsup": { 55 | "entry": [ 56 | "./src/index.ts" 57 | ], 58 | "sourceMap": true, 59 | "splitting": false, 60 | "clean": true, 61 | "dts": true, 62 | "format": [ 63 | "cjs", 64 | "esm" 65 | ] 66 | }, 67 | "gitHead": "ded40e4551bde7ae93083181bf26bd1b38bbfcfb" 68 | } 69 | -------------------------------------------------------------------------------- /packages/node-crypto/src/test/crypto.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { digest, ES256, ES384, ES512, generateSalt } 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 | for (const algObj of [ES256, ES384, ES512]) { 38 | (nodeVersionMajor < 20 ? test.skip : test)(algObj.alg, async () => { 39 | const { privateKey, publicKey } = await algObj.generateKeyPair(); 40 | expect(privateKey).toBeDefined(); 41 | expect(publicKey).toBeDefined(); 42 | expect(typeof privateKey).toBe('object'); 43 | expect(typeof publicKey).toBe('object'); 44 | 45 | const data = 46 | '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.'; 47 | const signer = await algObj.getSigner(privateKey); 48 | const signature = await signer(data); 49 | expect(signature).toBeDefined(); 50 | expect(typeof signature).toBe('string'); 51 | 52 | const verifier = await algObj.getVerifier(publicKey); 53 | const result = await verifier(data, signature); 54 | expect(result).toBe(true); 55 | }); 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /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 | // OPTIONAL. The Issuer of the Verifiable Credential. The value is a case-sensitive string containing a StringOrURI value. 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/types/src/test/type.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { 3 | type DisclosureFrame, 4 | KB_JWT_TYP, 5 | type PresentationFrame, 6 | SD_DECOY, 7 | SD_DIGEST, 8 | SD_LIST_KEY, 9 | SD_SEPARATOR, 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 | -------------------------------------------------------------------------------- /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-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-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, ES256, generateSalt } 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, { 78 | requiredClaimKeys: ['firstname', 'id'], 79 | keyBindingNonce: '1234', 80 | }); 81 | console.log(verified); 82 | 83 | const generalVerified = await generalJSONSdJwt.verify(generalJSON); 84 | console.log(generalVerified); 85 | })(); 86 | -------------------------------------------------------------------------------- /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, { 57 | requiredClaimKeys: requiredClaims, 58 | }); 59 | console.log(verified); 60 | })(); 61 | -------------------------------------------------------------------------------- /packages/core/src/kbjwt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type JwtPayload, 3 | KB_JWT_TYP, 4 | type KbVerifier, 5 | type kbHeader, 6 | type kbPayload, 7 | } from '@sd-jwt/types'; 8 | import { SDJWTException } from '@sd-jwt/utils'; 9 | import { Jwt } from './jwt'; 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: { 18 | verifier: KbVerifier; 19 | payload: JwtPayload; 20 | nonce: string; 21 | }) { 22 | if (!this.header || !this.payload || !this.signature) { 23 | throw new SDJWTException('Verify Error: Invalid JWT'); 24 | } 25 | 26 | if ( 27 | !this.header.alg || 28 | this.header.alg === 'none' || 29 | !this.header.typ || 30 | this.header.typ !== KB_JWT_TYP || 31 | !this.payload.iat || 32 | !this.payload.aud || 33 | !this.payload.nonce || 34 | // this is for backward compatibility with version 06 35 | !( 36 | this.payload.sd_hash || 37 | (this.payload as Record | undefined)?._sd_hash 38 | ) 39 | ) { 40 | throw new SDJWTException('Invalid Key Binding Jwt'); 41 | } 42 | 43 | const data = this.getUnsignedToken(); 44 | const verified = await values.verifier( 45 | data, 46 | this.signature, 47 | values.payload, 48 | ); 49 | if (!verified) { 50 | throw new SDJWTException('Verify Error: Invalid JWT Signature'); 51 | } 52 | if (this.payload.nonce !== values.nonce) { 53 | throw new SDJWTException('Verify Error: Invalid Nonce'); 54 | } 55 | 56 | return { payload: this.payload, header: this.header }; 57 | } 58 | 59 | // This function is for creating KBJwt object for verify properly 60 | public static fromKBEncode< 61 | Header extends kbHeader = kbHeader, 62 | Payload extends kbPayload = kbPayload, 63 | >(encodedJwt: string): KBJwt { 64 | const { header, payload, signature } = Jwt.decodeJWT( 65 | encodedJwt, 66 | ); 67 | 68 | const jwt = new KBJwt({ 69 | header, 70 | payload, 71 | signature, 72 | encoded: encodedJwt, 73 | }); 74 | 75 | return jwt; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /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 | "presentedClaims": { 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/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 | "presentedClaims": { 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/jwt-status-list/src/status-list-jwt.ts: -------------------------------------------------------------------------------- 1 | import type { JwtPayload } from '@sd-jwt/types'; 2 | import { base64urlDecode } from '@sd-jwt/utils'; 3 | import { StatusList } from './status-list'; 4 | import { SLException } from './status-list-exception'; 5 | import type { 6 | JWTwithStatusListPayload, 7 | StatusListEntry, 8 | StatusListJWTHeaderParameters, 9 | StatusListJWTPayload, 10 | } from './types'; 11 | 12 | /** 13 | * Decode a JWT and return the payload. 14 | * @param jwt JWT token in compact JWS serialization. 15 | * @returns Payload of the JWT. 16 | */ 17 | function decodeJwt(jwt: string): T { 18 | const parts = jwt.split('.'); 19 | return JSON.parse(base64urlDecode(parts[1])); 20 | } 21 | 22 | /** 23 | * Adds the status list to the payload and header of a JWT. 24 | * @param list 25 | * @param payload 26 | * @param header 27 | * @returns The header and payload with the status list added. 28 | */ 29 | export function createHeaderAndPayload( 30 | list: StatusList, 31 | payload: JwtPayload, 32 | header: StatusListJWTHeaderParameters, 33 | ) { 34 | // 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 35 | 36 | if (!payload.iss) { 37 | throw new SLException('iss field is required'); 38 | } 39 | if (!payload.sub) { 40 | throw new SLException('sub field is required'); 41 | } 42 | if (!payload.iat) { 43 | throw new SLException('iat field is required'); 44 | } 45 | //exp and tll are optional. We will not validate the business logic of the values like exp > iat etc. 46 | 47 | header.typ = 'statuslist+jwt'; 48 | payload.status_list = { 49 | bits: list.getBitsPerStatus(), 50 | lst: list.compressStatusList(), 51 | }; 52 | return { header, payload }; 53 | } 54 | 55 | /** 56 | * Get the status list from a JWT, but do not verify the signature. 57 | * @param jwt 58 | * @returns 59 | */ 60 | export function getListFromStatusListJWT(jwt: string): StatusList { 61 | const payload = decodeJwt(jwt); 62 | const statusList = payload.status_list; 63 | return StatusList.decompressStatusList(statusList.lst, statusList.bits); 64 | } 65 | 66 | /** 67 | * Get the status list entry from a JWT, but do not verify the signature. 68 | * @param jwt 69 | * @returns 70 | */ 71 | export function getStatusListFromJWT(jwt: string): StatusListEntry { 72 | const payload = decodeJwt(jwt); 73 | return payload.status.status_list; 74 | } 75 | -------------------------------------------------------------------------------- /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: Math.floor(Date.now() / 1000), 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, { 65 | requiredClaimKeys: requiredClaims, 66 | }); 67 | console.log(verified); 68 | })(); 69 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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/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. -------------------------------------------------------------------------------- /packages/browser-crypto/src/test/crypto.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { digest, ES256, ES384, ES512, generateSalt, getHasher } 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 | for (const algLib of [ 46 | { 47 | name: 'ES256', 48 | lib: ES256, 49 | }, 50 | { 51 | name: 'ES384', 52 | lib: ES384, 53 | }, 54 | { 55 | name: 'ES512', 56 | lib: ES512, 57 | }, 58 | ]) { 59 | (nodeVersionMajor < 20 ? test.skip : test)(`${algLib.name} alg`, () => { 60 | expect(algLib.lib.alg).toBe(algLib.name); 61 | }); 62 | 63 | (nodeVersionMajor < 20 ? test.skip : test)(algLib.name, async () => { 64 | const { privateKey, publicKey } = await algLib.lib.generateKeyPair(); 65 | expect(privateKey).toBeDefined(); 66 | expect(publicKey).toBeDefined(); 67 | expect(typeof privateKey).toBe('object'); 68 | expect(typeof publicKey).toBe('object'); 69 | 70 | const data = 71 | '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.'; 72 | const signature = await (await algLib.lib.getSigner(privateKey))(data); 73 | expect(signature).toBeDefined(); 74 | expect(typeof signature).toBe('string'); 75 | 76 | const result = await (await algLib.lib.getVerifier(publicKey))( 77 | data, 78 | signature, 79 | ); 80 | expect(result).toBe(true); 81 | }); 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /packages/hash/src/sha.ts: -------------------------------------------------------------------------------- 1 | import { 2 | sha256 as nobleSha256, 3 | sha384 as nobleSha384, 4 | sha512 as nobleSha512, 5 | } from '@noble/hashes/sha2.js'; 6 | import { SDJWTException } from '@sd-jwt/utils'; 7 | 8 | export const sha256 = (text: string | ArrayBuffer): Uint8Array => { 9 | const uint8Array = 10 | typeof text === 'string' ? toUTF8Array(text) : new Uint8Array(text); 11 | const hashBytes = nobleSha256(uint8Array); 12 | return hashBytes; 13 | }; 14 | 15 | export const sha384 = (text: string | ArrayBuffer): Uint8Array => { 16 | const uint8Array = 17 | typeof text === 'string' ? toUTF8Array(text) : new Uint8Array(text); 18 | const hashBytes = nobleSha384(uint8Array); 19 | return hashBytes; 20 | }; 21 | 22 | export const sha512 = (text: string | ArrayBuffer): Uint8Array => { 23 | const uint8Array = 24 | typeof text === 'string' ? toUTF8Array(text) : new Uint8Array(text); 25 | const hashBytes = nobleSha512(uint8Array); 26 | return hashBytes; 27 | }; 28 | 29 | type HasherAlgorithm = 'sha256' | 'sha384' | 'sha512' | (string & {}); 30 | 31 | export const hasher = ( 32 | data: string | ArrayBuffer, 33 | algorithm: HasherAlgorithm = 'sha256', 34 | ) => { 35 | const alg = toCryptoAlg(algorithm); 36 | 37 | switch (alg) { 38 | case 'sha256': 39 | return sha256(data); 40 | case 'sha384': 41 | return sha384(data); 42 | case 'sha512': 43 | return sha512(data); 44 | default: 45 | throw new SDJWTException(`Unsupported algorithm: ${algorithm}`); 46 | } 47 | }; 48 | 49 | const toCryptoAlg = (hashAlg: HasherAlgorithm): string => 50 | // To cover sha-256, sha256, SHA-256, SHA256 51 | hashAlg 52 | .replace('-', '') 53 | .toLowerCase(); 54 | 55 | function toUTF8Array(str: string) { 56 | const utf8: Array = []; 57 | for (let i = 0; i < str.length; i++) { 58 | let charcode = str.charCodeAt(i); 59 | if (charcode < 0x80) utf8.push(charcode); 60 | else if (charcode < 0x800) { 61 | utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f)); 62 | } else if (charcode < 0xd800 || charcode >= 0xe000) { 63 | utf8.push( 64 | 0xe0 | (charcode >> 12), 65 | 0x80 | ((charcode >> 6) & 0x3f), 66 | 0x80 | (charcode & 0x3f), 67 | ); 68 | } 69 | // surrogate pair 70 | else { 71 | i++; 72 | // UTF-16 encodes 0x10000-0x10FFFF by 73 | // subtracting 0x10000 and splitting the 74 | // 20 bits of 0x0-0xFFFFF into two halves 75 | charcode = 76 | 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); 77 | utf8.push( 78 | 0xf0 | (charcode >> 18), 79 | 0x80 | ((charcode >> 12) & 0x3f), 80 | 0x80 | ((charcode >> 6) & 0x3f), 81 | 0x80 | (charcode & 0x3f), 82 | ); 83 | } 84 | } 85 | return new Uint8Array(utf8); 86 | } 87 | -------------------------------------------------------------------------------- /packages/core/src/flattenJSON.ts: -------------------------------------------------------------------------------- 1 | import { splitSdJwt } from '@sd-jwt/decode'; 2 | import { SD_SEPARATOR } from '@sd-jwt/types'; 3 | import { SDJWTException } from '@sd-jwt/utils'; 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 | -------------------------------------------------------------------------------- /examples/present-example/present.ts: -------------------------------------------------------------------------------- 1 | import { digest } from '@sd-jwt/crypto-nodejs'; 2 | import { decodeSdJwt, getClaims } from '@sd-jwt/decode'; 3 | import { present, presentableKeys } from '@sd-jwt/present'; 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/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, { 84 | requiredClaimKeys: requiredClaims, 85 | }); 86 | console.log('verified:', verified); 87 | })(); 88 | -------------------------------------------------------------------------------- /packages/jwt-status-list/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## jwt-status-list 9 | 10 | An implementation of the [Token Status List](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/) for a JWT representation, not for CBOR. 11 | 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. 12 | 13 | ## Installation 14 | 15 | To install this project, run the following command: 16 | 17 | ```bash 18 | # using npm 19 | npm install @sd-jwt/jwt-status-list 20 | 21 | # using yarn 22 | yarn add @sd-jwt/jwt-status-list 23 | 24 | # using pnpm 25 | pnpm install @sd-jwt/jwt-status-list 26 | ``` 27 | 28 | Ensure you have Node.js installed as a prerequisite. 29 | 30 | ## Usage 31 | 32 | Creation of a JWT Status List: 33 | 34 | ```typescript 35 | // pass the list as an array and the amount of bits per entry. 36 | const list = new StatusList([1, 0, 1, 1, 1], 1); 37 | const iss = 'https://example.com'; 38 | const payload: JWTPayload = { 39 | iss, 40 | sub: `${iss}/statuslist/1`, 41 | iat: Math.floor(Date.now() / 1000), // issued at time in seconds 42 | ttl: 3000, // time to live in seconds, optional 43 | exp: Math.floor(Date.now() / 1000) + 3600, // expiration time in seconds, optional 44 | }; 45 | const header: JWTHeaderParameters = { alg: 'ES256' }; 46 | 47 | const jwt = createHeaderAndPayload(list, payload, header); 48 | 49 | // Sign the JWT with the private key, e.g. using the `jose` library 50 | const jwt = await new SignJWT(values.payload) 51 | .setProtectedHeader(values.header) 52 | .sign(privateKey); 53 | 54 | ``` 55 | 56 | Interaction with a JWT status list on low level: 57 | 58 | ```typescript 59 | //validation of the JWT is not provided by this library!!! 60 | 61 | // jwt that includes the status list reference 62 | const reference = getStatusListFromJWT(jwt); 63 | 64 | // download the status list 65 | const list = await fetch(reference.uri); 66 | 67 | //TODO: validate that the list jwt is signed by the issuer and is not expired!!! 68 | 69 | //extract the status list 70 | const statusList = getListFromStatusListJWT(list); 71 | 72 | //get the status of a specific entry 73 | const status = statusList.getStatus(reference.idx); 74 | ``` 75 | 76 | ### Integration into sd-jwt-vc 77 | 78 | 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. 79 | 80 | ### Caching the status list 81 | 82 | 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. 83 | 84 | ## Development 85 | 86 | Install the dependencies: 87 | 88 | ```bash 89 | pnpm install 90 | ``` 91 | 92 | Run the tests: 93 | 94 | ```bash 95 | pnpm test 96 | ``` 97 | -------------------------------------------------------------------------------- /packages/utils/src/disclosure.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DisclosureData, 3 | HasherAndAlg, 4 | HasherAndAlgSync, 5 | } from '@sd-jwt/types'; 6 | import { 7 | base64urlDecode, 8 | base64urlEncode, 9 | uint8ArrayToBase64Url, 10 | } from './base64url'; 11 | import { SDJWTException } from './error'; 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 | -------------------------------------------------------------------------------- /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: Math.floor(Date.now() / 1000), 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, { 92 | requiredClaimKeys: requiredClaims, 93 | }); 94 | console.log('verified:', verified); 95 | })(); 96 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, 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 | 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, { 107 | requiredClaimKeys: requiredClaims, 108 | }); 109 | console.log('verified:', verified); 110 | })(); 111 | -------------------------------------------------------------------------------- /examples/sd-jwt-vc-example/all.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 | 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( 57 | { 58 | iss: 'Issuer', 59 | iat: Math.floor(Date.now() / 1000), 60 | vct: 'ExampleCredentials', 61 | ...claims, 62 | }, 63 | disclosureFrame, 64 | ); 65 | console.log('encodedJwt:', credential); 66 | 67 | // Holder Receive the credential from the issuer and validate it 68 | // Return a result of header and payload 69 | const validated = await sdjwt.validate(credential); 70 | console.log('validated:', validated); 71 | 72 | // You can decode the SD JWT to get the payload and the disclosures 73 | const sdJwtToken = await sdjwt.decode(credential); 74 | 75 | // You can get the keys of the claims from the decoded SD JWT 76 | const keys = await sdJwtToken.keys(digest); 77 | console.log({ keys }); 78 | 79 | // You can get the claims from the decoded SD JWT 80 | const payloads = await sdJwtToken.getClaims(digest); 81 | 82 | // You can get the presentable keys from the decoded SD JWT 83 | const presentableKeys = await sdJwtToken.presentableKeys(digest); 84 | 85 | console.log({ 86 | payloads: JSON.stringify(payloads, null, 2), 87 | disclosures: JSON.stringify(sdJwtToken.disclosures, null, 2), 88 | claim: JSON.stringify(sdJwtToken.jwt?.payload, null, 2), 89 | presentableKeys, 90 | }); 91 | 92 | console.log( 93 | '================================================================', 94 | ); 95 | 96 | // Holder Define the presentation frame to specify which claims should be presented 97 | // The list of presented claims must be a subset of the disclosed claims 98 | // the presentation frame is determined by the verifier or the protocol that was agreed upon between the holder and the verifier 99 | const presentationFrame = { firstname: true, id: true }; 100 | 101 | // Create a presentation using the issued credential and the presentation frame 102 | // return a Encoded SD JWT. Holder send the presentation to the verifier 103 | const presentation = await sdjwt.present( 104 | credential, 105 | presentationFrame, 106 | ); 107 | console.log('presentedSDJwt:', presentation); 108 | 109 | // Verifier Define the required claims that need to be verified in the presentation 110 | const requiredClaims = ['firstname', 'id', 'data.ssn']; 111 | 112 | // Verify the presentation using the public key and the required claims 113 | // return a boolean result 114 | const verified = await sdjwt.verify(credential, { 115 | requiredClaimKeys: requiredClaims, 116 | }); 117 | console.log('verified:', verified); 118 | })(); 119 | -------------------------------------------------------------------------------- /packages/sd-jwt-vc/README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/openwallet-foundation/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/sd-jwt-js) 4 | ![Stars](https://img.shields.io/github/stars/openwallet-foundation/sd-jwt-js) 5 | 6 | # SD-JWT Implementation in JavaScript (TypeScript) 7 | 8 | ## SD-JWT-VC 9 | 10 | ### About 11 | 12 | SD-JWT-VC format based on the core functions 13 | 14 | Check the detail description in our github [repo](https://github.com/openwallet-foundation/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/sd-jwt-vc 23 | 24 | # using yarn 25 | yarn add @sd-jwt/sd-jwt-vc 26 | 27 | # using pnpm 28 | pnpm install @sd-jwt/sd-jwt-vc 29 | ``` 30 | 31 | Ensure you have Node.js installed as a prerequisite. 32 | 33 | ### Usage 34 | 35 | Here's a basic example of how to use this library: 36 | 37 | ```jsx 38 | import { DisclosureFrame } from '@sd-jwt/sd-jwt-vc'; 39 | 40 | // identifier of the issuer 41 | const iss = 'University'; 42 | 43 | // issuance time 44 | const iat = Math.floor(Date.now() / 1000); // current time in seconds 45 | 46 | //unique identifier of the schema 47 | const vct = 'University-Degree'; 48 | 49 | // Issuer defines the claims object with the user's information 50 | const claims = { 51 | firstname: 'John', 52 | lastname: 'Doe', 53 | ssn: '123-45-6789', 54 | id: '1234', 55 | }; 56 | 57 | // Issuer defines the disclosure frame to specify which claims can be disclosed/undisclosed 58 | const disclosureFrame: DisclosureFrame = { 59 | _sd: ['firstname', 'lastname', 'ssn'], 60 | }; 61 | 62 | // Issuer issues a signed JWT credential with the specified claims and disclosure frame 63 | // returns an encoded JWT 64 | const credential = await sdjwt.issue( 65 | { iss, iat, vct, ...claims }, 66 | disclosureFrame, 67 | ); 68 | 69 | // Holder may validate the credential from the issuer 70 | const valid = await sdjwt.validate(credential); 71 | 72 | // Holder defines the presentation frame to specify which claims should be presented 73 | // The list of presented claims must be a subset of the disclosed claims 74 | const presentationFrame = { firstname: true, ssn: true }; 75 | 76 | // Holder creates a presentation using the issued credential and the presentation frame 77 | // returns an encoded SD JWT. 78 | const presentation = await sdjwt.present(credential, presentationFrame); 79 | 80 | // Verifier can verify the presentation using the Issuer's public key 81 | const verified = await sdjwt.verify(presentation); 82 | ``` 83 | 84 | Check out more details in our [documentation](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/docs) or [examples](https://github.com/openwallet-foundation/sd-jwt-js/tree/main/examples) 85 | 86 | ### Revocation 87 | 88 | To add revocation capabilities, you can use the `@sd-jwt/jwt-status-list` library to create a JWT Status List and include it in the SD-JWT-VC. 89 | 90 | You can pass a dedicated `statusVerifier` function in the configuration to verify the signature of the payload of the JWT of the statuslist. If no function is provided, it will fallback to the verifier that is also used for the sd-jwt-vc. 91 | 92 | ### Type Metadata 93 | 94 | By setting the `loadTypeMetadataFormat` to `true` like this: 95 | 96 | ```typescript 97 | const sdjwt = new SDJwtVcInstance({ 98 | signer, 99 | signAlg: 'EdDSA', 100 | verifier, 101 | hasher: digest, 102 | hashAlg: 'sha-256', 103 | saltGenerator: generateSalt, 104 | loadTypeMetadataFormat: true, 105 | }); 106 | ``` 107 | 108 | The library will load load the type metadata format based on the `vct` value according to the [SD-JWT-VC specification](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-08.html#name-sd-jwt-vc-type-metadata) and validate this schema. 109 | 110 | Since at this point the display is not yet implemented, the library will only validate the schema and return the type metadata format. In the future the values of the type metadata can be fetched via a function call. 111 | 112 | ### Dependencies 113 | 114 | - @sd-jwt/core 115 | - @sd-jwt/types 116 | - @sd-jwt/utils 117 | - @sd-jwt/jwt-status-list 118 | -------------------------------------------------------------------------------- /packages/jwt-status-list/src/test/status-list-jwt.spec.ts: -------------------------------------------------------------------------------- 1 | import { generateKeyPairSync, type KeyObject } from 'node:crypto'; 2 | import type { JwtPayload } from '@sd-jwt/types'; 3 | import { jwtVerify, SignJWT } from 'jose'; 4 | import { beforeAll, describe, expect, it } from 'vitest'; 5 | import { StatusList } from '../status-list'; 6 | import { 7 | createHeaderAndPayload, 8 | getListFromStatusListJWT, 9 | getStatusListFromJWT, 10 | } from '../status-list-jwt'; 11 | import type { 12 | JWTwithStatusListPayload, 13 | StatusListJWTHeaderParameters, 14 | } from '../types'; 15 | 16 | describe('JWTStatusList', () => { 17 | let publicKey: KeyObject; 18 | let privateKey: KeyObject; 19 | 20 | const header: StatusListJWTHeaderParameters = { 21 | alg: 'ES256', 22 | typ: 'statuslist+jwt', 23 | }; 24 | 25 | beforeAll(() => { 26 | // Generate a key pair for testing 27 | const keyPair = generateKeyPairSync('ec', { 28 | namedCurve: 'P-256', 29 | }); 30 | publicKey = keyPair.publicKey; 31 | privateKey = keyPair.privateKey; 32 | }); 33 | 34 | it('should create a JWT with a status list', async () => { 35 | const statusList = 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: Math.floor(Date.now() / 1000), 41 | }; 42 | 43 | const values = createHeaderAndPayload(statusList, payload, header); 44 | 45 | const jwt = await new SignJWT(values.payload) 46 | .setProtectedHeader(values.header) 47 | .sign(privateKey); 48 | // Verify the signed JWT with the public key 49 | const verified = await jwtVerify(jwt, publicKey); 50 | expect(verified.payload.status_list).toEqual({ 51 | bits: statusList.getBitsPerStatus(), 52 | lst: statusList.compressStatusList(), 53 | }); 54 | expect(verified.protectedHeader.typ).toBe('statuslist+jwt'); 55 | }); 56 | 57 | it('should get the status list from a JWT without verifying the signature', async () => { 58 | const list = [1, 0, 1, 0, 1]; 59 | const statusList = new StatusList(list, 1); 60 | const iss = 'https://example.com'; 61 | const payload: JwtPayload = { 62 | iss, 63 | sub: `${iss}/statuslist/1`, 64 | iat: Math.floor(Date.now() / 1000), 65 | }; 66 | 67 | const values = createHeaderAndPayload(statusList, payload, header); 68 | 69 | const jwt = await new SignJWT(values.payload) 70 | .setProtectedHeader(values.header) 71 | .sign(privateKey); 72 | 73 | const extractedList = getListFromStatusListJWT(jwt); 74 | for (let i = 0; i < list.length; i++) { 75 | expect(extractedList.getStatus(i)).toBe(list[i]); 76 | } 77 | }); 78 | 79 | it('should throw an error if the JWT is invalid', async () => { 80 | const list = [1, 0, 1, 0, 1]; 81 | const statusList = new StatusList(list, 2); 82 | const iss = 'https://example.com'; 83 | let payload: JwtPayload = { 84 | sub: `${iss}/statuslist/1`, 85 | iat: Math.floor(Date.now() / 1000), 86 | }; 87 | expect(() => { 88 | createHeaderAndPayload(statusList, payload as JwtPayload, header); 89 | }).toThrow('iss field is required'); 90 | 91 | payload = { 92 | iss, 93 | iat: Math.floor(Date.now() / 1000), 94 | }; 95 | expect(() => createHeaderAndPayload(statusList, payload, header)).toThrow( 96 | 'sub field is required', 97 | ); 98 | 99 | payload = { 100 | iss, 101 | sub: `${iss}/statuslist/1`, 102 | }; 103 | expect(() => createHeaderAndPayload(statusList, payload, header)).toThrow( 104 | 'iat field is required', 105 | ); 106 | }); 107 | 108 | it('should get the status entry from a JWT', async () => { 109 | const payload: JWTwithStatusListPayload = { 110 | iss: 'https://example.com', 111 | sub: 'https://example.com/status/1', 112 | iat: Math.floor(Date.now() / 1000), 113 | status: { 114 | status_list: { 115 | idx: 0, 116 | uri: 'https://example.com/status/1', 117 | }, 118 | }, 119 | }; 120 | const jwt = await new SignJWT(payload) 121 | .setProtectedHeader({ alg: 'ES256' }) 122 | .sign(privateKey); 123 | const reference = getStatusListFromJWT(jwt); 124 | expect(reference).toEqual(payload.status.status_list); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /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.17.1](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.17.0...v0.17.1) (2025-11-18) 7 | 8 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 9 | 10 | 11 | 12 | 13 | 14 | # [0.17.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.16.0...v0.17.0) (2025-10-23) 15 | 16 | 17 | ### Features 18 | 19 | * alter base64url to internal utils package for RN support ([#328](https://github.com/openwallet-foundation/sd-jwt-js/issues/328)) ([09708e7](https://github.com/openwallet-foundation/sd-jwt-js/commit/09708e7881c1e3e003444d7200af686eda4b8a55)) 20 | 21 | 22 | 23 | 24 | 25 | # [0.16.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.15.1...v0.16.0) (2025-10-07) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * change repo url from labs to other one ([#323](https://github.com/openwallet-foundation/sd-jwt-js/issues/323)) ([f68c847](https://github.com/openwallet-foundation/sd-jwt-js/commit/f68c8476c2f04bb9d53acd4059b59caf271df015)) 31 | * set correct url in package json ([#321](https://github.com/openwallet-foundation/sd-jwt-js/issues/321)) ([554152c](https://github.com/openwallet-foundation/sd-jwt-js/commit/554152cc819bbc3afb504b25f4a2018a92fb72f1)) 32 | 33 | 34 | ### Features 35 | 36 | * updates all dependencies to latest versions + biome updates ([#324](https://github.com/openwallet-foundation/sd-jwt-js/issues/324)) ([75d5780](https://github.com/openwallet-foundation/sd-jwt-js/commit/75d5780fb53c5e2c886537b283503fc6fb088a4a)) 37 | 38 | 39 | 40 | 41 | 42 | # [0.15.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.14.1...v0.15.0) (2025-08-20) 43 | 44 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 45 | 46 | 47 | 48 | 49 | 50 | # [0.14.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.13.0...v0.14.0) (2025-06-30) 51 | 52 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 53 | 54 | 55 | 56 | 57 | 58 | # [0.13.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.12.0...v0.13.0) (2025-06-25) 59 | 60 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 61 | 62 | 63 | 64 | 65 | 66 | # [0.12.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.11.0...v0.12.0) (2025-06-21) 67 | 68 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 69 | 70 | 71 | 72 | 73 | 74 | # [0.11.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.10.0...v0.11.0) (2025-06-17) 75 | 76 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 77 | 78 | 79 | 80 | 81 | 82 | # [0.10.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 83 | 84 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 85 | 86 | 87 | 88 | 89 | 90 | ## [0.9.2](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 91 | 92 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 93 | 94 | 95 | 96 | 97 | 98 | ## [0.9.1](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 99 | 100 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 101 | 102 | 103 | 104 | 105 | 106 | # [0.9.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 107 | 108 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 109 | 110 | 111 | 112 | 113 | 114 | # [0.8.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 115 | 116 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 117 | 118 | 119 | 120 | 121 | 122 | ## [0.7.2](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 123 | 124 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 125 | 126 | 127 | 128 | 129 | 130 | ## [0.7.1](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 131 | 132 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 133 | 134 | 135 | 136 | 137 | 138 | # [0.7.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 139 | 140 | **Note:** Version bump only for package @sd-jwt/jwt-status-list 141 | -------------------------------------------------------------------------------- /packages/core/src/generalJSON.ts: -------------------------------------------------------------------------------- 1 | import { splitSdJwt } from '@sd-jwt/decode'; 2 | import { SD_SEPARATOR, type Signer } from '@sd-jwt/types'; 3 | import { base64urlEncode, SDJWTException } from '@sd-jwt/utils'; 4 | 5 | export type GeneralJSONData = { 6 | payload: string; 7 | disclosures: Array; 8 | kb_jwt?: string; 9 | signatures: Array<{ 10 | protected: string; 11 | signature: string; 12 | kid?: string; 13 | }>; 14 | }; 15 | 16 | export type GeneralJSONSerialized = { 17 | payload: string; 18 | signatures: Array<{ 19 | header: { 20 | disclosures?: Array; 21 | kid?: string; 22 | kb_jwt?: string; 23 | }; 24 | protected: string; 25 | signature: string; 26 | }>; 27 | }; 28 | 29 | export class GeneralJSON { 30 | public payload: string; 31 | public disclosures: Array; 32 | public kb_jwt?: string; 33 | public signatures: Array<{ 34 | protected: string; 35 | signature: string; 36 | kid?: string; 37 | }>; 38 | 39 | constructor(data: GeneralJSONData) { 40 | this.payload = data.payload; 41 | this.disclosures = data.disclosures; 42 | this.kb_jwt = data.kb_jwt; 43 | this.signatures = data.signatures; 44 | } 45 | 46 | public static fromEncode(encodedSdJwt: string) { 47 | const { jwt, disclosures, kbJwt } = splitSdJwt(encodedSdJwt); 48 | 49 | const { 0: protectedHeader, 1: payload, 2: signature } = jwt.split('.'); 50 | if (!protectedHeader || !payload || !signature) { 51 | throw new SDJWTException('Invalid JWT'); 52 | } 53 | 54 | return new GeneralJSON({ 55 | payload, 56 | disclosures, 57 | kb_jwt: kbJwt, 58 | signatures: [ 59 | { 60 | protected: protectedHeader, 61 | signature, 62 | }, 63 | ], 64 | }); 65 | } 66 | 67 | public static fromSerialized(json: GeneralJSONSerialized) { 68 | if (!json.signatures[0]) { 69 | throw new SDJWTException('Invalid JSON'); 70 | } 71 | const disclosures = json.signatures[0].header?.disclosures ?? []; 72 | const kb_jwt = json.signatures[0].header?.kb_jwt; 73 | return new GeneralJSON({ 74 | payload: json.payload, 75 | disclosures, 76 | kb_jwt, 77 | signatures: json.signatures.map((s) => { 78 | return { 79 | protected: s.protected, 80 | signature: s.signature, 81 | kid: s.header?.kid, 82 | }; 83 | }), 84 | }); 85 | } 86 | 87 | public toJson() { 88 | return { 89 | payload: this.payload, 90 | signatures: this.signatures.map((s, i) => { 91 | if (i !== 0) { 92 | // If present, disclosures and kb_jwt, MUST be included in the first unprotected header and 93 | // MUST NOT be present in any following unprotected headers. 94 | return { 95 | header: { 96 | kid: s.kid, 97 | }, 98 | protected: s.protected, 99 | signature: s.signature, 100 | }; 101 | } 102 | return { 103 | header: { 104 | disclosures: this.disclosures, 105 | kid: s.kid, 106 | kb_jwt: this.kb_jwt, 107 | }, 108 | protected: s.protected, 109 | signature: s.signature, 110 | }; 111 | }), 112 | }; 113 | } 114 | 115 | public toEncoded(index: number) { 116 | if (index < 0 || index >= this.signatures.length) { 117 | throw new SDJWTException('Index out of bounds'); 118 | } 119 | const data: string[] = []; 120 | 121 | const { protected: protectedHeader, signature } = this.signatures[index]; 122 | const jwt = `${protectedHeader}.${this.payload}.${signature}`; 123 | data.push(jwt); 124 | 125 | if (this.disclosures && this.disclosures.length > 0) { 126 | const disclosures = this.disclosures.join(SD_SEPARATOR); 127 | data.push(disclosures); 128 | } 129 | 130 | const kb = this.kb_jwt ?? ''; 131 | data.push(kb); 132 | return data.join(SD_SEPARATOR); 133 | } 134 | 135 | public async addSignature( 136 | protectedHeader: Record, 137 | signer: Signer, 138 | kid?: string, 139 | ) { 140 | const header = base64urlEncode(JSON.stringify(protectedHeader)); 141 | const signature = await signer(`${header}.${this.payload}`); 142 | this.signatures.push({ 143 | protected: header, 144 | signature, 145 | kid, 146 | }); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /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.17.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.16.0...v0.17.0) (2025-10-23) 7 | 8 | **Note:** Version bump only for package @sd-jwt/crypto-browser 9 | 10 | 11 | 12 | 13 | 14 | # [0.16.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.15.1...v0.16.0) (2025-10-07) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * change repo url from labs to other one ([#323](https://github.com/openwallet-foundation/sd-jwt-js/issues/323)) ([f68c847](https://github.com/openwallet-foundation/sd-jwt-js/commit/f68c8476c2f04bb9d53acd4059b59caf271df015)) 20 | * set correct url in package json ([#321](https://github.com/openwallet-foundation/sd-jwt-js/issues/321)) ([554152c](https://github.com/openwallet-foundation/sd-jwt-js/commit/554152cc819bbc3afb504b25f4a2018a92fb72f1)) 21 | 22 | 23 | ### Features 24 | 25 | * updates all dependencies to latest versions + biome updates ([#324](https://github.com/openwallet-foundation/sd-jwt-js/issues/324)) ([75d5780](https://github.com/openwallet-foundation/sd-jwt-js/commit/75d5780fb53c5e2c886537b283503fc6fb088a4a)) 26 | 27 | 28 | 29 | 30 | 31 | # [0.15.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.14.1...v0.15.0) (2025-08-20) 32 | 33 | **Note:** Version bump only for package @sd-jwt/crypto-browser 34 | 35 | 36 | 37 | 38 | 39 | # [0.14.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.13.0...v0.14.0) (2025-06-30) 40 | 41 | **Note:** Version bump only for package @sd-jwt/crypto-browser 42 | 43 | 44 | 45 | 46 | 47 | # [0.13.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.12.0...v0.13.0) (2025-06-25) 48 | 49 | **Note:** Version bump only for package @sd-jwt/crypto-browser 50 | 51 | 52 | 53 | 54 | 55 | # [0.12.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.11.0...v0.12.0) (2025-06-21) 56 | 57 | **Note:** Version bump only for package @sd-jwt/crypto-browser 58 | 59 | 60 | 61 | 62 | 63 | # [0.11.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.10.0...v0.11.0) (2025-06-17) 64 | 65 | **Note:** Version bump only for package @sd-jwt/crypto-browser 66 | 67 | 68 | 69 | 70 | 71 | # [0.10.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.9.2...v0.10.0) (2025-03-24) 72 | 73 | **Note:** Version bump only for package @sd-jwt/crypto-browser 74 | 75 | 76 | 77 | 78 | 79 | ## [0.9.2](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.9.1...v0.9.2) (2025-01-29) 80 | 81 | **Note:** Version bump only for package @sd-jwt/crypto-browser 82 | 83 | 84 | 85 | 86 | 87 | ## [0.9.1](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.9.0...v0.9.1) (2024-12-13) 88 | 89 | **Note:** Version bump only for package @sd-jwt/crypto-browser 90 | 91 | 92 | 93 | 94 | 95 | # [0.9.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.8.0...v0.9.0) (2024-12-10) 96 | 97 | **Note:** Version bump only for package @sd-jwt/crypto-browser 98 | 99 | 100 | 101 | 102 | 103 | # [0.8.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.7.2...v0.8.0) (2024-11-26) 104 | 105 | **Note:** Version bump only for package @sd-jwt/crypto-browser 106 | 107 | 108 | 109 | 110 | 111 | ## [0.7.2](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.7.1...v0.7.2) (2024-07-19) 112 | 113 | **Note:** Version bump only for package @sd-jwt/crypto-browser 114 | 115 | 116 | 117 | 118 | 119 | ## [0.7.1](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.7.0...v0.7.1) (2024-05-21) 120 | 121 | **Note:** Version bump only for package @sd-jwt/crypto-browser 122 | 123 | 124 | 125 | 126 | 127 | # [0.7.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.6.1...v0.7.0) (2024-05-13) 128 | 129 | **Note:** Version bump only for package @sd-jwt/crypto-browser 130 | 131 | 132 | 133 | 134 | 135 | ## [0.6.1](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.6.0...v0.6.1) (2024-03-18) 136 | 137 | **Note:** Version bump only for package @sd-jwt/crypto-browser 138 | 139 | 140 | 141 | 142 | 143 | # [0.6.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.5.0...v0.6.0) (2024-03-12) 144 | 145 | **Note:** Version bump only for package @sd-jwt/crypto-browser 146 | 147 | 148 | 149 | 150 | 151 | # [0.5.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.4.0...v0.5.0) (2024-03-11) 152 | 153 | 154 | ### Features 155 | 156 | * make _digest value public in disclosure ([#151](https://github.com/openwallet-foundation/sd-jwt-js/issues/151)) ([7a3fbd7](https://github.com/openwallet-foundation/sd-jwt-js/commit/7a3fbd7db19b6501978340c972b171743d287285)) 157 | 158 | 159 | 160 | 161 | 162 | # 0.4.0 (2024-03-08) 163 | 164 | 165 | ### Features 166 | 167 | * es crypto features to nodejs and browser ([#106](https://github.com/openwallet-foundation/sd-jwt-js/issues/106)) ([3ba74e9](https://github.com/openwallet-foundation/sd-jwt-js/commit/3ba74e936dbc39698d47c6c8c1da956430e937f8)) 168 | --------------------------------------------------------------------------------