From e95cac526abd5aaca7f5e24a1a67b9c7e05731a6 Mon Sep 17 00:00:00 2001 From: "Lukas.J.Han" Date: Tue, 20 Feb 2024 17:43:30 +0900 Subject: [PATCH] Package Splitting works (#68) Signed-off-by: Lukas Signed-off-by: Lukas.J.Han --- .github/workflows/test-pr.yaml | 2 +- examples/README.md | 36 +- examples/core-example/README.md | 36 ++ examples/{ => core-example}/all.ts | 3 +- examples/{ => core-example}/basic.ts | 3 +- examples/{ => core-example}/custom.ts | 3 +- examples/{ => core-example}/custom_header.ts | 3 +- examples/{ => core-example}/decode.ts | 2 +- examples/{ => core-example}/decoy.ts | 3 +- examples/{ => core-example}/kb.ts | 3 +- examples/{ => core-example}/package.json | 7 +- examples/{ => core-example}/sdjwtobject.ts | 3 +- examples/core-example/tsconfig.json | 8 + examples/{ => core-example}/utils.ts | 4 +- examples/decode-example/README.md | 9 + examples/decode-example/decode.ts | 23 ++ examples/decode-example/package.json | 23 ++ examples/decode-example/tsconfig.json | 8 + examples/pnpm-lock.yaml | 146 ------- examples/present-example/README.md | 10 + examples/present-example/package.json | 24 ++ examples/present-example/present.ts | 60 +++ examples/present-example/tsconfig.json | 8 + examples/tsconfig.json | 109 ----- package.json | 7 +- packages/broswer-crypto/package.json | 66 +++ packages/broswer-crypto/src/crypto.ts | 28 ++ packages/broswer-crypto/src/index.ts | 1 + .../broswer-crypto/src/test/crypto.spec.ts | 7 + packages/broswer-crypto/tsconfig.json | 7 + .../broswer-crypto/vitest.config.mts | 0 packages/core/package.json | 25 +- packages/core/src/decoy.ts | 7 +- packages/core/src/index.ts | 15 +- packages/core/src/jwt.ts | 19 +- packages/core/src/kbjwt.ts | 6 +- packages/core/src/sdjwt.ts | 141 +------ packages/core/src/test/decoy.spec.ts | 4 +- packages/core/src/test/index.spec.ts | 5 +- packages/core/src/test/jwt.spec.ts | 4 +- packages/core/src/test/kbjwt.spec.ts | 4 +- packages/core/src/test/sdjwt.spec.ts | 9 +- packages/core/test/app-e2e.spec.ts | 4 +- packages/decode/package.json | 71 ++++ packages/decode/src/decode.ts | 256 ++++++++++++ packages/decode/src/index.ts | 1 + packages/decode/src/test/decode.spec.ts | 91 ++++ packages/decode/tsconfig.json | 7 + packages/decode/vitest.config.mts | 14 + packages/hash/package.json | 71 ++++ packages/hash/src/index.ts | 1 + packages/{core => hash}/src/sha256.ts | 14 +- .../{core => hash}/src/test/sha256.spec.ts | 25 +- packages/hash/tsconfig.json | 7 + packages/hash/vitest.config.mts | 14 + packages/node-crypto/package.json | 66 +++ .../src/crypto.ts} | 12 +- packages/node-crypto/src/index.ts | 1 + packages/node-crypto/src/test/crypto.spec.ts | 34 ++ packages/node-crypto/tsconfig.json | 7 + packages/node-crypto/vitest.config.mts | 14 + packages/present/package.json | 72 ++++ packages/present/src/index.ts | 1 + packages/present/src/present.ts | 71 ++++ packages/present/src/test/present.spec.ts | 41 ++ packages/present/tsconfig.json | 7 + packages/present/vitest.config.mts | 14 + packages/types/package.json | 69 ++++ packages/types/src/index.ts | 1 + packages/types/src/test/type.spec.ts | 35 ++ packages/{core => types}/src/type.ts | 42 +- packages/types/tsconfig.json | 7 + packages/types/vitest.config.mts | 14 + packages/utils/package.json | 71 ++++ packages/{core => utils}/src/base64url.ts | 0 packages/{core => utils}/src/disclosure.ts | 31 +- packages/{core => utils}/src/error.ts | 0 packages/utils/src/index.ts | 3 + .../src/test/base64url.spec.ts | 0 .../src/test/disclosure.spec.ts | 5 +- .../{core => utils}/src/test/error.spec.ts | 0 packages/utils/tsconfig.json | 7 + packages/utils/vitest.config.mts | 14 + pnpm-lock.yaml | 390 +++++++++++++++++- 84 files changed, 1976 insertions(+), 520 deletions(-) create mode 100644 examples/core-example/README.md rename examples/{ => core-example}/all.ts (96%) rename examples/{ => core-example}/basic.ts (94%) rename examples/{ => core-example}/custom.ts (96%) rename examples/{ => core-example}/custom_header.ts (91%) rename examples/{ => core-example}/decode.ts (97%) rename examples/{ => core-example}/decoy.ts (90%) rename examples/{ => core-example}/kb.ts (91%) rename examples/{ => core-example}/package.json (80%) rename examples/{ => core-example}/sdjwtobject.ts (94%) create mode 100644 examples/core-example/tsconfig.json rename examples/{ => core-example}/utils.ts (92%) create mode 100644 examples/decode-example/README.md create mode 100644 examples/decode-example/decode.ts create mode 100644 examples/decode-example/package.json create mode 100644 examples/decode-example/tsconfig.json delete mode 100644 examples/pnpm-lock.yaml create mode 100644 examples/present-example/README.md create mode 100644 examples/present-example/package.json create mode 100644 examples/present-example/present.ts create mode 100644 examples/present-example/tsconfig.json delete mode 100644 examples/tsconfig.json create mode 100644 packages/broswer-crypto/package.json create mode 100644 packages/broswer-crypto/src/crypto.ts create mode 100644 packages/broswer-crypto/src/index.ts create mode 100644 packages/broswer-crypto/src/test/crypto.spec.ts create mode 100644 packages/broswer-crypto/tsconfig.json rename vitest.config.mts => packages/broswer-crypto/vitest.config.mts (100%) create mode 100644 packages/decode/package.json create mode 100644 packages/decode/src/decode.ts create mode 100644 packages/decode/src/index.ts create mode 100644 packages/decode/src/test/decode.spec.ts create mode 100644 packages/decode/tsconfig.json create mode 100644 packages/decode/vitest.config.mts create mode 100644 packages/hash/package.json create mode 100644 packages/hash/src/index.ts rename packages/{core => hash}/src/sha256.ts (74%) rename packages/{core => hash}/src/test/sha256.spec.ts (80%) create mode 100644 packages/hash/tsconfig.json create mode 100644 packages/hash/vitest.config.mts create mode 100644 packages/node-crypto/package.json rename packages/{core/src/test/crypto.spec.ts => node-crypto/src/crypto.ts} (75%) create mode 100644 packages/node-crypto/src/index.ts create mode 100644 packages/node-crypto/src/test/crypto.spec.ts create mode 100644 packages/node-crypto/tsconfig.json create mode 100644 packages/node-crypto/vitest.config.mts create mode 100644 packages/present/package.json create mode 100644 packages/present/src/index.ts create mode 100644 packages/present/src/present.ts create mode 100644 packages/present/src/test/present.spec.ts create mode 100644 packages/present/tsconfig.json create mode 100644 packages/present/vitest.config.mts create mode 100644 packages/types/package.json create mode 100644 packages/types/src/index.ts create mode 100644 packages/types/src/test/type.spec.ts rename packages/{core => types}/src/type.ts (70%) create mode 100644 packages/types/tsconfig.json create mode 100644 packages/types/vitest.config.mts create mode 100644 packages/utils/package.json rename packages/{core => utils}/src/base64url.ts (100%) rename packages/{core => utils}/src/disclosure.ts (63%) rename packages/{core => utils}/src/error.ts (100%) create mode 100644 packages/utils/src/index.ts rename packages/{core => utils}/src/test/base64url.spec.ts (100%) rename packages/{core => utils}/src/test/disclosure.spec.ts (96%) rename packages/{core => utils}/src/test/error.spec.ts (100%) create mode 100644 packages/utils/tsconfig.json create mode 100644 packages/utils/vitest.config.mts diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index e2f0a9d..23dea36 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -17,7 +17,7 @@ jobs: - name: Install pnpm run: npm install -g pnpm - name: Install Dependencies - run: pnpm install + run: pnpm install && pnpm build && pnpm install - name: Run Tests(Browser) run: pnpm test:browser - name: Run Tests(Node) diff --git a/examples/README.md b/examples/README.md index 190bbf7..387e39b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,13 +2,7 @@ This directory contains examples of how to use the SD JWT(sd-jwt-js) library. -## How to run the examples - -```bash -pnpm install -``` - -## Run the example +## How to run the example ```bash pnpm run {example_file_name} @@ -19,28 +13,6 @@ pnpm run all ### Example lists -- basic: Example of basic usage(issue, validate, present, verify) of SD JWT -- all: Example of issue, present and verify the comprehensive data. -- custom: Example of using custom hasher and salt generator for SD JWT -- custom_header: Example of using custom header for SD JWT -- sdjwtobject: Example of using SD JWT Object -- decoy: Example of adding decoy digest in SD JWT -- kb: key binding example in SD JWT -- decode: Decoding example of a SD JWT sample - -### Variables In Examples - -- claims: the user's information -- disclosureFrame: specify which claims should be disclosed -- credential: Issued Encoded SD JWT. -- validated: result of SD JWT validation -- presentationFrame: specify which claims should be presented -- presentation: Presented Encoded SD JWT. -- requiredClaims: specify which claims should be verified -- verified: result of verification -- sdJwtToken: SD JWT Token Object -- SDJwtInstance: SD JWT Instance - -## More examples from tests - -You can find more examples from [tests](../test). +- core: Example of basic usage(issue, validate, present, verify) of SD JWT +- decode: Decoding example of a SD JWT (only use decode package) +- present: Example of presenting the SD JWT (only use present, decode package) diff --git a/examples/core-example/README.md b/examples/core-example/README.md new file mode 100644 index 0000000..bb9fb3c --- /dev/null +++ b/examples/core-example/README.md @@ -0,0 +1,36 @@ +# SD JWT Core Examples + +This directory contains example of basic usage(issue, validate, present, verify) of SD JWT + +## Run the example + +```bash +pnpm run {example_file_name} + +# example +pnpm run all +``` + +### Example lists + +- basic: Example of basic usage(issue, validate, present, verify) of SD JWT +- all: Example of issue, present and verify the comprehensive data. +- custom: Example of using custom hasher and salt generator for SD JWT +- custom_header: Example of using custom header for SD JWT +- sdjwtobject: Example of using SD JWT Object +- decoy: Example of adding decoy digest in SD JWT +- kb: key binding example in SD JWT +- decode: Decoding example of a SD JWT sample + +### Variables In Examples + +- claims: the user's information +- disclosureFrame: specify which claims should be disclosed +- credential: Issued Encoded SD JWT. +- validated: result of SD JWT validation +- presentationFrame: specify which claims should be presented +- presentation: Presented Encoded SD JWT. +- requiredClaims: specify which claims should be verified +- verified: result of verification +- sdJwtToken: SD JWT Token Object +- SDJwtInstance: SD JWT Instance diff --git a/examples/all.ts b/examples/core-example/all.ts similarity index 96% rename from examples/all.ts rename to examples/core-example/all.ts index f0e7207..107d636 100644 --- a/examples/all.ts +++ b/examples/core-example/all.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/basic.ts b/examples/core-example/basic.ts similarity index 94% rename from examples/basic.ts rename to examples/core-example/basic.ts index 6507b03..5d6b766 100644 --- a/examples/basic.ts +++ b/examples/core-example/basic.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/custom.ts b/examples/core-example/custom.ts similarity index 96% rename from examples/custom.ts rename to examples/core-example/custom.ts index b53989e..75a30e0 100644 --- a/examples/custom.ts +++ b/examples/core-example/custom.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/custom_header.ts b/examples/core-example/custom_header.ts similarity index 91% rename from examples/custom_header.ts rename to examples/core-example/custom_header.ts index 59c6c38..440d2bd 100644 --- a/examples/custom_header.ts +++ b/examples/core-example/custom_header.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/decode.ts b/examples/core-example/decode.ts similarity index 97% rename from examples/decode.ts rename to examples/core-example/decode.ts index f3bc9fa..d162510 100644 --- a/examples/decode.ts +++ b/examples/core-example/decode.ts @@ -1,4 +1,4 @@ -import { SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/decoy.ts b/examples/core-example/decoy.ts similarity index 90% rename from examples/decoy.ts rename to examples/core-example/decoy.ts index 07bfe4e..7242816 100644 --- a/examples/decoy.ts +++ b/examples/core-example/decoy.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/kb.ts b/examples/core-example/kb.ts similarity index 91% rename from examples/kb.ts rename to examples/core-example/kb.ts index f91448f..e6d40f0 100644 --- a/examples/kb.ts +++ b/examples/core-example/kb.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/package.json b/examples/core-example/package.json similarity index 80% rename from examples/package.json rename to examples/core-example/package.json index 617a7e9..8d401cb 100644 --- a/examples/package.json +++ b/examples/core-example/package.json @@ -1,10 +1,10 @@ { - "name": "sdjwt-examples", + "name": "sdjwt-core-examples", "version": "1.0.0", "description": "", "main": "index.js", + "private": true, "scripts": { - "prepare": "cd ../ && pnpm install && pnpm build", "basic": "ts-node basic.ts", "all": "ts-node all.ts", "sdjwtobject": "ts-node sdjwtobject.ts", @@ -23,6 +23,7 @@ "typescript": "^5.3.3" }, "dependencies": { - "@hopae/sd-jwt": "link:.." + "@hopae/sd-jwt-core": "workspace:*", + "@hopae/sd-jwt-type": "workspace:*" } } diff --git a/examples/sdjwtobject.ts b/examples/core-example/sdjwtobject.ts similarity index 94% rename from examples/sdjwtobject.ts rename to examples/core-example/sdjwtobject.ts index c09b71f..7c8c55c 100644 --- a/examples/sdjwtobject.ts +++ b/examples/core-example/sdjwtobject.ts @@ -1,4 +1,5 @@ -import { DisclosureFrame, SDJwtInstance } from '@hopae/sd-jwt'; +import { SDJwtInstance } from '@hopae/sd-jwt-core'; +import { DisclosureFrame } from '@hopae/sd-jwt-type'; import { createSignerVerifier, digest, generateSalt } from './utils'; (async () => { diff --git a/examples/core-example/tsconfig.json b/examples/core-example/tsconfig.json new file mode 100644 index 0000000..940ce53 --- /dev/null +++ b/examples/core-example/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "references": [ + { + "path": "../../packages/core" + } + ] +} diff --git a/examples/utils.ts b/examples/core-example/utils.ts similarity index 92% rename from examples/utils.ts rename to examples/core-example/utils.ts index 552d56a..1913a31 100644 --- a/examples/utils.ts +++ b/examples/core-example/utils.ts @@ -1,5 +1,5 @@ -import Crypto from 'node:crypto'; -import { Signer, Verifier } from '@hopae/sd-jwt'; +import Crypto from 'crypto'; +import { Signer, Verifier } from '@hopae/sd-jwt-type'; export const createSignerVerifier = () => { const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519'); diff --git a/examples/decode-example/README.md b/examples/decode-example/README.md new file mode 100644 index 0000000..0ec816d --- /dev/null +++ b/examples/decode-example/README.md @@ -0,0 +1,9 @@ +# SD JWT Core Examples + +This directory contains decoding example of a SD JWT (only use decode package) + +## Run the example + +```bash +pnpm run decode +``` diff --git a/examples/decode-example/decode.ts b/examples/decode-example/decode.ts new file mode 100644 index 0000000..d31bb79 --- /dev/null +++ b/examples/decode-example/decode.ts @@ -0,0 +1,23 @@ +import { decodeSdJwt, getClaims } from '@hopae/sd-jwt-decode'; +import { digest } from '@hopae/sd-jwt-node-crypto'; + +(async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; + const decodedSdJwt = await decodeSdJwt(sdjwt, digest); + console.log('The decoded SD JWT is:'); + console.log(JSON.stringify(decodedSdJwt, null, 2)); + console.log( + '================================================================', + ); + + // Get the claims from the SD JWT + const claims = await getClaims( + decodedSdJwt.jwt.payload, + decodedSdJwt.disclosures, + digest, + ); + + console.log('The claims are:'); + console.log(JSON.stringify(claims, null, 2)); +})(); diff --git a/examples/decode-example/package.json b/examples/decode-example/package.json new file mode 100644 index 0000000..0157880 --- /dev/null +++ b/examples/decode-example/package.json @@ -0,0 +1,23 @@ +{ + "name": "sdjwt-decode-examples", + "version": "1.0.0", + "description": "", + "main": "index.js", + "private": true, + "scripts": { + "decode": "ts-node decode.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/node": "^20.10.4", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "dependencies": { + "@hopae/sd-jwt-decode": "workspace:*", + "@hopae/sd-jwt-type": "workspace:*", + "@hopae/sd-jwt-node-crypto": "workspace:*" + } +} diff --git a/examples/decode-example/tsconfig.json b/examples/decode-example/tsconfig.json new file mode 100644 index 0000000..d60c01b --- /dev/null +++ b/examples/decode-example/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "references": [ + { + "path": "../../packages/decode" + } + ] +} diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml deleted file mode 100644 index 836657d..0000000 --- a/examples/pnpm-lock.yaml +++ /dev/null @@ -1,146 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - '@hopae/sd-jwt': - specifier: link:.. - version: link:.. - -devDependencies: - '@types/node': - specifier: ^20.10.4 - version: 20.10.4 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@20.10.4)(typescript@5.3.3) - typescript: - specifier: ^5.3.3 - version: 5.3.3 - -packages: - - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true - - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true - - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true - - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true - - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true - - /@types/node@20.10.4: - resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} - dependencies: - undici-types: 5.26.5 - dev: true - - /acorn-walk@8.3.1: - resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} - engines: {node: '>=0.4.0'} - dev: true - - /acorn@8.11.2: - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true - - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - - /ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3): - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.10.4 - acorn: 8.11.2 - acorn-walk: 8.3.1 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.3.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true - - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true - - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true diff --git a/examples/present-example/README.md b/examples/present-example/README.md new file mode 100644 index 0000000..7f72a53 --- /dev/null +++ b/examples/present-example/README.md @@ -0,0 +1,10 @@ +# SD JWT Core Examples + +This directory contains example of presenting the SD JWT (only use present, decode package) + +## Run the example + +```bash +pnpm run present + +``` diff --git a/examples/present-example/package.json b/examples/present-example/package.json new file mode 100644 index 0000000..445823d --- /dev/null +++ b/examples/present-example/package.json @@ -0,0 +1,24 @@ +{ + "name": "sdjwt-present-examples", + "version": "1.0.0", + "description": "", + "main": "index.js", + "private": true, + "scripts": { + "present": "ts-node present.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/node": "^20.10.4", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "dependencies": { + "@hopae/sd-jwt-present": "workspace:*", + "@hopae/sd-jwt-decode": "workspace:*", + "@hopae/sd-jwt-type": "workspace:*", + "@hopae/sd-jwt-node-crypto": "workspace:*" + } +} diff --git a/examples/present-example/present.ts b/examples/present-example/present.ts new file mode 100644 index 0000000..55b95df --- /dev/null +++ b/examples/present-example/present.ts @@ -0,0 +1,60 @@ +import { present, presentableKeys } from '@hopae/sd-jwt-present'; +import { decodeSdJwt, getClaims } from '@hopae/sd-jwt-decode'; +import { digest } from '@hopae/sd-jwt-node-crypto'; + +(async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; + const decodedSdJwt = await decodeSdJwt(sdjwt, digest); + console.log('The decoded Disclosures are:'); + console.log(JSON.stringify(decodedSdJwt.disclosures, null, 2)); + console.log( + '================================================================', + ); + + const claims = await getClaims( + decodedSdJwt.jwt.payload, + decodedSdJwt.disclosures, + digest, + ); + + console.log('The claims are:'); + console.log(JSON.stringify(claims, null, 2)); + + // You can get presentable keys from the decoded SD JWT + const keys = await presentableKeys( + decodedSdJwt.jwt.payload, + decodedSdJwt.disclosures, + digest, + ); + console.log('The presentable keys are:', keys); + + // You can present the SD JWT with the combination of presentable keys + const presentedSdJwt = await present( + sdjwt, + ['foo', 'arr.0', 'arr', 'test.zzz'], + digest, + ); + + console.log('The presented SD JWT is:', presentedSdJwt); + + console.log( + '================================================================', + ); + + // If you decoded the presented SD JWT, you can see the presented disclosures + // It only contains the disclosed keys you presented + const presentedDecodedSdJwt = await decodeSdJwt(presentedSdJwt, digest); + + console.log('The decoded Disclosures are:'); + console.log(JSON.stringify(presentedDecodedSdJwt.disclosures, null, 2)); + + const presentedClaims = await getClaims( + presentedDecodedSdJwt.jwt.payload, + presentedDecodedSdJwt.disclosures, + digest, + ); + + console.log('The presented claims are:'); + console.log(JSON.stringify(presentedClaims, null, 2)); +})(); diff --git a/examples/present-example/tsconfig.json b/examples/present-example/tsconfig.json new file mode 100644 index 0000000..c3727dc --- /dev/null +++ b/examples/present-example/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "references": [ + { + "path": "../../packages/present" + } + ] +} diff --git a/examples/tsconfig.json b/examples/tsconfig.json deleted file mode 100644 index e075f97..0000000 --- a/examples/tsconfig.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} diff --git a/package.json b/package.json index c2003c2..2a45cfd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,12 @@ "digital-wallet", "sd-jwt", "sdjwt", - "sd-jwt-vc" + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" ], "repository": { "type": "git", diff --git a/packages/broswer-crypto/package.json b/packages/broswer-crypto/package.json new file mode 100644 index 0000000..294b4be --- /dev/null +++ b/packages/broswer-crypto/package.json @@ -0,0 +1,66 @@ +{ + "name": "@hopae/sd-jwt-browser-crypto", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/broswer-crypto/src/crypto.ts b/packages/broswer-crypto/src/crypto.ts new file mode 100644 index 0000000..3d7eada --- /dev/null +++ b/packages/broswer-crypto/src/crypto.ts @@ -0,0 +1,28 @@ +export const generateSalt = (length: number): string => { + if (length <= 0) { + return ''; + } + + const array = new Uint8Array(length); + globalThis.crypto.getRandomValues(array); + + const salt = Array.from(array, (byte) => + byte.toString(16).padStart(2, '0'), + ).join(''); + + return salt; +}; + +export async function digest( + data: string, + algorithm: string = 'SHA-256', +): Promise { + const { subtle } = globalThis.crypto; + const ec = new TextEncoder(); + const digest = await subtle.digest(algorithm, ec.encode(data)); + return new Uint8Array(digest); +} + +export const getHasher = (algorithm: string = 'SHA-256') => { + return (data: string) => digest(data, algorithm); +}; diff --git a/packages/broswer-crypto/src/index.ts b/packages/broswer-crypto/src/index.ts new file mode 100644 index 0000000..f170247 --- /dev/null +++ b/packages/broswer-crypto/src/index.ts @@ -0,0 +1 @@ +export * from './crypto'; diff --git a/packages/broswer-crypto/src/test/crypto.spec.ts b/packages/broswer-crypto/src/test/crypto.spec.ts new file mode 100644 index 0000000..87fcaea --- /dev/null +++ b/packages/broswer-crypto/src/test/crypto.spec.ts @@ -0,0 +1,7 @@ +import { describe, expect, test } from 'vitest'; + +describe('This file is for utility functions', () => { + test('crypto', () => { + expect('1').toStrictEqual('1'); + }); +}); diff --git a/packages/broswer-crypto/tsconfig.json b/packages/broswer-crypto/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/broswer-crypto/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/vitest.config.mts b/packages/broswer-crypto/vitest.config.mts similarity index 100% rename from vitest.config.mts rename to packages/broswer-crypto/vitest.config.mts diff --git a/packages/core/package.json b/packages/core/package.json index 414c3b9..f004b36 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@hopae/sd-jwt", + "name": "@hopae/sd-jwt-core", "version": "2.0.1", "description": "sd-jwt draft 7 implementation in typescript", "main": "dist/index.js", @@ -13,9 +13,9 @@ }, "scripts": { "build": "rm -rf **/dist && tsup", - "test": "vitest ./src/test/*.spec.ts", - "test:browser": "vitest ./src/test/*.spec.ts --environment jsdom", - "test:e2e": "vitest ./test/*e2e.spec.ts --environment node", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:e2e": "vitest run ./test/*e2e.spec.ts --environment node", "test:cov": "vitest run --coverage" }, "keywords": [ @@ -23,7 +23,12 @@ "digital-wallet", "sd-jwt", "sdjwt", - "sd-jwt-vc" + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" ], "repository": { "type": "git", @@ -36,17 +41,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@noble/hashes": "1.0.0", "@types/node": "^20.10.2", "@vitest/coverage-v8": "^1.2.2", - "js-base64": "^3.7.6", "jsdom": "^24.0.0", "lerna": "^8.1.2", "ts-node": "^10.9.1", "tsup": "^8.0.2", "typescript": "^5.3.2", "vite": "^5.1.1", - "vitest": "^1.2.2" + "vitest": "^1.2.2", + "@hopae/sd-jwt-node-crypto": "workspace:*" + }, + "dependencies": { + "@hopae/sd-jwt-util": "workspace:*", + "@hopae/sd-jwt-type": "workspace:*", + "@hopae/sd-jwt-decode": "workspace:*" }, "tsup": { "entry": [ diff --git a/packages/core/src/decoy.ts b/packages/core/src/decoy.ts index 6f40e1b..dfc37a6 100644 --- a/packages/core/src/decoy.ts +++ b/packages/core/src/decoy.ts @@ -1,6 +1,9 @@ -import { HasherAndAlg, SaltGenerator } from './type'; -import { Uint8ArrayToBase64Url } from './base64url'; +import { HasherAndAlg, SaltGenerator } from '@hopae/sd-jwt-type'; +import { Uint8ArrayToBase64Url } from '@hopae/sd-jwt-util'; +// This function creates a decoy value that can be used to obscure SD JWT payload. +// The value is basically a hash of a random salt. So the value is not predictable. +// return value is a base64url encoded string. export const createDecoy = async ( hash: HasherAndAlg, saltGenerator: SaltGenerator, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 61a367e..78da3be 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,4 @@ -import { SDJWTException } from './error'; +import { SDJWTException } from '@hopae/sd-jwt-util'; import { Jwt } from './jwt'; import { KBJwt } from './kbjwt'; import { SDJwt, pack } from './sdjwt'; @@ -9,16 +9,12 @@ import { SDJWTCompact, SDJWTConfig, SD_JWT_TYP, -} from './type'; +} from '@hopae/sd-jwt-type'; -export * from './type'; export * from './sdjwt'; export * from './kbjwt'; export * from './jwt'; -export * from './base64url'; export * from './decoy'; -export * from './disclosure'; -export * from './sha256'; export class SDJwtInstance { public static DEFAULT_hashAlg = 'sha-256'; @@ -70,7 +66,7 @@ export class SDJwtInstance { payload: Payload, disclosureFrame?: DisclosureFrame, options?: { - header?: object; + header?: object; // This is for customizing the header of the jwt }, ): Promise { if (!this.userConfig.hasher) { @@ -137,6 +133,9 @@ export class SDJwtInstance { return sdjwt.present(presentationKeys.sort(), hasher); } + // This function is for verifying the SD JWT + // If requiredClaimKeys is provided, it will check if the required claim keys are presentation in the SD JWT + // If requireKeyBindings is true, it will check if the key binding JWT is presentation and verify it public async verify( encodedSDJwt: string, requiredClaimKeys?: string[], @@ -177,6 +176,8 @@ export class SDJwtInstance { return { payload, header, kb }; } + // This function is for validating the SD JWT + // Just checking signature and return its the claims public async validate(encodedSDJwt: string) { if (!this.userConfig.hasher) { throw new SDJWTException('Hasher not found'); diff --git a/packages/core/src/jwt.ts b/packages/core/src/jwt.ts index 382245b..35d2bbd 100644 --- a/packages/core/src/jwt.ts +++ b/packages/core/src/jwt.ts @@ -1,6 +1,6 @@ -import { Base64urlDecode, Base64urlEncode } from './base64url'; -import { SDJWTException } from './error'; -import { Base64urlString, Signer, Verifier } from './type'; +import { Base64urlEncode, SDJWTException } from '@hopae/sd-jwt-util'; +import { Base64urlString, Signer, Verifier } from '@hopae/sd-jwt-type'; +import { decodeJwt } from '@hopae/sd-jwt-decode'; export type JwtData< Header extends Record, @@ -11,6 +11,8 @@ export type JwtData< signature?: Base64urlString; }; +// This class is used to create and verify JWT +// Contains header, payload, and signature export class Jwt< Header extends Record = Record, Payload extends Record = Record, @@ -31,16 +33,7 @@ export class Jwt< >( jwt: string, ): { header: Header; payload: Payload; signature: Base64urlString } { - const { 0: header, 1: payload, 2: signature, length } = jwt.split('.'); - if (length !== 3) { - throw new SDJWTException('Invalid JWT as input'); - } - - return { - header: JSON.parse(Base64urlDecode(header)), - payload: JSON.parse(Base64urlDecode(payload)), - signature: signature, - }; + return decodeJwt(jwt); } public static fromEncode< diff --git a/packages/core/src/kbjwt.ts b/packages/core/src/kbjwt.ts index 8069fc1..37ba722 100644 --- a/packages/core/src/kbjwt.ts +++ b/packages/core/src/kbjwt.ts @@ -1,11 +1,12 @@ -import { SDJWTException } from './error'; +import { SDJWTException } from '@hopae/sd-jwt-util'; import { Jwt } from './jwt'; -import { Verifier, kbHeader, kbPayload } from './type'; +import { Verifier, kbHeader, kbPayload } from '@hopae/sd-jwt-type'; export class KBJwt< Header extends kbHeader = kbHeader, Payload extends kbPayload = kbPayload, > extends Jwt { + // Checking the validity of the key binding jwt public async verify(verifier: Verifier) { if ( !this.header?.alg || @@ -21,6 +22,7 @@ export class KBJwt< return await super.verify(verifier); } + // This function is for creating KBJwt object for verify properly public static fromKBEncode< Header extends kbHeader = kbHeader, Payload extends kbPayload = kbPayload, diff --git a/packages/core/src/sdjwt.ts b/packages/core/src/sdjwt.ts index 33a6399..ce876e7 100644 --- a/packages/core/src/sdjwt.ts +++ b/packages/core/src/sdjwt.ts @@ -1,6 +1,5 @@ import { createDecoy } from './decoy'; -import { Disclosure } from './disclosure'; -import { SDJWTException } from './error'; +import { SDJWTException, Disclosure } from '@hopae/sd-jwt-util'; import { Jwt } from './jwt'; import { KBJwt } from './kbjwt'; import { @@ -15,7 +14,12 @@ import { SaltGenerator, kbHeader, kbPayload, -} from './type'; +} from '@hopae/sd-jwt-type'; +import { + createHashMapping, + getSDAlgAndPayload, + unpack, +} from '@hopae/sd-jwt-decode'; export type SDJwtData< Header extends Record, @@ -308,134 +312,3 @@ export const pack = async ( } return { packedClaims, disclosures }; }; - -export const unpackArray = ( - arr: Array, - map: Record>, - prefix: string = '', -): { unpackedObj: any; disclosureKeymap: Record } => { - const keys: Record = {}; - const unpackedArray: any[] = []; - arr.forEach((item, idx) => { - if (item instanceof Object) { - if (item[SD_LIST_KEY]) { - const hash = item[SD_LIST_KEY]; - const disclosed = map[hash]; - if (disclosed) { - const presentKey = prefix ? `${prefix}.${idx}` : `${idx}`; - keys[presentKey] = hash; - - const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( - disclosed.value, - map, - presentKey, - ); - unpackedArray.push(unpackedObj); - Object.assign(keys, disclosureKeys); - } - } else { - const newKey = prefix ? `${prefix}.${idx}` : `${idx}`; - const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( - item, - map, - newKey, - ); - unpackedArray.push(unpackedObj); - Object.assign(keys, disclosureKeys); - } - } else { - unpackedArray.push(item); - } - }); - return { unpackedObj: unpackedArray, disclosureKeymap: keys }; -}; - -export const unpackObj = ( - obj: any, - map: Record>, - prefix: string = '', -): { unpackedObj: any; disclosureKeymap: Record } => { - const keys: Record = {}; - if (obj instanceof Object) { - if (obj instanceof Array) { - return unpackArray(obj, map, prefix); - } - - for (const key in obj) { - if ( - key !== SD_DIGEST && - key !== SD_LIST_KEY && - obj[key] instanceof Object - ) { - const newKey = prefix ? `${prefix}.${key}` : key; - const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( - obj[key], - map, - newKey, - ); - obj[key] = unpackedObj; - Object.assign(keys, disclosureKeys); - } - } - - const { _sd, ...payload } = obj; - const claims: any = {}; - if (_sd) { - _sd.forEach((hash: string) => { - const disclosed = map[hash]; - if (disclosed && disclosed.key) { - const presentKey = prefix - ? `${prefix}.${disclosed.key}` - : disclosed.key; - keys[presentKey] = hash; - - const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( - disclosed.value, - map, - presentKey, - ); - claims[disclosed.key] = unpackedObj; - Object.assign(keys, disclosureKeys); - } - }); - } - - const unpackedObj = Object.assign(payload, claims); - return { unpackedObj, disclosureKeymap: keys }; - } - return { unpackedObj: obj, disclosureKeymap: keys }; -}; - -export const createHashMapping = async ( - disclosures: Array>, - hash: HasherAndAlg, -) => { - const map: Record> = {}; - for (let i = 0; i < disclosures.length; i++) { - const disclosure = disclosures[i]; - const digest = await disclosure.digest(hash); - map[digest] = disclosure; - } - return map; -}; - -export const getSDAlgAndPayload = (sdjwtPayload: any) => { - const { _sd_alg, ...payload } = sdjwtPayload; - if (typeof _sd_alg !== 'string') { - // This is for compatibility - return { _sd_alg: 'sha-256', payload }; - } - return { _sd_alg, payload }; -}; - -export const unpack = async ( - sdjwtPayload: any, - disclosures: Array>, - hasher: Hasher, -) => { - const { _sd_alg, payload } = getSDAlgAndPayload(sdjwtPayload); - const hash = { hasher, alg: _sd_alg }; - const map = await createHashMapping(disclosures, hash); - - return unpackObj(payload, map); -}; diff --git a/packages/core/src/test/decoy.spec.ts b/packages/core/src/test/decoy.spec.ts index ee28193..dc66b3b 100644 --- a/packages/core/src/test/decoy.spec.ts +++ b/packages/core/src/test/decoy.spec.ts @@ -1,7 +1,7 @@ import { createDecoy } from '../decoy'; import { describe, expect, test } from 'vitest'; -import { Base64urlEncode } from '../base64url'; -import { digest, generateSalt } from './crypto.spec'; +import { Base64urlEncode } from '@hopae/sd-jwt-util'; +import { digest, generateSalt } from '@hopae/sd-jwt-node-crypto'; const hash = { hasher: digest, diff --git a/packages/core/src/test/index.spec.ts b/packages/core/src/test/index.spec.ts index d01f1aa..81c8ead 100644 --- a/packages/core/src/test/index.spec.ts +++ b/packages/core/src/test/index.spec.ts @@ -1,7 +1,8 @@ -import { Signer, Verifier, SDJwtInstance } from '../index'; +import { SDJwtInstance } from '../index'; +import { Signer, Verifier } from '@hopae/sd-jwt-type'; import Crypto from 'node:crypto'; import { describe, expect, test } from 'vitest'; -import { digest, generateSalt } from './crypto.spec'; +import { digest, generateSalt } from '@hopae/sd-jwt-node-crypto'; export const createSignerVerifier = () => { const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519'); diff --git a/packages/core/src/test/jwt.spec.ts b/packages/core/src/test/jwt.spec.ts index ab85aab..b8e0d2f 100644 --- a/packages/core/src/test/jwt.spec.ts +++ b/packages/core/src/test/jwt.spec.ts @@ -1,7 +1,7 @@ -import { SDJWTException } from '../error'; +import { SDJWTException } from '@hopae/sd-jwt-util'; import { Jwt } from '../jwt'; import Crypto from 'node:crypto'; -import { Signer, Verifier } from '../type'; +import { Signer, Verifier } from '@hopae/sd-jwt-type'; import { describe, expect, test } from 'vitest'; describe('JWT', () => { diff --git a/packages/core/src/test/kbjwt.spec.ts b/packages/core/src/test/kbjwt.spec.ts index fea5e51..61d211e 100644 --- a/packages/core/src/test/kbjwt.spec.ts +++ b/packages/core/src/test/kbjwt.spec.ts @@ -1,6 +1,6 @@ -import { SDJWTException } from '../error'; +import { SDJWTException } from '@hopae/sd-jwt-util'; import { KBJwt } from '../kbjwt'; -import { KB_JWT_TYP, Signer, Verifier } from '../type'; +import { KB_JWT_TYP, Signer, Verifier } from '@hopae/sd-jwt-type'; import Crypto from 'node:crypto'; import { describe, expect, test } from 'vitest'; diff --git a/packages/core/src/test/sdjwt.spec.ts b/packages/core/src/test/sdjwt.spec.ts index f5bb10b..5ddde09 100644 --- a/packages/core/src/test/sdjwt.spec.ts +++ b/packages/core/src/test/sdjwt.spec.ts @@ -1,10 +1,11 @@ -import { Disclosure } from '../disclosure'; +import { Disclosure } from '@hopae/sd-jwt-util'; import { Jwt } from '../jwt'; -import { SDJwt, createHashMapping, listKeys, pack, unpack } from '../sdjwt'; +import { SDJwt, listKeys, pack } from '../sdjwt'; import Crypto from 'node:crypto'; import { describe, test, expect } from 'vitest'; -import { DisclosureFrame, Signer } from '../type'; -import { generateSalt, digest as hasher } from './crypto.spec'; +import { DisclosureFrame, Signer } from '@hopae/sd-jwt-type'; +import { generateSalt, digest as hasher } from '@hopae/sd-jwt-node-crypto'; +import { unpack, createHashMapping } from '@hopae/sd-jwt-decode'; const hash = { alg: 'SHA256', hasher }; diff --git a/packages/core/test/app-e2e.spec.ts b/packages/core/test/app-e2e.spec.ts index c37d541..6ab3d2b 100644 --- a/packages/core/test/app-e2e.spec.ts +++ b/packages/core/test/app-e2e.spec.ts @@ -1,10 +1,10 @@ import Crypto from 'node:crypto'; import { SDJwtInstance } from '../src'; -import { DisclosureFrame, Signer, Verifier } from '../src/type'; +import { DisclosureFrame, Signer, Verifier } from '@hopae/sd-jwt-type'; import fs from 'fs'; import path from 'path'; import { describe, expect, test } from 'vitest'; -import { digest, generateSalt } from '../src/test/crypto.spec'; +import { digest, generateSalt } from '@hopae/sd-jwt-node-crypto'; export const createSignerVerifier = () => { const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519'); diff --git a/packages/decode/package.json b/packages/decode/package.json new file mode 100644 index 0000000..46866a5 --- /dev/null +++ b/packages/decode/package.json @@ -0,0 +1,71 @@ +{ + "name": "@hopae/sd-jwt-decode", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2", + "@hopae/sd-jwt-node-crypto": "workspace:*" + }, + "dependencies": { + "@hopae/sd-jwt-util": "workspace:*", + "@hopae/sd-jwt-type": "workspace:*" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/decode/src/decode.ts b/packages/decode/src/decode.ts new file mode 100644 index 0000000..5e2236a --- /dev/null +++ b/packages/decode/src/decode.ts @@ -0,0 +1,256 @@ +import { + Base64urlDecode, + SDJWTException, + Disclosure, +} from '@hopae/sd-jwt-util'; +import { + Hasher, + HasherAndAlg, + SD_DIGEST, + SD_LIST_KEY, + SD_SEPARATOR, +} from '@hopae/sd-jwt-type'; + +export const decodeJwt = < + H extends Record, + T extends Record, +>( + jwt: string, +): { header: H; payload: T; signature: string } => { + const { 0: header, 1: payload, 2: signature, length } = jwt.split('.'); + if (length !== 3) { + throw new SDJWTException('Invalid JWT as input'); + } + + return { + header: JSON.parse(Base64urlDecode(header)), + payload: JSON.parse(Base64urlDecode(payload)), + signature: signature, + }; +}; + +// Split the sdjwt into 3 parts: jwt, disclosures and keybinding jwt. each part is base64url encoded +// It's separated by the ~ character +// +// If there is no keybinding jwt, the third part will be undefined +// If there are no disclosures, the second part will be an empty array +export const splitSdJwt = ( + sdjwt: string, +): { jwt: string; disclosures: string[]; kbJwt?: string } => { + const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR); + if (encodedDisclosures.length === 0) { + // if input is just jwt, then return here. + // This is for compatibility with jwt + return { + jwt: encodedJwt, + disclosures: [], + }; + } + + const encodedKeyBindingJwt = encodedDisclosures.pop(); + return { + jwt: encodedJwt, + disclosures: encodedDisclosures, + kbJwt: encodedKeyBindingJwt || undefined, + }; +}; + +// Decode the sdjwt into the jwt, disclosures and keybinding jwt +// jwt, disclosures and keybinding jwt are also decoded +export const decodeSdJwt = async ( + sdjwt: string, + hasher: Hasher, +): Promise => { + const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR); + const jwt = decodeJwt(encodedJwt); + + if (encodedDisclosures.length === 0) { + // if input is just jwt, then return here. + // This is for compatibility with jwt + return { + jwt, + disclosures: [], + }; + } + + const encodedKeyBindingJwt = encodedDisclosures.pop(); + const kbJwt = encodedKeyBindingJwt + ? decodeJwt(encodedKeyBindingJwt) + : undefined; + + const { _sd_alg } = getSDAlgAndPayload(jwt.payload); + + const disclosures = await Promise.all( + encodedDisclosures.map((ed) => + Disclosure.fromEncode(ed, { alg: _sd_alg, hasher }), + ), + ); + + return { + jwt, + disclosures, + kbJwt, + }; +}; + +// Get the claims from jwt and disclosures +// The digested values are matched with the disclosures and the claims are extracted +export const getClaims = async ( + rawPayload: any, + disclosures: Array>, + hasher: Hasher, +): Promise => { + const { unpackedObj } = await unpack(rawPayload, disclosures, hasher); + return unpackedObj as T; +}; + +export const unpackArray = ( + arr: Array, + map: Record>, + prefix: string = '', +): { unpackedObj: any; disclosureKeymap: Record } => { + const keys: Record = {}; + const unpackedArray: any[] = []; + arr.forEach((item, idx) => { + if (item instanceof Object) { + if (item[SD_LIST_KEY]) { + const hash = item[SD_LIST_KEY]; + const disclosed = map[hash]; + if (disclosed) { + const presentKey = prefix ? `${prefix}.${idx}` : `${idx}`; + keys[presentKey] = hash; + + const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( + disclosed.value, + map, + presentKey, + ); + unpackedArray.push(unpackedObj); + Object.assign(keys, disclosureKeys); + } + } else { + const newKey = prefix ? `${prefix}.${idx}` : `${idx}`; + const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( + item, + map, + newKey, + ); + unpackedArray.push(unpackedObj); + Object.assign(keys, disclosureKeys); + } + } else { + unpackedArray.push(item); + } + }); + return { unpackedObj: unpackedArray, disclosureKeymap: keys }; +}; + +export const unpackObj = ( + obj: any, + map: Record>, + prefix: string = '', +): { unpackedObj: any; disclosureKeymap: Record } => { + const keys: Record = {}; + if (obj instanceof Object) { + if (obj instanceof Array) { + return unpackArray(obj, map, prefix); + } + + for (const key in obj) { + if ( + key !== SD_DIGEST && + key !== SD_LIST_KEY && + obj[key] instanceof Object + ) { + const newKey = prefix ? `${prefix}.${key}` : key; + const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( + obj[key], + map, + newKey, + ); + obj[key] = unpackedObj; + Object.assign(keys, disclosureKeys); + } + } + + const { _sd, ...payload } = obj; + const claims: any = {}; + if (_sd) { + _sd.forEach((hash: string) => { + const disclosed = map[hash]; + if (disclosed && disclosed.key) { + const presentKey = prefix + ? `${prefix}.${disclosed.key}` + : disclosed.key; + keys[presentKey] = hash; + + const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj( + disclosed.value, + map, + presentKey, + ); + claims[disclosed.key] = unpackedObj; + Object.assign(keys, disclosureKeys); + } + }); + } + + const unpackedObj = Object.assign(payload, claims); + return { unpackedObj, disclosureKeymap: keys }; + } + return { unpackedObj: obj, disclosureKeymap: keys }; +}; + +// Creates a mapping of the digests of the disclosures to the actual disclosures +export const createHashMapping = async ( + disclosures: Array>, + hash: HasherAndAlg, +) => { + const map: Record> = {}; + for (let i = 0; i < disclosures.length; i++) { + const disclosure = disclosures[i]; + const digest = await disclosure.digest(hash); + map[digest] = disclosure; + } + return map; +}; + +// Extract _sd_alg. If it is not present, it is assumed to be sha-256 +export const getSDAlgAndPayload = (sdjwtPayload: any) => { + const { _sd_alg, ...payload } = sdjwtPayload; + if (typeof _sd_alg !== 'string') { + // This is for compatibility + return { _sd_alg: 'sha-256', payload }; + } + return { _sd_alg, payload }; +}; + +// Match the digests of the disclosures with the claims and extract the claims +// unpack function use unpackObj and unpackArray to recursively unpack the claims +export const unpack = async ( + sdjwtPayload: any, + disclosures: Array>, + hasher: Hasher, +) => { + const { _sd_alg, payload } = getSDAlgAndPayload(sdjwtPayload); + const hash = { hasher, alg: _sd_alg }; + const map = await createHashMapping(disclosures, hash); + + return unpackObj(payload, map); +}; + +// This is the type of the object that is returned by the decodeSdJwt function +// It is a combination of the decoded jwt, the disclosures and the keybinding jwt +export type DecodedSDJwt = { + jwt: { + header: Record; + payload: Record; // raw payload of sd-jwt + signature: string; + }; + disclosures: Array>; + kbJwt?: { + header: Record; + payload: Record; + signature: string; + }; +}; diff --git a/packages/decode/src/index.ts b/packages/decode/src/index.ts new file mode 100644 index 0000000..92fccb9 --- /dev/null +++ b/packages/decode/src/index.ts @@ -0,0 +1 @@ +export * from './decode'; diff --git a/packages/decode/src/test/decode.spec.ts b/packages/decode/src/test/decode.spec.ts new file mode 100644 index 0000000..5d37beb --- /dev/null +++ b/packages/decode/src/test/decode.spec.ts @@ -0,0 +1,91 @@ +import { describe, expect, test } from 'vitest'; +import { decodeJwt, decodeSdJwt, getClaims, splitSdJwt } from '../decode'; +import { digest } from '@hopae/sd-jwt-node-crypto'; + +describe('decode tests', () => { + test('decode jwt', () => { + const jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; + const { header, payload, signature } = decodeJwt(jwt); + expect(signature).toBeDefined(); + expect(header).toStrictEqual({ alg: 'HS256', typ: 'JWT' }); + expect(payload).toStrictEqual({ + sub: '1234567890', + name: 'John Doe', + iat: 1516239022, + }); + }); + + test('decode jwt with invalid input', () => { + const jwt = 'invalid.invalid'; + expect(() => decodeJwt(jwt)).toThrow('Invalid JWT as input'); + }); + + test('split sdjwt', () => { + const sdjwt = 'h.p.s~d1~d2~'; + const { jwt, disclosures, kbJwt } = splitSdJwt(sdjwt); + expect(jwt).toBe('h.p.s'); + expect(disclosures).toStrictEqual(['d1', 'd2']); + expect(kbJwt).toBeUndefined(); + }); + + test('split sdjwt with kbjwt', () => { + const sdjwt = 'h.p.s~d1~d2~kbh.kbp.kbs'; + const { jwt, disclosures, kbJwt } = splitSdJwt(sdjwt); + expect(jwt).toBe('h.p.s'); + expect(disclosures).toStrictEqual(['d1', 'd2']); + expect(kbJwt).toBe('kbh.kbp.kbs'); + }); + + test('decode sdjwt', async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~'; + const decodedSdJwt = await decodeSdJwt(sdjwt, digest); + expect(decodedSdJwt).toBeDefined(); + expect(decodedSdJwt.kbJwt).toBeUndefined(); + expect(decodedSdJwt.disclosures.length).toEqual(1); + expect(decodedSdJwt.jwt).toBeDefined(); + }); + + test('decode jwt', async () => { + const jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6InNkK2p3dCJ9.eyJsYXN0bmFtZSI6IkRvZSIsInNzbiI6IjEyMy00NS02Nzg5IiwiX3NkIjpbIk4yUXhZV1UxTlRnME1qQmpOR1JpWVRCaU1tRmtaamN5WXpSbFpXUmhaRGd5WkRCbE1qaGhZVGcwTnpJMU9XSXpZek5qWkdNNE1qZG1NVGN6TmpZd05RIiwiWlRSalkyUTVOemRoWkRVM05tWTFZV0UyTmpka01XVmpNRE16WXpOak5qQmtNak5pT0dZelpHSTBOelV4TURsak9EWTRNREEzWm1JeFpUY3daREZqTmciXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.mX14Sw86xy8NFQta7tCfNmhVCqzfaJ_K3VEIhTjbLDY'; + const decodedSdJwt = await decodeSdJwt(jwt, digest); + expect(decodedSdJwt).toBeDefined(); + expect(decodedSdJwt.kbJwt).toBeUndefined(); + expect(decodedSdJwt.disclosures.length).toEqual(0); + expect(decodedSdJwt.jwt).toBeDefined(); + }); + + test('get claims', async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~'; + const decodedSdJwt = await decodeSdJwt(sdjwt, digest); + const claims = await getClaims( + decodedSdJwt.jwt.payload, + decodedSdJwt.disclosures, + digest, + ); + expect(claims).toStrictEqual({ + foo: 'bar', + }); + }); + + test('getClaims #2', async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; + const decodedSdJwt = await decodeSdJwt(sdjwt, digest); + const claims = await getClaims( + decodedSdJwt.jwt.payload, + decodedSdJwt.disclosures, + digest, + ); + expect(claims).toStrictEqual({ + foo: 'bar', + arr: ['1', '2', { a: '1' }], + test: { + zzz: 'xxx', + }, + }); + }); +}); diff --git a/packages/decode/tsconfig.json b/packages/decode/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/decode/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/decode/vitest.config.mts b/packages/decode/vitest.config.mts new file mode 100644 index 0000000..0e6ce1e --- /dev/null +++ b/packages/decode/vitest.config.mts @@ -0,0 +1,14 @@ +// vite.config.ts +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + // Common test configuration + globals: true, + coverage: { + reporter: ['text', 'json', 'html'], + }, + environment: 'node', // Default environment + // Override per environment as needed + }, +}); diff --git a/packages/hash/package.json b/packages/hash/package.json new file mode 100644 index 0000000..a126f05 --- /dev/null +++ b/packages/hash/package.json @@ -0,0 +1,71 @@ +{ + "name": "@hopae/sd-jwt-hash", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@hopae/sd-jwt-node-crypto": "workspace:*", + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2" + }, + "dependencies": { + "@noble/hashes": "1.0.0", + "@hopae/sd-jwt-util": "workspace:*" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/hash/src/index.ts b/packages/hash/src/index.ts new file mode 100644 index 0000000..ed4719c --- /dev/null +++ b/packages/hash/src/index.ts @@ -0,0 +1 @@ +export * from './sha256'; diff --git a/packages/core/src/sha256.ts b/packages/hash/src/sha256.ts similarity index 74% rename from packages/core/src/sha256.ts rename to packages/hash/src/sha256.ts index 1182acd..a28f6f6 100644 --- a/packages/core/src/sha256.ts +++ b/packages/hash/src/sha256.ts @@ -1,4 +1,5 @@ import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; +import { SDJWTException } from '@hopae/sd-jwt-util'; export const sha256 = (text: string): Uint8Array => { const uint8Array = toUTF8Array(text); @@ -6,8 +7,19 @@ export const sha256 = (text: string): Uint8Array => { return hashBytes; }; +export const hasher = (data: string, algorithm: string) => { + if (toCryptoAlg(algorithm) !== 'sha256') { + throw new SDJWTException('Not implemented'); + } + return sha256(data); +}; + +const toCryptoAlg = (hashAlg: string): string => + // To cover sha-256, sha256, SHA-256, SHA256 + hashAlg.replace('-', '').toLowerCase(); + function toUTF8Array(str: string) { - const utf8 = []; + const utf8: Array = []; for (let i = 0; i < str.length; i++) { let charcode = str.charCodeAt(i); if (charcode < 0x80) utf8.push(charcode); diff --git a/packages/core/src/test/sha256.spec.ts b/packages/hash/src/test/sha256.spec.ts similarity index 80% rename from packages/core/src/test/sha256.spec.ts rename to packages/hash/src/test/sha256.spec.ts index a47a89c..e761fb1 100644 --- a/packages/core/src/test/sha256.spec.ts +++ b/packages/hash/src/test/sha256.spec.ts @@ -1,6 +1,7 @@ -import { digest } from './crypto.spec'; +import { digest } from '@hopae/sd-jwt-node-crypto'; import { bytesToHex } from '@noble/hashes/utils'; -import { sha256 } from '../sha256'; +import { hasher, sha256 } from '../sha256'; +import { describe, expect, test } from 'vitest'; describe('SHA-256 tests', () => { test('test#1', async () => { @@ -102,4 +103,24 @@ describe('SHA-256 tests', () => { const s2 = bytesToHex(sha256(payload)); expect(s1).toStrictEqual(s2); }); + + test('Hasher', async () => { + const s1 = bytesToHex(await digest('test')); + const s2 = bytesToHex(hasher('test', 'SHA-256')); + const s3 = bytesToHex(hasher('test', 'SHA256')); + const s4 = bytesToHex(hasher('test', 'sha256')); + const s5 = bytesToHex(hasher('test', 'sha-256')); + expect(s1).toStrictEqual(s2); + expect(s1).toStrictEqual(s3); + expect(s1).toStrictEqual(s4); + expect(s1).toStrictEqual(s5); + }); + + test('Hasher failed', async () => { + try { + const s1 = hasher('test', 'sha-512'); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } + }); }); diff --git a/packages/hash/tsconfig.json b/packages/hash/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/hash/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/hash/vitest.config.mts b/packages/hash/vitest.config.mts new file mode 100644 index 0000000..0e6ce1e --- /dev/null +++ b/packages/hash/vitest.config.mts @@ -0,0 +1,14 @@ +// vite.config.ts +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + // Common test configuration + globals: true, + coverage: { + reporter: ['text', 'json', 'html'], + }, + environment: 'node', // Default environment + // Override per environment as needed + }, +}); diff --git a/packages/node-crypto/package.json b/packages/node-crypto/package.json new file mode 100644 index 0000000..e9665a5 --- /dev/null +++ b/packages/node-crypto/package.json @@ -0,0 +1,66 @@ +{ + "name": "@hopae/sd-jwt-node-crypto", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/core/src/test/crypto.spec.ts b/packages/node-crypto/src/crypto.ts similarity index 75% rename from packages/core/src/test/crypto.spec.ts rename to packages/node-crypto/src/crypto.ts index e4cbfdc..19b6299 100644 --- a/packages/core/src/test/crypto.spec.ts +++ b/packages/node-crypto/src/crypto.ts @@ -1,10 +1,12 @@ -import { describe, expect, test } from 'vitest'; import { createHash, randomBytes } from 'crypto'; export const generateSalt = (length: number): string => { + if (length <= 0) { + return ''; + } const saltBytes = randomBytes(length); const salt = saltBytes.toString('hex'); - return salt; + return salt.substring(0, length); }; export const digest = async ( @@ -20,9 +22,3 @@ export const digest = async ( const toNodeCryptoAlg = (hashAlg: string): string => hashAlg.replace('-', '').toLowerCase(); - -describe('This file is for utility functions', () => { - test('crypto', () => { - expect('1').toStrictEqual('1'); - }); -}); diff --git a/packages/node-crypto/src/index.ts b/packages/node-crypto/src/index.ts new file mode 100644 index 0000000..f170247 --- /dev/null +++ b/packages/node-crypto/src/index.ts @@ -0,0 +1 @@ +export * from './crypto'; diff --git a/packages/node-crypto/src/test/crypto.spec.ts b/packages/node-crypto/src/test/crypto.spec.ts new file mode 100644 index 0000000..54cfa60 --- /dev/null +++ b/packages/node-crypto/src/test/crypto.spec.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from 'vitest'; +import { generateSalt, digest } from '../crypto'; + +describe('This file is for utility functions', () => { + test('crypto', () => { + expect('1').toStrictEqual('1'); + }); + + test('generateSalt', async () => { + const salt = generateSalt(8); + expect(salt).toBeDefined(); + expect(salt.length).toBe(8); + }); + + test('generateSalt 0 length', async () => { + const salt = generateSalt(0); + expect(salt).toBeDefined(); + expect(salt.length).toBe(0); + }); + + test('digest', async () => { + const payload = 'test1'; + const s1 = await digest(payload); + expect(s1).toBeDefined(); + expect(s1.length).toBe(32); + }); + + test('digest', async () => { + const payload = 'test1'; + const s1 = await digest(payload, 'SHA512'); + expect(s1).toBeDefined(); + expect(s1.length).toBe(64); + }); +}); diff --git a/packages/node-crypto/tsconfig.json b/packages/node-crypto/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/node-crypto/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/node-crypto/vitest.config.mts b/packages/node-crypto/vitest.config.mts new file mode 100644 index 0000000..0e6ce1e --- /dev/null +++ b/packages/node-crypto/vitest.config.mts @@ -0,0 +1,14 @@ +// vite.config.ts +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + // Common test configuration + globals: true, + coverage: { + reporter: ['text', 'json', 'html'], + }, + environment: 'node', // Default environment + // Override per environment as needed + }, +}); diff --git a/packages/present/package.json b/packages/present/package.json new file mode 100644 index 0000000..097447f --- /dev/null +++ b/packages/present/package.json @@ -0,0 +1,72 @@ +{ + "name": "@hopae/sd-jwt-present", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2", + "@hopae/sd-jwt-node-crypto": "workspace:*" + }, + "dependencies": { + "@hopae/sd-jwt-util": "workspace:*", + "@hopae/sd-jwt-type": "workspace:*", + "@hopae/sd-jwt-decode": "workspace:*" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/present/src/index.ts b/packages/present/src/index.ts new file mode 100644 index 0000000..07bb01e --- /dev/null +++ b/packages/present/src/index.ts @@ -0,0 +1 @@ +export * from './present'; diff --git a/packages/present/src/present.ts b/packages/present/src/present.ts new file mode 100644 index 0000000..b521f8e --- /dev/null +++ b/packages/present/src/present.ts @@ -0,0 +1,71 @@ +import { Hasher, SD_SEPARATOR } from '@hopae/sd-jwt-type'; +import { Disclosure, SDJWTException } from '@hopae/sd-jwt-util'; +import { + createHashMapping, + decodeSdJwt, + getSDAlgAndPayload, + splitSdJwt, + unpack, +} from '@hopae/sd-jwt-decode'; + +// Presentable keys +// The presentable keys are the path of JSON object that are presentable in the SD JWT +// e.g. if the SD JWT has the following payload and set sd like this: +// { +// "foo": "bar", // sd +// "arr": [ // sd +// "1", // sd +// "2", +// { +// "a": "1" // sd +// } +// ], +// "test": { +// "zzz": "xxx" // sd +// } +// } +// The presentable keys are: ["arr", "arr.0", "arr.2.a", "foo", "test.zzz"] +export const presentableKeys = async ( + rawPayload: any, + disclosures: Array>, + hasher: Hasher, +): Promise => { + const { disclosureKeymap } = await unpack(rawPayload, disclosures, hasher); + return Object.keys(disclosureKeymap).sort(); +}; + +export const present = async ( + sdJwt: string, + keys: string[], + hasher: Hasher, +): Promise => { + const { jwt, kbJwt } = splitSdJwt(sdJwt); + const { + jwt: { payload }, + disclosures, + } = await decodeSdJwt(sdJwt, hasher); + + const { _sd_alg: alg } = getSDAlgAndPayload(payload); + const hash = { alg, hasher }; + + // hashmap: => + // to match the digest with the disclosure + const hashmap = await createHashMapping(disclosures, hash); + const { disclosureKeymap } = await unpack(payload, disclosures, hasher); + const presentableKeys = Object.keys(disclosureKeymap); + + const missingKeys = keys.filter((k) => !presentableKeys.includes(k)); + if (missingKeys.length > 0) { + throw new SDJWTException( + `Invalid sd-jwt: invalid present keys: ${missingKeys.join(', ')}`, + ); + } + + const presentedDisclosures = keys.map((k) => hashmap[disclosureKeymap[k]]); + + return [ + jwt, + ...presentedDisclosures.map((d) => d.encode()), + kbJwt ?? '', + ].join(SD_SEPARATOR); +}; diff --git a/packages/present/src/test/present.spec.ts b/packages/present/src/test/present.spec.ts new file mode 100644 index 0000000..bb16305 --- /dev/null +++ b/packages/present/src/test/present.spec.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from 'vitest'; +import { digest } from '@hopae/sd-jwt-node-crypto'; +import { present, presentableKeys } from '../present'; +import { decodeSdJwt } from '@hopae/sd-jwt-decode'; + +describe('Present tests', () => { + test('presentableKeys', async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; + const decodedSdJwt = await decodeSdJwt(sdjwt, digest); + const keys = await presentableKeys( + decodedSdJwt.jwt.payload, + decodedSdJwt.disclosures, + digest, + ); + expect(keys).toStrictEqual(['arr', 'arr.0', 'arr.2.a', 'foo', 'test.zzz']); + }); + + test('present', async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; + const presentedSdJwt = await present( + sdjwt, + ['foo', 'arr.0', 'test.zzz'], + digest, + ); + expect(presentedSdJwt).toStrictEqual( + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~', + ); + }); + + test('present error', async () => { + const sdjwt = + 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~'; + try { + await present(sdjwt, ['notthekey'], digest); + } catch (e) { + expect(e).toBeDefined(); + } + }); +}); diff --git a/packages/present/tsconfig.json b/packages/present/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/present/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/present/vitest.config.mts b/packages/present/vitest.config.mts new file mode 100644 index 0000000..0e6ce1e --- /dev/null +++ b/packages/present/vitest.config.mts @@ -0,0 +1,14 @@ +// vite.config.ts +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + // Common test configuration + globals: true, + coverage: { + reporter: ['text', 'json', 'html'], + }, + environment: 'node', // Default environment + // Override per environment as needed + }, +}); diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..15e9ef2 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,69 @@ +{ + "name": "@hopae/sd-jwt-type", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2" + }, + "dependencies": { + "js-base64": "^3.7.6" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..b38ebc9 --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1 @@ +export * from './type'; diff --git a/packages/types/src/test/type.spec.ts b/packages/types/src/test/type.spec.ts new file mode 100644 index 0000000..23e6bbe --- /dev/null +++ b/packages/types/src/test/type.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from 'vitest'; +import { + SD_SEPARATOR, + SD_LIST_KEY, + SD_DIGEST, + SD_JWT_TYP, + SD_DECOY, + KB_JWT_TYP, +} from '../type'; + +describe('Variable tests', () => { + test('SD_SEPARATOR', () => { + expect(SD_SEPARATOR).toBe('~'); + }); + + test('SD_LIST_KEY', () => { + expect(SD_LIST_KEY).toBe('...'); + }); + + test('SD_DIGEST', () => { + expect(SD_DIGEST).toBe('_sd'); + }); + + test('SD_JWT_TYP', () => { + expect(SD_JWT_TYP).toBe('sd-jwt'); + }); + + test('SD_DECOY', () => { + expect(SD_DECOY).toBe('_sd_decoy'); + }); + + test('KB_JWT_TYP', () => { + expect(KB_JWT_TYP).toBe('kb+jwt'); + }); +}); diff --git a/packages/core/src/type.ts b/packages/types/src/type.ts similarity index 70% rename from packages/core/src/type.ts rename to packages/types/src/type.ts index ab8aae5..1f15369 100644 --- a/packages/core/src/type.ts +++ b/packages/types/src/type.ts @@ -1,5 +1,3 @@ -import { Jwt } from './jwt'; - export const SD_SEPARATOR = '~'; export const SD_LIST_KEY = '...'; export const SD_DIGEST = '_sd'; @@ -10,6 +8,8 @@ export const KB_JWT_TYP = 'kb+jwt'; export type SDJWTCompact = string; export type Base64urlString = string; +export type DisclosureData = [string, string, T] | [string, T]; + export type SDJWTConfig = { omitTyp?: boolean; hasher?: Hasher; @@ -31,8 +31,6 @@ export type kbPayload = { sd_hash: string; }; -export type KeyBinding = Jwt; - export type KBOptions = { payload: kbPayload; }; @@ -55,6 +53,42 @@ type NonNever = { export type SD = { [SD_DIGEST]?: Array }; export type DECOY = { [SD_DECOY]?: number }; +/** + * This is a disclosureFrame type that is used to represent the structure of what is being disclosed. + * DisclosureFrame is made from the payload type. + * + * For example, if the payload is + * { + * foo: 'bar', + * test: { + * zzz: 'yyy', + * } + * arr: ['1', '2', {a: 'b'}] + * } + * + * The disclosureFrame can be subset of: + * { + * _sd: ["foo", "test", "arr"], + * test: { + * _sd: ["zzz"], + * }, + * arr: { + * _sd: ["0", "1", "2"], + * "2": { + * _sd: ["a"], + * } + * } + * } + * + * The disclosureFrame can be used with decoy. + * Decoy can be used like this: + * { + * ... + * _sd: ... + * _sd_decoy: 1 // number of decoy in this layer + * } + * + */ type Frame = Payload extends Array ? U extends object ? Record> & SD & DECOY diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/types/vitest.config.mts b/packages/types/vitest.config.mts new file mode 100644 index 0000000..0e6ce1e --- /dev/null +++ b/packages/types/vitest.config.mts @@ -0,0 +1,14 @@ +// vite.config.ts +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + // Common test configuration + globals: true, + coverage: { + reporter: ['text', 'json', 'html'], + }, + environment: 'node', // Default environment + // Override per environment as needed + }, +}); diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 0000000..524ad9f --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,71 @@ +{ + "name": "@hopae/sd-jwt-util", + "version": "2.0.1", + "description": "sd-jwt draft 7 implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "test": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "verifiable-credentials", + "digital-wallet", + "sd-jwt", + "sdjwt", + "sd-jwt-vc", + "decentralized-identity", + "ssi", + "oauth", + "oauth2", + "openid-connect" + ], + "repository": { + "type": "git", + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js" + }, + "author": "Lukas.J.Han ", + "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki", + "bugs": { + "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^20.10.2", + "@vitest/coverage-v8": "^1.2.2", + "jsdom": "^24.0.0", + "lerna": "^8.1.2", + "ts-node": "^10.9.1", + "tsup": "^8.0.2", + "typescript": "^5.3.2", + "vite": "^5.1.1", + "vitest": "^1.2.2", + "@hopae/sd-jwt-node-crypto": "workspace:*" + }, + "dependencies": { + "js-base64": "^3.7.6", + "@hopae/sd-jwt-type": "workspace:*" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + } +} diff --git a/packages/core/src/base64url.ts b/packages/utils/src/base64url.ts similarity index 100% rename from packages/core/src/base64url.ts rename to packages/utils/src/base64url.ts diff --git a/packages/core/src/disclosure.ts b/packages/utils/src/disclosure.ts similarity index 63% rename from packages/core/src/disclosure.ts rename to packages/utils/src/disclosure.ts index 0d57200..f8f2fee 100644 --- a/packages/core/src/disclosure.ts +++ b/packages/utils/src/disclosure.ts @@ -4,18 +4,23 @@ import { Base64urlEncode, } from './base64url'; import { SDJWTException } from './error'; -import { HasherAndAlg } from './type'; - -export type DisclosureData = [string, string, T] | [string, T]; +import { HasherAndAlg, DisclosureData } from '@hopae/sd-jwt-type'; export class Disclosure { public salt: string; public key?: string; public value: T; private _digest: string | undefined; + private _encoded: string | undefined; + + public constructor( + data: DisclosureData, + _meta?: { digest: string; encoded: string }, + ) { + // If the meta is provided, then we assume that the data is already encoded and digested + this._digest = _meta?.digest; + this._encoded = _meta?.encoded; - public constructor(data: DisclosureData, digest?: string) { - this._digest = digest; if (data.length === 2) { this.salt = data[0]; this.value = data[1]; @@ -38,15 +43,23 @@ export class Disclosure { const digest = await hasher(s, alg); const digestStr = Uint8ArrayToBase64Url(digest); const item = JSON.parse(Base64urlDecode(s)) as DisclosureData; - return Disclosure.fromArray(item, digestStr); + return Disclosure.fromArray(item, { digest: digestStr, encoded: s }); } - public static fromArray(item: DisclosureData, digest?: string) { - return new Disclosure(item, digest); + public static fromArray( + item: DisclosureData, + _meta?: { digest: string; encoded: string }, + ) { + return new Disclosure(item, _meta); } public encode() { - return Base64urlEncode(JSON.stringify(this.decode())); + if (!this._encoded) { + // we use JSON.stringify to encode the data + // It's the most reliable and universal way to encode JSON object + this._encoded = Base64urlEncode(JSON.stringify(this.decode())); + } + return this._encoded; } public decode(): DisclosureData { diff --git a/packages/core/src/error.ts b/packages/utils/src/error.ts similarity index 100% rename from packages/core/src/error.ts rename to packages/utils/src/error.ts diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 0000000..eb5e254 --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,3 @@ +export * from './base64url'; +export * from './error'; +export * from './disclosure'; diff --git a/packages/core/src/test/base64url.spec.ts b/packages/utils/src/test/base64url.spec.ts similarity index 100% rename from packages/core/src/test/base64url.spec.ts rename to packages/utils/src/test/base64url.spec.ts diff --git a/packages/core/src/test/disclosure.spec.ts b/packages/utils/src/test/disclosure.spec.ts similarity index 96% rename from packages/core/src/test/disclosure.spec.ts rename to packages/utils/src/test/disclosure.spec.ts index 0be44ac..37ae010 100644 --- a/packages/core/src/test/disclosure.spec.ts +++ b/packages/utils/src/test/disclosure.spec.ts @@ -1,8 +1,7 @@ -import { generateSalt, digest as hasher } from './crypto.spec'; +import { generateSalt, digest as hasher } from '@hopae/sd-jwt-node-crypto'; import { Disclosure } from '../disclosure'; -import { SDJWTException } from '../error'; import { describe, expect, test } from 'vitest'; -import { Base64urlEncode } from '../base64url'; +import { Base64urlEncode, SDJWTException } from '../index'; const hash = { alg: 'SHA256', hasher }; diff --git a/packages/core/src/test/error.spec.ts b/packages/utils/src/test/error.spec.ts similarity index 100% rename from packages/core/src/test/error.spec.ts rename to packages/utils/src/test/error.spec.ts diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 0000000..2a11ecd --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/utils/vitest.config.mts b/packages/utils/vitest.config.mts new file mode 100644 index 0000000..0e6ce1e --- /dev/null +++ b/packages/utils/vitest.config.mts @@ -0,0 +1,14 @@ +// vite.config.ts +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + // Common test configuration + globals: true, + coverage: { + reporter: ['text', 'json', 'html'], + }, + environment: 'node', // Default environment + // Override per environment as needed + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2f2d77..9a1606d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,20 +12,350 @@ importers: specifier: ^8.1.2 version: 8.1.2 + examples/core-example: + dependencies: + '@hopae/sd-jwt-core': + specifier: workspace:* + version: link:../../packages/core + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../../packages/types + devDependencies: + '@types/node': + specifier: ^20.10.4 + version: 20.11.19 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.19)(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + + examples/decode-example: + dependencies: + '@hopae/sd-jwt-decode': + specifier: workspace:* + version: link:../../packages/decode + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../../packages/node-crypto + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../../packages/types + devDependencies: + '@types/node': + specifier: ^20.10.4 + version: 20.11.19 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.19)(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + + examples/present-example: + dependencies: + '@hopae/sd-jwt-decode': + specifier: workspace:* + version: link:../../packages/decode + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../../packages/node-crypto + '@hopae/sd-jwt-present': + specifier: workspace:* + version: link:../../packages/present + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../../packages/types + devDependencies: + '@types/node': + specifier: ^20.10.4 + version: 20.11.19 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.19)(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + + packages/broswer-crypto: + devDependencies: + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + packages/core: + dependencies: + '@hopae/sd-jwt-decode': + specifier: workspace:* + version: link:../decode + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../types + '@hopae/sd-jwt-util': + specifier: workspace:* + version: link:../utils + devDependencies: + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../node-crypto + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + + packages/decode: + dependencies: + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../types + '@hopae/sd-jwt-util': + specifier: workspace:* + version: link:../utils devDependencies: + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../node-crypto + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + + packages/hash: + dependencies: + '@hopae/sd-jwt-util': + specifier: workspace:* + version: link:../utils '@noble/hashes': specifier: 1.0.0 version: 1.0.0 + devDependencies: + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../node-crypto + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + + packages/node-crypto: + devDependencies: '@types/node': specifier: ^20.10.2 version: 20.10.2 '@vitest/coverage-v8': specifier: ^1.2.2 version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + + packages/present: + dependencies: + '@hopae/sd-jwt-decode': + specifier: workspace:* + version: link:../decode + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../types + '@hopae/sd-jwt-util': + specifier: workspace:* + version: link:../utils + devDependencies: + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../node-crypto + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + + packages/types: + dependencies: js-base64: specifier: ^3.7.6 version: 3.7.6 + devDependencies: + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 + lerna: + specifier: ^8.1.2 + version: 8.1.2 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.2)(typescript@5.3.2) + tsup: + specifier: ^8.0.2 + version: 8.0.2(ts-node@10.9.1)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + vite: + specifier: ^5.1.1 + version: 5.1.1(@types/node@20.10.2) + vitest: + specifier: ^1.2.2 + version: 1.2.2(@types/node@20.10.2)(jsdom@24.0.0) + + packages/utils: + dependencies: + '@hopae/sd-jwt-type': + specifier: workspace:* + version: link:../types + js-base64: + specifier: ^3.7.6 + version: 3.7.6 + devDependencies: + '@hopae/sd-jwt-node-crypto': + specifier: workspace:* + version: link:../node-crypto + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + '@vitest/coverage-v8': + specifier: ^1.2.2 + version: 1.2.2(vitest@1.2.2) jsdom: specifier: ^24.0.0 version: 24.0.0 @@ -466,7 +796,7 @@ packages: /@noble/hashes@1.0.0: resolution: {integrity: sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==} - dev: true + dev: false /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1078,6 +1408,12 @@ packages: undici-types: 5.26.5 dev: true + /@types/node@20.11.19: + resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -1176,11 +1512,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true - /acorn-walk@8.3.0: - resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} - engines: {node: '>=0.4.0'} - dev: true - /acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -2917,7 +3248,7 @@ packages: /js-base64@3.7.6: resolution: {integrity: sha512-NPrWuHFxFUknr1KqJRDgUQPexQF0uIJWjeT+2KjEePhitQxQEx5EJBG1lVn5/hc8aLycTpXrDOgPQ6Zq+EDiTA==} - dev: true + dev: false /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3921,9 +4252,9 @@ packages: engines: {node: '>=10'} dependencies: bl: 4.1.0 - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 - cli-spinners: 2.6.1 + cli-spinners: 2.9.2 is-interactive: 1.0.0 log-symbols: 4.1.0 strip-ansi: 6.0.1 @@ -5062,8 +5393,8 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.10.2 - acorn: 8.11.2 - acorn-walk: 8.3.0 + acorn: 8.11.3 + acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -5073,6 +5404,37 @@ packages: yn: 3.1.1 dev: true + /ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.11.19 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -5187,6 +5549,12 @@ packages: hasBin: true dev: true + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + /ufo@1.4.0: resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} dev: true