From b8b1a17b9e7f37a4c0b73ccb465f2b2b5a3ad03d Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 15 Oct 2024 14:09:26 -0500 Subject: [PATCH 1/3] Update media types --- README.md | 26 +++--- package-lock.json | 4 +- package.json | 2 +- src/credential/issuer.ts | 10 +-- src/presentation/holder.ts | 10 +-- src/types.ts | 4 +- src/validator/w3c.ts | 2 +- src/verifier/verifier.ts | 14 +-- test/__fixtures__/claimset_1.yml | 2 +- test/__fixtures__/index.ts | 2 +- test/json-schema/better-schema-errors.test.ts | 6 +- test/json-schema/json-schema-tests.test.ts | 16 ++-- test/json-schema/json-schema-version.test.ts | 8 +- test/json-schema/no-dollar-id.test.ts | 8 +- .../optional-schema-validation.test.ts | 4 +- test/json-schema/sanity-tests.test.ts | 12 +-- test/jwt-e2e.test.ts | 88 +++++++++++++++++++ test/sanity/status-list.sanity.test.ts | 2 + test/w3c/1-credentials.test.ts | 12 +-- test/w3c/2-presentations.test.ts | 70 +++++++-------- test/w3c/3-schema.test.ts | 12 +-- test/w3c/4-status.test.ts | 40 ++++----- test/w3c/5-data-model.test.ts | 8 +- 23 files changed, 224 insertions(+), 138 deletions(-) create mode 100644 test/jwt-e2e.test.ts diff --git a/README.md b/README.md index bc14a22..e01b9fd 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ const issuerSigner = { const issued = await transmute .issuer({ alg, - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: issuerSigner, }) .issue({ @@ -173,11 +173,11 @@ const validated = await transmute } if (id === `${baseURL}/credentials/status/3`) { return { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, content: await transmute .issuer({ alg: "ES384", - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: issuerSigner, }) .issue({ @@ -207,11 +207,11 @@ credentialSubject: } if (id === `${baseURL}/credentials/status/4`) { return { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, content: await transmute .issuer({ alg: "ES384", - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: issuerSigner, }) .issue({ @@ -239,7 +239,7 @@ credentialSubject: }), }; } - if (content != undefined && type === `application/vc-ld+jwt`) { + if (content != undefined && type === `application/vc+jwt`) { const { kid } = jose.decodeProtectedHeader( transmute.text.decoder.decode(content) ); @@ -256,7 +256,7 @@ credentialSubject: }, }) .validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); @@ -272,7 +272,7 @@ credentialSubject: const presentation = await transmute .holder({ alg, - type: "application/vp-ld+jwt", + type: "application/vp+jwt", }) .issue({ signer: issuerSigner, @@ -283,13 +283,13 @@ const presentation = await transmute // this part is built from disclosures without key binding below. // "verifiableCredential": [{ // "@context": "https://www.w3.org/ns/credentials/v2", - // "id": "data:application/vc-ld+sd-jwt;QzVjV...RMjU", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", // "type": "EnvelopedVerifiableCredential" // }] }, disclosures: [ { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, credential: issued, }, ], @@ -305,7 +305,7 @@ const validation = await transmute 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+jwt`) { + if (content != undefined && type === `application/vp+jwt`) { const { kid } = jose.decodeProtectedHeader( transmute.text.decoder.decode(content) ); @@ -322,7 +322,7 @@ const validation = await transmute }, }) .validate({ - type: `application/vp-ld+jwt`, + type: `application/vp+jwt`, content: presentation, }); // { @@ -338,7 +338,7 @@ const validation = await transmute // "verifiableCredential": [ // { // "@context": "https://www.w3.org/ns/credentials/v2", -// "id": "data:application/vc-ld+jwt;eyJraWQiOiJkaWQ6ZX... +// "id": "data:application/vc+jwt;eyJraWQiOiJkaWQ6ZX... ``` ## Develop diff --git a/package-lock.json b/package-lock.json index 363e9ad..f20a48b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.3.3", + "version": "0.3.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@transmute/verifiable-credentials", - "version": "0.3.3", + "version": "0.3.4", "license": "Apache-2.0", "dependencies": { "@transmute/cose": "^0.2.7", diff --git a/package.json b/package.json index 46374b2..87fadd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@transmute/verifiable-credentials", - "version": "0.3.3", + "version": "0.3.4", "description": "An opinionated typescript library for w3c verifiable credentials.", "main": "./dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/credential/issuer.ts b/src/credential/issuer.ts index 3e1dd25..ef4d25b 100644 --- a/src/credential/issuer.ts +++ b/src/credential/issuer.ts @@ -41,8 +41,8 @@ const sdJwtCredentialIssuer = (issuer: RequestCredentialIssuer) => { const sdJwsDigester = await sd.digester() const sdIssuer = await sd.issuer({ alg: issuer.alg, - typ: 'application/vc-ld+sd-jwt', - cty: 'application/vc-ld', + typ: 'application/vc+sd-jwt', + cty: 'application/vc', salter: sdJwsSalter, digester: sdJwsDigester, signer: { @@ -62,11 +62,11 @@ const sdJwtCredentialIssuer = (issuer: RequestCredentialIssuer) => { } export const issuer = (issuer: RequestCredentialIssuer) => { - if (issuer.type === 'application/vc-ld+jwt') { + if (issuer.type === 'application/vc+jwt') { return jwtCredentialIssuer(issuer) - } else if (issuer.type === 'application/vc-ld+sd-jwt') { + } else if (issuer.type === 'application/vc+sd-jwt') { return sdJwtCredentialIssuer(issuer) - } else if (issuer.type === 'application/vc-ld+cose') { + } else if (issuer.type === 'application/vc+cose') { return coseSign1CredentialIssuer(issuer) } diff --git a/src/presentation/holder.ts b/src/presentation/holder.ts index f0f682f..b494e48 100644 --- a/src/presentation/holder.ts +++ b/src/presentation/holder.ts @@ -87,7 +87,7 @@ const sdJwtPresentationIssuer = (holder: RequestPresentationHolder) => { vp.verifiableCredential.push({ "@context": "https://www.w3.org/ns/credentials/v2", - id: `data:application/vc-ld+sd-jwt;${sdJwtFnard}`, // great job everyone. + id: `data:application/vc+sd-jwt;${sdJwtFnard}`, // great job everyone. type: "EnvelopedVerifiableCredential", }); } @@ -163,13 +163,13 @@ const unsecuredPresentationOfSecuredCredentials = ( }; export const holder = (holder: RequestPresentationHolder) => { - if (holder.type === "application/vp-ld+jwt") { + if (holder.type === "application/vp+jwt") { return jwtPresentationIssuer(holder); - } else if (holder.type === "application/vp-ld+sd-jwt") { + } else if (holder.type === "application/vp+sd-jwt") { return sdJwtPresentationIssuer(holder); - } else if (holder.type === "application/vp-ld+cose") { + } else if (holder.type === "application/vp+cose") { return coseSign1PresentationIssuer(holder); - } else if (holder.type === "application/vp-ld") { + } else if (holder.type === "application/vp") { return unsecuredPresentationOfSecuredCredentials(holder); } throw new Error("presentation type is not supported."); diff --git a/src/types.ts b/src/types.ts index f1bf3c5..84be2d5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,8 +7,8 @@ export type SupportedSignatureAlgorithms = 'ES256' | 'ES384' | 'ES512' export type SupportedKeyFormats = 'application/jwk+json' | 'application/cose-key' | 'application/pkcs8' -export type VcContentType = `application/vc-ld` -export type VpContentType = `application/vp-ld` +export type VcContentType = `application/vc` +export type VpContentType = `application/vp` export type Jwt = `${string}.${string}.${string}` export type SdJwt = `${Jwt}${string}~${string}` diff --git a/src/validator/w3c.ts b/src/validator/w3c.ts index e596338..ae8b90e 100644 --- a/src/validator/w3c.ts +++ b/src/validator/w3c.ts @@ -6,7 +6,7 @@ const identifierAlias = [ '/issuer' ] -const allowedProtocols = ['https'] +const allowedProtocols = ['https', 'data'] const isPointerToIdentifier = (pointer: string) => { return pointer.endsWith('/id') || identifierAlias.includes(pointer) diff --git a/src/verifier/verifier.ts b/src/verifier/verifier.ts index 5831655..83fd67c 100644 --- a/src/verifier/verifier.ts +++ b/src/verifier/verifier.ts @@ -128,22 +128,22 @@ export const verifier = ({ resolver }: RequestVerifier) => { return { verify: async ({ type, content, audience, nonce }: RequestVerify): Promise => { switch (type) { - case 'application/vc-ld+cose': - case 'application/vp-ld+cose': { + case 'application/vc+cose': + case 'application/vp+cose': { return verifyCoseSign1({ resolver }, { type, content, audience, nonce }) as T } - case 'application/vc-ld+jwt': - case 'application/vp-ld+jwt': + case 'application/vc+jwt': + case 'application/vp+jwt': case 'application/kb+jwt': { return verifyJwt({ resolver }, { type, content, audience, nonce }) as T } - case 'application/vc-ld+sd-jwt': { + case 'application/vc+sd-jwt': { return verifySdJwtCredential({ resolver }, { type, content, audience, nonce }) as T } - case 'application/vp-ld+sd-jwt': { + case 'application/vp+sd-jwt': { return verifySdJwtPresentation({ resolver }, { type, content, audience, nonce }) as T } - case 'application/vp-ld': { + case 'application/vp': { return verifyUnsecuredPresentation({ resolver }, { type, content, audience, nonce }) as T } default: { diff --git a/test/__fixtures__/claimset_1.yml b/test/__fixtures__/claimset_1.yml index 8c78bdd..2639d82 100644 --- a/test/__fixtures__/claimset_1.yml +++ b/test/__fixtures__/claimset_1.yml @@ -8,5 +8,5 @@ holder: id: "https://university.example/issuers/565049" verifiableCredential: - "@context": "https://www.w3.org/ns/credentials/v2" - id: "data:application/vc-ld+sd-jwt;QzVjV...RMjU" + id: "data:application/vc+sd-jwt;QzVjV...RMjU" type: EnvelopedVerifiableCredential diff --git a/test/__fixtures__/index.ts b/test/__fixtures__/index.ts index e812d78..0399b42 100644 --- a/test/__fixtures__/index.ts +++ b/test/__fixtures__/index.ts @@ -88,7 +88,7 @@ export const text = (claims: string) => { }; export const review = async (claimset: Uint8Array) => { - const type = "application/vc-ld+jwt"; + const type = "application/vc+jwt"; return validator({ resolver, }) diff --git a/test/json-schema/better-schema-errors.test.ts b/test/json-schema/better-schema-errors.test.ts index f090721..4da0ece 100644 --- a/test/json-schema/better-schema-errors.test.ts +++ b/test/json-schema/better-schema-errors.test.ts @@ -21,7 +21,7 @@ it("validate twice without error", async () => { const issued = await transmute .issuer({ alg, - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -97,7 +97,7 @@ credentialSubject: }; } - if (content != undefined && type === `application/vc-ld+jwt`) { + if (content != undefined && type === `application/vc+jwt`) { const { kid } = jose.decodeProtectedHeader( transmute.text.decoder.decode(content) ); @@ -114,7 +114,7 @@ credentialSubject: }, }); const validation1 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(validation1.verified).toBe(true); diff --git a/test/json-schema/json-schema-tests.test.ts b/test/json-schema/json-schema-tests.test.ts index 815c9e3..db6f887 100644 --- a/test/json-schema/json-schema-tests.test.ts +++ b/test/json-schema/json-schema-tests.test.ts @@ -29,7 +29,7 @@ describe("json schema tests", () => { const issued = await transmute .issuer({ alg, - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -114,11 +114,11 @@ credentialSubject: } if (id === `${baseURL}/credentials/status/3`) { return { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, content: await transmute .issuer({ alg: "ES384", - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -160,11 +160,11 @@ credentialSubject: } if (id === `${baseURL}/credentials/status/4`) { return { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, content: await transmute .issuer({ alg: "ES384", - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -204,7 +204,7 @@ credentialSubject: }), }; } - if (content != undefined && type === `application/vc-ld+jwt`) { + if (content != undefined && type === `application/vc+jwt`) { const { kid } = jose.decodeProtectedHeader( transmute.text.decoder.decode(content) ); @@ -222,13 +222,13 @@ credentialSubject: }); const valid1 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(valid1.verified).toBe(true); const valid2 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(valid2.verified).toBe(true); diff --git a/test/json-schema/json-schema-version.test.ts b/test/json-schema/json-schema-version.test.ts index 03ce4ce..e847ec5 100644 --- a/test/json-schema/json-schema-version.test.ts +++ b/test/json-schema/json-schema-version.test.ts @@ -24,7 +24,7 @@ describe("json schema tests", () => { issued = await transmute .issuer({ alg, - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -92,7 +92,7 @@ credentialSubject: `), }; } - if (content != undefined && type === `application/vc-ld+jwt`) { + if (content != undefined && type === `application/vc+jwt`) { return { type: "application/jwk+json", content: publicKey, @@ -104,12 +104,12 @@ credentialSubject: }); // call valdiate twice for sanity const valid1 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(valid1.verified).toBe(true); const valid2 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(valid2.verified).toBe(true); diff --git a/test/json-schema/no-dollar-id.test.ts b/test/json-schema/no-dollar-id.test.ts index 0f461c5..f1315ee 100644 --- a/test/json-schema/no-dollar-id.test.ts +++ b/test/json-schema/no-dollar-id.test.ts @@ -24,7 +24,7 @@ describe("json schema tests", () => { issued = await transmute .issuer({ alg, - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -90,7 +90,7 @@ credentialSubject: `), }; } - if (content != undefined && type === `application/vc-ld+jwt`) { + if (content != undefined && type === `application/vc+jwt`) { return { type: "application/jwk+json", content: publicKey, @@ -102,12 +102,12 @@ credentialSubject: }); // call valdiate twice for sanity const valid1 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(valid1.verified).toBe(true); const valid2 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(valid2.verified).toBe(true); diff --git a/test/json-schema/optional-schema-validation.test.ts b/test/json-schema/optional-schema-validation.test.ts index eeb7d2e..cb03f87 100644 --- a/test/json-schema/optional-schema-validation.test.ts +++ b/test/json-schema/optional-schema-validation.test.ts @@ -22,7 +22,7 @@ beforeAll(async () => { issued = await transmute .issuer({ alg, - type: "application/vc-ld+jwt", + type: "application/vc+jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes) @@ -81,7 +81,7 @@ it("can disable schema validation", async () => { }, }); const validation1 = await validator.validate({ - type: "application/vc-ld+jwt", + type: "application/vc+jwt", content: issued, }); expect(validation1.verified).toBe(true); diff --git a/test/json-schema/sanity-tests.test.ts b/test/json-schema/sanity-tests.test.ts index 7ee47b1..961c561 100644 --- a/test/json-schema/sanity-tests.test.ts +++ b/test/json-schema/sanity-tests.test.ts @@ -12,7 +12,7 @@ const createTestCase = async (claimset: string, schema: string, publicKey: any, const issued = await transmute .issuer({ alg, - type: "application/vc-ld+sd-jwt", + type: "application/vc+sd-jwt", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes).setProtectedHeader({ kid: `did:example:123#key-42`, alg }).sign( @@ -45,11 +45,11 @@ const createTestCase = async (claimset: string, schema: string, publicKey: any, } if (id === `https://vendor.example/credentials/status/3`) { return { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, content: await transmute .issuer({ alg: "ES384", - type: "application/vc-ld+cose", + type: "application/vc+cose", signer: { sign: async (bytes: Uint8Array) => { const jws = await new jose.CompactSign(bytes).setProtectedHeader({ kid: `did:example:123#key-42`, alg }).sign( @@ -84,13 +84,13 @@ credentialSubject: }), }; } - if (content != undefined && type === `application/vc-ld+sd-jwt`) { + if (content != undefined && type === `application/vc+sd-jwt`) { return { type: "application/jwk+json", content: publicKey, }; } - if (content != undefined && type === `application/vc-ld+jwt`) { + if (content != undefined && type === `application/vc+jwt`) { return { type: "application/jwk+json", content: publicKey, @@ -103,7 +103,7 @@ credentialSubject: }); // call valdiate twice for sanity const valid1 = await validator.validate({ - type: "application/vc-ld+sd-jwt", + type: "application/vc+sd-jwt", content: issued, }); return valid1; diff --git a/test/jwt-e2e.test.ts b/test/jwt-e2e.test.ts new file mode 100644 index 0000000..2dd093a --- /dev/null +++ b/test/jwt-e2e.test.ts @@ -0,0 +1,88 @@ + +import * as jose from 'jose' +import * as transmute from '../src' +import * as fixtures from '../test/__fixtures__' + +it('application/vp+jwt', async () => { + const privateKey = await transmute.key.generate({ + type: 'application/jwk+json', + alg: 'ES256' + }) + const publicKey = await transmute.key.publicFromPrivate({ + type: 'application/jwk+json', + content: privateKey + }) + const signer = { + sign: async (bytes: Uint8Array) => { + return transmute.text.encoder.encode(await new jose.CompactSign( + bytes + ) + .setProtectedHeader({ kid: 'key-42', alg: 'ES256' }) + .sign(await transmute.key.importKeyLike({ + type: 'application/jwk+json', + content: privateKey + }))) + } + } + const vp = await transmute + .holder({ + alg: 'ES384', + type: 'application/vp+jwt', + }) + .issue({ + signer, + // vp of enveloped + presentation: { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + ], + "type": ["VerifiablePresentation"], + holder: "https://university.example/issuers/565049", + // this part is built from disclosures without key binding below. + // "verifiableCredential": [{ + // "@context": "https://www.w3.org/ns/credentials/v2", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", + // "type": "EnvelopedVerifiableCredential" + // }] + }, + disclosures: [ + { + type: `application/vc+jwt`, + credential: await transmute + .issuer({ + alg: 'ES384', // 🔥 remove me from this layer. + type: `application/vc+jwt`, // expand cty everywhere for readability + signer + }) + .issue({ + claimset: fixtures.claimset_0, + }) + } + ] + }) + const validated = await transmute. + validator({ + resolver: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + resolve: async ({ id, type, content }: any) => { + // ignore hints about message + // return the same public key for tests + return { + type: 'application/jwk+json', + content: publicKey + } + } + } + }) + .validate({ + type: 'application/vp+jwt', + content: vp + }) + expect(validated.verified).toBe(true) + expect(validated.content.holder).toBe('https://university.example/issuers/565049') + expect(validated.content.verifiableCredential[0].id.startsWith('data:application/vc+jwt;')).toBe(true) + const token = validated.content.verifiableCredential[0].id.split('vc+jwt;').pop() as string + const decoded = jose.decodeJwt(token) + expect(decoded['@context']).toBeDefined() + expect(validated.warnings.length).toBe(0) +}) \ No newline at end of file diff --git a/test/sanity/status-list.sanity.test.ts b/test/sanity/status-list.sanity.test.ts index ffca809..95ddc80 100644 --- a/test/sanity/status-list.sanity.test.ts +++ b/test/sanity/status-list.sanity.test.ts @@ -8,6 +8,8 @@ describe('Status List', () => { const list2 = await status.bs(8).set(2, true).encode(); expect(list2).toBe('H4sIAAAAAAAAA1MAAEXPbOkBAAAA') + const statusSet = status.bs('H4sIAAAAAAAAA-3OMQ0AAAgDsOHfNBp2kZBWQRMAAAAAAAAAAAAAAL6Z6wAAAAAAtQVQdb5gAEAAAA').get(11924) + expect(statusSet).toBe(false) }) it('create claimset', async () => { diff --git a/test/w3c/1-credentials.test.ts b/test/w3c/1-credentials.test.ts index b904e50..1736e3c 100644 --- a/test/w3c/1-credentials.test.ts +++ b/test/w3c/1-credentials.test.ts @@ -53,8 +53,8 @@ const jwk: transmute.VerifierResolver = { } describe('COSE Sign1 based W3C Verifiable Credentials', () => { - it('application/vc-ld+cose', async () => { - const type = 'application/vc-ld+cose' + it('application/vc+cose', async () => { + const type = 'application/vc+cose' const vc = await transmute .issuer({ alg: 'ES384', @@ -77,8 +77,8 @@ describe('COSE Sign1 based W3C Verifiable Credentials', () => { }) describe('JWT based W3C Verifiable Credentials', () => { - it('application/vc-ld+jwt', async () => { - const type = 'application/vc-ld+jwt' + it('application/vc+jwt', async () => { + const type = 'application/vc+jwt' const vc = await transmute .issuer({ alg: 'ES384', @@ -101,8 +101,8 @@ describe('JWT based W3C Verifiable Credentials', () => { }) describe('SD-JWT based W3C Verifiable Credentials', () => { - it('application/vc-ld+sd-jwt', async () => { - const type = 'application/vc-ld+sd-jwt' + it('application/vc+sd-jwt', async () => { + const type = 'application/vc+sd-jwt' const vc = await transmute .issuer({ // 🔥 implication is that both alg and kid do not belong at this layer... diff --git a/test/w3c/2-presentations.test.ts b/test/w3c/2-presentations.test.ts index a3e20e1..0d1c765 100644 --- a/test/w3c/2-presentations.test.ts +++ b/test/w3c/2-presentations.test.ts @@ -52,8 +52,8 @@ const resolver: transmute.VerifierResolver = { describe('Unsecured W3C Verifiable Presentations', () => { // unsecured VP, with disclosure of SD-JWT VC without key binding - it('application/vp-ld', async () => { - const type = 'application/vp-ld' + it('application/vp', async () => { + const type = 'application/vp' const vp = await transmute .holder({ @@ -71,16 +71,16 @@ describe('Unsecured W3C Verifiable Presentations', () => { // this part is built from disclosures without key binding below. // "verifiableCredential": [{ // "@context": "https://www.w3.org/ns/credentials/v2", - // "id": "data:application/vc-ld+sd-jwt;QzVjV...RMjU", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", // "type": "EnvelopedVerifiableCredential" // }] }, disclosures: [{ - type: `application/vc-ld+sd-jwt`, + type: `application/vc+sd-jwt`, credential: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+sd-jwt', + type: 'application/vc+sd-jwt', signer: jws }) .issue({ @@ -91,11 +91,11 @@ describe('Unsecured W3C Verifiable Presentations', () => { disclosure: fixtures.claimset_0, }, { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, credential: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+jwt', + type: 'application/vc+jwt', signer: jws }) .issue({ @@ -112,15 +112,15 @@ describe('Unsecured W3C Verifiable Presentations', () => { content: vp }) expect(verified.holder).toBe('https://university.example/issuers/565049') - expect(verified.verifiableCredential[0].id.startsWith('data:application/vc-ld+sd-jwt;ey')).toBe(true) - expect(verified.verifiableCredential[1].id.startsWith('data:application/vc-ld+jwt;ey')).toBe(true) + expect(verified.verifiableCredential[0].id.startsWith('data:application/vc+sd-jwt;ey')).toBe(true) + expect(verified.verifiableCredential[1].id.startsWith('data:application/vc+jwt;ey')).toBe(true) }) }) describe('COSE Sign1 based W3C Verifiable Presentations', () => { - it('application/vp-ld+cose', async () => { - const type = 'application/vp-ld+cose' + it('application/vp+cose', async () => { + const type = 'application/vp+cose' const vc = await transmute .holder({ alg: 'ES384', @@ -137,18 +137,18 @@ describe('COSE Sign1 based W3C Verifiable Presentations', () => { // this part is built from disclosures without key binding below. // "verifiableCredential": [{ // "@context": "https://www.w3.org/ns/credentials/v2", - // "id": "data:application/vc-ld+sd-jwt;QzVjV...RMjU", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", // "type": "EnvelopedVerifiableCredential" // }] }, disclosures: [ { - type: `application/vc-ld+cose`, + type: `application/vc+cose`, credential: await transmute .issuer({ // 🔥 remove alg from this layer alg: 'ES384', - type: `application/vc-ld+cose`, // expand cty everywhere for readability + type: `application/vc+cose`, // expand cty everywhere for readability signer: coseSign1 }) .issue({ @@ -169,14 +169,14 @@ describe('COSE Sign1 based W3C Verifiable Presentations', () => { expect(verified.holder).toBe('https://university.example/issuers/565049') - expect(verified.verifiableCredential[0].id.startsWith('data:application/vc-ld+cose;')).toBe(true) + expect(verified.verifiableCredential[0].id.startsWith('data:application/vc+cose;')).toBe(true) }) }) describe('JWT based W3C Verifiable Presentations', () => { - it('application/vp-ld+jwt', async () => { - const type = 'application/vp-ld+jwt' + it('application/vp+jwt', async () => { + const type = 'application/vp+jwt' const vp = await transmute .holder({ alg: 'ES384', @@ -194,17 +194,17 @@ describe('JWT based W3C Verifiable Presentations', () => { // this part is built from disclosures without key binding below. // "verifiableCredential": [{ // "@context": "https://www.w3.org/ns/credentials/v2", - // "id": "data:application/vc-ld+sd-jwt;QzVjV...RMjU", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", // "type": "EnvelopedVerifiableCredential" // }] }, disclosures: [ { - type: `application/vc-ld+jwt`, + type: `application/vc+jwt`, credential: await transmute .issuer({ alg: 'ES384', // 🔥 remove me from this layer. - type: `application/vc-ld+jwt`, // expand cty everywhere for readability + type: `application/vc+jwt`, // expand cty everywhere for readability signer: jws }) .issue({ @@ -222,15 +222,15 @@ describe('JWT based W3C Verifiable Presentations', () => { content: vp }) expect(verified.holder).toBe('https://university.example/issuers/565049') - expect(verified.verifiableCredential[0].id.startsWith('data:application/vc-ld+jwt;')).toBe(true) + expect(verified.verifiableCredential[0].id.startsWith('data:application/vc+jwt;')).toBe(true) }) }) describe('SD-JWT based W3C Verifiable Presentations', () => { - it('application/vp-ld+sd-jwt (without key binding)', async () => { + it('application/vp+sd-jwt (without key binding)', async () => { // this content type always implies an sd-jwt secured json-ld object (vp) contain enveloped Fnards. - const type = 'application/vp-ld+sd-jwt' + const type = 'application/vp+sd-jwt' const vp = await transmute .holder({ alg: 'ES384', // renmove me when possible @@ -250,18 +250,18 @@ describe('SD-JWT based W3C Verifiable Presentations', () => { // this part is built from disclosures with or without key binding below. // "verifiableCredential": [{ // "@context": "https://www.w3.org/ns/credentials/v2", - // "id": "data:application/vc-ld+sd-jwt;QzVjV...RMjU", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", // "type": "EnvelopedVerifiableCredential" // }] }, disclosures: [{ - type: `application/vc-ld+sd-jwt`, + type: `application/vc+sd-jwt`, audience: undefined, nonce: undefined, credential: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+sd-jwt', + type: 'application/vc+sd-jwt', signer: jws }) .issue({ @@ -282,14 +282,14 @@ describe('SD-JWT based W3C Verifiable Presentations', () => { content: vp }) expect(verified.holder.id).toBe('https://university.example/issuers/565049') - expect(verified.verifiableCredential[0].id.startsWith('data:application/vc-ld+sd-jwt;ey')).toBe(true) + expect(verified.verifiableCredential[0].id.startsWith('data:application/vc+sd-jwt;ey')).toBe(true) }) - it('application/vp-ld+sd-jwt (with key binding)', async () => { + it('application/vp+sd-jwt (with key binding)', async () => { // dislosable claimset will need to be updated // every time the test keys change. // console.log(sd.YAML.dumps(await cose.key.convertCoseKeyToJsonWebKey(await cose.cbor.decode(publicKeyContent)))) - const type = 'application/vp-ld+sd-jwt' + const type = 'application/vp+sd-jwt' const vp = await transmute .holder({ alg: 'ES384', @@ -312,17 +312,17 @@ describe('SD-JWT based W3C Verifiable Presentations', () => { // this part is built from disclosures with or without key binding below. // "verifiableCredential": [{ // "@context": "https://www.w3.org/ns/credentials/v2", - // "id": "data:application/vc-ld+sd-jwt;QzVjV...RMjU", + // "id": "data:application/vc+sd-jwt;QzVjV...RMjU", // "type": "EnvelopedVerifiableCredential" // }] }, disclosures: [{ - type: `application/vc-ld+sd-jwt`, + type: `application/vc+sd-jwt`, // internal params credential: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+sd-jwt', + type: 'application/vc+sd-jwt', signer: jws }) .issue({ @@ -351,17 +351,17 @@ describe('SD-JWT based W3C Verifiable Presentations', () => { nonce: 'nonce-456', }) expect(verified.holder.id).toBe('https://university.example/issuers/565049') - expect(verified.verifiableCredential[0].id.startsWith('data:application/vc-ld+sd-jwt;ey')).toBe(true) + expect(verified.verifiableCredential[0].id.startsWith('data:application/vc+sd-jwt;ey')).toBe(true) // ok now verify the nested vc as well. - const envelopedVc = verified.verifiableCredential[0].id.replace('data:application/vc-ld+sd-jwt;', '') + const envelopedVc = verified.verifiableCredential[0].id.replace('data:application/vc+sd-jwt;', '') const verified2 = await transmute. verifier({ resolver }) .verify({ // this content type always implies an sd-jwt secured json-ld object (vp) contain enveloped Fnards. - type: 'application/vc-ld+sd-jwt', + type: 'application/vc+sd-jwt', content: transmute.text.encoder.encode(envelopedVc), audience: 'aud-123', nonce: 'nonce-456', diff --git a/test/w3c/3-schema.test.ts b/test/w3c/3-schema.test.ts index fc60140..cd97c1d 100644 --- a/test/w3c/3-schema.test.ts +++ b/test/w3c/3-schema.test.ts @@ -32,7 +32,7 @@ describe('JSON Schema Validator for W3C Verifiable Credentials', () => { resolver: { // eslint-disable-next-line @typescript-eslint/no-unused-vars resolve: async ({ id, type, content }) => { - if (type === 'application/vc-ld+cose') { + if (type === 'application/vc+cose') { return { type: privateKeyType, content: publicKeyContent @@ -66,11 +66,11 @@ describe('JSON Schema Validator for W3C Verifiable Credentials', () => { } }) .validate({ - type: 'application/vc-ld+cose', + type: 'application/vc+cose', content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -98,7 +98,7 @@ credentialSubject: resolver: { // eslint-disable-next-line @typescript-eslint/no-unused-vars resolve: async ({ id, type, content }) => { - if (type === 'application/vc-ld+cose') { + if (type === 'application/vc+cose') { return { type: privateKeyType, content: publicKeyContent @@ -132,11 +132,11 @@ credentialSubject: } }) .validate({ - type: 'application/vc-ld+cose', + type: 'application/vc+cose', content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ diff --git a/test/w3c/4-status.test.ts b/test/w3c/4-status.test.ts index 4dc7e4b..b2c792d 100644 --- a/test/w3c/4-status.test.ts +++ b/test/w3c/4-status.test.ts @@ -38,11 +38,11 @@ describe('Bitstring Status List Credential Validator for W3C Verifiable Credenti // instead of content for some cases... if (id === 'https://example.com/credentials/status/3') { return { - type: `application/vc-ld+cose`, + type: `application/vc+cose`, content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -60,7 +60,7 @@ describe('Bitstring Status List Credential Validator for W3C Verifiable Credenti // public key for credential with status // normally we would look at protected header // in content here, this is just for testing - if (type === 'application/vc-ld+cose') { + if (type === 'application/vc+cose') { return { type: privateKeyType, content: publicKeyContent @@ -71,11 +71,11 @@ describe('Bitstring Status List Credential Validator for W3C Verifiable Credenti } }) .validate({ - type: 'application/vc-ld+cose', + type: 'application/vc+cose', content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -131,11 +131,11 @@ credentialSubject: ` return { - type: `application/vc-ld+cose`, + type: `application/vc+cose`, content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -148,7 +148,7 @@ credentialSubject: // public key for credential with status // normally we would look at protected header // in content here, this is just for testing - if (type === 'application/vc-ld+cose') { + if (type === 'application/vc+cose') { return { type: privateKeyType, content: publicKeyContent @@ -159,11 +159,11 @@ credentialSubject: } }) .validate({ - type: 'application/vc-ld+cose', + type: 'application/vc+cose', content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -203,11 +203,11 @@ credentialSubject: // instead of content for some cases... if (id === 'https://example.com/credentials/status/3') { return { - type: `application/vc-ld+cose`, + type: `application/vc+cose`, content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -225,7 +225,7 @@ credentialSubject: // public key for credential with status // normally we would look at protected header // in content here, this is just for testing - if (type === 'application/vc-ld+cose') { + if (type === 'application/vc+cose') { return { type: privateKeyType, content: publicKeyContent @@ -236,11 +236,11 @@ credentialSubject: } }) .validate({ - type: 'application/vc-ld+cose', + type: 'application/vc+cose', content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -296,11 +296,11 @@ credentialSubject: ` return { - type: `application/vc-ld+cose`, + type: `application/vc+cose`, content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ @@ -313,7 +313,7 @@ credentialSubject: // public key for credential with status // normally we would look at protected header // in content here, this is just for testing - if (type === 'application/vc-ld+cose') { + if (type === 'application/vc+cose') { return { type: privateKeyType, content: publicKeyContent @@ -324,11 +324,11 @@ credentialSubject: } }) .validate({ - type: 'application/vc-ld+cose', + type: 'application/vc+cose', content: await transmute .issuer({ alg: 'ES384', - type: 'application/vc-ld+cose', + type: 'application/vc+cose', signer: coseSign1 }) .issue({ diff --git a/test/w3c/5-data-model.test.ts b/test/w3c/5-data-model.test.ts index 15168f5..fad5268 100644 --- a/test/w3c/5-data-model.test.ts +++ b/test/w3c/5-data-model.test.ts @@ -258,7 +258,7 @@ describe("Presentations", () => { id: https://university.example/issuers/565049 verifiableCredential: - "@context": https://www.w3.org/ns/credentials/v2 - id: data:application/vc-ld+sd-jwt;QzVjV...RMjU + id: data:application/vc+sd-jwt;QzVjV...RMjU type: EnvelopedVerifiableCredential - "@context": - https://www.w3.org/ns/credentials/v2 @@ -271,11 +271,7 @@ describe("Presentations", () => { - id: https://university.example/issuers/2 `) ); - expect(validation.warnings).toEqual([{ - message: 'Identifier will not be well understood: data:application/vc-ld+sd-jwt;QzVjV...RMjU', - pointer: '/verifiableCredential/0/id', - reference: 'https://www.w3.org/TR/2024/CRD-vc-data-model-2.0-20240205/#identifiers' - }]); + expect(validation.warnings).toEqual([]); }) it("warns when non-object value is used", async () => { From 8cfccf3747881745a389e88f61e6617a5d7acaa4 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 15 Oct 2024 14:09:47 -0500 Subject: [PATCH 2/3] more readable bits --- src/status-list/StatusList/Bitstring.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/status-list/StatusList/Bitstring.ts b/src/status-list/StatusList/Bitstring.ts index d67aead..698216e 100644 --- a/src/status-list/StatusList/Bitstring.ts +++ b/src/status-list/StatusList/Bitstring.ts @@ -109,9 +109,21 @@ export class Bitstring { assert.isNumber(position, 'position') const { length, leftToRightIndexing } = this const { index, bit } = _parsePosition(position, length, leftToRightIndexing) - return !!(this.bits[index] & bit) + const actualBitSet = this.bits[index] & bit % 8 + // Let bitstring be a list of bits with a minimum size of 16KB, where each bit is initialized to 0 (zero). + // .... + // When a single bit specifies a status, such as "revoked" or "suspended", + // then that status is expected to be true when the bit is set (1) and false when unset (0). + if (actualBitSet === 1) { + return true + } + if (actualBitSet === 0) { + return false + } + throw new Error('Invalid bit') } + async encodeBits() { return base64url.encode(gzip(this.bits)) } @@ -146,6 +158,10 @@ function _parsePosition( const index = Math.floor(position / 8) const rem = position % 8 const shift = leftToRightIndexing ? 7 - rem : rem - const bit = 1 << shift + + // When a single bit specifies a status, such as "revoked" or "suspended", + // then that status is expected to be true when the bit is set (1) and false when unset (0). + const bit = (1 << shift) + // the real bit value is bit % 8 return { index, bit } } From d42aa073ec1ce78113658a6229fd15ba3b7ee9f6 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 15 Oct 2024 14:11:33 -0500 Subject: [PATCH 3/3] Update src/status-list/StatusList/Bitstring.ts --- src/status-list/StatusList/Bitstring.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/status-list/StatusList/Bitstring.ts b/src/status-list/StatusList/Bitstring.ts index 698216e..efdd8cb 100644 --- a/src/status-list/StatusList/Bitstring.ts +++ b/src/status-list/StatusList/Bitstring.ts @@ -162,6 +162,5 @@ function _parsePosition( // When a single bit specifies a status, such as "revoked" or "suspended", // then that status is expected to be true when the bit is set (1) and false when unset (0). const bit = (1 << shift) - // the real bit value is bit % 8 return { index, bit } }