From 59fcd1693859dcd0f2aa723e668796f36a699f2f Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 8 Feb 2024 07:46:09 -0600 Subject: [PATCH 1/7] 0.1.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9fceca8..e151d2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.1.6", + "version": "0.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@transmute/verifiable-credentials", - "version": "0.1.6", + "version": "0.1.7", "license": "Apache-2.0", "dependencies": { "@transmute/cose": "^0.1.1", diff --git a/package.json b/package.json index 7a67d9a..4396db0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.1.6", + "version": "0.1.7", "description": "An opinionated typescript library for w3c verifiable credentials.", "main": "./dist/index.js", "typings": "dist/index.d.ts", From a2978e45b0d98c1742f86beb6cecc72037513ad0 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 8 Feb 2024 07:46:18 -0600 Subject: [PATCH 2/7] 0.2.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e151d2d..0decfa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.1.7", + "version": "0.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@transmute/verifiable-credentials", - "version": "0.1.7", + "version": "0.2.0", "license": "Apache-2.0", "dependencies": { "@transmute/cose": "^0.1.1", diff --git a/package.json b/package.json index 4396db0..29659fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.1.7", + "version": "0.2.0", "description": "An opinionated typescript library for w3c verifiable credentials.", "main": "./dist/index.js", "typings": "dist/index.d.ts", From c6cc255efede37414377b21f160be86cec707c33 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 8 Feb 2024 07:52:21 -0600 Subject: [PATCH 3/7] test fixture location --- .npmignore | 1 + package.json | 2 +- .../__fixtures__/broken_context.yml | 0 {src/cr1 => test}/__fixtures__/claimset_0.yml | 0 {src/cr1 => test}/__fixtures__/claimset_1.yml | 0 {src/cr1 => test}/__fixtures__/claimset_2.yml | 0 .../__fixtures__/claimset_disclosable_0.yml | 0 .../claimset_disclosable_0_disclosure_0.yml | 0 .../__fixtures__/claimset_disclosable_1.yml | 0 .../__fixtures__/holder_0_private_key.cbor | Bin .../__fixtures__/holder_0_public_key.cbor | Bin {src/cr1 => test}/__fixtures__/index.ts | 36 +++++++++--------- .../__fixtures__/issuer_0_private_key.json | 0 .../__fixtures__/issuer_0_public_key.json | 0 .../__fixtures__/minimal_credential.yml | 0 .../minimal_credential_with_bad_urls.yml | 0 .../minimal_credential_with_dids.yml | 0 test/w3c-cr-1/0-keys.test.ts | 8 ++-- test/w3c-cr-1/1-credentials.test.ts | 2 +- test/w3c-cr-1/2-presentations.test.ts | 2 +- test/w3c-cr-1/3-schema.test.ts | 4 +- test/w3c-cr-1/4-status.test.ts | 4 +- test/w3c-cr-1/5-data-model.test.ts | 32 ++++++++-------- 23 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 .npmignore rename {src/cr1 => test}/__fixtures__/broken_context.yml (100%) rename {src/cr1 => test}/__fixtures__/claimset_0.yml (100%) rename {src/cr1 => test}/__fixtures__/claimset_1.yml (100%) rename {src/cr1 => test}/__fixtures__/claimset_2.yml (100%) rename {src/cr1 => test}/__fixtures__/claimset_disclosable_0.yml (100%) rename {src/cr1 => test}/__fixtures__/claimset_disclosable_0_disclosure_0.yml (100%) rename {src/cr1 => test}/__fixtures__/claimset_disclosable_1.yml (100%) rename {src/cr1 => test}/__fixtures__/holder_0_private_key.cbor (100%) rename {src/cr1 => test}/__fixtures__/holder_0_public_key.cbor (100%) rename {src/cr1 => test}/__fixtures__/index.ts (60%) rename {src/cr1 => test}/__fixtures__/issuer_0_private_key.json (100%) rename {src/cr1 => test}/__fixtures__/issuer_0_public_key.json (100%) rename {src/cr1 => test}/__fixtures__/minimal_credential.yml (100%) rename {src/cr1 => test}/__fixtures__/minimal_credential_with_bad_urls.yml (100%) rename {src/cr1 => test}/__fixtures__/minimal_credential_with_dids.yml (100%) diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..5c2b742 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +./test/__fixtures__/* \ No newline at end of file diff --git a/package.json b/package.json index 29659fa..817ef2c 100644 --- a/package.json +++ b/package.json @@ -53,4 +53,4 @@ "uuid": "^9.0.1", "yaml": "^2.3.4" } -} +} \ No newline at end of file diff --git a/src/cr1/__fixtures__/broken_context.yml b/test/__fixtures__/broken_context.yml similarity index 100% rename from src/cr1/__fixtures__/broken_context.yml rename to test/__fixtures__/broken_context.yml diff --git a/src/cr1/__fixtures__/claimset_0.yml b/test/__fixtures__/claimset_0.yml similarity index 100% rename from src/cr1/__fixtures__/claimset_0.yml rename to test/__fixtures__/claimset_0.yml diff --git a/src/cr1/__fixtures__/claimset_1.yml b/test/__fixtures__/claimset_1.yml similarity index 100% rename from src/cr1/__fixtures__/claimset_1.yml rename to test/__fixtures__/claimset_1.yml diff --git a/src/cr1/__fixtures__/claimset_2.yml b/test/__fixtures__/claimset_2.yml similarity index 100% rename from src/cr1/__fixtures__/claimset_2.yml rename to test/__fixtures__/claimset_2.yml diff --git a/src/cr1/__fixtures__/claimset_disclosable_0.yml b/test/__fixtures__/claimset_disclosable_0.yml similarity index 100% rename from src/cr1/__fixtures__/claimset_disclosable_0.yml rename to test/__fixtures__/claimset_disclosable_0.yml diff --git a/src/cr1/__fixtures__/claimset_disclosable_0_disclosure_0.yml b/test/__fixtures__/claimset_disclosable_0_disclosure_0.yml similarity index 100% rename from src/cr1/__fixtures__/claimset_disclosable_0_disclosure_0.yml rename to test/__fixtures__/claimset_disclosable_0_disclosure_0.yml diff --git a/src/cr1/__fixtures__/claimset_disclosable_1.yml b/test/__fixtures__/claimset_disclosable_1.yml similarity index 100% rename from src/cr1/__fixtures__/claimset_disclosable_1.yml rename to test/__fixtures__/claimset_disclosable_1.yml diff --git a/src/cr1/__fixtures__/holder_0_private_key.cbor b/test/__fixtures__/holder_0_private_key.cbor similarity index 100% rename from src/cr1/__fixtures__/holder_0_private_key.cbor rename to test/__fixtures__/holder_0_private_key.cbor diff --git a/src/cr1/__fixtures__/holder_0_public_key.cbor b/test/__fixtures__/holder_0_public_key.cbor similarity index 100% rename from src/cr1/__fixtures__/holder_0_public_key.cbor rename to test/__fixtures__/holder_0_public_key.cbor diff --git a/src/cr1/__fixtures__/index.ts b/test/__fixtures__/index.ts similarity index 60% rename from src/cr1/__fixtures__/index.ts rename to test/__fixtures__/index.ts index 495983e..61f5abb 100644 --- a/src/cr1/__fixtures__/index.ts +++ b/test/__fixtures__/index.ts @@ -1,51 +1,51 @@ import fs from 'fs' import * as jose from "jose"; -import { importKeyLike } from '../key/importKeyLike' +import { importKeyLike } from '../../src/cr1/key/importKeyLike' -import { encoder, decoder } from '../text'; +import { encoder, decoder } from '../../src/cr1/text'; -import { issuer } from '../credential' -import { validator } from '../validator' +import { issuer } from '../../src/cr1/credential' +import { validator } from '../../src/cr1/validator' // keys export const issuer_0_key_type = 'application/jwk+json' -export const issuer_0_private_key = fs.readFileSync('./src/cr1/__fixtures__/issuer_0_private_key.json') -export const issuer_0_public_key = fs.readFileSync('./src/cr1/__fixtures__/issuer_0_public_key.json') +export const issuer_0_private_key = fs.readFileSync('./test/__fixtures__/issuer_0_private_key.json') +export const issuer_0_public_key = fs.readFileSync('./test/__fixtures__/issuer_0_public_key.json') export const holder_0_key_type = 'application/cose-key' -export const holder_0_private_key = fs.readFileSync('./src/cr1/__fixtures__/holder_0_private_key.cbor') -export const holder_0_public_key = fs.readFileSync('./src/cr1/__fixtures__/holder_0_public_key.cbor') +export const holder_0_private_key = fs.readFileSync('./test/__fixtures__/holder_0_private_key.cbor') +export const holder_0_public_key = fs.readFileSync('./test/__fixtures__/holder_0_public_key.cbor') // vc -export const claimset_0 = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/claimset_0.yml')) +export const claimset_0 = new Uint8Array(fs.readFileSync('./test/__fixtures__/claimset_0.yml')) // vp -export const claimset_1 = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/claimset_1.yml')) +export const claimset_1 = new Uint8Array(fs.readFileSync('./test/__fixtures__/claimset_1.yml')) // sd claims without key binding -export const claimset_disclosable_0 = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/claimset_disclosable_0.yml')) +export const claimset_disclosable_0 = new Uint8Array(fs.readFileSync('./test/__fixtures__/claimset_disclosable_0.yml')) -export const claimset_disclosable_0_disclosure = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/claimset_disclosable_0_disclosure_0.yml')) +export const claimset_disclosable_0_disclosure = new Uint8Array(fs.readFileSync('./test/__fixtures__/claimset_disclosable_0_disclosure_0.yml')) // sd claims with key binding -export const claimset_disclosable_1 = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/claimset_disclosable_1.yml')) +export const claimset_disclosable_1 = new Uint8Array(fs.readFileSync('./test/__fixtures__/claimset_disclosable_1.yml')) // a credential with a schema -export const claimset_2 = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/claimset_2.yml')) +export const claimset_2 = new Uint8Array(fs.readFileSync('./test/__fixtures__/claimset_2.yml')) // data model conformance examples -export const minimal_credential = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/minimal_credential.yml')) +export const minimal_credential = new Uint8Array(fs.readFileSync('./test/__fixtures__/minimal_credential.yml')) -export const minimal_credential_with_dids = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/minimal_credential_with_dids.yml')) +export const minimal_credential_with_dids = new Uint8Array(fs.readFileSync('./test/__fixtures__/minimal_credential_with_dids.yml')) -export const minimal_credential_with_bad_urls = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/minimal_credential_with_bad_urls.yml')) +export const minimal_credential_with_bad_urls = new Uint8Array(fs.readFileSync('./test/__fixtures__/minimal_credential_with_bad_urls.yml')) -export const broken_context = new Uint8Array(fs.readFileSync('./src/cr1/__fixtures__/broken_context.yml')) +export const broken_context = new Uint8Array(fs.readFileSync('./test/__fixtures__/broken_context.yml')) const jws = { diff --git a/src/cr1/__fixtures__/issuer_0_private_key.json b/test/__fixtures__/issuer_0_private_key.json similarity index 100% rename from src/cr1/__fixtures__/issuer_0_private_key.json rename to test/__fixtures__/issuer_0_private_key.json diff --git a/src/cr1/__fixtures__/issuer_0_public_key.json b/test/__fixtures__/issuer_0_public_key.json similarity index 100% rename from src/cr1/__fixtures__/issuer_0_public_key.json rename to test/__fixtures__/issuer_0_public_key.json diff --git a/src/cr1/__fixtures__/minimal_credential.yml b/test/__fixtures__/minimal_credential.yml similarity index 100% rename from src/cr1/__fixtures__/minimal_credential.yml rename to test/__fixtures__/minimal_credential.yml diff --git a/src/cr1/__fixtures__/minimal_credential_with_bad_urls.yml b/test/__fixtures__/minimal_credential_with_bad_urls.yml similarity index 100% rename from src/cr1/__fixtures__/minimal_credential_with_bad_urls.yml rename to test/__fixtures__/minimal_credential_with_bad_urls.yml diff --git a/src/cr1/__fixtures__/minimal_credential_with_dids.yml b/test/__fixtures__/minimal_credential_with_dids.yml similarity index 100% rename from src/cr1/__fixtures__/minimal_credential_with_dids.yml rename to test/__fixtures__/minimal_credential_with_dids.yml diff --git a/test/w3c-cr-1/0-keys.test.ts b/test/w3c-cr-1/0-keys.test.ts index 98ad300..f44fab6 100644 --- a/test/w3c-cr-1/0-keys.test.ts +++ b/test/w3c-cr-1/0-keys.test.ts @@ -39,8 +39,8 @@ describe.skip('key generation', () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = jwk const publicKeyContent = encoder.encode(JSON.stringify(publicKeyJwk, null, 2)) - fs.writeFileSync('./src/cr1/__fixtures__/issuer_0_private_key.json', k1) - fs.writeFileSync('./src/cr1/__fixtures__/issuer_0_public_key.json', publicKeyContent) + fs.writeFileSync('./test/__fixtures__/issuer_0_private_key.json', k1) + fs.writeFileSync('./test/__fixtures__/issuer_0_public_key.json', publicKeyContent) }) it('application/cose-key', async () => { const k1 = await cr1.key.generate({ @@ -52,8 +52,8 @@ describe.skip('key generation', () => { const importedKey = await jose.importJWK(jwk) expect(importedKey).toBeDefined() const publicKeyCose = await cose.key.publicFromPrivate(coseKey) - fs.writeFileSync('./src/cr1/__fixtures__/holder_0_private_key.cbor', k1) - fs.writeFileSync('./src/cr1/__fixtures__/holder_0_public_key', cose.cbor.encode(publicKeyCose)) + fs.writeFileSync('./test/__fixtures__/holder_0_private_key.cbor', k1) + fs.writeFileSync('./test/__fixtures__/holder_0_public_key', cose.cbor.encode(publicKeyCose)) }) it('application/pkcs8', async () => { const k1 = await cr1.key.generate({ diff --git a/test/w3c-cr-1/1-credentials.test.ts b/test/w3c-cr-1/1-credentials.test.ts index 4184a17..422186e 100644 --- a/test/w3c-cr-1/1-credentials.test.ts +++ b/test/w3c-cr-1/1-credentials.test.ts @@ -3,7 +3,7 @@ import * as jose from 'jose' import * as cose from '@transmute/cose' import * as transmute from '../../src' -import * as fixtures from '../../src/cr1/__fixtures__' +import * as fixtures from '../../test/__fixtures__' const jws = { diff --git a/test/w3c-cr-1/2-presentations.test.ts b/test/w3c-cr-1/2-presentations.test.ts index 0e03c81..0e6525f 100644 --- a/test/w3c-cr-1/2-presentations.test.ts +++ b/test/w3c-cr-1/2-presentations.test.ts @@ -2,7 +2,7 @@ import * as jose from 'jose' import * as transmute from '../../src' import * as cose from '@transmute/cose' -import * as fixtures from '../../src/cr1/__fixtures__' +import * as fixtures from '../../test/__fixtures__' const coseSign1 = { sign: async (bytes: Uint8Array) => { diff --git a/test/w3c-cr-1/3-schema.test.ts b/test/w3c-cr-1/3-schema.test.ts index 5be62db..4829e65 100644 --- a/test/w3c-cr-1/3-schema.test.ts +++ b/test/w3c-cr-1/3-schema.test.ts @@ -3,8 +3,8 @@ import * as cose from '@transmute/cose' import * as transmute from '../../src' const privateKeyType = 'application/jwk+json' -const privateKeyContent = fs.readFileSync('./src/cr1/__fixtures__/issuer_0_private_key.json') -const publicKeyContent = fs.readFileSync('./src/cr1/__fixtures__/issuer_0_public_key.json') +const privateKeyContent = fs.readFileSync('./test/__fixtures__/issuer_0_private_key.json') +const publicKeyContent = fs.readFileSync('./test/__fixtures__/issuer_0_public_key.json') const coseSign1 = { sign: async (bytes: Uint8Array) => { diff --git a/test/w3c-cr-1/4-status.test.ts b/test/w3c-cr-1/4-status.test.ts index 9009876..453d667 100644 --- a/test/w3c-cr-1/4-status.test.ts +++ b/test/w3c-cr-1/4-status.test.ts @@ -3,8 +3,8 @@ import * as cose from '@transmute/cose' import * as transmute from '../../src' const privateKeyType = 'application/jwk+json' -const privateKeyContent = fs.readFileSync('./src/cr1/__fixtures__/issuer_0_private_key.json') -const publicKeyContent = fs.readFileSync('./src/cr1/__fixtures__/issuer_0_public_key.json') +const privateKeyContent = fs.readFileSync('./test/__fixtures__/issuer_0_private_key.json') +const publicKeyContent = fs.readFileSync('./test/__fixtures__/issuer_0_public_key.json') const coseSign1 = { sign: async (bytes: Uint8Array) => { diff --git a/test/w3c-cr-1/5-data-model.test.ts b/test/w3c-cr-1/5-data-model.test.ts index f2c686a..6b8b200 100644 --- a/test/w3c-cr-1/5-data-model.test.ts +++ b/test/w3c-cr-1/5-data-model.test.ts @@ -1,5 +1,5 @@ -import * as fixtures from "../../src/cr1/__fixtures__"; +import * as fixtures from "../../test/__fixtures__"; const { text, review } = fixtures @@ -182,7 +182,7 @@ credentialSubject: // https://www.w3.org/TR/2024/CRD-vc-data-model-2.0-20240205/#issuer // MUST BE: URL, or Object with ID that is URL describe("Issuer", () => { - it("can be object with id as url", async () => { + it("can be object with id as url", async () => { const validation = await review( text(` "@context": @@ -199,7 +199,7 @@ credentialSubject: expect(validation.warnings).toEqual([]); }) - it("can be url", async () => { + it("can be url", async () => { const validation = await review( text(` "@context": @@ -215,7 +215,7 @@ credentialSubject: expect(validation.warnings).toEqual([]); }) - it("gives warning if issuer not valid url", async () => { + it("gives warning if issuer not valid url", async () => { const validation = await review( text(` "@context": @@ -236,7 +236,7 @@ credentialSubject: }); }) - it("gives warning if issuer.id not valid url", async () => { + it("gives warning if issuer.id not valid url", async () => { const validation = await review( text(` "@context": @@ -262,7 +262,7 @@ credentialSubject: // https://www.w3.org/TR/2024/CRD-vc-data-model-2.0-20240205/#presentations-0 describe("Presentations", () => { describe("verifiableCredential", () => { - it("can be an array of enveloped credentials and credentials", async () => { + it("can be an array of enveloped credentials and credentials", async () => { const validation = await review( text(` "@context": @@ -293,7 +293,7 @@ describe("Presentations", () => { }]); }) - it("warns when non-object value is used", async () => { + it("warns when non-object value is used", async () => { const validation = await review( text(` "@context": @@ -314,7 +314,7 @@ describe("Presentations", () => { }]); }) - it("warns when enveloped credential id is not valid", async () => { + it("warns when enveloped credential id is not valid", async () => { const validation = await review( text(` "@context": @@ -352,7 +352,7 @@ describe("Presentations", () => { }) }); describe("holder", () => { - it("can be object with id as url", async () => { + it("can be object with id as url", async () => { const validation = await review( text(` "@context": @@ -365,8 +365,8 @@ describe("Presentations", () => { ); expect(validation.warnings).toEqual([]); }) - - it("can be url", async () => { + + it("can be url", async () => { const validation = await review( text(` "@context": @@ -378,8 +378,8 @@ describe("Presentations", () => { ); expect(validation.warnings).toEqual([]); }) - - it("gives warning if issuer not valid url", async () => { + + it("gives warning if issuer not valid url", async () => { const validation = await review( text(` "@context": @@ -396,8 +396,8 @@ describe("Presentations", () => { reference: 'https://www.w3.org/TR/vc-data-model-2.0/#presentations-0' }); }) - - it("gives warning if issuer.id not valid url", async () => { + + it("gives warning if issuer.id not valid url", async () => { const validation = await review( text(` "@context": @@ -416,7 +416,7 @@ describe("Presentations", () => { }); }) }); - + }); // it.todo('data model tests') From 7e2205c50727c8ea34fa1a64bfdcaad8ce541610 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 24 Apr 2024 17:07:21 -0500 Subject: [PATCH 4/7] better test example --- src/cr1/key/generate.ts | 2 +- .../jwt-product-passports/integration.test.ts | 217 ++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 test/jwt-product-passports/integration.test.ts diff --git a/src/cr1/key/generate.ts b/src/cr1/key/generate.ts index 65e60c0..cf77f35 100644 --- a/src/cr1/key/generate.ts +++ b/src/cr1/key/generate.ts @@ -17,7 +17,7 @@ export type RequestGenerateCredentialKey = { } -export const generate = async (req: RequestGenerateCredentialKey): Promise => { +export const generate = async (req: RequestGenerateCredentialKey): Promise => { if (req.type === 'application/jwk+json') { const obj = await cose.key.generate(req.alg, 'application/jwk+json') const text = JSON.stringify(obj, null, 2) diff --git a/test/jwt-product-passports/integration.test.ts b/test/jwt-product-passports/integration.test.ts new file mode 100644 index 0000000..82351d5 --- /dev/null +++ b/test/jwt-product-passports/integration.test.ts @@ -0,0 +1,217 @@ + +import * as jose from 'jose' +import moment from 'moment' + +import * as vc from '../../src' + +const alg = `ES256` +const statusListSize = 131072 +const revocationIndex = 94567 +const suspensionIndex = 23452 + +const issuer = `did:example:123` +const baseURL = `https://vendor.example/api/` + +describe('product passport', () => { + it('issue application/vc+ld+json+jwt using application/jwk+json', async () => { + const privateKey = await vc.key.generate({ + alg, + type: 'application/jwk+json' + }) + expect(vc.text.decoder.decode(privateKey).startsWith(`{ + "kid"`)).toBe(true) + const publicKey = await vc.key.publicFromPrivate({ + type: 'application/jwk+json', + content: privateKey + }) + const issued = await vc + .issuer({ + alg: 'ES256', + type: 'application/vc+ld+json+jwt', + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign( + bytes + ) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign(await vc.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey + })) + return vc.text.encoder.encode(jws) + } + } + }) + .issue({ + claimset: vc.text.encoder.encode(` +"@context": + - https://www.w3.org/ns/credentials/v2 + - https://www.w3.org/ns/credentials/examples/v2 + +id: ${baseURL}/credentials/3732 +type: + - VerifiableCredential + - ExampleDegreeCredential +issuer: + id: ${issuer} + name: "Example University" +validFrom: ${moment().toISOString()} +credentialSchema: + id: ${baseURL}/schemas/product-passport + type: JsonSchema +credentialStatus: + - id: ${baseURL}/credentials/status/3#${revocationIndex} + type: BitstringStatusListEntry + statusPurpose: revocation + statusListIndex: "${revocationIndex}" + statusListCredential: "${baseURL}/credentials/status/3" + - id: ${baseURL}/credentials/status/4#${suspensionIndex} + type: BitstringStatusListEntry + statusPurpose: suspension + statusListIndex: "${suspensionIndex}" + statusListCredential: "${baseURL}/credentials/status/4" +credentialSubject: + id: did:example:ebfeb1f712ebc6f1c276e12ec21 + degree: + type: ExampleBachelorDegree + subtype: Bachelor of Science and Arts +`), + }) + + const validated = await vc.validator({ + resolver: { + resolve: async ({ id, type, content }) => { + // Resolve external resources according to verifier policy + // In this case, we return inline exampes... + if (id === `${baseURL}/schemas/product-passport`) { + return { + type: `application/schema+json`, + content: vc.text.encoder.encode(` +{ + "$id": "${baseURL}/schemas/product-passport", + "title": "Example JSON Schema", + "description": "This is a test schema", + "type": "object", + "properties": { + "credentialSubject": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + } + } +} + `) + } + } + if (id === `${baseURL}/credentials/status/3`) { + return { + type: `application/vc+ld+json+jwt`, + content: await vc + .issuer({ + alg: 'ES384', + type: 'application/vc+ld+json+cose', + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign( + bytes + ) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign(await vc.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey + })) + return vc.text.encoder.encode(jws) + } + } + }) + .issue({ + claimset: vc.text.encoder.encode( + ` +"@context": + - https://www.w3.org/ns/credentials/v2 +id: ${baseURL}/status/3#list +type: + - VerifiableCredential + - BitstringStatusListCredential +issuer: + id: ${issuer} +validFrom: ${moment().toISOString()} +credentialSubject: + id: ${baseURL}/status/3#list#list + type: BitstringStatusList + statusPurpose: revocation + encodedList: ${await vc.status.bs(statusListSize).set(revocationIndex, false).encode()} +`.trim() + ) + }) + } + } + if (id === `${baseURL}/credentials/status/4`) { + return { + type: `application/vc+ld+json+jwt`, + content: await vc + .issuer({ + alg: 'ES384', + type: 'application/vc+ld+json+cose', + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign( + bytes + ) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign(await vc.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey + })) + return vc.text.encoder.encode(jws) + } + } + }) + .issue({ + claimset: vc.text.encoder.encode( + ` +"@context": + - https://www.w3.org/ns/credentials/v2 +id: ${baseURL}/status/4#list +type: + - VerifiableCredential + - BitstringStatusListCredential +issuer: + id: ${issuer} +validFrom: ${moment().toISOString()} +credentialSubject: + id: ${baseURL}/status/4#list#list + type: BitstringStatusList + statusPurpose: suspension + encodedList: ${await vc.status.bs(statusListSize).set(suspensionIndex, false).encode()} +`.trim() + ) + }) + } + } + if (content != undefined && type === `application/vc+ld+json+jwt`) { + const { kid } = jose.decodeProtectedHeader(vc.text.decoder.decode(content)) + // lookup public key on a resolver + if (kid === `did:example:123#key-42`) { + return { + type: "application/jwk+json", + content: publicKey + } + } + } + throw new Error('Resolver option not supported.') + } + } + }).validate({ + type: 'application/vc+ld+json+jwt', + content: issued, + }) + expect(validated.valid).toBe(true) + expect(validated.schema[`${baseURL}/schemas/product-passport`].valid).toBe(true) + expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`].valid).toBe(false) + expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`].valid).toBe(false) + }) +}) From 163fa5a37770d02a3ae204a9f4b8a446a386e09e Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 24 Apr 2024 17:15:47 -0500 Subject: [PATCH 5/7] default to latest json schema version --- src/cr1/validator/ajv.ts | 5 +++++ src/cr1/validator/index.ts | 5 +---- test/jwt-product-passports/integration.test.ts | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/cr1/validator/ajv.ts diff --git a/src/cr1/validator/ajv.ts b/src/cr1/validator/ajv.ts new file mode 100644 index 0000000..87200bc --- /dev/null +++ b/src/cr1/validator/ajv.ts @@ -0,0 +1,5 @@ +import Ajv from 'ajv/dist/2020' + +export const ajv = new Ajv({ + strict: false, +}) diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index cf5f13d..fc1e1cf 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -1,4 +1,3 @@ -import Ajv from 'ajv' import { RequestValidator, @@ -19,9 +18,7 @@ import { bs } from '../../cr1/status-list' import { conformance } from './w3c' -const ajv = new Ajv({ - strict: false, -}) +import { ajv } from "./ajv" export const validator = ({ resolver }: RequestValidator) => { return { diff --git a/test/jwt-product-passports/integration.test.ts b/test/jwt-product-passports/integration.test.ts index 82351d5..540d0b0 100644 --- a/test/jwt-product-passports/integration.test.ts +++ b/test/jwt-product-passports/integration.test.ts @@ -89,6 +89,7 @@ credentialSubject: content: vc.text.encoder.encode(` { "$id": "${baseURL}/schemas/product-passport", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Example JSON Schema", "description": "This is a test schema", "type": "object", From 3866a9e49982a69d2d7f69d7c67ac6d65f792392 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 24 Apr 2024 17:47:22 -0500 Subject: [PATCH 6/7] update readme --- README.md | 336 +++++++++++++++++- src/cr1/key/generate.ts | 8 +- src/cr1/types.ts | 5 + src/cr1/validator/index.ts | 7 +- .../jwt-product-passports/integration.test.ts | 117 ++++-- 5 files changed, 441 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 8f77e0f..08fe0ef 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,342 @@ npm i @transmute/verifiable-credentials@latest --save import * as transmute from "@transmute/verifiable-credentials"; ``` +### Generating Keys + +```ts +const privateKey = await transmute.key.generate({ + alg, + type: "application/jwk+json", +}); +// console.log(new TextDecoder().decode(privateKey)) +// { +// "kid": "xSgm4GQOT_ZyYFApew0GnRvPWt70omVJV9XVB5tsmN8", +// "alg": "ES256", +// "kty": "EC", +// "crv": "P-256", +// "x": "XRkZngz2KSCrLdXKGCRNyDzBgsovioZIqMWnF42nmdg", +// "y": "H2t6Xxdg8p8Cqn2-hsuWnXYj0192He4zTZghAxNXllo", +// ... +// } +const publicKey = await transmute.key.publicFromPrivate({ + type: "application/jwk+json", + content: privateKey, +}); +// console.log(new TextDecoder().decode(publicKey)) +// { +// "kid": "xSgm4GQOT_ZyYFApew0GnRvPWt70omVJV9XVB5tsmN8", +// "alg": "ES256", +// "kty": "EC", +// "crv": "P-256", +// "x": "XRkZngz2KSCrLdXKGCRNyDzBgsovioZIqMWnF42nmdg", +// "y": "H2t6Xxdg8p8Cqn2-hsuWnXYj0192He4zTZghAxNXllo", +// } +``` + +### Issuing Credentials + +```ts +const alg = `ES256`; +const statusListSize = 131072; +const revocationIndex = 94567; +const suspensionIndex = 23452; + +const issuer = `did:example:123`; +const baseURL = `https://vendor.example/api`; +const issued = await transmute + .issuer({ + alg, + type: "application/vc+ld+json+jwt", + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign(bytes) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign( + await transmute.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey, + }) + ); + return transmute.text.encoder.encode(jws); + }, + }, + }) + .issue({ + claimset: transmute.text.encoder.encode(` +"@context": + - https://www.w3.org/ns/credentials/v2 + - https://www.w3.org/ns/credentials/examples/v2 + +id: ${baseURL}/credentials/3732 +type: + - VerifiableCredential + - ExampleDegreeCredential +issuer: + id: ${issuer} + name: "Example University" +validFrom: ${moment().toISOString()} +credentialSchema: + id: ${baseURL}/schemas/product-passport + type: JsonSchema +credentialStatus: + - id: ${baseURL}/credentials/status/3#${revocationIndex} + type: BitstringStatusListEntry + statusPurpose: revocation + statusListIndex: "${revocationIndex}" + statusListCredential: "${baseURL}/credentials/status/3" + - id: ${baseURL}/credentials/status/4#${suspensionIndex} + type: BitstringStatusListEntry + statusPurpose: suspension + statusListIndex: "${suspensionIndex}" + statusListCredential: "${baseURL}/credentials/status/4" +credentialSubject: + id: did:example:ebfeb1f712ebc6f1c276e12ec21 + degree: + type: ExampleBachelorDegree + subtype: Bachelor of Science and Arts +`), + }); +// console.log(new TextDecoder().decode(issued)) +// eyJraWQiOiJkaWQ6ZXhhbXBsZToxMjMja2V5LTQyIiwiYWxnIjoiRVMyNTYifQ.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvdjIiLCJodHRwczovL3ZlbmRvci5leGFtcGxlL2FwaS9jb250ZXh0L3YyIl0sImlkIjoiaHR0cHM6Ly92ZW5kb3IuZXhhbXBsZS9hcGkvY3JlZGVudGlhbHMvMzczMiIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFeGFtcGxlRGVncmVlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJkaWQ6ZXhhbXBsZToxMjMiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5In0sInZhbGlkRnJvbSI6IjIwMjQtMDQtMjRUMjI6MjM6MDIuODU2WiIsImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL3ZlbmRvci5leGFtcGxlL2FwaS9zY2hlbWFzL3Byb2R1Y3QtcGFzc3BvcnQiLCJ0eXBlIjoiSnNvblNjaGVtYSJ9LCJjcmVkZW50aWFsU3RhdHVzIjpbeyJpZCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy8zIzk0NTY3IiwidHlwZSI6IkJpdHN0cmluZ1N0YXR1c0xpc3RFbnRyeSIsInN0YXR1c1B1cnBvc2UiOiJyZXZvY2F0aW9uIiwic3RhdHVzTGlzdEluZGV4IjoiOTQ1NjciLCJzdGF0dXNMaXN0Q3JlZGVudGlhbCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy8zIn0seyJpZCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy80IzIzNDUyIiwidHlwZSI6IkJpdHN0cmluZ1N0YXR1c0xpc3RFbnRyeSIsInN0YXR1c1B1cnBvc2UiOiJzdXNwZW5zaW9uIiwic3RhdHVzTGlzdEluZGV4IjoiMjM0NTIiLCJzdGF0dXNMaXN0Q3JlZGVudGlhbCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy80In1dLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSIsImRlZ3JlZSI6eyJ0eXBlIjoiRXhhbXBsZUJhY2hlbG9yRGVncmVlIiwic3VidHlwZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19.xHjfiUwx61qmoVMGLrHT8FI-ZYUHXQy4B6oF0Cb5EOTYYPXdwjW9sa1l5aa008xvsFvrcNats9TywmN2nNKz6A +``` + +### Validating Credentials + +```ts +const validated = await transmute + .validator({ + resolver: { + resolve: async ({ id, type, content }) => { + // Resolve external resources according to verifier policy + // In this case, we return inline exampes... + if (id === `${baseURL}/schemas/product-passport`) { + return { + type: `application/schema+json`, + content: transmute.text.encoder.encode(` +{ + "$id": "${baseURL}/schemas/product-passport", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Example JSON Schema", + "description": "This is a test schema", + "type": "object", + "properties": { + "credentialSubject": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + } + } +} + `), + }; + } + if (id === `${baseURL}/credentials/status/3`) { + return { + type: `application/vc+ld+json+jwt`, + content: await transmute + .issuer({ + alg: "ES384", + type: "application/vc+ld+json+cose", + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign(bytes) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign( + await transmute.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey, + }) + ); + return transmute.text.encoder.encode(jws); + }, + }, + }) + .issue({ + claimset: transmute.text.encoder.encode( + ` +"@context": + - https://www.w3.org/ns/credentials/v2 +id: ${baseURL}/status/3#list +type: + - VerifiableCredential + - BitstringStatusListCredential +issuer: + id: ${issuer} +validFrom: ${moment().toISOString()} +credentialSubject: + id: ${baseURL}/status/3#list#list + type: BitstringStatusList + statusPurpose: revocation + encodedList: ${await transmute.status + .bs(statusListSize) + .set(revocationIndex, false) + .encode()} +`.trim() + ), + }), + }; + } + if (id === `${baseURL}/credentials/status/4`) { + return { + type: `application/vc+ld+json+jwt`, + content: await transmute + .issuer({ + alg: "ES384", + type: "application/vc+ld+json+cose", + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign(bytes) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign( + await transmute.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey, + }) + ); + return transmute.text.encoder.encode(jws); + }, + }, + }) + .issue({ + claimset: transmute.text.encoder.encode( + ` +"@context": + - https://www.w3.org/ns/credentials/v2 +id: ${baseURL}/status/4#list +type: + - VerifiableCredential + - BitstringStatusListCredential +issuer: + id: ${issuer} +validFrom: ${moment().toISOString()} +credentialSubject: + id: ${baseURL}/status/4#list#list + type: BitstringStatusList + statusPurpose: suspension + encodedList: ${await transmute.status + .bs(statusListSize) + .set(suspensionIndex, false) + .encode()} +`.trim() + ), + }), + }; + } + if (content != undefined && type === `application/vc+ld+json+jwt`) { + const { kid } = jose.decodeProtectedHeader( + transmute.text.decoder.decode(content) + ); + // lookup public key by kid on a trusted resolver + if (kid === `did:example:123#key-42`) { + return { + type: "application/jwk+json", + content: publicKey, + }; + } + } + throw new Error("Resolver option not supported."); + }, + }, + }) + .validate({ + type: "application/vc+ld+json+jwt", + content: issued, + }); + +// expect(validated.valid).toBe(true) +// expect(validated.schema[`${baseURL}/schemas/product-passport`].valid).toBe(true) +// expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`].valid).toBe(false) +// expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`].valid).toBe(false) +``` + +### Issuing Presentations + +```ts +const presentation = await transmute + .holder({ + alg, + type: "application/vp+ld+json+jwt", + }) + .issue({ + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign(bytes) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign( + await transmute.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey, + }) + ); + return transmute.text.encoder.encode(jws); + }, + }, + presentation: { + "@context": ["https://www.w3.org/ns/credentials/v2"], + type: ["VerifiablePresentation"], + holder: `${baseURL}/holders/565049`, + // this part is built from disclosures without key binding below. + // "verifiableCredential": [{ + // "@context": "https://www.w3.org/ns/credentials/v2", + // "id": "data:application/vc+ld+json+sd-jwt;QzVjV...RMjU", + // "type": "EnvelopedVerifiableCredential" + // }] + }, + disclosures: [ + { + type: `application/vc+ld+json+jwt`, + credential: issued, + }, + ], + }); +``` + +### Validating Presentations + ```ts -// todo... +const validation = await transmute + .validator({ + resolver: { + resolve: async ({ type, content }) => { + // Resolve external resources according to verifier policy + // In this case, we return inline exampes... + if (content != undefined && type === `application/vp+ld+json+jwt`) { + const { kid } = jose.decodeProtectedHeader( + transmute.text.decoder.decode(content) + ); + // lookup public key on a resolver + if (kid === `did:example:123#key-42`) { + return { + type: "application/jwk+json", + content: publicKey, + }; + } + } + throw new Error("Resolver option not supported."); + }, + }, + }) + .validate({ + type: `application/vp+ld+json+jwt`, + content: presentation, + }); +// { +// "valid": true, +// "content": { +// "@context": [ +// "https://www.w3.org/ns/credentials/v2" +// ], +// "type": [ +// "VerifiablePresentation" +// ], +// "holder": "https://vendor.example/api/holders/565049", +// "verifiableCredential": [ +// { +// "@context": "https://www.w3.org/ns/credentials/v2", +// "id": "data:application/vc+ld+json+jwt;eyJraWQiOiJkaWQ6ZX... ``` ## Develop diff --git a/src/cr1/key/generate.ts b/src/cr1/key/generate.ts index cf77f35..4971399 100644 --- a/src/cr1/key/generate.ts +++ b/src/cr1/key/generate.ts @@ -17,15 +17,15 @@ export type RequestGenerateCredentialKey = { } -export const generate = async (req: RequestGenerateCredentialKey): Promise => { +export const generate = async (req: RequestGenerateCredentialKey): Promise => { if (req.type === 'application/jwk+json') { const obj = await cose.key.generate(req.alg, 'application/jwk+json') const text = JSON.stringify(obj, null, 2) - return encoder.encode(text) + return encoder.encode(text) as T } if (req.type === 'application/cose-key') { const result = await cose.key.generate(req.alg, 'application/cose-key') - return new Uint8Array(cose.cbor.encode(result)) + return new Uint8Array(cose.cbor.encode(result)) as T } if (req.type === 'application/pkcs8') { const result = await cose.certificate.root({ @@ -35,7 +35,7 @@ export const generate = async (req: RequestGenerateCredentialKey nbf: req.nbf || moment().toISOString(), // now exp: req.nbf || moment().add(5, 'minutes').toISOString() // in 5 minutes }) - return encoder.encode(result.private) + return encoder.encode(result.private) as T } throw new Error('Unsupported content type for private key') } \ No newline at end of file diff --git a/src/cr1/types.ts b/src/cr1/types.ts index 81a672f..6cda8d9 100644 --- a/src/cr1/types.ts +++ b/src/cr1/types.ts @@ -250,4 +250,9 @@ export type ValidationResult = { schema: Record status: Record warnings: ConformanceWarningMessage[] +} + + +export type TraceablePresentationValidationResult = ValidationResult & { + content: VerifiablePresentationWithHolderObject & VerifiablePresentationOfEnveloped } \ No newline at end of file diff --git a/src/cr1/validator/index.ts b/src/cr1/validator/index.ts index fc1e1cf..0ab52cf 100644 --- a/src/cr1/validator/index.ts +++ b/src/cr1/validator/index.ts @@ -7,7 +7,8 @@ import { BitstringStatusListCredential, ValidationResult, VerifiableCredential, - JsonSchemaError + JsonSchemaError, + TraceablePresentationValidationResult } from "../types" import { verifier } from "../verifier" @@ -22,7 +23,7 @@ import { ajv } from "./ajv" export const validator = ({ resolver }: RequestValidator) => { return { - validate: async ({ type, content }: SecuredContentType) => { + validate: async ({ type, content }: SecuredContentType) => { const verified = await verifier({ resolver }).verify({ type, content }) const validation: ValidationResult = { valid: true, @@ -82,7 +83,7 @@ export const validator = ({ resolver }: RequestValidator) => { } } } - return conformance(validation) + return conformance(validation) as T } } } \ No newline at end of file diff --git a/test/jwt-product-passports/integration.test.ts b/test/jwt-product-passports/integration.test.ts index 540d0b0..5e429c9 100644 --- a/test/jwt-product-passports/integration.test.ts +++ b/test/jwt-product-passports/integration.test.ts @@ -2,7 +2,7 @@ import * as jose from 'jose' import moment from 'moment' -import * as vc from '../../src' +import * as transmute from '../../src' const alg = `ES256` const statusListSize = 131072 @@ -10,23 +10,24 @@ const revocationIndex = 94567 const suspensionIndex = 23452 const issuer = `did:example:123` -const baseURL = `https://vendor.example/api/` +const baseURL = `https://vendor.example/api` describe('product passport', () => { it('issue application/vc+ld+json+jwt using application/jwk+json', async () => { - const privateKey = await vc.key.generate({ + const privateKey = await transmute.key.generate({ alg, type: 'application/jwk+json' }) - expect(vc.text.decoder.decode(privateKey).startsWith(`{ + + expect(transmute.text.decoder.decode(privateKey).startsWith(`{ "kid"`)).toBe(true) - const publicKey = await vc.key.publicFromPrivate({ + const publicKey = await transmute.key.publicFromPrivate({ type: 'application/jwk+json', content: privateKey }) - const issued = await vc + const issued = await transmute .issuer({ - alg: 'ES256', + alg, type: 'application/vc+ld+json+jwt', signer: { sign: async (bytes: Uint8Array) => { @@ -34,19 +35,19 @@ describe('product passport', () => { bytes ) .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) - .sign(await vc.key.importKeyLike({ + .sign(await transmute.key.importKeyLike({ type: "application/jwk+json", content: privateKey })) - return vc.text.encoder.encode(jws) + return transmute.text.encoder.encode(jws) } } }) .issue({ - claimset: vc.text.encoder.encode(` + claimset: transmute.text.encoder.encode(` "@context": - https://www.w3.org/ns/credentials/v2 - - https://www.w3.org/ns/credentials/examples/v2 + - ${baseURL}/context/v2 id: ${baseURL}/credentials/3732 type: @@ -78,7 +79,9 @@ credentialSubject: `), }) - const validated = await vc.validator({ + + + const validated = await transmute.validator({ resolver: { resolve: async ({ id, type, content }) => { // Resolve external resources according to verifier policy @@ -86,7 +89,7 @@ credentialSubject: if (id === `${baseURL}/schemas/product-passport`) { return { type: `application/schema+json`, - content: vc.text.encoder.encode(` + content: transmute.text.encoder.encode(` { "$id": "${baseURL}/schemas/product-passport", "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -110,7 +113,7 @@ credentialSubject: if (id === `${baseURL}/credentials/status/3`) { return { type: `application/vc+ld+json+jwt`, - content: await vc + content: await transmute .issuer({ alg: 'ES384', type: 'application/vc+ld+json+cose', @@ -120,16 +123,16 @@ credentialSubject: bytes ) .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) - .sign(await vc.key.importKeyLike({ + .sign(await transmute.key.importKeyLike({ type: "application/jwk+json", content: privateKey })) - return vc.text.encoder.encode(jws) + return transmute.text.encoder.encode(jws) } } }) .issue({ - claimset: vc.text.encoder.encode( + claimset: transmute.text.encoder.encode( ` "@context": - https://www.w3.org/ns/credentials/v2 @@ -144,7 +147,7 @@ credentialSubject: id: ${baseURL}/status/3#list#list type: BitstringStatusList statusPurpose: revocation - encodedList: ${await vc.status.bs(statusListSize).set(revocationIndex, false).encode()} + encodedList: ${await transmute.status.bs(statusListSize).set(revocationIndex, false).encode()} `.trim() ) }) @@ -153,7 +156,7 @@ credentialSubject: if (id === `${baseURL}/credentials/status/4`) { return { type: `application/vc+ld+json+jwt`, - content: await vc + content: await transmute .issuer({ alg: 'ES384', type: 'application/vc+ld+json+cose', @@ -163,16 +166,16 @@ credentialSubject: bytes ) .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) - .sign(await vc.key.importKeyLike({ + .sign(await transmute.key.importKeyLike({ type: "application/jwk+json", content: privateKey })) - return vc.text.encoder.encode(jws) + return transmute.text.encoder.encode(jws) } } }) .issue({ - claimset: vc.text.encoder.encode( + claimset: transmute.text.encoder.encode( ` "@context": - https://www.w3.org/ns/credentials/v2 @@ -187,14 +190,14 @@ credentialSubject: id: ${baseURL}/status/4#list#list type: BitstringStatusList statusPurpose: suspension - encodedList: ${await vc.status.bs(statusListSize).set(suspensionIndex, false).encode()} + encodedList: ${await transmute.status.bs(statusListSize).set(suspensionIndex, false).encode()} `.trim() ) }) } } if (content != undefined && type === `application/vc+ld+json+jwt`) { - const { kid } = jose.decodeProtectedHeader(vc.text.decoder.decode(content)) + const { kid } = jose.decodeProtectedHeader(transmute.text.decoder.decode(content)) // lookup public key on a resolver if (kid === `did:example:123#key-42`) { return { @@ -214,5 +217,71 @@ credentialSubject: expect(validated.schema[`${baseURL}/schemas/product-passport`].valid).toBe(true) expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`].valid).toBe(false) expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`].valid).toBe(false) + + + const vp = await transmute + .holder({ + alg, + type: 'application/vp+ld+json+jwt', + }) + .issue({ + signer: { + sign: async (bytes: Uint8Array) => { + const jws = await new jose.CompactSign( + bytes + ) + .setProtectedHeader({ kid: `${issuer}#key-42`, alg }) + .sign(await transmute.key.importKeyLike({ + type: "application/jwk+json", + content: privateKey + })) + return transmute.text.encoder.encode(jws) + } + }, + presentation: { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + ], + "type": ["VerifiablePresentation"], + holder: `${baseURL}/holders/565049`, + // this part is built from disclosures without key binding below. + // "verifiableCredential": [{ + // "@context": "https://www.w3.org/ns/credentials/v2", + // "id": "data:application/vc+ld+json+sd-jwt;QzVjV...RMjU", + // "type": "EnvelopedVerifiableCredential" + // }] + }, + disclosures: [ + { + type: `application/vc+ld+json+jwt`, + credential: issued + } + ] + }) + const presentation = await transmute.validator({ + resolver: { + resolve: async ({ type, content }) => { + // Resolve external resources according to verifier policy + // In this case, we return inline exampes... + if (content != undefined && type === `application/vp+ld+json+jwt`) { + const { kid } = jose.decodeProtectedHeader(transmute.text.decoder.decode(content)) + // lookup public key on a resolver + if (kid === `did:example:123#key-42`) { + return { + type: "application/jwk+json", + content: publicKey + } + } + } + throw new Error('Resolver option not supported.') + } + } + }) + .validate({ + type: `application/vp+ld+json+jwt`, + content: vp + }) + expect(presentation.content.holder).toBe('https://vendor.example/api/holders/565049') + expect(presentation.content.verifiableCredential[0].id.startsWith('data:application/vc+ld+json+jwt;')).toBe(true) }) }) From 8f038770f691bfdc6389087f5be7005eb5fc375e Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 24 Apr 2024 17:48:33 -0500 Subject: [PATCH 7/7] 0.2.1 --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0decfa9..b0f0b38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@transmute/verifiable-credentials", - "version": "0.2.0", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { "@transmute/cose": "^0.1.1", diff --git a/package.json b/package.json index 817ef2c..31d0ae9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.2.0", + "version": "0.2.1", "description": "An opinionated typescript library for w3c verifiable credentials.", "main": "./dist/index.js", "typings": "dist/index.d.ts", @@ -53,4 +53,4 @@ "uuid": "^9.0.1", "yaml": "^2.3.4" } -} \ No newline at end of file +}