diff --git a/package-lock.json b/package-lock.json index a94595314..3c4d6eb52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15296,9 +15296,9 @@ "license": "Apache-2.0", "dependencies": { "@sphereon/pex": "2.1.0", - "@web5/common": "0.2.2", - "@web5/crypto": "0.2.4", - "@web5/dids": "0.2.4" + "@web5/common": "0.2.3", + "@web5/crypto": "0.4.0", + "@web5/dids": "0.4.0" }, "devDependencies": { "@playwright/test": "1.40.1", @@ -15326,36 +15326,6 @@ "node": ">=18.0.0" } }, - "packages/credentials/node_modules/@noble/ciphers": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.0.tgz", - "integrity": "sha512-xaUaUUDWbHIFSxaQ/pIe+33VG2mfJp6N/KxKLmZr5biWdNznCAmfu24QRhX10BbVAuqOahAoyp0S4M9md6GPDw==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "packages/credentials/node_modules/@typescript-eslint/parser": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", @@ -15424,109 +15394,6 @@ } } }, - "packages/credentials/node_modules/@web5/common": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@web5/common/-/common-0.2.2.tgz", - "integrity": "sha512-dRn6SmALExeTLMTK/W5ozGarfaddK+Lraf5OjuIGLAaLfcX1RWx3oDMoY5Hr9LjfxHJC8mGXB8DnKflbeYJRgA==", - "dependencies": { - "level": "8.0.0", - "multiformats": "11.0.2", - "readable-stream": "4.4.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/credentials/node_modules/@web5/crypto": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.4.tgz", - "integrity": "sha512-heRUuV10mZ04dWp1C2mNF/EEPw8nnRe+yAXvmclJ+4XUHL6+mY7j+hjYOTKUAQzd4ouvbHrpJM0uYcUntA3AeA==", - "dependencies": { - "@noble/ciphers": "0.4.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@web5/common": "0.2.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/credentials/node_modules/@web5/dids": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@web5/dids/-/dids-0.2.4.tgz", - "integrity": "sha512-e+m+xgpiM8ydTJgWcPdwmjILLMZYdl2kwahlO22mK0azSKVrg1klpGrUODzqkrWrQ5O0tnOyqEy39FcD5Sy11w==", - "dependencies": { - "@decentralized-identity/ion-pow-sdk": "1.0.17", - "@decentralized-identity/ion-sdk": "1.0.1", - "@web5/common": "0.2.2", - "@web5/crypto": "0.2.2", - "did-resolver": "4.1.0", - "dns-packet": "5.6.1", - "level": "8.0.0", - "ms": "2.1.3", - "pkarr": "1.1.1", - "z32": "1.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/credentials/node_modules/@web5/dids/node_modules/@noble/ciphers": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", - "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@web5/dids/node_modules/@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", - "dependencies": { - "@noble/hashes": "1.3.1" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@web5/dids/node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/credentials/node_modules/@web5/dids/node_modules/@web5/crypto": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-0.2.2.tgz", - "integrity": "sha512-vHFg0wXQSQXrwuBNQyDHnmSZchfTfO6/Sv+7rDsNkvofs+6lGTE8CZ02cwUYMeIwTRMLer12c+fMfzYrXokEUQ==", - "dependencies": { - "@noble/ciphers": "0.1.4", - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@web5/common": "0.2.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/credentials/node_modules/@web5/dids/node_modules/@web5/crypto/node_modules/@web5/common": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@web5/common/-/common-0.2.1.tgz", - "integrity": "sha512-Tt5P17HgQCx+Epw0IHnhRKqp5UU3E4xtsE8PkdghOBnvntBB0op5P6efvR1WqmJft5+VunDHt3yZAZstuqQkNg==", - "dependencies": { - "level": "8.0.0", - "multiformats": "11.0.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/credentials/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", diff --git a/packages/credentials/README.md b/packages/credentials/README.md index eae8bb03f..4f7f65c32 100644 --- a/packages/credentials/README.md +++ b/packages/credentials/README.md @@ -69,8 +69,8 @@ Sign a `VerifiableCredential` with a DID: First create a `Did` object as follows: ```javascript -import { DidKeyMethod } from '@web5/dids'; -const issuer = await DidKeyMethod.create(); +import { DidKey } from '@web5/dids'; +const issuer: BearerDid = await DidKey.create(); ``` Then sign the VC using the `did` object diff --git a/packages/credentials/package.json b/packages/credentials/package.json index a62fe7585..668ec55cd 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -75,9 +75,9 @@ }, "dependencies": { "@sphereon/pex": "2.1.0", - "@web5/common": "0.2.2", - "@web5/crypto": "0.2.4", - "@web5/dids": "0.2.4" + "@web5/common": "0.2.3", + "@web5/crypto": "0.4.0", + "@web5/dids": "0.4.0" }, "devDependencies": { "@playwright/test": "1.40.1", diff --git a/packages/credentials/src/jwt.ts b/packages/credentials/src/jwt.ts index 719f21cb8..b56d77b19 100644 --- a/packages/credentials/src/jwt.ts +++ b/packages/credentials/src/jwt.ts @@ -1,18 +1,16 @@ -import type { PortableDid } from '@web5/dids'; +import { BearerDid } from '@web5/dids'; import type { JwtPayload, - Web5Crypto, - CryptoAlgorithm, JwtHeaderParams, - JwkParamsEcPrivate, - JwkParamsOkpPrivate, JwkParamsEcPublic, JwkParamsOkpPublic, } from '@web5/crypto'; import { Convert } from '@web5/common'; -import { EdDsaAlgorithm, EcdsaAlgorithm } from '@web5/crypto'; -import { DidDhtMethod, DidIonMethod, DidKeyMethod, DidResolver, utils as didUtils } from '@web5/dids'; +import { LocalKeyManager as CryptoApi } from '@web5/crypto'; +import { DidDht, DidIon, DidKey, DidJwk, DidWeb, DidResolver, utils as didUtils } from '@web5/dids'; + +const crypto = new CryptoApi(); /** * Result of parsing a JWT. @@ -49,7 +47,7 @@ export type ParseJwtOptions = { * Parameters for signing a JWT. */ export type SignJwtOptions = { - signerDid: PortableDid + signerDid: BearerDid payload: JwtPayload } @@ -60,49 +58,16 @@ export type VerifyJwtOptions = { jwt: string } -/** - * Represents a signer with a specific cryptographic algorithm and options. - * @template T - The type of cryptographic options. - */ -type Signer = { - signer: CryptoAlgorithm, - options?: T | undefined - alg: string - crv: string -} - -const secp256k1Signer: Signer = { - signer : new EcdsaAlgorithm(), - options : { name: 'ES256K'}, - alg : 'ES256K', - crv : 'secp256k1' -}; - -const ed25519Signer: Signer = { - signer : new EdDsaAlgorithm(), - options : { name: 'EdDSA' }, - alg : 'EdDSA', - crv : 'Ed25519' -}; - /** * Class for handling Compact JSON Web Tokens (JWTs). * This class provides methods to create, verify, and decode JWTs using various cryptographic algorithms. * More information on JWTs can be found [here](https://datatracker.ietf.org/doc/html/rfc7519) */ export class Jwt { - /** supported cryptographic algorithms. keys are `${alg}:${crv}`. */ - static algorithms: { [alg: string]: Signer } = { - 'ES256K:' : secp256k1Signer, - 'ES256K:secp256k1' : secp256k1Signer, - ':secp256k1' : secp256k1Signer, - 'EdDSA:Ed25519' : ed25519Signer - }; - /** * DID Resolver instance for resolving decentralized identifiers. */ - static didResolver: DidResolver = new DidResolver({ didResolvers: [DidIonMethod, DidKeyMethod, DidDhtMethod] }); + static didResolver: DidResolver = new DidResolver({ didResolvers: [DidDht, DidIon, DidKey, DidJwk, DidWeb] }); /** * Creates a signed JWT. @@ -117,17 +82,17 @@ export class Jwt { */ static async sign(options: SignJwtOptions): Promise { const { signerDid, payload } = options; - const privateKeyJwk = signerDid.keySet.verificationMethodKeys![0].privateKeyJwk! as JwkParamsEcPrivate | JwkParamsOkpPrivate; + const signer = await signerDid.getSigner(); - let vmId = signerDid.document.verificationMethod![0].id; + let vmId = signer.keyId; if (vmId.charAt(0) === '#') { - vmId = `${signerDid.did}${vmId}`; + vmId = `${signerDid.uri}${vmId}`; } const header: JwtHeaderParams = { typ : 'JWT', - alg : privateKeyJwk.alg!, - kid : vmId + alg : signer.algorithm, + kid : vmId, }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); @@ -136,14 +101,8 @@ export class Jwt { const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; const toSignBytes = Convert.string(toSign).toUint8Array(); - const algorithmId = `${header.alg}:${privateKeyJwk['crv'] || ''}`; - if (!(algorithmId in Jwt.algorithms)) { - throw new Error(`Signing failed: ${algorithmId} not supported`); - } - - const { signer, options: signatureAlgorithm } = Jwt.algorithms[algorithmId]; + const signatureBytes = await signer.sign({ data: toSignBytes }); - const signatureBytes = await signer.sign({ key: privateKeyJwk, data: toSignBytes, algorithm: signatureAlgorithm! }); const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); return `${toSign}.${base64UrlEncodedSignature}`; @@ -168,13 +127,13 @@ export class Jwt { } // TODO: should really be looking for verificationMethod with authentication verification relationship - const dereferenceResult = await Jwt.didResolver.dereference({ didUrl: decodedJwt.header.kid! }); + const dereferenceResult = await Jwt.didResolver.dereference(decodedJwt.header.kid!); if (dereferenceResult.dereferencingMetadata.error) { throw new Error(`Failed to resolve ${decodedJwt.header.kid}`); } const verificationMethod = dereferenceResult.contentStream; - if (!verificationMethod || !didUtils.isVerificationMethod(verificationMethod)) { // ensure that appropriate verification method was found + if (!verificationMethod || !didUtils.isDidVerificationMethod(verificationMethod)) { // ensure that appropriate verification method was found throw new Error('Verification failed: Expected kid in JWT header to dereference a DID Document Verification Method'); } @@ -184,23 +143,19 @@ export class Jwt { throw new Error('Verification failed: Expected kid in JWT header to dereference to a DID Document Verification Method with publicKeyJwk'); } + if(publicKeyJwk.alg && (publicKeyJwk.alg !== decodedJwt.header.alg)) { + throw new Error('Verification failed: Expected alg in JWT header to match DID Document Verification Method alg'); + } + const signedData = `${encodedJwt.header}.${encodedJwt.payload}`; const signedDataBytes = Convert.string(signedData).toUint8Array(); const signatureBytes = Convert.base64Url(encodedJwt.signature).toUint8Array(); - const algorithmId = `${decodedJwt.header.alg}:${publicKeyJwk['crv'] || ''}`; - if (!(algorithmId in Jwt.algorithms)) { - throw new Error(`Verification failed: ${algorithmId} not supported`); - } - - const { signer, options: signatureAlgorithm } = Jwt.algorithms[algorithmId]; - - const isSignatureValid = await signer.verify({ - algorithm : signatureAlgorithm!, + const isSignatureValid = await crypto.verify({ key : publicKeyJwk, + signature : signatureBytes, data : signedDataBytes, - signature : signatureBytes }); if (!isSignatureValid) { diff --git a/packages/credentials/src/verifiable-credential.ts b/packages/credentials/src/verifiable-credential.ts index e70a11fca..d96690792 100644 --- a/packages/credentials/src/verifiable-credential.ts +++ b/packages/credentials/src/verifiable-credential.ts @@ -1,4 +1,4 @@ -import type { PortableDid } from '@web5/dids'; +import type { BearerDid } from '@web5/dids'; import type { ICredential, ICredentialSubject} from '@sphereon/ssi-types'; import { utils as cryptoUtils } from '@web5/crypto'; @@ -41,7 +41,7 @@ export type VerifiableCredentialCreateOptions = { * @param did - The issuer DID of the credential, represented as a PortableDid. */ export type VerifiableCredentialSignOptions = { - did: PortableDid; + did: BearerDid; }; type CredentialSubject = ICredentialSubject; diff --git a/packages/credentials/tests/jwt.spec.ts b/packages/credentials/tests/jwt.spec.ts index 63c8ea92e..452c27051 100644 --- a/packages/credentials/tests/jwt.spec.ts +++ b/packages/credentials/tests/jwt.spec.ts @@ -2,8 +2,8 @@ import type { JwtHeaderParams, JwtPayload, PrivateKeyJwk } from '@web5/crypto'; import { expect } from 'chai'; import { Convert } from '@web5/common'; -import { Secp256k1 } from '@web5/crypto'; -import { DidKeyMethod } from '@web5/dids'; +import { Ed25519 } from '@web5/crypto'; +import { DidJwk, DidKey, PortableDid } from '@web5/dids'; import { Jwt } from '../src/jwt.js'; @@ -70,7 +70,7 @@ describe('Jwt', () => { describe('verify()', () => { it('throws error if JWT is expired', async () => { - const did = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' }); + const did = await DidKey.create({ options: { algorithm: 'secp256k1'} }); const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.document.verificationMethod![0].id }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); @@ -85,8 +85,8 @@ describe('Jwt', () => { } }); it('throws error if JWT header kid does not dereference a verification method', async () => { - const did = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' }); - const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.did }; + const did = await DidKey.create({ options: { algorithm: 'secp256k1'} }); + const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.uri }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) }; @@ -100,9 +100,9 @@ describe('Jwt', () => { } }); - it('throws error if alg is not supported', async () => { - const did = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' }); - const header: JwtHeaderParams = { typ: 'JWT', alg: 'RS256', kid: did.document.verificationMethod![0].id }; + it('throws error if public key alg is not supported', async () => { + const did = await DidJwk.create({ options: { algorithm: 'secp256k1'} }); + const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256', kid: did.document.verificationMethod![0].id }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) }; @@ -112,13 +112,47 @@ describe('Jwt', () => { await Jwt.verify({ jwt: `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}.hijk` }); expect.fail(); } catch(e: any) { - expect(e.message).to.include('not supported'); + expect(e.message).to.include('Verification failed: Expected alg in JWT header to match DID Document Verification Method alg'); } }); it('returns signer DID if verification succeeds', async () => { - const did = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' }); - const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.document.verificationMethod![0].id }; + const portableDid: PortableDid = { + uri : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + document : { + '@context' : 'https://www.w3.org/ns/did/v1', + id : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + verificationMethod : [ + { + id : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN#z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', // You may need to adjust the ID based on your requirements + type : 'JsonWebKey2020', // Adjust the type according to your needs, assuming JsonWebKey2020 + controller : 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + publicKeyJwk : { + kty : 'OKP', + crv : 'Ed25519', + x : 'VnSOQ-n7kRcYd0XGW2MNCv7DDY5py5XhNcjM7-Y1HVM', + }, + }, + ], + authentication: [ + 'did:key:z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN#z6MkkGkByH7rSY3uxDEPTk1CZzPG5hvf564ABFLQzCFwyYNN', + ], + // Add other fields like assertionMethod, capabilityInvocation, etc., as needed + }, + metadata : {}, // Populate according to DidMetadata interface + privateKeys : [ + { + kty : 'OKP', + crv : 'Ed25519', + x : 'VnSOQ-n7kRcYd0XGW2MNCv7DDY5py5XhNcjM7-Y1HVM', + d : 'iTD5DIOKZNkwgzsND-I8CLIXmgTxfQ1HUzl9fpMktAo', + }, + ], + }; + + const did = await DidKey.import({ portableDid }); + + const header: JwtHeaderParams = { typ: 'JWT', alg: 'EdDSA', kid: did.document.verificationMethod![0].id }; const base64UrlEncodedHeader = Convert.object(header).toBase64Url(); const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) }; @@ -127,9 +161,9 @@ describe('Jwt', () => { const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`; const toSignBytes = Convert.string(toSign).toUint8Array(); - const privateKeyJwk = did.keySet.verificationMethodKeys![0].privateKeyJwk; + const privateKeyJwk = portableDid.privateKeys![0]; - const signatureBytes = await Secp256k1.sign({ key: privateKeyJwk as PrivateKeyJwk, data: toSignBytes }); + const signatureBytes = await Ed25519.sign({ key: privateKeyJwk as PrivateKeyJwk, data: toSignBytes }); const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url(); const jwt = `${toSign}.${base64UrlEncodedSignature}`; diff --git a/packages/credentials/tests/presentation-exchange.spec.ts b/packages/credentials/tests/presentation-exchange.spec.ts index c7bc6bb4b..8a6ee05fb 100644 --- a/packages/credentials/tests/presentation-exchange.spec.ts +++ b/packages/credentials/tests/presentation-exchange.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { DidKeyMethod, PortableDid } from '@web5/dids'; +import { BearerDid, DidKey } from '@web5/dids'; import type { Validated, PresentationDefinitionV2 } from '../src/presentation-exchange.js'; @@ -22,18 +22,18 @@ class OtherCredential { describe('PresentationExchange', () => { describe('Full Presentation Exchange', () => { - let issuerDid: PortableDid; + let issuerDid: BearerDid; let btcCredentialJwt: string; let presentationDefinition: PresentationDefinitionV2; let groupPresentationDefinition: PresentationDefinitionV2; before(async () => { - issuerDid = await DidKeyMethod.create(); + issuerDid = await DidKey.create(); const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, - subject : issuerDid.did, + issuer : issuerDid.uri, + subject : issuerDid.uri, data : new BitcoinCredential('btcAddress123'), }); @@ -60,8 +60,8 @@ describe('PresentationExchange', () => { it('should return the only one verifiable credential', async () => { const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, - subject : issuerDid.did, + issuer : issuerDid.uri, + subject : issuerDid.uri, data : new OtherCredential('otherstuff'), }); @@ -146,8 +146,8 @@ describe('PresentationExchange', () => { it('should fail to create a presentation with vc that does not match presentation definition', async () => { const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, - subject : issuerDid.did, + issuer : issuerDid.uri, + subject : issuerDid.uri, data : new OtherCredential('otherstuff'), }); diff --git a/packages/credentials/tests/verifiable-credential.spec.ts b/packages/credentials/tests/verifiable-credential.spec.ts index 795e76820..00beb160b 100644 --- a/packages/credentials/tests/verifiable-credential.spec.ts +++ b/packages/credentials/tests/verifiable-credential.spec.ts @@ -1,15 +1,15 @@ -import type { PortableDid } from '@web5/dids'; +import type { BearerDid, PortableDid } from '@web5/dids'; import sinon from 'sinon'; import { expect } from 'chai'; -import { DidDhtMethod, DidKeyMethod, DidIonMethod } from '@web5/dids'; +import { DidDht, DidKey, DidIon, DidJwk } from '@web5/dids'; import { Jwt } from '../src/jwt.js'; import { VerifiableCredential } from '../src/verifiable-credential.js'; import CredentialsVerifyTestVector from '../../../web5-spec/test-vectors/credentials/verify.json' assert { type: 'json' }; -describe('Verifiable Credential Tests', () => { - let issuerDid: PortableDid; +describe('Verifiable Credential Tests', async() => { + let issuerDid: BearerDid; class StreetCredibility { constructor( @@ -19,21 +19,21 @@ describe('Verifiable Credential Tests', () => { } beforeEach(async () => { - issuerDid = await DidKeyMethod.create(); + issuerDid = await DidKey.create(); }); describe('Verifiable Credential (VC)', () => { it('create vc works', async () => { - const subjectDid = issuerDid.did; + const subjectDid = issuerDid.uri; const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, + issuer : issuerDid.uri, subject : subjectDid, data : new StreetCredibility('high', true), }); - expect(vc.issuer).to.equal(issuerDid.did); + expect(vc.issuer).to.equal(issuerDid.uri); expect(vc.subject).to.equal(subjectDid); expect(vc.type).to.equal('StreetCred'); expect(vc.vcDataModel.issuanceDate).to.not.be.undefined; @@ -41,12 +41,12 @@ describe('Verifiable Credential Tests', () => { }); it('create and sign vc with did:key', async () => { - const did = await DidKeyMethod.create(); + const did = await DidKey.create(); const vc = await VerifiableCredential.create({ type : 'TBDeveloperCredential', - subject : did.did, - issuer : did.did, + subject : did.uri, + issuer : did.uri, data : { username: 'nitro' } @@ -57,21 +57,46 @@ describe('Verifiable Credential Tests', () => { await VerifiableCredential.verify({ vcJwt }); for( const currentVc of [vc, VerifiableCredential.parseJwt({ vcJwt })]){ - expect(currentVc.issuer).to.equal(did.did); - expect(currentVc.subject).to.equal(did.did); + expect(currentVc.issuer).to.equal(did.uri); + expect(currentVc.subject).to.equal(did.uri); expect(currentVc.type).to.equal('TBDeveloperCredential'); expect(currentVc.vcDataModel.issuanceDate).to.not.be.undefined; - expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.did, username: 'nitro'}); + expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.uri, username: 'nitro'}); + } + }); + + it('create and sign vc with did:jwk', async () => { + const did = await DidJwk.create(); + + const vc = await VerifiableCredential.create({ + type : 'TBDeveloperCredential', + subject : did.uri, + issuer : did.uri, + data : { + username: 'nitro' + } + }); + + const vcJwt = await vc.sign({ did }); + + await VerifiableCredential.verify({ vcJwt }); + + for( const currentVc of [vc, VerifiableCredential.parseJwt({ vcJwt })]){ + expect(currentVc.issuer).to.equal(did.uri); + expect(currentVc.subject).to.equal(did.uri); + expect(currentVc.type).to.equal('TBDeveloperCredential'); + expect(currentVc.vcDataModel.issuanceDate).to.not.be.undefined; + expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.uri, username: 'nitro'}); } }); it('create and sign vc with did:ion', async () => { - const did = await DidIonMethod.create(); + const did = await DidIon.create(); const vc = await VerifiableCredential.create({ type : 'TBDeveloperCredential', - subject : did.did, - issuer : did.did, + subject : did.uri, + issuer : did.uri, data : { username: 'nitro' } @@ -82,11 +107,36 @@ describe('Verifiable Credential Tests', () => { await VerifiableCredential.verify({ vcJwt }); for (const currentVc of [vc, VerifiableCredential.parseJwt({ vcJwt })]){ - expect(currentVc.issuer).to.equal(did.did); - expect(currentVc.subject).to.equal(did.did); + expect(currentVc.issuer).to.equal(did.uri); + expect(currentVc.subject).to.equal(did.uri); expect(currentVc.type).to.equal('TBDeveloperCredential'); expect(currentVc.vcDataModel.issuanceDate).to.not.be.undefined; - expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.did, username: 'nitro'}); + expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.uri, username: 'nitro'}); + } + }); + + it('create and sign vc with did:dht', async () => { + const did = await DidDht.create(); + + const vc = await VerifiableCredential.create({ + type : 'TBDeveloperCredential', + subject : did.uri, + issuer : did.uri, + data : { + username: 'nitro' + } + }); + + const vcJwt = await vc.sign({ did }); + + await VerifiableCredential.verify({ vcJwt }); + + for (const currentVc of [vc, VerifiableCredential.parseJwt({ vcJwt })]){ + expect(currentVc.issuer).to.equal(did.uri); + expect(currentVc.subject).to.equal(did.uri); + expect(currentVc.type).to.equal('TBDeveloperCredential'); + expect(currentVc.vcDataModel.issuanceDate).to.not.be.undefined; + expect(currentVc.vcDataModel.credentialSubject).to.deep.equal({ id: did.uri, username: 'nitro'}); } }); @@ -140,11 +190,11 @@ describe('Verifiable Credential Tests', () => { }); it('signing with Ed25519 key works', async () => { - const subjectDid = issuerDid.did; + const subjectDid = issuerDid.uri; const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, + issuer : issuerDid.uri, subject : subjectDid, data : new StreetCredibility('high', true), }); @@ -158,12 +208,12 @@ describe('Verifiable Credential Tests', () => { }); it('signing with secp256k1 key works', async () => { - const did = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' }); + const did = await DidKey.create({ options: { algorithm: 'secp256k1'} }); const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : did.did, - subject : did.did, + issuer : did.uri, + subject : did.uri, data : new StreetCredibility('high', true), }); @@ -182,12 +232,13 @@ describe('Verifiable Credential Tests', () => { }); it('parseJwt checks if missing vc property', async () => { - const did = await DidKeyMethod.create(); + const did = await DidKey.create(); + const jwt = await Jwt.sign({ signerDid : did, payload : { - iss : did.did, - sub : did.did + iss : did.uri, + sub : did.uri } }); @@ -199,8 +250,8 @@ describe('Verifiable Credential Tests', () => { it('parseJwt returns an instance of VerifiableCredential on success', async () => { const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, - subject : issuerDid.did, + issuer : issuerDid.uri, + subject : issuerDid.uri, data : new StreetCredibility('high', true), }); @@ -238,21 +289,22 @@ describe('Verifiable Credential Tests', () => { it('verify does not throw an exception with valid vc', async () => { const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : issuerDid.did, - subject : issuerDid.did, + issuer : issuerDid.uri, + subject : issuerDid.uri, data : new StreetCredibility('high', true), }); const vcJwt = await vc.sign({did: issuerDid}); const { issuer, subject, vc: credential } = await VerifiableCredential.verify({ vcJwt }); - expect(issuer).to.equal(issuerDid.did); - expect(subject).to.equal(issuerDid.did); + expect(issuer).to.equal(issuerDid.uri); + expect(subject).to.equal(issuerDid.uri); expect(credential).to.not.be.null; }); it('verify throws exception if vc property does not exist', async () => { - const did = await DidKeyMethod.create(); + const did = await DidKey.create(); + const jwt = await Jwt.sign({ payload : { jti: 'hi' }, signerDid : did @@ -266,7 +318,8 @@ describe('Verifiable Credential Tests', () => { }); it('verify throws exception if vc property is invalid', async () => { - const did = await DidKeyMethod.create(); + const did = await DidKey.create(); + const jwt = await Jwt.sign({ payload : { vc: 'hi' }, signerDid : did @@ -281,100 +334,71 @@ describe('Verifiable Credential Tests', () => { }); it('verify does not throw an exception with vaild vc signed by did:dht', async () => { - const mockDocument: PortableDid = { - keySet: { - verificationMethodKeys: [ - { - privateKeyJwk: { - d : '_8gihSI-m8aOCCM6jHg33d8kxdImPBN4C5_bZIu10XU', - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - ext : 'true', - key_ops : [ - 'sign' - ], - x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', - kid : '0' - }, - publicKeyJwk: { - alg : 'EdDSA', - crv : 'Ed25519', - kty : 'OKP', - ext : 'true', - key_ops : [ - 'verify' - ], - x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo', - kid : '0' - }, - relationships: [ - 'authentication', - 'assertionMethod', - 'capabilityInvocation', - 'capabilityDelegation' - ] - } - ] - - }, - did : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + const portableDid: PortableDid = { + uri : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', document : { - id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + '@context' : 'https://www.w3.org/ns/did/v1', + id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', verificationMethod : [ { - id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy#0', - type : 'JsonWebKey2020', - controller : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0', + type : 'JsonWebKey', + controller : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', publicKeyJwk : { crv : 'Ed25519', kty : 'OKP', + x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', + kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', alg : 'EdDSA', - kid : '0', - x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo' - } - } - ], - authentication: [ - '#0' - ], - assertionMethod: [ - '#0' - ], - capabilityInvocation: [ - '#0' + }, + }, ], - capabilityDelegation: [ - '#0' - ] - } + authentication : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + assertionMethod : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + capabilityDelegation : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + capabilityInvocation : ['did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0'], + }, + metadata : {}, + privateKeys : [ + { + crv : 'Ed25519', + d : 'hdSIwbQwVD-fNOVEgt-k3mMl44Ip1iPi58Ex6VDGxqY', + kty : 'OKP', + x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', + kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', + alg : 'EdDSA', + }, + ], }; - const didDhtCreateStub = sinon.stub(DidDhtMethod, 'create').resolves(mockDocument); - const alice = await DidDhtMethod.create({ publish: true }); + const bearerDid = await DidDht.import({ portableDid }); + + const didDhtCreateStub = sinon.stub(DidDht, 'create').resolves(bearerDid); + + const alice = await DidDht.create({options: { publish: true }}); const vc = await VerifiableCredential.create({ type : 'StreetCred', - issuer : alice.did, - subject : alice.did, + issuer : alice.uri, + subject : alice.uri, data : new StreetCredibility('high', true), }); - const dhtDidResolutionSpy = sinon.stub(DidDhtMethod, 'resolve').resolves({ + const dhtDidResolutionSpy = sinon.stub(DidDht, 'resolve').resolves({ '@context' : 'https://w3id.org/did-resolution/v1', didDocument : { - id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', verificationMethod : [ { - id : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy#0', - type : 'JsonWebKey2020', - controller : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + id : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo#0', + type : 'JsonWebKey', + controller : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', publicKeyJwk : { crv : 'Ed25519', kty : 'OKP', + x : 'VYKm2SCIV9Vz3BRy-v5R9GHz3EOJCPvZ1_gP1e3XiB0', + kid : 'cyvOypa6k-4ffsRWcza37s5XVOh1kO9ICUeo1ZxHVM8', alg : 'EdDSA', - kid : '0', - x : 'Qm88q6jAN9tfnrLt5V2zAiZs7wD_jnewHp7HIvM3dGo' } } ], @@ -394,8 +418,8 @@ describe('Verifiable Credential Tests', () => { didDocumentMetadata : {}, didResolutionMetadata : { did: { - didString : 'did:dht:ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', - methodSpecificId : 'ejzu3k7eay57szh6sms6kzpuyeug35ay9688xcy6u5d1fh3zqtiy', + didString : 'did:dht:ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', + methodSpecificId : 'ksbkpsjytbm7kh6hnt3xi91t6to98zndtrrxzsqz9y87m5qztyqo', method : 'dht' } } @@ -416,12 +440,7 @@ describe('Verifiable Credential Tests', () => { const vectors = CredentialsVerifyTestVector.vectors; for (const vector of vectors) { - const { input, errors, description } = vector; - - // TODO: DID:JWK is not supported yet - if (description === 'verify a jwt verifiable credential signed with a did:jwk') { - continue; - } + const { input, errors } = vector; if (errors) { let errorOccurred = false;