diff --git a/apps/vc-api/docs/openapi.json b/apps/vc-api/docs/openapi.json index 235bca1..6dd4a40 100644 --- a/apps/vc-api/docs/openapi.json +++ b/apps/vc-api/docs/openapi.json @@ -626,7 +626,7 @@ "KeyDescriptionDto": { "type": "object", "properties": { - "keyId": { "type": "string", "description": "id of key (for example, JWK thumbprint)" } + "keyId": { "type": "string", "description": "id of key (for example, public key base58 string)" } }, "required": ["keyId"] }, @@ -653,14 +653,15 @@ }, "required": ["statusCode", "message", "error"] }, - "DidMethod": { "type": "string", "enum": ["key", "ethr"] }, + "DidMethod": { + "type": "string", + "description": "DID Method to create.\nMust be one of \"key\" or \"ethr\"", + "enum": ["key", "ethr"] + }, "CreateDidOptionsDto": { "type": "object", "properties": { - "method": { - "description": "DID Method to create.\nMust be one of \"key\" or \"ethr\"", - "$ref": "#/components/schemas/DidMethod" - }, + "method": { "$ref": "#/components/schemas/DidMethod" }, "keyId": { "type": "string", "description": "id of key (for example, JWK thumbprint).\nThis key must be known to the server already.\nIf provided, DID will be created using this key.\nCurrently only supported for did:key." @@ -801,64 +802,36 @@ }, "required": ["@context", "type", "issuer", "issuanceDate", "credentialSubject", "proof"] }, - "ProofPurpose": { - "type": "string", - "enum": [ - "assertionMethod", - "authentication", - "keyAgreement", - "contactAgreement", - "capabilityInvocation", - "capabilityDelegation" - ] - }, - "VerifyOptionsDto": { - "type": "object", - "properties": { - "verificationMethod": { - "type": "string", - "description": "The URI of the verificationMethod used for the proof. Default assertionMethod URI." - }, - "proofPurpose": { - "description": "The purpose of the proof. Default 'assertionMethod'.", - "$ref": "#/components/schemas/ProofPurpose" - }, - "created": { - "type": "string", - "description": "The date and time of the proof (with a maximum accuracy in seconds). Default current system time." - }, - "challenge": { - "type": "string", - "description": "A challenge provided by the requesting party of the proof. For example 6e62f66e-67de-11eb-b490-ef3eeefa55f2" - }, - "domain": { - "type": "string", - "description": "The intended domain of validity for the proof. For example website.example" - } - } - }, "VerifyCredentialDto": { "type": "object", "properties": { "verifiableCredential": { "description": "A JSON-LD Verifiable Credential with a proof. https://w3c-ccg.github.io/vc-api/issuer.html#operation/issueCredential", "allOf": [{ "$ref": "#/components/schemas/VerifiableCredentialDto" }] - }, - "options": { - "description": "Parameters for verifying a verifiable credential or a verifiable presentation https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyCredential https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyPresentation", - "allOf": [{ "$ref": "#/components/schemas/VerifyOptionsDto" }] } }, - "required": ["verifiableCredential", "options"] + "required": ["verifiableCredential"] }, "VerificationResultDto": { "type": "object", "properties": { - "checks": { "description": "The checks performed", "type": "array", "items": { "type": "string" } }, "warnings": { "description": "Warnings", "type": "array", "items": { "type": "string" } }, - "errors": { "description": "Errors", "type": "array", "items": { "type": "string" } } + "errors": { "description": "Errors", "type": "array", "items": { "type": "string" } }, + "verified": { "type": "boolean", "description": "Is the credential valid" } }, - "required": ["checks", "warnings", "errors"] + "required": ["warnings", "errors", "verified"] + }, + "ProofPurpose": { + "type": "string", + "description": "The purpose of the proof. Default 'assertionMethod'.", + "enum": [ + "assertionMethod", + "authentication", + "keyAgreement", + "contactAgreement", + "capabilityInvocation", + "capabilityDelegation" + ] }, "ProvePresentationOptionsDto": { "type": "object", @@ -871,10 +844,7 @@ "type": "string", "description": "The URI of the verificationMethod used for the proof. Default assertionMethod URI." }, - "proofPurpose": { - "description": "The purpose of the proof. Default 'assertionMethod'.", - "$ref": "#/components/schemas/ProofPurpose" - }, + "proofPurpose": { "$ref": "#/components/schemas/ProofPurpose" }, "created": { "type": "string", "description": "The date and time of the proof (with a maximum accuracy in seconds). Default current system time." @@ -969,6 +939,28 @@ }, "required": ["presentation", "options"] }, + "VerifyOptionsDto": { + "type": "object", + "properties": { + "verificationMethod": { + "type": "string", + "description": "The URI of the verificationMethod used for the proof. Default assertionMethod URI." + }, + "proofPurpose": { "$ref": "#/components/schemas/ProofPurpose" }, + "created": { + "type": "string", + "description": "The date and time of the proof (with a maximum accuracy in seconds). Default current system time." + }, + "challenge": { + "type": "string", + "description": "A challenge provided by the requesting party of the proof. For example 6e62f66e-67de-11eb-b490-ef3eeefa55f2" + }, + "domain": { + "type": "string", + "description": "The intended domain of validity for the proof. For example website.example" + } + } + }, "VerifyPresentationDto": { "type": "object", "properties": { @@ -989,12 +981,7 @@ }, "ExchangeInteractServiceDefinitionDto": { "type": "object", - "properties": { - "type": { - "description": "The \"type\" of the interact service.\nSee Verifiable Presentation Request [Interaction Types](https://w3c-ccg.github.io/vp-request-spec/#interaction-types) for background.", - "$ref": "#/components/schemas/VpRequestInteractServiceType" - } - }, + "properties": { "type": { "$ref": "#/components/schemas/VpRequestInteractServiceType" } }, "required": ["type"] }, "PresentationDefinitionDto": { @@ -1016,14 +1003,15 @@ "required": ["presentationDefinition"] }, "VpRequestDidAuthQueryDto": { "type": "object", "properties": {} }, - "VpRequestQueryType": { "type": "string", "enum": ["DIDAuth", "PresentationDefinition"] }, + "VpRequestQueryType": { + "type": "string", + "description": "Query types as listed in the VP Request spec.\nhttps://w3c-ccg.github.io/vp-request-spec/#query-and-response-types\n\nThe \"PresentationDefinition\" type is proposed here: https://github.com/w3c-ccg/vp-request-spec/issues/7", + "enum": ["DIDAuth", "PresentationDefinition"] + }, "VpRequestQueryDto": { "type": "object", "properties": { - "type": { - "description": "Query types as listed in the VP Request spec.\nhttps://w3c-ccg.github.io/vp-request-spec/#query-and-response-types\n\nThe \"PresentationDefinition\" type is proposed here: https://github.com/w3c-ccg/vp-request-spec/issues/7", - "$ref": "#/components/schemas/VpRequestQueryType" - }, + "type": { "$ref": "#/components/schemas/VpRequestQueryType" }, "credentialQuery": { "type": "array", "description": "The credential query.\nIt should correspond to the query type.", @@ -1169,14 +1157,15 @@ }, "required": ["transactionId", "exchangeId", "vpRequest"] }, - "ReviewResult": { "type": "string", "enum": ["approved", "rejected"] }, + "ReviewResult": { + "type": "string", + "description": "The judgement made by the reviewer", + "enum": ["approved", "rejected"] + }, "SubmissionReviewDto": { "type": "object", "properties": { - "result": { - "description": "The judgement made by the reviewer", - "$ref": "#/components/schemas/ReviewResult" - }, + "result": { "$ref": "#/components/schemas/ReviewResult" }, "vp": { "description": "A reviewer may want to include credentials (wrapped in a VP) to the holder", "allOf": [{ "$ref": "#/components/schemas/VerifiablePresentationDto" }] diff --git a/apps/vc-api/package.json b/apps/vc-api/package.json index 290a0ef..8f613e1 100644 --- a/apps/vc-api/package.json +++ b/apps/vc-api/package.json @@ -38,7 +38,6 @@ "rxjs": "^7.8.1", "typeorm": "^0.3.17", "better-sqlite3": "~8.4.0", - "@spruceid/didkit-wasm-node": "~0.2.1", "did-resolver": "~4.1.0", "class-validator": "~0.14.0", "class-transformer": "~0.5.1", diff --git a/apps/vc-api/src/credo/__mocks__/credo.service.ts b/apps/vc-api/src/credo/__mocks__/credo.service.ts index 706806b..335e5f5 100644 --- a/apps/vc-api/src/credo/__mocks__/credo.service.ts +++ b/apps/vc-api/src/credo/__mocks__/credo.service.ts @@ -4,7 +4,13 @@ export const mockCredoService = { }, agent: { wallet: { - createKey: jest.fn() + createKey: jest.fn() + }, + w3cCredentials: { + signCredential: jest.fn(), + verifyCredential: jest.fn(), + signPresentation: jest.fn(), + verifyPresentation: jest.fn() } } -}; \ No newline at end of file +}; diff --git a/apps/vc-api/src/vc-api/credentials/credentials.service.spec.ts b/apps/vc-api/src/vc-api/credentials/credentials.service.spec.ts index 430c1f3..03ec1ef 100644 --- a/apps/vc-api/src/vc-api/credentials/credentials.service.spec.ts +++ b/apps/vc-api/src/vc-api/credentials/credentials.service.spec.ts @@ -4,7 +4,6 @@ */ import { Test, TestingModule } from '@nestjs/testing'; -import { keyToVerificationMethod } from '@spruceid/didkit-wasm-node'; import { CredentialsService } from './credentials.service'; import { IssueOptionsDto } from './dtos/issue-options.dto'; import { VerifyOptionsDto } from './dtos/verify-options.dto'; @@ -20,10 +19,16 @@ import { getChargingDataCredential, presentationDefinition, rebeamVerifiablePresentation, - rebeamPresentation + rebeamPresentation, + verifiablePresentation, + didAuth } from '../../../test/vc-api/credential.service.spec.data'; -import { challenge, did, key, didDoc } from '../../../test/vc-api/credential.service.spec.key'; +import { did, key, didDoc } from '../../../test/vc-api/credential.service.spec.key'; import { ProvePresentationOptionsDto } from './dtos/prove-presentation-options.dto'; +import { CredoService } from '../../credo/credo.service'; +import { mockCredoService } from '../../credo/__mocks__/credo.service'; +import { CredoModule } from '../../credo/credo.module'; +import { JsonTransformer, W3cJsonLdVerifiableCredential, W3cJsonLdVerifiablePresentation } from '@credo-ts/core'; describe('CredentialsService', () => { let service: CredentialsService; @@ -33,6 +38,7 @@ describe('CredentialsService', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ + imports: [CredoModule], providers: [ CredentialsService, { @@ -47,6 +53,10 @@ describe('CredentialsService', () => { useValue: { getPrivateKeyFromKeyId: jest.fn() } + }, + { + provide: CredoService, + useValue: mockCredoService } ] }).compile(); @@ -54,7 +64,7 @@ describe('CredentialsService', () => { didService = module.get(DIDService); keyService = module.get(KeyService); service = module.get(CredentialsService); - verificationMethod = await keyToVerificationMethod('key', JSON.stringify(key)); + verificationMethod = didDoc.verificationMethod[0].id; }); afterEach(async () => { @@ -77,6 +87,10 @@ describe('CredentialsService', () => { jest.spyOn(didService, 'getDID').mockResolvedValueOnce(didDoc); jest.spyOn(didService, 'getVerificationMethod').mockResolvedValueOnce(didDoc.verificationMethod[0]); jest.spyOn(keyService, 'getPrivateKeyFromKeyId').mockResolvedValueOnce(key); + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'signCredential') + .mockResolvedValue(W3cJsonLdVerifiableCredential.fromJson(expectedVc)); + const vc = await service.issueCredential({ credential, options: issuanceOptions }); expect(vc['proof']['jws']).toBeDefined(); /** @@ -105,10 +119,16 @@ describe('CredentialsService', () => { } }); jest.spyOn(keyService, 'getPrivateKeyFromKeyId').mockResolvedValue(key); + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'signCredential') + .mockResolvedValue(W3cJsonLdVerifiableCredential.fromJson(energyContractVerifiableCredential)); const vc1 = await service.issueCredential({ credential: energyContractCredential, options: issueOptions }); + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'signCredential') + .mockResolvedValue(W3cJsonLdVerifiableCredential.fromJson(chargingDataVerifiableCredential)); const vc2 = await service.issueCredential({ credential: getChargingDataCredential(did), options: issueOptions @@ -117,27 +137,40 @@ describe('CredentialsService', () => { vc1, vc2 ]); + const presentationDto = JSON.parse(JSON.stringify(presentation)); + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'signPresentation') + .mockResolvedValue(JsonTransformer.fromJSON(verifiablePresentation, W3cJsonLdVerifiablePresentation)); + const vp = await service.provePresentation({ - presentation, - options: { proofPurpose: ProofPurpose.authentication, verificationMethod } + presentation: presentationDto, + options: { proofPurpose: ProofPurpose.authentication, verificationMethod, challenge: 'some-challenge' } }); + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'verifyPresentation') + .mockResolvedValue({ isValid: true }); const verificationResult = await service.verifyPresentation(vp, { proofPurpose: ProofPurpose.authentication, - verificationMethod + verificationMethod, + challenge: 'some-challenge' }); - expect(verificationResult).toEqual({ checks: ['proof'], warnings: [], errors: [] }); + expect(verificationResult.verified).toBeTruthy(); }); it('should prove a vp', async () => { const presentationOptions: ProvePresentationOptionsDto = { verificationMethod: didDoc.verificationMethod[0].id, proofPurpose: ProofPurpose.authentication, - created: rebeamVerifiablePresentation.proof.created + created: rebeamVerifiablePresentation.proof.created, + challenge: 'some-challenge' }; - jest.spyOn(didService, 'getDID').mockResolvedValueOnce(didDoc); - jest.spyOn(didService, 'getVerificationMethod').mockResolvedValueOnce(didDoc.verificationMethod[0]); - jest.spyOn(keyService, 'getPrivateKeyFromKeyId').mockResolvedValueOnce(key); + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'signPresentation') + .mockResolvedValue( + JsonTransformer.fromJSON(rebeamVerifiablePresentation, W3cJsonLdVerifiablePresentation) + ); + const vp = await service.provePresentation({ presentation: rebeamPresentation, options: presentationOptions @@ -153,39 +186,48 @@ describe('CredentialsService', () => { expect(vp).toEqual(expectedVpCopy); }); + it('should be able to generate DIDAuth', async () => { + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'signPresentation') + .mockResolvedValue( + JsonTransformer.fromJSON(didAuth.signedPresentation, W3cJsonLdVerifiablePresentation, {validate: false}) + ); + const vp = await service.didAuthenticate(didAuth.presentation); + expect(vp.holder).toEqual(did); + expect(vp.proof).toBeDefined(); + const verifyOptions: VerifyOptionsDto = { + proofPurpose: ProofPurpose.authentication, + verificationMethod, + challenge: 'some-challenge' + }; + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'verifyPresentation') + .mockResolvedValue({ isValid: true }); + const authVerification = await service.verifyPresentation(vp, verifyOptions); + expect(authVerification.verified).toBeTruthy(); + expect(authVerification.errors).toHaveLength(0); + }); + it('should be able to verify a credential', async () => { - const verifyOptions: VerifyOptionsDto = {}; - const result = await service.verifyCredential(energyContractVerifiableCredential, verifyOptions); - const expectedResult = { checks: ['proof'], warnings: [], errors: [] }; + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'verifyCredential') + .mockResolvedValue({ isValid: true }); + const result = await service.verifyCredential(energyContractVerifiableCredential); + const expectedResult = { verified: true, errors: [], warnings: [] }; expect(result).toEqual(expectedResult); }); it('should be able to verify a presentation', async () => { const verifyOptions: VerifyOptionsDto = { proofPurpose: ProofPurpose.authentication, - verificationMethod + verificationMethod, + challenge: 'some-challenge' }; + jest + .spyOn(mockCredoService.agent.w3cCredentials, 'verifyPresentation') + .mockResolvedValue({ isValid: true }); const result = await service.verifyPresentation(rebeamVerifiablePresentation, verifyOptions); - const expectedResult = { checks: ['proof'], warnings: [], errors: [] }; + const expectedResult = { verified: true, errors: [], warnings: [] }; expect(result).toEqual(expectedResult); }); - - it('should be able to generate DIDAuth', async () => { - const issuanceOptions: ProvePresentationOptionsDto = { - verificationMethod: didDoc.verificationMethod[0].id, - proofPurpose: ProofPurpose.authentication, - created: '2021-11-16T14:52:19.514Z', - challenge - }; - jest.spyOn(didService, 'getDID').mockResolvedValueOnce(didDoc); - jest.spyOn(didService, 'getVerificationMethod').mockResolvedValueOnce(didDoc.verificationMethod[0]); - jest.spyOn(keyService, 'getPrivateKeyFromKeyId').mockResolvedValueOnce(key); - const vp = await service.didAuthenticate({ did, options: issuanceOptions }); - expect(vp.holder).toEqual(did); - expect(vp.proof).toBeDefined(); - const authVerification = await service.verifyPresentation(vp, { challenge }); - expect(authVerification.checks).toHaveLength(1); - expect(authVerification.checks[0]).toEqual('proof'); - expect(authVerification.errors).toHaveLength(0); - }); }); diff --git a/apps/vc-api/src/vc-api/credentials/credentials.service.ts b/apps/vc-api/src/vc-api/credentials/credentials.service.ts index 225ec54..2bb4617 100644 --- a/apps/vc-api/src/vc-api/credentials/credentials.service.ts +++ b/apps/vc-api/src/vc-api/credentials/credentials.service.ts @@ -2,51 +2,34 @@ * Copyright 2021 - 2023 Energy Web Foundation * SPDX-License-Identifier: Apache-2.0 */ - import { BadRequestException, Injectable, InternalServerErrorException } from '@nestjs/common'; -import { - issueCredential, - verifyCredential, - issuePresentation, - verifyPresentation, - DIDAuth -} from '@spruceid/didkit-wasm-node'; -import { JWK } from 'jose'; import { DIDService } from '../../did/did.service'; -import { KeyService } from '../../key/key.service'; -import { IssueOptionsDto } from './dtos/issue-options.dto'; import { IssueCredentialDto } from './dtos/issue-credential.dto'; import { VerifiableCredentialDto } from './dtos/verifiable-credential.dto'; import { VerifiablePresentationDto } from './dtos/verifiable-presentation.dto'; import { VerifyOptionsDto } from './dtos/verify-options.dto'; import { VerificationResultDto } from './dtos/verification-result.dto'; -import { AuthenticateDto } from './dtos/authenticate.dto'; import { ProvePresentationDto } from './dtos/prove-presentation.dto'; import { CredentialVerifier } from './types/credential-verifier'; import { PresentationDto } from './dtos/presentation.dto'; import { IPresentationDefinition, IVerifiableCredential, PEX, ProofPurpose, Status } from '@sphereon/pex'; import { VerificationMethod } from 'did-resolver'; -import { ProvePresentationOptionsDto } from './dtos/prove-presentation-options.dto'; -import { didKitExecutor } from './utils/did-kit-executor.function'; - -/** - * Credential issuance options that Spruce accepts - * Full options are here: https://github.com/spruceid/didkit/blob/main/cli/README.md#didkit-vc-issue-credential - */ -interface ISpruceIssueOptions { - proofPurpose: string; - verificationMethod: string; - created?: string; - challenge?: string; -} - -/** - * Credential verification options that Spruce accepts - * Full options are here: https://github.com/spruceid/didkit/blob/main/cli/README.md#didkit-vc-verify-credential - */ -interface ISpruceVerifyOptions { - challenge?: string; -} +import { CredoService } from '../../credo/credo.service'; +import { + ClaimFormat, + JsonTransformer, + W3cCredential, + W3cJsonLdSignCredentialOptions, + W3cJsonLdVerifiableCredential, + W3cJsonLdVerifiablePresentation, + W3cPresentation, + W3cSignPresentationOptions, + W3cVerifyCredentialOptions, + W3cVerifyCredentialResult, + W3cVerifyPresentationOptions +} from '@credo-ts/core'; +import { AuthenticateDto } from './dtos/authenticate.dto'; +import { transformVerificationResult } from './utils/verification-result-transformer'; /** * This service provide the VC-API operations @@ -54,7 +37,7 @@ interface ISpruceVerifyOptions { */ @Injectable() export class CredentialsService implements CredentialVerifier { - constructor(private didService: DIDService, private keyService: KeyService) {} + constructor(private didService: DIDService, private credoService: CredoService) {} async issueCredential(issueDto: IssueCredentialDto): Promise { const verificationMethod = await this.getVerificationMethodForDid( @@ -62,33 +45,29 @@ export class CredentialsService implements CredentialVerifier { ? issueDto.credential.issuer : issueDto.credential.issuer.id ); - const key = await this.getKeyForVerificationMethod(verificationMethod.id); - const proofOptions = this.mapVcApiIssueOptionsToSpruceIssueOptions( - issueDto.options || ({} as IssueOptionsDto), - verificationMethod.id - ); - - return didKitExecutor( - () => - issueCredential( - JSON.stringify(issueDto.credential), - JSON.stringify(proofOptions), - JSON.stringify(key) - ), - 'issueCredential' - ); + const credentialOption: W3cJsonLdSignCredentialOptions = { + credential: W3cCredential.fromJson(issueDto.credential), + format: ClaimFormat.LdpVc, + proofType: 'Ed25519Signature2018', + verificationMethod: verificationMethod.id + }; + const w3cVerifiableCredential = + await this.credoService.agent.w3cCredentials.signCredential(credentialOption); + return w3cVerifiableCredential.toJson() as VerifiableCredentialDto; } async verifyCredential( - vc: VerifiableCredentialDto, - options: VerifyOptionsDto + vc: VerifiableCredentialDto ): Promise { - const verifyOptions: ISpruceVerifyOptions = options; + const w3cVerifyCredentialOptions: W3cVerifyCredentialOptions = { + credential: W3cJsonLdVerifiableCredential.fromJson(vc) + }; - return didKitExecutor( - () => verifyCredential(JSON.stringify(vc), JSON.stringify(verifyOptions)), - 'verifyCredential' - ); + // Using "any" until Credo types are fixed https://github.com/openwallet-foundation/credo-ts/issues/2043 + const verifyCredential: W3cVerifyCredentialResult = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await this.credoService.agent.w3cCredentials.verifyCredential(w3cVerifyCredentialOptions as any); + return transformVerificationResult(verifyCredential); } /** @@ -109,27 +88,6 @@ export class CredentialsService implements CredentialVerifier { } const presentation = pex.presentationFrom(presentationDefinition, verifiableCredential); - // embed submission context to workaround failing to load context 'https://identity.foundation/presentation-exchange/submission/v1' - const submissionContext = { - PresentationSubmission: { - '@id': 'https://identity.foundation/presentation-exchange/#presentation-submission', - '@context': { - '@version': '1.1', - presentation_submission: { - '@id': 'https://identity.foundation/presentation-exchange/#presentation-submission', - '@type': '@json' - } - } - } - }; - const submissionContextUri = 'https://identity.foundation/presentation-exchange/submission/v1'; - - presentation['@context'] = Array.isArray(presentation['@context']) - ? presentation['@context'] - : [presentation['@context']]; - - presentation['@context'] = presentation['@context'].filter((c) => c !== submissionContextUri); - presentation['@context'].push(submissionContext as any); return presentation as PresentationDto; } @@ -137,18 +95,21 @@ export class CredentialsService implements CredentialVerifier { const verificationMethodId = provePresentationDto.options.verificationMethod ?? (await this.getVerificationMethodForDid(provePresentationDto.presentation.holder)).id; - const key = await this.getKeyForVerificationMethod(verificationMethodId); - const proofOptions = this.mapVcApiPresentationOptionsToSpruceIssueOptions(provePresentationDto.options); - - return didKitExecutor( - () => - issuePresentation( - JSON.stringify(provePresentationDto.presentation), - JSON.stringify(proofOptions), - JSON.stringify(key) - ), - 'issuePresentation' - ); + + const signPresentationOption: W3cSignPresentationOptions = { + presentation: this.transformToCredoPresentation(provePresentationDto.presentation, W3cPresentation), + challenge: provePresentationDto.options.challenge, + verificationMethod: verificationMethodId, + format: ClaimFormat.LdpVp, + proofType: 'Ed25519Signature2018', + proofPurpose: provePresentationDto.options.proofPurpose + }; + const w3cVerifiablePresentation = + await this.credoService.agent.w3cCredentials.signPresentation( + signPresentationOption + ); + + return w3cVerifiablePresentation.toJson() as VerifiablePresentationDto; } /** @@ -159,30 +120,32 @@ export class CredentialsService implements CredentialVerifier { if (authenticateDto.options.proofPurpose !== ProofPurpose.authentication) { throw new BadRequestException('proof purpose must be authentication for DIDAuth'); } + const signPresentationDto: ProvePresentationDto = { + presentation: { + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation'], + holder: authenticateDto.did + }, + options: authenticateDto.options + }; - const verificationMethodId = - authenticateDto.options.verificationMethod ?? - (await this.getVerificationMethodForDid(authenticateDto.did)).id; - - const key = await this.getKeyForVerificationMethod(verificationMethodId); - const proofOptions = this.mapVcApiPresentationOptionsToSpruceIssueOptions(authenticateDto.options); + const signed = await this.provePresentation(signPresentationDto); - return didKitExecutor( - () => DIDAuth(authenticateDto.did, JSON.stringify(proofOptions), JSON.stringify(key)), - 'DIDAuth' - ); + return signed; } async verifyPresentation( vp: VerifiablePresentationDto, options: VerifyOptionsDto ): Promise { - const verifyOptions: ISpruceVerifyOptions = options; - - return didKitExecutor( - () => verifyPresentation(JSON.stringify(vp), JSON.stringify(verifyOptions)), - 'verifyPresentation' + const w3cVerifyPresentationOptions: W3cVerifyPresentationOptions = { + presentation: this.transformToCredoPresentation(vp, W3cJsonLdVerifiablePresentation), + challenge: options.challenge ?? vp.proof.challenge + }; + const verifyPresentation = await this.credoService.agent.w3cCredentials.verifyPresentation( + w3cVerifyPresentationOptions ); + return transformVerificationResult(verifyPresentation); } private async getVerificationMethodForDid(did: string): Promise { @@ -196,60 +159,20 @@ export class CredentialsService implements CredentialVerifier { } /** - * TODO: Maybe we should check if the issuer of the credential controls the associated verification method - * @param desiredVerificationMethod - * @returns the privateKey that can issue proofs as the verification method + * Credo is expecting the the verifiableCredential property in a presentation. + * However, this property is optional in both the v1.1 and v2 VC data models. + * For example, it isn't present if the presentation is only for authentication. + * Credo issue to resolve underlying cause: https://github.com/openwallet-foundation/credo-ts/issues/2038 + * @param presentation presentation to transform to a Credo type + * @param targetClass the desired Credo type + * @returns instance of targetClass */ - private async getKeyForVerificationMethod(desiredVerificationMethodId: string): Promise { - const verificationMethod = await this.didService.getVerificationMethod(desiredVerificationMethodId); - if (!verificationMethod) { - throw new InternalServerErrorException('This verification method is not known to this wallet'); - } - const keyID = verificationMethod.publicKeyJwk?.kid; - if (!keyID) { - throw new InternalServerErrorException( - 'There is no key ID (kid) associated with this verification method. Unable to retrieve private key' - ); - } - const privateKey = await this.keyService.getPrivateKeyFromKeyId(keyID); - if (!privateKey) { - throw new InternalServerErrorException('Unable to retrieve private key for this verification method'); - } - return privateKey; - } - - /** - * As the Spruce proof issuance options may not align perfectly with the VC-API spec issuanceOptions, - * this method provides a translation between the two - * @param options - * @returns - */ - private mapVcApiIssueOptionsToSpruceIssueOptions( - options: IssueOptionsDto, - verificationMethodId: string - ): ISpruceIssueOptions { - return { - proofPurpose: ProofPurpose.assertionMethod, // Issuance is always an "assertion" proof, AFAIK - verificationMethod: verificationMethodId, - created: options.created, - challenge: options.challenge - }; - } - - /** - * As the Spruce proof presentation options may not align perfectly with the VC-API spec provePresentationOptions, - * this method provides a translation between the two - * @param options - * @returns - */ - private mapVcApiPresentationOptionsToSpruceIssueOptions( - options: ProvePresentationOptionsDto - ): ISpruceIssueOptions { - return { - proofPurpose: options.proofPurpose, - verificationMethod: options.verificationMethod, - created: options.created, - challenge: options.challenge - }; + private transformToCredoPresentation( + presentation, + targetClass: new (...args: unknown[]) => T + ): T { + return JsonTransformer.fromJSON(presentation, targetClass, { + validate: !(presentation.verifiableCredential === undefined) + }); } } diff --git a/apps/vc-api/src/vc-api/credentials/dtos/prove-presentation-options.dto.ts b/apps/vc-api/src/vc-api/credentials/dtos/prove-presentation-options.dto.ts index 8c956d4..1c6997e 100644 --- a/apps/vc-api/src/vc-api/credentials/dtos/prove-presentation-options.dto.ts +++ b/apps/vc-api/src/vc-api/credentials/dtos/prove-presentation-options.dto.ts @@ -45,12 +45,11 @@ export class ProvePresentationOptionsDto { created?: string; @IsString() - @IsOptional() @ApiPropertyOptional({ description: 'A challenge provided by the requesting party of the proof. For example 6e62f66e-67de-11eb-b490-ef3eeefa55f2' }) - challenge?: string; + challenge: string; @IsString() @IsOptional() diff --git a/apps/vc-api/src/vc-api/credentials/dtos/verifiable-presentation.dto.ts b/apps/vc-api/src/vc-api/credentials/dtos/verifiable-presentation.dto.ts index c8fffc8..531d1ef 100644 --- a/apps/vc-api/src/vc-api/credentials/dtos/verifiable-presentation.dto.ts +++ b/apps/vc-api/src/vc-api/credentials/dtos/verifiable-presentation.dto.ts @@ -14,5 +14,7 @@ import { ApiProperty } from '@nestjs/swagger'; export class VerifiablePresentationDto extends PresentationDto { @IsObject() @ApiProperty({ description: 'A JSON-LD Linked Data proof.' }) - proof: Record; + proof: Record & { + challenge?: string; + }; } diff --git a/apps/vc-api/src/vc-api/credentials/dtos/verification-result.dto.ts b/apps/vc-api/src/vc-api/credentials/dtos/verification-result.dto.ts index 0e4470b..ea19e0c 100644 --- a/apps/vc-api/src/vc-api/credentials/dtos/verification-result.dto.ts +++ b/apps/vc-api/src/vc-api/credentials/dtos/verification-result.dto.ts @@ -3,9 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { IsArray, IsString } from 'class-validator'; +import { IsArray } from 'class-validator'; import { VerificationResult } from '../types/verification-result'; import { ApiProperty } from '@nestjs/swagger'; +import { ProblemDetail } from '../types/problem-detail'; /** * A response object from verification of a credential or a presentation. @@ -13,17 +14,13 @@ import { ApiProperty } from '@nestjs/swagger'; */ export class VerificationResultDto implements VerificationResult { @IsArray() - @IsString({ each: true }) - @ApiProperty({ description: 'The checks performed' }) - checks: string[]; - - @IsArray() - @IsString({ each: true }) @ApiProperty({ description: 'Warnings' }) - warnings: string[]; + warnings?: ProblemDetail[]; @IsArray() - @IsString({ each: true }) @ApiProperty({ description: 'Errors' }) - errors: string[]; + errors?: ProblemDetail[]; + + @ApiProperty({ description: 'Is the credential valid' }) + verified?: boolean; } diff --git a/apps/vc-api/src/vc-api/credentials/dtos/verify-credential.dto.ts b/apps/vc-api/src/vc-api/credentials/dtos/verify-credential.dto.ts index b9d1776..6bcf29c 100644 --- a/apps/vc-api/src/vc-api/credentials/dtos/verify-credential.dto.ts +++ b/apps/vc-api/src/vc-api/credentials/dtos/verify-credential.dto.ts @@ -4,8 +4,6 @@ */ import { VerifiableCredentialDto } from './verifiable-credential.dto'; -import { VerifyOptionsDto } from './verify-options.dto'; - import { IsObject, ValidateNested, IsDefined } from 'class-validator'; import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; @@ -25,21 +23,4 @@ export class VerifyCredentialDto { 'https://w3c-ccg.github.io/vc-api/issuer.html#operation/issueCredential' }) verifiableCredential: VerifiableCredentialDto; - - /** - * Parameters for verifying a verifiable credential or a verifiable presentation - * https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyCredential - * https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyPresentation - */ - @IsObject() - @IsDefined() - @ValidateNested() - @Type(() => VerifyOptionsDto) - @ApiProperty({ - description: - 'Parameters for verifying a verifiable credential or a verifiable presentation ' + - 'https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyCredential ' + - 'https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyPresentation' - }) - options: VerifyOptionsDto; } diff --git a/apps/vc-api/src/vc-api/credentials/dtos/verify-options.dto.ts b/apps/vc-api/src/vc-api/credentials/dtos/verify-options.dto.ts index 0194fe0..1adf594 100644 --- a/apps/vc-api/src/vc-api/credentials/dtos/verify-options.dto.ts +++ b/apps/vc-api/src/vc-api/credentials/dtos/verify-options.dto.ts @@ -44,7 +44,7 @@ export class VerifyOptionsDto implements VerifyOptions { description: 'A challenge provided by the requesting party of the proof. For example 6e62f66e-67de-11eb-b490-ef3eeefa55f2' }) - challenge?: string; + challenge: string; @IsString() @IsOptional() diff --git a/apps/vc-api/src/vc-api/credentials/types/problem-detail.ts b/apps/vc-api/src/vc-api/credentials/types/problem-detail.ts new file mode 100644 index 0000000..8f55394 --- /dev/null +++ b/apps/vc-api/src/vc-api/credentials/types/problem-detail.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 - 2023 Energy Web Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface ProblemDetail { + /** + * Title + */ + title: string; + + /** + * Type + */ + type?: string; + + /** + * Status + */ + status?: string; + + /** + * Detail + */ + detail?: string; + + /** + * Instance + */ + instance?: any; +} diff --git a/apps/vc-api/src/vc-api/credentials/types/verification-result.ts b/apps/vc-api/src/vc-api/credentials/types/verification-result.ts index c1e9e6f..667866f 100644 --- a/apps/vc-api/src/vc-api/credentials/types/verification-result.ts +++ b/apps/vc-api/src/vc-api/credentials/types/verification-result.ts @@ -3,23 +3,25 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ProblemDetail } from './problem-detail'; + /** * A response object from verification of a credential or a presentation. * https://w3c-ccg.github.io/vc-api/verifier.html */ export interface VerificationResult { /** - * The checks performed + * Warnings */ - checks: string[]; + warnings?: ProblemDetail[]; /** - * Warnings + * Errors */ - warnings: string[]; + errors?: ProblemDetail[]; /** - * Errors + * Verification status */ - errors: string[]; + verified?: boolean; } diff --git a/apps/vc-api/src/vc-api/credentials/types/verify-options.ts b/apps/vc-api/src/vc-api/credentials/types/verify-options.ts index d149919..d5c06ae 100644 --- a/apps/vc-api/src/vc-api/credentials/types/verify-options.ts +++ b/apps/vc-api/src/vc-api/credentials/types/verify-options.ts @@ -9,6 +9,6 @@ * - https://w3c-ccg.github.io/vc-api/verifier.html#operation/verifyPresentation */ export interface VerifyOptions { - challenge?: string; + challenge: string; proofPurpose?: string; } diff --git a/apps/vc-api/src/vc-api/credentials/utils/did-kit-executor.function.spec.ts b/apps/vc-api/src/vc-api/credentials/utils/did-kit-executor.function.spec.ts deleted file mode 100644 index 2a910c8..0000000 --- a/apps/vc-api/src/vc-api/credentials/utils/did-kit-executor.function.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2021 - 2023 Energy Web Foundation - * SPDX-License-Identifier: Apache-2.0 - */ - -import { didKitExecutor } from './did-kit-executor.function'; -import { BadRequestException } from '@nestjs/common'; - -describe('didKitExecutor', function () { - it('should be defined', async function () { - expect(didKitExecutor).toBeDefined(); - }); - - describe('when executed', function () { - describe('when valid function provided', function () { - const spy = jest.fn().mockResolvedValue(JSON.stringify({ foo: 'bar' })); - - it('should call the function', async function () { - await didKitExecutor(() => spy(), 'spy'); - - expect(spy).toHaveBeenCalled(); - }); - - it('should return parsed data', async function () { - expect(await didKitExecutor(() => spy(), 'spy')).toEqual({ foo: 'bar' }); - }); - }); - - describe('when called with function that throws a `string` type of error', function () { - const spy = jest.fn(); - - beforeEach(async function () { - spy.mockReset().mockImplementation(async () => { - throw 'string error'; - }); - }); - - it('should throw BadRequestException', async function () { - await expect(() => didKitExecutor(() => spy(), 'spy')).rejects.toBeInstanceOf(BadRequestException); - }); - - describe('and the exception message', function () { - it('should be correct', async function () { - try { - await didKitExecutor(() => spy(), 'methodName'); - } catch (err) { - expect(err.message).toEqual('@spruceid/didkit-wasm-node.methodName error: string error'); - } - }); - }); - }); - - describe('when called with function that throws instance of the Error', function () { - const spy = jest.fn(); - - beforeEach(async function () { - spy.mockReset().mockImplementation(async () => { - throw new Error('something unexpected happened'); - }); - }); - - it('should throw BadRequestException', async function () { - await expect(() => didKitExecutor(() => spy(), 'spy')).rejects.toBeInstanceOf(Error); - }); - - describe('and the exception message', function () { - it('should be correct', async function () { - try { - await didKitExecutor(() => spy(), 'methodName'); - } catch (err) { - expect(err.message).toEqual('something unexpected happened'); - } - }); - }); - }); - - describe('when called with function that returns unparsable result', function () { - const spy = jest.fn(); - - beforeEach(async function () { - spy.mockReset().mockImplementation(async () => { - return 'unparable string'; - }); - }); - - it('should throw instance of the Error', async function () { - await expect(() => didKitExecutor(() => spy(), 'spy')).rejects.toBeInstanceOf(Error); - }); - - describe('and error message', function () { - it('should be correct', async function () { - try { - await didKitExecutor(() => spy(), 'methodName'); - } catch (err) { - expect(err.message).toEqual('@spruceid/didkit-wasm-node.methodName returned unparseable result'); - } - }); - }); - }); - }); -}); diff --git a/apps/vc-api/src/vc-api/credentials/utils/did-kit-executor.function.ts b/apps/vc-api/src/vc-api/credentials/utils/did-kit-executor.function.ts deleted file mode 100644 index 855e06c..0000000 --- a/apps/vc-api/src/vc-api/credentials/utils/did-kit-executor.function.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2021 - 2023 Energy Web Foundation - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BadRequestException } from '@nestjs/common'; - -/** - * This function reduces repeatability of code when calling didkid methods that require exceptions - * thrown to be additionally checked and to be decided if `BadRequestException` needs to be thrown. - */ -export async function didKitExecutor( - closure: () => Promise, - didKitFunctionName: string -): Promise { - const serializedJson = await closure().catch((err) => { - if (typeof err === 'string') { - // assumption is that non-didkit errors will be of object type - throw new BadRequestException(`@spruceid/didkit-wasm-node.${didKitFunctionName} error: ${err}`); - } - - throw err; - }); - - try { - return JSON.parse(serializedJson) as T; - } catch (err) { - throw Error(`@spruceid/didkit-wasm-node.${didKitFunctionName} returned unparseable result`); - } -} diff --git a/apps/vc-api/src/vc-api/credentials/utils/verification-result-transformer.ts b/apps/vc-api/src/vc-api/credentials/utils/verification-result-transformer.ts new file mode 100644 index 0000000..0ddc2a0 --- /dev/null +++ b/apps/vc-api/src/vc-api/credentials/utils/verification-result-transformer.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2021 - 2023 Energy Web Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { W3cVerifyCredentialResult, W3cVerifyPresentationResult } from '@credo-ts/core'; +import { VerificationResultDto } from '../dtos/verification-result.dto'; + +/** + * This function reduces repeatability of code when tranforming the verification result + */ +export function transformVerificationResult( + verificationResult: W3cVerifyCredentialResult | W3cVerifyPresentationResult +): VerificationResultDto { + return { + verified: verificationResult.isValid, + errors: verificationResult.isValid + ? [] + : verificationResult.error['errors'].map((e) => { + return { title: e.message }; + }), + warnings: [] + }; +} diff --git a/apps/vc-api/src/vc-api/exchanges/dtos/exchange-response.dto.ts b/apps/vc-api/src/vc-api/exchanges/dtos/exchange-response.dto.ts index 09603ba..7705662 100644 --- a/apps/vc-api/src/vc-api/exchanges/dtos/exchange-response.dto.ts +++ b/apps/vc-api/src/vc-api/exchanges/dtos/exchange-response.dto.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { IsArray, IsBoolean, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsBoolean, IsOptional, ValidateNested } from 'class-validator'; import { VerifiablePresentationDto } from '../../credentials/dtos/verifiable-presentation.dto'; import { VpRequestDto } from './vp-request.dto'; import { Type } from 'class-transformer'; diff --git a/apps/vc-api/src/vc-api/exchanges/dtos/exchange-verification-result.dto.ts b/apps/vc-api/src/vc-api/exchanges/dtos/exchange-verification-result.dto.ts new file mode 100644 index 0000000..d1b5f8a --- /dev/null +++ b/apps/vc-api/src/vc-api/exchanges/dtos/exchange-verification-result.dto.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2021 - 2023 Energy Web Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IsArray, IsString } from 'class-validator'; +import { ExchangeVerificationResult } from '../types/exchange-verification-result'; +import { ApiProperty } from '@nestjs/swagger'; + +/** + * A response object from verification of a credential or a presentation. + * https://w3c-ccg.github.io/vc-api/verifier.html + */ +export class ExchangeVerificationResultDto implements ExchangeVerificationResult { + @IsArray() + @IsString({ each: true }) + @ApiProperty({ description: 'Warnings' }) + warnings: string[]; + + @IsArray() + @IsString({ each: true }) + @ApiProperty({ description: 'Errors' }) + errors: string[]; + + @ApiProperty({ description: 'Is the credential valid' }) + verified: boolean; +} diff --git a/apps/vc-api/src/vc-api/exchanges/entities/presentation-submission.entity.ts b/apps/vc-api/src/vc-api/exchanges/entities/presentation-submission.entity.ts index 11c4d7e..d92588f 100644 --- a/apps/vc-api/src/vc-api/exchanges/entities/presentation-submission.entity.ts +++ b/apps/vc-api/src/vc-api/exchanges/entities/presentation-submission.entity.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { VerificationResult } from '../../credentials/types/verification-result'; +import { ExchangeVerificationResult } from '../types/exchange-verification-result'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { VerifiablePresentation } from '../types/verifiable-presentation'; @@ -15,7 +15,7 @@ import { VerifiablePresentation } from '../types/verifiable-presentation'; */ @Entity() export class PresentationSubmissionEntity { - constructor(vp: VerifiablePresentation, verificationResult: VerificationResult) { + constructor(vp: VerifiablePresentation, verificationResult: ExchangeVerificationResult) { this.vpHolder = vp?.holder; this.verificationResult = verificationResult; } @@ -27,7 +27,7 @@ export class PresentationSubmissionEntity { * The result of the verification of the submitted VP */ @Column('simple-json') - verificationResult: VerificationResult; + verificationResult: ExchangeVerificationResult; @Column('text', { nullable: true }) vpHolder: string; diff --git a/apps/vc-api/src/vc-api/exchanges/entities/transaction.entity.spec.ts b/apps/vc-api/src/vc-api/exchanges/entities/transaction.entity.spec.ts index b3d861e..bbd7fe2 100644 --- a/apps/vc-api/src/vc-api/exchanges/entities/transaction.entity.spec.ts +++ b/apps/vc-api/src/vc-api/exchanges/entities/transaction.entity.spec.ts @@ -27,7 +27,7 @@ describe('TransactionEntity', () => { }; const submissionVerificationResult = { - checks: ['proof'], + verified: true, warnings: [], errors: [] }; diff --git a/apps/vc-api/src/vc-api/exchanges/exchange.service.spec.ts b/apps/vc-api/src/vc-api/exchanges/exchange.service.spec.ts index 332aa04..785c27b 100644 --- a/apps/vc-api/src/vc-api/exchanges/exchange.service.spec.ts +++ b/apps/vc-api/src/vc-api/exchanges/exchange.service.spec.ts @@ -29,7 +29,7 @@ const vp = { }; const submissionVerificationResult = { - checks: ['proof'], + verified: true, warnings: [], errors: [] }; @@ -241,7 +241,6 @@ describe('ExchangeService', () => { it('should contain correct verificationResult', async function () { expect(presentationSubmission.verificationResult).toEqual({ - checks: ['proof'], errors: [], warnings: [] }); @@ -290,7 +289,7 @@ describe('ExchangeService', () => { beforeEach(async function () { jest.spyOn(mockSubmissionVerifier, 'verifyVpRequestSubmission').mockResolvedValue({ - checks: [], + verified: false, warnings: [], errors: ['error 1', 'error 2', 'error 3'] }); diff --git a/apps/vc-api/src/vc-api/exchanges/types/exchange-verification-result.ts b/apps/vc-api/src/vc-api/exchanges/types/exchange-verification-result.ts new file mode 100644 index 0000000..e7b83bd --- /dev/null +++ b/apps/vc-api/src/vc-api/exchanges/types/exchange-verification-result.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2021 - 2023 Energy Web Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * A response object from verification of a credential or a presentation. + * https://w3c-ccg.github.io/vc-api/verifier.html + */ + +export interface ExchangeVerificationResult { + /** /** + * verified + */ + verified: boolean; + + /** /** + * Warnings + */ + warnings: string[]; + + /** /** + * Errors + */ + errors: string[]; +} diff --git a/apps/vc-api/src/vc-api/exchanges/types/submission-verifier.ts b/apps/vc-api/src/vc-api/exchanges/types/submission-verifier.ts index a5914b9..298a2ed 100644 --- a/apps/vc-api/src/vc-api/exchanges/types/submission-verifier.ts +++ b/apps/vc-api/src/vc-api/exchanges/types/submission-verifier.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { VerificationResult } from '../../credentials/types/verification-result'; +import { ExchangeVerificationResultDto } from '../dtos/exchange-verification-result.dto'; import { VpRequestEntity } from '../entities/vp-request.entity'; import { VerifiablePresentation } from './verifiable-presentation'; @@ -15,5 +15,5 @@ export interface SubmissionVerifier { verifyVpRequestSubmission: ( vp: VerifiablePresentation, vpRequest: VpRequestEntity - ) => Promise; + ) => Promise; } diff --git a/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.spec.ts b/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.spec.ts index 23c2723..5e2b7cf 100644 --- a/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.spec.ts +++ b/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.spec.ts @@ -5,17 +5,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { CredentialsService } from '../credentials/credentials.service'; -import { VerificationResult } from '../credentials/types/verification-result'; import { VpRequestEntity } from './entities/vp-request.entity'; import { VerifiablePresentation } from './types/verifiable-presentation'; import { VpRequestQuery } from './types/vp-request-query'; import { VpRequestQueryType } from './types/vp-request-query-type'; import { VpSubmissionVerifierService } from './vp-submission-verifier.service'; +import { ExchangeVerificationResultDto } from './dtos/exchange-verification-result.dto'; const presentationVerificationResult = { - checks: ['proof'], warnings: [], - errors: [] + errors: [], + verified: true }; const mockCredentialService = { @@ -48,7 +48,7 @@ describe('VpSubmissionVerifierService', () => { async function getVerificationResult( query: VpRequestQuery[], vp: VerifiablePresentation - ): Promise { + ): Promise { const vpRequest: VpRequestEntity = { challenge, query, diff --git a/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.ts b/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.ts index 0092493..a08580f 100644 --- a/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.ts +++ b/apps/vc-api/src/vc-api/exchanges/vp-submission-verifier.service.ts @@ -6,11 +6,11 @@ import { Injectable } from '@nestjs/common'; import { ProofPurpose, IPresentationDefinition, PEX, IPresentation } from '@sphereon/pex'; import { CredentialsService } from '../credentials/credentials.service'; -import { VerificationResult } from '../credentials/types/verification-result'; import { VpRequestEntity } from './entities/vp-request.entity'; import { SubmissionVerifier } from './types/submission-verifier'; import { VerifiablePresentation } from './types/verifiable-presentation'; import { VpRequestQueryType } from './types/vp-request-query-type'; +import { ExchangeVerificationResultDto } from './dtos/exchange-verification-result.dto'; /** * Inspired by https://github.com/gataca-io/vui-core/blob/6c599bdf7086f9a702e6657089fa343ae62a417a/service/validatorServiceDIFPE.go @@ -26,12 +26,15 @@ export class VpSubmissionVerifierService implements SubmissionVerifier { public async verifyVpRequestSubmission( vp: VerifiablePresentation, vpRequest: VpRequestEntity - ): Promise { - const proofVerifiactionResult = await this.verifyPresentationProof(vp, vpRequest.challenge); + ): Promise { + const proofVerifiactionResult: ExchangeVerificationResultDto = await this.verifyPresentationProof( + vp, + vpRequest.challenge + ); const vpRequestValidationErrors = this.validatePresentationAgainstVpRequest(vp, vpRequest); return { + verified: proofVerifiactionResult.verified, errors: [...proofVerifiactionResult.errors, ...vpRequestValidationErrors], - checks: [...proofVerifiactionResult.checks], warnings: [] }; } @@ -39,21 +42,24 @@ export class VpSubmissionVerifierService implements SubmissionVerifier { private async verifyPresentationProof( vp: VerifiablePresentation, challenge: string - ): Promise { + ): Promise { const verifyOptions = { challenge, proofPurpose: ProofPurpose.authentication, verificationMethod: vp.proof.verificationMethod as string //TODO: fix types here }; const result = await this.credentialsService.verifyPresentation(vp, verifyOptions); - if (!result.checks.includes('proof') || result.errors.length > 0) { - return { - errors: [`verification of presentation proof not successful`, ...result.errors], - checks: [], - warnings: [] - }; - } - return result; + return result.verified + ? { + verified: true, + warnings: [], + errors: [] + } + : { + verified: false, + errors: [...result.errors.map((e) => e.title)], + warnings: [] + }; } private validatePresentationAgainstVpRequest( diff --git a/apps/vc-api/src/vc-api/vc-api.controller.ts b/apps/vc-api/src/vc-api/vc-api.controller.ts index a4ed7c9..fee70c8 100644 --- a/apps/vc-api/src/vc-api/vc-api.controller.ts +++ b/apps/vc-api/src/vc-api/vc-api.controller.ts @@ -9,7 +9,6 @@ import { Controller, Get, HttpCode, - InternalServerErrorException, Logger, NotFoundException, Param, @@ -32,11 +31,9 @@ import { getSchemaPath } from '@nestjs/swagger'; import { Response } from 'express'; -import { IPresentationDefinition } from '@sphereon/pex'; import { CredentialsService } from './credentials/credentials.service'; import { IssueCredentialDto } from './credentials/dtos/issue-credential.dto'; import { VerifiableCredentialDto } from './credentials/dtos/verifiable-credential.dto'; -import { AuthenticateDto } from './credentials/dtos/authenticate.dto'; import { VerifiablePresentationDto } from './credentials/dtos/verifiable-presentation.dto'; import { ExchangeService } from './exchanges/exchange.service'; import { ExchangeResponseDto } from './exchanges/dtos/exchange-response.dto'; @@ -52,6 +49,8 @@ import { BadRequestErrorResponseDto } from '../dtos/bad-request-error-response.d import { ConflictErrorResponseDto } from '../dtos/conflict-error-response.dto'; import { NotFoundErrorResponseDto } from '../dtos/not-found-error-response.dto'; import { InternalServerErrorResponseDto } from '../dtos/internal-server-error-response.dto'; +import { IPresentationDefinition } from '@sphereon/pex'; +import { AuthenticateDto } from './credentials/dtos/authenticate.dto'; /** * VcApi API conforms to W3C vc-api @@ -105,11 +104,10 @@ export class VcApiController { verifyCredentialDto: VerifyCredentialDto ): Promise { const verificationResult = await this.vcApiService.verifyCredential( - verifyCredentialDto.verifiableCredential, - verifyCredentialDto.options + verifyCredentialDto.verifiableCredential ); - if (verificationResult.errors.length > 0) { + if (verificationResult.errors.length) { throw new BadRequestException(verificationResult); } diff --git a/apps/vc-api/test/vc-api/credential.service.spec.data.ts b/apps/vc-api/test/vc-api/credential.service.spec.data.ts index bf6a67a..091d68e 100644 --- a/apps/vc-api/test/vc-api/credential.service.spec.data.ts +++ b/apps/vc-api/test/vc-api/credential.service.spec.data.ts @@ -8,6 +8,9 @@ import { CredentialDto } from 'src/vc-api/credentials/dtos/credential.dto'; import { Presentation } from 'src/vc-api/exchanges/types/presentation'; import { VerifiableCredential } from 'src/vc-api/exchanges/types/verifiable-credential'; import { did } from './credential.service.spec.key'; +import { AuthenticateDto } from 'src/vc-api/credentials/dtos/authenticate.dto'; +import { VerifiablePresentationDto } from 'src/vc-api/credentials/dtos/verifiable-presentation.dto'; +import { VerificationResultDto } from 'src/vc-api/credentials/dtos/verification-result.dto'; export const presentationDefinition = { id: '286bc1e0-f1bd-488a-a873-8d71be3c690e', @@ -153,12 +156,12 @@ export const getChargingDataCredential: (issuerDid: string) => CredentialDto = ( export const energyContractVerifiableCredential: VerifiableCredential = { ...energyContractCredential, proof: { + verificationMethod: + 'did:key:z6MknVzMUmfDwYnWBcnTtVYknYcnP63SiQWTVnjfrGCQhRm9#z6MknVzMUmfDwYnWBcnTtVYknYcnP63SiQWTVnjfrGCQhRm9', type: 'Ed25519Signature2018', + created: '2024-09-05T09:23:26Z', proofPurpose: 'assertionMethod', - verificationMethod: - 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF#z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', - created: '2021-11-16T14:52:19.514Z', - jws: 'eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..zgBHxtdwo17BK6EZCQik9Bxa_rLn-B2DgK3bkCVFZWQqlWb-W7goxPWBqidUrr2iufYoFdsdQwmoYBeu973YBA' + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..Ldfx8MwoKWt6WARPGPM02OYupP5V76FCsFXlgPCd36Ykqwm7lDAX3QPjplGE_yQtYHnTZsnjN868L-Ls6rREBw' } }; @@ -214,11 +217,160 @@ export const rebeamPresentation: Presentation = { export const rebeamVerifiablePresentation = { ...rebeamPresentation, proof: { + verificationMethod: + 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V#z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V', type: 'Ed25519Signature2018', - proofPurpose: 'authentication', + created: '2024-09-12T06:59:38Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..b6H2ufv0xPwBxAZhSSyOppipkzdpiIKhfZu8NHRZoy0AjL1RIE8XQzaaeumNXUtcRcDALAuawbOf1UxC3_64CQ' + } +}; + +export const verifiablePresentation = { + '@context': ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiablePresentation', 'PresentationSubmission'], + presentation_submission: { + id: 'ysEtA_34FRA2_uHr3Lwmv', + definition_id: '286bc1e0-f1bd-488a-a873-8d71be3c690e', + descriptor_map: [ + { + id: 'energy_supplier_customer_contract', + format: 'ldp_vc', + path: '$.verifiableCredential[0]' + }, + { + id: 'charging_data', + format: 'ldp_vc', + path: '$.verifiableCredential[1]' + } + ] + }, + verifiableCredential: [ + { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + value: 'ew:value', + namespace: 'ew:namespace', + ew: 'https://energyweb.org/ld-context-2022#', + key: 'ew:key', + role: { + '@id': 'ew:role', + '@type': 'ew:Role' + }, + version: 'ew:version', + EWFRole: 'ew:EWFRole', + issuerFields: { + '@id': 'ew:issuerFields', + '@type': 'ew:IssuerFields' + } + } + ], + id: 'urn:uuid:7f94d397-3e70-4a43-945e-1a13069e636f', + type: ['VerifiableCredential', 'EWFRole'], + credentialSubject: { + issuerFields: [ + { + key: 'accountId', + value: 'energycustomerid1' + } + ], + role: { + namespace: 'customer.roles.rebeam.apps.eliagroup.iam.ewc', + version: '1' + }, + id: 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V' + }, + issuer: 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V', + issuanceDate: '2022-03-18T08:57:32.477Z', + proof: { + verificationMethod: + 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V#z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V', + type: 'Ed25519Signature2018', + created: '2024-09-12T06:59:38Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..b6H2ufv0xPwBxAZhSSyOppipkzdpiIKhfZu8NHRZoy0AjL1RIE8XQzaaeumNXUtcRcDALAuawbOf1UxC3_64CQ' + } + }, + { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + timestamp: 'ew:timestamp', + kwh: 'ew:kwh', + chargingData: { + '@id': 'ew:chargingData', + '@type': 'ew:chargingData' + }, + ChargingData: 'ew:ChargingData', + contractDID: 'ew:contractDID', + evseId: 'ew:evseId', + ew: 'https://energyweb.org/ld-context-2022#' + } + ], + id: 'urn:uuid:a6032135-75d6-4019-b59d-420168c7cd85', + type: ['VerifiableCredential', 'ChargingData'], + credentialSubject: { + chargingData: { + contractDID: 'did:ethr:blxm-local:0x429eCb49aAC34E076f19D5C91d7e8B956AEf9c08', + evseId: '123', + kwh: '5', + timestamp: '2022-04-05T15:45:35.346Z' + }, + id: 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V' + }, + issuer: 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V', + issuanceDate: '2022-03-18T08:57:32.477Z', + proof: { + verificationMethod: + 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V#z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V', + type: 'Ed25519Signature2018', + created: '2024-09-12T07:01:01Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..fmm_D7phS_zROTFF_3Za6ifhDirktBPpXGSc3dm-JuDzpMeU7rTZND88uq6K0hEumzr5pyQeaMW1KP77nog7AQ' + } + } + ], + proof: { verificationMethod: - 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF#z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', - created: '2022-05-23T11:11:07.777Z', - jws: 'eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..E4Tk6RGBmazl1k1iJ6itu0sUOWz7tRkJf-GP1nPXmoFmfdPH2uEEwTBlSvPSTfTkyZxCs_ra7nfVUCLtkqWIAA' + 'did:key:z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V#z6Mkif6x1XFzFH67qiiXjQi96HzudWqdsqV76wLQa1vMVy4V', + type: 'Ed25519Signature2018', + created: '2024-09-12T11:09:21Z', + proofPurpose: 'authentication', + challenge: 'some-challenge-2', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..-Qa6tDkgtW1RoO3zZtC_cSEaBTYfJ4l0ypSGfbiLvKEeZ8xd1W-2Q51K2KJ-gSybVDw-0hZWaeDTW7IpFRZZDA' } }; + +export const didAuth = { + presentation: { + did: 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', + options: { + verificationMethod: 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF#z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', + proofPurpose: 'authentication', + challenge: 'some-challenge', + } + } as AuthenticateDto, + signedPresentation: { + '@context': [ + 'https://www.w3.org/2018/credentials/v1' + ], + type: [ + 'VerifiablePresentation' + ], + holder: 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', + proof: { + verificationMethod: 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF#z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', + type: 'Ed25519Signature2018', + created: '2024-09-18T17:55:18Z', + proofPurpose: 'authentication', + challenge: 'some-challenge', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..22IJaDl9ALJ89CqBEnoVgEor0C-L-bERR6I1eHz7c_Ak3VeQgjuZG9aOa_1R_tr3ECI-hCL55G2JReEk-bi9DQ' + } + } as VerifiablePresentationDto, + verifiedPresentationResult: { + verified: true, + errors: [], + warnings: [] + } as VerificationResultDto +}; diff --git a/apps/vc-api/test/vc-api/credential.service.spec.key.ts b/apps/vc-api/test/vc-api/credential.service.spec.key.ts index 574bea1..c82f2f7 100644 --- a/apps/vc-api/test/vc-api/credential.service.spec.key.ts +++ b/apps/vc-api/test/vc-api/credential.service.spec.key.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { keyToDID } from '@spruceid/didkit-wasm-node'; - export const key = { kty: 'OKP', crv: 'Ed25519', @@ -13,7 +11,7 @@ export const key = { kid: 'zWME913jdYrILuYD-ot-jDbmzqz34HqlCUZ6CMdJnyo' }; -export const did = keyToDID('key', JSON.stringify(key)); // "did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF" +export const did = "did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF"; export const didDoc = { id: 'did:key:z6MkoB84PJkXzFpbqtfYV5WqBKHCSDf7A1SeepwzvE36QvCF', verificationMethod: [ diff --git a/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card-credential-exchange.ts b/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card-credential-exchange.ts index 52e9fbd..5458b3c 100644 --- a/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card-credential-exchange.ts +++ b/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card-credential-exchange.ts @@ -78,7 +78,8 @@ export class ConsentandResidentCardCredentialIssuance { return { errors: ['verification method for issuance not available'] }; } const presentationOptions: ProvePresentationOptionsDto = { - verificationMethod: verificationMethodURI + verificationMethod: verificationMethodURI, + challenge: 'some-challenge' }; const provePresentationDto = { options: presentationOptions, @@ -139,7 +140,8 @@ export class ConsentandResidentCardCredentialIssuance { return { errors: ['verification method for issuance not available'] }; } const presentationOptions: ProvePresentationOptionsDto = { - verificationMethod: verificationMethodURI + verificationMethod: verificationMethodURI, + challenge: 'some-challenge' }; const provePresentationDto = { options: presentationOptions, diff --git a/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card.e2e-suite.ts b/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card.e2e-suite.ts index eba60b4..175f24b 100644 --- a/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card.e2e-suite.ts +++ b/apps/vc-api/test/vc-api/exchanges/consent-and-resident-card-credentialexchange/consent-and-resident-card.e2e-suite.ts @@ -57,7 +57,6 @@ export const consentAndResidentCardExchangeSuite = () => { expect(issuanceExchangeContinuationEndpoint).toContain(issuanceExchangeEndpoint); // As holder, create new DID and presentation to authentication as this DID - // DID auth presentation: https://github.com/spruceid/didkit/blob/c5c422f2469c2c5cc2f6e6d8746e95b552fce3ed/lib/web/src/lib.rs#L382 holderDIDDoc = await walletClient.createDID('key'); holderVerificationMethod = holderDIDDoc.verificationMethod[0].id; const options: ProvePresentationOptionsDto = { @@ -85,11 +84,11 @@ export const consentAndResidentCardExchangeSuite = () => { const transaction = await walletClient.getExchangeTransaction(exchange.getExchangeId(), transactionId); // As the issuer, check the result of the transaction verification - expect(transaction.presentationSubmission.verificationResult.checks).toContain('proof'); + expect(transaction.presentationSubmission.verificationResult.verified).toBeTruthy(); expect(transaction.presentationSubmission.verificationResult.errors).toHaveLength(0); // As the issuer, create a presentation to provide the credential to the holder - const holderKeyId = holderDIDDoc.verificationMethod[0].publicKeyJwk.kid; + const holderKeyId = holderDIDDoc.verificationMethod[0].publicKeyBase58; const issueResultConsentCredential = await exchange.issueConsentCredential(holderKeyId, walletClient); issuedVPConsentCredential = issueResultConsentCredential.vp; const issueResultResidentCard = await exchange.issueResidentCardCredential(didAuthVp, walletClient); @@ -101,7 +100,8 @@ export const consentAndResidentCardExchangeSuite = () => { verifiableCredential: [issuedVPResidentCard.verifiableCredential[0]] }; const residentCardPresentationOptions: ProvePresentationOptionsDto = { - verificationMethod: holderVerificationMethod + verificationMethod: holderVerificationMethod, + challenge: 'some-challenge' }; const provePresentationDto = { options: residentCardPresentationOptions, diff --git a/apps/vc-api/test/vc-api/exchanges/rebeam/rebeam-supplier.ts b/apps/vc-api/test/vc-api/exchanges/rebeam/rebeam-supplier.ts index 220ce36..dc55554 100644 --- a/apps/vc-api/test/vc-api/exchanges/rebeam/rebeam-supplier.ts +++ b/apps/vc-api/test/vc-api/exchanges/rebeam/rebeam-supplier.ts @@ -31,7 +31,8 @@ export class RebeamSupplier { return { errors: ['verification method for issuance not available'] }; } const presentationOptions: ProvePresentationOptionsDto = { - verificationMethod: verificationMethodURI + verificationMethod: verificationMethodURI, + challenge: 'some-challenge' }; const provePresentationDto = { options: presentationOptions, diff --git a/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card-issuance.exchange.ts b/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card-issuance.exchange.ts index 06cd3ee..3544e3d 100644 --- a/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card-issuance.exchange.ts +++ b/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card-issuance.exchange.ts @@ -78,7 +78,8 @@ export class ResidentCardIssuance { return { errors: ['verification method for issuance not available'] }; } const presentationOptions: ProvePresentationOptionsDto = { - verificationMethod: verificationMethodURI + verificationMethod: verificationMethodURI, + challenge: 'some-challenge' }; const provePresentationDto = { options: presentationOptions, diff --git a/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card.e2e-suite.ts b/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card.e2e-suite.ts index 5016d7c..86f8d68 100644 --- a/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card.e2e-suite.ts +++ b/apps/vc-api/test/vc-api/exchanges/resident-card/resident-card.e2e-suite.ts @@ -43,7 +43,6 @@ export const residentCardExchangeSuite = () => { expect(issuanceExchangeContinuationEndpoint).toContain(issuanceExchangeEndpoint); // As holder, create new DID and presentation to authentication as this DID - // DID auth presentation: https://github.com/spruceid/didkit/blob/c5c422f2469c2c5cc2f6e6d8746e95b552fce3ed/lib/web/src/lib.rs#L382 const holderDIDDoc = await walletClient.createDID('key'); const holderVerificationMethod = holderDIDDoc.verificationMethod[0].id; const options: ProvePresentationOptionsDto = { @@ -71,7 +70,7 @@ export const residentCardExchangeSuite = () => { const transaction = await walletClient.getExchangeTransaction(exchange.getExchangeId(), transactionId); // As the issuer, check the result of the transaction verification - expect(transaction.presentationSubmission.verificationResult.checks).toContain('proof'); + expect(transaction.presentationSubmission.verificationResult.verified).toBeTruthy(); expect(transaction.presentationSubmission.verificationResult.errors).toHaveLength(0); // As the issuer, create a presentation to provide the credential to the holder diff --git a/common/config/rush/pnpm-config.json b/common/config/rush/pnpm-config.json new file mode 100644 index 0000000..57fd648 --- /dev/null +++ b/common/config/rush/pnpm-config.json @@ -0,0 +1,77 @@ +{ + /** + * Specifies the location of the PNPM store. There are two possible values: + * + * - "local" - use the "pnpm-store" folder in the current configured temp folder: + * "common/temp/pnpm-store" by default. + * - "global" - use PNPM's global store, which has the benefit of being shared + * across multiple repo folders, but the disadvantage of less isolation for builds + * (e.g. bugs or incompatibilities when two repos use different releases of PNPM) + * + * RUSH_PNPM_STORE_PATH will override the directory that will be used as the store + * + * In all cases, the store path will be overridden by the environment variable RUSH_PNPM_STORE_PATH. + * + * The default value is "local". + */ + // "pnpmStore": "local", + /** + * If true, then Rush will add the "--strict-peer-dependencies" option when invoking PNPM. + * This causes "rush install" to fail if there are unsatisfied peer dependencies, which is + * an invalid state that can cause build failures or incompatible dependency versions. + * (For historical reasons, JavaScript package managers generally do not treat this invalid + * state as an error.) + * + * The default value is false to avoid legacy compatibility issues. + * It is strongly recommended to set strictPeerDependencies=true. + */ + // "strictPeerDependencies": true, + /** + * Configures the strategy used to select versions during installation. + * + * This feature requires PNPM version 3.1 or newer. It corresponds to the "--resolution-strategy" command-line + * option for PNPM. Possible values are "fast" and "fewer-dependencies". PNPM's default is "fast", but this may + * be incompatible with certain packages, for example the "@types" packages from DefinitelyTyped. Rush's default + * is "fewer-dependencies", which causes PNPM to avoid installing a newer version if an already installed version + * can be reused; this is more similar to NPM's algorithm. + * + * After modifying this field, it's recommended to run "rush update --full" so that the package manager + * will recalculate all version selections. + */ + // "resolutionStrategy": "fast", + /** + * If true, then `rush install` will report an error if manual modifications + * were made to the PNPM shrinkwrap file without running "rush update" afterwards. + * + * This feature protects against accidental inconsistencies that may be introduced + * if the PNPM shrinkwrap file ("pnpm-lock.yaml") is manually edited. When this + * feature is enabled, "rush update" will append a hash to the file as a YAML comment, + * and then "rush update" and "rush install" will validate the hash. Note that this does not prohibit + * manual modifications, but merely requires "rush update" be run + * afterwards, ensuring that PNPM can report or repair any potential inconsistencies. + * + * To temporarily disable this validation when invoking "rush install", use the + * "--bypass-policy" command-line parameter. + * + * The default value is false. + */ + // "preventManualShrinkwrapChanges": true, + /** + * If true, then `rush install` will use the PNPM workspaces feature to perform the + * install. + * + * This feature uses PNPM to perform the entire monorepo install. When using workspaces, Rush will + * generate a "pnpm-workspace.yaml" file referencing all local projects to install. Rush will + * also generate a "pnpmfile.js" which is used to provide preferred versions support. When install + * is run, this pnpmfile will be used to replace dependency version ranges with a smaller subset + * of the original range. If the preferred version is not fully a subset of the original version + * range, it will be left as-is. After this, the pnpmfile.js provided in the repository (if one + * exists) will be called to further modify package dependencies. + * + * This option is experimental. The default value is false. + */ + "useWorkspaces": true, + "globalPatchedDependencies": { + "@credo-ts/core@0.5.10": "patches/@credo-ts__core@0.5.10.patch" + } +} diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index ff276b8..fca94ee 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + '@credo-ts/core@0.5.10': + hash: vxhh4qvntk5wpgrlz27ksgqauu + path: patches/@credo-ts__core@0.5.10.patch + importers: .: {} @@ -15,7 +20,7 @@ importers: version: 0.5.10(@hyperledger/aries-askar-shared@0.2.3)(expo@51.0.30)(react-native@0.75.2) '@credo-ts/core': specifier: ^0.5.3 - version: 0.5.10(expo@51.0.30)(react-native@0.75.2) + version: 0.5.10(patch_hash=vxhh4qvntk5wpgrlz27ksgqauu)(expo@51.0.30)(react-native@0.75.2) '@credo-ts/node': specifier: ^0.5.3 version: 0.5.10(expo@51.0.30)(react-native@0.75.2) @@ -58,9 +63,6 @@ importers: '@sphereon/pex': specifier: 1.1.3 version: 1.1.3 - '@spruceid/didkit-wasm-node': - specifier: ~0.2.1 - version: 0.2.1 axios: specifier: ~1.4.0 version: 1.4.0 @@ -184,7 +186,7 @@ importers: version: 0.5.10(@hyperledger/aries-askar-shared@0.2.3)(expo@51.0.30)(react-native@0.75.2) '@credo-ts/core': specifier: ^0.5.3 - version: 0.5.10(expo@51.0.30)(react-native@0.75.2) + version: 0.5.10(patch_hash=vxhh4qvntk5wpgrlz27ksgqauu)(expo@51.0.30)(react-native@0.75.2) '@credo-ts/node': specifier: ^0.5.3 version: 0.5.10(expo@51.0.30)(react-native@0.75.2) @@ -2153,7 +2155,7 @@ packages: '@animo-id/expo-secure-environment': optional: true dependencies: - '@credo-ts/core': 0.5.10(expo@51.0.30)(react-native@0.75.2) + '@credo-ts/core': 0.5.10(patch_hash=vxhh4qvntk5wpgrlz27ksgqauu)(expo@51.0.30)(react-native@0.75.2) '@hyperledger/aries-askar-shared': 0.2.3 bn.js: 5.2.1 class-transformer: 0.5.1 @@ -2169,7 +2171,7 @@ packages: - web-streams-polyfill dev: false - /@credo-ts/core@0.5.10(expo@51.0.30)(react-native@0.75.2): + /@credo-ts/core@0.5.10(patch_hash=vxhh4qvntk5wpgrlz27ksgqauu)(expo@51.0.30)(react-native@0.75.2): resolution: {integrity: sha512-Y9AysZqiLURxJpu4SO8/rZ6wZ3mSZjvn0WorFLUREMcySCX/z+flYIOSkdE5YALvBy6luzqcEFB01qkpgfH6hw==} dependencies: '@digitalcredentials/jsonld': 6.0.0(expo@51.0.30)(react-native@0.75.2) @@ -2220,13 +2222,14 @@ packages: - supports-color - web-streams-polyfill dev: false + patched: true /@credo-ts/node@0.5.10(expo@51.0.30)(react-native@0.75.2): resolution: {integrity: sha512-x82G3vXVZ2tOVWVAw1Gc/jzNgeH2LrtLJ8qn251IpcELQraVaZHfUqFE3avlW0RSs8I/VhtNaJlOMj1x6zfsHg==} dependencies: '@2060.io/ffi-napi': 4.0.9 '@2060.io/ref-napi': 3.0.6 - '@credo-ts/core': 0.5.10(expo@51.0.30)(react-native@0.75.2) + '@credo-ts/core': 0.5.10(patch_hash=vxhh4qvntk5wpgrlz27ksgqauu)(expo@51.0.30)(react-native@0.75.2) '@types/express': 4.17.21 express: 4.19.2 ws: 8.18.0 diff --git a/common/pnpm-patches/@credo-ts__core@0.5.10.patch b/common/pnpm-patches/@credo-ts__core@0.5.10.patch new file mode 100644 index 0000000..04f4988 --- /dev/null +++ b/common/pnpm-patches/@credo-ts__core@0.5.10.patch @@ -0,0 +1,13 @@ +diff --git a/build/modules/vc/data-integrity/W3cJsonLdCredentialService.js b/build/modules/vc/data-integrity/W3cJsonLdCredentialService.js +index c833d0108e8852127afac1c87cffca0aba1268dc..e3391b11fd9b7b7fe85b027eb68ea110e4324499 100644 +--- a/build/modules/vc/data-integrity/W3cJsonLdCredentialService.js ++++ b/build/modules/vc/data-integrity/W3cJsonLdCredentialService.js +@@ -179,7 +179,7 @@ let W3cJsonLdCredentialService = class W3cJsonLdCredentialService { + domain: options.domain, + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), + }); +- return utils_1.JsonTransformer.fromJSON(result, W3cJsonLdVerifiablePresentation_1.W3cJsonLdVerifiablePresentation); ++ return utils_1.JsonTransformer.fromJSON(result, W3cJsonLdVerifiablePresentation_1.W3cJsonLdVerifiablePresentation, { validate: !(result.verifiableCredential === undefined) }); + } + /** + * Verifies a presentation including the credentials it includes diff --git a/rush.json b/rush.json index 8c7fe76..1806bd9 100644 --- a/rush.json +++ b/rush.json @@ -28,83 +28,6 @@ // "npmVersion": "6.14.15", // "yarnVersion": "1.9.4", - /** - * Options that are only used when the PNPM package manager is selected - */ - "pnpmOptions": { - /** - * Specifies the location of the PNPM store. There are two possible values: - * - * - "local" - use the "pnpm-store" folder in the current configured temp folder: - * "common/temp/pnpm-store" by default. - * - "global" - use PNPM's global store, which has the benefit of being shared - * across multiple repo folders, but the disadvantage of less isolation for builds - * (e.g. bugs or incompatibilities when two repos use different releases of PNPM) - * - * RUSH_PNPM_STORE_PATH will override the directory that will be used as the store - * - * In all cases, the store path will be overridden by the environment variable RUSH_PNPM_STORE_PATH. - * - * The default value is "local". - */ - // "pnpmStore": "local", - /** - * If true, then Rush will add the "--strict-peer-dependencies" option when invoking PNPM. - * This causes "rush install" to fail if there are unsatisfied peer dependencies, which is - * an invalid state that can cause build failures or incompatible dependency versions. - * (For historical reasons, JavaScript package managers generally do not treat this invalid - * state as an error.) - * - * The default value is false to avoid legacy compatibility issues. - * It is strongly recommended to set strictPeerDependencies=true. - */ - // "strictPeerDependencies": true, - /** - * Configures the strategy used to select versions during installation. - * - * This feature requires PNPM version 3.1 or newer. It corresponds to the "--resolution-strategy" command-line - * option for PNPM. Possible values are "fast" and "fewer-dependencies". PNPM's default is "fast", but this may - * be incompatible with certain packages, for example the "@types" packages from DefinitelyTyped. Rush's default - * is "fewer-dependencies", which causes PNPM to avoid installing a newer version if an already installed version - * can be reused; this is more similar to NPM's algorithm. - * - * After modifying this field, it's recommended to run "rush update --full" so that the package manager - * will recalculate all version selections. - */ - // "resolutionStrategy": "fast", - /** - * If true, then `rush install` will report an error if manual modifications - * were made to the PNPM shrinkwrap file without running "rush update" afterwards. - * - * This feature protects against accidental inconsistencies that may be introduced - * if the PNPM shrinkwrap file ("pnpm-lock.yaml") is manually edited. When this - * feature is enabled, "rush update" will append a hash to the file as a YAML comment, - * and then "rush update" and "rush install" will validate the hash. Note that this does not prohibit - * manual modifications, but merely requires "rush update" be run - * afterwards, ensuring that PNPM can report or repair any potential inconsistencies. - * - * To temporarily disable this validation when invoking "rush install", use the - * "--bypass-policy" command-line parameter. - * - * The default value is false. - */ - // "preventManualShrinkwrapChanges": true, - /** - * If true, then `rush install` will use the PNPM workspaces feature to perform the - * install. - * - * This feature uses PNPM to perform the entire monorepo install. When using workspaces, Rush will - * generate a "pnpm-workspace.yaml" file referencing all local projects to install. Rush will - * also generate a "pnpmfile.js" which is used to provide preferred versions support. When install - * is run, this pnpmfile will be used to replace dependency version ranges with a smaller subset - * of the original range. If the preferred version is not fully a subset of the original version - * range, it will be left as-is. After this, the pnpmfile.js provided in the repository (if one - * exists) will be called to further modify package dependencies. - * - * This option is experimental. The default value is false. - */ - "useWorkspaces": true - }, /** * Older releases of the Node.js engine may be missing features required by your system. * Other releases may have bugs. In particular, the "latest" version will not be a